From aa05993cf0cf43679722dc91a9aa1847c1131256 Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 5 Jan 2021 15:26:11 -0600 Subject: [PATCH 001/422] Bundle callback.html into final build --- ci/build/build-release.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ci/build/build-release.sh b/ci/build/build-release.sh index c87645d34..a9de1b7e7 100755 --- a/ci/build/build-release.sh +++ b/ci/build/build-release.sh @@ -79,8 +79,9 @@ bundle_vscode() { rsync "$VSCODE_SRC_PATH/extensions/yarn.lock" "$VSCODE_OUT_PATH/extensions" rsync "$VSCODE_SRC_PATH/extensions/postinstall.js" "$VSCODE_OUT_PATH/extensions" - mkdir -p "$VSCODE_OUT_PATH/resources/linux" + mkdir -p "$VSCODE_OUT_PATH/resources/"{linux,web} rsync "$VSCODE_SRC_PATH/resources/linux/code.png" "$VSCODE_OUT_PATH/resources/linux/code.png" + rsync "$VSCODE_SRC_PATH/resources/web/callback.html" "$VSCODE_OUT_PATH/resources/web/callback.html" # Adds the commit and date to product.json jq --slurp '.[0] * .[1]' "$VSCODE_SRC_PATH/product.json" <( From 05530db20e06801c7d60691c17a28cf8ce37238f Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 5 Jan 2021 15:28:42 -0600 Subject: [PATCH 002/422] Fix symlink_asar failing if link is broken This can happen if you `yarn release` without keeping node modules. --- ci/lib.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/lib.sh b/ci/lib.sh index 49f9ed951..96a413f1c 100755 --- a/ci/lib.sh +++ b/ci/lib.sh @@ -103,7 +103,7 @@ RELEASE_PATH="${RELEASE_PATH-release}" # Code itself but also extensions will look specifically in this directory for # files (like the ripgrep binary or the oniguruma wasm). symlink_asar() { - if [ ! -e node_modules.asar ]; then + if [ ! -L node_modules.asar ]; then if [ "${WINDIR-}" ]; then # mklink takes the link name first. mklink /J node_modules.asar node_modules From cb11e1f75054ea1faa79f8736d30f568582f1a50 Mon Sep 17 00:00:00 2001 From: Asher Date: Fri, 8 Jan 2021 10:37:47 -0600 Subject: [PATCH 003/422] Fix typings rsync --- ci/build/build-release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/build/build-release.sh b/ci/build/build-release.sh index 659ca31ac..8d28b8363 100755 --- a/ci/build/build-release.sh +++ b/ci/build/build-release.sh @@ -45,7 +45,7 @@ bundle_code_server() { # Add typings for plugins mkdir -p "$RELEASE_PATH/typings" - rsync typings/pluginapi.d.ts"$RELEASE_PATH/typings" + rsync typings/pluginapi.d.ts "$RELEASE_PATH/typings" # Adds the commit to package.json jq --slurp '.[0] * .[1]' package.json <( From 693fdbefb4a2e9e7d18cf445d0b55e83b6fbcc99 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Fri, 8 Jan 2021 22:59:35 -0500 Subject: [PATCH 004/422] browser: Add favicon.afdesign It requires git-lfs to pull down if you want to adjust the favicon and also the affinity designer software available only on Windows and Mac. Might be a good idea to switch to Figma at some point and commit a .fig file. --- .gitattributes | 1 + src/browser/favicon.afdesign | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 .gitattributes create mode 100644 src/browser/favicon.afdesign diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..dc5caf936 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.afdesign filter=lfs diff=lfs merge=lfs -text diff --git a/src/browser/favicon.afdesign b/src/browser/favicon.afdesign new file mode 100644 index 000000000..b654f32e8 --- /dev/null +++ b/src/browser/favicon.afdesign @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:625d2049c38ae27df0613fa533020e889fa98affd603050f46d3748be7b90d0b +size 38675 From fa2aed6d468697c968175e2f649a0206e9e8353a Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Sat, 9 Jan 2021 01:45:08 -0500 Subject: [PATCH 005/422] gen_icons.sh: Document pwa-icon vs favicon having different design --- ci/dev/gen_icons.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ci/dev/gen_icons.sh b/ci/dev/gen_icons.sh index 55ec0cc6c..432b8f62f 100755 --- a/ci/dev/gen_icons.sh +++ b/ci/dev/gen_icons.sh @@ -14,6 +14,9 @@ main() { # -background defaults to white but we want it transparent. # https://imagemagick.org/script/command-line-options.php#background convert -quiet -background transparent -resize 256x256 favicon.svg favicon.ico + # We do not generate the pwa-icon from the favicon as they are slightly different + # designs and sizes. + # See favicon.afdesign and #2401 for details on the differences. convert -quiet -background transparent -resize 192x192 pwa-icon.png pwa-icon-192.png convert -quiet -background transparent -resize 512x512 pwa-icon.png pwa-icon-512.png From f15580b28ae021b549a723847497d6f8252b3eb2 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Sat, 9 Jan 2021 01:49:39 -0500 Subject: [PATCH 006/422] favicon: Add dark mode support Closes #2538 Works as expected on latest Firefox and Chromium. --- ci/dev/gen_icons.sh | 18 ++++++++++++++++++ src/browser/media/favicon-dark-support.svg | 7 +++++++ src/browser/pages/error.html | 2 +- src/browser/pages/login.html | 2 +- src/browser/pages/vscode.html | 2 +- 5 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 src/browser/media/favicon-dark-support.svg diff --git a/ci/dev/gen_icons.sh b/ci/dev/gen_icons.sh index 432b8f62f..9d27486dc 100755 --- a/ci/dev/gen_icons.sh +++ b/ci/dev/gen_icons.sh @@ -21,6 +21,24 @@ main() { convert -quiet -background transparent -resize 512x512 pwa-icon.png pwa-icon-512.png # We use -quiet above to avoid https://github.com/ImageMagick/ImageMagick/issues/884 + + # The following adds dark mode support for the favicon as favicon-dark-support.svg + # There is no similar capability for pwas or .ico so we can only add support to the svg. + favicon_dark_style="" + # See https://stackoverflow.com/a/22901380/4283659 + # This escapes all newlines so that sed will accept them. + favicon_dark_style="$(printf "%s\n" "$favicon_dark_style" | sed -e ':a' -e 'N' -e '$!ba' -e 's/\n/\\n/g')" + sed "$( + cat -n << EOF +s% favicon-dark-support.svg } main "$@" diff --git a/src/browser/media/favicon-dark-support.svg b/src/browser/media/favicon-dark-support.svg new file mode 100644 index 000000000..06f1fa00d --- /dev/null +++ b/src/browser/media/favicon-dark-support.svg @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/src/browser/pages/error.html b/src/browser/pages/error.html index 4d24d85f5..73a9599bd 100644 --- a/src/browser/pages/error.html +++ b/src/browser/pages/error.html @@ -11,7 +11,7 @@ content="style-src 'self'; manifest-src 'self'; img-src 'self' data:; font-src 'self' data:;" /> {{ERROR_TITLE}} - code-server - + diff --git a/src/browser/pages/login.html b/src/browser/pages/login.html index 9f74b296f..ef3f16a40 100644 --- a/src/browser/pages/login.html +++ b/src/browser/pages/login.html @@ -11,7 +11,7 @@ content="style-src 'self'; script-src 'self' 'unsafe-inline'; manifest-src 'self'; img-src 'self' data:; font-src 'self' data:;" /> code-server login - + diff --git a/src/browser/pages/vscode.html b/src/browser/pages/vscode.html index c46cb47a2..ef61fa5eb 100644 --- a/src/browser/pages/vscode.html +++ b/src/browser/pages/vscode.html @@ -24,7 +24,7 @@ - + diff --git a/.gitignore b/.gitignore index 3428cb87f..fdb7c563b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ node-* /plugins /lib/coder-cloud-agent .home -coverage \ No newline at end of file +coverage +**/.DS_Store \ No newline at end of file diff --git a/.tours/contributing.tour b/.tours/contributing.tour index 2bece390d..1dd8e6b2a 100644 --- a/.tours/contributing.tour +++ b/.tours/contributing.tour @@ -50,7 +50,7 @@ { "file": "src/node/heart.ts", "line": 7, - "description": "code-server's heart beats to indicate recent activity.\n\nAlso documented here: [https://github.com/cdr/code-server/blob/master/doc/FAQ.md#heartbeat-file](https://github.com/cdr/code-server/blob/master/doc/FAQ.md#heartbeat-file)" + "description": "code-server's heart beats to indicate recent activity.\n\nAlso documented here: [https://github.com/cdr/code-server/blob/master/docs/FAQ.md#heartbeat-file](https://github.com/cdr/code-server/blob/master/docs/FAQ.md#heartbeat-file)" }, { "file": "src/node/socket.ts", @@ -80,12 +80,12 @@ { "file": "src/node/routes/domainProxy.ts", "line": 18, - "description": "code-server provides a built-in proxy to help in developing web-based applications. This is the code for the domain-based proxy.\n\nAlso documented here: [https://github.com/cdr/code-server/blob/master/doc/FAQ.md#how-do-i-securely-access-web-services](https://github.com/cdr/code-server/blob/master/doc/FAQ.md#how-do-i-securely-access-web-services)" + "description": "code-server provides a built-in proxy to help in developing web-based applications. This is the code for the domain-based proxy.\n\nAlso documented here: [https://github.com/cdr/code-server/blob/master/docs/FAQ.md#how-do-i-securely-access-web-services](https://github.com/cdr/code-server/blob/master/docs/FAQ.md#how-do-i-securely-access-web-services)" }, { "file": "src/node/routes/pathProxy.ts", "line": 19, - "description": "Here is the path-based version of the proxy.\n\nAlso documented here: [https://github.com/cdr/code-server/blob/master/doc/FAQ.md#how-do-i-securely-access-web-services](https://github.com/cdr/code-server/blob/master/doc/FAQ.md#how-do-i-securely-access-web-services)" + "description": "Here is the path-based version of the proxy.\n\nAlso documented here: [https://github.com/cdr/code-server/blob/master/docs/FAQ.md#how-do-i-securely-access-web-services](https://github.com/cdr/code-server/blob/master/docs/FAQ.md#how-do-i-securely-access-web-services)" }, { "file": "src/node/proxy.ts", @@ -95,7 +95,7 @@ { "file": "src/node/routes/health.ts", "line": 5, - "description": "A simple endpoint that lets you see if code-server is up.\n\nAlso documented here: [https://github.com/cdr/code-server/blob/master/doc/FAQ.md#healthz-endpoint](https://github.com/cdr/code-server/blob/master/doc/FAQ.md#healthz-endpoint)" + "description": "A simple endpoint that lets you see if code-server is up.\n\nAlso documented here: [https://github.com/cdr/code-server/blob/master/docs/FAQ.md#healthz-endpoint](https://github.com/cdr/code-server/blob/master/docs/FAQ.md#healthz-endpoint)" }, { "file": "src/node/routes/login.ts", @@ -145,7 +145,7 @@ { "directory": "lib/vscode", "line": 1, - "description": "code-server makes use of VS Code's frontend web/remote support. Most of the modifications implement the remote server since that portion of the code is closed source and not released with VS Code.\n\nWe also have a few bug fixes and have added some features (like client-side extensions). See [https://github.com/cdr/code-server/blob/master/doc/CONTRIBUTING.md#modifications-to-vs-code](https://github.com/cdr/code-server/blob/master/doc/CONTRIBUTING.md#modifications-to-vs-code) for a list.\n\nWe make an effort to keep the modifications as few as possible." + "description": "code-server makes use of VS Code's frontend web/remote support. Most of the modifications implement the remote server since that portion of the code is closed source and not released with VS Code.\n\nWe also have a few bug fixes and have added some features (like client-side extensions). See [https://github.com/cdr/code-server/blob/master/docs/CONTRIBUTING.md#modifications-to-vs-code](https://github.com/cdr/code-server/blob/master/docs/CONTRIBUTING.md#modifications-to-vs-code) for a list.\n\nWe make an effort to keep the modifications as few as possible." } ] } \ No newline at end of file diff --git a/.tours/start-development.tour b/.tours/start-development.tour index 41a379ee9..4df15077d 100644 --- a/.tours/start-development.tour +++ b/.tours/start-development.tour @@ -20,7 +20,7 @@ { "file": "src/node/app.ts", "line": 62, - "description": "## That's it!\n\n\nThat's all there is to it! When this tour ends, your terminal session may stop, but just use `yarn watch` to start developing from here on out!\n\n\nIf you haven't already, be sure to check out these resources:\n- [Tour: Contributing](command:codetour.startTourByTitle?[\"Contributing\")\n- [Docs: FAQ.md](https://github.com/cdr/code-server/blob/master/doc/FAQ.md)\n- [Docs: CONTRIBUTING.md](https://github.com/cdr/code-server/blob/master/doc/CONTRIBUTING.md)\n- [Community: GitHub Discussions](https://github.com/cdr/code-server/discussions)\n- [Community: Slack](https://community.coder.com)" + "description": "## That's it!\n\n\nThat's all there is to it! When this tour ends, your terminal session may stop, but just use `yarn watch` to start developing from here on out!\n\n\nIf you haven't already, be sure to check out these resources:\n- [Tour: Contributing](command:codetour.startTourByTitle?[\"Contributing\")\n- [Docs: FAQ.md](https://github.com/cdr/code-server/blob/master/docs/FAQ.md)\n- [Docs: CONTRIBUTING.md](https://github.com/cdr/code-server/blob/master/docs/CONTRIBUTING.md)\n- [Community: GitHub Discussions](https://github.com/cdr/code-server/discussions)\n- [Community: Slack](https://community.coder.com)" } ] } diff --git a/README.md b/README.md index e3b2bef4a..a0a5f36b3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Run [VS Code](https://github.com/Microsoft/vscode) on any machine anywhere and access it in the browser. -![Screenshot](./doc/assets/screenshot.png) +![Screenshot](./docs/assets/screenshot.png) ## Highlights @@ -17,7 +17,7 @@ Run [VS Code](https://github.com/Microsoft/vscode) on any machine anywhere and a There are two ways to get started: 1. Using the [install script](./install.sh), which automates most of the process. The script uses the system package manager (if possible) -2. Manually installing code-server; see [Installation](./doc/install.md) for instructions applicable to most use cases +2. Manually installing code-server; see [Installation](./docs/install.md) for instructions applicable to most use cases If you choose to use the install script, you can preview what occurs during the install process: @@ -33,7 +33,7 @@ curl -fsSL https://code-server.dev/install.sh | sh When done, the install script prints out instructions for running and starting code-server. -We also have an in-depth [setup and configuration](./doc/guide.md) guide. +We also have an in-depth [setup and configuration](./docs/guide.md) guide. ### Cloud Program ☁️ @@ -51,11 +51,11 @@ Proxying code-server to Coder Cloud, you can access your IDE at https://valmar-j ## FAQ -See [./doc/FAQ.md](./doc/FAQ.md). +See [./docs/FAQ.md](./docs/FAQ.md). ## Want to help? -See [CONTRIBUTING](./doc/CONTRIBUTING.md) for details. +See [CONTRIBUTING](./docs/CONTRIBUTING.md) for details. ## Hiring diff --git a/ci/README.md b/ci/README.md index b261a0ff4..df8af9f6f 100644 --- a/ci/README.md +++ b/ci/README.md @@ -16,7 +16,7 @@ Make sure you have `$GITHUB_TOKEN` set and [hub](https://github.com/github/hub) 1. Update the version of code-server and make a PR. 1. Update in `package.json` - 2. Update in [./doc/install.md](../doc/install.md) + 2. Update in [./docs/install.md](../docs/install.md) 3. Update in [./ci/helm-chart/README.md](../ci/helm-chart/README.md) - Remember to update the chart version as well on top of appVersion in `Chart.yaml`. - Run `rg -g '!yarn.lock' -g '!*.svg' '3\.7\.5'` to ensure all values have been @@ -62,7 +62,7 @@ NOTE: we have to manually change the color because the default is red if coverag This directory contains scripts used for the development of code-server. - [./ci/dev/image](./dev/image) - - See [./doc/CONTRIBUTING.md](../doc/CONTRIBUTING.md) for docs on the development container. + - See [./docs/CONTRIBUTING.md](../docs/CONTRIBUTING.md) for docs on the development container. - [./ci/dev/fmt.sh](./dev/fmt.sh) (`yarn fmt`) - Runs formatters. - [./ci/dev/lint.sh](./dev/lint.sh) (`yarn lint`) @@ -73,7 +73,7 @@ This directory contains scripts used for the development of code-server. - Runs `yarn fmt`, `yarn lint` and `yarn test`. - [./ci/dev/watch.ts](./dev/watch.ts) (`yarn watch`) - Starts a process to build and launch code-server and restart on any code changes. - - Example usage in [./doc/CONTRIBUTING.md](../doc/CONTRIBUTING.md). + - Example usage in [./docs/CONTRIBUTING.md](../docs/CONTRIBUTING.md). - [./ci/dev/gen_icons.sh](./ci/dev/gen_icons.sh) (`yarn icons`) - Generates the various icons from a single `.svg` favicon in `src/browser/media/favicon.svg`. diff --git a/ci/build/npm-postinstall.sh b/ci/build/npm-postinstall.sh index b12d9a864..bbe23322c 100755 --- a/ci/build/npm-postinstall.sh +++ b/ci/build/npm-postinstall.sh @@ -33,7 +33,7 @@ main() { if ! vscode_yarn; then echo "You may not have the required dependencies to build the native modules." - echo "Please see https://github.com/cdr/code-server/blob/master/doc/npm.md" + echo "Please see https://github.com/cdr/code-server/blob/master/docs/npm.md" exit 1 fi } diff --git a/ci/build/release-github-draft.sh b/ci/build/release-github-draft.sh index 4e077a356..1f33c34cc 100755 --- a/ci/build/release-github-draft.sh +++ b/ci/build/release-github-draft.sh @@ -20,6 +20,7 @@ maintains all user data in \`~/.local/share/code-server\` so that it is preserve installations. ## New Features + - ⭐ Summarize new features here with references to issues ## Bug Fixes diff --git a/ci/dev/fmt.sh b/ci/dev/fmt.sh index a2bf80328..c01c0debc 100755 --- a/ci/dev/fmt.sh +++ b/ci/dev/fmt.sh @@ -23,12 +23,12 @@ main() { git ls-files "${prettierExts[@]}" | grep -v "lib/vscode" | grep -v 'helm-chart' ) - doctoc --title '# FAQ' doc/FAQ.md > /dev/null - doctoc --title '# Setup Guide' doc/guide.md > /dev/null - doctoc --title '# Install' doc/install.md > /dev/null - doctoc --title '# npm Install Requirements' doc/npm.md > /dev/null - doctoc --title '# Contributing' doc/CONTRIBUTING.md > /dev/null - doctoc --title '# iPad' doc/ipad.md > /dev/null + doctoc --title '# FAQ' docs/FAQ.md > /dev/null + doctoc --title '# Setup Guide' docs/guide.md > /dev/null + doctoc --title '# Install' docs/install.md > /dev/null + doctoc --title '# npm Install Requirements' docs/npm.md > /dev/null + doctoc --title '# Contributing' docs/CONTRIBUTING.md > /dev/null + doctoc --title '# iPad' docs/ipad.md > /dev/null if [[ ${CI-} && $(git ls-files --other --modified --exclude-standard) ]]; then echo "Files need generation or are formatted incorrectly:" diff --git a/doc/CONTRIBUTING.md b/docs/CONTRIBUTING.md similarity index 100% rename from doc/CONTRIBUTING.md rename to docs/CONTRIBUTING.md diff --git a/doc/FAQ.md b/docs/FAQ.md similarity index 100% rename from doc/FAQ.md rename to docs/FAQ.md diff --git a/doc/assets/screenshot.png b/docs/assets/screenshot.png similarity index 100% rename from doc/assets/screenshot.png rename to docs/assets/screenshot.png diff --git a/doc/guide.md b/docs/guide.md similarity index 99% rename from doc/guide.md rename to docs/guide.md index 3d04dc0c5..2e5ebc371 100644 --- a/doc/guide.md +++ b/docs/guide.md @@ -22,9 +22,9 @@ To reiterate, `code-server` lets you run VS Code on a remote server and then acc Further docs are at: - [README](../README.md) for a general overview -- [INSTALL](../doc/install.md) for installation +- [INSTALL](../docs/install.md) for installation - [FAQ](./FAQ.md) for common questions. -- [CONTRIBUTING](../doc/CONTRIBUTING.md) for development docs +- [CONTRIBUTING](../docs/CONTRIBUTING.md) for development docs We highly recommend reading the [FAQ](./FAQ.md) on the [Differences compared to VS Code](./FAQ.md#differences-compared-to-vs-code) before beginning. diff --git a/doc/install.md b/docs/install.md similarity index 100% rename from doc/install.md rename to docs/install.md diff --git a/doc/ipad.md b/docs/ipad.md similarity index 100% rename from doc/ipad.md rename to docs/ipad.md diff --git a/doc/npm.md b/docs/npm.md similarity index 100% rename from doc/npm.md rename to docs/npm.md diff --git a/doc/triage.md b/docs/triage.md similarity index 100% rename from doc/triage.md rename to docs/triage.md diff --git a/install.sh b/install.sh index 9decd53f7..bf341cae4 100755 --- a/install.sh +++ b/install.sh @@ -2,7 +2,7 @@ set -eu # code-server's automatic install script. -# See https://github.com/cdr/code-server/blob/master/doc/install.md +# See https://github.com/cdr/code-server/blob/master/docs/install.md usage() { arg0="$0" @@ -67,7 +67,7 @@ Usage: It will cache all downloaded assets into ~/.cache/code-server -More installation docs are at https://github.com/cdr/code-server/blob/master/doc/install.md +More installation docs are at https://github.com/cdr/code-server/blob/master/docs/install.md EOF } From a1a0aec4721447c169a884487bee601776c4ca4d Mon Sep 17 00:00:00 2001 From: Joe Previte Date: Wed, 3 Feb 2021 09:49:45 -0700 Subject: [PATCH 051/422] Create CODE_OF_CONDUCT.md --- docs/CODE_OF_CONDUCT.md | 76 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 docs/CODE_OF_CONDUCT.md diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..525dc1db4 --- /dev/null +++ b/docs/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at joe@coder.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq From d7f67b80dfc514f9a5b1ac57e72a2512c451cdb5 Mon Sep 17 00:00:00 2001 From: Joe Previte Date: Wed, 3 Feb 2021 09:52:59 -0700 Subject: [PATCH 052/422] chore: add CODE_OF_CONDUCT to fmt script --- ci/dev/fmt.sh | 1 + docs/CODE_OF_CONDUCT.md | 29 ++++++++++++++++------------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/ci/dev/fmt.sh b/ci/dev/fmt.sh index c01c0debc..139c0ea06 100755 --- a/ci/dev/fmt.sh +++ b/ci/dev/fmt.sh @@ -28,6 +28,7 @@ main() { doctoc --title '# Install' docs/install.md > /dev/null doctoc --title '# npm Install Requirements' docs/npm.md > /dev/null doctoc --title '# Contributing' docs/CONTRIBUTING.md > /dev/null + doctoc --title '# Contributor Covenant Code of Conduct' docs/CODE_OF_CONDUCT.md > /dev/null doctoc --title '# iPad' docs/ipad.md > /dev/null if [[ ${CI-} && $(git ls-files --other --modified --exclude-standard) ]]; then diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md index 525dc1db4..95b8673e2 100644 --- a/docs/CODE_OF_CONDUCT.md +++ b/docs/CODE_OF_CONDUCT.md @@ -1,3 +1,6 @@ + + + # Contributor Covenant Code of Conduct ## Our Pledge @@ -14,22 +17,22 @@ appearance, race, religion, or sexual identity and orientation. Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting ## Our Responsibilities From 74dc5a881f442c270652e82456af77a34b7978ae Mon Sep 17 00:00:00 2001 From: Joe Previte Date: Wed, 3 Feb 2021 11:06:19 -0700 Subject: [PATCH 053/422] refactor: update email address --- docs/CODE_OF_CONDUCT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md index 95b8673e2..c5483f4cf 100644 --- a/docs/CODE_OF_CONDUCT.md +++ b/docs/CODE_OF_CONDUCT.md @@ -58,7 +58,7 @@ further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at joe@coder.com. All +reported by contacting the project team at contact@coder.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. From 43aa0401e064ab4c02f0ff2670a00c6db414fbc3 Mon Sep 17 00:00:00 2001 From: Joe Previte Date: Wed, 3 Feb 2021 11:08:06 -0700 Subject: [PATCH 054/422] Update docs/CODE_OF_CONDUCT.md --- docs/CODE_OF_CONDUCT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md index c5483f4cf..fe14b3a64 100644 --- a/docs/CODE_OF_CONDUCT.md +++ b/docs/CODE_OF_CONDUCT.md @@ -58,7 +58,7 @@ further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at contact@coder.com. All +reported by contacting the project team at opensource@coder.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. From 71cf459ececd572e32e0ac5f6695809db852fed4 Mon Sep 17 00:00:00 2001 From: Joe Previte Date: Wed, 3 Feb 2021 11:22:13 -0700 Subject: [PATCH 055/422] feat: add tests for common/util --- test/util.test.ts | 107 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 1 deletion(-) diff --git a/test/util.test.ts b/test/util.test.ts index d5eb37e46..d6883e103 100644 --- a/test/util.test.ts +++ b/test/util.test.ts @@ -1,4 +1,7 @@ -import { normalize } from "../src/common/util" +import { logger as l } from "@coder/logger" +import { arrayify, getFirstString, normalize, plural, resolveBase, split, trimSlashes } from "../src/common/util" + +type LocationLike = Pick describe("util", () => { describe("normalize", () => { @@ -15,4 +18,106 @@ describe("util", () => { expect(normalize("qux", true)).toBe("qux") }) }) + + describe("split", () => { + it("should split at a comma", () => { + expect(split("Hello,world", ",")).toStrictEqual(["Hello", "world"]) + }) + + it("shouldn't split if the delimiter doesn't exist", () => { + expect(split("Hello world", ",")).toStrictEqual(["Hello world", ""]) + }) + }) + + describe("plural", () => { + it("should add an s if count is greater than 1", () => { + expect(plural(2, "dog")).toBe("dogs") + }) + it("should NOT add an s if the count is 1", () => { + expect(plural(1, "dog")).toBe("dog") + }) + }) + + describe("trimSlashes", () => { + it("should remove leading slashes", () => { + expect(trimSlashes("/hello-world")).toBe("hello-world") + }) + + it("should remove trailing slashes", () => { + expect(trimSlashes("hello-world/")).toBe("hello-world") + }) + + it("should remove both leading and trailing slashes", () => { + expect(trimSlashes("/hello-world/")).toBe("hello-world") + }) + + it("should remove multiple leading and trailing slashes", () => { + expect(trimSlashes("///hello-world////")).toBe("hello-world") + }) + }) + + describe("resolveBase", () => { + beforeEach(() => { + const location: LocationLike = { + pathname: "/healthz", + origin: "http://localhost:8080", + } + + // Because resolveBase is not a pure function + // and relies on the global location to be set + // we set it before all the tests + // and tell TS that our location should be looked at + // as Location (even though it's missing some properties) + global.location = location as Location + }) + + it("should resolve a base", () => { + expect(resolveBase("localhost:8080")).toBe("/localhost:8080") + }) + + it("should resolve a base with a forward slash at the beginning", () => { + expect(resolveBase("/localhost:8080")).toBe("/localhost:8080") + }) + + it("should resolve a base with query params", () => { + expect(resolveBase("localhost:8080?folder=hello-world")).toBe("/localhost:8080") + }) + + it("should resolve a base with a path", () => { + expect(resolveBase("localhost:8080/hello/world")).toBe("/localhost:8080/hello/world") + }) + + it("should resolve a base to an empty string when not provided", () => { + expect(resolveBase()).toBe("") + }) + }) + + describe("arrayify", () => { + it("should return value it's already an array", () => { + expect(arrayify(["hello", "world"])).toStrictEqual(["hello", "world"]) + }) + it("should wrap the value in an array if not an array", () => { + expect( + arrayify({ + name: "Coder", + version: "3.8", + }), + ).toStrictEqual([{ name: "Coder", version: "3.8" }]) + }) + it("should return an empty array if the value is undefined", () => { + expect(arrayify(undefined)).toStrictEqual([]) + }) + }) + + describe("getFirstString", () => { + it("should return the string if passed a string", () => { + expect(getFirstString("Hello world!")).toBe("Hello world!") + }) + it("should get the first string from an array", () => { + expect(getFirstString(["Hello", "World"])).toBe("Hello") + }) + it("should return undefined if the value isn't an array or a string", () => { + expect(getFirstString({ name: "Coder" })).toBe(undefined) + }) + }) }) From 3cebfcd447cb97a43c1fdf71704edbb16e5ddea4 Mon Sep 17 00:00:00 2001 From: Joe Previte Date: Wed, 3 Feb 2021 16:10:17 -0700 Subject: [PATCH 056/422] feat: add tests for logError --- test/util.test.ts | 52 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/test/util.test.ts b/test/util.test.ts index d6883e103..45179bdcd 100644 --- a/test/util.test.ts +++ b/test/util.test.ts @@ -1,5 +1,16 @@ -import { logger as l } from "@coder/logger" -import { arrayify, getFirstString, normalize, plural, resolveBase, split, trimSlashes } from "../src/common/util" +// Note: we need to import logger from the root +// because this is the logger used in logError in ../src/common/util +import { logger } from "../node_modules/@coder/logger" +import { + arrayify, + getFirstString, + logError, + normalize, + plural, + resolveBase, + split, + trimSlashes, +} from "../src/common/util" type LocationLike = Pick @@ -96,6 +107,7 @@ describe("util", () => { it("should return value it's already an array", () => { expect(arrayify(["hello", "world"])).toStrictEqual(["hello", "world"]) }) + it("should wrap the value in an array if not an array", () => { expect( arrayify({ @@ -104,6 +116,7 @@ describe("util", () => { }), ).toStrictEqual([{ name: "Coder", version: "3.8" }]) }) + it("should return an empty array if the value is undefined", () => { expect(arrayify(undefined)).toStrictEqual([]) }) @@ -113,11 +126,46 @@ describe("util", () => { it("should return the string if passed a string", () => { expect(getFirstString("Hello world!")).toBe("Hello world!") }) + it("should get the first string from an array", () => { expect(getFirstString(["Hello", "World"])).toBe("Hello") }) + it("should return undefined if the value isn't an array or a string", () => { expect(getFirstString({ name: "Coder" })).toBe(undefined) }) }) + + describe("logError", () => { + let spy: jest.SpyInstance + + beforeEach(() => { + spy = jest.spyOn(logger, "error") + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + afterAll(() => { + jest.restoreAllMocks() + }) + + it("should log an error with the message and stack trace", () => { + const message = "You don't have access to that folder." + const error = new Error(message) + + logError("ui", error) + + expect(spy).toHaveBeenCalled() + expect(spy).toHaveBeenCalledWith(`ui: ${error.message} ${error.stack}`) + }) + + it("should log an error, even if not an instance of error", () => { + logError("api", "oh no") + + expect(spy).toHaveBeenCalled() + expect(spy).toHaveBeenCalledWith("api: oh no") + }) + }) }) From 323339d15aea8dde24869f3d4b769bea523cc275 Mon Sep 17 00:00:00 2001 From: Joe Previte Date: Wed, 3 Feb 2021 16:52:06 -0700 Subject: [PATCH 057/422] feat: add jsdom for browser-ish tests --- test/package.json | 3 ++- test/yarn.lock | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/test/package.json b/test/package.json index 2a5e96138..e3cb94f61 100644 --- a/test/package.json +++ b/test/package.json @@ -1,11 +1,12 @@ { - "#": "We must put jest in a sub-directory otherwise VS Code somehow picks up", "#": "the types and generates conflicts with mocha.", "devDependencies": { "@types/jest": "^26.0.20", + "@types/jsdom": "^16.2.6", "@types/node-fetch": "^2.5.8", "@types/supertest": "^2.0.10", "jest": "^26.6.3", + "jsdom": "^16.4.0", "node-fetch": "^2.6.1", "playwright": "^1.8.0", "supertest": "^6.1.1", diff --git a/test/yarn.lock b/test/yarn.lock index a8bc09050..4f6ae7ab3 100644 --- a/test/yarn.lock +++ b/test/yarn.lock @@ -551,6 +551,15 @@ jest-diff "^26.0.0" pretty-format "^26.0.0" +"@types/jsdom@^16.2.6": + version "16.2.6" + resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-16.2.6.tgz#9ddf0521e49be5365797e690c3ba63148e562c29" + integrity sha512-yQA+HxknGtW9AkRTNyiSH3OKW5V+WzO8OPTdne99XwJkYC+KYxfNIcoJjeiSqP3V00PUUpFP6Myoo9wdIu78DQ== + dependencies: + "@types/node" "*" + "@types/parse5" "*" + "@types/tough-cookie" "*" + "@types/node-fetch@^2.5.8": version "2.5.8" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.8.tgz#e199c835d234c7eb0846f6618012e558544ee2fb" @@ -569,6 +578,11 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== +"@types/parse5@*": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.0.tgz#38590dc2c3cf5717154064e3ee9b6947ee21b299" + integrity sha512-oPwPSj4a1wu9rsXTEGIJz91ISU725t0BmSnUhb57sI+M8XEmvUop84lzuiYdq0Y5M6xLY8DBPg0C2xEQKLyvBA== + "@types/prettier@^2.0.0": version "2.1.6" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.1.6.tgz#f4b1efa784e8db479cdb8b14403e2144b1e9ff03" @@ -594,6 +608,11 @@ dependencies: "@types/superagent" "*" +"@types/tough-cookie@*": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.0.tgz#fef1904e4668b6e5ecee60c52cc6a078ffa6697d" + integrity sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A== + "@types/yargs-parser@*": version "20.2.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.0.tgz#dd3e6699ba3237f0348cd085e4698780204842f9" From 4f6efced683c9d66facf7b231a43d1f4a0bf1841 Mon Sep 17 00:00:00 2001 From: Joe Previte Date: Wed, 3 Feb 2021 16:52:17 -0700 Subject: [PATCH 058/422] feat: add tests for getOptions --- test/package.json | 2 +- test/util.test.ts | 91 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/test/package.json b/test/package.json index e3cb94f61..4a408464c 100644 --- a/test/package.json +++ b/test/package.json @@ -1,5 +1,5 @@ { - "#": "the types and generates conflicts with mocha.", + "#": "We must put jest in a sub-directory otherwise VS Code somehow picks up the types and generates conflicts with mocha.", "devDependencies": { "@types/jest": "^26.0.20", "@types/jsdom": "^16.2.6", diff --git a/test/util.test.ts b/test/util.test.ts index 45179bdcd..418756a58 100644 --- a/test/util.test.ts +++ b/test/util.test.ts @@ -1,9 +1,12 @@ +import { JSDOM } from "jsdom" // Note: we need to import logger from the root // because this is the logger used in logError in ../src/common/util import { logger } from "../node_modules/@coder/logger" import { arrayify, + generateUuid, getFirstString, + getOptions, logError, normalize, plural, @@ -12,6 +15,10 @@ import { trimSlashes, } from "../src/common/util" +const dom = new JSDOM() +global.document = dom.window.document +// global.window = (dom.window as unknown) as Window & typeof globalThis + type LocationLike = Pick describe("util", () => { @@ -49,6 +56,20 @@ describe("util", () => { }) }) + describe("generateUuid", () => { + it("should generate a unique uuid", () => { + const uuid = generateUuid() + const uuid2 = generateUuid() + expect(uuid).toHaveLength(24) + expect(typeof uuid).toBe("string") + expect(uuid).not.toBe(uuid2) + }) + it("should generate a uuid of a specific length", () => { + const uuid = generateUuid(10) + expect(uuid).toHaveLength(10) + }) + }) + describe("trimSlashes", () => { it("should remove leading slashes", () => { expect(trimSlashes("/hello-world")).toBe("hello-world") @@ -103,6 +124,76 @@ describe("util", () => { }) }) + describe("getOptions", () => { + // Things to mock + // logger + // location + // document + beforeEach(() => { + const location: LocationLike = { + pathname: "/healthz", + origin: "http://localhost:8080", + // search: "?environmentId=600e0187-0909d8a00cb0a394720d4dce", + } + + // Because resolveBase is not a pure function + // and relies on the global location to be set + // we set it before all the tests + // and tell TS that our location should be looked at + // as Location (even though it's missing some properties) + global.location = location as Location + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it("should return options with base and cssStaticBase even if it doesn't exist", () => { + expect(getOptions()).toStrictEqual({ + base: "", + csStaticBase: "", + }) + }) + + it("should return options when they do exist", () => { + // Mock getElementById + const spy = jest.spyOn(document, "getElementById") + // Create a fake element and set the attribute + const mockElement = document.createElement("div") + mockElement.setAttribute( + "data-settings", + '{"base":".","csStaticBase":"./static/development/Users/jp/Dev/code-server","logLevel":2,"disableTelemetry":false,"disableUpdateCheck":false}', + ) + // Return mockElement from the spy + // this way, when we call "getElementById" + // it returns the element + spy.mockImplementation(() => mockElement) + + expect(getOptions()).toStrictEqual({ + base: "", + csStaticBase: "/static/development/Users/jp/Dev/code-server", + disableTelemetry: false, + disableUpdateCheck: false, + logLevel: 2, + }) + }) + + it("should include queryOpts", () => { + // Trying to understand how the implementation works + // 1. It grabs the search params from location.search (i.e. ?) + // 2. it then grabs the "options" param if it exists + // 3. then it creates a new options object + // spreads the original options + // then parses the queryOpts + location.search = '?options={"logLevel":2}' + expect(getOptions()).toStrictEqual({ + base: "", + csStaticBase: "", + logLevel: 2, + }) + }) + }) + describe("arrayify", () => { it("should return value it's already an array", () => { expect(arrayify(["hello", "world"])).toStrictEqual(["hello", "world"]) From c08e3bb06d62a229bf6fb008b5cfccf2a2a6d829 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Thu, 4 Feb 2021 17:29:44 -0500 Subject: [PATCH 059/422] Add /absproxy to remove --proxy-path-passthrough See https://github.com/cdr/code-server/issues/2222#issuecomment-765235938 Makes way more sense. --- src/node/cli.ts | 5 ---- src/node/routes/index.ts | 21 ++++++++++++++-- src/node/routes/pathProxy.ts | 46 ++++++++++++++++++++++-------------- test/proxy.test.ts | 16 +++++++------ 4 files changed, 56 insertions(+), 32 deletions(-) diff --git a/src/node/cli.ts b/src/node/cli.ts index c8ca387a9..0990797d8 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -50,7 +50,6 @@ export interface Args extends VsArgs { "show-versions"?: boolean "uninstall-extension"?: string[] "proxy-domain"?: string[] - "proxy-path-passthrough"?: boolean locale?: string _: string[] "reuse-window"?: boolean @@ -173,10 +172,6 @@ const options: Options> = { "uninstall-extension": { type: "string[]", description: "Uninstall a VS Code extension by id." }, "show-versions": { type: "boolean", description: "Show VS Code extension versions." }, "proxy-domain": { type: "string[]", description: "Domain used for proxying ports." }, - "proxy-path-passthrough": { - type: "boolean", - description: "Whether the path proxy should leave the /proxy/ in the request path when proxying.", - }, "ignore-last-opened": { type: "boolean", short: "e", diff --git a/src/node/routes/index.ts b/src/node/routes/index.ts index a3fb8bd4f..dd4cc126a 100644 --- a/src/node/routes/index.ts +++ b/src/node/routes/index.ts @@ -103,8 +103,25 @@ export const register = async ( app.use("/", domainProxy.router) wsApp.use("/", domainProxy.wsRouter.router) - app.use("/proxy", proxy.router) - wsApp.use("/proxy", proxy.wsRouter.router) + app.all("/proxy/(:port)(/*)?", (req, res) => { + proxy.proxy(req, res) + }) + wsApp.get("/proxy/(:port)(/*)?", (req, res) => { + proxy.wsProxy(req as WebsocketRequest) + }) + // These two routes pass through the path directly. + // So the proxied app must be aware it is running + // under /absproxy// + app.all("/absproxy/(:port)(/*)?", (req, res) => { + proxy.proxy(req, res, { + passthroughPath: true, + }) + }) + wsApp.get("/absproxy/(:port)(/*)?", (req, res) => { + proxy.wsProxy(req as WebsocketRequest, { + passthroughPath: true, + }) + }) app.use(bodyParser.json()) app.use(bodyParser.urlencoded({ extended: true })) diff --git a/src/node/routes/pathProxy.ts b/src/node/routes/pathProxy.ts index 6a6b9d29c..31fc53366 100644 --- a/src/node/routes/pathProxy.ts +++ b/src/node/routes/pathProxy.ts @@ -1,14 +1,13 @@ -import { Request, Router } from "express" +import { Request, Response } from "express" +import * as path from "path" import qs from "qs" import { HttpCode, HttpError } from "../../common/http" import { normalize } from "../../common/util" import { authenticated, ensureAuthenticated, redirect } from "../http" -import { proxy } from "../proxy" -import { Router as WsRouter } from "../wsRouter" +import { proxy as _proxy } from "../proxy" +import { WebsocketRequest } from "../wsRouter" -export const router = Router() - -const getProxyTarget = (req: Request, passthroughPath: boolean): string => { +const getProxyTarget = (req: Request, passthroughPath?: boolean): string => { if (passthroughPath) { return `http://0.0.0.0:${req.params.port}/${req.originalUrl}` } @@ -16,7 +15,13 @@ const getProxyTarget = (req: Request, passthroughPath: boolean): string => { return `http://0.0.0.0:${req.params.port}/${req.params[0] || ""}${query ? `?${query}` : ""}` } -router.all("/(:port)(/*)?", (req, res) => { +export function proxy( + req: Request, + res: Response, + opts?: { + passthroughPath?: boolean + }, +): void { if (!authenticated(req)) { // If visiting the root (/:port only) redirect to the login page. if (!req.params[0] || req.params[0] === "/") { @@ -28,22 +33,27 @@ router.all("/(:port)(/*)?", (req, res) => { throw new HttpError("Unauthorized", HttpCode.Unauthorized) } - if (!req.args["proxy-path-passthrough"]) { + if (!opts?.passthroughPath) { // Absolute redirects need to be based on the subpath when rewriting. - ;(req as any).base = `${req.baseUrl}/${req.params.port}` + // See proxy.ts. + ;(req as any).base = req.path.split(path.sep).slice(0, 3).join(path.sep) } - proxy.web(req, res, { + _proxy.web(req, res, { ignorePath: true, - target: getProxyTarget(req, req.args["proxy-path-passthrough"] || false), + target: getProxyTarget(req, opts?.passthroughPath), }) -}) +} -export const wsRouter = WsRouter() - -wsRouter.ws("/(:port)(/*)?", ensureAuthenticated, (req) => { - proxy.ws(req, req.ws, req.head, { +export function wsProxy( + req: WebsocketRequest, + opts?: { + passthroughPath?: boolean + }, +): void { + ensureAuthenticated(req) + _proxy.ws(req, req.ws, req.head, { ignorePath: true, - target: getProxyTarget(req, req.args["proxy-path-passthrough"] || false), + target: getProxyTarget(req, opts?.passthroughPath), }) -}) +} diff --git a/test/proxy.test.ts b/test/proxy.test.ts index 0ef5fd796..84a2c35b0 100644 --- a/test/proxy.test.ts +++ b/test/proxy.test.ts @@ -7,6 +7,7 @@ describe("proxy", () => { const nhooyrDevServer = new httpserver.HttpServer() let codeServer: httpserver.HttpServer | undefined let proxyPath: string + let absProxyPath: string let e: express.Express beforeAll(async () => { @@ -14,6 +15,7 @@ describe("proxy", () => { e(req, res) }) proxyPath = `/proxy/${nhooyrDevServer.port()}/wsup` + absProxyPath = proxyPath.replace("/proxy/", "/absproxy/") }) afterAll(async () => { @@ -43,11 +45,11 @@ describe("proxy", () => { }) it("should not rewrite the base path", async () => { - e.get(proxyPath, (req, res) => { + e.get(absProxyPath, (req, res) => { res.json("joe is the best") }) - ;[, , codeServer] = await integration.setup(["--auth=none", "--proxy-path-passthrough=true"], "") - const resp = await codeServer.fetch(proxyPath) + ;[, , codeServer] = await integration.setup(["--auth=none"], "") + const resp = await codeServer.fetch(absProxyPath) expect(resp.status).toBe(200) const json = await resp.json() expect(json).toBe("joe is the best") @@ -69,15 +71,15 @@ describe("proxy", () => { }) it("should not rewrite redirects", async () => { - const finalePath = proxyPath.replace("/wsup", "/finale") - e.post(proxyPath, (req, res) => { + const finalePath = absProxyPath.replace("/wsup", "/finale") + e.post(absProxyPath, (req, res) => { res.redirect(307, finalePath) }) e.post(finalePath, (req, res) => { res.json("redirect success") }) - ;[, , codeServer] = await integration.setup(["--auth=none", "--proxy-path-passthrough=true"], "") - const resp = await codeServer.fetch(proxyPath, { + ;[, , codeServer] = await integration.setup(["--auth=none"], "") + const resp = await codeServer.fetch(absProxyPath, { method: "POST", }) expect(resp.status).toBe(200) From 05a0f213a7ac2e4e5ade3ab7252d09fad422cb3f Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Fri, 5 Feb 2021 02:31:18 -0500 Subject: [PATCH 060/422] Update proxy path passthrough documentation Includes updated create-react-app docs. Closes #2565 --- docs/FAQ.md | 28 ++++++++++++++++------------ src/node/proxy.ts | 3 +-- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index 94535bd09..611855221 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -16,6 +16,7 @@ - [Sub-paths](#sub-paths) - [Sub-domains](#sub-domains) - [Why does the code-server proxy strip `/proxy/` from the request path?](#why-does-the-code-server-proxy-strip-proxyport-from-the-request-path) + - [Proxying to Create React App](#proxying-to-create-react-app) - [Multi-tenancy](#multi-tenancy) - [Docker in code-server container?](#docker-in-code-server-container) - [How can I disable telemetry?](#how-can-i-disable-telemetry) @@ -226,25 +227,28 @@ However many people prefer the cleaner aesthetic of no trailing slashes. This co to the base path as you cannot use relative redirects correctly anymore. See the above link. -For users who are ok with this tradeoff, pass `--proxy-path-passthrough` to code-server -and the path will be passed as is. +For users who are ok with this tradeoff, use `/absproxy` instead and the path will be +passed as is. e.g. `/absproxy/3000/my-app-path` -This is particularly a problem with the `start` script in create-react-app. See +### Proxying to Create React App + +You must use `/absproxy/` with create-react-app. +See [#2565](https://github.com/cdr/code-server/issues/2565) and [#2222](https://github.com/cdr/code-server/issues/2222). You will need to inform -create-react-app of the path at which you are serving via `homepage` field in your -`package.json`. e.g. you'd add the following for the default CRA port: +create-react-app of the path at which you are serving via `$PUBLIC_URL` and webpack +via `$WDS_SOCKET_PATH`. -```json - "homepage": "/proxy/3000", +e.g. + +```sh +PUBLIC_URL=/absproxy/3000 \ + WDS_SOCKET_PATH=$PUBLIC_URL/sockjs-node \ + BROWSER=none yarn start ``` -Then visit `https://my-code-server-address.io/proxy/3000` to see your app exposed through +Then visit `https://my-code-server-address.io/absproxy/3000` to see your app exposed through code-server! -Unfortunately `webpack-dev-server`'s websocket connections will not go through as it -always uses `/sockjs-node`. So hot reloading will not work until the `create-react-app` -team fix this bug. - Highly recommend using the subdomain approach instead to avoid this class of issue. ## Multi-tenancy diff --git a/src/node/proxy.ts b/src/node/proxy.ts index 35fd5d81a..c03d3d5d4 100644 --- a/src/node/proxy.ts +++ b/src/node/proxy.ts @@ -9,8 +9,7 @@ proxy.on("error", (error, _, res) => { }) // Intercept the response to rewrite absolute redirects against the base path. -// Is disabled when the request has no base path which means --proxy-path-passthrough has -// been enabled. +// Is disabled when the request has no base path which means /absproxy is in use. proxy.on("proxyRes", (res, req) => { if (res.headers.location && res.headers.location.startsWith("/") && (req as any).base) { res.headers.location = (req as any).base + res.headers.location From 2a2dade3098dea0e6370ce96d2488d45047d2dd6 Mon Sep 17 00:00:00 2001 From: Joe Previte Date: Fri, 5 Feb 2021 14:07:00 -0700 Subject: [PATCH 061/422] feat: update version in package.json to 3.8.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e94e0668c..51479b901 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-server", "license": "MIT", - "version": "3.8.0", + "version": "3.8.1", "description": "Run VS Code on a remote server.", "homepage": "https://github.com/cdr/code-server", "bugs": { From 25bf871e16a3c4d22155ebe1b163e16811a0c332 Mon Sep 17 00:00:00 2001 From: Joe Previte Date: Fri, 5 Feb 2021 14:07:27 -0700 Subject: [PATCH 062/422] docs(install.md): update to 3.8.1 --- docs/install.md | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/docs/install.md b/docs/install.md index 437c0df07..1538e0822 100644 --- a/docs/install.md +++ b/docs/install.md @@ -2,18 +2,19 @@ # Install -- [Upgrading](#upgrading) -- [install.sh](#installsh) - - [Flags](#flags) - - [Detection Reference](#detection-reference) -- [Debian, Ubuntu](#debian-ubuntu) -- [Fedora, CentOS, RHEL, SUSE](#fedora-centos-rhel-suse) -- [Arch Linux](#arch-linux) -- [yarn, npm](#yarn-npm) -- [macOS](#macos) -- [Standalone Releases](#standalone-releases) -- [Docker](#docker) -- [helm](#helm) +- [Install](#install) + - [Upgrading](#upgrading) + - [install.sh](#installsh) + - [Flags](#flags) + - [Detection Reference](#detection-reference) + - [Debian, Ubuntu](#debian-ubuntu) + - [Fedora, CentOS, RHEL, SUSE](#fedora-centos-rhel-suse) + - [Arch Linux](#arch-linux) + - [yarn, npm](#yarn-npm) + - [macOS](#macos) + - [Standalone Releases](#standalone-releases) + - [Docker](#docker) + - [helm](#helm) @@ -87,8 +88,8 @@ commands presented in the rest of this document. ## Debian, Ubuntu ```bash -curl -fOL https://github.com/cdr/code-server/releases/download/v3.8.0/code-server_3.8.0_amd64.deb -sudo dpkg -i code-server_3.8.0_amd64.deb +curl -fOL https://github.com/cdr/code-server/releases/download/v3.8.1/code-server_3.8.1_amd64.deb +sudo dpkg -i code-server_3.8.1_amd64.deb sudo systemctl enable --now code-server@$USER # Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml ``` @@ -96,8 +97,8 @@ sudo systemctl enable --now code-server@$USER ## Fedora, CentOS, RHEL, SUSE ```bash -curl -fOL https://github.com/cdr/code-server/releases/download/v3.8.0/code-server-3.8.0-amd64.rpm -sudo rpm -i code-server-3.8.0-amd64.rpm +curl -fOL https://github.com/cdr/code-server/releases/download/v3.8.1/code-server-3.8.1-amd64.rpm +sudo rpm -i code-server-3.8.1-amd64.rpm sudo systemctl enable --now code-server@$USER # Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml ``` @@ -166,10 +167,10 @@ Here is an example script for installing and using a standalone `code-server` re ```bash mkdir -p ~/.local/lib ~/.local/bin -curl -fL https://github.com/cdr/code-server/releases/download/v3.8.0/code-server-3.8.0-linux-amd64.tar.gz \ +curl -fL https://github.com/cdr/code-server/releases/download/v3.8.1/code-server-3.8.1-linux-amd64.tar.gz \ | tar -C ~/.local/lib -xz -mv ~/.local/lib/code-server-3.8.0-linux-amd64 ~/.local/lib/code-server-3.8.0 -ln -s ~/.local/lib/code-server-3.8.0/bin/code-server ~/.local/bin/code-server +mv ~/.local/lib/code-server-3.8.1-linux-amd64 ~/.local/lib/code-server-3.8.1 +ln -s ~/.local/lib/code-server-3.8.1/bin/code-server ~/.local/bin/code-server PATH="~/.local/bin:$PATH" code-server # Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml From 244775dab587421b250676f14a957dd8889ae356 Mon Sep 17 00:00:00 2001 From: Joe Previte Date: Fri, 5 Feb 2021 14:08:35 -0700 Subject: [PATCH 063/422] docs(helm chart readme): update to 3.8.1 --- ci/helm-chart/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/helm-chart/README.md b/ci/helm-chart/README.md index c8ba35a12..03cd71ccb 100644 --- a/ci/helm-chart/README.md +++ b/ci/helm-chart/README.md @@ -1,6 +1,6 @@ # code-server -![Version: 1.0.0](https://img.shields.io/badge/Version-1.0.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 3.8.0](https://img.shields.io/badge/AppVersion-3.8.0-informational?style=flat-square) +![Version: 1.0.0](https://img.shields.io/badge/Version-1.0.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 3.8.1](https://img.shields.io/badge/AppVersion-3.8.1-informational?style=flat-square) [code-server](https://github.com/cdr/code-server) code-server is VS Code running on a remote server, accessible through the browser. @@ -72,7 +72,7 @@ and their default values. | hostnameOverride | string | `""` | | | image.pullPolicy | string | `"Always"` | | | image.repository | string | `"codercom/code-server"` | | -| image.tag | string | `"3.8.0"` | | +| image.tag | string | `"3.8.1"` | | | imagePullSecrets | list | `[]` | | | ingress.enabled | bool | `false` | | | nameOverride | string | `""` | | From 05d8b61a326b5ea1ffaf44818d56f8aac5b7eb3b Mon Sep 17 00:00:00 2001 From: Joe Previte Date: Fri, 5 Feb 2021 14:09:16 -0700 Subject: [PATCH 064/422] chore: update to 3.8.1 in Chart.yaml --- ci/helm-chart/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/helm-chart/Chart.yaml b/ci/helm-chart/Chart.yaml index 0862625cb..bb9f408ad 100644 --- a/ci/helm-chart/Chart.yaml +++ b/ci/helm-chart/Chart.yaml @@ -20,4 +20,4 @@ version: 1.0.3 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. -appVersion: 3.8.0 +appVersion: 3.8.1 From 55c916a987816b9ee68de9583eab45fb20c8135e Mon Sep 17 00:00:00 2001 From: Joe Previte Date: Fri, 5 Feb 2021 14:10:27 -0700 Subject: [PATCH 065/422] docs: update release doc with rg instructions --- ci/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/README.md b/ci/README.md index df8af9f6f..bd38e06d1 100644 --- a/ci/README.md +++ b/ci/README.md @@ -21,6 +21,7 @@ Make sure you have `$GITHUB_TOKEN` set and [hub](https://github.com/github/hub) - Remember to update the chart version as well on top of appVersion in `Chart.yaml`. - Run `rg -g '!yarn.lock' -g '!*.svg' '3\.7\.5'` to ensure all values have been changed. Replace the numbers as needed. + - You can install `rg` or `ripgrep` on macOS [here](https://formulae.brew.sh/formula/ripgrep). 4. Update the code coverage badge (see [here](#updating-code-coverage-in-readme) for instructions) 2. GitHub actions will generate the `npm-package`, `release-packages` and `release-images` artifacts. 1. You do not have to wait for these. From 07da291d72673032cf201491a84d81ed070520bb Mon Sep 17 00:00:00 2001 From: Joe Previte Date: Fri, 5 Feb 2021 14:11:34 -0700 Subject: [PATCH 066/422] chore: update v to 3.8.1 in values.yaml --- ci/helm-chart/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/helm-chart/values.yaml b/ci/helm-chart/values.yaml index 7a04321d0..b240264cc 100644 --- a/ci/helm-chart/values.yaml +++ b/ci/helm-chart/values.yaml @@ -6,7 +6,7 @@ replicaCount: 1 image: repository: codercom/code-server - tag: '3.8.0' + tag: '3.8.1' pullPolicy: Always imagePullSecrets: [] From 2a127f168ce751e58a790bd31fc8c723eaca433f Mon Sep 17 00:00:00 2001 From: Joe Previte Date: Fri, 5 Feb 2021 14:13:26 -0700 Subject: [PATCH 067/422] docs: update code coverage badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a0a5f36b3..e17965a1f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # code-server · [!["GitHub Discussions"](https://img.shields.io/badge/%20GitHub-%20Discussions-gray.svg?longCache=true&logo=github&colorB=purple)](https://github.com/cdr/code-server/discussions) [!["Join us on Slack"](https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen)](https://cdr.co/join-community) [![Twitter Follow](https://img.shields.io/twitter/follow/CoderHQ?label=%40CoderHQ&style=social)](https://twitter.com/coderhq) -![Lines](https://img.shields.io/badge/Coverage-46.71%25-green.svg) +![Lines](https://img.shields.io/badge/Coverage-40.7%25-green.svg) Run [VS Code](https://github.com/Microsoft/vscode) on any machine anywhere and access it in the browser. From 41ad0c0c4c14df78e1bfa3a44d8b633ed2fc26e5 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Fri, 5 Feb 2021 16:26:34 -0500 Subject: [PATCH 068/422] release-github-draft.sh: Remove incorrect assets reference I think at some point this script created the release and attached assets but that's not the case anymore. For some reason this would error with undefined variable reference for joe but bash doesn't complain for me or Asher. Not sure what the difference is. --- ci/build/release-github-draft.sh | 2 +- docs/install.md | 25 ++++++++++++------------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/ci/build/release-github-draft.sh b/ci/build/release-github-draft.sh index 1f33c34cc..835b7b431 100755 --- a/ci/build/release-github-draft.sh +++ b/ci/build/release-github-draft.sh @@ -10,7 +10,7 @@ main() { hub release create \ --file - \ -t "$(git rev-parse HEAD)" \ - --draft "${assets[@]}" "v$VERSION" << EOF + --draft "v$VERSION" << EOF v$VERSION VS Code v$(vscode_version) diff --git a/docs/install.md b/docs/install.md index 1538e0822..3a1f79092 100644 --- a/docs/install.md +++ b/docs/install.md @@ -2,19 +2,18 @@ # Install -- [Install](#install) - - [Upgrading](#upgrading) - - [install.sh](#installsh) - - [Flags](#flags) - - [Detection Reference](#detection-reference) - - [Debian, Ubuntu](#debian-ubuntu) - - [Fedora, CentOS, RHEL, SUSE](#fedora-centos-rhel-suse) - - [Arch Linux](#arch-linux) - - [yarn, npm](#yarn-npm) - - [macOS](#macos) - - [Standalone Releases](#standalone-releases) - - [Docker](#docker) - - [helm](#helm) +- [Upgrading](#upgrading) +- [install.sh](#installsh) + - [Flags](#flags) + - [Detection Reference](#detection-reference) +- [Debian, Ubuntu](#debian-ubuntu) +- [Fedora, CentOS, RHEL, SUSE](#fedora-centos-rhel-suse) +- [Arch Linux](#arch-linux) +- [yarn, npm](#yarn-npm) +- [macOS](#macos) +- [Standalone Releases](#standalone-releases) +- [Docker](#docker) +- [helm](#helm) From 164d11e0277c8c39171c0b5398461a67bdb3ac72 Mon Sep 17 00:00:00 2001 From: Joe Previte Date: Mon, 8 Feb 2021 16:20:43 -0700 Subject: [PATCH 069/422] chore: clean up comment in util.test --- test/util.test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/util.test.ts b/test/util.test.ts index 418756a58..78985554e 100644 --- a/test/util.test.ts +++ b/test/util.test.ts @@ -125,10 +125,6 @@ describe("util", () => { }) describe("getOptions", () => { - // Things to mock - // logger - // location - // document beforeEach(() => { const location: LocationLike = { pathname: "/healthz", From 719481e84ead17b8b55f5c673b11841f7b00950d Mon Sep 17 00:00:00 2001 From: Joe Previte Date: Mon, 8 Feb 2021 16:21:30 -0700 Subject: [PATCH 070/422] refactor: add getPackageJson fn in constants --- package.json | 5 +++-- src/node/constants.ts | 17 ++++++++++++----- yarn.lock | 5 +++++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 51479b901..5409ba4a9 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ }, "main": "out/node/entry.js", "devDependencies": { + "@schemastore/package": "^0.0.6", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/express": "^4.17.8", @@ -62,8 +63,8 @@ "stylelint": "^13.0.0", "stylelint-config-recommended": "^3.0.0", "ts-node": "^9.0.0", - "wtfnode": "^0.8.4", - "typescript": "^4.1.3" + "typescript": "^4.1.3", + "wtfnode": "^0.8.4" }, "resolutions": { "@types/node": "^12.12.7", diff --git a/src/node/constants.ts b/src/node/constants.ts index d6ba953ea..c39beb05e 100644 --- a/src/node/constants.ts +++ b/src/node/constants.ts @@ -1,13 +1,20 @@ import { logger } from "@coder/logger" +import { JSONSchemaForNPMPackageJsonFiles } from "@schemastore/package" import * as path from "path" -let pkg: { version?: string; commit?: string } = {} -try { - pkg = require("../../package.json") -} catch (error) { - logger.warn(error.message) +export function getPackageJson(relativePath: string): JSONSchemaForNPMPackageJsonFiles { + let pkg = {} + try { + pkg = require(relativePath) + } catch (error) { + logger.warn(error.message) + } + + return pkg } +const pkg = getPackageJson("../../package.json") + export const version = pkg.version || "development" export const commit = pkg.commit || "development" export const rootPath = path.resolve(__dirname, "../..") diff --git a/yarn.lock b/yarn.lock index cb51b9912..93173db81 100644 --- a/yarn.lock +++ b/yarn.lock @@ -980,6 +980,11 @@ "@parcel/utils" "^1.11.0" physical-cpu-count "^2.0.0" +"@schemastore/package@^0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@schemastore/package/-/package-0.0.6.tgz#9a76713da1c7551293b7e72e4f387f802bfd5d81" + integrity sha512-uNloNHoyHttSSdeuEkkSC+mdxJXMKlcUPOMb//qhQbIQijXg8x54VmAw3jm6GJZQ5DBtIqGBd66zEQCDCChQVA== + "@stylelint/postcss-css-in-js@^0.37.2": version "0.37.2" resolved "https://registry.yarnpkg.com/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz#7e5a84ad181f4234a2480803422a47b8749af3d2" From a2a6122252aea99bb8f2b0cbd7e3ef1247326db0 Mon Sep 17 00:00:00 2001 From: Joe Previte Date: Mon, 8 Feb 2021 16:21:37 -0700 Subject: [PATCH 071/422] feat: add tests for constants --- test/constants.test.ts | 58 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 test/constants.test.ts diff --git a/test/constants.test.ts b/test/constants.test.ts new file mode 100644 index 000000000..457f57fae --- /dev/null +++ b/test/constants.test.ts @@ -0,0 +1,58 @@ +// Note: we need to import logger from the root +// because this is the logger used in logError in ../src/common/util +import { logger } from "../node_modules/@coder/logger" +import { commit, getPackageJson, version } from "../src/node/constants" + +describe("constants", () => { + describe("getPackageJson", () => { + let spy: jest.SpyInstance + + beforeEach(() => { + spy = jest.spyOn(logger, "warn") + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + afterAll(() => { + jest.restoreAllMocks() + }) + + it("should log a warning if package.json not found", () => { + const expectedErrorMessage = "Cannot find module './package.json' from 'src/node/constants.ts'" + + getPackageJson("./package.json") + + expect(spy).toHaveBeenCalled() + expect(spy).toHaveBeenCalledWith(expectedErrorMessage) + }) + + it("should find the package.json", () => { + // the function calls require from src/node/constants + // so to get the root package.json we need to use ../../ + const packageJson = getPackageJson("../../package.json") + expect(Object.keys(packageJson).length).toBeGreaterThan(0) + expect(packageJson.name).toBe("code-server") + expect(packageJson.description).toBe("Run VS Code on a remote server.") + expect(packageJson.repository).toBe("https://github.com/cdr/code-server") + }) + }) + describe("version", () => { + it("should return the package.json version", () => { + // Source: https://gist.github.com/jhorsman/62eeea161a13b80e39f5249281e17c39#gistcomment-2896416 + const validSemVar = new RegExp("^(0|[1-9]d*).(0|[1-9]d*).(0|[1-9]d*)") + const isValidSemVar = validSemVar.test(version) + expect(version).not.toBe(null) + expect(isValidSemVar).toBe(true) + }) + }) + + describe("commit", () => { + it("should return 'development' if commit is undefined", () => { + // In development, the commit is not stored in our package.json + // But when we build code-server and release it, it is + expect(commit).toBe("development") + }) + }) +}) From e4a830e9b7ca039c7c70697786d29f5b6679d775 Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 9 Feb 2021 16:06:38 +0000 Subject: [PATCH 072/422] Squashed 'lib/vscode/' changes from e5a624b788..3e344b17b7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 3e344b17b7 Remove extrenuous file. (#113825) 36f9eaf1e7 Merge pull request #113596 from Ry0taK/release/1.52 e73a6b082c Create ryotak.txt ea3859d4ba Bump version number to 1.52.1 (#112659) 608dfc1363 Catch all localhost duplicates when forwarding a port (#112575) 8b5ece7c20 Merge pull request #112431 from microsoft/misolori/notebook-color-icons c11e9282d8 Merge pull request #112565 from microsoft/isidorn/enableBreakpointsFor 2b6973a35d debug: check for existence of enableBreakpointsFor.languageIds 374d05c17c Revert "debug: stop supporting enableBreakpointsFor" f93c8a0386 Fix notebook status bar icon colors (fixes #112323) 940b5f4bb5 chore: fix run-on values for snap build (#112245) dbbf7079b2 Make sure that tunnel information isn't set on desktop (#112228) 99edf4ff0f fix linux deb repo pointer (#112226) 91a50e3022 Set tunnel information for embedders (#112215) c6cb541539 Fix port forwarded nofication showing for the wrong port (#112160) 6caba06920 Merge pull request #112141 from microsoft/joh/fix/111913 cb971b8ffd Preserve whitespace in tree hover (#112133) e4af2d8ad6 disable proposed API checks on top-level getters 6f933020e8 Just set textContent for custom hover (#112075) d66e3740a4 Merge pull request #112054 from microsoft/connor4312/js-debug-1.52.2 b4ec131cff retry all cosmosdb ops (#112072) 5e585c5fb4 use proper repos for linux arm (#112026) 37c2de252e debug: bump js-debug bc13785d3d Retry createAsset sproc due to ECONNRESET (#112020) 559a78eaaa Mitigate #109728 (#112008) 62d6b82a6f Revert "build: create asset should still try to add asset" (#112009) 30e0c5784b fix #111898 (#111928) 532ba43c78 Remove console.log 39f33b588a wrap font faimly with quotes dc8ac78031 fix #111910. Avoid notebook save race condition. 97e4af4d20 Fixes #111899: Maintain compatibility for context keys like `vim.use` acbe8f273e Only render `vscode-remote://` extensions as remote 9254ec3b28 fixes #111577 7413cc2493 yarn gulp vscode 528ca4c9ea fix duplicate imports 8e5a1da05e Update doc comment for resolveTreeItem Related to #111715 478c7b633f Make welcome buttons look better in wide pane Fixes #111744 d298a8d5d6 Fix timing issue with environment tunnels Fixes microsoft/vscode-remote-release#4112 7fcdba7fea Add heuristic for when to notify about forwarded port Fixes microsoft/vscode-remote-release#4133 2e3236827e suppress refactor preview from onWill-handler, workaround for https://github.com/microsoft/vscode/issues/111873 de3dd1383c Active indent lines do not render in High Contrast theme. Fixes #111137 8f410da37f Revert "fixes #110353" 6ed64df492 repl: font family 0c947cb4ee fixes #96264 6a1ad5b9b8 :lipstick: code lens font fix 60bf040b98 workaround for https://github.com/microsoft/vscode/issues/111871, fixes https://github.com/microsoft/vscode/issues/111666 5e350b1b79 build: create asset should still try to add asset 7256c3ab26 :lipstick: code lens font 6a58335d02 explorerViewer drop await c2f68baa78 search: fix handle empty results from ripgrep 63137db51c debug: fix auto attach not turning on if reselecting current state c70d984fa2 debug: bump js-debug ff66544ada Allow using upper case hex characters for color ff824f2776 Bump actions b617b725a0 update notebook execute kb shortcut tooltip for windows. 1aa26c3775 Fixes #105808 9d5c351c71 notebook list view scrollbar should be below cell toolbar 53eac52308 Fixes #111499 1c1c59fd0f Kill code processes after each integration test suite run (#111844) 39fb92c6c5 Remove redundant when clauses 62b119aa22 Fixes bad has implementation 370e092880 extJupyter 41eb987d09 add log for save/saveas 7b1154d447 update resolve time stats even if they are the same. 7ecce71a48 Fixes #110376 450ae69a33 Fix #111835 2964a69479 Fix #111798: Getting Started: capitalized "Editor" looks weird 061e84830d fixes #110775 a82ae0c9b7 Fix #108266 564ff91763 fixes #111832 95f6cfa0ae More 💅: adjust overall padding & sizing (refs #111753) ae93d48902 fixes #111583 15f38fe338 Fix ports view order in remote explorer Fixes #111830 a63786db23 Add paren, bracket, and brace to autoclosing pairs for make Fixes #89191 3c4bbf6da2 Fix ports view listener leak Same kind of cause as https://github.com/microsoft/vscode/issues/107903 Fixes #111772 6d222ba785 Fixes #97196: Clear out semantic tokens when there are no more providers 0447415697 Hide the editor's cursor when doing composition (fixes #106663) 79fb30f5b6 Fix resolveTreeItem called twice Fixes #111749 07333dec48 node-debug@1.44.15 87e43299ad update DAP to 1.43 09a7ad2ae7 fix raw timers telemetry event 4095701c70 Exclude endgame-plan issues from query 0ea221b325 fix query, remove commas 4f33022639 add verification query for issues from non-vscode team members c0ad6a56c0 Slightly darken list active bg on light theme (fixes #111750) 92192baf76 Bring content closer to center vertical when space permitting Ref #111753 f603b548c5 Polish getting started styles (#111774) af63e8263a Fix #111706: Getting started: images need alt text 2012817c0d Fix #111699: workbench.startupEditor setting not showing Getting Started tab 5215161c88 fixes #108712 5b121903fc Fix issue preventing cells from leaving "running" state Fix #110973 253844006a fix #111735. 1fc36c35bb remove extrenouus log fixes #111759 b122603b86 Fix comment decoration weirdness, fixes https://github.com/microsoft/vscode-pull-request-github/issues/2309 9d9ae54aa8 Add jpg to vscodeResources in order to bring gettingStarted images into the bundle 44dafcc840 Allow an `EncodedTokensProvider` to also provide a tokenize method a9100d686c Update verification notebook milestone 4884986dd6 tweak smart select test a2193cb827 fix #111714 efd8b0141e explorer: update creation labels acaecbdc86 fixes #111602 0db0fa8fc9 Prompt when hitting a different UndoRedoSrouce when undoing (#111640) 2b179bff4a Extract `_undo`, `_redo` 0ac5e95251 Fix #109644 df2c328e59 string literal does not work with status bar item [background] color (fix #111687) d758a74bf7 Merge pull request #110902 from Wscats/patch-5 d79110a329 Avoid layer breaker bbdbcd333b Update contextkey.ts 9cd45f96de fixes #111710 383dc83e7f Fix the standalone editor build 97646e138a workbench.tree.expandMode 49ef641300 fixes #111639 af29768f18 Fix #110844 e16d1f06d7 Add default URI if not provided in dialog API Fixes #111585 f2ae4927f9 update distro 980ef5cb52 Merge pull request #110846 from Wscats/patch-3 94dd681d16 more fixes for #95697 16ffa1deff Add GreaterEquals and SmallerEquals and fix implementations 65c70884c1 Fix #106989 763b155fe9 Fix #111574 51b1029e98 Check detected tunnels before auto forwarding Fixes microsoft/vscode-remote-release#4112 87538e9cea Merge remote-tracking branch 'origin/master' into pr/Wscats/110846 2ba6946272 fixes #111581 8d171e6492 Improve multiple lock files notification Fixes #111589 0be03ecc6d fixes #111693 5d591624ae add install in browser action 16e91f1011 fix https://github.com/microsoft/vscode/issues/111741 f622d7b6f1 fixes #111657 9a9c6b5557 Extra character in icon hover. Fixes #111681 b9b92e3152 Fix tree item not getting resolved after data change Fixes #111711 f4187f2202 fixes #111732 #111733 806eaddcbe Fix resolveTreeItem getting called too early Fixes #111613 0587f828d6 fixes #111617 399be0e564 fixes #111671 3730238647 fixes #111601 e024fd8b96 add cancel-option to continue bisect model dialog, https://github.com/microsoft/vscode/issues/111667 914b5633f5 fixes #111593 2388291cbc start extension bisect with all extensions disabled and stop early if problem reproduces, https://github.com/microsoft/vscode/issues/111672 a1e5a1a593 Small typo in notebook mime type icon description. Fixes #111674 6d4532c484 align continue bisect messages, https://github.com/microsoft/vscode/issues/111667 1cad5ab365 Prepend `vs/nls` to `workerMain.js` (fixes #111599) 00bbeb3973 Fixes #111649: Have the diff editor control `wordWrapOverride1` and the toggle word wrap action `wordWrapOverride2` such that they can function independently 8041440e5b linux: disable integration tests af848910f7 Merge pull request #111661 from dataleaky/master c44732014d windows - prefer to focus window where files open in 14bf080705 Keep Editors Open menu choice in the editor '...' menu should be a checked option (fix #111668) b2ef118857 window.restoreWindows: preserve description polish (fix #111607) c76a42acc6 Bad rendering of status bar item with error background color (fix #111618) 1e0094545c Merge branch 'master' into master d9ea0ced98 build: fix config 60f5e6ee9a build: disable exploration sync 647cecb29d build: exclude electron-11.x.y branch from continuous build 5e54da666c fix #111675 and #111673 9dba02c075 re #109594. b2c2c20fe7 fix #109626. 8fa61d9652 Fix a dependency URL a0aeb3a559 Update package.json 4d9277a23a Optimize autosave message when changing settings. Fixes #111648 c96643feab Fix #111598 0e8f9d143b Merge pull request #111541 from mdesrosiers/handle-find-widget-history-delayer-promise-rejection f89bb0d54a Tweak verification needed query c7e849f9be fix https://github.com/microsoft/vscode/issues/111604 af5c09a10c Remove console.log from hoverWidget Fixes #111609 3689660fcc Add descriptions for npm.packageManager options Fixes #111628 72cce5194c fixes #110511 f67050c029 Missing jsdoc in LinkedEditingRanges. Fixes #111590 e0f804f483 fixes #111496 55489fdd47 Move ports veiw into remote explorer by default 4ca1a30f5d Update distro commit 9395c9403b ignore already verified items d531b6dfe9 proxy - various login dialog fixes c5ffc57ee6 Get ready for ports view experiment 20bf09bc66 [linked editing] finalize LinkedEditingRangeProvider. Fixes #109923 ec2bcdbb57 #111573 also check for setting value type 6e7d1fa680 Fix #111573 bf21395291 column options in openWith-command can be undefined, fyi @mjbvz 3472159cb1 Merge pull request #111408 from microsoft/aeschli/notebookIcons 7536644522 Fixes #111569 648ed9f9d1 unblock build f019356d4d Revert "Make users save new file (#110330)" e3105b9453 Add jpg exclude to hygene bd627caa54 Add actual images for content e428ce2d26 Dont let product icon overflow 5a3c6cb697 Fix offcenter footer 35e9278e50 Fix scrolling on getting staarted 562f909902 Revert "fix: c++ exception in keytar module" b0fba33756 Styling tweaks to getting started 8baa2cdaaf Better shape images in getting started d95abcca66 Prevent error when reopening markdown preview. (#107205) (#111449) 1c4cc602c6 Fix merge conflict 8b59a9ce1a Focus on the content window for iframe based webviews b854c0bffd Make vscode.openWith a proper api command (#111006) 6062ebe318 Bump highlight.js in /extensions/markdown-language-features (#111262) 9e5f14c1bc Default strict null checks to off for implicit projects 304d3d7e36 Handle rejected promise in history delayer 18ecb87939 fix html yarn.lock b03cdca765 icon doc 15b05ff05a fix #108788. 68eaa95360 Bumps version of github-browser c358910b01 Fixes #110880 - sets useConfigOnly=true on commit 634fb13135 [css][html] update dependencies 8448b1680c [html] update dependencies c941749f5b Enable persistance of getting started ui state dceba9ebb7 [css][html] Option to disable MDN Reference popups only (without effecting other functionalities). For #97979 c6e90c5ea3 Update 'statusBarItem.errorBackground' default color token (refs #110214) ec13471816 [json] Inconsistency between files.insertFinalNewline and Format Document. Fixes #17359 d132d93aeb Register `DeleteInsideWord` as an editor action 0b97b6b160 [json] update dependencies 025952c4d5 Rename `editor.atomicSoftTabs` to `editor.stickyTabStops` bb33ef06bf Fix #99530 de36470f39 Fixes #110897: Always disable word wrapping in left hand side editor of inline diff 833231811f Define a label for `deleteInsideWord` 3fb2f6f976 chore: bump distro 47ce2a8d6d #111845 fix localization tests 155691d0be chore: bump electron@9.3.5 (#111530) 5c71b5425a Fix #111357 b548ac2211 more build fixes 6dfdbe24d9 update distro 8cb4a6907b support passing log level using query param ac165d7f97 bump cache salt 8c2a384efa :lipstick: format file 858817eb3c Update description of `remote.autoForwardPorts` e7f19cf61a don't validate MessagePort inside iframe as safari seems to have trouble with that bf24f189f1 Clean up defaultFolderPath 9689508fa5 Run OSS Tool and make necessary updates 2afcea867a Use argparse 1.0.9 in npm extension 648090440f caching is HARD aa36c6211b move duplicated code into central place 2c5901053a spelling error in command name (fix #111455) f5fb75d829 fix tests 0f1492d6da chore - remove unneccessary non null assertions 8ce7356104 Fix defaultFolderPath so that userhome is correct 2890fd321f Fix #111485 a5a37240bc fixes #108001 eee332ba85 fix misuse of VSCODE_ARCH in builds 2018df47d8 support font features, like ligatures, in code lens, https://github.com/microsoft/vscode/issues/16038 b7211aa875 fixes #107651 9172ecfad8 :up: distro a4054cda6d update references viewlet, also no more proposed API usage required cedc2850ec Exclude testplan-item label f47aae014c Merge pull request #111441 from microsoft/ben/native-tests 673c1adcb0 fix layer breaker 1efcfbf242 api - fix exception in status bar ef2a900dec sandbox - add SH1 to HEX utility using crypto.subtle with fallback to our own solution fbf7566d39 tests - add test suite for native modules 7723f2548c Fix #109406 8ee75c19bb Escape more white space in appendText Fixes #110464 c5ab321020 fix https://github.com/microsoft/vscode/issues/110554 444f79cc3a Improve quiet light diff syntax highlighting. For #107926 07224f0681 update to latest DAP e24ea5ef61 Merge pull request #107926 from alisonnoyes/master 1a6d7f3118 [json] performance warning message can't be ignored. Fixes #105988 e6d6661247 fixes #111410 cfa02997d2 fixes #111413 e2dd774fa4 fix yarn retry logic 627ad0b4ee rename OnTypeRename -> LinkedEditing (for #109923) f13720627f Improve task dependency cycle check Fixes #111369 cd9be282b7 Add condition editing UI to breakpoint filters 6795d766a0 remove old logging API proposal, fixes https://github.com/microsoft/vscode/issues/85992 79b6d35bd6 remove TreeItem2 01bbb81052 Finalize markdown tree tooltip and resolveTreeItem Fixes #100741 016655c546 fixes #104629 8891878a3b bump cache 210a1a1327 Merge pull request #111405 from microsoft/joao/trusted-extension-uris 3495d2fd76 :lipstick: 6391d710cf adopt latest loader, pass trusted types policy to loader, adopt in loader-usages 483e81f3b8 Fix duplicate port forwards for ipv4 vs ipv6 Fixes https://github.com/microsoft/vscode/issues/111400 f8d1c07e16 use createChannelReceiver, createChannelSender 995983da54 github: get all branches c3414f3cf3 Always show the open view option in the open in browser command cae277a203 Add a command for open port in browser Part of https://github.com/microsoft/vscode/issues/111402 2fb5b5b696 fixes #44542 0321ca5d96 fixes #106664 6c8f62432f Merge branch 'master' into aeschli/notebookIcons 8012c255e5 fix keybinding smoke test after icon name changes 4e45d27a72 fix stopIcon name 35a8955dc1 Merge branch 'master' into aeschli/notebookIcons 4ae452929e Merge pull request #111383 from microsoft/aeschli/extensionsIcons 3a92391c20 Merge pull request #111377 from microsoft/aeschli/preferencesIcons fddc9bb221 success/error icons f6249641f2 externalize notebook items bafc05ff0a fix retry 71836f95dd fixes #111366 6253e47773 #44542 ability to reveal setting 42bb62a06f Exclude endgame-plan label 1e0af43774 trusted ext urls: use 1 hour instead of 10 mins c471973938 Merge branch 'master' into aeschli/preferencesIcons 44ddde116f adopt checkProposedApiEnabled 14d59bef92 Allow status items with error background color (fix #110214) (#111353) 14415847e1 trusted extension urls 2cd7a70d62 original urls get passed along with openUrl ac9e250cce support node 15 cfe2e99ad8 debug: load all stack frames text shuold have initial color when selected so it is readable 4cb5bb656a Merge branch 'master' into aeschli/preferencesIcons 8e6517069a Merge branch 'master' into aeschli/extensionsIcons 98f80c4047 Fix #110698 8784b77bfd fix #110699 1e77bd81d8 registerCodicon 8234453386 update tests 83f8b96a94 add extension icons 43f5beb307 fix terminal icons 09f281dff6 dirty dff icons 2f0552737c terminal icons 5c2127ec74 Fix #110964 f70430eae4 download to temp location first and rename 26a0c24c6c fixes #111218 818174d4e8 bulk edit service: Show infinte progress when there is only 1 item since we do not know how long it takes 4ac4cf3dff some more cleanup, enfore proposed API checks, fyi @RMacfarlane c84fa206d4 add preferences icons c219b0673c :lipstick: discover and mark more proposed types 44af5d2af2 marker view icons f0bb23ca02 more icon work 58a90729c4 externalize activitybar icons 1e6e371d3b :liptsick: sort API types and namespaces, enforce proposed API for types a94217739a Don't auto forward ports in WSL Fixes #111375 a5bda3ee95 support later node for building f81e91d1c9 Merge pull request #111363 from microsoft/joh/playwright cb8259c7b7 Fix forwarded ports restore race e5111fc439 Only show "Not Forwarded" when auto forward is disabled Part of microsoft/vscode-remote-release#4021 58fe1b9dfa Add badge back to ports view Part of microsoft/vscode-remote-release#4021 3018240290 add user data init markers 047063458b Merge branch 'master' into joh/playwright b2b5647451 disable two comparer test that fail in latest safari, https://github.com/microsoft/vscode/issues/111368 fyi @bpasero 6a974d536b Change port language to "forwarded" and add icon Part of microsoft/vscode-remote-release#4021 b269cd9464 Add open pull request query 2d4ec09c1d add yarn lock file 9ee2a1ee1e :up: update playwright 3c3ed7fb58 Fix #111351 b01183bda2 Add extension source to forwarded ports Part of microsoft/vscode-remote-release#4021 2f8f00d622 add dropDownButton-icon description 1aba2b87c6 fix unnecessary import 8648658c44 use codicon for openEditorWith picker a6a53198f6 make viewPaneContainer twisties themable 32a18e3f5b add Codicon.dropDownButton 20b649729e button: use CSSIcon 75c71b49cc Fix #107152 6692bf17e8 find completion item color in detail and at the start/end of documentation, fixes https://github.com/microsoft/vscode/issues/109794 d9c33ab941 Fix port nofication cooldown c13542b7ca Remove heading for forwarded ports Part of microsoft/vscode-remote-release#4021 ebaf0a2ef0 read line preview when having symbol results, https://github.com/microsoft/vscode/issues/109523 dcf4cd2a50 Improve tunnelFactory doc and remove an escaping undefined 8ba0fd356e IEnvironmentVariableInfo: Use ThemeIcon b4912314fc move file decoration API to stable, https://github.com/microsoft/vscode/issues/54938 38a200c746 :lipstick: c6be304059 Ignore flush errors during dispose since the underlying socket might be already closed 2d2a9e7527 Prevent unwanted concatenation of "null" to feedback URL (#111325) 4031280b29 Go to symbol in editor, open to side should reset scroll position in original editor (fix #111346) 5e6a2779f6 add try/catch for #111177 d49955575e :lipstick: relative pattern a5e20f89e4 :lipstick: imports 392b6a94e0 perf - remove redundant performance usages d547170675 sandbox - move some changes from PR over to master 0a80aacc7b chore: update cache 764620efae fix: c++ exception in keytar module 5f569de4cd more removal of non-null 0921f711c3 Add GettingStartedService/Registry and initial getting started UI (#111175) ef03adf3a0 notebook editor widget always creates list in ctor. 3f0ada11fd core action active editor context should always have a view model. f24f7be7f0 less non-null operator in notebookEditorWidget 76882e4eb4 avoid memory leak of large text buffer from notebook cell text model. 2c20676f5c update distro (and hopefully unstuck builds) 2ecb47d4ec Use ThemeIcon & IconRegistry for custom icons c59ddc6a1f update distro cad45e538a Add `ProtocolMessageType.ReplayRequest` such that missing messages can be requested again 6edf58b9ae Avoid layer breaker (using `process` from `/browser/`) 3cd515d33e support installing web preferred extensions on web server af52f2cb8c `TextModel` should not dispose its `TextBuffer` 8dce3899da avoid unnecessary deepclone 5b243ab54b don't leak env vars into process.env; fixes #83187 b51a171a75 Update codicons https://github.com/microsoft/vscode-codicons/commit/cbe2a17f2965d0f3ff363830fee4ebae5fee7c4d e2c9d1a7a3 Avoid using `innerHTML` (#108400) d7bdbe118e Allow running `yarn tsec-compile-check` also on Windows 84cf12f40b Add trusted types policies where `.innerHTML` needs to be used (#108400) 89c255a523 generate icon-css from theme.comon.IconRegistry 71462d03fe Fixes #111309 0ab259ff27 remove unsued code, fyi @roblourens, https://github.com/microsoft/vscode/issues/106741 37e9cceddc Enable sandbox, contextIsolation and vscode-file for process explorer and issue reporter (#111304) 545332f793 explorer: polish progress e287b8c9d3 explorer: hook in cancellation bef58ff84a forgot! 5cb2ee7b8b confirmed extension -> user trusted extension cab737e707 Make `extensionKind: ['ui', 'web', 'workspace']` pick the web worker extension host if possible b909e20513 progress: Increment by percentage points since progress API expects that 5a0ab56492 use native performance.mark when avialable e3e2837476 Bring Running Extensions to the web aa91d04552 add cancellation token to working copy service and hook it up with file operation participants a500715422 fix terrapin order a59aa579b6 fix debug toolbar colors 37274abda9 update distro 043c17ef7d update azure cosmos :shrug: d387fa978a Extract `AbstractRuntimeExtensionsEditor` to separate file 97f01b33e9 Move profiling related state down from `AbstractRuntimeExtensionsEditor` ba867135c5 Extract `AbstractRuntimeExtensionsEditor` 97a896091d remove startupPerf experiement, https://github.com/microsoft/vscode-internalbacklog/issues/1620 a8b6afefab process explorer - prevent process loops d189ecf936 issues - check for sender being destroyed before reply cda7b564e8 fixes #111191 182fe687b7 #111291 fix tests d63fee4308 Fix #111291 2864ebf134 Do not include iteration plans into the open issues ad8b6baef6 Extract `SaveExtensionHostProfileAction` to a separate file 6dc1a9540c "undo delete" operation recreates file contents when intital create had contents, fixes https://github.com/microsoft/vscode/issues/111162 72f4a2a7de Fixed $REPOS macro 897d2ec9a4 Merge remote-tracking branch 'origin/master' 3de0ea500a Merge branch 'joao/snap-arm' a15493cf90 Extract `DebugExtensionHostAction` to its own file 3ceea17846 npm: use project dir when using npm 84ce131781 Emoji characters in col 40 of an empty file causes crash (fix #111235) bb60e21a36 fix esrp for arm64 799e72cc2f Add tunnel creation options to web api 221a5570b6 Fixing how escape key works in listWidget (#110760) 7bbf45c14e env - only show one notifications not two (fix #111246) ce6f5e0f8f Make ports view flat when there're only forwarded ports Part of microsoft/vscode-remote-release#4021 6e631518ab Extract `ReportExtensionIssueAction` to a separate file fb54cd1cd7 Move inline actions when ports view is in the panel Part of microsoft/vscode-remote-release#4021 9abd43315c Manually release reference to piece tree when disposing (#107999) 48f4109f3d unify win32 build templates e6d2bd326d fix darwin build 97a7e63de8 also check cancellation token in outer perform function, https://github.com/microsoft/vscode/issues/111281 7b1da3a3d9 add IBulkEditOptions#token, https://github.com/microsoft/vscode/issues/111281 927290270a build: - ENABLE_TERRAPIN env - .build/terrapin cache flag - fix alpine cache - enable web cache 96b2c670d7 debt - extensions path is always defined ffec932655 add timeout to terrapin step 236298851a :lipstick: use linked list instead of array-remove gymnastics, fyi @bpasero 2ea9ecd6da increate file participant default time to 1 minute, show notification progress which allows for cancellation, https://github.com/microsoft/vscode/issues/111208 00eea33b23 More cleanup for armhfp and aarch64 rpm hardcoded dependencies (#111253) 41ef01de56 Fix #111212 1460908e18 bootstrap - move portable mode into bootstrap-node 2433e29383 sandbox - use IPC communication to read bundles ec5da2bdc1 build: rebuild keytar for arm64 c6e908997b jsdoc :lipstick: for relative pattern f37ca74411 Merge pull request #111269 from dsanders11/patch-1 614a865d1d Fix error message 3898e2ced3 Update extension install button to match default button style 8c9dfcbcb4 fix: appName in upload-vscode-configuration task 093acbdcbf timeline icons: use codicon registry for now 5a04894296 update distro 357fc9d16b Use codicons instead of string literals 0728b59411 fix: upload-vscode-configuration task for macOS f645a8d8c8 Link to ThemeIcon id reference 6a41811e10 Merge pull request #111183 from microsoft/aeschli/themableDebugIcons 3cfc3ac372 remove duplicate 'codicon' class (for #111240) 668b1d2439 explorer file operations: show progress 24e9b29b39 wording for output show more. fd15b93261 remove layer breaker 40a81495f2 fix: removed plugin helper entitlement 07bd0e3c35 :lipstick: 23e7e2fef0 showNotebookDocument. 160baa3b8d :lipstick: f9aa23fb6c relative pattern - update JSDoc comment 2e8bbdb41e api - test relative pattern with URI 2be5cc1845 #107999 Use disposable store and add onWillDispose listener 562d9451bc Fix #109114 66cb21b0b1 Fix #107999 5987e40765 Add vscode.Uri to types for vscode.RelativePattern's base parameter (#111155) c37ffd83ba fixes #111240 bd40e2555b remove TS on-type-rename feature, https://github.com/microsoft/vscode/issues/109923, fyi @mjbvz a5344096a2 raceTimeout test failed (fix #111233) 950c942b7c TunnelCreationInformation -> TunnelCreationOptions Fixes #110795 c0ecf176e7 Move ports view into the panel Part of microsoft/vscode-remote-release#4021 17e9e4ca1a Merge branch 'joao/retry-yarn' c4b7d10912 Use property bag for tunnel creation elevation Fixes #110795 e052c8e045 move down common icons 197f1341e6 explorerService: consolidate bulkFileService apply in one location 2a8ee68eaa #110721 pass action runner afce5c425d Add simple check for task dependency cycles Fixes #111186 cf4f2fa6c8 #11123 more fixes ac21b369b3 Merge branch 'master' into aeschli/themableDebugIcons c2d3d7f857 bump distro 8df4bc17da Merge .nativeignore and .moduleignore. Fixes #111220 fed34f982e startup util fixes 95e32fc67e update cache salt ff0785571f explorer: limit undo file content to 5mb a4e4e7ede3 Merge pull request #111013 from microsoft/isidor/doNotUseTrashFlag b5861b0330 update the monaco.d.ts 4563ba1f94 add start function to explicitly start performance observer b7851bfc80 :chore: bump distro 050963b8d1 missing terrapin usage ad3adfa28e retry yarn install 900e4474e9 missing terrapin usage 63efefb897 Merge pull request #111182 from microsoft/sandy081/notifications/dropdown 1ff7e57724 Merge branch 'master' into sandy081/notifications/dropdown 875ad7bdd7 Fix #111168 7e4f90277d run prettier over yml files 9a657db088 add tiny util to allow performance mark collection (via perf_hooks) independent of amd or commonjs usage 384d7b5e9e implement review comments a327c55c85 Merge branch 'master' into aeschli/themableDebugIcons 4ddf7bc0a7 Fix null characters showing in forwarded ports d970e263c7 more fixes for #111221 08d9b15245 fix dropdown position a486099d30 fixes #111221 723ee37c02 bootstrap - expose preload process to base lib d2365da65c rename doNotUseTrash to skipTrashBin 4d58b56772 Merge branch 'master' into isidor/doNotUseTrashFlag 0ec40638a8 Merge pull request #111042 from microsoft/isidorn/bulkFileEditsMaxSize 41025928ad bootstrap - fix more compile errors ae64039e43 Move `runtimeExtensionsInput` to `/common/` de85ef3a20 Fix hardcoded rpm dependencies for armv7hl and aarch64 (#111198) e8ba7e51ee bootstrap - fix compile errors 6038823879 add doc 2efde187ac HTML Semantic Tokens test failures. Fixes #111214 44281ba1d5 do not support secondary option on menu ff281d39f3 fix - pass the action runner 155b218dcf web - home indicator :lipstick: 2046205637 debt - fix layer break with dependency to v8-inspect-profiler 4aae851063 Update Codicons: Add 'circle-large-outline' and 'circle-large-filled' https://github.com/microsoft/vscode-codicons/commit/7ddcbfb5f46e97691028f19b475d9183f43ac245 abf3964921 Updating Codicons: Add 'pass-filled' https://github.com/microsoft/vscode-codicons/ c3e1f0e02a re #102503. allow open notebook on the side. 9ce365f085 chore: bump distro 0b7158b190 :lipstick: e66547a1d9 ensure the editor has enough top padding when there is decoration ::after: { top } 0baf6bb9cc chore: update to electron 11 (#110759) 10d92e5efa Prompt user with info about terminal config, add settings search by @id, and add sendToShell setting (#110987) 6f87c1cf7f Removed "duplicate" label 8e76838da5 Tweaked endgame notebook, added my endgame notebook 2a1c8bbaf2 re #108464. 1732cb9540 DAP: add support for conditional exceptions 4c55c7264f Revert "Fix #58600, Format Emmet package.json (#110891)" 40a902c9cf Multiline markdownEnumDescription should not break split json settings editor Fix #110840 a7b1267006 both custom and native hover showing (#111178) 542de8a8d7 Fix bug where tasks.json tasks aren't resolved eecbbcd3e3 fixes #110720 34b0cedeb7 fixes #108073 ef5a912876 fixes #109097 0223a22d87 externalize debug icons 535943628c revert a2ca2bbb1c revert onCancel 5e82d27fa7 support menu with prompt choices a85b3391ef Consider to remove migrateFromOldCachedViewContainersValue (fix #109726) 6c415c2486 Merge branch 'joao/terrapin' ea989da383 Feedback from resolveTreeItem API proposal review Part of #100741 21a42246d3 fix compile 4e1eff7083 web - turn the home indicator into a menu f19b19018c Automatic port forwarding polish Part of microsoft/vscode-remote-release#4021 e9fc5e147d Fixes microsoft/monaco-editor#2192 9a07d50f52 Delegate to the command service as a fallback (microsoft/monaco-editor#2195) 951509368c Add terminal hack for task line data back in Fixes #105159 93534b15da fixes #104793 12983c8175 Merge remote-tracking branch 'origin/master' 93f8adf263 :lipstick: 07d680b7c8 semantic highligting: deprecate member, add method. Fixes microsoft/language-server-protocol#1087 e81eb57671 Merge branch 'master' into pr/107705 ee6e905a8a cache OpenURLOptions in ExtensionUrlBootstrapHandler (#110725) eaf5eaa29f Allow name to be updated on extension forwarded ports Fixes microsoft/vscode-remote-release#4028 4b6b2bc6c2 update cache salt 3a37613eb5 Automatically forward candidate ports Part of microsoft/vscode-remote-release#4021 ab6878688f fix terrapin usage d632381e71 Add running process info to tunnels Part of microsoft/vscode-remote-release#4021 1436b523d4 [themes] Generate Color Theme From Current Settings doesn't have all colors. Fixes #111147 0a78fe165a Merge pull request #111110 from chenjigeng/master 6026ab576d fix #109104, #105884. 986e1248f6 bump distro 4ae26a1563 Update milestone 52098eaeb0 env - tweak shell resolve experience on startup eae037b391 Fixed Hover style incorrect when writing javascript in HTML fb80c0e44a Merge pull request #107126 from Timmmm/atomic_tabs 88856f1a1c Simplify changes from #108193 a194746aa3 auto attach: allow temporarily disabling 45ec698b12 Avoid walking whitespace twice when going left & small stylistic changes f1cfe2d3a6 Update CodeActionOnSaveParticipant (#108193) 2b75c3d5ac updated javascript for in snippet (#111062) 9195c9ab14 Expose TS server tracing (#110534) 06be9f4029 testing: tests and speed improvements 3880463037 Temporarily only warn on duplicate scoped context 93e7dda5a1 Error if a new ScopedContextKeyService conflicts with an existing ScopedContextKeyService (#110363) c9d89dd5ce `editor.atomicSoftTabs` can be a simple editor option 25e8ca8e18 Merge remote-tracking branch 'origin/master' into pr/Timmmm/107126 b2bd3540b0 Merge pull request #108448 from xndcn/patch-1 7265dfb6d7 testing: smarter change event 87b8061711 Merge pull request #110917 from dsanders11/patch-2 0ffe32409d Don't show complete command arguments in the native tooltips 786eca5cd6 Merge branch 'master' into patch-2 a0b7c2310f support dropdown actions in notifications cbeaf4fc55 Merge pull request #108379 from KapitanOczywisty/patch-2 b2b3b015d5 Sort line decorations 796a38f930 Merge remote-tracking branch 'origin/master' into pr/KapitanOczywisty/108379 f25f1abfaf give the option completely to notebook extension for now. d5a74a912b add trusted status bar item. 6b19da7102 update execution button when trusted notebook metadata changes. 1b3b40265b trusted notebook metadata 4d4f3660a0 opacity for notebook cell status 33c058d042 fix mouse click on text status item d23c882470 use status bar item for execute cell placeholder fc98345ba1 Extract output container renderer efea77ab7c move cell output element to its own module da00706deb cellDnd 57ca27e5f5 merge cell action view 8cf97fefd9 trust notebook prep 4b5dc2fcbc WorkspaceFileEditOptions add maxSize d1280418d7 testing: initial api implementation ff1887be3e Preserve per-window environment variables between workspace changes (fix #108571) 7d1cd1ff33 Fixes #99313 690d0e33c1 window - cleanup reload() method 1568c0ca45 :up: distro fb277edc0f env - take window configuration into account when resolving shellEnv 542a82754c yarn watch: save errors in separate files, make error regex multiline aware 1273299f3d :lipstick: bootstrap-window 6f3fcd2ce0 env - move shell env into bootstrap-window and let user env win over shell env 69de6464ce explorer: pass the folder flag when deleting directories fe65b26426 perf - add ellapsedWaitForShellEnv to measure blocked time on startup 5f8aa18104 Merge commit 'a36c68b9ec3d6a0aca9799d7a10be741a6658a51' into joao/terrapin bffd7a6ad8 Fix #111014 185212db53 Revisit explicit use of ConfigurationTarget.USER when updating config (#109373) 7207ee201f env - also surface shell env resolve errors to users 8a6fea66c0 Fixes #107636 c8e59100c4 Validate line numbers bb7b7dc7f2 Fix doc comment for CustomExecution api Fixes #110981 d84b5eb2da bulkFileEdits: do not use file trash flag 936048e028 :up: distro a81ea8c4fa fix tests 9103955a6d [themes] wait applying settings until user data initialization is completed. Fixes #111009 6fdc79d81a env - set timeout back to 10s but show warning when it takes long 6541e52b5b icons: remote polish c89f783319 Fix installing forever bug in server acfd724aac Pick up latest TS 4.2 for building VS Code caf01baa45 Copy createSettingMatchRegExp locally to fix cycle b4c0cfb513 Better note current version in TS version UI 67b0e2d72d Pick up TS 4.1 final release ea4d99921c update distro 1dcbd67f00 Load `applicationinsights` lazily 5d75857c59 Update marked cgmanifest entry 59428623f9 Cancel encodedSemanticClassifications-full on resource change c833899a84 adds @features: settings search type and dropdown option to Configure Terminal Settings (#110874) ffacef4727 Fix #58600, Format Emmet package.json (#110891) 8b720d6740 chore: bump keytar@7.2.0 (#110977) f0ed8aa2e9 named codicons for views (for #92791) 5ebe7eb5f6 fix #110750. 195779a054 Fix #110870 2fb9c3d5ce Fix #110702 692e61eda1 Fix #110702 145c6e5beb #110905 also optimise for builtin extensions arg 3946a0a314 fix another variation of the "palette" typo... d72f3fba3b Move LoaderStats down to `/base/` 508ef0928c Better loader configuration for nodejs processes d99a9ade8a Update to latest loader a245552aa9 update distro 5876a5e4ae Merge pull request #110930 from microsoft/joh/undoOpts 83246704d4 update distro b0d577c1b7 update doc 22895e9856 Introduce a server method to check if extensions are ready on server 835ce347fe bump distro 76e1b21f4f fixes #110800 624aca5bf5 Merge pull request #110855 from microsoft/joh/pureoutput 56c3caa680 fix https://github.com/microsoft/vscode/issues/110666 4a1cea9306 fix suggest explain mode for complex labels 3665005621 add undo options (undo group id, isUndoing) to working copy service and bulk edit logic 37f44b2e30 Merge pull request #109511 from chanmaoooo/deleteWordEntire 8374d6a4c7 Detect ports to forward more often Part of microsoft/vscode-remote-release#4021 cf3194d184 Add tests and improve the implementation 71fce013c9 Weird on-type-rename-decoration. Fixes #110878 06bf8e6539 editors - update readonly options when file providers change (fix #110854) 2f05337db0 Merge commit 'a36c68b9ec3d6a0aca9799d7a10be741a6658a51' into joao/snap-arm 990ca74d22 Docs for FileSystemWatcher limitations (#110858) 23a826ff3c more jsdoc for #54938 eb6356cfd7 debt - properly implement interface 47eb468acb sandbox - fix process in sandboxed environment (allow to run on Windows) d04f3178dc proper name for register function: registerFileDecorationProvider, #54938 710c859af0 Use label for "Follow link" command's tooltip a36c68b9ec Fix #110905 c11ef72380 Merge branch 'master' into patch-3 a89864b295 fix tests 268091616c Merge branch 'master' into patch-3 b1bc453c55 Fix notebook focus treatment in HC a5c190e1ad fix #110894 7077e069cf :lipstick: 1d42a1c1c3 debounce editor blur focus state update. 113e0428f6 Fix markdown cell focus treatment 3598f9714d Add new codespaces extension id to allow list ab4cab8ac5 rename to `DeleteInsideWord`, remove keybinding ddff206d37 Simplifications: substitute constant arguments 59fa30c069 Scope toolbar visibility to when notebook is in focus (refs #110883) 36a6f53480 Make notebook cell toolbar appear when not hovering over notebook (refs #110883) 334ede72c6 Fixes #97906: Do not classify user installed extensions as built-in extensions d265071a78 Merge branch 'master' into joao/snap-arm 4f61f6d59e damn builds 7e0e697dc8 nbdiff. add menu to disposable store. 80169ca843 leaking disposables in notebook diff editor. 5ac643def7 re #109549. Reveal the first change in notebook diff editor on open. c983b2ae4b explorer actions: use bulk edit service 21c0490036 explorerService: add hasViewFocus and undoRedoSource 49a1f40890 update my-work notebook f4b9edc587 take local value when merging with conflicts 7dbdcf19a8 Merge branch 'master' into joao/snap-arm 8f1f1fda1b what the ad6f4faafb use better name for async markdown rendering, https://github.com/microsoft/vscode/issues/67806 f5a7eb3848 markdown renderer listens to img load and sends resize events, https://github.com/microsoft/vscode/issues/67806 cacb0c90d9 Add `UndoRedoSource` as a means to mark and retrieve undo/redo elements based on their source 9e704c93be Merge pull request #110867 from microsoft/isidorn/bulkEditServiceSupportFolder 63f04d87da bulkEditService: support folder creation 25a5576bbe improve browser extensions view 5cca5c598c empty commit cbfd4d5a9f fix snapcraft x64 6d439732d7 Reduce diff 2a499549ce add deleteWordEntire command 0718c3415f fix test 4ef843ec02 Merge pull request #97203 from bzarco/launch-multi-root-args 54a9df4857 debug console: increase maring right of count badge 78908e7eb4 bump distro 8513f398c4 command for open external 02ecc884d7 enable web extensions in web + server 671f169516 #100415 :lipstick: ff468f6536 #100415 have fixed height only for initial empty installed view 8d25d42560 Merge pull request #110856 from microsoft/isidorn/addCopyOperation 28d71bdbab fix snap target-arch 11644ab3f8 Merge branch 'master' into joh/pureoutput 20d2857142 fix wrong file decoration check 74cefb3f13 bulkEdits: add copy to WorkspaceFileEditOptions 93a0ad81c3 Merge branch 'master' into launch-multi-root-args ca20be531e simplify ITransformedDisplayOutputDto type 3d708b3c64 #100415 show installed extensions view always 7ec1a6899c remove transformXYZOutput 5dd81b4077 move orderedMimeTypes and pickedMimeTypeIndex out of the domain model into the UX (its sole user) 3d013dc76f move git rename to command palette 363534db96 Fix #100415 f6f12c1bcf linux build: fix step condition 11d9d4da8f fix compile error edf43e529a build snap packages for linux arm 12b808cf08 make event optional, event naming, relax badge length a little, https://github.com/microsoft/vscode/issues/54938 f9c3f0dda2 Codicons in prompt (#96430) a34cad5c6b Codicons in prompt (#96430) 72defdc77c Support greater than and less than 896848239d fix typos: pallete -> palette !!! b80117cc34 web - enable dnd to download in pwa (fix #83517) f11635ef11 :up: distro 23ed0649cb storage - remove deprecated methods (fix #109967) d21a51c233 re #109549. undo redo should edit markdown cell. 0109e4d504 fix #110826. e7ce16ceb0 Preserve old showEmmetcommands ID #58600 33e7e030d2 Move Show Emmet Commands command to extension #58600 9641ad2253 Don't use local server for signin in remote scenarios, fixes #104628 f3db091486 re #109549. hook notebook cell language picker into Change Language Mode. 9ecb7e4039 re #109549. click on status bar focus the editor 11341a0d37 Merge pull request #109987 from lf-/patch-3 e0eb2e6e75 Fixes #110468 9fbf87e38a Move management of `inDiffEditor` to the other editor context keys ba5371a252 Render "Extension" in Source column when the keybinding comes from a user installed extension (fixes #110653) 596a96ec7a Add `ResolvedKeybindingItem.isBuiltinExtension` 22b4c45bc1 update distro 58be98a0aa `native-keymap@2.2.1` 8a11346573 re #110581. 3184dca0bc cli server: add OpenExternalCommandPipeArgs 965c8c4cfd chore: bump distro fb6a2a3258 Bump vscode-ripgrep Now has pcre2 in arm linux build 82b485f805 chore: bump keytar@7.1.0 (#110815) 8edc150fe7 Try resolving URIs immediately from the tunnelFactory Part of https://github.com/microsoft/vscode-remote-release/issues/4012 05606c1e09 reuse terminals only for unchanged config; fixes #81345 87050c7fb5 re #109967. 0593b3eb54 Backup location can change in same session (fix #109019) d3057c54c8 remove console.time 7bf226cf50 add console log service to cli cd3caa2d7d provide a promise to wait until initialization is finished 221b8289c4 Merge pull request #110792 from microsoft/alex/native-keymap 8c93891a4d Minor renames ee4071ccbe Simplify handling of `diffEditor.codeLens` f405a3dcde editors - stop trying to be smart about default editor options in `vscode.diff` command f9f2637f79 debt - onDidBeforeRun => onBeforeRun 9c4c195980 Make users save new file (#110330) 442b1d1b61 Move all tree view out of contrib Fixes #110374 c4d2181da6 Clone (#110333) 540cb21d92 Add an elevate parameter to tunnelFactory Part of #110795 171a21b631 Add doc to resolveTreeItem Part of #100741 0c2c8dd8b8 Inline allOf clauses for additionalProperties: false (fixes microsoft/vscode-remote-release#2967) 60c625e9f0 fixes #110768 f255e3e00e Merge pull request #110573 from microsoft/joh/tsQuickRename 28b16c26d8 Move usage of `native-keymap` to the main process 8b192ae90f Move `IKeyboardLayoutService` to `/platform/` d8e40aeedf fix tests on linux 1b7d1e21ca fix tests - dispose 793b679ebc adopt latest API 0cd3c4c994 fixes #110775 fd610cfa5f Merge branch 'master' into joh/tsQuickRename 8667e71704 #109019 remove dependency on backup location 6479a9c46a add node task to fix build breaks (#110762) b25867b709 Support --diff parameter with reading from stdin (fix #110426) 2feb009c8e Editors: consider to allow name/description for file based editors (fix #110738) d19c7c52fd fix #110740. c4f50933f7 Remove some unused issue reporter code c839f49d8d Inline DeferredPromise Noticed that this was imported from /test/. Not going to make it a nice async helper because this isn't generally a great pattern. It's used in SearchService right now sort of as part of a workaround for issues dealing with EH search providers. When we straighten that code out we'll be able to just delete this. Fix #110610 566917c00b :lipstick: 0408c0010f use piece tree for size and line limit calculation. 6327e4b8da re #110581. adopt text truncation in text/plain. 09b47ace4a re #110581. truncate large simple text output. d878434d63 prevent keyup of action baritem without keydown fixes #110745 4fb3e8e6c1 set tabindex on submenu entry fixes #110765 9c3522ec80 Include experiment info in issue reporter data 274bf78da4 Merge pull request #110748 from microsoft/misolori/notebook-styles cabfaa63d0 Merge branch 'master' into misolori/notebook-styles 00fa5d3884 [typescript-language-features] Update importModuleSpecifierPreference values (#110536) 96949c76e5 Merge `IKeymapService` and `IKeyboardLayoutService` 8f32cbb1d6 Align `IKeyboardLayoutService` and `IKeymapService` c2f89b213a Move keyboard mapper instantiation to KeyboardLayoutService c0d53daa9e Emit `IKeyboardLayoutService.onDidChangeKeyboardLayout` only in case of a change 3224201b07 expose current flights on exp service 771f9f9a48 Update distro 744694ed31 Extract usage of `native-keymap` into IKeyboardLayoutService e7fe18ddf8 Update default styles and polish focus treatment cce8f8bc69 Fix #72878 6b85b9ff0e fixes Title: custom title zooms in #110716 fdd103cae2 Fix #100498 15353669a6 Merge branch 'master' into joh/tsQuickRename 8134d8c9e7 rename to OnTypeRenameRangeProvider. For #109923 0b8a242be0 surface latest jsbeatify settings. Fixes #84606. Fixes #79334 ec8e86a03e fixes #109603 for macOS web e62a2054a1 Merge pull request #108451 from justjosias/patch-1 ce864da274 fix showing remote extensions f663779523 Merge pull request #110572 from Wscats/patch-2 00525e9f7c Fixes #110603: Add `diffEditor.wordWrap` c704881abb Validate dynamic configurations only on launch 62e830be77 Git/SCM: Use vscode.open and vscode.diff for a better editor opening experience (#110733) da9a12b837 Fix #104055 9ba554073b debug: add unit tests for repl output count 11d835b65d debug hover polish the tip 97b9ca6028 debug console: respect element.count in aria label b39c9d3cc9 assign extension to menu actions 0454fdfcd7 fix tests cda2529387 debug: collapse identical lines in the debug console 876d1f09cc introduce new variable ${fileWorkspaceFolder}; fixes #84162 ea3b8da874 also quick rename type parameters, ignore declare only things e6bb245b6c use predefined constants f9224001d5 Merge branch 'master' into joh/tsQuickRename b04e9c8f9d :lipstick: generate command docs 504b891937 move commands to their locations (#110714) 8f9aeb83da fix invalid command converter behaviour 2b06004f0d :lipstick: 402e7afde7 https -> http in port forwarding view 85714e1007 clamp suggest line height between 8 and 100, https://github.com/microsoft/vscode/issues/110078 e416cba1b3 fix https://github.com/microsoft/vscode/issues/110707 46ff33b6b6 Subpixel antialiasing in some setting item labels Fix #86336 b4f09c5de3 Change search.actionsPosition default, fix #107163 2f34433853 Tweak settings tree sizing Fix #109095 4618539da2 💄 af6843bacf Convert terminal ext api null args to undefined Fix #110253 0180ce8eac Bump distro for merge resolve 89a418cc43 Add vscode-regexpp to remote/package.json 5afc5cd160 search: intelligently normalize crlf in regex search 7dde16206f Merge pull request #110094 from olivercoad/conflicting-autoclose-pairs 5d2ae44c01 Small style changes d1d59f8b74 Fix #27716 5da5495a7f search: fix copy all containing extraneous crlf line endings fbaf7b1187 fix #110427. 7a1ee06e24 increase titlebar height for macos big sur (#110592) 7a5fa10646 Merge remote-tracking branch 'origin/master' into pr/olivercoad/110094 8c76afad6c Fixes #108160: Handle padding in mouse target computation 9d389f0364 Handle error case for candidate ports Part of microsoft/vscode-remote-release#2776 f2ea8849e4 Merge branch 'master' into patch-2 c6efea07b0 :lipstick: c5e6489b3d fixes #110430 f3efe70c9a don't attempt to convert API types inside the renderer, break up mainThread-api arguments (and plan future removal) addaad3f99 Don't have 'show local' button in open dialog when remote Fixes #110264 043d8e2086 #109019 make fileSystemBackupsHome updatable 19c93615dd Properly call dispose on extension created tunnels Part of microsoft/vscode-remote-release#3961 0ec4ce387d Fix #110574 04be17a373 Fix #45244 17813ce44e Open command options are not mixed-in in the right order eea8648466 Use vscode.open in NPM scripts view Part of #110497 a9837acc6e Optimize code readability b851bf9ca0 Merge branch 'master' into joh/tsQuickRename 2ac7284762 add api argument stubs for strings and numbers 5c1789667c debug: make exception widget accessible 63ac48bf6d debt - move api command things into extHostCommand so that they can be contributed from anywhere 01dd9f0307 :lipstick: 71e24bc99b Fix #39543 70061a8b22 modernize vscode.resolveNotebookContentProviders-command, fyi @rebornix didn't find a unit-test for this! 443c0db7c9 modernize vscode.executeColorPresentationProvider-command e25b8819f7 modernize vscode.executeCodeActionProvider-command f3439ece09 modernize vscode.executeCodeLensProvider-command 94a57406aa deprecate old api command story 74703883e3 modernize vscode.executeSignatureHelpProvider-command 883936de2e modernize vscode.executeCompletionItemProvider-command d310cba731 Handle conflicting multi-char auto closing pairs (fixes #72177) 4e4f02f632 Allow ms-vscode.github-richnav to access proposed API c5ee747748 Merge pull request #110541 from loganrosen/ember-cli 29e7305d9d progress bar: report progress 39bea31ae8 Update classifier.json 1f11563975 debt - move contrib only service to contrib (output) 5b5e79558a debt - lift checkbox to workbench 76a8ea640e Add undefined to TaskProcessEndedDTO.exitCode (#110508) 0998e4ccbb respect user settings when creating terminals; fixes #109111 5d7ca3a879 debug hover: show tip to show language hover 45ce70792d fix tests b5cd02f178 #106348 Adopt codicon for npm view 3ee6fc3f68 Fix showing newly enabled extension in enabled section 3d8ad7d74e tweak setting for #5312, fyi @usernamehw 336b58c8a2 Fix #106348 f1560e9a19 Merge pull request #110494 from usernamehw/word_suggestion_any_language b07d19a768 storage - remove store2 (#109967) 8f82b02a4c Merge pull request #109842 from microsoft/joh/98228 20b49a4a9c storage - more target adoption (#109967) d9893b5d64 :lipstick: editor memento 38f55ece05 environment - add OS info (#106528) 710da39135 Mark .ember-cli as jsonc instead of json ee46c1f89e Adopt Action2 for more search editor commands to fix #110407 bf6776b980 fix #87730. 293af2074e handle last line of copying lines. 63e42d414b re #97497. f47b588736 :lipstick: 629cf65721 fix #97497. 591039e34c debug: fix compound integrated terminal launches breaking cb7297a10a :lipstick: 2b900dcf11 fix #110503. 4358d8bf30 rename 'dirSep' to 'pathSeparator' b155f9757a Update seti df143f19b7 no double spread 8d73ce7ed2 :lipstick: no more homemade iteration 2fdc607f19 Merge pull request #110273 from microsoft/connor4312/fix-search-freeze-on-long-lines 217ad5ac66 Typo: Uncommited → Uncommitted 7f6ea40bc2 terminal: typeahead state management for verified lines 4c46272797 Stores the actual config object in the cache e96cfbccbc 💄 576d8af2df Changes to use TernarySearchTree 8a5605bd21 Splits out loop & uses regex to improve perf Addresses PR feedback d21cbabe6b Adds default context caching for objects Clears all cached config context values for objects 165e49bfe8 use rename info instead of highlight info ce7bd67d46 explore on-type-rename for TypeScript 16ffcb50fa adopt latest references viewlet, https://github.com/microsoft/vscode/issues/85636 611f28952f trustedDomains: loosen restrictions on what a * can be fixes #110501 b0ce55403a Merge pull request #105887 from nrayburn-tech/dirSep edaf854a74 Remove "files.autoSaveDelay" from Commonly Used 753a1c095a Adjust debug toolbar shadow (fixes #110422) 561501492a Remove unneeded constructor from TreeItem2 0286c4f793 Add missing bit of TreeItemLabel API 4dae492ac7 Merge pull request #110428 from microsoft/connor4312/disallow-nested-config-resolver-keys 31b0068af0 Make TaskProcessEndEvent's exitCode reflect the truth Fixes #110185 630e706ad2 Add open context to certain commands (#110475) e526f16131 Prioritize configured tasks in `getTask` Fixes #109939 2e15bd0592 Add a setting to include word based suggestions regardless of the language 7b5849b123 Fix build ea5e971611 Tolerate strings again (fixes #110432) b785be4c73 Finalize TreeItemLabel API Fixes #61579 5758f6fcd4 fix cmd + down in open editors view fa856cf019 #67603 Add new extensions automatically to the list 7bfc7edadb open editors: introduce sorting 37103467dc dispose list elements on list dispose dc74330d39 fixes #110429 690db82ef2 tweak fix for #29210 and thereby fix https://github.com/microsoft/vscode/issues/106090 52e9e02e0a Fixes #110141: Do not push undo stack elements for a no-op EOL change 549da12a6d fixes #104061 717a730f3a window - fix undefined access e4da961570 Merge pull request #110455 from microsoft/joh/open 94d3497dac :lipstick: a9c04968f3 Merge pull request #110423 from dsanders11/patch-1 1cfb74c330 windows restore - add a new setting choice to restore the previous session even when opening a file or folder cc9616baf8 use 'enablement' instead of 'preconditions' e275af7e79 render activation events as code fb0128c7ec Fix progress bit overflow bug (#110411) b321d3c364 Merge branch 'master' into joh/open 33a49a9c63 Fix #67603 02316b635d editors - fix listener leak (#110336) 050a123750 Fix F6 in extension page webviews 4da8e50404 Don't show users a notification if a save is cancelled (#110344) 17c29f0b99 Add support for web TS Server logging over postMessage afcfc97316 Fix method spelling 7c6994d1b5 Use toOpenedFilePath to ensure we don't ask TS server for projectInfo if a file is not open fb93010aa2 Make sure we use the same path normalizer in a few additional places in buffer sync 805aa7e8f4 Make ResourceMap treat the same file with different scheme as different 67817c33a1 fix #40713. bb8d5f1842 terminal: update typeahead tests 0ef0d2d65a fix #109765. 97664e1452 fix #74622. b2eca1fd4d Fix InMemoryDocument to support both OS line endings Co-authored-by: mjbvz Co-authored-by: meganrogge bd98013973 insert a 1 sec delay before using a newly created integr. terminal; a companion fix for #38578 56947b3a75 terminal: only start typeahead predictions after the first prediction for a line is valid 65c3fb0a08 [css] add ':' as trigger character. Fixes microsoft/vscode-css-languageservice#108 13669e6bbb fix https://github.com/microsoft/vscode/issues/110420 02f6319ca1 Fix autoSaveDelay missing from Commonly Used, add warnings for settings patterns d5febf9628 config: disallow nested config resolver keys fac5cc3a54 #102906 add tests df171cfcab #102906 tweak wording f086ee122c Set StorageTarget in auth service, #109967 dea609b7ba #102906 wording tweaks f1c6ce7c9e clarify effect of command enablement b3c2cc3a7f debug: fix integrated terminal not changing drive 0e708ea0f7 fix #90897. 776d80f470 Merge pull request #104997 from rotem-bar/jensui/102906 0d23be5d74 Merge branch 'master' into jensui/102906 a48ad3b1d8 clear find widget reveal timeout d55e884024 Merge pull request #110370 from jeanp413/fix-107104 166f7a4cb0 Merge branch 'master' into fix-107104 f066dfcda8 Fix #67905 4ef91c2e40 Fix #94416 593d3dd94d debug: normalize drive letter in loaded sources view f2ccceef3d fix #84772. 49d1396aeb update pattern and add to grammar 900bcb7632 Merge remote-tracking branch 'origin/master' into connor4312/fix-search-freeze-on-long-lines 3e797ae1aa Update drop shadows (refs #92301) a986b578a2 fix #106570 4d6c4ae0b7 insert a 1 sec delay before using a newly created terminal; fixes #38578 6a8fe0a8da let CommandsConverter actually convert commands, make sure "magic" commands handle uri components b794105cb6 Fix #90548 0bda02ba0d tweak throttle based on actual numbers 40558fc8e1 report `suggest.durations.json`-even only every 500th time 2b21aab9e2 fix #110410 8367f475e8 explorer: use EditorResourceAccessor 5bf6d0cabc fixes #65188 d7b84b8fd7 update monaco.d.ts e69d768e53 adopt feedback for #109923 776193fdc0 explorer: reveal diff editor ab7c9d605a fix reading store from config 740087a3e0 #109056 remove hack to use dialog service 61970cb6f3 preview - fix italic for description fb5e7bae09 do not remove from remote if key is not registered - compute unregistered keys from last sync state 86621ccdee do not remove key in cloud if it is not registered 87fd567da0 add commit characters to explain mode, https://github.com/microsoft/vscode/issues/110382 efef7c1d15 layout code lens content widget when adding commands, fixes https://github.com/microsoft/vscode/issues/110332 bcf2f23cef Can't save workspace locally when using remote extension. Fixes #109713 49342801cd [semantic highlighting] improve fallback scope for macro. Fixes #110150 14075af62e don't resolve code lens when typing, immediately resolve code lens after receiving them, fixes https://github.com/microsoft/vscode/issues/96783 49bd4e4b34 Merge branch 'master' into dirSep 82c65e753f Add Refresh context menu action to candidate ports Fixes microsoft/vscode-remote-release#2927 d4e1ae56ed fix alpine build 5e5ce9ec53 Better compacting of URLs in ports view 8dc55cf219 fix sandbox 8c8e2a888c debt - make dialog handlers a workbench core piece c58384b87b distro eba1343a10 cleanup linux alpine build b7cb5e2e1a Merge pull request #109643 from eltociear/patch-1 f27d73be72 fixes #110353 14140d2a19 Revert "Merge pull request #109049 from digeff/searchView/context/extensionPoints" 31c034604e remove old forUris, rename forUris2 to forUris, https://github.com/microsoft/vscode/issues/110241 a19b26ef69 Fixes #107104 7a322c44db Try to fix the build f5898a0e59 surround match in zero-width spaces to avoid clobbering 3f62d10652 Removes deprecated IDefaultLayout interfaces 35f9bac07d Disable markdown smart select tests #110365 0572704226 Make settingLayout patterns strict Fix #110129 7ba372c80c Fix tests f23e47b9a2 Extract getParentFlowToElement c8877809c9 Pass webview editors an explicit scoped context key service aab1c59227 Use UriIdentityService for #110241 a28b99d78b notebook: echo complete error object on preload function exception 49695de8a1 notebook: execute kernel preloads in series 2b9e62fcf0 Merge remote-tracking branch 'origin/master' into connor4312/fix-search-freeze-on-long-lines 00f32f9a26 make search editor ranges work for hidden text 046654ae65 make search editor ranges work for hidden text a856e60a0c Better support dragging and dropping with webview views 10514bf423 Fix webview scrollbar colors for firefox 2b820b4bff Hook custom editor backup cancellation into the extension layer 7a4e81a674 fix catastrophic backtracking when parsing markdown files (#109964) a1bb5ac207 Merge pull request #109049 from digeff/searchView/context/extensionPoints 9a08a10708 Support ctrl+v PS override on Firefox too 982bfc2334 Update decorations synchronously when the wrapping info changes 86f773db82 Merge remote-tracking branch 'origin/master' into searchView/context/extensionPoints 559f9b60d1 Applied feedback 809db2993b fixup! 63d15a97c7 Support character markup in mardown smart select (#110195) 4bfa0a0aca fix build db6fbd581a Merge branch 'master' into connor4312/fix-search-freeze-on-long-lines d2701267df Potential fix for microsoft/vscode-internalbacklog#1604 038835d88c Merge pull request #109551 from maldahleh/109255-support-detail-text-settings-dropdown 1ffb469a15 add no-drag to context view css in menu.ts fixes #110282 1ce5b5773e fix #105757. 3030d4f380 Remove registered color for selectBoxCustom detail 414c387375 fix #108950. 0dafeae79f :lipstick: 636c588eda Refactor DialogService to use model and contributions (#109980) 57203b243d debug: add ability to close exception widget 92b9426701 Remove unused variable 014cca917e Merge branch 'master' into 109255-support-detail-text-settings-dropdown cb23c0892e quote powershell arguments properly; fixes #68151 5184dff52f terminal: fix typeahead edge case fcfc8531aa #110241 fix tests 96acd5f37f Merge pull request #108401 from turara:resolve-78733 3ef3247e85 Remove participants list from comment headers 140ce6130d adopt latest references viewlet extensions, https://github.com/microsoft/vscode/issues/85636 e7a598f648 Allow context view to align with anchor (#110277) 50416be1c6 #110241 Adopt TernarySearchTree.forUris2 00c4572909 Merge branch 'master' into resolve-78733 7065675056 Fix #109177 77154ae5c2 Merge pull request #110272 from microsoft/misolori/shadows 0a7f58fbf7 Merge branch 'master' into misolori/shadows 5fbd3b43bb Style terminal scroll bar on Firefox 3ab3514bd2 Force ctrl+v to not be sent to shell in Firefox 3d61a39074 fixes #103031 d34ba652f3 clarify explorer.enableDragAndDrop setting 02bd5f139b fix focus issue on list e4cbfba51b Don't change LANG vars containing 'euc' 09ab4faf90 :lipstick: remove LinkedList#toArray which became obsolete with iterators d10b4cc81e polish when selecting config ab6a005750 Fix #86077 d26f927791 Merge pull request #110268 from microsoft/alex/diff-word-wrap 415955a5cd Allow word wrapping in diff editor cd395984ca Make inline lightbulb aware of wrapping of changed or deleted lines 982689e007 added GH PR extension to list of tracked repos 912e8f7e99 Merge pull request #110306 from microsoft/joh/wordBasedAll e8b69537cd fixes #40295 74b2510985 word based completion includes words from other files of same language ad88db1031 fixes #110304 76abce66de Merge branch 'pr/107958' ddefc990cd correct codicon size for code lens placeholders 62877b66d8 Merge branch 'master' into pr/107958 0e4b1fec5d Render char diffs in the wrapped deleted or change lines in the inline diff 13f9f73353 :lipstick: 1d18ebe47e Merge branch 'master' into pr/107074 ecfb8771e4 Merge remote-tracking branch 'origin/master' 6a2da6d725 :lipstick: 7200f73de2 Merge branch 'master' into pr/106368 77a57165e4 debt - race more minimal edits computation against 1sec timeout 2c1272b794 fixes #104945 64ef9869b6 Fix filtering on * for simple file dialog Fixes #110265 55fdf94d67 fix https://github.com/microsoft/vscode/issues/109776 cb63744475 Adopt TernarySearchTree#forUris2 (#110241) a56c7406d7 Fix issue where toggling between inline and side-by-side was forcing word wrap off 3f8e8a1733 Push view zones in the original editor when the original lines are wrapping in the inline diff case 438f610860 strict path casing in extension host profiler, https://github.com/microsoft/vscode/issues/110241 156d702f5a Diff editor: show path as description (fix #109224) d751b6bcd2 add IExtUri#ignorePathCasing, use forUris2 in decorations service, https://github.com/microsoft/vscode/issues/110241 f2a9b63c64 Do not show hover on icon label after click Fixes #110284 f5e9a01715 make default not ignore path casing, https://github.com/microsoft/vscode/issues/110241 3bf2afdc01 add TernarySearchTree#forUri2 which allow correct path casing handling, https://github.com/microsoft/vscode/issues/110241 21c84a4351 restore old case ignore behaviour unless explicitly set 9b0522f3dc scmViewPane: do not render whitespace in commit message input field (#107913) b3cc19b819 :lipstick: use async-await for code lens provider logic c0c4ceed44 fixes #110290 351e6172e1 update code lens when editor gains focus, https://github.com/microsoft/vscode/issues/83363 0216c71896 Revert "Use editor base weight when contributing peek commands, fixes https://github.com/microsoft/vscode/issues/109727#issuecomment-720986472" 1357f6d494 editor preview - ensure simple file dialog opens pinned too 65f805d98e fix build 6c32ada9b5 show extension editor manage action always 2815e732f7 Fix refocusing clearing existing forcus for iframe based webviews 4247b296eb Including ` as part of foldEndPairCharacters (#110108) d04956f6a7 Switches the 'Fix all' quick fix provider to use resolveCodeAction (#107853) ffb8c08dd9 Enable IgnoreMenuShortcuts for iframe based webviews df7fdd6515 search: fix freezing ui on long lines c9bebe2cc9 Fix #98041 37a01430cc Tweak shadow colors 840bc2ef1c Set explicit StorageTarget for webviews 0efbfd9190 fix #108765. c067c9b947 Improve rendering of view zones with changed or deleted text (inline diff editor) 59b020a362 Merge pull request #110255 from microsoft/rzhao271/format-uri 8248e19817 Merge branch 'master' into rzhao271/format-uri 5ce31a6e8b fixes build break b473bc55de Clean up drop shadows 2f7f100e44 Render inline diff view zones in batch 701e6713f9 Merge pull request #110112 from microsoft/misolori/squiggles-bg-theme 93a7a07559 Do not store the original content in the diff information for inline diff margin actions 2b9ae05554 Merge branch 'master' into misolori/squiggles-bg-theme 51eb0901d4 add setting for code lens font family and size, https://github.com/microsoft/vscode/issues/16038 5e8f9cf1d2 :lipstick: code lens fcef0e3db4 Use Array.flat() (#110189) 4e0c77f8aa Add `IViewModel.createLineBreaksComputer()` e300dfcdd2 terminal: allow excluding programs by name from typeahead b5f6a521e0 Fix vscode-emmet-helper issue #1 d29487617b update language e5530fc4f0 fix #105920. 62d39a7dba Adopt storagetarget in Exp services refs #109967 98a3c4fe64 reduce work when menu is not visible fixes #108712 ef6b438769 Fix #88703 a45abdbd1b Update seti 87d49a7b66 Fix #108300 226503ba0a Merge pull request #110213 from jeanp413/fix-110212 ea6ee515f1 Add color tokens for warning/info bg f1ebde547c fixes #109781 876af4ccfd no max width for welcome buttons 1c7d982b93 fixes #105201 Co-authored-by: rebornix 6e6654a679 Fix #91534 f0580d497e Fix #101441 d6fb7989f5 Push view zones to accomodate equal but differently wrapped lines 1256b16ee1 Merge remote-tracking branch 'origin/master' 7509a0103e Fix #103941 91aa548b05 Merge branch 'master' into pr/104614 4ecf3f8f02 Merge remote-tracking branch 'origin/master' 98e0e93ae0 :lipstick: 61500a468c Fix #94289 c96f362934 Merge branch 'master' into pr/99324 f18700d036 Merge remote-tracking branch 'origin/master' 309cabdf51 :lipstick: 961cbd66b7 CLI help: consider to separate --file-uri and --folder-uri. Fixes #110206 978d39324a Merge branch 'master' into pr/104312 e7e38c161a Be aware of line mapping when rendering diff view zones and diff overview decorations 7e5609afa9 feat: implement Git: Push Tags command (#110096) e3754e6f8d group uninstall and install another version actions into same group 294406d7a1 Fix #110121 23579d815f [file icon theme] icon match the last word of folder name not the whole name. Fixes #110183 ec084a25bd :lipstick: df4524f713 Merge branch 'master' into pr/97525 86d848d8e7 :lipstick: 592fa5cdaf Merge branch 'master' into pr/97486 fd306e8cb4 Merge branch 'pr/97322' dd97a9d335 :lipstick: d40499936f Merge pull request #108682 from Siegrift/enable-tsec-language-service-plugin 551db7ec94 Add new fileDirnameBasename variable Fixes #78316 8cfcd9a3fe Merge pull request #110178 from vfcp/master 2720a8022e Allow user tasks to run when no folder is open 66ea9c5ca1 Pass in actual editors a0031aa6ac Small refactorings 07f0bade37 remove duplicated context key expressions, fixes https://github.com/microsoft/vscode/issues/97381 5290baabac improve message when files is too large for formatting, fixes https://github.com/microsoft/vscode/issues/105986 8cebd10782 set line height for suggest details, fixes https://github.com/microsoft/vscode/issues/110172 3ed1d0050c Merge branch 'master' into enable-tsec-language-service-plugin 14767349ab window - some renames :lipstick: 147f623629 Merge branch 'master' into pr/97322 4439de9a3b tweak setting name `editor.suggest.showInlineDetails`, #109690 7efc22cc85 git.publish: do not await notification resolution related to #109977 776541c380 Setting to Disable Split Editor on Drag and Drop (fix #71016) c73c0b30f2 :lipstick: let -> const 35ff2c0df8 fixes #110012 85958bcecd fix https://github.com/microsoft/vscode/issues/97451 aede2434b0 fixes microsoft/vscode-remote-release#3180 5a534883c7 Fixes #110212 875c2ced66 Finalize FoldingRangeProvider.onDidChangeFoldingRanges. Fixes #108929 1fab0083f9 Renames 7ca4a47ba5 add vscode-test 4e24bef648 :lipstick: 54ba2bad67 Merge pull request #104923 from AE1020/scrollbar-paging abcc621872 Rename option to `scrollByPage` 3517c02303 files - stop checking for BOM before writing 7bfd7fb685 Keyboard shortcuts for switching focus between left & right sides of diff view (fix #95068) eb8c718e81 debt - fix compile errors in bootstrap files 3154dd4f69 editors - add workbench.editor.enablePreview to most commonly used settings 59a1893d6d Log number of loaded certificates (#91794) f9e19c9ad1 only hide outputs when output is transient. 148b85862a fix #89250. 9ad6e7edf9 Correct themeLabel in package.nls.json for "Tomorrow Night Blue" theme 7095ef144b Revert "Revert "Fix #99971"" a2963771d1 hover info should not be broken within word (#106885) 024df33553 feat: add setting for default stash message 109d9984c1 fix: commit template appears as default stash message 19c0c60064 feat: detect default stash message use commit message as default stash message if commit message box is populated 03c38e6702 Add more logging to github auth provider 1d5854f611 Unassign myself from the classifier 3338ff4e18 Fixes navigation commands for webviews 6e2aa0bfb7 clean up code and remove unused methods ce7e6d9978 Bump actions & fix #109699 153ba443d5 Use string.matchAll (#110074) b419f2f169 Adopt StorageTarget in searchEditorInput Ref #109967 5961799c53 Escape backslashes in issue reporter data, fixes #105494 be410d1a3d explain why we modify textarea input handler in firefox. fc07b2f170 add comments to clarify code 83f000df03 Merge pull request #106873 from belcherj/patch-1 cb044cec21 Merge remote-tracking branch 'origin/master' into pr/belcherj/106873 339681f631 Use excludive selectors for search editor contributions to fix #91453. cc @jrieken fca7107884 fix #105901. 35bad7021d disable submenus rendered as dropdowns (#109934) c8cfffe09f Add vscode-encrypt to nativeignore df9cf627b1 Merge pull request #110095 from microsoft/rzhao271/webgl-description 9d5dbc6a1a Bump vscode-ripgrep 9d0346ad2f Fix #107858 c91facce0a Add an action to remove recently used tasks Fixes #93403 8335fcc3fc debug: do not auto pick dynamic launch configs 779a171f9d Align configure task quick pick closer to terminal Part of https://github.com/microsoft/vscode/issues/93864 ad19579a44 Don't clear filter for Show All Tasks Fixes #97467 15afc78232 Fix exthosttree tests a1025c64c1 fixes #110077 9f2ce53867 debug: stop supporting enableBreakpointsFor 081632d225 debug: remove legacy adapterExecutableCommand e95c40c1e4 API proposal for revealing tree view when no elements Fixes #90005 ce4fb6e323 fixes #96264 0cc93f5c48 debug: split ConfigurationManager into ConfigurationManager and AdapterManager 6ce30f1dce remove deprecated debugAdapterExectuable debug proposed api f53678cb4b Update branch for sql grammar Fixes https://github.com/microsoft/vscode/issues/109251 85534c004c Only reset theme icon color in treeview once 4b0855f292 Improve the dynamic launch config UI f63d695254 Fallback to userhome when no "file" scheme folders open (#110066) 0302b84221 web - set version to 1.52.0-dev 63ce8b4732 fixes #92146 d03925a88c Merge branch 'master' into pr/95915 e380037a06 [themes] Use light theme for --disable-extensions. Fixes #97058 168ebd1e66 fixes #95599 256066f94c Fix preffered pm when pm can't be determined 57ec100886 fixes #40548 1593959b5e In case of multiple possible longest common substrings, try to give preference to the ones containing the most ammount of text 24b28f57be Always use `vscode.open` to open markdown links 7f7307b89a Fix command for renabling extensions when running with cli flag, fixes #94532 4059ff428f Allow cancelling GitHub sign in, fixes #109101 93d736db23 fix #93326. e026e9a77e Update JS/TS grammars 194068f693 adopt StorageTarget in SCM #109967 48ebbe9ffa Merge pull request #110038 from aasimkhan30/aasim/fix/checkboxOutline 663a049f0a Merge branch 'master' into aasim/fix/checkboxOutline 25c6703f39 debug: fix debugees blocking when writing stderr 024368a638 removed unnecessary important from outline-offset in checkbox c258fff193 Fix compile error 80a9ecff3e debug: cancel hover evaluate requests when hovering off dialog 451c2b61d3 Specify type of CodeLensProvider instead of using casts 9f4737fa3f Exclude definitions from js/ts references code lens d88ac6f164 Finalize the isWritableFileSystem API d54c3c67dc fix #74353 18902550f7 fix #70306. 11faf89369 added outline offset to checkbox to make focus visible. 05049f7fbb Add Definition list to Markdown Snippets (#110026) 8a7189599e Add color token for error bg 0440be9aa4 Fixes #110033 c2fafb10e3 fix #57197. a58d816580 Don't remove -l from tasks on Mac if setting is set Fixes #107563 5a73a68e1f Revert "Fix #99971" faf36c2f47 Fix #99971 fd951da3ae remote indicator :lipstick: 736a46fc8b Merge remote-tracking branch 'origin/master' 49990bebe4 add git CommandErrorOutputTextDocumentContentProvider 2eb805ad75 Mention Inherit Env setting (fixes microsoft/vscode-remote-release#3030) cd95d90589 Use correct context key service in custom trees Fixes #99767 ec471da613 Merge branch 'master' into pr/95354 cd6466909b fixes #92034 e3ace1fe1f Merge branch 'master' into pr/94369 7b6561ff5b Merge remote-tracking branch 'origin/master' 08d271585d :lipstick: eb1cea5432 Merge branch 'master' into pr/94358 8fe4a348ec Remove focus when scrolling (#94280) 544c986e0a Added a warning before dropping a stash. (#94267) 5afaec5c21 Merge remote-tracking branch 'origin/master' 7328c3ccb0 Merge branch 'master' into pr/91838 a089e24fed fix typo 818d57db15 Better diff for deleted file in merge conflict. Fixes #88973 (#91245) 36e2868372 preview - pin an editor when a navigation starts to another editor (#109779) d6af4893ed Update grammars 1dbff8bdd1 Remote Host status bar entry has no max width (fix #107451) 47a956eb2e Fix #109023 9832b90fde explorer: Revisit explicit use of ConfigurationTarget.USER when updating config 520a050cd3 Add quotes to autoClosingPairs for make Fixes #89191 d43e45b796 updates 578d7381da fix #7989 b0a7c8496d IconLabel markdown title debt (#109914) 786f507696 Fix #107320 f021eee884 layout - write to correct config target (#109373) bfb73347bc Added a grooming delta notebook a69a01af9e Remove unnecessary argument (#109373) fa4f11cf16 fix stray console.log aaf17b5d9f Ignore dev container logs in url finder Fixes microsoft/vscode-remote-release#3953 ac2ca0b539 typescript - fix nls issue 1cbdea3da0 Revisit explicit use of ConfigurationTarget.USER when updating config. For #109373 a69f07abf0 Revisit use of ConfigurationTarget.User in tasks Part of #109373 22c2bad746 Merge pull request #109966 from microsoft/aeschli/remoteThemeFix 4f88f448a0 Adopt StorageTarget. For #109967 c486bbf493 editors - add a menu item to keep editors open 1082f3e58c Adopt storage target in tasks and remote explorer Part of #109967 a3658a2fee Merge branch 'master' into scrollbar-paging 1c0d056915 editors - some preview tweaks 19044c847c #109373 do not pass user target 92b6b9c80a storage - adopt more storage targets (#109967) 805ba83b59 Storage service error in shared process console (fix #109985) eae74f5125 Loc comment #100941 e57af55eaa Pick up latest TS nightly for building VS Code 4a54027a82 Disable rename after refactoring in interactive playground e6079bd127 fix #105614. 373ea1b969 Fix build 6eeaab8e5d Handle cancelled sign in when publishing to GitHub, fixes #96502 2e6d3bdf6c fix #27836. 0f623956c2 Show dialog when there are no trusted extensions for account, fixes #96359 5463322d19 Clear trusted extensions on sign out, fixes #96616 f4e49a5f3a php: revert break deindent behavior 6bbb17d318 terminal: unregister csi handler when not in use for performance 81a25a894c Rename implicit project settings and deprecate old ones ff7cc33236 Use objects.equals for comparing configuration objects 05fee0cf3d Extract implictProjectConfiguration class 64705a07dc Add two new setting for strict null and strict funtion in js/ts that are implicit projects c38a12c26c Quote jsconfig and tsconfig in setting 341124ff39 #109896 Remove IStorageKeysSyncRegistryService and introduce new service for syncing extensions storage 4a6a5ab1f8 Explicit fontLigatures description 05602b399f terminal: clear typeahead after timeout 8141dcf29a Extract adjustFoldingEnd 97db458fa4 Show closing ) while folding JS/TS 0608e3f5bd Pick up TS 4.1-rc for insiders a7cd9408a9 add comment 6d5736aa3c #109967 Adopt to new storage API c2dafddbae Add splitLines helper function (#109869) 73eb361f29 notebooks: expose scriptUrl global to renderers and kernels 4286c603d6 Remove unused variable bb8e52bca6 Update comments panel message text, fixes #91623 7183777033 fix: run to cursor with multiple paused sessions d11872b127 Fix comments panel focus, fixes #103466 b35ea7e5a0 fix #54829 08e556dc5d fix #109486 and #109489 f8a91e4210 Adopt IStorageService.store2 in terminal 320602f7b6 Fix #109860 660222bdec fix https://github.com/microsoft/vscode/issues/103818 267817cf46 debug: adopt storageService.store2 bc5c7923f8 Merge branch 'master' into aeschli/remoteThemeFix 7ae6a45879 adopt StorageTarget, https://github.com/microsoft/vscode/issues/109967 35026262bc storage - avoid Object.entries in platform 2c292d3af4 fix tests 92314d61a5 reverse the eye open/closed icons for hiding snippets 2db5a355bc storage - adopt some StorageTargets f5f4af6db3 Update C++ grammar 5e4c53cffc add another test for https://github.com/microsoft/vscode/issues/96545 c8d123ded5 Explorer: move off iterating over file changes 4b9e0bba6e Merge pull request #108964 from turara/fix-96545 3c33f50f39 Merge branch 'master' into aeschli/remoteThemeFix d0343e68c0 sequencer for set and reload theme operations 9db1b1492a Merge remote-tracking branch 'origin/master' c3b5f2bf1c cleanup for #89313 09829053d1 adopt storage changes in extension bisect fa61ee7caf fix flawed comparison, increase threshold ea84245395 Lint Sprüngli e3a79e9e44 [css/html/json] Randomize inspect ports to avoid conflicts 1b9cd42b9b remove unused import 0382313947 Merge branch 'master' into pr/89313 4cd46e32b2 Merge remote-tracking branch 'origin/master' e5285908ca :lipstick: f923ac98e9 storage - document enum better 90aa30e660 Merge branch 'master' into pr/89249 fe458b4aa2 Merge remote-tracking branch 'origin/master' 6029cd4e3a Merge branch 'refs/heads/pr/88772' 163bd1dcb3 storage - rename storage change event e216a598d3 don't presist little size changes ea14fc2c2f add command to reset suggest widget sizes d562ace335 adopt to new storage api 8f5311c771 Merge pull request #109960 from microsoft/ben/storage-target 4f038dba60 :lipstick: e6b53ff8da adopt using new storage service to sync storage 744eed0a96 Add icon in Windows explorer window (#86283) e61be087c6 Merge branch 'master' into pr/88772 a665899915 fix compile and tests 194c7ea237 test name dfb74a6c97 some comments and more tests 1f7234c0ee workspace color theme setting not picked up 214125ebcb fix https://github.com/microsoft/vscode/issues/101870 c2944c6f22 another failing/skipped test for https://github.com/microsoft/vscode/issues/101870 130aa96b42 add to #109818 0feb41eed0 test machine storage is not synced b561be9e4c Add url finding for python 2.7 simple server Fixes microsoft/vscode-remote-release#3949 59bfe65e18 store synced state in user scope 8145e9f37a Fix local scheme for registered file systems in file picker Part of #https://github.com/microsoft/vscode/issues/109345 c49bc9b1cb fix tests a0ce43416d make snippet prefix optional and hide snippets without prefix from IntelliSense and TabCompletion, fixes https://github.com/microsoft/vscode/issues/73635 8b712ee66d - Use global keys with user scope from storage service to sync - Remove versioning of keys b449c95a84 Merge pull request #109916 from microsoft/joh/snippetIgnore 1f7528568e storage - add target to change event and cache targets bd7a83c110 tweak wording 41d6a0a560 Merge branch 'master' into joh/snippetIgnore 21cfbaeeee Merge commit '265a2f6424dfbd3a9788652c7d376a7991d049a3' into joao/terrapin 57c7e40a50 bulk edit - respect sideBySide options from event 7a00f134f7 do not show sync icon when extensio is not installed 265a2f6424 Supress marked logging for using sanitize 983ced6961 Changes to const enum cddb1b75f1 Moves newline handling into the hover Adds enum for parameter e06d374787 Avoids paragraphs for newlines in string tooltips This better matches the native tooltip styling of newlines 1c2005331c [json] use links for $ref, not goto definition (fixes #101474) e934668bd0 Merge pull request #109833 from fleon/patch-1 c68b238af3 Use `self` directly 51a4bbdfa5 1.10 is bad on macOS, revert this temporarily Revert "chore: bump vscode-ripgrep@1.10.0" ab9bb823ea debug: allow serverReadyAction to run a launch config by name 57340b736b Warning when invoking explorer.newFile (fix #100604) (#109905) 7831795dcb Backups: remove legacy migration code path. Fixes #101484 9c00847244 OnTypeRenameProvider API wording 529388e524 :lipstick: d88f099116 Remember History on Debug Console filter between vscode sessions b9833bb962 Fix #99105 7278373f3d fixes #99709 bafccc8ebb format f17f51878e update milestones in notebooks ffd61f2917 fix markdown string integration test 0e43c7a495 Include description in custom tree aria label Part of #108480 7dca88de88 fixes #64188 503135e9b8 expose snippet enablement inside "Insert Snippet" picker fc37a9d0d3 Move tasks onBeforeShutdown to electron-browser Fixes #108649 e52382a0b3 findExecutable should check PATH in a case-insensitive way Fix #109863 e01e0ebe39 [theme] Git:Input Validation Subject Length - not working on Monokai Theme.Fixes #101081 aba089cf11 Fix #95268 bd0c897c90 Fix #95211 20b534d41e add persisted snippet enablement 70227b45b5 remove unused label ef941c2c3c :lipstick: more async-await f5490aed3d Fix #108023 7936093c33 git: merge resources should return undefined original resource 5b3b6b8026 :lipstick: async-await a3a5d6a6e1 Fix tasks test 796db55744 Remove iframe around web worker in codespace case 56b686f9fd :lipstick: snippets 80d59864b1 appendText should escape leading whitespace, should escape leading quote character, also remove duplicated implementations, fixes https://github.com/microsoft/vscode/issues/68983, fixes https://github.com/microsoft/vscode/issues/109040 7d1affcf90 Throw useful error if task execution is undefined https://github.com/microsoft/vscode/issues/109861 5905725b5c Locally, but exported, constants hurt minification (a little) (fix #109899) eeaa42d650 storage - let flush return a promise when done 2dce4b75e6 Merge branch 'master' into joao/terrapin 1703e7e71e fix bad JSON a8036f5d27 Show current debug line in overview ruler 4ec0cd5f29 fixes #92709 153835e0f5 Fix #99963 e720530819 Open to side: do not first use fileService for resolving, since item is already part of explorer model 7601718ccc Use editor base weight when contributing peek commands, fixes https://github.com/microsoft/vscode/issues/109727#issuecomment-720986472 82ff980017 use item labels for themes dfaf4ef080 Merge branch 'master' into joao/terrapin 4d9e54c0b8 increase job timeout 37e16f845b storage - adopt new API in one place b64171bd0b storage - introduce onDidChangeTarget event 6f26a40b76 Fix #101612 183b2a2892 [themes] theme names are all not localized. Fixes #109840 c8b48d0d99 Fix #103438 8d429660a9 fix message cecd55133f Fix #106989 6a242516e4 storage - first cut target support (user, machine) 1d98078624 fix #106857 ad1dc7f87a Strip newlines from the code actions title 296aa89012 debug: improve attach to vscode config 6acc9ff1ee editor cmd+e find with selection. 90b17ed115 Make it easier to override markdown table stylings 699e3e5afa Update marked 49873aa674 Bump actions 1ca6360d5e terminal: fix typeahead eol naming 1351f36293 :lipstick: 06c401b358 terminal: cleanup typeahead tests 25e70bb455 terminal: update typeahead tests 5e636afbcf Remove unused imports 4e15db0b49 Fixes always white comment body border f25cb53571 Bump distro 4f1f830735 Merge remote-tracking branch 'origin/master' into searchView/context/extensionPoints bce86a137e Fixed other context menus using this context bf161eb5cd use mouse events for iPadOS instead of pointer events as iOS sometimes does not emit them 16d8eb9c57 Merge pull request #109630 from microsoft/eagerTerminalReconnection c1a6bc16b8 terminal: capture cursor position correctly in typeahead backspace 220a067d8e Merge pull request #109850 from kena0ki/issue##109636 3258a28683 clarify activity bar action menu layout refs #40262 fd9ace8458 terminal: fix typeahead leaving stray characters with backspaces cbb21a30dd terminal: restore old cursor style after failed backspace 933d888b2a Merge branch 'master' into eagerTerminalReconnection efb8cba23a Fix #108817 e6e9d6c36b add report issue to extension bisect, move issue service interface to workbench/services, allow to file issue against disabled extension if explicitly names, fyi @RMacfarlane 6c5cc99c77 Merge branch 'master' into issue##109636 a9dfd7e0ed fixes #109849 a0012e5f63 fix terrapin step 3b3b94525f fixes #95697 6e9fe5a638 fixes #71994 a25c5efdf3 :lipstick: fd8411e75e Fix #89559 4b49bae903 Update xterm.css c110357096 fixes #67685 a5d719c570 Try new Rust grammar (#108254) 6d20077821 fixes #67315 7964dfd6e8 :lipstick: async-await 8600d333fc debt - make editor smart select a "real" editor option 9d936d51f3 support ThemeIcon#color in workspace edit metadata, https://github.com/microsoft/vscode/issues/109460 4e198a4b4b :lipstsick: use underscore for privates bea76730f1 feat: Autodetect npm.packageManager #102050 (#102494) 1bd7cf9e41 use gh username for todo-tags, nuke some todo-tags ab94234299 Merge pull request #109740 from microsoft/joh/extbisect 0729278ad1 use label2 when CompletionEntry#source is a path inside the workspace 441698a621 do not allow editor override for walkthough inputs, fixes https://github.com/microsoft/vscode/issues/109595 98262ef050 fixes #109818 7901aa87e4 wording, show number of disabled extensions, option to keep bad extension disabled, towards filing issue 7cce3438d1 #107657 skip it unitl fixed 4198638fac Fix #109755 744f2ab731 web - use unbuffered upload/download for small files to speed things up f3a23697a9 Editors: revisit "preview" editors default behaviour (#109779) d6c2656f19 [regression] Files served by readonly filesystem show double '(read-only)' suffix in titlebar again (fix #109733) 2d7aacfa92 notifications - reduce weight of ESC for closing if toast has no focus d56b608506 sandbox - bring in browser based keyboard service to enable basic keyboard usage 0268b334e5 enable enableExperimentalProxyLoginDialog per default 5956564b72 storage - IWorkspaceStorageChangeEvent => IStorageChangeEvent 87f7879528 flush storage, some wording tweaks 44e28513b1 Merge branch 'master' into joh/extbisect 3e8c2e036d add "editor.suggest.showStatusDetailsInline"-setting to disable/enable inline details, https://github.com/microsoft/vscode/issues/109690 10a98746f2 Use `var` instead of `const` for the `global` variable 7e25008c60 Merge pull request #109750 from microsoft/sandy081/fix109709 5264ab94a3 chore: bump vscode-ripgrep@1.10.0 8abfa9bc03 Bump version to 1.52.0 580ac9765d rename variable to MatchRange to avoid argumentProcessor 75894bf0a6 Applied feedback bb1be0326d Added extensibility points for searchView/context 5662d49333 Revert "Revert "Fix #109709"" bba3e33b7c fix compilo a77dcf3cb1 tweak wordings bc957f402a add commands and logic to bisect extensions 7cedcfa44f Merge branch 'master' into eagerTerminalReconnection 485badfd8d Fire onDidChangeConnectionState at the right time 574ee2032c Set initial terminal focus based on connection state a607b1437b Fixed typo 'sequece' to 'sequence' bb4b04f206 Terminal reconnection - Get rid of awkward "empty tab" concept 5d0525b50d Reconnect terminals at startup instead of waiting for the terminal view to become visible 994b071092 #109255 Add encoding description to settings file encoding drop-down 7f5714beec chore(extensions): incorrect localize key c79c26babe Merge branch 'master' into resolve-78733 9dffbf40b6 Update SearchView#updateTextFromFindWidgetOrSelection to check seedWithNearestWord 18b30c6a8c Fix SearchView#updateTextFromSelection method to return correct value 10f7c0573e Update OneSnippet#move method to fix #96545. 8d535e661b Update matchCase and wholeWord options when updating text from find widget in SearchView c9b479c4a1 use terrapin 720cdd5e1b Merge branch 'master' into patch-1 2f232ccbca Enable tsec language service plugin 5b5431d552 Merge branch 'master' into scrollbar-paging 36446b1b97 Merge branch 'master' into atomic_tabs 3064431dc5 Fix remaining hightlights d3a3f39dbf Fix overlay area for minimap slider e6cddb065a Update regular expression option when updating text from find widget in SearchView f0583c0db2 Extract updateText method in SearchView d000256957 Add atomicSoftTabs in one place that I missed 3ca4059ea5 Move mouse handling to a lower level to handle more cases d119c92489 Add atomic tabs option bb42ea52e0 Typo hightlight -> highlight 66160a8417 Add updateTextFromFindWidgetOrSelection method to SearchView c9fb066990 Ignore LineDecoration order when comparing e51cc0eae7 Move changes to scmViewPane b08e733365 Merge branch 'master' into fix-89145 02cfa0b9a8 Create empty commit with changed files #107753 b9b6241354 Merge remote-tracking branch 'upstream/master' Merging bug fix with original VSCode repo ff2a9a476e Fixed issue 104346 ceeb974856 Wire up `preconditions` for viewsWelcome buttons, and use it af4fb00687 Initial 'Git extension is activating...' in SCM view 4e00a9b1a5 fix #106487 Contribute an "activating" placeholder message and button 822ca5f07b don't declare a variable that isn't used 2f1c0213d3 Fix composition logic for Firefox 3cc907a220 Fix return 3890d7fba9 Formatting 22ca0c1e8a Add providerName option to git.api.getRemoteSources fa3c6026e2 Merge branch 'master' into dirSep b955b58eee Add dirSep variable launch.json 16d89b0694 Merge branch 'master' into scrollbar-paging 6089fec6a4 Merge branch 'master' into scrollbar-paging aca9ae3288 Option: editor.scrollbar.gutterClickMovesByPage 8b21b331fa Amend scrollbarState.test.ts for new positions 2fa97c8794 feat(extensions): added an option to disable related dependencies when disabling an extension with enabled dependencies 11664e62a3 Make Clicking in Scrollbars Move By Page ff8d4feeb4 Git: Add cherryPick command b4c528cbfd fixes #103281 663ecdbb2a Merge branch 'master' into better-checkout-type d9d1be4e49 Avoid debug ae540536b4 Rewrite checkout items 3f585d7400 Add better support for checkout type config df3af97279 fix fetchOnPull behavior for Sync fc797d2430 fix 97472 bed300cd4f Don't fail when resolving config variables without a `folderUri`. 8561cbb8ae Add force checkout and smart checkout 31ee5b9644 fix something b24cb8b47d Add rename by git context menu 1531898fdb avoid loading and twisty set at the same time 1243ff76e4 Changed name of the setting to openAfterClone b64cb1ecf4 Merge branch 'master' of https://github.com/microsoft/vscode 4420bbfbcf Offer to show git command output on failure 64b8e933ba Merge branch 'master' into fix-89145 5a9b210d91 Merge branch 'master' of https://github.com/microsoft/vscode 26e1217ce9 Merge branch 'master' of https://github.com/microsoft/vscode 156d5ab281 Added setting for opening cloned repository without prompt. #93300 412a44e9bc Git: ask to save unsaved files before stashing bc85a9ffdb Added user choice for opening the folder always. 725985401d Merge branch 'master' into amend-message 37bca69ff1 :lipstick: 3b9dca805c Merge branch 'master' into fix-89145 f23fcb72f5 allow git amend message only 33b5a9ba25 Merge branch 'master' into ignore_sub cdc6c051e5 Persist scm tree view state between sessions 712ceb8279 Fixes #89145 2abdb90472 git.pruneOnFetch setting implemented d7ed37e864 add ignoreSubmodules option REVERT: e5a624b788 Bump version number to 1.51.1 (#110355) REVERT: 02f4e3563b reset icon color once REVERT: 314b97acc6 Fixes #110033 REVERT: 67a784d64f Merge pull request #110247 from microsoft/joh/fix110188 REVERT: b0f1494e96 restore old case ignore behaviour unless explicitly set REVERT: fcac248b07 Merge pull request #109961 from microsoft/joh/fix/109906 REVERT: 9a44531938 fix flawed comparison, increase threshold REVERT: f918d52947 don't presist little size changes REVERT: f37df00d4b add command to reset suggest widget sizes REVERT: 271026ee11 Avoids paragraphs for newlines in string tooltips This better matches the native tooltip styling of newlines REVERT: a5dfd9dcf8 Merge pull request #109882 from microsoft/roblou/fix109863 REVERT: a46559b134 findExecutable should check PATH in a case-insensitive way Fix #109863 REVERT: c57a3408d8 Merge pull request #109859 from microsoft/connor4312/typeahead-151-candidates REVERT: c82d350c6c terminal: cleanup typeahead tests REVERT: 54cadfb2cd terminal: update typeahead tests REVERT: 192fd218cc terminal: fix typeahead leaving stray characters with backspaces REVERT: 3d37590985 terminal: restore old cursor style after failed backspace REVERT: fce8fc82ca Merge pull request #109829 from microsoft/sandy081/fix109709 REVERT: 0bc9b1de71 Revert "Revert "Fix #109709"" git-subtree-dir: lib/vscode git-subtree-split: 3e344b17b7b63b23379a023d575a54ab3a838b6b --- .eslintrc.json | 1 + .github/ISSUE_TEMPLATE/config.yml | 2 +- .github/calendar.yml | 68 +- .github/classifier.json | 12 +- .github/commands.json | 13 + .github/commands.yml | 21 +- .github/endgame/insiders.yml | 10 +- .github/insiders.yml | 10 +- .github/similarity.yml | 6 +- .github/workflows/author-verified.yml | 6 +- .github/workflows/ci.yml | 38 +- .github/workflows/codeql.yml | 61 +- .github/workflows/commands.yml | 4 +- .github/workflows/deep-classifier-monitor.yml | 4 +- .github/workflows/deep-classifier-runner.yml | 4 +- .github/workflows/deep-classifier-scraper.yml | 4 +- .github/workflows/devcontainer-cache.yml | 7 +- .github/workflows/english-please.yml | 4 +- .github/workflows/feature-request.yml | 4 +- .github/workflows/latest-release-monitor.yml | 4 +- .github/workflows/locker.yml | 6 +- .github/workflows/needs-more-info-closer.yml | 6 +- .github/workflows/on-label.yml | 6 +- .github/workflows/on-open.yml | 4 +- .../workflows/release-pipeline-labeler.yml | 4 +- .github/workflows/rich-navigation.yml | 40 +- .../workflows/test-plan-item-validator.yml | 4 +- .vscode/launch.json | 56 +- .vscode/notebooks/api.github-issues | 2 +- .vscode/notebooks/endgame.github-issues | 146 +- .../notebooks/grooming-delta.github-issues | 767 + .vscode/notebooks/grooming.github-issues | 2 +- .vscode/notebooks/my-endgame.github-issues | 218 + .vscode/notebooks/my-work.github-issues | 2 +- .vscode/notebooks/verification.github-issues | 2 +- .yarnrc | 2 +- ThirdPartyNotices.txt | 137 +- azure-pipelines.yml | 5 + build/.cachesalt | 2 +- build/.moduleignore | 114 +- build/.nativeignore | 102 - build/azure-pipelines/common/createAsset.ts | 3 +- build/azure-pipelines/common/createBuild.ts | 3 +- build/azure-pipelines/common/releaseBuild.ts | 3 +- build/azure-pipelines/common/retry.ts | 26 + build/azure-pipelines/common/sync-mooncake.ts | 5 +- .../darwin/continuous-build-darwin.yml | 126 +- .../darwin/product-build-darwin.yml | 537 +- build/azure-pipelines/darwin/publish.sh | 12 +- build/azure-pipelines/distro-build.yml | 54 +- build/azure-pipelines/exploration-build.yml | 53 +- .../linux/alpine/install-dependencies.sh | 5 + build/azure-pipelines/linux/alpine/publish.sh | 28 + .../linux/continuous-build-linux.yml | 148 +- .../linux/multiarch/alpine/build.sh | 3 - .../linux/multiarch/alpine/prebuild.sh | 3 - .../linux/multiarch/alpine/publish.sh | 3 - .../linux/multiarch/arm64/build.sh | 3 - .../linux/multiarch/arm64/prebuild.sh | 3 - .../linux/multiarch/arm64/publish.sh | 3 - .../linux/multiarch/armhf/build.sh | 3 - .../linux/multiarch/armhf/prebuild.sh | 3 - .../linux/multiarch/armhf/publish.sh | 3 - .../linux/product-build-alpine.yml | 135 + .../linux/product-build-linux-multiarch.yml | 115 - .../linux/product-build-linux.yml | 388 +- build/azure-pipelines/linux/publish.sh | 14 +- .../linux/snap-build-linux.yml | 84 +- build/azure-pipelines/product-build.yml | 327 +- build/azure-pipelines/product-compile.yml | 265 +- .../publish-types/publish-types.yml | 128 +- build/azure-pipelines/release.yml | 32 +- build/azure-pipelines/sync-mooncake.yml | 36 +- .../azure-pipelines/web/product-build-web.yml | 209 +- .../win32/continuous-build-win32.yml | 140 +- .../win32/product-build-win32-arm64.yml | 192 - .../win32/product-build-win32.yml | 466 +- build/azure-pipelines/win32/retry.ps1 | 19 + build/darwin/sign.ts | 3 +- build/gulpfile.editor.js | 2 +- build/gulpfile.extensions.js | 2 +- build/gulpfile.vscode.js | 21 +- build/gulpfile.vscode.linux.js | 3 + build/hygiene.js | 3 +- .../eslint/code-no-unexternalized-strings.ts | 5 +- build/lib/i18n.resources.json | 12 + build/lib/reporter.ts | 113 +- build/npm/preinstall.js | 4 +- build/package.json | 4 +- build/win32/code.iss | 124 + build/yarn.lock | 206 +- cglicenses.json | 32 +- cgmanifest.json | 4 +- .../configuration-editing/.vscodeignore | 2 + .../build/inline-allOf.ts | 103 + .../configuration-editing/build/tsconfig.json | 7 + extensions/configuration-editing/package.json | 4 +- .../schemas/attachContainer.schema.json | 2 +- .../devContainer.schema.generated.json | 832 + ...hema.json => devContainer.schema.src.json} | 4 +- extensions/cpp/build/update-grammars.js | 6 +- extensions/cpp/cgmanifest.json | 4 +- extensions/cpp/syntaxes/c.tmLanguage.json | 300 +- .../cpp.embedded.macro.tmLanguage.json | 13421 ++++--------- extensions/cpp/syntaxes/cpp.tmLanguage.json | 15766 ++++++++++------ .../test/colorize-results/test-78769_cpp.json | 14 +- .../test/colorize-results/test-80644_cpp.json | 80 +- .../test/colorize-results/test-92369_cpp.json | 1907 +- .../cpp/test/colorize-results/test_cc.json | 26 +- .../cpp/test/colorize-results/test_cpp.json | 34 +- .../css-language-features/.vscode/launch.json | 24 +- .../client/src/node/cssClientMain.ts | 3 +- .../css-language-features/server/package.json | 2 +- .../server/src/cssServer.ts | 2 +- .../css-language-features/server/yarn.lock | 8 +- extensions/debug-auto-launch/src/extension.ts | 117 +- extensions/debug-server-ready/package.json | 37 +- .../debug-server-ready/package.nls.json | 4 +- .../debug-server-ready/src/extension.ts | 7 +- extensions/emmet/package.json | 7 +- extensions/emmet/package.nls.json | 1 + extensions/emmet/src/abbreviationActions.ts | 1 + extensions/emmet/src/emmetCommon.ts | 4 + .../emmet/src/evaluateMathExpression.ts | 35 +- .../src/test/evaluateMathExpression.test.ts | 52 + .../src/typings/emmetio__math-expression.d.ts | 13 - extensions/emmet/yarn.lock | 62 +- extensions/git/package.json | 145 +- extensions/git/package.nls.json | 29 +- extensions/git/src/api/api1.ts | 2 +- extensions/git/src/api/git.d.ts | 1 + extensions/git/src/commands.ts | 567 +- extensions/git/src/decorationProvider.ts | 10 +- extensions/git/src/git.ts | 78 +- extensions/git/src/remoteSource.ts | 73 +- extensions/git/src/repository.ts | 306 +- extensions/git/src/timelineProvider.ts | 2 +- .../github-authentication/src/extension.ts | 9 + .../github-authentication/src/github.ts | 3 + .../github-authentication/src/githubServer.ts | 10 +- extensions/github/src/publish.ts | 28 +- extensions/github/src/pushErrorHandler.ts | 10 +- extensions/github/src/remoteSourceProvider.ts | 60 +- extensions/github/src/typings/git.d.ts | 2 + .../client/src/htmlClient.ts | 18 +- .../client/src/node/htmlClientMain.ts | 2 +- .../html-language-features/package.json | 39 +- .../html-language-features/package.nls.json | 7 +- .../server/package.json | 4 +- .../server/src/htmlServer.ts | 18 +- .../server/src/modes/cssMode.ts | 6 +- .../server/src/modes/htmlMode.ts | 10 +- .../server/src/modes/javascriptMode.ts | 6 +- .../src/modes/javascriptSemanticTokens.ts | 8 +- .../server/src/modes/languageModes.ts | 4 +- .../server/src/test/semanticTokens.test.ts | 14 +- .../html-language-features/server/yarn.lock | 16 +- extensions/html-language-features/yarn.lock | 8 +- .../snippets/javascript.code-snippets | 2 +- .../syntaxes/JavaScript.tmLanguage.json | 53 +- .../syntaxes/JavaScriptReact.tmLanguage.json | 53 +- .../test/colorize-results/test_jsx.json | 2 +- .../client/src/jsonClient.ts | 21 +- .../client/src/node/jsonClientMain.ts | 2 +- .../json-language-features/package.json | 4 +- .../json-language-features/package.nls.json | 1 + .../server/package.json | 6 +- .../server/src/jsonServer.ts | 8 +- .../json-language-features/server/yarn.lock | 41 +- extensions/json-language-features/yarn.lock | 20 +- extensions/json/cgmanifest.json | 2 +- extensions/json/package.json | 6 +- extensions/json/syntaxes/JSON.tmLanguage.json | 2 +- .../json/syntaxes/JSONC.tmLanguage.json | 2 +- extensions/make/language-configuration.json | 47 +- extensions/markdown-basics/cgmanifest.json | 14 +- .../snippets/markdown.code-snippets | 7 +- .../syntaxes/markdown.tmLanguage.json | 35 +- .../media/markdown.css | 16 +- .../markdown-language-features/package.json | 2 +- .../src/commands/openDocumentLink.ts | 84 +- .../src/features/documentLinkProvider.ts | 21 +- .../src/features/foldingProvider.ts | 3 +- .../src/features/preview.ts | 2 +- .../src/features/smartSelect.ts | 373 +- .../src/features/workspaceSymbolProvider.ts | 3 +- .../src/test/inMemoryDocument.ts | 10 +- .../src/test/smartSelect.test.ts | 351 +- .../src/util/arrays.ts | 4 - .../test-workspace/a.md | 12 +- .../test-workspace/b.md | 4 +- .../markdown-language-features/tsconfig.json | 2 + .../markdown-language-features/yarn.lock | 8 +- .../microsoft-authentication/src/AADHelper.ts | 2 +- extensions/npm/README.md | 2 +- extensions/npm/package.json | 19 +- extensions/npm/package.nls.json | 4 + extensions/npm/src/commands.ts | 8 +- .../npm/src/features/bowerJSONContribution.ts | 12 +- .../npm/src/features/jsonContributions.ts | 28 +- .../src/features/packageJSONContribution.ts | 28 +- extensions/npm/src/npmMain.ts | 11 +- extensions/npm/src/npmView.ts | 47 +- extensions/npm/src/preferred-pm.ts | 80 + extensions/npm/src/scriptHover.ts | 10 +- extensions/npm/src/tasks.ts | 142 +- extensions/npm/tsconfig.json | 2 +- extensions/npm/yarn.lock | 143 + extensions/package.json | 2 +- extensions/php/.vscode/launch.json | 3 +- extensions/php/language-configuration.json | 2 +- extensions/rust/cgmanifest.json | 10 +- extensions/rust/package.json | 39 +- extensions/rust/syntaxes/rust.tmLanguage.json | 1589 +- .../test/colorize-results/test-6611_rs.json | 1094 +- .../rust/test/colorize-results/test_rs.json | 721 +- extensions/search-result/package.json | 1 + extensions/search-result/src/extension.ts | 55 +- .../syntaxes/generateTMLanguage.js | 5 + .../syntaxes/searchResult.tmLanguage.json | 4 + extensions/sql/build/update-grammar.js | 10 + extensions/sql/cgmanifest.json | 2 +- extensions/sql/package.json | 37 +- extensions/sql/syntaxes/sql.tmLanguage.json | 10 +- extensions/theme-abyss/package.json | 5 +- extensions/theme-abyss/package.nls.json | 5 +- .../theme-abyss/themes/abyss-color-theme.json | 4 +- extensions/theme-defaults/package.json | 14 +- extensions/theme-defaults/package.nls.json | 10 +- .../themes/hc_black_defaults.json | 11 +- .../theme-defaults/themes/light_defaults.json | 5 +- extensions/theme-kimbie-dark/package.json | 5 +- extensions/theme-kimbie-dark/package.nls.json | 5 +- .../themes/kimbie-dark-color-theme.json | 2 +- extensions/theme-monokai-dimmed/package.json | 5 +- .../theme-monokai-dimmed/package.nls.json | 5 +- extensions/theme-monokai/package.json | 5 +- extensions/theme-monokai/package.nls.json | 5 +- .../themes/monokai-color-theme.json | 6 +- extensions/theme-quietlight/package.json | 5 +- extensions/theme-quietlight/package.nls.json | 5 +- .../themes/quietlight-color-theme.json | 36 +- extensions/theme-red/package.json | 5 +- extensions/theme-red/package.nls.json | 5 +- extensions/theme-seti/cgmanifest.json | 2 +- extensions/theme-seti/icons/seti.woff | Bin 35436 -> 35676 bytes .../theme-seti/icons/vs-seti-icon-theme.json | 292 +- extensions/theme-seti/package.json | 2 +- extensions/theme-seti/package.nls.json | 5 +- extensions/theme-solarized-dark/package.json | 5 +- .../theme-solarized-dark/package.nls.json | 5 +- .../themes/solarized-dark-color-theme.json | 4 +- extensions/theme-solarized-light/package.json | 5 +- .../theme-solarized-light/package.nls.json | 5 +- .../themes/solarized-light-color-theme.json | 4 +- .../theme-tomorrow-night-blue/package.json | 7 +- .../package.nls.json | 5 +- ...n => tomorrow-night-blue-color-theme.json} | 16 +- extensions/typescript-basics/cgmanifest.json | 2 +- .../syntaxes/TypeScript.tmLanguage.json | 51 +- .../syntaxes/TypeScriptReact.tmLanguage.json | 51 +- .../typescript-language-features/package.json | 60 +- .../package.nls.json | 16 +- .../codeLens/baseCodeLensProvider.ts | 4 +- .../codeLens/implementationsCodeLens.ts | 4 +- .../codeLens/referencesCodeLens.ts | 10 +- .../src/languageFeatures/completions.ts | 9 +- .../fileConfigurationManager.ts | 18 +- .../src/languageFeatures/folding.ts | 20 +- .../src/languageFeatures/quickFix.ts | 85 +- .../src/languageFeatures/refactor.ts | 18 +- .../src/languageFeatures/semanticTokens.ts | 6 +- .../src/tsServer/bufferSyncSupport.ts | 9 +- .../src/tsServer/serverProcess.browser.ts | 16 + .../src/tsServer/spawner.ts | 22 +- .../src/tsServer/versionStatus.ts | 5 +- .../src/typeScriptServiceClientHost.ts | 8 +- .../src/typescriptService.ts | 4 +- .../src/typescriptServiceClient.ts | 12 +- .../src/utils/configuration.ts | 74 +- .../src/utils/resourceMap.ts | 13 +- .../src/utils/tsconfig.ts | 12 +- .../src/singlefolder-tests/workspace.test.ts | 34 + extensions/yarn.lock | 8 +- package.json | 25 +- product.json | 12 +- remote/package.json | 3 +- remote/yarn.lock | 27 +- resources/linux/debian/postinst.template | 2 +- resources/linux/rpm/dependencies.json | 239 +- resources/linux/snap/snapcraft.yaml | 4 + resources/web/code-web.js | 4 + scripts/code.bat | 1 - scripts/code.sh | 1 - scripts/test-integration.sh | 25 + src/bootstrap-amd.js | 6 +- src/bootstrap-fork.js | 10 +- src/bootstrap-node.js | 70 + src/bootstrap-window.js | 110 +- src/bootstrap.js | 121 +- src/buildfile.js | 2 +- src/cli.js | 5 +- src/main.js | 34 +- src/paths.js | 45 +- src/vs/base/browser/contextmenu.ts | 3 +- src/vs/base/browser/dom.ts | 46 +- src/vs/base/browser/globalMouseMoveMonitor.ts | 12 +- src/vs/base/browser/hash.ts | 25 + src/vs/base/browser/markdownRenderer.ts | 94 +- src/vs/base/browser/ui/actionbar/actionbar.ts | 15 +- .../ui/breadcrumbs/breadcrumbsWidget.ts | 4 +- src/vs/base/browser/ui/button/button.css | 11 +- src/vs/base/browser/ui/button/button.ts | 167 +- src/vs/base/browser/ui/checkbox/checkbox.ts | 4 +- .../browser/ui/codicons/codicon/codicon.ttf | Bin 62324 -> 62836 bytes .../base/browser/ui/codicons/codiconStyles.ts | 13 +- .../browser/ui/contextview/contextview.ts | 70 +- src/vs/base/browser/ui/dialog/dialog.css | 3 +- src/vs/base/browser/ui/dialog/dialog.ts | 29 +- src/vs/base/browser/ui/dropdown/dropdown.css | 4 + .../ui/dropdown/dropdownActionViewItem.ts | 14 +- src/vs/base/browser/ui/grid/grid.ts | 4 +- src/vs/base/browser/ui/grid/gridview.ts | 40 +- src/vs/base/browser/ui/hover/hover.css | 1 - src/vs/base/browser/ui/iconLabel/iconLabel.ts | 66 +- .../base/browser/ui/iconLabel/iconlabel.css | 4 +- src/vs/base/browser/ui/list/listPaging.ts | 4 + src/vs/base/browser/ui/list/listView.ts | 4 +- src/vs/base/browser/ui/list/listWidget.ts | 14 +- src/vs/base/browser/ui/menu/menu.ts | 14 +- src/vs/base/browser/ui/menu/menubar.ts | 6 +- .../browser/ui/progressbar/progressbar.css | 9 +- .../browser/ui/progressbar/progressbar.ts | 1 + src/vs/base/browser/ui/sash/sash.css | 43 +- src/vs/base/browser/ui/sash/sash.ts | 12 + .../browser/ui/scrollbar/abstractScrollbar.ts | 12 +- .../ui/scrollbar/horizontalScrollbar.ts | 9 +- .../browser/ui/scrollbar/scrollableElement.ts | 11 +- .../ui/scrollbar/scrollableElementOptions.ts | 6 + .../browser/ui/scrollbar/scrollbarState.ts | 22 + .../browser/ui/scrollbar/verticalScrollbar.ts | 9 +- src/vs/base/browser/ui/selectBox/selectBox.ts | 1 + .../browser/ui/selectBox/selectBoxCustom.css | 9 + .../browser/ui/selectBox/selectBoxCustom.ts | 17 +- src/vs/base/browser/ui/splitview/paneview.css | 19 +- .../base/browser/ui/splitview/splitview.css | 21 +- src/vs/base/browser/ui/splitview/splitview.ts | 36 +- src/vs/base/browser/ui/toolbar/toolbar.ts | 7 +- src/vs/base/browser/ui/tree/abstractTree.ts | 20 +- src/vs/base/browser/ui/tree/asyncDataTree.ts | 6 +- .../ui/tree/compressedObjectTreeModel.ts | 9 +- src/vs/base/browser/ui/tree/objectTree.ts | 5 +- src/vs/base/browser/ui/tree/tree.ts | 2 +- src/vs/base/browser/ui/tree/treeIcons.ts | 12 +- src/vs/base/common/actions.ts | 21 +- src/vs/base/common/amd.ts | 131 + src/vs/base/common/arrays.ts | 14 + src/vs/base/common/async.ts | 39 + src/vs/base/common/codicons.ts | 25 +- src/vs/base/common/collections.ts | 30 + src/vs/base/common/diff/diff.ts | 72 + src/vs/base/common/event.ts | 2 +- src/vs/base/common/hash.ts | 10 +- src/vs/base/common/htmlContent.ts | 40 +- src/vs/base/common/json.ts | 2 +- src/vs/base/common/labels.ts | 4 + src/vs/base/common/linkedList.ts | 8 - src/vs/base/common/map.ts | 100 +- src/vs/base/common/marked/cgmanifest.json | 4 +- src/vs/base/common/marked/marked.js | 109 +- src/vs/base/common/network.ts | 51 + src/vs/base/common/performance.js | 4 +- src/vs/base/common/platform.ts | 9 +- src/vs/base/common/process.ts | 14 +- src/vs/base/common/resourceTree.ts | 12 +- src/vs/base/common/resources.ts | 38 +- src/vs/base/common/strings.ts | 24 +- src/vs/base/common/uri.ts | 2 +- src/vs/base/common/uuid.ts | 5 +- src/vs/base/common/worker/simpleWorker.ts | 4 + src/vs/base/node/paths.ts | 9 +- src/vs/base/parts/ipc/common/ipc.net.ts | 39 +- .../quickinput/browser/media/quickInput.css | 5 + .../parts/quickinput/browser/quickInput.ts | 35 +- .../parts/sandbox/electron-browser/preload.js | 132 +- .../parts/sandbox/electron-sandbox/globals.ts | 42 +- src/vs/base/parts/storage/common/storage.ts | 32 +- .../parts/storage/test/node/storage.test.ts | 14 + src/vs/base/test/browser/comparers.test.ts | 4 +- .../test/{common => browser}/hash.test.ts | 24 +- .../test/browser/markdownRenderer.test.ts | 22 +- .../ui/scrollbar/scrollbarState.test.ts | 10 +- .../browser/ui/splitview/splitview.test.ts | 8 +- src/vs/base/test/common/async.test.ts | 79 +- src/vs/base/test/common/linkedList.test.ts | 2 +- src/vs/base/test/common/map.test.ts | 153 +- .../base/test/common/markdownString.test.ts | 18 +- src/vs/base/test/common/network.test.ts | 70 + src/vs/base/test/common/processes.test.ts | 1 - src/vs/base/test/common/strings.test.ts | 5 + src/vs/base/worker/workerMain.ts | 10 +- .../code/browser/workbench/workbench-dev.html | 9 +- src/vs/code/browser/workbench/workbench.html | 9 +- src/vs/code/browser/workbench/workbench.ts | 8 + .../sharedProcess/sharedProcessMain.ts | 12 +- .../electron-browser/workbench/workbench.js | 31 +- src/vs/code/electron-main/app.ts | 269 +- src/vs/code/electron-main/auth2.ts | 3 +- src/vs/code/electron-main/main.ts | 12 +- src/vs/code/electron-main/protocol.ts | 108 + src/vs/code/electron-main/sharedProcess.ts | 5 +- src/vs/code/electron-main/window.ts | 81 +- .../issue/issueReporterMain.ts | 19 +- .../issue/issueReporterModel.ts | 27 +- .../issue/issueReporterPage.ts | 26 +- .../issue/test/testReporterModel.test.ts | 94 +- .../processExplorer/processExplorerMain.ts | 16 +- .../electron-sandbox/workbench/workbench.js | 29 +- src/vs/code/node/cli.ts | 62 +- src/vs/code/node/cliProcessMain.ts | 84 +- src/vs/code/node/shellEnv.ts | 84 +- .../test/electron-main/nativeHelpers.test.ts | 40 - .../editor/browser/controller/mouseHandler.ts | 15 +- .../editor/browser/controller/mouseTarget.ts | 58 +- .../browser/controller/pointerHandler.ts | 2 +- .../browser/controller/textAreaHandler.ts | 2 + .../browser/controller/textAreaInput.ts | 7 +- .../editor/browser/core/markdownRenderer.ts | 8 +- src/vs/editor/browser/editorBrowser.ts | 13 +- src/vs/editor/browser/editorDom.ts | 8 +- src/vs/editor/browser/editorExtensions.ts | 43 - .../services/abstractCodeEditorService.ts | 3 + .../browser/services/bulkEditService.ts | 6 + .../browser/services/codeEditorService.ts | 2 + .../browser/services/codeEditorServiceImpl.ts | 30 +- .../editor/browser/services/openerService.ts | 26 +- .../browser/view/domLineBreaksComputer.ts | 9 +- src/vs/editor/browser/view/viewController.ts | 4 + .../browser/view/viewUserInputEvents.ts | 7 + .../currentLineHighlight.ts | 23 +- .../editorScrollbar/editorScrollbar.ts | 1 + .../browser/viewParts/minimap/minimap.ts | 11 +- .../viewParts/viewCursors/viewCursors.ts | 17 +- .../editor/browser/widget/codeEditorWidget.ts | 35 +- .../editor/browser/widget/diffEditorWidget.ts | 1098 +- src/vs/editor/browser/widget/diffReview.ts | 17 +- .../editor/browser/widget/inlineDiffMargin.ts | 38 +- .../common/config/commonEditorConfig.ts | 20 + src/vs/editor/common/config/editorOptions.ts | 355 +- src/vs/editor/common/controller/cursor.ts | 2 +- .../controller/cursorAtomicMoveOperations.ts | 160 + .../editor/common/controller/cursorCommon.ts | 15 +- .../controller/cursorDeleteOperations.ts | 2 +- .../common/controller/cursorMoveCommands.ts | 12 +- .../common/controller/cursorMoveOperations.ts | 29 +- .../common/controller/cursorTypeOperations.ts | 91 +- .../common/controller/cursorWordOperations.ts | 118 +- src/vs/editor/common/editorContextKeys.ts | 1 + src/vs/editor/common/model.ts | 27 +- src/vs/editor/common/model/editStack.ts | 17 + src/vs/editor/common/model/mirrorTextModel.ts | 3 +- .../pieceTreeTextBuffer.ts | 12 +- src/vs/editor/common/model/textModel.ts | 24 +- src/vs/editor/common/model/tokensStore.ts | 4 + src/vs/editor/common/modes.ts | 42 +- .../common/modes/languageConfiguration.ts | 29 +- .../modes/languageConfigurationRegistry.ts | 6 + .../common/modes/supports/indentRules.ts | 16 +- .../common/modes/textToHtmlTokenizer.ts | 2 +- .../common/services/editorSimpleWorker.ts | 38 +- .../services/editorWorkerServiceImpl.ts | 59 +- .../editor/common/services/getIconClasses.ts | 4 +- .../common/services/modelServiceImpl.ts | 4 + .../common/standalone/standaloneEnums.ts | 220 +- src/vs/editor/common/view/viewEvents.ts | 16 +- .../common/viewLayout/lineDecorations.ts | 18 + .../editor/common/viewLayout/linesLayout.ts | 17 + src/vs/editor/common/viewLayout/viewLayout.ts | 7 + .../common/viewLayout/viewLineRenderer.ts | 2 +- .../viewModel/monospaceLineBreaksComputer.ts | 3 +- .../common/viewModel/splitLinesCollection.ts | 75 +- .../common/viewModel/viewEventHandler.ts | 19 +- src/vs/editor/common/viewModel/viewModel.ts | 64 + .../editor/common/viewModel/viewModelImpl.ts | 14 +- .../editor/contrib/codeAction/codeAction.ts | 6 +- .../contrib/codeAction/codeActionMenu.ts | 8 +- .../test/codeActionKeybindingResolver.test.ts | 3 +- .../editor/contrib/codelens/codeLensCache.ts | 4 +- src/vs/editor/contrib/codelens/codelens.ts | 17 +- .../contrib/codelens/codelensController.ts | 51 +- .../editor/contrib/codelens/codelensWidget.ts | 31 +- src/vs/editor/contrib/colorPicker/color.ts | 15 +- .../contrib/colorPicker/colorContributions.ts | 57 + .../contrib/colorPicker/colorDetector.ts | 10 +- .../contrib/colorPicker/colorPickerWidget.ts | 2 +- src/vs/editor/contrib/comment/comment.ts | 28 +- .../contrib/comment/lineCommentCommand.ts | 26 +- .../comment/test/lineCommentCommand.test.ts | 4 +- src/vs/editor/contrib/dnd/dnd.ts | 11 + src/vs/editor/contrib/find/findController.ts | 79 +- .../editor/contrib/find/findOptionsWidget.ts | 2 +- src/vs/editor/contrib/find/findWidget.ts | 115 +- .../contrib/find/test/findController.test.ts | 97 +- src/vs/editor/contrib/folding/folding.ts | 6 +- .../contrib/folding/foldingDecorations.ts | 18 +- src/vs/editor/contrib/format/formatActions.ts | 7 +- src/vs/editor/contrib/gotoError/gotoError.ts | 7 +- .../contrib/gotoError/gotoErrorWidget.ts | 5 +- .../editor/contrib/gotoSymbol/goToCommands.ts | 3 + .../gotoSymbol/peek/referencesController.ts | 18 +- .../contrib/gotoSymbol/referencesModel.ts | 18 +- src/vs/editor/contrib/hover/hover.ts | 9 + .../editor/contrib/hover/modesContentHover.ts | 58 +- .../editor/contrib/indentation/indentation.ts | 14 +- .../linesOperations/copyLinesCommand.ts | 14 +- .../linesOperations/linesOperations.ts | 5 +- .../linesOperations/moveLinesCommand.ts | 97 +- .../test/moveLinesCommand.test.ts | 53 + .../linkedEditing.ts} | 96 +- .../test/linkedEditing.test..ts} | 47 +- src/vs/editor/contrib/links/links.ts | 12 +- .../contrib/message/messageController.css | 16 + .../contrib/message/messageController.ts | 35 +- .../editor/contrib/multicursor/multicursor.ts | 5 + .../multicursor/test/multicursor.test.ts | 8 +- .../parameterHints/parameterHintsWidget.ts | 13 +- .../parameterHints/provideSignatureHelp.ts | 41 +- .../editorNavigationQuickAccess.ts | 27 +- .../quickAccess/gotoLineQuickAccess.ts | 7 +- .../quickAccess/gotoSymbolQuickAccess.ts | 22 +- src/vs/editor/contrib/rename/rename.ts | 2 +- .../editor/contrib/rename/renameInputField.ts | 2 +- .../editor/contrib/smartSelect/smartSelect.ts | 180 +- .../editor/contrib/snippet/snippetSession.ts | 81 +- .../contrib/snippet/snippetVariables.ts | 4 +- .../snippet/test/snippetController2.test.ts | 9 +- .../snippet/test/snippetSession.test.ts | 78 + .../snippet/test/snippetVariables.test.ts | 3 +- src/vs/editor/contrib/suggest/resizable.ts | 6 +- src/vs/editor/contrib/suggest/suggest.ts | 58 +- .../editor/contrib/suggest/suggestMemory.ts | 9 +- src/vs/editor/contrib/suggest/suggestModel.ts | 11 +- .../editor/contrib/suggest/suggestWidget.ts | 444 +- .../contrib/suggest/suggestWidgetDetails.ts | 6 +- .../contrib/suggest/suggestWidgetRenderer.ts | 52 +- .../viewportSemanticTokens.ts | 8 +- .../test/wordOperations.test.ts | 121 +- .../contrib/wordOperations/wordOperations.ts | 34 +- src/vs/editor/editor.all.ts | 4 +- src/vs/editor/editor.api.ts | 8 +- src/vs/editor/standalone/browser/colorizer.ts | 7 +- .../standalone/browser/simpleServices.ts | 7 +- .../browser/standaloneCodeEditor.ts | 4 + .../standalone/browser/standaloneEditor.ts | 3 +- .../standalone/browser/standaloneLanguages.ts | 33 +- .../standalone/browser/standaloneServices.ts | 3 - .../browser/standaloneThemeServiceImpl.ts | 11 +- .../test/browser/controller/cursor.test.ts | 122 +- .../test/browser/controller/imeTester.ts | 19 +- .../editor/test/browser/editorTestServices.ts | 1 + .../services/decorationRenderOptions.test.ts | 16 +- .../cursorAtomicMoveOperations.test.ts | 152 + .../test/common/diff/diffComputer.test.ts | 27 + .../textBufferAutoTestUtils.ts | 7 +- .../pieceTreeTextBuffer.test.ts | 15 +- .../services/editorSimpleWorker.test.ts | 2 +- .../viewLayout/editorLayoutProvider.test.ts | 4 +- .../monospaceLineBreaksComputer.test.ts | 3 +- .../viewModel/splitLinesCollection.test.ts | 4 +- src/vs/loader.js | 88 +- src/vs/monaco.d.ts | 366 +- .../browser/menuEntryActionViewItem.ts | 8 +- src/vs/platform/actions/common/actions.ts | 2 +- .../backup/electron-main/backupMainService.ts | 41 +- src/vs/platform/backup/node/backup.ts | 5 - .../electron-main/backupMainService.test.ts | 70 +- .../configuration/common/configuration.ts | 2 +- .../common/configurationRegistry.ts | 3 + .../test/common/configurationModels.test.ts | 3 +- .../contextkey/browser/contextKeyService.ts | 22 +- .../platform/contextkey/common/contextkey.ts | 334 +- .../contextkey/test/common/contextkey.test.ts | 80 + .../contextview/browser/contextMenuHandler.ts | 3 +- .../contextview/browser/contextView.ts | 3 +- .../diagnostics/node/diagnosticsService.ts | 14 +- src/vs/platform/dialogs/common/dialogs.ts | 66 +- .../display/common/displayMainService.ts} | 9 +- .../electron-main/displayMainService.ts | 47 + src/vs/platform/environment/common/argv.ts | 3 +- .../environment/common/environment.ts | 2 +- .../electron-main/environmentMainService.ts | 8 +- src/vs/platform/environment/node/argv.ts | 10 +- .../platform/environment/node/argvHelper.ts | 8 +- .../environment/node/environmentService.ts | 11 +- .../environment}/test/node/argv.test.ts | 1 + .../test/node/nativeModules.test.ts | 105 + .../common/extensionEnablementService.ts | 23 +- .../common/extensionGalleryService.ts | 4 +- .../common/extensionManagement.ts | 5 +- .../common/extensionManagementIpc.ts | 36 +- .../common/extensionUrlTrust.ts | 13 + .../electron-sandbox/extensionTipsService.ts | 6 +- .../node/extensionDownloader.ts | 31 +- .../node/extensionManagementService.ts | 37 +- .../node/extensionUrlTrustService.ts | 97 + .../node/extensionsScanner.ts | 2 +- src/vs/platform/files/common/fileService.ts | 3 +- src/vs/platform/files/common/files.ts | 30 +- .../nsfw/test/nsfwWatcherService.test.ts | 24 +- .../watcher/unix/chokidarWatcherService.ts | 2 +- .../unix/test/chockidarWatcherService.test.ts | 44 +- src/vs/platform/issue/common/issue.ts | 1 + .../issue/electron-main/issueMainService.ts | 48 +- .../keybinding/common/keybindingResolver.ts | 15 +- .../keybinding/common/keybindingsRegistry.ts | 8 +- .../common/resolvedKeybindingItem.ts | 4 +- .../common/abstractKeybindingService.test.ts | 3 +- .../test/common/keybindingResolver.test.ts | 3 +- .../keyboardLayout}/common/dispatchConfig.ts | 0 .../keyboardLayout/common/keyboardLayout.ts | 260 + .../common/keyboardLayoutMainService.ts | 18 + .../keyboardLayout}/common/keyboardMapper.ts | 0 .../keyboardLayoutMainService.ts | 59 + .../launch/electron-main/launchMainService.ts | 16 +- .../electron-main/lifecycleMainService.ts | 10 +- src/vs/platform/list/browser/listService.ts | 91 +- src/vs/platform/log/common/fileLogService.ts | 4 +- src/vs/platform/log/common/log.ts | 52 +- src/vs/platform/log/node/spdlogService.ts | 3 +- .../platform/menubar/electron-main/menubar.ts | 9 +- .../electron-main/nativeHostMainService.ts | 12 +- .../notification/common/notification.ts | 34 +- src/vs/platform/opener/common/opener.ts | 6 + src/vs/platform/product/common/product.ts | 3 +- .../platform/product/common/productService.ts | 1 + .../quickinput/browser/commandsQuickAccess.ts | 12 +- src/vs/platform/remote/common/tunnel.ts | 23 +- src/vs/platform/remote/node/tunnelService.ts | 5 +- .../common/serviceMachineId.ts | 6 +- .../storage/browser/storageService.ts | 43 +- src/vs/platform/storage/common/storage.ts | 348 +- .../platform/storage/node/storageService.ts | 50 +- .../test/common/storageService.test.ts | 198 + .../storage/test/node/storageService.test.ts | 75 +- .../telemetry/browser/errorTelemetry.ts | 2 +- .../telemetry/node/appInsightsAppender.ts | 58 +- .../platform/telemetry/node/errorTelemetry.ts | 2 +- src/vs/platform/theme/browser/checkbox.ts | 26 - src/vs/platform/theme/common/colorRegistry.ts | 9 +- src/vs/platform/theme/common/iconRegistry.ts | 82 +- src/vs/platform/theme/common/themeService.ts | 68 +- .../common/tokenClassificationRegistry.ts | 5 +- .../theme/electron-main/themeMainService.ts | 4 +- src/vs/platform/undoRedo/common/undoRedo.ts | 31 +- .../undoRedo/common/undoRedoService.ts | 161 +- .../electron-main/updateService.darwin.ts | 3 +- src/vs/platform/url/common/url.ts | 2 + src/vs/platform/url/common/urlIpc.ts | 4 +- .../url/electron-main/electronUrlListener.ts | 23 +- .../common/extensionsStorageSync.ts | 97 + .../userDataSync/common/extensionsSync.ts | 67 +- .../userDataSync/common/globalStateMerge.ts | 119 +- .../userDataSync/common/globalStateSync.ts | 67 +- .../userDataSync/common/keybindingsSync.ts | 39 +- .../userDataSync/common/settingsSync.ts | 3 +- .../userDataSync/common/storageKeys.ts | 134 - .../common/userDataAutoSyncService.ts | 18 +- .../userDataSync/common/userDataSync.ts | 6 +- .../userDataSync/common/userDataSyncIpc.ts | 64 - .../common/userDataSyncMachines.ts | 4 +- .../userDataSyncResourceEnablementService.ts | 12 +- .../common/userDataSyncService.ts | 30 +- .../common/userDataSyncStoreService.ts | 83 +- .../test/common/globalStateMerge.test.ts | 199 +- .../test/common/globalStateSync.test.ts | 45 +- .../test/common/userDataSyncClient.ts | 4 +- .../common/userDataSyncStoreService.test.ts | 14 +- .../webview/common/webviewManagerService.ts | 10 +- .../electron-main/webviewMainService.ts | 27 +- src/vs/platform/windows/common/windows.ts | 22 +- .../platform/windows/electron-main/windows.ts | 13 +- .../electron-main/windowsMainService.ts | 346 +- .../electron-main/windowsStateStorage.test.ts | 0 src/vs/platform/workspace/common/workspace.ts | 10 +- .../workspace/test/common/testWorkspace.ts | 15 +- .../workspace/test/common/workspace.test.ts | 12 +- .../workspacesHistoryMainService.ts | 92 +- src/vs/vscode.d.ts | 253 +- src/vs/vscode.proposed.d.ts | 550 +- .../api/browser/extensionHost.contribution.ts | 1 + .../api/browser/mainThreadAuthentication.ts | 35 +- .../api/browser/mainThreadBulkEdits.ts | 10 +- .../api/browser/mainThreadCodeInsets.ts | 4 +- .../api/browser/mainThreadCommands.ts | 30 +- .../api/browser/mainThreadComments.ts | 9 +- .../api/browser/mainThreadCustomEditors.ts | 7 +- .../api/browser/mainThreadDebugService.ts | 14 +- .../api/browser/mainThreadDialogs.ts | 19 +- .../browser/mainThreadDocumentsAndEditors.ts | 44 +- .../api/browser/mainThreadEditors.ts | 63 +- .../mainThreadFileSystemEventService.ts | 12 +- .../api/browser/mainThreadLanguageFeatures.ts | 31 +- .../api/browser/mainThreadNotebook.ts | 111 +- src/vs/workbench/api/browser/mainThreadSCM.ts | 8 +- .../api/browser/mainThreadStatusBar.ts | 4 +- .../api/browser/mainThreadStorage.ts | 17 +- .../workbench/api/browser/mainThreadTask.ts | 4 +- .../api/browser/mainThreadTesting.ts | 77 + .../api/browser/mainThreadTreeViews.ts | 14 +- .../api/browser/mainThreadTunnelService.ts | 40 +- .../api/browser/mainThreadWebviewPanels.ts | 5 +- .../api/browser/viewsExtensionPoint.ts | 21 +- src/vs/workbench/api/common/apiCommands.ts | 175 +- .../api/common/configurationExtensionPoint.ts | 4 + .../workbench/api/common/extHost.api.impl.ts | 231 +- .../workbench/api/common/extHost.protocol.ts | 97 +- .../api/common/extHostApiCommands.ts | 451 +- .../workbench/api/common/extHostCommands.ts | 127 +- .../api/common/extHostCustomEditors.ts | 4 +- .../api/common/extHostDebugService.ts | 82 +- .../api/common/extHostDecorations.ts | 6 +- .../common/extHostDocumentContentProviders.ts | 3 +- .../common/extHostFileSystemEventService.ts | 12 +- .../api/common/extHostLanguageFeatures.ts | 115 +- .../workbench/api/common/extHostNotebook.ts | 54 +- .../workbench/api/common/extHostQuickOpen.ts | 6 +- src/vs/workbench/api/common/extHostSCM.ts | 82 +- .../workbench/api/common/extHostStatusBar.ts | 51 +- src/vs/workbench/api/common/extHostStorage.ts | 2 +- src/vs/workbench/api/common/extHostTask.ts | 27 +- src/vs/workbench/api/common/extHostTesting.ts | 794 + .../workbench/api/common/extHostTreeViews.ts | 26 +- .../api/common/extHostTunnelService.ts | 15 +- .../api/common/extHostTypeConverters.ts | 123 +- src/vs/workbench/api/common/extHostTypes.ts | 193 +- .../api/common/extHostWebviewPanels.ts | 4 +- .../workbench/api/common/extHostWorkspace.ts | 44 +- .../api/common/menusExtensionPoint.ts | 36 +- src/vs/workbench/api/common/shared/editor.ts | 39 - src/vs/workbench/api/common/shared/tasks.ts | 2 +- src/vs/workbench/api/node/extHostCLIServer.ts | 22 +- .../workbench/api/node/extHostDebugService.ts | 90 +- src/vs/workbench/api/node/extHostTask.ts | 4 + .../api/node/extHostTerminalService.ts | 11 +- .../api/node/extHostTunnelService.ts | 70 +- .../browser/actions/layoutActions.ts | 14 +- .../workbench/browser/actions/listCommands.ts | 20 +- .../browser/actions/navigationActions.ts | 31 +- .../browser/actions/workspaceActions.ts | 2 +- .../browser/actions/workspaceCommands.ts | 51 +- src/vs/workbench/browser/dnd.ts | 6 +- src/vs/workbench/browser/layout.ts | 231 +- src/vs/workbench/browser/media/style.css | 4 + .../parts/activitybar/activitybarActions.ts | 396 +- .../parts/activitybar/activitybarPart.ts | 279 +- .../activitybar/media/activityaction.css | 4 - .../activitybar/media/activitybarpart.css | 8 - .../workbench/browser/parts/compositePart.ts | 4 +- .../parts/dialogs/dialog.web.contribution.ts | 76 + .../parts/dialogs/dialogHandler.ts} | 11 +- .../browser/parts/editor/binaryEditor.ts | 8 +- .../parts/editor/breadcrumbsControl.ts | 8 +- .../browser/parts/editor/breadcrumbsPicker.ts | 3 +- .../parts/editor/editor.contribution.ts | 50 +- .../workbench/browser/parts/editor/editor.ts | 7 +- .../browser/parts/editor/editorCommands.ts | 242 +- .../browser/parts/editor/editorDropTarget.ts | 49 +- .../browser/parts/editor/editorGroupView.ts | 23 +- .../browser/parts/editor/editorPane.ts | 40 +- .../browser/parts/editor/editorPart.ts | 6 +- .../browser/parts/editor/editorStatus.ts | 19 +- .../browser/parts/editor/editorWidgets.ts | 2 + .../browser/parts/editor/editorsObserver.ts | 4 +- .../parts/editor/media/breadcrumbscontrol.css | 2 +- .../browser/parts/editor/tabsTitleControl.ts | 4 +- .../browser/parts/editor/textDiffEditor.ts | 47 +- .../browser/parts/editor/textEditor.ts | 30 +- .../parts/editor/textResourceEditor.ts | 8 +- .../notifications/media/notificationsList.css | 15 +- .../media/notificationsToasts.css | 2 +- .../notifications/notificationsActions.ts | 28 +- .../notifications/notificationsCenter.ts | 2 +- .../notifications/notificationsCommands.ts | 32 +- .../notifications/notificationsToasts.ts | 2 +- .../notifications/notificationsViewer.ts | 44 +- .../browser/parts/panel/media/panelpart.css | 4 - .../browser/parts/panel/panelActions.ts | 16 +- .../browser/parts/panel/panelPart.ts | 13 +- .../browser/parts/statusbar/statusbarPart.ts | 12 +- .../browser/parts/titlebar/menubarControl.ts | 101 +- .../browser/parts/titlebar/titlebarPart.ts | 10 +- .../parts/views}/media/views.css | 6 +- .../workbench/browser/parts/views/treeView.ts | 1092 +- .../browser/parts/views/viewPaneContainer.ts | 59 +- src/vs/workbench/browser/web.main.ts | 12 +- .../browser/workbench.contribution.ts | 11 +- src/vs/workbench/browser/workbench.ts | 7 +- src/vs/workbench/common/component.ts | 6 +- src/vs/workbench/common/dialogs.ts | 50 + src/vs/workbench/common/editor.ts | 74 +- .../common/editor/diffEditorInput.ts | 52 +- .../common/editor/resourceEditorInput.ts | 1 + .../common/editor/textResourceEditorInput.ts | 32 +- src/vs/workbench/common/memento.ts | 12 +- src/vs/workbench/common/notifications.ts | 8 +- src/vs/workbench/common/theme.ts | 14 +- src/vs/workbench/common/views.ts | 38 +- .../electron-browser/backupRestorer.test.ts | 2 +- .../electron-browser/backupTracker.test.ts | 5 +- .../contrib/bulkEdit/browser/bulkCellEdits.ts | 13 +- .../bulkEdit/browser/bulkEditService.ts | 68 +- .../contrib/bulkEdit/browser/bulkFileEdits.ts | 110 +- .../contrib/bulkEdit/browser/bulkTextEdits.ts | 32 +- .../browser/preview/bulkEdit.contribution.ts | 17 +- .../bulkEdit/browser/preview/bulkEditPane.ts | 15 +- .../browser/preview/bulkEditPreview.ts | 3 +- .../bulkEdit/browser/preview/bulkEditTree.ts | 15 +- .../browser/callHierarchy.contribution.ts | 11 +- .../browser/callHierarchyPeek.ts | 8 +- .../contrib/cli/node/cli.contribution.ts | 192 +- .../browser/accessibility/accessibility.ts | 4 +- .../codeEditor/browser/diffEditorHelper.ts | 2 +- .../browser/find/simpleFindReplaceWidget.ts | 15 +- .../browser/find/simpleFindWidget.ts | 11 +- .../browser/largeFileOptimizations.ts | 8 +- .../quickaccess/gotoLineQuickAccess.ts | 8 +- .../quickaccess/gotoSymbolQuickAccess.ts | 12 +- .../codeEditor/browser/saveParticipants.ts | 12 +- .../browser/toggleColumnSelection.ts | 4 +- .../codeEditor/browser/toggleMinimap.ts | 4 +- .../browser/toggleMultiCursorModifier.ts | 4 +- .../browser/toggleRenderControlCharacter.ts | 4 +- .../browser/toggleRenderWhitespace.ts | 4 +- .../codeEditor/browser/toggleWordWrap.ts | 169 +- .../electron-browser/startDebugTextMate.ts | 3 +- .../codeEditor.contribution.ts | 1 + .../displayChangeRemeasureFonts.ts | 28 + .../contrib/comments/browser/commentNode.ts | 3 +- .../comments/browser/commentThreadWidget.ts | 28 +- .../contrib/comments/browser/commentsView.ts | 37 +- .../contrib/comments/browser/media/panel.css | 7 +- .../contrib/comments/common/commentModel.ts | 2 +- .../customEditor/browser/customEditors.ts | 6 +- .../common/contributedCustomEditors.ts | 8 +- .../browser/breakpointEditorContribution.ts | 48 +- .../contrib/debug/browser/breakpointsView.ts | 253 +- .../browser/callStackEditorContribution.ts | 26 +- .../contrib/debug/browser/callStackView.ts | 27 +- .../debug/browser/debug.contribution.ts | 41 +- .../debug/browser/debugANSIHandling.ts | 12 +- .../debug/browser/debugActionViewItems.ts | 44 +- .../contrib/debug/browser/debugActions.ts | 27 +- .../debug/browser/debugAdapterManager.ts | 268 + .../contrib/debug/browser/debugCommands.ts | 8 +- .../browser/debugConfigurationManager.ts | 375 +- .../debug/browser/debugEditorActions.ts | 160 +- .../debug/browser/debugEditorContribution.ts | 19 +- .../contrib/debug/browser/debugHover.ts | 23 +- .../contrib/debug/browser/debugIcons.ts | 71 + .../contrib/debug/browser/debugProgress.ts | 2 +- .../contrib/debug/browser/debugQuickAccess.ts | 27 +- .../contrib/debug/browser/debugService.ts | 42 +- .../contrib/debug/browser/debugSession.ts | 13 +- .../contrib/debug/browser/debugToolBar.ts | 33 +- .../contrib/debug/browser/debugViewlet.ts | 5 +- .../contrib/debug/browser/exceptionWidget.ts | 56 +- .../debug/browser/loadedScriptsView.ts | 6 +- .../debug/browser/media/debugHover.css | 11 +- .../debug/browser/media/debugViewlet.css | 7 +- .../debug/browser/media/exceptionWidget.css | 10 +- .../contrib/debug/browser/media/repl.css | 9 + .../contrib/debug/browser/rawDebugSession.ts | 6 +- .../workbench/contrib/debug/browser/repl.ts | 26 +- .../contrib/debug/browser/replFilter.ts | 7 +- .../contrib/debug/browser/replViewer.ts | 44 +- .../contrib/debug/browser/variablesView.ts | 9 +- .../debug/browser/watchExpressionsView.ts | 5 +- .../contrib/debug/browser/welcomeView.ts | 9 +- .../workbench/contrib/debug/common/debug.ts | 60 +- .../contrib/debug/common/debugModel.ts | 26 +- .../contrib/debug/common/debugProtocol.d.ts | 91 +- .../contrib/debug/common/debugStorage.ts | 14 +- .../contrib/debug/common/debugViewModel.ts | 14 +- .../contrib/debug/common/debugger.ts | 17 +- .../contrib/debug/common/replModel.ts | 39 +- .../contrib/debug/node/debugAdapter.ts | 8 +- .../workbench/contrib/debug/node/terminals.ts | 11 +- .../debug/test/browser/breakpoints.test.ts | 50 +- .../debug/test/browser/callStack.test.ts | 8 +- .../contrib/debug/test/browser/mockDebug.ts | 15 +- .../contrib/debug/test/browser/repl.test.ts | 41 +- .../debugANSIHandling.test.ts | 8 +- .../contrib/debug/test/node/debugger.test.ts | 7 +- .../browser/actions/showEmmetCommands.ts | 39 - .../emmet/browser/emmet.contribution.ts | 1 - .../experiments/common/experimentService.ts | 16 +- .../experimentalPrompts.test.ts | 2 +- .../abstractRuntimeExtensionsEditor.ts | 474 + .../browser/browserRuntimeExtensionsEditor.ts | 62 + .../dynamicWorkspaceRecommendations.ts | 4 +- .../extensions/browser/extensionEditor.ts | 148 +- ...ensionRecommendationNotificationService.ts | 39 +- .../extensionRecommendationsService.ts | 12 +- .../browser/extensions.contribution.ts | 262 +- .../browser/extensions.web.contribution.ts | 17 + .../extensions/browser/extensionsActions.ts | 1024 +- .../extensions/browser/extensionsIcons.ts | 33 + .../extensions/browser/extensionsList.ts | 20 +- .../extensions/browser/extensionsViewlet.ts | 254 +- .../extensions/browser/extensionsViews.ts | 387 +- .../extensions/browser/extensionsWidgets.ts | 46 +- .../browser/extensionsWorkbenchService.ts | 32 +- .../browser/fileBasedRecommendations.ts | 46 +- .../extensions/browser/media/extension.css | 9 + .../browser/media/extensionActions.css | 6 +- .../browser/media/extensionEditor.css | 10 +- .../browser/media/extensionsWidgets.css | 14 +- .../media/runtimeExtensionsEditor.css | 5 - .../browser/remoteExtensionsInstaller.ts | 19 +- .../browser/workspaceRecommendations.ts | 39 +- .../contrib/extensions/common/extensions.ts | 13 +- .../extensions/common/extensionsInput.ts | 3 +- .../runtimeExtensionsInput.ts | 0 .../debugExtensionHostAction.ts | 55 + .../extensionProfileService.ts | 8 +- .../extensions.contribution.ts | 43 +- .../extensionsAutoProfiler.ts | 6 +- .../extensionsSlowActions.ts | 0 .../reportExtensionIssueAction.ts | 78 + .../runtimeExtensionsEditor.ts | 601 +- .../electron-sandbox/extensionsActions.ts | 22 +- .../extensionRecommendationsService.test.ts | 56 +- .../extensionsActions.test.ts | 74 +- .../electron-browser/extensionsViews.test.ts | 34 +- .../extensionsWorkbenchService.test.ts | 43 +- .../browser/externalTerminal.contribution.ts | 3 +- .../externalTerminal/node/TerminalHelper.scpt | Bin 15244 -> 16284 bytes .../externalTerminal/node/iTermHelper.scpt | Bin 7380 -> 7498 bytes .../contrib/feedback/browser/feedback.ts | 4 +- .../feedback/browser/feedbackStatusbarItem.ts | 2 +- .../files/browser/editors/textFileEditor.ts | 17 +- .../editors/textFileSaveErrorHandler.ts | 15 +- .../{common => browser}/explorerService.ts | 201 +- .../contrib/files/browser/explorerViewlet.ts | 11 +- .../files/browser/fileActions.contribution.ts | 8 +- .../contrib/files/browser/fileActions.ts | 192 +- .../contrib/files/browser/fileCommands.ts | 39 +- .../files/browser/files.contribution.ts | 56 +- .../workbench/contrib/files/browser/files.ts | 49 +- .../files/browser/media/explorerviewlet.css | 1 - .../views/explorerDecorationsProvider.ts | 2 +- .../files/browser/views/explorerView.ts | 36 +- .../files/browser/views/explorerViewer.ts | 213 +- .../files/browser/views/openEditorsView.ts | 86 +- .../files/common/editors/fileEditorInput.ts | 63 +- .../workbench/contrib/files/common/files.ts | 52 +- .../fileActions.contribution.ts | 15 +- .../files/electron-sandbox/textFileEditor.ts | 2 +- .../test/browser/fileEditorInput.test.ts | 97 +- .../format/browser/formatActionsNone.ts | 4 +- .../electron-sandbox/issue.contribution.ts | 2 +- .../issue/electron-sandbox/issueActions.ts | 2 +- .../issue/electron-sandbox/issueService.ts | 10 +- .../browser/localizations.contribution.ts | 9 +- .../contrib/logs/common/logConstants.ts | 2 + .../contrib/logs/common/logs.contribution.ts | 20 +- .../contrib/logs/common/logsActions.ts | 2 +- .../markers/browser/markers.contribution.ts | 12 +- .../contrib/markers/browser/markers.ts | 45 +- .../markers/browser/markersFilterOptions.ts | 50 +- .../contrib/markers/browser/markersModel.ts | 13 +- .../markers/browser/markersTreeViewer.ts | 13 +- .../contrib/markers/browser/markersView.ts | 120 +- .../markers/browser/markersViewActions.ts | 8 +- .../contrib/notebook/browser/constants.ts | 2 +- .../notebook/browser/contrib/coreActions.ts | 107 +- .../browser/contrib/find/findController.ts | 13 +- .../notebook/browser/contrib/fold/folding.ts | 2 +- .../browser/contrib/fold/foldingModel.ts | 6 +- .../notebook/browser/contrib/scm/scm.ts | 163 - .../browser/contrib/status/editorStatus.ts | 20 +- .../notebook/browser/diff/cellComponents.ts | 57 +- .../browser/diff/notebookDiffActions.ts | 12 +- .../browser/diff/notebookTextDiffEditor.ts | 30 +- .../notebook/browser/media/notebook.css | 60 +- .../notebook/browser/notebook.contribution.ts | 15 +- .../notebook/browser/notebookBrowser.ts | 34 +- .../notebook/browser/notebookEditorInput.ts | 4 +- .../notebook/browser/notebookEditorWidget.ts | 503 +- .../browser/notebookEditorWidgetService.ts | 1 - .../contrib/notebook/browser/notebookIcons.ts | 32 + .../notebook/browser/notebookServiceImpl.ts | 177 +- .../notebook/browser/view/notebookCellList.ts | 31 +- .../view/output/transforms/richTransform.ts | 11 +- .../view/output/transforms/streamTransform.ts | 11 +- .../view/output/transforms/textHelper.ts | 117 + .../view/renderers/backLayerWebView.ts | 10 +- .../browser/view/renderers/cellActionView.ts | 27 +- .../view/renderers/{dnd.ts => cellDnd.ts} | 0 .../browser/view/renderers/cellOutput.ts | 564 + .../browser/view/renderers/cellRenderer.ts | 71 +- .../browser/view/renderers/cellWidgets.ts | 148 +- .../browser/view/renderers/codeCell.ts | 528 +- .../view/renderers/commonViewComponents.ts | 26 - .../browser/view/renderers/markdownCell.ts | 97 +- .../browser/view/renderers/sizeObserver.ts | 78 - .../browser/view/renderers/webviewPreloads.ts | 94 +- .../browser/viewModel/baseCellViewModel.ts | 42 +- .../browser/viewModel/codeCellViewModel.ts | 39 +- .../browser/viewModel/notebookViewModel.ts | 6 +- .../common/model/notebookCellTextModel.ts | 4 + .../common/model/notebookTextModel.ts | 17 +- .../contrib/notebook/common/notebookCommon.ts | 38 +- .../notebook/common/notebookEditorModel.ts | 65 +- .../notebook/common/notebookService.ts | 8 +- .../notebook/test/notebookViewModel.test.ts | 6 +- .../notebook/test/testNotebookEditor.ts | 3 + .../outline/browser/outline.contribution.ts | 6 +- .../contrib/outline/browser/outlinePane.ts | 9 +- .../output/browser/output.contribution.ts | 20 +- .../contrib/output/browser/outputServices.ts | 6 +- .../contrib/output/browser/outputView.ts | 20 +- .../output/common/outputChannelModel.ts | 2 +- .../common/outputChannelModelService.ts | 4 +- .../output/common/outputLinkComputer.ts | 2 +- .../outputChannelModelService.ts | 4 +- .../browser/performance.contribution.ts | 2 +- .../performance/browser/perfviewEditor.ts | 140 +- .../preferences/browser/keybindingWidgets.ts | 18 +- .../preferences/browser/keybindingsEditor.ts | 121 +- .../browser/keyboardLayoutPicker.ts | 19 +- .../preferences/browser/media/preferences.css | 22 - .../browser/media/settingsEditor2.css | 4 +- .../browser/preferences.contribution.ts | 39 +- .../preferences/browser/preferencesActions.ts | 2 +- .../preferences/browser/preferencesEditor.ts | 3 +- .../preferences/browser/preferencesIcons.ts | 28 + .../browser/preferencesRenderers.ts | 21 +- .../preferences/browser/preferencesWidgets.ts | 40 +- .../preferences/browser/settingsEditor2.ts | 43 +- .../preferences/browser/settingsLayout.ts | 15 +- .../preferences/browser/settingsTree.ts | 50 +- .../preferences/browser/settingsTreeModels.ts | 92 +- .../preferences/browser/settingsWidgets.ts | 14 +- .../contrib/preferences/browser/tocTree.ts | 9 +- .../contrib/preferences/common/preferences.ts | 30 +- .../test/browser/settingsTreeModels.test.ts | 84 +- .../browser/commandsQuickAccess.ts | 21 +- .../browser/relauncher.contribution.ts | 2 +- .../remote/browser/media/tunnelView.css | 26 +- .../contrib/remote/browser/remote.ts | 42 +- .../contrib/remote/browser/remoteExplorer.ts | 358 +- .../contrib/remote/browser/remoteIndicator.ts | 21 +- .../contrib/remote/browser/tunnelView.ts | 224 +- .../contrib/remote/browser/urlFinder.ts | 64 +- .../remote/common/remote.contribution.ts | 2 +- .../contrib/remote/common/tunnelFactory.ts | 19 +- .../contrib/sash/browser/sash.contribution.ts | 12 + .../contrib/scm/browser/dirtydiffDecorator.ts | 53 +- .../scm/browser/media/dirtydiffDecorator.css | 15 - .../contrib/scm/browser/scm.contribution.ts | 19 +- .../scm/browser/scmRepositoryRenderer.ts | 15 +- .../contrib/scm/browser/scmViewPane.ts | 189 +- .../contrib/scm/browser/scmViewService.ts | 103 +- src/vs/workbench/contrib/scm/common/scm.ts | 3 +- .../contrib/scm/common/scmService.ts | 4 +- .../contrib/search/browser/replaceService.ts | 6 +- .../search/browser/search.contribution.ts | 12 +- .../contrib/search/browser/searchActions.ts | 23 +- .../contrib/search/browser/searchIcons.ts | 34 +- .../contrib/search/browser/searchView.ts | 82 +- .../contrib/search/browser/searchWidget.ts | 25 +- .../search/common/searchHistoryService.ts | 4 +- .../contrib/search/common/searchModel.ts | 8 +- .../search/test/browser/queryBuilder.test.ts | 3 +- .../search/test/browser/searchViewlet.test.ts | 5 + .../search/test/common/searchModel.test.ts | 5 + .../search/test/common/searchResult.test.ts | 5 + .../electron-browser/queryBuilder.test.ts | 3 +- .../contrib/searchEditor/browser/constants.ts | 1 + .../browser/searchEditor.contribution.ts | 195 +- .../searchEditor/browser/searchEditor.ts | 4 +- .../browser/searchEditorActions.ts | 3 +- .../searchEditor/browser/searchEditorInput.ts | 8 +- .../contrib/snippets/browser/insertSnippet.ts | 122 +- .../snippets/browser/snippets.contribution.ts | 17 +- .../contrib/snippets/browser/snippetsFile.ts | 34 +- .../snippets/browser/snippetsService.ts | 154 +- .../test/browser/snippetsService.test.ts | 9 +- .../partsSplash.contribution.ts | 2 +- .../browser/languageSurveys.contribution.ts | 37 +- .../surveys/browser/nps.contribution.ts | 28 +- .../tags/browser/workspaceTagsService.ts | 2 +- .../contrib/tags/common/workspaceTags.ts | 2 +- .../tags/electron-browser/workspaceTags.ts | 10 +- .../electron-browser/workspaceTagsService.ts | 21 +- .../electron-browser/workspaceTags.test.ts | 34 +- .../tasks/browser/abstractTaskService.ts | 215 +- .../tasks/browser/runAutomaticTasks.ts | 8 +- .../contrib/tasks/browser/taskQuickPick.ts | 37 +- .../tasks/browser/terminalTaskSystem.ts | 54 +- .../contrib/tasks/common/problemCollectors.ts | 3 +- .../contrib/tasks/common/taskService.ts | 1 + .../tasks/electron-browser/taskService.ts | 154 +- .../tasks/test/common/configuration.test.ts | 3 +- .../browser/telemetry.contribution.ts | 16 +- .../browser/environmentVariableInfo.ts | 10 +- .../terminalValidatedLocalLinkProvider.ts | 5 + .../terminal/browser/media/scrollbar.css | 4 + .../terminal/browser/media/terminal.css | 4 - .../contrib/terminal/browser/media/xterm.css | 8 +- .../terminal/browser/terminal.contribution.ts | 26 +- .../contrib/terminal/browser/terminal.ts | 13 +- .../terminal/browser/terminalActions.ts | 46 +- .../terminal/browser/terminalConfigHelper.ts | 9 +- .../contrib/terminal/browser/terminalIcons.ts | 14 + .../terminal/browser/terminalInstance.ts | 57 +- .../terminal/browser/terminalQuickAccess.ts | 6 +- .../terminal/browser/terminalService.ts | 98 +- .../contrib/terminal/browser/terminalTab.ts | 20 - .../browser/terminalTypeAheadAddon.ts | 297 +- .../contrib/terminal/browser/terminalView.ts | 29 +- .../widgets/environmentVariableInfoWidget.ts | 3 +- .../terminal/common/environmentVariable.ts | 3 +- .../common/environmentVariableService.ts | 4 +- .../contrib/terminal/common/terminal.ts | 7 +- .../terminal/common/terminalConfiguration.ts | 18 +- .../terminal/common/terminalEnvironment.ts | 3 +- .../contrib/terminal/node/terminal.ts | 2 +- .../test/browser/terminalConfigHelper.test.ts | 35 +- .../test/browser/terminalTypeahead.test.ts | 65 +- .../testing/browser/testing.contribution.ts | 10 + .../contrib/testing/common/testCollection.ts | 229 + .../contrib/testing/common/testService.ts | 30 + .../contrib/testing/common/testServiceImpl.ts | 128 + .../themes/browser/themes.contribution.ts | 13 +- .../browser/themes.test.contribution.ts | 3 +- .../timeline/browser/timeline.contribution.ts | 10 +- .../contrib/timeline/browser/timelinePane.ts | 15 +- .../contrib/update/browser/update.ts | 86 +- .../contrib/url/browser/trustedDomains.ts | 10 +- .../trustedDomainsFileSystemProvider.ts | 12 +- .../url/browser/trustedDomainsValidator.ts | 14 +- .../contrib/url/browser/url.contribution.ts | 2 +- .../url/test/browser/trustedDomains.test.ts | 8 + .../userDataSync/browser/userDataSync.ts | 24 +- .../browser/userDataSyncMergesView.ts | 8 +- .../userDataSync/browser/userDataSyncViews.ts | 9 +- .../userDataSync.contribution.ts | 2 +- .../contrib/views/browser/treeView.ts | 1059 -- .../browser/dynamicWebviewEditorOverlay.ts | 32 +- .../contrib/webview/browser/pre/main.js | 20 +- .../contrib/webview/browser/webview.ts | 6 +- .../browser/webviewWindowDragMonitor.ts | 35 + .../electron-browser/webviewElement.ts | 76 +- .../webviewIgnoreMenuShortcutsManager.ts | 74 + .../electron-sandbox/iframeWebviewElement.ts | 22 +- .../windowIgnoreMenuShortcutsManager.ts | 46 + .../webviewPanel/browser/webviewEditor.ts | 34 +- .../webviewView/browser/webviewViewPane.ts | 14 +- .../common/viewsWelcomeContribution.ts | 2 + .../common/viewsWelcomeExtensionPoint.ts | 6 + .../browser/gettingStarted.contribution.ts | 52 + .../gettingStarted/browser/gettingStarted.css | 248 + .../gettingStarted/browser/gettingStarted.ts | 430 + .../browser/vs_code_editor_getting_started.ts | 38 + .../page/browser/vs_code_welcome_page.ts | 5 +- .../page/browser/welcomePage.contribution.ts | 15 +- .../welcome/page/browser/welcomePage.ts | 26 +- .../welcome/page/browser/welcomePageColors.ts | 12 + .../browser/telemetryOptOut.ts | 10 +- .../electron-sandbox/telemetryOptOut.ts | 4 +- .../browser/editor/editorWalkThrough.ts | 2 +- .../walkThrough/browser/walkThroughPart.css | 1 + .../electron-browser/desktop.main.ts | 45 +- .../actions/developerActions.ts | 5 +- .../electron-sandbox/desktop.contribution.ts | 15 +- .../electron-sandbox/desktop.main.ts | 8 +- .../parts/dialogs/dialog.contribution.ts | 100 + .../parts/dialogs/dialogHandler.ts} | 79 +- .../parts/titlebar/titlebarPart.ts | 18 +- .../sandbox.simpleservices.ts | 96 +- src/vs/workbench/electron-sandbox/window.ts | 42 +- .../browser/authenticationService.ts | 6 +- .../backup/browser/backupFileService.ts | 38 + .../backup/common/backupFileService.ts | 29 +- .../backupFileService.ts | 17 +- .../backupFileService.test.ts | 6 +- .../configuration/browser/configuration.ts | 3 +- .../browser/configurationService.ts | 59 +- .../common/configurationEditingService.ts | 2 +- .../test/common/configurationModels.test.ts | 3 +- .../configurationEditingService.test.ts | 50 +- .../configurationService.test.ts | 96 +- .../browser/configurationResolverService.ts | 16 +- .../common/variableResolver.ts | 87 +- .../configurationResolverService.test.ts | 15 + .../electron-sandbox/contextmenuService.ts | 2 +- .../decorations/browser/decorationsService.ts | 29 +- .../test/browser/decorationsService.test.ts | 10 +- .../browser/abstractFileDialogService.ts | 25 +- .../dialogs/browser/fileDialogService.ts | 8 +- .../dialogs/browser/simpleFileDialog.ts | 12 +- .../services/dialogs/common/dialogService.ts | 38 + .../electron-sandbox/fileDialogService.ts | 8 +- .../editor/browser/codeEditorService.ts | 24 +- .../services/editor/browser/editorService.ts | 32 +- .../editor/common/editorGroupsService.ts | 25 +- .../services/editor/common/editorOpenWith.ts | 3 +- .../test/browser/editorsObserver.test.ts | 6 +- .../environment/browser/environmentService.ts | 7 +- .../environment/common/environmentService.ts | 1 - .../electron-browser/environmentService.ts | 19 +- .../electron-sandbox/environmentService.ts | 5 +- .../experiment/common/experimentService.ts | 46 +- .../browser/extensionBisect.ts | 337 + .../browser/extensionEnablementService.ts | 29 +- .../browser/extensionUrlTrustService.ts | 18 + .../common/extensionManagement.ts | 5 +- .../extensionManagementServerService.ts | 5 +- .../common/extensionManagementService.ts | 104 +- .../common/webExtensionsScannerService.ts | 47 +- .../extensionManagementService.ts | 6 +- .../extensionUrlTrustService.ts | 20 + .../extensionEnablementService.test.ts | 51 +- .../extensionIgnoredRecommendationsService.ts | 11 +- .../common/extensionRecommendations.ts | 5 - .../common/workspaceExtensionsConfig.ts | 233 +- .../extensions/browser/extensionService.ts | 11 +- .../extensions/browser/extensionUrlHandler.ts | 63 +- .../extensions/common/extensionHostMain.ts | 2 - .../cachedExtensionScanner.ts | 20 +- .../services/extensions/node/proxyResolver.ts | 7 +- .../electron-browser/extensionService.test.ts | 88 + .../worker/extensionHostWorkerMain.ts | 10 +- .../httpWebWorkerExtensionHostIframe.html | 9 +- .../httpsWebWorkerExtensionHostIframe.html | 9 +- .../common/gettingStartedContent.ts | 170 + .../common/gettingStartedRegistry.ts | 150 + .../common/gettingStartedService.ts | 195 + .../common/media/ColorTheme.jpg | Bin 0 -> 101006 bytes .../common/media/Extensions.jpg | Bin 0 -> 103983 bytes .../gettingStarted/common/media/Languages.jpg | Bin 0 -> 101550 bytes .../common/media/OpenFolder.jpg | Bin 0 -> 103714 bytes .../services/history/browser/history.ts | 6 +- .../host/browser/browserHostService.ts | 5 +- .../workbench/services/host/browser/host.ts | 2 +- .../electron-sandbox/nativeHostService.ts | 4 +- .../services/hover/browser/hoverWidget.ts | 42 +- .../integrity/node/integrityService.ts | 4 +- .../issue/common}/issue.ts | 0 .../keybinding/browser/keybindingService.ts | 70 +- ...mapService.ts => keyboardLayoutService.ts} | 19 +- .../keybinding/common/keybindingEditing.ts | 38 +- .../services/keybinding/common/keymapInfo.ts | 185 +- .../common/macLinuxFallbackKeyboardMapper.ts | 2 +- .../common/macLinuxKeyboardMapper.ts | 47 +- .../common/windowsKeyboardMapper.ts | 50 +- .../electron-browser/nativeKeymapService.ts | 174 - .../electron-sandbox/nativeKeyboardLayout.ts | 144 + .../browserKeyboardMapper.test.ts | 2 +- .../keybindingEditing.test.ts | 16 +- .../keyboardMapperTestUtils.ts | 2 +- .../macLinuxKeyboardMapper.test.ts | 3 +- .../windowsKeyboardMapper.test.ts | 3 +- .../services/label/test/browser/label.test.ts | 3 +- .../electron-sandbox/lifecycleService.ts | 4 +- .../common/notificationService.ts | 13 +- .../services/output/node/outputAppender.ts | 5 +- .../preferences/browser/preferencesService.ts | 79 +- .../common/keybindingsEditorModel.ts | 23 +- .../preferences/common/preferences.ts | 42 +- .../preferences/common/preferencesModels.ts | 4 +- .../common/preferencesValidation.ts | 24 +- .../common/keybindingsEditorModel.test.ts | 49 +- .../test/common/preferencesValidation.test.ts | 16 + .../common/abstractRemoteAgentService.ts | 7 + .../common/remoteAgentEnvironmentChannel.ts | 4 + .../remote/common/remoteAgentService.ts | 2 + .../remote/common/remoteExplorerService.ts | 266 +- .../services/search/common/search.ts | 53 +- .../services/search/common/searchService.ts | 39 +- .../search/node/ripgrepTextSearchEngine.ts | 87 +- .../search/test/common/search.test.ts | 25 + .../test/node/ripgrepTextSearchEngine.test.ts | 50 +- .../browser/workbenchCommonProperties.ts | 4 +- .../electron-browser/telemetryService.ts | 2 +- .../electron-browser/commonProperties.test.ts | 4 +- .../browser/abstractTextMateService.ts | 4 +- .../textfile/browser/textFileService.ts | 26 +- .../textfile/common/textFileEditorModel.ts | 3 +- .../services/textfile/common/textfiles.ts | 10 - .../electron-browser/nativeTextFileService.ts | 4 +- .../test/common/textFileService.io.test.ts | 14 +- .../common/textResourcePropertiesService.ts | 4 +- .../themes/browser/fileIconThemeData.ts | 5 +- .../themes/browser/productIconThemeData.ts | 4 +- .../themes/browser/workbenchThemeService.ts | 181 +- .../services/themes/common/colorThemeData.ts | 5 +- .../themes/common/themeConfiguration.ts | 12 +- .../themes/common/themeExtensionPoints.ts | 26 +- .../services/timer/browser/timerService.ts | 43 +- .../common/untitledTextEditorModel.ts | 10 +- .../test/browser/untitledTextEditor.test.ts | 14 +- .../services/userData/browser/userDataInit.ts | 83 +- .../userData/common/fileUserDataProvider.ts | 54 +- .../fileUserDataProvider.test.ts | 163 +- .../browser/userDataSyncWorkbenchService.ts | 12 +- .../userDataSync/common/userDataSync.ts | 4 + .../storageKeysSyncRegistryService.ts | 20 - .../views/browser/viewDescriptorService.ts | 18 +- .../views/common/viewContainerModel.ts | 51 +- .../test/browser/viewContainerModel.test.ts | 8 +- .../workingCopyFileOperationParticipant.ts | 37 +- .../common/workingCopyFileService.ts | 44 +- .../abstractWorkspaceEditingService.ts | 3 +- .../workspaces/browser/workspacesService.ts | 11 +- .../workspaceEditingService.ts | 4 +- .../browser/api/extHostApiCommands.test.ts | 10 +- .../browser/api/extHostConfiguration.test.ts | 5 +- .../browser/api/extHostDecorations.test.ts | 9 +- .../test/browser/api/extHostTesting.test.ts | 310 + .../test/browser/api/extHostTreeViews.test.ts | 56 +- .../browser/api/extHostTypeConverter.test.ts | 13 +- .../test/browser/api/extHostTypes.test.ts | 6 + .../test/browser/api/extHostWebview.test.ts | 6 +- .../test/browser/api/extHostWorkspace.test.ts | 21 +- .../browser/api/mainThreadTreeViews.test.ts | 2 +- src/vs/workbench/test/browser/part.test.ts | 12 +- .../parts/editor/breadcrumbModel.test.ts | 3 +- .../test/browser/parts/editor/editor.test.ts | 2 +- .../parts/editor/editorDiffModel.test.ts | 2 +- .../browser/parts/editor/editorGroups.test.ts | 9 +- .../browser/parts/editor/editorInput.test.ts | 11 +- .../browser/parts/editor/editorPane.test.ts | 56 + .../test/browser/workbenchTestServices.ts | 23 +- src/vs/workbench/test/common/memento.test.ts | 71 +- .../test/common/workbenchTestServices.ts | 8 +- .../electron-browser/workbenchTestServices.ts | 3 +- src/vs/workbench/workbench.common.main.ts | 8 +- src/vs/workbench/workbench.desktop.main.ts | 8 +- src/vs/workbench/workbench.sandbox.main.ts | 13 +- src/vs/workbench/workbench.web.api.ts | 14 +- src/vs/workbench/workbench.web.main.ts | 24 +- test/automation/src/extensions.ts | 6 +- test/automation/src/keybindings.ts | 14 +- .../src/areas/extensions/extensions.test.ts | 2 +- .../smoke/src/areas/notebook/notebook.test.ts | 2 +- .../src/areas/preferences/preferences.test.ts | 2 +- .../src/areas/workbench/localization.test.ts | 2 +- yarn.lock | 702 +- 1351 files changed, 56356 insertions(+), 39096 deletions(-) create mode 100644 .vscode/notebooks/grooming-delta.github-issues create mode 100644 .vscode/notebooks/my-endgame.github-issues delete mode 100644 build/.nativeignore create mode 100644 build/azure-pipelines/common/retry.ts create mode 100755 build/azure-pipelines/linux/alpine/install-dependencies.sh create mode 100755 build/azure-pipelines/linux/alpine/publish.sh delete mode 100755 build/azure-pipelines/linux/multiarch/alpine/build.sh delete mode 100755 build/azure-pipelines/linux/multiarch/alpine/prebuild.sh delete mode 100755 build/azure-pipelines/linux/multiarch/alpine/publish.sh delete mode 100755 build/azure-pipelines/linux/multiarch/arm64/build.sh delete mode 100755 build/azure-pipelines/linux/multiarch/arm64/prebuild.sh delete mode 100755 build/azure-pipelines/linux/multiarch/arm64/publish.sh delete mode 100755 build/azure-pipelines/linux/multiarch/armhf/build.sh delete mode 100755 build/azure-pipelines/linux/multiarch/armhf/prebuild.sh delete mode 100755 build/azure-pipelines/linux/multiarch/armhf/publish.sh create mode 100644 build/azure-pipelines/linux/product-build-alpine.yml delete mode 100644 build/azure-pipelines/linux/product-build-linux-multiarch.yml delete mode 100644 build/azure-pipelines/win32/product-build-win32-arm64.yml create mode 100644 build/azure-pipelines/win32/retry.ps1 create mode 100755 extensions/configuration-editing/build/inline-allOf.ts create mode 100644 extensions/configuration-editing/build/tsconfig.json create mode 100644 extensions/configuration-editing/schemas/devContainer.schema.generated.json rename extensions/configuration-editing/schemas/{devContainer.schema.json => devContainer.schema.src.json} (97%) create mode 100644 extensions/emmet/src/test/evaluateMathExpression.test.ts delete mode 100644 extensions/emmet/src/typings/emmetio__math-expression.d.ts create mode 100644 extensions/npm/src/preferred-pm.ts create mode 100644 extensions/sql/build/update-grammar.js rename extensions/theme-tomorrow-night-blue/themes/{tomorrow-night-blue-theme.json => tomorrow-night-blue-color-theme.json} (96%) create mode 100644 src/vs/base/browser/hash.ts rename src/vs/base/test/{common => browser}/hash.test.ts (85%) create mode 100644 src/vs/base/test/common/network.test.ts create mode 100644 src/vs/code/electron-main/protocol.ts delete mode 100644 src/vs/code/test/electron-main/nativeHelpers.test.ts create mode 100644 src/vs/editor/common/controller/cursorAtomicMoveOperations.ts create mode 100644 src/vs/editor/contrib/colorPicker/colorContributions.ts rename src/vs/editor/contrib/{rename/onTypeRename.ts => linkedEditing/linkedEditing.ts} (81%) rename src/vs/editor/contrib/{rename/test/onTypeRename.test.ts => linkedEditing/test/linkedEditing.test..ts} (91%) create mode 100644 src/vs/editor/test/common/controller/cursorAtomicMoveOperations.test.ts rename src/vs/{editor/contrib/rename/media/onTypeRename.css => platform/display/common/displayMainService.ts} (68%) create mode 100644 src/vs/platform/display/electron-main/displayMainService.ts rename src/vs/{code => platform/environment}/test/node/argv.test.ts (99%) create mode 100644 src/vs/platform/environment/test/node/nativeModules.test.ts create mode 100644 src/vs/platform/extensionManagement/common/extensionUrlTrust.ts create mode 100644 src/vs/platform/extensionManagement/node/extensionUrlTrustService.ts rename src/vs/{workbench/services/keybinding => platform/keyboardLayout}/common/dispatchConfig.ts (100%) create mode 100644 src/vs/platform/keyboardLayout/common/keyboardLayout.ts create mode 100644 src/vs/platform/keyboardLayout/common/keyboardLayoutMainService.ts rename src/vs/{workbench/services/keybinding => platform/keyboardLayout}/common/keyboardMapper.ts (100%) create mode 100644 src/vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService.ts create mode 100644 src/vs/platform/storage/test/common/storageService.test.ts delete mode 100644 src/vs/platform/theme/browser/checkbox.ts create mode 100644 src/vs/platform/userDataSync/common/extensionsStorageSync.ts delete mode 100644 src/vs/platform/userDataSync/common/storageKeys.ts rename src/vs/{code => platform/windows}/test/electron-main/windowsStateStorage.test.ts (100%) create mode 100644 src/vs/workbench/api/browser/mainThreadTesting.ts create mode 100644 src/vs/workbench/api/common/extHostTesting.ts delete mode 100644 src/vs/workbench/api/common/shared/editor.ts create mode 100644 src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts rename src/vs/workbench/{services/dialogs/browser/dialogService.ts => browser/parts/dialogs/dialogHandler.ts} (91%) rename src/vs/workbench/{contrib/views/browser => browser/parts/views}/media/views.css (98%) create mode 100644 src/vs/workbench/common/dialogs.ts create mode 100644 src/vs/workbench/contrib/codeEditor/electron-sandbox/displayChangeRemeasureFonts.ts create mode 100644 src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts create mode 100644 src/vs/workbench/contrib/debug/browser/debugIcons.ts delete mode 100644 src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts create mode 100644 src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts create mode 100644 src/vs/workbench/contrib/extensions/browser/browserRuntimeExtensionsEditor.ts create mode 100644 src/vs/workbench/contrib/extensions/browser/extensions.web.contribution.ts create mode 100644 src/vs/workbench/contrib/extensions/browser/extensionsIcons.ts rename src/vs/workbench/contrib/extensions/{electron-browser => browser}/media/runtimeExtensionsEditor.css (91%) rename src/vs/workbench/contrib/extensions/{electron-browser => common}/runtimeExtensionsInput.ts (100%) create mode 100644 src/vs/workbench/contrib/extensions/electron-browser/debugExtensionHostAction.ts rename src/vs/workbench/contrib/extensions/{electron-sandbox => electron-browser}/extensionsSlowActions.ts (100%) create mode 100644 src/vs/workbench/contrib/extensions/electron-browser/reportExtensionIssueAction.ts rename src/vs/workbench/contrib/files/{common => browser}/explorerService.ts (68%) delete mode 100644 src/vs/workbench/contrib/notebook/browser/contrib/scm/scm.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/notebookIcons.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper.ts rename src/vs/workbench/contrib/notebook/browser/view/renderers/{dnd.ts => cellDnd.ts} (100%) create mode 100644 src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts delete mode 100644 src/vs/workbench/contrib/notebook/browser/view/renderers/commonViewComponents.ts delete mode 100644 src/vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver.ts rename src/vs/workbench/{services => contrib}/output/common/outputChannelModel.ts (99%) rename src/vs/workbench/{services => contrib}/output/common/outputChannelModelService.ts (75%) rename src/vs/workbench/{services => contrib}/output/electron-browser/outputChannelModelService.ts (97%) create mode 100644 src/vs/workbench/contrib/preferences/browser/preferencesIcons.ts create mode 100644 src/vs/workbench/contrib/terminal/browser/terminalIcons.ts create mode 100644 src/vs/workbench/contrib/testing/browser/testing.contribution.ts create mode 100644 src/vs/workbench/contrib/testing/common/testCollection.ts create mode 100644 src/vs/workbench/contrib/testing/common/testService.ts create mode 100644 src/vs/workbench/contrib/testing/common/testServiceImpl.ts delete mode 100644 src/vs/workbench/contrib/views/browser/treeView.ts create mode 100644 src/vs/workbench/contrib/webview/browser/webviewWindowDragMonitor.ts create mode 100644 src/vs/workbench/contrib/webview/electron-browser/webviewIgnoreMenuShortcutsManager.ts create mode 100644 src/vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager.ts create mode 100644 src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution.ts create mode 100644 src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css create mode 100644 src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts create mode 100644 src/vs/workbench/contrib/welcome/gettingStarted/browser/vs_code_editor_getting_started.ts create mode 100644 src/vs/workbench/contrib/welcome/page/browser/welcomePageColors.ts create mode 100644 src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts rename src/vs/workbench/{services/dialogs/electron-sandbox/dialogService.ts => electron-sandbox/parts/dialogs/dialogHandler.ts} (74%) create mode 100644 src/vs/workbench/services/backup/browser/backupFileService.ts rename src/vs/workbench/services/backup/{node => electron-browser}/backupFileService.ts (53%) create mode 100644 src/vs/workbench/services/dialogs/common/dialogService.ts create mode 100644 src/vs/workbench/services/extensionManagement/browser/extensionBisect.ts create mode 100644 src/vs/workbench/services/extensionManagement/browser/extensionUrlTrustService.ts create mode 100644 src/vs/workbench/services/extensionManagement/electron-sandbox/extensionUrlTrustService.ts create mode 100644 src/vs/workbench/services/extensions/test/electron-browser/extensionService.test.ts create mode 100644 src/vs/workbench/services/gettingStarted/common/gettingStartedContent.ts create mode 100644 src/vs/workbench/services/gettingStarted/common/gettingStartedRegistry.ts create mode 100644 src/vs/workbench/services/gettingStarted/common/gettingStartedService.ts create mode 100644 src/vs/workbench/services/gettingStarted/common/media/ColorTheme.jpg create mode 100644 src/vs/workbench/services/gettingStarted/common/media/Extensions.jpg create mode 100644 src/vs/workbench/services/gettingStarted/common/media/Languages.jpg create mode 100644 src/vs/workbench/services/gettingStarted/common/media/OpenFolder.jpg rename src/vs/workbench/{contrib/issue/electron-sandbox => services/issue/common}/issue.ts (100%) rename src/vs/workbench/services/keybinding/browser/{keymapService.ts => keyboardLayoutService.ts} (95%) delete mode 100644 src/vs/workbench/services/keybinding/electron-browser/nativeKeymapService.ts create mode 100644 src/vs/workbench/services/keybinding/electron-sandbox/nativeKeyboardLayout.ts delete mode 100644 src/vs/workbench/services/userDataSync/electron-browser/storageKeysSyncRegistryService.ts create mode 100644 src/vs/workbench/test/browser/api/extHostTesting.test.ts diff --git a/.eslintrc.json b/.eslintrc.json index 055bc22f8..fd72f0e58 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -983,6 +983,7 @@ "collapse", "create", "delete", + "discover", "dispose", "edit", "end", diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 4a04784b8..51e7f3660 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: false contact_links: - name: Question - url: https://stackoverflow.com/questions/tagged/visual-studio-code + url: https://stackoverflow.com/questions/tagged/visual-studio-code about: Please ask and answer questions here. diff --git a/.github/calendar.yml b/.github/calendar.yml index f14957960..c1b5a2569 100644 --- a/.github/calendar.yml +++ b/.github/calendar.yml @@ -1,37 +1,37 @@ { - '2018-01-29 18:00, US/Pacific': 'endgame', - '2018-02-07 12:00, US/Pacific': 'release', # 1.20.0 - '2018-02-12 12:00, US/Pacific': 'development', - '2018-02-14 16:00, Europe/Zurich': 'release', # 1.20.1 - '2018-02-19 16:00, Europe/Zurich': 'development', - '2018-02-26 18:00, US/Pacific': 'endgame', - '2018-03-07 12:00, US/Pacific': 'release', # 1.21.0 - '2018-03-12 12:00, US/Pacific': 'development', - '2018-03-15 12:00, US/Pacific': 'release', # 1.21.1 - '2018-03-20 12:00, US/Pacific': 'development', - '2018-03-26 18:00, US/Pacific': 'endgame', - '2018-04-06 18:00, US/Pacific': 'release', # 1.22.1 - '2018-04-11 18:00, US/Pacific': 'development', - '2018-04-12 12:00, US/Pacific': 'release', # 1.22.2 - '2018-04-17 12:00, US/Pacific': 'development', - '2018-04-23 18:00, US/Pacific': 'endgame', - '2018-05-03 12:00, US/Pacific': 'release', # 1.23.0 - '2018-05-08 12:00, US/Pacific': 'development', - '2018-05-10 12:00, US/Pacific': 'release', # 1.23.1 - '2018-05-15 12:00, US/Pacific': 'development', - '2018-05-28 18:00, US/Pacific': 'endgame', + "2018-01-29 18:00, US/Pacific": "endgame", + "2018-02-07 12:00, US/Pacific": "release", # 1.20.0 + "2018-02-12 12:00, US/Pacific": "development", + "2018-02-14 16:00, Europe/Zurich": "release", # 1.20.1 + "2018-02-19 16:00, Europe/Zurich": "development", + "2018-02-26 18:00, US/Pacific": "endgame", + "2018-03-07 12:00, US/Pacific": "release", # 1.21.0 + "2018-03-12 12:00, US/Pacific": "development", + "2018-03-15 12:00, US/Pacific": "release", # 1.21.1 + "2018-03-20 12:00, US/Pacific": "development", + "2018-03-26 18:00, US/Pacific": "endgame", + "2018-04-06 18:00, US/Pacific": "release", # 1.22.1 + "2018-04-11 18:00, US/Pacific": "development", + "2018-04-12 12:00, US/Pacific": "release", # 1.22.2 + "2018-04-17 12:00, US/Pacific": "development", + "2018-04-23 18:00, US/Pacific": "endgame", + "2018-05-03 12:00, US/Pacific": "release", # 1.23.0 + "2018-05-08 12:00, US/Pacific": "development", + "2018-05-10 12:00, US/Pacific": "release", # 1.23.1 + "2018-05-15 12:00, US/Pacific": "development", + "2018-05-28 18:00, US/Pacific": "endgame", # 'release' not needed anymore, return to 'development' after releasing. - '2018-06-06 12:00, US/Pacific': 'development', # 1.24.0 released - '2018-06-25 18:00, US/Pacific': 'endgame', - '2018-07-05 12:00, US/Pacific': 'development', # 1.25.0 released - '2018-07-30 18:00, US/Pacific': 'endgame', - '2018-08-13 12:00, US/Pacific': 'development', # 1.26.0 released - '2018-08-27 18:00, US/Pacific': 'endgame', - '2018-09-05 12:00, US/Pacific': 'development', # 1.27.0 released - '2018-09-24 18:00, US/Pacific': 'endgame', - '2018-10-08 09:00, US/Pacific': 'development', # 1.28.0 released - '2018-10-29 18:00, US/Pacific': 'endgame', - '2018-11-12 11:00, US/Pacific': 'development', # 1.29.0 released - '2018-12-03 18:00, US/Pacific': 'endgame', - '2018-12-12 13:00, US/Pacific': 'development', # 1.30.0 released + "2018-06-06 12:00, US/Pacific": "development", # 1.24.0 released + "2018-06-25 18:00, US/Pacific": "endgame", + "2018-07-05 12:00, US/Pacific": "development", # 1.25.0 released + "2018-07-30 18:00, US/Pacific": "endgame", + "2018-08-13 12:00, US/Pacific": "development", # 1.26.0 released + "2018-08-27 18:00, US/Pacific": "endgame", + "2018-09-05 12:00, US/Pacific": "development", # 1.27.0 released + "2018-09-24 18:00, US/Pacific": "endgame", + "2018-10-08 09:00, US/Pacific": "development", # 1.28.0 released + "2018-10-29 18:00, US/Pacific": "endgame", + "2018-11-12 11:00, US/Pacific": "development", # 1.29.0 released + "2018-12-03 18:00, US/Pacific": "endgame", + "2018-12-12 13:00, US/Pacific": "development", # 1.30.0 released } diff --git a/.github/classifier.json b/.github/classifier.json index e4acc63c1..0ff0712c3 100644 --- a/.github/classifier.json +++ b/.github/classifier.json @@ -72,7 +72,7 @@ "file-watcher": {"assign": ["bpasero"]}, "font-rendering": {"assign": []}, "formatting": {"assign": []}, - "git": {"assign": ["joaomoreno"]}, + "git": {"assign": ["eamodio"]}, "gpu": {"assign": ["deepak1556"]}, "grammar": {"assign": ["mjbvz"]}, "grid-view": {"assign": ["joaomoreno"]}, @@ -81,9 +81,9 @@ "icon-brand": {"assign": []}, "icons-product": {"assign": ["misolori"]}, "install-update": {"assign": []}, - "integrated-terminal": {"assign": ["Tyriar"]}, - "integrated-terminal-conpty": {"assign": ["Tyriar"]}, - "integrated-terminal-links": {"assign": ["Tyriar"]}, + "integrated-terminal": {"assign": ["meganrogge"]}, + "integrated-terminal-conpty": {"assign": ["meganrogge"]}, + "integrated-terminal-links": {"assign": ["meganrogge"]}, "integration-test": {"assign": []}, "intellisense-config": {"assign": []}, "ipc": {"assign": ["joaomoreno"]}, @@ -119,7 +119,7 @@ "remote": {"assign": []}, "remote-explorer": {"assign": ["alexr00"]}, "rename": {"assign": ["jrieken"]}, - "scm": {"assign": ["joaomoreno"]}, + "scm": {"assign": ["eamodio"]}, "screencast-mode": {"assign": ["lszomoru"]}, "search": {"assign": ["roblourens"]}, "search-editor": {"assign": ["JacksonKearl"]}, @@ -159,7 +159,7 @@ "workbench-electron": {"assign": ["deepak1556"]}, "workbench-feedback": {"assign": ["bpasero"]}, "workbench-history": {"assign": ["bpasero"]}, - "workbench-hot-exit": {"assign": ["Tyriar"]}, + "workbench-hot-exit": {"assign": []}, "workbench-launch": {"assign": []}, "workbench-link": {"assign": []}, "workbench-multiroot": {"assign": ["bpasero"]}, diff --git a/.github/commands.json b/.github/commands.json index 7a1220f7d..980b2906b 100644 --- a/.github/commands.json +++ b/.github/commands.json @@ -202,6 +202,19 @@ "addLabel": "*caused-by-extension", "comment": "It looks like this is caused by the Python extension. Please file it with the repository [here](https://github.com/microsoft/vscode-python). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" }, + { + "type": "comment", + "name": "extJupyter", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "close", + "addLabel": "*caused-by-extension", + "comment": "It looks like this is caused by the Jupyter extension. Please file it with the repository [here](https://github.com/microsoft/vscode-jupyter). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + }, { "type": "comment", "name": "extC", diff --git a/.github/commands.yml b/.github/commands.yml index 64fdf683b..5073658e5 100644 --- a/.github/commands.yml +++ b/.github/commands.yml @@ -1,12 +1,13 @@ { - perform: true, - commands: [ - { - type: 'comment', - name: 'findDuplicates', - allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], - action: 'comment', - comment: "Potential duplicates:\n${potentialDuplicates}" - } - ] + perform: true, + commands: + [ + { + type: "comment", + name: "findDuplicates", + allowUsers: ["cleidigh", "usernamehw", "gjsjohnmurray", "IllusionMH"], + action: "comment", + comment: "Potential duplicates:\n${potentialDuplicates}", + }, + ], } diff --git a/.github/endgame/insiders.yml b/.github/endgame/insiders.yml index f11bd611c..4996474f4 100644 --- a/.github/endgame/insiders.yml +++ b/.github/endgame/insiders.yml @@ -1,6 +1,6 @@ { - insidersLabel: 'insiders', - insidersColor: '006b75', - action: 'add', - perform: true -} \ No newline at end of file + insidersLabel: "insiders", + insidersColor: "006b75", + action: "add", + perform: true, +} diff --git a/.github/insiders.yml b/.github/insiders.yml index e9a49ce78..ab59e873b 100644 --- a/.github/insiders.yml +++ b/.github/insiders.yml @@ -1,6 +1,6 @@ { - insidersLabel: 'insiders', - insidersColor: '006b75', - action: 'remove', - perform: true -} \ No newline at end of file + insidersLabel: "insiders", + insidersColor: "006b75", + action: "remove", + perform: true, +} diff --git a/.github/similarity.yml b/.github/similarity.yml index cd51cd2da..cb00a4c9a 100644 --- a/.github/similarity.yml +++ b/.github/similarity.yml @@ -1,5 +1,5 @@ { - perform: true, - whenCreatedByTeam: false, - comment: "(Experimental duplicate detection)\nThanks for submitting this issue. Please also check if it is already covered by an existing one, like:\n${potentialDuplicates}" + perform: true, + whenCreatedByTeam: false, + comment: "(Experimental duplicate detection)\nThanks for submitting this issue. Please also check if it is already covered by an existing one, like:\n${potentialDuplicates}", } diff --git a/.github/workflows/author-verified.yml b/.github/workflows/author-verified.yml index de8311d7a..6924d9650 100644 --- a/.github/workflows/author-verified.yml +++ b/.github/workflows/author-verified.yml @@ -16,8 +16,8 @@ jobs: if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'author-verification-requested') uses: actions/checkout@v2 with: - repository: 'microsoft/vscode-github-triage-actions' - ref: v37 + repository: "microsoft/vscode-github-triage-actions" + ref: v41 path: ./actions - name: Install Actions if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'author-verification-requested') @@ -34,7 +34,7 @@ jobs: with: appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}} token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} - requestVerificationComment: "This bug has been fixed in to the latest release of [VS Code Insiders](https://code.visualstudio.com/insiders/)!\n\n@${author}, you can help us out by commenting `/verified` if things are now working as expected.\n\nIf things still don't seem right, please ensure you're on version ${commit} of Insiders (today's or later - you can use `Help: About` in the command pallette to check), and leave a comment letting us know what isn't working as expected.\n\nHappy Coding!" + requestVerificationComment: "This bug has been fixed in to the latest release of [VS Code Insiders](https://code.visualstudio.com/insiders/)!\n\n@${author}, you can help us out by commenting `/verified` if things are now working as expected.\n\nIf things still don't seem right, please ensure you're on version ${commit} of Insiders (today's or later - you can use `Help: About` in the command palette to check), and leave a comment letting us know what isn't working as expected.\n\nHappy Coding!" pendingReleaseLabel: awaiting-insiders-release verifiedLabel: verified authorVerificationRequestedLabel: author-verification-requested diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d509cce2a..08775eab9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -123,22 +123,22 @@ jobs: CHILD_CONCURRENCY: "1" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v1 - # TODO: rename azure-pipelines/linux/xvfb.init to github-actions - - run: | - sudo apt-get update - sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libgbm1 - sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb - sudo chmod +x /etc/init.d/xvfb - sudo update-rc.d xvfb defaults - sudo service xvfb start - name: Setup Build Environment - - uses: actions/setup-node@v1 - with: - node-version: 10 - - run: yarn --frozen-lockfile - name: Install Dependencies - - run: yarn monaco-compile-check - name: Run Monaco Editor Checks - - run: yarn gulp editor-esm-bundle - name: Editor Distro & ESM Bundle + - uses: actions/checkout@v1 + # TODO: rename azure-pipelines/linux/xvfb.init to github-actions + - run: | + sudo apt-get update + sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libgbm1 + sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb + sudo chmod +x /etc/init.d/xvfb + sudo update-rc.d xvfb defaults + sudo service xvfb start + name: Setup Build Environment + - uses: actions/setup-node@v1 + with: + node-version: 10 + - run: yarn --frozen-lockfile + name: Install Dependencies + - run: yarn monaco-compile-check + name: Run Monaco Editor Checks + - run: yarn gulp editor-esm-bundle + name: Editor Distro & ESM Bundle diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 017708844..88761f8aa 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,48 +2,47 @@ name: "Code Scanning" on: schedule: - - cron: '0 0 * * 2' + - cron: "0 0 * * 2" jobs: CodeQL-Build: - # CodeQL runs on ubuntu-latest and windows-latest runs-on: ubuntu-latest steps: - - name: Checkout repository - uses: actions/checkout@v2 - with: - # We must fetch at least the immediate parents so that if this is - # a pull request then we can checkout the head. - fetch-depth: 2 + - name: Checkout repository + uses: actions/checkout@v2 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 - # If this run was triggered by a pull request event, then checkout - # the head of the pull request instead of the merge commit. - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: javascript + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: javascript - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language - #- run: | - # make bootstrap - # make release + #- run: | + # make bootstrap + # make release - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index cac277575..c2b8f95e0 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -11,9 +11,9 @@ jobs: - name: Checkout Actions uses: actions/checkout@v2 with: - repository: 'microsoft/vscode-github-triage-actions' + repository: "microsoft/vscode-github-triage-actions" path: ./actions - ref: v37 + ref: v41 - name: Install Actions run: npm install --production --prefix ./actions - name: Run Commands diff --git a/.github/workflows/deep-classifier-monitor.yml b/.github/workflows/deep-classifier-monitor.yml index ef7fedaec..2734f6b78 100644 --- a/.github/workflows/deep-classifier-monitor.yml +++ b/.github/workflows/deep-classifier-monitor.yml @@ -10,8 +10,8 @@ jobs: - name: Checkout Actions uses: actions/checkout@v2 with: - repository: 'microsoft/vscode-github-triage-actions' - ref: v37 + repository: "microsoft/vscode-github-triage-actions" + ref: v41 path: ./actions - name: Install Actions run: npm install --production --prefix ./actions diff --git a/.github/workflows/deep-classifier-runner.yml b/.github/workflows/deep-classifier-runner.yml index de49bf3bc..1aaf05a87 100644 --- a/.github/workflows/deep-classifier-runner.yml +++ b/.github/workflows/deep-classifier-runner.yml @@ -12,8 +12,8 @@ jobs: - name: Checkout Actions uses: actions/checkout@v2 with: - repository: 'microsoft/vscode-github-triage-actions' - ref: v37 + repository: "microsoft/vscode-github-triage-actions" + ref: v41 path: ./actions - name: Install Actions run: npm install --production --prefix ./actions diff --git a/.github/workflows/deep-classifier-scraper.yml b/.github/workflows/deep-classifier-scraper.yml index acf3c50da..42b5f1b63 100644 --- a/.github/workflows/deep-classifier-scraper.yml +++ b/.github/workflows/deep-classifier-scraper.yml @@ -10,8 +10,8 @@ jobs: - name: Checkout Actions uses: actions/checkout@v2 with: - repository: 'microsoft/vscode-github-triage-actions' - ref: v37 + repository: "microsoft/vscode-github-triage-actions" + ref: v41 path: ./actions - name: Install Actions run: npm install --production --prefix ./actions diff --git a/.github/workflows/devcontainer-cache.yml b/.github/workflows/devcontainer-cache.yml index 82290a475..a250b56cd 100644 --- a/.github/workflows/devcontainer-cache.yml +++ b/.github/workflows/devcontainer-cache.yml @@ -4,12 +4,12 @@ on: push: # Currently doing this for master, but could be done for PRs as well branches: - - 'master' + - "master" # Only updates to these files result in changes to installed packages, so skip otherwise paths: - - '**/package-lock.json' - - '**/yarn.lock' + - "**/package-lock.json" + - "**/yarn.lock" jobs: devcontainer: @@ -38,4 +38,3 @@ jobs: if [ "$GIT_BRANCH" == "" ]; then GIT_BRANCH=master; fi .devcontainer/cache/build-cache-image.sh "${{ secrets.CONTAINER_IMAGE_REGISTRY }}/public/vscode/devcontainers/repos/microsoft/vscode" "${GIT_BRANCH}" - diff --git a/.github/workflows/english-please.yml b/.github/workflows/english-please.yml index 0fdae28b0..5253fb73d 100644 --- a/.github/workflows/english-please.yml +++ b/.github/workflows/english-please.yml @@ -12,8 +12,8 @@ jobs: if: contains(github.event.issue.labels.*.name, '*english-please') uses: actions/checkout@v2 with: - repository: 'microsoft/vscode-github-triage-actions' - ref: v37 + repository: "microsoft/vscode-github-triage-actions" + ref: v41 path: ./actions - name: Install Actions if: contains(github.event.issue.labels.*.name, '*english-please') diff --git a/.github/workflows/feature-request.yml b/.github/workflows/feature-request.yml index 005e8818b..ec3bbb5e1 100644 --- a/.github/workflows/feature-request.yml +++ b/.github/workflows/feature-request.yml @@ -16,9 +16,9 @@ jobs: if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'feature-request') uses: actions/checkout@v2 with: - repository: 'microsoft/vscode-github-triage-actions' + repository: "microsoft/vscode-github-triage-actions" path: ./actions - ref: v37 + ref: v41 - name: Install Actions if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'feature-request') run: npm install --production --prefix ./actions diff --git a/.github/workflows/latest-release-monitor.yml b/.github/workflows/latest-release-monitor.yml index f4fe00799..b27d79fda 100644 --- a/.github/workflows/latest-release-monitor.yml +++ b/.github/workflows/latest-release-monitor.yml @@ -12,9 +12,9 @@ jobs: - name: Checkout Actions uses: actions/checkout@v2 with: - repository: 'microsoft/vscode-github-triage-actions' + repository: "microsoft/vscode-github-triage-actions" path: ./actions - ref: v37 + ref: v41 - name: Install Actions run: npm install --production --prefix ./actions - name: Install Storage Module diff --git a/.github/workflows/locker.yml b/.github/workflows/locker.yml index 4d5e9ea1b..dca0f5be6 100644 --- a/.github/workflows/locker.yml +++ b/.github/workflows/locker.yml @@ -12,9 +12,9 @@ jobs: - name: Checkout Actions uses: actions/checkout@v2 with: - repository: 'microsoft/vscode-github-triage-actions' + repository: "microsoft/vscode-github-triage-actions" path: ./actions - ref: v37 + ref: v41 - name: Install Actions run: npm install --production --prefix ./actions - name: Run Locker @@ -24,3 +24,5 @@ jobs: appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}} daysSinceUpdate: 3 ignoredLabel: "*out-of-scope" + ignoreLabelUntil: "author-verification-requested" + labelUntil: "verified" diff --git a/.github/workflows/needs-more-info-closer.yml b/.github/workflows/needs-more-info-closer.yml index 8ef8347f6..75b7af423 100644 --- a/.github/workflows/needs-more-info-closer.yml +++ b/.github/workflows/needs-more-info-closer.yml @@ -12,9 +12,9 @@ jobs: - name: Checkout Actions uses: actions/checkout@v2 with: - repository: 'microsoft/vscode-github-triage-actions' + repository: "microsoft/vscode-github-triage-actions" path: ./actions - ref: v37 + ref: v41 - name: Install Actions run: npm install --production --prefix ./actions - name: Run Needs More Info Closer @@ -24,7 +24,7 @@ jobs: token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} label: needs more info closeDays: 7 - additionalTeam: "cleidigh|usernamehw|gjsjohnmurray|IllusionMH" + additionalTeam: "cleidigh|usernamehw|gjsjohnmurray|IllusionMH" closeComment: "This issue has been closed automatically because it needs more information and has not had recent activity. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" pingDays: 80 pingComment: "Hey @${assignee}, this issue might need further attention.\n\n@${author}, you can help us out by closing this issue if the problem no longer exists, or adding more information." diff --git a/.github/workflows/on-label.yml b/.github/workflows/on-label.yml index 60d2cfe22..67b8ec94c 100644 --- a/.github/workflows/on-label.yml +++ b/.github/workflows/on-label.yml @@ -10,8 +10,8 @@ jobs: - name: Checkout Actions uses: actions/checkout@v2 with: - repository: 'microsoft/vscode-github-triage-actions' - ref: v37 + repository: "microsoft/vscode-github-triage-actions" + ref: v41 path: ./actions - name: Install Actions run: npm install --production --prefix ./actions @@ -28,7 +28,7 @@ jobs: uses: ./actions/author-verified with: appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}} - requestVerificationComment: "This bug has been fixed in to the latest release of [VS Code Insiders](https://code.visualstudio.com/insiders/)!\n\n@${author}, you can help us out by confirming things are working as expected in the latest Insiders release. If things look good, please leave a comment with the text `/verified` to let us know. If not, please ensure you're on version ${commit} of Insiders (today's or later - you can use `Help: About` in the command pallete to check), and leave a comment letting us know what isn't working as expected.\n\nHappy Coding!" + requestVerificationComment: "This bug has been fixed in to the latest release of [VS Code Insiders](https://code.visualstudio.com/insiders/)!\n\n@${author}, you can help us out by confirming things are working as expected in the latest Insiders release. If things look good, please leave a comment with the text `/verified` to let us know. If not, please ensure you're on version ${commit} of Insiders (today's or later - you can use `Help: About` in the command palette to check), and leave a comment letting us know what isn't working as expected.\n\nHappy Coding!" pendingReleaseLabel: awaiting-insiders-release verifiedLabel: verified authorVerificationRequestedLabel: author-verification-requested diff --git a/.github/workflows/on-open.yml b/.github/workflows/on-open.yml index ba392bc4c..ebfa1cb34 100644 --- a/.github/workflows/on-open.yml +++ b/.github/workflows/on-open.yml @@ -10,8 +10,8 @@ jobs: - name: Checkout Actions uses: actions/checkout@v2 with: - repository: 'microsoft/vscode-github-triage-actions' - ref: v37 + repository: "microsoft/vscode-github-triage-actions" + ref: v41 path: ./actions - name: Install Actions run: npm install --production --prefix ./actions diff --git a/.github/workflows/release-pipeline-labeler.yml b/.github/workflows/release-pipeline-labeler.yml index 0b38142f2..e5ea1a26b 100644 --- a/.github/workflows/release-pipeline-labeler.yml +++ b/.github/workflows/release-pipeline-labeler.yml @@ -12,8 +12,8 @@ jobs: - name: Checkout Actions uses: actions/checkout@v2 with: - repository: 'microsoft/vscode-github-triage-actions' - ref: v37 + repository: "microsoft/vscode-github-triage-actions" + ref: v41 path: ./actions - name: Checkout Repo if: github.event_name != 'issues' diff --git a/.github/workflows/rich-navigation.yml b/.github/workflows/rich-navigation.yml index bd2444b60..aee0796fa 100644 --- a/.github/workflows/rich-navigation.yml +++ b/.github/workflows/rich-navigation.yml @@ -9,24 +9,28 @@ jobs: richnav: runs-on: windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v2 - - uses: actions/cache@v2 - id: caching-stage - name: Cache VS Code dependencies - with: - path: node_modules - key: ${{ runner.os }}-dependencies-${{ hashfiles('yarn.lock') }} - restore-keys: ${{ runner.os }}-dependencies- + - uses: actions/cache@v2 + id: caching-stage + name: Cache VS Code dependencies + with: + path: node_modules + key: ${{ runner.os }}-dependencies-${{ hashfiles('yarn.lock') }} + restore-keys: ${{ runner.os }}-dependencies- - - name: Install dependencies - if: steps.caching-stage.outputs.cache-hit != 'true' - run: yarn --frozen-lockfile - env: - CHILD_CONCURRENCY: 1 + - uses: actions/setup-node@v1 + with: + node-version: 10 - - uses: microsoft/RichCodeNavIndexer@v0.1 - with: - languages: typescript - repo-token: ${{ secrets.GITHUB_TOKEN }} - continue-on-error: true + - name: Install dependencies + if: steps.caching-stage.outputs.cache-hit != 'true' + run: yarn --frozen-lockfile + env: + CHILD_CONCURRENCY: 1 + + - uses: microsoft/RichCodeNavIndexer@v0.1 + with: + languages: typescript + repo-token: ${{ secrets.GITHUB_TOKEN }} + continue-on-error: true diff --git a/.github/workflows/test-plan-item-validator.yml b/.github/workflows/test-plan-item-validator.yml index 3174a463b..6c2752107 100644 --- a/.github/workflows/test-plan-item-validator.yml +++ b/.github/workflows/test-plan-item-validator.yml @@ -12,9 +12,9 @@ jobs: if: contains(github.event.issue.labels.*.name, 'testplan-item') || contains(github.event.issue.labels.*.name, 'invalid-testplan-item') uses: actions/checkout@v2 with: - repository: 'microsoft/vscode-github-triage-actions' + repository: "microsoft/vscode-github-triage-actions" path: ./actions - ref: v37 + ref: v41 - name: Install Actions if: contains(github.event.issue.labels.*.name, 'testplan-item') || contains(github.event.issue.labels.*.name, 'invalid-testplan-item') run: npm install --production --prefix ./actions diff --git a/.vscode/launch.json b/.vscode/launch.json index 6153d7a99..a83c164f3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -41,10 +41,7 @@ "port": 5876, "outFiles": [ "${workspaceFolder}/out/**/*.js" - ], - "presentation": { - "hidden": true, - } + ] }, { "type": "node", @@ -200,7 +197,8 @@ "request": "attach", "name": "Attach to VS Code", "browserAttachLocation": "workspace", - "port": 9222 + "port": 9222, + "perScriptSourcemaps": "yes" }, { "type": "pwa-chrome", @@ -377,7 +375,7 @@ } }, { - "type": "node", + "type": "pwa-node", "request": "launch", "name": "Run Unit Tests", "program": "${workspaceFolder}/test/unit/electron/index.js", @@ -396,6 +394,41 @@ "outFiles": [ "${workspaceFolder}/out/**/*.js" ], + "cascadeTerminateToConfigurations": [ + "Attach to VS Code" + ], + "env": { + "MOCHA_COLORS": "true" + }, + "presentation": { + "hidden": true + } + }, + { + "type": "pwa-node", + "request": "launch", + "name": "Run Unit Tests For Current File", + "program": "${workspaceFolder}/test/unit/electron/index.js", + "runtimeExecutable": "${workspaceFolder}/.build/electron/Code - OSS.app/Contents/MacOS/Electron", + "windows": { + "runtimeExecutable": "${workspaceFolder}/.build/electron/Code - OSS.exe" + }, + "linux": { + "runtimeExecutable": "${workspaceFolder}/.build/electron/code-oss" + }, + "cascadeTerminateToConfigurations": [ + "Attach to VS Code" + ], + "outputCapture": "std", + "args": [ + "--remote-debugging-port=9222", + "--run", + "${relativeFile}" + ], + "cwd": "${workspaceFolder}", + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], "env": { "MOCHA_COLORS": "true" }, @@ -482,6 +515,17 @@ "group": "1_vscode", "order": 2 } + }, + { + "name": "Debug Unit Tests (Current File)", + "configurations": [ + "Attach to VS Code", + "Run Unit Tests For Current File" + ], + "presentation": { + "group": "1_vscode", + "order": 2 + } } ] } diff --git a/.vscode/notebooks/api.github-issues b/.vscode/notebooks/api.github-issues index 2eb4b6432..8ff55e2c6 100644 --- a/.vscode/notebooks/api.github-issues +++ b/.vscode/notebooks/api.github-issues @@ -8,7 +8,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"September 2020\"", + "value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"November 2020\"", "editable": true }, { diff --git a/.vscode/notebooks/endgame.github-issues b/.vscode/notebooks/endgame.github-issues index 0d6e45334..e749a11c4 100644 --- a/.vscode/notebooks/endgame.github-issues +++ b/.vscode/notebooks/endgame.github-issues @@ -2,43 +2,49 @@ { "kind": 1, "language": "markdown", - "value": "# Endgame", - "editable": true - }, - { - "kind": 1, - "language": "markdown", - "value": "## Macros", + "value": "#### Macros", "editable": true }, { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github\n\n$MILESTONE=milestone:\"October 2020\"\n\n$MINE=assignee:@me", + "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server\n\n$MILESTONE=milestone:\"November 2020\"", + "editable": false + }, + { + "kind": 1, + "language": "markdown", + "value": "# Preparation", "editable": true }, { "kind": 1, "language": "markdown", - "value": "## Endgame Champion", - "editable": true - }, - { - "kind": 1, - "language": "markdown", - "value": "### Test Plan Items", + "value": "## Open Pull Requests on the Milestone", "editable": true }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE label:testplan-item is:open", + "value": "$REPOS $MILESTONE is:pr is:open", "editable": true }, { "kind": 1, "language": "markdown", - "value": "### Feature Requests Missing Labels", + "value": "## Open Issues on the Milestone", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "$REPOS $MILESTONE is:issue is:open -label:iteration-plan -label:endgame-plan -label:testplan-item", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "## Feature Requests Missing Labels", "editable": true }, { @@ -50,133 +56,55 @@ { "kind": 1, "language": "markdown", - "value": "### Open Issues on the Milestone", + "value": "# Testing", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "## Test Plan Items", "editable": true }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -label:testplan-item -label:iteration-plan -label:endgame-plan is:open", + "value": "$REPOS $MILESTONE is:issue is:open label:testplan-item", "editable": true }, { "kind": 1, "language": "markdown", - "value": "## Testing", - "editable": true - }, - { - "kind": 1, - "language": "markdown", - "value": "### My Assigned Test Plan Items", + "value": "## Verification Needed", "editable": true }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE label:testplan-item is:open", + "value": "$REPOS $MILESTONE is:issue is:closed label:feature-request label:verification-needed -label:verified", "editable": true }, { "kind": 1, "language": "markdown", - "value": "### My Created Test Plan Items", + "value": "# Verification", "editable": true }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE author:@me label:testplan-item", + "value": "$REPOS $MILESTONE is:issue is:closed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found", "editable": true }, { "kind": 1, "language": "markdown", - "value": "## Verification", - "editable": true - }, - { - "kind": 1, - "language": "markdown", - "value": "### Issues to Verify", - "editable": true - }, - { - "kind": 1, - "language": "markdown", - "value": "#### Issues filed by me", + "value": "# Candidates", "editable": true }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE author:@me sort:updated-asc is:closed is:issue label:bug -label:verified -label:on-testplan -label:duplicate -label:*duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found", - "editable": true - }, - { - "kind": 1, - "language": "markdown", - "value": "#### Issues filed by others", - "editable": true - }, - { - "kind": 2, - "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE -author:@me sort:updated-asc is:closed is:issue label:bug -label:verified -label:on-testplan -label:duplicate -label:*duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found", - "editable": true - }, - { - "kind": 1, - "language": "markdown", - "value": "### Verification Needed", - "editable": true - }, - { - "kind": 2, - "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:closed label:feature-request -label:verification-needed -label:on-testplan -label:verified -label:*duplicate ", - "editable": true - }, - { - "kind": 1, - "language": "markdown", - "value": "### My Issues Needing Verification", - "editable": true - }, - { - "kind": 2, - "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE sort:updated-desc is:closed is:issue -label:verified -label:on-testplan -label:duplicate -label:*duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found\n", - "editable": true - }, - { - "kind": 1, - "language": "markdown", - "value": "### My Open Issues", - "editable": true - }, - { - "kind": 2, - "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:open is:issue", - "editable": true - }, - { - "kind": 1, - "language": "markdown", - "value": "## Documentation", - "editable": true - }, - { - "kind": 1, - "language": "markdown", - "value": "### Needing Release Notes", - "editable": true - }, - { - "kind": 2, - "language": "github-issues", - "value": "repo:microsoft/vscode $MILESTONE $MINE is:closed label:feature-request -label:on-release-notes", + "value": "$REPOS $MILESTONE label:candidate", "editable": true } ] \ No newline at end of file diff --git a/.vscode/notebooks/grooming-delta.github-issues b/.vscode/notebooks/grooming-delta.github-issues new file mode 100644 index 000000000..8bc9d38b4 --- /dev/null +++ b/.vscode/notebooks/grooming-delta.github-issues @@ -0,0 +1,767 @@ +[ + { + "kind": 1, + "language": "markdown", + "value": "## Config", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "$since=2020-10-01", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode\n\nQuery exceeds the maximum result. Run the query manually: `is:issue is:open closed:>2020-10-01`", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "//repo:microsoft/vscode is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "//repo:microsoft/vscode is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-remote-release", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-remote-release is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-remote-release is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# monaco-editor", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/monaco-editor is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/monaco-editor is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-docs", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-docs is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-docs is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-js-debug", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-js-debug is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-js-debug is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# language-server-protocol", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/language-server-protocol is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/language-server-protocol is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-eslint", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-eslint is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-eslint is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-css-languageservice", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-css-languageservice is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-css-languageservice is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-test", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-test is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-test is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-pull-request-github" + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-pull-request-github is:issue closed:>$since" + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-test is:issue created:>$since" + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-chrome-debug (deprecated)", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-chrome-debug is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-chrome-debug is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-chrome-debug-core", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-chrome-debug-core is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-chrome-debug-core is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-debugadapter-node", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-debugadapter-node is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-debugadapter-node is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-emmet-helper", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-emmet-helper is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-emmet-helper is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-extension-vscode\n\nDeprecated", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-extension-vscode is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-extension-vscode is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-extension-samples", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-extension-samples is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-extension-samples is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-filewatcher-windows", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-filewatcher-windows is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-filewatcher-windows is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-generator-code", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-generator-code is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-generator-code is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-html-languageservice", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-html-languageservice is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-html-languageservice is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-jshint", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-jshint is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-jshint is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-json-languageservice", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-json-languageservice is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-json-languageservice is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-languageserver-node", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-languageserver-node is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-languageserver-node is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-loader", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-loader is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-loader is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-mono-debug", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-mono-debug is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-mono-debug is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-node-debug", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-node-debug is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-node-debug is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-node-debug2", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-node-debug2 is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-node-debug2 is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-recipes", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-recipes is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-recipes is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-textmate", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-textmate is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-textmate is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-themes", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-themes is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-themes is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-vsce", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-vsce is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-vsce is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-website", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-website is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-website is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# vscode-windows-process-tree", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-windows-process-tree is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode-windows-process-tree is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# debug-adapter-protocol", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/debug-adapter-protocol is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/debug-adapter-protocol is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# inno-updater", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/inno-updater is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/inno-updater is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# language-server-protocol-inspector", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/language-server-protocol-inspector is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/language-server-protocol-inspector is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# monaco-languages", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/monaco-languages is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/monaco-languages is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# monaco-typescript", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/monaco-typescript is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/monaco-typescript is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# monaco-css", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/monaco-css is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/monaco-css is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# monaco-json", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/monaco-json is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/monaco-json is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# monaco-html", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/monaco-html is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/monaco-html is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# monaco-editor-webpack-plugin", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/monaco-editor-webpack-plugin is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/monaco-editor-webpack-plugin is:issue created:>$since", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# node-jsonc-parser", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/node-jsonc-parser is:issue closed:>$since", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/node-jsonc-parser is:issue created:>$since", + "editable": true + } +] \ No newline at end of file diff --git a/.vscode/notebooks/grooming.github-issues b/.vscode/notebooks/grooming.github-issues index 687ea5b57..f44b2c71e 100644 --- a/.vscode/notebooks/grooming.github-issues +++ b/.vscode/notebooks/grooming.github-issues @@ -20,7 +20,7 @@ { "kind": 2, "language": "github-issues", - "value": "repo:microsoft/vscode is:open is:issue assignee:@me -label:L10N -label:VIM -label:api -label:api-finalization -label:api-proposal -label:authentication -label:breadcrumbs -label:callhierarchy -label:code-lens -label:color-palette -label:comments -label:config -label:context-keys -label:css-less-scss -label:custom-editors -label:debug -label:debug-console -label:dialogs -label:diff-editor -label:dropdown -label:editor -label:editor-RTL -label:editor-autoclosing -label:editor-autoindent -label:editor-bracket-matching -label:editor-clipboard -label:editor-code-actions -label:editor-color-picker -label:editor-columnselect -label:editor-commands -label:editor-comments -label:editor-contrib -label:editor-core -label:editor-drag-and-drop -label:editor-error-widget -label:editor-find -label:editor-folding -label:editor-highlight -label:editor-hover -label:editor-indent-detection -label:editor-indent-guides -label:editor-input -label:editor-input-IME -label:editor-insets -label:editor-minimap -label:editor-multicursor -label:editor-parameter-hints -label:editor-render-whitespace -label:editor-rendering -label:editor-scrollbar -label:editor-symbols -label:editor-synced-region -label:editor-textbuffer -label:editor-theming -label:editor-wordnav -label:editor-wrapping -label:emmet -label:error-list -label:explorer-custom -label:extension-host -label:extension-recommendations -label:extensions -label:extensions-development -label:file-decorations -label:file-encoding -label:file-explorer -label:file-glob -label:file-guess-encoding -label:file-io -label:file-watcher -label:font-rendering -label:formatting -label:git -label:github -label:gpu -label:grammar -label:grid-view -label:html -label:i18n -label:icon-brand -label:icons-product -label:install-update -label:integrated-terminal -label:integrated-terminal-conpty -label:integrated-terminal-links -label:integrated-terminal-rendering -label:integrated-terminal-winpty -label:intellisense-config -label:ipc -label:issue-bot -label:issue-reporter -label:javascript -label:json -label:keybindings -label:keybindings-editor -label:keyboard-layout -label:label-provider -label:languages-basic -label:languages-diagnostics -label:languages-guessing -label:layout -label:lcd-text-rendering -label:list -label:log -label:markdown -label:marketplace -label:menus -label:merge-conflict -label:notebook -label:outline -label:output -label:perf -label:perf-bloat -label:perf-startup -label:php -label:portable-mode -label:proxy -label:quick-pick -label:references-viewlet -label:release-notes -label:remote -label:remote-explorer -label:rename -label:samples -label:sandbox -label:scm -label:screencast-mode -label:search -label:search-api -label:search-editor -label:search-replace -label:semantic-tokens -label:settings-editor -label:settings-sync -label:settings-sync-server -label:shared-process -label:simple-file-dialog -label:smart-select -label:snap -label:snippets -label:splitview -label:suggest -label:sync-error-handling -label:tasks -label:telemetry -label:themes -label:timeline -label:timeline-git -label:titlebar -label:tokenization -label:touch/pointer -label:trackpad/scroll -label:tree -label:typescript -label:undo-redo -label:uri -label:ux -label:variable-resolving -label:vscode-build -label:vscode-website -label:web -label:webview -label:workbench-actions -label:workbench-cli -label:workbench-diagnostics -label:workbench-dnd -label:workbench-editor-grid -label:workbench-editors -label:workbench-electron -label:workbench-feedback -label:workbench-history -label:workbench-hot-exit -label:workbench-hover -label:workbench-launch -label:workbench-link -label:workbench-multiroot -label:workbench-notifications -label:workbench-os-integration -label:workbench-rapid-render -label:workbench-run-as-admin -label:workbench-state -label:workbench-status -label:workbench-tabs -label:workbench-touchbar -label:workbench-views -label:workbench-welcome -label:workbench-window -label:workbench-zen -label:workspace-edit -label:workspace-symbols -label:zoom", + "value": "repo:microsoft/vscode is:open is:issue assignee:@me -label:L10N -label:VIM -label:api -label:api-finalization -label:api-proposal -label:authentication -label:breadcrumbs -label:callhierarchy -label:code-lens -label:color-palette -label:comments -label:config -label:context-keys -label:css-less-scss -label:custom-editors -label:debug -label:debug-console -label:dialogs -label:diff-editor -label:dropdown -label:editor -label:editor-RTL -label:editor-autoclosing -label:editor-autoindent -label:editor-bracket-matching -label:editor-clipboard -label:editor-code-actions -label:editor-color-picker -label:editor-columnselect -label:editor-commands -label:editor-comments -label:editor-contrib -label:editor-core -label:editor-drag-and-drop -label:editor-error-widget -label:editor-find -label:editor-folding -label:editor-highlight -label:editor-hover -label:editor-indent-detection -label:editor-indent-guides -label:editor-input -label:editor-input-IME -label:editor-insets -label:editor-minimap -label:editor-multicursor -label:editor-parameter-hints -label:editor-render-whitespace -label:editor-rendering -label:editor-scrollbar -label:editor-symbols -label:editor-synced-region -label:editor-textbuffer -label:editor-theming -label:editor-wordnav -label:editor-wrapping -label:emmet -label:error-list -label:explorer-custom -label:extension-host -label:extension-recommendations -label:extensions -label:extensions-development -label:file-decorations -label:file-encoding -label:file-explorer -label:file-glob -label:file-guess-encoding -label:file-io -label:file-watcher -label:font-rendering -label:formatting -label:git -label:github -label:gpu -label:grammar -label:grid-view -label:html -label:i18n -label:icon-brand -label:icons-product -label:install-update -label:integrated-terminal -label:integrated-terminal-conpty -label:integrated-terminal-links -label:integrated-terminal-rendering -label:integrated-terminal-winpty -label:intellisense-config -label:ipc -label:issue-bot -label:issue-reporter -label:javascript -label:json -label:keybindings -label:keybindings-editor -label:keyboard-layout -label:label-provider -label:languages-basic -label:languages-diagnostics -label:languages-guessing -label:layout -label:lcd-text-rendering -label:list -label:log -label:markdown -label:marketplace -label:menus -label:merge-conflict -label:notebook -label:outline -label:output -label:perf -label:perf-bloat -label:perf-startup -label:php -label:portable-mode -label:proxy -label:quick-pick -label:references-viewlet -label:release-notes -label:remote -label:remote-explorer -label:rename -label:sandbox -label:scm -label:screencast-mode -label:search -label:search-api -label:search-editor -label:search-replace -label:semantic-tokens -label:settings-editor -label:settings-sync -label:settings-sync-server -label:shared-process -label:simple-file-dialog -label:smart-select -label:snap -label:snippets -label:splitview -label:suggest -label:sync-error-handling -label:tasks -label:telemetry -label:themes -label:timeline -label:timeline-git -label:titlebar -label:tokenization -label:touch/pointer -label:trackpad/scroll -label:tree -label:typescript -label:undo-redo -label:uri -label:ux -label:variable-resolving -label:vscode-build -label:vscode-website -label:web -label:webview -label:workbench-actions -label:workbench-cli -label:workbench-diagnostics -label:workbench-dnd -label:workbench-editor-grid -label:workbench-editors -label:workbench-electron -label:workbench-feedback -label:workbench-history -label:workbench-hot-exit -label:workbench-hover -label:workbench-launch -label:workbench-link -label:workbench-multiroot -label:workbench-notifications -label:workbench-os-integration -label:workbench-rapid-render -label:workbench-run-as-admin -label:workbench-state -label:workbench-status -label:workbench-tabs -label:workbench-touchbar -label:workbench-views -label:workbench-welcome -label:workbench-window -label:workbench-zen -label:workspace-edit -label:workspace-symbols -label:zoom", "editable": true } ] \ No newline at end of file diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues new file mode 100644 index 000000000..54ec5328e --- /dev/null +++ b/.vscode/notebooks/my-endgame.github-issues @@ -0,0 +1,218 @@ +[ + { + "kind": 1, + "language": "markdown", + "value": "#### Macros", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server\n\n$MILESTONE=milestone:\"November 2020\"\n\n$MINE=assignee:@me", + "editable": false + }, + { + "kind": 1, + "language": "markdown", + "value": "# Preparation", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "## Open Pull Requests on the Milestone", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "$REPOS $MILESTONE $MINE is:pr is:open", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "## Open Issues on the Milestone", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "$REPOS $MILESTONE $MINE is:issue is:open -label:iteration-plan -label:endgame-plan -label:testplan-item", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "## Feature Requests Missing Labels", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "$REPOS $MILESTONE $MINE is:issue is:closed label:feature-request -label:verification-needed -label:on-testplan -label:verified -label:*duplicate", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "## Test Plan Items", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "$REPOS $MILESTONE is:issue is:open author:@me label:testplan-item", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "## Verification Needed", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "$REPOS $MILESTONE $MINE is:issue is:closed label:feature-request label:verification-needed", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# Testing", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "## Test Plan Items", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "$REPOS $MILESTONE $MINE is:issue is:open label:testplan-item", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "## Verification Needed", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed -assignee:@me -label:verified label:feature-request label:verification-needed", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# Fixing", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "## Open Issues", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "$REPOS $MILESTONE $MINE is:issue is:open -label:endgame-plan", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "## Open Bugs", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "$REPOS $MILESTONE $MINE is:issue is:open label:bug", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# Verification", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "## My Issues (verification-steps-needed)", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "$REPOS $MILESTONE $MINE is:issue is:open label:bug label:verification-steps-needed", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "## My Issues (verification-found)", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "$REPOS $MILESTONE $MINE is:issue is:open label:bug label:verification-found", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "## Issues filed by me", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed author:@me sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "## Issues filed from outside team", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:bamurtaugh -author:bpasero -author:btholt -author:chrisdias -author:chrmarti -author:Chuxel -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:eamodio -author:egamma -author:fiveisprime -author:gregvanl -author:isidorn -author:ItalyPaleAle -author:JacksonKearl -author:joaomoreno -author:jrieken -author:kieferrm -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:ornellaalt -author:orta -author:rebornix -author:RMacfarlane -author:roblourens -author:rzhao271 -author:sana-ajani -author:sandy081 -author:sbatten -author:stevencl -author:Tyriar -author:weinand", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "## Issues filed by others", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed -author:@me sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "# Release Notes", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "repo:microsoft/vscode $MILESTONE is:issue is:closed label:feature-request -label:on-release-notes", + "editable": true + } +] \ No newline at end of file diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index a5de65e0b..55fc585e3 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -8,7 +8,7 @@ { "kind": 2, "language": "github-issues", - "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks\n\n// current milestone name\n$milestone=milestone:\"September 2020\"", + "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog\n\n// current milestone name\n$milestone=milestone:\"November 2020\"", "editable": true }, { diff --git a/.vscode/notebooks/verification.github-issues b/.vscode/notebooks/verification.github-issues index 67db0cb97..582859397 100644 --- a/.vscode/notebooks/verification.github-issues +++ b/.vscode/notebooks/verification.github-issues @@ -14,7 +14,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repos=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks \n$milestone=milestone:\"October 2020\"", + "value": "$repos=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks \n$milestone=milestone:\"November 2020\"", "editable": true }, { diff --git a/.yarnrc b/.yarnrc index d97527dab..264a5e3a3 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://electronjs.org/headers" -target "9.3.3" +target "9.3.5" runtime "electron" diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index 074844d81..0d0db3aca 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -28,44 +28,45 @@ This project incorporates components from the projects listed below. The origina 21. Ionic documentation version 1.2.4 (https://github.com/ionic-team/ionic-site) 22. ionide/ionide-fsgrammar (https://github.com/ionide/ionide-fsgrammar) 23. jeff-hykin/cpp-textmate-grammar version 1.12.11 (https://github.com/jeff-hykin/cpp-textmate-grammar) -24. jeff-hykin/cpp-textmate-grammar version 1.14.15 (https://github.com/jeff-hykin/cpp-textmate-grammar) +24. jeff-hykin/cpp-textmate-grammar version 1.15.3 (https://github.com/jeff-hykin/cpp-textmate-grammar) 25. js-beautify version 1.6.8 (https://github.com/beautify-web/js-beautify) 26. Jxck/assert version 1.0.0 (https://github.com/Jxck/assert) 27. language-docker (https://github.com/moby/moby) 28. language-less version 0.34.2 (https://github.com/atom/language-less) 29. language-php version 0.44.5 (https://github.com/atom/language-php) -30. language-rust version 0.4.12 (https://github.com/zargony/atom-language-rust) -31. MagicStack/MagicPython version 1.1.1 (https://github.com/MagicStack/MagicPython) -32. marked version 0.6.2 (https://github.com/markedjs/marked) -33. mdn-data version 1.1.12 (https://github.com/mdn/data) -34. microsoft/TypeScript-TmLanguage version 0.0.1 (https://github.com/microsoft/TypeScript-TmLanguage) -35. microsoft/vscode-JSON.tmLanguage (https://github.com/microsoft/vscode-JSON.tmLanguage) +30. MagicStack/MagicPython version 1.1.1 (https://github.com/MagicStack/MagicPython) +31. marked version 1.1.0 (https://github.com/markedjs/marked) +32. mdn-data version 1.1.12 (https://github.com/mdn/data) +33. microsoft/TypeScript-TmLanguage version 0.0.1 (https://github.com/microsoft/TypeScript-TmLanguage) +34. microsoft/vscode-JSON.tmLanguage (https://github.com/microsoft/vscode-JSON.tmLanguage) +35. microsoft/vscode-markdown-tm-grammar (https://github.com/microsoft/vscode-markdown-tm-grammar) 36. microsoft/vscode-mssql version 1.9.0 (https://github.com/microsoft/vscode-mssql) 37. mmims/language-batchfile version 0.7.5 (https://github.com/mmims/language-batchfile) 38. octref/language-css version 0.42.11 (https://github.com/octref/language-css) 39. PowerShell/EditorSyntax version 1.0.0 (https://github.com/PowerShell/EditorSyntax) -40. seti-ui version 0.1.0 (https://github.com/jesseweed/seti-ui) -41. shaders-tmLanguage version 0.1.0 (https://github.com/tgjones/shaders-tmLanguage) -42. textmate/asp.vb.net.tmbundle (https://github.com/textmate/asp.vb.net.tmbundle) -43. textmate/c.tmbundle (https://github.com/textmate/c.tmbundle) -44. textmate/diff.tmbundle (https://github.com/textmate/diff.tmbundle) -45. textmate/git.tmbundle (https://github.com/textmate/git.tmbundle) -46. textmate/groovy.tmbundle (https://github.com/textmate/groovy.tmbundle) -47. textmate/html.tmbundle (https://github.com/textmate/html.tmbundle) -48. textmate/ini.tmbundle (https://github.com/textmate/ini.tmbundle) -49. textmate/javascript.tmbundle (https://github.com/textmate/javascript.tmbundle) -50. textmate/lua.tmbundle (https://github.com/textmate/lua.tmbundle) -51. textmate/markdown.tmbundle (https://github.com/textmate/markdown.tmbundle) -52. textmate/perl.tmbundle (https://github.com/textmate/perl.tmbundle) -53. textmate/ruby.tmbundle (https://github.com/textmate/ruby.tmbundle) -54. textmate/yaml.tmbundle (https://github.com/textmate/yaml.tmbundle) -55. TypeScript-TmLanguage version 0.1.8 (https://github.com/microsoft/TypeScript-TmLanguage) -56. TypeScript-TmLanguage version 1.0.0 (https://github.com/microsoft/TypeScript-TmLanguage) -57. Unicode version 12.0.0 (https://home.unicode.org/) -58. vscode-codicons version 0.0.1 (https://github.com/microsoft/vscode-codicons) -59. vscode-logfile-highlighter version 2.8.0 (https://github.com/emilast/vscode-logfile-highlighter) -60. vscode-swift version 0.0.1 (https://github.com/owensd/vscode-swift) -61. Web Background Synchronization (https://github.com/WICG/background-sync) +40. rust-syntax version 0.2.13 (https://github.com/dustypomerleau/rust-syntax) +41. seti-ui version 0.1.0 (https://github.com/jesseweed/seti-ui) +42. shaders-tmLanguage version 0.1.0 (https://github.com/tgjones/shaders-tmLanguage) +43. textmate/asp.vb.net.tmbundle (https://github.com/textmate/asp.vb.net.tmbundle) +44. textmate/c.tmbundle (https://github.com/textmate/c.tmbundle) +45. textmate/diff.tmbundle (https://github.com/textmate/diff.tmbundle) +46. textmate/git.tmbundle (https://github.com/textmate/git.tmbundle) +47. textmate/groovy.tmbundle (https://github.com/textmate/groovy.tmbundle) +48. textmate/html.tmbundle (https://github.com/textmate/html.tmbundle) +49. textmate/ini.tmbundle (https://github.com/textmate/ini.tmbundle) +50. textmate/javascript.tmbundle (https://github.com/textmate/javascript.tmbundle) +51. textmate/lua.tmbundle (https://github.com/textmate/lua.tmbundle) +52. textmate/markdown.tmbundle (https://github.com/textmate/markdown.tmbundle) +53. textmate/perl.tmbundle (https://github.com/textmate/perl.tmbundle) +54. textmate/ruby.tmbundle (https://github.com/textmate/ruby.tmbundle) +55. textmate/yaml.tmbundle (https://github.com/textmate/yaml.tmbundle) +56. TypeScript-TmLanguage version 0.1.8 (https://github.com/microsoft/TypeScript-TmLanguage) +57. TypeScript-TmLanguage version 1.0.0 (https://github.com/microsoft/TypeScript-TmLanguage) +58. Unicode version 12.0.0 (https://home.unicode.org/) +59. vscode-codicons version 0.0.1 (https://github.com/microsoft/vscode-codicons) +60. vscode-logfile-highlighter version 2.8.0 (https://github.com/emilast/vscode-logfile-highlighter) +61. vscode-swift version 0.0.1 (https://github.com/owensd/vscode-swift) +62. Web Background Synchronization (https://github.com/WICG/background-sync) %% atom/language-clojure NOTICES AND INFORMATION BEGIN HERE @@ -1101,31 +1102,6 @@ suitability for any purpose. ========================================= END OF language-php NOTICES AND INFORMATION -%% language-rust NOTICES AND INFORMATION BEGIN HERE -========================================= -The MIT License (MIT) - -Copyright © `2013` `Andreas Neuhaus` `http://zargony.com/` - -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. -========================================= -END OF language-rust NOTICES AND INFORMATION - %% MagicStack/MagicPython NOTICES AND INFORMATION BEGIN HERE ========================================= The MIT License @@ -1164,6 +1140,7 @@ all code is your original work. `` ## Marked +Copyright (c) 2018+, MarkedJS (https://github.com/markedjs/) Copyright (c) 2011-2018, Christopher Jeffrey (https://github.com/chjj/) Permission is hereby granted, free of charge, to any person obtaining a copy @@ -1632,6 +1609,32 @@ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFT ========================================= END OF microsoft/vscode-JSON.tmLanguage NOTICES AND INFORMATION +%% microsoft/vscode-markdown-tm-grammar NOTICES AND INFORMATION BEGIN HERE +========================================= +The MIT License (MIT) + +Copyright (c) Microsoft 2018 + +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. +========================================= +END OF microsoft/vscode-markdown-tm-grammar NOTICES AND INFORMATION + %% microsoft/vscode-mssql NOTICES AND INFORMATION BEGIN HERE ========================================= ------------------------------------------ START OF LICENSE ----------------------------------------- @@ -1738,6 +1741,32 @@ SOFTWARE. ========================================= END OF PowerShell/EditorSyntax NOTICES AND INFORMATION +%% rust-syntax NOTICES AND INFORMATION BEGIN HERE +========================================= +MIT License + +Copyright (c) 2020 Dustin Pomerleau + +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. +========================================= +END OF rust-syntax NOTICES AND INFORMATION + %% seti-ui NOTICES AND INFORMATION BEGIN HERE ========================================= Copyright (c) 2014 Jesse Weed diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b2f9a13c2..cce8119d4 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -16,3 +16,8 @@ jobs: vmImage: macOS-latest steps: - template: build/azure-pipelines/darwin/continuous-build-darwin.yml + +trigger: + branches: + exclude: + - electron-11.x.y diff --git a/build/.cachesalt b/build/.cachesalt index 3f2ee542a..a7a4d5082 100644 --- a/build/.cachesalt +++ b/build/.cachesalt @@ -1 +1 @@ -2020-10-05T20:24:23.714Z +2020-11-30T16:21:34.566Z diff --git a/build/.moduleignore b/build/.moduleignore index 489d4709f..d1f9194ba 100644 --- a/build/.moduleignore +++ b/build/.moduleignore @@ -1,5 +1,117 @@ +# cleanup rules for node modules, .gitignore style -# additional cleanup rules for node modules, .gitignore style +# native node modules + +nan/** +*/node_modules/nan/** + +fsevents/binding.gyp +fsevents/fsevents.cc +fsevents/build/** +fsevents/src/** +fsevents/test/** +!fsevents/**/*.node + +vscode-sqlite3/binding.gyp +vscode-sqlite3/benchmark/** +vscode-sqlite3/cloudformation/** +vscode-sqlite3/deps/** +vscode-sqlite3/test/** +vscode-sqlite3/build/** +vscode-sqlite3/src/** +!vscode-sqlite3/build/Release/*.node + +windows-mutex/binding.gyp +windows-mutex/build/** +windows-mutex/src/** +!windows-mutex/**/*.node + +native-keymap/binding.gyp +native-keymap/build/** +native-keymap/src/** +native-keymap/deps/** +!native-keymap/build/Release/*.node + +native-is-elevated/binding.gyp +native-is-elevated/build/** +native-is-elevated/src/** +native-is-elevated/deps/** +!native-is-elevated/build/Release/*.node + +native-watchdog/binding.gyp +native-watchdog/build/** +native-watchdog/src/** +!native-watchdog/build/Release/*.node + +spdlog/binding.gyp +spdlog/build/** +spdlog/deps/** +spdlog/src/** +spdlog/test/** +spdlog/*.yml +!spdlog/build/Release/*.node + +jschardet/dist/** + +windows-foreground-love/binding.gyp +windows-foreground-love/build/** +windows-foreground-love/src/** +!windows-foreground-love/**/*.node + +windows-process-tree/binding.gyp +windows-process-tree/build/** +windows-process-tree/src/** +!windows-process-tree/**/*.node + +keytar/binding.gyp +keytar/build/** +keytar/src/** +keytar/script/** +keytar/node_modules/** +!keytar/**/*.node + +node-pty/binding.gyp +node-pty/build/** +node-pty/src/** +node-pty/tools/** +node-pty/deps/** +node-pty/scripts/** +!node-pty/build/Release/*.exe +!node-pty/build/Release/*.dll +!node-pty/build/Release/*.node + +vscode-nsfw/binding.gyp +vscode-nsfw/build/** +vscode-nsfw/src/** +vscode-nsfw/openpa/** +vscode-nsfw/includes/** +!vscode-nsfw/build/Release/*.node +!vscode-nsfw/**/*.a + +vsda/build/** +vsda/ci/** +vsda/src/** +vsda/.gitignore +vsda/binding.gyp +vsda/README.md +vsda/targets +!vsda/build/Release/vsda.node + +vscode-encrypt/build/** +vscode-encrypt/src/** +vscode-encrypt/vendor/** +vscode-encrypt/.gitignore +vscode-encrypt/binding.gyp +vscode-encrypt/README.md +!vscode-encrypt/build/Release/vscode-encrypt-native.node + +vscode-windows-ca-certs/**/* +!vscode-windows-ca-certs/package.json +!vscode-windows-ca-certs/**/*.node + +node-addon-api/**/* + +# other node modules **/docs/** **/example/** diff --git a/build/.nativeignore b/build/.nativeignore deleted file mode 100644 index a27530745..000000000 --- a/build/.nativeignore +++ /dev/null @@ -1,102 +0,0 @@ -# cleanup rules for native node modules, .gitignore style - -nan/** -*/node_modules/nan/** - -fsevents/binding.gyp -fsevents/fsevents.cc -fsevents/build/** -fsevents/src/** -fsevents/test/** -!fsevents/**/*.node - -vscode-sqlite3/binding.gyp -vscode-sqlite3/benchmark/** -vscode-sqlite3/cloudformation/** -vscode-sqlite3/deps/** -vscode-sqlite3/test/** -vscode-sqlite3/build/** -vscode-sqlite3/src/** -!vscode-sqlite3/build/Release/*.node - -windows-mutex/binding.gyp -windows-mutex/build/** -windows-mutex/src/** -!windows-mutex/**/*.node - -native-keymap/binding.gyp -native-keymap/build/** -native-keymap/src/** -native-keymap/deps/** -!native-keymap/build/Release/*.node - -native-is-elevated/binding.gyp -native-is-elevated/build/** -native-is-elevated/src/** -native-is-elevated/deps/** -!native-is-elevated/build/Release/*.node - -native-watchdog/binding.gyp -native-watchdog/build/** -native-watchdog/src/** -!native-watchdog/build/Release/*.node - -spdlog/binding.gyp -spdlog/build/** -spdlog/deps/** -spdlog/src/** -spdlog/test/** -spdlog/*.yml -!spdlog/build/Release/*.node - -jschardet/dist/** - -windows-foreground-love/binding.gyp -windows-foreground-love/build/** -windows-foreground-love/src/** -!windows-foreground-love/**/*.node - -windows-process-tree/binding.gyp -windows-process-tree/build/** -windows-process-tree/src/** -!windows-process-tree/**/*.node - -keytar/binding.gyp -keytar/build/** -keytar/src/** -keytar/script/** -keytar/node_modules/** -!keytar/**/*.node - -node-pty/binding.gyp -node-pty/build/** -node-pty/src/** -node-pty/tools/** -node-pty/deps/** -node-pty/scripts/** -!node-pty/build/Release/*.exe -!node-pty/build/Release/*.dll -!node-pty/build/Release/*.node - -vscode-nsfw/binding.gyp -vscode-nsfw/build/** -vscode-nsfw/src/** -vscode-nsfw/openpa/** -vscode-nsfw/includes/** -!vscode-nsfw/build/Release/*.node -!vscode-nsfw/**/*.a - -vsda/build/** -vsda/ci/** -vsda/src/** -vsda/.gitignore -vsda/binding.gyp -vsda/README.md -vsda/targets -!vsda/build/Release/vsda.node - -vscode-windows-ca-certs/**/* -!vscode-windows-ca-certs/package.json -!vscode-windows-ca-certs/**/*.node - -node-addon-api/**/* diff --git a/build/azure-pipelines/common/createAsset.ts b/build/azure-pipelines/common/createAsset.ts index d7e62629c..daf60d710 100644 --- a/build/azure-pipelines/common/createAsset.ts +++ b/build/azure-pipelines/common/createAsset.ts @@ -11,6 +11,7 @@ import * as crypto from 'crypto'; import * as azure from 'azure-storage'; import * as mime from 'mime'; import { CosmosClient } from '@azure/cosmos'; +import { retry } from './retry'; interface Asset { platform: string; @@ -121,7 +122,7 @@ async function main(): Promise { const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, key: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); const scripts = client.database('builds').container(quality).scripts; - await scripts.storedProcedure('createAsset').execute('', [commit, asset, true]); + await retry(() => scripts.storedProcedure('createAsset').execute('', [commit, asset, true])); } main().then(() => { diff --git a/build/azure-pipelines/common/createBuild.ts b/build/azure-pipelines/common/createBuild.ts index c8fd66b79..e314d7c09 100644 --- a/build/azure-pipelines/common/createBuild.ts +++ b/build/azure-pipelines/common/createBuild.ts @@ -6,6 +6,7 @@ 'use strict'; import { CosmosClient } from '@azure/cosmos'; +import { retry } from './retry'; if (process.argv.length !== 3) { console.error('Usage: node createBuild.js VERSION'); @@ -48,7 +49,7 @@ async function main(): Promise { const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, key: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); const scripts = client.database('builds').container(quality).scripts; - await scripts.storedProcedure('createBuild').execute('', [{ ...build, _partitionKey: '' }]); + await retry(() => scripts.storedProcedure('createBuild').execute('', [{ ...build, _partitionKey: '' }])); } main().then(() => { diff --git a/build/azure-pipelines/common/releaseBuild.ts b/build/azure-pipelines/common/releaseBuild.ts index ac49e7b00..d42b3f1a0 100644 --- a/build/azure-pipelines/common/releaseBuild.ts +++ b/build/azure-pipelines/common/releaseBuild.ts @@ -6,6 +6,7 @@ 'use strict'; import { CosmosClient } from '@azure/cosmos'; +import { retry } from './retry'; function getEnv(name: string): string { const result = process.env[name]; @@ -58,7 +59,7 @@ async function main(): Promise { console.log(`Releasing build ${commit}...`); const scripts = client.database('builds').container(quality).scripts; - await scripts.storedProcedure('releaseBuild').execute('', [commit]); + await retry(() => scripts.storedProcedure('releaseBuild').execute('', [commit])); } main().then(() => { diff --git a/build/azure-pipelines/common/retry.ts b/build/azure-pipelines/common/retry.ts new file mode 100644 index 000000000..173767659 --- /dev/null +++ b/build/azure-pipelines/common/retry.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. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +export async function retry(fn: () => Promise): Promise { + for (let run = 1; run <= 10; run++) { + try { + return await fn(); + } catch (err) { + if (!/ECONNRESET/.test(err.message)) { + throw err; + } + + const millis = (Math.random() * 200) + (50 * Math.pow(1.5, run)); + console.log(`Failed with ECONNRESET, retrying in ${millis}ms...`); + + // maximum delay is 10th retry: ~3 seconds + await new Promise(c => setTimeout(c, millis)); + } + } + + throw new Error('Retried too many times'); +} diff --git a/build/azure-pipelines/common/sync-mooncake.ts b/build/azure-pipelines/common/sync-mooncake.ts index 76f12185e..4ffe7a8f1 100644 --- a/build/azure-pipelines/common/sync-mooncake.ts +++ b/build/azure-pipelines/common/sync-mooncake.ts @@ -9,6 +9,7 @@ import * as url from 'url'; import * as azure from 'azure-storage'; import * as mime from 'mime'; import { CosmosClient } from '@azure/cosmos'; +import { retry } from './retry'; function log(...args: any[]) { console.log(...[`[${new Date().toISOString()}]`, ...args]); @@ -99,8 +100,8 @@ async function sync(commit: string, quality: string): Promise { log(` Updating build in DB...`); const mooncakeUrl = `${process.env['MOONCAKE_CDN_URL']}${blobPath}`; - await container.scripts.storedProcedure('setAssetMooncakeUrl') - .execute('', [commit, asset.platform, asset.type, mooncakeUrl]); + await retry(() => container.scripts.storedProcedure('setAssetMooncakeUrl') + .execute('', [commit, asset.platform, asset.type, mooncakeUrl])); log(` Done ✔️`); } catch (err) { diff --git a/build/azure-pipelines/darwin/continuous-build-darwin.yml b/build/azure-pipelines/darwin/continuous-build-darwin.yml index 954e5bca6..55efe5f32 100644 --- a/build/azure-pipelines/darwin/continuous-build-darwin.yml +++ b/build/azure-pipelines/darwin/continuous-build-darwin.yml @@ -1,79 +1,79 @@ steps: -- task: NodeTool@0 - inputs: - versionSpec: "12.14.1" + - task: NodeTool@0 + inputs: + versionSpec: "12.14.1" -- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" + - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + inputs: + versionSpec: "1.x" -- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - inputs: - keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' - targetfolder: '**/node_modules, !**/node_modules/**/node_modules' - vstsFeed: 'vscode-build-cache' + - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 + inputs: + keyfile: ".yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock" + targetfolder: "**/node_modules, !**/node_modules/**/node_modules" + vstsFeed: "vscode-build-cache" -- script: | - CHILD_CONCURRENCY=1 yarn --frozen-lockfile - displayName: Install Dependencies - condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) + - script: | + CHILD_CONCURRENCY=1 yarn --frozen-lockfile + displayName: Install Dependencies + condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) -- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - inputs: - keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' - targetfolder: '**/node_modules, !**/node_modules/**/node_modules' - vstsFeed: 'vscode-build-cache' - condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) + - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 + inputs: + keyfile: ".yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock" + targetfolder: "**/node_modules, !**/node_modules/**/node_modules" + vstsFeed: "vscode-build-cache" + condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) -- script: | - set -e - yarn postinstall - displayName: Run postinstall scripts - condition: and(succeeded(), eq(variables['CacheRestored'], 'true')) + - script: | + set -e + yarn postinstall + displayName: Run postinstall scripts + condition: and(succeeded(), eq(variables['CacheRestored'], 'true')) -- script: | - yarn electron x64 - displayName: Download Electron + - script: | + yarn electron x64 + displayName: Download Electron -- script: | - yarn monaco-compile-check - displayName: Run Monaco Editor Checks + - script: | + yarn monaco-compile-check + displayName: Run Monaco Editor Checks -- script: | - yarn valid-layers-check - displayName: Run Valid Layers Checks + - script: | + yarn valid-layers-check + displayName: Run Valid Layers Checks -- script: | - yarn compile - displayName: Compile Sources + - script: | + yarn compile + displayName: Compile Sources -- script: | - yarn download-builtin-extensions - displayName: Download Built-in Extensions + - script: | + yarn download-builtin-extensions + displayName: Download Built-in Extensions -- script: | - ./scripts/test.sh --tfs "Unit Tests" - displayName: Run Unit Tests (Electron) + - script: | + ./scripts/test.sh --tfs "Unit Tests" + displayName: Run Unit Tests (Electron) -- script: | - yarn test-browser --browser chromium --browser webkit --browser firefox --tfs "Browser Unit Tests" - displayName: Run Unit Tests (Browser) + - script: | + yarn test-browser --browser chromium --browser webkit --browser firefox --tfs "Browser Unit Tests" + displayName: Run Unit Tests (Browser) -- script: | - ./scripts/test-integration.sh --tfs "Integration Tests" - displayName: Run Integration Tests (Electron) + - script: | + ./scripts/test-integration.sh --tfs "Integration Tests" + displayName: Run Integration Tests (Electron) -- task: PublishPipelineArtifact@0 - inputs: - artifactName: crash-dump-macos - targetPath: .build/crashes - displayName: 'Publish Crash Reports' - continueOnError: true - condition: failed() + - task: PublishPipelineArtifact@0 + inputs: + artifactName: crash-dump-macos + targetPath: .build/crashes + displayName: "Publish Crash Reports" + continueOnError: true + condition: failed() -- task: PublishTestResults@2 - displayName: Publish Tests Results - inputs: - testResultsFiles: '*-results.xml' - searchFolder: '$(Build.ArtifactStagingDirectory)/test-results' - condition: succeededOrFailed() + - task: PublishTestResults@2 + displayName: Publish Tests Results + inputs: + testResultsFiles: "*-results.xml" + searchFolder: "$(Build.ArtifactStagingDirectory)/test-results" + condition: succeededOrFailed() diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 0500f84ac..6f7dec16a 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -1,270 +1,337 @@ steps: -- script: | - mkdir -p .build - echo -n $BUILD_SOURCEVERSION > .build/commit - echo -n $VSCODE_QUALITY > .build/quality - displayName: Prepare cache flag + - script: | + mkdir -p .build + echo -n $BUILD_SOURCEVERSION > .build/commit + echo -n $VSCODE_QUALITY > .build/quality + echo -n $ENABLE_TERRAPIN > .build/terrapin + displayName: Prepare compilation cache flags -- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - inputs: - keyfile: 'build/.cachesalt, .build/commit, .build/quality' - targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' - vstsFeed: 'npm-vscode' - platformIndependent: true - alias: 'Compilation' + - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 + inputs: + keyfile: "build/.cachesalt, .build/commit, .build/quality, .build/terrapin" + targetfolder: ".build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min" + vstsFeed: "npm-vscode" + platformIndependent: true + alias: "Compilation" -- script: | - set -e - exit 1 - displayName: Check RestoreCache - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + - script: | + set -e + exit 1 + displayName: Check RestoreCache + condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) -- task: NodeTool@0 - inputs: - versionSpec: "12.14.1" + - task: NodeTool@0 + inputs: + versionSpec: "12.14.1" -- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" + - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + inputs: + versionSpec: "1.x" -- task: AzureKeyVault@1 - displayName: 'Azure Key Vault: Get Secrets' - inputs: - azureSubscription: 'vscode-builds-subscription' - KeyVaultName: vscode + - task: AzureKeyVault@1 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: "vscode-builds-subscription" + KeyVaultName: vscode -- script: | - set -e + - script: | + set -e - cat << EOF > ~/.netrc - machine github.com - login vscode - password $(github-distro-mixin-password) - EOF + cat << EOF > ~/.netrc + machine github.com + login vscode + password $(github-distro-mixin-password) + EOF - git config user.email "vscode@microsoft.com" - git config user.name "VSCode" - displayName: Prepare tooling + git config user.email "vscode@microsoft.com" + git config user.name "VSCode" + displayName: Prepare tooling -- script: | - set -e - git remote add distro "https://github.com/$(VSCODE_MIXIN_REPO).git" - git fetch distro - git merge $(node -p "require('./package.json').distro") - displayName: Merge distro + - script: | + set -e + sudo xcode-select -s /Applications/Xcode_12.2.app + displayName: Switch to Xcode 12 + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'arm64')) -- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - inputs: - keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' - targetfolder: '**/node_modules, !**/node_modules/**/node_modules' - vstsFeed: 'npm-vscode' + - script: | + set -e + git remote add distro "https://github.com/$(VSCODE_MIXIN_REPO).git" + git fetch distro + git merge $(node -p "require('./package.json').distro") + displayName: Merge distro -- script: | - set -e - CHILD_CONCURRENCY=1 yarn --frozen-lockfile - displayName: Install dependencies - condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) + - script: | + npx https://aka.ms/enablesecurefeed standAlone + displayName: Switch to Terrapin packages + timeoutInMinutes: 5 + condition: and(succeeded(), eq(variables['ENABLE_TERRAPIN'], 'true')) -- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - inputs: - keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' - targetfolder: '**/node_modules, !**/node_modules/**/node_modules' - vstsFeed: 'npm-vscode' - condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) + - script: | + echo -n $(VSCODE_ARCH) > .build/arch + displayName: Prepare yarn cache flags -- script: | - set -e - yarn postinstall - displayName: Run postinstall scripts - condition: and(succeeded(), eq(variables['CacheRestored'], 'true')) + - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 + inputs: + keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock" + targetfolder: "**/node_modules, !**/node_modules/**/node_modules" + vstsFeed: "npm-vscode" -- script: | - set -e - node build/azure-pipelines/mixin - displayName: Mix in quality + - script: | + set -e + npm install -g node-gyp@7.1.0 + node-gyp --version + displayName: Update node-gyp + condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) -- script: | - set -e - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-darwin-min-ci - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-reh-darwin-min-ci - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-reh-web-darwin-min-ci - displayName: Build + - script: | + set -e + export npm_config_arch=$(VSCODE_ARCH) + export npm_config_node_gyp=$(which node-gyp) + export SDKROOT=/Applications/Xcode_12.2.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk + export CHILD_CONCURRENCY="1" -- script: | - set -e - ./scripts/test.sh --build --tfs "Unit Tests" - displayName: Run unit tests (Electron) - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + for i in {1..3}; do # try 3 times, for Terrapin + yarn --frozen-lockfile && 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 dependencies + condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) -- script: | - set -e - yarn test-browser --build --browser chromium --browser webkit --browser firefox --tfs "Browser Unit Tests" - displayName: Run unit tests (Browser) - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 + inputs: + keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock" + targetfolder: "**/node_modules, !**/node_modules/**/node_modules" + vstsFeed: "npm-vscode" + condition: and(succeeded(), ne(variables['CacheRestored'], '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 - APP_NAME="`ls $APP_ROOT | head -n 1`" - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin" \ - ./scripts/test-integration.sh --build --tfs "Integration Tests" - displayName: Run integration tests (Electron) - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - script: | + set -e + export npm_config_arch=$(VSCODE_ARCH) + export npm_config_node_gyp=$(which node-gyp) + export SDKROOT=/Applications/Xcode_12.2.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk + ls /Applications/Xcode_12.2.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/ + yarn postinstall + displayName: Run postinstall scripts + condition: and(succeeded(), eq(variables['CacheRestored'], 'true')) -- script: | - set -e - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin" \ - ./resources/server/test/test-web-integration.sh --browser webkit - displayName: Run integration tests (Browser) - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - script: | + set -e + export npm_config_arch=$(VSCODE_ARCH) + export npm_config_node_gyp=$(which node-gyp) + export npm_config_build_from_source=true + export SDKROOT=/Applications/Xcode_12.2.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk + ls /Applications/Xcode_12.2.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/ + yarn electron-rebuild + cd ./node_modules/keytar + node-gyp rebuild + displayName: Rebuild native modules for ARM64 + condition: eq(variables['VSCODE_ARCH'], 'arm64') -- script: | - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-darwin - APP_NAME="`ls $APP_ROOT | head -n 1`" - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin" \ - ./resources/server/test/test-remote-integration.sh - displayName: Run remote integration tests (Electron) - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - script: | + set -e + node build/azure-pipelines/mixin + displayName: Mix in quality -- script: | - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-darwin - APP_NAME="`ls $APP_ROOT | head -n 1`" - yarn smoketest --build "$APP_ROOT/$APP_NAME" - continueOnError: true - displayName: Run smoke tests (Electron) - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - script: | + set -e + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp vscode-darwin-$(VSCODE_ARCH)-min-ci + displayName: Build -- script: | - set -e - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin" \ - yarn smoketest --web --headless - continueOnError: true - displayName: Run smoke tests (Browser) - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - script: | + set -e + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp vscode-reh-darwin-min-ci + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp vscode-reh-web-darwin-min-ci + displayName: Build reh + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64')) -- task: PublishPipelineArtifact@0 - inputs: - artifactName: crash-dump-macos - targetPath: .build/crashes - displayName: 'Publish Crash Reports' - continueOnError: true - condition: failed() + - script: | + set -e + yarn electron $(VSCODE_ARCH) + displayName: Download Electron + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) -- task: PublishTestResults@2 - displayName: Publish Tests Results - inputs: - testResultsFiles: '*-results.xml' - searchFolder: '$(Build.ArtifactStagingDirectory)/test-results' - condition: succeededOrFailed() + - script: | + set -e + security create-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain + security default-keychain -s $(agent.tempdirectory)/buildagent.keychain + security unlock-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain + echo "$(macos-developer-certificate)" | base64 -D > $(agent.tempdirectory)/cert.p12 + security import $(agent.tempdirectory)/cert.p12 -k $(agent.tempdirectory)/buildagent.keychain -P "$(macos-developer-certificate-key)" -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k pwd $(agent.tempdirectory)/buildagent.keychain + VSCODE_ARCH="$(VSCODE_ARCH)" DEBUG=electron-osx-sign* node build/darwin/sign.js + displayName: Set Hardened Entitlements -- script: | - set -e - security create-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain - security default-keychain -s $(agent.tempdirectory)/buildagent.keychain - security unlock-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain - echo "$(macos-developer-certificate)" | base64 -D > $(agent.tempdirectory)/cert.p12 - security import $(agent.tempdirectory)/cert.p12 -k $(agent.tempdirectory)/buildagent.keychain -P "$(macos-developer-certificate-key)" -T /usr/bin/codesign - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k pwd $(agent.tempdirectory)/buildagent.keychain - DEBUG=electron-osx-sign* node build/darwin/sign.js - displayName: Set Hardened Entitlements + - script: | + set -e + ./scripts/test.sh --build --tfs "Unit Tests" + displayName: Run unit tests (Electron) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) -- script: | - set -e - pushd $(agent.builddirectory)/VSCode-darwin && zip -r -X -y $(agent.builddirectory)/VSCode-darwin.zip * && popd - displayName: Archive build + - script: | + set -e + yarn test-browser --build --browser chromium --browser webkit --browser firefox --tfs "Browser Unit Tests" + displayName: Run unit tests (Browser) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) -- task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 - inputs: - ConnectedServiceName: 'ESRP CodeSign' - FolderPath: '$(agent.builddirectory)' - Pattern: 'VSCode-darwin.zip' - signConfigType: inlineSignParams - inlineOperation: | - [ - { - "keyCode": "CP-401337-Apple", - "operationSetCode": "MacAppDeveloperSign", - "parameters": [ - { - "parameterName": "Hardening", - "parameterValue": "--options=runtime" - } - ], - "toolName": "sign", - "toolVersion": "1.0" - } - ] - SessionTimeout: 60 - displayName: Codesign + - 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" \ + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin" \ + ./scripts/test-integration.sh --build --tfs "Integration Tests" + displayName: Run integration tests (Electron) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) -- script: | - zip -d $(agent.builddirectory)/VSCode-darwin.zip "*.pkg" - displayName: Clean Archive + - script: | + set -e + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin" \ + ./resources/server/test/test-web-integration.sh --browser webkit + displayName: Run integration tests (Browser) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) -- script: | - APP_ROOT=$(agent.builddirectory)/VSCode-darwin - APP_NAME="`ls $APP_ROOT | head -n 1`" - BUNDLE_IDENTIFIER=$(node -p "require(\"$APP_ROOT/$APP_NAME/Contents/Resources/app/product.json\").darwinBundleIdentifier") - echo "##vso[task.setvariable variable=BundleIdentifier]$BUNDLE_IDENTIFIER" - displayName: Export bundle identifier + - script: | + 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" \ + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin" \ + ./resources/server/test/test-remote-integration.sh + displayName: Run remote integration tests (Electron) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) -- task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 - inputs: - ConnectedServiceName: 'ESRP CodeSign' - FolderPath: '$(agent.builddirectory)' - Pattern: 'VSCode-darwin.zip' - signConfigType: inlineSignParams - inlineOperation: | - [ - { - "keyCode": "CP-401337-Apple", - "operationSetCode": "MacAppNotarize", - "parameters": [ - { - "parameterName": "BundleId", - "parameterValue": "$(BundleIdentifier)" - } - ], - "toolName": "sign", - "toolVersion": "1.0" - } - ] - SessionTimeout: 60 - displayName: Notarization + - script: | + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) + APP_NAME="`ls $APP_ROOT | head -n 1`" + yarn smoketest --build "$APP_ROOT/$APP_NAME" + continueOnError: true + displayName: Run smoke tests (Electron) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) -- script: | - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-darwin - APP_NAME="`ls $APP_ROOT | head -n 1`" - "$APP_ROOT/$APP_NAME/Contents/Resources/app/bin/code" --export-default-configuration=.build - displayName: Verify start after signing (export configuration) + - script: | + set -e + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin" \ + yarn smoketest --web --headless + continueOnError: true + displayName: Run smoke tests (Browser) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) -- script: | - set -e - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ - AZURE_STORAGE_ACCESS_KEY="$(ticino-storage-key)" \ - AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ - VSCODE_ARCH="$(VSCODE_ARCH)" \ - ./build/azure-pipelines/darwin/publish.sh - displayName: Publish + - task: PublishPipelineArtifact@0 + inputs: + artifactName: crash-dump-macos-$(VSCODE_ARCH) + targetPath: .build/crashes + displayName: "Publish Crash Reports" + continueOnError: true + condition: failed() -- script: | - AZURE_STORAGE_ACCESS_KEY="$(ticino-storage-key)" \ - yarn gulp upload-vscode-configuration - displayName: Upload configuration (for Bing settings search) - continueOnError: true + - task: PublishTestResults@2 + displayName: Publish Tests Results + inputs: + testResultsFiles: "*-results.xml" + searchFolder: "$(Build.ArtifactStagingDirectory)/test-results" + condition: succeededOrFailed() -- task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: 'Component Detection' - continueOnError: true + - script: | + set -e + pushd $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) && zip -r -X -y $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH).zip * && popd + displayName: Archive build + + - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 + inputs: + ConnectedServiceName: "ESRP CodeSign" + FolderPath: "$(agent.builddirectory)" + Pattern: "VSCode-darwin-$(VSCODE_ARCH).zip" + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "keyCode": "CP-401337-Apple", + "operationSetCode": "MacAppDeveloperSign", + "parameters": [ + { + "parameterName": "Hardening", + "parameterValue": "--options=runtime" + } + ], + "toolName": "sign", + "toolVersion": "1.0" + } + ] + SessionTimeout: 60 + displayName: Codesign + + - script: | + zip -d $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH).zip "*.pkg" + displayName: Clean Archive + + - script: | + APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) + APP_NAME="`ls $APP_ROOT | head -n 1`" + BUNDLE_IDENTIFIER=$(node -p "require(\"$APP_ROOT/$APP_NAME/Contents/Resources/app/product.json\").darwinBundleIdentifier") + echo "##vso[task.setvariable variable=BundleIdentifier]$BUNDLE_IDENTIFIER" + displayName: Export bundle identifier + + - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 + inputs: + ConnectedServiceName: "ESRP CodeSign" + FolderPath: "$(agent.builddirectory)" + Pattern: "VSCode-darwin-$(VSCODE_ARCH).zip" + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "keyCode": "CP-401337-Apple", + "operationSetCode": "MacAppNotarize", + "parameters": [ + { + "parameterName": "BundleId", + "parameterValue": "$(BundleIdentifier)" + } + ], + "toolName": "sign", + "toolVersion": "1.0" + } + ] + SessionTimeout: 60 + displayName: Notarization + + - script: | + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) + APP_NAME="`ls $APP_ROOT | head -n 1`" + "$APP_ROOT/$APP_NAME/Contents/Resources/app/bin/code" --export-default-configuration=.build + displayName: Verify start after signing (export configuration) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64')) + + - script: | + set -e + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ + AZURE_STORAGE_ACCESS_KEY="$(ticino-storage-key)" \ + AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ + VSCODE_ARCH="$(VSCODE_ARCH)" \ + ./build/azure-pipelines/darwin/publish.sh + displayName: Publish + + - script: | + AZURE_STORAGE_ACCESS_KEY="$(ticino-storage-key)" \ + VSCODE_ARCH="$(VSCODE_ARCH)" \ + yarn gulp upload-vscode-configuration + displayName: Upload configuration (for Bing settings search) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64')) + continueOnError: true + + - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 + displayName: "Component Detection" + continueOnError: true diff --git a/build/azure-pipelines/darwin/publish.sh b/build/azure-pipelines/darwin/publish.sh index 51ac2e635..c9f5b4bab 100755 --- a/build/azure-pipelines/darwin/publish.sh +++ b/build/azure-pipelines/darwin/publish.sh @@ -1,12 +1,18 @@ #!/usr/bin/env bash set -e +# Publish DEB +case $VSCODE_ARCH in + x64) ASSET_ID="darwin" ;; + arm64) ASSET_ID="darwin-arm64" ;; +esac + # publish the build node build/azure-pipelines/common/createAsset.js \ - darwin \ + "$ASSET_ID" \ archive \ - "VSCode-darwin-$VSCODE_QUALITY.zip" \ - ../VSCode-darwin.zip + "VSCode-$ASSET_ID.zip" \ + ../VSCode-darwin-$VSCODE_ARCH.zip if [ "$VSCODE_ARCH" == "x64" ]; then # package Remote Extension Host diff --git a/build/azure-pipelines/distro-build.yml b/build/azure-pipelines/distro-build.yml index f9bdf7fef..99bad6b72 100644 --- a/build/azure-pipelines/distro-build.yml +++ b/build/azure-pipelines/distro-build.yml @@ -1,42 +1,42 @@ trigger: branches: - include: ['master', 'release/*'] + include: ["master", "release/*"] pr: branches: - include: ['master', 'release/*'] + include: ["master", "release/*"] steps: -- task: NodeTool@0 - inputs: - versionSpec: "12.14.1" + - task: NodeTool@0 + inputs: + versionSpec: "12.14.1" -- task: AzureKeyVault@1 - displayName: 'Azure Key Vault: Get Secrets' - inputs: - azureSubscription: 'vscode-builds-subscription' - KeyVaultName: vscode + - task: AzureKeyVault@1 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: "vscode-builds-subscription" + KeyVaultName: vscode -- script: | - set -e + - script: | + set -e - cat << EOF > ~/.netrc - machine github.com - login vscode - password $(github-distro-mixin-password) - EOF + cat << EOF > ~/.netrc + machine github.com + login vscode + password $(github-distro-mixin-password) + EOF - git config user.email "vscode@microsoft.com" - git config user.name "VSCode" + git config user.email "vscode@microsoft.com" + git config user.name "VSCode" - git remote add distro "https://github.com/$VSCODE_MIXIN_REPO.git" - git fetch distro + git remote add distro "https://github.com/$VSCODE_MIXIN_REPO.git" + git fetch distro - # Push master branch into oss/master - git push distro origin/master:refs/heads/oss/master + # Push master branch into oss/master + git push distro origin/master:refs/heads/oss/master - # Push every release branch into oss/release - git for-each-ref --format="%(refname:short)" refs/remotes/origin/release/* | sed 's/^origin\/\(.*\)$/\0:refs\/heads\/oss\/\1/' | xargs git push distro + # Push every release branch into oss/release + git for-each-ref --format="%(refname:short)" refs/remotes/origin/release/* | sed 's/^origin\/\(.*\)$/\0:refs\/heads\/oss\/\1/' | xargs git push distro - git merge $(node -p "require('./package.json').distro") + git merge $(node -p "require('./package.json').distro") - displayName: Sync & Merge Distro + displayName: Sync & Merge Distro diff --git a/build/azure-pipelines/exploration-build.yml b/build/azure-pipelines/exploration-build.yml index cf1ced09d..4a7acf129 100644 --- a/build/azure-pipelines/exploration-build.yml +++ b/build/azure-pipelines/exploration-build.yml @@ -1,40 +1,35 @@ pool: - vmImage: 'Ubuntu-16.04' + vmImage: "Ubuntu-16.04" -trigger: - branches: - include: ['master'] -pr: - branches: - include: ['master'] +trigger: none steps: -- task: NodeTool@0 - inputs: - versionSpec: "12.14.1" + - task: NodeTool@0 + inputs: + versionSpec: "12.14.1" -- task: AzureKeyVault@1 - displayName: 'Azure Key Vault: Get Secrets' - inputs: - azureSubscription: 'vscode-builds-subscription' - KeyVaultName: vscode + - task: AzureKeyVault@1 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: "vscode-builds-subscription" + KeyVaultName: vscode -- script: | - set -e + - script: | + set -e - cat << EOF > ~/.netrc - machine github.com - login vscode - password $(github-distro-mixin-password) - EOF + cat << EOF > ~/.netrc + machine github.com + login vscode + password $(github-distro-mixin-password) + EOF - git config user.email "vscode@microsoft.com" - git config user.name "VSCode" + git config user.email "vscode@microsoft.com" + git config user.name "VSCode" - git checkout origin/electron-11.x.y - git merge origin/master + git checkout origin/electron-11.x.y + git merge origin/master - # Push master branch into exploration branch - git push origin HEAD:electron-11.x.y + # Push master branch into exploration branch + git push origin HEAD:electron-11.x.y - displayName: Sync & Merge Exploration + displayName: Sync & Merge Exploration diff --git a/build/azure-pipelines/linux/alpine/install-dependencies.sh b/build/azure-pipelines/linux/alpine/install-dependencies.sh new file mode 100755 index 000000000..1d2a23254 --- /dev/null +++ b/build/azure-pipelines/linux/alpine/install-dependencies.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -e + +echo "Installing remote dependencies" +(cd remote && rm -rf node_modules && yarn) \ No newline at end of file diff --git a/build/azure-pipelines/linux/alpine/publish.sh b/build/azure-pipelines/linux/alpine/publish.sh new file mode 100755 index 000000000..4bf874c63 --- /dev/null +++ b/build/azure-pipelines/linux/alpine/publish.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -e +REPO="$(pwd)" +ROOT="$REPO/.." + +PLATFORM_LINUX="linux-alpine" + +# Publish Remote Extension Host +LEGACY_SERVER_BUILD_NAME="vscode-reh-$PLATFORM_LINUX" +SERVER_BUILD_NAME="vscode-server-$PLATFORM_LINUX" +SERVER_TARBALL_FILENAME="vscode-server-$PLATFORM_LINUX.tar.gz" +SERVER_TARBALL_PATH="$ROOT/$SERVER_TARBALL_FILENAME" + +rm -rf $ROOT/vscode-server-*.tar.* +(cd $ROOT && mv $LEGACY_SERVER_BUILD_NAME $SERVER_BUILD_NAME && tar --owner=0 --group=0 -czf $SERVER_TARBALL_PATH $SERVER_BUILD_NAME) + +node build/azure-pipelines/common/createAsset.js "server-$PLATFORM_LINUX" archive-unsigned "$SERVER_TARBALL_FILENAME" "$SERVER_TARBALL_PATH" + +# Publish Remote Extension Host (Web) +LEGACY_SERVER_BUILD_NAME="vscode-reh-web-$PLATFORM_LINUX" +SERVER_BUILD_NAME="vscode-server-$PLATFORM_LINUX-web" +SERVER_TARBALL_FILENAME="vscode-server-$PLATFORM_LINUX-web.tar.gz" +SERVER_TARBALL_PATH="$ROOT/$SERVER_TARBALL_FILENAME" + +rm -rf $ROOT/vscode-server-*.tar.* +(cd $ROOT && mv $LEGACY_SERVER_BUILD_NAME $SERVER_BUILD_NAME && tar --owner=0 --group=0 -czf $SERVER_TARBALL_PATH $SERVER_BUILD_NAME) + +node build/azure-pipelines/common/createAsset.js "server-$PLATFORM_LINUX-web" archive-unsigned "$SERVER_TARBALL_FILENAME" "$SERVER_TARBALL_PATH" diff --git a/build/azure-pipelines/linux/continuous-build-linux.yml b/build/azure-pipelines/linux/continuous-build-linux.yml index 58e14ab0d..e777f821d 100644 --- a/build/azure-pipelines/linux/continuous-build-linux.yml +++ b/build/azure-pipelines/linux/continuous-build-linux.yml @@ -1,92 +1,92 @@ steps: -- script: | - set -e - sudo apt-get update - sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 - sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb - sudo chmod +x /etc/init.d/xvfb - sudo update-rc.d xvfb defaults - sudo service xvfb start + - script: | + set -e + sudo apt-get update + sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 + sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb + sudo chmod +x /etc/init.d/xvfb + sudo update-rc.d xvfb defaults + sudo service xvfb start -- task: NodeTool@0 - inputs: - versionSpec: "12.14.1" + - task: NodeTool@0 + inputs: + versionSpec: "12.14.1" -- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" + - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + inputs: + versionSpec: "1.x" -- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - inputs: - keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' - targetfolder: '**/node_modules, !**/node_modules/**/node_modules' - vstsFeed: 'vscode-build-cache' + - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 + inputs: + keyfile: ".yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock" + targetfolder: "**/node_modules, !**/node_modules/**/node_modules" + vstsFeed: "vscode-build-cache" -- script: | - CHILD_CONCURRENCY=1 yarn --frozen-lockfile - displayName: Install Dependencies - condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) + - script: | + CHILD_CONCURRENCY=1 yarn --frozen-lockfile + displayName: Install Dependencies + condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) -- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - inputs: - keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' - targetfolder: '**/node_modules, !**/node_modules/**/node_modules' - vstsFeed: 'vscode-build-cache' - condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) + - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 + inputs: + keyfile: ".yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock" + targetfolder: "**/node_modules, !**/node_modules/**/node_modules" + vstsFeed: "vscode-build-cache" + condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) -- script: | - set -e - yarn postinstall - displayName: Run postinstall scripts - condition: and(succeeded(), eq(variables['CacheRestored'], 'true')) + - script: | + set -e + yarn postinstall + displayName: Run postinstall scripts + condition: and(succeeded(), eq(variables['CacheRestored'], 'true')) -- script: | - yarn electron x64 - displayName: Download Electron + - script: | + yarn electron x64 + displayName: Download Electron -- script: | - yarn gulp hygiene - displayName: Run Hygiene Checks + - script: | + yarn gulp hygiene + displayName: Run Hygiene Checks -- script: | - yarn monaco-compile-check - displayName: Run Monaco Editor Checks + - script: | + yarn monaco-compile-check + displayName: Run Monaco Editor Checks -- script: | - yarn valid-layers-check - displayName: Run Valid Layers Checks + - script: | + yarn valid-layers-check + displayName: Run Valid Layers Checks -- script: | - yarn compile - displayName: Compile Sources + - script: | + yarn compile + displayName: Compile Sources -- script: | - yarn download-builtin-extensions - displayName: Download Built-in Extensions + - script: | + yarn download-builtin-extensions + displayName: Download Built-in Extensions -- script: | - DISPLAY=:10 ./scripts/test.sh --tfs "Unit Tests" - displayName: Run Unit Tests (Electron) + - script: | + DISPLAY=:10 ./scripts/test.sh --tfs "Unit Tests" + displayName: Run Unit Tests (Electron) -- script: | - DISPLAY=:10 yarn test-browser --browser chromium --tfs "Browser Unit Tests" - displayName: Run Unit Tests (Browser) + - script: | + DISPLAY=:10 yarn test-browser --browser chromium --tfs "Browser Unit Tests" + displayName: Run Unit Tests (Browser) -- script: | - DISPLAY=:10 ./scripts/test-integration.sh --tfs "Integration Tests" - displayName: Run Integration Tests (Electron) + - script: | + DISPLAY=:10 ./scripts/test-integration.sh --tfs "Integration Tests" + displayName: Run Integration Tests (Electron) -- task: PublishPipelineArtifact@0 - inputs: - artifactName: crash-dump-linux - targetPath: .build/crashes - displayName: 'Publish Crash Reports' - continueOnError: true - condition: failed() + - task: PublishPipelineArtifact@0 + inputs: + artifactName: crash-dump-linux + targetPath: .build/crashes + displayName: "Publish Crash Reports" + continueOnError: true + condition: failed() -- task: PublishTestResults@2 - displayName: Publish Tests Results - inputs: - testResultsFiles: '*-results.xml' - searchFolder: '$(Build.ArtifactStagingDirectory)/test-results' - condition: succeededOrFailed() + - task: PublishTestResults@2 + displayName: Publish Tests Results + inputs: + testResultsFiles: "*-results.xml" + searchFolder: "$(Build.ArtifactStagingDirectory)/test-results" + condition: succeededOrFailed() diff --git a/build/azure-pipelines/linux/multiarch/alpine/build.sh b/build/azure-pipelines/linux/multiarch/alpine/build.sh deleted file mode 100755 index 4f01bc9a7..000000000 --- a/build/azure-pipelines/linux/multiarch/alpine/build.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -set -e -echo 'noop' \ No newline at end of file diff --git a/build/azure-pipelines/linux/multiarch/alpine/prebuild.sh b/build/azure-pipelines/linux/multiarch/alpine/prebuild.sh deleted file mode 100755 index 4f01bc9a7..000000000 --- a/build/azure-pipelines/linux/multiarch/alpine/prebuild.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -set -e -echo 'noop' \ No newline at end of file diff --git a/build/azure-pipelines/linux/multiarch/alpine/publish.sh b/build/azure-pipelines/linux/multiarch/alpine/publish.sh deleted file mode 100755 index 4f01bc9a7..000000000 --- a/build/azure-pipelines/linux/multiarch/alpine/publish.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -set -e -echo 'noop' \ No newline at end of file diff --git a/build/azure-pipelines/linux/multiarch/arm64/build.sh b/build/azure-pipelines/linux/multiarch/arm64/build.sh deleted file mode 100755 index 4f01bc9a7..000000000 --- a/build/azure-pipelines/linux/multiarch/arm64/build.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -set -e -echo 'noop' \ No newline at end of file diff --git a/build/azure-pipelines/linux/multiarch/arm64/prebuild.sh b/build/azure-pipelines/linux/multiarch/arm64/prebuild.sh deleted file mode 100755 index 4f01bc9a7..000000000 --- a/build/azure-pipelines/linux/multiarch/arm64/prebuild.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -set -e -echo 'noop' \ No newline at end of file diff --git a/build/azure-pipelines/linux/multiarch/arm64/publish.sh b/build/azure-pipelines/linux/multiarch/arm64/publish.sh deleted file mode 100755 index 4f01bc9a7..000000000 --- a/build/azure-pipelines/linux/multiarch/arm64/publish.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -set -e -echo 'noop' \ No newline at end of file diff --git a/build/azure-pipelines/linux/multiarch/armhf/build.sh b/build/azure-pipelines/linux/multiarch/armhf/build.sh deleted file mode 100755 index 4f01bc9a7..000000000 --- a/build/azure-pipelines/linux/multiarch/armhf/build.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -set -e -echo 'noop' \ No newline at end of file diff --git a/build/azure-pipelines/linux/multiarch/armhf/prebuild.sh b/build/azure-pipelines/linux/multiarch/armhf/prebuild.sh deleted file mode 100755 index 4f01bc9a7..000000000 --- a/build/azure-pipelines/linux/multiarch/armhf/prebuild.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -set -e -echo 'noop' \ No newline at end of file diff --git a/build/azure-pipelines/linux/multiarch/armhf/publish.sh b/build/azure-pipelines/linux/multiarch/armhf/publish.sh deleted file mode 100755 index 4f01bc9a7..000000000 --- a/build/azure-pipelines/linux/multiarch/armhf/publish.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -set -e -echo 'noop' \ No newline at end of file diff --git a/build/azure-pipelines/linux/product-build-alpine.yml b/build/azure-pipelines/linux/product-build-alpine.yml new file mode 100644 index 000000000..9a54bc2bc --- /dev/null +++ b/build/azure-pipelines/linux/product-build-alpine.yml @@ -0,0 +1,135 @@ +steps: + - script: | + mkdir -p .build + echo -n $BUILD_SOURCEVERSION > .build/commit + echo -n $VSCODE_QUALITY > .build/quality + echo -n $ENABLE_TERRAPIN > .build/terrapin + displayName: Prepare compilation cache flags + + - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 + inputs: + keyfile: "build/.cachesalt, .build/commit, .build/quality, .build/terrapin" + targetfolder: ".build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min" + vstsFeed: "npm-vscode" + platformIndependent: true + alias: "Compilation" + + - script: | + set -e + exit 1 + displayName: Check RestoreCache + condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + + - task: NodeTool@0 + inputs: + versionSpec: "12.14.1" + + - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + inputs: + versionSpec: "1.x" + + - task: AzureKeyVault@1 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: "vscode-builds-subscription" + KeyVaultName: vscode + + - task: Docker@1 + displayName: "Pull image" + inputs: + azureSubscriptionEndpoint: "vscode-builds-subscription" + azureContainerRegistry: vscodehub.azurecr.io + command: "Run an image" + imageName: "vscode-linux-build-agent:alpine" + containerCommand: uname + + - script: | + set -e + + cat << EOF > ~/.netrc + machine github.com + login vscode + password $(github-distro-mixin-password) + EOF + + git config user.email "vscode@microsoft.com" + git config user.name "VSCode" + displayName: Prepare tooling + + - script: | + set -e + git remote add distro "https://github.com/$(VSCODE_MIXIN_REPO).git" + git fetch distro + git merge $(node -p "require('./package.json').distro") + displayName: Merge distro + + - script: | + npx https://aka.ms/enablesecurefeed standAlone + displayName: Switch to Terrapin packages + timeoutInMinutes: 5 + condition: and(succeeded(), eq(variables['ENABLE_TERRAPIN'], 'true')) + + - script: | + echo -n "alpine" > .build/arch + displayName: Prepare yarn cache flags + + - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 + inputs: + keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock" + targetfolder: "**/node_modules, !**/node_modules/**/node_modules" + vstsFeed: "npm-vscode" + + - script: | + set -e + export CHILD_CONCURRENCY="1" + for i in {1..3}; do # try 3 times, for Terrapin + yarn --frozen-lockfile && 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 dependencies + condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) + + - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 + inputs: + keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock" + targetfolder: "**/node_modules, !**/node_modules/**/node_modules" + vstsFeed: "npm-vscode" + condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) + + - script: | + set -e + yarn postinstall + displayName: Run postinstall scripts + condition: and(succeeded(), eq(variables['CacheRestored'], 'true')) + + - script: | + set -e + node build/azure-pipelines/mixin + displayName: Mix in quality + + - script: | + set -e + docker run -e VSCODE_QUALITY -e CHILD_CONCURRENCY=1 -v $(pwd):/root/vscode -v ~/.netrc:/root/.netrc vscodehub.azurecr.io/vscode-linux-build-agent:alpine /root/vscode/build/azure-pipelines/linux/alpine/install-dependencies.sh + displayName: Prebuild + + - script: | + set -e + yarn gulp vscode-reh-linux-alpine-min-ci + yarn gulp vscode-reh-web-linux-alpine-min-ci + displayName: Build + + - script: | + set -e + AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ + AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + ./build/azure-pipelines/linux/alpine/publish.sh + displayName: Publish + + - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 + displayName: "Component Detection" + continueOnError: true diff --git a/build/azure-pipelines/linux/product-build-linux-multiarch.yml b/build/azure-pipelines/linux/product-build-linux-multiarch.yml deleted file mode 100644 index 258f87ea3..000000000 --- a/build/azure-pipelines/linux/product-build-linux-multiarch.yml +++ /dev/null @@ -1,115 +0,0 @@ -steps: -- script: | - mkdir -p .build - echo -n $BUILD_SOURCEVERSION > .build/commit - echo -n $VSCODE_QUALITY > .build/quality - displayName: Prepare cache flag - -- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - inputs: - keyfile: 'build/.cachesalt, .build/commit, .build/quality' - targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' - vstsFeed: 'npm-vscode' - platformIndependent: true - alias: 'Compilation' - -- script: | - set -e - exit 1 - displayName: Check RestoreCache - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) - -- task: NodeTool@0 - inputs: - versionSpec: "12.14.1" - -- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" - -- task: AzureKeyVault@1 - displayName: 'Azure Key Vault: Get Secrets' - inputs: - azureSubscription: 'vscode-builds-subscription' - KeyVaultName: vscode - -- task: Docker@1 - displayName: 'Pull image' - inputs: - azureSubscriptionEndpoint: 'vscode-builds-subscription' - azureContainerRegistry: vscodehub.azurecr.io - command: 'Run an image' - imageName: 'vscode-linux-build-agent:$(VSCODE_ARCH)' - containerCommand: uname - -- script: | - set -e - - cat << EOF > ~/.netrc - machine github.com - login vscode - password $(github-distro-mixin-password) - EOF - - git config user.email "vscode@microsoft.com" - git config user.name "VSCode" - displayName: Prepare tooling - -- script: | - set -e - git remote add distro "https://github.com/$(VSCODE_MIXIN_REPO).git" - git fetch distro - git merge $(node -p "require('./package.json').distro") - displayName: Merge distro - -- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - inputs: - keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' - targetfolder: '**/node_modules, !**/node_modules/**/node_modules' - vstsFeed: 'npm-vscode' - -- script: | - set -e - CHILD_CONCURRENCY=1 yarn --frozen-lockfile - displayName: Install dependencies - condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - -- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - inputs: - keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' - targetfolder: '**/node_modules, !**/node_modules/**/node_modules' - vstsFeed: 'npm-vscode' - condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - -- script: | - set -e - yarn postinstall - displayName: Run postinstall scripts - condition: and(succeeded(), eq(variables['CacheRestored'], 'true')) - -- script: | - set -e - node build/azure-pipelines/mixin - displayName: Mix in quality - -- script: | - set -e - CHILD_CONCURRENCY=1 ./build/azure-pipelines/linux/multiarch/$(VSCODE_ARCH)/prebuild.sh - displayName: Prebuild - -- script: | - set -e - ./build/azure-pipelines/linux/multiarch/$(VSCODE_ARCH)/build.sh - displayName: Build - -- script: | - set -e - AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ - AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - ./build/azure-pipelines/linux/multiarch/$(VSCODE_ARCH)/publish.sh - displayName: Publish - -- task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: 'Component Detection' - continueOnError: true diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 031a49164..1f7884a1c 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -1,217 +1,233 @@ steps: -- script: | - mkdir -p .build - echo -n $BUILD_SOURCEVERSION > .build/commit - echo -n $VSCODE_QUALITY > .build/quality - displayName: Prepare cache flag + - script: | + mkdir -p .build + echo -n $BUILD_SOURCEVERSION > .build/commit + echo -n $VSCODE_QUALITY > .build/quality + echo -n $ENABLE_TERRAPIN > .build/terrapin + displayName: Prepare compilation cache flags -- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - inputs: - keyfile: 'build/.cachesalt, .build/commit, .build/quality' - targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' - vstsFeed: 'npm-vscode' - platformIndependent: true - alias: 'Compilation' + - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 + inputs: + keyfile: "build/.cachesalt, .build/commit, .build/quality, .build/terrapin" + targetfolder: ".build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min" + vstsFeed: "npm-vscode" + platformIndependent: true + alias: "Compilation" -- script: | - set -e - exit 1 - displayName: Check RestoreCache - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + - script: | + set -e + exit 1 + displayName: Check RestoreCache + condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) -- task: NodeTool@0 - inputs: - versionSpec: "12.14.1" + - task: NodeTool@0 + inputs: + versionSpec: "12.14.1" -- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" + - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + inputs: + versionSpec: "1.x" -- task: AzureKeyVault@1 - displayName: 'Azure Key Vault: Get Secrets' - inputs: - azureSubscription: 'vscode-builds-subscription' - KeyVaultName: vscode + - task: AzureKeyVault@1 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: "vscode-builds-subscription" + KeyVaultName: vscode -- script: | - set -e - cat << EOF > ~/.netrc - machine github.com - login vscode - password $(github-distro-mixin-password) - EOF + - script: | + set -e + cat << EOF > ~/.netrc + machine github.com + login vscode + password $(github-distro-mixin-password) + EOF - git config user.email "vscode@microsoft.com" - git config user.name "VSCode" - displayName: Prepare tooling + git config user.email "vscode@microsoft.com" + git config user.name "VSCode" + displayName: Prepare tooling -- script: | - set -e - git remote add distro "https://github.com/$(VSCODE_MIXIN_REPO).git" - git fetch distro - git merge $(node -p "require('./package.json').distro") - displayName: Merge distro + - script: | + set -e + git remote add distro "https://github.com/$(VSCODE_MIXIN_REPO).git" + git fetch distro + git merge $(node -p "require('./package.json').distro") + displayName: Merge distro -- script: | - echo -n $VSCODE_ARCH > .build/arch - displayName: Prepare arch cache flag + - script: | + npx https://aka.ms/enablesecurefeed standAlone + displayName: Switch to Terrapin packages + timeoutInMinutes: 5 + condition: and(succeeded(), eq(variables['ENABLE_TERRAPIN'], 'true')) -- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - inputs: - keyfile: '.build/arch, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' - targetfolder: '**/node_modules, !**/node_modules/**/node_modules' - vstsFeed: 'npm-vscode' + - script: | + echo -n $(VSCODE_ARCH) > .build/arch + displayName: Prepare yarn cache flags -- script: | - set -e - CHILD_CONCURRENCY=1 npm_config_arch=$(NPM_ARCH) yarn --frozen-lockfile - displayName: Install dependencies - condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) + - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 + inputs: + keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock" + targetfolder: "**/node_modules, !**/node_modules/**/node_modules" + vstsFeed: "npm-vscode" -- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - inputs: - keyfile: '.build/arch, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' - targetfolder: '**/node_modules, !**/node_modules/**/node_modules' - vstsFeed: 'npm-vscode' - condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) + - script: | + set -e + export npm_config_arch=$(NPM_ARCH) + export CHILD_CONCURRENCY="1" + for i in {1..3}; do # try 3 times, for Terrapin + yarn --frozen-lockfile && 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 dependencies + condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) -- script: | - set -e - yarn postinstall - displayName: Run postinstall scripts - condition: and(succeeded(), eq(variables['CacheRestored'], 'true')) + - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 + inputs: + keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock" + targetfolder: "**/node_modules, !**/node_modules/**/node_modules" + vstsFeed: "npm-vscode" + condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) -- script: | - set -e - node build/azure-pipelines/mixin - displayName: Mix in quality + - script: | + set -e + yarn postinstall + displayName: Run postinstall scripts + condition: and(succeeded(), eq(variables['CacheRestored'], 'true')) -- script: | - set -e - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-linux-$(VSCODE_ARCH)-min-ci - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-reh-linux-$(VSCODE_ARCH)-min-ci - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-reh-web-linux-$(VSCODE_ARCH)-min-ci - displayName: Build + - script: | + set -e + node build/azure-pipelines/mixin + displayName: Mix in quality -- script: | - set -e - service xvfb start - displayName: Start xvfb - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - script: | + set -e + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp vscode-linux-$(VSCODE_ARCH)-min-ci + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp vscode-reh-linux-$(VSCODE_ARCH)-min-ci + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp vscode-reh-web-linux-$(VSCODE_ARCH)-min-ci + displayName: Build -- script: | - set -e - DISPLAY=:10 ./scripts/test.sh --build --tfs "Unit Tests" - displayName: Run unit tests (Electron) - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - script: | + set -e + service xvfb start + displayName: Start xvfb + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) -- script: | - set -e - DISPLAY=:10 yarn test-browser --build --browser chromium --tfs "Browser Unit Tests" - displayName: Run unit tests (Browser) - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - script: | + set -e + DISPLAY=:10 ./scripts/test.sh --build --tfs "Unit Tests" + displayName: Run unit tests (Electron) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], '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_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ - DISPLAY=:10 ./scripts/test-integration.sh --build --tfs "Integration Tests" - displayName: Run integration tests (Electron) - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - script: | + set -e + DISPLAY=:10 yarn test-browser --build --browser chromium --tfs "Browser Unit Tests" + displayName: Run unit tests (Browser) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) -- script: | - set -e - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \ - DISPLAY=:10 ./resources/server/test/test-web-integration.sh --browser chromium - displayName: Run integration tests (Browser) - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], '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" \ + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ + DISPLAY=:10 ./scripts/test-integration.sh --build --tfs "Integration Tests" + displayName: Run integration tests (Electron) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) -- script: | - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ - DISPLAY=:10 ./resources/server/test/test-remote-integration.sh - displayName: Run remote integration tests (Electron) - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - script: | + set -e + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \ + DISPLAY=:10 ./resources/server/test/test-web-integration.sh --browser chromium + displayName: Run integration tests (Browser) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) -- task: PublishPipelineArtifact@0 - inputs: - artifactName: 'crash-dump-linux-$(VSCODE_ARCH)' - targetPath: .build/crashes - displayName: 'Publish Crash Reports' - continueOnError: true - condition: failed() + - script: | + 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" \ + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ + DISPLAY=:10 ./resources/server/test/test-remote-integration.sh + displayName: Run remote integration tests (Electron) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) -- task: PublishTestResults@2 - displayName: Publish Tests Results - inputs: - testResultsFiles: '*-results.xml' - searchFolder: '$(Build.ArtifactStagingDirectory)/test-results' - condition: and(succeededOrFailed(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - task: PublishPipelineArtifact@0 + inputs: + artifactName: "crash-dump-linux-$(VSCODE_ARCH)" + targetPath: .build/crashes + displayName: "Publish Crash Reports" + continueOnError: true + condition: failed() -- script: | - set -e - yarn gulp "vscode-linux-$(VSCODE_ARCH)-build-deb" - yarn gulp "vscode-linux-$(VSCODE_ARCH)-build-rpm" - displayName: Build deb, rpm packages + - task: PublishTestResults@2 + displayName: Publish Tests Results + inputs: + testResultsFiles: "*-results.xml" + searchFolder: "$(Build.ArtifactStagingDirectory)/test-results" + condition: and(succeededOrFailed(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) -- script: | - set -e - yarn gulp "vscode-linux-$(VSCODE_ARCH)-prepare-snap" - displayName: Prepare snap package - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64')) + - script: | + set -e + yarn gulp "vscode-linux-$(VSCODE_ARCH)-build-deb" + yarn gulp "vscode-linux-$(VSCODE_ARCH)-build-rpm" + displayName: Build deb, rpm packages -# needed for code signing -- task: UseDotNet@2 - displayName: 'Install .NET Core SDK 2.x' - inputs: - version: 2.x + - script: | + set -e + yarn gulp "vscode-linux-$(VSCODE_ARCH)-prepare-snap" + displayName: Prepare snap package -- task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 - inputs: - ConnectedServiceName: 'ESRP CodeSign' - FolderPath: '.build/linux/rpm' - Pattern: '*.rpm' - signConfigType: inlineSignParams - inlineOperation: | - [ - { - "keyCode": "CP-450779-Pgp", - "operationSetCode": "LinuxSign", - "parameters": [ ], - "toolName": "sign", - "toolVersion": "1.0" - } - ] - SessionTimeout: 120 - displayName: Codesign rpm + # needed for code signing + - task: UseDotNet@2 + displayName: "Install .NET Core SDK 2.x" + inputs: + version: 2.x -- script: | - set -e - AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ - AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - VSCODE_ARCH="$(VSCODE_ARCH)" \ - ./build/azure-pipelines/linux/publish.sh - displayName: Publish + - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 + inputs: + ConnectedServiceName: "ESRP CodeSign" + FolderPath: ".build/linux/rpm" + Pattern: "*.rpm" + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "keyCode": "CP-450779-Pgp", + "operationSetCode": "LinuxSign", + "parameters": [ ], + "toolName": "sign", + "toolVersion": "1.0" + } + ] + SessionTimeout: 120 + displayName: Codesign rpm -- task: PublishPipelineArtifact@0 - displayName: 'Publish Pipeline Artifact' - inputs: - artifactName: 'snap-$(VSCODE_ARCH)' - targetPath: .build/linux/snap-tarball - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64')) + - script: | + set -e + AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ + AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + VSCODE_ARCH="$(VSCODE_ARCH)" \ + ./build/azure-pipelines/linux/publish.sh + displayName: Publish -- task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: 'Component Detection' - continueOnError: true + - task: PublishPipelineArtifact@0 + displayName: "Publish Pipeline Artifact" + inputs: + artifactName: "snap-$(VSCODE_ARCH)" + targetPath: .build/linux/snap-tarball + + - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 + displayName: "Component Detection" + continueOnError: true diff --git a/build/azure-pipelines/linux/publish.sh b/build/azure-pipelines/linux/publish.sh index 72fe2ad7b..e0f1ade30 100755 --- a/build/azure-pipelines/linux/publish.sh +++ b/build/azure-pipelines/linux/publish.sh @@ -52,11 +52,9 @@ RPM_PATH="$REPO/.build/linux/rpm/$RPM_ARCH/$RPM_FILENAME" node build/azure-pipelines/common/createAsset.js "$PLATFORM_RPM" package "$RPM_FILENAME" "$RPM_PATH" -if [ "$VSCODE_ARCH" == "x64" ]; then - # Publish Snap - # Pack snap tarball artifact, in order to preserve file perms - mkdir -p $REPO/.build/linux/snap-tarball - SNAP_TARBALL_PATH="$REPO/.build/linux/snap-tarball/snap-$VSCODE_ARCH.tar.gz" - rm -rf $SNAP_TARBALL_PATH - (cd .build/linux && tar -czf $SNAP_TARBALL_PATH snap) -fi +# Publish Snap +# Pack snap tarball artifact, in order to preserve file perms +mkdir -p $REPO/.build/linux/snap-tarball +SNAP_TARBALL_PATH="$REPO/.build/linux/snap-tarball/snap-$VSCODE_ARCH.tar.gz" +rm -rf $SNAP_TARBALL_PATH +(cd .build/linux && tar -czf $SNAP_TARBALL_PATH snap) diff --git a/build/azure-pipelines/linux/snap-build-linux.yml b/build/azure-pipelines/linux/snap-build-linux.yml index 39c39e86c..f08c7b3c3 100644 --- a/build/azure-pipelines/linux/snap-build-linux.yml +++ b/build/azure-pipelines/linux/snap-build-linux.yml @@ -1,52 +1,56 @@ steps: -- task: NodeTool@0 - inputs: - versionSpec: "12.14.1" + - task: NodeTool@0 + inputs: + versionSpec: "12.14.1" -- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" + - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + inputs: + versionSpec: "1.x" -- task: AzureKeyVault@1 - displayName: 'Azure Key Vault: Get Secrets' - inputs: - azureSubscription: 'vscode-builds-subscription' - KeyVaultName: vscode + - task: AzureKeyVault@1 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: "vscode-builds-subscription" + KeyVaultName: vscode -- task: DownloadPipelineArtifact@0 - displayName: 'Download Pipeline Artifact' - inputs: - artifactName: snap-x64 - targetPath: .build/linux/snap-tarball + - task: DownloadPipelineArtifact@0 + displayName: "Download Pipeline Artifact" + inputs: + artifactName: snap-$(VSCODE_ARCH) + targetPath: .build/linux/snap-tarball -- script: | - set -e + - script: | + set -e - # Get snapcraft version - snapcraft --version + # Get snapcraft version + snapcraft --version - # Make sure we get latest packages - sudo apt-get update - sudo apt-get upgrade -y + # Make sure we get latest packages + sudo apt-get update + sudo apt-get upgrade -y - # Define variables - REPO="$(pwd)" - SNAP_ROOT="$REPO/.build/linux/snap/x64" + # Define variables + REPO="$(pwd)" + SNAP_ROOT="$REPO/.build/linux/snap/$(VSCODE_ARCH)" - # Install build dependencies - (cd build && yarn) + # Install build dependencies + (cd build && yarn) - # Unpack snap tarball artifact, in order to preserve file perms - SNAP_TARBALL_PATH="$REPO/.build/linux/snap-tarball/snap-x64.tar.gz" - (cd .build/linux && tar -xzf $SNAP_TARBALL_PATH) + # Unpack snap tarball artifact, in order to preserve file perms + SNAP_TARBALL_PATH="$REPO/.build/linux/snap-tarball/snap-$(VSCODE_ARCH).tar.gz" + (cd .build/linux && tar -xzf $SNAP_TARBALL_PATH) - # Create snap package - BUILD_VERSION="$(date +%s)" - SNAP_FILENAME="code-$VSCODE_QUALITY-$BUILD_VERSION.snap" - SNAP_PATH="$SNAP_ROOT/$SNAP_FILENAME" - (cd $SNAP_ROOT/code-* && sudo --preserve-env snapcraft snap --output "$SNAP_PATH") + # Create snap package + BUILD_VERSION="$(date +%s)" + SNAP_FILENAME="code-$VSCODE_QUALITY-$(VSCODE_ARCH)-$BUILD_VERSION.snap" + SNAP_PATH="$SNAP_ROOT/$SNAP_FILENAME" + case $(VSCODE_ARCH) in + x64) SNAPCRAFT_TARGET_ARGS="" ;; + *) SNAPCRAFT_TARGET_ARGS="--target-arch $(VSCODE_ARCH)" ;; + esac + (cd $SNAP_ROOT/code-* && sudo --preserve-env snapcraft snap $SNAPCRAFT_TARGET_ARGS --output "$SNAP_PATH") - # Publish snap package - AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ - AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ - node build/azure-pipelines/common/createAsset.js "linux-snap-x64" package "$SNAP_FILENAME" "$SNAP_PATH" + # Publish snap package + AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ + AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ + node build/azure-pipelines/common/createAsset.js "linux-snap-$(VSCODE_ARCH)" package "$SNAP_FILENAME" "$SNAP_PATH" diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index 83966344f..ebe2fe6ab 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -2,160 +2,201 @@ trigger: none pr: none schedules: -- cron: "0 5 * * Mon-Fri" - displayName: Mon-Fri at 7:00 - branches: - include: - - master + - cron: "0 5 * * Mon-Fri" + displayName: Mon-Fri at 7:00 + branches: + include: + - master resources: containers: - - container: vscode-x64 - image: vscodehub.azurecr.io/vscode-linux-build-agent:x64 - endpoint: VSCodeHub - - container: vscode-arm64 - image: vscodehub.azurecr.io/vscode-linux-build-agent:stretch-arm64 - endpoint: VSCodeHub - - container: vscode-armhf - image: vscodehub.azurecr.io/vscode-linux-build-agent:stretch-armhf - endpoint: VSCodeHub - - container: snapcraft - image: snapcore/snapcraft:stable + - container: vscode-x64 + image: vscodehub.azurecr.io/vscode-linux-build-agent:x64 + endpoint: VSCodeHub + - container: vscode-arm64 + image: vscodehub.azurecr.io/vscode-linux-build-agent:stretch-arm64 + endpoint: VSCodeHub + - container: vscode-armhf + image: vscodehub.azurecr.io/vscode-linux-build-agent:stretch-armhf + endpoint: VSCodeHub + - container: snapcraft + image: snapcore/snapcraft:stable stages: -- stage: Compile - jobs: - - job: Compile - pool: - vmImage: 'Ubuntu-16.04' - container: vscode-x64 - steps: - - template: product-compile.yml + - stage: Compile + jobs: + - job: Compile + pool: + vmImage: "Ubuntu-16.04" + container: vscode-x64 + variables: + VSCODE_ARCH: x64 + steps: + - template: product-compile.yml -- stage: Windows - dependsOn: - - Compile - condition: and(succeeded(), eq(variables['VSCODE_COMPILE_ONLY'], 'false')) - pool: - vmImage: VS2017-Win2016 - jobs: - - job: Windows - condition: and(succeeded(), eq(variables['VSCODE_BUILD_WIN32'], 'true')) - variables: - VSCODE_ARCH: x64 - steps: - - template: win32/product-build-win32.yml - - - job: Windows32 - condition: and(succeeded(), eq(variables['VSCODE_BUILD_WIN32_32BIT'], 'true')) - variables: - VSCODE_ARCH: ia32 - steps: - - template: win32/product-build-win32.yml - - - job: WindowsARM64 - condition: and(succeeded(), eq(variables['VSCODE_BUILD_WIN32_ARM64'], 'true')) - variables: - VSCODE_ARCH: arm64 - steps: - - template: win32/product-build-win32-arm64.yml - -- stage: Linux - dependsOn: - - Compile - condition: and(succeeded(), eq(variables['VSCODE_COMPILE_ONLY'], 'false')) - pool: - vmImage: 'Ubuntu-16.04' - jobs: - - job: Linux - condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX'], 'true')) - container: vscode-x64 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - steps: - - template: linux/product-build-linux.yml - - - job: LinuxSnap + - stage: Windows dependsOn: - - Linux - condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX'], 'true')) - container: snapcraft - variables: - VSCODE_ARCH: x64 - steps: - - template: linux/snap-build-linux.yml + - Compile + condition: and(succeeded(), eq(variables['VSCODE_COMPILE_ONLY'], 'false')) + pool: + vmImage: VS2017-Win2016 + jobs: + - job: Windows + condition: and(succeeded(), eq(variables['VSCODE_BUILD_WIN32'], 'true')) + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: x64 + steps: + - template: win32/product-build-win32.yml - - job: LinuxArmhf - condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX_ARMHF'], 'true')) - container: vscode-armhf - variables: - VSCODE_ARCH: armhf - NPM_ARCH: armv7l - steps: - - template: linux/product-build-linux.yml + - job: Windows32 + condition: and(succeeded(), eq(variables['VSCODE_BUILD_WIN32_32BIT'], 'true')) + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: ia32 + steps: + - template: win32/product-build-win32.yml - - job: LinuxArm64 - condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX_ARM64'], 'true')) - container: vscode-arm64 - variables: - VSCODE_ARCH: arm64 - NPM_ARCH: arm64 - steps: - - template: linux/product-build-linux.yml + - job: WindowsARM64 + condition: and(succeeded(), eq(variables['VSCODE_BUILD_WIN32_ARM64'], 'true')) + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: arm64 + steps: + - template: win32/product-build-win32.yml - - job: LinuxAlpine - condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX_ALPINE'], 'true')) - variables: - VSCODE_ARCH: alpine - steps: - - template: linux/product-build-linux-multiarch.yml + - stage: Linux + dependsOn: + - Compile + condition: and(succeeded(), eq(variables['VSCODE_COMPILE_ONLY'], 'false')) + pool: + vmImage: "Ubuntu-16.04" + jobs: + - job: Linux + condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX'], 'true')) + container: vscode-x64 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 + steps: + - template: linux/product-build-linux.yml - - job: LinuxWeb - condition: and(succeeded(), eq(variables['VSCODE_BUILD_WEB'], 'true')) - variables: - VSCODE_ARCH: x64 - steps: - - template: web/product-build-web.yml + - job: LinuxSnap + dependsOn: + - Linux + condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX'], 'true')) + container: snapcraft + variables: + VSCODE_ARCH: x64 + steps: + - template: linux/snap-build-linux.yml -- stage: macOS - dependsOn: - - Compile - condition: and(succeeded(), eq(variables['VSCODE_COMPILE_ONLY'], 'false')) - pool: - vmImage: macOS-latest - jobs: - - job: macOS - condition: and(succeeded(), eq(variables['VSCODE_BUILD_MACOS'], 'true')) - variables: - VSCODE_ARCH: x64 - steps: - - template: darwin/product-build-darwin.yml + - job: LinuxArmhf + condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX_ARMHF'], 'true')) + container: vscode-armhf + variables: + VSCODE_ARCH: armhf + NPM_ARCH: armv7l + steps: + - template: linux/product-build-linux.yml -- stage: Mooncake - dependsOn: - - Windows - - Linux - - macOS - condition: and(succeededOrFailed(), eq(variables['VSCODE_COMPILE_ONLY'], 'false')) - pool: - vmImage: 'Ubuntu-16.04' - jobs: - - job: SyncMooncake - displayName: Sync Mooncake - steps: - - template: sync-mooncake.yml + - job: LinuxSnapArmhf + dependsOn: + - LinuxArmhf + condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX_ARMHF'], 'true')) + container: snapcraft + variables: + VSCODE_ARCH: armhf + steps: + - template: linux/snap-build-linux.yml -- stage: Publish - dependsOn: - - Windows - - Linux - - macOS - condition: and(succeeded(), eq(variables['VSCODE_COMPILE_ONLY'], 'false'), or(eq(variables['VSCODE_RELEASE'], 'true'), and(or(eq(variables['VSCODE_QUALITY'], 'insider'), eq(variables['VSCODE_QUALITY'], 'exploration')), eq(variables['Build.Reason'], 'Schedule')))) - pool: - vmImage: 'Ubuntu-16.04' - jobs: - - job: BuildService - displayName: Build Service - steps: - - template: release.yml + - job: LinuxArm64 + condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX_ARM64'], 'true')) + container: vscode-arm64 + variables: + VSCODE_ARCH: arm64 + NPM_ARCH: arm64 + steps: + - template: linux/product-build-linux.yml + + - job: LinuxSnapArm64 + dependsOn: + - LinuxArm64 + condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX_ARM64'], 'true')) + container: snapcraft + variables: + VSCODE_ARCH: arm64 + steps: + - template: linux/snap-build-linux.yml + + - job: LinuxAlpine + condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX_ALPINE'], 'true')) + steps: + - template: linux/product-build-alpine.yml + + - job: LinuxWeb + condition: and(succeeded(), eq(variables['VSCODE_BUILD_WEB'], 'true')) + variables: + VSCODE_ARCH: x64 + steps: + - template: web/product-build-web.yml + + - stage: macOS + dependsOn: + - Compile + condition: and(succeeded(), eq(variables['VSCODE_COMPILE_ONLY'], 'false')) + pool: + vmImage: macOS-latest + jobs: + - job: macOS + condition: and(succeeded(), eq(variables['VSCODE_BUILD_MACOS'], 'true')) + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: x64 + steps: + - template: darwin/product-build-darwin.yml + + - stage: macOSARM64 + dependsOn: + - Compile + condition: and(succeeded(), eq(variables['VSCODE_COMPILE_ONLY'], 'false')) + pool: + vmImage: macOS-latest + jobs: + - job: macOSARM64 + condition: and(succeeded(), eq(variables['VSCODE_BUILD_MACOS_ARM64'], 'true')) + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: arm64 + steps: + - template: darwin/product-build-darwin.yml + + - stage: Mooncake + dependsOn: + - Windows + - Linux + - macOS + - macOSARM64 + condition: and(succeededOrFailed(), eq(variables['VSCODE_COMPILE_ONLY'], 'false')) + pool: + vmImage: "Ubuntu-16.04" + jobs: + - job: SyncMooncake + displayName: Sync Mooncake + steps: + - template: sync-mooncake.yml + + - stage: Publish + dependsOn: + - Windows + - Linux + - macOS + - macOSARM64 + condition: and(succeeded(), eq(variables['VSCODE_COMPILE_ONLY'], 'false'), or(eq(variables['VSCODE_RELEASE'], 'true'), and(or(eq(variables['VSCODE_QUALITY'], 'insider'), eq(variables['VSCODE_QUALITY'], 'exploration')), eq(variables['Build.Reason'], 'Schedule')))) + pool: + vmImage: "Ubuntu-16.04" + jobs: + - job: BuildService + displayName: Build Service + steps: + - template: release.yml diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index 2eff40b53..eaf0a65e8 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -1,146 +1,161 @@ steps: -- script: | - mkdir -p .build - echo -n $BUILD_SOURCEVERSION > .build/commit - echo -n $VSCODE_QUALITY > .build/quality - displayName: Prepare cache flag + - script: | + mkdir -p .build + echo -n $BUILD_SOURCEVERSION > .build/commit + echo -n $VSCODE_QUALITY > .build/quality + echo -n $ENABLE_TERRAPIN > .build/terrapin + displayName: Prepare compilation cache flag -- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - inputs: - keyfile: 'build/.cachesalt, .build/commit, .build/quality' - targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' - vstsFeed: 'npm-vscode' - platformIndependent: true - alias: 'Compilation' - dryRun: true + - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 + inputs: + keyfile: "build/.cachesalt, .build/commit, .build/quality, .build/terrapin" + targetfolder: ".build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min" + vstsFeed: "npm-vscode" + platformIndependent: true + alias: "Compilation" + dryRun: true -- task: NodeTool@0 - inputs: - versionSpec: "12.14.1" - condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) + - task: NodeTool@0 + inputs: + versionSpec: "12.14.1" + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) -- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" - condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) + - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + inputs: + versionSpec: "1.x" + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) -- task: AzureKeyVault@1 - displayName: 'Azure Key Vault: Get Secrets' - inputs: - azureSubscription: 'vscode-builds-subscription' - KeyVaultName: vscode - condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) + - task: AzureKeyVault@1 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: "vscode-builds-subscription" + KeyVaultName: vscode + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) -- script: | - set -e - cat << EOF > ~/.netrc - machine github.com - login vscode - password $(github-distro-mixin-password) - EOF + - script: | + set -e + cat << EOF > ~/.netrc + machine github.com + login vscode + password $(github-distro-mixin-password) + EOF - git config user.email "vscode@microsoft.com" - git config user.name "VSCode" - displayName: Prepare tooling - condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) + git config user.email "vscode@microsoft.com" + git config user.name "VSCode" + displayName: Prepare tooling + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) -- script: | - set -e - git remote add distro "https://github.com/$(VSCODE_MIXIN_REPO).git" - git fetch distro - git merge $(node -p "require('./package.json').distro") - displayName: Merge distro - condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) + - script: | + set -e + git remote add distro "https://github.com/$(VSCODE_MIXIN_REPO).git" + git fetch distro + git merge $(node -p "require('./package.json').distro") + displayName: Merge distro + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) -- script: | - echo -n $VSCODE_ARCH > .build/arch - displayName: Prepare arch cache flag + - script: | + npx https://aka.ms/enablesecurefeed standAlone + displayName: Switch to Terrapin packages + timeoutInMinutes: 5 + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), eq(variables['ENABLE_TERRAPIN'], 'true')) -- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - inputs: - keyfile: '.build/arch, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' - targetfolder: '**/node_modules, !**/node_modules/**/node_modules' - vstsFeed: 'npm-vscode' - condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) + - script: | + echo -n $(VSCODE_ARCH) > .build/arch + displayName: Prepare yarn cache flags -- script: | - set -e - CHILD_CONCURRENCY=1 yarn --frozen-lockfile - displayName: Install dependencies - condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), ne(variables['CacheRestored'], 'true')) + - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 + inputs: + keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock" + targetfolder: "**/node_modules, !**/node_modules/**/node_modules" + vstsFeed: "npm-vscode" + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) -- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - inputs: - keyfile: '.build/arch, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' - targetfolder: '**/node_modules, !**/node_modules/**/node_modules' - vstsFeed: 'npm-vscode' - condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), ne(variables['CacheRestored'], 'true')) + - script: | + set -e + export CHILD_CONCURRENCY="1" + for i in {1..3}; do # try 3 times, for Terrapin + yarn --frozen-lockfile && 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 dependencies + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), ne(variables['CacheRestored'], 'true')) -- script: | - set -e - yarn postinstall - displayName: Run postinstall scripts - condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), eq(variables['CacheRestored'], 'true')) + - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 + inputs: + keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock" + targetfolder: "**/node_modules, !**/node_modules/**/node_modules" + vstsFeed: "npm-vscode" + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), ne(variables['CacheRestored'], 'true')) -# Mixin must run before optimize, because the CSS loader will -# inline small SVGs -- script: | - set -e - node build/azure-pipelines/mixin - displayName: Mix in quality - condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) + - script: | + set -e + yarn postinstall + displayName: Run postinstall scripts + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), eq(variables['CacheRestored'], 'true')) -- script: | - set -e - yarn gulp hygiene - yarn monaco-compile-check - yarn valid-layers-check - displayName: Run hygiene, monaco compile & valid layers checks - condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + # Mixin must run before optimize, because the CSS loader will + # inline small SVGs + - script: | + set -e + node build/azure-pipelines/mixin + displayName: Mix in quality + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) -- script: | - set - - ./build/azure-pipelines/common/extract-telemetry.sh - displayName: Extract Telemetry - condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) + - script: | + set -e + yarn gulp hygiene + yarn monaco-compile-check + yarn valid-layers-check + displayName: Run hygiene, monaco compile & valid layers checks + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) -- script: | - set -e - AZURE_WEBVIEW_STORAGE_ACCESS_KEY="$(vscode-webview-storage-key)" \ - ./build/azure-pipelines/common/publish-webview.sh - displayName: Publish Webview - condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) + - script: | + set - + ./build/azure-pipelines/common/extract-telemetry.sh + displayName: Extract Telemetry + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) -- script: | - set -e - yarn gulp compile-build - yarn gulp compile-extensions-build - yarn gulp minify-vscode - yarn gulp minify-vscode-reh - yarn gulp minify-vscode-reh-web - displayName: Compile - condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) + - script: | + set -e + AZURE_WEBVIEW_STORAGE_ACCESS_KEY="$(vscode-webview-storage-key)" \ + ./build/azure-pipelines/common/publish-webview.sh + displayName: Publish Webview + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) -- script: | - set -e - AZURE_STORAGE_ACCESS_KEY="$(ticino-storage-key)" \ - node build/azure-pipelines/upload-sourcemaps - displayName: Upload sourcemaps - condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) + - script: | + set -e + yarn gulp compile-build + yarn gulp compile-extensions-build + yarn gulp minify-vscode + yarn gulp minify-vscode-reh + yarn gulp minify-vscode-reh-web + displayName: Compile + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) -- script: | - set -e - VERSION=`node -p "require(\"./package.json\").version"` - AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ - node build/azure-pipelines/common/createBuild.js $VERSION - displayName: Create build - condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) + - script: | + set -e + AZURE_STORAGE_ACCESS_KEY="$(ticino-storage-key)" \ + node build/azure-pipelines/upload-sourcemaps + displayName: Upload sourcemaps + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) -- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - inputs: - keyfile: 'build/.cachesalt, .build/commit, .build/quality' - targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' - vstsFeed: 'npm-vscode' - platformIndependent: true - alias: 'Compilation' - condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) + - script: | + set -e + VERSION=`node -p "require(\"./package.json\").version"` + AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ + node build/azure-pipelines/common/createBuild.js $VERSION + displayName: Create build + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) + + - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 + inputs: + keyfile: "build/.cachesalt, .build/commit, .build/quality, .build/terrapin" + targetfolder: ".build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min" + vstsFeed: "npm-vscode" + platformIndependent: true + alias: "Compilation" + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) diff --git a/build/azure-pipelines/publish-types/publish-types.yml b/build/azure-pipelines/publish-types/publish-types.yml index 10b6aa4e1..5f1cf6c28 100644 --- a/build/azure-pipelines/publish-types/publish-types.yml +++ b/build/azure-pipelines/publish-types/publish-types.yml @@ -2,82 +2,82 @@ trigger: branches: - include: ['refs/tags/*'] + include: ["refs/tags/*"] pr: none steps: -- task: NodeTool@0 - inputs: - versionSpec: "12.14.1" + - task: NodeTool@0 + inputs: + versionSpec: "12.14.1" -- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" + - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + inputs: + versionSpec: "1.x" -- bash: | - TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`) - CHANNEL="G1C14HJ2F" + - bash: | + TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`) + CHANNEL="G1C14HJ2F" - if [ "$TAG_VERSION" == "1.999.0" ]; then - MESSAGE=". Someone pushed 1.999.0 tag. Please delete it ASAP from remote and local." + if [ "$TAG_VERSION" == "1.999.0" ]; then + MESSAGE=". Someone pushed 1.999.0 tag. Please delete it ASAP from remote and local." + + curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \ + -H 'Content-type: application/json; charset=utf-8' \ + --data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$MESSAGE"'"}' \ + https://slack.com/api/chat.postMessage + + exit 1 + fi + displayName: Check 1.999.0 tag + + - bash: | + # Install build dependencies + (cd build && yarn) + node build/azure-pipelines/publish-types/check-version.js + displayName: Check version + + - bash: | + git config --global user.email "vscode@microsoft.com" + git config --global user.name "VSCode" + + git clone https://$(GITHUB_TOKEN)@github.com/DefinitelyTyped/DefinitelyTyped.git --depth=1 + node build/azure-pipelines/publish-types/update-types.js + + TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`) + + cd DefinitelyTyped + + git diff --color | cat + git add -A + git status + git checkout -b "vscode-types-$TAG_VERSION" + git commit -m "VS Code $TAG_VERSION Extension API" + git push origin "vscode-types-$TAG_VERSION" + + displayName: Push update to DefinitelyTyped + + - bash: | + TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`) + CHANNEL="G1C14HJ2F" + + MESSAGE="DefinitelyTyped/DefinitelyTyped#vscode-types-$TAG_VERSION created. Endgame master, please open this link, examine changes and create a PR:" + LINK="https://github.com/DefinitelyTyped/DefinitelyTyped/compare/vscode-types-$TAG_VERSION?quick_pull=1&body=Updating%20VS%20Code%20Extension%20API.%20See%20https%3A%2F%2Fgithub.com%2Fmicrosoft%2Fvscode%2Fissues%2F70175%20for%20details." + MESSAGE2="[@eamodio, @jrieken, @kmaetzel, @egamma]. Please review and merge PR to publish @types/vscode." curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \ -H 'Content-type: application/json; charset=utf-8' \ --data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$MESSAGE"'"}' \ https://slack.com/api/chat.postMessage - exit 1 - fi - displayName: Check 1.999.0 tag + curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \ + -H 'Content-type: application/json; charset=utf-8' \ + --data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$LINK"'"}' \ + https://slack.com/api/chat.postMessage -- bash: | - # Install build dependencies - (cd build && yarn) - node build/azure-pipelines/publish-types/check-version.js - displayName: Check version + curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \ + -H 'Content-type: application/json; charset=utf-8' \ + --data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$MESSAGE2"'"}' \ + https://slack.com/api/chat.postMessage -- bash: | - git config --global user.email "vscode@microsoft.com" - git config --global user.name "VSCode" - - git clone https://$(GITHUB_TOKEN)@github.com/DefinitelyTyped/DefinitelyTyped.git --depth=1 - node build/azure-pipelines/publish-types/update-types.js - - TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`) - - cd DefinitelyTyped - - git diff --color | cat - git add -A - git status - git checkout -b "vscode-types-$TAG_VERSION" - git commit -m "VS Code $TAG_VERSION Extension API" - git push origin "vscode-types-$TAG_VERSION" - - displayName: Push update to DefinitelyTyped - -- bash: | - TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`) - CHANNEL="G1C14HJ2F" - - MESSAGE="DefinitelyTyped/DefinitelyTyped#vscode-types-$TAG_VERSION created. Endgame master, please open this link, examine changes and create a PR:" - LINK="https://github.com/DefinitelyTyped/DefinitelyTyped/compare/vscode-types-$TAG_VERSION?quick_pull=1&body=Updating%20VS%20Code%20Extension%20API.%20See%20https%3A%2F%2Fgithub.com%2Fmicrosoft%2Fvscode%2Fissues%2F70175%20for%20details." - MESSAGE2="[@eamodio, @jrieken, @kmaetzel, @egamma]. Please review and merge PR to publish @types/vscode." - - curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \ - -H 'Content-type: application/json; charset=utf-8' \ - --data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$MESSAGE"'"}' \ - https://slack.com/api/chat.postMessage - - curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \ - -H 'Content-type: application/json; charset=utf-8' \ - --data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$LINK"'"}' \ - https://slack.com/api/chat.postMessage - - curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \ - -H 'Content-type: application/json; charset=utf-8' \ - --data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$MESSAGE2"'"}' \ - https://slack.com/api/chat.postMessage - - displayName: Send message on Slack + displayName: Send message on Slack diff --git a/build/azure-pipelines/release.yml b/build/azure-pipelines/release.yml index f5365b80c..edd293c04 100644 --- a/build/azure-pipelines/release.yml +++ b/build/azure-pipelines/release.yml @@ -1,22 +1,22 @@ steps: -- task: NodeTool@0 - inputs: - versionSpec: "10.x" + - task: NodeTool@0 + inputs: + versionSpec: "10.x" -- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" + - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + inputs: + versionSpec: "1.x" -- task: AzureKeyVault@1 - displayName: 'Azure Key Vault: Get Secrets' - inputs: - azureSubscription: 'vscode-builds-subscription' - KeyVaultName: vscode + - task: AzureKeyVault@1 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: "vscode-builds-subscription" + KeyVaultName: vscode -- script: | - set -e + - script: | + set -e - (cd build ; yarn) + (cd build ; yarn) - AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ - node build/azure-pipelines/common/releaseBuild.js + AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ + node build/azure-pipelines/common/releaseBuild.js diff --git a/build/azure-pipelines/sync-mooncake.yml b/build/azure-pipelines/sync-mooncake.yml index 49dfc9ced..109709418 100644 --- a/build/azure-pipelines/sync-mooncake.yml +++ b/build/azure-pipelines/sync-mooncake.yml @@ -1,24 +1,24 @@ steps: -- task: NodeTool@0 - inputs: - versionSpec: "12.14.1" + - task: NodeTool@0 + inputs: + versionSpec: "12.14.1" -- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" + - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + inputs: + versionSpec: "1.x" -- task: AzureKeyVault@1 - displayName: 'Azure Key Vault: Get Secrets' - inputs: - azureSubscription: 'vscode-builds-subscription' - KeyVaultName: vscode + - task: AzureKeyVault@1 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: "vscode-builds-subscription" + KeyVaultName: vscode -- script: | - set -e + - script: | + set -e - (cd build ; yarn) + (cd build ; yarn) - AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ - AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ - MOONCAKE_STORAGE_ACCESS_KEY="$(vscode-mooncake-storage-key)" \ - node build/azure-pipelines/common/sync-mooncake.js "$VSCODE_QUALITY" + AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ + AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ + MOONCAKE_STORAGE_ACCESS_KEY="$(vscode-mooncake-storage-key)" \ + node build/azure-pipelines/common/sync-mooncake.js "$VSCODE_QUALITY" diff --git a/build/azure-pipelines/web/product-build-web.yml b/build/azure-pipelines/web/product-build-web.yml index aded67174..33282adbe 100644 --- a/build/azure-pipelines/web/product-build-web.yml +++ b/build/azure-pipelines/web/product-build-web.yml @@ -1,113 +1,132 @@ steps: -- script: | - mkdir -p .build - echo -n $BUILD_SOURCEVERSION > .build/commit - echo -n $VSCODE_QUALITY > .build/quality - displayName: Prepare cache flag + - script: | + mkdir -p .build + echo -n $BUILD_SOURCEVERSION > .build/commit + echo -n $VSCODE_QUALITY > .build/quality + echo -n $ENABLE_TERRAPIN > .build/terrapin + displayName: Prepare compilation cache flag -- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - inputs: - keyfile: 'build/.cachesalt, .build/commit, .build/quality' - targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' - vstsFeed: 'npm-vscode' - platformIndependent: true - alias: 'Compilation' + - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 + inputs: + keyfile: "build/.cachesalt, .build/commit, .build/quality, .build/terrapin" + targetfolder: ".build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min" + vstsFeed: "npm-vscode" + platformIndependent: true + alias: "Compilation" -- script: | - set -e - exit 1 - displayName: Check RestoreCache - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + - script: | + set -e + exit 1 + displayName: Check RestoreCache + condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) -- task: NodeTool@0 - inputs: - versionSpec: "12.14.1" + - task: NodeTool@0 + inputs: + versionSpec: "12.14.1" -- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" + - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + inputs: + versionSpec: "1.x" -- task: AzureKeyVault@1 - displayName: 'Azure Key Vault: Get Secrets' - inputs: - azureSubscription: 'vscode-builds-subscription' - KeyVaultName: vscode + - task: AzureKeyVault@1 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: "vscode-builds-subscription" + KeyVaultName: vscode -- script: | - set -e - cat << EOF > ~/.netrc - machine github.com - login vscode - password $(github-distro-mixin-password) - EOF + - script: | + set -e + cat << EOF > ~/.netrc + machine github.com + login vscode + password $(github-distro-mixin-password) + EOF - git config user.email "vscode@microsoft.com" - git config user.name "VSCode" - displayName: Prepare tooling + git config user.email "vscode@microsoft.com" + git config user.name "VSCode" + displayName: Prepare tooling -- script: | - set -e - git remote add distro "https://github.com/$(VSCODE_MIXIN_REPO).git" - git fetch distro - git merge $(node -p "require('./package.json').distro") - displayName: Merge distro + - script: | + set -e + git remote add distro "https://github.com/$(VSCODE_MIXIN_REPO).git" + git fetch distro + git merge $(node -p "require('./package.json').distro") + displayName: Merge distro -# - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 -# inputs: -# keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' -# targetfolder: '**/node_modules, !**/node_modules/**/node_modules' -# vstsFeed: 'npm-vscode' + - script: | + npx https://aka.ms/enablesecurefeed standAlone + displayName: Switch to Terrapin packages + timeoutInMinutes: 5 + condition: and(succeeded(), eq(variables['ENABLE_TERRAPIN'], 'true')) -- script: | - set -e - CHILD_CONCURRENCY=1 yarn --frozen-lockfile - displayName: Install dependencies - # condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) + - script: | + echo -n "web" > .build/arch + displayName: Prepare yarn cache flag -# - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 -# inputs: -# keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' -# targetfolder: '**/node_modules, !**/node_modules/**/node_modules' -# vstsFeed: 'npm-vscode' -# condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) + - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 + inputs: + keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock" + targetfolder: "**/node_modules, !**/node_modules/**/node_modules" + vstsFeed: "npm-vscode" -# - script: | -# set -e -# yarn postinstall -# displayName: Run postinstall scripts -# condition: and(succeeded(), eq(variables['CacheRestored'], 'true')) + - script: | + set -e + export CHILD_CONCURRENCY="1" + for i in {1..3}; do # try 3 times, for Terrapin + yarn --frozen-lockfile && 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 dependencies + condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) -- script: | - set -e - node build/azure-pipelines/mixin - displayName: Mix in quality + - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 + inputs: + keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock" + targetfolder: "**/node_modules, !**/node_modules/**/node_modules" + vstsFeed: "npm-vscode" + condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) -- script: | - set -e - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-web-min-ci - displayName: Build + - script: | + set -e + yarn postinstall + displayName: Run postinstall scripts + condition: and(succeeded(), eq(variables['CacheRestored'], 'true')) -- script: | - set -e - AZURE_STORAGE_ACCOUNT="$(web-storage-account)" \ - AZURE_STORAGE_ACCESS_KEY="$(web-storage-key)" \ - node build/azure-pipelines/upload-cdn.js - displayName: Upload to CDN + - script: | + set -e + node build/azure-pipelines/mixin + displayName: Mix in quality - # upload only the workbench.web.api.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 -- script: | - set -e - AZURE_STORAGE_ACCESS_KEY="$(ticino-storage-key)" \ - node build/azure-pipelines/upload-sourcemaps out-vscode-web-min out-vscode-web-min/vs/workbench/workbench.web.api.js.map - displayName: Upload sourcemaps (Web) + - script: | + set -e + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp vscode-web-min-ci + displayName: Build -- script: | - set -e - AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ - AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - ./build/azure-pipelines/web/publish.sh - displayName: Publish + - script: | + set -e + AZURE_STORAGE_ACCOUNT="$(web-storage-account)" \ + AZURE_STORAGE_ACCESS_KEY="$(web-storage-key)" \ + node build/azure-pipelines/upload-cdn.js + displayName: Upload to CDN + + # upload only the workbench.web.api.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 + - script: | + set -e + AZURE_STORAGE_ACCESS_KEY="$(ticino-storage-key)" \ + node build/azure-pipelines/upload-sourcemaps out-vscode-web-min out-vscode-web-min/vs/workbench/workbench.web.api.js.map + displayName: Upload sourcemaps (Web) + + - script: | + set -e + AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ + AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + ./build/azure-pipelines/web/publish.sh + displayName: Publish diff --git a/build/azure-pipelines/win32/continuous-build-win32.yml b/build/azure-pipelines/win32/continuous-build-win32.yml index 6107eacc6..87b820cb0 100644 --- a/build/azure-pipelines/win32/continuous-build-win32.yml +++ b/build/azure-pipelines/win32/continuous-build-win32.yml @@ -1,87 +1,87 @@ steps: -- task: NodeTool@0 - inputs: - versionSpec: "12.14.1" + - task: NodeTool@0 + inputs: + versionSpec: "12.14.1" -- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" + - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + inputs: + versionSpec: "1.x" -- task: UsePythonVersion@0 - inputs: - versionSpec: '2.x' - addToPath: true + - task: UsePythonVersion@0 + inputs: + versionSpec: "2.x" + addToPath: true -- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - inputs: - keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' - targetfolder: '**/node_modules, !**/node_modules/**/node_modules' - vstsFeed: 'vscode-build-cache' + - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 + inputs: + keyfile: ".yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock" + targetfolder: "**/node_modules, !**/node_modules/**/node_modules" + vstsFeed: "vscode-build-cache" -- powershell: | - yarn --frozen-lockfile - env: - CHILD_CONCURRENCY: "1" - displayName: Install Dependencies - condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) + - powershell: | + yarn --frozen-lockfile + env: + CHILD_CONCURRENCY: "1" + displayName: Install Dependencies + condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) -- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - inputs: - keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' - targetfolder: '**/node_modules, !**/node_modules/**/node_modules' - vstsFeed: 'vscode-build-cache' - condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) + - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 + inputs: + keyfile: ".yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock" + targetfolder: "**/node_modules, !**/node_modules/**/node_modules" + vstsFeed: "vscode-build-cache" + condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) -- powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn postinstall } - displayName: Run postinstall scripts - condition: and(succeeded(), eq(variables['CacheRestored'], 'true')) + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn postinstall } + displayName: Run postinstall scripts + condition: and(succeeded(), eq(variables['CacheRestored'], 'true')) -- powershell: | - yarn electron - displayName: Download Electron + - powershell: | + yarn electron + displayName: Download Electron -- powershell: | - yarn monaco-compile-check - displayName: Run Monaco Editor Checks + - powershell: | + yarn monaco-compile-check + displayName: Run Monaco Editor Checks -- script: | - yarn valid-layers-check - displayName: Run Valid Layers Checks + - script: | + yarn valid-layers-check + displayName: Run Valid Layers Checks -- powershell: | - yarn compile - displayName: Compile Sources + - powershell: | + yarn compile + displayName: Compile Sources -- powershell: | - yarn download-builtin-extensions - displayName: Download Built-in Extensions + - powershell: | + yarn download-builtin-extensions + displayName: Download Built-in Extensions -- powershell: | - .\scripts\test.bat --tfs "Unit Tests" - displayName: Run Unit Tests (Electron) + - powershell: | + .\scripts\test.bat --tfs "Unit Tests" + displayName: Run Unit Tests (Electron) -- powershell: | - yarn test-browser --browser chromium --browser firefox --tfs "Browser Unit Tests" - displayName: Run Unit Tests (Browser) + - powershell: | + yarn test-browser --browser chromium --browser firefox --tfs "Browser Unit Tests" + displayName: Run Unit Tests (Browser) -- powershell: | - .\scripts\test-integration.bat --tfs "Integration Tests" - displayName: Run Integration Tests (Electron) + - powershell: | + .\scripts\test-integration.bat --tfs "Integration Tests" + displayName: Run Integration Tests (Electron) -- task: PublishPipelineArtifact@0 - displayName: 'Publish Crash Reports' - inputs: - artifactName: crash-dump-windows - targetPath: .build\crashes - continueOnError: true - condition: failed() + - task: PublishPipelineArtifact@0 + displayName: "Publish Crash Reports" + inputs: + artifactName: crash-dump-windows + targetPath: .build\crashes + continueOnError: true + condition: failed() -- task: PublishTestResults@2 - displayName: Publish Tests Results - inputs: - testResultsFiles: '*-results.xml' - searchFolder: '$(Build.ArtifactStagingDirectory)/test-results' - condition: succeededOrFailed() + - task: PublishTestResults@2 + displayName: Publish Tests Results + inputs: + testResultsFiles: "*-results.xml" + searchFolder: "$(Build.ArtifactStagingDirectory)/test-results" + condition: succeededOrFailed() diff --git a/build/azure-pipelines/win32/product-build-win32-arm64.yml b/build/azure-pipelines/win32/product-build-win32-arm64.yml deleted file mode 100644 index 2e53167e6..000000000 --- a/build/azure-pipelines/win32/product-build-win32-arm64.yml +++ /dev/null @@ -1,192 +0,0 @@ -steps: -- powershell: | - mkdir .build -ea 0 - "$env:BUILD_SOURCEVERSION" | Out-File -Encoding ascii -NoNewLine .build\commit - "$env:VSCODE_QUALITY" | Out-File -Encoding ascii -NoNewLine .build\quality - displayName: Prepare cache flag - -- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - inputs: - keyfile: 'build/.cachesalt, .build/commit, .build/quality' - targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' - vstsFeed: 'npm-vscode' - platformIndependent: true - alias: 'Compilation' - -- powershell: | - $ErrorActionPreference = "Stop" - exit 1 - displayName: Check RestoreCache - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) - -- task: NodeTool@0 - inputs: - versionSpec: "12.14.1" - -- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" - -- task: UsePythonVersion@0 - inputs: - versionSpec: '2.x' - addToPath: true - -- task: AzureKeyVault@1 - displayName: 'Azure Key Vault: Get Secrets' - inputs: - azureSubscription: 'vscode-builds-subscription' - KeyVaultName: vscode - -- powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - "machine github.com`nlogin vscode`npassword $(github-distro-mixin-password)" | Out-File "$env:USERPROFILE\_netrc" -Encoding ASCII - - exec { git config user.email "vscode@microsoft.com" } - exec { git config user.name "VSCode" } - - mkdir .build -ea 0 - "$(VSCODE_ARCH)" | Out-File -Encoding ascii -NoNewLine .build\arch - displayName: Prepare tooling - -- powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { git remote add distro "https://github.com/$(VSCODE_MIXIN_REPO).git" } - exec { git fetch distro } - exec { git merge $(node -p "require('./package.json').distro") } - displayName: Merge distro - -- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - inputs: - keyfile: 'build/.cachesalt, .build/arch, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' - targetfolder: '**/node_modules, !**/node_modules/**/node_modules' - vstsFeed: 'npm-vscode' - -- powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $env:npm_config_arch="$(VSCODE_ARCH)" - $env:CHILD_CONCURRENCY="1" - exec { yarn --frozen-lockfile } - displayName: Install dependencies - condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - -- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - inputs: - keyfile: 'build/.cachesalt, .build/arch, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' - targetfolder: '**/node_modules, !**/node_modules/**/node_modules' - vstsFeed: 'npm-vscode' - condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - -- powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn postinstall } - displayName: Run postinstall scripts - condition: and(succeeded(), eq(variables['CacheRestored'], 'true')) - -- powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { node build/azure-pipelines/mixin } - displayName: Mix in quality - -- powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" - exec { yarn gulp "vscode-win32-$env:VSCODE_ARCH-min-ci" } - exec { yarn gulp "vscode-win32-$env:VSCODE_ARCH-code-helper" } - exec { yarn gulp "vscode-win32-$env:VSCODE_ARCH-inno-updater" } - displayName: Build - -- task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 - inputs: - ConnectedServiceName: 'ESRP CodeSign' - FolderPath: '$(agent.builddirectory)/VSCode-win32-$(VSCODE_ARCH)' - Pattern: '*.dll,*.exe,*.node' - signConfigType: inlineSignParams - inlineOperation: | - [ - { - "keyCode": "CP-230012", - "operationSetCode": "SigntoolSign", - "parameters": [ - { - "parameterName": "OpusName", - "parameterValue": "VS Code" - }, - { - "parameterName": "OpusInfo", - "parameterValue": "https://code.visualstudio.com/" - }, - { - "parameterName": "Append", - "parameterValue": "/as" - }, - { - "parameterName": "FileDigest", - "parameterValue": "/fd \"SHA256\"" - }, - { - "parameterName": "PageHash", - "parameterValue": "/NPH" - }, - { - "parameterName": "TimeStamp", - "parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" - } - ], - "toolName": "sign", - "toolVersion": "1.0" - }, - { - "keyCode": "CP-230012", - "operationSetCode": "SigntoolVerify", - "parameters": [ - { - "parameterName": "VerifyAll", - "parameterValue": "/all" - } - ], - "toolName": "sign", - "toolVersion": "1.0" - } - ] - SessionTimeout: 120 - -- task: NuGetCommand@2 - displayName: Install ESRPClient.exe - inputs: - restoreSolution: 'build\azure-pipelines\win32\ESRPClient\packages.config' - feedsToUse: config - nugetConfigPath: 'build\azure-pipelines\win32\ESRPClient\NuGet.config' - externalFeedCredentials: 3fc0b7f7-da09-4ae7-a9c8-d69824b1819b - restoreDirectory: packages - -- task: ESRPImportCertTask@1 - displayName: Import ESRP Request Signing Certificate - inputs: - ESRP: 'ESRP CodeSign' - -- task: PowerShell@2 - inputs: - targetType: filePath - filePath: .\build\azure-pipelines\win32\import-esrp-auth-cert.ps1 - arguments: "$(ESRP-SSL-AADAuth)" - displayName: Import ESRP Auth Certificate - -- powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $env:AZURE_STORAGE_ACCESS_KEY_2 = "$(vscode-storage-key)" - $env:AZURE_DOCUMENTDB_MASTERKEY = "$(builds-docdb-key-readwrite)" - $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" - .\build\azure-pipelines\win32\publish.ps1 - displayName: Publish - -- task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: 'Component Detection' - continueOnError: true diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 43bd2479a..1992ac1af 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -1,254 +1,272 @@ steps: -- powershell: | - mkdir .build -ea 0 - "$env:BUILD_SOURCEVERSION" | Out-File -Encoding ascii -NoNewLine .build\commit - "$env:VSCODE_QUALITY" | Out-File -Encoding ascii -NoNewLine .build\quality - displayName: Prepare cache flag + - powershell: | + mkdir .build -ea 0 + "$env:BUILD_SOURCEVERSION" | Out-File -Encoding ascii -NoNewLine .build\commit + "$env:VSCODE_QUALITY" | Out-File -Encoding ascii -NoNewLine .build\quality + "$env:ENABLE_TERRAPIN" | Out-File -Encoding ascii -NoNewLine .build\terrapin + displayName: Prepare compilation cache flags -- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - inputs: - keyfile: 'build/.cachesalt, .build/commit, .build/quality' - targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' - vstsFeed: 'npm-vscode' - platformIndependent: true - alias: 'Compilation' + - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 + inputs: + keyfile: "build/.cachesalt, .build/commit, .build/quality, .build/terrapin" + targetfolder: ".build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min" + vstsFeed: "npm-vscode" + platformIndependent: true + alias: "Compilation" -- powershell: | - $ErrorActionPreference = "Stop" - exit 1 - displayName: Check RestoreCache - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + - powershell: | + $ErrorActionPreference = "Stop" + exit 1 + displayName: Check RestoreCache + condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) -- task: NodeTool@0 - inputs: - versionSpec: "12.14.1" + - task: NodeTool@0 + inputs: + versionSpec: "12.14.1" -- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" + - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + inputs: + versionSpec: "1.x" -- task: UsePythonVersion@0 - inputs: - versionSpec: '2.x' - addToPath: true + - task: UsePythonVersion@0 + inputs: + versionSpec: "2.x" + addToPath: true -- task: AzureKeyVault@1 - displayName: 'Azure Key Vault: Get Secrets' - inputs: - azureSubscription: 'vscode-builds-subscription' - KeyVaultName: vscode + - task: AzureKeyVault@1 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: "vscode-builds-subscription" + KeyVaultName: vscode -- powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - "machine github.com`nlogin vscode`npassword $(github-distro-mixin-password)" | Out-File "$env:USERPROFILE\_netrc" -Encoding ASCII + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + "machine github.com`nlogin vscode`npassword $(github-distro-mixin-password)" | Out-File "$env:USERPROFILE\_netrc" -Encoding ASCII - exec { git config user.email "vscode@microsoft.com" } - exec { git config user.name "VSCode" } + exec { git config user.email "vscode@microsoft.com" } + exec { git config user.name "VSCode" } + displayName: Prepare tooling - mkdir .build -ea 0 - "$(VSCODE_ARCH)" | Out-File -Encoding ascii -NoNewLine .build\arch - displayName: Prepare tooling + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { git remote add distro "https://github.com/$(VSCODE_MIXIN_REPO).git" } + exec { git fetch distro } + exec { git merge $(node -p "require('./package.json').distro") } + displayName: Merge distro -- powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { git remote add distro "https://github.com/$(VSCODE_MIXIN_REPO).git" } - exec { git fetch distro } - exec { git merge $(node -p "require('./package.json').distro") } - displayName: Merge distro + - script: | + npx https://aka.ms/enablesecurefeed standAlone + displayName: Switch to Terrapin packages + timeoutInMinutes: 5 + condition: and(succeeded(), eq(variables['ENABLE_TERRAPIN'], 'true')) -- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - inputs: - keyfile: 'build/.cachesalt, .build/arch, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' - targetfolder: '**/node_modules, !**/node_modules/**/node_modules' - vstsFeed: 'npm-vscode' + - powershell: | + "$(VSCODE_ARCH)" | Out-File -Encoding ascii -NoNewLine .build\arch + displayName: Prepare yarn cache flags -- powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $env:npm_config_arch="$(VSCODE_ARCH)" - $env:CHILD_CONCURRENCY="1" - exec { yarn --frozen-lockfile } - displayName: Install dependencies - condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) + - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 + inputs: + keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock" + targetfolder: "**/node_modules, !**/node_modules/**/node_modules" + vstsFeed: "npm-vscode" -- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - inputs: - keyfile: 'build/.cachesalt, .build/arch, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' - targetfolder: '**/node_modules, !**/node_modules/**/node_modules' - vstsFeed: 'npm-vscode' - condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + . build/azure-pipelines/win32/retry.ps1 + $ErrorActionPreference = "Stop" + $env:npm_config_arch="$(VSCODE_ARCH)" + $env:CHILD_CONCURRENCY="1" + retry { exec { yarn --frozen-lockfile } } + displayName: Install dependencies + condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) -- powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn postinstall } - displayName: Run postinstall scripts - condition: and(succeeded(), eq(variables['CacheRestored'], 'true')) + - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 + inputs: + keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock" + targetfolder: "**/node_modules, !**/node_modules/**/node_modules" + vstsFeed: "npm-vscode" + condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) -- powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { node build/azure-pipelines/mixin } - displayName: Mix in quality + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn postinstall } + displayName: Run postinstall scripts + condition: and(succeeded(), eq(variables['CacheRestored'], 'true')) -- powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" - exec { yarn gulp "vscode-win32-$env:VSCODE_ARCH-min-ci" } - exec { yarn gulp "vscode-reh-win32-$env:VSCODE_ARCH-min-ci" } - exec { yarn gulp "vscode-reh-web-win32-$env:VSCODE_ARCH-min-ci" } - exec { yarn gulp "vscode-win32-$env:VSCODE_ARCH-code-helper" } - exec { yarn gulp "vscode-win32-$env:VSCODE_ARCH-inno-updater" } - displayName: Build + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { node build/azure-pipelines/mixin } + displayName: Mix in quality -- powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn electron $(VSCODE_ARCH) } - exec { .\scripts\test.bat --build --tfs "Unit Tests" } - displayName: Run unit tests (Electron) - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" + exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-min-ci" } + exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-code-helper" } + exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-inno-updater" } + echo "##vso[task.setvariable variable=CodeSigningFolderPath]$(agent.builddirectory)/VSCode-win32-$(VSCODE_ARCH)" + displayName: Build -- powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn test-browser --build --browser chromium --browser firefox --tfs "Browser Unit Tests" } - displayName: Run unit tests (Browser) - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" + exec { yarn gulp "vscode-reh-win32-$(VSCODE_ARCH)-min-ci" } + exec { yarn gulp "vscode-reh-web-win32-$(VSCODE_ARCH)-min-ci" } + echo "##vso[task.setvariable variable=CodeSigningFolderPath]$(CodeSigningFolderPath),$(agent.builddirectory)/vscode-reh-win32-$(VSCODE_ARCH)" + displayName: Build Server + condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) -- 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 - exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\scripts\test-integration.bat --build --tfs "Integration Tests" } - displayName: Run integration tests (Electron) - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn electron $(VSCODE_ARCH) } + exec { .\scripts\test.bat --build --tfs "Unit Tests" } + displayName: Run unit tests (Electron) + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) -- powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)"; .\resources\server\test\test-web-integration.bat --browser firefox } - displayName: Run integration tests (Browser) - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn test-browser --build --browser chromium --browser firefox --tfs "Browser Unit Tests" } + displayName: Run unit tests (Browser) + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) -- powershell: | - . 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 - exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\resources\server\test\test-remote-integration.bat } - displayName: Run remote integration tests (Electron) - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], '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 + exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\scripts\test-integration.bat --build --tfs "Integration Tests" } + displayName: Run integration tests (Electron) + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) -- task: PublishPipelineArtifact@0 - inputs: - artifactName: crash-dump-windows-$(VSCODE_ARCH) - targetPath: .build\crashes - displayName: 'Publish Crash Reports' - continueOnError: true - condition: failed() + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)"; .\resources\server\test\test-web-integration.bat --browser firefox } + displayName: Run integration tests (Browser) + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) -- task: PublishTestResults@2 - displayName: Publish Tests Results - inputs: - testResultsFiles: '*-results.xml' - searchFolder: '$(Build.ArtifactStagingDirectory)/test-results' - condition: succeededOrFailed() + - powershell: | + . 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 + exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\resources\server\test\test-remote-integration.bat } + displayName: Run remote integration tests (Electron) + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) -- task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 - inputs: - ConnectedServiceName: 'ESRP CodeSign' - FolderPath: '$(agent.builddirectory)/VSCode-win32-$(VSCODE_ARCH),$(agent.builddirectory)/vscode-reh-win32-$(VSCODE_ARCH)' - Pattern: '*.dll,*.exe,*.node' - signConfigType: inlineSignParams - inlineOperation: | - [ - { - "keyCode": "CP-230012", - "operationSetCode": "SigntoolSign", - "parameters": [ - { - "parameterName": "OpusName", - "parameterValue": "VS Code" - }, - { - "parameterName": "OpusInfo", - "parameterValue": "https://code.visualstudio.com/" - }, - { - "parameterName": "Append", - "parameterValue": "/as" - }, - { - "parameterName": "FileDigest", - "parameterValue": "/fd \"SHA256\"" - }, - { - "parameterName": "PageHash", - "parameterValue": "/NPH" - }, - { - "parameterName": "TimeStamp", - "parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" - } - ], - "toolName": "sign", - "toolVersion": "1.0" - }, - { - "keyCode": "CP-230012", - "operationSetCode": "SigntoolVerify", - "parameters": [ - { - "parameterName": "VerifyAll", - "parameterValue": "/all" - } - ], - "toolName": "sign", - "toolVersion": "1.0" - } - ] - SessionTimeout: 120 + - task: PublishPipelineArtifact@0 + inputs: + artifactName: crash-dump-windows-$(VSCODE_ARCH) + targetPath: .build\crashes + displayName: "Publish Crash Reports" + continueOnError: true + condition: failed() -- task: NuGetCommand@2 - displayName: Install ESRPClient.exe - inputs: - restoreSolution: 'build\azure-pipelines\win32\ESRPClient\packages.config' - feedsToUse: config - nugetConfigPath: 'build\azure-pipelines\win32\ESRPClient\NuGet.config' - externalFeedCredentials: 'ESRP Nuget' - restoreDirectory: packages + - task: PublishTestResults@2 + displayName: Publish Tests Results + inputs: + testResultsFiles: "*-results.xml" + searchFolder: "$(Build.ArtifactStagingDirectory)/test-results" + condition: and(succeededOrFailed(), ne(variables['VSCODE_ARCH'], 'arm64')) -- task: ESRPImportCertTask@1 - displayName: Import ESRP Request Signing Certificate - inputs: - ESRP: 'ESRP CodeSign' + - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 + inputs: + ConnectedServiceName: "ESRP CodeSign" + FolderPath: "$(CodeSigningFolderPath)" + Pattern: "*.dll,*.exe,*.node" + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "keyCode": "CP-230012", + "operationSetCode": "SigntoolSign", + "parameters": [ + { + "parameterName": "OpusName", + "parameterValue": "VS Code" + }, + { + "parameterName": "OpusInfo", + "parameterValue": "https://code.visualstudio.com/" + }, + { + "parameterName": "Append", + "parameterValue": "/as" + }, + { + "parameterName": "FileDigest", + "parameterValue": "/fd \"SHA256\"" + }, + { + "parameterName": "PageHash", + "parameterValue": "/NPH" + }, + { + "parameterName": "TimeStamp", + "parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" + } + ], + "toolName": "sign", + "toolVersion": "1.0" + }, + { + "keyCode": "CP-230012", + "operationSetCode": "SigntoolVerify", + "parameters": [ + { + "parameterName": "VerifyAll", + "parameterValue": "/all" + } + ], + "toolName": "sign", + "toolVersion": "1.0" + } + ] + SessionTimeout: 120 -- task: PowerShell@2 - inputs: - targetType: filePath - filePath: .\build\azure-pipelines\win32\import-esrp-auth-cert.ps1 - arguments: "$(ESRP-SSL-AADAuth)" - displayName: Import ESRP Auth Certificate + - task: NuGetCommand@2 + displayName: Install ESRPClient.exe + inputs: + restoreSolution: 'build\azure-pipelines\win32\ESRPClient\packages.config' + feedsToUse: config + nugetConfigPath: 'build\azure-pipelines\win32\ESRPClient\NuGet.config' + externalFeedCredentials: "ESRP Nuget" + restoreDirectory: packages -- powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $env:AZURE_STORAGE_ACCESS_KEY_2 = "$(vscode-storage-key)" - $env:AZURE_DOCUMENTDB_MASTERKEY = "$(builds-docdb-key-readwrite)" - $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" - .\build\azure-pipelines\win32\publish.ps1 - displayName: Publish + - task: ESRPImportCertTask@1 + displayName: Import ESRP Request Signing Certificate + inputs: + ESRP: "ESRP CodeSign" -- task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: 'Component Detection' - continueOnError: true + - task: PowerShell@2 + inputs: + targetType: filePath + filePath: .\build\azure-pipelines\win32\import-esrp-auth-cert.ps1 + arguments: "$(ESRP-SSL-AADAuth)" + displayName: Import ESRP Auth Certificate + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $env:AZURE_STORAGE_ACCESS_KEY_2 = "$(vscode-storage-key)" + $env:AZURE_DOCUMENTDB_MASTERKEY = "$(builds-docdb-key-readwrite)" + $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" + .\build\azure-pipelines\win32\publish.ps1 + displayName: Publish + + - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 + displayName: "Component Detection" + continueOnError: true diff --git a/build/azure-pipelines/win32/retry.ps1 b/build/azure-pipelines/win32/retry.ps1 new file mode 100644 index 000000000..002a5e274 --- /dev/null +++ b/build/azure-pipelines/win32/retry.ps1 @@ -0,0 +1,19 @@ +function Retry +{ + [CmdletBinding()] + param( + [Parameter(Position=0,Mandatory=1)][scriptblock]$cmd + ) + $retry = 0 + + while ($retry++ -lt 3) { + try { + & $cmd + return + } catch { + # noop + } + } + + throw "Max retries reached" +} diff --git a/build/darwin/sign.ts b/build/darwin/sign.ts index ee5d2eeb1..538dc97ad 100644 --- a/build/darwin/sign.ts +++ b/build/darwin/sign.ts @@ -13,6 +13,7 @@ import * as product from '../../product.json'; async function main(): Promise { const buildDir = process.env['AGENT_BUILDDIRECTORY']; const tempDir = process.env['AGENT_TEMPDIRECTORY']; + const arch = process.env['VSCODE_ARCH']; if (!buildDir) { throw new Error('$AGENT_BUILDDIRECTORY not set'); @@ -23,7 +24,7 @@ async function main(): Promise { } const baseDir = path.dirname(__dirname); - const appRoot = path.join(buildDir, 'VSCode-darwin'); + const appRoot = path.join(buildDir, `VSCode-darwin-${arch}`); const appName = product.nameLong + '.app'; const appFrameworkPath = path.join(appRoot, appName, 'Contents', 'Frameworks'); const helperAppBaseName = product.nameShort; diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index aa0282bff..118fe6e81 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -434,7 +434,7 @@ function createTscCompileTask(watch) { // stdio: [null, 'pipe', 'inherit'] }); let errors = []; - let reporter = createReporter(); + let reporter = createReporter('monaco'); let report; // eslint-disable-next-line no-control-regex let magic = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g; // https://stackoverflow.com/questions/25245716/remove-all-ansi-colors-styles-from-strings diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index 6ae72e4cf..c204e84fd 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -64,7 +64,7 @@ const tasks = compilations.map(function (tsconfigFile) { } function createPipeline(build, emitError) { - const reporter = createReporter(); + const reporter = createReporter('extensions'); overrideOptions.inlineSources = Boolean(build); overrideOptions.base = path.dirname(absolutePath); diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 3c8bbe5b8..42a9c2b27 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -58,7 +58,7 @@ const vscodeResources = [ 'out-build/bootstrap-node.js', 'out-build/bootstrap-window.js', 'out-build/paths.js', - 'out-build/vs/**/*.{svg,png,html}', + 'out-build/vs/**/*.{svg,png,html,jpg}', '!out-build/vs/code/browser/**/*.html', '!out-build/vs/editor/standalone/**/*.svg', 'out-build/vs/base/common/performance.js', @@ -201,13 +201,12 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op const telemetry = gulp.src('.build/telemetry/**', { base: '.build/telemetry', dot: true }); - const jsFilter = util.filter(data => !data.isDirectory() &&/\.js$/.test(data.path)); + const jsFilter = util.filter(data => !data.isDirectory() && /\.js$/.test(data.path)); const root = path.resolve(path.join(__dirname, '..')); const dependenciesSrc = _.flatten(productionDependencies.map(d => path.relative(root, d.path)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`])); const deps = gulp.src(dependenciesSrc, { base: '.', dot: true }) .pipe(filter(['**', '!**/package-lock.json', '!**/yarn.lock', '!**/*.js.map'])) - .pipe(util.cleanNodeModules(path.join(__dirname, '.nativeignore'))) .pipe(util.cleanNodeModules(path.join(__dirname, '.moduleignore'))) .pipe(jsFilter) .pipe(util.rewriteSourceMappingURL(sourceMappingURLBase)) @@ -326,7 +325,8 @@ const BUILD_TARGETS = [ { platform: 'win32', arch: 'ia32' }, { platform: 'win32', arch: 'x64' }, { platform: 'win32', arch: 'arm64' }, - { platform: 'darwin', arch: null, opts: { stats: true } }, + { platform: 'darwin', arch: 'x64', opts: { stats: true } }, + { platform: 'darwin', arch: 'arm64', opts: { stats: true } }, { platform: 'linux', arch: 'ia32' }, { platform: 'linux', arch: 'x64' }, { platform: 'linux', arch: 'armhf' }, @@ -338,7 +338,7 @@ BUILD_TARGETS.forEach(buildTarget => { const arch = buildTarget.arch; const opts = buildTarget.opts; - ['', 'min'].forEach(minified => { + const [vscode, vscodeMin] = ['', 'min'].map(minified => { const sourceFolderName = `out-vscode${dashed(minified)}`; const destinationFolderName = `VSCode${dashed(platform)}${dashed(arch)}`; @@ -355,7 +355,14 @@ BUILD_TARGETS.forEach(buildTarget => { vscodeTaskCI )); gulp.task(vscodeTask); + + return vscodeTask; }); + + if (process.platform === platform && process.arch === arch) { + gulp.task(task.define('vscode', task.series(vscode))); + gulp.task(task.define('vscode-min', task.series(vscodeMin))); + } }); // Transifex Localizations @@ -456,8 +463,10 @@ const generateVSCodeConfigurationTask = task.define('generate-vscode-configurati 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(buildDir, `VSCode-darwin/${appName}/Contents/Resources/app/bin/code`); + const appPath = path.join(appRoot, appName, 'Contents', 'Resources', 'app', 'bin', 'code'); const codeProc = cp.exec( `${appPath} --export-default-configuration='${allConfigDetailsPath}' --wait --user-data-dir='${userDataDir}' --extensions-dir='${extensionsDir}'`, (err, stdout, stderr) => { diff --git a/build/gulpfile.vscode.linux.js b/build/gulpfile.vscode.linux.js index 1d8a09e4f..b6732b7fa 100644 --- a/build/gulpfile.vscode.linux.js +++ b/build/gulpfile.vscode.linux.js @@ -98,6 +98,7 @@ function prepareDebPackage(arch) { .pipe(replace('@@ARCHITECTURE@@', debArch)) .pipe(replace('@@QUALITY@@', product.quality || '@@QUALITY@@')) .pipe(replace('@@UPDATEURL@@', product.updateUrl || '@@UPDATEURL@@')) + .pipe(replace('@@REPOSITORY_NAME@@', arch === 'x64' ? 'vscode' : 'code')) .pipe(rename('DEBIAN/postinst')); const all = es.merge(control, postinst, postrm, prerm, desktops, appdata, workspaceMime, icon, bash_completion, zsh_completion, code); @@ -237,6 +238,8 @@ function prepareSnapPackage(arch) { const snapcraft = gulp.src('resources/linux/snap/snapcraft.yaml', { base: '.' }) .pipe(replace('@@NAME@@', product.applicationName)) .pipe(replace('@@VERSION@@', commit.substr(0, 8))) + // Possible run-on values https://snapcraft.io/docs/architectures + .pipe(replace('@@ARCHITECTURE@@', arch === 'x64' ? 'amd64' : arch)) .pipe(rename('snap/snapcraft.yaml')); const electronLaunch = gulp.src('resources/linux/snap/electron-launch', { base: '.' }) diff --git a/build/hygiene.js b/build/hygiene.js index b95b4ee98..ee6598912 100644 --- a/build/hygiene.js +++ b/build/hygiene.js @@ -83,7 +83,7 @@ const indentationFilter = [ '!src/vs/*/**/*.d.ts', '!src/typings/**/*.d.ts', '!extensions/**/*.d.ts', - '!**/*.{svg,exe,png,bmp,scpt,bat,cmd,cur,ttf,woff,eot,md,ps1,template,yaml,yml,d.ts.recipe,ico,icns,plist}', + '!**/*.{svg,exe,png,bmp,jpg,scpt,bat,cmd,cur,ttf,woff,eot,md,ps1,template,yaml,yml,d.ts.recipe,ico,icns,plist}', '!build/{lib,download,darwin}/**/*.js', '!build/**/*.sh', '!build/azure-pipelines/**/*.js', @@ -119,6 +119,7 @@ const copyrightFilter = [ '!resources/win32/bin/code.js', '!resources/web/code-web.js', '!resources/completions/**', + '!extensions/configuration-editing/build/inline-allOf.ts', '!extensions/markdown-language-features/media/highlight.css', '!extensions/html-language-features/server/src/modes/typescript/*', '!extensions/*/server/bin/*', diff --git a/build/lib/eslint/code-no-unexternalized-strings.ts b/build/lib/eslint/code-no-unexternalized-strings.ts index 29db884cd..8d9e142bb 100644 --- a/build/lib/eslint/code-no-unexternalized-strings.ts +++ b/build/lib/eslint/code-no-unexternalized-strings.ts @@ -47,7 +47,7 @@ export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { // extract key so that it can be checked later let key: string | undefined; if (isStringLiteral(keyNode)) { - doubleQuotedStringLiterals.delete(keyNode); //todo@joh reconsider + doubleQuotedStringLiterals.delete(keyNode); key = keyNode.value; } else if (keyNode.type === AST_NODE_TYPES.ObjectExpression) { @@ -55,7 +55,7 @@ export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { if (property.type === AST_NODE_TYPES.Property && !property.computed) { if (property.key.type === AST_NODE_TYPES.Identifier && property.key.name === 'key') { if (isStringLiteral(property.value)) { - doubleQuotedStringLiterals.delete(property.value); //todo@joh reconsider + doubleQuotedStringLiterals.delete(property.value); key = property.value.value; break; } @@ -123,4 +123,3 @@ export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { }; } }; - diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 47f0155d8..9ec898986 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -62,6 +62,10 @@ "name": "vs/workbench/contrib/debug", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/dialogs", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/emmet", "project": "vscode-workbench" @@ -365,6 +369,14 @@ { "name": "vs/workbench/services/authentication", "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/extensionRecommendations", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/gettingStarted", + "project": "vscode-workbench" } ] } diff --git a/build/lib/reporter.ts b/build/lib/reporter.ts index ec9088175..f875fce90 100644 --- a/build/lib/reporter.ts +++ b/build/lib/reporter.ts @@ -12,72 +12,89 @@ import * as ansiColors from 'ansi-colors'; import * as fs from 'fs'; import * as path from 'path'; -const allErrors: string[][] = []; -let startTime: number | null = null; -let count = 0; +class ErrorLog { + constructor(public id: string) { + } + allErrors: string[][] = []; + startTime: number | null = null; + count = 0; -function onStart(): void { - if (count++ > 0) { - return; + onStart(): void { + if (this.count++ > 0) { + return; + } + + this.startTime = new Date().getTime(); + fancyLog(`Starting ${ansiColors.green('compilation')}${this.id ? ansiColors.blue(` ${this.id}`) : ''}...`); } - startTime = new Date().getTime(); - fancyLog(`Starting ${ansiColors.green('compilation')}...`); -} + onEnd(): void { + if (--this.count > 0) { + return; + } -function onEnd(): void { - if (--count > 0) { - return; + this.log(); + } + + log(): void { + const errors = _.flatten(this.allErrors); + const seen = new Set(); + + errors.map(err => { + if (!seen.has(err)) { + seen.add(err); + fancyLog(`${ansiColors.red('Error')}: ${err}`); + } + }); + + fancyLog(`Finished ${ansiColors.green('compilation')}${this.id ? ansiColors.blue(` ${this.id}`) : ''} with ${errors.length} errors after ${ansiColors.magenta((new Date().getTime() - this.startTime!) + ' ms')}`); + + const regex = /^([^(]+)\((\d+),(\d+)\): (.*)$/s; + const messages = errors + .map(err => regex.exec(err)) + .filter(match => !!match) + .map(x => x as string[]) + .map(([, path, line, column, message]) => ({ path, line: parseInt(line), column: parseInt(column), message })); + + try { + const logFileName = 'log' + (this.id ? `_${this.id}` : ''); + fs.writeFileSync(path.join(buildLogFolder, logFileName), JSON.stringify(messages)); + } catch (err) { + //noop + } } - log(); } -const buildLogPath = path.join(path.dirname(path.dirname(__dirname)), '.build', 'log'); +const errorLogsById = new Map(); +function getErrorLog(id: string = '') { + let errorLog = errorLogsById.get(id); + if (!errorLog) { + errorLog = new ErrorLog(id); + errorLogsById.set(id, errorLog); + } + return errorLog; +} + +const buildLogFolder = path.join(path.dirname(path.dirname(__dirname)), '.build'); try { - fs.mkdirSync(path.dirname(buildLogPath)); + fs.mkdirSync(buildLogFolder); } catch (err) { // ignore } -function log(): void { - const errors = _.flatten(allErrors); - const seen = new Set(); - - errors.map(err => { - if (!seen.has(err)) { - seen.add(err); - fancyLog(`${ansiColors.red('Error')}: ${err}`); - } - }); - - const regex = /^([^(]+)\((\d+),(\d+)\): (.*)$/; - const messages = errors - .map(err => regex.exec(err)) - .filter(match => !!match) - .map(x => x as string[]) - .map(([, path, line, column, message]) => ({ path, line: parseInt(line), column: parseInt(column), message })); - - try { - - fs.writeFileSync(buildLogPath, JSON.stringify(messages)); - } catch (err) { - //noop - } - - fancyLog(`Finished ${ansiColors.green('compilation')} with ${errors.length} errors after ${ansiColors.magenta((new Date().getTime() - startTime!) + ' ms')}`); -} - export interface IReporter { (err: string): void; hasErrors(): boolean; end(emitError: boolean): NodeJS.ReadWriteStream; } -export function createReporter(): IReporter { +export function createReporter(id?: string): IReporter { + const errorLog = getErrorLog(id); + const errors: string[] = []; - allErrors.push(errors); + errorLog.allErrors.push(errors); const result = (err: string) => errors.push(err); @@ -85,14 +102,14 @@ export function createReporter(): IReporter { result.end = (emitError: boolean): NodeJS.ReadWriteStream => { errors.length = 0; - onStart(); + errorLog.onStart(); return es.through(undefined, function () { - onEnd(); + errorLog.onEnd(); if (emitError && errors.length > 0) { if (!(errors as any).__logged__) { - log(); + errorLog.log(); } (errors as any).__logged__ = true; diff --git a/build/npm/preinstall.js b/build/npm/preinstall.js index cb88d37ad..94dbb3b9e 100644 --- a/build/npm/preinstall.js +++ b/build/npm/preinstall.js @@ -7,8 +7,8 @@ let err = false; const majorNodeVersion = parseInt(/^(\d+)\./.exec(process.versions.node)[1]); -if (majorNodeVersion < 10 || majorNodeVersion >= 13) { - console.error('\033[1;31m*** Please use node >=10 and <=12.\033[0;0m'); +if (majorNodeVersion < 10 || majorNodeVersion >= 16) { + console.error('\033[1;31m*** Please use node >=10 and <=16.\033[0;0m'); err = true; } diff --git a/build/package.json b/build/package.json index d7e68e7ce..20fc702f9 100644 --- a/build/package.json +++ b/build/package.json @@ -48,7 +48,7 @@ "minimist": "^1.2.3", "request": "^2.85.0", "terser": "4.3.8", - "typescript": "^4.1.0-dev.20201018", + "typescript": "^4.2.0-dev.20201119", "vsce": "1.48.0", "vscode-telemetry-extractor": "^1.6.0", "xml2js": "^0.4.17" @@ -60,6 +60,6 @@ "npmCheckJs": "tsc --noEmit" }, "dependencies": { - "@azure/cosmos": "^3.4.0" + "@azure/cosmos": "^3.9.3" } } diff --git a/build/win32/code.iss b/build/win32/code.iss index 7f4b36e71..50dcf76b8 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -108,6 +108,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ascx\OpenWithProgids Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ascx"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,ASCX}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ascx"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ascx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\xml.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ascx\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ascx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.asp\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -115,6 +116,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.asp\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.asp"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,ASP}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.asp"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.asp\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\html.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.asp\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.asp\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.aspx\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -122,6 +124,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.aspx\OpenWithProgids Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.aspx"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,ASPX}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.aspx"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.aspx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\html.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.aspx\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.aspx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bash\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -129,6 +132,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bash\OpenWithProgids Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Bash}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\shell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bash_login\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -136,6 +140,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bash_login\OpenWithP Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_login"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Bash Login}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_login"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_login\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\shell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_login\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_login\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bash_logout\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -143,6 +148,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bash_logout\OpenWith Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_logout"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Bash Logout}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_logout"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_logout\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\shell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_logout\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_logout\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bash_profile\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -150,6 +156,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bash_profile\OpenWit Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_profile"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Bash Profile}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_profile"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_profile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\shell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_profile\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_profile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bashrc\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -157,6 +164,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bashrc\OpenWithProgi Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bashrc"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Bash RC}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bashrc"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bashrc\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\shell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bashrc\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bashrc\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bib\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -164,6 +172,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bib\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bib"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,BibTeX}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bib"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bib\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bib\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bib\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bowerrc\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -171,6 +180,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bowerrc\OpenWithProg Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bowerrc"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Bower RC}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bowerrc"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bowerrc\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\bower.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bowerrc\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bowerrc\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.c++\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -185,6 +195,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.c\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.c"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.c"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.c\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\c.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.c\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.c\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cc\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -192,6 +203,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cc\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cc"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C++}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cc"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cc\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\cpp.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cc\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cc\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cjs\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -199,6 +211,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cjs\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cjs"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,JavaScript}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cjs"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cjs\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\javascript.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cjs\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cjs\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.clj\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -206,6 +219,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.clj\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.clj"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Clojure}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.clj"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.clj\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.clj\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.clj\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cljs\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -213,6 +227,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cljs\OpenWithProgids Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cljs"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,ClojureScript}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cljs"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cljs\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cljs\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cljs\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cljx\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -220,6 +235,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cljx\OpenWithProgids Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cljx"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,CLJX}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cljx"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cljx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cljx\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cljx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.clojure\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -227,6 +243,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.clojure\OpenWithProg Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.clojure"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Clojure}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.clojure"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.clojure\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.clojure\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.clojure\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.code-workspace\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -234,6 +251,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.code-workspace\OpenW Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.code-workspace"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Code Workspace}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.code"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.code-workspace\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.code-workspace\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.code-workspace\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.coffee\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -241,6 +259,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.coffee\OpenWithProgi Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.coffee"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,CoffeeScript}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.coffee"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.coffee\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.coffee\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.coffee\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.config\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -248,6 +267,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.config\OpenWithProgi Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.config"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Configuration}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.config"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.config\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\config.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.config\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.config\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.containerfile\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -262,6 +282,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cpp\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cpp"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C++}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cpp"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cpp\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\cpp.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cpp\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cpp\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cs\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -269,6 +290,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cs\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cs"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C#}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cs"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cs\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\csharp.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cs\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cs\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cshtml\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -276,6 +298,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cshtml\OpenWithProgi Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cshtml"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,CSHTML}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cshtml"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cshtml\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\html.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cshtml\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cshtml\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.csproj\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -283,6 +306,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.csproj\OpenWithProgi Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csproj"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C# Project}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csproj"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csproj\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\xml.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csproj\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csproj\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.css\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -290,6 +314,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.css\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.css"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,CSS}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.css"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.css\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\css.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.css\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.css\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.csx\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -297,6 +322,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.csx\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csx"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C# Script}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csx"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\csharp.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csx\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ctp\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -304,6 +330,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ctp\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ctp"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,CakePHP Template}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ctp"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ctp\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ctp\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ctp\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cxx\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -311,6 +338,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cxx\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cxx"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C++}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cxx"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cxx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\cpp.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cxx\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cxx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.dockerfile\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -318,6 +346,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.dockerfile\OpenWithP Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dockerfile"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Dockerfile}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dockerfile"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dockerfile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dockerfile\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dockerfile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.dot\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -325,6 +354,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.dot\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dot"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Dot}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dot"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dot\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dot\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dot\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.dtd\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -332,6 +362,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.dtd\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dtd"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Document Type Definition}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dtd"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dtd\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\xml.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dtd\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dtd\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.editorconfig\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -339,6 +370,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.editorconfig\OpenWit Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.editorconfig"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Editor Config}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.editorconfig"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.editorconfig\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\config.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.editorconfig\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.editorconfig\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.edn\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -346,6 +378,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.edn\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.edn"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Extensible Data Notation}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.edn"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.edn\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.edn\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.edn\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.eyaml\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -353,6 +386,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.eyaml\OpenWithProgid Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.eyaml"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Hiera Eyaml}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.eyaml"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.eyaml\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\yaml.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.eyaml\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.eyaml\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.eyml\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -360,6 +394,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.eyml\OpenWithProgids Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.eyml"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Hiera Eyaml}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.eyml"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.eyml\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\yaml.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.eyml\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.eyml\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.fs\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -367,6 +402,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.fs\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fs"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,F#}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fs"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fs\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fs\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fs\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.fsi\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -374,6 +410,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.fsi\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsi"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,F# Signature}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsi"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsi\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsi\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsi\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.fsscript\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -381,6 +418,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.fsscript\OpenWithPro Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsscript"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,F# Script}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsscript"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsscript\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsscript\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsscript\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.fsx\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -388,6 +426,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.fsx\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsx"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,F# Script}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsx"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsx\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.gemspec\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -395,6 +434,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.gemspec\OpenWithProg Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gemspec"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Gemspec}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gemspec"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gemspec\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\ruby.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gemspec\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gemspec\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.gitattributes\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -403,6 +443,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitat Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitattributes"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitattributes"; ValueType: string; ValueName: "AlwaysShowExt"; ValueData: ""; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitattributes\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\config.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitattributes\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitattributes\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.gitconfig\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -411,6 +452,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitco Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitconfig"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitconfig"; ValueType: string; ValueName: "AlwaysShowExt"; ValueData: ""; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitconfig\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\config.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitconfig\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitconfig\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.gitignore\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -419,6 +461,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitig Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitignore"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitignore"; ValueType: string; ValueName: "AlwaysShowExt"; ValueData: ""; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitignore\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\config.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitignore\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitignore\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.go\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -426,6 +469,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.go\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.go"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Go}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.go"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.go\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\go.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.go\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.go\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.h\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -433,6 +477,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.h\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.h"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C Header}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.h"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.h\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\c.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.h\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.h\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.handlebars\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -440,6 +485,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.handlebars\OpenWithP Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.handlebars"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Handlebars}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.handlebars"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.handlebars\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.handlebars\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.handlebars\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.hbs\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -447,6 +493,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.hbs\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hbs"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Handlebars}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hbs"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hbs\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hbs\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hbs\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.h++\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -461,6 +508,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.hh\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hh"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C++ Header}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hh"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hh\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\cpp.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hh\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hh\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.hpp\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -468,6 +516,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.hpp\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hpp"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C++ Header}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hpp"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hpp\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\cpp.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hpp\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hpp\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.htm\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -475,6 +524,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.htm\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.htm"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,HTML}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.htm"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.htm\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\html.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.htm\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.htm\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.html\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -482,6 +532,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.html\OpenWithProgids Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.html"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,HTML}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.html"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.html\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\html.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.html\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.html\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.hxx\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -489,6 +540,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.hxx\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hxx"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C++ Header}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hxx"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hxx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\cpp.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hxx\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hxx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ini\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -496,6 +548,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ini\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ini"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,INI}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ini"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ini\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\config.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ini\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ini\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jade\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -503,6 +556,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jade\OpenWithProgids Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jade"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Jade}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jade"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jade\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\jade.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jade\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jade\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jav\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -510,6 +564,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jav\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jav"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Java}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jav"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jav\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\java.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jav\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jav\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.java\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -517,6 +572,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.java\OpenWithProgids Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.java"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Java}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.java"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.java\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\java.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.java\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.java\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.js\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -524,6 +580,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.js\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.js"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,JavaScript}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.js"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.js\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\javascript.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.js\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.js\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jsx\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -531,6 +588,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jsx\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jsx"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,JavaScript}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jsx"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jsx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\react.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jsx\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jsx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jscsrc\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -538,6 +596,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jscsrc\OpenWithProgi Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jscsrc"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,JSCS RC}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jscsrc"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jscsrc\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\javascript.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jscsrc\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jscsrc\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jshintrc\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -545,6 +604,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jshintrc\OpenWithPro Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jshintrc"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,JSHint RC}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jshintrc"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jshintrc\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\javascript.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jshintrc\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jshintrc\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jshtm\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -552,6 +612,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jshtm\OpenWithProgid Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jshtm"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,JavaScript HTML Template}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jshtm"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jshtm\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\html.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jshtm\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jshtm\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.json\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -559,6 +620,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.json\OpenWithProgids Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.json"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,JSON}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.json"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.json\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\json.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.json\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.json\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jsp\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -566,6 +628,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jsp\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jsp"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Java Server Pages}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jsp"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jsp\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\html.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jsp\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jsp\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.less\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -573,6 +636,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.less\OpenWithProgids Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.less"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,LESS}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.less"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.less\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\less.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.less\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.less\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.lua\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -580,6 +644,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.lua\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.lua"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Lua}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.lua"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.lua\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.lua\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.lua\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.m\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -587,6 +652,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.m\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.m"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Objective C}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.m"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.m\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.m\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.m\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.makefile\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -594,6 +660,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.makefile\OpenWithPro Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.makefile"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Makefile}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.makefile"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.makefile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.makefile\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.makefile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.markdown\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -601,6 +668,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.markdown\OpenWithPro Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.markdown"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Markdown}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.markdown"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.markdown\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\markdown.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.markdown\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.markdown\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.md\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -608,6 +676,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.md\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.md"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Markdown}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.md"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.md\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\markdown.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.md\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.md\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mdoc\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -615,6 +684,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mdoc\OpenWithProgids Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdoc"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,MDoc}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdoc"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdoc\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\markdown.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdoc\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdoc\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mdown\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -622,6 +692,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mdown\OpenWithProgid Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdown"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Markdown}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdown"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdown\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\markdown.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdown\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdown\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mdtext\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -629,6 +700,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mdtext\OpenWithProgi Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdtext"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Markdown}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdtext"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdtext\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\markdown.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdtext\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdtext\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mdtxt\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -636,6 +708,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mdtxt\OpenWithProgid Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdtxt"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Markdown}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdtxt"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdtxt\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\markdown.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdtxt\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdtxt\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mdwn\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -643,6 +716,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mdwn\OpenWithProgids Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdwn"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Markdown}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdwn"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdwn\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\markdown.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdwn\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdwn\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mkd\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -650,6 +724,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mkd\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mkd"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Markdown}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mkd"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mkd\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\markdown.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mkd\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mkd\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mkdn\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -657,6 +732,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mkdn\OpenWithProgids Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mkdn"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Markdown}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mkdn"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mkdn\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\markdown.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mkdn\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mkdn\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ml\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -664,6 +740,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ml\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ml"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,OCaml}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ml"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ml\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ml\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ml\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mli\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -671,6 +748,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mli\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mli"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,OCaml}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mli"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mli\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mli\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mli\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mjs\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -678,6 +756,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mjs\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mjs"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,JavaScript}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mjs"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mjs\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\javascript.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mjs\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mjs\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.npmignore\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -686,6 +765,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.npmig Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.npmignore"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.npmignore"; ValueType: string; ValueName: "AlwaysShowExt"; ValueData: ""; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.npmignore\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.npmignore\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.npmignore\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.php\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -693,6 +773,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.php\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.php"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,PHP}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.php"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.php\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\php.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.php\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.php\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.phtml\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -700,6 +781,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.phtml\OpenWithProgid Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.phtml"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,PHP HTML}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.phtml"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.phtml\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\html.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.phtml\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.phtml\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pl\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -707,6 +789,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pl\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pl"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Perl}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pl"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pl\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pl\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pl\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pl6\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -714,6 +797,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pl6\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pl6"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Perl 6}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pl6"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pl6\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pl6\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pl6\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pm\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -721,6 +805,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pm\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pm"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Perl Module}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pm"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pm\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pm\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pm\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pm6\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -728,6 +813,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pm6\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pm6"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Perl 6 Module}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pm6"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pm6\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pm6\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pm6\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pod\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -735,6 +821,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pod\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pod"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Perl POD}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pod"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pod\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pod\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pod\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pp\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -742,6 +829,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pp\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pp"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Perl}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pp"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pp\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pp\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pp\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.profile\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -749,6 +837,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.profile\OpenWithProg Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.profile"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Profile}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.profile"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.profile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\shell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.profile\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.profile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.properties\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -756,6 +845,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.properties\OpenWithP Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.properties"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Properties}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.properties"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.properties\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.properties\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.properties\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ps1\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -763,6 +853,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ps1\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ps1"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,PowerShell}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ps1"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ps1\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\powershell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ps1\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ps1\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.psd1\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -770,6 +861,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.psd1\OpenWithProgids Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psd1"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,PowerShell Module Manifest}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psd1"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psd1\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\powershell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psd1\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psd1\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.psgi\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -777,6 +869,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.psgi\OpenWithProgids Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psgi"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Perl CGI}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psgi"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psgi\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psgi\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psgi\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.psm1\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -784,6 +877,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.psm1\OpenWithProgids Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psm1"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,PowerShell Module}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psm1"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psm1\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\powershell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psm1\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psm1\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.py\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -791,6 +885,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.py\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.py"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Python}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.py"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.py\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\python.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.py\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.py\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.r\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -798,6 +893,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.r\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.r"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,R}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.r"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.r\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.r\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.r\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.rb\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -805,6 +901,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.rb\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rb"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Ruby}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rb"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rb\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\ruby.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rb\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rb\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.rhistory\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -812,6 +909,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.rhistory\OpenWithPro Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rhistory"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,R History}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rhistory"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rhistory\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\shell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rhistory\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rhistory\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.rprofile\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -819,6 +917,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.rprofile\OpenWithPro Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rprofile"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,R Profile}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rprofile"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rprofile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\shell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rprofile\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rprofile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.rs\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -826,6 +925,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.rs\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rs"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Rust}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rs"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rs\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rs\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rs\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.rt\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -833,6 +933,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.rt\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rt"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Rich Text}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rt"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rt\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rt\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rt\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.scss\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -840,6 +941,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.scss\OpenWithProgids Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.scss"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Sass}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.scss"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.scss\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\sass.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.scss\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.scss\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.sh\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -847,6 +949,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.sh\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sh"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,SH}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sh"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sh\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\shell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sh\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sh\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.shtml\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -854,6 +957,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.shtml\OpenWithProgid Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.shtml"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,SHTML}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.shtml"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.shtml\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\html.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.shtml\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.shtml\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.sql\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -861,6 +965,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.sql\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sql"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,SQL}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sql"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sql\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\sql.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sql\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sql\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.svg\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -868,6 +973,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.svg\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svg"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,SVG}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svg"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svg\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svg\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svg\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.svgz\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -875,6 +981,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.svgz\OpenWithProgids Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svgz"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,SVGZ}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svgz"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svgz\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svgz\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svgz\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.t\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -882,6 +989,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.t\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.t"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Perl}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.t"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.t\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.t\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.t\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.tex\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -889,6 +997,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.tex\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.tex"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,LaTeX}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.tex"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.tex\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.tex\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.tex\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ts\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -896,6 +1005,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ts\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ts"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,TypeScript}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ts"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ts\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\typescript.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ts\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ts\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.tsx\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -903,6 +1013,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.tsx\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.tsx"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,TypeScript}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.tsx"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.tsx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\react.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.tsx\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.tsx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.txt\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -910,6 +1021,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.txt\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.txt"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Text}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.txt"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.txt\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.txt\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.txt\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.vb\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -917,6 +1029,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.vb\OpenWithProgids"; Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.vb"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Visual Basic}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.vb"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.vb\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.vb\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.vb\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.vue\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -924,6 +1037,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.vue\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.vue"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,VUE}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.vue"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.vue\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\vue.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.vue\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.vue\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.wxi\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -931,6 +1045,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.wxi\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxi"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,WiX Include}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxi"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxi\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxi\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxi\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.wxl\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -938,6 +1053,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.wxl\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxl"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,WiX Localization}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxl"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxl\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxl\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxl\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.wxs\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -945,6 +1061,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.wxs\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxs"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,WiX}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxs"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxs\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxs\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxs\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.xaml\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -952,6 +1069,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.xaml\OpenWithProgids Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xaml"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,XAML}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xaml"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xaml\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\xml.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xaml\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xaml\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.xml\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -959,6 +1077,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.xml\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xml"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,XML}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xml"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xml\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\xml.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xml\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xml\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.yaml\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -966,6 +1085,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.yaml\OpenWithProgids Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.yaml"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Yaml}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.yaml"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.yaml\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\yaml.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.yaml\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.yaml\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.yml\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -973,6 +1093,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.yml\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.yml"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Yaml}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.yml"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.yml\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\yaml.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.yml\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.yml\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.zsh\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles @@ -980,14 +1101,17 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.zsh\OpenWithProgids" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.zsh"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,ZSH}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.zsh"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.zsh\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\shell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.zsh\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.zsh\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}SourceFile"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,{#NameLong}}"; Flags: uninsdeletekey Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}SourceFile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico" +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}SourceFile\shell\open"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe""" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}SourceFile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1""" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\Applications\{#ExeBasename}.exe"; ValueType: none; ValueName: ""; Flags: uninsdeletekey Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\Applications\{#ExeBasename}.exe\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico" +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\Applications\{#ExeBasename}.exe\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe""" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\Applications\{#ExeBasename}.exe\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1""" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\*\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "{cm:OpenWithCodeContextMenu,{#ShellNameShort}}"; Tasks: addcontextmenufiles; Flags: uninsdeletekey diff --git a/build/yarn.lock b/build/yarn.lock index afb6ff0b7..17c5c201d 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -2,21 +2,22 @@ # yarn lockfile v1 -"@azure/cosmos@^3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@azure/cosmos/-/cosmos-3.4.0.tgz#96f36a4522be23e1389d0516ea4d77e5fc153221" - integrity sha512-4ym+ezk7qBe4s7/tb6IJ5kmXE4xgEbAPbraT3382oeCRlYpGrblIZIDoWbthMCJfLyLBDX5T05Fhm18QeY1R/w== +"@azure/cosmos@^3.9.3": + version "3.9.3" + resolved "https://registry.yarnpkg.com/@azure/cosmos/-/cosmos-3.9.3.tgz#7e95ff92e5c3e9da7e8316bc50c9cc928be6c1d6" + integrity sha512-1mh8a6LAIykz24tJvQpafXiABUfq+HSAZBFJVZXea0Rd0qG8Ia9z8AK9FtPbC1nPvDC2RID2mRIjJvYbxRM/BA== dependencies: "@types/debug" "^4.1.4" debug "^4.1.1" fast-json-stable-stringify "^2.0.0" + jsbi "^3.1.3" node-abort-controller "^1.0.4" node-fetch "^2.6.0" - os-name "^3.1.0" priorityqueuejs "^1.0.0" semaphore "^1.0.5" - tslib "^1.9.3" - uuid "^3.3.2" + tslib "^2.0.0" + universal-user-agent "^6.0.0" + uuid "^8.3.0" "@dsherret/to-absolute-glob@^2.0.2": version "2.0.2" @@ -926,17 +927,6 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -cross-spawn@^6.0.0: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - cryptiles@2.x.x: version "2.0.5" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" @@ -1004,13 +994,20 @@ debug@2.X, debug@^2.6.8: dependencies: ms "2.0.0" -debug@4, debug@^4.1.1: +debug@4: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== dependencies: ms "^2.1.1" +debug@^4.1.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -1241,19 +1238,6 @@ event-stream@3.3.4: stream-combiner "~0.0.4" through "~2.3.1" -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - extend-shallow@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" @@ -1324,9 +1308,9 @@ fast-glob@^3.0.3: micromatch "^4.0.2" fast-json-stable-stringify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" - integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fastq@^1.6.0: version "1.6.0" @@ -1441,13 +1425,6 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-stream@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -1922,11 +1899,6 @@ is-relative@^1.0.0: dependencies: is-unc-path "^1.0.0" -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= - is-symbol@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" @@ -1978,11 +1950,6 @@ isbinaryfile@^3.0.2: dependencies: buffer-alloc "^1.2.0" -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" @@ -1993,6 +1960,11 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= +jsbi@^3.1.3: + version "3.1.4" + resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-3.1.4.tgz#9654dd02207a66a4911b4e4bb74265bc2cbc9dd0" + integrity sha512-52QRRFSsi9impURE8ZUbzAMCLjPm4THO7H2fcuIvaaeFTbSysvkodbQQXIVsNgq/ypDbq6dJiuGKL0vZ/i9hUg== + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -2216,11 +2188,6 @@ lodash@^4.15.0, lodash@^4.17.10: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== -macos-release@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f" - integrity sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA== - make-error-cause@^1.1.1: version "1.2.2" resolved "https://registry.yarnpkg.com/make-error-cause/-/make-error-cause-1.2.2.tgz#df0388fcd0b37816dff0a5fb8108939777dcbc9d" @@ -2338,7 +2305,7 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -ms@^2.1.1: +ms@2.1.2, ms@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== @@ -2366,20 +2333,15 @@ mute-stream@~0.0.4: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - node-abort-controller@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-1.0.4.tgz#4095e41d58b2fae169d2f9892904d603e11c7a39" - integrity sha512-7cNtLKTAg0LrW3ViS2C7UfIzbL3rZd8L0++5MidbKqQVJ8yrH6+1VRSHl33P0ZjBTbOJd37d9EYekvHyKkB0QQ== + version "1.1.0" + resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-1.1.0.tgz#8a734a631b022af29963be7245c1483cbb9e070d" + integrity sha512-dEYmUqjtbivotqjraOe8UvhT/poFfog1BQRNsZm/MSEDDESk2cQ1tvD8kGyuN07TM/zoW+n42odL8zTeJupYdQ== node-fetch@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" - integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== + version "2.6.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== normalize-path@^2.0.1, normalize-path@^2.1.1: version "2.1.1" @@ -2395,13 +2357,6 @@ now-and-later@^2.0.0: dependencies: once "^1.3.2" -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= - dependencies: - path-key "^2.0.0" - nth-check@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" @@ -2476,14 +2431,6 @@ os-homedir@^1.0.0: resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= -os-name@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801" - integrity sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg== - dependencies: - macos-release "^2.2.0" - windows-release "^3.1.0" - os-tmpdir@^1.0.0, os-tmpdir@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -2497,11 +2444,6 @@ osenv@^0.1.3: os-homedir "^1.0.0" os-tmpdir "^1.0.0" -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= - p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -2555,11 +2497,6 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-key@^2.0.0, path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= - path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -2662,14 +2599,6 @@ pump@^2.0.0: end-of-stream "^1.1.0" once "^1.3.1" -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - pumpify@^1.3.5: version "1.5.1" resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" @@ -2971,11 +2900,6 @@ semver@^5.1.0, semver@^5.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== -semver@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" @@ -2986,23 +2910,6 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= - dependencies: - shebang-regex "^1.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= - -signal-exit@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= - slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -3175,11 +3082,6 @@ strip-bom@2.X: dependencies: is-utf8 "^0.2.0" -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= - supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -3307,10 +3209,10 @@ tslib@^1.8.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== -tslib@^1.9.3: - version "1.10.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" - integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== +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== tsutils@^3.17.1: version "3.17.1" @@ -3349,10 +3251,10 @@ typescript@^3.0.1: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== -typescript@^4.1.0-dev.20201018: - version "4.1.0-dev.20201018" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.0-dev.20201018.tgz#1a4b8e3f9b640218a44299773371354d75bcfa34" - integrity sha512-cOFYP1I+IrMWa6ZfefxcacZha1pQMxrq8DGMBLkvrl8k3CqIdD8APq9LXaMj/PWrB8IPgDprY6jHwqiHg0/oGA== +typescript@^4.2.0-dev.20201119: + version "4.2.0-dev.20201119" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.0-dev.20201119.tgz#d4a43511cd9931adac05e1a47b6425f6b0e76cc3" + integrity sha512-HIgv+D/0VpVYRTbcVVf9oac/0GtLKMqaufTcPgohNaFWlCOh4lq8syefANgENXTG5Q4VEC6xwDGzHW6EJAVr3A== typical@^4.0.0: version "4.0.0" @@ -3395,6 +3297,11 @@ unique-stream@^2.0.2: json-stable-stringify-without-jsonify "^1.0.1" through2-filter "^3.0.0" +universal-user-agent@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" + integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -3433,9 +3340,14 @@ uuid@^3.1.0: integrity sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA== uuid@^3.3.2: - version "3.3.3" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" - integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +uuid@^8.3.0: + version "8.3.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31" + integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg== validator@~3.35.0: version "3.35.0" @@ -3589,20 +3501,6 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@^1.2.9: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -windows-release@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.2.0.tgz#8122dad5afc303d833422380680a79cdfa91785f" - integrity sha512-QTlz2hKLrdqukrsapKsINzqMgOUpQW268eJ0OaOpJN32h272waxR9fkB9VoWRtK7uKHG5EHJcTXQBD8XZVJkFA== - dependencies: - execa "^1.0.0" - wordwrap@~0.0.2: version "0.0.3" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" diff --git a/cglicenses.json b/cglicenses.json index 56f92bef6..a0131e4f9 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -389,5 +389,35 @@ "", "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: The license at https://github.com/jquery/esprima/blob/master/LICENSE.BSD + // cannot be found by the OSS tool automatically. + "name": "esprima", + "fullLicenseText": [ + "BSD 2-Clause \"Simplified\" License", + "Copyright JS Foundation and other contributors, https://js.foundation/", + "", + "Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:", + " * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.", + " * 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 COPYRIGHT HOLDERS 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 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." + ] + }, + { + // Reason: The license at https://github.com/LinusU/load-yaml-file/blob/master/readme.md + // cannot be found by the OSS tool automatically. + "name": "load-yaml-file", + "fullLicenseText": [ + "MIT License", + "Copyright (C) 2012-2018 by various contributors (see AUTHORS)", + "", + "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." + ] } -] \ No newline at end of file +] diff --git a/cgmanifest.json b/cgmanifest.json index bd95a0d80..656e3f821 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "0b5f24002b4f18adee112ed39fe269aa51f6705c" + "commitHash": "415c1f9e9b35d9599b1a8ad1200476afa47a3323" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "9.3.3" + "version": "9.3.5" }, { "component": { diff --git a/extensions/configuration-editing/.vscodeignore b/extensions/configuration-editing/.vscodeignore index de8e6dc59..c5234c346 100644 --- a/extensions/configuration-editing/.vscodeignore +++ b/extensions/configuration-editing/.vscodeignore @@ -5,3 +5,5 @@ out/** extension.webpack.config.js extension-browser.webpack.config.js yarn.lock +build/** +schemas/*.schema.src.json diff --git a/extensions/configuration-editing/build/inline-allOf.ts b/extensions/configuration-editing/build/inline-allOf.ts new file mode 100755 index 000000000..7da64fddd --- /dev/null +++ b/extensions/configuration-editing/build/inline-allOf.ts @@ -0,0 +1,103 @@ +#!/usr/bin/env ts-node + +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// Inlines "allOf"s to allow for "additionalProperties": false. (https://github.com/microsoft/vscode-remote-release/issues/2967) +// Run this manually after updating devContainer.schema.src.json. + +import * as fs from 'fs'; + +function transform(schema: any) { + + const definitions = Object.keys(schema.definitions) + .reduce((d, k) => { + d[`#/definitions/${k}`] = (schema.definitions as any)[k]; + return d; + }, {} as any); + + function copy(from: any) { + const type = Array.isArray(from) ? 'array' : typeof from; + switch (type) { + case 'object': { + const to: any = {}; + for (const key in from) { + switch (key) { + case 'definitions': + break; + case 'oneOf': + const list = copy(from[key]) + .reduce((a: any[], o: any) => { + if (o.oneOf) { + a.push(...o.oneOf); + } else { + a.push(o); + } + return a; + }, [] as any[]); + if (list.length === 1) { + Object.assign(to, list[0]); + } else { + to.oneOf = list; + } + break; + case 'allOf': + const all = copy(from[key]); + const leaves = all.map((one: any) => (one.oneOf ? one.oneOf : [one])); + function cross(res: any, leaves: any[][]): any[] { + if (leaves.length) { + const rest = leaves.slice(1); + return ([] as any[]).concat(...leaves[0].map(leaf => { + const intermediate = { ...res, ...leaf }; + if ('properties' in res && 'properties' in leaf) { + intermediate.properties = { + ...res.properties, + ...leaf.properties, + }; + } + return cross(intermediate, rest); + })); + } + return [res]; + } + const list2 = cross({}, leaves); + if (list2.length === 1) { + Object.assign(to, list2[0]); + } else { + to.oneOf = list2; + } + break; + case '$ref': + const ref = from[key]; + const definition = definitions[ref]; + if (definition) { + Object.assign(to, copy(definition)); + } else { + to[key] = ref; + } + break; + default: + to[key] = copy(from[key]); + break; + } + } + if (to.type === 'object' && !('additionalProperties' in to)) { + to.additionalProperties = false; + } + return to; + } + case 'array': { + return from.map(copy); + } + default: + return from; + } + } + + return copy(schema); +} + +const devContainer = JSON.parse(fs.readFileSync('../schemas/devContainer.schema.src.json', 'utf8')); +fs.writeFileSync('../schemas/devContainer.schema.generated.json', JSON.stringify(transform(devContainer), undefined, ' ')); diff --git a/extensions/configuration-editing/build/tsconfig.json b/extensions/configuration-editing/build/tsconfig.json new file mode 100644 index 000000000..0ac2f0ded --- /dev/null +++ b/extensions/configuration-editing/build/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../shared.tsconfig.json", + "compilerOptions": { + "resolveJsonModule": true, + "outDir": "./out" + } +} diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index 3afe9fa43..423400413 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -111,11 +111,11 @@ }, { "fileMatch": "/.devcontainer/devcontainer.json", - "url": "./schemas/devContainer.schema.json" + "url": "./schemas/devContainer.schema.generated.json" }, { "fileMatch": "/.devcontainer.json", - "url": "./schemas/devContainer.schema.json" + "url": "./schemas/devContainer.schema.generated.json" }, { "fileMatch": "%APP_SETTINGS_HOME%/globalStorage/ms-vscode-remote.remote-containers/nameConfigs/*.json", diff --git a/extensions/configuration-editing/schemas/attachContainer.schema.json b/extensions/configuration-editing/schemas/attachContainer.schema.json index b408c46f4..b3b865333 100644 --- a/extensions/configuration-editing/schemas/attachContainer.schema.json +++ b/extensions/configuration-editing/schemas/attachContainer.schema.json @@ -31,7 +31,7 @@ "null" ] }, - "description": "Remote environment variables." + "description": "Remote environment variables. If these are used in the Integrated Terminal, make sure the 'Terminal > Integrated: Inherit Env' setting is enabled." }, "remoteUser": { "type": "string", diff --git a/extensions/configuration-editing/schemas/devContainer.schema.generated.json b/extensions/configuration-editing/schemas/devContainer.schema.generated.json new file mode 100644 index 000000000..8cdf40090 --- /dev/null +++ b/extensions/configuration-editing/schemas/devContainer.schema.generated.json @@ -0,0 +1,832 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Defines a dev container", + "allowComments": true, + "allowTrailingCommas": true, + "oneOf": [ + { + "type": "object", + "properties": { + "build": { + "type": "object", + "description": "Docker build-related options.", + "properties": { + "dockerfile": { + "type": "string", + "description": "The location of the Dockerfile that defines the contents of the container. The path is relative to the folder containing the `devcontainer.json` file." + }, + "context": { + "type": "string", + "description": "The location of the context folder for building the Docker image. The path is relative to the folder containing the `devcontainer.json` file." + }, + "target": { + "type": "string", + "description": "Target stage in a multi-stage build." + }, + "args": { + "type": "object", + "additionalProperties": { + "type": [ + "string" + ] + }, + "description": "Build arguments." + } + }, + "required": [ + "dockerfile" + ], + "additionalProperties": false + }, + "appPort": { + "type": [ + "integer", + "string", + "array" + ], + "description": "Application ports that are exposed by the container. This can be a single port or an array of ports. Each port can be a number or a string. A number is mapped to the same port on the host. A string is passed to Docker unchanged and can be used to map ports differently, e.g. \"8000:8010\".", + "items": { + "type": [ + "integer", + "string" + ] + } + }, + "containerEnv": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Container environment variables." + }, + "containerUser": { + "type": "string", + "description": "The user the container will be started with. The default is the user on the Docker image." + }, + "updateRemoteUserUID": { + "type": "boolean", + "description": "Controls whether on Linux the container's user should be updated with the local user's UID and GID. On by default." + }, + "mounts": { + "type": "array", + "description": "Mount points to set up when creating the container. See Docker's documentation for the --mount option for the supported syntax.", + "items": { + "type": "string" + } + }, + "runArgs": { + "type": "array", + "description": "The arguments required when starting in the container.", + "items": { + "type": "string" + } + }, + "shutdownAction": { + "type": "string", + "enum": [ + "none", + "stopContainer" + ], + "description": "Action to take when the VS Code window is closed. The default is to stop the container." + }, + "overrideCommand": { + "type": "boolean", + "description": "Whether to overwrite the command specified in the image. The default is true." + }, + "workspaceFolder": { + "type": "string", + "description": "The path of the workspace folder inside the container." + }, + "workspaceMount": { + "type": "string", + "description": "The --mount parameter for docker run. The default is to mount the project folder at /workspaces/$project." + }, + "name": { + "type": "string", + "description": "A name to show for the workspace folder." + }, + "extensions": { + "type": "array", + "description": "An array of extensions that should be installed into the container.", + "items": { + "type": "string", + "pattern": "^([a-z0-9A-Z][a-z0-9\\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\\-A-Z]*)(@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?$", + "errorMessage": "Expected format: '${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'." + } + }, + "settings": { + "$ref": "vscode://schemas/settings/machine", + "description": "Machine specific settings that should be copied into the container. These are only copied when connecting to the container for the first time, rebuilding the container then triggers it again." + }, + "forwardPorts": { + "type": "array", + "description": "Ports that are forwarded from the container to the local machine.", + "items": { + "type": "integer", + "maximum": 65535, + "minimum": 0 + } + }, + "remoteEnv": { + "type": "object", + "additionalProperties": { + "type": [ + "string", + "null" + ] + }, + "description": "Remote environment variables. If these are used in the Integrated Terminal, make sure the 'Terminal > Integrated: Inherit Env' setting is enabled." + }, + "remoteUser": { + "type": "string", + "description": "The user VS Code Server will be started with. The default is the same user as the container." + }, + "initializeCommand": { + "type": [ + "string", + "array" + ], + "description": "A command to run locally before anything else. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, + "postCreateCommand": { + "type": [ + "string", + "array" + ], + "description": "A command to run after creating the container. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, + "postStartCommand": { + "type": [ + "string", + "array" + ], + "description": "A command to run after starting the container. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, + "postAttachCommand": { + "type": [ + "string", + "array" + ], + "description": "A command to run after attaching to the container. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, + "devPort": { + "type": "integer", + "description": "The port VS Code can use to connect to its backend." + }, + "userEnvProbe": { + "type": "string", + "enum": [ + "none", + "loginShell", + "loginInteractiveShell", + "interactiveShell" + ], + "description": "User environment probe to run. The default is none." + }, + "codespaces": { + "type": "object", + "additionalProperties": true, + "description": "Codespaces-specific configuration." + } + }, + "required": [ + "build" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "dockerFile": { + "type": "string", + "description": "The location of the Dockerfile that defines the contents of the container. The path is relative to the folder containing the `devcontainer.json` file." + }, + "context": { + "type": "string", + "description": "The location of the context folder for building the Docker image. The path is relative to the folder containing the `devcontainer.json` file." + }, + "build": { + "description": "Docker build-related options.", + "type": "object", + "properties": { + "target": { + "type": "string", + "description": "Target stage in a multi-stage build." + }, + "args": { + "type": "object", + "additionalProperties": { + "type": [ + "string" + ] + }, + "description": "Build arguments." + } + }, + "additionalProperties": false + }, + "appPort": { + "type": [ + "integer", + "string", + "array" + ], + "description": "Application ports that are exposed by the container. This can be a single port or an array of ports. Each port can be a number or a string. A number is mapped to the same port on the host. A string is passed to Docker unchanged and can be used to map ports differently, e.g. \"8000:8010\".", + "items": { + "type": [ + "integer", + "string" + ] + } + }, + "containerEnv": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Container environment variables." + }, + "containerUser": { + "type": "string", + "description": "The user the container will be started with. The default is the user on the Docker image." + }, + "updateRemoteUserUID": { + "type": "boolean", + "description": "Controls whether on Linux the container's user should be updated with the local user's UID and GID. On by default." + }, + "mounts": { + "type": "array", + "description": "Mount points to set up when creating the container. See Docker's documentation for the --mount option for the supported syntax.", + "items": { + "type": "string" + } + }, + "runArgs": { + "type": "array", + "description": "The arguments required when starting in the container.", + "items": { + "type": "string" + } + }, + "shutdownAction": { + "type": "string", + "enum": [ + "none", + "stopContainer" + ], + "description": "Action to take when the VS Code window is closed. The default is to stop the container." + }, + "overrideCommand": { + "type": "boolean", + "description": "Whether to overwrite the command specified in the image. The default is true." + }, + "workspaceFolder": { + "type": "string", + "description": "The path of the workspace folder inside the container." + }, + "workspaceMount": { + "type": "string", + "description": "The --mount parameter for docker run. The default is to mount the project folder at /workspaces/$project." + }, + "name": { + "type": "string", + "description": "A name to show for the workspace folder." + }, + "extensions": { + "type": "array", + "description": "An array of extensions that should be installed into the container.", + "items": { + "type": "string", + "pattern": "^([a-z0-9A-Z][a-z0-9\\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\\-A-Z]*)(@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?$", + "errorMessage": "Expected format: '${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'." + } + }, + "settings": { + "$ref": "vscode://schemas/settings/machine", + "description": "Machine specific settings that should be copied into the container. These are only copied when connecting to the container for the first time, rebuilding the container then triggers it again." + }, + "forwardPorts": { + "type": "array", + "description": "Ports that are forwarded from the container to the local machine.", + "items": { + "type": "integer", + "maximum": 65535, + "minimum": 0 + } + }, + "remoteEnv": { + "type": "object", + "additionalProperties": { + "type": [ + "string", + "null" + ] + }, + "description": "Remote environment variables. If these are used in the Integrated Terminal, make sure the 'Terminal > Integrated: Inherit Env' setting is enabled." + }, + "remoteUser": { + "type": "string", + "description": "The user VS Code Server will be started with. The default is the same user as the container." + }, + "initializeCommand": { + "type": [ + "string", + "array" + ], + "description": "A command to run locally before anything else. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, + "postCreateCommand": { + "type": [ + "string", + "array" + ], + "description": "A command to run after creating the container. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, + "postStartCommand": { + "type": [ + "string", + "array" + ], + "description": "A command to run after starting the container. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, + "postAttachCommand": { + "type": [ + "string", + "array" + ], + "description": "A command to run after attaching to the container. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, + "devPort": { + "type": "integer", + "description": "The port VS Code can use to connect to its backend." + }, + "userEnvProbe": { + "type": "string", + "enum": [ + "none", + "loginShell", + "loginInteractiveShell", + "interactiveShell" + ], + "description": "User environment probe to run. The default is none." + }, + "codespaces": { + "type": "object", + "additionalProperties": true, + "description": "Codespaces-specific configuration." + } + }, + "required": [ + "dockerFile" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "image": { + "type": "string", + "description": "The docker image that will be used to create the container." + }, + "appPort": { + "type": [ + "integer", + "string", + "array" + ], + "description": "Application ports that are exposed by the container. This can be a single port or an array of ports. Each port can be a number or a string. A number is mapped to the same port on the host. A string is passed to Docker unchanged and can be used to map ports differently, e.g. \"8000:8010\".", + "items": { + "type": [ + "integer", + "string" + ] + } + }, + "containerEnv": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Container environment variables." + }, + "containerUser": { + "type": "string", + "description": "The user the container will be started with. The default is the user on the Docker image." + }, + "updateRemoteUserUID": { + "type": "boolean", + "description": "Controls whether on Linux the container's user should be updated with the local user's UID and GID. On by default." + }, + "mounts": { + "type": "array", + "description": "Mount points to set up when creating the container. See Docker's documentation for the --mount option for the supported syntax.", + "items": { + "type": "string" + } + }, + "runArgs": { + "type": "array", + "description": "The arguments required when starting in the container.", + "items": { + "type": "string" + } + }, + "shutdownAction": { + "type": "string", + "enum": [ + "none", + "stopContainer" + ], + "description": "Action to take when the VS Code window is closed. The default is to stop the container." + }, + "overrideCommand": { + "type": "boolean", + "description": "Whether to overwrite the command specified in the image. The default is true." + }, + "workspaceFolder": { + "type": "string", + "description": "The path of the workspace folder inside the container." + }, + "workspaceMount": { + "type": "string", + "description": "The --mount parameter for docker run. The default is to mount the project folder at /workspaces/$project." + }, + "name": { + "type": "string", + "description": "A name to show for the workspace folder." + }, + "extensions": { + "type": "array", + "description": "An array of extensions that should be installed into the container.", + "items": { + "type": "string", + "pattern": "^([a-z0-9A-Z][a-z0-9\\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\\-A-Z]*)(@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?$", + "errorMessage": "Expected format: '${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'." + } + }, + "settings": { + "$ref": "vscode://schemas/settings/machine", + "description": "Machine specific settings that should be copied into the container. These are only copied when connecting to the container for the first time, rebuilding the container then triggers it again." + }, + "forwardPorts": { + "type": "array", + "description": "Ports that are forwarded from the container to the local machine.", + "items": { + "type": "integer", + "maximum": 65535, + "minimum": 0 + } + }, + "remoteEnv": { + "type": "object", + "additionalProperties": { + "type": [ + "string", + "null" + ] + }, + "description": "Remote environment variables. If these are used in the Integrated Terminal, make sure the 'Terminal > Integrated: Inherit Env' setting is enabled." + }, + "remoteUser": { + "type": "string", + "description": "The user VS Code Server will be started with. The default is the same user as the container." + }, + "initializeCommand": { + "type": [ + "string", + "array" + ], + "description": "A command to run locally before anything else. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, + "postCreateCommand": { + "type": [ + "string", + "array" + ], + "description": "A command to run after creating the container. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, + "postStartCommand": { + "type": [ + "string", + "array" + ], + "description": "A command to run after starting the container. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, + "postAttachCommand": { + "type": [ + "string", + "array" + ], + "description": "A command to run after attaching to the container. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, + "devPort": { + "type": "integer", + "description": "The port VS Code can use to connect to its backend." + }, + "userEnvProbe": { + "type": "string", + "enum": [ + "none", + "loginShell", + "loginInteractiveShell", + "interactiveShell" + ], + "description": "User environment probe to run. The default is none." + }, + "codespaces": { + "type": "object", + "additionalProperties": true, + "description": "Codespaces-specific configuration." + } + }, + "required": [ + "image" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "dockerComposeFile": { + "type": [ + "string", + "array" + ], + "description": "The name of the docker-compose file(s) used to start the services.", + "items": { + "type": "string" + } + }, + "service": { + "type": "string", + "description": "The service you want to work on." + }, + "runServices": { + "type": "array", + "description": "An array of services that should be started and stopped.", + "items": { + "type": "string" + } + }, + "workspaceFolder": { + "type": "string", + "description": "The path of the workspace folder inside the container. This is typically the target path of a volume mount in the docker-compose.yml." + }, + "shutdownAction": { + "type": "string", + "enum": [ + "none", + "stopCompose" + ], + "description": "Action to take when the VS Code window is closed. The default is to stop the containers." + }, + "name": { + "type": "string", + "description": "A name to show for the workspace folder." + }, + "extensions": { + "type": "array", + "description": "An array of extensions that should be installed into the container.", + "items": { + "type": "string", + "pattern": "^([a-z0-9A-Z][a-z0-9\\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\\-A-Z]*)(@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?$", + "errorMessage": "Expected format: '${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'." + } + }, + "settings": { + "$ref": "vscode://schemas/settings/machine", + "description": "Machine specific settings that should be copied into the container. These are only copied when connecting to the container for the first time, rebuilding the container then triggers it again." + }, + "forwardPorts": { + "type": "array", + "description": "Ports that are forwarded from the container to the local machine.", + "items": { + "type": "integer", + "maximum": 65535, + "minimum": 0 + } + }, + "remoteEnv": { + "type": "object", + "additionalProperties": { + "type": [ + "string", + "null" + ] + }, + "description": "Remote environment variables. If these are used in the Integrated Terminal, make sure the 'Terminal > Integrated: Inherit Env' setting is enabled." + }, + "remoteUser": { + "type": "string", + "description": "The user VS Code Server will be started with. The default is the same user as the container." + }, + "initializeCommand": { + "type": [ + "string", + "array" + ], + "description": "A command to run locally before anything else. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, + "postCreateCommand": { + "type": [ + "string", + "array" + ], + "description": "A command to run after creating the container. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, + "postStartCommand": { + "type": [ + "string", + "array" + ], + "description": "A command to run after starting the container. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, + "postAttachCommand": { + "type": [ + "string", + "array" + ], + "description": "A command to run after attaching to the container. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, + "devPort": { + "type": "integer", + "description": "The port VS Code can use to connect to its backend." + }, + "userEnvProbe": { + "type": "string", + "enum": [ + "none", + "loginShell", + "loginInteractiveShell", + "interactiveShell" + ], + "description": "User environment probe to run. The default is none." + }, + "codespaces": { + "type": "object", + "additionalProperties": true, + "description": "Codespaces-specific configuration." + } + }, + "required": [ + "dockerComposeFile", + "service", + "workspaceFolder" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "A name to show for the workspace folder." + }, + "extensions": { + "type": "array", + "description": "An array of extensions that should be installed into the container.", + "items": { + "type": "string", + "pattern": "^([a-z0-9A-Z][a-z0-9\\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\\-A-Z]*)(@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?$", + "errorMessage": "Expected format: '${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'." + } + }, + "settings": { + "$ref": "vscode://schemas/settings/machine", + "description": "Machine specific settings that should be copied into the container. These are only copied when connecting to the container for the first time, rebuilding the container then triggers it again." + }, + "forwardPorts": { + "type": "array", + "description": "Ports that are forwarded from the container to the local machine.", + "items": { + "type": "integer", + "maximum": 65535, + "minimum": 0 + } + }, + "remoteEnv": { + "type": "object", + "additionalProperties": { + "type": [ + "string", + "null" + ] + }, + "description": "Remote environment variables. If these are used in the Integrated Terminal, make sure the 'Terminal > Integrated: Inherit Env' setting is enabled." + }, + "remoteUser": { + "type": "string", + "description": "The user VS Code Server will be started with. The default is the same user as the container." + }, + "initializeCommand": { + "type": [ + "string", + "array" + ], + "description": "A command to run locally before anything else. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, + "postCreateCommand": { + "type": [ + "string", + "array" + ], + "description": "A command to run after creating the container. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, + "postStartCommand": { + "type": [ + "string", + "array" + ], + "description": "A command to run after starting the container. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, + "postAttachCommand": { + "type": [ + "string", + "array" + ], + "description": "A command to run after attaching to the container. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, + "devPort": { + "type": "integer", + "description": "The port VS Code can use to connect to its backend." + }, + "userEnvProbe": { + "type": "string", + "enum": [ + "none", + "loginShell", + "loginInteractiveShell", + "interactiveShell" + ], + "description": "User environment probe to run. The default is none." + }, + "codespaces": { + "type": "object", + "additionalProperties": true, + "description": "Codespaces-specific configuration." + } + }, + "additionalProperties": false + } + ] +} \ No newline at end of file diff --git a/extensions/configuration-editing/schemas/devContainer.schema.json b/extensions/configuration-editing/schemas/devContainer.schema.src.json similarity index 97% rename from extensions/configuration-editing/schemas/devContainer.schema.json rename to extensions/configuration-editing/schemas/devContainer.schema.src.json index 4719d53e6..ba140ba94 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.src.json @@ -3,7 +3,6 @@ "description": "Defines a dev container", "allowComments": true, "allowTrailingCommas": true, - "type": "object", "definitions": { "devContainerCommon": { "type": "object", @@ -42,7 +41,7 @@ "null" ] }, - "description": "Remote environment variables." + "description": "Remote environment variables. If these are used in the Integrated Terminal, make sure the 'Terminal > Integrated: Inherit Env' setting is enabled." }, "remoteUser": { "type": "string", @@ -104,6 +103,7 @@ }, "codespaces": { "type": "object", + "additionalProperties": true, "description": "Codespaces-specific configuration." } } diff --git a/extensions/cpp/build/update-grammars.js b/extensions/cpp/build/update-grammars.js index e6293eb2e..28b4ca923 100644 --- a/extensions/cpp/build/update-grammars.js +++ b/extensions/cpp/build/update-grammars.js @@ -6,9 +6,9 @@ var updateGrammar = require('../../../build/npm/update-grammar'); -updateGrammar.update('jeff-hykin/cpp-textmate-grammar', '/syntaxes/c.tmLanguage.json', './syntaxes/c.tmLanguage.json', undefined, 'master', 'source/languages/cpp/'); -updateGrammar.update('jeff-hykin/cpp-textmate-grammar', '/syntaxes/cpp.tmLanguage.json', './syntaxes/cpp.tmLanguage.json', undefined, 'master', 'source/languages/cpp/'); -updateGrammar.update('jeff-hykin/cpp-textmate-grammar', '/syntaxes/cpp.embedded.macro.tmLanguage.json', './syntaxes/cpp.embedded.macro.tmLanguage.json', undefined, 'master', 'source/languages/cpp/'); +updateGrammar.update('jeff-hykin/cpp-textmate-grammar', 'syntaxes/c.tmLanguage.json', './syntaxes/c.tmLanguage.json', undefined, 'master', 'source/languages/cpp/'); +updateGrammar.update('jeff-hykin/cpp-textmate-grammar', 'syntaxes/cpp.tmLanguage.json', './syntaxes/cpp.tmLanguage.json', undefined, 'master', 'source/languages/cpp/'); +updateGrammar.update('jeff-hykin/cpp-textmate-grammar', 'syntaxes/cpp.embedded.macro.tmLanguage.json', './syntaxes/cpp.embedded.macro.tmLanguage.json', undefined, 'master', 'source/languages/cpp/'); // `source.c.platform` which is still included by other grammars updateGrammar.update('textmate/c.tmbundle', 'Syntaxes/Platform.tmLanguage', './syntaxes/platform.tmLanguage.json'); diff --git a/extensions/cpp/cgmanifest.json b/extensions/cpp/cgmanifest.json index 070312a63..05938de60 100644 --- a/extensions/cpp/cgmanifest.json +++ b/extensions/cpp/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "jeff-hykin/cpp-textmate-grammar", "repositoryUrl": "https://github.com/jeff-hykin/cpp-textmate-grammar", - "commitHash": "666808cab3907fc91ed4d3901060ee6b045cca58" + "commitHash": "ad9f1541fd376740c30a7257614c9cb5ed25005d" } }, "license": "MIT", - "version": "1.14.15", + "version": "1.15.3", "description": "The files syntaxes/c.json and syntaxes/c++.json were derived from https://github.com/atom/language-c which was originally converted from the C TextMate bundle https://github.com/textmate/c.tmbundle." }, { diff --git a/extensions/cpp/syntaxes/c.tmLanguage.json b/extensions/cpp/syntaxes/c.tmLanguage.json index eef07eeb5..952730f5a 100644 --- a/extensions/cpp/syntaxes/c.tmLanguage.json +++ b/extensions/cpp/syntaxes/c.tmLanguage.json @@ -1,10 +1,10 @@ { "information_for_contributors": [ - "This file has been converted from https://github.com/jeff-hykin/cpp-textmate-grammar/blob/master//syntaxes/c.tmLanguage.json", + "This file has been converted from https://github.com/jeff-hykin/cpp-textmate-grammar/blob/master/syntaxes/c.tmLanguage.json", "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/cpp-textmate-grammar/commit/72b309aabb63bf14a3cdf0280149121db005d616", + "version": "https://github.com/jeff-hykin/cpp-textmate-grammar/commit/afa42b3f5529833714ae354fbc638ece8f253074", "name": "C", "scopeName": "source.c", "patterns": [ @@ -364,7 +364,7 @@ { "name": "meta.function.c", "begin": "(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:\\n|$)", + "captures": { "1": { - "name": "meta.encoding.c" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "2": { - "name": "punctuation.definition.string.begin.assembly.c" - } - }, - "end": "(\")", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.assembly.c" - } - }, - "patterns": [ - { - "include": "source.asm" + "name": "comment.block.c punctuation.definition.comment.begin.c" }, - { - "include": "source.x86" + "3": { + "name": "comment.block.c" }, - { - "include": "source.x86_64" - }, - { - "include": "source.arm" - }, - { - "include": "#backslash_escapes" - }, - { - "include": "#string_escaped_char" - }, - { - "match": "(?=not)possible" + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.c punctuation.definition.comment.end.c" + }, + { + "match": "\\*", + "name": "comment.block.c" + } + ] } - ] - }, - { - "begin": "(\\()", - "beginCaptures": { - "1": { - "name": "punctuation.section.parens.begin.bracket.round.assembly.inner.c" - } - }, - "end": "(\\))", - "endCaptures": { - "1": { - "name": "punctuation.section.parens.end.bracket.round.assembly.inner.c" - } - }, - "patterns": [ - { - "include": "#evaluation_context" - } - ] - }, - { - "match": ":", - "name": "punctuation.separator.delimiter.colon.assembly.c" + } }, { "include": "#comments_context" }, { "include": "#comments" + }, + { + "begin": "(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))\\()", + "beginCaptures": { + "1": { + "name": "punctuation.section.parens.begin.bracket.round.assembly.c" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.c punctuation.definition.comment.begin.c" + }, + "4": { + "name": "comment.block.c" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.c punctuation.definition.comment.end.c" + }, + { + "match": "\\*", + "name": "comment.block.c" + } + ] + } + }, + "end": "(\\))", + "endCaptures": { + "1": { + "name": "punctuation.section.parens.end.bracket.round.assembly.c" + } + }, + "patterns": [ + { + "name": "string.quoted.double.c", + "contentName": "meta.embedded.assembly.c", + "begin": "(R?)(\")", + "beginCaptures": { + "1": { + "name": "meta.encoding.c" + }, + "2": { + "name": "punctuation.definition.string.begin.assembly.c" + } + }, + "end": "(\")", + "endCaptures": { + "1": { + "name": "punctuation.definition.string.end.assembly.c" + } + }, + "patterns": [ + { + "include": "source.asm" + }, + { + "include": "source.x86" + }, + { + "include": "source.x86_64" + }, + { + "include": "source.arm" + }, + { + "include": "#backslash_escapes" + }, + { + "include": "#string_escaped_char" + } + ] + }, + { + "begin": "(\\()", + "beginCaptures": { + "1": { + "name": "punctuation.section.parens.begin.bracket.round.assembly.inner.c" + } + }, + "end": "(\\))", + "endCaptures": { + "1": { + "name": "punctuation.section.parens.end.bracket.round.assembly.inner.c" + } + }, + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + { + "match": "\\[((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))([a-zA-Z_]\\w*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))\\]", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.c punctuation.definition.comment.begin.c" + }, + "3": { + "name": "comment.block.c" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.c punctuation.definition.comment.end.c" + }, + { + "match": "\\*", + "name": "comment.block.c" + } + ] + }, + "5": { + "name": "variable.other.asm.label.c" + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "name": "comment.block.c punctuation.definition.comment.begin.c" + }, + "8": { + "name": "comment.block.c" + }, + "9": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.c punctuation.definition.comment.end.c" + }, + { + "match": "\\*", + "name": "comment.block.c" + } + ] + } + } + }, + { + "match": ":", + "name": "punctuation.separator.delimiter.colon.assembly.c" + }, + { + "include": "#comments_context" + }, + { + "include": "#comments" + } + ] } ] } @@ -3194,4 +3342,4 @@ "name": "punctuation.vararg-ellipses.c" } } -} +} \ No newline at end of file diff --git a/extensions/cpp/syntaxes/cpp.embedded.macro.tmLanguage.json b/extensions/cpp/syntaxes/cpp.embedded.macro.tmLanguage.json index 4c0e9a582..151cad6fe 100644 --- a/extensions/cpp/syntaxes/cpp.embedded.macro.tmLanguage.json +++ b/extensions/cpp/syntaxes/cpp.embedded.macro.tmLanguage.json @@ -1,10 +1,10 @@ { "information_for_contributors": [ - "This file has been converted from https://github.com/jeff-hykin/cpp-textmate-grammar/blob/master//syntaxes/cpp.embedded.macro.tmLanguage.json", + "This file has been converted from https://github.com/jeff-hykin/cpp-textmate-grammar/blob/master/syntaxes/cpp.embedded.macro.tmLanguage.json", "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/cpp-textmate-grammar/commit/666808cab3907fc91ed4d3901060ee6b045cca58", + "version": "https://github.com/jeff-hykin/cpp-textmate-grammar/commit/ad9f1541fd376740c30a7257614c9cb5ed25005d", "name": "C++", "scopeName": "source.cpp.embedded.macro", "patterns": [ @@ -27,13 +27,13 @@ "include": "#using_namespace" }, { - "include": "#type_alias" + "include": "source.cpp#type_alias" }, { - "include": "#using_name" + "include": "source.cpp#using_name" }, { - "include": "#namespace_alias" + "include": "source.cpp#namespace_alias" }, { "include": "#namespace_block" @@ -51,10 +51,10 @@ "include": "#typedef_union" }, { - "include": "#typedef_keyword" + "include": "source.cpp#misc_keywords" }, { - "include": "#standard_declares" + "include": "source.cpp#standard_declares" }, { "include": "#class_block" @@ -69,13 +69,13 @@ "include": "#enum_block" }, { - "include": "#template_isolated_definition" + "include": "source.cpp#template_isolated_definition" }, { "include": "#template_definition" }, { - "include": "#access_control_keywords" + "include": "source.cpp#access_control_keywords" }, { "include": "#block" @@ -94,56 +94,20 @@ } ], "repository": { - "access_control_keywords": { - "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(((?:protected|private|public))\\s*(:))", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "5": { - "name": "storage.type.modifier.access.control.$6.cpp" - }, - "7": { - "name": "punctuation.separator.colon.access.control.cpp" - } - } - }, "alignas_attribute": { - "name": "support.other.attribute.cpp", - "begin": "(alignas\\()", + "begin": "alignas\\(", + "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\n)|$)", + "captures": { "1": { - "name": "meta.encoding.cpp" + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] }, "2": { - "name": "punctuation.definition.string.begin.assembly.cpp" - } - }, - "end": "(\")|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\(", + "end": "\\)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]", + "captures": { + "1": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "5": { + "name": "variable.other.asm.label.cpp" + }, + "6": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "7": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "8": { + "name": "comment.block.cpp" + }, + "9": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": ":", + "name": "punctuation.separator.delimiter.colon.assembly.cpp" + }, + { + "include": "#comments_context" + }, + { + "include": "#comments" + } + ] } ] }, - "assignment_operator": { - "match": "\\=", - "name": "keyword.operator.assignment.cpp" - }, "attributes_context": { "patterns": [ { @@ -402,24 +489,20 @@ } ] }, - "backslash_escapes": { - "match": "(?x)\\\\ (\n\\\\\t\t\t |\n[abefnprtv'\"?] |\n[0-3]\\d{,2}\t |\n[4-7]\\d?\t\t|\nx[a-fA-F0-9]{,2} |\nu[a-fA-F0-9]{,4} |\nU[a-fA-F0-9]{,8} )", - "name": "constant.character.escape.cpp" - }, "block": { - "name": "meta.block.cpp", - "begin": "({)", + "begin": "{", + "end": "}|(?=\\s*#\\s*(?:elif|else|endif)\\b)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", + "begin": "(?:\\s)*+(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?={)|(?:((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:(?!:)))?)", + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", + "captures": { + "1": { + "name": "storage.type.modifier.final.cpp" + }, + "2": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=:|{|$)", + "captures": { + "1": { + "name": "entity.name.type.class.cpp" + }, + "2": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "storage.type.modifier.final.cpp" + }, + "7": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": "DLLEXPORT", + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.other.preprocessor.macro.predefined.probably.$0.cpp" + } + ] + }, "12": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "source.cpp#inline_comment" } ] }, "13": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "14": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "15": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "16": { + "14": { "name": "comment.block.cpp" }, - "17": { + "15": { "patterns": [ { "match": "\\*\\/", @@ -768,134 +804,35 @@ } ] }, - "18": { + "16": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "source.cpp#inline_comment" } ] }, + "17": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "18": { + "name": "comment.block.cpp" + }, "19": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "20": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "21": { - "name": "comment.block.cpp" - }, - "22": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "23": { - "name": "entity.name.type.$3.cpp" - }, - "24": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "25": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "26": { - "name": "comment.block.cpp" - }, - "27": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "28": { - "name": "storage.type.modifier.final.cpp" - }, - "29": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "31": { - "name": "comment.block.cpp" - }, - "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "37": { "name": "punctuation.separator.colon.inheritance.cpp" - }, - "38": { - "patterns": [ - { - "include": "#inheritance_context" - } - ] } }, - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>)|(?=(?|\\?\\?>|(?=(?|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\S)(?![:{])", - "captures": { - "1": { - "name": "storage.type.class.declare.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "entity.name.type.class.cpp" - }, - "7": { - "patterns": [ - { - "match": "\\*", - "name": "storage.modifier.pointer.cpp" - }, - { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - }, - "name": "invalid.illegal.reference-type.cpp" - }, - { - "match": "\\&", - "name": "storage.modifier.reference.cpp" - } - ] - }, - "8": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "9": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "10": { - "name": "comment.block.cpp" - }, - "11": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "12": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "14": { - "name": "comment.block.cpp" - }, - "15": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "16": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "17": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "18": { - "name": "comment.block.cpp" - }, - "19": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "20": { - "name": "variable.other.object.declare.cpp" - }, - "21": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "22": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "23": { - "name": "comment.block.cpp" - }, - "24": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - } - }, - "comma": { - "match": ",", - "name": "punctuation.separator.delimiter.comma.cpp" - }, - "comma_in_template_argument": { - "match": ",", - "name": "punctuation.separator.delimiter.comma.template.argument.cpp" - }, "comments": { "patterns": [ { - "name": "comment.line.double-slash.documentation.cpp", - "begin": "(?:^)(?>\\s*)(\\/\\/[!\\/]+)", + "begin": "^(?:(?:\\s)+)?+(\\/\\/[!\\/]+)", + "end": "(?<=\\n)(?|%|\"|\\.|=|::|\\||\\-\\-|\\-\\-\\-)\\b(?:\\{[^}]*\\})?", "name": "storage.type.class.doxygen.cpp" }, { - "match": "((?<=[\\s*!\\/])[\\\\@](?:a|em|e))\\s+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@](?:a|em|e))(?:\\s)+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1186,7 +939,7 @@ } }, { - "match": "((?<=[\\s*!\\/])[\\\\@]b)\\s+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@]b)(?:\\s)+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1197,7 +950,7 @@ } }, { - "match": "((?<=[\\s*!\\/])[\\\\@](?:c|p))\\s+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@](?:c|p))(?:\\s)+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1216,22 +969,30 @@ "name": "storage.type.class.doxygen.cpp" }, { - "match": "((?<=[\\s*!\\/])[\\\\@]param)\\s+(\\b\\w+\\b)", + "match": "((?<=[\\s*!\\/])[\\\\@]param)(?:\\s*\\[((?:,?(?:(?:\\s)+)?(?:in|out)(?:(?:\\s)+)?)+)\\])?(?:\\s)+(\\b\\w+\\b)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" }, "2": { + "patterns": [ + { + "match": "in|out", + "name": "keyword.other.parameter.direction.$0.cpp" + } + ] + }, + "3": { "name": "variable.parameter.cpp" } } }, { - "match": "(?<=[\\s*!\\/])[\\\\@](?:arg|attention|author|authors|brief|bug|copyright|date|deprecated|details|exception|invariant|li|note|par|paragraph|param|post|pre|remark|remarks|result|return|returns|retval|sa|see|short|since|test|throw|todo|tparam|version|warning|xrefitem)\\b(?:\\{[^}]*\\})?", + "match": "(?<=[\\s*!\\/])[\\\\@](?:arg|attention|author|authors|brief|bug|copyright|date|deprecated|details|exception|invariant|li|note|par|paragraph|param|post|pre|remark|remarks|result|return|returns|retval|sa|see|short|since|test|throw|throws|todo|tparam|version|warning|xrefitem)\\b(?:\\{[^}]*\\})?", "name": "storage.type.class.doxygen.cpp" }, { - "match": "(?<=[\\s*!\\/])[\\\\@](?:code|cond|docbookonly|dot|htmlonly|internal|latexonly|link|manonly|msc|parblock|rtfonly|secreflist|uml|verbatim|xmlonly|endcode|endcond|enddocbookonly|enddot|endhtmlonly|endinternal|endlatexonly|endlink|endmanonly|endmsc|endparblock|endrtfonly|endsecreflist|enduml|endverbatim|endxmlonly)\\b(?:\\{[^}]*\\})?", + "match": "(?<=[\\s*!\\/])[\\\\@](?:code|cond|docbookonly|dot|htmlonly|internal|latexonly|link|manonly|msc|parblock|rtfonly|secreflist|startuml|verbatim|xmlonly|endcode|endcond|enddocbookonly|enddot|endhtmlonly|endinternal|endlatexonly|endlink|endmanonly|endmsc|endparblock|endrtfonly|endsecreflist|enduml|endverbatim|endxmlonly)\\b(?:\\{[^}]*\\})?", "name": "storage.type.class.doxygen.cpp" }, { @@ -1253,7 +1014,7 @@ "name": "storage.type.class.doxygen.cpp" }, { - "match": "((?<=[\\s*!\\/])[\\\\@](?:a|em|e))\\s+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@](?:a|em|e))(?:\\s)+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1264,7 +1025,7 @@ } }, { - "match": "((?<=[\\s*!\\/])[\\\\@]b)\\s+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@]b)(?:\\s)+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1275,7 +1036,7 @@ } }, { - "match": "((?<=[\\s*!\\/])[\\\\@](?:c|p))\\s+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@](?:c|p))(?:\\s)+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1294,22 +1055,30 @@ "name": "storage.type.class.doxygen.cpp" }, { - "match": "((?<=[\\s*!\\/])[\\\\@]param)\\s+(\\b\\w+\\b)", + "match": "((?<=[\\s*!\\/])[\\\\@]param)(?:\\s*\\[((?:,?(?:(?:\\s)+)?(?:in|out)(?:(?:\\s)+)?)+)\\])?(?:\\s)+(\\b\\w+\\b)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" }, "2": { + "patterns": [ + { + "match": "in|out", + "name": "keyword.other.parameter.direction.$0.cpp" + } + ] + }, + "3": { "name": "variable.parameter.cpp" } } }, { - "match": "(?<=[\\s*!\\/])[\\\\@](?:arg|attention|author|authors|brief|bug|copyright|date|deprecated|details|exception|invariant|li|note|par|paragraph|param|post|pre|remark|remarks|result|return|returns|retval|sa|see|short|since|test|throw|todo|tparam|version|warning|xrefitem)\\b(?:\\{[^}]*\\})?", + "match": "(?<=[\\s*!\\/])[\\\\@](?:arg|attention|author|authors|brief|bug|copyright|date|deprecated|details|exception|invariant|li|note|par|paragraph|param|post|pre|remark|remarks|result|return|returns|retval|sa|see|short|since|test|throw|throws|todo|tparam|version|warning|xrefitem)\\b(?:\\{[^}]*\\})?", "name": "storage.type.class.doxygen.cpp" }, { - "match": "(?<=[\\s*!\\/])[\\\\@](?:code|cond|docbookonly|dot|htmlonly|internal|latexonly|link|manonly|msc|parblock|rtfonly|secreflist|uml|verbatim|xmlonly|endcode|endcond|enddocbookonly|enddot|endhtmlonly|endinternal|endlatexonly|endlink|endmanonly|endmsc|endparblock|endrtfonly|endsecreflist|enduml|endverbatim|endxmlonly)\\b(?:\\{[^}]*\\})?", + "match": "(?<=[\\s*!\\/])[\\\\@](?:code|cond|docbookonly|dot|htmlonly|internal|latexonly|link|manonly|msc|parblock|rtfonly|secreflist|startuml|verbatim|xmlonly|endcode|endcond|enddocbookonly|enddot|endhtmlonly|endinternal|endlatexonly|endlink|endmanonly|endmsc|endparblock|endrtfonly|endsecreflist|enduml|endverbatim|endxmlonly)\\b(?:\\{[^}]*\\})?", "name": "storage.type.class.doxygen.cpp" }, { @@ -1325,26 +1094,26 @@ "name": "comment.block.documentation.cpp" }, { - "name": "comment.block.documentation.cpp", - "begin": "((?>\\s*)\\/\\*[!*]+(?:(?:\\n|$)|(?=\\s)))", + "begin": "(?:(?:\\s)+)?+\\/\\*[!*]+(?:(?:(?:\\n)|$)|(?=\\s))", + "end": "[!*]*\\*\\/|(?=(?|%|\"|\\.|=|::|\\||\\-\\-|\\-\\-\\-)\\b(?:\\{[^}]*\\})?", "name": "storage.type.class.doxygen.cpp" }, { - "match": "((?<=[\\s*!\\/])[\\\\@](?:a|em|e))\\s+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@](?:a|em|e))(?:\\s)+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1355,7 +1124,7 @@ } }, { - "match": "((?<=[\\s*!\\/])[\\\\@]b)\\s+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@]b)(?:\\s)+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1366,7 +1135,7 @@ } }, { - "match": "((?<=[\\s*!\\/])[\\\\@](?:c|p))\\s+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@](?:c|p))(?:\\s)+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1385,22 +1154,30 @@ "name": "storage.type.class.doxygen.cpp" }, { - "match": "((?<=[\\s*!\\/])[\\\\@]param)\\s+(\\b\\w+\\b)", + "match": "((?<=[\\s*!\\/])[\\\\@]param)(?:\\s*\\[((?:,?(?:(?:\\s)+)?(?:in|out)(?:(?:\\s)+)?)+)\\])?(?:\\s)+(\\b\\w+\\b)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" }, "2": { + "patterns": [ + { + "match": "in|out", + "name": "keyword.other.parameter.direction.$0.cpp" + } + ] + }, + "3": { "name": "variable.parameter.cpp" } } }, { - "match": "(?<=[\\s*!\\/])[\\\\@](?:arg|attention|author|authors|brief|bug|copyright|date|deprecated|details|exception|invariant|li|note|par|paragraph|param|post|pre|remark|remarks|result|return|returns|retval|sa|see|short|since|test|throw|todo|tparam|version|warning|xrefitem)\\b(?:\\{[^}]*\\})?", + "match": "(?<=[\\s*!\\/])[\\\\@](?:arg|attention|author|authors|brief|bug|copyright|date|deprecated|details|exception|invariant|li|note|par|paragraph|param|post|pre|remark|remarks|result|return|returns|retval|sa|see|short|since|test|throw|throws|todo|tparam|version|warning|xrefitem)\\b(?:\\{[^}]*\\})?", "name": "storage.type.class.doxygen.cpp" }, { - "match": "(?<=[\\s*!\\/])[\\\\@](?:code|cond|docbookonly|dot|htmlonly|internal|latexonly|link|manonly|msc|parblock|rtfonly|secreflist|uml|verbatim|xmlonly|endcode|endcond|enddocbookonly|enddot|endhtmlonly|endinternal|endlatexonly|endlink|endmanonly|endmsc|endparblock|endrtfonly|endsecreflist|enduml|endverbatim|endxmlonly)\\b(?:\\{[^}]*\\})?", + "match": "(?<=[\\s*!\\/])[\\\\@](?:code|cond|docbookonly|dot|htmlonly|internal|latexonly|link|manonly|msc|parblock|rtfonly|secreflist|startuml|verbatim|xmlonly|endcode|endcond|enddocbookonly|enddot|endhtmlonly|endinternal|endlatexonly|endlink|endmanonly|endmsc|endparblock|endrtfonly|endsecreflist|enduml|endverbatim|endxmlonly)\\b(?:\\{[^}]*\\})?", "name": "storage.type.class.doxygen.cpp" }, { @@ -1410,7 +1187,7 @@ ] }, { - "include": "#emacs_file_banner" + "include": "source.cpp#emacs_file_banner" }, { "include": "#block_comment" @@ -1419,31 +1196,31 @@ "include": "#line_comment" }, { - "include": "#invalid_comment_end" + "include": "source.cpp#invalid_comment_end" } ] }, "constructor_inline": { - "name": "meta.function.definition.special.constructor.cpp", - "begin": "(^((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?:constexpr|explicit|mutable|virtual|inline|friend)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:constexpr)|(?:explicit)|(?:mutable)|(?:virtual)|(?:inline)|(?:friend))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(default)|(delete))", - "captures": { - "1": { - "name": "keyword.operator.assignment.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "keyword.other.default.constructor.cpp" - }, - "7": { - "name": "keyword.other.delete.constructor.cpp" - } - } - } - ] - }, - { - "include": "#functional_specifiers_pre_parameters" - }, - { - "begin": "(:)", - "beginCaptures": { + "match": "(\\=)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(default)|(delete))", + "captures": { "1": { + "name": "keyword.operator.assignment.cpp" + }, + "2": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "keyword.other.default.constructor.cpp" + }, + "7": { + "name": "keyword.other.delete.constructor.cpp" + } + } + }, + { + "include": "source.cpp#functional_specifiers_pre_parameters" + }, + { + "begin": ":", + "end": "(?=\\{)|(?=(?(?:(?>[^<>]*)\\g<3>?)+)>)\\s*)?(\\()", + "begin": "((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?(\\()", + "end": "\\)|(?=(?|\\?\\?>)|(?=(?|\\?\\?>|(?=(?|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<14>?)+)>)\\s*)?::)*)(((?>(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))::((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\16((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\()))", + "begin": "\\s*+((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)(((?>(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))::((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\14((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\())", + "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(default)|(delete))", - "captures": { - "1": { - "name": "keyword.operator.assignment.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "keyword.other.default.constructor.cpp" - }, - "7": { - "name": "keyword.other.delete.constructor.cpp" - } - } - } - ] - }, - { - "include": "#functional_specifiers_pre_parameters" - }, - { - "begin": "(:)", - "beginCaptures": { + "match": "(\\=)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(default)|(delete))", + "captures": { "1": { + "name": "keyword.operator.assignment.cpp" + }, + "2": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "keyword.other.default.constructor.cpp" + }, + "7": { + "name": "keyword.other.delete.constructor.cpp" + } + } + }, + { + "include": "source.cpp#functional_specifiers_pre_parameters" + }, + { + "begin": ":", + "end": "(?=\\{)|(?=(?(?:(?>[^<>]*)\\g<3>?)+)>)\\s*)?(\\()", + "begin": "((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?(\\()", + "end": "\\)|(?=(?|\\?\\?>)|(?=(?|\\?\\?>|(?=(?|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(?![\\w<:.]))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\{)", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\{)", + "end": "\\}|(?=(?|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?:constexpr|explicit|mutable|virtual|inline|friend)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*)(~(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:constexpr)|(?:explicit)|(?:mutable)|(?:virtual)|(?:inline)|(?:friend))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)(~(?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(default)|(delete))", - "captures": { - "1": { - "name": "keyword.operator.assignment.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "keyword.other.default.constructor.cpp" - }, - "7": { - "name": "keyword.other.delete.constructor.cpp" + "match": "(\\=)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(default)|(delete))", + "captures": { + "1": { + "name": "keyword.operator.assignment.cpp" + }, + "2": { + "patterns": [ + { + "include": "source.cpp#inline_comment" } - } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "keyword.other.default.constructor.cpp" + }, + "7": { + "name": "keyword.other.delete.constructor.cpp" } - ] + } }, { - "contentName": "meta.function.definition.parameters.special.member.destructor.cpp", - "begin": "(\\()", + "begin": "\\(", + "end": "\\)|(?=(?|\\?\\?>)|(?=(?|\\?\\?>|(?=(?|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<14>?)+)>)\\s*)?::)*)(((?>(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))::((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))~\\16((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\()))", + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)(((?>(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))::((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))~\\14((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\())", + "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(default)|(delete))", - "captures": { - "1": { - "name": "keyword.operator.assignment.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "keyword.other.default.constructor.cpp" - }, - "7": { - "name": "keyword.other.delete.constructor.cpp" + "match": "(\\=)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(default)|(delete))", + "captures": { + "1": { + "name": "keyword.operator.assignment.cpp" + }, + "2": { + "patterns": [ + { + "include": "source.cpp#inline_comment" } - } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "keyword.other.default.constructor.cpp" + }, + "7": { + "name": "keyword.other.delete.constructor.cpp" } - ] + } }, { - "contentName": "meta.function.definition.parameters.special.member.destructor.cpp", - "begin": "(\\()", + "begin": "\\(", + "end": "\\)|(?=(?|\\?\\?>)|(?=(?|\\?\\?>|(?=(?|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*((?:error|warning)))\\b\\s*", + "begin": "(^((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?((?:error|warning)))\\b(?:(?:\\s)+)?", + "end": "(?[#;\\/=*C~]+)(?![#;\\/=*C~]))\\s*.+\\s*\\8\\s*(?:\\n|$)))|(^\\s*((\\/\\*)\\s*?((?>[#;\\/=*C~]+)(?![#;\\/=*C~]))\\s*.+\\s*\\8\\s*\\*\\/)))", - "captures": { - "1": { - "name": "meta.toc-list.banner.double-slash.cpp" - }, - "2": { - "name": "comment.line.double-slash.cpp" - }, - "3": { - "name": "punctuation.definition.comment.cpp" - }, - "4": { - "name": "meta.banner.character.cpp" - }, - "5": { - "name": "meta.toc-list.banner.block.cpp" - }, - "6": { - "name": "comment.line.banner.cpp" - }, - "7": { - "name": "punctuation.definition.comment.cpp" - }, - "8": { - "name": "meta.banner.character.cpp" - } - } - }, - "empty_square_brackets": { - "name": "storage.modifier.array.bracket.square.cpp", - "match": "(?-mix:(?-mix:(?(?:(?>[^<>]*)\\g<15>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<15>?)+)>)\\s*)?(::))?\\s*((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?(::))?(?:(?:\\s)+)?((?|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>)|(?=(?|\\?\\?>|(?=(?|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\S)(?![:{])", - "captures": { - "1": { - "name": "storage.type.enum.declare.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "entity.name.type.enum.cpp" - }, - "7": { - "patterns": [ - { - "match": "\\*", - "name": "storage.modifier.pointer.cpp" - }, - { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - }, - "name": "invalid.illegal.reference-type.cpp" - }, - { - "match": "\\&", - "name": "storage.modifier.reference.cpp" - } - ] - }, - "8": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "9": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "10": { - "name": "comment.block.cpp" - }, - "11": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "12": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "14": { - "name": "comment.block.cpp" - }, - "15": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "16": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "17": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "18": { - "name": "comment.block.cpp" - }, - "19": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "20": { - "name": "variable.other.object.declare.cpp" - }, - "21": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "22": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "23": { - "name": "comment.block.cpp" - }, - "24": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - } - }, - "enumerator_list": { - "match": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(extern)(?=\\s*\\\"))", + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(extern)(?=\\s*\\\")", + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>)|(?=(?|\\?\\?>|(?=(?|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)|(?=(?(?:(?>[^<>]*)\\g<12>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(((?(?:(?>[^<>]*)\\g<12>?)+)>)\\s*)?(\\()", + "begin": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?(\\()", + "end": "\\)|(?=(?))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<70>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<70>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<70>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<70>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\())", + "begin": "(?:(?:^|\\G|(?<=;|\\}))|(?<=>))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)(?:\\s)*+)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\()", + "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))", + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", "captures": { "1": { "name": "storage.modifier.$1.cpp" @@ -4002,7 +3521,7 @@ "2": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -4029,12 +3548,12 @@ ] }, "12": { - "name": "storage.modifier.$1.cpp" + "name": "storage.modifier.$12.cpp" }, "13": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -4060,32 +3579,50 @@ "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -4264,45 +3782,95 @@ } ] }, - "45": { + "36": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, - "46": { + "37": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "47": { + "38": { "name": "comment.block.cpp" }, + "39": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "40": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "41": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "42": { + "name": "comment.block.cpp" + }, + "43": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "44": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "45": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "46": { + "name": "comment.block.cpp" + }, + "47": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "48": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "source.cpp#inline_comment" } ] }, "49": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "50": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "51": { + "50": { "name": "comment.block.cpp" }, - "52": { + "51": { "patterns": [ { "match": "\\*\\/", @@ -4314,10 +3882,13 @@ } ] }, + "52": { + "name": "storage.type.modifier.calling-convention.cpp" + }, "53": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -4342,35 +3913,28 @@ "57": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#scope_resolution_function_definition_inner_generated" } ] }, "58": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.cpp" }, "59": { - "name": "comment.block.cpp" - }, - "60": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#template_call_range" } ] }, + "60": {}, "61": { - "name": "storage.type.modifier.calling-convention.cpp" + "name": "entity.name.function.definition.cpp" }, "62": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -4391,83 +3955,39 @@ "name": "comment.block.cpp" } ] - }, - "66": { - "patterns": [ - { - "include": "#scope_resolution_function_definition_inner_generated" - } - ] - }, - "67": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.cpp" - }, - "69": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "71": { - "name": "entity.name.function.definition.cpp" - }, - "72": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "73": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "74": { - "name": "comment.block.cpp" - }, - "75": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] } }, - "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<23>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<23>?)+>)?(?![\\w<:.]))", + "captures": { + "1": { + "name": "punctuation.definition.function.return-type.cpp" + }, + "2": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "meta.qualified_type.cpp", + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + }, + { + "match": "(?|(?=(?|\\?\\?>)|(?=(?|\\?\\?>|(?=(?|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()(\\*)\\s*((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)\\s*(?:(\\[)(\\w*)(\\])\\s*)*(\\))\\s*(\\()", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", + "end": "(\\))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=[{=,);>]|\\n)(?!\\()|(?=(?|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -4739,111 +4459,110 @@ } ] }, - "29": { + "20": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, + "21": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "22": { + "name": "comment.block.cpp" + }, + "23": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "24": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "29": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "31": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "37": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "38": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "39": { - "name": "comment.block.cpp" - }, - "40": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "41": { "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cpp" }, - "42": { + "33": { "name": "punctuation.definition.function.pointer.dereference.cpp" }, - "43": { + "34": { "name": "variable.other.definition.pointer.function.cpp" }, - "44": { + "35": { "name": "punctuation.definition.begin.bracket.square.cpp" }, - "45": { + "36": { "patterns": [ { "include": "#evaluation_context" } ] }, - "46": { + "37": { "name": "punctuation.definition.end.bracket.square.cpp" }, - "47": { + "38": { "name": "punctuation.section.parens.end.bracket.round.function.pointer.cpp" }, - "48": { + "39": { "name": "punctuation.section.parameters.begin.bracket.round.function.pointer.cpp" } }, - "end": "(\\))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=[{=,);]|\\n)(?!\\()|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()(\\*)\\s*((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)\\s*(?:(\\[)(\\w*)(\\])\\s*)*(\\))\\s*(\\()", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", + "end": "(\\))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=[{=,);>]|\\n)(?!\\()|(?=(?|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -5091,111 +4810,110 @@ } ] }, - "29": { + "20": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, + "21": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "22": { + "name": "comment.block.cpp" + }, + "23": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "24": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "29": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "31": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "37": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "38": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "39": { - "name": "comment.block.cpp" - }, - "40": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "41": { "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cpp" }, - "42": { + "33": { "name": "punctuation.definition.function.pointer.dereference.cpp" }, - "43": { + "34": { "name": "variable.parameter.pointer.function.cpp" }, - "44": { + "35": { "name": "punctuation.definition.begin.bracket.square.cpp" }, - "45": { + "36": { "patterns": [ { "include": "#evaluation_context" } ] }, - "46": { + "37": { "name": "punctuation.definition.end.bracket.square.cpp" }, - "47": { + "38": { "name": "punctuation.section.parens.end.bracket.round.function.pointer.cpp" }, - "48": { + "39": { "name": "punctuation.section.parameters.begin.bracket.round.function.pointer.cpp" } }, - "end": "(\\))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=[{=,);]|\\n)(?!\\()|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)", - "captures": { - "1": { - "name": "keyword.control.goto.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "entity.name.label.call.cpp" - } - } - }, - "include": { - "match": "(?:^)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((#)\\s*((?:include|include_next))\\b)\\s*(?:(?:(?:((<)[^>]*(>?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=\\/\\/)))|((\\\")[^\\\"]*(\\\"?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=\\/\\/))))|(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:\\.(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)*((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=(?:\\/\\/|;)))))|((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=(?:\\/\\/|;))))", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "5": { - "name": "keyword.control.directive.$7.cpp" - }, - "6": { - "name": "punctuation.definition.directive.cpp" - }, - "8": { - "name": "string.quoted.other.lt-gt.include.cpp" - }, - "9": { - "name": "punctuation.definition.string.begin.cpp" - }, - "10": { - "name": "punctuation.definition.string.end.cpp" - }, - "11": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "12": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "13": { - "name": "comment.block.cpp" - }, - "14": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "15": { - "name": "string.quoted.double.include.cpp" - }, - "16": { - "name": "punctuation.definition.string.begin.cpp" - }, - "17": { - "name": "punctuation.definition.string.end.cpp" - }, - "18": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "19": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "20": { - "name": "comment.block.cpp" - }, - "21": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "22": { - "name": "entity.name.other.preprocessor.macro.include.cpp" - }, - "23": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "24": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "25": { - "name": "comment.block.cpp" - }, - "26": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "27": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "28": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "29": { - "name": "comment.block.cpp" - }, - "30": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "31": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "32": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "33": { - "name": "comment.block.cpp" - }, - "34": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - }, - "name": "meta.preprocessor.include.cpp" - }, "inheritance_context": { "patterns": [ { @@ -5527,7 +5024,7 @@ "name": "punctuation.separator.delimiter.comma.inheritance.cpp" }, { - "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(?![\\w<:.]))", + "match": "(?<=protected|virtual|private|public|,|:)(?:(?:\\s)+)?(?!(?:(?:(?:protected)|(?:private)|(?:public))|virtual))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)?(?![\\w<:.]))", "captures": { "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "5": { - "name": "comment.block.cpp" + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] }, "6": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, "7": { "patterns": [ { - "include": "#inline_comment" + "match": "::", + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.type.cpp" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "13": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" - }, - "15": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "17": { - "name": "entity.name.scope-resolution.cpp" - }, - "18": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "20": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" - }, - "21": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "22": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "23": { - "name": "comment.block.cpp" - }, - "24": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "25": { - "name": "entity.name.type.cpp" - }, - "26": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - } + "12": {} } } ] }, - "inline_comment": { - "match": "(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/))", - "captures": { - "1": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "2": { - "name": "comment.block.cpp" - }, - "3": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - } - }, - "invalid_comment_end": { - "match": "\\*\\/", - "name": "invalid.illegal.unexpected.punctuation.definition.comment.end.cpp" - }, - "label": { - "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "5": { - "name": "entity.name.label.cpp" - }, - "6": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "7": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "8": { - "name": "comment.block.cpp" - }, - "9": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "10": { - "name": "punctuation.separator.label.cpp" - } - } - }, "lambdas": { - "begin": "((?:(?<=[^\\s]|^)(?])|(?<=\\Wreturn|^return))\\s*(\\[(?!\\[| *+\"| *+\\d))((?>(?:[^\\[\\]]|((?(?:(?>[^\\[\\]]*)\\g<4>?)+)\\]))*))(\\](?!\\[)))", + "begin": "(?:(?<=[^\\s]|^)(?])|(?<=\\Wreturn|^return))(?:(?:\\s)+)?(\\[(?!\\[| *+\"| *+\\d))((?:[^\\[\\]]|((??)++\\]))*+)(\\](?!((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))[\\[\\];]))", + "end": "(?<=[;}])|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?=\\]|\\z|$)|(,))|(\\=))", + "match": "((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?=\\]|\\z|$)|(,))|(\\=))", "captures": { "1": { "name": "variable.parameter.capture.cpp" @@ -5815,7 +5251,7 @@ "2": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -5850,26 +5286,52 @@ } ] }, - "5": { + "3": {}, + "4": { "name": "punctuation.definition.capture.end.lambda.cpp" + }, + "5": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "6": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "7": { + "name": "comment.block.cpp" + }, + "8": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] } }, - "end": "(?<=})|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*line\\b)", + "begin": "^((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?line\\b", + "end": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*define\\b)\\s*((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?define\\b)(?:(?:\\s)+)?((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?\\*|->)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*(?:(?:(?:\\.\\*|\\.))|(?:(?:->\\*|->)))\\s*)*)\\s*(\\b(?!uint_least64_t[^(?-mix:\\w)]|uint_least16_t[^(?-mix:\\w)]|uint_least32_t[^(?-mix:\\w)]|int_least16_t[^(?-mix:\\w)]|uint_fast64_t[^(?-mix:\\w)]|uint_fast32_t[^(?-mix:\\w)]|uint_fast16_t[^(?-mix:\\w)]|uint_least8_t[^(?-mix:\\w)]|int_least64_t[^(?-mix:\\w)]|int_least32_t[^(?-mix:\\w)]|int_fast32_t[^(?-mix:\\w)]|int_fast16_t[^(?-mix:\\w)]|int_least8_t[^(?-mix:\\w)]|uint_fast8_t[^(?-mix:\\w)]|int_fast64_t[^(?-mix:\\w)]|int_fast8_t[^(?-mix:\\w)]|suseconds_t[^(?-mix:\\w)]|useconds_t[^(?-mix:\\w)]|in_addr_t[^(?-mix:\\w)]|uintmax_t[^(?-mix:\\w)]|uintmax_t[^(?-mix:\\w)]|uintptr_t[^(?-mix:\\w)]|blksize_t[^(?-mix:\\w)]|in_port_t[^(?-mix:\\w)]|intmax_t[^(?-mix:\\w)]|unsigned[^(?-mix:\\w)]|blkcnt_t[^(?-mix:\\w)]|uint32_t[^(?-mix:\\w)]|u_quad_t[^(?-mix:\\w)]|uint16_t[^(?-mix:\\w)]|intmax_t[^(?-mix:\\w)]|uint64_t[^(?-mix:\\w)]|intptr_t[^(?-mix:\\w)]|swblk_t[^(?-mix:\\w)]|wchar_t[^(?-mix:\\w)]|u_short[^(?-mix:\\w)]|qaddr_t[^(?-mix:\\w)]|caddr_t[^(?-mix:\\w)]|daddr_t[^(?-mix:\\w)]|fixpt_t[^(?-mix:\\w)]|nlink_t[^(?-mix:\\w)]|segsz_t[^(?-mix:\\w)]|clock_t[^(?-mix:\\w)]|ssize_t[^(?-mix:\\w)]|int16_t[^(?-mix:\\w)]|int32_t[^(?-mix:\\w)]|int64_t[^(?-mix:\\w)]|uint8_t[^(?-mix:\\w)]|int8_t[^(?-mix:\\w)]|mode_t[^(?-mix:\\w)]|quad_t[^(?-mix:\\w)]|ushort[^(?-mix:\\w)]|u_long[^(?-mix:\\w)]|u_char[^(?-mix:\\w)]|double[^(?-mix:\\w)]|size_t[^(?-mix:\\w)]|signed[^(?-mix:\\w)]|time_t[^(?-mix:\\w)]|key_t[^(?-mix:\\w)]|ino_t[^(?-mix:\\w)]|gid_t[^(?-mix:\\w)]|dev_t[^(?-mix:\\w)]|div_t[^(?-mix:\\w)]|float[^(?-mix:\\w)]|u_int[^(?-mix:\\w)]|uid_t[^(?-mix:\\w)]|short[^(?-mix:\\w)]|off_t[^(?-mix:\\w)]|pid_t[^(?-mix:\\w)]|id_t[^(?-mix:\\w)]|bool[^(?-mix:\\w)]|char[^(?-mix:\\w)]|id_t[^(?-mix:\\w)]|uint[^(?-mix:\\w)]|void[^(?-mix:\\w)]|long[^(?-mix:\\w)]|auto[^(?-mix:\\w)]|int[^(?-mix:\\w)])(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b(?!\\())", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "5": { - "name": "variable.language.this.cpp" - }, - "6": { - "name": "variable.other.object.access.cpp" - }, - "7": { - "name": "punctuation.separator.dot-access.cpp" - }, - "8": { - "name": "punctuation.separator.pointer-access.cpp" - }, - "9": { - "patterns": [ - { - "match": "(?<=(?:\\.\\*|\\.|->|->\\*))\\s*(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?\\*|->)))", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "5": { - "name": "variable.language.this.cpp" - }, - "6": { - "name": "variable.other.object.property.cpp" - }, - "7": { - "name": "punctuation.separator.dot-access.cpp" - }, - "8": { - "name": "punctuation.separator.pointer-access.cpp" - } - } - }, - { - "match": "(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?\\*|->)))", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "5": { - "name": "variable.language.this.cpp" - }, - "6": { - "name": "variable.other.object.access.cpp" - }, - "7": { - "name": "punctuation.separator.dot-access.cpp" - }, - "8": { - "name": "punctuation.separator.pointer-access.cpp" - } - } - }, - { - "include": "#member_access" - }, - { - "include": "#method_access" - } - ] - }, - "10": { - "name": "variable.other.property.cpp" - } - } - }, - "memory_operators": { - "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?:(delete)\\s*(\\[\\])|(delete))|(new))(?!\\w))", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "5": { - "name": "keyword.operator.wordlike.cpp" - }, - "6": { - "name": "keyword.operator.delete.array.cpp" - }, - "7": { - "name": "keyword.operator.delete.array.bracket.cpp" - }, - "8": { - "name": "keyword.operator.delete.cpp" - }, - "9": { - "name": "keyword.operator.new.cpp" - } - } - }, "method_access": { - "begin": "(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?\\*|->)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*(?:(?:(?:\\.\\*|\\.))|(?:(?:->\\*|->)))\\s*)*)\\s*(~?(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*(\\()", + "begin": "(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:(?:\\s)+)?(?:(?:\\.\\*|\\.)|(?:->\\*|->))(?:(?:\\s)+)?)*)(?:(?:\\s)+)?(~?(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)(?:(?:\\s)+)?(\\()", + "end": "\\)|(?=(?|->\\*))\\s*(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?\\*|->)))", + "match": "(?<=(?:\\.\\*|\\.|->|->\\*))(?:(?:\\s)+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))", "captures": { "1": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -6350,12 +5615,12 @@ } }, { - "match": "(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?\\*|->)))", + "match": "(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))", "captures": { "1": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -6392,7 +5657,7 @@ } }, { - "include": "#member_access" + "include": "source.cpp#member_access" }, { "include": "#method_access" @@ -6406,9 +5671,8 @@ "name": "punctuation.section.arguments.begin.bracket.round.function.member.cpp" } }, - "end": "(\\))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((import))\\s*(?:(?:(?:((<)[^>]*(>?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=\\/\\/)))|((\\\")[^\\\"]*(\\\"?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=\\/\\/))))|(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:\\.(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)*((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=(?:\\/\\/|;)))))|((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=(?:\\/\\/|;))))\\s*(;?)", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "5": { - "name": "keyword.control.directive.import.cpp" - }, - "7": { - "name": "string.quoted.other.lt-gt.include.cpp" - }, - "8": { - "name": "punctuation.definition.string.begin.cpp" - }, - "9": { - "name": "punctuation.definition.string.end.cpp" - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "12": { - "name": "comment.block.cpp" - }, - "13": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "14": { - "name": "string.quoted.double.include.cpp" - }, - "15": { - "name": "punctuation.definition.string.begin.cpp" - }, - "16": { - "name": "punctuation.definition.string.end.cpp" - }, - "17": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "18": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "19": { - "name": "comment.block.cpp" - }, - "20": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "21": { - "name": "entity.name.other.preprocessor.macro.include.cpp" - }, - "22": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "23": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "24": { - "name": "comment.block.cpp" - }, - "25": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "26": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "27": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "28": { - "name": "comment.block.cpp" - }, - "29": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "30": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "31": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "32": { - "name": "comment.block.cpp" - }, - "33": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "34": { - "name": "punctuation.terminator.statement.cpp" - } - }, - "name": "meta.preprocessor.import.cpp" - }, "ms_attributes": { - "name": "support.other.attribute.cpp", - "begin": "(__declspec\\()", + "begin": "__declspec\\(", + "end": "\\)|(?=(?(?:(?>[^<>]*)\\g<9>?)+)>)\\s*)?::)*\\s*+)\\s*((?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?(?:(?>[^<>]*)\\g<5>?)+)>)\\s*)?::)*\\s*+)\\s*((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<4>?)+>)(?:\\s)*+)?::)*\\s*+)(?:(?:\\s)+)?((?|\\?\\?>)|(?=(?|\\?\\?>|(?=(?|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?::)*)(operator)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?::)*)(?:(?:((?:delete\\[\\]|delete|new\\[\\]|<=>|<<=|new|>>=|\\->\\*|\\/=|%=|&=|>=|\\|=|\\+\\+|\\-\\-|\\(\\)|\\[\\]|\\->|\\+\\+|<<|>>|\\-\\-|<=|\\^=|==|!=|&&|\\|\\||\\+=|\\-=|\\*=|,|\\+|\\-|!|~|\\*|&|\\*|\\/|%|\\+|\\-|<|>|&|\\^|\\||=))|((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:\\[\\])?)))|(\"\")((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\<|\\())", + "begin": "(?:(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)(operator)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)(?:(?:((?:(?:delete\\[\\])|(?:delete)|(?:new\\[\\])|(?:<=>)|(?:<<=)|(?:new)|(?:>>=)|(?:\\->\\*)|(?:\\/=)|(?:%=)|(?:&=)|(?:>=)|(?:\\|=)|(?:\\+\\+)|(?:\\-\\-)|(?:\\(\\))|(?:\\[\\])|(?:\\->)|(?:\\+\\+)|(?:<<)|(?:>>)|(?:\\-\\-)|(?:<=)|(?:\\^=)|(?:==)|(?:!=)|(?:&&)|(?:\\|\\|)|(?:\\+=)|(?:\\-=)|(?:\\*=)|,|(?:\\+)|(?:\\-)|!|~|(?:\\*)|&|(?:\\*)|(?:\\/)|%|(?:\\+)|(?:\\-)|<|>|&|(?:\\^)|(?:\\|)|=))|((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:\\[\\])?)))|(\"\")((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\<|\\()", + "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -7366,20 +6103,20 @@ } ] }, - "30": { + "20": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, - "31": { + "21": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "32": { + "22": { "name": "comment.block.cpp" }, - "33": { + "23": { "patterns": [ { "match": "\\*\\/", @@ -7391,135 +6128,135 @@ } ] }, - "34": { + "24": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, - "35": { + "25": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "36": { + "26": { "name": "comment.block.cpp" }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "29": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "30": { + "name": "comment.block.cpp" + }, + "31": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "32": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "33": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "34": { + "name": "comment.block.cpp" + }, + "35": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "36": { + "name": "storage.type.modifier.calling-convention.cpp" + }, "37": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "source.cpp#inline_comment" } ] }, "38": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "39": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "40": { + "39": { "name": "comment.block.cpp" }, + "40": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "41": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "source.cpp#inline_comment" } ] }, "42": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "43": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "44": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "45": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "46": { - "name": "storage.type.modifier.calling-convention.cpp" - }, - "47": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "48": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "49": { - "name": "comment.block.cpp" - }, - "50": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "51": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "52": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "53": { - "name": "comment.block.cpp" - }, - "54": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "55": { "patterns": [ { "match": "::", @@ -7534,31 +6271,31 @@ } ] }, - "57": { - "name": "meta.template.call.cpp", + "46": { "patterns": [ { "include": "#template_call_range" } ] }, - "59": { + "47": {}, + "48": { "name": "keyword.other.operator.overload.cpp" }, - "60": { + "49": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, - "61": { + "50": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "62": { + "51": { "name": "comment.block.cpp" }, - "63": { + "52": { "patterns": [ { "match": "\\*\\/", @@ -7570,7 +6307,7 @@ } ] }, - "64": { + "53": { "patterns": [ { "match": "::", @@ -7585,33 +6322,33 @@ } ] }, - "66": { - "name": "meta.template.call.cpp", + "54": { "patterns": [ { "include": "#template_call_range" } ] }, - "68": { + "55": {}, + "56": { "name": "entity.name.operator.cpp" }, - "69": { + "57": { "name": "entity.name.operator.type.cpp" }, - "70": { + "58": { "patterns": [ { "match": "\\*", "name": "entity.name.operator.type.pointer.cpp" }, { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -7642,20 +6379,20 @@ } ] }, - "71": { + "59": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, - "72": { + "60": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "73": { + "61": { "name": "comment.block.cpp" }, - "74": { + "62": { "patterns": [ { "match": "\\*\\/", @@ -7667,104 +6404,104 @@ } ] }, - "75": { + "63": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, - "76": { + "64": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "77": { + "65": { "name": "comment.block.cpp" }, + "66": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "67": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "68": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "69": { + "name": "comment.block.cpp" + }, + "70": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "71": { + "name": "entity.name.operator.type.array.cpp" + }, + "72": { + "name": "entity.name.operator.custom-literal.cpp" + }, + "73": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "74": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "75": { + "name": "comment.block.cpp" + }, + "76": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "77": { + "name": "entity.name.operator.custom-literal.cpp" + }, "78": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "source.cpp#inline_comment" } ] }, "79": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "80": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "81": { - "name": "comment.block.cpp" - }, - "82": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "83": { - "name": "entity.name.operator.type.array.cpp" - }, - "84": { - "name": "entity.name.operator.custom-literal.cpp" - }, - "85": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "86": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "87": { - "name": "comment.block.cpp" - }, - "88": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "89": { - "name": "entity.name.operator.custom-literal.cpp" - }, - "90": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "91": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "92": { - "name": "comment.block.cpp" - }, - "93": { "patterns": [ { "match": "\\*\\/", @@ -7777,17 +6514,19 @@ ] } }, - "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>)|(?=(?|\\?\\?>|(?=(?|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\w)", + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\w)", + "end": "(?:(?=\\))|(,))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))+)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=,|\\)|=)", + "match": "((?:((?:(?:volatile)|(?:register)|(?:restrict)|(?:static)|(?:extern)|(?:const)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))+)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\s)*+(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=,|\\)|=)", "captures": { "1": { "patterns": [ @@ -7998,7 +6994,7 @@ "3": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -8023,7 +7019,7 @@ "7": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -8046,159 +7042,34 @@ ] }, "11": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "12": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "13": { - "name": "comment.block.cpp" - }, - "14": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "15": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "16": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "17": { - "name": "comment.block.cpp" - }, - "18": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "19": { "name": "storage.type.primitive.cpp storage.type.built-in.primitive.cpp" }, - "20": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "21": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "22": { - "name": "comment.block.cpp" - }, - "23": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "24": { + "12": { "name": "storage.type.cpp storage.type.built-in.cpp" }, - "25": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "26": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "27": { - "name": "comment.block.cpp" - }, - "28": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "29": { + "13": { "name": "support.type.posix-reserved.pthread.cpp support.type.built-in.posix-reserved.pthread.cpp" }, - "30": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "31": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "32": { - "name": "comment.block.cpp" - }, - "33": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "34": { + "14": { "name": "support.type.posix-reserved.cpp support.type.built-in.posix-reserved.cpp" }, - "35": { + "15": { "name": "entity.name.type.parameter.cpp" }, - "36": { + "16": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, - "37": { + "17": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "38": { + "18": { "name": "comment.block.cpp" }, - "39": { + "19": { "patterns": [ { "match": "\\*\\/", @@ -8216,15 +7087,16 @@ "include": "#storage_types" }, { - "include": "#scope_resolution_parameter_inner_generated" + "include": "source.cpp#scope_resolution_parameter_inner_generated" }, { - "match": "(?:struct|class|union|enum)", + "match": "(?:(?:struct)|(?:class)|(?:union)|(?:enum))", "name": "storage.type.$0.cpp" }, { "begin": "(?<==)", "end": "(?:(?=\\))|(,))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\)|,|\\[|=|\\n)", + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\)|,|\\[|=|\\n)", "captures": { "1": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -8273,7 +7146,7 @@ "6": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -8301,19 +7174,19 @@ "include": "#attributes_context" }, { - "name": "meta.bracket.square.array.cpp", - "begin": "(\\[)", + "begin": "\\[", + "end": "\\]|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*)", + "match": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*))", "captures": { "0": { "patterns": [ @@ -8337,12 +7210,12 @@ "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -8376,7 +7249,7 @@ "1": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -8401,7 +7274,7 @@ "5": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -8427,528 +7300,14 @@ } ] }, - "parameter_class": { - "match": "(class)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:\\[((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\]((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?=,|\\)|\\n)", - "captures": { - "1": { - "name": "storage.type.class.parameter.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "entity.name.type.class.parameter.cpp" - }, - "7": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "8": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "9": { - "name": "comment.block.cpp" - }, - "10": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "11": { - "patterns": [ - { - "match": "\\*", - "name": "storage.modifier.pointer.cpp" - }, - { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - }, - "name": "invalid.illegal.reference-type.cpp" - }, - { - "match": "\\&", - "name": "storage.modifier.reference.cpp" - } - ] - }, - "12": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "14": { - "name": "comment.block.cpp" - }, - "15": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "16": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "17": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "18": { - "name": "comment.block.cpp" - }, - "19": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "20": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "21": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "22": { - "name": "comment.block.cpp" - }, - "23": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "24": { - "name": "variable.other.object.declare.cpp" - }, - "25": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "26": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "27": { - "name": "comment.block.cpp" - }, - "28": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "29": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "31": { - "name": "comment.block.cpp" - }, - "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - } - }, - "parameter_enum": { - "match": "(enum)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:\\[((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\]((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?=,|\\)|\\n)", - "captures": { - "1": { - "name": "storage.type.enum.parameter.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "entity.name.type.enum.parameter.cpp" - }, - "7": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "8": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "9": { - "name": "comment.block.cpp" - }, - "10": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "11": { - "patterns": [ - { - "match": "\\*", - "name": "storage.modifier.pointer.cpp" - }, - { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - }, - "name": "invalid.illegal.reference-type.cpp" - }, - { - "match": "\\&", - "name": "storage.modifier.reference.cpp" - } - ] - }, - "12": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "14": { - "name": "comment.block.cpp" - }, - "15": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "16": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "17": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "18": { - "name": "comment.block.cpp" - }, - "19": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "20": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "21": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "22": { - "name": "comment.block.cpp" - }, - "23": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "24": { - "name": "variable.other.object.declare.cpp" - }, - "25": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "26": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "27": { - "name": "comment.block.cpp" - }, - "28": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "29": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "31": { - "name": "comment.block.cpp" - }, - "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - } - }, "parameter_or_maybe_value": { - "name": "meta.parameter.cpp", - "begin": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\w)", + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\w)", + "end": "(?:(?=\\))|(,))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))+)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=,|\\)|=)", + "match": "((?:((?:(?:volatile)|(?:register)|(?:restrict)|(?:static)|(?:extern)|(?:const)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))+)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\s)*+(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=,|\\)|=)", "captures": { "1": { "patterns": [ @@ -9015,7 +7374,7 @@ "3": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -9040,7 +7399,7 @@ "7": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -9063,159 +7422,34 @@ ] }, "11": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "12": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "13": { - "name": "comment.block.cpp" - }, - "14": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "15": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "16": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "17": { - "name": "comment.block.cpp" - }, - "18": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "19": { "name": "storage.type.primitive.cpp storage.type.built-in.primitive.cpp" }, - "20": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "21": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "22": { - "name": "comment.block.cpp" - }, - "23": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "24": { + "12": { "name": "storage.type.cpp storage.type.built-in.cpp" }, - "25": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "26": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "27": { - "name": "comment.block.cpp" - }, - "28": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "29": { + "13": { "name": "support.type.posix-reserved.pthread.cpp support.type.built-in.posix-reserved.pthread.cpp" }, - "30": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "31": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "32": { - "name": "comment.block.cpp" - }, - "33": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "34": { + "14": { "name": "support.type.posix-reserved.cpp support.type.built-in.posix-reserved.cpp" }, - "35": { + "15": { "name": "entity.name.type.parameter.cpp" }, - "36": { + "16": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, - "37": { + "17": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "38": { + "18": { "name": "comment.block.cpp" }, - "39": { + "19": { "patterns": [ { "match": "\\*\\/", @@ -9236,15 +7470,16 @@ "include": "#function_call" }, { - "include": "#scope_resolution_parameter_inner_generated" + "include": "source.cpp#scope_resolution_parameter_inner_generated" }, { - "match": "(?:struct|class|union|enum)", + "match": "(?:(?:struct)|(?:class)|(?:union)|(?:enum))", "name": "storage.type.$0.cpp" }, { "begin": "(?<==)", "end": "(?:(?=\\))|(,))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=(?:\\)|,|\\[|=|\\/\\/|(?:\\n|$)))", + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=(?:\\)|,|\\[|=|\\/\\/|(?:(?:\\n)|$)))", "captures": { "1": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -9290,7 +7525,7 @@ "6": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -9318,19 +7553,19 @@ "include": "#attributes_context" }, { - "name": "meta.bracket.square.array.cpp", - "begin": "(\\[)", + "begin": "\\[", + "end": "\\]|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*)", + "match": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*))", "captures": { "0": { "patterns": [ @@ -9354,12 +7589,12 @@ "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -9393,7 +7628,7 @@ "1": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -9418,7 +7653,7 @@ "5": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -9447,537 +7682,23 @@ } ] }, - "parameter_struct": { - "match": "(struct)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:\\[((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\]((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?=,|\\)|\\n)", - "captures": { - "1": { - "name": "storage.type.struct.parameter.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "entity.name.type.struct.parameter.cpp" - }, - "7": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "8": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "9": { - "name": "comment.block.cpp" - }, - "10": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "11": { - "patterns": [ - { - "match": "\\*", - "name": "storage.modifier.pointer.cpp" - }, - { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - }, - "name": "invalid.illegal.reference-type.cpp" - }, - { - "match": "\\&", - "name": "storage.modifier.reference.cpp" - } - ] - }, - "12": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "14": { - "name": "comment.block.cpp" - }, - "15": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "16": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "17": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "18": { - "name": "comment.block.cpp" - }, - "19": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "20": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "21": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "22": { - "name": "comment.block.cpp" - }, - "23": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "24": { - "name": "variable.other.object.declare.cpp" - }, - "25": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "26": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "27": { - "name": "comment.block.cpp" - }, - "28": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "29": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "31": { - "name": "comment.block.cpp" - }, - "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - } - }, - "parameter_union": { - "match": "(union)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:\\[((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\]((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?=,|\\)|\\n)", - "captures": { - "1": { - "name": "storage.type.union.parameter.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "entity.name.type.union.parameter.cpp" - }, - "7": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "8": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "9": { - "name": "comment.block.cpp" - }, - "10": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "11": { - "patterns": [ - { - "match": "\\*", - "name": "storage.modifier.pointer.cpp" - }, - { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - }, - "name": "invalid.illegal.reference-type.cpp" - }, - { - "match": "\\&", - "name": "storage.modifier.reference.cpp" - } - ] - }, - "12": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "14": { - "name": "comment.block.cpp" - }, - "15": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "16": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "17": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "18": { - "name": "comment.block.cpp" - }, - "19": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "20": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "21": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "22": { - "name": "comment.block.cpp" - }, - "23": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "24": { - "name": "variable.other.object.declare.cpp" - }, - "25": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "26": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "27": { - "name": "comment.block.cpp" - }, - "28": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "29": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "31": { - "name": "comment.block.cpp" - }, - "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - } - }, "parentheses": { - "name": "meta.parens.cpp", - "begin": "(\\()", + "begin": "\\(", + "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*pragma\\b)", + "begin": "^((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?pragma\\b", + "end": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*pragma\\s+mark)\\s+(.*)", - "captures": { - "1": { - "name": "keyword.control.directive.pragma.pragma-mark.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "punctuation.definition.directive.cpp" - }, - "7": { - "name": "entity.name.tag.pragma-mark.cpp" - } - }, - "name": "meta.preprocessor.pragma.cpp" - }, - "predefined_macros": { - "patterns": [ - { - "match": "\\b(__cplusplus|__DATE__|__FILE__|__LINE__|__STDC__|__STDC_HOSTED__|__STDC_NO_COMPLEX__|__STDC_VERSION__|__STDCPP_THREADS__|__TIME__|NDEBUG|__OBJC__|__ASSEMBLER__|__ATOM__|__AVX__|__AVX2__|_CHAR_UNSIGNED|__CLR_VER|_CONTROL_FLOW_GUARD|__COUNTER__|__cplusplus_cli|__cplusplus_winrt|_CPPRTTI|_CPPUNWIND|_DEBUG|_DLL|__FUNCDNAME__|__FUNCSIG__|__FUNCTION__|_INTEGRAL_MAX_BITS|__INTELLISENSE__|_ISO_VOLATILE|_KERNEL_MODE|_M_AMD64|_M_ARM|_M_ARM_ARMV7VE|_M_ARM_FP|_M_ARM64|_M_CEE|_M_CEE_PURE|_M_CEE_SAFE|_M_FP_EXCEPT|_M_FP_FAST|_M_FP_PRECISE|_M_FP_STRICT|_M_IX86|_M_IX86_FP|_M_X64|_MANAGED|_MSC_BUILD|_MSC_EXTENSIONS|_MSC_FULL_VER|_MSC_VER|_MSVC_LANG|__MSVC_RUNTIME_CHECKS|_MT|_NATIVE_WCHAR_T_DEFINED|_OPENMP|_PREFAST|__TIMESTAMP__|_VC_NO_DEFAULTLIB|_WCHAR_T_DEFINED|_WIN32|_WIN64|_WINRT_DLL|_ATL_VER|_MFC_VER|__GFORTRAN__|__GNUC__|__GNUC_MINOR__|__GNUC_PATCHLEVEL__|__GNUG__|__STRICT_ANSI__|__BASE_FILE__|__INCLUDE_LEVEL__|__ELF__|__VERSION__|__OPTIMIZE__|__OPTIMIZE_SIZE__|__NO_INLINE__|__GNUC_STDC_INLINE__|__CHAR_UNSIGNED__|__WCHAR_UNSIGNED__|__REGISTER_PREFIX__|__REGISTER_PREFIX__|__SIZE_TYPE__|__PTRDIFF_TYPE__|__WCHAR_TYPE__|__WINT_TYPE__|__INTMAX_TYPE__|__UINTMAX_TYPE__|__SIG_ATOMIC_TYPE__|__INT8_TYPE__|__INT16_TYPE__|__INT32_TYPE__|__INT64_TYPE__|__UINT8_TYPE__|__UINT16_TYPE__|__UINT32_TYPE__|__UINT64_TYPE__|__INT_LEAST8_TYPE__|__INT_LEAST16_TYPE__|__INT_LEAST32_TYPE__|__INT_LEAST64_TYPE__|__UINT_LEAST8_TYPE__|__UINT_LEAST16_TYPE__|__UINT_LEAST32_TYPE__|__UINT_LEAST64_TYPE__|__INT_FAST8_TYPE__|__INT_FAST16_TYPE__|__INT_FAST32_TYPE__|__INT_FAST64_TYPE__|__UINT_FAST8_TYPE__|__UINT_FAST16_TYPE__|__UINT_FAST32_TYPE__|__UINT_FAST64_TYPE__|__INTPTR_TYPE__|__UINTPTR_TYPE__|__CHAR_BIT__|__SCHAR_MAX__|__WCHAR_MAX__|__SHRT_MAX__|__INT_MAX__|__LONG_MAX__|__LONG_LONG_MAX__|__WINT_MAX__|__SIZE_MAX__|__PTRDIFF_MAX__|__INTMAX_MAX__|__UINTMAX_MAX__|__SIG_ATOMIC_MAX__|__INT8_MAX__|__INT16_MAX__|__INT32_MAX__|__INT64_MAX__|__UINT8_MAX__|__UINT16_MAX__|__UINT32_MAX__|__UINT64_MAX__|__INT_LEAST8_MAX__|__INT_LEAST16_MAX__|__INT_LEAST32_MAX__|__INT_LEAST64_MAX__|__UINT_LEAST8_MAX__|__UINT_LEAST16_MAX__|__UINT_LEAST32_MAX__|__UINT_LEAST64_MAX__|__INT_FAST8_MAX__|__INT_FAST16_MAX__|__INT_FAST32_MAX__|__INT_FAST64_MAX__|__UINT_FAST8_MAX__|__UINT_FAST16_MAX__|__UINT_FAST32_MAX__|__UINT_FAST64_MAX__|__INTPTR_MAX__|__UINTPTR_MAX__|__WCHAR_MIN__|__WINT_MIN__|__SIG_ATOMIC_MIN__|__SCHAR_WIDTH__|__SHRT_WIDTH__|__INT_WIDTH__|__LONG_WIDTH__|__LONG_LONG_WIDTH__|__PTRDIFF_WIDTH__|__SIG_ATOMIC_WIDTH__|__SIZE_WIDTH__|__WCHAR_WIDTH__|__WINT_WIDTH__|__INT_LEAST8_WIDTH__|__INT_LEAST16_WIDTH__|__INT_LEAST32_WIDTH__|__INT_LEAST64_WIDTH__|__INT_FAST8_WIDTH__|__INT_FAST16_WIDTH__|__INT_FAST32_WIDTH__|__INT_FAST64_WIDTH__|__INTPTR_WIDTH__|__INTMAX_WIDTH__|__SIZEOF_INT__|__SIZEOF_LONG__|__SIZEOF_LONG_LONG__|__SIZEOF_SHORT__|__SIZEOF_POINTER__|__SIZEOF_FLOAT__|__SIZEOF_DOUBLE__|__SIZEOF_LONG_DOUBLE__|__SIZEOF_SIZE_T__|__SIZEOF_WCHAR_T__|__SIZEOF_WINT_T__|__SIZEOF_PTRDIFF_T__|__BYTE_ORDER__|__ORDER_LITTLE_ENDIAN__|__ORDER_BIG_ENDIAN__|__ORDER_PDP_ENDIAN__|__FLOAT_WORD_ORDER__|__DEPRECATED|__EXCEPTIONS|__GXX_RTTI|__USING_SJLJ_EXCEPTIONS__|__GXX_EXPERIMENTAL_CXX0X__|__GXX_WEAK__|__NEXT_RUNTIME__|__LP64__|_LP64|__SSP__|__SSP_ALL__|__SSP_STRONG__|__SSP_EXPLICIT__|__SANITIZE_ADDRESS__|__SANITIZE_THREAD__|__GCC_HAVE_SYNC_COMPARE_AND_SWAP_1|__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2|__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4|__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8|__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16|__HAVE_SPECULATION_SAFE_VALUE|__GCC_HAVE_DWARF2_CFI_ASM|__FP_FAST_FMA|__FP_FAST_FMAF|__FP_FAST_FMAL|__FP_FAST_FMAF16|__FP_FAST_FMAF32|__FP_FAST_FMAF64|__FP_FAST_FMAF128|__FP_FAST_FMAF32X|__FP_FAST_FMAF64X|__FP_FAST_FMAF128X|__GCC_IEC_559|__GCC_IEC_559_COMPLEX|__NO_MATH_ERRNO__|__has_builtin|__has_feature|__has_extension|__has_cpp_attribute|__has_c_attribute|__has_attribute|__has_declspec_attribute|__is_identifier|__has_include|__has_include_next|__has_warning|__BASE_FILE__|__FILE_NAME__|__clang__|__clang_major__|__clang_minor__|__clang_patchlevel__|__clang_version__|__fp16|_Float16)\\b", - "captures": { - "1": { - "name": "entity.name.other.preprocessor.macro.predefined.$1.cpp" - } - } - }, - { - "match": "\\b__([A-Z_]+)__\\b", - "name": "entity.name.other.preprocessor.macro.predefined.probably.$1.cpp" + "include": "source.cpp#line_continuation_character" } ] }, @@ -10142,30 +7775,31 @@ "include": "#comments" }, { - "include": "#language_constants" + "include": "source.cpp#language_constants" }, { - "include": "#string_context_c" + "include": "#string_context" }, { - "include": "#preprocessor_number_literal" + "include": "source.cpp#d9bc4796b0b_preprocessor_number_literal" }, { "include": "#operators" }, { - "include": "#predefined_macros" + "include": "source.cpp#predefined_macros" }, { - "include": "#macro_name" + "include": "source.cpp#macro_name" }, { - "include": "#line_continuation_character" + "include": "source.cpp#line_continuation_character" } ] }, "preprocessor_conditional_defined": { "begin": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*((?:(?:ifndef|ifdef)|if)))", + "begin": "^((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?((?:(?:ifndef|ifdef)|if))", + "end": "^(?!\\s*+#\\s*(?:else|endif))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<26>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<26>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<26>?)+)>)\\s*)?(?![\\w<:.])", - "captures": { - "0": { - "name": "meta.qualified_type.cpp", - "patterns": [ - { - "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", - "captures": { - "0": { - "patterns": [ - { - "include": "#scope_resolution_inner_generated" - } - ] - }, - "1": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" - }, - "3": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - } - } - }, - "scope_resolution_function_call": { - "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", - "captures": { - "0": { - "patterns": [ - { - "include": "#scope_resolution_function_call_inner_generated" - } - ] - }, - "1": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.call.cpp" - }, - "3": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - } - } - }, - "scope_resolution_function_call_inner_generated": { - "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", - "captures": { - "1": { - "patterns": [ - { - "include": "#scope_resolution_function_call_inner_generated" - } - ] - }, - "2": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.call.cpp" - }, - "4": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "6": { - "name": "entity.name.scope-resolution.function.call.cpp" - }, - "7": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "9": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.call.cpp" - } - } - }, - "scope_resolution_function_definition": { - "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", - "captures": { - "0": { - "patterns": [ - { - "include": "#scope_resolution_function_definition_inner_generated" - } - ] - }, - "1": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.cpp" - }, - "3": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - } - } - }, - "scope_resolution_function_definition_inner_generated": { - "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", - "captures": { - "1": { - "patterns": [ - { - "include": "#scope_resolution_function_definition_inner_generated" - } - ] - }, - "2": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.cpp" - }, - "4": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "6": { - "name": "entity.name.scope-resolution.function.definition.cpp" - }, - "7": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "9": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.cpp" - } - } - }, - "scope_resolution_function_definition_operator_overload": { - "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", - "captures": { - "0": { - "patterns": [ - { - "include": "#scope_resolution_function_definition_operator_overload_inner_generated" - } - ] - }, - "1": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.operator-overload.cpp" - }, - "3": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - } - } - }, - "scope_resolution_function_definition_operator_overload_inner_generated": { - "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", - "captures": { - "1": { - "patterns": [ - { - "include": "#scope_resolution_function_definition_operator_overload_inner_generated" - } - ] - }, - "2": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.operator-overload.cpp" - }, - "4": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "6": { - "name": "entity.name.scope-resolution.function.definition.operator-overload.cpp" - }, - "7": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "9": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.operator-overload.cpp" - } - } - }, - "scope_resolution_inner_generated": { - "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", - "captures": { - "1": { - "patterns": [ - { - "include": "#scope_resolution_inner_generated" - } - ] - }, - "2": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" - }, - "4": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "6": { - "name": "entity.name.scope-resolution.cpp" - }, - "7": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "9": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" - } - } - }, - "scope_resolution_namespace_alias": { - "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", - "captures": { - "0": { - "patterns": [ - { - "include": "#scope_resolution_namespace_alias_inner_generated" - } - ] - }, - "1": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.alias.cpp" - }, - "3": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - } - } - }, - "scope_resolution_namespace_alias_inner_generated": { - "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", - "captures": { - "1": { - "patterns": [ - { - "include": "#scope_resolution_namespace_alias_inner_generated" - } - ] - }, - "2": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.alias.cpp" - }, - "4": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "6": { - "name": "entity.name.scope-resolution.namespace.alias.cpp" - }, - "7": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "9": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.alias.cpp" - } - } - }, - "scope_resolution_namespace_block": { - "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", - "captures": { - "0": { - "patterns": [ - { - "include": "#scope_resolution_namespace_block_inner_generated" - } - ] - }, - "1": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.block.cpp" - }, - "3": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - } - } - }, - "scope_resolution_namespace_block_inner_generated": { - "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", - "captures": { - "1": { - "patterns": [ - { - "include": "#scope_resolution_namespace_block_inner_generated" - } - ] - }, - "2": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.block.cpp" - }, - "4": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "6": { - "name": "entity.name.scope-resolution.namespace.block.cpp" - }, - "7": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "9": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.block.cpp" - } - } - }, - "scope_resolution_namespace_using": { - "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", - "captures": { - "0": { - "patterns": [ - { - "include": "#scope_resolution_namespace_using_inner_generated" - } - ] - }, - "1": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.using.cpp" - }, - "3": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - } - } - }, - "scope_resolution_namespace_using_inner_generated": { - "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", - "captures": { - "1": { - "patterns": [ - { - "include": "#scope_resolution_namespace_using_inner_generated" - } - ] - }, - "2": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.using.cpp" - }, - "4": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "6": { - "name": "entity.name.scope-resolution.namespace.using.cpp" - }, - "7": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "9": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.using.cpp" - } - } - }, - "scope_resolution_parameter": { - "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", - "captures": { - "0": { - "patterns": [ - { - "include": "#scope_resolution_parameter_inner_generated" - } - ] - }, - "1": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.parameter.cpp" - }, - "3": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - } - } - }, - "scope_resolution_parameter_inner_generated": { - "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", - "captures": { - "1": { - "patterns": [ - { - "include": "#scope_resolution_parameter_inner_generated" - } - ] - }, - "2": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.parameter.cpp" - }, - "4": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "6": { - "name": "entity.name.scope-resolution.parameter.cpp" - }, - "7": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "9": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.parameter.cpp" - } - } - }, - "scope_resolution_template_call": { - "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", - "captures": { - "0": { - "patterns": [ - { - "include": "#scope_resolution_template_call_inner_generated" - } - ] - }, - "1": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp" - }, - "3": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - } - } - }, - "scope_resolution_template_call_inner_generated": { - "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", - "captures": { - "1": { - "patterns": [ - { - "include": "#scope_resolution_template_call_inner_generated" - } - ] - }, - "2": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp" - }, - "4": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "6": { - "name": "entity.name.scope-resolution.template.call.cpp" - }, - "7": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "9": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp" - } - } - }, - "scope_resolution_template_definition": { - "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", - "captures": { - "0": { - "patterns": [ - { - "include": "#scope_resolution_template_definition_inner_generated" - } - ] - }, - "1": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.definition.cpp" - }, - "3": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - } - } - }, - "scope_resolution_template_definition_inner_generated": { - "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", - "captures": { - "1": { - "patterns": [ - { - "include": "#scope_resolution_template_definition_inner_generated" - } - ] - }, - "2": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.definition.cpp" - }, - "4": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "6": { - "name": "entity.name.scope-resolution.template.definition.cpp" - }, - "7": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "9": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.definition.cpp" - } - } - }, - "semicolon": { - "match": ";", - "name": "punctuation.terminator.statement.cpp" - }, - "simple_type": { - "match": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?", - "captures": { - "1": { - "name": "meta.qualified_type.cpp", - "patterns": [ - { - "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - }, - "name": "invalid.illegal.reference-type.cpp" - }, - { - "match": "\\&", - "name": "storage.modifier.reference.cpp" - } - ] - }, - "29": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "31": { - "name": "comment.block.cpp" - }, - "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - } - }, - "single_line_macro": { - "match": "^((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))#define.*(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", + "begin": "(\\bsizeof\\.\\.\\.)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?={)|(?:((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:(?!:)))?)", + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", + "captures": { + "1": { + "name": "storage.type.modifier.final.cpp" + }, + "2": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=:|{|$)", + "captures": { + "1": { + "name": "entity.name.type.struct.cpp" + }, + "2": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "storage.type.modifier.final.cpp" + }, + "7": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": "DLLEXPORT", + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.other.preprocessor.macro.predefined.probably.$0.cpp" + } + ] + }, "12": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "source.cpp#inline_comment" } ] }, "13": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "14": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "15": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "16": { + "14": { "name": "comment.block.cpp" }, - "17": { + "15": { "patterns": [ { "match": "\\*\\/", @@ -12252,134 +8531,35 @@ } ] }, - "18": { + "16": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "source.cpp#inline_comment" } ] }, + "17": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "18": { + "name": "comment.block.cpp" + }, "19": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "20": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "21": { - "name": "comment.block.cpp" - }, - "22": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "23": { - "name": "entity.name.type.$3.cpp" - }, - "24": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "25": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "26": { - "name": "comment.block.cpp" - }, - "27": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "28": { - "name": "storage.type.modifier.final.cpp" - }, - "29": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "31": { - "name": "comment.block.cpp" - }, - "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "37": { "name": "punctuation.separator.colon.inheritance.cpp" - }, - "38": { - "patterns": [ - { - "include": "#inheritance_context" - } - ] } }, - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>)|(?=(?|\\?\\?>|(?=(?|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\S)(?![:{])", - "captures": { - "1": { - "name": "storage.type.struct.declare.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "entity.name.type.struct.cpp" - }, - "7": { - "patterns": [ - { - "match": "\\*", - "name": "storage.modifier.pointer.cpp" - }, - { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - }, - "name": "invalid.illegal.reference-type.cpp" - }, - { - "match": "\\&", - "name": "storage.modifier.reference.cpp" - } - ] - }, - "8": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "9": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "10": { - "name": "comment.block.cpp" - }, - "11": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "12": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "14": { - "name": "comment.block.cpp" - }, - "15": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "16": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "17": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "18": { - "name": "comment.block.cpp" - }, - "19": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "20": { - "name": "variable.other.object.declare.cpp" - }, - "21": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "22": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "23": { - "name": "comment.block.cpp" - }, - "24": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - } - }, "switch_conditional_parentheses": { - "name": "meta.conditional.switch.cpp", - "begin": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>)|(?=(?|\\?\\?>|(?=(?|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)|(?=(?(?:(?>[^<>]*)\\g<1>?)+)>)\\s*", - "captures": { - "0": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - } - } - }, "template_call_range": { - "name": "meta.template.call.cpp", - "begin": "(<)", + "begin": "<", + "end": ">|(?=(?)|(?=(?|(?=(?)|(?=(?|(?=(?)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)|((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s+)+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))|((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*(\\.\\.\\.)\\s*((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*(?:(,)|(?=>|$))", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "5": { - "name": "storage.type.template.argument.$5.cpp" - }, - "6": { - "patterns": [ - { - "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", - "name": "storage.type.template.argument.$0.cpp" - } - ] - }, - "7": { - "name": "entity.name.type.template.cpp" - }, - "8": { - "name": "storage.type.template.cpp" - }, - "9": { - "name": "punctuation.vararg-ellipses.template.definition.cpp" - }, - "10": { - "name": "entity.name.type.template.cpp" - }, - "11": { - "name": "punctuation.separator.delimiter.comma.template.argument.cpp" - } - } - }, "template_definition_context": { "patterns": [ { - "include": "#scope_resolution_template_definition_inner_generated" + "include": "source.cpp#scope_resolution_template_definition_inner_generated" }, { - "include": "#template_definition_argument" + "include": "source.cpp#template_definition_argument" }, { - "include": "#template_argument_defaulted" + "include": "source.cpp#template_argument_defaulted" }, { - "include": "#template_call_innards" + "include": "source.cpp#template_call_innards" }, { "include": "#evaluation_context" } ] }, - "template_isolated_definition": { - "match": "(?\\s*$)", - "captures": { - "1": { - "name": "storage.type.template.cpp" - }, - "2": { - "name": "punctuation.section.angle-brackets.start.template.definition.cpp" - }, - "3": { - "name": "meta.template.definition.cpp", - "patterns": [ - { - "include": "#template_definition_context" - } - ] - }, - "4": { - "name": "punctuation.section.angle-brackets.end.template.definition.cpp" - } - } - }, "ternary_operator": { - "applyEndPatternLast": true, - "begin": "(\\?)", + "begin": "\\?", + "end": ":|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\s*)?(?![\\w<:.]))\\s*(\\=)\\s*((?:typename)?)\\s*((?:(?:(?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\s*)?(?![\\w<:.]))|(.*(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:(\\[)(\\w*)(\\])\\s*)?\\s*(?:(;)|\\n)", - "captures": { - "1": { - "name": "keyword.other.using.directive.cpp" - }, - "2": { - "name": "meta.qualified_type.cpp", - "patterns": [ - { - "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - }, - "name": "invalid.illegal.reference-type.cpp" - }, - { - "match": "\\&", - "name": "storage.modifier.reference.cpp" - } - ] - }, - "61": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "62": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "63": { - "name": "comment.block.cpp" - }, - "64": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "65": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "66": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "67": { - "name": "comment.block.cpp" - }, - "68": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "69": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "70": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "71": { - "name": "comment.block.cpp" - }, - "72": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "73": { - "name": "punctuation.definition.begin.bracket.square.cpp" - }, - "74": { - "patterns": [ - { - "include": "#evaluation_context" - } - ] - }, - "75": { - "name": "punctuation.definition.end.bracket.square.cpp" - }, - "76": { - "name": "punctuation.terminator.statement.cpp" - } - }, - "name": "meta.declaration.type.alias.cpp" - }, - "type_casting_operators": { - "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?={)|(?:((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:(?!:)))?)", + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", + "captures": { + "1": { + "name": "storage.type.modifier.final.cpp" + }, + "2": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=:|{|$)", + "captures": { + "1": { + "name": "entity.name.type.class.cpp" + }, + "2": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "storage.type.modifier.final.cpp" + }, + "7": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": "DLLEXPORT", + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.other.preprocessor.macro.predefined.probably.$0.cpp" + } + ] + }, "12": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "source.cpp#inline_comment" } ] }, "13": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "14": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "15": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "16": { + "14": { "name": "comment.block.cpp" }, - "17": { + "15": { "patterns": [ { "match": "\\*\\/", @@ -13759,134 +9200,35 @@ } ] }, - "18": { + "16": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "source.cpp#inline_comment" } ] }, + "17": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "18": { + "name": "comment.block.cpp" + }, "19": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "20": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "21": { - "name": "comment.block.cpp" - }, - "22": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "23": { - "name": "entity.name.type.$3.cpp" - }, - "24": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "25": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "26": { - "name": "comment.block.cpp" - }, - "27": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "28": { - "name": "storage.type.modifier.final.cpp" - }, - "29": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "31": { - "name": "comment.block.cpp" - }, - "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "37": { "name": "punctuation.separator.colon.inheritance.cpp" - }, - "38": { - "patterns": [ - { - "include": "#inheritance_context" - } - ] } }, - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>)|(?=(?|\\?\\?>|(?=(?|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -13998,7 +9345,7 @@ "2": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -14023,7 +9370,7 @@ "6": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -14048,7 +9395,7 @@ "10": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -14085,47 +9432,67 @@ ] }, "typedef_function_pointer": { - "begin": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()(\\*)\\s*((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)\\s*(?:(\\[)(\\w*)(\\])\\s*)*(\\))\\s*(\\()", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", + "end": "(\\))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=[{=,);>]|\\n)(?!\\()|(?=(?|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -14304,111 +9652,110 @@ } ] }, - "29": { + "20": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, + "21": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "22": { + "name": "comment.block.cpp" + }, + "23": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "24": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "29": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "31": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "37": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "38": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "39": { - "name": "comment.block.cpp" - }, - "40": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "41": { "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cpp" }, - "42": { + "33": { "name": "punctuation.definition.function.pointer.dereference.cpp" }, - "43": { + "34": { "name": "entity.name.type.alias.cpp entity.name.type.pointer.function.cpp" }, - "44": { + "35": { "name": "punctuation.definition.begin.bracket.square.cpp" }, - "45": { + "36": { "patterns": [ { "include": "#evaluation_context" } ] }, - "46": { + "37": { "name": "punctuation.definition.end.bracket.square.cpp" }, - "47": { + "38": { "name": "punctuation.section.parens.end.bracket.round.function.pointer.cpp" }, - "48": { + "39": { "name": "punctuation.section.parameters.begin.bracket.round.function.pointer.cpp" } }, - "end": "(\\))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=[{=,);]|\\n)(?!\\()|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?={)|(?:((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:(?!:)))?)", + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", + "captures": { + "1": { + "name": "storage.type.modifier.final.cpp" + }, + "2": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=:|{|$)", + "captures": { + "1": { + "name": "entity.name.type.struct.cpp" + }, + "2": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "storage.type.modifier.final.cpp" + }, + "7": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": "DLLEXPORT", + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.other.preprocessor.macro.predefined.probably.$0.cpp" + } + ] + }, "12": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "source.cpp#inline_comment" } ] }, "13": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "14": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "15": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "16": { + "14": { "name": "comment.block.cpp" }, - "17": { + "15": { "patterns": [ { "match": "\\*\\/", @@ -14587,134 +10005,35 @@ } ] }, - "18": { + "16": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "source.cpp#inline_comment" } ] }, + "17": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "18": { + "name": "comment.block.cpp" + }, "19": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "20": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "21": { - "name": "comment.block.cpp" - }, - "22": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "23": { - "name": "entity.name.type.$3.cpp" - }, - "24": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "25": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "26": { - "name": "comment.block.cpp" - }, - "27": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "28": { - "name": "storage.type.modifier.final.cpp" - }, - "29": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "31": { - "name": "comment.block.cpp" - }, - "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "37": { "name": "punctuation.separator.colon.inheritance.cpp" - }, - "38": { - "patterns": [ - { - "include": "#inheritance_context" - } - ] } }, - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>)|(?=(?|\\?\\?>|(?=(?|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -14826,7 +10150,7 @@ "2": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -14851,7 +10175,7 @@ "6": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -14876,7 +10200,7 @@ "10": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -14913,101 +10237,205 @@ ] }, "typedef_union": { - "begin": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?={)|(?:((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:(?!:)))?)", + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", + "captures": { + "1": { + "name": "storage.type.modifier.final.cpp" + }, + "2": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=:|{|$)", + "captures": { + "1": { + "name": "entity.name.type.union.cpp" + }, + "2": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "storage.type.modifier.final.cpp" + }, + "7": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": "DLLEXPORT", + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.other.preprocessor.macro.predefined.probably.$0.cpp" + } + ] + }, "12": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "source.cpp#inline_comment" } ] }, "13": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "14": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "15": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "16": { + "14": { "name": "comment.block.cpp" }, - "17": { + "15": { "patterns": [ { "match": "\\*\\/", @@ -15019,134 +10447,35 @@ } ] }, - "18": { + "16": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "source.cpp#inline_comment" } ] }, + "17": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "18": { + "name": "comment.block.cpp" + }, "19": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "20": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "21": { - "name": "comment.block.cpp" - }, - "22": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "23": { - "name": "entity.name.type.$3.cpp" - }, - "24": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "25": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "26": { - "name": "comment.block.cpp" - }, - "27": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "28": { - "name": "storage.type.modifier.final.cpp" - }, - "29": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "31": { - "name": "comment.block.cpp" - }, - "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "37": { "name": "punctuation.separator.colon.inheritance.cpp" - }, - "38": { - "patterns": [ - { - "include": "#inheritance_context" - } - ] } }, - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>)|(?=(?|\\?\\?>|(?=(?|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -15258,7 +10592,7 @@ "2": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -15283,7 +10617,7 @@ "6": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -15308,7 +10642,7 @@ "10": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -15345,8 +10679,8 @@ ] }, "typeid_operator": { - "contentName": "meta.arguments.operator.typeid.cpp", - "begin": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<36>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<36>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<36>?)+)>)\\s*)?(?![\\w<:.]))", - "captures": { + "union_block": { + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?={)|(?:((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:(?!:)))?)", + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", + "captures": { + "1": { + "name": "storage.type.modifier.final.cpp" + }, + "2": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } }, { - "include": "#attributes_context" + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=:|{|$)", + "captures": { + "1": { + "name": "entity.name.type.union.cpp" + }, + "2": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "storage.type.modifier.final.cpp" + }, + "7": { + "patterns": [ + { + "include": "source.cpp#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } }, { - "include": "#function_type" - }, - { - "include": "#storage_types" - }, - { - "include": "#number_literal" - }, - { - "include": "#string_context" - }, - { - "include": "#comma" - }, - { - "include": "#scope_resolution_inner_generated" - }, - { - "include": "#template_call_range" + "match": "DLLEXPORT", + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" }, { "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", - "name": "entity.name.type.cpp" - } - ] - }, - "11": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "name": "entity.name.other.preprocessor.macro.predefined.probably.$0.cpp" } ] }, "12": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -15523,7 +10930,7 @@ "16": { "patterns": [ { - "include": "#inline_comment" + "include": "source.cpp#inline_comment" } ] }, @@ -15545,367 +10952,10 @@ } ] }, - "21": { - "patterns": [ - { - "include": "#scope_resolution_inner_generated" - } - ] - }, - "22": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" - }, - "24": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "26": { - "name": "entity.name.scope-resolution.cpp" - }, - "27": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "29": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" - }, - "30": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "31": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "32": { - "name": "comment.block.cpp" - }, - "33": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "34": { - "name": "entity.name.type.cpp" - }, - "35": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - } - } - }, - "undef": { - "match": "((?:^)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*undef\\b)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", - "beginCaptures": { - "1": { - "name": "meta.head.union.cpp" - }, - "3": { - "name": "storage.type.$3.cpp" - }, - "4": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "5": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "6": { - "name": "comment.block.cpp" - }, - "7": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "8": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "#number_literal" - } - ] - }, - "9": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "10": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "11": { - "name": "comment.block.cpp" - }, - "12": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "13": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "14": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "15": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "16": { - "name": "comment.block.cpp" - }, - "17": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "18": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "#number_literal" - } - ] - }, - "19": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, "20": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "21": { - "name": "comment.block.cpp" - }, - "22": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "23": { - "name": "entity.name.type.$3.cpp" - }, - "24": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "25": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "26": { - "name": "comment.block.cpp" - }, - "27": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "28": { - "name": "storage.type.modifier.final.cpp" - }, - "29": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "31": { - "name": "comment.block.cpp" - }, - "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "37": { "name": "punctuation.separator.colon.inheritance.cpp" - }, - "38": { - "patterns": [ - { - "include": "#inheritance_context" - } - ] } }, - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>)|(?=(?|\\?\\?>|(?=(?|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\S)(?![:{])", - "captures": { - "1": { - "name": "storage.type.union.declare.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "entity.name.type.union.cpp" - }, - "7": { - "patterns": [ - { - "match": "\\*", - "name": "storage.modifier.pointer.cpp" - }, - { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - }, - "name": "invalid.illegal.reference-type.cpp" - }, - { - "match": "\\&", - "name": "storage.modifier.reference.cpp" - } - ] - }, - "8": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "9": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "10": { - "name": "comment.block.cpp" - }, - "11": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "12": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "14": { - "name": "comment.block.cpp" - }, - "15": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "16": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "17": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "18": { - "name": "comment.block.cpp" - }, - "19": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "20": { - "name": "variable.other.object.declare.cpp" - }, - "21": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "22": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "23": { - "name": "comment.block.cpp" - }, - "24": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - } - }, - "using_name": { - "match": "(using)\\s+(?!namespace\\b)", - "captures": { - "1": { - "name": "keyword.other.using.directive.cpp" - } - } - }, "using_namespace": { - "name": "meta.using-namespace.cpp", - "begin": "(?(?:(?>[^<>]*)\\g<7>?)+)>)\\s*)?::)*\\s*+)?((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<6>?)+>)(?:\\s)*+)?::)*\\s*+)?((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(((?:protected|private|public))\\s*(:))", + "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(((?:(?:protected)|(?:private)|(?:public)))(?:(?:\\s)+)?(:))", "captures": { "1": { "patterns": [ @@ -105,45 +105,55 @@ ] }, "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "5": { - "name": "storage.type.modifier.access.control.$6.cpp" + "3": { + "name": "storage.type.modifier.access.control.$4.cpp" }, - "7": { + "4": {}, + "5": { "name": "punctuation.separator.colon.access.control.cpp" } } }, "alignas_attribute": { - "name": "support.other.attribute.cpp", - "begin": "(alignas\\()", + "begin": "alignas\\(", + "end": "\\)", "beginCaptures": { - "1": { + "0": { "name": "punctuation.section.attribute.begin.cpp" } }, - "end": "(\\))", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.attribute.end.cpp" } }, + "name": "support.other.attribute.cpp", "patterns": [ { "include": "#attributes_context" @@ -151,6 +161,8 @@ { "begin": "\\(", "end": "\\)", + "beginCaptures": {}, + "endCaptures": {}, "patterns": [ { "include": "#attributes_context" @@ -161,7 +173,7 @@ ] }, { - "match": "(using)\\s+((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.operator.alignas.cpp" @@ -228,12 +240,12 @@ "name": "punctuation.section.arguments.begin.bracket.round.operator.alignas.cpp" } }, - "end": "(\\))", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.arguments.end.bracket.round.operator.alignas.cpp" } }, + "contentName": "meta.arguments.operator.alignas", "patterns": [ { "include": "#evaluation_context" @@ -241,8 +253,8 @@ ] }, "alignof_operator": { - "contentName": "meta.arguments.operator.alignof.cpp", - "begin": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.operator.alignof.cpp" @@ -276,12 +288,12 @@ "name": "punctuation.section.arguments.begin.bracket.round.operator.alignof.cpp" } }, - "end": "(\\))", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.arguments.end.bracket.round.operator.alignof.cpp" } }, + "contentName": "meta.arguments.operator.alignof", "patterns": [ { "include": "#evaluation_context" @@ -289,96 +301,221 @@ ] }, "assembly": { - "name": "meta.asm.cpp", - "begin": "(\\b(?:__asm__|asm)\\b)\\s*((?:volatile)?)\\s*(\\()", + "begin": "(\\b(?:__asm__|asm)\\b)(?:(?:\\s)+)?((?:volatile)?)", + "end": "(?!\\G)", "beginCaptures": { "1": { "name": "storage.type.asm.cpp" }, "2": { "name": "storage.modifier.cpp" - }, - "3": { - "name": "punctuation.section.parens.begin.bracket.round.assembly.cpp" - } - }, - "end": "(\\))", - "endCaptures": { - "1": { - "name": "punctuation.section.parens.end.bracket.round.assembly.cpp" } }, + "endCaptures": {}, + "name": "meta.asm.cpp", "patterns": [ { - "name": "string.quoted.double.cpp", - "contentName": "meta.embedded.assembly.cpp", - "begin": "(R?)(\")", - "beginCaptures": { + "match": "^((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\n)|$)", + "captures": { "1": { - "name": "meta.encoding.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "2": { - "name": "punctuation.definition.string.begin.assembly.cpp" - } - }, - "end": "(\")", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.assembly.cpp" - } - }, - "patterns": [ - { - "include": "source.asm" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - { - "include": "source.x86" + "3": { + "name": "comment.block.cpp" }, - { - "include": "source.x86_64" - }, - { - "include": "source.arm" - }, - { - "include": "#backslash_escapes" - }, - { - "include": "#string_escaped_char" - }, - { - "match": "(?=not)possible" + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] } - ] - }, - { - "begin": "(\\()", - "beginCaptures": { - "1": { - "name": "punctuation.section.parens.begin.bracket.round.assembly.inner.cpp" - } - }, - "end": "(\\))", - "endCaptures": { - "1": { - "name": "punctuation.section.parens.end.bracket.round.assembly.inner.cpp" - } - }, - "patterns": [ - { - "include": "#evaluation_context" - } - ] - }, - { - "match": ":", - "name": "punctuation.separator.delimiter.colon.assembly.cpp" + } }, { "include": "#comments_context" }, { "include": "#comments" + }, + { + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\(", + "end": "\\)", + "beginCaptures": { + "0": { + "name": "punctuation.section.parens.begin.bracket.round.assembly.cpp" + }, + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.parens.end.bracket.round.assembly.cpp" + } + }, + "patterns": [ + { + "begin": "(R?)(\")", + "end": "\"", + "beginCaptures": { + "1": { + "name": "meta.encoding.cpp" + }, + "2": { + "name": "punctuation.definition.string.begin.assembly.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.assembly.cpp" + } + }, + "name": "string.quoted.double.cpp", + "contentName": "meta.embedded.assembly", + "patterns": [ + { + "include": "source.asm" + }, + { + "include": "source.x86" + }, + { + "include": "source.x86_64" + }, + { + "include": "source.arm" + }, + { + "include": "#backslash_escapes" + }, + { + "include": "#string_escaped_char" + } + ] + }, + { + "begin": "\\(", + "end": "\\)", + "beginCaptures": { + "0": { + "name": "punctuation.section.parens.begin.bracket.round.assembly.inner.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.parens.end.bracket.round.assembly.inner.cpp" + } + }, + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + { + "match": "\\[((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "5": { + "name": "variable.other.asm.label.cpp" + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "8": { + "name": "comment.block.cpp" + }, + "9": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": ":", + "name": "punctuation.separator.delimiter.colon.assembly.cpp" + }, + { + "include": "#comments_context" + }, + { + "include": "#comments" + } + ] } ] }, @@ -403,23 +540,23 @@ ] }, "backslash_escapes": { - "match": "(?x)\\\\ (\n\\\\\t\t\t |\n[abefnprtv'\"?] |\n[0-3]\\d{,2}\t |\n[4-7]\\d?\t\t|\nx[a-fA-F0-9]{,2} |\nu[a-fA-F0-9]{,4} |\nU[a-fA-F0-9]{,8} )", - "name": "constant.character.escape.cpp" + "match": "(?x)\\\\ (\n\\\\\t\t\t |\n[abefnprtv'\"?] |\n[0-3][0-7]{,2}\t |\n[4-7]\\d?\t\t|\nx[a-fA-F0-9]{,2} |\nu[a-fA-F0-9]{,4} |\nU[a-fA-F0-9]{,8} )", + "name": "constant.character.escape" }, "block": { - "name": "meta.block.cpp", - "begin": "({)", + "begin": "{", + "end": "}|(?=\\s*#\\s*(?:elif|else|endif)\\b)", "beginCaptures": { - "1": { + "0": { "name": "punctuation.section.block.begin.bracket.curly.cpp" } }, - "end": "(}|(?=\\s*#\\s*(?:elif|else|endif)\\b))", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.end.bracket.curly.cpp" } }, + "name": "meta.block.cpp", "patterns": [ { "include": "#function_body_context" @@ -427,192 +564,42 @@ ] }, "block_comment": { - "name": "comment.block.cpp", "begin": "\\s*+(\\/\\*)", + "end": "\\*\\/", "beginCaptures": { "1": { "name": "punctuation.definition.comment.begin.cpp" } }, - "end": "(\\*\\/)", "endCaptures": { - "1": { + "0": { "name": "punctuation.definition.comment.end.cpp" } - } + }, + "name": "comment.block.cpp" }, "builtin_storage_type_initilizer": { - "begin": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", + "begin": "(?:\\s)*+(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?={)|(?:((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:(?!:)))?)", + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", "beginCaptures": { - "1": { + "0": { "name": "meta.head.class.cpp" }, - "3": { - "name": "storage.type.$3.cpp" + "1": { + "name": "storage.type.$1.cpp" }, - "4": { + "2": { "patterns": [ { "include": "#inline_comment" } ] }, - "5": { + "3": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "6": { + "4": { "name": "comment.block.cpp" }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, "7": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "8": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "#number_literal" - } - ] - }, - "9": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "10": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "11": { + "9": { "name": "comment.block.cpp" }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", + "captures": { + "1": { + "name": "storage.type.modifier.final.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=:|{|$)", + "captures": { + "1": { + "name": "entity.name.type.class.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "storage.type.modifier.final.cpp" + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": "DLLEXPORT", + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.other.preprocessor.macro.predefined.probably.$0.cpp" + } + ] + }, "12": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "13": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "14": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "15": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "16": { + "14": { "name": "comment.block.cpp" }, - "17": { + "15": { "patterns": [ { "match": "\\*\\/", @@ -768,134 +858,35 @@ } ] }, - "18": { + "16": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "#inline_comment" } ] }, + "17": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "18": { + "name": "comment.block.cpp" + }, "19": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "20": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "21": { - "name": "comment.block.cpp" - }, - "22": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "23": { - "name": "entity.name.type.$3.cpp" - }, - "24": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "25": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "26": { - "name": "comment.block.cpp" - }, - "27": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "28": { - "name": "storage.type.modifier.final.cpp" - }, - "29": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "31": { - "name": "comment.block.cpp" - }, - "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "37": { "name": "punctuation.separator.colon.inheritance.cpp" - }, - "38": { - "patterns": [ - { - "include": "#inheritance_context" - } - ] } }, - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))", "endCaptures": { "1": { "name": "punctuation.terminator.statement.cpp" @@ -904,16 +895,18 @@ "name": "punctuation.terminator.statement.cpp" } }, + "name": "meta.block.class.cpp", "patterns": [ { - "name": "meta.head.class.cpp", "begin": "\\G ?", - "end": "((?:\\{|<%|\\?\\?<|(?=;)))", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.begin.bracket.curly.class.cpp" } }, + "name": "meta.head.class.cpp", "patterns": [ { "include": "#ever_present_context" @@ -927,14 +920,15 @@ ] }, { - "name": "meta.body.class.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "(\\}|%>|\\?\\?>)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.end.bracket.curly.class.cpp" } }, + "name": "meta.body.class.cpp", "patterns": [ { "include": "#function_pointer" @@ -954,9 +948,11 @@ ] }, { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, "name": "meta.tail.class.cpp", - "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -966,7 +962,7 @@ ] }, "class_declare": { - "match": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\S)(?![:{])", + "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{a-zA-Z])", "captures": { "1": { "name": "storage.type.class.declare.cpp" @@ -979,34 +975,43 @@ ] }, "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "6": { + "4": { "name": "entity.name.type.class.cpp" }, - "7": { + "5": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ @@ -1042,6 +1047,40 @@ } ] }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, "8": { "patterns": [ { @@ -1050,98 +1089,100 @@ ] }, "9": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "10": { - "name": "comment.block.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "11": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, "12": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "14": { - "name": "comment.block.cpp" - }, - "15": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "16": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "17": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "18": { - "name": "comment.block.cpp" - }, - "19": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "20": { "name": "variable.other.object.declare.cpp" }, - "21": { + "13": { "patterns": [ { "include": "#inline_comment" } ] }, - "22": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "23": { - "name": "comment.block.cpp" - }, - "24": { + "14": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] } @@ -1158,14 +1199,15 @@ "comments": { "patterns": [ { - "name": "comment.line.double-slash.documentation.cpp", - "begin": "(?:^)(?>\\s*)(\\/\\/[!\\/]+)", + "begin": "^(?:(?:\\s)+)?+(\\/\\/[!\\/]+)", + "end": "(?<=\\n)(?\\s*)\\/\\*[!*]+(?:(?:\\n|$)|(?=\\s)))", + "begin": "(?:(?:\\s)+)?+\\/\\*[!*]+(?:(?:(?:\\n)|$)|(?=\\s))", + "end": "[!*]*\\*\\/", "beginCaptures": { - "1": { + "0": { "name": "punctuation.definition.comment.begin.documentation.cpp" } }, - "end": "([!*]*\\*\\/)", "endCaptures": { - "1": { + "0": { "name": "punctuation.definition.comment.end.documentation.cpp" } }, + "name": "comment.block.documentation.cpp", "patterns": [ { "match": "(?<=[\\s*!\\/])[\\\\@](?:callergraph|callgraph|else|endif|f\\$|f\\[|f\\]|hidecallergraph|hidecallgraph|hiderefby|hiderefs|hideinitializer|htmlinclude|n|nosubgrouping|private|privatesection|protected|protectedsection|public|publicsection|pure|showinitializer|showrefby|showrefs|tableofcontents|\\$|\\#|<|>|%|\"|\\.|=|::|\\||\\-\\-|\\-\\-\\-)\\b(?:\\{[^}]*\\})?", "name": "storage.type.class.doxygen.cpp" }, { - "match": "((?<=[\\s*!\\/])[\\\\@](?:a|em|e))\\s+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@](?:a|em|e))(?:\\s)+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1355,7 +1413,7 @@ } }, { - "match": "((?<=[\\s*!\\/])[\\\\@]b)\\s+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@]b)(?:\\s)+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1366,7 +1424,7 @@ } }, { - "match": "((?<=[\\s*!\\/])[\\\\@](?:c|p))\\s+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@](?:c|p))(?:\\s)+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1385,22 +1443,30 @@ "name": "storage.type.class.doxygen.cpp" }, { - "match": "((?<=[\\s*!\\/])[\\\\@]param)\\s+(\\b\\w+\\b)", + "match": "((?<=[\\s*!\\/])[\\\\@]param)(?:\\s*\\[((?:,?(?:(?:\\s)+)?(?:in|out)(?:(?:\\s)+)?)+)\\])?(?:\\s)+(\\b\\w+\\b)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" }, "2": { + "patterns": [ + { + "match": "in|out", + "name": "keyword.other.parameter.direction.$0.cpp" + } + ] + }, + "3": { "name": "variable.parameter.cpp" } } }, { - "match": "(?<=[\\s*!\\/])[\\\\@](?:arg|attention|author|authors|brief|bug|copyright|date|deprecated|details|exception|invariant|li|note|par|paragraph|param|post|pre|remark|remarks|result|return|returns|retval|sa|see|short|since|test|throw|todo|tparam|version|warning|xrefitem)\\b(?:\\{[^}]*\\})?", + "match": "(?<=[\\s*!\\/])[\\\\@](?:arg|attention|author|authors|brief|bug|copyright|date|deprecated|details|exception|invariant|li|note|par|paragraph|param|post|pre|remark|remarks|result|return|returns|retval|sa|see|short|since|test|throw|throws|todo|tparam|version|warning|xrefitem)\\b(?:\\{[^}]*\\})?", "name": "storage.type.class.doxygen.cpp" }, { - "match": "(?<=[\\s*!\\/])[\\\\@](?:code|cond|docbookonly|dot|htmlonly|internal|latexonly|link|manonly|msc|parblock|rtfonly|secreflist|uml|verbatim|xmlonly|endcode|endcond|enddocbookonly|enddot|endhtmlonly|endinternal|endlatexonly|endlink|endmanonly|endmsc|endparblock|endrtfonly|endsecreflist|enduml|endverbatim|endxmlonly)\\b(?:\\{[^}]*\\})?", + "match": "(?<=[\\s*!\\/])[\\\\@](?:code|cond|docbookonly|dot|htmlonly|internal|latexonly|link|manonly|msc|parblock|rtfonly|secreflist|startuml|verbatim|xmlonly|endcode|endcond|enddocbookonly|enddot|endhtmlonly|endinternal|endlatexonly|endlink|endmanonly|endmsc|endparblock|endrtfonly|endsecreflist|enduml|endverbatim|endxmlonly)\\b(?:\\{[^}]*\\})?", "name": "storage.type.class.doxygen.cpp" }, { @@ -1424,26 +1490,26 @@ ] }, "constructor_inline": { - "name": "meta.function.definition.special.constructor.cpp", - "begin": "(^((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?:constexpr|explicit|mutable|virtual|inline|friend)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:constexpr)|(?:explicit)|(?:mutable)|(?:virtual)|(?:inline)|(?:friend))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?|\\?\\?>)|(?=[;>\\[\\]=]))", "beginCaptures": { - "1": { + "0": { "name": "meta.head.function.definition.special.constructor.cpp" }, - "2": { + "1": { "patterns": [ { "include": "#inline_comment" } ] }, - "3": { + "2": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "4": { + "3": { "name": "comment.block.cpp" }, - "5": { + "4": { "patterns": [ { "match": "\\*\\/", @@ -1455,52 +1521,52 @@ } ] }, + "5": { + "patterns": [ + { + "include": "#functional_specifiers_pre_parameters" + } + ] + }, "6": { "patterns": [ { - "include": "#functional_specifiers_pre_parameters" + "include": "#inline_comment" } ] }, "7": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "8": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "9": { + "8": { "name": "comment.block.cpp" }, + "9": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "10": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "11": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "12": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "13": { "name": "comment.block.cpp" }, - "14": { + "13": { "patterns": [ { "match": "\\*\\/", @@ -1512,23 +1578,23 @@ } ] }, - "15": { + "14": { "name": "storage.type.modifier.calling-convention.cpp" }, - "16": { + "15": { "patterns": [ { "include": "#inline_comment" } ] }, - "17": { + "16": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "18": { + "17": { "name": "comment.block.cpp" }, - "19": { + "18": { "patterns": [ { "match": "\\*\\/", @@ -1540,83 +1606,82 @@ } ] }, - "20": { + "19": { "name": "entity.name.function.constructor.cpp entity.name.function.definition.special.constructor.cpp" } }, - "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", + "endCaptures": {}, + "name": "meta.function.definition.special.constructor.cpp", "patterns": [ { - "name": "meta.head.function.definition.special.constructor.cpp", "begin": "\\G ?", - "end": "((?:\\{|<%|\\?\\?<|(?=;)))", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.begin.bracket.curly.function.definition.special.constructor.cpp" } }, + "name": "meta.head.function.definition.special.constructor.cpp", "patterns": [ { "include": "#ever_present_context" }, { - "patterns": [ - { - "match": "(\\=)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(default)|(delete))", - "captures": { - "1": { - "name": "keyword.operator.assignment.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "keyword.other.default.constructor.cpp" - }, - "7": { - "name": "keyword.other.delete.constructor.cpp" + "match": "(\\=)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(default)|(delete))", + "captures": { + "1": { + "name": "keyword.operator.assignment.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" } - } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "keyword.other.default.constructor.cpp" + }, + "7": { + "name": "keyword.other.delete.constructor.cpp" } - ] + } }, { "include": "#functional_specifiers_pre_parameters" }, { - "begin": "(:)", + "begin": ":", + "end": "(?=\\{)", "beginCaptures": { - "1": { + "0": { "name": "punctuation.separator.initializers.cpp" } }, - "end": "(?=\\{)", + "endCaptures": {}, "patterns": [ { - "contentName": "meta.parameter.initialization.cpp", - "begin": "((?(?:(?>[^<>]*)\\g<3>?)+)>)\\s*)?(\\()", + "begin": "((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?(\\()", + "end": "\\)", "beginCaptures": { "1": { "name": "entity.name.function.call.initializer.cpp" @@ -1629,16 +1694,17 @@ } ] }, + "3": {}, "4": { "name": "punctuation.section.arguments.begin.bracket.round.function.call.initializer.cpp" } }, - "end": "(\\))", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.arguments.end.bracket.round.function.call.initializer.cpp" } }, + "contentName": "meta.parameter.initialization", "patterns": [ { "include": "#evaluation_context" @@ -1646,8 +1712,8 @@ ] }, { - "contentName": "meta.parameter.initialization.cpp", "begin": "((?|\\?\\?>)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.end.bracket.curly.function.definition.special.constructor.cpp" } }, + "name": "meta.body.function.definition.special.constructor.cpp", "patterns": [ { "include": "#function_body_context" @@ -1720,9 +1796,11 @@ ] }, { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, "name": "meta.tail.function.definition.special.constructor.cpp", - "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -1732,26 +1810,26 @@ ] }, "constructor_root": { - "name": "meta.function.definition.special.constructor.cpp", - "begin": "(\\s*+((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<14>?)+)>)\\s*)?::)*)(((?>(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))::((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\16((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\()))", + "begin": "\\s*+((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)(((?>(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))::((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\14((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\())", + "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", "beginCaptures": { - "1": { + "0": { "name": "meta.head.function.definition.special.constructor.cpp" }, - "2": { + "1": { "patterns": [ { "include": "#inline_comment" } ] }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { "name": "comment.block.cpp" }, - "5": { + "4": { "patterns": [ { "match": "\\*\\/", @@ -1763,23 +1841,23 @@ } ] }, - "6": { + "5": { "name": "storage.type.modifier.calling-convention.cpp" }, - "7": { + "6": { "patterns": [ { "include": "#inline_comment" } ] }, - "8": { + "7": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "9": { + "8": { "name": "comment.block.cpp" }, - "10": { + "9": { "patterns": [ { "match": "\\*\\/", @@ -1791,7 +1869,7 @@ } ] }, - "11": { + "10": { "patterns": [ { "match": "::", @@ -1806,15 +1884,15 @@ } ] }, - "13": { - "name": "meta.template.call.cpp", + "11": { "patterns": [ { "include": "#template_call_range" } ] }, - "15": { + "12": {}, + "13": { "patterns": [ { "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?=:)", @@ -1830,45 +1908,46 @@ } ] }, - "17": { + "14": {}, + "15": { "patterns": [ { "include": "#inline_comment" } ] }, + "16": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "17": { + "name": "comment.block.cpp" + }, "18": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "19": { - "name": "comment.block.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "20": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "21": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "22": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "23": { "name": "comment.block.cpp" }, - "24": { + "22": { "patterns": [ { "match": "\\*\\/", @@ -1880,20 +1959,20 @@ } ] }, - "25": { + "23": { "patterns": [ { "include": "#inline_comment" } ] }, - "26": { + "24": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "27": { + "25": { "name": "comment.block.cpp" }, - "28": { + "26": { "patterns": [ { "match": "\\*\\/", @@ -1906,79 +1985,78 @@ ] } }, - "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", + "endCaptures": {}, + "name": "meta.function.definition.special.constructor.cpp", "patterns": [ { - "name": "meta.head.function.definition.special.constructor.cpp", "begin": "\\G ?", - "end": "((?:\\{|<%|\\?\\?<|(?=;)))", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.begin.bracket.curly.function.definition.special.constructor.cpp" } }, + "name": "meta.head.function.definition.special.constructor.cpp", "patterns": [ { "include": "#ever_present_context" }, { - "patterns": [ - { - "match": "(\\=)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(default)|(delete))", - "captures": { - "1": { - "name": "keyword.operator.assignment.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "keyword.other.default.constructor.cpp" - }, - "7": { - "name": "keyword.other.delete.constructor.cpp" + "match": "(\\=)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(default)|(delete))", + "captures": { + "1": { + "name": "keyword.operator.assignment.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" } - } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "keyword.other.default.constructor.cpp" + }, + "7": { + "name": "keyword.other.delete.constructor.cpp" } - ] + } }, { "include": "#functional_specifiers_pre_parameters" }, { - "begin": "(:)", + "begin": ":", + "end": "(?=\\{)", "beginCaptures": { - "1": { + "0": { "name": "punctuation.separator.initializers.cpp" } }, - "end": "(?=\\{)", + "endCaptures": {}, "patterns": [ { - "contentName": "meta.parameter.initialization.cpp", - "begin": "((?(?:(?>[^<>]*)\\g<3>?)+)>)\\s*)?(\\()", + "begin": "((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?(\\()", + "end": "\\)", "beginCaptures": { "1": { "name": "entity.name.function.call.initializer.cpp" @@ -1991,16 +2069,17 @@ } ] }, + "3": {}, "4": { "name": "punctuation.section.arguments.begin.bracket.round.function.call.initializer.cpp" } }, - "end": "(\\))", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.arguments.end.bracket.round.function.call.initializer.cpp" } }, + "contentName": "meta.parameter.initialization", "patterns": [ { "include": "#evaluation_context" @@ -2008,8 +2087,8 @@ ] }, { - "contentName": "meta.parameter.initialization.cpp", "begin": "((?|\\?\\?>)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.end.bracket.curly.function.definition.special.constructor.cpp" } }, + "name": "meta.body.function.definition.special.constructor.cpp", "patterns": [ { "include": "#function_body_context" @@ -2082,9 +2171,11 @@ ] }, { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, "name": "meta.tail.function.definition.special.constructor.cpp", - "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -2094,7 +2185,7 @@ ] }, "control_flow_keywords": { - "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "5": { - "name": "keyword.control.$5.cpp" + "3": { + "name": "keyword.control.$3.cpp" } } }, "cpp_attributes": { - "name": "support.other.attribute.cpp", - "begin": "(\\[\\[)", + "begin": "\\[\\[", + "end": "\\]\\]", "beginCaptures": { - "1": { + "0": { "name": "punctuation.section.attribute.begin.cpp" } }, - "end": "(\\]\\])", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.attribute.end.cpp" } }, + "name": "support.other.attribute.cpp", "patterns": [ { "include": "#attributes_context" @@ -2147,6 +2247,8 @@ { "begin": "\\(", "end": "\\)", + "beginCaptures": {}, + "endCaptures": {}, "patterns": [ { "include": "#attributes_context" @@ -2157,7 +2259,7 @@ ] }, { - "match": "(using)\\s+((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(?![\\w<:.]))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\{)", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\{)", + "end": "\\}", "beginCaptures": { "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cpp" + } + }, + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] }, { "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", @@ -2289,109 +2409,90 @@ } ] }, + "11": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.type.cpp" + }, + { + "match": "(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((import))(?:(?:\\s)+)?(?:(?:(?:((<)[^>]*(>?)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=\\/\\/)))|((\\\")[^\\\"]*((?:\\\")?)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=\\/\\/))))|(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:\\.(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)*((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=(?:\\/\\/|;)))))|((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=(?:\\/\\/|;))))(?:(?:\\s)+)?(;?)", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "3": { + "name": "keyword.control.directive.import.cpp" + }, + "5": { + "name": "string.quoted.other.lt-gt.include.cpp" + }, + "6": { + "name": "punctuation.definition.string.begin.cpp" + }, + "7": { + "name": "punctuation.definition.string.end.cpp" + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "10": { + "name": "string.quoted.double.include.cpp" + }, + "11": { + "name": "punctuation.definition.string.begin.cpp" + }, + "12": { + "name": "punctuation.definition.string.end.cpp" + }, + "13": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "14": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "15": { + "name": "entity.name.other.preprocessor.macro.include.cpp" + }, + "16": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "17": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "18": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "19": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "20": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "21": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "22": { + "name": "punctuation.terminator.statement.cpp" + } + }, + "name": "meta.preprocessor.import.cpp" + }, + "d9bc4796b0b_preprocessor_number_literal": { + "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.other.decltype.cpp storage.type.decltype.cpp" @@ -2437,12 +3030,12 @@ "name": "punctuation.section.arguments.begin.bracket.round.decltype.cpp" } }, - "end": "(\\))", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.arguments.end.bracket.round.decltype.cpp" } }, + "contentName": "meta.arguments.decltype", "patterns": [ { "include": "#evaluation_context" @@ -2450,8 +3043,8 @@ ] }, "decltype_specifier": { - "contentName": "meta.arguments.decltype.cpp", - "begin": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.other.decltype.cpp storage.type.decltype.cpp" @@ -2485,12 +3078,12 @@ "name": "punctuation.section.arguments.begin.bracket.round.decltype.cpp" } }, - "end": "(\\))", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.arguments.end.bracket.round.decltype.cpp" } }, + "contentName": "meta.arguments.decltype", "patterns": [ { "include": "#evaluation_context" @@ -2498,8 +3091,8 @@ ] }, "default_statement": { - "name": "meta.conditional.case.cpp", - "begin": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?:constexpr|explicit|mutable|virtual|inline|friend)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*)(~(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:constexpr)|(?:explicit)|(?:mutable)|(?:virtual)|(?:inline)|(?:friend))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)(~(?|\\?\\?>)|(?=[;>\\[\\]=]))", "beginCaptures": { - "1": { + "0": { "name": "meta.head.function.definition.special.member.destructor.cpp" }, - "2": { + "1": { "patterns": [ { "include": "#inline_comment" } ] }, - "3": { + "2": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "4": { + "3": { "name": "comment.block.cpp" }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "5": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "6": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "7": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "8": { "name": "comment.block.cpp" }, - "9": { + "8": { "patterns": [ { "match": "\\*\\/", @@ -2602,23 +3195,23 @@ } ] }, - "10": { + "9": { "name": "storage.type.modifier.calling-convention.cpp" }, - "11": { + "10": { "patterns": [ { "include": "#inline_comment" } ] }, - "12": { + "11": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "13": { + "12": { "name": "comment.block.cpp" }, - "14": { + "13": { "patterns": [ { "match": "\\*\\/", @@ -2630,27 +3223,27 @@ } ] }, - "15": { + "14": { "patterns": [ { "include": "#functional_specifiers_pre_parameters" } ] }, - "16": { + "15": { "patterns": [ { "include": "#inline_comment" } ] }, - "17": { + "16": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "18": { + "17": { "name": "comment.block.cpp" }, - "19": { + "18": { "patterns": [ { "match": "\\*\\/", @@ -2662,81 +3255,88 @@ } ] }, - "20": { + "19": { "name": "entity.name.function.destructor.cpp entity.name.function.definition.special.member.destructor.cpp" } }, - "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", + "endCaptures": {}, + "name": "meta.function.definition.special.member.destructor.cpp", "patterns": [ { - "name": "meta.head.function.definition.special.member.destructor.cpp", "begin": "\\G ?", - "end": "((?:\\{|<%|\\?\\?<|(?=;)))", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.begin.bracket.curly.function.definition.special.member.destructor.cpp" } }, + "name": "meta.head.function.definition.special.member.destructor.cpp", "patterns": [ { "include": "#ever_present_context" }, { - "patterns": [ - { - "match": "(\\=)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(default)|(delete))", - "captures": { - "1": { - "name": "keyword.operator.assignment.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "keyword.other.default.constructor.cpp" - }, - "7": { - "name": "keyword.other.delete.constructor.cpp" + "match": "(\\=)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(default)|(delete))", + "captures": { + "1": { + "name": "keyword.operator.assignment.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" } - } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "keyword.other.default.constructor.cpp" + }, + "7": { + "name": "keyword.other.delete.constructor.cpp" } - ] + } }, { - "contentName": "meta.function.definition.parameters.special.member.destructor.cpp", - "begin": "(\\()", + "begin": "\\(", + "end": "\\)", "beginCaptures": { - "1": { + "0": { "name": "punctuation.section.parameters.begin.bracket.round.special.member.destructor.cpp" } }, - "end": "(\\))", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.parameters.end.bracket.round.special.member.destructor.cpp" } + }, + "contentName": "meta.function.definition.parameters.special.member.destructor", + "patterns": [] + }, + { + "match": "((?:(?:final)|(?:override)))+", + "captures": { + "1": { + "name": "keyword.operator.wordlike.cpp keyword.operator.$1.cpp" + } } }, { @@ -2745,14 +3345,15 @@ ] }, { - "name": "meta.body.function.definition.special.member.destructor.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "(\\}|%>|\\?\\?>)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.end.bracket.curly.function.definition.special.member.destructor.cpp" } }, + "name": "meta.body.function.definition.special.member.destructor.cpp", "patterns": [ { "include": "#function_body_context" @@ -2760,9 +3361,11 @@ ] }, { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, "name": "meta.tail.function.definition.special.member.destructor.cpp", - "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -2772,26 +3375,26 @@ ] }, "destructor_root": { - "name": "meta.function.definition.special.member.destructor.cpp", - "begin": "(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<14>?)+)>)\\s*)?::)*)(((?>(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))::((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))~\\16((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\()))", + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)(((?>(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))::((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))~\\14((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\())", + "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", "beginCaptures": { - "1": { + "0": { "name": "meta.head.function.definition.special.member.destructor.cpp" }, - "2": { + "1": { "patterns": [ { "include": "#inline_comment" } ] }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { "name": "comment.block.cpp" }, - "5": { + "4": { "patterns": [ { "match": "\\*\\/", @@ -2803,23 +3406,23 @@ } ] }, - "6": { + "5": { "name": "storage.type.modifier.calling-convention.cpp" }, - "7": { + "6": { "patterns": [ { "include": "#inline_comment" } ] }, - "8": { + "7": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "9": { + "8": { "name": "comment.block.cpp" }, - "10": { + "9": { "patterns": [ { "match": "\\*\\/", @@ -2831,7 +3434,7 @@ } ] }, - "11": { + "10": { "patterns": [ { "match": "::", @@ -2846,15 +3449,15 @@ } ] }, - "13": { - "name": "meta.template.call.cpp", + "11": { "patterns": [ { "include": "#template_call_range" } ] }, - "15": { + "12": {}, + "13": { "patterns": [ { "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?=:)", @@ -2870,45 +3473,46 @@ } ] }, - "17": { + "14": {}, + "15": { "patterns": [ { "include": "#inline_comment" } ] }, + "16": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "17": { + "name": "comment.block.cpp" + }, "18": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "19": { - "name": "comment.block.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "20": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "21": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "22": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "23": { "name": "comment.block.cpp" }, - "24": { + "22": { "patterns": [ { "match": "\\*\\/", @@ -2920,20 +3524,20 @@ } ] }, - "25": { + "23": { "patterns": [ { "include": "#inline_comment" } ] }, - "26": { + "24": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "27": { + "25": { "name": "comment.block.cpp" }, - "28": { + "26": { "patterns": [ { "match": "\\*\\/", @@ -2946,77 +3550,84 @@ ] } }, - "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", + "endCaptures": {}, + "name": "meta.function.definition.special.member.destructor.cpp", "patterns": [ { - "name": "meta.head.function.definition.special.member.destructor.cpp", "begin": "\\G ?", - "end": "((?:\\{|<%|\\?\\?<|(?=;)))", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.begin.bracket.curly.function.definition.special.member.destructor.cpp" } }, + "name": "meta.head.function.definition.special.member.destructor.cpp", "patterns": [ { "include": "#ever_present_context" }, { - "patterns": [ - { - "match": "(\\=)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(default)|(delete))", - "captures": { - "1": { - "name": "keyword.operator.assignment.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "keyword.other.default.constructor.cpp" - }, - "7": { - "name": "keyword.other.delete.constructor.cpp" + "match": "(\\=)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(default)|(delete))", + "captures": { + "1": { + "name": "keyword.operator.assignment.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" } - } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "keyword.other.default.constructor.cpp" + }, + "7": { + "name": "keyword.other.delete.constructor.cpp" } - ] + } }, { - "contentName": "meta.function.definition.parameters.special.member.destructor.cpp", - "begin": "(\\()", + "begin": "\\(", + "end": "\\)", "beginCaptures": { - "1": { + "0": { "name": "punctuation.section.parameters.begin.bracket.round.special.member.destructor.cpp" } }, - "end": "(\\))", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.parameters.end.bracket.round.special.member.destructor.cpp" } + }, + "contentName": "meta.function.definition.parameters.special.member.destructor", + "patterns": [] + }, + { + "match": "((?:(?:final)|(?:override)))+", + "captures": { + "1": { + "name": "keyword.operator.wordlike.cpp keyword.operator.$1.cpp" + } } }, { @@ -3025,14 +3636,15 @@ ] }, { - "name": "meta.body.function.definition.special.member.destructor.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "(\\}|%>|\\?\\?>)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.end.bracket.curly.function.definition.special.member.destructor.cpp" } }, + "name": "meta.body.function.definition.special.member.destructor.cpp", "patterns": [ { "include": "#function_body_context" @@ -3040,9 +3652,11 @@ ] }, { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, "name": "meta.tail.function.definition.special.member.destructor.cpp", - "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -3052,8 +3666,8 @@ ] }, "diagnostic": { - "name": "meta.preprocessor.diagnostic.$reference(directive).cpp", - "begin": "((?:^)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*((?:error|warning)))\\b\\s*", + "begin": "(^((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?((?:error|warning)))\\b(?:(?:\\s)+)?", + "end": "(?[#;\\/=*C~]+)(?![#;\\/=*C~]))\\s*.+\\s*\\8\\s*(?:\\n|$)))|(^\\s*((\\/\\*)\\s*?((?>[#;\\/=*C~]+)(?![#;\\/=*C~]))\\s*.+\\s*\\8\\s*\\*\\/)))", + "match": "(?:(^(?:(?:\\s)+)?((\\/\\/)(?:(?:\\s)+)?((?:[#;\\/=*C~]+)++(?![#;\\/=*C~]))(?:(?:\\s)+)?.+(?:(?:\\s)+)?\\4(?:(?:\\s)+)?(?:\\n|$)))|(^(?:(?:\\s)+)?((\\/\\*)(?:(?:\\s)+)?((?:[#;\\/=*C~]+)++(?![#;\\/=*C~]))(?:(?:\\s)+)?.+(?:(?:\\s)+)?\\8(?:(?:\\s)+)?\\*\\/)))", "captures": { "1": { "name": "meta.toc-list.banner.double-slash.cpp" @@ -3174,23 +3792,23 @@ } }, "empty_square_brackets": { - "name": "storage.modifier.array.bracket.square.cpp", - "match": "(?-mix:(?-mix:(?(?:(?>[^<>]*)\\g<15>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<15>?)+)>)\\s*)?(::))?\\s*((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?(::))?(?:(?:\\s)+)?((?|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", "beginCaptures": { - "1": { + "0": { "name": "meta.head.enum.cpp" }, - "2": { + "1": { "name": "storage.type.enum.cpp" }, - "3": { - "name": "storage.type.enum.enum-key.$3.cpp" + "2": { + "name": "storage.type.enum.enum-key.$2.cpp" }, - "4": { + "3": { "patterns": [ { "include": "#attributes_context" @@ -3200,22 +3818,33 @@ } ] }, - "5": { + "4": { "name": "entity.name.type.enum.cpp" }, - "6": { + "5": { "name": "punctuation.separator.colon.type-specifier.cpp" }, - "8": { + "6": { "patterns": [ { "include": "#scope_resolution_inner_generated" } ] }, - "9": { + "7": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, + "8": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": {}, + "10": { + "name": "entity.name.scope-resolution.cpp" + }, "11": { "name": "meta.template.call.cpp", "patterns": [ @@ -3224,25 +3853,14 @@ } ] }, + "12": {}, "13": { - "name": "entity.name.scope-resolution.cpp" - }, - "14": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "16": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, - "17": { - "name": "storage.type.integral.$17.cpp" + "14": { + "name": "storage.type.integral.$14.cpp" } }, - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))", "endCaptures": { "1": { "name": "punctuation.terminator.statement.cpp" @@ -3251,16 +3869,18 @@ "name": "punctuation.terminator.statement.cpp" } }, + "name": "meta.block.enum.cpp", "patterns": [ { - "name": "meta.head.enum.cpp", "begin": "\\G ?", - "end": "((?:\\{|<%|\\?\\?<|(?=;)))", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.begin.bracket.curly.enum.cpp" } }, + "name": "meta.head.enum.cpp", "patterns": [ { "include": "$self" @@ -3268,14 +3888,15 @@ ] }, { - "name": "meta.body.enum.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "(\\}|%>|\\?\\?>)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.end.bracket.curly.enum.cpp" } }, + "name": "meta.body.enum.cpp", "patterns": [ { "include": "#ever_present_context" @@ -3295,9 +3916,11 @@ ] }, { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, "name": "meta.tail.enum.cpp", - "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -3307,7 +3930,7 @@ ] }, "enum_declare": { - "match": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\S)(?![:{])", + "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{a-zA-Z])", "captures": { "1": { "name": "storage.type.enum.declare.cpp" @@ -3320,34 +3943,43 @@ ] }, "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "6": { + "4": { "name": "entity.name.type.enum.cpp" }, - "7": { + "5": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ @@ -3383,6 +4015,40 @@ } ] }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, "8": { "patterns": [ { @@ -3391,105 +4057,107 @@ ] }, "9": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "10": { - "name": "comment.block.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "11": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, "12": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "14": { - "name": "comment.block.cpp" - }, - "15": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "16": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "17": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "18": { - "name": "comment.block.cpp" - }, - "19": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "20": { "name": "variable.other.object.declare.cpp" }, - "21": { + "13": { "patterns": [ { "include": "#inline_comment" } ] }, - "22": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "23": { - "name": "comment.block.cpp" - }, - "24": { + "14": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] } } }, "enumerator_list": { - "match": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "5": { - "name": "keyword.control.exception.$5.cpp" + "3": { + "name": "keyword.control.exception.$3.cpp" } } }, "extern_block": { - "name": "meta.block.extern.cpp", - "begin": "(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(extern)(?=\\s*\\\"))", + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(extern)(?=\\s*\\\")", + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", "beginCaptures": { - "1": { + "0": { "name": "meta.head.extern.cpp" }, - "2": { + "1": { "patterns": [ { "include": "#inline_comment" } ] }, - "3": { + "2": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "4": { + "3": { "name": "comment.block.cpp" }, - "5": { + "4": { "patterns": [ { "match": "\\*\\/", @@ -3726,11 +4394,10 @@ } ] }, - "6": { + "5": { "name": "storage.type.extern.cpp" } }, - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))", "endCaptures": { "1": { "name": "punctuation.terminator.statement.cpp" @@ -3739,16 +4406,18 @@ "name": "punctuation.terminator.statement.cpp" } }, + "name": "meta.block.extern.cpp", "patterns": [ { - "name": "meta.head.extern.cpp", "begin": "\\G ?", - "end": "((?:\\{|<%|\\?\\?<|(?=;)))", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.begin.bracket.curly.extern.cpp" } }, + "name": "meta.head.extern.cpp", "patterns": [ { "include": "$self" @@ -3756,14 +4425,15 @@ ] }, { - "name": "meta.body.extern.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "(\\}|%>|\\?\\?>)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.end.bracket.curly.extern.cpp" } }, + "name": "meta.body.extern.cpp", "patterns": [ { "include": "$self" @@ -3771,9 +4441,11 @@ ] }, { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, "name": "meta.tail.extern.cpp", - "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -3812,7 +4484,7 @@ "include": "#typedef_union" }, { - "include": "#typedef_keyword" + "include": "#misc_keywords" }, { "include": "#standard_declares" @@ -3859,7 +4531,8 @@ ] }, "function_call": { - "begin": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<12>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(((?(?:(?>[^<>]*)\\g<12>?)+)>)\\s*)?(\\()", + "begin": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?(\\()", + "end": "\\)", "beginCaptures": { "1": { "patterns": [ @@ -3871,31 +4544,31 @@ "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.call.cpp" }, - "4": { - "name": "meta.template.call.cpp", + "3": { "patterns": [ { "include": "#template_call_range" } ] }, - "6": { + "4": {}, + "5": { "name": "entity.name.function.call.cpp" }, - "7": { + "6": { "patterns": [ { "include": "#inline_comment" } ] }, - "8": { + "7": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "9": { + "8": { "name": "comment.block.cpp" }, - "10": { + "9": { "patterns": [ { "match": "\\*\\/", @@ -3907,7 +4580,7 @@ } ] }, - "11": { + "10": { "name": "meta.template.call.cpp", "patterns": [ { @@ -3915,13 +4588,13 @@ } ] }, - "13": { + "11": {}, + "12": { "name": "punctuation.section.arguments.begin.bracket.round.function.call.cpp" } }, - "end": "(\\))", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.arguments.end.bracket.round.function.call.cpp" } }, @@ -3932,26 +4605,26 @@ ] }, "function_definition": { - "name": "meta.function.definition.cpp", - "begin": "((?:(?:^|\\G|(?<=;|\\}))|(?<=>))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<70>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<70>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<70>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<70>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\())", + "begin": "(?:(?:^|\\G|(?<=;|\\}))|(?<=>))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)(?:\\s)*+)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\()", + "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", "beginCaptures": { - "1": { + "0": { "name": "meta.head.function.definition.cpp" }, - "2": { + "1": { "patterns": [ { "include": "#inline_comment" } ] }, - "3": { + "2": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "4": { + "3": { "name": "comment.block.cpp" }, - "5": { + "4": { "patterns": [ { "match": "\\*\\/", @@ -3963,38 +4636,48 @@ } ] }, - "6": { + "5": { "name": "storage.type.template.cpp" }, - "7": { + "6": { "patterns": [ { "include": "#inline_comment" } ] }, - "8": { + "7": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "9": { + "8": { "name": "comment.block.cpp" }, + "9": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "10": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + "include": "#attributes_context" }, { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#number_literal" } ] }, "11": { "patterns": [ { - "match": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))", + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", "captures": { "1": { "name": "storage.modifier.$1.cpp" @@ -4029,7 +4712,7 @@ ] }, "12": { - "name": "storage.modifier.$1.cpp" + "name": "storage.modifier.$12.cpp" }, "13": { "patterns": [ @@ -4060,15 +4743,16 @@ "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cpp" + } + }, + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] }, { "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", @@ -4153,52 +4854,43 @@ } ] }, + "27": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.type.cpp" + }, + { + "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ @@ -4264,45 +4946,95 @@ } ] }, - "45": { + "36": { "patterns": [ { "include": "#inline_comment" } ] }, - "46": { + "37": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "47": { + "38": { "name": "comment.block.cpp" }, + "39": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "40": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "41": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "42": { + "name": "comment.block.cpp" + }, + "43": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "44": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "45": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "46": { + "name": "comment.block.cpp" + }, + "47": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "48": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "49": { "patterns": [ { "include": "#inline_comment" } ] }, - "50": { + "49": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "51": { + "50": { "name": "comment.block.cpp" }, - "52": { + "51": { "patterns": [ { "match": "\\*\\/", @@ -4314,6 +5046,9 @@ } ] }, + "52": { + "name": "storage.type.modifier.calling-convention.cpp" + }, "53": { "patterns": [ { @@ -4342,30 +5077,23 @@ "57": { "patterns": [ { - "include": "#inline_comment" + "include": "#scope_resolution_function_definition_inner_generated" } ] }, "58": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.cpp" }, "59": { - "name": "comment.block.cpp" - }, - "60": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#template_call_range" } ] }, + "60": {}, "61": { - "name": "storage.type.modifier.calling-convention.cpp" + "name": "entity.name.function.definition.cpp" }, "62": { "patterns": [ @@ -4391,83 +5119,39 @@ "name": "comment.block.cpp" } ] - }, - "66": { - "patterns": [ - { - "include": "#scope_resolution_function_definition_inner_generated" - } - ] - }, - "67": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.cpp" - }, - "69": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "71": { - "name": "entity.name.function.definition.cpp" - }, - "72": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "73": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "74": { - "name": "comment.block.cpp" - }, - "75": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] } }, - "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", + "endCaptures": {}, + "name": "meta.function.definition.cpp", "patterns": [ { - "name": "meta.head.function.definition.cpp", "begin": "\\G ?", - "end": "((?:\\{|<%|\\?\\?<|(?=;)))", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.begin.bracket.curly.function.definition.cpp" } }, + "name": "meta.head.function.definition.cpp", "patterns": [ { "include": "#ever_present_context" }, { - "contentName": "meta.function.definition.parameters.cpp", - "begin": "(\\()", + "begin": "\\(", + "end": "\\)", "beginCaptures": { - "1": { + "0": { "name": "punctuation.section.parameters.begin.bracket.round.cpp" } }, - "end": "(\\))", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.parameters.end.bracket.round.cpp" } }, + "contentName": "meta.function.definition.parameters", "patterns": [ { "include": "#ever_present_context" @@ -4483,20 +5167,218 @@ } ] }, + { + "match": "(?<=^|\\))(?:(?:\\s)+)?(->)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<23>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<23>?)+>)?(?![\\w<:.]))", + "captures": { + "1": { + "name": "punctuation.definition.function.return-type.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "meta.qualified_type.cpp", + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + }, + { + "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cpp" + } + }, + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.type.cpp" + } + ] + }, + "7": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "10": { + "name": "comment.block.cpp" + }, + "11": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "14": { + "name": "comment.block.cpp" + }, + "15": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "16": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.type.cpp" + }, + { + "match": "(?|\\?\\?>)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.end.bracket.curly.function.definition.cpp" } }, + "name": "meta.body.function.definition.cpp", "patterns": [ { "include": "#function_body_context" @@ -4504,9 +5386,11 @@ ] }, { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, "name": "meta.tail.function.definition.cpp", - "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -4529,21 +5413,23 @@ ] }, "function_pointer": { - "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()(\\*)\\s*((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)\\s*(?:(\\[)(\\w*)(\\])\\s*)*(\\))\\s*(\\()", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", + "end": "(\\))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=[{=,);>]|\\n)(?!\\()", "beginCaptures": { "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cpp" + } + }, + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] }, { "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", @@ -4628,52 +5531,43 @@ } ] }, + "11": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.type.cpp" + }, + { + "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ @@ -4739,111 +5623,110 @@ } ] }, - "29": { + "20": { "patterns": [ { "include": "#inline_comment" } ] }, + "21": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "22": { + "name": "comment.block.cpp" + }, + "23": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "29": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "31": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "37": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "38": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "39": { - "name": "comment.block.cpp" - }, - "40": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "41": { "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cpp" }, - "42": { + "33": { "name": "punctuation.definition.function.pointer.dereference.cpp" }, - "43": { + "34": { "name": "variable.other.definition.pointer.function.cpp" }, - "44": { + "35": { "name": "punctuation.definition.begin.bracket.square.cpp" }, - "45": { + "36": { "patterns": [ { "include": "#evaluation_context" } ] }, - "46": { + "37": { "name": "punctuation.definition.end.bracket.square.cpp" }, - "47": { + "38": { "name": "punctuation.section.parens.end.bracket.round.function.pointer.cpp" }, - "48": { + "39": { "name": "punctuation.section.parameters.begin.bracket.round.function.pointer.cpp" } }, - "end": "(\\))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=[{=,);]|\\n)(?!\\()", "endCaptures": { "1": { "name": "punctuation.section.parameters.end.bracket.round.function.pointer.cpp" @@ -4881,21 +5764,23 @@ ] }, "function_pointer_parameter": { - "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()(\\*)\\s*((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)\\s*(?:(\\[)(\\w*)(\\])\\s*)*(\\))\\s*(\\()", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", + "end": "(\\))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=[{=,);>]|\\n)(?!\\()", "beginCaptures": { "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cpp" + } + }, + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] }, { "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", @@ -4980,52 +5882,43 @@ } ] }, + "11": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.type.cpp" + }, + { + "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ @@ -5091,111 +5974,110 @@ } ] }, - "29": { + "20": { "patterns": [ { "include": "#inline_comment" } ] }, + "21": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "22": { + "name": "comment.block.cpp" + }, + "23": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "29": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "31": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "37": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "38": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "39": { - "name": "comment.block.cpp" - }, - "40": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "41": { "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cpp" }, - "42": { + "33": { "name": "punctuation.definition.function.pointer.dereference.cpp" }, - "43": { + "34": { "name": "variable.parameter.pointer.function.cpp" }, - "44": { + "35": { "name": "punctuation.definition.begin.bracket.square.cpp" }, - "45": { + "36": { "patterns": [ { "include": "#evaluation_context" } ] }, - "46": { + "37": { "name": "punctuation.definition.end.bracket.square.cpp" }, - "47": { + "38": { "name": "punctuation.section.parens.end.bracket.round.function.pointer.cpp" }, - "48": { + "39": { "name": "punctuation.section.parameters.begin.bracket.round.function.pointer.cpp" } }, - "end": "(\\))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=[{=,);]|\\n)(?!\\()", "endCaptures": { "1": { "name": "punctuation.section.parameters.end.bracket.round.function.pointer.cpp" @@ -5233,23 +6115,23 @@ ] }, "functional_specifiers_pre_parameters": { - "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)", + "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)", "captures": { "1": { "name": "keyword.control.goto.cpp" @@ -5312,30 +6196,42 @@ ] }, "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "6": { + "4": { "name": "entity.name.label.call.cpp" } } }, + "identifier": { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*" + }, "include": { - "match": "(?:^)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((#)\\s*((?:include|include_next))\\b)\\s*(?:(?:(?:((<)[^>]*(>?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=\\/\\/)))|((\\\")[^\\\"]*(\\\"?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=\\/\\/))))|(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:\\.(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)*((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=(?:\\/\\/|;)))))|((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=(?:\\/\\/|;))))", + "match": "^((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((#)(?:(?:\\s)+)?((?:include|include_next))\\b)(?:(?:\\s)+)?(?:(?:(?:((<)[^>]*(>?)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=\\/\\/)))|((\\\")[^\\\"]*((?:\\\")?)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=\\/\\/))))|(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:\\.(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)*((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=(?:\\/\\/|;)))))|((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=(?:\\/\\/|;))))", "captures": { "1": { "patterns": [ @@ -5345,172 +6241,226 @@ ] }, "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "5": { - "name": "keyword.control.directive.$7.cpp" + "3": { + "name": "keyword.control.directive.$5.cpp" }, - "6": { + "4": { "name": "punctuation.definition.directive.cpp" }, - "8": { + "6": { "name": "string.quoted.other.lt-gt.include.cpp" }, - "9": { + "7": { "name": "punctuation.definition.string.begin.cpp" }, - "10": { + "8": { "name": "punctuation.definition.string.end.cpp" }, - "11": { + "9": { "patterns": [ { "include": "#inline_comment" } ] }, + "10": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "11": { + "name": "string.quoted.double.include.cpp" + }, "12": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "punctuation.definition.string.begin.cpp" }, "13": { - "name": "comment.block.cpp" + "name": "punctuation.definition.string.end.cpp" }, "14": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "15": { - "name": "string.quoted.double.include.cpp" + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "16": { - "name": "punctuation.definition.string.begin.cpp" + "name": "entity.name.other.preprocessor.macro.include.cpp" }, "17": { - "name": "punctuation.definition.string.end.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "18": { "patterns": [ { - "include": "#inline_comment" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, "19": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "20": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "21": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "22": { - "name": "entity.name.other.preprocessor.macro.include.cpp" - }, - "23": { "patterns": [ { - "include": "#inline_comment" - } - ] - }, - "24": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "25": { - "name": "comment.block.cpp" - }, - "26": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "27": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "28": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "29": { - "name": "comment.block.cpp" - }, - "30": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "31": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "32": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "33": { - "name": "comment.block.cpp" - }, - "34": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] } @@ -5527,7 +6477,7 @@ "name": "punctuation.separator.delimiter.comma.inheritance.cpp" }, { - "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(?![\\w<:.]))", + "match": "(?<=protected|virtual|private|public|,|:)(?:(?:\\s)+)?(?!(?:(?:(?:protected)|(?:private)|(?:public))|virtual))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)?(?![\\w<:.]))", "captures": { "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cpp" + } + }, + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] }, { "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", @@ -5592,122 +6560,147 @@ ] }, "4": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "5": { - "name": "comment.block.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "6": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, "7": { "patterns": [ { - "include": "#inline_comment" + "match": "::", + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.type.cpp" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "25": { - "name": "entity.name.type.cpp" - }, - "26": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - } + "12": {} } } ] }, + "inline_builtin_storage_type": { + "match": "(?:\\s)*+(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/))", + "match": "(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/))", "captures": { "1": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" @@ -5734,7 +6727,7 @@ "name": "invalid.illegal.unexpected.punctuation.definition.comment.end.cpp" }, "label": { - "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)", + "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:)", "captures": { "1": { "patterns": [ @@ -5744,70 +6737,89 @@ ] }, "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "5": { + "3": { "name": "entity.name.label.cpp" }, - "6": { + "4": { "patterns": [ { "include": "#inline_comment" } ] }, - "7": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "8": { - "name": "comment.block.cpp" - }, - "9": { + "5": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "10": { + "6": { "name": "punctuation.separator.label.cpp" } } }, "lambdas": { - "begin": "((?:(?<=[^\\s]|^)(?])|(?<=\\Wreturn|^return))\\s*(\\[(?!\\[| *+\"| *+\\d))((?>(?:[^\\[\\]]|((?(?:(?>[^\\[\\]]*)\\g<4>?)+)\\]))*))(\\](?!\\[)))", + "begin": "(?:(?<=[^\\s]|^)(?])|(?<=\\Wreturn|^return))(?:(?:\\s)+)?(\\[(?!\\[| *+\"| *+\\d))((?:[^\\[\\]]|((??)++\\]))*+)(\\](?!((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))[\\[\\];]))", + "end": "(?<=[;}])", "beginCaptures": { - "2": { + "1": { "name": "punctuation.definition.capture.begin.lambda.cpp" }, - "3": { + "2": { "name": "meta.lambda.capture.cpp", "patterns": [ { "include": "#the_this_keyword" }, { - "match": "((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?=\\]|\\z|$)|(,))|(\\=))", + "match": "((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?=\\]|\\z|$)|(,))|(\\=))", "captures": { "1": { "name": "variable.parameter.capture.cpp" @@ -5850,26 +6862,52 @@ } ] }, - "5": { + "3": {}, + "4": { "name": "punctuation.definition.capture.end.lambda.cpp" + }, + "5": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "6": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "7": { + "name": "comment.block.cpp" + }, + "8": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] } }, - "end": "(?<=})", + "endCaptures": {}, "patterns": [ { - "name": "meta.function.definition.parameters.lambda.cpp", - "begin": "(\\()", + "begin": "\\(", + "end": "\\)", "beginCaptures": { - "1": { + "0": { "name": "punctuation.definition.parameters.begin.lambda.cpp" } }, - "end": "(\\))", "endCaptures": { - "1": { + "0": { "name": "punctuation.definition.parameters.end.lambda.cpp" } }, + "name": "meta.function.definition.parameters.lambda.cpp", "patterns": [ { "include": "#function_parameter_context" @@ -5877,7 +6915,7 @@ ] }, { - "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*line\\b)", + "begin": "^((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?line\\b", + "end": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*define\\b)\\s*((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?define\\b)(?:(?:\\s)+)?((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?\\*|->)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*(?:(?:(?:\\.\\*|\\.))|(?:(?:->\\*|->)))\\s*)*)\\s*(\\b(?!uint_least64_t[^(?-mix:\\w)]|uint_least16_t[^(?-mix:\\w)]|uint_least32_t[^(?-mix:\\w)]|int_least16_t[^(?-mix:\\w)]|uint_fast64_t[^(?-mix:\\w)]|uint_fast32_t[^(?-mix:\\w)]|uint_fast16_t[^(?-mix:\\w)]|uint_least8_t[^(?-mix:\\w)]|int_least64_t[^(?-mix:\\w)]|int_least32_t[^(?-mix:\\w)]|int_fast32_t[^(?-mix:\\w)]|int_fast16_t[^(?-mix:\\w)]|int_least8_t[^(?-mix:\\w)]|uint_fast8_t[^(?-mix:\\w)]|int_fast64_t[^(?-mix:\\w)]|int_fast8_t[^(?-mix:\\w)]|suseconds_t[^(?-mix:\\w)]|useconds_t[^(?-mix:\\w)]|in_addr_t[^(?-mix:\\w)]|uintmax_t[^(?-mix:\\w)]|uintmax_t[^(?-mix:\\w)]|uintptr_t[^(?-mix:\\w)]|blksize_t[^(?-mix:\\w)]|in_port_t[^(?-mix:\\w)]|intmax_t[^(?-mix:\\w)]|unsigned[^(?-mix:\\w)]|blkcnt_t[^(?-mix:\\w)]|uint32_t[^(?-mix:\\w)]|u_quad_t[^(?-mix:\\w)]|uint16_t[^(?-mix:\\w)]|intmax_t[^(?-mix:\\w)]|uint64_t[^(?-mix:\\w)]|intptr_t[^(?-mix:\\w)]|swblk_t[^(?-mix:\\w)]|wchar_t[^(?-mix:\\w)]|u_short[^(?-mix:\\w)]|qaddr_t[^(?-mix:\\w)]|caddr_t[^(?-mix:\\w)]|daddr_t[^(?-mix:\\w)]|fixpt_t[^(?-mix:\\w)]|nlink_t[^(?-mix:\\w)]|segsz_t[^(?-mix:\\w)]|clock_t[^(?-mix:\\w)]|ssize_t[^(?-mix:\\w)]|int16_t[^(?-mix:\\w)]|int32_t[^(?-mix:\\w)]|int64_t[^(?-mix:\\w)]|uint8_t[^(?-mix:\\w)]|int8_t[^(?-mix:\\w)]|mode_t[^(?-mix:\\w)]|quad_t[^(?-mix:\\w)]|ushort[^(?-mix:\\w)]|u_long[^(?-mix:\\w)]|u_char[^(?-mix:\\w)]|double[^(?-mix:\\w)]|size_t[^(?-mix:\\w)]|signed[^(?-mix:\\w)]|time_t[^(?-mix:\\w)]|key_t[^(?-mix:\\w)]|ino_t[^(?-mix:\\w)]|gid_t[^(?-mix:\\w)]|dev_t[^(?-mix:\\w)]|div_t[^(?-mix:\\w)]|float[^(?-mix:\\w)]|u_int[^(?-mix:\\w)]|uid_t[^(?-mix:\\w)]|short[^(?-mix:\\w)]|off_t[^(?-mix:\\w)]|pid_t[^(?-mix:\\w)]|id_t[^(?-mix:\\w)]|bool[^(?-mix:\\w)]|char[^(?-mix:\\w)]|id_t[^(?-mix:\\w)]|uint[^(?-mix:\\w)]|void[^(?-mix:\\w)]|long[^(?-mix:\\w)]|auto[^(?-mix:\\w)]|int[^(?-mix:\\w)])(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b(?!\\())", + "match": "(?:((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:(?:\\s)+)?(?:(?:\\.\\*|\\.)|(?:->\\*|->))(?:(?:\\s)+)?)*)(?:(?:\\s)+)?(\\b(?!uint_least32_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_least16_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_least64_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_least32_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_least64_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_fast32_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_fast64_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_least8_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_fast16_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_least16_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_fast16_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_least8_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_fast8_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_fast64_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_fast32_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_fast8_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|suseconds_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|useconds_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|in_addr_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uintmax_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uintmax_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uintmax_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|in_port_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uintptr_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|blksize_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint32_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint64_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|u_quad_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|intmax_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|intmax_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|unsigned[^Pattern.new(\n match: \\/\\w\\/,\n)]|blkcnt_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint16_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|intptr_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|swblk_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|wchar_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|u_short[^Pattern.new(\n match: \\/\\w\\/,\n)]|qaddr_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|caddr_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|daddr_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|fixpt_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|nlink_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|segsz_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|clock_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|ssize_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int16_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int32_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int64_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint8_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int8_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|mode_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|quad_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|ushort[^Pattern.new(\n match: \\/\\w\\/,\n)]|u_long[^Pattern.new(\n match: \\/\\w\\/,\n)]|u_char[^Pattern.new(\n match: \\/\\w\\/,\n)]|double[^Pattern.new(\n match: \\/\\w\\/,\n)]|signed[^Pattern.new(\n match: \\/\\w\\/,\n)]|time_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|size_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|key_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|div_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|ino_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uid_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|gid_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|off_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|pid_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|float[^Pattern.new(\n match: \\/\\w\\/,\n)]|dev_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|u_int[^Pattern.new(\n match: \\/\\w\\/,\n)]|short[^Pattern.new(\n match: \\/\\w\\/,\n)]|bool[^Pattern.new(\n match: \\/\\w\\/,\n)]|id_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint[^Pattern.new(\n match: \\/\\w\\/,\n)]|long[^Pattern.new(\n match: \\/\\w\\/,\n)]|char[^Pattern.new(\n match: \\/\\w\\/,\n)]|void[^Pattern.new(\n match: \\/\\w\\/,\n)]|auto[^Pattern.new(\n match: \\/\\w\\/,\n)]|id_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int[^Pattern.new(\n match: \\/\\w\\/,\n)])(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b(?!\\())", "captures": { "1": { "patterns": [ @@ -6092,39 +7132,48 @@ ] }, "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "5": { + "3": { "name": "variable.language.this.cpp" }, - "6": { + "4": { "name": "variable.other.object.access.cpp" }, - "7": { + "5": { "name": "punctuation.separator.dot-access.cpp" }, - "8": { + "6": { "name": "punctuation.separator.pointer-access.cpp" }, - "9": { + "7": { "patterns": [ { - "match": "(?<=(?:\\.\\*|\\.|->|->\\*))\\s*(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?\\*|->)))", + "match": "(?<=(?:\\.\\*|\\.|->|->\\*))(?:(?:\\s)+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))", "captures": { "1": { "patterns": [ @@ -6166,7 +7215,7 @@ } }, { - "match": "(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?\\*|->)))", + "match": "(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))", "captures": { "1": { "patterns": [ @@ -6215,13 +7264,13 @@ } ] }, - "10": { + "8": { "name": "variable.other.property.cpp" } } }, "memory_operators": { - "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?:(delete)\\s*(\\[\\])|(delete))|(new))(?!\\w))", + "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(delete)(?:(?:\\s)+)?(\\[\\])|(delete))|(new))(?!\\w))", "captures": { "1": { "patterns": [ @@ -6231,42 +7280,52 @@ ] }, "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "5": { + "3": { "name": "keyword.operator.wordlike.cpp" }, - "6": { + "4": { "name": "keyword.operator.delete.array.cpp" }, - "7": { + "5": { "name": "keyword.operator.delete.array.bracket.cpp" }, - "8": { + "6": { "name": "keyword.operator.delete.cpp" }, - "9": { + "7": { "name": "keyword.operator.new.cpp" } } }, "method_access": { - "begin": "(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?\\*|->)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*(?:(?:(?:\\.\\*|\\.))|(?:(?:->\\*|->)))\\s*)*)\\s*(~?(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*(\\()", + "begin": "(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:(?:\\s)+)?(?:(?:\\.\\*|\\.)|(?:->\\*|->))(?:(?:\\s)+)?)*)(?:(?:\\s)+)?(~?(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)(?:(?:\\s)+)?(\\()", + "end": "\\)", "beginCaptures": { "1": { "patterns": [ @@ -6308,7 +7367,7 @@ "9": { "patterns": [ { - "match": "(?<=(?:\\.\\*|\\.|->|->\\*))\\s*(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?\\*|->)))", + "match": "(?<=(?:\\.\\*|\\.|->|->\\*))(?:(?:\\s)+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))", "captures": { "1": { "patterns": [ @@ -6350,7 +7409,7 @@ } }, { - "match": "(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?\\*|->)))", + "match": "(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))", "captures": { "1": { "patterns": [ @@ -6406,9 +7465,8 @@ "name": "punctuation.section.arguments.begin.bracket.round.function.member.cpp" } }, - "end": "(\\))", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.arguments.end.bracket.round.function.member.cpp" } }, @@ -6418,12 +7476,8 @@ } ] }, - "misc_storage_modifiers": { - "match": "\\b(?:export|mutable|typename|thread_local|register|restrict|static|volatile|inline)\\b", - "name": "storage.modifier.$0.cpp" - }, - "module_import": { - "match": "(?:^)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((import))\\s*(?:(?:(?:((<)[^>]*(>?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=\\/\\/)))|((\\\")[^\\\"]*(\\\"?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=\\/\\/))))|(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:\\.(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)*((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=(?:\\/\\/|;)))))|((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=(?:\\/\\/|;))))\\s*(;?)", + "misc_keywords": { + "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "5": { - "name": "keyword.control.directive.import.cpp" - }, - "7": { - "name": "string.quoted.other.lt-gt.include.cpp" - }, - "8": { - "name": "punctuation.definition.string.begin.cpp" - }, - "9": { - "name": "punctuation.definition.string.end.cpp" - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "12": { - "name": "comment.block.cpp" - }, - "13": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "14": { - "name": "string.quoted.double.include.cpp" - }, - "15": { - "name": "punctuation.definition.string.begin.cpp" - }, - "16": { - "name": "punctuation.definition.string.end.cpp" - }, - "17": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "18": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "19": { - "name": "comment.block.cpp" - }, - "20": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "21": { - "name": "entity.name.other.preprocessor.macro.include.cpp" - }, - "22": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "23": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "24": { - "name": "comment.block.cpp" - }, - "25": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "26": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "27": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "28": { - "name": "comment.block.cpp" - }, - "29": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "30": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "31": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "32": { - "name": "comment.block.cpp" - }, - "33": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "34": { - "name": "punctuation.terminator.statement.cpp" + "name": "keyword.other.$3.cpp" } - }, - "name": "meta.preprocessor.import.cpp" + } }, "ms_attributes": { - "name": "support.other.attribute.cpp", - "begin": "(__declspec\\()", + "begin": "__declspec\\(", + "end": "\\)", "beginCaptures": { - "1": { + "0": { "name": "punctuation.section.attribute.begin.cpp" } }, - "end": "(\\))", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.attribute.end.cpp" } }, + "name": "support.other.attribute.cpp", "patterns": [ { "include": "#attributes_context" @@ -6626,6 +7539,8 @@ { "begin": "\\(", "end": "\\)", + "beginCaptures": {}, + "endCaptures": {}, "patterns": [ { "include": "#attributes_context" @@ -6636,7 +7551,7 @@ ] }, { - "match": "(using)\\s+((?(?:(?>[^<>]*)\\g<9>?)+)>)\\s*)?::)*\\s*+)\\s*((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<8>?)+>)(?:\\s)*+)?::)*\\s*+)(?:(?:\\s)+)?((?|\\?\\?>)|(?=[;>\\[\\]=]))", "beginCaptures": { - "1": { + "0": { "name": "meta.head.namespace.cpp" }, - "2": { + "1": { "name": "keyword.other.namespace.definition.cpp storage.type.namespace.definition.cpp" } }, - "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", + "endCaptures": {}, + "name": "meta.block.namespace.cpp", "patterns": [ { - "name": "meta.head.namespace.cpp", "begin": "\\G ?", - "end": "((?:\\{|<%|\\?\\?<|(?=;)))", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.begin.bracket.curly.namespace.cpp" } }, + "name": "meta.head.namespace.cpp", "patterns": [ { "include": "#ever_present_context" @@ -6739,7 +7655,7 @@ "include": "#attributes_context" }, { - "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<5>?)+)>)\\s*)?::)*\\s*+)\\s*((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<4>?)+>)(?:\\s)*+)?::)*\\s*+)(?:(?:\\s)+)?((?|\\?\\?>)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.end.bracket.curly.namespace.cpp" } }, + "name": "meta.body.namespace.cpp", "patterns": [ { "include": "$self" @@ -6788,9 +7705,11 @@ ] }, { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, "name": "meta.tail.namespace.cpp", - "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -6800,8 +7719,8 @@ ] }, "noexcept_operator": { - "contentName": "meta.arguments.operator.noexcept.cpp", - "begin": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.operator.noexcept.cpp" @@ -6835,51 +7754,18 @@ "name": "punctuation.section.arguments.begin.bracket.round.operator.noexcept.cpp" } }, - "end": "(\\))", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.arguments.end.bracket.round.operator.noexcept.cpp" } }, + "contentName": "meta.arguments.operator.noexcept", "patterns": [ { "include": "#evaluation_context" } ] }, - "non_primitive_types": { - "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?::)*)(operator)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?::)*)(?:(?:((?:delete\\[\\]|delete|new\\[\\]|<=>|<<=|new|>>=|\\->\\*|\\/=|%=|&=|>=|\\|=|\\+\\+|\\-\\-|\\(\\)|\\[\\]|\\->|\\+\\+|<<|>>|\\-\\-|<=|\\^=|==|!=|&&|\\|\\||\\+=|\\-=|\\*=|,|\\+|\\-|!|~|\\*|&|\\*|\\/|%|\\+|\\-|<|>|&|\\^|\\||=))|((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:\\[\\])?)))|(\"\")((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\<|\\())", + "begin": "(?:(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)(operator)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)(?:(?:((?:(?:delete\\[\\])|(?:delete)|(?:new\\[\\])|(?:<=>)|(?:<<=)|(?:new)|(?:>>=)|(?:\\->\\*)|(?:\\/=)|(?:%=)|(?:&=)|(?:>=)|(?:\\|=)|(?:\\+\\+)|(?:\\-\\-)|(?:\\(\\))|(?:\\[\\])|(?:\\->)|(?:\\+\\+)|(?:<<)|(?:>>)|(?:\\-\\-)|(?:<=)|(?:\\^=)|(?:==)|(?:!=)|(?:&&)|(?:\\|\\|)|(?:\\+=)|(?:\\-=)|(?:\\*=)|,|(?:\\+)|(?:\\-)|!|~|(?:\\*)|&|(?:\\*)|(?:\\/)|%|(?:\\+)|(?:\\-)|<|>|&|(?:\\^)|(?:\\|)|=))|((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:\\[\\])?)))|(\"\")((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\<|\\()", + "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", "beginCaptures": { - "1": { + "0": { "name": "meta.head.function.definition.special.operator-overload.cpp" }, - "2": { + "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cpp" + } + }, + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] }, { "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", @@ -7195,7 +8101,7 @@ } ] }, - "3": { + "2": { "patterns": [ { "include": "#attributes_context" @@ -7205,102 +8111,93 @@ } ] }, - "4": { + "3": { "patterns": [ { "include": "#inline_comment" } ] }, - "5": { + "4": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "6": { + "5": { "name": "comment.block.cpp" }, + "6": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "7": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "8": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "9": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "10": { + "9": { "name": "comment.block.cpp" }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "11": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + "match": "::", + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.type.cpp" }, { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ @@ -7366,20 +8253,20 @@ } ] }, - "30": { + "20": { "patterns": [ { "include": "#inline_comment" } ] }, - "31": { + "21": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "32": { + "22": { "name": "comment.block.cpp" }, - "33": { + "23": { "patterns": [ { "match": "\\*\\/", @@ -7391,135 +8278,135 @@ } ] }, - "34": { + "24": { "patterns": [ { "include": "#inline_comment" } ] }, - "35": { + "25": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "36": { + "26": { "name": "comment.block.cpp" }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "29": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "30": { + "name": "comment.block.cpp" + }, + "31": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "32": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "33": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "34": { + "name": "comment.block.cpp" + }, + "35": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "36": { + "name": "storage.type.modifier.calling-convention.cpp" + }, "37": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "38": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "39": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "40": { + "39": { "name": "comment.block.cpp" }, + "40": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "41": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "42": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "43": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "44": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "45": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "46": { - "name": "storage.type.modifier.calling-convention.cpp" - }, - "47": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "48": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "49": { - "name": "comment.block.cpp" - }, - "50": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "51": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "52": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "53": { - "name": "comment.block.cpp" - }, - "54": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "55": { "patterns": [ { "match": "::", @@ -7534,31 +8421,31 @@ } ] }, - "57": { - "name": "meta.template.call.cpp", + "46": { "patterns": [ { "include": "#template_call_range" } ] }, - "59": { + "47": {}, + "48": { "name": "keyword.other.operator.overload.cpp" }, - "60": { + "49": { "patterns": [ { "include": "#inline_comment" } ] }, - "61": { + "50": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "62": { + "51": { "name": "comment.block.cpp" }, - "63": { + "52": { "patterns": [ { "match": "\\*\\/", @@ -7570,7 +8457,7 @@ } ] }, - "64": { + "53": { "patterns": [ { "match": "::", @@ -7585,28 +8472,28 @@ } ] }, - "66": { - "name": "meta.template.call.cpp", + "54": { "patterns": [ { "include": "#template_call_range" } ] }, - "68": { + "55": {}, + "56": { "name": "entity.name.operator.cpp" }, - "69": { + "57": { "name": "entity.name.operator.type.cpp" }, - "70": { + "58": { "patterns": [ { "match": "\\*", "name": "entity.name.operator.type.pointer.cpp" }, { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ @@ -7642,20 +8529,20 @@ } ] }, - "71": { + "59": { "patterns": [ { "include": "#inline_comment" } ] }, - "72": { + "60": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "73": { + "61": { "name": "comment.block.cpp" }, - "74": { + "62": { "patterns": [ { "match": "\\*\\/", @@ -7667,104 +8554,104 @@ } ] }, - "75": { + "63": { "patterns": [ { "include": "#inline_comment" } ] }, - "76": { + "64": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "77": { + "65": { "name": "comment.block.cpp" }, + "66": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "67": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "68": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "69": { + "name": "comment.block.cpp" + }, + "70": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "71": { + "name": "entity.name.operator.type.array.cpp" + }, + "72": { + "name": "entity.name.operator.custom-literal.cpp" + }, + "73": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "74": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "75": { + "name": "comment.block.cpp" + }, + "76": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "77": { + "name": "entity.name.operator.custom-literal.cpp" + }, "78": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "79": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "80": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "81": { - "name": "comment.block.cpp" - }, - "82": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "83": { - "name": "entity.name.operator.type.array.cpp" - }, - "84": { - "name": "entity.name.operator.custom-literal.cpp" - }, - "85": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "86": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "87": { - "name": "comment.block.cpp" - }, - "88": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "89": { - "name": "entity.name.operator.custom-literal.cpp" - }, - "90": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "91": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "92": { - "name": "comment.block.cpp" - }, - "93": { "patterns": [ { "match": "\\*\\/", @@ -7777,17 +8664,19 @@ ] } }, - "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", + "endCaptures": {}, + "name": "meta.function.definition.special.operator-overload.cpp", "patterns": [ { - "name": "meta.head.function.definition.special.operator-overload.cpp", "begin": "\\G ?", - "end": "((?:\\{|<%|\\?\\?<|(?=;)))", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.begin.bracket.curly.function.definition.special.operator-overload.cpp" } }, + "name": "meta.head.function.definition.special.operator-overload.cpp", "patterns": [ { "include": "#ever_present_context" @@ -7796,19 +8685,19 @@ "include": "#template_call_range" }, { - "contentName": "meta.function.definition.parameters.special.operator-overload.cpp", - "begin": "(\\()", + "begin": "\\(", + "end": "\\)", "beginCaptures": { - "1": { + "0": { "name": "punctuation.section.parameters.begin.bracket.round.special.operator-overload.cpp" } }, - "end": "(\\))", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.parameters.end.bracket.round.special.operator-overload.cpp" } }, + "contentName": "meta.function.definition.parameters.special.operator-overload", "patterns": [ { "include": "#function_parameter_context" @@ -7827,14 +8716,15 @@ ] }, { - "name": "meta.body.function.definition.special.operator-overload.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "(\\}|%>|\\?\\?>)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.end.bracket.curly.function.definition.special.operator-overload.cpp" } }, + "name": "meta.body.function.definition.special.operator-overload.cpp", "patterns": [ { "include": "#function_body_context" @@ -7842,9 +8732,11 @@ ] }, { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, "name": "meta.tail.function.definition.special.operator-overload.cpp", - "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -7856,22 +8748,292 @@ "operators": { "patterns": [ { - "include": "#sizeof_operator" + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "keyword.operator.functionlike.cpp keyword.operator.sizeof.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "punctuation.section.arguments.begin.bracket.round.operator.sizeof.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.operator.sizeof.cpp" + } + }, + "contentName": "meta.arguments.operator.sizeof", + "patterns": [ + { + "include": "#evaluation_context" + } + ] }, { - "include": "#alignof_operator" + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "keyword.operator.functionlike.cpp keyword.operator.alignof.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "punctuation.section.arguments.begin.bracket.round.operator.alignof.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.operator.alignof.cpp" + } + }, + "contentName": "meta.arguments.operator.alignof", + "patterns": [ + { + "include": "#evaluation_context" + } + ] }, { - "include": "#alignas_operator" + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "keyword.operator.functionlike.cpp keyword.operator.alignas.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "punctuation.section.arguments.begin.bracket.round.operator.alignas.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.operator.alignas.cpp" + } + }, + "contentName": "meta.arguments.operator.alignas", + "patterns": [ + { + "include": "#evaluation_context" + } + ] }, { - "include": "#typeid_operator" + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "keyword.operator.functionlike.cpp keyword.operator.typeid.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "punctuation.section.arguments.begin.bracket.round.operator.typeid.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.operator.typeid.cpp" + } + }, + "contentName": "meta.arguments.operator.typeid", + "patterns": [ + { + "include": "#evaluation_context" + } + ] }, { - "include": "#noexcept_operator" + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "keyword.operator.functionlike.cpp keyword.operator.noexcept.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "punctuation.section.arguments.begin.bracket.round.operator.noexcept.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.operator.noexcept.cpp" + } + }, + "contentName": "meta.arguments.operator.noexcept", + "patterns": [ + { + "include": "#evaluation_context" + } + ] }, { - "include": "#sizeof_variadic_operator" + "begin": "(\\bsizeof\\.\\.\\.)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "keyword.operator.functionlike.cpp keyword.operator.sizeof.variadic.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "punctuation.section.arguments.begin.bracket.round.operator.sizeof.variadic.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.operator.sizeof.variadic.cpp" + } + }, + "contentName": "meta.arguments.operator.sizeof.variadic", + "patterns": [ + { + "include": "#evaluation_context" + } + ] }, { "match": "--", @@ -7920,22 +9082,1326 @@ "over_qualified_types": { "patterns": [ { - "include": "#parameter_struct" + "match": "(struct)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", + "captures": { + "1": { + "name": "storage.type.struct.parameter.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.struct.parameter.cpp" + }, + "5": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "6": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "7": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cpp" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "14": { + "name": "variable.other.object.declare.cpp" + }, + "15": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "16": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "17": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "18": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + } + } }, { - "include": "#parameter_enum" + "match": "(enum)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", + "captures": { + "1": { + "name": "storage.type.enum.parameter.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.enum.parameter.cpp" + }, + "5": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "6": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "7": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cpp" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "14": { + "name": "variable.other.object.declare.cpp" + }, + "15": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "16": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "17": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "18": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + } + } }, { - "include": "#parameter_union" + "match": "(union)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", + "captures": { + "1": { + "name": "storage.type.union.parameter.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.union.parameter.cpp" + }, + "5": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "6": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "7": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cpp" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "14": { + "name": "variable.other.object.declare.cpp" + }, + "15": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "16": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "17": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "18": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + } + } }, { - "include": "#parameter_class" + "match": "(class)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", + "captures": { + "1": { + "name": "storage.type.class.parameter.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.class.parameter.cpp" + }, + "5": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "6": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "7": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cpp" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "14": { + "name": "variable.other.object.declare.cpp" + }, + "15": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "16": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "17": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "18": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + } + } } ] }, "parameter": { - "name": "meta.parameter.cpp", - "begin": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\w)", + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\w)", + "end": "(?:(?=\\))|(,))", "beginCaptures": { "1": { "patterns": [ @@ -7963,12 +10429,12 @@ ] } }, - "end": "(?:(?=\\))|(,))", "endCaptures": { "1": { "name": "punctuation.separator.delimiter.comma.cpp" } }, + "name": "meta.parameter.cpp", "patterns": [ { "include": "#ever_present_context" @@ -7983,7 +10449,7 @@ "include": "#vararg_ellipses" }, { - "match": "((?:((?:volatile|register|restrict|static|extern|const))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))+)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=,|\\)|=)", + "match": "((?:((?:(?:volatile)|(?:register)|(?:restrict)|(?:static)|(?:extern)|(?:const)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))+)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\s)*+(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=,|\\)|=)", "captures": { "1": { "patterns": [ @@ -8046,159 +10512,34 @@ ] }, "11": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "12": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "13": { - "name": "comment.block.cpp" - }, - "14": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "15": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "16": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "17": { - "name": "comment.block.cpp" - }, - "18": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "19": { "name": "storage.type.primitive.cpp storage.type.built-in.primitive.cpp" }, - "20": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "21": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "22": { - "name": "comment.block.cpp" - }, - "23": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "24": { + "12": { "name": "storage.type.cpp storage.type.built-in.cpp" }, - "25": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "26": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "27": { - "name": "comment.block.cpp" - }, - "28": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "29": { + "13": { "name": "support.type.posix-reserved.pthread.cpp support.type.built-in.posix-reserved.pthread.cpp" }, - "30": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "31": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "32": { - "name": "comment.block.cpp" - }, - "33": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "34": { + "14": { "name": "support.type.posix-reserved.cpp support.type.built-in.posix-reserved.cpp" }, - "35": { + "15": { "name": "entity.name.type.parameter.cpp" }, - "36": { + "16": { "patterns": [ { "include": "#inline_comment" } ] }, - "37": { + "17": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "38": { + "18": { "name": "comment.block.cpp" }, - "39": { + "19": { "patterns": [ { "match": "\\*\\/", @@ -8219,12 +10560,13 @@ "include": "#scope_resolution_parameter_inner_generated" }, { - "match": "(?:struct|class|union|enum)", + "match": "(?:(?:struct)|(?:class)|(?:union)|(?:enum))", "name": "storage.type.$0.cpp" }, { "begin": "(?<==)", "end": "(?:(?=\\))|(,))", + "beginCaptures": {}, "endCaptures": { "1": { "name": "punctuation.separator.delimiter.comma.cpp" @@ -8237,10 +10579,11 @@ ] }, { - "include": "#assignment_operator" + "match": "\\=", + "name": "keyword.operator.assignment.cpp" }, { - "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\)|,|\\[|=|\\n)", + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\)|,|\\[|=|\\n)", "captures": { "1": { "patterns": [ @@ -8301,19 +10644,19 @@ "include": "#attributes_context" }, { - "name": "meta.bracket.square.array.cpp", - "begin": "(\\[)", + "begin": "\\[", + "end": "\\]", "beginCaptures": { - "1": { + "0": { "name": "punctuation.definition.begin.bracket.square.array.type.cpp" } }, - "end": "(\\])", "endCaptures": { - "1": { + "0": { "name": "punctuation.definition.end.bracket.square.array.type.cpp" } }, + "name": "meta.bracket.square.array.cpp", "patterns": [ { "include": "#evaluation_context" @@ -8328,7 +10671,7 @@ "include": "#template_call_range" }, { - "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*)", + "match": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*))", "captures": { "0": { "patterns": [ @@ -8337,7 +10680,7 @@ "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ @@ -8428,7 +10771,7 @@ ] }, "parameter_class": { - "match": "(class)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:\\[((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\]((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?=,|\\)|\\n)", + "match": "(class)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", "captures": { "1": { "name": "storage.type.class.parameter.cpp" @@ -8441,59 +10784,77 @@ ] }, "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "6": { + "4": { "name": "entity.name.type.class.parameter.cpp" }, - "7": { + "5": { "patterns": [ { "include": "#inline_comment" } ] }, - "8": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "9": { - "name": "comment.block.cpp" - }, - "10": { + "6": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "11": { + "7": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ @@ -8529,6 +10890,74 @@ } ] }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, "12": { "patterns": [ { @@ -8537,155 +10966,141 @@ ] }, "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "14": { - "name": "comment.block.cpp" + "name": "variable.other.object.declare.cpp" }, "15": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "16": { "patterns": [ { - "include": "#inline_comment" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, "17": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "18": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "19": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "20": { "patterns": [ { - "include": "#inline_comment" - } - ] - }, - "21": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "22": { - "name": "comment.block.cpp" - }, - "23": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "24": { - "name": "variable.other.object.declare.cpp" - }, - "25": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "26": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "27": { - "name": "comment.block.cpp" - }, - "28": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "29": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "31": { - "name": "comment.block.cpp" - }, - "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] } } }, "parameter_enum": { - "match": "(enum)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:\\[((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\]((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?=,|\\)|\\n)", + "match": "(enum)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", "captures": { "1": { "name": "storage.type.enum.parameter.cpp" @@ -8698,59 +11113,77 @@ ] }, "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "6": { + "4": { "name": "entity.name.type.enum.parameter.cpp" }, - "7": { + "5": { "patterns": [ { "include": "#inline_comment" } ] }, - "8": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "9": { - "name": "comment.block.cpp" - }, - "10": { + "6": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "11": { + "7": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ @@ -8786,6 +11219,74 @@ } ] }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, "12": { "patterns": [ { @@ -8794,156 +11295,142 @@ ] }, "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "14": { - "name": "comment.block.cpp" + "name": "variable.other.object.declare.cpp" }, "15": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "16": { "patterns": [ { - "include": "#inline_comment" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, "17": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "18": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "19": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "20": { "patterns": [ { - "include": "#inline_comment" - } - ] - }, - "21": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "22": { - "name": "comment.block.cpp" - }, - "23": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "24": { - "name": "variable.other.object.declare.cpp" - }, - "25": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "26": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "27": { - "name": "comment.block.cpp" - }, - "28": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "29": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "31": { - "name": "comment.block.cpp" - }, - "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] } } }, "parameter_or_maybe_value": { - "name": "meta.parameter.cpp", - "begin": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\w)", + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\w)", + "end": "(?:(?=\\))|(,))", "beginCaptures": { "1": { "patterns": [ @@ -8971,12 +11458,12 @@ ] } }, - "end": "(?:(?=\\))|(,))", "endCaptures": { "1": { "name": "punctuation.separator.delimiter.comma.cpp" } }, + "name": "meta.parameter.cpp", "patterns": [ { "include": "#ever_present_context" @@ -9000,7 +11487,7 @@ "include": "#vararg_ellipses" }, { - "match": "((?:((?:volatile|register|restrict|static|extern|const))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))+)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=,|\\)|=)", + "match": "((?:((?:(?:volatile)|(?:register)|(?:restrict)|(?:static)|(?:extern)|(?:const)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))+)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\s)*+(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=,|\\)|=)", "captures": { "1": { "patterns": [ @@ -9063,159 +11550,34 @@ ] }, "11": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "12": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "13": { - "name": "comment.block.cpp" - }, - "14": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "15": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "16": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "17": { - "name": "comment.block.cpp" - }, - "18": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "19": { "name": "storage.type.primitive.cpp storage.type.built-in.primitive.cpp" }, - "20": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "21": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "22": { - "name": "comment.block.cpp" - }, - "23": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "24": { + "12": { "name": "storage.type.cpp storage.type.built-in.cpp" }, - "25": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "26": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "27": { - "name": "comment.block.cpp" - }, - "28": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "29": { + "13": { "name": "support.type.posix-reserved.pthread.cpp support.type.built-in.posix-reserved.pthread.cpp" }, - "30": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "31": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "32": { - "name": "comment.block.cpp" - }, - "33": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "34": { + "14": { "name": "support.type.posix-reserved.cpp support.type.built-in.posix-reserved.cpp" }, - "35": { + "15": { "name": "entity.name.type.parameter.cpp" }, - "36": { + "16": { "patterns": [ { "include": "#inline_comment" } ] }, - "37": { + "17": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "38": { + "18": { "name": "comment.block.cpp" }, - "39": { + "19": { "patterns": [ { "match": "\\*\\/", @@ -9239,12 +11601,13 @@ "include": "#scope_resolution_parameter_inner_generated" }, { - "match": "(?:struct|class|union|enum)", + "match": "(?:(?:struct)|(?:class)|(?:union)|(?:enum))", "name": "storage.type.$0.cpp" }, { "begin": "(?<==)", "end": "(?:(?=\\))|(,))", + "beginCaptures": {}, "endCaptures": { "1": { "name": "punctuation.separator.delimiter.comma.cpp" @@ -9257,7 +11620,7 @@ ] }, { - "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=(?:\\)|,|\\[|=|\\/\\/|(?:\\n|$)))", + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=(?:\\)|,|\\[|=|\\/\\/|(?:(?:\\n)|$)))", "captures": { "1": { "patterns": [ @@ -9318,19 +11681,19 @@ "include": "#attributes_context" }, { - "name": "meta.bracket.square.array.cpp", - "begin": "(\\[)", + "begin": "\\[", + "end": "\\]", "beginCaptures": { - "1": { + "0": { "name": "punctuation.definition.begin.bracket.square.array.type.cpp" } }, - "end": "(\\])", "endCaptures": { - "1": { + "0": { "name": "punctuation.definition.end.bracket.square.array.type.cpp" } }, + "name": "meta.bracket.square.array.cpp", "patterns": [ { "include": "#evaluation_context" @@ -9345,7 +11708,7 @@ "include": "#template_call_range" }, { - "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*)", + "match": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*))", "captures": { "0": { "patterns": [ @@ -9354,7 +11717,7 @@ "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ @@ -9448,7 +11811,7 @@ ] }, "parameter_struct": { - "match": "(struct)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:\\[((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\]((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?=,|\\)|\\n)", + "match": "(struct)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", "captures": { "1": { "name": "storage.type.struct.parameter.cpp" @@ -9461,59 +11824,77 @@ ] }, "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "6": { + "4": { "name": "entity.name.type.struct.parameter.cpp" }, - "7": { + "5": { "patterns": [ { "include": "#inline_comment" } ] }, - "8": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "9": { - "name": "comment.block.cpp" - }, - "10": { + "6": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "11": { + "7": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ @@ -9549,6 +11930,74 @@ } ] }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, "12": { "patterns": [ { @@ -9557,155 +12006,141 @@ ] }, "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "14": { - "name": "comment.block.cpp" + "name": "variable.other.object.declare.cpp" }, "15": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "16": { "patterns": [ { - "include": "#inline_comment" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, "17": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "18": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "19": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "20": { "patterns": [ { - "include": "#inline_comment" - } - ] - }, - "21": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "22": { - "name": "comment.block.cpp" - }, - "23": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "24": { - "name": "variable.other.object.declare.cpp" - }, - "25": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "26": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "27": { - "name": "comment.block.cpp" - }, - "28": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "29": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "31": { - "name": "comment.block.cpp" - }, - "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] } } }, "parameter_union": { - "match": "(union)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:\\[((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\]((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?=,|\\)|\\n)", + "match": "(union)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", "captures": { "1": { "name": "storage.type.union.parameter.cpp" @@ -9718,59 +12153,77 @@ ] }, "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "6": { + "4": { "name": "entity.name.type.union.parameter.cpp" }, - "7": { + "5": { "patterns": [ { "include": "#inline_comment" } ] }, - "8": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "9": { - "name": "comment.block.cpp" - }, - "10": { + "6": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "11": { + "7": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ @@ -9806,6 +12259,74 @@ } ] }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, "12": { "patterns": [ { @@ -9814,167 +12335,153 @@ ] }, "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "14": { - "name": "comment.block.cpp" + "name": "variable.other.object.declare.cpp" }, "15": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "16": { "patterns": [ { - "include": "#inline_comment" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, "17": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "18": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "19": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "20": { "patterns": [ { - "include": "#inline_comment" - } - ] - }, - "21": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "22": { - "name": "comment.block.cpp" - }, - "23": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "24": { - "name": "variable.other.object.declare.cpp" - }, - "25": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "26": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "27": { - "name": "comment.block.cpp" - }, - "28": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "29": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "31": { - "name": "comment.block.cpp" - }, - "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] } } }, "parentheses": { - "name": "meta.parens.cpp", - "begin": "(\\()", + "begin": "\\(", + "end": "\\)", "beginCaptures": { - "1": { + "0": { "name": "punctuation.section.parens.begin.bracket.round.cpp" } }, - "end": "(\\))", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.parens.end.bracket.round.cpp" } }, + "name": "meta.parens.cpp", "patterns": [ { "include": "#over_qualified_types" @@ -9988,60 +12495,27 @@ } ] }, - "posix_reserved_types": { - "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*pragma\\b)", + "begin": "^((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?pragma\\b", + "end": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*pragma\\s+mark)\\s+(.*)", + "match": "(^((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?pragma(?:\\s)+mark)(?:\\s)+(.*)", "captures": { "1": { "name": "keyword.control.directive.pragma.pragma-mark.cpp" @@ -10091,27 +12566,36 @@ ] }, "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "6": { + "4": { "name": "punctuation.definition.directive.cpp" }, - "7": { + "5": { "name": "entity.name.tag.pragma-mark.cpp" } }, @@ -10145,10 +12629,10 @@ "include": "#language_constants" }, { - "include": "#string_context_c" + "include": "#string_context" }, { - "include": "#preprocessor_number_literal" + "include": "#d9bc4796b0b_preprocessor_number_literal" }, { "include": "#operators" @@ -10166,6 +12650,7 @@ }, "preprocessor_conditional_defined": { "begin": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*((?:(?:ifndef|ifdef)|if)))", + "begin": "^((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?((?:(?:ifndef|ifdef)|if))", + "end": "^(?!\\s*+#\\s*(?:else|endif))", "beginCaptures": { - "1": { - "name": "keyword.control.directive.conditional.$7.cpp" + "0": { + "name": "keyword.control.directive.conditional.$6.cpp" }, - "2": { + "1": { "patterns": [ { "include": "#inline_comment" } ] }, - "3": { + "2": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "4": { + "3": { "name": "comment.block.cpp" }, - "5": { + "4": { "patterns": [ { "match": "\\*\\/", @@ -10232,16 +12717,19 @@ } ] }, - "6": { + "5": { "name": "punctuation.definition.directive.cpp" - } + }, + "6": {} }, - "end": "(?:^)(?!\\s*+#\\s*(?:else|endif))", + "endCaptures": {}, "patterns": [ { - "name": "meta.preprocessor.conditional.cpp", "begin": "\\G(?<=ifndef|ifdef|if)", "end": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "5": { + "3": { "name": "punctuation.definition.directive.cpp" } }, - "name": "keyword.control.directive.$6.cpp" + "name": "keyword.control.directive.$4.cpp" }, - "preprocessor_number_literal": { - "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<26>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<26>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<26>?)+)>)\\s*)?(?![\\w<:.])", + "match": "\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)?(?![\\w<:.])", "captures": { "0": { - "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cpp" + } + }, + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] }, { "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", @@ -10664,120 +12896,127 @@ ] }, "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "4": { - "name": "comment.block.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "5": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, "6": { "patterns": [ { - "include": "#inline_comment" + "match": "::", + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.type.cpp" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] } - } + }, + "name": "meta.qualified_type.cpp" }, "qualifiers_and_specifiers_post_parameters": { - "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "3": { - "name": "comment.block.cpp" + "name": "storage.modifier.specifier.functional.post-parameters.$3.cpp" }, "4": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "5": { - "name": "storage.modifier.specifier.functional.post-parameters.$5.cpp" + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] } } }, "scope_resolution": { - "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -10822,8 +13104,7 @@ "1": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, - "3": { - "name": "meta.template.call.cpp", + "2": { "patterns": [ { "include": "#template_call_range" @@ -10833,7 +13114,7 @@ } }, "scope_resolution_function_call": { - "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -10845,8 +13126,7 @@ "1": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.call.cpp" }, - "3": { - "name": "meta.template.call.cpp", + "2": { "patterns": [ { "include": "#template_call_range" @@ -10856,7 +13136,7 @@ } }, "scope_resolution_function_call_inner_generated": { - "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -10868,18 +13148,18 @@ "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.call.cpp" }, - "4": { - "name": "meta.template.call.cpp", + "3": { "patterns": [ { "include": "#template_call_range" } ] }, - "6": { + "4": {}, + "5": { "name": "entity.name.scope-resolution.function.call.cpp" }, - "7": { + "6": { "name": "meta.template.call.cpp", "patterns": [ { @@ -10887,13 +13167,14 @@ } ] }, - "9": { + "7": {}, + "8": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.call.cpp" } } }, "scope_resolution_function_definition": { - "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -10905,8 +13186,7 @@ "1": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.cpp" }, - "3": { - "name": "meta.template.call.cpp", + "2": { "patterns": [ { "include": "#template_call_range" @@ -10916,7 +13196,7 @@ } }, "scope_resolution_function_definition_inner_generated": { - "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -10928,18 +13208,18 @@ "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.cpp" }, - "4": { - "name": "meta.template.call.cpp", + "3": { "patterns": [ { "include": "#template_call_range" } ] }, - "6": { + "4": {}, + "5": { "name": "entity.name.scope-resolution.function.definition.cpp" }, - "7": { + "6": { "name": "meta.template.call.cpp", "patterns": [ { @@ -10947,13 +13227,14 @@ } ] }, - "9": { + "7": {}, + "8": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.cpp" } } }, "scope_resolution_function_definition_operator_overload": { - "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -10965,8 +13246,7 @@ "1": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.operator-overload.cpp" }, - "3": { - "name": "meta.template.call.cpp", + "2": { "patterns": [ { "include": "#template_call_range" @@ -10976,7 +13256,7 @@ } }, "scope_resolution_function_definition_operator_overload_inner_generated": { - "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -10988,18 +13268,18 @@ "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.operator-overload.cpp" }, - "4": { - "name": "meta.template.call.cpp", + "3": { "patterns": [ { "include": "#template_call_range" } ] }, - "6": { + "4": {}, + "5": { "name": "entity.name.scope-resolution.function.definition.operator-overload.cpp" }, - "7": { + "6": { "name": "meta.template.call.cpp", "patterns": [ { @@ -11007,13 +13287,14 @@ } ] }, - "9": { + "7": {}, + "8": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.operator-overload.cpp" } } }, "scope_resolution_inner_generated": { - "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -11025,18 +13306,18 @@ "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, - "4": { - "name": "meta.template.call.cpp", + "3": { "patterns": [ { "include": "#template_call_range" } ] }, - "6": { + "4": {}, + "5": { "name": "entity.name.scope-resolution.cpp" }, - "7": { + "6": { "name": "meta.template.call.cpp", "patterns": [ { @@ -11044,13 +13325,14 @@ } ] }, - "9": { + "7": {}, + "8": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" } } }, "scope_resolution_namespace_alias": { - "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -11062,8 +13344,7 @@ "1": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.alias.cpp" }, - "3": { - "name": "meta.template.call.cpp", + "2": { "patterns": [ { "include": "#template_call_range" @@ -11073,7 +13354,7 @@ } }, "scope_resolution_namespace_alias_inner_generated": { - "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -11085,18 +13366,18 @@ "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.alias.cpp" }, - "4": { - "name": "meta.template.call.cpp", + "3": { "patterns": [ { "include": "#template_call_range" } ] }, - "6": { + "4": {}, + "5": { "name": "entity.name.scope-resolution.namespace.alias.cpp" }, - "7": { + "6": { "name": "meta.template.call.cpp", "patterns": [ { @@ -11104,13 +13385,14 @@ } ] }, - "9": { + "7": {}, + "8": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.alias.cpp" } } }, "scope_resolution_namespace_block": { - "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -11122,8 +13404,7 @@ "1": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.block.cpp" }, - "3": { - "name": "meta.template.call.cpp", + "2": { "patterns": [ { "include": "#template_call_range" @@ -11133,7 +13414,7 @@ } }, "scope_resolution_namespace_block_inner_generated": { - "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -11145,18 +13426,18 @@ "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.block.cpp" }, - "4": { - "name": "meta.template.call.cpp", + "3": { "patterns": [ { "include": "#template_call_range" } ] }, - "6": { + "4": {}, + "5": { "name": "entity.name.scope-resolution.namespace.block.cpp" }, - "7": { + "6": { "name": "meta.template.call.cpp", "patterns": [ { @@ -11164,13 +13445,14 @@ } ] }, - "9": { + "7": {}, + "8": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.block.cpp" } } }, "scope_resolution_namespace_using": { - "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -11182,8 +13464,7 @@ "1": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.using.cpp" }, - "3": { - "name": "meta.template.call.cpp", + "2": { "patterns": [ { "include": "#template_call_range" @@ -11193,7 +13474,7 @@ } }, "scope_resolution_namespace_using_inner_generated": { - "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -11205,18 +13486,18 @@ "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.using.cpp" }, - "4": { - "name": "meta.template.call.cpp", + "3": { "patterns": [ { "include": "#template_call_range" } ] }, - "6": { + "4": {}, + "5": { "name": "entity.name.scope-resolution.namespace.using.cpp" }, - "7": { + "6": { "name": "meta.template.call.cpp", "patterns": [ { @@ -11224,13 +13505,14 @@ } ] }, - "9": { + "7": {}, + "8": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.using.cpp" } } }, "scope_resolution_parameter": { - "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -11242,8 +13524,7 @@ "1": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.parameter.cpp" }, - "3": { - "name": "meta.template.call.cpp", + "2": { "patterns": [ { "include": "#template_call_range" @@ -11253,7 +13534,7 @@ } }, "scope_resolution_parameter_inner_generated": { - "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -11265,18 +13546,18 @@ "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.parameter.cpp" }, - "4": { - "name": "meta.template.call.cpp", + "3": { "patterns": [ { "include": "#template_call_range" } ] }, - "6": { + "4": {}, + "5": { "name": "entity.name.scope-resolution.parameter.cpp" }, - "7": { + "6": { "name": "meta.template.call.cpp", "patterns": [ { @@ -11284,13 +13565,14 @@ } ] }, - "9": { + "7": {}, + "8": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.parameter.cpp" } } }, "scope_resolution_template_call": { - "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -11302,8 +13584,7 @@ "1": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp" }, - "3": { - "name": "meta.template.call.cpp", + "2": { "patterns": [ { "include": "#template_call_range" @@ -11313,7 +13594,7 @@ } }, "scope_resolution_template_call_inner_generated": { - "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -11325,18 +13606,18 @@ "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp" }, - "4": { - "name": "meta.template.call.cpp", + "3": { "patterns": [ { "include": "#template_call_range" } ] }, - "6": { + "4": {}, + "5": { "name": "entity.name.scope-resolution.template.call.cpp" }, - "7": { + "6": { "name": "meta.template.call.cpp", "patterns": [ { @@ -11344,13 +13625,14 @@ } ] }, - "9": { + "7": {}, + "8": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp" } } }, "scope_resolution_template_definition": { - "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -11362,8 +13644,7 @@ "1": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.definition.cpp" }, - "3": { - "name": "meta.template.call.cpp", + "2": { "patterns": [ { "include": "#template_call_range" @@ -11373,7 +13654,7 @@ } }, "scope_resolution_template_definition_inner_generated": { - "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -11385,18 +13666,18 @@ "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.definition.cpp" }, - "4": { - "name": "meta.template.call.cpp", + "3": { "patterns": [ { "include": "#template_call_range" } ] }, - "6": { + "4": {}, + "5": { "name": "entity.name.scope-resolution.template.definition.cpp" }, - "7": { + "6": { "name": "meta.template.call.cpp", "patterns": [ { @@ -11404,7 +13685,8 @@ } ] }, - "9": { + "7": {}, + "8": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.definition.cpp" } } @@ -11414,21 +13696,22 @@ "name": "punctuation.terminator.statement.cpp" }, "simple_type": { - "match": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?", + "match": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)?(?![\\w<:.]))(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?", "captures": { "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cpp" + } + }, + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] }, { "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", @@ -11471,124 +13771,132 @@ ] }, "4": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "5": { - "name": "comment.block.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "6": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, "7": { "patterns": [ { - "include": "#inline_comment" + "match": "::", + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.type.cpp" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "25": { - "name": "entity.name.type.cpp" - }, - "26": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "28": { + "12": {}, + "13": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ @@ -11624,60 +13932,78 @@ } ] }, - "29": { + "14": { "patterns": [ { "include": "#inline_comment" } ] }, - "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "31": { - "name": "comment.block.cpp" - }, - "32": { + "15": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "33": { + "16": { "patterns": [ { "include": "#inline_comment" } ] }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { + "17": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] } } }, "single_line_macro": { - "match": "^((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))#define.*(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))#define.*(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] } } }, "sizeof_operator": { - "contentName": "meta.arguments.operator.sizeof.cpp", - "begin": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.operator.sizeof.cpp" @@ -11752,12 +14087,12 @@ "name": "punctuation.section.arguments.begin.bracket.round.operator.sizeof.cpp" } }, - "end": "(\\))", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.arguments.end.bracket.round.operator.sizeof.cpp" } }, + "contentName": "meta.arguments.operator.sizeof", "patterns": [ { "include": "#evaluation_context" @@ -11765,8 +14100,8 @@ ] }, "sizeof_variadic_operator": { - "contentName": "meta.arguments.operator.sizeof.variadic.cpp", - "begin": "(\\bsizeof\\.\\.\\.)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", + "begin": "(\\bsizeof\\.\\.\\.)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.operator.sizeof.variadic.cpp" @@ -11800,12 +14135,12 @@ "name": "punctuation.section.arguments.begin.bracket.round.operator.sizeof.variadic.cpp" } }, - "end": "(\\))", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.arguments.end.bracket.round.operator.sizeof.variadic.cpp" } }, + "contentName": "meta.arguments.operator.sizeof.variadic", "patterns": [ { "include": "#evaluation_context" @@ -11813,20 +14148,20 @@ ] }, "square_brackets": { - "name": "meta.bracket.square.access.cpp", + "name": "meta.bracket.square.access", "begin": "([a-zA-Z_][a-zA-Z_0-9]*|(?<=[\\]\\)]))?(\\[)(?!\\])", "beginCaptures": { "1": { - "name": "variable.other.object.cpp" + "name": "variable.other.object" }, "2": { - "name": "punctuation.definition.begin.bracket.square.cpp" + "name": "punctuation.definition.begin.bracket.square" } }, "end": "\\]", "endCaptures": { "0": { - "name": "punctuation.definition.end.bracket.square.cpp" + "name": "punctuation.definition.end.bracket.square" } }, "patterns": [ @@ -11838,21 +14173,918 @@ "standard_declares": { "patterns": [ { - "include": "#struct_declare" + "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{a-zA-Z])", + "captures": { + "1": { + "name": "storage.type.struct.declare.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.struct.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "12": { + "name": "variable.other.object.declare.cpp" + }, + "13": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "14": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + } + } }, { - "include": "#union_declare" + "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{a-zA-Z])", + "captures": { + "1": { + "name": "storage.type.union.declare.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.union.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "12": { + "name": "variable.other.object.declare.cpp" + }, + "13": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "14": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + } + } }, { - "include": "#enum_declare" + "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{a-zA-Z])", + "captures": { + "1": { + "name": "storage.type.enum.declare.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.enum.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "12": { + "name": "variable.other.object.declare.cpp" + }, + "13": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "14": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + } + } }, { - "include": "#class_declare" + "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{a-zA-Z])", + "captures": { + "1": { + "name": "storage.type.class.declare.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.class.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "12": { + "name": "variable.other.object.declare.cpp" + }, + "13": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "14": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + } + } } ] }, "static_assert": { - "begin": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", "beginCaptures": { "1": { "patterns": [ @@ -11911,22 +15143,22 @@ "name": "punctuation.section.arguments.begin.bracket.round.static_assert.cpp" } }, - "end": "(\\))", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.arguments.end.bracket.round.static_assert.cpp" } }, "patterns": [ { - "name": "meta.static_assert.message.cpp", - "begin": "(,)\\s*(?=(?:L|u8|u|U\\s*\\\")?)", + "begin": "(,)(?:(?:\\s)+)?(?=(?:L|u8|u|U(?:(?:\\s)+)?\\\")?)", + "end": "(?=\\))", "beginCaptures": { "1": { "name": "punctuation.separator.delimiter.comma.cpp" } }, - "end": "(?=\\))", + "endCaptures": {}, + "name": "meta.static_assert.message.cpp", "patterns": [ { "include": "#string_context" @@ -11938,8 +15170,47 @@ } ] }, + "std_space": { + "match": "(?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))", + "captures": { + "0": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "1": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + } + } + }, "storage_specifiers": { - "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "5": { - "name": "storage.modifier.specifier.$5.cpp" + "3": { + "name": "storage.modifier.specifier.$3.cpp" } } }, @@ -11977,16 +15257,7 @@ "include": "#storage_specifiers" }, { - "include": "#primitive_types" - }, - { - "include": "#non_primitive_types" - }, - { - "include": "#pthread_types" - }, - { - "include": "#posix_reserved_types" + "include": "#inline_builtin_storage_type" }, { "include": "#decltype" @@ -11999,22 +15270,22 @@ "string_context": { "patterns": [ { - "name": "string.quoted.double.cpp", - "begin": "(((?:u|u8|U|L)?)\")", + "begin": "((?:u|u8|U|L)?)\"", + "end": "\"", "beginCaptures": { - "1": { + "0": { "name": "punctuation.definition.string.begin.cpp" }, - "2": { + "1": { "name": "meta.encoding.cpp" } }, - "end": "(\")", "endCaptures": { - "1": { + "0": { "name": "punctuation.definition.string.end.cpp" } }, + "name": "string.quoted.double.cpp", "patterns": [ { "match": "(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8})", @@ -12029,8 +15300,15 @@ "name": "constant.character.escape.cpp" }, { - "match": "\\\\x[0-9a-fA-F]{2,2}", - "name": "constant.character.escape.cpp" + "match": "(?:(\\\\x0*[0-9a-fA-F]{2}(?![0-9a-fA-F]))|((?:\\\\x[0-9a-fA-F]*|\\\\x)))", + "captures": { + "1": { + "name": "constant.character.escape.cpp" + }, + "2": { + "name": "invalid.illegal.unknown-escape.cpp" + } + } }, { "include": "#string_escapes_context_c" @@ -12038,23 +15316,34 @@ ] }, { - "name": "string.quoted.single.cpp", - "begin": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?={)|(?:((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:(?!:)))?)", + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", "beginCaptures": { - "1": { + "0": { "name": "meta.head.struct.cpp" }, - "3": { - "name": "storage.type.$3.cpp" + "1": { + "name": "storage.type.$1.cpp" }, - "4": { + "2": { "patterns": [ { "include": "#inline_comment" } ] }, - "5": { + "3": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "6": { + "4": { "name": "comment.block.cpp" }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, "7": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "8": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "#number_literal" - } - ] - }, - "9": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "10": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "11": { + "9": { "name": "comment.block.cpp" }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", + "captures": { + "1": { + "name": "storage.type.modifier.final.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=:|{|$)", + "captures": { + "1": { + "name": "entity.name.type.struct.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "storage.type.modifier.final.cpp" + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": "DLLEXPORT", + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.other.preprocessor.macro.predefined.probably.$0.cpp" + } + ] + }, "12": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "13": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "14": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "15": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "16": { + "14": { "name": "comment.block.cpp" }, - "17": { + "15": { "patterns": [ { "match": "\\*\\/", @@ -12252,134 +15664,35 @@ } ] }, - "18": { + "16": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "#inline_comment" } ] }, + "17": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "18": { + "name": "comment.block.cpp" + }, "19": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "20": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "21": { - "name": "comment.block.cpp" - }, - "22": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "23": { - "name": "entity.name.type.$3.cpp" - }, - "24": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "25": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "26": { - "name": "comment.block.cpp" - }, - "27": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "28": { - "name": "storage.type.modifier.final.cpp" - }, - "29": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "31": { - "name": "comment.block.cpp" - }, - "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "37": { "name": "punctuation.separator.colon.inheritance.cpp" - }, - "38": { - "patterns": [ - { - "include": "#inheritance_context" - } - ] } }, - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))", "endCaptures": { "1": { "name": "punctuation.terminator.statement.cpp" @@ -12388,16 +15701,18 @@ "name": "punctuation.terminator.statement.cpp" } }, + "name": "meta.block.struct.cpp", "patterns": [ { - "name": "meta.head.struct.cpp", "begin": "\\G ?", - "end": "((?:\\{|<%|\\?\\?<|(?=;)))", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.begin.bracket.curly.struct.cpp" } }, + "name": "meta.head.struct.cpp", "patterns": [ { "include": "#ever_present_context" @@ -12411,14 +15726,15 @@ ] }, { - "name": "meta.body.struct.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "(\\}|%>|\\?\\?>)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.end.bracket.curly.struct.cpp" } }, + "name": "meta.body.struct.cpp", "patterns": [ { "include": "#function_pointer" @@ -12438,9 +15754,11 @@ ] }, { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, "name": "meta.tail.struct.cpp", - "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -12450,7 +15768,7 @@ ] }, "struct_declare": { - "match": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\S)(?![:{])", + "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{a-zA-Z])", "captures": { "1": { "name": "storage.type.struct.declare.cpp" @@ -12463,34 +15781,43 @@ ] }, "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "6": { + "4": { "name": "entity.name.type.struct.cpp" }, - "7": { + "5": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ @@ -12526,6 +15853,40 @@ } ] }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, "8": { "patterns": [ { @@ -12534,106 +15895,108 @@ ] }, "9": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "10": { - "name": "comment.block.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "11": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, "12": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "14": { - "name": "comment.block.cpp" - }, - "15": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "16": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "17": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "18": { - "name": "comment.block.cpp" - }, - "19": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "20": { "name": "variable.other.object.declare.cpp" }, - "21": { + "13": { "patterns": [ { "include": "#inline_comment" } ] }, - "22": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "23": { - "name": "comment.block.cpp" - }, - "24": { + "14": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] } } }, "switch_conditional_parentheses": { - "name": "meta.conditional.switch.cpp", - "begin": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", "beginCaptures": { "1": { "patterns": [ @@ -12664,12 +16027,12 @@ "name": "punctuation.section.parens.begin.bracket.round.conditional.switch.cpp" } }, - "end": "(\\))", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.parens.end.bracket.round.conditional.switch.cpp" } }, + "name": "meta.conditional.switch.cpp", "patterns": [ { "include": "#evaluation_context" @@ -12680,26 +16043,26 @@ ] }, "switch_statement": { - "name": "meta.block.switch.cpp", - "begin": "(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?|\\?\\?>)|(?=[;>\\[\\]=]))", "beginCaptures": { - "1": { + "0": { "name": "meta.head.switch.cpp" }, - "2": { + "1": { "patterns": [ { "include": "#inline_comment" } ] }, - "3": { + "2": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "4": { + "3": { "name": "comment.block.cpp" }, - "5": { + "4": { "patterns": [ { "match": "\\*\\/", @@ -12711,21 +16074,23 @@ } ] }, - "6": { + "5": { "name": "keyword.control.switch.cpp" } }, - "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", + "endCaptures": {}, + "name": "meta.block.switch.cpp", "patterns": [ { - "name": "meta.head.switch.cpp", "begin": "\\G ?", - "end": "((?:\\{|<%|\\?\\?<|(?=;)))", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.begin.bracket.curly.switch.cpp" } }, + "name": "meta.head.switch.cpp", "patterns": [ { "include": "#switch_conditional_parentheses" @@ -12736,14 +16101,15 @@ ] }, { - "name": "meta.body.switch.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "(\\}|%>|\\?\\?>)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.end.bracket.curly.switch.cpp" } }, + "name": "meta.body.switch.cpp", "patterns": [ { "include": "#default_statement" @@ -12760,9 +16126,11 @@ ] }, { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, "name": "meta.tail.switch.cpp", - "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -12772,7 +16140,7 @@ ] }, "template_argument_defaulted": { - "match": "(?<=<|,)\\s*((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s+)*)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*([=])", + "match": "(?<=<|,)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:\\s)+)*)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)(?:(?:\\s)+)?([=])", "captures": { "1": { "name": "storage.type.template.cpp" @@ -12820,32 +16188,32 @@ ] }, "template_call_innards": { - "match": "((?(?:(?>[^<>]*)\\g<1>?)+)>)\\s*", + "match": "((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<1>?)+>)(?:\\s)*+", "captures": { "0": { - "name": "meta.template.call.cpp", "patterns": [ { "include": "#template_call_range" } ] } - } + }, + "name": "meta.template.call.cpp" }, "template_call_range": { - "name": "meta.template.call.cpp", - "begin": "(<)", + "begin": "<", + "end": ">", "beginCaptures": { - "1": { + "0": { "name": "punctuation.section.angle-brackets.begin.template.call.cpp" } }, - "end": "(>)", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.angle-brackets.end.template.call.cpp" } }, + "name": "meta.template.call.cpp", "patterns": [ { "include": "#template_call_context" @@ -12853,8 +16221,8 @@ ] }, "template_definition": { - "name": "meta.template.definition.cpp", - "begin": "(?", "beginCaptures": { "1": { "name": "storage.type.template.cpp" @@ -12863,23 +16231,23 @@ "name": "punctuation.section.angle-brackets.start.template.definition.cpp" } }, - "end": "(>)", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.angle-brackets.end.template.definition.cpp" } }, + "name": "meta.template.definition.cpp", "patterns": [ { - "begin": "((?<=\\w)\\s*<)", + "begin": "(?<=\\w)(?:(?:\\s)+)?<", + "end": ">", "beginCaptures": { - "1": { + "0": { "name": "punctuation.section.angle-brackets.begin.template.call.cpp" } }, - "end": "(>)", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.angle-brackets.begin.template.call.cpp" } }, @@ -12895,7 +16263,7 @@ ] }, "template_definition_argument": { - "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)|((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s+)+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))|((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*(\\.\\.\\.)\\s*((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*(?:(,)|(?=>|$))", + "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)|((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:\\s)+)+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))|((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)(?:(?:\\s)+)?(\\.\\.\\.)(?:(?:\\s)+)?((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))(?:(?:\\s)+)?(?:(,)|(?=>|$))", "captures": { "1": { "patterns": [ @@ -12905,27 +16273,36 @@ ] }, "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "5": { - "name": "storage.type.template.argument.$5.cpp" + "3": { + "name": "storage.type.template.argument.$3.cpp" }, - "6": { + "4": { "patterns": [ { "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", @@ -12933,19 +16310,19 @@ } ] }, - "7": { + "5": { "name": "entity.name.type.template.cpp" }, - "8": { + "6": { "name": "storage.type.template.cpp" }, - "9": { + "7": { "name": "punctuation.vararg-ellipses.template.definition.cpp" }, - "10": { + "8": { "name": "entity.name.type.template.cpp" }, - "11": { + "9": { "name": "punctuation.separator.delimiter.comma.template.argument.cpp" } } @@ -12970,7 +16347,7 @@ ] }, "template_isolated_definition": { - "match": "(?\\s*$)", + "match": "(?(?:(?:\\s)+)?$)", "captures": { "1": { "name": "storage.type.template.cpp" @@ -12992,16 +16369,15 @@ } }, "ternary_operator": { - "applyEndPatternLast": true, - "begin": "(\\?)", + "begin": "\\?", + "end": ":", "beginCaptures": { - "1": { + "0": { "name": "keyword.operator.ternary.cpp" } }, - "end": "(:)", "endCaptures": { - "1": { + "0": { "name": "keyword.operator.ternary.cpp" } }, @@ -13060,9 +16436,6 @@ { "include": "#storage_types" }, - { - "include": "#misc_storage_modifiers" - }, { "include": "#lambdas" }, @@ -13081,19 +16454,17 @@ { "include": "#square_brackets" }, - { - "include": "#empty_square_brackets" - }, { "include": "#semicolon" }, { "include": "#comma" } - ] + ], + "applyEndPatternLast": 1 }, "the_this_keyword": { - "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "5": { + "3": { "name": "variable.language.this.cpp" } } }, "type_alias": { - "match": "(using)\\s*(?!namespace)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\s*)?(?![\\w<:.]))\\s*(\\=)\\s*((?:typename)?)\\s*((?:(?:(?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\s*)?(?![\\w<:.]))|(.*(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:(\\[)(\\w*)(\\])\\s*)?\\s*(?:(;)|\\n)", + "match": "(using)(?:(?:\\s)+)?(?!namespace)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)?(?![\\w<:.]))(?:(?:\\s)+)?(\\=)(?:(?:\\s)+)?((?:typename)?)(?:(?:\\s)+)?((?:(?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)?(?![\\w<:.]))|(.*(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)?(?:(?:\\s)+)?(?:(;)|\\n)", "captures": { "1": { "name": "keyword.other.using.directive.cpp" @@ -13135,15 +16515,16 @@ "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cpp" + } + }, + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] }, { "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", @@ -13186,79 +16584,260 @@ ] }, "5": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "6": { - "name": "comment.block.cpp" - }, - "7": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "8": { + "6": { "patterns": [ { "include": "#inline_comment" } ] }, - "9": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "7": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, - "10": { - "name": "comment.block.cpp" + "8": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.type.cpp" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, "14": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + "name": "keyword.operator.assignment.cpp" + }, + "15": { + "name": "keyword.other.typename.cpp" }, "16": { - "name": "meta.template.call.cpp", "patterns": [ { - "include": "#template_call_range" + "include": "#storage_specifiers" + } + ] + }, + "17": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, "18": { - "name": "entity.name.scope-resolution.cpp" - }, - "19": { - "name": "meta.template.call.cpp", + "name": "meta.qualified_type.cpp", "patterns": [ { - "include": "#template_call_range" + "match": "::", + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + }, + { + "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cpp" + } + }, + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.type.cpp" + } + ] + }, + "19": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "20": { + "patterns": [ + { + "include": "#inline_comment" } ] }, "21": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "22": { "patterns": [ @@ -13268,213 +16847,89 @@ ] }, "23": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "24": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.type.cpp" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "30": { - "name": "keyword.other.typename.cpp" - }, - "31": { - "patterns": [ - { - "include": "#storage_specifiers" - } - ] - }, - "32": { - "name": "meta.qualified_type.cpp", - "patterns": [ - { - "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ @@ -13525,102 +16980,129 @@ } ] }, - "61": { + "32": { "patterns": [ { "include": "#inline_comment" } ] }, - "62": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "63": { - "name": "comment.block.cpp" - }, - "64": { + "33": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "65": { + "34": { "patterns": [ { "include": "#inline_comment" } ] }, - "66": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "67": { - "name": "comment.block.cpp" - }, - "68": { + "35": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "69": { + "36": { "patterns": [ { "include": "#inline_comment" } ] }, - "70": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "71": { - "name": "comment.block.cpp" - }, - "72": { + "37": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "73": { + "38": { "name": "punctuation.definition.begin.bracket.square.cpp" }, - "74": { + "39": { "patterns": [ { "include": "#evaluation_context" } ] }, - "75": { + "40": { "name": "punctuation.definition.end.bracket.square.cpp" }, - "76": { + "41": { "name": "punctuation.terminator.statement.cpp" } }, "name": "meta.declaration.type.alias.cpp" }, "type_casting_operators": { - "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "5": { - "name": "keyword.operator.wordlike.cpp keyword.operator.cast.$5.cpp" + "3": { + "name": "keyword.operator.wordlike.cpp keyword.operator.cast.$3.cpp" } } }, "typedef_class": { - "begin": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?={)|(?:((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:(?!:)))?)", + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", "beginCaptures": { - "1": { + "0": { "name": "meta.head.class.cpp" }, - "3": { - "name": "storage.type.$3.cpp" + "1": { + "name": "storage.type.$1.cpp" }, - "4": { + "2": { "patterns": [ { "include": "#inline_comment" } ] }, - "5": { + "3": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "6": { + "4": { "name": "comment.block.cpp" }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, "7": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "8": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "#number_literal" - } - ] - }, - "9": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "10": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "11": { + "9": { "name": "comment.block.cpp" }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", + "captures": { + "1": { + "name": "storage.type.modifier.final.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=:|{|$)", + "captures": { + "1": { + "name": "entity.name.type.class.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "storage.type.modifier.final.cpp" + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": "DLLEXPORT", + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.other.preprocessor.macro.predefined.probably.$0.cpp" + } + ] + }, "12": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "13": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "14": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "15": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "16": { + "14": { "name": "comment.block.cpp" }, - "17": { + "15": { "patterns": [ { "match": "\\*\\/", @@ -13759,134 +17354,35 @@ } ] }, - "18": { + "16": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "#inline_comment" } ] }, + "17": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "18": { + "name": "comment.block.cpp" + }, "19": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "20": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "21": { - "name": "comment.block.cpp" - }, - "22": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "23": { - "name": "entity.name.type.$3.cpp" - }, - "24": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "25": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "26": { - "name": "comment.block.cpp" - }, - "27": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "28": { - "name": "storage.type.modifier.final.cpp" - }, - "29": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "31": { - "name": "comment.block.cpp" - }, - "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "37": { "name": "punctuation.separator.colon.inheritance.cpp" - }, - "38": { - "patterns": [ - { - "include": "#inheritance_context" - } - ] } }, - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))", "endCaptures": { "1": { "name": "punctuation.terminator.statement.cpp" @@ -13895,16 +17391,18 @@ "name": "punctuation.terminator.statement.cpp" } }, + "name": "meta.block.class.cpp", "patterns": [ { - "name": "meta.head.class.cpp", "begin": "\\G ?", - "end": "((?:\\{|<%|\\?\\?<|(?=;)))", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.begin.bracket.curly.class.cpp" } }, + "name": "meta.head.class.cpp", "patterns": [ { "include": "#ever_present_context" @@ -13918,14 +17416,15 @@ ] }, { - "name": "meta.body.class.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "(\\}|%>|\\?\\?>)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.end.bracket.curly.class.cpp" } }, + "name": "meta.body.class.cpp", "patterns": [ { "include": "#function_pointer" @@ -13945,12 +17444,14 @@ ] }, { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, "name": "meta.tail.class.cpp", - "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)", "patterns": [ { - "match": "(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ @@ -14085,30 +17586,33 @@ ] }, "typedef_function_pointer": { - "begin": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()(\\*)\\s*((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)\\s*(?:(\\[)(\\w*)(\\])\\s*)*(\\))\\s*(\\()", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", + "end": "(\\))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=[{=,);>]|\\n)(?!\\()", "beginCaptures": { "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cpp" + } + }, + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] }, { "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", @@ -14193,52 +17714,43 @@ } ] }, + "11": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.type.cpp" + }, + { + "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ @@ -14304,111 +17806,110 @@ } ] }, - "29": { + "20": { "patterns": [ { "include": "#inline_comment" } ] }, + "21": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "22": { + "name": "comment.block.cpp" + }, + "23": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "29": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "31": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "37": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "38": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "39": { - "name": "comment.block.cpp" - }, - "40": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "41": { "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cpp" }, - "42": { + "33": { "name": "punctuation.definition.function.pointer.dereference.cpp" }, - "43": { + "34": { "name": "entity.name.type.alias.cpp entity.name.type.pointer.function.cpp" }, - "44": { + "35": { "name": "punctuation.definition.begin.bracket.square.cpp" }, - "45": { + "36": { "patterns": [ { "include": "#evaluation_context" } ] }, - "46": { + "37": { "name": "punctuation.definition.end.bracket.square.cpp" }, - "47": { + "38": { "name": "punctuation.section.parens.end.bracket.round.function.pointer.cpp" }, - "48": { + "39": { "name": "punctuation.section.parameters.begin.bracket.round.function.pointer.cpp" } }, - "end": "(\\))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=[{=,);]|\\n)(?!\\()", "endCaptures": { "1": { "name": "punctuation.section.parameters.end.bracket.round.function.pointer.cpp" @@ -14447,135 +17948,206 @@ } ] }, - "typedef_keyword": { - "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?={)|(?:((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:(?!:)))?)", + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", "beginCaptures": { - "1": { + "0": { "name": "meta.head.struct.cpp" }, - "3": { - "name": "storage.type.$3.cpp" + "1": { + "name": "storage.type.$1.cpp" }, - "4": { + "2": { "patterns": [ { "include": "#inline_comment" } ] }, - "5": { + "3": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "6": { + "4": { "name": "comment.block.cpp" }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, "7": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "8": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "#number_literal" - } - ] - }, - "9": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "10": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "11": { + "9": { "name": "comment.block.cpp" }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", + "captures": { + "1": { + "name": "storage.type.modifier.final.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=:|{|$)", + "captures": { + "1": { + "name": "entity.name.type.struct.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "storage.type.modifier.final.cpp" + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": "DLLEXPORT", + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.other.preprocessor.macro.predefined.probably.$0.cpp" + } + ] + }, "12": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "13": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "14": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "15": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "16": { + "14": { "name": "comment.block.cpp" }, - "17": { + "15": { "patterns": [ { "match": "\\*\\/", @@ -14587,134 +18159,35 @@ } ] }, - "18": { + "16": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "#inline_comment" } ] }, + "17": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "18": { + "name": "comment.block.cpp" + }, "19": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "20": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "21": { - "name": "comment.block.cpp" - }, - "22": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "23": { - "name": "entity.name.type.$3.cpp" - }, - "24": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "25": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "26": { - "name": "comment.block.cpp" - }, - "27": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "28": { - "name": "storage.type.modifier.final.cpp" - }, - "29": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "31": { - "name": "comment.block.cpp" - }, - "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "37": { "name": "punctuation.separator.colon.inheritance.cpp" - }, - "38": { - "patterns": [ - { - "include": "#inheritance_context" - } - ] } }, - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))", "endCaptures": { "1": { "name": "punctuation.terminator.statement.cpp" @@ -14723,16 +18196,18 @@ "name": "punctuation.terminator.statement.cpp" } }, + "name": "meta.block.struct.cpp", "patterns": [ { - "name": "meta.head.struct.cpp", "begin": "\\G ?", - "end": "((?:\\{|<%|\\?\\?<|(?=;)))", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.begin.bracket.curly.struct.cpp" } }, + "name": "meta.head.struct.cpp", "patterns": [ { "include": "#ever_present_context" @@ -14746,14 +18221,15 @@ ] }, { - "name": "meta.body.struct.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "(\\}|%>|\\?\\?>)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.end.bracket.curly.struct.cpp" } }, + "name": "meta.body.struct.cpp", "patterns": [ { "include": "#function_pointer" @@ -14773,12 +18249,14 @@ ] }, { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, "name": "meta.tail.struct.cpp", - "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)", "patterns": [ { - "match": "(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ @@ -14913,101 +18391,205 @@ ] }, "typedef_union": { - "begin": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?={)|(?:((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:(?!:)))?)", + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", "beginCaptures": { - "1": { + "0": { "name": "meta.head.union.cpp" }, - "3": { - "name": "storage.type.$3.cpp" + "1": { + "name": "storage.type.$1.cpp" }, - "4": { + "2": { "patterns": [ { "include": "#inline_comment" } ] }, - "5": { + "3": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "6": { + "4": { "name": "comment.block.cpp" }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, "7": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "8": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "#number_literal" - } - ] - }, - "9": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "10": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "11": { + "9": { "name": "comment.block.cpp" }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", + "captures": { + "1": { + "name": "storage.type.modifier.final.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=:|{|$)", + "captures": { + "1": { + "name": "entity.name.type.union.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "storage.type.modifier.final.cpp" + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": "DLLEXPORT", + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.other.preprocessor.macro.predefined.probably.$0.cpp" + } + ] + }, "12": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "13": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "14": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "15": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "16": { + "14": { "name": "comment.block.cpp" }, - "17": { + "15": { "patterns": [ { "match": "\\*\\/", @@ -15019,134 +18601,35 @@ } ] }, - "18": { + "16": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "#inline_comment" } ] }, + "17": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "18": { + "name": "comment.block.cpp" + }, "19": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "20": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "21": { - "name": "comment.block.cpp" - }, - "22": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "23": { - "name": "entity.name.type.$3.cpp" - }, - "24": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "25": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "26": { - "name": "comment.block.cpp" - }, - "27": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "28": { - "name": "storage.type.modifier.final.cpp" - }, - "29": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "31": { - "name": "comment.block.cpp" - }, - "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "37": { "name": "punctuation.separator.colon.inheritance.cpp" - }, - "38": { - "patterns": [ - { - "include": "#inheritance_context" - } - ] } }, - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))", "endCaptures": { "1": { "name": "punctuation.terminator.statement.cpp" @@ -15155,16 +18638,18 @@ "name": "punctuation.terminator.statement.cpp" } }, + "name": "meta.block.union.cpp", "patterns": [ { - "name": "meta.head.union.cpp", "begin": "\\G ?", - "end": "((?:\\{|<%|\\?\\?<|(?=;)))", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.begin.bracket.curly.union.cpp" } }, + "name": "meta.head.union.cpp", "patterns": [ { "include": "#ever_present_context" @@ -15178,14 +18663,15 @@ ] }, { - "name": "meta.body.union.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "(\\}|%>|\\?\\?>)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.end.bracket.curly.union.cpp" } }, + "name": "meta.body.union.cpp", "patterns": [ { "include": "#function_pointer" @@ -15205,12 +18691,14 @@ ] }, { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, "name": "meta.tail.union.cpp", - "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)", "patterns": [ { - "match": "(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ @@ -15345,8 +18833,8 @@ ] }, "typeid_operator": { - "contentName": "meta.arguments.operator.typeid.cpp", - "begin": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.operator.typeid.cpp" @@ -15380,12 +18868,12 @@ "name": "punctuation.section.arguments.begin.bracket.round.operator.typeid.cpp" } }, - "end": "(\\))", "endCaptures": { - "1": { + "0": { "name": "punctuation.section.arguments.end.bracket.round.operator.typeid.cpp" } }, + "contentName": "meta.arguments.operator.typeid", "patterns": [ { "include": "#evaluation_context" @@ -15393,7 +18881,7 @@ ] }, "typename": { - "match": "(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|if|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<36>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<36>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|final|const|endif|ifdef|undef|error|using|audit|axiom|line|else|elif|true|NULL|case|goto|else|this|new|asm|not|and|xor|try|for|if|do|or|if)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<36>?)+)>)\\s*)?(?![\\w<:.]))", + "match": "(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<17>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<17>?)+>)?(?![\\w<:.]))", "captures": { "1": { "name": "storage.modifier.cpp" @@ -15406,61 +18894,80 @@ ] }, "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "6": { + "4": { "patterns": [ { "include": "#inline_comment" } ] }, - "7": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "8": { - "name": "comment.block.cpp" - }, - "9": { + "5": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "10": { + "6": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cpp" + } + }, + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] }, { "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", @@ -15485,7 +19009,7 @@ } ] }, - "11": { + "7": { "patterns": [ { "include": "#attributes_context" @@ -15495,128 +19019,136 @@ } ] }, - "12": { + "8": { "patterns": [ { "include": "#inline_comment" } ] }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "12": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.type.cpp" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "17": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "18": { - "name": "comment.block.cpp" - }, - "19": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "21": { - "patterns": [ - { - "include": "#scope_resolution_inner_generated" - } - ] - }, - "22": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" - }, - "24": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "26": { - "name": "entity.name.scope-resolution.cpp" - }, - "27": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "29": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" - }, - "30": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "31": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "32": { - "name": "comment.block.cpp" - }, - "33": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "34": { - "name": "entity.name.type.cpp" - }, - "35": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - } + "17": {} } }, "undef": { - "match": "((?:^)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*undef\\b)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?undef\\b)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "6": { + "4": { "name": "punctuation.definition.directive.cpp" }, - "7": { + "5": { "patterns": [ { "include": "#inline_comment" } ] }, - "8": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "9": { - "name": "comment.block.cpp" - }, - "10": { + "6": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "11": { + "7": { "name": "entity.name.function.preprocessor.cpp" } }, "name": "meta.preprocessor.undef.cpp" }, "union_block": { - "name": "meta.block.union.cpp", - "begin": "((((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?={)|(?:((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:(?!:)))?)", + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", "beginCaptures": { - "1": { + "0": { "name": "meta.head.union.cpp" }, - "3": { - "name": "storage.type.$3.cpp" + "1": { + "name": "storage.type.$1.cpp" }, - "4": { + "2": { "patterns": [ { "include": "#inline_comment" } ] }, - "5": { + "3": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "6": { + "4": { "name": "comment.block.cpp" }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, "7": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "8": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "#number_literal" - } - ] - }, - "9": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "10": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "11": { + "9": { "name": "comment.block.cpp" }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", + "captures": { + "1": { + "name": "storage.type.modifier.final.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=:|{|$)", + "captures": { + "1": { + "name": "entity.name.type.union.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "storage.type.modifier.final.cpp" + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + { + "match": "DLLEXPORT", + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.other.preprocessor.macro.predefined.probably.$0.cpp" + } + ] + }, "12": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "13": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "14": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "15": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "16": { + "14": { "name": "comment.block.cpp" }, - "17": { + "15": { "patterns": [ { "match": "\\*\\/", @@ -15778,134 +19431,35 @@ } ] }, - "18": { + "16": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "#inline_comment" } ] }, + "17": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "18": { + "name": "comment.block.cpp" + }, "19": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "20": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "21": { - "name": "comment.block.cpp" - }, - "22": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "23": { - "name": "entity.name.type.$3.cpp" - }, - "24": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "25": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "26": { - "name": "comment.block.cpp" - }, - "27": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "28": { - "name": "storage.type.modifier.final.cpp" - }, - "29": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "30": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "31": { - "name": "comment.block.cpp" - }, - "32": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "33": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "34": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "35": { - "name": "comment.block.cpp" - }, - "36": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "37": { "name": "punctuation.separator.colon.inheritance.cpp" - }, - "38": { - "patterns": [ - { - "include": "#inheritance_context" - } - ] } }, - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))", "endCaptures": { "1": { "name": "punctuation.terminator.statement.cpp" @@ -15914,16 +19468,18 @@ "name": "punctuation.terminator.statement.cpp" } }, + "name": "meta.block.union.cpp", "patterns": [ { - "name": "meta.head.union.cpp", "begin": "\\G ?", - "end": "((?:\\{|<%|\\?\\?<|(?=;)))", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.begin.bracket.curly.union.cpp" } }, + "name": "meta.head.union.cpp", "patterns": [ { "include": "#ever_present_context" @@ -15937,14 +19493,15 @@ ] }, { - "name": "meta.body.union.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "(\\}|%>|\\?\\?>)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, "endCaptures": { - "1": { + "0": { "name": "punctuation.section.block.end.bracket.curly.union.cpp" } }, + "name": "meta.body.union.cpp", "patterns": [ { "include": "#function_pointer" @@ -15964,9 +19521,11 @@ ] }, { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, "name": "meta.tail.union.cpp", - "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", - "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -15976,7 +19535,7 @@ ] }, "union_declare": { - "match": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\S)(?![:{])", + "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{a-zA-Z])", "captures": { "1": { "name": "storage.type.union.declare.cpp" @@ -15989,34 +19548,43 @@ ] }, "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, - "6": { + "4": { "name": "entity.name.type.union.cpp" }, - "7": { + "5": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", "captures": { "1": { "patterns": [ @@ -16052,6 +19620,40 @@ } ] }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, "8": { "patterns": [ { @@ -16060,105 +19662,107 @@ ] }, "9": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] }, "10": { - "name": "comment.block.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "11": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] }, "12": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "14": { - "name": "comment.block.cpp" - }, - "15": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "16": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "17": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "18": { - "name": "comment.block.cpp" - }, - "19": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "20": { "name": "variable.other.object.declare.cpp" }, - "21": { + "13": { "patterns": [ { "include": "#inline_comment" } ] }, - "22": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "23": { - "name": "comment.block.cpp" - }, - "24": { + "14": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } } ] } } }, "using_name": { - "match": "(using)\\s+(?!namespace\\b)", + "match": "(using)(?:\\s)+(?!namespace\\b)", "captures": { "1": { "name": "keyword.other.using.directive.cpp" @@ -16166,8 +19770,8 @@ } }, "using_namespace": { - "name": "meta.using-namespace.cpp", - "begin": "(?(?:(?>[^<>]*)\\g<7>?)+)>)\\s*)?::)*\\s*+)?((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<6>?)+>)(?:\\s)*+)?::)*\\s*+)?((?", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.end.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ",", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.delimiter.comma.template.argument.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "pugi", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp entity.name.scope-resolution.template.call.cpp", - "r": { - "dark_plus": "entity.name.scope-resolution: #4EC9B0", - "light_plus": "entity.name.scope-resolution: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.scope-resolution: #4EC9B0" - } - }, - { - "c": "::", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "xml_node", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp entity.name.type.cpp", - "r": { - "dark_plus": "entity.name.type: #4EC9B0", - "light_plus": "entity.name.type: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.type: #4EC9B0" - } - }, - { - "c": ",", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.delimiter.comma.template.argument.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "std", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp entity.name.scope-resolution.template.call.cpp", - "r": { - "dark_plus": "entity.name.scope-resolution: #4EC9B0", - "light_plus": "entity.name.scope-resolution: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.scope-resolution: #4EC9B0" - } - }, - { - "c": "::", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "less", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp entity.name.type.cpp", - "r": { - "dark_plus": "entity.name.type: #4EC9B0", - "light_plus": "entity.name.type: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.type: #4EC9B0" - } - }, - { - "c": "<", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.begin.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "std", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp entity.name.scope-resolution.template.call.cpp", - "r": { - "dark_plus": "entity.name.scope-resolution: #4EC9B0", - "light_plus": "entity.name.scope-resolution: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.scope-resolution: #4EC9B0" - } - }, - { - "c": "::", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "basic_string", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp entity.name.type.cpp", - "r": { - "dark_plus": "entity.name.type: #4EC9B0", - "light_plus": "entity.name.type: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.type: #4EC9B0" - } - }, - { - "c": "<", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.begin.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "char", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", - "r": { - "dark_plus": "storage.type: #569CD6", - "light_plus": "storage.type: #0000FF", - "dark_vs": "storage.type: #569CD6", - "light_vs": "storage.type: #0000FF", - "hc_black": "storage.type: #569CD6" - } - }, - { - "c": ">", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.end.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ">", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.end.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ",", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.delimiter.comma.template.argument.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "std", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp entity.name.scope-resolution.template.call.cpp", - "r": { - "dark_plus": "entity.name.scope-resolution: #4EC9B0", - "light_plus": "entity.name.scope-resolution: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.scope-resolution: #4EC9B0" - } - }, - { - "c": "::", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "allocator", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp entity.name.type.cpp", - "r": { - "dark_plus": "entity.name.type: #4EC9B0", - "light_plus": "entity.name.type: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.type: #4EC9B0" - } - }, - { - "c": "<", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.begin.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "std", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp entity.name.scope-resolution.template.call.cpp", - "r": { - "dark_plus": "entity.name.scope-resolution: #4EC9B0", - "light_plus": "entity.name.scope-resolution: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.scope-resolution: #4EC9B0" - } - }, - { - "c": "::", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "pair", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp entity.name.type.cpp", - "r": { - "dark_plus": "entity.name.type: #4EC9B0", - "light_plus": "entity.name.type: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.type: #4EC9B0" - } - }, - { - "c": "<", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.begin.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "const", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp storage.modifier.specifier.const.cpp", - "r": { - "dark_plus": "storage.modifier: #569CD6", - "light_plus": "storage.modifier: #0000FF", - "dark_vs": "storage.modifier: #569CD6", - "light_vs": "storage.modifier: #0000FF", - "hc_black": "storage.modifier: #569CD6" - } - }, - { - "c": " ", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "std", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp entity.name.scope-resolution.template.call.cpp", - "r": { - "dark_plus": "entity.name.scope-resolution: #4EC9B0", - "light_plus": "entity.name.scope-resolution: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.scope-resolution: #4EC9B0" - } - }, - { - "c": "::", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "basic_string", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp entity.name.type.cpp", - "r": { - "dark_plus": "entity.name.type: #4EC9B0", - "light_plus": "entity.name.type: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.type: #4EC9B0" - } - }, - { - "c": "<", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.begin.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "char", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", - "r": { - "dark_plus": "storage.type: #569CD6", - "light_plus": "storage.type: #0000FF", - "dark_vs": "storage.type: #569CD6", - "light_vs": "storage.type: #0000FF", - "hc_black": "storage.type: #569CD6" - } - }, - { - "c": ">", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.end.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ",", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.delimiter.comma.template.argument.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "pugi", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp entity.name.scope-resolution.template.call.cpp", - "r": { - "dark_plus": "entity.name.scope-resolution: #4EC9B0", - "light_plus": "entity.name.scope-resolution: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.scope-resolution: #4EC9B0" - } - }, - { - "c": "::", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "xml_node", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp entity.name.type.cpp", - "r": { - "dark_plus": "entity.name.type: #4EC9B0", - "light_plus": "entity.name.type: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.type: #4EC9B0" - } - }, - { - "c": ">", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.end.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ">", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.end.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ">", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.end.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ">", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.end.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ">", - "t": "source.cpp meta.template.call.cpp meta.template.call.cpp punctuation.section.angle-brackets.end.template.call.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "::", - "t": "source.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "type dnode", + "c": "std::tuple_element<0, std::pair, pugi::xml_node, std::less >, std::allocator, pugi::xml_node> > > > >::type dnode", "t": "source.cpp", "r": { "dark_plus": "default: #D4D4D4", @@ -869,29 +11,7 @@ } }, { - "c": "std", - "t": "source.cpp entity.name.scope-resolution.cpp", - "r": { - "dark_plus": "entity.name.scope-resolution: #4EC9B0", - "light_plus": "entity.name.scope-resolution: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.scope-resolution: #4EC9B0" - } - }, - { - "c": "::", - "t": "source.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "_Rb_tree_iterator", + "c": "std::_Rb_tree_iterator, pugi::xml_node, std::less >, std::allocator, pugi::xml_node> > > > > > dnode_it = dnodes_.find(uid.position)", "t": "source.cpp", "r": { "dark_plus": "default: #D4D4D4", @@ -900,1028 +20,5 @@ "light_vs": "default: #000000", "hc_black": "default: #FFFFFF" } - }, - { - "c": "<", - "t": "source.cpp keyword.operator.comparison.cpp", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": "std", - "t": "source.cpp entity.name.scope-resolution.cpp", - "r": { - "dark_plus": "entity.name.scope-resolution: #4EC9B0", - "light_plus": "entity.name.scope-resolution: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.scope-resolution: #4EC9B0" - } - }, - { - "c": "::", - "t": "source.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "pair", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<", - "t": "source.cpp keyword.operator.comparison.cpp", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": "const", - "t": "source.cpp storage.modifier.specifier.const.cpp", - "r": { - "dark_plus": "storage.modifier: #569CD6", - "light_plus": "storage.modifier: #0000FF", - "dark_vs": "storage.modifier: #569CD6", - "light_vs": "storage.modifier: #0000FF", - "hc_black": "storage.modifier: #569CD6" - } - }, - { - "c": " ", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "long", - "t": "source.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", - "r": { - "dark_plus": "storage.type: #569CD6", - "light_plus": "storage.type: #0000FF", - "dark_vs": "storage.type: #569CD6", - "light_vs": "storage.type: #0000FF", - "hc_black": "storage.type: #569CD6" - } - }, - { - "c": ",", - "t": "source.cpp punctuation.separator.delimiter.comma.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "std", - "t": "source.cpp entity.name.scope-resolution.cpp", - "r": { - "dark_plus": "entity.name.scope-resolution: #4EC9B0", - "light_plus": "entity.name.scope-resolution: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.scope-resolution: #4EC9B0" - } - }, - { - "c": "::", - "t": "source.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "pair", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<", - "t": "source.cpp keyword.operator.comparison.cpp", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": "pugi", - "t": "source.cpp entity.name.scope-resolution.cpp", - "r": { - "dark_plus": "entity.name.scope-resolution: #4EC9B0", - "light_plus": "entity.name.scope-resolution: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.scope-resolution: #4EC9B0" - } - }, - { - "c": "::", - "t": "source.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "xml_node", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ",", - "t": "source.cpp punctuation.separator.delimiter.comma.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "std", - "t": "source.cpp entity.name.scope-resolution.cpp", - "r": { - "dark_plus": "entity.name.scope-resolution: #4EC9B0", - "light_plus": "entity.name.scope-resolution: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.scope-resolution: #4EC9B0" - } - }, - { - "c": "::", - "t": "source.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "map", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<", - "t": "source.cpp keyword.operator.comparison.cpp", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": "std", - "t": "source.cpp entity.name.scope-resolution.cpp", - "r": { - "dark_plus": "entity.name.scope-resolution: #4EC9B0", - "light_plus": "entity.name.scope-resolution: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.scope-resolution: #4EC9B0" - } - }, - { - "c": "::", - "t": "source.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "basic_string", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<", - "t": "source.cpp keyword.operator.comparison.cpp", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": "char", - "t": "source.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", - "r": { - "dark_plus": "storage.type: #569CD6", - "light_plus": "storage.type: #0000FF", - "dark_vs": "storage.type: #569CD6", - "light_vs": "storage.type: #0000FF", - "hc_black": "storage.type: #569CD6" - } - }, - { - "c": ">", - "t": "source.cpp keyword.operator.comparison.cpp", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": ",", - "t": "source.cpp punctuation.separator.delimiter.comma.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "pugi", - "t": "source.cpp entity.name.scope-resolution.cpp", - "r": { - "dark_plus": "entity.name.scope-resolution: #4EC9B0", - "light_plus": "entity.name.scope-resolution: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.scope-resolution: #4EC9B0" - } - }, - { - "c": "::", - "t": "source.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "xml_node", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ",", - "t": "source.cpp punctuation.separator.delimiter.comma.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "std", - "t": "source.cpp entity.name.scope-resolution.cpp", - "r": { - "dark_plus": "entity.name.scope-resolution: #4EC9B0", - "light_plus": "entity.name.scope-resolution: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.scope-resolution: #4EC9B0" - } - }, - { - "c": "::", - "t": "source.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "less", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<", - "t": "source.cpp keyword.operator.comparison.cpp", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": "std", - "t": "source.cpp entity.name.scope-resolution.cpp", - "r": { - "dark_plus": "entity.name.scope-resolution: #4EC9B0", - "light_plus": "entity.name.scope-resolution: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.scope-resolution: #4EC9B0" - } - }, - { - "c": "::", - "t": "source.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "basic_string", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<", - "t": "source.cpp keyword.operator.comparison.cpp", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": "char", - "t": "source.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", - "r": { - "dark_plus": "storage.type: #569CD6", - "light_plus": "storage.type: #0000FF", - "dark_vs": "storage.type: #569CD6", - "light_vs": "storage.type: #0000FF", - "hc_black": "storage.type: #569CD6" - } - }, - { - "c": ">", - "t": "source.cpp keyword.operator.comparison.cpp", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": " ", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ">", - "t": "source.cpp keyword.operator.comparison.cpp", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": ",", - "t": "source.cpp punctuation.separator.delimiter.comma.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "std", - "t": "source.cpp entity.name.scope-resolution.cpp", - "r": { - "dark_plus": "entity.name.scope-resolution: #4EC9B0", - "light_plus": "entity.name.scope-resolution: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.scope-resolution: #4EC9B0" - } - }, - { - "c": "::", - "t": "source.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "allocator", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<", - "t": "source.cpp keyword.operator.comparison.cpp", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": "std", - "t": "source.cpp entity.name.scope-resolution.cpp", - "r": { - "dark_plus": "entity.name.scope-resolution: #4EC9B0", - "light_plus": "entity.name.scope-resolution: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.scope-resolution: #4EC9B0" - } - }, - { - "c": "::", - "t": "source.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "pair", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<", - "t": "source.cpp keyword.operator.comparison.cpp", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": "const", - "t": "source.cpp storage.modifier.specifier.const.cpp", - "r": { - "dark_plus": "storage.modifier: #569CD6", - "light_plus": "storage.modifier: #0000FF", - "dark_vs": "storage.modifier: #569CD6", - "light_vs": "storage.modifier: #0000FF", - "hc_black": "storage.modifier: #569CD6" - } - }, - { - "c": " ", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "std", - "t": "source.cpp entity.name.scope-resolution.cpp", - "r": { - "dark_plus": "entity.name.scope-resolution: #4EC9B0", - "light_plus": "entity.name.scope-resolution: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.scope-resolution: #4EC9B0" - } - }, - { - "c": "::", - "t": "source.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "basic_string", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<", - "t": "source.cpp keyword.operator.comparison.cpp", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": "char", - "t": "source.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", - "r": { - "dark_plus": "storage.type: #569CD6", - "light_plus": "storage.type: #0000FF", - "dark_vs": "storage.type: #569CD6", - "light_vs": "storage.type: #0000FF", - "hc_black": "storage.type: #569CD6" - } - }, - { - "c": ">", - "t": "source.cpp keyword.operator.comparison.cpp", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": ",", - "t": "source.cpp punctuation.separator.delimiter.comma.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "pugi", - "t": "source.cpp entity.name.scope-resolution.cpp", - "r": { - "dark_plus": "entity.name.scope-resolution: #4EC9B0", - "light_plus": "entity.name.scope-resolution: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.scope-resolution: #4EC9B0" - } - }, - { - "c": "::", - "t": "source.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "xml_node", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ">", - "t": "source.cpp keyword.operator.comparison.cpp", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": " ", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ">", - "t": "source.cpp keyword.operator.comparison.cpp", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": " ", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ">", - "t": "source.cpp keyword.operator.comparison.cpp", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": " ", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ">", - "t": "source.cpp keyword.operator.comparison.cpp", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": " ", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ">", - "t": "source.cpp keyword.operator.comparison.cpp", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": " ", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ">", - "t": "source.cpp keyword.operator.comparison.cpp", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": " dnode_it ", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "=", - "t": "source.cpp keyword.operator.assignment.cpp", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": " ", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "dnodes_", - "t": "source.cpp variable.other.object.access.cpp", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ".", - "t": "source.cpp punctuation.separator.dot-access.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "find", - "t": "source.cpp entity.name.function.member.cpp", - "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.function: #DCDCAA" - } - }, - { - "c": "(", - "t": "source.cpp punctuation.section.arguments.begin.bracket.round.function.member.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "uid", - "t": "source.cpp variable.other.object.access.cpp", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ".", - "t": "source.cpp punctuation.separator.dot-access.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "position", - "t": "source.cpp variable.other.property.cpp", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ")", - "t": "source.cpp punctuation.section.arguments.end.bracket.round.function.member.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } } ] \ No newline at end of file diff --git a/extensions/cpp/test/colorize-results/test_cc.json b/extensions/cpp/test/colorize-results/test_cc.json index 324b231bb..cb2fb192e 100644 --- a/extensions/cpp/test/colorize-results/test_cc.json +++ b/extensions/cpp/test/colorize-results/test_cc.json @@ -122,7 +122,7 @@ }, { "c": "%d", - "t": "source.cpp string.quoted.double.cpp constant.other.placeholder.cpp", + "t": "source.cpp string.quoted.double.cpp constant.other.placeholder", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -430,7 +430,7 @@ }, { "c": "%d", - "t": "source.cpp string.quoted.double.cpp constant.other.placeholder.cpp", + "t": "source.cpp string.quoted.double.cpp constant.other.placeholder", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -474,7 +474,7 @@ }, { "c": "user_candidate", - "t": "source.cpp meta.bracket.square.access.cpp variable.other.object.cpp", + "t": "source.cpp meta.bracket.square.access variable.other.object", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -485,7 +485,7 @@ }, { "c": "[", - "t": "source.cpp meta.bracket.square.access.cpp punctuation.definition.begin.bracket.square.cpp", + "t": "source.cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -496,7 +496,7 @@ }, { "c": "i", - "t": "source.cpp meta.bracket.square.access.cpp", + "t": "source.cpp meta.bracket.square.access", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -507,7 +507,7 @@ }, { "c": "]", - "t": "source.cpp meta.bracket.square.access.cpp punctuation.definition.end.bracket.square.cpp", + "t": "source.cpp meta.bracket.square.access punctuation.definition.end.bracket.square", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -727,7 +727,7 @@ }, { "c": "O", - "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp entity.name.type.parameter.cpp", + "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp entity.name.type.parameter.cpp", "r": { "dark_plus": "entity.name.type: #4EC9B0", "light_plus": "entity.name.type: #267F99", @@ -738,7 +738,7 @@ }, { "c": " ", - "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp", + "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -749,7 +749,7 @@ }, { "c": "obj", - "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp variable.parameter.cpp", + "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp variable.parameter.cpp", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -1464,7 +1464,7 @@ }, { "c": "%s", - "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp string.quoted.double.cpp constant.other.placeholder.cpp", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp string.quoted.double.cpp constant.other.placeholder", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -1486,7 +1486,7 @@ }, { "c": "%s", - "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp string.quoted.double.cpp constant.other.placeholder.cpp", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp string.quoted.double.cpp constant.other.placeholder", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -1805,7 +1805,7 @@ }, { "c": "movw $0x38, %ax; ltr %ax", - "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp meta.asm.cpp string.quoted.double.cpp meta.embedded.assembly.cpp", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp meta.asm.cpp string.quoted.double.cpp meta.embedded.assembly", "r": { "dark_plus": "meta.embedded.assembly: #CE9178", "light_plus": "meta.embedded.assembly: #A31515", @@ -1979,4 +1979,4 @@ "hc_black": "default: #FFFFFF" } } -] +] \ No newline at end of file diff --git a/extensions/cpp/test/colorize-results/test_cpp.json b/extensions/cpp/test/colorize-results/test_cpp.json index 10f6041bb..7752bbfa6 100644 --- a/extensions/cpp/test/colorize-results/test_cpp.json +++ b/extensions/cpp/test/colorize-results/test_cpp.json @@ -287,7 +287,7 @@ }, { "c": "Rectangle", - "t": "source.cpp meta.block.class.cpp meta.head.class.cpp entity.name.type.class.cpp", + "t": "source.cpp meta.block.class.cpp entity.name.type.class.cpp", "r": { "dark_plus": "entity.name.type: #4EC9B0", "light_plus": "entity.name.type: #267F99", @@ -298,7 +298,7 @@ }, { "c": " ", - "t": "source.cpp meta.block.class.cpp meta.head.class.cpp", + "t": "source.cpp meta.block.class.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -485,7 +485,7 @@ }, { "c": "int", - "t": "source.cpp meta.block.class.cpp meta.body.class.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "t": "source.cpp meta.block.class.cpp meta.body.class.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -496,7 +496,7 @@ }, { "c": ",", - "t": "source.cpp meta.block.class.cpp meta.body.class.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp punctuation.separator.delimiter.comma.cpp", + "t": "source.cpp meta.block.class.cpp meta.body.class.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp punctuation.separator.delimiter.comma.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -507,7 +507,7 @@ }, { "c": "int", - "t": "source.cpp meta.block.class.cpp meta.body.class.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "t": "source.cpp meta.block.class.cpp meta.body.class.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -793,7 +793,7 @@ }, { "c": "int", - "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -804,7 +804,7 @@ }, { "c": " ", - "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp", + "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -815,7 +815,7 @@ }, { "c": "x", - "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp variable.parameter.cpp", + "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp variable.parameter.cpp", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -826,7 +826,7 @@ }, { "c": ",", - "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp punctuation.separator.delimiter.comma.cpp", + "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp punctuation.separator.delimiter.comma.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -837,7 +837,7 @@ }, { "c": " ", - "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp", + "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -848,7 +848,7 @@ }, { "c": "int", - "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -859,7 +859,7 @@ }, { "c": " ", - "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp", + "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -870,7 +870,7 @@ }, { "c": "y", - "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp variable.parameter.cpp", + "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp variable.parameter.cpp", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -1123,7 +1123,7 @@ }, { "c": "long", - "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.head.function.definition.special.operator-overload.cpp meta.function.definition.parameters.special.operator-overload.cpp meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.head.function.definition.special.operator-overload.cpp meta.function.definition.parameters.special.operator-overload meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -1134,7 +1134,7 @@ }, { "c": " ", - "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.head.function.definition.special.operator-overload.cpp meta.function.definition.parameters.special.operator-overload.cpp meta.parameter.cpp", + "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.head.function.definition.special.operator-overload.cpp meta.function.definition.parameters.special.operator-overload meta.parameter.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1145,7 +1145,7 @@ }, { "c": "double", - "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.head.function.definition.special.operator-overload.cpp meta.function.definition.parameters.special.operator-overload.cpp meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.head.function.definition.special.operator-overload.cpp meta.function.definition.parameters.special.operator-overload meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -1519,7 +1519,7 @@ }, { "c": "movl %a %b", - "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp meta.asm.cpp string.quoted.double.cpp meta.embedded.assembly.cpp", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp meta.asm.cpp string.quoted.double.cpp meta.embedded.assembly", "r": { "dark_plus": "meta.embedded.assembly: #CE9178", "light_plus": "meta.embedded.assembly: #A31515", diff --git a/extensions/css-language-features/.vscode/launch.json b/extensions/css-language-features/.vscode/launch.json index 4f7166e6d..8fccefff4 100644 --- a/extensions/css-language-features/.vscode/launch.json +++ b/extensions/css-language-features/.vscode/launch.json @@ -1,14 +1,5 @@ { "version": "0.2.0", - "compounds": [ - { - "name": "Debug Extension and Language Server", - "configurations": [ - "Launch Extension", - "Attach Language Server" - ] - } - ], "configurations": [ { "name": "Launch Extension", @@ -41,19 +32,6 @@ ] }, { - "name": "Attach Language Server", - "type": "node", - "request": "attach", - "protocol": "inspector", - "port": 6044, - "sourceMaps": true, - "outFiles": [ - "${workspaceFolder}/server/out/**/*.js" - ], - "smartStep": true, - "restart": true - }, - { "name": "Server Unit Tests", "type": "node", "request": "launch", @@ -74,4 +52,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/extensions/css-language-features/client/src/node/cssClientMain.ts b/extensions/css-language-features/client/src/node/cssClientMain.ts index cc675b494..b88838d89 100644 --- a/extensions/css-language-features/client/src/node/cssClientMain.ts +++ b/extensions/css-language-features/client/src/node/cssClientMain.ts @@ -11,14 +11,13 @@ import { TextDecoder } from 'util'; // this method is called when vs code is activated export function activate(context: ExtensionContext) { - const clientMain = extensions.getExtension('vscode.css-language-features')?.packageJSON?.main || ''; const serverMain = `./server/${clientMain.indexOf('/dist/') !== -1 ? 'dist' : 'out'}/node/cssServerMain`; const serverModule = context.asAbsolutePath(serverMain); // The debug options for the server - const debugOptions = { execArgv: ['--nolazy', '--inspect=6044'] }; + const debugOptions = { execArgv: ['--nolazy', '--inspect=' + (7000 + Math.round(Math.random() * 999))] }; // If the extension is launch in debug mode the debug server options are use // Otherwise the run options are used diff --git a/extensions/css-language-features/server/package.json b/extensions/css-language-features/server/package.json index f249a512a..5a2cd56cf 100644 --- a/extensions/css-language-features/server/package.json +++ b/extensions/css-language-features/server/package.json @@ -10,7 +10,7 @@ "main": "./out/node/cssServerMain", "browser": "./dist/browser/cssServerMain", "dependencies": { - "vscode-css-languageservice": "^4.3.5", + "vscode-css-languageservice": "^4.4.0", "vscode-languageserver": "7.0.0-next.3", "vscode-uri": "^2.1.2" }, diff --git a/extensions/css-language-features/server/src/cssServer.ts b/extensions/css-language-features/server/src/cssServer.ts index 0b30955bd..e62a8ecc2 100644 --- a/extensions/css-language-features/server/src/cssServer.ts +++ b/extensions/css-language-features/server/src/cssServer.ts @@ -90,7 +90,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) const capabilities: ServerCapabilities = { textDocumentSync: TextDocumentSyncKind.Incremental, - completionProvider: snippetSupport ? { resolveProvider: false, triggerCharacters: ['/', '-'] } : undefined, + completionProvider: snippetSupport ? { resolveProvider: false, triggerCharacters: ['/', '-', ':'] } : undefined, hoverProvider: true, documentSymbolProvider: true, referencesProvider: true, diff --git a/extensions/css-language-features/server/yarn.lock b/extensions/css-language-features/server/yarn.lock index 2d2ffcf8e..af0eddbb9 100644 --- a/extensions/css-language-features/server/yarn.lock +++ b/extensions/css-language-features/server/yarn.lock @@ -696,10 +696,10 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -vscode-css-languageservice@^4.3.5: - version "4.3.5" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.3.5.tgz#92f8817057dee7c381df2289aad539c7b553548a" - integrity sha512-g9Pjxt9T32jhY0nTOo7WRFm0As27IfdaAxcFa8c7Rml1ZqBn3XXbkExjzxY7sBWYm7I1Tp4dK6UHXHoUQHGwig== +vscode-css-languageservice@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.4.0.tgz#a7c5edf3057e707601ca18fa3728784a298513b4" + integrity sha512-jWi+297PJUUWTHwlcrZz0zIuEXuHOBJIQMapXmEzbosWGv/gMnNSAMV4hTKnl5wzxvZKZzV6j+WFdrSlKQ5qnw== dependencies: vscode-languageserver-textdocument "^1.0.1" vscode-languageserver-types "3.16.0-next.2" diff --git a/extensions/debug-auto-launch/src/extension.ts b/extensions/debug-auto-launch/src/extension.ts index 5ae907a9d..b47641f2d 100644 --- a/extensions/debug-auto-launch/src/extension.ts +++ b/extensions/debug-auto-launch/src/extension.ts @@ -9,9 +9,13 @@ import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); -const TEXT_ALWAYS = localize('status.text.auto.attach.always', 'Auto Attach: Always'); -const TEXT_SMART = localize('status.text.auto.attach.smart', 'Auto Attach: Smart'); -const TEXT_WITH_FLAG = localize('status.text.auto.attach.withFlag', 'Auto Attach: With Flag'); +const TEXT_STATUSBAR_LABEL = { + [State.Disabled]: localize('status.text.auto.attach.disabled', 'Auto Attach: Disabled'), + [State.Always]: localize('status.text.auto.attach.always', 'Auto Attach: Always'), + [State.Smart]: localize('status.text.auto.attach.smart', 'Auto Attach: Smart'), + [State.OnlyWithFlag]: localize('status.text.auto.attach.withFlag', 'Auto Attach: With Flag'), +}; + const TEXT_STATE_LABEL = { [State.Disabled]: localize('debug.javascript.autoAttach.disabled.label', 'Disabled'), [State.Always]: localize('debug.javascript.autoAttach.always.label', 'Always'), @@ -41,6 +45,9 @@ const TEXT_STATE_DESCRIPTION = { }; const TEXT_TOGGLE_WORKSPACE = localize('scope.workspace', 'Toggle auto attach in this workspace'); const TEXT_TOGGLE_GLOBAL = localize('scope.global', 'Toggle auto attach on this machine'); +const TEXT_TEMP_DISABLE = localize('tempDisable.disable', 'Temporarily disable auto attach in this session'); +const TEXT_TEMP_ENABLE = localize('tempDisable.enable', 'Re-enable auto attach'); +const TEXT_TEMP_DISABLE_LABEL = localize('tempDisable.suffix', 'Auto Attach: Disabled'); const TOGGLE_COMMAND = 'extension.node-debug.toggleAutoAttach'; const STORAGE_IPC = 'jsDebugIpcState'; @@ -65,12 +72,13 @@ const enum State { let currentState: Promise<{ context: vscode.ExtensionContext; state: State | null }>; let statusItem: vscode.StatusBarItem | undefined; // and there is no status bar item let server: Promise | undefined; // auto attach server +let isTemporarilyDisabled = false; // whether the auto attach server is disabled temporarily, reset whenever the state changes export function activate(context: vscode.ExtensionContext): void { currentState = Promise.resolve({ context, state: null }); context.subscriptions.push( - vscode.commands.registerCommand(TOGGLE_COMMAND, toggleAutoAttachSetting), + vscode.commands.registerCommand(TOGGLE_COMMAND, toggleAutoAttachSetting.bind(null, context)), ); context.subscriptions.push( @@ -108,24 +116,36 @@ function getDefaultScope(info: ReturnType { +async function toggleAutoAttachSetting(context: vscode.ExtensionContext, scope?: vscode.ConfigurationTarget): Promise { const section = vscode.workspace.getConfiguration(SETTING_SECTION); scope = scope || getDefaultScope(section.inspect(SETTING_STATE)); const isGlobalScope = scope === vscode.ConfigurationTarget.Global; - const quickPick = vscode.window.createQuickPick(); + const quickPick = vscode.window.createQuickPick(); const current = readCurrentState(); - quickPick.items = [State.Always, State.Smart, State.OnlyWithFlag, State.Disabled].map(state => ({ + const items: PickItem[] = [State.Always, State.Smart, State.OnlyWithFlag, State.Disabled].map(state => ({ state, label: TEXT_STATE_LABEL[state], description: TEXT_STATE_DESCRIPTION[state], alwaysShow: true, })); - quickPick.activeItems = quickPick.items.filter(i => i.state === current); + if (current !== State.Disabled) { + items.unshift({ + setTempDisabled: !isTemporarilyDisabled, + label: isTemporarilyDisabled ? TEXT_TEMP_ENABLE : TEXT_TEMP_DISABLE, + alwaysShow: true, + }); + } + + quickPick.items = items; + quickPick.activeItems = isTemporarilyDisabled + ? [items[0]] + : quickPick.items.filter(i => 'state' in i && i.state === current); quickPick.title = isGlobalScope ? TEXT_TOGGLE_GLOBAL : TEXT_TOGGLE_WORKSPACE; quickPick.buttons = [ { @@ -136,7 +156,7 @@ async function toggleAutoAttachSetting(scope?: vscode.ConfigurationTarget): Prom quickPick.show(); - const result = await new Promise(resolve => { + let result = await new Promise(resolve => { quickPick.onDidAccept(() => resolve(quickPick.selectedItems[0])); quickPick.onDidHide(() => resolve(undefined)); quickPick.onDidTriggerButton(() => { @@ -155,11 +175,26 @@ async function toggleAutoAttachSetting(scope?: vscode.ConfigurationTarget): Prom } if ('scope' in result) { - return await toggleAutoAttachSetting(result.scope); + return await toggleAutoAttachSetting(context, result.scope); } if ('state' in result) { - section.update(SETTING_STATE, result.state, scope); + if (result.state !== current) { + section.update(SETTING_STATE, result.state, scope); + } else if (isTemporarilyDisabled) { + result = { setTempDisabled: false }; + } + } + + if ('setTempDisabled' in result) { + updateStatusBar(context, current, true); + isTemporarilyDisabled = result.setTempDisabled; + if (result.setTempDisabled) { + await destroyAttachServer(); + } else { + await createAttachServer(context); // unsets temp disabled var internally + } + updateStatusBar(context, current, false); } } @@ -168,26 +203,6 @@ function readCurrentState(): State { return section.get(SETTING_STATE) ?? State.Disabled; } -/** - * Makes sure the status bar exists and is visible. - */ -function ensureStatusBarExists(context: vscode.ExtensionContext) { - if (!statusItem) { - statusItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); - statusItem.command = TOGGLE_COMMAND; - statusItem.tooltip = localize( - 'status.tooltip.auto.attach', - 'Automatically attach to node.js processes in debug mode', - ); - statusItem.show(); - context.subscriptions.push(statusItem); - } else { - statusItem.show(); - } - - return statusItem; -} - async function clearJsDebugAttachState(context: vscode.ExtensionContext) { await context.workspaceState.update(STORAGE_IPC, undefined); await vscode.commands.executeCommand('extension.js-debug.clearAutoAttachVariables'); @@ -275,28 +290,46 @@ interface CachedIpcState { const transitions: { [S in State]: (context: vscode.ExtensionContext) => Promise } = { async [State.Disabled](context) { await clearJsDebugAttachState(context); - statusItem?.hide(); }, async [State.OnlyWithFlag](context) { await createAttachServer(context); - const statusItem = ensureStatusBarExists(context); - statusItem.text = TEXT_WITH_FLAG; }, async [State.Smart](context) { await createAttachServer(context); - const statusItem = ensureStatusBarExists(context); - statusItem.text = TEXT_SMART; }, async [State.Always](context) { await createAttachServer(context); - const statusItem = ensureStatusBarExists(context); - statusItem.text = TEXT_ALWAYS; }, }; +/** + * Ensures the status bar text reflects the current state. + */ +function updateStatusBar(context: vscode.ExtensionContext, state: State, busy = false) { + if (state === State.Disabled && !busy) { + statusItem?.hide(); + return; + } + + if (!statusItem) { + statusItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); + statusItem.command = TOGGLE_COMMAND; + statusItem.tooltip = localize( + 'status.tooltip.auto.attach', + 'Automatically attach to node.js processes in debug mode', + ); + context.subscriptions.push(statusItem); + } + + let text = busy ? '$(loading) ' : ''; + text += isTemporarilyDisabled ? TEXT_TEMP_DISABLE_LABEL : TEXT_STATUSBAR_LABEL[state]; + statusItem.text = text; + statusItem.show(); +} + /** * Updates the auto attach feature based on the user or workspace setting */ @@ -306,7 +339,13 @@ function updateAutoAttach(newState: State) { return { context, state: oldState }; } + if (oldState !== null) { + updateStatusBar(context, oldState, true); + } + await transitions[newState](context); + isTemporarilyDisabled = false; + updateStatusBar(context, newState, false); return { context, state: newState }; }); } diff --git a/extensions/debug-server-ready/package.json b/extensions/debug-server-ready/package.json index abb9a683e..111699064 100644 --- a/extensions/debug-server-ready/package.json +++ b/extensions/debug-server-ready/package.json @@ -39,8 +39,7 @@ "openExternally" ], "enumDescriptions": [ - "%debug.server.ready.action.openExternally.description%", - "%debug.server.ready.action.debugWithChrome.description%" + "%debug.server.ready.action.openExternally.description%" ], "markdownDescription": "%debug.server.ready.action.description%", "default": "openExternally" @@ -71,7 +70,6 @@ "debugWithChrome" ], "enumDescriptions": [ - "%debug.server.ready.action.openExternally.description%", "%debug.server.ready.action.debugWithChrome.description%" ], "markdownDescription": "%debug.server.ready.action.description%", @@ -93,6 +91,39 @@ "default": "${workspaceFolder}" } } + }, + { + "type": "object", + "additionalProperties": false, + "markdownDescription": "%debug.server.ready.serverReadyAction.description%", + "default": { + "action": "startDebugging", + "name": "" + }, + "required": ["name"], + "properties": { + "action": { + "type": "string", + "enum": [ + "startDebugging" + ], + "enumDescriptions": [ + "%debug.server.ready.action.startDebugging.description%" + ], + "markdownDescription": "%debug.server.ready.action.description%", + "default": "startDebugging" + }, + "pattern": { + "type": "string", + "markdownDescription": "%debug.server.ready.pattern.description%", + "default": "listening on port ([0-9]+)" + }, + "name": { + "type": "string", + "markdownDescription": "%debug.server.ready.debugConfigName.description%", + "default": "Launch Browser" + } + } } ] } diff --git a/extensions/debug-server-ready/package.nls.json b/extensions/debug-server-ready/package.nls.json index e9d2705cf..c5212d7ee 100644 --- a/extensions/debug-server-ready/package.nls.json +++ b/extensions/debug-server-ready/package.nls.json @@ -6,7 +6,9 @@ "debug.server.ready.action.description": "What to do with the URI when the server is ready.", "debug.server.ready.action.openExternally.description": "Open URI externally with the default application.", "debug.server.ready.action.debugWithChrome.description": "Start debugging with the 'Debugger for Chrome'.", + "debug.server.ready.action.startDebugging.description": "Run another launch configuration.", "debug.server.ready.pattern.description": "Server is ready if this pattern appears on the debug console. The first capture group must include a URI or a port number.", "debug.server.ready.uriFormat.description": "A format string used when constructing the URI from a port number. The first '%s' is substituted with the port number.", - "debug.server.ready.webRoot.description": "Value passed to the debug configuration for the 'Debugger for Chrome'." + "debug.server.ready.webRoot.description": "Value passed to the debug configuration for the 'Debugger for Chrome'.", + "debug.server.ready.debugConfigName.description": "Name of the launch configuration to run." } diff --git a/extensions/debug-server-ready/src/extension.ts b/extensions/debug-server-ready/src/extension.ts index 9f8737f30..19fc67322 100644 --- a/extensions/debug-server-ready/src/extension.ts +++ b/extensions/debug-server-ready/src/extension.ts @@ -16,9 +16,10 @@ const WEB_ROOT = '${workspaceFolder}'; interface ServerReadyAction { pattern: string; - action?: 'openExternally' | 'debugWithChrome'; + action?: 'openExternally' | 'debugWithChrome' | 'startDebugging'; uriFormat?: string; webRoot?: string; + name?: string; } class ServerReadyDetector extends vscode.Disposable { @@ -155,6 +156,10 @@ class ServerReadyDetector extends vscode.Disposable { }); break; + case 'startDebugging': + vscode.debug.startDebugging(session.workspaceFolder, args.name || 'unspecified'); + break; + default: // not supported break; diff --git a/extensions/emmet/package.json b/extensions/emmet/package.json index fbb17884a..12178fa8b 100644 --- a/extensions/emmet/package.json +++ b/extensions/emmet/package.json @@ -342,6 +342,11 @@ "command": "editor.emmet.action.reflectCSSValue", "title": "%command.reflectCSSValue%", "category": "Emmet" + }, + { + "command": "workbench.action.showEmmetCommands", + "title": "%command.showEmmetCommands%", + "category": "" } ], "menus": { @@ -433,7 +438,7 @@ "dependencies": { "@emmetio/css-parser": "ramya-rao-a/css-parser#vscode", "@emmetio/html-matcher": "^0.3.3", - "@emmetio/math-expression": "^0.1.1", + "@emmetio/math-expression": "^1.0.4", "image-size": "^0.5.2", "vscode-emmet-helper": "~2.0.0", "vscode-html-languageservice": "^3.0.3" diff --git a/extensions/emmet/package.nls.json b/extensions/emmet/package.nls.json index 3f3bf912f..2a1add893 100644 --- a/extensions/emmet/package.nls.json +++ b/extensions/emmet/package.nls.json @@ -23,6 +23,7 @@ "command.decrementNumberByOneTenth": "Decrement by 0.1", "command.incrementNumberByTen": "Increment by 10", "command.decrementNumberByTen": "Decrement by 10", + "command.showEmmetCommands": "Show Emmet Commands", "emmetSyntaxProfiles": "Define profile for specified syntax or use your own profile with specific rules.", "emmetExclude": "An array of languages where Emmet abbreviations should not be expanded.", "emmetExtensionsPath": "Path to a folder containing Emmet profiles and snippets.", diff --git a/extensions/emmet/src/abbreviationActions.ts b/extensions/emmet/src/abbreviationActions.ts index 16dc7b61d..a37741b8e 100644 --- a/extensions/emmet/src/abbreviationActions.ts +++ b/extensions/emmet/src/abbreviationActions.ts @@ -138,6 +138,7 @@ function doWrapping(individualLines: boolean, args: any) { newText = newText.replace(/\$\{[\d]*(:[^}]*)?\}/g, (match) => { // Replacing Placeholders return match.replace(/^\$\{[\d]*:/, '').replace('}', ''); }); + newText = newText.replace(/\\\$/g, '$'); // Remove backslashes before $ builder.replace(oldPreviewRange, newText); const expandedTextLines = newText.split('\n'); diff --git a/extensions/emmet/src/emmetCommon.ts b/extensions/emmet/src/emmetCommon.ts index b508b2a9f..e768b03af 100644 --- a/extensions/emmet/src/emmetCommon.ts +++ b/extensions/emmet/src/emmetCommon.ts @@ -124,6 +124,10 @@ export function activateEmmetExtension(context: vscode.ExtensionContext) { return reflectCssValue(); })); + context.subscriptions.push(vscode.commands.registerCommand('workbench.action.showEmmetCommands', () => { + vscode.commands.executeCommand('workbench.action.quickOpen', '>Emmet: '); + })); + updateEmmetExtensionsPath(); context.subscriptions.push(vscode.workspace.onDidChangeConfiguration((e) => { diff --git a/extensions/emmet/src/evaluateMathExpression.ts b/extensions/emmet/src/evaluateMathExpression.ts index 8a7de6ca4..588d4dce9 100644 --- a/extensions/emmet/src/evaluateMathExpression.ts +++ b/extensions/emmet/src/evaluateMathExpression.ts @@ -6,24 +6,42 @@ /* Based on @sergeche's work in his emmet plugin */ import * as vscode from 'vscode'; -import evaluate from '@emmetio/math-expression'; +import evaluate, { extract } from '@emmetio/math-expression'; import { DocumentStreamReader } from './bufferStream'; -export function evaluateMathExpression() { +export function evaluateMathExpression(): Thenable { if (!vscode.window.activeTextEditor) { vscode.window.showInformationMessage('No editor is active'); - return; + return Promise.resolve(false); } const editor = vscode.window.activeTextEditor; const stream = new DocumentStreamReader(editor.document); - editor.edit(editBuilder => { + return editor.edit(editBuilder => { editor.selections.forEach(selection => { - const pos = selection.isReversed ? selection.anchor : selection.active; - stream.pos = pos; + // startpos always comes before endpos + const startpos = selection.isReversed ? selection.active : selection.anchor; + const endpos = selection.isReversed ? selection.anchor : selection.active; + const selectionText = stream.substring(startpos, endpos); try { - const result = String(evaluate(stream, true)); - editBuilder.replace(new vscode.Range(stream.pos, pos), result); + if (selectionText) { + // respect selections + const result = String(evaluate(selectionText)); + editBuilder.replace(new vscode.Range(startpos, endpos), result); + } else { + // no selection made, extract expression from line + const lineToSelectionEnd = stream.substring(new vscode.Position(selection.end.line, 0), endpos); + const extractedIndices = extract(lineToSelectionEnd); + if (!extractedIndices) { + throw new Error('Invalid extracted indices'); + } + const result = String(evaluate(lineToSelectionEnd.substr(extractedIndices[0], extractedIndices[1]))); + const rangeToReplace = new vscode.Range( + new vscode.Position(selection.end.line, extractedIndices[0]), + new vscode.Position(selection.end.line, extractedIndices[1]) + ); + editBuilder.replace(rangeToReplace, result); + } } catch (err) { vscode.window.showErrorMessage('Could not evaluate expression'); // Ignore error since most likely it’s because of non-math expression @@ -31,5 +49,4 @@ export function evaluateMathExpression() { } }); }); - } diff --git a/extensions/emmet/src/test/evaluateMathExpression.test.ts b/extensions/emmet/src/test/evaluateMathExpression.test.ts new file mode 100644 index 000000000..553d04171 --- /dev/null +++ b/extensions/emmet/src/test/evaluateMathExpression.test.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 'mocha'; +import * as assert from 'assert'; +import { Position, Selection } from 'vscode'; +import { withRandomFileEditor, closeAllEditors } from './testUtils'; +import { evaluateMathExpression } from '../evaluateMathExpression'; + +suite('Tests for Evaluate Math Expression', () => { + teardown(closeAllEditors); + + function testEvaluateMathExpression(fileContents: string, selection: [number, number] | number, expectedFileContents: string): Thenable { + return withRandomFileEditor(fileContents, 'html', async (editor, _doc) => { + const selectionToUse = typeof selection === 'number' ? + new Selection(new Position(0, selection), new Position(0, selection)) : + new Selection(new Position(0, selection[0]), new Position(0, selection[1])); + editor.selection = selectionToUse; + + await evaluateMathExpression(); + + assert.strictEqual(editor.document.getText(), expectedFileContents); + return Promise.resolve(); + }); + } + + test('Selected sanity check', () => { + return testEvaluateMathExpression('1 + 2', [0, 5], '3'); + }); + + test('Selected with surrounding text', () => { + return testEvaluateMathExpression('test1 + 2test', [4, 9], 'test3test'); + }); + + test('Selected with number not part of selection', () => { + return testEvaluateMathExpression('test3 1+2', [6, 9], 'test3 3'); + }); + + test('Non-selected sanity check', () => { + return testEvaluateMathExpression('1 + 2', 5, '3'); + }); + + test('Non-selected midway', () => { + return testEvaluateMathExpression('1 + 2', 1, '1 + 2'); + }); + + test('Non-selected with surrounding text', () => { + return testEvaluateMathExpression('test1 + 3test', 9, 'test4test'); + }); +}); diff --git a/extensions/emmet/src/typings/emmetio__math-expression.d.ts b/extensions/emmet/src/typings/emmetio__math-expression.d.ts deleted file mode 100644 index 4c3d862b0..000000000 --- a/extensions/emmet/src/typings/emmetio__math-expression.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) Microsoft Corporation. All rights reserved. -* Licensed under the MIT License. See License.txt in the project root for license information. -*--------------------------------------------------------------------------------------------*/ - -declare module '@emmetio/math-expression' { - import { BufferStream } from 'EmmetNode'; - - function index(stream: BufferStream, backward: boolean): number; - - export default index; -} - diff --git a/extensions/emmet/yarn.lock b/extensions/emmet/yarn.lock index bb19fbcd0..d956865f4 100644 --- a/extensions/emmet/yarn.lock +++ b/extensions/emmet/yarn.lock @@ -31,13 +31,12 @@ "@emmetio/stream-reader" "^2.0.0" "@emmetio/stream-reader-utils" "^0.1.0" -"@emmetio/math-expression@^0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@emmetio/math-expression/-/math-expression-0.1.1.tgz#1ff2c7f05800f64c57ca89038ee18bce9f5776dc" - integrity sha1-H/LH8FgA9kxXyokDjuGLzp9Xdtw= +"@emmetio/math-expression@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@emmetio/math-expression/-/math-expression-1.0.4.tgz#cb657ed944f82b3728f863bf5ece1b1ff3ae7497" + integrity sha512-1m7y8/VeXCAfgFoPGTerbqCIadApcIINujd3TaM/LRLPPKiod8aT1PPmh542spnsUSsSnZJjbuF7xiO4WFA42g== dependencies: - "@emmetio/stream-reader" "^2.0.1" - "@emmetio/stream-reader-utils" "^0.1.0" + "@emmetio/scanner" "^1.0.0" "@emmetio/scanner@^1.0.0": version "1.0.0" @@ -49,15 +48,15 @@ resolved "https://registry.yarnpkg.com/@emmetio/stream-reader-utils/-/stream-reader-utils-0.1.0.tgz#244cb02c77ec2e74f78a9bd318218abc9c500a61" integrity sha1-JEywLHfsLnT3ipvTGCGKvJxQCmE= -"@emmetio/stream-reader@^2.0.0", "@emmetio/stream-reader@^2.0.1", "@emmetio/stream-reader@^2.2.0": +"@emmetio/stream-reader@^2.0.0", "@emmetio/stream-reader@^2.2.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@emmetio/stream-reader/-/stream-reader-2.2.0.tgz#46cffea119a0a003312a21c2d9b5628cb5fcd442" integrity sha1-Rs/+oRmgoAMxKiHC2bVijLX81EI= "@types/node@^12.11.7": - version "12.19.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.2.tgz#9565ed5c72ba96038fc3add643edd5e7820598e7" - integrity sha512-SRH6QM0IMOBBFmDiJ75vlhcbUEYEquvSuhsVW9ijG20JvdFTfOrB1p6ddZxz5y/JNnbf+9HoHhjhOVSX2hsJyA== + version "12.19.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.4.tgz#cdfbb62e26c7435ed9aab9c941393cc3598e9b46" + integrity sha512-o3oj1bETk8kBwzz1WlO6JWL/AfAA3Vm6J1B3C9CsdxHYp7XgPiH7OEXPUbZTndHlRaIElrANkQfe6ZmfJb3H2w== ajv@^6.12.3: version "6.12.6" @@ -178,9 +177,9 @@ aws-sign2@~0.7.0: integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= aws4@^1.8.0: - version "1.10.1" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" - integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA== + version "1.11.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" + integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== balanced-match@^1.0.0: version "1.0.0" @@ -392,12 +391,12 @@ debug@^2.2.0: dependencies: ms "2.0.0" -debug@^3.1.0: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== +debug@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" + integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg== dependencies: - ms "^2.1.1" + ms "2.1.2" decamelize@^1.1.2: version "1.2.0" @@ -994,9 +993,9 @@ is-buffer@^1.1.5, is-buffer@~1.1.6: integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== is-core-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.0.0.tgz#58531b70aed1db7c0e8d4eb1a0a2d1ddd64bd12d" - integrity sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw== + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.1.0.tgz#a4cc031d9b1aca63eecbd18a650e13cb4eeab946" + integrity sha512-YcV7BgVMRFRua2FqQzKtTDMz8iCuLEyGKjr70q8Zm1yy2qKcurbFEd79PAdHV77oL3NrAaOVQIbMmiHQCHB7ZA== dependencies: has "^1.0.3" @@ -1410,7 +1409,7 @@ lodash.values@~2.4.1: dependencies: lodash.keys "~2.4.1" -lodash@^4.16.4: +lodash@^4.17.15: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -1572,12 +1571,12 @@ mocha-junit-reporter@^1.17.0: xml "^1.0.0" mocha-multi-reporters@^1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/mocha-multi-reporters/-/mocha-multi-reporters-1.1.7.tgz#cc7f3f4d32f478520941d852abb64d9988587d82" - integrity sha1-zH8/TTL0eFIJQdhSq7ZNmYhYfYI= + version "1.5.1" + resolved "https://registry.yarnpkg.com/mocha-multi-reporters/-/mocha-multi-reporters-1.5.1.tgz#c73486bed5519e1d59c9ce39ac7a9792600e5676" + integrity sha512-Yb4QJOaGLIcmB0VY7Wif5AjvLMUFAdV57D2TWEva1Y0kU/3LjKpeRVmlMIfuO1SVbauve459kgtIizADqxMWPg== dependencies: - debug "^3.1.0" - lodash "^4.16.4" + debug "^4.1.1" + lodash "^4.17.15" mocha@^2.3.3: version "2.5.3" @@ -1605,7 +1604,7 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -ms@^2.1.1: +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== @@ -2413,14 +2412,15 @@ vinyl@~2.0.1: replace-ext "^1.0.0" vscode-emmet-helper@~2.0.0: - version "2.0.8" - resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-2.0.8.tgz#7c3cf8027d1a75d29625e029d516da7bc56c1fdb" - integrity sha512-Wyf+b5pua+13eZSHCpmob1x915x/od4z6lIia9T2N4v7+CUYNxDisBu3/ShIM3qg3YiYvTCOm+Yx/CLd2khcVw== + version "2.0.9" + resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-2.0.9.tgz#16244c087cba4e379116f268384bb644649db6ad" + integrity sha512-S6RjnR9gUicl8LsYnQAMNqqOxolud9gcj+NpPyEnxfxp1YIBuC9oetj6l6N9VMZBWu6tL77wmf+/EJsRx1PDPA== dependencies: emmet "^2.1.5" jsonc-parser "^2.3.0" vscode-languageserver-textdocument "^1.0.1" vscode-languageserver-types "^3.15.1" + vscode-nls "^5.0.0" vscode-uri "^2.1.2" vscode-html-languageservice@^3.0.3: diff --git a/extensions/git/package.json b/extensions/git/package.json index c9430e7c6..bea5f96a6 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -181,6 +181,12 @@ "category": "Git", "icon": "$(discard)" }, + { + "command": "git.rename", + "title": "%command.rename%", + "category": "Git", + "icon": "$(discard)" + }, { "command": "git.commit", "title": "%command.commit%", @@ -278,6 +284,11 @@ "title": "%command.checkout%", "category": "Git" }, + { + "command": "git.checkoutDetached", + "title": "%command.checkoutDetached%", + "category": "Git" + }, { "command": "git.branch", "title": "%command.branch%", @@ -368,6 +379,11 @@ "title": "%command.pushToForce%", "category": "Git" }, + { + "command": "git.pushTags", + "title": "%command.pushTags%", + "category": "Git" + }, { "command": "git.pushWithTags", "title": "%command.pushFollowTags%", @@ -378,6 +394,11 @@ "title": "%command.pushFollowTagsForce%", "category": "Git" }, + { + "command": "git.cherryPick", + "title": "%command.cherryPick%", + "category": "Git" + }, { "command": "git.addRemote", "title": "%command.addRemote%", @@ -605,6 +626,10 @@ "command": "git.cleanAllUntracked", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, + { + "command": "git.rename", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && resourceScheme == file" + }, { "command": "git.commit", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" @@ -705,6 +730,10 @@ "command": "git.renameBranch", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, + { + "command": "git.cherryPick", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" + }, { "command": "git.pull", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" @@ -769,6 +798,10 @@ "command": "git.pushWithTagsForce", "when": "config.git.enabled && !git.missing && config.git.allowForcePush && gitOpenRepositoryCount != 0" }, + { + "command": "git.pushTags", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" + }, { "command": "git.addRemote", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" @@ -1279,17 +1312,17 @@ { "command": "git.stageSelectedRanges", "group": "2_git@1", - "when": "isInDiffRightEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "isInDiffRightEditor && !isInEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" }, { "command": "git.unstageSelectedRanges", "group": "2_git@2", - "when": "isInDiffRightEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "isInDiffRightEditor && !isInEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" }, { "command": "git.revertSelectedRanges", "group": "2_git@3", - "when": "isInDiffRightEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "isInDiffRightEditor && !isInEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" } ], "scm/change/title": [ @@ -1657,21 +1690,27 @@ "scope": "resource" }, "git.checkoutType": { - "type": "string", - "enum": [ - "all", - "local", - "tags", - "remote" - ], - "enumDescriptions": [ - "%config.checkoutType.all%", - "%config.checkoutType.local%", - "%config.checkoutType.tags%", - "%config.checkoutType.remote%" - ], + "type": "array", + "items": { + "type": "string", + "enum": [ + "local", + "tags", + "remote" + ], + "enumDescriptions": [ + "%config.checkoutType.local%", + "%config.checkoutType.tags%", + "%config.checkoutType.remote%" + ] + }, + "uniqueItems": true, "markdownDescription": "%config.checkoutType%", - "default": "all" + "default": [ + "local", + "remote", + "tags" + ] }, "git.ignoreLegacyWarning": { "type": "boolean", @@ -1751,6 +1790,28 @@ "description": "%config.enableStatusBarSync%", "scope": "resource" }, + "git.followTagsWhenSync": { + "type": "boolean", + "scope": "resource", + "default": false, + "description": "%config.followTagsWhenSync%" + }, + "git.promptToSaveFilesBeforeStash": { + "type": "string", + "enum": [ + "always", + "staged", + "never" + ], + "enumDescriptions": [ + "%config.promptToSaveFilesBeforeStash.always%", + "%config.promptToSaveFilesBeforeStash.staged%", + "%config.promptToSaveFilesBeforeStash.never%" + ], + "scope": "resource", + "default": "always", + "description": "%config.promptToSaveFilesBeforeStash%" + }, "git.promptToSaveFilesBeforeCommit": { "type": "string", "enum": [ @@ -1783,6 +1844,23 @@ "scope": "resource", "default": "none" }, + "git.openAfterClone": { + "type": "string", + "enum": [ + "always", + "alwaysNewWindow", + "whenNoFolderOpen", + "prompt" + ], + "enumDescriptions": [ + "%config.openAfterClone.always%", + "%config.openAfterClone.alwaysNewWindow%", + "%config.openAfterClone.whenNoFolderOpen%", + "%config.openAfterClone.prompt%" + ], + "default": "prompt", + "description": "%config.openAfterClone%" + }, "git.showInlineOpenFileAction": { "type": "boolean", "default": true, @@ -1840,6 +1918,12 @@ "default": false, "description": "%config.alwaysSignOff%" }, + "git.ignoreSubmodules": { + "type": "boolean", + "scope": "resource", + "default": false, + "description": "%config.ignoreSubmodules%" + }, "git.ignoredRepositories": { "type": "array", "items": { @@ -1876,6 +1960,12 @@ "default": false, "description": "%config.fetchOnPull%" }, + "git.pruneOnFetch": { + "type": "boolean", + "scope": "resource", + "default": false, + "description": "%config.pruneOnFetch%" + }, "git.pullTags": { "type": "boolean", "scope": "resource", @@ -1962,6 +2052,12 @@ "default": true, "description": "%config.terminalAuthentication%" }, + "git.useCommitInputAsStashMessage": { + "type": "boolean", + "scope": "resource", + "default": false, + "description": "%config.useCommitInputAsStashMessage%" + }, "git.githubAuthentication": { "deprecationMessage": "This setting is now deprecated, please use `github.gitAuthentication` instead." }, @@ -2166,31 +2262,36 @@ { "view": "scm", "contents": "%view.workbench.scm.empty%", - "when": "config.git.enabled && git.state == initialized && workbenchState == empty", + "when": "config.git.enabled && workbenchState == empty", + "enablement": "git.state == initialized", "group": "2_open@1" }, { "view": "scm", "contents": "%view.workbench.scm.folder%", - "when": "config.git.enabled && git.state == initialized && workbenchState == folder", + "when": "config.git.enabled && workbenchState == folder", + "enablement": "git.state == initialized", "group": "5_scm@1" }, { "view": "scm", "contents": "%view.workbench.scm.workspace%", - "when": "config.git.enabled && git.state == initialized && workbenchState == workspace && workspaceFolderCount != 0", + "when": "config.git.enabled && workbenchState == workspace && workspaceFolderCount != 0", + "enablement": "git.state == initialized", "group": "5_scm@1" }, { "view": "scm", "contents": "%view.workbench.scm.emptyWorkspace%", - "when": "config.git.enabled && git.state == initialized && workbenchState == workspace && workspaceFolderCount == 0", + "when": "config.git.enabled && workbenchState == workspace && workspaceFolderCount == 0", + "enablement": "git.state == initialized", "group": "2_open@1" }, { "view": "explorer", "contents": "%view.workbench.cloneRepository%", - "when": "config.git.enabled && git.state == initialized", + "when": "config.git.enabled", + "enablement": "git.state == initialized", "group": "5_scm@1" } ] diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index e2a46033b..6158a7e0c 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -23,6 +23,7 @@ "command.unstage": "Unstage Changes", "command.unstageAll": "Unstage All Changes", "command.unstageSelectedRanges": "Unstage Selected Ranges", + "command.rename": "Rename", "command.clean": "Discard Changes", "command.cleanAll": "Discard All Changes", "command.cleanAllTracked": "Discard All Tracked Changes", @@ -46,10 +47,12 @@ "command.restoreCommitTemplate": "Restore Commit Template", "command.undoCommit": "Undo Last Commit", "command.checkout": "Checkout to...", + "command.checkoutDetached": "Checkout to (Detached)...", "command.branch": "Create Branch...", "command.branchFrom": "Create Branch From...", "command.deleteBranch": "Delete Branch...", "command.renameBranch": "Rename Branch...", + "command.cherryPick": "Cherry Pick...", "command.merge": "Merge Branch...", "command.rebase": "Rebase Branch...", "command.createTag": "Create Tag", @@ -66,6 +69,7 @@ "command.pushToForce": "Push to... (Force)", "command.pushFollowTags": "Push (Follow Tags)", "command.pushFollowTagsForce": "Push (Follow Tags, Force)", + "command.pushTags": "Push Tags", "command.addRemote": "Add Remote...", "command.removeRemote": "Remove Remote", "command.sync": "Sync", @@ -100,11 +104,10 @@ "config.countBadge.all": "Count all changes.", "config.countBadge.tracked": "Count only tracked changes.", "config.countBadge.off": "Turn off counter.", - "config.checkoutType": "Controls what type of branches are listed when running `Checkout to...`.", - "config.checkoutType.all": "Show all references.", - "config.checkoutType.local": "Show only local branches.", - "config.checkoutType.tags": "Show only tags.", - "config.checkoutType.remote": "Show only remote branches.", + "config.checkoutType": "Controls what type of git refs are listed when running `Checkout to...`.", + "config.checkoutType.local": "Local branches", + "config.checkoutType.tags": "Tags", + "config.checkoutType.remote": "Remote branches", "config.branchValidationRegex": "A regular expression to validate new branch names.", "config.branchWhitespaceChar": "The character to replace whitespace in new branch names.", "config.ignoreLegacyWarning": "Ignores the legacy Git warning.", @@ -121,6 +124,11 @@ "config.discardAllScope": "Controls what changes are discarded by the `Discard all changes` command. `all` discards all changes. `tracked` discards only tracked files. `prompt` shows a prompt dialog every time the action is run.", "config.decorations.enabled": "Controls whether Git contributes colors and badges to the explorer and the open editors view.", "config.enableStatusBarSync": "Controls whether the Git Sync command appears in the status bar.", + "config.followTagsWhenSync": "Follow push all tags when running the sync command.", + "config.promptToSaveFilesBeforeStash": "Controls whether Git should check for unsaved files before stashing changes.", + "config.promptToSaveFilesBeforeStash.always": "Check for any unsaved files.", + "config.promptToSaveFilesBeforeStash.staged": "Check only for unsaved staged files.", + "config.promptToSaveFilesBeforeStash.never": "Disable this check.", "config.promptToSaveFilesBeforeCommit": "Controls whether Git should check for unsaved files before committing.", "config.promptToSaveFilesBeforeCommit.always": "Check for any unsaved files.", "config.promptToSaveFilesBeforeCommit.staged": "Check only for unsaved staged files.", @@ -129,6 +137,11 @@ "config.postCommitCommand.none": "Don't run any command after a commit.", "config.postCommitCommand.push": "Run 'Git Push' after a successful commit.", "config.postCommitCommand.sync": "Run 'Git Sync' after a successful commit.", + "config.openAfterClone": "Controls whether to open a repository automatically after cloning.", + "config.openAfterClone.always": "Always open in current window.", + "config.openAfterClone.alwaysNewWindow": "Always open in a new window.", + "config.openAfterClone.whenNoFolderOpen": "Only open in current window when no folder is opened.", + "config.openAfterClone.prompt": "Always prompt for action.", "config.showInlineOpenFileAction": "Controls whether to show an inline Open File action in the Git changes view.", "config.showPushSuccessNotification": "Controls whether to show a notification when a push is successful.", "config.inputValidation": "Controls when to show commit message input validation.", @@ -138,6 +151,7 @@ "config.detectSubmodulesLimit": "Controls the limit of git submodules detected.", "config.alwaysShowStagedChangesResourceGroup": "Always show the Staged Changes resource group.", "config.alwaysSignOff": "Controls the signoff flag for all commits.", + "config.ignoreSubmodules": "Ignore modifications to submodules in the file tree.", "config.ignoredRepositories": "List of git repositories to ignore.", "config.scanRepositories": "List of paths to search for git repositories in.", "config.showProgress": "Controls whether git actions should show progress.", @@ -145,6 +159,7 @@ "config.confirmEmptyCommits": "Always confirm the creation of empty commits for the 'Git: Commit Empty' command.", "config.fetchOnPull": "When enabled, fetch all branches when pulling. Otherwise, fetch just the current one.", "config.pullTags": "Fetch all tags when pulling.", + "config.pruneOnFetch": "Prune when fetching.", "config.autoStash": "Stash any changes before pulling and restore them after successful pull.", "config.allowForcePush": "Controls whether force push (with or without lease) is enabled.", "config.useForcePushWithLease": "Controls whether force pushing uses the safer force-with-lease variant.", @@ -164,6 +179,8 @@ "config.timeline.date": "Controls which date to use for items in the Timeline view", "config.timeline.date.committed": "Use the committed date", "config.timeline.date.authored": "Use the authored date", + "config.useCommitInputAsStashMessage": "Controls whether to use the message from the commit input box as the default stash message.", + "submenu.explorer": "Git", "submenu.commit": "Commit", "submenu.commit.amend": "Amend", "submenu.commit.signoff": "Sign Off", @@ -188,5 +205,5 @@ "view.workbench.scm.folder": "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).", "view.workbench.scm.workspace": "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).", "view.workbench.scm.emptyWorkspace": "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).", - "view.workbench.cloneRepository": "You can also clone a repository from a URL. To learn more about how to use git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).\n[Clone Repository](command:git.clone)" + "view.workbench.cloneRepository": "You can also clone a repository from a URL. To learn more about how to use git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).\n[Clone Repository](command:git.clone 'Clone a repository once the git extension has activated')" } diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index 8bcdba441..0a309c6f9 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -357,7 +357,7 @@ export function registerAPICommands(extension: GitExtensionImpl): Disposable { return; } - return pickRemoteSource(extension.model, opts); + return pickRemoteSource(extension.model, opts as any); })); return Disposable.from(...disposables); diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index f63b0b12c..b9a09632b 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -212,6 +212,7 @@ export interface RemoteSourceProvider { readonly icon?: string; // codicon name readonly supportsQuery?: boolean; getRemoteSources(query?: string): ProviderResult; + getBranches?(url: string): ProviderResult; publishRepository?(repository: Repository): Promise; } diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 83fe906c8..2c24d9095 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3,10 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { lstat, Stats } from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection } from 'vscode'; +import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider } from 'vscode'; import TelemetryReporter from 'vscode-extension-telemetry'; import * as nls from 'vscode-nls'; import { Branch, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourceProvider } from './api/git'; @@ -31,14 +30,14 @@ class CheckoutItem implements QuickPickItem { constructor(protected ref: Ref) { } - async run(repository: Repository): Promise { + async run(repository: Repository, opts?: { detached?: boolean }): Promise { const ref = this.ref.name; if (!ref) { return; } - await repository.checkout(ref); + await repository.checkout(ref, opts); } } @@ -55,7 +54,7 @@ class CheckoutRemoteHeadItem extends CheckoutItem { return localize('remote branch at', "Remote branch at {0}", this.shortCommit); } - async run(repository: Repository): Promise { + async run(repository: Repository, opts?: { detached?: boolean }): Promise { if (!this.ref.name) { return; } @@ -63,9 +62,9 @@ class CheckoutRemoteHeadItem extends CheckoutItem { const branches = await repository.findTrackingBranches(this.ref.name); if (branches.length > 0) { - await repository.checkout(branches[0].name!); + await repository.checkout(branches[0].name!, opts); } else { - await repository.checkoutTracking(this.ref.name); + await repository.checkoutTracking(this.ref.name, opts); } } } @@ -114,31 +113,21 @@ class RebaseItem implements QuickPickItem { } class CreateBranchItem implements QuickPickItem { - - constructor(private cc: CommandCenter) { } - get label(): string { return '$(plus) ' + localize('create branch', 'Create new branch...'); } get description(): string { return ''; } - get alwaysShow(): boolean { return true; } - - async run(repository: Repository): Promise { - await this.cc.branch(repository); - } } class CreateBranchFromItem implements QuickPickItem { - - constructor(private cc: CommandCenter) { } - get label(): string { return '$(plus) ' + localize('create branch from', 'Create new branch from...'); } get description(): string { return ''; } - get alwaysShow(): boolean { return true; } +} - async run(repository: Repository): Promise { - await this.cc.branch(repository); - } +class CheckoutDetachedItem implements QuickPickItem { + get label(): string { return '$(debug-disconnect) ' + localize('checkout detached', 'Checkout detached...'); } + get description(): string { return ''; } + get alwaysShow(): boolean { return true; } } class HEADItem implements QuickPickItem { @@ -217,18 +206,53 @@ async function categorizeResourceByResolution(resources: Resource[]): Promise<{ function createCheckoutItems(repository: Repository): CheckoutItem[] { const config = workspace.getConfiguration('git'); - const checkoutType = config.get('checkoutType') || 'all'; - const includeTags = checkoutType === 'all' || checkoutType === 'tags'; - const includeRemotes = checkoutType === 'all' || checkoutType === 'remote'; + const checkoutTypeConfig = config.get('checkoutType'); + let checkoutTypes: string[]; - const heads = repository.refs.filter(ref => ref.type === RefType.Head) - .map(ref => new CheckoutItem(ref)); - const tags = (includeTags ? repository.refs.filter(ref => ref.type === RefType.Tag) : []) - .map(ref => new CheckoutTagItem(ref)); - const remoteHeads = (includeRemotes ? repository.refs.filter(ref => ref.type === RefType.RemoteHead) : []) - .map(ref => new CheckoutRemoteHeadItem(ref)); + if (checkoutTypeConfig === 'all' || !checkoutTypeConfig || checkoutTypeConfig.length === 0) { + checkoutTypes = ['local', 'remote', 'tags']; + } else if (typeof checkoutTypeConfig === 'string') { + checkoutTypes = [checkoutTypeConfig]; + } else { + checkoutTypes = checkoutTypeConfig; + } - return [...heads, ...tags, ...remoteHeads]; + const processors = checkoutTypes.map(getCheckoutProcessor) + .filter(p => !!p) as CheckoutProcessor[]; + + for (const ref of repository.refs) { + for (const processor of processors) { + processor.onRef(ref); + } + } + + return processors.reduce((r, p) => r.concat(...p.items), []); +} + +class CheckoutProcessor { + + private refs: Ref[] = []; + get items(): CheckoutItem[] { return this.refs.map(r => new this.ctor(r)); } + constructor(private type: RefType, private ctor: { new(ref: Ref): CheckoutItem }) { } + + onRef(ref: Ref): void { + if (ref.type === this.type) { + this.refs.push(ref); + } + } +} + +function getCheckoutProcessor(type: string): CheckoutProcessor | undefined { + switch (type) { + case 'local': + return new CheckoutProcessor(RefType.Head, CheckoutItem); + case 'remote': + return new CheckoutProcessor(RefType.RemoteHead, CheckoutRemoteHeadItem); + case 'tags': + return new CheckoutProcessor(RefType.Tag, CheckoutTagItem); + } + + return undefined; } function sanitizeRemoteName(name: string) { @@ -246,6 +270,7 @@ enum PushType { Push, PushTo, PushFollowTags, + PushTags } interface PushOptions { @@ -254,9 +279,27 @@ interface PushOptions { silent?: boolean; } +class CommandErrorOutputTextDocumentContentProvider implements TextDocumentContentProvider { + + private items = new Map(); + + set(uri: Uri, contents: string): void { + this.items.set(uri.path, contents); + } + + delete(uri: Uri): void { + this.items.delete(uri.path); + } + + provideTextDocumentContent(uri: Uri): string | undefined { + return this.items.get(uri.path); + } +} + export class CommandCenter { private disposables: Disposable[]; + private commandErrors = new CommandErrorOutputTextDocumentContentProvider(); constructor( private git: Git, @@ -273,6 +316,8 @@ export class CommandCenter { return commands.registerCommand(commandId, command); } }); + + this.disposables.push(workspace.registerTextDocumentContentProvider('git-output', this.commandErrors)); } @command('git.setLogLevel') @@ -311,165 +356,14 @@ export class CommandCenter { } @command('git.openResource') - async openResource(resource: Resource, preserveFocus: boolean): Promise { + async openResource(resource: Resource): Promise { const repository = this.model.getRepository(resource.resourceUri); if (!repository) { return; } - const config = workspace.getConfiguration('git', Uri.file(repository.root)); - const openDiffOnClick = config.get('openDiffOnClick'); - - if (openDiffOnClick) { - await this._openResource(resource, undefined, preserveFocus, false); - } else { - await this.openFile(resource); - } - } - - private async _openResource(resource: Resource, preview?: boolean, preserveFocus?: boolean, preserveSelection?: boolean): Promise { - let stat: Stats | undefined; - - try { - stat = await new Promise((c, e) => lstat(resource.resourceUri.fsPath, (err, stat) => err ? e(err) : c(stat))); - } catch (err) { - // noop - } - - let left: Uri | undefined; - let right: Uri | undefined; - - if (stat && stat.isDirectory()) { - const repository = this.model.getRepositoryForSubmodule(resource.resourceUri); - - if (repository) { - right = toGitUri(resource.resourceUri, resource.resourceGroupType === ResourceGroupType.Index ? 'index' : 'wt', { submoduleOf: repository.root }); - } - } else { - if (resource.type !== Status.DELETED_BY_THEM) { - left = this.getLeftResource(resource); - } - - right = this.getRightResource(resource); - } - - const title = this.getTitle(resource); - - if (!right) { - // TODO - console.error('oh no'); - return; - } - - const opts: TextDocumentShowOptions = { - preserveFocus, - preview, - viewColumn: ViewColumn.Active - }; - - const activeTextEditor = window.activeTextEditor; - - // Check if active text editor has same path as other editor. we cannot compare via - // URI.toString() here because the schemas can be different. Instead we just go by path. - if (preserveSelection && activeTextEditor && activeTextEditor.document.uri.path === right.path) { - opts.selection = activeTextEditor.selection; - } - - if (!left) { - await commands.executeCommand('vscode.open', right, opts, title); - } else { - await commands.executeCommand('vscode.diff', left, right, title, opts); - } - } - - private getLeftResource(resource: Resource): Uri | undefined { - switch (resource.type) { - case Status.INDEX_MODIFIED: - case Status.INDEX_RENAMED: - case Status.INDEX_ADDED: - return toGitUri(resource.original, 'HEAD'); - - case Status.MODIFIED: - case Status.UNTRACKED: - return toGitUri(resource.resourceUri, '~'); - - case Status.DELETED_BY_THEM: - return toGitUri(resource.resourceUri, ''); - } - return undefined; - } - - private getRightResource(resource: Resource): Uri | undefined { - switch (resource.type) { - case Status.INDEX_MODIFIED: - case Status.INDEX_ADDED: - case Status.INDEX_COPIED: - case Status.INDEX_RENAMED: - return toGitUri(resource.resourceUri, ''); - - case Status.INDEX_DELETED: - case Status.DELETED: - return toGitUri(resource.resourceUri, 'HEAD'); - - case Status.DELETED_BY_US: - return toGitUri(resource.resourceUri, '~3'); - - case Status.DELETED_BY_THEM: - return toGitUri(resource.resourceUri, '~2'); - - case Status.MODIFIED: - case Status.UNTRACKED: - case Status.IGNORED: - case Status.INTENT_TO_ADD: - const repository = this.model.getRepository(resource.resourceUri); - - if (!repository) { - return; - } - - const uriString = resource.resourceUri.toString(); - const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString); - - if (indexStatus && indexStatus.renameResourceUri) { - return indexStatus.renameResourceUri; - } - - return resource.resourceUri; - - case Status.BOTH_ADDED: - case Status.BOTH_MODIFIED: - return resource.resourceUri; - } - return undefined; - } - - private getTitle(resource: Resource): string { - const basename = path.basename(resource.resourceUri.fsPath); - - switch (resource.type) { - case Status.INDEX_MODIFIED: - case Status.INDEX_RENAMED: - case Status.INDEX_ADDED: - return localize('git.title.index', '{0} (Index)', basename); - - case Status.MODIFIED: - case Status.BOTH_ADDED: - case Status.BOTH_MODIFIED: - return localize('git.title.workingTree', '{0} (Working Tree)', basename); - - case Status.DELETED_BY_US: - return localize('git.title.theirs', '{0} (Theirs)', basename); - - case Status.DELETED_BY_THEM: - return localize('git.title.ours', '{0} (Ours)', basename); - - case Status.UNTRACKED: - return localize('git.title.untracked', '{0} (Untracked)', basename); - - default: - return ''; - } + await resource.open(); } async cloneRepository(url?: string, parentPath?: string, options: { recursive?: boolean } = {}): Promise { @@ -531,35 +425,54 @@ export class CommandCenter { (progress, token) => this.git.clone(url!, { parentPath: parentPath!, progress, recursive: options.recursive }, token) ); - let message = localize('proposeopen', "Would you like to open the cloned repository?"); - const open = localize('openrepo', "Open"); - const openNewWindow = localize('openreponew', "Open in New Window"); - const choices = [open, openNewWindow]; + const config = workspace.getConfiguration('git'); + const openAfterClone = config.get<'always' | 'alwaysNewWindow' | 'whenNoFolderOpen' | 'prompt'>('openAfterClone'); - const addToWorkspace = localize('add', "Add to Workspace"); - if (workspace.workspaceFolders) { - message = localize('proposeopen2', "Would you like to open the cloned repository, or add it to the current workspace?"); - choices.push(addToWorkspace); + enum PostCloneAction { Open, OpenNewWindow, AddToWorkspace } + let action: PostCloneAction | undefined = undefined; + + if (openAfterClone === 'always') { + action = PostCloneAction.Open; + } else if (openAfterClone === 'alwaysNewWindow') { + action = PostCloneAction.OpenNewWindow; + } else if (openAfterClone === 'whenNoFolderOpen' && !workspace.workspaceFolders) { + action = PostCloneAction.Open; } - const result = await window.showInformationMessage(message, ...choices); + if (action === undefined) { + let message = localize('proposeopen', "Would you like to open the cloned repository?"); + const open = localize('openrepo', "Open"); + const openNewWindow = localize('openreponew', "Open in New Window"); + const choices = [open, openNewWindow]; + + const addToWorkspace = localize('add', "Add to Workspace"); + if (workspace.workspaceFolders) { + message = localize('proposeopen2', "Would you like to open the cloned repository, or add it to the current workspace?"); + choices.push(addToWorkspace); + } + + const result = await window.showInformationMessage(message, ...choices); + + action = result === open ? PostCloneAction.Open + : result === openNewWindow ? PostCloneAction.OpenNewWindow + : result === addToWorkspace ? PostCloneAction.AddToWorkspace : undefined; + } - const openFolder = result === open; /* __GDPR__ "clone" : { "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true } } */ - this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: openFolder ? 1 : 0 }); + this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: action === PostCloneAction.Open || action === PostCloneAction.OpenNewWindow ? 1 : 0 }); const uri = Uri.file(repositoryPath); - if (openFolder) { + if (action === PostCloneAction.Open) { commands.executeCommand('vscode.openFolder', uri, { forceReuseWindow: true }); - } else if (result === addToWorkspace) { + } else if (action === PostCloneAction.AddToWorkspace) { workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri }); - } else if (result === openNewWindow) { + } else if (action === PostCloneAction.OpenNewWindow) { commands.executeCommand('vscode.openFolder', uri, { forceNewWindow: true }); } } catch (err) { @@ -761,7 +674,10 @@ export class CommandCenter { try { document = await workspace.openTextDocument(uri); } catch (error) { - await commands.executeCommand('vscode.open', uri, opts); + await commands.executeCommand('vscode.open', uri, { + ...opts, + override: arg instanceof Resource && arg.type === Status.BOTH_MODIFIED ? false : undefined + }); continue; } @@ -801,7 +717,7 @@ export class CommandCenter { return; } - const HEAD = this.getLeftResource(resource); + const HEAD = resource.leftUri; const basename = path.basename(resource.resourceUri.fsPath); const title = `${basename} (HEAD)`; @@ -819,10 +735,6 @@ export class CommandCenter { @command('git.openChange') async openChange(arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise { - const preserveFocus = arg instanceof Resource; - const preview = !(arg instanceof Resource); - - const preserveSelection = arg instanceof Uri || !arg; let resources: Resource[] | undefined = undefined; if (arg instanceof Uri) { @@ -849,10 +761,33 @@ export class CommandCenter { } for (const resource of resources) { - await this._openResource(resource, preview, preserveFocus, preserveSelection); + await resource.openChange(); } } + @command('git.rename', { repository: true }) + async rename(repository: Repository, fromUri: Uri | undefined): Promise { + fromUri = fromUri ?? window.activeTextEditor?.document.uri; + + if (!fromUri) { + return; + } + + const from = path.relative(repository.root, fromUri.path); + let to = await window.showInputBox({ + value: from, + valueSelection: [from.length - path.basename(from).length, from.length] + }); + + to = to?.trim(); + + if (!to) { + return; + } + + await repository.move(from, to); + } + @command('git.stage') async stage(...resourceStates: SourceControlResourceState[]): Promise { this.outputChannel.appendLine(`git.stage ${resourceStates.length}`); @@ -1018,6 +953,10 @@ export class CommandCenter { @command('git.stageChange') async stageChange(uri: Uri, changes: LineChange[], index: number): Promise { + if (!uri) { + return; + } + const textEditor = window.visibleTextEditors.filter(e => e.document.uri.toString() === uri.toString())[0]; if (!textEditor) { @@ -1068,6 +1007,10 @@ export class CommandCenter { @command('git.revertChange') async revertChange(uri: Uri, changes: LineChange[], index: number): Promise { + if (!uri) { + return; + } + const textEditor = window.visibleTextEditors.filter(e => e.document.uri.toString() === uri.toString())[0]; if (!textEditor) { @@ -1397,7 +1340,7 @@ export class CommandCenter { ? localize('unsaved files single', "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)) : localize('unsaved files', "There are {0} unsaved files.\n\nWould you like to save them before committing?", documents.length); const saveAndCommit = localize('save and commit', "Save All & Commit"); - const commit = localize('commit', "Commit Anyway"); + const commit = localize('commit', "Commit Staged Changes"); const pick = await window.showWarningMessage(message, { modal: true }, saveAndCommit, commit); if (pick === saveAndCommit) { @@ -1409,8 +1352,14 @@ export class CommandCenter { } } + if (!opts) { + opts = { all: noStagedChanges }; + } else if (!opts.all && noStagedChanges && !opts.empty) { + opts = { ...opts, all: true }; + } + // no changes, and the user has not configured to commit all in this case - if (!noUnstagedChanges && noStagedChanges && !enableSmartCommit) { + if (!noUnstagedChanges && noStagedChanges && !enableSmartCommit && !opts.empty) { const suggestSmartCommit = config.get('suggestSmartCommit') === true; if (!suggestSmartCommit) { @@ -1434,13 +1383,7 @@ export class CommandCenter { } } - if (!opts) { - opts = { all: noStagedChanges }; - } else if (!opts.all && noStagedChanges) { - opts = { ...opts, all: true }; - } - - // enable signing of commits if configurated + // enable signing of commits if configured opts.signCommit = enableCommitSigning; if (config.get('alwaysSignOff')) { @@ -1458,10 +1401,18 @@ export class CommandCenter { // no staged changes and no tracked unstaged changes || (noStagedChanges && smartCommitChanges === 'tracked' && repository.workingTreeGroup.resourceStates.every(r => r.type === Status.UNTRACKED)) ) + // amend allows changing only the commit message + && !opts.amend && !opts.empty ) { - window.showInformationMessage(localize('no changes', "There are no changes to commit.")); - return false; + const commitAnyway = localize('commit anyway', "Create Empty Commit"); + const answer = await window.showInformationMessage(localize('no changes', "There are no changes to commit."), commitAnyway); + + if (answer !== commitAnyway) { + return false; + } + + opts.empty = true; } if (opts.noVerify) { @@ -1484,9 +1435,9 @@ export class CommandCenter { } } - const message = await getCommitMessage(); + let message = await getCommitMessage(); - if (!message) { + if (!message && !opts.amend) { return false; } @@ -1523,7 +1474,7 @@ export class CommandCenter { let value: string | undefined = undefined; if (opts && opts.amend && repository.HEAD && repository.HEAD.commit) { - value = (await repository.getCommit(repository.HEAD.commit)).message; + return undefined; } const branchName = repository.headShortName; @@ -1690,20 +1641,38 @@ export class CommandCenter { } @command('git.checkout', { repository: true }) - async checkout(repository: Repository, treeish: string): Promise { - if (typeof treeish === 'string') { - await repository.checkout(treeish); + async checkout(repository: Repository, treeish?: string): Promise { + return this._checkout(repository, { treeish }); + } + + @command('git.checkoutDetached', { repository: true }) + async checkoutDetached(repository: Repository, treeish?: string): Promise { + return this._checkout(repository, { detached: true, treeish }); + } + + private async _checkout(repository: Repository, opts?: { detached?: boolean, treeish?: string }): Promise { + if (typeof opts?.treeish === 'string') { + await repository.checkout(opts?.treeish, opts); return true; } - const createBranch = new CreateBranchItem(this); - const createBranchFrom = new CreateBranchFromItem(this); - const picks = [createBranch, createBranchFrom, ...createCheckoutItems(repository)]; - const placeHolder = localize('select a ref to checkout', 'Select a ref to checkout'); + const createBranch = new CreateBranchItem(); + const createBranchFrom = new CreateBranchFromItem(); + const checkoutDetached = new CheckoutDetachedItem(); + const picks: QuickPickItem[] = []; + + if (!opts?.detached) { + picks.push(createBranch, createBranchFrom, checkoutDetached); + } + + picks.push(...createCheckoutItems(repository)); const quickpick = window.createQuickPick(); quickpick.items = picks; - quickpick.placeholder = placeHolder; + quickpick.placeholder = opts?.detached + ? localize('select a ref to checkout detached', 'Select a ref to checkout in detached mode') + : localize('select a ref to checkout', 'Select a ref to checkout'); + quickpick.show(); const choice = await new Promise(c => quickpick.onDidAccept(() => c(quickpick.activeItems[0]))); @@ -1717,8 +1686,31 @@ export class CommandCenter { await this._branch(repository, quickpick.value); } else if (choice === createBranchFrom) { await this._branch(repository, quickpick.value, true); + } else if (choice === checkoutDetached) { + return this._checkout(repository, { detached: true }); } else { - await (choice as CheckoutItem).run(repository); + const item = choice as CheckoutItem; + + try { + await item.run(repository, opts); + } catch (err) { + if (err.gitErrorCode !== GitErrorCodes.DirtyWorkTree) { + throw err; + } + + const force = localize('force', "Force Checkout"); + const stash = localize('stashcheckout', "Stash & Checkout"); + const choice = await window.showWarningMessage(localize('local changes', "Your local changes would be overwritten by checkout."), { modal: true }, force, stash); + + if (choice === force) { + await this.cleanAll(repository); + await item.run(repository, opts); + } else if (choice === stash) { + await this.stash(repository); + await item.run(repository, opts); + await this.stashPopLatest(repository); + } + } } return true; @@ -1849,8 +1841,8 @@ export class CommandCenter { @command('git.merge', { repository: true }) async merge(repository: Repository): Promise { const config = workspace.getConfiguration('git'); - const checkoutType = config.get('checkoutType') || 'all'; - const includeRemotes = checkoutType === 'all' || checkoutType === 'remote'; + const checkoutType = config.get('checkoutType'); + const includeRemotes = checkoutType === 'all' || checkoutType === 'remote' || checkoutType?.includes('remote'); const heads = repository.refs.filter(ref => ref.type === RefType.Head) .filter(ref => ref.name || ref.commit) @@ -1874,8 +1866,8 @@ export class CommandCenter { @command('git.rebase', { repository: true }) async rebase(repository: Repository): Promise { const config = workspace.getConfiguration('git'); - const checkoutType = config.get('checkoutType') || 'all'; - const includeRemotes = checkoutType === 'all' || checkoutType === 'remote'; + const checkoutType = config.get('checkoutType'); + const includeRemotes = checkoutType === 'all' || checkoutType === 'remote' || checkoutType?.includes('remote'); const heads = repository.refs.filter(ref => ref.type === RefType.Head) .filter(ref => ref.name !== repository.HEAD?.name) @@ -2068,7 +2060,7 @@ export class CommandCenter { forcePushMode = config.get('useForcePushWithLease') === true ? ForcePushMode.ForceWithLease : ForcePushMode.Force; if (config.get('confirmForcePush')) { - const message = localize('confirm force push', "You are about to force push your changes, this can be destructive and could inadvertedly overwrite changes made by others.\n\nAre you sure to continue?"); + const message = localize('confirm force push', "You are about to force push your changes, this can be destructive and could inadvertently overwrite changes made by others.\n\nAre you sure to continue?"); const yes = localize('ok', "OK"); const neverAgain = localize('never ask again', "OK, Don't Ask Again"); const pick = await window.showWarningMessage(message, { modal: true }, yes, neverAgain); @@ -2086,6 +2078,10 @@ export class CommandCenter { return; } + if (pushOptions.pushType === PushType.PushTags) { + await repository.pushTags(undefined, forcePushMode); + } + if (!repository.HEAD || !repository.HEAD.name) { if (!pushOptions.silent) { window.showWarningMessage(localize('nobranch', "Please check out a branch to push to a remote.")); @@ -2157,6 +2153,21 @@ export class CommandCenter { await this._push(repository, { pushType: PushType.PushFollowTags, forcePush: true }); } + @command('git.cherryPick', { repository: true }) + async cherryPick(repository: Repository): Promise { + const hash = await window.showInputBox({ + placeHolder: localize('commit hash', "Commit Hash"), + prompt: localize('provide commit hash', "Please provide the commit hash"), + ignoreFocusOut: true + }); + + if (!hash) { + return; + } + + await repository.cherryPick(hash); + } + @command('git.pushTo', { repository: true }) async pushTo(repository: Repository): Promise { await this._push(repository, { pushType: PushType.PushTo }); @@ -2167,6 +2178,11 @@ export class CommandCenter { await this._push(repository, { pushType: PushType.PushTo, forcePush: true }); } + @command('git.pushTags', { repository: true }) + async pushTags(repository: Repository): Promise { + await this._push(repository, { pushType: PushType.PushTags }); + } + @command('git.addRemote', { repository: true }) async addRemote(repository: Repository): Promise { const url = await pickRemoteSource(this.model, { @@ -2200,6 +2216,7 @@ export class CommandCenter { } await repository.addRemote(name, url); + await repository.fetch(name); return name; } @@ -2413,7 +2430,45 @@ export class CommandCenter { return; } - const message = await this.getStashMessage(); + const config = workspace.getConfiguration('git', Uri.file(repository.root)); + const promptToSaveFilesBeforeStashing = config.get<'always' | 'staged' | 'never'>('promptToSaveFilesBeforeStash'); + + if (promptToSaveFilesBeforeStashing !== 'never') { + let documents = workspace.textDocuments + .filter(d => !d.isUntitled && d.isDirty && isDescendant(repository.root, d.uri.fsPath)); + + if (promptToSaveFilesBeforeStashing === '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 + ? localize('unsaved stash files single', "The following file has unsaved changes which won't be included in the stash if you proceed: {0}.\n\nWould you like to save it before stashing?", path.basename(documents[0].uri.fsPath)) + : localize('unsaved stash files', "There are {0} unsaved files.\n\nWould you like to save them before stashing?", documents.length); + const saveAndStash = localize('save and stash', "Save All & Stash"); + const stash = localize('stash', "Stash Anyway"); + const pick = await window.showWarningMessage(message, { modal: true }, saveAndStash, stash); + + if (pick === saveAndStash) { + await Promise.all(documents.map(d => d.save())); + } else if (pick !== stash) { + return; // do not stash on cancel + } + } + } + + let message: string | undefined; + + if (config.get('useCommitInputAsStashMessage') && (!repository.sourceControl.commitTemplate || repository.inputBox.value !== repository.sourceControl.commitTemplate)) { + message = repository.inputBox.value; + } + + message = await window.showInputBox({ + value: message, + prompt: localize('provide stash message', "Optionally provide a stash message"), + placeHolder: localize('stash message', "Stash message") + }); if (typeof message === 'undefined') { return; @@ -2422,13 +2477,6 @@ export class CommandCenter { await repository.createStash(message, includeUntracked); } - private async getStashMessage(): Promise { - return await window.showInputBox({ - prompt: localize('provide stash message', "Optionally provide a stash message"), - placeHolder: localize('stash message', "Stash message") - }); - } - @command('git.stash', { repository: true }) stash(repository: Repository): Promise { return this._stash(repository); @@ -2496,6 +2544,16 @@ export class CommandCenter { return; } + // request confirmation for the operation + const yes = localize('yes', "Yes"); + const result = await window.showWarningMessage( + localize('sure drop', "Are you sure you want to drop the stash: {0}?", stash.description), + yes + ); + if (result !== yes) { + return; + } + await repository.dropStash(stash.index); } @@ -2614,6 +2672,31 @@ export class CommandCenter { const outputChannel = this.outputChannel as OutputChannel; choices.set(openOutputChannelChoice, () => outputChannel.show()); + const showCommandOutputChoice = localize('show command output', "Show Command Output"); + if (err.stderr) { + choices.set(showCommandOutputChoice, async () => { + const timestamp = new Date().getTime(); + const uri = Uri.parse(`git-output:/git-error-${timestamp}`); + + let command = 'git'; + + if (err.gitArgs) { + command = `${command} ${err.gitArgs.join(' ')}`; + } else if (err.gitCommand) { + command = `${command} ${err.gitCommand}`; + } + + this.commandErrors.set(uri, `> ${command}\n${err.stderr}`); + + try { + const doc = await workspace.openTextDocument(uri); + await window.showTextDocument(doc); + } finally { + this.commandErrors.delete(uri); + } + }); + } + switch (err.gitErrorCode) { case GitErrorCodes.DirtyWorkTree: message = localize('clean repo', "Please clean your repository working tree before checkout."); diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts index a4fbf0ad4..63c026051 100644 --- a/extensions/git/src/decorationProvider.ts +++ b/extensions/git/src/decorationProvider.ts @@ -15,18 +15,18 @@ class GitIgnoreDecorationProvider implements FileDecorationProvider { private static Decoration: FileDecoration = { color: new ThemeColor('gitDecoration.ignoredResourceForeground') }; - readonly onDidChange: Event; + readonly onDidChangeFileDecorations: Event; private queue = new Map>; }>(); private disposables: Disposable[] = []; constructor(private model: Model) { - this.onDidChange = fireEvent(anyEvent( + this.onDidChangeFileDecorations = fireEvent(anyEvent( filterEvent(workspace.onDidSaveTextDocument, e => /\.gitignore$|\.git\/info\/exclude$/.test(e.uri.path)), model.onDidOpenRepository, model.onDidCloseRepository )); - this.disposables.push(window.registerDecorationProvider(this)); + this.disposables.push(window.registerFileDecorationProvider(this)); } async provideFileDecoration(uri: Uri): Promise { @@ -93,14 +93,14 @@ class GitDecorationProvider implements FileDecorationProvider { }; private readonly _onDidChangeDecorations = new EventEmitter(); - readonly onDidChange: Event = this._onDidChangeDecorations.event; + readonly onDidChangeFileDecorations: Event = this._onDidChangeDecorations.event; private disposables: Disposable[] = []; private decorations = new Map(); constructor(private repository: Repository) { this.disposables.push( - window.registerDecorationProvider(this), + window.registerFileDecorationProvider(this), repository.onDidRunGitStatus(this.onDidRunGitStatus, this) ); } diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 1a4119959..af536fe6c 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -260,6 +260,7 @@ export interface IGitErrorData { exitCode?: number; gitErrorCode?: string; gitCommand?: string; + gitArgs?: string[]; } export class GitError { @@ -271,6 +272,7 @@ export class GitError { exitCode?: number; gitErrorCode?: string; gitCommand?: string; + gitArgs?: string[]; constructor(data: IGitErrorData) { if (data.error) { @@ -287,6 +289,7 @@ export class GitError { this.exitCode = data.exitCode; this.gitErrorCode = data.gitErrorCode; this.gitCommand = data.gitCommand; + this.gitArgs = data.gitArgs; } toString(): string { @@ -535,7 +538,8 @@ export class Git { stderr: result.stderr, exitCode: result.exitCode, gitErrorCode: getGitErrorCode(result.stderr), - gitCommand: args[0] + gitCommand: args[0], + gitArgs: args })); } @@ -1295,13 +1299,17 @@ export class Repository { await this.run(['update-index', add, '--cacheinfo', mode, hash, path]); } - async checkout(treeish: string, paths: string[], opts: { track?: boolean } = Object.create(null)): Promise { + async checkout(treeish: string, paths: string[], opts: { track?: boolean, detached?: boolean } = Object.create(null)): Promise { const args = ['checkout', '-q']; if (opts.track) { args.push('--track'); } + if (opts.detached) { + args.push('--detach'); + } + if (treeish) { args.push(treeish); } @@ -1317,23 +1325,30 @@ export class Repository { } catch (err) { if (/Please,? commit your changes or stash them/.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.DirtyWorkTree; + err.gitTreeish = treeish; } throw err; } } - async commit(message: string, opts: CommitOptions = Object.create(null)): Promise { - const args = ['commit', '--quiet', '--allow-empty-message', '--file', '-']; + async commit(message: string | undefined, opts: CommitOptions = Object.create(null)): Promise { + const args = ['commit', '--quiet', '--allow-empty-message']; if (opts.all) { args.push('--all'); } - if (opts.amend) { + if (opts.amend && message) { args.push('--amend'); } + if (opts.amend && !message) { + args.push('--amend', '--no-edit'); + } else { + args.push('--file', '-'); + } + if (opts.signoff) { args.push('--signoff'); } @@ -1350,8 +1365,11 @@ export class Repository { args.push('--no-verify'); } + // Stops git from guessing at user/email + args.splice(0, 0, '-c', 'user.useConfigOnly=true'); + try { - await this.run(args, { input: message || '' }); + await this.run(args, !opts.amend || message ? { input: message || '' } : {}); } catch (commitErr) { await this.handleCommitError(commitErr); } @@ -1414,6 +1432,11 @@ export class Repository { await this.run(args); } + async move(from: string, to: string): Promise { + const args = ['mv', from, to]; + await this.run(args); + } + async setBranchUpstream(name: string, upstream: string): Promise { const args = ['branch', '--set-upstream-to', upstream, name]; await this.run(args); @@ -1536,9 +1559,11 @@ export class Repository { await this.run(args); } - async fetch(options: { remote?: string, ref?: string, all?: boolean, prune?: boolean, depth?: number, silent?: boolean } = {}): Promise { + async fetch(options: { remote?: string, ref?: string, all?: boolean, prune?: boolean, depth?: number, silent?: boolean, readonly cancellationToken?: CancellationToken } = {}): Promise { const args = ['fetch']; - const spawnOptions: SpawnOptions = {}; + const spawnOptions: SpawnOptions = { + cancellationToken: options.cancellationToken, + }; if (options.remote) { args.push(options.remote); @@ -1635,7 +1660,7 @@ export class Repository { } } - async push(remote?: string, name?: string, setUpstream: boolean = false, tags = false, forcePushMode?: ForcePushMode): Promise { + async push(remote?: string, name?: string, setUpstream: boolean = false, followTags = false, forcePushMode?: ForcePushMode, tags = false): Promise { const args = ['push']; if (forcePushMode === ForcePushMode.ForceWithLease) { @@ -1648,10 +1673,14 @@ export class Repository { args.push('-u'); } - if (tags) { + if (followTags) { args.push('--follow-tags'); } + if (tags) { + args.push('--tags'); + } + if (remote) { args.push(remote); } @@ -1677,6 +1706,11 @@ export class Repository { } } + async cherryPick(commitHash: string): Promise { + const args = ['cherry-pick', commitHash]; + await this.run(args); + } + async blame(path: string): Promise { try { const args = ['blame', sanitizePath(path)]; @@ -1761,11 +1795,17 @@ export class Repository { } } - getStatus(limit = 5000): Promise<{ status: IFileStatus[]; didHitLimit: boolean; }> { + getStatus(opts?: { limit?: number, ignoreSubmodules?: boolean }): Promise<{ status: IFileStatus[]; didHitLimit: boolean; }> { return new Promise<{ status: IFileStatus[]; didHitLimit: boolean; }>((c, e) => { const parser = new GitStatusParser(); const env = { GIT_OPTIONAL_LOCKS: '0' }; - const child = this.stream(['status', '-z', '-u'], { env }); + const args = ['status', '-z', '-u']; + + if (opts?.ignoreSubmodules) { + args.push('--ignore-submodules'); + } + + const child = this.stream(args, { env }); const onExit = (exitCode: number) => { if (exitCode !== 0) { @@ -1775,13 +1815,15 @@ export class Repository { stderr, exitCode, gitErrorCode: getGitErrorCode(stderr), - gitCommand: 'status' + gitCommand: 'status', + gitArgs: args })); } c({ status: parser.status, didHitLimit: false }); }; + const limit = opts?.limit ?? 5000; const onStdoutData = (raw: string) => { parser.update(raw); @@ -1845,7 +1887,7 @@ export class Repository { args.push('--sort', `-${opts.sort}`); } - args.push('--format', '%(refname) %(objectname)'); + args.push('--format', '%(refname) %(objectname) %(*objectname)'); if (opts?.pattern) { args.push(opts.pattern); @@ -1860,12 +1902,12 @@ export class Repository { const fn = (line: string): Ref | null => { let match: RegExpExecArray | null; - if (match = /^refs\/heads\/([^ ]+) ([0-9a-f]{40})$/.exec(line)) { + if (match = /^refs\/heads\/([^ ]+) ([0-9a-f]{40}) ([0-9a-f]{40})?$/.exec(line)) { return { name: match[1], commit: match[2], type: RefType.Head }; - } else if (match = /^refs\/remotes\/([^/]+)\/([^ ]+) ([0-9a-f]{40})$/.exec(line)) { + } else if (match = /^refs\/remotes\/([^/]+)\/([^ ]+) ([0-9a-f]{40}) ([0-9a-f]{40})?$/.exec(line)) { return { name: `${match[1]}/${match[2]}`, commit: match[3], type: RefType.RemoteHead, remote: match[1] }; - } else if (match = /^refs\/tags\/([^ ]+) ([0-9a-f]{40})$/.exec(line)) { - return { name: match[1], commit: match[2], type: RefType.Tag }; + } else if (match = /^refs\/tags\/([^ ]+) ([0-9a-f]{40}) ([0-9a-f]{40})?$/.exec(line)) { + return { name: match[1], commit: match[3] ?? match[2], type: RefType.Tag }; } return null; diff --git a/extensions/git/src/remoteSource.ts b/extensions/git/src/remoteSource.ts index b736f606e..cbe437d6a 100644 --- a/extensions/git/src/remoteSource.ts +++ b/extensions/git/src/remoteSource.ts @@ -59,7 +59,8 @@ class RemoteSourceProviderQuickPick { this.quickpick.items = remoteSources.map(remoteSource => ({ label: remoteSource.name, description: remoteSource.description || (typeof remoteSource.url === 'string' ? remoteSource.url : remoteSource.url[0]), - remoteSource + remoteSource, + alwaysShow: true })); } } catch (err) { @@ -80,12 +81,30 @@ class RemoteSourceProviderQuickPick { export interface PickRemoteSourceOptions { readonly providerLabel?: (provider: RemoteSourceProvider) => string; readonly urlLabel?: string; + readonly providerName?: string; + readonly branch?: boolean; // then result is PickRemoteSourceResult } -export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions = {}): Promise { +export interface PickRemoteSourceResult { + readonly url: string; + readonly branch?: string; +} + +export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch?: false | undefined }): Promise; +export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch: true }): Promise; +export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions = {}): Promise { const quickpick = window.createQuickPick<(QuickPickItem & { provider?: RemoteSourceProvider, url?: string })>(); quickpick.ignoreFocusOut = true; + if (options.providerName) { + const provider = model.getRemoteProviders() + .filter(provider => provider.name === options.providerName)[0]; + + if (provider) { + return await pickProviderSource(provider, options); + } + } + const providers = model.getRemoteProviders() .map(provider => ({ label: (provider.icon ? `$(${provider.icon}) ` : '') + (options.providerLabel ? options.providerLabel(provider) : provider.name), alwaysShow: true, provider })); @@ -116,18 +135,48 @@ export async function pickRemoteSource(model: Model, options: PickRemoteSourceOp if (result.url) { return result.url; } else if (result.provider) { - const quickpick = new RemoteSourceProviderQuickPick(result.provider); - const remote = await quickpick.pick(); - - if (remote) { - if (typeof remote.url === 'string') { - return remote.url; - } else if (remote.url.length > 0) { - return await window.showQuickPick(remote.url, { ignoreFocusOut: true, placeHolder: localize('pick url', "Choose a URL to clone from.") }); - } - } + return await pickProviderSource(result.provider, options); } } return undefined; } + +async function pickProviderSource(provider: RemoteSourceProvider, options: PickRemoteSourceOptions = {}): Promise { + const quickpick = new RemoteSourceProviderQuickPick(provider); + const remote = await quickpick.pick(); + + let url: string | undefined; + + if (remote) { + if (typeof remote.url === 'string') { + url = remote.url; + } else if (remote.url.length > 0) { + url = await window.showQuickPick(remote.url, { ignoreFocusOut: true, placeHolder: localize('pick url', "Choose a URL to clone from.") }); + } + } + + if (!url || !options.branch) { + return url; + } + + if (!provider.getBranches) { + return { url }; + } + + const branches = await provider.getBranches(url); + + if (!branches) { + return { url }; + } + + const branch = await window.showQuickPick(branches, { + placeHolder: localize('branch name', "Branch name") + }); + + if (!branch) { + return { url }; + } + + return { url, branch }; +} diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 6b4fd0188..87f24395d 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -75,13 +75,21 @@ export class Resource implements SourceControlResourceState { return this._resourceUri; } - @memoize + get leftUri(): Uri | undefined { + return this.resources[0]; + } + + get rightUri(): Uri { + return this.resources[1]; + } + get command(): Command { - return { - command: 'git.openResource', - title: localize('open', "Open"), - arguments: [this] - }; + return this._commandResolver.resolveDefaultCommand(this); + } + + @memoize + private get resources(): [Uri | undefined, Uri] { + return this._commandResolver.getResources(this); } get resourceGroupType(): ResourceGroupType { return this._resourceGroupType; } @@ -262,12 +270,28 @@ export class Resource implements SourceControlResourceState { } constructor( + private _commandResolver: ResourceCommandResolver, private _resourceGroupType: ResourceGroupType, private _resourceUri: Uri, private _type: Status, private _useIcons: boolean, - private _renameResourceUri?: Uri + private _renameResourceUri?: Uri, ) { } + + async open(): Promise { + const command = this.command; + await commands.executeCommand(command.command, ...(command.arguments || [])); + } + + async openFile(): Promise { + const command = this._commandResolver.resolveFileCommand(this); + await commands.executeCommand(command.command, ...(command.arguments || [])); + } + + async openChange(): Promise { + const command = this._commandResolver.resolveChangeCommand(this); + await commands.executeCommand(command.command, ...(command.arguments || [])); + } } export const enum Operation { @@ -292,6 +316,7 @@ export const enum Operation { Fetch = 'Fetch', Pull = 'Pull', Push = 'Push', + CherryPick = 'CherryPick', Sync = 'Sync', Show = 'Show', Stage = 'Stage', @@ -315,6 +340,8 @@ export const enum Operation { Blame = 'Blame', Log = 'Log', LogFile = 'LogFile', + + Move = 'Move' } function isReadOnly(operation: Operation): boolean { @@ -550,6 +577,142 @@ class DotGitWatcher implements IFileWatcher { } } +class ResourceCommandResolver { + + constructor(private repository: Repository) { } + + resolveDefaultCommand(resource: Resource): Command { + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const openDiffOnClick = config.get('openDiffOnClick', true); + return openDiffOnClick ? this.resolveChangeCommand(resource) : this.resolveFileCommand(resource); + } + + resolveFileCommand(resource: Resource): Command { + return { + command: 'vscode.open', + title: localize('open', "Open"), + arguments: [resource.resourceUri] + }; + } + + resolveChangeCommand(resource: Resource): Command { + const title = this.getTitle(resource); + + if (!resource.leftUri) { + return { + command: 'vscode.open', + title: localize('open', "Open"), + arguments: [resource.rightUri, { override: resource.type === Status.BOTH_MODIFIED ? false : undefined }, title] + }; + } else { + return { + command: 'vscode.diff', + title: localize('open', "Open"), + arguments: [resource.leftUri, resource.rightUri, title] + }; + } + } + + getResources(resource: Resource): [Uri | undefined, Uri] { + for (const submodule of this.repository.submodules) { + if (path.join(this.repository.root, submodule.path) === resource.resourceUri.fsPath) { + return [undefined, toGitUri(resource.resourceUri, resource.resourceGroupType === ResourceGroupType.Index ? 'index' : 'wt', { submoduleOf: this.repository.root })]; + } + } + + return [this.getLeftResource(resource), this.getRightResource(resource)]; + } + + private getLeftResource(resource: Resource): Uri | undefined { + switch (resource.type) { + case Status.INDEX_MODIFIED: + case Status.INDEX_RENAMED: + case Status.INDEX_ADDED: + return toGitUri(resource.original, 'HEAD'); + + case Status.MODIFIED: + case Status.UNTRACKED: + return toGitUri(resource.resourceUri, '~'); + + case Status.DELETED_BY_US: + case Status.DELETED_BY_THEM: + return toGitUri(resource.resourceUri, '~1'); + } + return undefined; + } + + private getRightResource(resource: Resource): Uri { + switch (resource.type) { + case Status.INDEX_MODIFIED: + case Status.INDEX_ADDED: + case Status.INDEX_COPIED: + case Status.INDEX_RENAMED: + return toGitUri(resource.resourceUri, ''); + + case Status.INDEX_DELETED: + case Status.DELETED: + return toGitUri(resource.resourceUri, 'HEAD'); + + case Status.DELETED_BY_US: + return toGitUri(resource.resourceUri, '~3'); + + case Status.DELETED_BY_THEM: + return toGitUri(resource.resourceUri, '~2'); + + case Status.MODIFIED: + case Status.UNTRACKED: + case Status.IGNORED: + case Status.INTENT_TO_ADD: + const uriString = resource.resourceUri.toString(); + const [indexStatus] = this.repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString); + + if (indexStatus && indexStatus.renameResourceUri) { + return indexStatus.renameResourceUri; + } + + return resource.resourceUri; + + case Status.BOTH_ADDED: + case Status.BOTH_MODIFIED: + return resource.resourceUri; + } + + throw new Error('Should never happen'); + } + + private getTitle(resource: Resource): string { + const basename = path.basename(resource.resourceUri.fsPath); + + switch (resource.type) { + case Status.INDEX_MODIFIED: + case Status.INDEX_RENAMED: + case Status.INDEX_ADDED: + return localize('git.title.index', '{0} (Index)', basename); + + case Status.MODIFIED: + case Status.BOTH_ADDED: + case Status.BOTH_MODIFIED: + return localize('git.title.workingTree', '{0} (Working Tree)', basename); + + case Status.INDEX_DELETED: + case Status.DELETED: + return localize('git.title.deleted', '{0} (Deleted)', basename); + + case Status.DELETED_BY_US: + return localize('git.title.theirs', '{0} (Theirs)', basename); + + case Status.DELETED_BY_THEM: + return localize('git.title.ours', '{0} (Ours)', basename); + + case Status.UNTRACKED: + return localize('git.title.untracked', '{0} (Untracked)', basename); + + default: + return ''; + } + } +} + export class Repository implements Disposable { private _onDidChangeRepository = new EventEmitter(); @@ -680,6 +843,7 @@ export class Repository implements Disposable { private isRepositoryHuge = false; private didWarnAboutLimit = false; + private resourceCommandResolver = new ResourceCommandResolver(this); private disposables: Disposable[] = []; constructor( @@ -746,11 +910,12 @@ export class Repository implements Disposable { onConfigListener(updateIndexGroupVisibility, this, this.disposables); updateIndexGroupVisibility(); - const onConfigListenerForBranchSortOrder = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.branchSortOrder', root)); - onConfigListenerForBranchSortOrder(this.updateModelState, this, this.disposables); - - const onConfigListenerForUntracked = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.untrackedChanges', root)); - onConfigListenerForUntracked(this.updateModelState, this, this.disposables); + filterEvent(workspace.onDidChangeConfiguration, e => + e.affectsConfiguration('git.branchSortOrder', root) + || e.affectsConfiguration('git.untrackedChanges', root) + || e.affectsConfiguration('git.ignoreSubmodules', root) + || e.affectsConfiguration('git.openDiffOnClick', root) + )(this.updateModelState, this, this.disposables); const updateInputBoxVisibility = () => { const config = workspace.getConfiguration('git', root); @@ -864,6 +1029,12 @@ export class Repository implements Disposable { return; } + const path = uri.path; + + if (this.mergeGroup.resourceStates.some(r => r.resourceUri.path === path)) { + return undefined; + } + return toGitUri(uri, '', { replaceFileExtension: true }); } @@ -976,7 +1147,7 @@ export class Repository implements Disposable { await this.run(Operation.RevertFiles, () => this.repository.revert('HEAD', resources.map(r => r.fsPath))); } - async commit(message: string, opts: CommitOptions = Object.create(null)): Promise { + async commit(message: string | undefined, opts: CommitOptions = Object.create(null)): Promise { if (this.rebaseCommit) { await this.run(Operation.RebaseContinue, async () => { if (opts.all) { @@ -1053,6 +1224,14 @@ export class Repository implements Disposable { await this.run(Operation.RenameBranch, () => this.repository.renameBranch(name)); } + async cherryPick(commitHash: string): Promise { + await this.run(Operation.CherryPick, () => this.repository.cherryPick(commitHash)); + } + + async move(from: string, to: string): Promise { + await this.run(Operation.Move, () => this.repository.move(from, to)); + } + async getBranch(name: string): Promise { return await this.run(Operation.GetBranch, () => this.repository.getBranch(name)); } @@ -1081,12 +1260,12 @@ export class Repository implements Disposable { await this.run(Operation.DeleteTag, () => this.repository.deleteTag(name)); } - async checkout(treeish: string): Promise { - await this.run(Operation.Checkout, () => this.repository.checkout(treeish, [])); + async checkout(treeish: string, opts?: { detached?: boolean }): Promise { + await this.run(Operation.Checkout, () => this.repository.checkout(treeish, [], opts)); } - async checkoutTracking(treeish: string): Promise { - await this.run(Operation.CheckoutTracking, () => this.repository.checkout(treeish, [], { track: true })); + async checkoutTracking(treeish: string, opts: { detached?: boolean } = {}): Promise { + await this.run(Operation.CheckoutTracking, () => this.repository.checkout(treeish, [], { ...opts, track: true })); } async findTrackingBranches(upstreamRef: string): Promise { @@ -1119,21 +1298,31 @@ export class Repository implements Disposable { @throttle async fetchDefault(options: { silent?: boolean } = {}): Promise { - await this.run(Operation.Fetch, () => this.repository.fetch(options)); + await this._fetch({ silent: options.silent }); } @throttle async fetchPrune(): Promise { - await this.run(Operation.Fetch, () => this.repository.fetch({ prune: true })); + await this._fetch({ prune: true }); } @throttle async fetchAll(): Promise { - await this.run(Operation.Fetch, () => this.repository.fetch({ all: true })); + await this._fetch({ all: true }); } async fetch(remote?: string, ref?: string, depth?: number): Promise { - await this.run(Operation.Fetch, () => this.repository.fetch({ remote, ref, depth })); + await this._fetch({ remote, ref, depth }); + } + + private async _fetch(options: { remote?: string, ref?: string, all?: boolean, prune?: boolean, depth?: number, silent?: boolean } = {}): Promise { + if (!options.prune) { + const config = workspace.getConfiguration('git', Uri.file(this.root)); + const prune = config.get('pruneOnFetch'); + options.prune = prune; + } + + await this.run(Operation.Fetch, async () => this.repository.fetch(options)); } @throttle @@ -1169,11 +1358,12 @@ export class Repository implements Disposable { const fetchOnPull = config.get('fetchOnPull'); const tags = config.get('pullTags'); + // When fetchOnPull is enabled, fetch all branches when pulling if (fetchOnPull) { - await this.repository.pull(rebase, undefined, undefined, { unshallow, tags }); - } else { - await this.repository.pull(rebase, remote, branch, { unshallow, tags }); + await this.repository.fetch({ all: true }); } + + await this.repository.pull(rebase, remote, branch, { unshallow, tags }); }); }); } @@ -1199,6 +1389,10 @@ export class Repository implements Disposable { await this.run(Operation.Push, () => this._push(remote, undefined, false, true, forcePushMode)); } + async pushTags(remote?: string, forcePushMode?: ForcePushMode): Promise { + await this.run(Operation.Push, () => this._push(remote, undefined, false, false, forcePushMode, true)); + } + async blame(path: string): Promise { return await this.run(Operation.Blame, () => this.repository.blame(path)); } @@ -1229,11 +1423,18 @@ export class Repository implements Disposable { const config = workspace.getConfiguration('git', Uri.file(this.root)); const fetchOnPull = config.get('fetchOnPull'); const tags = config.get('pullTags'); + const followTags = config.get('followTagsWhenSync'); const supportCancellation = config.get('supportCancellation'); - const fn = fetchOnPull - ? async (cancellationToken?: CancellationToken) => await this.repository.pull(rebase, undefined, undefined, { tags, cancellationToken }) - : async (cancellationToken?: CancellationToken) => await this.repository.pull(rebase, remoteName, pullBranch, { tags, cancellationToken }); + const fn = async (cancellationToken?: CancellationToken) => { + // When fetchOnPull is enabled, fetch all branches when pulling + if (fetchOnPull) { + await this.repository.fetch({ all: true, cancellationToken }); + } + + await this.repository.pull(rebase, remoteName, pullBranch, { tags, cancellationToken }); + }; + if (supportCancellation) { const opts: ProgressOptions = { @@ -1256,7 +1457,7 @@ export class Repository implements Disposable { const shouldPush = this.HEAD && (typeof this.HEAD.ahead === 'number' ? this.HEAD.ahead > 0 : true); if (shouldPush) { - await this._push(remoteName, pushBranch); + await this._push(remoteName, pushBranch, false, followTags); } }); }); @@ -1418,9 +1619,9 @@ export class Repository implements Disposable { return ignored; } - private async _push(remote?: string, refspec?: string, setUpstream: boolean = false, tags = false, forcePushMode?: ForcePushMode): Promise { + private async _push(remote?: string, refspec?: string, setUpstream: boolean = false, followTags = false, forcePushMode?: ForcePushMode, tags = false): Promise { try { - await this.repository.push(remote, refspec, setUpstream, tags, forcePushMode); + await this.repository.push(remote, refspec, setUpstream, followTags, forcePushMode, tags); } catch (err) { if (!remote || !refspec) { throw err; @@ -1518,9 +1719,12 @@ export class Repository implements Disposable { @throttle private async updateModelState(): Promise { - const { status, didHitLimit } = await this.repository.getStatus(); - const config = workspace.getConfiguration('git'); const scopedConfig = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const ignoreSubmodules = scopedConfig.get('ignoreSubmodules'); + + const { status, didHitLimit } = await this.repository.getStatus({ ignoreSubmodules }); + + const config = workspace.getConfiguration('git'); const shouldIgnore = config.get('ignoreLimitWarning') === true; const useIcons = !config.get('decorations.enabled', true); this.isRepositoryHuge = didHitLimit; @@ -1595,36 +1799,36 @@ export class Repository implements Disposable { switch (raw.x + raw.y) { case '??': switch (untrackedChanges) { - case 'mixed': return workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.UNTRACKED, useIcons)); - case 'separate': return untracked.push(new Resource(ResourceGroupType.Untracked, uri, Status.UNTRACKED, useIcons)); + case 'mixed': return workingTree.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.UNTRACKED, useIcons)); + case 'separate': return untracked.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Untracked, uri, Status.UNTRACKED, useIcons)); default: return undefined; } case '!!': switch (untrackedChanges) { - case 'mixed': return workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.IGNORED, useIcons)); - case 'separate': return untracked.push(new Resource(ResourceGroupType.Untracked, uri, Status.IGNORED, useIcons)); + case 'mixed': return workingTree.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.IGNORED, useIcons)); + case 'separate': return untracked.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Untracked, uri, Status.IGNORED, useIcons)); default: return undefined; } - case 'DD': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_DELETED, useIcons)); - case 'AU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.ADDED_BY_US, useIcons)); - case 'UD': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.DELETED_BY_THEM, useIcons)); - case 'UA': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.ADDED_BY_THEM, useIcons)); - case 'DU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.DELETED_BY_US, useIcons)); - case 'AA': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_ADDED, useIcons)); - case 'UU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_MODIFIED, useIcons)); + case 'DD': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.BOTH_DELETED, useIcons)); + case 'AU': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.ADDED_BY_US, useIcons)); + case 'UD': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.DELETED_BY_THEM, useIcons)); + case 'UA': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.ADDED_BY_THEM, useIcons)); + case 'DU': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.DELETED_BY_US, useIcons)); + case 'AA': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.BOTH_ADDED, useIcons)); + case 'UU': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.BOTH_MODIFIED, useIcons)); } switch (raw.x) { - case 'M': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_MODIFIED, useIcons)); break; - case 'A': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_ADDED, useIcons)); break; - case 'D': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_DELETED, useIcons)); break; - case 'R': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_RENAMED, useIcons, renameUri)); break; - case 'C': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_COPIED, useIcons, renameUri)); break; + case 'M': index.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_MODIFIED, useIcons)); break; + case 'A': index.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_ADDED, useIcons)); break; + case 'D': index.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_DELETED, useIcons)); break; + case 'R': index.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_RENAMED, useIcons, renameUri)); break; + case 'C': index.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_COPIED, useIcons, renameUri)); break; } switch (raw.y) { - case 'M': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.MODIFIED, useIcons, renameUri)); break; - case 'D': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.DELETED, useIcons, renameUri)); break; - case 'A': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_ADD, useIcons, renameUri)); break; + case 'M': workingTree.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.MODIFIED, useIcons, renameUri)); break; + case 'D': workingTree.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.DELETED, useIcons, renameUri)); break; + case 'A': workingTree.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_ADD, useIcons, renameUri)); break; } return undefined; diff --git a/extensions/git/src/timelineProvider.ts b/extensions/git/src/timelineProvider.ts index a315963a5..6e2408366 100644 --- a/extensions/git/src/timelineProvider.ts +++ b/extensions/git/src/timelineProvider.ts @@ -200,7 +200,7 @@ export class GitTimelineProvider implements TimelineProvider { if (working) { const date = new Date(); - const item = new GitTimelineItem('', index ? '~' : 'HEAD', localize('git.timeline.uncommitedChanges', 'Uncommited Changes'), date.getTime(), 'working', 'git:file:working'); + const item = new GitTimelineItem('', index ? '~' : 'HEAD', localize('git.timeline.uncommitedChanges', 'Uncommitted Changes'), date.getTime(), 'working', 'git:file:working'); // TODO@eamodio: Replace with a better icon -- reflecting its status maybe? item.iconPath = new (ThemeIcon as any)('git-commit'); item.description = ''; diff --git a/extensions/github-authentication/src/extension.ts b/extensions/github-authentication/src/extension.ts index 98b715f80..b49942b52 100644 --- a/extensions/github-authentication/src/extension.ts +++ b/extensions/github-authentication/src/extension.ts @@ -40,6 +40,15 @@ export async function activate(context: vscode.ExtensionContext) { onDidChangeSessions.fire({ added: [session.id], removed: [], changed: [] }); return session; } catch (e) { + // If login was cancelled, do not notify user. + if (e.message === 'Cancelled') { + /* __GDPR__ + "loginCancelled" : { } + */ + telemetryReporter.sendTelemetryEvent('loginCancelled'); + throw e; + } + /* __GDPR__ "loginFailed" : { } */ diff --git a/extensions/github-authentication/src/github.ts b/extensions/github-authentication/src/github.ts index f28ac97c6..d1cf45b7d 100644 --- a/extensions/github-authentication/src/github.ts +++ b/extensions/github-authentication/src/github.ts @@ -180,9 +180,12 @@ export class GitHubAuthenticationProvider { } public async logout(id: string) { + Logger.info(`Logging out of ${id}`); const sessionIndex = this._sessions.findIndex(session => session.id === id); if (sessionIndex > -1) { this._sessions.splice(sessionIndex, 1); + } else { + Logger.error('Session not found'); } await this.storeSessions(); diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts index 55c0e4ef4..b50285c9a 100644 --- a/extensions/github-authentication/src/githubServer.ts +++ b/extensions/github-authentication/src/githubServer.ts @@ -23,7 +23,7 @@ class UriEventHandler extends vscode.EventEmitter implements vscode. export const uriHandler = new UriEventHandler; -const onDidManuallyProvideToken = new vscode.EventEmitter(); +const onDidManuallyProvideToken = new vscode.EventEmitter(); @@ -91,7 +91,7 @@ export class GitHubServer { return Promise.race([ existingPromise, - promiseFromEvent(onDidManuallyProvideToken.event) + promiseFromEvent(onDidManuallyProvideToken.event, (token: string | undefined): string => { if (!token) { throw new Error('Cancelled'); } return token; }) ]).finally(() => { this._pendingStates.delete(scopes); this._codeExchangePromises.delete(scopes); @@ -147,7 +147,11 @@ export class GitHubServer { public async manuallyProvideToken() { const uriOrToken = await vscode.window.showInputBox({ prompt: 'Token', ignoreFocusOut: true }); - if (!uriOrToken) { return; } + if (!uriOrToken) { + onDidManuallyProvideToken.fire(undefined); + return; + } + try { const uri = vscode.Uri.parse(uriOrToken); if (!uri.scheme || uri.scheme === 'file') { throw new Error; } diff --git a/extensions/github/src/publish.ts b/extensions/github/src/publish.ts index 0abb606b5..cf8f306cf 100644 --- a/extensions/github/src/publish.ts +++ b/extensions/github/src/publish.ts @@ -9,6 +9,7 @@ import { API as GitAPI, Repository } from './typings/git'; import { getOctokit } from './auth'; import { TextEncoder } from 'util'; import { basename } from 'path'; +import { Octokit } from '@octokit/rest'; const localize = nls.loadMessageBundle(); @@ -57,9 +58,18 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository) quickpick.show(); quickpick.busy = true; - const octokit = await getOctokit(); - const user = await octokit.users.getAuthenticated({}); - const owner = user.data.login; + let owner: string; + let octokit: Octokit; + try { + octokit = await getOctokit(); + const user = await octokit.users.getAuthenticated({}); + owner = user.data.login; + } catch (e) { + // User has cancelled sign in + quickpick.dispose(); + return; + } + quickpick.busy = false; let repo: string | undefined; @@ -139,7 +149,7 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository) new Promise(c => quickpick.onDidHide(() => c(undefined))) ]); - if (!result) { + if (!result || result.length === 0) { return; } @@ -192,9 +202,9 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository) } const openInGitHub = 'Open In GitHub'; - const action = await vscode.window.showInformationMessage(`Successfully published the '${owner}/${repo}' repository on GitHub.`, openInGitHub); - - if (action === openInGitHub) { - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(githubRepository.html_url)); - } + vscode.window.showInformationMessage(`Successfully published the '${owner}/${repo}' repository on GitHub.`, openInGitHub).then(action => { + if (action === openInGitHub) { + vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(githubRepository.html_url)); + } + }); } diff --git a/extensions/github/src/pushErrorHandler.ts b/extensions/github/src/pushErrorHandler.ts index a08a8d45c..0b2f3b9de 100644 --- a/extensions/github/src/pushErrorHandler.ts +++ b/extensions/github/src/pushErrorHandler.ts @@ -40,8 +40,14 @@ async function handlePushError(repository: Repository, remote: Remote, refspec: // Issue: what if there's already another `origin` repo? await repository.addRemote('origin', ghRepository.clone_url); - await repository.fetch('origin', remoteName); - await repository.setBranchUpstream(localName, `origin/${remoteName}`); + + try { + await repository.fetch('origin', remoteName); + await repository.setBranchUpstream(localName, `origin/${remoteName}`); + } catch { + // noop + } + await repository.push('origin', localName, true); return [octokit, ghRepository]; diff --git a/extensions/github/src/remoteSourceProvider.ts b/extensions/github/src/remoteSourceProvider.ts index 9d0ab4c58..17b153006 100644 --- a/extensions/github/src/remoteSourceProvider.ts +++ b/extensions/github/src/remoteSourceProvider.ts @@ -8,6 +8,12 @@ import { getOctokit } from './auth'; import { Octokit } from '@octokit/rest'; import { publishRepository } from './publish'; +function parse(url: string): { owner: string, repo: string } | undefined { + const match = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\.git/i.exec(url) + || /^git@github\.com:([^/]+)\/([^/]+)\.git/i.exec(url); + return (match && { owner: match[1], repo: match[2] }) ?? undefined; +} + function asRemoteSource(raw: any): RemoteSource { return { name: `$(github) ${raw.full_name}`, @@ -28,17 +34,30 @@ export class GithubRemoteSourceProvider implements RemoteSourceProvider { async getRemoteSources(query?: string): Promise { const octokit = await getOctokit(); - const [fromUser, fromQuery] = await Promise.all([ + + if (query) { + const repository = parse(query); + + if (repository) { + const raw = await octokit.repos.get(repository); + return [asRemoteSource(raw.data)]; + } + } + + const all = await Promise.all([ this.getUserRemoteSources(octokit, query), this.getQueryRemoteSources(octokit, query) ]); - const userRepos = new Set(fromUser.map(r => r.name)); + const map = new Map(); - return [ - ...fromUser, - ...fromQuery.filter(r => !userRepos.has(r.name)) - ]; + for (const group of all) { + for (const remoteSource of group) { + map.set(remoteSource.name, remoteSource); + } + } + + return [...map.values()]; } private async getUserRemoteSources(octokit: Octokit, query?: string): Promise { @@ -61,6 +80,35 @@ export class GithubRemoteSourceProvider implements RemoteSourceProvider { return raw.data.items.map(asRemoteSource); } + async getBranches(url: string): Promise { + const repository = parse(url); + + if (!repository) { + return []; + } + + const octokit = await getOctokit(); + + const branches: string[] = []; + let page = 1; + + while (true) { + let res = await octokit.repos.listBranches({ ...repository, per_page: 100, page }); + + if (res.data.length === 0) { + break; + } + + branches.push(...res.data.map(b => b.name)); + page++; + } + + const repo = await octokit.repos.get(repository); + const defaultBranch = repo.data.default_branch; + + return branches.sort((a, b) => a === defaultBranch ? -1 : b === defaultBranch ? 1 : 0); + } + publishRepository(repository: Repository): Promise { return publishRepository(this.gitAPI, repository); } diff --git a/extensions/github/src/typings/git.d.ts b/extensions/github/src/typings/git.d.ts index 54f21b96d..b9a09632b 100644 --- a/extensions/github/src/typings/git.d.ts +++ b/extensions/github/src/typings/git.d.ts @@ -130,6 +130,7 @@ export interface CommitOptions { signoff?: boolean; signCommit?: boolean; empty?: boolean; + noVerify?: boolean; } export interface BranchQuery { @@ -211,6 +212,7 @@ export interface RemoteSourceProvider { readonly icon?: string; // codicon name readonly supportsQuery?: boolean; getRemoteSources(query?: string): ProviderResult; + getBranches?(url: string): ProviderResult; publishRepository?(repository: Repository): Promise; } diff --git a/extensions/html-language-features/client/src/htmlClient.ts b/extensions/html-language-features/client/src/htmlClient.ts index 6917b5695..494b935a7 100644 --- a/extensions/html-language-features/client/src/htmlClient.ts +++ b/extensions/html-language-features/client/src/htmlClient.ts @@ -27,8 +27,8 @@ namespace CustomDataChangedNotification { namespace TagCloseRequest { export const type: RequestType = new RequestType('html/tag'); } -namespace OnTypeRenameRequest { - export const type: RequestType = new RequestType('html/onTypeRename'); +namespace LinkedEditingRequest { + export const type: RequestType = new RequestType('html/linkedEditing'); } // experimental: semantic tokens @@ -44,7 +44,7 @@ namespace SemanticTokenLegendRequest { } namespace SettingIds { - export const renameOnType = 'editor.renameOnType'; + export const linkedRename = 'editor.linkedRename'; export const formatEnable = 'html.format.enable'; } @@ -169,10 +169,10 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua } }); - disposable = languages.registerOnTypeRenameProvider(documentSelector, { - async provideOnTypeRenameRanges(document, position) { + disposable = languages.registerLinkedEditingRangeProvider(documentSelector, { + async provideLinkedEditingRanges(document, position) { const param = client.code2ProtocolConverter.asTextDocumentPositionParams(document, position); - return client.sendRequest(OnTypeRenameRequest.type, param).then(response => { + return client.sendRequest(LinkedEditingRequest.type, param).then(response => { if (response) { return { ranges: response.map(r => client.protocol2CodeConverter.asRange(r)) @@ -301,7 +301,7 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua const promptForTypeOnRenameKey = 'html.promptForTypeOnRename'; const promptForTypeOnRename = extensions.getExtension('formulahendry.auto-rename-tag') !== undefined && (context.globalState.get(promptForTypeOnRenameKey) !== false) && - !workspace.getConfiguration('editor', { languageId: 'html' }).get('renameOnType'); + !workspace.getConfiguration('editor', { languageId: 'html' }).get('linkedRename'); if (promptForTypeOnRename) { const activeEditorListener = window.onDidChangeActiveTextEditor(async e => { @@ -309,9 +309,9 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua context.globalState.update(promptForTypeOnRenameKey, false); activeEditorListener.dispose(); const configure = localize('configureButton', 'Configure'); - const res = await window.showInformationMessage(localize('renameOnTypeQuestion', 'VS Code now has built-in support for auto-renaming tags. Do you want to enable it?'), configure); + const res = await window.showInformationMessage(localize('linkedRenameQuestion', 'VS Code now has built-in support for auto-renaming tags. Do you want to enable it?'), configure); if (res === configure) { - commands.executeCommand('workbench.action.openSettings', SettingIds.renameOnType); + commands.executeCommand('workbench.action.openSettings', SettingIds.linkedRename); } } }); diff --git a/extensions/html-language-features/client/src/node/htmlClientMain.ts b/extensions/html-language-features/client/src/node/htmlClientMain.ts index c58515b37..097bda1f6 100644 --- a/extensions/html-language-features/client/src/node/htmlClientMain.ts +++ b/extensions/html-language-features/client/src/node/htmlClientMain.ts @@ -24,7 +24,7 @@ export function activate(context: ExtensionContext) { const serverModule = context.asAbsolutePath(serverMain); // The debug options for the server - const debugOptions = { execArgv: ['--nolazy', '--inspect=6044'] }; + const debugOptions = { execArgv: ['--nolazy', '--inspect=' + (8000 + Math.round(Math.random() * 999))] }; // If the extension is launch in debug mode the debug server options are use // Otherwise the run options are used diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index 204ae49b1..c2c211ccb 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -138,6 +138,31 @@ ], "description": "%html.format.wrapAttributes.desc%" }, + "html.format.wrapAttributesIndentSize": { + "type": [ + "number", + "null" + ], + "scope": "resource", + "default": null, + "description": "%html.format.wrapAttributesIndentSize.desc%" + }, + "html.format.templating": { + "type": [ + "boolean" + ], + "scope": "resource", + "default": false, + "description": "%html.format.templating.desc%" + }, + "html.format.unformattedContentDelimiter": { + "type": [ + "string" + ], + "scope": "resource", + "default": "", + "markdownDescription": "%html.format.unformattedContentDelimiter.desc%" + }, "html.suggest.html5": { "type": "boolean", "scope": "resource", @@ -162,6 +187,18 @@ "default": true, "description": "%html.autoClosingTags%" }, + "html.hover.documentation": { + "type": "boolean", + "scope": "resource", + "default": true, + "description": "%html.hover.documentation%" + }, + "html.hover.references": { + "type": "boolean", + "scope": "resource", + "default": true, + "description": "%html.hover.references%" + }, "html.mirrorCursorOnMatchingTag": { "type": "boolean", "scope": "resource", @@ -204,7 +241,7 @@ "dependencies": { "vscode-extension-telemetry": "0.1.1", "vscode-languageclient": "7.0.0-next.5.1", - "vscode-nls": "^4.1.2" + "vscode-nls": "^5.0.0" }, "devDependencies": { "@types/node": "^12.11.7" diff --git a/extensions/html-language-features/package.nls.json b/extensions/html-language-features/package.nls.json index ff5815029..49c632091 100644 --- a/extensions/html-language-features/package.nls.json +++ b/extensions/html-language-features/package.nls.json @@ -20,11 +20,16 @@ "html.format.wrapAttributes.alignedmultiple": "Wrap when line length is exceeded, align attributes vertically.", "html.format.wrapAttributes.preserve": "Preserve wrapping of attributes", "html.format.wrapAttributes.preservealigned": "Preserve wrapping of attributes but align.", + "html.format.templating.desc": "Honor django, erb, handlebars and php templating language tags.", + "html.format.unformattedContentDelimiter.desc": "Keep text content together between this string.", + "html.format.wrapAttributesIndentSize.desc": "Alignment size when using 'force aligned' and 'aligned multiple' in `#html.format.wrapAttributes#` or `null` to use the default indent size.", "html.suggest.html5.desc": "Controls whether the built-in HTML language support suggests HTML5 tags, properties and values.", "html.trace.server.desc": "Traces the communication between VS Code and the HTML language server.", "html.validate.scripts": "Controls whether the built-in HTML language support validates embedded scripts.", "html.validate.styles": "Controls whether the built-in HTML language support validates embedded styles.", "html.autoClosingTags": "Enable/disable autoclosing of HTML tags.", "html.mirrorCursorOnMatchingTag": "Enable/disable mirroring cursor on matching HTML tag.", - "html.mirrorCursorOnMatchingTagDeprecationMessage": "Deprecated in favor of `editor.renameOnType`" + "html.mirrorCursorOnMatchingTagDeprecationMessage": "Deprecated in favor of `editor.linkedEditing`", + "html.hover.documentation": "Show tag and attribute documentation in hover.", + "html.hover.references": "Show references to MDN in hover." } diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json index 62bf41766..65ce7cc7e 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -9,8 +9,8 @@ }, "main": "./out/node/htmlServerMain", "dependencies": { - "vscode-css-languageservice": "^4.3.5", - "vscode-html-languageservice": "^3.1.4", + "vscode-css-languageservice": "^4.4.0", + "vscode-html-languageservice": "^3.2.0", "vscode-languageserver": "7.0.0-next.3", "vscode-nls": "^5.0.0", "vscode-uri": "^2.1.2" diff --git a/extensions/html-language-features/server/src/htmlServer.ts b/extensions/html-language-features/server/src/htmlServer.ts index 1ddfef6ce..7b76a8577 100644 --- a/extensions/html-language-features/server/src/htmlServer.ts +++ b/extensions/html-language-features/server/src/htmlServer.ts @@ -33,8 +33,8 @@ namespace CustomDataChangedNotification { namespace TagCloseRequest { export const type: RequestType = new RequestType('html/tag'); } -namespace OnTypeRenameRequest { - export const type: RequestType = new RequestType('html/onTypeRename'); +namespace LinkedEditingRequest { + export const type: RequestType = new RequestType('html/linkedEditing'); } // experimental: semantic tokens @@ -284,7 +284,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) if (!mode || !mode.doComplete) { return { isIncomplete: true, items: [] }; } - const doComplete = mode.doComplete!; + const doComplete = mode.doComplete; if (mode.getId() !== 'html') { /* __GDPR__ @@ -321,8 +321,10 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) const document = documents.get(textDocumentPosition.textDocument.uri); if (document) { const mode = languageModes.getModeAtPosition(document, textDocumentPosition.position); - if (mode && mode.doHover) { - return mode.doHover(document, textDocumentPosition.position); + const doHover = mode?.doHover; + if (doHover) { + const settings = await getDocumentSettings(document, () => doHover.length > 2); + return doHover(document, textDocumentPosition.position, settings); } } return null; @@ -508,15 +510,15 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }, null, `Error while computing rename for ${params.textDocument.uri}`, token); }); - connection.onRequest(OnTypeRenameRequest.type, (params, token) => { + connection.onRequest(LinkedEditingRequest.type, (params, token) => { return runSafe(async () => { const document = documents.get(params.textDocument.uri); if (document) { const pos = params.position; if (pos.character > 0) { const mode = languageModes.getModeAtPosition(document, Position.create(pos.line, pos.character - 1)); - if (mode && mode.doOnTypeRename) { - return mode.doOnTypeRename(document, pos); + if (mode && mode.doLinkedEditing) { + return mode.doLinkedEditing(document, pos); } } } diff --git a/extensions/html-language-features/server/src/modes/cssMode.ts b/extensions/html-language-features/server/src/modes/cssMode.ts index 9b3ddd9b7..f7c5455b7 100644 --- a/extensions/html-language-features/server/src/modes/cssMode.ts +++ b/extensions/html-language-features/server/src/modes/cssMode.ts @@ -5,7 +5,7 @@ import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache'; import { Stylesheet, LanguageService as CSSLanguageService } from 'vscode-css-languageservice'; -import { LanguageMode, Workspace, Color, TextDocument, Position, Range, CompletionList, DocumentContext } from './languageModes'; +import { LanguageMode, Workspace, Color, TextDocument, Position, Range, CompletionList, DocumentContext, Settings } from './languageModes'; import { HTMLDocumentRegions, CSS_STYLE_RULE } from './embeddedSupport'; export function getCSSMode(cssLanguageService: CSSLanguageService, documentRegions: LanguageModelCache, workspace: Workspace): LanguageMode { @@ -25,9 +25,9 @@ export function getCSSMode(cssLanguageService: CSSLanguageService, documentRegio const stylesheet = cssStylesheets.get(embedded); return cssLanguageService.doComplete2(embedded, position, stylesheet, documentContext) || CompletionList.create(); }, - async doHover(document: TextDocument, position: Position) { + async doHover(document: TextDocument, position: Position, settings?: Settings) { let embedded = embeddedCSSDocuments.get(document); - return cssLanguageService.doHover(embedded, position, cssStylesheets.get(embedded)); + return cssLanguageService.doHover(embedded, position, cssStylesheets.get(embedded), settings?.html?.hover); }, async findDocumentHighlight(document: TextDocument, position: Position) { let embedded = embeddedCSSDocuments.get(document); diff --git a/extensions/html-language-features/server/src/modes/htmlMode.ts b/extensions/html-language-features/server/src/modes/htmlMode.ts index 96462f8e0..a8a17ca8c 100644 --- a/extensions/html-language-features/server/src/modes/htmlMode.ts +++ b/extensions/html-language-features/server/src/modes/htmlMode.ts @@ -8,7 +8,7 @@ import { LanguageService as HTMLLanguageService, HTMLDocument, DocumentContext, FormattingOptions, HTMLFormatConfiguration, SelectionRange, TextDocument, Position, Range, FoldingRange, - LanguageMode, Workspace + LanguageMode, Workspace, Settings } from './languageModes'; export function getHTMLMode(htmlLanguageService: HTMLLanguageService, workspace: Workspace): LanguageMode { @@ -31,8 +31,8 @@ export function getHTMLMode(htmlLanguageService: HTMLLanguageService, workspace: let completionList = htmlLanguageService.doComplete2(document, position, htmlDocument, documentContext, options); return completionList; }, - async doHover(document: TextDocument, position: Position) { - return htmlLanguageService.doHover(document, position, htmlDocuments.get(document)); + async doHover(document: TextDocument, position: Position, settings?: Settings) { + return htmlLanguageService.doHover(document, position, htmlDocuments.get(document), settings?.html?.hover); }, async findDocumentHighlight(document: TextDocument, position: Position) { return htmlLanguageService.findDocumentHighlights(document, position, htmlDocuments.get(document)); @@ -80,9 +80,9 @@ export function getHTMLMode(htmlLanguageService: HTMLLanguageService, workspace: const htmlDocument = htmlDocuments.get(document); return htmlLanguageService.findMatchingTagPosition(document, position, htmlDocument); }, - async doOnTypeRename(document: TextDocument, position: Position) { + async doLinkedEditing(document: TextDocument, position: Position) { const htmlDocument = htmlDocuments.get(document); - return htmlLanguageService.findOnTypeRenameRanges(document, position, htmlDocument); + return htmlLanguageService.findLinkedEditingRanges(document, position, htmlDocument); }, dispose() { htmlDocuments.dispose(); diff --git a/extensions/html-language-features/server/src/modes/javascriptMode.ts b/extensions/html-language-features/server/src/modes/javascriptMode.ts index 086b8a906..1fc33faed 100644 --- a/extensions/html-language-features/server/src/modes/javascriptMode.ts +++ b/extensions/html-language-features/server/src/modes/javascriptMode.ts @@ -6,7 +6,7 @@ import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache'; import { SymbolInformation, SymbolKind, CompletionItem, Location, SignatureHelp, SignatureInformation, ParameterInformation, - Definition, TextEdit, TextDocument, Diagnostic, DiagnosticSeverity, Range, CompletionItemKind, Hover, MarkedString, + Definition, TextEdit, TextDocument, Diagnostic, DiagnosticSeverity, Range, CompletionItemKind, Hover, DocumentHighlight, DocumentHighlightKind, CompletionList, Position, FormattingOptions, FoldingRange, FoldingRangeKind, SelectionRange, LanguageMode, Settings, SemanticTokenData, Workspace, DocumentContext } from './languageModes'; @@ -142,10 +142,10 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache Promise; doComplete?: (document: TextDocument, position: Position, documentContext: DocumentContext, settings?: Settings) => Promise; doResolve?: (document: TextDocument, item: CompletionItem) => Promise; - doHover?: (document: TextDocument, position: Position) => Promise; + doHover?: (document: TextDocument, position: Position, settings?: Settings) => Promise; doSignatureHelp?: (document: TextDocument, position: Position) => Promise; doRename?: (document: TextDocument, position: Position, newName: string) => Promise; - doOnTypeRename?: (document: TextDocument, position: Position) => Promise; + doLinkedEditing?: (document: TextDocument, position: Position) => Promise; findDocumentHighlight?: (document: TextDocument, position: Position) => Promise; findDocumentSymbols?: (document: TextDocument) => Promise; findDocumentLinks?: (document: TextDocument, documentContext: DocumentContext) => Promise; diff --git a/extensions/html-language-features/server/src/test/semanticTokens.test.ts b/extensions/html-language-features/server/src/test/semanticTokens.test.ts index 6d815748e..548ce7d84 100644 --- a/extensions/html-language-features/server/src/test/semanticTokens.test.ts +++ b/extensions/html-language-features/server/src/test/semanticTokens.test.ts @@ -87,8 +87,8 @@ suite('HTML Semantic Tokens', () => { ]; await assertTokens(input, [ t(3, 11, 3, 'function.declaration'), t(3, 15, 2, 'parameter.declaration'), - t(4, 11, 3, 'function'), t(4, 15, 4, 'interface'), t(4, 20, 3, 'member'), t(4, 24, 2, 'parameter'), - t(6, 6, 6, 'variable'), t(6, 13, 8, 'property'), t(6, 24, 5, 'member'), t(6, 35, 7, 'member'), t(6, 43, 1, 'parameter.declaration'), t(6, 48, 3, 'function'), t(6, 52, 1, 'parameter') + t(4, 11, 3, 'function'), t(4, 15, 4, 'interface'), t(4, 20, 3, 'method'), t(4, 24, 2, 'parameter'), + t(6, 6, 6, 'variable'), t(6, 13, 8, 'property'), t(6, 24, 5, 'method'), t(6, 35, 7, 'method'), t(6, 43, 1, 'parameter.declaration'), t(6, 48, 3, 'function'), t(6, 52, 1, 'parameter') ]); }); @@ -115,9 +115,9 @@ suite('HTML Semantic Tokens', () => { t(3, 8, 1, 'class.declaration'), t(4, 11, 1, 'property.declaration.static'), t(5, 4, 1, 'property.declaration'), - t(6, 10, 1, 'member.declaration.async'), t(6, 23, 1, 'class'), t(6, 25, 1, 'property.static'), t(6, 40, 1, 'member.async'), + t(6, 10, 1, 'method.declaration.async'), t(6, 23, 1, 'class'), t(6, 25, 1, 'property.static'), t(6, 40, 1, 'method.async'), t(7, 8, 1, 'property.declaration'), t(7, 26, 1, 'property'), - t(8, 11, 1, 'member.declaration.static'), t(8, 28, 1, 'class'), t(8, 32, 1, 'property'), + t(8, 11, 1, 'method.declaration.static'), t(8, 28, 1, 'class'), t(8, 32, 1, 'property'), ]); }); @@ -157,7 +157,7 @@ suite('HTML Semantic Tokens', () => { t(3, 8, 1, 'variable.declaration.readonly'), t(4, 8, 1, 'class.declaration'), t(4, 28, 1, 'property.declaration.static.readonly'), t(4, 42, 3, 'property.declaration.static'), t(4, 47, 3, 'interface'), t(5, 13, 1, 'enum.declaration'), t(5, 17, 1, 'property.declaration.readonly'), t(5, 24, 1, 'property.declaration.readonly'), t(5, 28, 1, 'property.readonly'), - t(6, 2, 7, 'variable'), t(6, 10, 3, 'member'), t(6, 14, 1, 'variable.readonly'), t(6, 18, 1, 'class'), t(6, 20, 1, 'property.static.readonly'), t(6, 24, 1, 'class'), t(6, 26, 3, 'property.static'), t(6, 30, 6, 'property.readonly'), + t(6, 2, 7, 'variable'), t(6, 10, 3, 'method'), t(6, 14, 1, 'variable.readonly'), t(6, 18, 1, 'class'), t(6, 20, 1, 'property.static.readonly'), t(6, 24, 1, 'class'), t(6, 26, 3, 'property.static'), t(6, 30, 6, 'property.readonly'), ]); }); @@ -197,7 +197,7 @@ suite('HTML Semantic Tokens', () => { ]; await assertTokens(input, [ t(3, 11, 1, 'function.declaration'), t(3, 13, 1, 'typeParameter.declaration'), t(3, 16, 2, 'parameter.declaration'), t(3, 20, 1, 'typeParameter'), t(3, 24, 1, 'typeParameter'), t(3, 39, 2, 'parameter'), - t(6, 2, 6, 'variable'), t(6, 9, 5, 'member') + t(6, 2, 6, 'variable'), t(6, 9, 5, 'method') ]); }); @@ -215,7 +215,7 @@ suite('HTML Semantic Tokens', () => { /*9*/'', ]; await assertTokens(input, [ - t(3, 2, 6, 'variable'), t(3, 9, 5, 'member') + t(3, 2, 6, 'variable'), t(3, 9, 5, 'method') ], [Range.create(Position.create(2, 0), Position.create(4, 0))]); await assertTokens(input, [ diff --git a/extensions/html-language-features/server/yarn.lock b/extensions/html-language-features/server/yarn.lock index 2bb5bba8e..6d17972d5 100644 --- a/extensions/html-language-features/server/yarn.lock +++ b/extensions/html-language-features/server/yarn.lock @@ -880,20 +880,20 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -vscode-css-languageservice@^4.3.5: - version "4.3.5" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.3.5.tgz#92f8817057dee7c381df2289aad539c7b553548a" - integrity sha512-g9Pjxt9T32jhY0nTOo7WRFm0As27IfdaAxcFa8c7Rml1ZqBn3XXbkExjzxY7sBWYm7I1Tp4dK6UHXHoUQHGwig== +vscode-css-languageservice@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.4.0.tgz#a7c5edf3057e707601ca18fa3728784a298513b4" + integrity sha512-jWi+297PJUUWTHwlcrZz0zIuEXuHOBJIQMapXmEzbosWGv/gMnNSAMV4hTKnl5wzxvZKZzV6j+WFdrSlKQ5qnw== dependencies: vscode-languageserver-textdocument "^1.0.1" vscode-languageserver-types "3.16.0-next.2" vscode-nls "^5.0.0" vscode-uri "^2.1.2" -vscode-html-languageservice@^3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-3.1.4.tgz#0316dff77ee38dc176f40560cbf55e4f64f4f433" - integrity sha512-3M+bm+hNvwQcScVe5/ok9BXvctOiGJ4nlOkkFf+WKSDrYNkarZ/RByKOa1/iylbvZxJUPzbeziembWPe/dMvhw== +vscode-html-languageservice@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-3.2.0.tgz#e92269a04097d87bd23431e3a4e491a27b5447b9" + integrity sha512-aLWIoWkvb5HYTVE0kI9/u3P0ZAJGrYOSAAE6L0wqB9radKRtbJNrF9+BjSUFyCgBdNBE/GFExo35LoknQDJrfw== dependencies: vscode-languageserver-textdocument "^1.0.1" vscode-languageserver-types "3.16.0-next.2" diff --git a/extensions/html-language-features/yarn.lock b/extensions/html-language-features/yarn.lock index 0d4b9507c..7d639c7b4 100644 --- a/extensions/html-language-features/yarn.lock +++ b/extensions/html-language-features/yarn.lock @@ -71,10 +71,10 @@ vscode-languageserver-types@3.16.0-next.2: resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0-next.2.tgz#940bd15c992295a65eae8ab6b8568a1e8daa3083" integrity sha512-QjXB7CKIfFzKbiCJC4OWC8xUncLsxo19FzGVp/ADFvvi87PlmBSCAtZI5xwGjF5qE0xkLf0jjKUn3DzmpDP52Q== -vscode-nls@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167" - integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw== +vscode-nls@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" + integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== zone.js@0.7.6: version "0.7.6" diff --git a/extensions/javascript/snippets/javascript.code-snippets b/extensions/javascript/snippets/javascript.code-snippets index b005c80c8..a48a64c28 100644 --- a/extensions/javascript/snippets/javascript.code-snippets +++ b/extensions/javascript/snippets/javascript.code-snippets @@ -35,7 +35,7 @@ "prefix": "forin", "body": [ "for (const ${1:key} in ${2:object}) {", - "\tif (${2:object}.hasOwnProperty(${1:key})) {", + "\tif (Object.hasOwnProperty.call(${2:object}, ${1:key})) {", "\t\tconst ${3:element} = ${2:object}[${1:key}];", "\t\t$0", "\t}", diff --git a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json index 9a991cde0..c729c8ff9 100644 --- a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json +++ b/extensions/javascript/syntaxes/JavaScript.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/TypeScript-TmLanguage/commit/fa4e0d3a918db0eab8e5c5be952f3bd649968456", + "version": "https://github.com/microsoft/TypeScript-TmLanguage/commit/59b62cbc624e3a01368e5432d85af0c99a27d49d", "name": "JavaScript (with React support)", "scopeName": "source.js", "patterns": [ @@ -128,8 +128,18 @@ "match": "(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(\\!)?(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.js entity.name.function.js" @@ -487,7 +497,7 @@ "patterns": [ { "name": "meta.var-single-variable.expr.js", - "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.js variable.other.constant.js entity.name.function.js" @@ -871,7 +881,7 @@ } }, { - "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.js" @@ -1111,7 +1121,7 @@ "include": "#comment" }, { - "match": "(?x)(\\#?[_$[:alpha:]][_$[:alnum:]]*)(?:(\\?)|(\\!))?(?=\\s*\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(\\#?[_$[:alpha:]][_$[:alnum:]]*)(?:(\\?)|(\\!))?(?=\\s*\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "meta.definition.property.js entity.name.function.js" @@ -1251,6 +1261,9 @@ { "include": "#return-type" }, + { + "include": "#type-function-return-type" + }, { "include": "#decl-block" }, @@ -1434,7 +1447,7 @@ }, { "name": "meta.arrow.js", - "begin": "(?x) (?:\n (? is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", + "begin": "(?x) (?:\n (? is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", "beginCaptures": { "1": { "name": "storage.modifier.async.js" @@ -2469,7 +2482,7 @@ }, { "name": "string.regexp.js", - "begin": "(?<=\\))\\s*\\/(?![\\/*])(?=(?:[^\\/\\\\\\[]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\])+\\/([gimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", + "begin": "(?<=\\))\\s*\\/(?![\\/*])(?=(?:[^\\/\\\\\\[]|\\\\.|\\[([^\\]\\\\]|\\\\.)*\\])+\\/([gimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", "beginCaptures": { "0": { "name": "punctuation.definition.string.begin.js" @@ -2628,7 +2641,7 @@ }, { "name": "meta.object.member.js", - "match": "(?x)(?:([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*:(\\s*\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/)*\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*:(\\s*\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/)*\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "0": { "name": "meta.object-literal.key.js" @@ -2719,7 +2732,7 @@ "end": "(?=,|\\})", "patterns": [ { - "begin": "(?<=:)\\s*(async)?(?=\\s*(<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)\\(\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", + "begin": "(?<=:)\\s*(async)?(?=\\s*(<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)\\(\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", "beginCaptures": { "1": { "name": "storage.modifier.async.js" @@ -2752,7 +2765,7 @@ ] }, { - "begin": "(?<=:)\\s*(async)?\\s*(\\()(?=\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", + "begin": "(?<=:)\\s*(async)?\\s*(\\()(?=\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", "beginCaptures": { "1": { "name": "storage.modifier.async.js" @@ -2788,7 +2801,7 @@ ] }, { - "begin": "(?<=\\>)\\s*(\\()(?=\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", + "begin": "(?<=\\>)\\s*(\\()(?=\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", "beginCaptures": { "1": { "name": "meta.brace.round.js" @@ -2976,7 +2989,7 @@ "paren-expression-possibly-arrow": { "patterns": [ { - "begin": "(?<=[(=,])\\s*(async)?(?=\\s*((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?\\(\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", + "begin": "(?<=[(=,])\\s*(async)?(?=\\s*((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?\\(\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", "beginCaptures": { "1": { "name": "storage.modifier.async.js" @@ -3060,7 +3073,7 @@ } }, { - "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.js" @@ -3628,7 +3641,7 @@ "include": "#object-identifiers" }, { - "match": "(?x)(?:(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", + "match": "(?x)(?:(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", "captures": { "1": { "name": "punctuation.accessor.js" @@ -3840,7 +3853,7 @@ ] }, "possibly-arrow-return-type": { - "begin": "(?<=\\)|^)\\s*(:)(?=\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*=>)", + "begin": "(?<=\\)|^)\\s*(:)(?=\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*=>)", "beginCaptures": { "1": { "name": "meta.arrow.js meta.return.type.arrow.js keyword.operator.type.annotation.js" @@ -4159,7 +4172,7 @@ }, "patterns": [ { - "match": "(?x)(?:(?)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))))", + "match": "(?x)(?:(?)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))))", "captures": { "1": { "name": "storage.modifier.js" @@ -4677,7 +4690,7 @@ }, { "name": "string.regexp.js", - "begin": "((?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(\\!)?(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.js.jsx entity.name.function.js.jsx" @@ -487,7 +497,7 @@ "patterns": [ { "name": "meta.var-single-variable.expr.js.jsx", - "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.js.jsx variable.other.constant.js.jsx entity.name.function.js.jsx" @@ -871,7 +881,7 @@ } }, { - "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.js.jsx" @@ -1111,7 +1121,7 @@ "include": "#comment" }, { - "match": "(?x)(\\#?[_$[:alpha:]][_$[:alnum:]]*)(?:(\\?)|(\\!))?(?=\\s*\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(\\#?[_$[:alpha:]][_$[:alnum:]]*)(?:(\\?)|(\\!))?(?=\\s*\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "meta.definition.property.js.jsx entity.name.function.js.jsx" @@ -1251,6 +1261,9 @@ { "include": "#return-type" }, + { + "include": "#type-function-return-type" + }, { "include": "#decl-block" }, @@ -1434,7 +1447,7 @@ }, { "name": "meta.arrow.js.jsx", - "begin": "(?x) (?:\n (? is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", + "begin": "(?x) (?:\n (? is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", "beginCaptures": { "1": { "name": "storage.modifier.async.js.jsx" @@ -2469,7 +2482,7 @@ }, { "name": "string.regexp.js.jsx", - "begin": "(?<=\\))\\s*\\/(?![\\/*])(?=(?:[^\\/\\\\\\[]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\])+\\/([gimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", + "begin": "(?<=\\))\\s*\\/(?![\\/*])(?=(?:[^\\/\\\\\\[]|\\\\.|\\[([^\\]\\\\]|\\\\.)*\\])+\\/([gimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", "beginCaptures": { "0": { "name": "punctuation.definition.string.begin.js.jsx" @@ -2628,7 +2641,7 @@ }, { "name": "meta.object.member.js.jsx", - "match": "(?x)(?:([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*:(\\s*\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/)*\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*:(\\s*\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/)*\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "0": { "name": "meta.object-literal.key.js.jsx" @@ -2719,7 +2732,7 @@ "end": "(?=,|\\})", "patterns": [ { - "begin": "(?<=:)\\s*(async)?(?=\\s*(<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)\\(\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", + "begin": "(?<=:)\\s*(async)?(?=\\s*(<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)\\(\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", "beginCaptures": { "1": { "name": "storage.modifier.async.js.jsx" @@ -2752,7 +2765,7 @@ ] }, { - "begin": "(?<=:)\\s*(async)?\\s*(\\()(?=\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", + "begin": "(?<=:)\\s*(async)?\\s*(\\()(?=\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", "beginCaptures": { "1": { "name": "storage.modifier.async.js.jsx" @@ -2788,7 +2801,7 @@ ] }, { - "begin": "(?<=\\>)\\s*(\\()(?=\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", + "begin": "(?<=\\>)\\s*(\\()(?=\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", "beginCaptures": { "1": { "name": "meta.brace.round.js.jsx" @@ -2976,7 +2989,7 @@ "paren-expression-possibly-arrow": { "patterns": [ { - "begin": "(?<=[(=,])\\s*(async)?(?=\\s*((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?\\(\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", + "begin": "(?<=[(=,])\\s*(async)?(?=\\s*((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?\\(\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", "beginCaptures": { "1": { "name": "storage.modifier.async.js.jsx" @@ -3060,7 +3073,7 @@ } }, { - "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.js.jsx" @@ -3628,7 +3641,7 @@ "include": "#object-identifiers" }, { - "match": "(?x)(?:(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", + "match": "(?x)(?:(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", "captures": { "1": { "name": "punctuation.accessor.js.jsx" @@ -3840,7 +3853,7 @@ ] }, "possibly-arrow-return-type": { - "begin": "(?<=\\)|^)\\s*(:)(?=\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*=>)", + "begin": "(?<=\\)|^)\\s*(:)(?=\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*=>)", "beginCaptures": { "1": { "name": "meta.arrow.js.jsx meta.return.type.arrow.js.jsx keyword.operator.type.annotation.js.jsx" @@ -4159,7 +4172,7 @@ }, "patterns": [ { - "match": "(?x)(?:(?)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))))", + "match": "(?x)(?:(?)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))))", "captures": { "1": { "name": "storage.modifier.js.jsx" @@ -4677,7 +4690,7 @@ }, { "name": "string.regexp.js.jsx", - "begin": "((? { - window.showInformationMessage(`${message}\n${localize('configureLimit', 'Use setting \'{0}\' to configure the limit.', SettingIds.maxItemsComputed)}`); + client.onNotification(ResultLimitReachedNotification.type, async message => { + const shouldPrompt = context.globalState.get(StorageIds.maxItemsExceededInformation) !== false; + if (shouldPrompt) { + const ok = localize('ok', "Ok"); + const openSettings = localize('goToSetting', 'Open Settings'); + const neverAgain = localize('yes never again', "Don't Show Again"); + const pick = await window.showInformationMessage(`${message}\n${localize('configureLimit', 'Use setting \'{0}\' to configure the limit.', SettingIds.maxItemsComputed)}`, ok, openSettings, neverAgain); + if (pick === neverAgain) { + await context.globalState.update(StorageIds.maxItemsExceededInformation, false); + } else if (pick === openSettings) { + await commands.executeCommand('workbench.action.openSettings', SettingIds.maxItemsComputed); + } + } }); function updateFormatterRegistration() { @@ -315,6 +330,8 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua range: client.code2ProtocolConverter.asRange(range), options: client.code2ProtocolConverter.asFormattingOptions(options) }; + params.options.insertFinalNewline = workspace.getConfiguration('files', document).get('insertFinalNewline'); + return client.sendRequest(DocumentRangeFormattingRequest.type, params, token).then( client.protocol2CodeConverter.asTextEdits, (error) => { diff --git a/extensions/json-language-features/client/src/node/jsonClientMain.ts b/extensions/json-language-features/client/src/node/jsonClientMain.ts index 6e9aed7f1..eba269138 100644 --- a/extensions/json-language-features/client/src/node/jsonClientMain.ts +++ b/extensions/json-language-features/client/src/node/jsonClientMain.ts @@ -25,7 +25,7 @@ export function activate(context: ExtensionContext) { const serverModule = context.asAbsolutePath(serverMain); // The debug options for the server - const debugOptions = { execArgv: ['--nolazy', '--inspect=6044'] }; + const debugOptions = { execArgv: ['--nolazy', '--inspect=' + (6000 + Math.round(Math.random() * 999))] }; // If the extension is launch in debug mode the debug server options are use // Otherwise the run options are used diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index db003f051..a8e3231a9 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -127,10 +127,10 @@ ] }, "dependencies": { - "request-light": "^0.3.0", + "request-light": "^0.4.0", "vscode-extension-telemetry": "0.1.1", "vscode-languageclient": "7.0.0-next.5.1", - "vscode-nls": "^4.1.2" + "vscode-nls": "^5.0.0" }, "devDependencies": { "@types/node": "^12.11.7" diff --git a/extensions/json-language-features/package.nls.json b/extensions/json-language-features/package.nls.json index 59729a0ee..9315f7f2f 100644 --- a/extensions/json-language-features/package.nls.json +++ b/extensions/json-language-features/package.nls.json @@ -13,5 +13,6 @@ "json.schemaResolutionErrorMessage": "Unable to resolve schema.", "json.clickToRetry": "Click to retry.", "json.maxItemsComputed.desc": "The maximum number of outline symbols and folding regions computed (limited for performance reasons).", + "json.maxItemsExceededInformation.desc": "Show notification when exceeding the maximum number of outline symbols and folding regions.", "json.enableSchemaDownload.desc": "When enabled, JSON schemas can be fetched from http and https locations." } diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index cb0dded17..86df32285 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -12,9 +12,9 @@ }, "main": "./out/node/jsonServerMain", "dependencies": { - "jsonc-parser": "^2.2.1", - "request-light": "^0.3.0", - "vscode-json-languageservice": "^3.9.1", + "jsonc-parser": "^3.0.0", + "request-light": "^0.4.0", + "vscode-json-languageservice": "^3.11.0", "vscode-languageserver": "7.0.0-next.3", "vscode-uri": "^2.1.2" }, diff --git a/extensions/json-language-features/server/src/jsonServer.ts b/extensions/json-language-features/server/src/jsonServer.ts index 26191bc4c..e7d2526f1 100644 --- a/extensions/json-language-features/server/src/jsonServer.ts +++ b/extensions/json-language-features/server/src/jsonServer.ts @@ -141,7 +141,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) colorProvider: {}, foldingRangeProvider: true, selectionRangeProvider: true, - definitionProvider: true + documentLinkProvider: {} }; return { capabilities }; @@ -481,15 +481,15 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }, [], `Error while computing selection ranges for ${params.textDocument.uri}`, token); }); - connection.onDefinition((params, token) => { + connection.onDocumentLinks((params, token) => { return runSafeAsync(async () => { const document = documents.get(params.textDocument.uri); if (document) { const jsonDocument = getJSONDocument(document); - return languageService.findDefinition(document, params.position, jsonDocument); + return languageService.findLinks(document, jsonDocument); } return []; - }, [], `Error while computing definitions for ${params.textDocument.uri}`, token); + }, [], `Error while computing links for ${params.textDocument.uri}`, token); }); // Listen on the connection diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index c438b03d5..ec82f87b5 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -61,36 +61,31 @@ https-proxy-agent@^2.2.4: agent-base "^4.3.0" debug "^3.1.0" -jsonc-parser@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" - integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== - -jsonc-parser@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.3.1.tgz#59549150b133f2efacca48fe9ce1ec0659af2342" - integrity sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg== +jsonc-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22" + integrity sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA== ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -request-light@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.3.0.tgz#04daa783e7f0a70392328dda4b546f3e27845f2d" - integrity sha512-xlVlZVT0ZvCT+c3zm3SjeFCzchoQxsUUmx5fkal0I6RIDJK+lmb1UYyKJ7WM4dTfnzHP4ElWwAf8Dli8c0/tVA== +request-light@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.4.0.tgz#c6b91ef00b18cb0de75d2127e55b3a2c9f7f90f9" + integrity sha512-fimzjIVw506FBZLspTAXHdpvgvQebyjpNyLRd0e6drPPRq7gcrROeGWRyF81wLqFg5ijPgnOQbmfck5wdTqpSA== dependencies: http-proxy-agent "^2.1.0" https-proxy-agent "^2.2.4" - vscode-nls "^4.1.1" + vscode-nls "^4.1.2" -vscode-json-languageservice@^3.9.1: - version "3.9.1" - resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.9.1.tgz#f72b581f8cd2bd9b47445ccf8b0ddcde6aba7483" - integrity sha512-oJkknkdCVitQ5XPSRa0weHjUxt8eSCptaL+MBQQlRsa6Nb8XnEY0S5wYnLUFHzEvKzwt01/LKk8LdOixWEXkNA== +vscode-json-languageservice@^3.11.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.11.0.tgz#ad574b36c4346bd7830f1d34b5a5213d3af8d232" + integrity sha512-QxI+qV97uD7HHOCjh3MrM1TfbdwmTXrMckri5Tus1/FQiG3baDZb2C9Y0y8QThs7PwHYBIQXcAc59ZveCRZKPA== dependencies: - jsonc-parser "^2.3.1" + jsonc-parser "^3.0.0" vscode-languageserver-textdocument "^1.0.1" vscode-languageserver-types "3.16.0-next.2" vscode-nls "^5.0.0" @@ -126,10 +121,10 @@ vscode-languageserver@7.0.0-next.3: dependencies: vscode-languageserver-protocol "3.16.0-next.4" -vscode-nls@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c" - integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A== +vscode-nls@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167" + integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw== vscode-nls@^5.0.0: version "5.0.0" diff --git a/extensions/json-language-features/yarn.lock b/extensions/json-language-features/yarn.lock index 29972b7df..a1e87bdbc 100644 --- a/extensions/json-language-features/yarn.lock +++ b/extensions/json-language-features/yarn.lock @@ -94,14 +94,14 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -request-light@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.3.0.tgz#04daa783e7f0a70392328dda4b546f3e27845f2d" - integrity sha512-xlVlZVT0ZvCT+c3zm3SjeFCzchoQxsUUmx5fkal0I6RIDJK+lmb1UYyKJ7WM4dTfnzHP4ElWwAf8Dli8c0/tVA== +request-light@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.4.0.tgz#c6b91ef00b18cb0de75d2127e55b3a2c9f7f90f9" + integrity sha512-fimzjIVw506FBZLspTAXHdpvgvQebyjpNyLRd0e6drPPRq7gcrROeGWRyF81wLqFg5ijPgnOQbmfck5wdTqpSA== dependencies: http-proxy-agent "^2.1.0" https-proxy-agent "^2.2.4" - vscode-nls "^4.1.1" + vscode-nls "^4.1.2" semver@^5.3.0: version "5.5.0" @@ -146,16 +146,16 @@ vscode-languageserver-types@3.16.0-next.2: resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0-next.2.tgz#940bd15c992295a65eae8ab6b8568a1e8daa3083" integrity sha512-QjXB7CKIfFzKbiCJC4OWC8xUncLsxo19FzGVp/ADFvvi87PlmBSCAtZI5xwGjF5qE0xkLf0jjKUn3DzmpDP52Q== -vscode-nls@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c" - integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A== - vscode-nls@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167" integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw== +vscode-nls@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" + integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== + zone.js@0.7.6: version "0.7.6" resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009" diff --git a/extensions/json/cgmanifest.json b/extensions/json/cgmanifest.json index 53db20003..1af8426e5 100644 --- a/extensions/json/cgmanifest.json +++ b/extensions/json/cgmanifest.json @@ -14,4 +14,4 @@ } ], "version": 1 -} +} \ No newline at end of file diff --git a/extensions/json/package.json b/extensions/json/package.json index 73902a8ec..1b7331b20 100644 --- a/extensions/json/package.json +++ b/extensions/json/package.json @@ -33,8 +33,7 @@ ], "filenames": [ "composer.lock", - ".watchmanconfig", - ".ember-cli" + ".watchmanconfig" ], "mimetypes": [ "application/json", @@ -57,6 +56,9 @@ ".hintrc", ".babelrc" ], + "filenames": [ + ".ember-cli" + ], "configuration": "./language-configuration.json" } ], diff --git a/extensions/json/syntaxes/JSON.tmLanguage.json b/extensions/json/syntaxes/JSON.tmLanguage.json index 9454f0ed8..b53febdc8 100644 --- a/extensions/json/syntaxes/JSON.tmLanguage.json +++ b/extensions/json/syntaxes/JSON.tmLanguage.json @@ -210,4 +210,4 @@ ] } } -} +} \ No newline at end of file diff --git a/extensions/json/syntaxes/JSONC.tmLanguage.json b/extensions/json/syntaxes/JSONC.tmLanguage.json index bf65fce98..31828ba65 100644 --- a/extensions/json/syntaxes/JSONC.tmLanguage.json +++ b/extensions/json/syntaxes/JSONC.tmLanguage.json @@ -210,4 +210,4 @@ ] } } -} +} \ No newline at end of file diff --git a/extensions/make/language-configuration.json b/extensions/make/language-configuration.json index 802890392..82645b84d 100644 --- a/extensions/make/language-configuration.json +++ b/extensions/make/language-configuration.json @@ -3,8 +3,47 @@ "lineComment": "#" }, "brackets": [ - ["{", "}"], - ["[", "]"], - ["(", ")"] + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ] + ], + "autoClosingPairs": [ + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + { + "open": "'", + "close": "'", + "notIn": [ + "string", + "comment" + ] + }, + { + "open": "\"", + "close": "\"", + "notIn": [ + "string", + "comment" + ] + } ] -} \ No newline at end of file +} diff --git a/extensions/markdown-basics/cgmanifest.json b/extensions/markdown-basics/cgmanifest.json index 71df78ef4..5606dcc7c 100644 --- a/extensions/markdown-basics/cgmanifest.json +++ b/extensions/markdown-basics/cgmanifest.json @@ -26,7 +26,19 @@ ], "license": "TextMate Bundle License", "version": "0.0.0" + }, + { + "component": { + "type": "git", + "git": { + "name": "microsoft/vscode-markdown-tm-grammar", + "repositoryUrl": "https://github.com/microsoft/vscode-markdown-tm-grammar", + "commitHash": "11cf764606cb2cde54badb5d0e5a0758a8871c4b" + } + }, + "license": "MIT", + "version": "0.0.0" } ], "version": 1 -} \ No newline at end of file +} diff --git a/extensions/markdown-basics/snippets/markdown.code-snippets b/extensions/markdown-basics/snippets/markdown.code-snippets index b07f98413..f0753507b 100644 --- a/extensions/markdown-basics/snippets/markdown.code-snippets +++ b/extensions/markdown-basics/snippets/markdown.code-snippets @@ -64,6 +64,11 @@ "body": ["1. ${1:first}", "2. ${2:second}", "3. ${3:third}", "$0"], "description": "Insert ordered list" }, + "Insert definition list": { + "prefix": "definition list", + "body": ["${1:term}", ": ${2:definition}", "$0"], + "description": "Insert definition list" + }, "Insert horizontal rule": { "prefix": "horizontal rule", "body": "----------\n", @@ -83,5 +88,5 @@ "prefix": "strikethrough", "body": "~~${1:${TM_SELECTED_TEXT}}~~", "description": "Insert strikethrough" - } + }, } diff --git a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json index e8feaa75f..13ee6f360 100644 --- a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json +++ b/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/4be9cb335581f3559166c319607dac9100103083", + "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/f051a36bd9713dd722cbe1bdde9c8240d12f00b4", "name": "Markdown", "scopeName": "text.html.markdown", "patterns": [ @@ -2190,9 +2190,18 @@ }, "13": { "name": "punctuation.definition.string.end.markdown" + }, + "14": { + "name": "string.other.link.description.title.markdown" + }, + "15": { + "name": "punctuation.definition.string.begin.markdown" + }, + "16": { + "name": "punctuation.definition.string.end.markdown" } }, - "match": "(?x)\n \\s* # Leading whitespace\n (\\[)([^]]+?)(\\])(:) # Reference name\n [ \\t]* # Optional whitespace\n (?) # The url\n [ \\t]* # Optional whitespace\n (?:\n ((\\().+?(\\))) # Match title in quotes…\n | ((\").+?(\")) # or in parens.\n )? # Title is optional\n \\s* # Optional whitespace\n $\n", + "match": "(?x)\n \\s* # Leading whitespace\n (\\[)([^]]+?)(\\])(:) # Reference name\n [ \\t]* # Optional whitespace\n (?) # The url\n [ \\t]* # Optional whitespace\n (?:\n ((\\().+?(\\))) # Match title in parens…\n | ((\").+?(\")) # or in double quotes…\n | ((').+?(')) # or in single quotes.\n )? # Title is optional\n \\s* # Optional whitespace\n $\n", "name": "meta.link.reference.def.markdown" }, "list_paragraph": { @@ -2453,10 +2462,19 @@ "name": "punctuation.definition.string.markdown" }, "15": { + "name": "string.other.link.description.title.markdown" + }, + "16": { + "name": "punctuation.definition.string.markdown" + }, + "17": { + "name": "punctuation.definition.string.markdown" + }, + "18": { "name": "punctuation.definition.metadata.markdown" } }, - "match": "(?x)\n (\\!\\[)((?[^\\[\\]\\\\]|\\\\.|\\[\\g*+\\])*+)(\\])\n # Match the link text.\n (\\() # Opening paren for url\n (?) # The url\n [ \\t]* # Optional whitespace\n (?:\n ((\\().+?(\\))) # Match title in parens…\n | ((\").+?(\")) # or in quotes.\n )? # Title is optional\n \\s* # Optional whitespace\n (\\))\n", + "match": "(?x)\n (\\!\\[)((?[^\\[\\]\\\\]|\\\\.|\\[\\g*+\\])*+)(\\])\n # Match the link text.\n (\\() # Opening paren for url\n (?) # The url\n [ \\t]* # Optional whitespace\n (?:\n ((\\().+?(\\))) # Match title in parens…\n | ((\").+?(\")) # or in double quotes…\n | ((').+?(')) # or in single quotes.\n )? # Title is optional\n \\s* # Optional whitespace\n (\\))\n", "name": "meta.image.inline.markdown" }, "image-ref": { @@ -2616,10 +2634,19 @@ "name": "punctuation.definition.string.end.markdown" }, "16": { + "name": "string.other.link.description.title.markdown" + }, + "17": { + "name": "punctuation.definition.string.begin.markdown" + }, + "18": { + "name": "punctuation.definition.string.end.markdown" + }, + "19": { "name": "punctuation.definition.metadata.markdown" } }, - "match": "(?x)\n (\\[)((?[^\\[\\]\\\\]|\\\\.|\\[\\g*+\\])*+)(\\])\n # Match the link text.\n (\\() # Opening paren for url\n ((?>[^\\s()]+)|\\(\\g*\\))*)(>?) # The url\n [ \\t]* # Optional whitespace\n (?:\n ((\\().+?(\\))) # Match title in parens…\n | ((\").+?(\")) # or in quotes.\n )? # Title is optional\n \\s* # Optional whitespace\n (\\))\n", + "match": "(?x)\n (\\[)((?[^\\[\\]\\\\]|\\\\.|\\[\\g*+\\])*+)(\\])\n # Match the link text.\n (\\() # Opening paren for url\n ((?>[^\\s()]+)|\\(\\g*\\))*)(>?) # The url\n [ \\t]* # Optional whitespace\n (?:\n ((\\().+?(\\))) # Match title in parens…\n | ((\").+?(\")) # or in double quotes…\n | ((').+?(')) # or in single quotes.\n )? # Title is optional\n \\s* # Optional whitespace\n (\\))\n", "name": "meta.link.inline.markdown" }, "link-ref": { diff --git a/extensions/markdown-language-features/media/markdown.css b/extensions/markdown-language-features/media/markdown.css index f581cd002..edaa6f925 100644 --- a/extensions/markdown-language-features/media/markdown.css +++ b/extensions/markdown-language-features/media/markdown.css @@ -154,15 +154,13 @@ table { border-collapse: collapse; } -table > thead > tr > th { +th { text-align: left; border-bottom: 1px solid; } -table > thead > tr > th, -table > thead > tr > td, -table > tbody > tr > th, -table > tbody > tr > td { +th, +td { padding: 5px 10px; } @@ -217,22 +215,22 @@ pre code { border-color: rgb(0, 0, 0); } -.vscode-light table > thead > tr > th { +.vscode-light th { border-color: rgba(0, 0, 0, 0.69); } -.vscode-dark table > thead > tr > th { +.vscode-dark th { border-color: rgba(255, 255, 255, 0.69); } .vscode-light h1, .vscode-light hr, -.vscode-light table > tbody > tr + tr > td { +.vscode-light td { border-color: rgba(0, 0, 0, 0.18); } .vscode-dark h1, .vscode-dark hr, -.vscode-dark table > tbody > tr + tr > td { +.vscode-dark td { border-color: rgba(255, 255, 255, 0.18); } diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 34d6902fa..72b952e82 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -324,7 +324,7 @@ "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" }, "dependencies": { - "highlight.js": "9.15.10", + "highlight.js": "10.1.2", "markdown-it": "^10.0.0", "markdown-it-front-matter": "^0.2.1", "vscode-extension-telemetry": "0.1.1", diff --git a/extensions/markdown-language-features/src/commands/openDocumentLink.ts b/extensions/markdown-language-features/src/commands/openDocumentLink.ts index c17dc17cb..79288f2d1 100644 --- a/extensions/markdown-language-features/src/commands/openDocumentLink.ts +++ b/extensions/markdown-language-features/src/commands/openDocumentLink.ts @@ -12,10 +12,18 @@ import { TableOfContentsProvider } from '../tableOfContentsProvider'; import { isMarkdownFile } from '../util/file'; +type UriComponents = { + readonly scheme?: string; + readonly path: string; + readonly fragment?: string; + readonly authority?: string; + readonly query?: string; +}; + export interface OpenDocumentLinkArgs { - readonly path: {}; + readonly parts: UriComponents; readonly fragment: string; - readonly fromResource: {}; + readonly fromResource: UriComponents; } enum OpenMarkdownLinks { @@ -32,7 +40,7 @@ export class OpenDocumentLinkCommand implements Command { path: vscode.Uri, fragment: string, ): vscode.Uri { - const toJson = (uri: vscode.Uri) => { + const toJson = (uri: vscode.Uri): UriComponents => { return { scheme: uri.scheme, authority: uri.authority, @@ -42,7 +50,7 @@ export class OpenDocumentLinkCommand implements Command { }; }; return vscode.Uri.parse(`command:${OpenDocumentLinkCommand.id}?${encodeURIComponent(JSON.stringify({ - path: toJson(path), + parts: toJson(path), fragment, fromResource: toJson(fromResource), }))}`); @@ -56,36 +64,58 @@ export class OpenDocumentLinkCommand implements Command { return OpenDocumentLinkCommand.execute(this.engine, args); } - public static async execute(engine: MarkdownEngine, args: OpenDocumentLinkArgs) { + public static async execute(engine: MarkdownEngine, args: OpenDocumentLinkArgs): Promise { const fromResource = vscode.Uri.parse('').with(args.fromResource); - const targetResource = vscode.Uri.parse('').with(args.path); + + const targetResource = reviveUri(args.parts); + const column = this.getViewColumn(fromResource); - try { - return await this.tryOpen(engine, targetResource, args, column); - } catch { - if (extname(targetResource.path) === '') { - return this.tryOpen(engine, targetResource.with({ path: targetResource.path + '.md' }), args, column); - } - await vscode.commands.executeCommand('vscode.open', targetResource, column); - return undefined; + + const didOpen = await this.tryOpen(engine, targetResource, args, column); + if (didOpen) { + return; + } + + if (extname(targetResource.path) === '') { + await this.tryOpen(engine, targetResource.with({ path: targetResource.path + '.md' }), args, column); + return; } } - private static async tryOpen(engine: MarkdownEngine, resource: vscode.Uri, args: OpenDocumentLinkArgs, column: vscode.ViewColumn) { - if (vscode.window.activeTextEditor && isMarkdownFile(vscode.window.activeTextEditor.document)) { - if (vscode.window.activeTextEditor.document.uri.fsPath === resource.fsPath) { - return this.tryRevealLine(engine, vscode.window.activeTextEditor, args.fragment); + private static async tryOpen(engine: MarkdownEngine, resource: vscode.Uri, args: OpenDocumentLinkArgs, column: vscode.ViewColumn): Promise { + const tryUpdateForActiveFile = async (): Promise => { + if (vscode.window.activeTextEditor && isMarkdownFile(vscode.window.activeTextEditor.document)) { + if (vscode.window.activeTextEditor.document.uri.fsPath === resource.fsPath) { + await this.tryRevealLine(engine, vscode.window.activeTextEditor, args.fragment); + return true; + } } + return false; + }; + + if (await tryUpdateForActiveFile()) { + return true; + } + + let stat: vscode.FileStat; + try { + stat = await vscode.workspace.fs.stat(resource); + } catch { + return false; } - const stat = await vscode.workspace.fs.stat(resource); if (stat.type === vscode.FileType.Directory) { - return vscode.commands.executeCommand('revealInExplorer', resource); + await vscode.commands.executeCommand('revealInExplorer', resource); + return true; } - return vscode.workspace.openTextDocument(resource) - .then(document => vscode.window.showTextDocument(document, column)) - .then(editor => this.tryRevealLine(engine, editor, args.fragment)); + try { + await vscode.commands.executeCommand('vscode.open', resource, column); + } catch { + return false; + } + + return tryUpdateForActiveFile(); } private static getViewColumn(resource: vscode.Uri): vscode.ViewColumn { @@ -101,7 +131,7 @@ export class OpenDocumentLinkCommand implements Command { } private static async tryRevealLine(engine: MarkdownEngine, editor: vscode.TextEditor, fragment?: string) { - if (editor && fragment) { + if (fragment) { const toc = new TableOfContentsProvider(engine, editor.document); const entry = await toc.lookup(fragment); if (entry) { @@ -122,6 +152,12 @@ export class OpenDocumentLinkCommand implements Command { } } +function reviveUri(parts: any) { + if (parts.scheme === 'file') { + return vscode.Uri.file(parts.path); + } + return vscode.Uri.parse('').with(parts); +} export async function resolveLinkToMarkdownFile(path: string): Promise { try { diff --git a/extensions/markdown-language-features/src/features/documentLinkProvider.ts b/extensions/markdown-language-features/src/features/documentLinkProvider.ts index 6d85e6135..becb16879 100644 --- a/extensions/markdown-language-features/src/features/documentLinkProvider.ts +++ b/extensions/markdown-language-features/src/features/documentLinkProvider.ts @@ -65,19 +65,6 @@ function getWorkspaceFolder(document: vscode.TextDocument) { || vscode.workspace.workspaceFolders?.[0]?.uri; } -function matchAll( - pattern: RegExp, - text: string -): Array { - const out: RegExpMatchArray[] = []; - pattern.lastIndex = 0; - let match: RegExpMatchArray | null; - while ((match = pattern.exec(text))) { - out.push(match); - } - return out; -} - function extractDocumentLink( document: vscode.TextDocument, pre: number, @@ -103,7 +90,7 @@ function extractDocumentLink( } export default class LinkProvider implements vscode.DocumentLinkProvider { - private readonly linkPattern = /(\[((!\[[^\]]*?\]\(\s*)([^\s\(\)]+?)\s*\)\]|(?:\\\]|[^\]])*\])\(\s*)(([^\s\(\)]|\(\S*?\))+)\s*(".*?")?\)/g; + private readonly linkPattern = /(\[((!\[[^\]]*?\]\(\s*)([^\s\(\)]+?)\s*\)\]|(?:\\\]|[^\]])*\])\(\s*)(([^\s\(\)]|\([^\s\(\)]*?\))+)\s*(".*?")?\)/g; private readonly referenceLinkPattern = /(\[((?:\\\]|[^\]])+)\]\[\s*?)([^\s\]]*?)\]/g; private readonly definitionPattern = /^([\t ]*\[(?!\^)((?:\\\]|[^\]])+)\]:\s*)(\S+)/gm; @@ -124,7 +111,7 @@ export default class LinkProvider implements vscode.DocumentLinkProvider { document: vscode.TextDocument, ): vscode.DocumentLink[] { const results: vscode.DocumentLink[] = []; - for (const match of matchAll(this.linkPattern, text)) { + for (const match of text.matchAll(this.linkPattern)) { const matchImage = match[4] && extractDocumentLink(document, match[3].length + 1, match[4], match.index); if (matchImage) { results.push(matchImage); @@ -144,7 +131,7 @@ export default class LinkProvider implements vscode.DocumentLinkProvider { const results: vscode.DocumentLink[] = []; const definitions = this.getDefinitions(text, document); - for (const match of matchAll(this.referenceLinkPattern, text)) { + for (const match of text.matchAll(this.referenceLinkPattern)) { let linkStart: vscode.Position; let linkEnd: vscode.Position; let reference = match[3]; @@ -190,7 +177,7 @@ export default class LinkProvider implements vscode.DocumentLinkProvider { private getDefinitions(text: string, document: vscode.TextDocument) { const out = new Map(); - for (const match of matchAll(this.definitionPattern, text)) { + for (const match of text.matchAll(this.definitionPattern)) { const pre = match[1]; const reference = match[2]; const link = match[3].trim(); diff --git a/extensions/markdown-language-features/src/features/foldingProvider.ts b/extensions/markdown-language-features/src/features/foldingProvider.ts index 553c1a325..62c245c9a 100644 --- a/extensions/markdown-language-features/src/features/foldingProvider.ts +++ b/extensions/markdown-language-features/src/features/foldingProvider.ts @@ -7,7 +7,6 @@ import { Token } from 'markdown-it'; import * as vscode from 'vscode'; import { MarkdownEngine } from '../markdownEngine'; import { TableOfContentsProvider } from '../tableOfContentsProvider'; -import { flatten } from '../util/arrays'; const rangeLimit = 5000; @@ -27,7 +26,7 @@ export default class MarkdownFoldingProvider implements vscode.FoldingRangeProvi this.getHeaderFoldingRanges(document), this.getBlockFoldingRanges(document) ]); - return flatten(foldables).slice(0, rangeLimit); + return foldables.flat().slice(0, rangeLimit); } private async getRegions(document: vscode.TextDocument): Promise { diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts index f2fb80034..9244546c5 100644 --- a/extensions/markdown-language-features/src/features/preview.ts +++ b/extensions/markdown-language-features/src/features/preview.ts @@ -408,7 +408,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { } } - OpenDocumentLinkCommand.execute(this.engine, { path: hrefPath, fragment, fromResource: this.resource.toJSON() }); + OpenDocumentLinkCommand.execute(this.engine, { parts: { path: hrefPath }, fragment, fromResource: this.resource.toJSON() }); } //#region WebviewResourceProvider diff --git a/extensions/markdown-language-features/src/features/smartSelect.ts b/extensions/markdown-language-features/src/features/smartSelect.ts index 0be50d0ca..12b2bdd78 100644 --- a/extensions/markdown-language-features/src/features/smartSelect.ts +++ b/extensions/markdown-language-features/src/features/smartSelect.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { Token } from 'markdown-it'; import * as vscode from 'vscode'; import { MarkdownEngine } from '../markdownEngine'; @@ -15,7 +14,7 @@ export default class MarkdownSmartSelect implements vscode.SelectionRangeProvide ) { } public async provideSelectionRanges(document: vscode.TextDocument, positions: vscode.Position[], _token: vscode.CancellationToken): Promise { - let promises = await Promise.all(positions.map((position) => { + const promises = await Promise.all(positions.map((position) => { return this.provideSelectionRange(document, position, _token); })); return promises.filter(item => item !== undefined) as vscode.SelectionRange[]; @@ -24,54 +23,206 @@ export default class MarkdownSmartSelect implements vscode.SelectionRangeProvide private async provideSelectionRange(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken): Promise { const headerRange = await this.getHeaderSelectionRange(document, position); const blockRange = await this.getBlockSelectionRange(document, position, headerRange); - return blockRange ? blockRange : headerRange ? headerRange : undefined; + const inlineRange = await this.getInlineSelectionRange(document, position, blockRange); + return inlineRange || blockRange || headerRange; + } + private async getInlineSelectionRange(document: vscode.TextDocument, position: vscode.Position, blockRange?: vscode.SelectionRange): Promise { + return createInlineRange(document, position, blockRange); } private async getBlockSelectionRange(document: vscode.TextDocument, position: vscode.Position, headerRange?: vscode.SelectionRange): Promise { const tokens = await this.engine.parse(document); - let blockTokens = getTokensForPosition(tokens, position); + const blockTokens = getBlockTokensForPosition(tokens, position, headerRange); if (blockTokens.length === 0) { return undefined; } - let parentRange = headerRange ? headerRange : createBlockRange(document, position.line, blockTokens.shift()); - let currentRange: vscode.SelectionRange | undefined; + let currentRange: vscode.SelectionRange | undefined = headerRange ? headerRange : createBlockRange(blockTokens.shift()!, document, position.line); - for (const token of blockTokens) { - currentRange = createBlockRange(document, position.line, token, parentRange); - if (currentRange) { - parentRange = currentRange; - } - } - if (currentRange) { - return currentRange; - } else { - return parentRange; - } - } - - private async getHeaderSelectionRange(document: vscode.TextDocument, position: vscode.Position): Promise { - const tocProvider = new TableOfContentsProvider(this.engine, document); - const toc = await tocProvider.getToc(); - - let headerInfo = getHeadersForPosition(toc, position); - - let headers = headerInfo.headers; - - let parentRange: vscode.SelectionRange | undefined; - let currentRange: vscode.SelectionRange | undefined; - - for (let i = 0; i < headers.length; i++) { - currentRange = createHeaderRange(i === headers.length - 1, headerInfo.headerOnThisLine, headers[i], parentRange, getFirstChildHeader(document, headers[i], toc)); - if (currentRange && currentRange.parent) { - parentRange = currentRange; - } + for (let i = 0; i < blockTokens.length; i++) { + currentRange = createBlockRange(blockTokens[i], document, position.line, currentRange); } return currentRange; } + + private async getHeaderSelectionRange(document: vscode.TextDocument, position: vscode.Position): Promise { + + const tocProvider = new TableOfContentsProvider(this.engine, document); + const toc = await tocProvider.getToc(); + + const headerInfo = getHeadersForPosition(toc, position); + + const headers = headerInfo.headers; + + let currentRange: vscode.SelectionRange | undefined; + + for (let i = 0; i < headers.length; i++) { + currentRange = createHeaderRange(headers[i], i === headers.length - 1, headerInfo.headerOnThisLine, currentRange, getFirstChildHeader(document, headers[i], toc)); + } + return currentRange; + } +} + +function getHeadersForPosition(toc: TocEntry[], position: vscode.Position): { headers: TocEntry[], headerOnThisLine: boolean } { + const enclosingHeaders = toc.filter(header => header.location.range.start.line <= position.line && header.location.range.end.line >= position.line); + const sortedHeaders = enclosingHeaders.sort((header1, header2) => (header1.line - position.line) - (header2.line - position.line)); + const onThisLine = toc.find(header => header.line === position.line) !== undefined; + return { + headers: sortedHeaders, + headerOnThisLine: onThisLine + }; +} + +function createHeaderRange(header: TocEntry, isClosestHeaderToPosition: boolean, onHeaderLine: boolean, parent?: vscode.SelectionRange, startOfChildRange?: vscode.Position): vscode.SelectionRange | undefined { + const range = header.location.range; + const contentRange = new vscode.Range(range.start.translate(1), range.end); + if (onHeaderLine && isClosestHeaderToPosition && startOfChildRange) { + // selection was made on this header line, so select header and its content until the start of its first child + // then all of its content + return new vscode.SelectionRange(range.with(undefined, startOfChildRange), new vscode.SelectionRange(range, parent)); + } else if (onHeaderLine && isClosestHeaderToPosition) { + // selection was made on this header line and no children so expand to all of its content + return new vscode.SelectionRange(range, parent); + } else if (isClosestHeaderToPosition && startOfChildRange) { + // selection was made within content and has child so select content + // of this header then all content then header + return new vscode.SelectionRange(contentRange.with(undefined, startOfChildRange), new vscode.SelectionRange(contentRange, (new vscode.SelectionRange(range, parent)))); + } else { + // not on this header line so select content then header + return new vscode.SelectionRange(contentRange, new vscode.SelectionRange(range, parent)); + } +} + +function getBlockTokensForPosition(tokens: Token[], position: vscode.Position, parent?: vscode.SelectionRange): Token[] { + const enclosingTokens = tokens.filter(token => token.map && (token.map[0] <= position.line && token.map[1] > position.line) && (!parent || (token.map[0] >= parent.range.start.line && token.map[1] <= parent.range.end.line + 1)) && isBlockElement(token)); + if (enclosingTokens.length === 0) { + return []; + } + const sortedTokens = enclosingTokens.sort((token1, token2) => (token2.map[1] - token2.map[0]) - (token1.map[1] - token1.map[0])); + return sortedTokens; +} + +function createBlockRange(block: Token, document: vscode.TextDocument, cursorLine: number, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined { + if (block.type === 'fence') { + return createFencedRange(block, cursorLine, document, parent); + } else { + let startLine = document.lineAt(block.map[0]).isEmptyOrWhitespace ? block.map[0] + 1 : block.map[0]; + let endLine = startLine === block.map[1] ? block.map[1] : block.map[1] - 1; + if (block.type === 'paragraph_open' && block.map[1] - block.map[0] === 2) { + startLine = endLine = cursorLine; + } else if (isList(block) && document.lineAt(endLine).isEmptyOrWhitespace) { + endLine = endLine - 1; + } + const range = new vscode.Range(startLine, 0, endLine, document.lineAt(endLine).text?.length ?? 0); + if (parent?.range.contains(range) && !parent.range.isEqual(range)) { + return new vscode.SelectionRange(range, parent); + } else if (parent?.range.isEqual(range)) { + return parent; + } else { + return new vscode.SelectionRange(range); + } + } +} + +function createInlineRange(document: vscode.TextDocument, cursorPosition: vscode.Position, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined { + const lineText = document.lineAt(cursorPosition.line).text; + const boldSelection = createBoldRange(lineText, cursorPosition.character, cursorPosition.line, parent); + const italicSelection = createOtherInlineRange(lineText, cursorPosition.character, cursorPosition.line, true, parent); + let comboSelection: vscode.SelectionRange | undefined; + if (boldSelection && italicSelection && !boldSelection.range.isEqual(italicSelection.range)) { + if (boldSelection.range.contains(italicSelection.range)) { + comboSelection = createOtherInlineRange(lineText, cursorPosition.character, cursorPosition.line, true, boldSelection); + } else if (italicSelection.range.contains(boldSelection.range)) { + comboSelection = createBoldRange(lineText, cursorPosition.character, cursorPosition.line, italicSelection); + } + } + const linkSelection = createLinkRange(lineText, cursorPosition.character, cursorPosition.line, comboSelection || boldSelection || italicSelection || parent); + const inlineCodeBlockSelection = createOtherInlineRange(lineText, cursorPosition.character, cursorPosition.line, false, linkSelection || parent); + return inlineCodeBlockSelection || linkSelection || comboSelection || boldSelection || italicSelection; +} + +function createFencedRange(token: Token, cursorLine: number, document: vscode.TextDocument, parent?: vscode.SelectionRange): vscode.SelectionRange { + const startLine = token.map[0]; + const endLine = token.map[1] - 1; + const onFenceLine = cursorLine === startLine || cursorLine === endLine; + const fenceRange = new vscode.Range(startLine, 0, endLine, document.lineAt(endLine).text.length); + const contentRange = endLine - startLine > 2 && !onFenceLine ? new vscode.Range(startLine + 1, 0, endLine - 1, document.lineAt(endLine - 1).text.length) : undefined; + if (contentRange) { + return new vscode.SelectionRange(contentRange, new vscode.SelectionRange(fenceRange, parent)); + } else { + if (parent?.range.isEqual(fenceRange)) { + return parent; + } else { + return new vscode.SelectionRange(fenceRange, parent); + } + } +} + +function createBoldRange(lineText: string, cursorChar: number, cursorLine: number, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined { + const regex = /(?:^|(?<=\s))(?:\*\*\s*([^*]+)(?:\*\s*([^*]+)\s*?\*)*([^*]+)\s*?\*\*)/g; + const matches = [...lineText.matchAll(regex)].filter(match => lineText.indexOf(match[0]) <= cursorChar && lineText.indexOf(match[0]) + match[0].length >= cursorChar); + if (matches.length > 0) { + // should only be one match, so select first and index 0 contains the entire match + const bold = matches[0][0]; + const startIndex = lineText.indexOf(bold); + const cursorOnStars = cursorChar === startIndex || cursorChar === startIndex + 1 || cursorChar === startIndex + bold.length || cursorChar === startIndex + bold.length - 1; + const contentAndStars = new vscode.SelectionRange(new vscode.Range(cursorLine, startIndex, cursorLine, startIndex + bold.length), parent); + const content = new vscode.SelectionRange(new vscode.Range(cursorLine, startIndex + 2, cursorLine, startIndex + bold.length - 2), contentAndStars); + return cursorOnStars ? contentAndStars : content; + } + return undefined; +} + +function createOtherInlineRange(lineText: string, cursorChar: number, cursorLine: number, isItalic: boolean, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined { + const regex = isItalic ? /(?:^|(?<=\s))(?:\*\s*([^*]+)(?:\*\*\s*([^*]+)\s*?\*\*)*([^*]+)\s*?\*)/g : /\`[^\`]*\`/g; + const matches = [...lineText.matchAll(regex)].filter(match => lineText.indexOf(match[0]) <= cursorChar && lineText.indexOf(match[0]) + match[0].length >= cursorChar); + if (matches.length > 0) { + // should only be one match, so select first and index 0 contains the entire match + const match = matches[0][0]; + const startIndex = lineText.indexOf(match); + const cursorOnType = cursorChar === startIndex || cursorChar === startIndex + match.length; + const contentAndType = new vscode.SelectionRange(new vscode.Range(cursorLine, startIndex, cursorLine, startIndex + match.length), parent); + const content = new vscode.SelectionRange(new vscode.Range(cursorLine, startIndex + 1, cursorLine, startIndex + match.length - 1), contentAndType); + return cursorOnType ? contentAndType : content; + } + return undefined; +} + +function createLinkRange(lineText: string, cursorChar: number, cursorLine: number, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined { + const regex = /(\[[^\(\)]*\])(\([^\[\]]*\))/g; + const matches = [...lineText.matchAll(regex)].filter(match => lineText.indexOf(match[0]) <= cursorChar && lineText.indexOf(match[0]) + match[0].length > cursorChar); + + if (matches.length > 0) { + // should only be one match, so select first and index 0 contains the entire match, so match = [text](url) + const link = matches[0][0]; + const linkRange = new vscode.SelectionRange(new vscode.Range(cursorLine, lineText.indexOf(link), cursorLine, lineText.indexOf(link) + link.length), parent); + + const linkText = matches[0][1]; + const url = matches[0][2]; + + // determine if cursor is within [text] or (url) in order to know which should be selected + const nearestType = cursorChar >= lineText.indexOf(linkText) && cursorChar < lineText.indexOf(linkText) + linkText.length ? linkText : url; + + const indexOfType = lineText.indexOf(nearestType); + // determine if cursor is on a bracket or paren and if so, return the [content] or (content), skipping over the content range + const cursorOnType = cursorChar === indexOfType || cursorChar === indexOfType + nearestType.length; + + const contentAndNearestType = new vscode.SelectionRange(new vscode.Range(cursorLine, indexOfType, cursorLine, indexOfType + nearestType.length), linkRange); + const content = new vscode.SelectionRange(new vscode.Range(cursorLine, indexOfType + 1, cursorLine, indexOfType + nearestType.length - 1), contentAndNearestType); + return cursorOnType ? contentAndNearestType : content; + } + return undefined; +} + +function isList(token: Token): boolean { + return token.type ? ['ordered_list_open', 'list_item_open', 'bullet_list_open'].includes(token.type) : false; +} + +function isBlockElement(token: Token): boolean { + return !['list_item_close', 'paragraph_close', 'bullet_list_close', 'inline', 'heading_close', 'heading_open'].includes(token.type); } function getFirstChildHeader(document: vscode.TextDocument, header?: TocEntry, toc?: TocEntry[]): vscode.Position | undefined { @@ -80,159 +231,9 @@ function getFirstChildHeader(document: vscode.TextDocument, header?: TocEntry, t let children = toc.filter(t => header.location.range.contains(t.location.range) && t.location.range.start.line > header.location.range.start.line).sort((t1, t2) => t1.line - t2.line); if (children.length > 0) { childRange = children[0].location.range.start; - let lineText = document.lineAt(childRange.line - 1).text; + const lineText = document.lineAt(childRange.line - 1).text; return childRange ? childRange.translate(-1, lineText.length) : undefined; } } return undefined; } - -function getTokensForPosition(tokens: Token[], position: vscode.Position): Token[] { - let enclosingTokens = tokens.filter(token => token.map && (token.map[0] <= position.line && token.map[1] > position.line) && isBlockElement(token)); - - if (enclosingTokens.length === 0) { - return []; - } - - let sortedTokens = enclosingTokens.sort((token1, token2) => (token2.map[1] - token2.map[0]) - (token1.map[1] - token1.map[0])); - return sortedTokens; -} - -function getHeadersForPosition(toc: TocEntry[], position: vscode.Position): { headers: TocEntry[], headerOnThisLine: boolean } { - let enclosingHeaders = toc.filter(header => header.location.range.start.line <= position.line && header.location.range.end.line >= position.line); - let sortedHeaders = enclosingHeaders.sort((header1, header2) => (header1.line - position.line) - (header2.line - position.line)); - let onThisLine = toc.find(header => header.line === position.line) !== undefined; - return { - headers: sortedHeaders, - headerOnThisLine: onThisLine - }; -} - -function isBlockElement(token: Token): boolean { - return !['list_item_close', 'paragraph_close', 'bullet_list_close', 'inline', 'heading_close', 'heading_open'].includes(token.type); -} - -function createHeaderRange(isClosestHeaderToPosition: boolean, onHeaderLine: boolean, header?: TocEntry, parent?: vscode.SelectionRange, childStart?: vscode.Position): vscode.SelectionRange | undefined { - if (header) { - let contentRange = new vscode.Range(header.location.range.start.translate(1), header.location.range.end); - let headerPlusContentRange = header.location.range; - let partialContentRange = childStart && isClosestHeaderToPosition ? contentRange.with(undefined, childStart) : undefined; - if (onHeaderLine && isClosestHeaderToPosition && childStart) { - return new vscode.SelectionRange(header.location.range.with(undefined, childStart), new vscode.SelectionRange(header.location.range, parent)); - } else if (onHeaderLine && isClosestHeaderToPosition) { - return new vscode.SelectionRange(header.location.range, parent); - } else if (parent && parent.range.contains(headerPlusContentRange)) { - if (partialContentRange) { - return new vscode.SelectionRange(partialContentRange, new vscode.SelectionRange(contentRange, (new vscode.SelectionRange(headerPlusContentRange, parent)))); - } else { - return new vscode.SelectionRange(contentRange, new vscode.SelectionRange(headerPlusContentRange, parent)); - } - } else if (partialContentRange) { - return new vscode.SelectionRange(partialContentRange, new vscode.SelectionRange(contentRange, (new vscode.SelectionRange(headerPlusContentRange)))); - } else { - return new vscode.SelectionRange(contentRange, new vscode.SelectionRange(headerPlusContentRange)); - } - } else { - return undefined; - } -} - -function createBlockRange(document: vscode.TextDocument, cursorLine: number, block?: Token, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined { - if (block) { - if (block.type === 'fence') { - return createFencedRange(block, cursorLine, document, parent); - } else { - let startLine = document.lineAt(block.map[0]).isEmptyOrWhitespace ? block.map[0] + 1 : block.map[0]; - let endLine = startLine !== block.map[1] && isList(block.type) ? block.map[1] - 1 : block.map[1]; - let startPos = new vscode.Position(startLine, 0); - let endPos = new vscode.Position(endLine, getEndCharacter(document, startLine, endLine)); - let range = new vscode.Range(startPos, endPos); - if (parent && parent.range.contains(range) && !parent.range.isEqual(range)) { - return new vscode.SelectionRange(range, parent); - } else if (parent) { - if (rangeLinesEqual(range, parent.range)) { - return range.end.character > parent.range.end.character ? new vscode.SelectionRange(range) : parent; - } else if (parent.range.end.line + 1 === range.end.line) { - let adjustedRange = new vscode.Range(range.start, range.end.translate(-1, parent.range.end.character)); - if (adjustedRange.isEqual(parent.range)) { - return parent; - } else { - return new vscode.SelectionRange(adjustedRange, parent); - } - } else if (parent.range.end.line === range.end.line) { - let adjustedRange = new vscode.Range(parent.range.start, range.end.translate(undefined, parent.range.end.character)); - if (adjustedRange.isEqual(parent.range)) { - return parent; - } else { - return new vscode.SelectionRange(adjustedRange, parent.parent); - } - } else { - return parent; - } - } else { - return new vscode.SelectionRange(range); - } - } - } else { - return undefined; - } -} - -function createFencedRange(token: Token, cursorLine: number, document: vscode.TextDocument, parent?: vscode.SelectionRange): vscode.SelectionRange { - const startLine = token.map[0]; - const endLine = token.map[1] - 1; - let onFenceLine = cursorLine === startLine || cursorLine === endLine; - let fenceRange = new vscode.Range(new vscode.Position(startLine, 0), new vscode.Position(endLine, document.lineAt(endLine).text.length)); - let contentRange = endLine - startLine > 2 && !onFenceLine ? new vscode.Range(new vscode.Position(startLine + 1, 0), new vscode.Position(endLine - 1, getEndCharacter(document, startLine + 1, endLine))) : undefined; - if (parent && contentRange) { - if (parent.range.contains(fenceRange) && !parent.range.isEqual(fenceRange)) { - return new vscode.SelectionRange(contentRange, new vscode.SelectionRange(fenceRange, parent)); - } else if (parent.range.isEqual(fenceRange)) { - return new vscode.SelectionRange(contentRange, parent); - } else if (rangeLinesEqual(fenceRange, parent.range)) { - let revisedRange = fenceRange.end.character > parent.range.end.character ? fenceRange : parent.range; - return new vscode.SelectionRange(contentRange, new vscode.SelectionRange(revisedRange, getRealParent(parent, revisedRange))); - } else if (parent.range.end.line === fenceRange.end.line) { - parent.range.end.translate(undefined, fenceRange.end.character); - return new vscode.SelectionRange(contentRange, new vscode.SelectionRange(fenceRange, parent)); - } - } else if (contentRange) { - return new vscode.SelectionRange(contentRange, new vscode.SelectionRange(fenceRange)); - } else if (parent) { - if (parent.range.contains(fenceRange) && !parent.range.isEqual(fenceRange)) { - return new vscode.SelectionRange(fenceRange, parent); - } else if (parent.range.isEqual(fenceRange)) { - return parent; - } else if (rangeLinesEqual(fenceRange, parent.range)) { - let revisedRange = fenceRange.end.character > parent.range.end.character ? fenceRange : parent.range; - return new vscode.SelectionRange(revisedRange, parent.parent); - } else if (parent.range.end.line === fenceRange.end.line) { - parent.range.end.translate(undefined, fenceRange.end.character); - return new vscode.SelectionRange(fenceRange, parent); - } - } - return new vscode.SelectionRange(fenceRange, parent); -} - -function isList(type: string): boolean { - return type ? ['ordered_list_open', 'list_item_open', 'bullet_list_open'].includes(type) : false; -} - -function getEndCharacter(document: vscode.TextDocument, startLine: number, endLine: number): number { - let startLength = document.lineAt(startLine).text ? document.lineAt(startLine).text.length : 0; - let endLength = document.lineAt(startLine).text ? document.lineAt(startLine).text.length : 0; - let endChar = Math.max(startLength, endLength); - return startLine !== endLine ? 0 : endChar; -} - -function getRealParent(parent: vscode.SelectionRange, range: vscode.Range) { - let currentParent: vscode.SelectionRange | undefined = parent; - while (currentParent && !currentParent.range.contains(range)) { - currentParent = currentParent.parent; - } - return currentParent; -} - -function rangeLinesEqual(range: vscode.Range, parent: vscode.Range) { - return range.start.line === parent.start.line && range.end.line === parent.end.line; -} diff --git a/extensions/markdown-language-features/src/features/workspaceSymbolProvider.ts b/extensions/markdown-language-features/src/features/workspaceSymbolProvider.ts index f97bb0219..91f9dfcd3 100644 --- a/extensions/markdown-language-features/src/features/workspaceSymbolProvider.ts +++ b/extensions/markdown-language-features/src/features/workspaceSymbolProvider.ts @@ -9,7 +9,6 @@ import { isMarkdownFile } from '../util/file'; import { Lazy, lazy } from '../util/lazy'; import MDDocumentSymbolProvider from './documentSymbolProvider'; import { SkinnyTextDocument, SkinnyTextLine } from '../tableOfContentsProvider'; -import { flatten } from '../util/arrays'; export interface WorkspaceMarkdownDocumentProvider { getAllMarkdownDocuments(): Thenable>; @@ -136,7 +135,7 @@ export default class MarkdownWorkspaceSymbolProvider extends Disposable implemen } const allSymbolsSets = await Promise.all(Array.from(this._symbolCache.values()).map(x => x.value)); - const allSymbols = flatten(allSymbolsSets); + const allSymbols = allSymbolsSets.flat(); return allSymbols.filter(symbolInformation => symbolInformation.name.toLowerCase().indexOf(query.toLowerCase()) !== -1); } diff --git a/extensions/markdown-language-features/src/test/inMemoryDocument.ts b/extensions/markdown-language-features/src/test/inMemoryDocument.ts index 052216f90..8f5f6fe78 100644 --- a/extensions/markdown-language-features/src/test/inMemoryDocument.ts +++ b/extensions/markdown-language-features/src/test/inMemoryDocument.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; - +import * as os from 'os'; export class InMemoryDocument implements vscode.TextDocument { private readonly _lines: string[]; @@ -13,7 +13,7 @@ export class InMemoryDocument implements vscode.TextDocument { private readonly _contents: string, public readonly version = 1, ) { - this._lines = this._contents.split(/\n/g); + this._lines = this._contents.split(/\r\n|\n/g); } @@ -21,7 +21,7 @@ export class InMemoryDocument implements vscode.TextDocument { languageId: string = ''; isDirty: boolean = false; isClosed: boolean = false; - eol: vscode.EndOfLine = vscode.EndOfLine.LF; + eol: vscode.EndOfLine = os.platform() === 'win32' ? vscode.EndOfLine.CRLF : vscode.EndOfLine.LF; notebook: undefined; get fileName(): string { @@ -47,9 +47,9 @@ export class InMemoryDocument implements vscode.TextDocument { } positionAt(offset: number): vscode.Position { const before = this._contents.slice(0, offset); - const newLines = before.match(/\n/g); + const newLines = before.match(/\r\n|\n/g); const line = newLines ? newLines.length : 0; - const preCharacters = before.match(/(\n|^).*$/g); + const preCharacters = before.match(/(\r\n|\n|^).*$/g); return new vscode.Position(line, preCharacters ? preCharacters[0].length : 0); } getText(_range?: vscode.Range | undefined): string { diff --git a/extensions/markdown-language-features/src/test/smartSelect.test.ts b/extensions/markdown-language-features/src/test/smartSelect.test.ts index 787df59ac..6e77d56c2 100644 --- a/extensions/markdown-language-features/src/test/smartSelect.test.ts +++ b/extensions/markdown-language-features/src/test/smartSelect.test.ts @@ -14,10 +14,10 @@ const CURSOR = '$$CURSOR$$'; const testFileName = vscode.Uri.file('test.md'); -suite.only('markdown.SmartSelect', () => { +suite('markdown.SmartSelect', () => { test('Smart select single word', async () => { const ranges = await getSelectionRangesForDocument(`Hel${CURSOR}lo`); - assertNestedRangesEqual(ranges![0], [0, 1]); + assertNestedLineNumbersEqual(ranges![0], [0, 0]); }); test('Smart select multi-line paragraph', async () => { const ranges = await getSelectionRangesForDocument( @@ -26,12 +26,12 @@ suite.only('markdown.SmartSelect', () => { `For example, the[node debug adapter](https://github.com/microsoft/vscode-node-debug) and the [mono debug adapter]`, `(https://github.com/microsoft/vscode-mono-debug) have their own repositories. For a complete list, please visit the [Related Projects](https://github.com/microsoft/vscode/wiki/Related-Projects) page on our [wiki](https://github.com/microsoft/vscode/wiki).` )); - assertNestedRangesEqual(ranges![0], [0, 3]); + assertNestedLineNumbersEqual(ranges![0], [0, 2]); }); test('Smart select paragraph', async () => { const ranges = await getSelectionRangesForDocument(`Many of the core components and extensions to ${CURSOR}VS Code live in their own repositories on GitHub. For example, the [node debug adapter](https://github.com/microsoft/vscode-node-debug) and the [mono debug adapter](https://github.com/microsoft/vscode-mono-debug) have their own repositories. For a complete list, please visit the [Related Projects](https://github.com/microsoft/vscode/wiki/Related-Projects) page on our [wiki](https://github.com/microsoft/vscode/wiki).`); - assertNestedRangesEqual(ranges![0], [0, 1]); + assertNestedLineNumbersEqual(ranges![0], [0, 0]); }); test('Smart select html block', async () => { const ranges = await getSelectionRangesForDocument( @@ -40,7 +40,7 @@ suite.only('markdown.SmartSelect', () => { `${CURSOR}VS Code in action`, `

`)); - assertNestedRangesEqual(ranges![0], [0, 3]); + assertNestedLineNumbersEqual(ranges![0], [0, 2]); }); test('Smart select header on header line', async () => { const ranges = await getSelectionRangesForDocument( @@ -48,7 +48,7 @@ suite.only('markdown.SmartSelect', () => { `# Header${CURSOR}`, `Hello`)); - assertNestedRangesEqual(ranges![0], [0, 1]); + assertNestedLineNumbersEqual(ranges![0], [0, 1]); }); test('Smart select single word w grandparent header on text line', async () => { @@ -59,7 +59,7 @@ suite.only('markdown.SmartSelect', () => { `${CURSOR}Hello` )); - assertNestedRangesEqual(ranges![0], [2, 2], [1, 2]); + assertNestedLineNumbersEqual(ranges![0], [2, 2], [1, 2]); }); test('Smart select html block w parent header', async () => { const ranges = await getSelectionRangesForDocument( @@ -69,7 +69,7 @@ suite.only('markdown.SmartSelect', () => { `VS Code in action`, `

`)); - assertNestedRangesEqual(ranges![0], [1, 3], [1, 3], [0, 3]); + assertNestedLineNumbersEqual(ranges![0], [1, 1], [1, 3], [0, 3]); }); test('Smart select fenced code block', async () => { const ranges = await getSelectionRangesForDocument( @@ -78,7 +78,7 @@ suite.only('markdown.SmartSelect', () => { `a${CURSOR}`, `~~~`)); - assertNestedRangesEqual(ranges![0], [0, 2]); + assertNestedLineNumbersEqual(ranges![0], [0, 2]); }); test('Smart select list', async () => { const ranges = await getSelectionRangesForDocument( @@ -87,8 +87,7 @@ suite.only('markdown.SmartSelect', () => { `- ${CURSOR}item 2`, `- item 3`, `- item 4`)); - - assertNestedRangesEqual(ranges![0], [1, 1], [0, 3]); + assertNestedLineNumbersEqual(ranges![0], [1, 1], [0, 3]); }); test('Smart select list with fenced code block', async () => { const ranges = await getSelectionRangesForDocument( @@ -100,7 +99,7 @@ suite.only('markdown.SmartSelect', () => { `- item 3`, `- item 4`)); - assertNestedRangesEqual(ranges![0], [1, 3], [0, 5]); + assertNestedLineNumbersEqual(ranges![0], [1, 3], [0, 5]); }); test('Smart select multi cursor', async () => { const ranges = await getSelectionRangesForDocument( @@ -112,8 +111,8 @@ suite.only('markdown.SmartSelect', () => { `- ${CURSOR}item 3`, `- item 4`)); - assertNestedRangesEqual(ranges![0], [0, 0], [0, 5]); - assertNestedRangesEqual(ranges![1], [4, 4], [0, 5]); + assertNestedLineNumbersEqual(ranges![0], [0, 0], [0, 5]); + assertNestedLineNumbersEqual(ranges![1], [4, 4], [0, 5]); }); test('Smart select nested block quotes', async () => { const ranges = await getSelectionRangesForDocument( @@ -122,7 +121,7 @@ suite.only('markdown.SmartSelect', () => { `> item 2`, `>> ${CURSOR}item 3`, `>> item 4`)); - assertNestedRangesEqual(ranges![0], [2, 4], [0, 4]); + assertNestedLineNumbersEqual(ranges![0], [2, 2], [2, 3], [0, 3]); }); test('Smart select multi nested block quotes', async () => { const ranges = await getSelectionRangesForDocument( @@ -131,8 +130,7 @@ suite.only('markdown.SmartSelect', () => { `>> item 2`, `>>> ${CURSOR}item 3`, `>>>> item 4`)); - - assertNestedRangesEqual(ranges![0], [2, 3], [2, 4], [1, 4], [0, 4]); + assertNestedLineNumbersEqual(ranges![0], [2, 2], [2, 3], [1, 3], [0, 3]); }); test('Smart select subheader content', async () => { const ranges = await getSelectionRangesForDocument( @@ -143,7 +141,7 @@ suite.only('markdown.SmartSelect', () => { `${CURSOR}content 2`, `# main header 2`)); - assertNestedRangesEqual(ranges![0], [3, 3], [2, 3], [1, 3], [0, 3]); + assertNestedLineNumbersEqual(ranges![0], [3, 3], [2, 3], [1, 3], [0, 3]); }); test('Smart select subheader line', async () => { const ranges = await getSelectionRangesForDocument( @@ -154,7 +152,7 @@ suite.only('markdown.SmartSelect', () => { `content 2`, `# main header 2`)); - assertNestedRangesEqual(ranges![0], [2, 3], [1, 3], [0, 3]); + assertNestedLineNumbersEqual(ranges![0], [2, 3], [1, 3], [0, 3]); }); test('Smart select blank line', async () => { const ranges = await getSelectionRangesForDocument( @@ -165,7 +163,7 @@ suite.only('markdown.SmartSelect', () => { `content 2`, `# main header 2`)); - assertNestedRangesEqual(ranges![0], [1, 3], [0, 3]); + assertNestedLineNumbersEqual(ranges![0], [1, 3], [0, 3]); }); test('Smart select line between paragraphs', async () => { const ranges = await getSelectionRangesForDocument( @@ -174,7 +172,7 @@ suite.only('markdown.SmartSelect', () => { `${CURSOR}`, `paragraph 2`)); - assertNestedRangesEqual(ranges![0], [0, 3]); + assertNestedLineNumbersEqual(ranges![0], [0, 2]); }); test('Smart select empty document', async () => { const ranges = await getSelectionRangesForDocument(``, [new vscode.Position(0, 0)]); @@ -196,7 +194,7 @@ suite.only('markdown.SmartSelect', () => { `more content`, `# main header 2`)); - assertNestedRangesEqual(ranges![0], [4, 6], [3, 9], [3, 10], [2, 10], [1, 10], [0, 10]); + assertNestedLineNumbersEqual(ranges![0], [4, 6], [3, 9], [3, 10], [2, 10], [1, 10], [0, 10]); }); test('Smart select list with one element without selecting child subheader', async () => { const ranges = await getSelectionRangesForDocument( @@ -209,8 +207,7 @@ suite.only('markdown.SmartSelect', () => { ``, `content 2`, `# main header 2`)); - - assertNestedRangesEqual(ranges![0], [2, 3], [1, 3], [1, 6], [0, 6]); + assertNestedLineNumbersEqual(ranges![0], [2, 2], [2, 3], [1, 3], [1, 6], [0, 6]); }); test('Smart select content under header then subheaders and their content', async () => { const ranges = await getSelectionRangesForDocument( @@ -224,7 +221,7 @@ suite.only('markdown.SmartSelect', () => { `content 2`, `# main header 2`)); - assertNestedRangesEqual(ranges![0], [0, 3], [0, 6]); + assertNestedLineNumbersEqual(ranges![0], [0, 3], [0, 6]); }); test('Smart select last blockquote element under header then subheaders and their content', async () => { const ranges = await getSelectionRangesForDocument( @@ -242,7 +239,7 @@ suite.only('markdown.SmartSelect', () => { `content 2`, `# main header 2`)); - assertNestedRangesEqual(ranges![0], [4, 6], [2, 6], [1, 7], [1, 10], [0, 10]); + assertNestedLineNumbersEqual(ranges![0], [5, 5], [4, 5], [2, 5], [1, 7], [1, 10], [0, 10]); }); test('Smart select content of subheader then subheader then content of main header then main header', async () => { const ranges = await getSelectionRangesForDocument( @@ -266,7 +263,7 @@ suite.only('markdown.SmartSelect', () => { `- content 2`, `content 2`)); - assertNestedRangesEqual(ranges![0], [11, 12], [9, 12], [9, 17], [8, 17], [1, 17], [0, 17]); + assertNestedLineNumbersEqual(ranges![0], [11, 11], [9, 12], [9, 17], [8, 17], [1, 17], [0, 17]); }); test('Smart select last line content of subheader then subheader then content of main header then main header', async () => { const ranges = await getSelectionRangesForDocument( @@ -288,9 +285,9 @@ suite.only('markdown.SmartSelect', () => { `- content 2`, `- content 2`, `- content 2`, - `${CURSOR}content 2`)); + `- ${CURSOR}content 2`)); - assertNestedRangesEqual(ranges![0], [16, 17], [14, 17], [14, 17], [13, 17], [9, 17], [8, 17], [1, 17], [0, 17]); + assertNestedLineNumbersEqual(ranges![0], [17, 17], [14, 17], [13, 17], [9, 17], [8, 17], [1, 17], [0, 17]); }); test('Smart select last line content after content of subheader then subheader then content of main header then main header', async () => { const ranges = await getSelectionRangesForDocument( @@ -312,9 +309,9 @@ suite.only('markdown.SmartSelect', () => { `- content 2`, `- content 2`, `- content 2`, - `content 2${CURSOR}`)); + `- content 2${CURSOR}`)); - assertNestedRangesEqual(ranges![0], [16, 17], [14, 17], [14, 17], [13, 17], [9, 17], [8, 17], [1, 17], [0, 17]); + assertNestedLineNumbersEqual(ranges![0], [17, 17], [14, 17], [13, 17], [9, 17], [8, 17], [1, 17], [0, 17]); }); test('Smart select fenced code block then list then rest of content', async () => { const ranges = await getSelectionRangesForDocument( @@ -338,7 +335,7 @@ suite.only('markdown.SmartSelect', () => { `- content 2`, `- content 2`)); - assertNestedRangesEqual(ranges![0], [9, 11], [8, 12], [7, 17], [1, 17], [0, 17]); + assertNestedLineNumbersEqual(ranges![0], [9, 11], [8, 12], [8, 12], [7, 17], [1, 17], [0, 17]); }); test('Smart select fenced code block then list then rest of content on fenced line', async () => { const ranges = await getSelectionRangesForDocument( @@ -362,15 +359,289 @@ suite.only('markdown.SmartSelect', () => { `- content 2`, `- content 2`)); - assertNestedRangesEqual(ranges![0], [8, 12], [7, 17], [1, 17], [0, 17]); + assertNestedLineNumbersEqual(ranges![0], [8, 12], [7, 17], [1, 17], [0, 17]); + }); + test('Smart select without multiple ranges', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `# main header 1`, + ``, + ``, + `- ${CURSOR}paragraph`, + `- content`)); + + assertNestedLineNumbersEqual(ranges![0], [3, 3], [3, 4], [1, 4], [0, 4]); + }); + test('Smart select on second level of a list', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `* level 0`, + ` * level 1`, + ` * level 1`, + ` * level 2`, + ` * level 1`, + ` * level ${CURSOR}1`, + `* level 0`)); + + assertNestedLineNumbersEqual(ranges![0], [5, 5], [1, 5], [0, 5], [0, 6]); + }); + test('Smart select on third level of a list', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `* level 0`, + ` * level 1`, + ` * level 1`, + ` * level ${CURSOR}2`, + ` * level 2`, + ` * level 1`, + ` * level 1`, + `* level 0`)); + assertNestedLineNumbersEqual(ranges![0], [3, 3], [3, 4], [2, 4], [1, 6], [0, 6], [0, 7]); + }); + test('Smart select level 2 then level 1', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `* level 1`, + ` * level ${CURSOR}2`, + ` * level 2`, + `* level 1`)); + assertNestedLineNumbersEqual(ranges![0], [1, 1], [1, 2], [0, 2], [0, 3]); + }); + test('Smart select last list item', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `- level 1`, + `- level 2`, + `- level 2`, + `- level ${CURSOR}1`)); + assertNestedLineNumbersEqual(ranges![0], [3, 3], [0, 3]); + }); + test('Smart select without multiple ranges', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `# main header 1`, + ``, + ``, + `- ${CURSOR}paragraph`, + `- content`)); + + assertNestedLineNumbersEqual(ranges![0], [3, 3], [3, 4], [1, 4], [0, 4]); + }); + test('Smart select on second level of a list', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `* level 0`, + ` * level 1`, + ` * level 1`, + ` * level 2`, + ` * level 1`, + ` * level ${CURSOR}1`, + `* level 0`)); + + assertNestedLineNumbersEqual(ranges![0], [5, 5], [1, 5], [0, 5], [0, 6]); + }); + test('Smart select on third level of a list', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `* level 0`, + ` * level 1`, + ` * level 1`, + ` * level ${CURSOR}2`, + ` * level 2`, + ` * level 1`, + ` * level 1`, + `* level 0`)); + assertNestedLineNumbersEqual(ranges![0], [3, 3], [3, 4], [2, 4], [1, 6], [0, 6], [0, 7]); + }); + test('Smart select level 2 then level 1', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `* level 1`, + ` * level ${CURSOR}2`, + ` * level 2`, + `* level 1`)); + assertNestedLineNumbersEqual(ranges![0], [1, 1], [1, 2], [0, 2], [0, 3]); + }); + test('Smart select bold', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `stuff here **new${CURSOR}item** and here` + )); + assertNestedRangesEqual(ranges![0], [0, 13, 0, 30], [0, 11, 0, 32], [0, 0, 0, 41]); + }); + test('Smart select link', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `stuff here [text](https${CURSOR}://google.com) and here` + )); + assertNestedRangesEqual(ranges![0], [0, 18, 0, 46], [0, 17, 0, 47], [0, 11, 0, 47], [0, 0, 0, 56]); + }); + test('Smart select brackets', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `stuff here [te${CURSOR}xt](https://google.com) and here` + )); + assertNestedRangesEqual(ranges![0], [0, 12, 0, 26], [0, 11, 0, 27], [0, 11, 0, 47], [0, 0, 0, 56]); + }); + test('Smart select brackets under header in list', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `# main header 1`, + ``, + `- list`, + `paragraph`, + `## sub header`, + `- list`, + `- stuff here [te${CURSOR}xt](https://google.com) and here`, + `- list` + )); + assertNestedRangesEqual(ranges![0], [6, 14, 6, 28], [6, 13, 6, 29], [6, 13, 6, 49], [6, 0, 6, 58], [5, 0, 7, 6], [4, 0, 7, 6], [1, 0, 7, 6], [0, 0, 7, 6]); + }); + test('Smart select link under header in list', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `# main header 1`, + ``, + `- list`, + `paragraph`, + `## sub header`, + `- list`, + `- stuff here [text](${CURSOR}https://google.com) and here`, + `- list` + )); + assertNestedRangesEqual(ranges![0], [6, 20, 6, 48], [6, 19, 6, 49], [6, 13, 6, 49], [6, 0, 6, 58], [5, 0, 7, 6], [4, 0, 7, 6], [1, 0, 7, 6], [0, 0, 7, 6]); + }); + test('Smart select bold within list where multiple bold elements exists', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `# main header 1`, + ``, + `- list`, + `paragraph`, + `## sub header`, + `- list`, + `- stuff here [text] **${CURSOR}items in here** and **here**`, + `- list` + )); + assertNestedRangesEqual(ranges![0], [6, 22, 6, 45], [6, 20, 6, 47], [6, 0, 6, 60], [5, 0, 7, 6], [4, 0, 7, 6], [1, 0, 7, 6], [0, 0, 7, 6]); + }); + test('Smart select link in paragraph with multiple links', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `This[extension](https://marketplace.visualstudio.com/items?itemName=meganrogge.template-string-converter) addresses this [requ${CURSOR}est](https://github.com/microsoft/vscode/issues/56704) to convert Javascript/Typescript quotes to backticks when has been entered within a string.` + )); + assertNestedRangesEqual(ranges![0], [0, 123, 0, 140], [0, 122, 0, 141], [0, 122, 0, 191], [0, 0, 0, 283]); + }); + test('Smart select bold link', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `**[extens${CURSOR}ion](https://google.com)**` + )); + assertNestedRangesEqual(ranges![0], [0, 3, 0, 22], [0, 2, 0, 23], [0, 2, 0, 43], [0, 2, 0, 43], [0, 0, 0, 45], [0, 0, 0, 45]); + }); + test('Smart select inline code block', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `[\`code ${CURSOR} link\`]` + )); + assertNestedRangesEqual(ranges![0], [0, 2, 0, 22], [0, 1, 0, 23], [0, 0, 0, 24]); + }); + test('Smart select link with inline code block text', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `[\`code ${CURSOR} link\`](http://example.com)` + )); + assertNestedRangesEqual(ranges![0], [0, 2, 0, 22], [0, 1, 0, 23], [0, 1, 0, 23], [0, 0, 0, 24], [0, 0, 0, 44], [0, 0, 0, 44]); + }); + test('Smart select italic', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `*some nice ${CURSOR}text*` + )); + assertNestedRangesEqual(ranges![0], [0, 1, 0, 25], [0, 0, 0, 26], [0, 0, 0, 26]); + }); + test('Smart select italic link', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `*[extens${CURSOR}ion](https://google.com)*` + )); + assertNestedRangesEqual(ranges![0], [0, 2, 0, 21], [0, 1, 0, 22], [0, 1, 0, 42], [0, 1, 0, 42], [0, 0, 0, 43], [0, 0, 0, 43]); + }); + test('Smart select italic on end', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `*word1 word2 word3${CURSOR}*` + )); + assertNestedRangesEqual(ranges![0], [0, 1, 0, 28], [0, 0, 0, 29], [0, 0, 0, 29]); + }); + test('Smart select italic then bold', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `outer text **bold words *italic ${CURSOR} words* bold words** outer text` + )); + assertNestedRangesEqual(ranges![0], [0, 25, 0, 48], [0, 24, 0, 49], [0, 13, 0, 60], [0, 11, 0, 62], [0, 0, 0, 73]); + }); + test('Smart select bold then italic', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `outer text *italic words **bold ${CURSOR} words** italic words* outer text` + )); + assertNestedRangesEqual(ranges![0], [0, 27, 0, 48], [0, 25, 0, 50], [0, 12, 0, 63], [0, 11, 0, 64], [0, 0, 0, 75]); + }); + test('Third level header from release notes', async () => { + const ranges = await getSelectionRangesForDocument( + joinLines( + `---`, + `Order: 60`, + `TOCTitle: October 2020`, + `PageTitle: Visual Studio Code October 2020`, + `MetaDescription: Learn what is new in the Visual Studio Code October 2020 Release (1.51)`, + `MetaSocialImage: 1_51/release-highlights.png`, + `Date: 2020-11-6`, + `DownloadVersion: 1.51.1`, + `---`, + `# October 2020 (version 1.51)`, + ``, + `**Update 1.51.1**: The update addresses these [issues](https://github.com/microsoft/vscode/issues?q=is%3Aissue+milestone%3A%22October+2020+Recovery%22+is%3Aclosed+).`, + ``, + ``, + ``, + `---`, + ``, + `Welcome to the October 2020 release of Visual Studio Code. As announced in the [October iteration plan](https://github.com/microsoft/vscode/issues/108473), we focused on housekeeping GitHub issues and pull requests as documented in our issue grooming guide.`, + ``, + `We also worked with our partners at GitHub on GitHub Codespaces, which ended up being more involved than originally anticipated. To that end, we'll continue working on housekeeping for part of the November iteration.`, + ``, + `During this housekeeping milestone, we also addressed several feature requests and community [pull requests](#thank-you). Read on to learn about new features and settings.`, + ``, + `## Workbench`, + ``, + `### More prominent pinned tabs`, + ``, + `${CURSOR}Pinned tabs will now always show their pin icon, even while inactive, to make them easier to identify. If an editor is both pinned and contains unsaved changes, the icon reflects both states.`, + ``, + `![Inactive pinned tabs showing pin icons](images/1_51/pinned-tabs.png)` + ) + ); + assertNestedRangesEqual(ranges![0], [27, 0, 27, 201], [26, 0, 29, 70], [25, 0, 29, 70], [24, 0, 29, 70], [23, 0, 29, 70], [10, 0, 29, 70], [9, 0, 29, 70]); }); }); -function assertNestedRangesEqual(range: vscode.SelectionRange, ...expectedRanges: [number, number][]) { +function assertNestedLineNumbersEqual(range: vscode.SelectionRange, ...expectedRanges: [number, number][]) { const lineage = getLineage(range); - assert.strictEqual(lineage.length, expectedRanges.length, `expected depth: ${expectedRanges.length}, but was ${lineage.length}`); + assert.strictEqual(lineage.length, expectedRanges.length, `expected depth: ${expectedRanges.length}, but was ${lineage.length} ${getValues(lineage)}`); for (let i = 0; i < lineage.length; i++) { - assertRangesEqual(lineage[i], expectedRanges[i][0], expectedRanges[i][1], `parent at a depth of ${i}`); + assertLineNumbersEqual(lineage[i], expectedRanges[i][0], expectedRanges[i][1], `parent at a depth of ${i}`); + } +} + +function assertNestedRangesEqual(range: vscode.SelectionRange, ...expectedRanges: [number, number, number, number][]) { + const lineage = getLineage(range); + assert.strictEqual(lineage.length, expectedRanges.length, `expected depth: ${expectedRanges.length}, but was ${lineage.length} ${getValues(lineage)}`); + for (let i = 0; i < lineage.length; i++) { + assertLineNumbersEqual(lineage[i], expectedRanges[i][0], expectedRanges[i][2], `parent at a depth of ${i}`); + assert(lineage[i].range.start.character === expectedRanges[i][1], `parent at a depth of ${i} on start char`); + assert(lineage[i].range.end.character === expectedRanges[i][3], `parent at a depth of ${i} on end char`); } } @@ -384,7 +655,13 @@ function getLineage(range: vscode.SelectionRange): vscode.SelectionRange[] { return result; } -function assertRangesEqual(selectionRange: vscode.SelectionRange, startLine: number, endLine: number, message: string) { +function getValues(ranges: vscode.SelectionRange[]): string[] { + return ranges.map(range => { + return range.range.start.line + ' ' + range.range.start.character + ' ' + range.range.end.line + ' ' + range.range.end.character; + }); +} + +function assertLineNumbersEqual(selectionRange: vscode.SelectionRange, startLine: number, endLine: number, message: string) { assert.strictEqual(selectionRange.range.start.line, startLine, `failed on start line ${message}`); assert.strictEqual(selectionRange.range.end.line, endLine, `failed on end line ${message}`); } diff --git a/extensions/markdown-language-features/src/util/arrays.ts b/extensions/markdown-language-features/src/util/arrays.ts index ec0ed25c5..b778a24ec 100644 --- a/extensions/markdown-language-features/src/util/arrays.ts +++ b/extensions/markdown-language-features/src/util/arrays.ts @@ -15,8 +15,4 @@ export function equals(one: ReadonlyArray, other: ReadonlyArray, itemEq } return true; -} - -export function flatten(arr: ReadonlyArray[]): T[] { - return ([] as T[]).concat.apply([], arr); } \ No newline at end of file diff --git a/extensions/markdown-language-features/test-workspace/a.md b/extensions/markdown-language-features/test-workspace/a.md index 9d7091806..568bad19d 100644 --- a/extensions/markdown-language-features/test-workspace/a.md +++ b/extensions/markdown-language-features/test-workspace/a.md @@ -1,4 +1,10 @@ [b](b) -[b](b.md) -[b](./b.md) -[b](/b.md) + +[b.md](b.md) + +[./b.md](./b.md) + +[/b.md](/b.md) + +[b#header1](b#header1) + diff --git a/extensions/markdown-language-features/test-workspace/b.md b/extensions/markdown-language-features/test-workspace/b.md index 730f53ee6..524a6cb26 100644 --- a/extensions/markdown-language-features/test-workspace/b.md +++ b/extensions/markdown-language-features/test-workspace/b.md @@ -1,3 +1,5 @@ # b -[](./a) \ No newline at end of file +[./a](./a) + +# header1 \ No newline at end of file diff --git a/extensions/markdown-language-features/tsconfig.json b/extensions/markdown-language-features/tsconfig.json index ec7424a40..b02362c2c 100644 --- a/extensions/markdown-language-features/tsconfig.json +++ b/extensions/markdown-language-features/tsconfig.json @@ -6,6 +6,8 @@ "lib": [ "es6", "es2015.promise", + "es2019.array", + "es2020.string", "dom" ] }, diff --git a/extensions/markdown-language-features/yarn.lock b/extensions/markdown-language-features/yarn.lock index 5f1895e96..065943fa4 100644 --- a/extensions/markdown-language-features/yarn.lock +++ b/extensions/markdown-language-features/yarn.lock @@ -2131,10 +2131,10 @@ he@1.1.1: resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= -highlight.js@9.15.10: - version "9.15.10" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.15.10.tgz#7b18ed75c90348c045eef9ed08ca1319a2219ad2" - integrity sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw== +highlight.js@10.1.2: + version "10.1.2" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.1.2.tgz#c20db951ba1c22c055010648dfffd7b2a968e00c" + integrity sha512-Q39v/Mn5mfBlMff9r+zzA+gWxRsCRKwEMvYTiisLr/XUiFI/4puWt0Ojdko3R3JCNWGdOWaA5g/Yxqa23kC5AA== hmac-drbg@^1.0.0: version "1.0.1" diff --git a/extensions/microsoft-authentication/src/AADHelper.ts b/extensions/microsoft-authentication/src/AADHelper.ts index 8220adc6c..60455f02a 100644 --- a/extensions/microsoft-authentication/src/AADHelper.ts +++ b/extensions/microsoft-authentication/src/AADHelper.ts @@ -276,7 +276,7 @@ export class AzureActiveDirectoryService { } return new Promise(async (resolve, reject) => { - if (vscode.env.uiKind === vscode.UIKind.Web) { + if (vscode.env.remoteName !== undefined) { resolve(this.loginWithoutLocalServer(scope)); return; } diff --git a/extensions/npm/README.md b/extensions/npm/README.md index 144cdd275..82730c7e8 100644 --- a/extensions/npm/README.md +++ b/extensions/npm/README.md @@ -34,7 +34,7 @@ The extension fetches data from https://registry.npmjs.org and https://registry. - `npm.autoDetect` - Enable detecting scripts as tasks, the default is `on`. - `npm.runSilent` - Run npm script with the `--silent` option, the default is `false`. -- `npm.packageManager` - The package manager used to run the scripts: `npm`, `yarn` or `pnpm`, the default is `npm`. +- `npm.packageManager` - The package manager used to run the scripts: `auto`, `npm`, `yarn` or `pnpm`, the default is `auto`, which detects your package manager based on your files. - `npm.exclude` - Glob patterns for folders that should be excluded from automatic script detection. The pattern is matched against the **absolute path** of the package.json. For example, to exclude all test folders use '**/test/**'. - `npm.enableScriptExplorer` - Enable an explorer view for npm scripts. - `npm.scriptExplorerAction` - The default click action: `open` or `run`, the default is `open`. diff --git a/extensions/npm/package.json b/extensions/npm/package.json index 575a2ef6d..5210c5296 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -18,15 +18,21 @@ "watch": "gulp watch-extension:npm" }, "dependencies": { + "find-up": "^4.1.0", + "find-yarn-workspace-root": "^2.0.0", "jsonc-parser": "^2.2.1", "minimatch": "^3.0.4", "request-light": "^0.4.0", - "vscode-nls": "^4.1.1" + "vscode-nls": "^4.1.1", + "which-pm": "^2.0.0" }, "devDependencies": { "@types/minimatch": "^3.0.3", "@types/node": "^12.11.7" }, + "resolutions": { + "which-pm/load-yaml-file/**/argparse": "1.0.9" + }, "main": "./out/npmMain", "browser": "./dist/browser/npmBrowserMain", "activationEvents": [ @@ -56,7 +62,7 @@ { "id": "npm", "name": "%view.name%", - "icon": "images/code.svg", + "icon": "$(code)", "visibility": "hidden" } ] @@ -217,11 +223,18 @@ "scope": "resource", "type": "string", "enum": [ + "auto", "npm", "yarn", "pnpm" ], - "default": "npm", + "enumDescriptions": [ + "%config.npm.packageManager.auto%", + "%config.npm.packageManager.npm%", + "%config.npm.packageManager.yarn%", + "%config.npm.packageManager.pnpm%" + ], + "default": "auto", "description": "%config.npm.packageManager%" }, "npm.exclude": { diff --git a/extensions/npm/package.nls.json b/extensions/npm/package.nls.json index 8ecf37462..53ea18481 100644 --- a/extensions/npm/package.nls.json +++ b/extensions/npm/package.nls.json @@ -4,6 +4,10 @@ "config.npm.autoDetect": "Controls whether npm scripts should be automatically detected.", "config.npm.runSilent": "Run npm commands with the `--silent` option.", "config.npm.packageManager": "The package manager used to run scripts.", + "config.npm.packageManager.npm": "Use npm as the package manager for running scripts.", + "config.npm.packageManager.yarn": "Use yarn as the package manager for running scripts.", + "config.npm.packageManager.pnpm": "Use pnpm as the package manager for running scripts.", + "config.npm.packageManager.auto": "Auto-detect which package manager to use for running scripts based on lock files and installed package managers.", "config.npm.exclude": "Configure glob patterns for folders that should be excluded from automatic script detection.", "config.npm.enableScriptExplorer": "Enable an explorer view for npm scripts when there is no top-level 'package.json' file.", "config.npm.scriptExplorerAction": "The default click action used in the npm scripts explorer: `open` or `run`, the default is `open`.", diff --git a/extensions/npm/src/commands.ts b/extensions/npm/src/commands.ts index 7ca92879f..4f9131a44 100644 --- a/extensions/npm/src/commands.ts +++ b/extensions/npm/src/commands.ts @@ -15,7 +15,7 @@ import { const localize = nls.loadMessageBundle(); -export function runSelectedScript() { +export function runSelectedScript(context: vscode.ExtensionContext) { let editor = vscode.window.activeTextEditor; if (!editor) { return; @@ -27,15 +27,15 @@ export function runSelectedScript() { let script = findScriptAtPosition(contents, offset); if (script) { - runScript(script, document); + runScript(context, script, document); } else { let message = localize('noScriptFound', 'Could not find a valid npm script at the selection.'); vscode.window.showErrorMessage(message); } } -export async function selectAndRunScriptFromFolder(selectedFolder: vscode.Uri) { - let taskList: FolderTaskItem[] = await detectNpmScriptsForFolder(selectedFolder); +export async function selectAndRunScriptFromFolder(context: vscode.ExtensionContext, selectedFolder: vscode.Uri) { + let taskList: FolderTaskItem[] = await detectNpmScriptsForFolder(context, selectedFolder); if (taskList && taskList.length > 0) { const quickPick = vscode.window.createQuickPick(); diff --git a/extensions/npm/src/features/bowerJSONContribution.ts b/extensions/npm/src/features/bowerJSONContribution.ts index c3a827fd1..4fb0f0633 100644 --- a/extensions/npm/src/features/bowerJSONContribution.ts +++ b/extensions/npm/src/features/bowerJSONContribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { MarkdownString, CompletionItemKind, CompletionItem, DocumentSelector, SnippetString, workspace } from 'vscode'; +import { MarkdownString, CompletionItemKind, CompletionItem, DocumentSelector, SnippetString, workspace, Uri } from 'vscode'; import { IJSONContribution, ISuggestionsCollector } from './jsonContributions'; import { XHRRequest } from 'request-light'; import { Location } from 'jsonc-parser'; @@ -37,7 +37,7 @@ export class BowerJSONContribution implements IJSONContribution { return !!workspace.getConfiguration('npm').get('fetchOnlinePackageInfo'); } - public collectDefaultSuggestions(_resource: string, collector: ISuggestionsCollector): Thenable { + public collectDefaultSuggestions(_resource: Uri, collector: ISuggestionsCollector): Thenable { const defaultValue = { 'name': '${1:name}', 'description': '${2:description}', @@ -53,7 +53,7 @@ export class BowerJSONContribution implements IJSONContribution { return Promise.resolve(null); } - public collectPropertySuggestions(_resource: string, location: Location, currentWord: string, addValue: boolean, isLast: boolean, collector: ISuggestionsCollector): Thenable | null { + public collectPropertySuggestions(_resource: Uri, location: Location, currentWord: string, addValue: boolean, isLast: boolean, collector: ISuggestionsCollector): Thenable | null { if (!this.isEnabled()) { return null; } @@ -125,7 +125,7 @@ export class BowerJSONContribution implements IJSONContribution { return null; } - public collectValueSuggestions(_resource: string, location: Location, collector: ISuggestionsCollector): Promise | null { + public collectValueSuggestions(_resource: Uri, location: Location, collector: ISuggestionsCollector): Promise | null { if (!this.isEnabled()) { return null; } @@ -141,7 +141,7 @@ export class BowerJSONContribution implements IJSONContribution { return null; } - public resolveSuggestion(item: CompletionItem): Thenable | null { + public resolveSuggestion(_resource: Uri | undefined, item: CompletionItem): Thenable | null { if (item.kind === CompletionItemKind.Property && item.documentation === '') { return this.getInfo(item.label).then(documentation => { if (documentation) { @@ -182,7 +182,7 @@ export class BowerJSONContribution implements IJSONContribution { }); } - public getInfoContribution(_resource: string, location: Location): Thenable | null { + public getInfoContribution(_resource: Uri, location: Location): Thenable | null { if (!this.isEnabled()) { return null; } diff --git a/extensions/npm/src/features/jsonContributions.ts b/extensions/npm/src/features/jsonContributions.ts index 071d57b33..3954c2351 100644 --- a/extensions/npm/src/features/jsonContributions.ts +++ b/extensions/npm/src/features/jsonContributions.ts @@ -4,14 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { Location, getLocation, createScanner, SyntaxKind, ScanError, JSONScanner } from 'jsonc-parser'; -import { basename } from 'path'; import { BowerJSONContribution } from './bowerJSONContribution'; import { PackageJSONContribution } from './packageJSONContribution'; import { XHRRequest } from 'request-light'; import { CompletionItem, CompletionItemProvider, CompletionList, TextDocument, Position, Hover, HoverProvider, - CancellationToken, Range, MarkedString, DocumentSelector, languages, Disposable + CancellationToken, Range, MarkedString, DocumentSelector, languages, Disposable, Uri } from 'vscode'; export interface ISuggestionsCollector { @@ -23,11 +22,11 @@ export interface ISuggestionsCollector { export interface IJSONContribution { getDocumentSelector(): DocumentSelector; - getInfoContribution(fileName: string, location: Location): Thenable | null; - collectPropertySuggestions(fileName: string, location: Location, currentWord: string, addValue: boolean, isLast: boolean, result: ISuggestionsCollector): Thenable | null; - collectValueSuggestions(fileName: string, location: Location, result: ISuggestionsCollector): Thenable | null; - collectDefaultSuggestions(fileName: string, result: ISuggestionsCollector): Thenable; - resolveSuggestion?(item: CompletionItem): Thenable | null; + getInfoContribution(resourceUri: Uri, location: Location): Thenable | null; + collectPropertySuggestions(resourceUri: Uri, location: Location, currentWord: string, addValue: boolean, isLast: boolean, result: ISuggestionsCollector): Thenable | null; + collectValueSuggestions(resourceUri: Uri, location: Location, result: ISuggestionsCollector): Thenable | null; + collectDefaultSuggestions(resourceUri: Uri, result: ISuggestionsCollector): Thenable; + resolveSuggestion?(resourceUri: Uri | undefined, item: CompletionItem): Thenable | null; } export function addJSONProviders(xhr: XHRRequest, canRunNPM: boolean): Disposable { @@ -47,7 +46,6 @@ export class JSONHoverProvider implements HoverProvider { } public provideHover(document: TextDocument, position: Position, _token: CancellationToken): Thenable | null { - const fileName = basename(document.fileName); const offset = document.offsetAt(position); const location = getLocation(document.getText(), offset); if (!location.previousNode) { @@ -55,7 +53,7 @@ export class JSONHoverProvider implements HoverProvider { } const node = location.previousNode; if (node && node.offset <= offset && offset <= node.offset + node.length) { - const promise = this.jsonContribution.getInfoContribution(fileName, location); + const promise = this.jsonContribution.getInfoContribution(document.uri, location); if (promise) { return promise.then(htmlContent => { const range = new Range(document.positionAt(node.offset), document.positionAt(node.offset + node.length)); @@ -73,12 +71,14 @@ export class JSONHoverProvider implements HoverProvider { export class JSONCompletionItemProvider implements CompletionItemProvider { + private lastResource: Uri | undefined; + constructor(private jsonContribution: IJSONContribution) { } public resolveCompletionItem(item: CompletionItem, _token: CancellationToken): Thenable { if (this.jsonContribution.resolveSuggestion) { - const resolver = this.jsonContribution.resolveSuggestion(item); + const resolver = this.jsonContribution.resolveSuggestion(this.lastResource, item); if (resolver) { return resolver; } @@ -87,8 +87,8 @@ export class JSONCompletionItemProvider implements CompletionItemProvider { } public provideCompletionItems(document: TextDocument, position: Position, _token: CancellationToken): Thenable | null { + this.lastResource = document.uri; - const fileName = basename(document.fileName); const currentWord = this.getCurrentWord(document, position); let overwriteRange: Range; @@ -126,12 +126,12 @@ export class JSONCompletionItemProvider implements CompletionItemProvider { const scanner = createScanner(document.getText(), true); const addValue = !location.previousNode || !this.hasColonAfter(scanner, location.previousNode.offset + location.previousNode.length); const isLast = this.isLast(scanner, document.offsetAt(position)); - collectPromise = this.jsonContribution.collectPropertySuggestions(fileName, location, currentWord, addValue, isLast, collector); + collectPromise = this.jsonContribution.collectPropertySuggestions(document.uri, location, currentWord, addValue, isLast, collector); } else { if (location.path.length === 0) { - collectPromise = this.jsonContribution.collectDefaultSuggestions(fileName, collector); + collectPromise = this.jsonContribution.collectDefaultSuggestions(document.uri, collector); } else { - collectPromise = this.jsonContribution.collectValueSuggestions(fileName, location, collector); + collectPromise = this.jsonContribution.collectValueSuggestions(document.uri, location, collector); } } if (collectPromise) { diff --git a/extensions/npm/src/features/packageJSONContribution.ts b/extensions/npm/src/features/packageJSONContribution.ts index 7fc5475ab..3249cb765 100644 --- a/extensions/npm/src/features/packageJSONContribution.ts +++ b/extensions/npm/src/features/packageJSONContribution.ts @@ -3,13 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { MarkedString, CompletionItemKind, CompletionItem, DocumentSelector, SnippetString, workspace, MarkdownString } from 'vscode'; +import { MarkedString, CompletionItemKind, CompletionItem, DocumentSelector, SnippetString, workspace, MarkdownString, Uri } from 'vscode'; import { IJSONContribution, ISuggestionsCollector } from './jsonContributions'; import { XHRRequest } from 'request-light'; import { Location } from 'jsonc-parser'; import * as cp from 'child_process'; import * as nls from 'vscode-nls'; +import { dirname } from 'path'; const localize = nls.loadMessageBundle(); const LIMIT = 40; @@ -35,7 +36,7 @@ export class PackageJSONContribution implements IJSONContribution { public constructor(private xhr: XHRRequest, private canRunNPM: boolean) { } - public collectDefaultSuggestions(_fileName: string, result: ISuggestionsCollector): Thenable { + public collectDefaultSuggestions(_resource: Uri, result: ISuggestionsCollector): Thenable { const defaultValue = { 'name': '${1:name}', 'description': '${2:description}', @@ -60,7 +61,7 @@ export class PackageJSONContribution implements IJSONContribution { } public collectPropertySuggestions( - _resource: string, + _resource: Uri, location: Location, currentWord: string, addValue: boolean, @@ -183,7 +184,7 @@ export class PackageJSONContribution implements IJSONContribution { return Promise.resolve(null); } - public async collectValueSuggestions(_fileName: string, location: Location, result: ISuggestionsCollector): Promise { + public async collectValueSuggestions(resource: Uri, location: Location, result: ISuggestionsCollector): Promise { if (!this.isEnabled()) { return null; } @@ -191,7 +192,7 @@ export class PackageJSONContribution implements IJSONContribution { if ((location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']) || location.matches(['optionalDependencies', '*']) || location.matches(['peerDependencies', '*']))) { const currentKey = location.path[location.path.length - 1]; if (typeof currentKey === 'string') { - const info = await this.fetchPackageInfo(currentKey); + const info = await this.fetchPackageInfo(currentKey, resource); if (info && info.version) { let name = JSON.stringify(info.version); @@ -236,9 +237,9 @@ export class PackageJSONContribution implements IJSONContribution { return str; } - public resolveSuggestion(item: CompletionItem): Thenable | null { + public resolveSuggestion(resource: Uri | undefined, item: CompletionItem): Thenable | null { if (item.kind === CompletionItemKind.Property && !item.documentation) { - return this.fetchPackageInfo(item.label).then(info => { + return this.fetchPackageInfo(item.label, resource).then(info => { if (info) { item.documentation = this.getDocumentation(info.description, info.version, info.homepage); return item; @@ -266,13 +267,13 @@ export class PackageJSONContribution implements IJSONContribution { return false; } - private async fetchPackageInfo(pack: string): Promise { + private async fetchPackageInfo(pack: string, resource: Uri | undefined): Promise { if (!this.isValidNPMName(pack)) { return undefined; // avoid unnecessary lookups } let info: ViewPackageInfo | undefined; if (this.canRunNPM) { - info = await this.npmView(pack); + info = await this.npmView(pack, resource); } if (!info && this.onlineEnabled()) { info = await this.npmjsView(pack); @@ -280,10 +281,11 @@ export class PackageJSONContribution implements IJSONContribution { return info; } - private npmView(pack: string): Promise { + private npmView(pack: string, resource: Uri | undefined): Promise { return new Promise((resolve, _reject) => { const args = ['view', '--json', pack, 'description', 'dist-tags.latest', 'homepage', 'version']; - cp.execFile(process.platform === 'win32' ? 'npm.cmd' : 'npm', args, (error, stdout) => { + let cwd = resource && resource.scheme === 'file' ? dirname(resource.fsPath) : undefined; + cp.execFile(process.platform === 'win32' ? 'npm.cmd' : 'npm', args, { cwd }, (error, stdout) => { if (!error) { try { const content = JSON.parse(stdout); @@ -325,14 +327,14 @@ export class PackageJSONContribution implements IJSONContribution { return undefined; } - public getInfoContribution(_fileName: string, location: Location): Thenable | null { + public getInfoContribution(resource: Uri, location: Location): Thenable | null { if (!this.isEnabled()) { return null; } if ((location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']) || location.matches(['optionalDependencies', '*']) || location.matches(['peerDependencies', '*']))) { const pack = location.path[location.path.length - 1]; if (typeof pack === 'string') { - return this.fetchPackageInfo(pack).then(info => { + return this.fetchPackageInfo(pack, resource).then(info => { if (info) { return [this.getDocumentation(info.description, info.version, info.homepage)]; } diff --git a/extensions/npm/src/npmMain.ts b/extensions/npm/src/npmMain.ts index 67cdf56c2..568c5ea3d 100644 --- a/extensions/npm/src/npmMain.ts +++ b/extensions/npm/src/npmMain.ts @@ -31,6 +31,7 @@ export async function activate(context: vscode.ExtensionContext): Promise const canRunNPM = canRunNpmInCurrentWorkspace(); context.subscriptions.push(addJSONProviders(httpRequest.xhr, canRunNPM)); + registerTaskProvider(context); treeDataProvider = registerExplorer(context); @@ -48,7 +49,6 @@ export async function activate(context: vscode.ExtensionContext): Promise } })); - registerTaskProvider(context); registerHoverProvider(context); context.subscriptions.push(vscode.commands.registerCommand('npm.runSelectedScript', runSelectedScript)); @@ -58,7 +58,7 @@ export async function activate(context: vscode.ExtensionContext): Promise })); context.subscriptions.push(vscode.commands.registerCommand('npm.packageManager', (args) => { if (args instanceof vscode.Uri) { - return getPackageManager(args); + return getPackageManager(context, args); } return ''; })); @@ -71,6 +71,7 @@ function canRunNpmInCurrentWorkspace() { return false; } +let taskProvider: NpmTaskProvider; function registerTaskProvider(context: vscode.ExtensionContext): vscode.Disposable | undefined { if (vscode.workspace.workspaceFolders) { let watcher = vscode.workspace.createFileSystemWatcher('**/package.json'); @@ -82,8 +83,8 @@ function registerTaskProvider(context: vscode.ExtensionContext): vscode.Disposab let workspaceWatcher = vscode.workspace.onDidChangeWorkspaceFolders((_e) => invalidateScriptCaches()); context.subscriptions.push(workspaceWatcher); - let provider: vscode.TaskProvider = new NpmTaskProvider(); - let disposable = vscode.tasks.registerTaskProvider('npm', provider); + taskProvider = new NpmTaskProvider(context); + let disposable = vscode.tasks.registerTaskProvider('npm', taskProvider); context.subscriptions.push(disposable); return disposable; } @@ -92,7 +93,7 @@ function registerTaskProvider(context: vscode.ExtensionContext): vscode.Disposab function registerExplorer(context: vscode.ExtensionContext): NpmScriptsTreeDataProvider | undefined { if (vscode.workspace.workspaceFolders) { - let treeDataProvider = new NpmScriptsTreeDataProvider(context); + let treeDataProvider = new NpmScriptsTreeDataProvider(context, taskProvider!); const view = vscode.window.createTreeView('npm', { treeDataProvider: treeDataProvider, showCollapseAll: true }); context.subscriptions.push(view); return treeDataProvider; diff --git a/extensions/npm/src/npmView.ts b/extensions/npm/src/npmView.ts index c1b5f6fdd..8617b7f7f 100644 --- a/extensions/npm/src/npmView.ts +++ b/extensions/npm/src/npmView.ts @@ -7,14 +7,18 @@ import { JSONVisitor, visit } from 'jsonc-parser'; import * as path from 'path'; import { commands, Event, EventEmitter, ExtensionContext, + Range, Selection, Task, - TaskGroup, tasks, TextDocument, ThemeIcon, TreeDataProvider, TreeItem, TreeItemCollapsibleState, Uri, + TaskGroup, tasks, TextDocument, TextDocumentShowOptions, ThemeIcon, TreeDataProvider, TreeItem, TreeItemCollapsibleState, Uri, window, workspace, WorkspaceFolder } from 'vscode'; import * as nls from 'vscode-nls'; import { createTask, getTaskName, isAutoDetectionEnabled, isWorkspaceFolder, NpmTaskDefinition, - startDebugging + NpmTaskProvider, + startDebugging, + TaskLocation, + TaskWithLocation } from './tasks'; const localize = nls.loadMessageBundle(); @@ -43,7 +47,7 @@ class PackageJSON extends TreeItem { folder: Folder; scripts: NpmScript[] = []; - static getLabel(_folderName: string, relativePath: string): string { + static getLabel(relativePath: string): string { if (relativePath.length > 0) { return path.join(relativePath, packageName); } @@ -51,7 +55,7 @@ class PackageJSON extends TreeItem { } constructor(folder: Folder, relativePath: string) { - super(PackageJSON.getLabel(folder.label!, relativePath), TreeItemCollapsibleState.Expanded); + super(PackageJSON.getLabel(relativePath), TreeItemCollapsibleState.Expanded); this.folder = folder; this.path = relativePath; this.contextValue = 'packageJSON'; @@ -74,15 +78,20 @@ class NpmScript extends TreeItem { task: Task; package: PackageJSON; - constructor(_context: ExtensionContext, packageJson: PackageJSON, task: Task) { + constructor(_context: ExtensionContext, packageJson: PackageJSON, task: Task, public taskLocation?: TaskLocation) { super(task.name, TreeItemCollapsibleState.None); const command: ExplorerCommands = workspace.getConfiguration('npm').get('scriptExplorerAction') || 'open'; const commandList = { 'open': { title: 'Edit Script', - command: 'npm.openScript', - arguments: [this] + command: 'vscode.open', + arguments: [ + taskLocation?.document, + taskLocation ? { + selection: new Range(taskLocation.line, taskLocation.line) + } : undefined + ] }, 'run': { title: 'Run Script', @@ -123,7 +132,7 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider { private _onDidChangeTreeData: EventEmitter = new EventEmitter(); readonly onDidChangeTreeData: Event = this._onDidChangeTreeData.event; - constructor(context: ExtensionContext) { + constructor(context: ExtensionContext, public taskProvider: NpmTaskProvider) { const subscriptions = context.subscriptions; this.extensionContext = context; subscriptions.push(commands.registerCommand('npm.runScript', this.runScript, this)); @@ -137,7 +146,7 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider { } private async debugScript(script: NpmScript) { - startDebugging(script.task.definition.script, path.dirname(script.package.resourceUri!.fsPath), script.getFolder()); + startDebugging(this.extensionContext, script.task.definition.script, path.dirname(script.package.resourceUri!.fsPath), script.getFolder()); } private findScript(document: TextDocument, script?: NpmScript): number { @@ -181,7 +190,7 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider { if (!uri) { return; } - let task = createTask('install', 'install', selection.folder.workspaceFolder, uri, undefined, []); + let task = await createTask(this.extensionContext, 'install', 'install', selection.folder.workspaceFolder, uri, undefined, []); tasks.executeTask(task); } @@ -228,7 +237,7 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider { async getChildren(element?: TreeItem): Promise { if (!this.taskTree) { - let taskItems = await tasks.fetchTasks({ type: 'npm' }); + const taskItems = await this.taskProvider.tasksWithLocation; if (taskItems) { this.taskTree = this.buildTaskTree(taskItems); if (this.taskTree.length === 0) { @@ -265,7 +274,7 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider { return fullName === task.name; } - private buildTaskTree(tasks: Task[]): Folder[] | PackageJSON[] | NoScripts[] { + private buildTaskTree(tasks: TaskWithLocation[]): Folder[] | PackageJSON[] | NoScripts[] { let folders: Map = new Map(); let packages: Map = new Map(); @@ -273,22 +282,22 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider { let packageJson = null; tasks.forEach(each => { - if (isWorkspaceFolder(each.scope) && !this.isInstallTask(each)) { - folder = folders.get(each.scope.name); + if (isWorkspaceFolder(each.task.scope) && !this.isInstallTask(each.task)) { + folder = folders.get(each.task.scope.name); if (!folder) { - folder = new Folder(each.scope); - folders.set(each.scope.name, folder); + folder = new Folder(each.task.scope); + folders.set(each.task.scope.name, folder); } - let definition: NpmTaskDefinition = each.definition; + let definition: NpmTaskDefinition = each.task.definition; let relativePath = definition.path ? definition.path : ''; - let fullPath = path.join(each.scope.name, relativePath); + let fullPath = path.join(each.task.scope.name, relativePath); packageJson = packages.get(fullPath); if (!packageJson) { packageJson = new PackageJSON(folder, relativePath); folder.addPackage(packageJson); packages.set(fullPath, packageJson); } - let script = new NpmScript(this.extensionContext, packageJson, each); + let script = new NpmScript(this.extensionContext, packageJson, each.task, each.location); packageJson.addScript(script); } }); diff --git a/extensions/npm/src/preferred-pm.ts b/extensions/npm/src/preferred-pm.ts new file mode 100644 index 000000000..f9582bc70 --- /dev/null +++ b/extensions/npm/src/preferred-pm.ts @@ -0,0 +1,80 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import findWorkspaceRoot = require('../node_modules/find-yarn-workspace-root'); +import findUp = require('find-up'); +import * as path from 'path'; +import whichPM = require('which-pm'); +import { Uri, workspace } from 'vscode'; + +async function pathExists(filePath: string) { + try { + await workspace.fs.stat(Uri.file(filePath)); + } catch { + return false; + } + return true; +} + +async function isPNPMPreferred(pkgPath: string) { + if (await pathExists(path.join(pkgPath, 'pnpm-lock.yaml'))) { + return true; + } + if (await pathExists(path.join(pkgPath, 'shrinkwrap.yaml'))) { + return true; + } + if (await findUp('pnpm-lock.yaml', { cwd: pkgPath })) { + return true; + } + + return false; +} + +async function isYarnPreferred(pkgPath: string) { + if (await pathExists(path.join(pkgPath, 'yarn.lock'))) { + return true; + } + + try { + if (typeof findWorkspaceRoot(pkgPath) === 'string') { + return true; + } + } catch (err) { } + + return false; +} + +const isNPMPreferred = (pkgPath: string) => { + return pathExists(path.join(pkgPath, 'package-lock.json')); +}; + +export async function findPreferredPM(pkgPath: string): Promise<{ name: string, multiplePMDetected: boolean }> { + const detectedPackageManagers: string[] = []; + + if (await isNPMPreferred(pkgPath)) { + detectedPackageManagers.push('npm'); + } + + if (await isYarnPreferred(pkgPath)) { + detectedPackageManagers.push('yarn'); + } + + if (await isPNPMPreferred(pkgPath)) { + detectedPackageManagers.push('pnpm'); + } + + const pmUsedForInstallation: { name: string } | null = await whichPM(pkgPath); + + if (pmUsedForInstallation && !detectedPackageManagers.includes(pmUsedForInstallation.name)) { + detectedPackageManagers.push(pmUsedForInstallation.name); + } + + const multiplePMDetected = detectedPackageManagers.length > 1; + + return { + name: detectedPackageManagers[0] || 'npm', + multiplePMDetected + }; +} diff --git a/extensions/npm/src/scriptHover.ts b/extensions/npm/src/scriptHover.ts index b1edc2752..48482660f 100644 --- a/extensions/npm/src/scriptHover.ts +++ b/extensions/npm/src/scriptHover.ts @@ -30,7 +30,7 @@ export function invalidateHoverScriptsCache(document?: TextDocument) { export class NpmScriptHoverProvider implements HoverProvider { - constructor(context: ExtensionContext) { + constructor(private context: ExtensionContext) { context.subscriptions.push(commands.registerCommand('npm.runScriptFromHover', this.runScriptFromHover, this)); context.subscriptions.push(commands.registerCommand('npm.debugScriptFromHover', this.debugScriptFromHover, this)); context.subscriptions.push(workspace.onDidChangeTextDocument((e) => { @@ -98,13 +98,13 @@ export class NpmScriptHoverProvider implements HoverProvider { return `${prefix}[${label}](command:${cmd}?${encodedArgs} "${tooltip}")`; } - public runScriptFromHover(args: any) { + public async runScriptFromHover(args: any) { let script = args.script; let documentUri = args.documentUri; let folder = workspace.getWorkspaceFolder(documentUri); if (folder) { - let task = createTask(script, `run ${script}`, folder, documentUri); - tasks.executeTask(task); + let task = await createTask(this.context, script, `run ${script}`, folder, documentUri); + await tasks.executeTask(task); } } @@ -113,7 +113,7 @@ export class NpmScriptHoverProvider implements HoverProvider { let documentUri = args.documentUri; let folder = workspace.getWorkspaceFolder(documentUri); if (folder) { - startDebugging(script, dirname(documentUri.fsPath), folder); + startDebugging(this.context, script, dirname(documentUri.fsPath), folder); } } } diff --git a/extensions/npm/src/tasks.ts b/extensions/npm/src/tasks.ts index 02075cf12..fac16a396 100644 --- a/extensions/npm/src/tasks.ts +++ b/extensions/npm/src/tasks.ts @@ -5,13 +5,14 @@ import { TaskDefinition, Task, TaskGroup, WorkspaceFolder, RelativePattern, ShellExecution, Uri, workspace, - DebugConfiguration, debug, TaskProvider, TextDocument, tasks, TaskScope, QuickPickItem + DebugConfiguration, debug, TaskProvider, TextDocument, tasks, TaskScope, QuickPickItem, window, Position, ExtensionContext, env } from 'vscode'; import * as path from 'path'; import * as fs from 'fs'; import * as minimatch from 'minimatch'; import * as nls from 'vscode-nls'; import { JSONVisitor, visit, ParseErrorCode } from 'jsonc-parser'; +import { findPreferredPM } from './preferred-pm'; const localize = nls.loadMessageBundle(); @@ -27,20 +28,35 @@ export interface FolderTaskItem extends QuickPickItem { type AutoDetect = 'on' | 'off'; -let cachedTasks: Task[] | undefined = undefined; +let cachedTasks: TaskWithLocation[] | undefined = undefined; const INSTALL_SCRIPT = 'install'; +export interface TaskLocation { + document: Uri, + line: Position +} + +export interface TaskWithLocation { + task: Task, + location?: TaskLocation +} + export class NpmTaskProvider implements TaskProvider { - constructor() { + constructor(private context: ExtensionContext) { } - public provideTasks() { - return provideNpmScripts(); + get tasksWithLocation(): Promise { + return provideNpmScripts(this.context); } - public resolveTask(_task: Task): Task | undefined { + public async provideTasks() { + const tasks = await provideNpmScripts(this.context); + return tasks.map(task => task.task); + } + + public resolveTask(_task: Task): Promise | undefined { const npmTask = (_task.definition).script; if (npmTask) { const kind: NpmTaskDefinition = (_task.definition); @@ -54,7 +70,7 @@ export class NpmTaskProvider implements TaskProvider { } else { packageJsonUri = _task.scope.uri.with({ path: _task.scope.uri.path + '/package.json' }); } - return createTask(kind, `${kind.script === INSTALL_SCRIPT ? '' : 'run '}${kind.script}`, _task.scope, packageJsonUri); + return createTask(this.context, kind, `${kind.script === INSTALL_SCRIPT ? '' : 'run '}${kind.script}`, _task.scope, packageJsonUri); } return undefined; } @@ -107,8 +123,27 @@ export function isWorkspaceFolder(value: any): value is WorkspaceFolder { return value && typeof value !== 'number'; } -export function getPackageManager(folder: Uri): string { - return workspace.getConfiguration('npm', folder).get('packageManager', 'npm'); +export async function getPackageManager(extensionContext: ExtensionContext, folder: Uri): Promise { + let packageManagerName = workspace.getConfiguration('npm', folder).get('packageManager', 'npm'); + + if (packageManagerName === 'auto') { + const { name, multiplePMDetected } = await findPreferredPM(folder.fsPath); + packageManagerName = name; + const neverShowWarning = 'npm.multiplePMWarning.neverShow'; + if (multiplePMDetected && !extensionContext.globalState.get(neverShowWarning)) { + const multiplePMWarning = localize('npm.multiplePMWarning', 'Using {0} as the preferred package manager. Found multiple lockfiles for {1}.', packageManagerName, folder.fsPath); + const neverShowAgain = localize('npm.multiplePMWarning.doNotShow', "Do not show again"); + const learnMore = localize('npm.multiplePMWarning.learnMore', "Learn more"); + window.showInformationMessage(multiplePMWarning, learnMore, neverShowAgain).then(result => { + switch (result) { + case neverShowAgain: extensionContext.globalState.update(neverShowWarning, true); break; + case learnMore: env.openExternal(Uri.parse('https://nodejs.dev/learn/the-package-lock-json-file')); + } + }); + } + } + + return packageManagerName; } export async function hasNpmScripts(): Promise { @@ -132,10 +167,10 @@ export async function hasNpmScripts(): Promise { } } -async function detectNpmScripts(): Promise { +async function detectNpmScripts(context: ExtensionContext): Promise { - let emptyTasks: Task[] = []; - let allTasks: Task[] = []; + let emptyTasks: TaskWithLocation[] = []; + let allTasks: TaskWithLocation[] = []; let visitedPackageJsonFiles: Set = new Set(); let folders = workspace.workspaceFolders; @@ -149,7 +184,7 @@ async function detectNpmScripts(): Promise { let paths = await workspace.findFiles(relativePattern, '**/{node_modules,.vscode-test}/**'); for (const path of paths) { if (!isExcluded(folder, path) && !visitedPackageJsonFiles.has(path.fsPath)) { - let tasks = await provideNpmScriptsForFolder(path); + let tasks = await provideNpmScriptsForFolder(context, path); visitedPackageJsonFiles.add(path.fsPath); allTasks.push(...tasks); } @@ -163,7 +198,7 @@ async function detectNpmScripts(): Promise { } -export async function detectNpmScriptsForFolder(folder: Uri): Promise { +export async function detectNpmScriptsForFolder(context: ExtensionContext, folder: Uri): Promise { let folderTasks: FolderTaskItem[] = []; @@ -174,9 +209,9 @@ export async function detectNpmScriptsForFolder(folder: Uri): Promise = new Set(); for (const path of paths) { if (!visitedPackageJsonFiles.has(path.fsPath)) { - let tasks = await provideNpmScriptsForFolder(path); + let tasks = await provideNpmScriptsForFolder(context, path); visitedPackageJsonFiles.add(path.fsPath); - folderTasks.push(...tasks.map(t => ({ label: t.name, task: t }))); + folderTasks.push(...tasks.map(t => ({ label: t.task.name, task: t.task }))); } } return folderTasks; @@ -185,9 +220,9 @@ export async function detectNpmScriptsForFolder(folder: Uri): Promise { +export async function provideNpmScripts(context: ExtensionContext): Promise { if (!cachedTasks) { - cachedTasks = await detectNpmScripts(); + cachedTasks = await detectNpmScripts(context); } return cachedTasks; } @@ -223,8 +258,8 @@ function isDebugScript(script: string): boolean { return match !== null; } -async function provideNpmScriptsForFolder(packageJsonUri: Uri): Promise { - let emptyTasks: Task[] = []; +async function provideNpmScriptsForFolder(context: ExtensionContext, packageJsonUri: Uri): Promise { + let emptyTasks: TaskWithLocation[] = []; let folder = workspace.getWorkspaceFolder(packageJsonUri); if (!folder) { @@ -235,11 +270,13 @@ async function provideNpmScriptsForFolder(packageJsonUri: Uri): Promise return emptyTasks; } - const result: Task[] = []; + const result: TaskWithLocation[] = []; const prePostScripts = getPrePostScripts(scripts); - Object.keys(scripts).forEach(each => { - const task = createTask(each, `run ${each}`, folder!, packageJsonUri, scripts![each]); + + for (const each of scripts.keys()) { + const scriptValue = scripts.get(each)!; + const task = await createTask(context, each, `run ${each}`, folder!, packageJsonUri, scriptValue.script); const lowerCaseTaskName = each.toLowerCase(); if (isBuildTask(lowerCaseTaskName)) { task.group = TaskGroup.Build; @@ -251,13 +288,14 @@ async function provideNpmScriptsForFolder(packageJsonUri: Uri): Promise } // todo@connor4312: all scripts are now debuggable, what is a 'debug script'? - if (isDebugScript(scripts![each])) { + if (isDebugScript(scriptValue.script)) { task.group = TaskGroup.Rebuild; // hack: use Rebuild group to tag debug scripts } - result.push(task); - }); + result.push({ task, location: scriptValue.location }); + } + // always add npm install (without a problem matcher) - result.push(createTask(INSTALL_SCRIPT, INSTALL_SCRIPT, folder, packageJsonUri, 'install dependencies from package', [])); + result.push({ task: await createTask(context, INSTALL_SCRIPT, INSTALL_SCRIPT, folder, packageJsonUri, 'install dependencies from package', []) }); return result; } @@ -268,7 +306,7 @@ export function getTaskName(script: string, relativePath: string | undefined) { return script; } -export function createTask(script: NpmTaskDefinition | string, cmd: string, folder: WorkspaceFolder, packageJsonUri: Uri, detail?: string, matcher?: any): Task { +export async function createTask(context: ExtensionContext, script: NpmTaskDefinition | string, cmd: string, folder: WorkspaceFolder, packageJsonUri: Uri, detail?: string, matcher?: any): Promise { let kind: NpmTaskDefinition; if (typeof script === 'string') { kind = { type: 'npm', script: script }; @@ -276,27 +314,27 @@ export function createTask(script: NpmTaskDefinition | string, cmd: string, fold kind = script; } - function getCommandLine(folder: WorkspaceFolder, cmd: string): string { - let packageManager = getPackageManager(folder.uri); + const packageManager = await getPackageManager(context, folder.uri); + async function getCommandLine(cmd: string): Promise { if (workspace.getConfiguration('npm', folder.uri).get('runSilent')) { return `${packageManager} --silent ${cmd}`; } return `${packageManager} ${cmd}`; } - function getRelativePath(folder: WorkspaceFolder, packageJsonUri: Uri): string { + function getRelativePath(packageJsonUri: Uri): string { let rootUri = folder.uri; let absolutePath = packageJsonUri.path.substring(0, packageJsonUri.path.length - 'package.json'.length); return absolutePath.substring(rootUri.path.length + 1); } - let relativePackageJson = getRelativePath(folder, packageJsonUri); + let relativePackageJson = getRelativePath(packageJsonUri); if (relativePackageJson.length) { - kind.path = getRelativePath(folder, packageJsonUri); + kind.path = relativePackageJson; } let taskName = getTaskName(kind.script, relativePackageJson); let cwd = path.dirname(packageJsonUri.fsPath); - const task = new Task(kind, folder, taskName, 'npm', new ShellExecution(getCommandLine(folder, cmd), { cwd: cwd }), matcher); + const task = new Task(kind, folder, taskName, 'npm', new ShellExecution(await getCommandLine(cmd), { cwd: cwd }), matcher); task.detail = detail; return task; } @@ -337,33 +375,22 @@ async function exists(file: string): Promise { }); } -async function readFile(file: string): Promise { - return new Promise((resolve, reject) => { - fs.readFile(file, (err, data) => { - if (err) { - reject(err); - } - resolve(data.toString()); - }); - }); -} - -export function runScript(script: string, document: TextDocument) { +export async function runScript(context: ExtensionContext, script: string, document: TextDocument) { let uri = document.uri; let folder = workspace.getWorkspaceFolder(uri); if (folder) { - let task = createTask(script, `run ${script}`, folder, uri); + let task = await createTask(context, script, `run ${script}`, folder, uri); tasks.executeTask(task); } } -export function startDebugging(scriptName: string, cwd: string, folder: WorkspaceFolder) { +export async function startDebugging(context: ExtensionContext, scriptName: string, cwd: string, folder: WorkspaceFolder) { const config: DebugConfiguration = { type: 'pwa-node', request: 'launch', name: `Debug ${scriptName}`, cwd, - runtimeExecutable: getPackageManager(folder.uri), + runtimeExecutable: await getPackageManager(context, folder.uri), runtimeArgs: [ 'run', scriptName, @@ -378,10 +405,11 @@ export function startDebugging(scriptName: string, cwd: string, folder: Workspac export type StringMap = { [s: string]: string; }; -async function findAllScripts(buffer: string): Promise { - let scripts: StringMap = {}; +async function findAllScripts(document: TextDocument, buffer: string): Promise> { + let scripts: Map = new Map(); let script: string | undefined = undefined; let inScripts = false; + let scriptOffset = 0; let visitor: JSONVisitor = { onError(_error: ParseErrorCode, _offset: number, _length: number) { @@ -395,17 +423,18 @@ async function findAllScripts(buffer: string): Promise { onLiteralValue(value: any, _offset: number, _length: number) { if (script) { if (typeof value === 'string') { - scripts[script] = value; + scripts.set(script, { script: value, location: { document: document.uri, line: document.positionAt(scriptOffset) } }); } script = undefined; } }, - onObjectProperty(property: string, _offset: number, _length: number) { + onObjectProperty(property: string, offset: number, _length: number) { if (property === 'scripts') { inScripts = true; } else if (inScripts && !script) { script = property; + scriptOffset = offset; } else { // nested object which is invalid, ignore the script script = undefined; } @@ -493,7 +522,7 @@ export function findScriptAtPosition(buffer: string, offset: number): string | u return foundScript; } -export async function getScripts(packageJsonUri: Uri): Promise { +export async function getScripts(packageJsonUri: Uri): Promise | undefined> { if (packageJsonUri.scheme !== 'file') { return undefined; @@ -505,8 +534,9 @@ export async function getScripts(packageJsonUri: Uri): Promise)", - "decreaseIndentPattern": "^(.*\\*\\/)?\\s*((\\})|(\\)+[;,])|(\\][;,])|\\b(else:)|\\b((end(if|for(each)?|while|switch)|break);))" + "decreaseIndentPattern": "^(.*\\*\\/)?\\s*((\\})|(\\)+[;,])|(\\][;,])|\\b(else:)|\\b((end(if|for(each)?|while|switch));))" }, "folding": { "markers": { diff --git a/extensions/rust/cgmanifest.json b/extensions/rust/cgmanifest.json index 5181efba1..932a3926f 100644 --- a/extensions/rust/cgmanifest.json +++ b/extensions/rust/cgmanifest.json @@ -4,14 +4,14 @@ "component": { "type": "git", "git": { - "name": "language-rust", - "repositoryUrl": "https://github.com/zargony/atom-language-rust", - "commitHash": "7d59e2ad79fbe5925bd2fd3bd3857bf9f421ff6f" + "name": "rust-syntax", + "repositoryUrl": "https://github.com/dustypomerleau/rust-syntax", + "commitHash": "19f9aa86c0850b98db175754f019a2e79413353e" } }, "license": "MIT", - "description": "The files syntaxes/rust.tmLanguage.json was derived from the Atom package https://atom.io/packages/language-rust.", - "version": "0.4.12" + "description": "A TextMate-style grammar for Rust.", + "version": "0.2.13" } ], "version": 1 diff --git a/extensions/rust/package.json b/extensions/rust/package.json index 660783881..431e9fa20 100644 --- a/extensions/rust/package.json +++ b/extensions/rust/package.json @@ -5,21 +5,32 @@ "version": "1.0.0", "publisher": "vscode", "license": "MIT", - "engines": { "vscode": "*" }, + "engines": { + "vscode": "*" + }, "scripts": { - "update-grammar": "node ../../build/npm/update-grammar.js zargony/atom-language-rust grammars/rust.cson ./syntaxes/rust.tmLanguage.json" + "update-grammar": "node ../../build/npm/update-grammar.js dustypomerleau/rust-syntax syntaxes/rust.tmLanguage.json ./syntaxes/rust.tmLanguage.json" }, "contributes": { - "languages": [{ - "id": "rust", - "extensions": [".rs"], - "aliases": ["Rust", "rust"], - "configuration": "./language-configuration.json" - }], - "grammars": [{ - "language": "rust", - "path": "./syntaxes/rust.tmLanguage.json", - "scopeName":"source.rust" - }] + "languages": [ + { + "id": "rust", + "extensions": [ + ".rs" + ], + "aliases": [ + "Rust", + "rust" + ], + "configuration": "./language-configuration.json" + } + ], + "grammars": [ + { + "language": "rust", + "path": "./syntaxes/rust.tmLanguage.json", + "scopeName": "source.rust" + } + ] } -} \ No newline at end of file +} diff --git a/extensions/rust/syntaxes/rust.tmLanguage.json b/extensions/rust/syntaxes/rust.tmLanguage.json index 784bd8c9a..fdced0797 100644 --- a/extensions/rust/syntaxes/rust.tmLanguage.json +++ b/extensions/rust/syntaxes/rust.tmLanguage.json @@ -1,690 +1,1135 @@ { "information_for_contributors": [ - "This file has been converted from https://github.com/zargony/atom-language-rust/blob/master/grammars/rust.cson", + "This file has been converted from https://github.com/dustypomerleau/rust-syntax/blob/master/syntaxes/rust.tmLanguage.json", "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/zargony/atom-language-rust/commit/7d59e2ad79fbe5925bd2fd3bd3857bf9f421ff6f", + "version": "https://github.com/dustypomerleau/rust-syntax/commit/19f9aa86c0850b98db175754f019a2e79413353e", "name": "Rust", "scopeName": "source.rust", "patterns": [ { - "comment": "Implementation", - "begin": "\\b(impl)\\b", - "end": "\\{", + "comment": "boxed slice literal", + "begin": "(<)(\\[)", "beginCaptures": { "1": { - "name": "storage.type.rust" + "name": "punctuation.brackets.angle.rust" + }, + "2": { + "name": "punctuation.brackets.square.rust" + } + }, + "end": ">", + "endCaptures": { + "0": { + "name": "punctuation.brackets.angle.rust" } }, "patterns": [ { - "include": "#block_comment" + "include": "#block-comments" }, { - "include": "#line_comment" + "include": "#comments" }, { - "include": "#sigils" + "include": "#gtypes" }, { - "include": "#mut" + "include": "#lvariables" }, { - "include": "#dyn" + "include": "#lifetimes" }, { - "include": "#ref_lifetime" + "include": "#punctuation" }, { - "include": "#core_types" - }, - { - "include": "#core_marker" - }, - { - "include": "#core_traits" - }, - { - "include": "#std_types" - }, - { - "include": "#std_traits" - }, - { - "include": "#type_params" - }, - { - "include": "#where" - }, - { - "name": "storage.type.rust", - "match": "\\bfor\\b" - }, - { - "include": "#type" + "include": "#types" } ] }, { - "include": "#block_doc_comment" - }, - { - "include": "#block_comment" - }, - { - "include": "#line_doc_comment" - }, - { - "include": "#line_comment" - }, - { - "comment": "Attribute", - "name": "meta.attribute.rust", - "begin": "#\\!?\\[", - "end": "\\]", - "patterns": [ - { - "include": "#string_literal" - }, - { - "include": "#block_doc_comment" - }, - { - "include": "#block_comment" - }, - { - "include": "#line_doc_comment" - }, - { - "include": "#line_comment" - } - ] - }, - { - "comment": "Single-quote string literal (character)", - "name": "string.quoted.single.rust", - "match": "b?'([^'\\\\]|\\\\(x[0-9A-Fa-f]{2}|[0-2][0-7]{0,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.))'" - }, - { - "include": "#string_literal" - }, - { - "include": "#raw_string_literal" - }, - { - "comment": "Floating point literal (fraction)", - "name": "constant.numeric.float.rust", - "match": "\\b[0-9][0-9_]*\\.[0-9][0-9_]*([eE][+-]?[0-9_]+)?(f32|f64)?\\b" - }, - { - "comment": "Floating point literal (exponent)", - "name": "constant.numeric.float.rust", - "match": "\\b[0-9][0-9_]*(\\.[0-9][0-9_]*)?[eE][+-]?[0-9_]+(f32|f64)?\\b" - }, - { - "comment": "Floating point literal (typed)", - "name": "constant.numeric.float.rust", - "match": "\\b[0-9][0-9_]*(\\.[0-9][0-9_]*)?([eE][+-]?[0-9_]+)?(f32|f64)\\b" - }, - { - "comment": "Integer literal (decimal)", - "name": "constant.numeric.integer.decimal.rust", - "match": "\\b[0-9][0-9_]*([ui](8|16|32|64|128|s|size))?\\b" - }, - { - "comment": "Integer literal (hexadecimal)", - "name": "constant.numeric.integer.hexadecimal.rust", - "match": "\\b0x[a-fA-F0-9_]+([ui](8|16|32|64|128|s|size))?\\b" - }, - { - "comment": "Integer literal (octal)", - "name": "constant.numeric.integer.octal.rust", - "match": "\\b0o[0-7_]+([ui](8|16|32|64|128|s|size))?\\b" - }, - { - "comment": "Integer literal (binary)", - "name": "constant.numeric.integer.binary.rust", - "match": "\\b0b[01_]+([ui](8|16|32|64|128|s|size))?\\b" - }, - { - "comment": "Static storage modifier", - "name": "storage.modifier.static.rust", - "match": "\\bstatic\\b" - }, - { - "comment": "Boolean constant", - "name": "constant.language.boolean.rust", - "match": "\\b(true|false)\\b" - }, - { - "comment": "Control keyword", - "name": "keyword.control.rust", - "match": "\\b(async|await|break|continue|else|if|in|for|loop|match|return|try|while)\\b" - }, - { - "comment": "Keyword", - "name": "keyword.other.rust", - "match": "\\b(crate|extern|mod|let|ref|use|super|move)\\b" - }, - { - "comment": "Reserved keyword", - "name": "invalid.deprecated.rust", - "match": "\\b(abstract|alignof|become|do|final|macro|offsetof|override|priv|proc|pure|sizeof|typeof|virtual|yield)\\b" - }, - { - "include": "#unsafe" - }, - { - "include": "#sigils" - }, - { - "include": "#self" - }, - { - "include": "#mut" - }, - { - "include": "#dyn" - }, - { - "include": "#impl" - }, - { - "include": "#box" - }, - { - "include": "#lifetime" - }, - { - "include": "#ref_lifetime" - }, - { - "include": "#const" - }, - { - "include": "#pub" - }, - { - "comment": "Miscellaneous operator", - "name": "keyword.operator.misc.rust", - "match": "(=>|::|\\bas\\b)" - }, - { - "comment": "Comparison operator", - "name": "keyword.operator.comparison.rust", - "match": "(&&|\\|\\||==|!=)" - }, - { - "comment": "Assignment operator", - "name": "keyword.operator.assignment.rust", - "match": "(\\+=|-=|/=|\\*=|%=|\\^=|&=|\\|=|<<=|>>=|=)" - }, - { - "comment": "Arithmetic operator", - "name": "keyword.operator.arithmetic.rust", - "match": "(!|\\+|-|/|\\*|%|\\^|&|\\||<<|>>)" - }, - { - "comment": "Comparison operator (second group because of regex precedence)", - "name": "keyword.operator.comparison.rust", - "match": "(<=|>=|<|>)" - }, - { - "include": "#core_types" - }, - { - "include": "#core_vars" - }, - { - "include": "#core_marker" - }, - { - "include": "#core_traits" - }, - { - "include": "#std_types" - }, - { - "include": "#std_traits" - }, - { - "comment": "Built-in macro", - "name": "support.function.builtin.rust", - "match": "\\b(macro_rules|compile_error|format_args|env|option_env|concat_idents|concat|line|column|file|stringify|include|include_str|include_bytes|module_path|cfg)!" - }, - { - "comment": "Core macro", - "name": "support.function.core.rust", - "match": "\\b(panic|assert|assert_eq|assert_ne|debug_assert|debug_assert_eq|debug_assert_ne|try|write|writeln|unreachable|unimplemented)!" - }, - { - "comment": "Standard library macro", - "name": "support.function.std.rust", - "match": "\\b(format|print|println|eprint|eprintln|select|vec)!" - }, - { - "comment": "Logging macro", - "name": "support.function.log.rust", - "match": "\\b(log|error|warn|info|debug|trace|log_enabled)!" - }, - { - "comment": "Invokation of a macro", - "match": "\\b([a-zA-Z_][a-zA-Z0-9_]*\\!)\\s*[({\\[]", + "comment": "macro type metavariables", + "name": "meta.macro.metavariable.type.rust", + "match": "(\\$)((crate)|([A-Z][A-Za-z0-9_]*))((:)(block|expr|ident|item|lifetime|literal|meta|pat|path|stmt|tt|ty|vis))?", "captures": { "1": { + "name": "keyword.operator.macro.dollar.rust" + }, + "3": { + "name": "keyword.other.crate.rust" + }, + "4": { + "name": "entity.name.type.metavariable.rust" + }, + "6": { + "name": "keyword.operator.key-value.rust" + }, + "7": { + "name": "variable.other.metavariable.specifier.rust" + } + }, + "patterns": [ + { + "include": "#keywords" + } + ] + }, + { + "comment": "macro metavariables", + "name": "meta.macro.metavariable.rust", + "match": "(\\$)([a-z][A-Za-z0-9_]*)((:)(block|expr|ident|item|lifetime|literal|meta|pat|path|stmt|tt|ty|vis))?", + "captures": { + "1": { + "name": "keyword.operator.macro.dollar.rust" + }, + "2": { + "name": "variable.other.metavariable.name.rust" + }, + "4": { + "name": "keyword.operator.key-value.rust" + }, + "5": { + "name": "variable.other.metavariable.specifier.rust" + } + }, + "patterns": [ + { + "include": "#keywords" + } + ] + }, + { + "comment": "macro rules", + "name": "meta.macro.rules.rust", + "match": "\\b(macro_rules!)\\s+(([a-z0-9_]+)|([A-Z][a-z0-9_]*))\\s+(\\{)", + "captures": { + "1": { + "name": "entity.name.function.macro.rules.rust" + }, + "3": { "name": "entity.name.function.macro.rust" + }, + "4": { + "name": "entity.name.type.macro.rust" + }, + "5": { + "name": "punctuation.brackets.curly.rust" } } }, { - "comment": "Function call", - "match": "\\b([A-Za-z][A-Za-z0-9_]*|_[A-Za-z0-9_]+)\\s*\\(", + "comment": "attributes", + "name": "meta.attribute.rust", + "begin": "(#)(\\!?)(\\[)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.attribute.rust" + }, + "2": { + "name": "keyword.operator.attribute.inner.rust" + }, + "3": { + "name": "punctuation.brackets.attribute.rust" + } + }, + "end": "\\]", + "endCaptures": { + "0": { + "name": "punctuation.brackets.attribute.rust" + } + }, + "patterns": [ + { + "include": "#block-comments" + }, + { + "include": "#comments" + }, + { + "include": "#keywords" + }, + { + "include": "#punctuation" + }, + { + "include": "#strings" + }, + { + "include": "#gtypes" + }, + { + "include": "#types" + } + ] + }, + { + "comment": "modules", + "match": "(mod)\\s+((?:r#(?!crate|[Ss]elf|super))?[a-z][A-Za-z0-9_]*)", "captures": { "1": { - "name": "entity.name.function.rust" + "name": "keyword.control.rust" + }, + "2": { + "name": "entity.name.module.rust" } } }, { - "comment": "Function call with type parameters", - "begin": "\\b([A-Za-z][A-Za-z0-9_]*|_[A-Za-z0-9_]+)\\s*(::)(?=\\s*<.*>\\s*\\()", - "end": "\\(", - "captures": { - "1": { - "name": "entity.name.function.rust" - }, - "2": { - "name": "keyword.operator.misc.rust" - } - }, - "patterns": [ - { - "include": "#type_params" - } - ] - }, - { - "comment": "Function definition", - "begin": "\\b(fn)\\s+([A-Za-z][A-Za-z0-9_]*|_[A-Za-z0-9_]+)", - "end": "[\\{;]", + "comment": "external crate imports", + "name": "meta.import.rust", + "begin": "\\b(extern)\\s+(crate)", "beginCaptures": { "1": { - "name": "keyword.other.fn.rust" + "name": "keyword.control.rust" }, "2": { - "name": "entity.name.function.rust" + "name": "keyword.other.crate.rust" } }, - "patterns": [ - { - "include": "#block_comment" - }, - { - "include": "#line_comment" - }, - { - "include": "#sigils" - }, - { - "include": "#self" - }, - { - "include": "#mut" - }, - { - "include": "#dyn" - }, - { - "include": "#impl" - }, - { - "include": "#ref_lifetime" - }, - { - "include": "#core_types" - }, - { - "include": "#core_marker" - }, - { - "include": "#core_traits" - }, - { - "include": "#std_types" - }, - { - "include": "#std_traits" - }, - { - "include": "#type_params" - }, - { - "include": "#const" - }, - { - "include": "#where" - }, - { - "include": "#unsafe" - }, - { - "comment": "Function arguments", - "match": "\bfn\b", - "name": "keyword.other.fn.rust" - } - ] - }, - { - "comment": "Type declaration", - "begin": "\\b(enum|struct|trait|union)\\s+([a-zA-Z_][a-zA-Z0-9_]*)", - "end": "[\\{\\(;]", - "beginCaptures": { - "1": { - "name": "storage.type.rust" - }, - "2": { - "name": "entity.name.type.rust" - } - }, - "patterns": [ - { - "include": "#block_comment" - }, - { - "include": "#line_comment" - }, - { - "include": "#core_traits" - }, - { - "include": "#std_traits" - }, - { - "include": "#type_params" - }, - { - "include": "#core_types" - }, - { - "include": "#pub" - }, - { - "include": "#where" - } - ] - }, - { - "comment": "Type alias", - "begin": "\\b(type)\\s+([a-zA-Z_][a-zA-Z0-9_]*)", "end": ";", - "beginCaptures": { - "1": { - "name": "storage.type.rust" - }, - "2": { - "name": "entity.name.type.rust" + "endCaptures": { + "0": { + "name": "punctuation.semi.rust" } }, "patterns": [ { - "include": "#block_comment" + "include": "#block-comments" }, { - "include": "#line_comment" + "include": "#comments" }, { - "include": "#sigils" + "include": "#keywords" }, { - "include": "#mut" - }, - { - "include": "#dyn" - }, - { - "include": "#impl" - }, - { - "include": "#lifetime" - }, - { - "include": "#ref_lifetime" - }, - { - "include": "#core_types" - }, - { - "include": "#core_marker" - }, - { - "include": "#core_traits" - }, - { - "include": "#std_types" - }, - { - "include": "#std_traits" - }, - { - "include": "#type_params" + "include": "#punctuation" } ] + }, + { + "comment": "use statements", + "name": "meta.use.rust", + "begin": "\\b(use)\\s", + "beginCaptures": { + "1": { + "name": "keyword.control.rust" + } + }, + "end": ";", + "endCaptures": { + "0": { + "name": "punctuation.semi.rust" + } + }, + "patterns": [ + { + "include": "#block-comments" + }, + { + "include": "#comments" + }, + { + "include": "#keywords" + }, + { + "include": "#namespaces" + }, + { + "include": "#punctuation" + }, + { + "include": "#types" + }, + { + "include": "#lvariables" + } + ] + }, + { + "include": "#block-comments" + }, + { + "include": "#comments" + }, + { + "include": "#lvariables" + }, + { + "include": "#constants" + }, + { + "include": "#gtypes" + }, + { + "include": "#functions" + }, + { + "include": "#types" + }, + { + "include": "#keywords" + }, + { + "include": "#lifetimes" + }, + { + "include": "#macros" + }, + { + "include": "#namespaces" + }, + { + "include": "#punctuation" + }, + { + "include": "#strings" + }, + { + "include": "#variables" } ], "repository": { - "block_doc_comment": { - "comment": "Block documentation comment", - "name": "comment.block.documentation.rust", - "begin": "/\\*[\\*!](?![\\*/])", - "end": "\\*/", + "comments": { "patterns": [ { - "include": "#block_doc_comment" + "comment": "documentation comments", + "name": "comment.line.documentation.rust", + "match": "^\\s*///.*" }, { - "include": "#block_comment" + "comment": "line comments", + "name": "comment.line.double-slash.rust", + "match": "\\s*//.*" } ] }, - "block_comment": { - "comment": "Block comment", - "name": "comment.block.rust", - "begin": "/\\*", - "end": "\\*/", + "block-comments": { "patterns": [ { - "include": "#block_doc_comment" + "comment": "block comments", + "name": "comment.block.rust", + "begin": "/\\*(?!\\*)", + "end": "\\*/", + "patterns": [ + { + "include": "#block-comments" + } + ] }, { - "include": "#block_comment" + "comment": "block documentation comments", + "name": "comment.block.documentation.rust", + "begin": "/\\*\\*", + "end": "\\*/", + "patterns": [ + { + "include": "#block-comments" + } + ] } ] }, - "line_doc_comment": { - "comment": "Single-line documentation comment", - "name": "comment.line.documentation.rust", - "begin": "//[!/](?=[^/])", - "end": "$" + "constants": { + "patterns": [ + { + "comment": "ALL CAPS constants", + "name": "constant.other.caps.rust", + "match": "\\b[A-Z]{2}[A-Z0-9_]*\\b" + }, + { + "comment": "constant declarations", + "match": "\\b(const)\\s+([A-Z][A-Za-z0-9_]*)\\b", + "captures": { + "1": { + "name": "keyword.control.rust" + }, + "2": { + "name": "constant.other.caps.rust" + } + } + }, + { + "comment": "decimal integers and floats", + "name": "constant.numeric.decimal.rust", + "match": "\\b\\d[\\d_]*(\\.?)[\\d_]*(?:(E)([+-])([\\d_]+))?(f32|f64|i128|i16|i32|i64|i8|isize|u128|u16|u32|u64|u8|usize)?\\b", + "captures": { + "1": { + "name": "punctuation.separator.dot.decimal.rust" + }, + "2": { + "name": "keyword.operator.exponent.rust" + }, + "3": { + "name": "keyword.operator.exponent.sign.rust" + }, + "4": { + "name": "constant.numeric.decimal.exponent.mantissa.rust" + }, + "5": { + "name": "entity.name.type.numeric.rust" + } + } + }, + { + "comment": "hexadecimal integers", + "name": "constant.numeric.hex.rust", + "match": "\\b0x[\\da-fA-F_]+(i128|i16|i32|i64|i8|isize|u128|u16|u32|u64|u8|usize)?\\b", + "captures": { + "1": { + "name": "entity.name.type.numeric.rust" + } + } + }, + { + "comment": "octal integers", + "name": "constant.numeric.oct.rust", + "match": "\\b0o[0-7_]+(i128|i16|i32|i64|i8|isize|u128|u16|u32|u64|u8|usize)?\\b", + "captures": { + "1": { + "name": "entity.name.type.numeric.rust" + } + } + }, + { + "comment": "binary integers", + "name": "constant.numeric.bin.rust", + "match": "\\b0b[01_]+(i128|i16|i32|i64|i8|isize|u128|u16|u32|u64|u8|usize)?\\b", + "captures": { + "1": { + "name": "entity.name.type.numeric.rust" + } + } + }, + { + "comment": "booleans", + "name": "constant.language.bool.rust", + "match": "\\btrue|false\\b" + } + ] }, - "line_comment": { - "comment": "Single-line comment", - "name": "comment.line.double-slash.rust", - "begin": "//", - "end": "$" - }, - "escaped_character": { + "escapes": { + "comment": "escapes: ASCII, byte, Unicode, quote, regex", "name": "constant.character.escape.rust", - "match": "\\\\(x[0-9A-Fa-f]{2}|[0-2][0-7]{0,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.)" - }, - "string_literal": { - "comment": "Double-quote string literal", - "name": "string.quoted.double.rust", - "begin": "b?\"", - "end": "\"", - "patterns": [ - { - "include": "#escaped_character" - } - ] - }, - "raw_string_literal": { - "comment": "Raw double-quote string literal", - "name": "string.quoted.double.raw.rust", - "begin": "b?r(#*)\"", - "end": "\"\\1" - }, - "sigils": { - "comment": "Sigil", - "name": "keyword.operator.sigil.rust", - "match": "[&*](?=[a-zA-Z0-9_\\(\\[\\|\\\"]+)" - }, - "self": { - "comment": "Self variable", - "name": "variable.language.rust", - "match": "\\bself\\b" - }, - "mut": { - "comment": "Mutable storage modifier", - "name": "storage.modifier.mut.rust", - "match": "\\bmut\\b" - }, - "dyn": { - "comment": "Dynamic modifier", - "name": "storage.modifier.dyn.rust", - "match": "\\bdyn\\b" - }, - "impl": { - "comment": "Existential type modifier", - "name": "storage.modifier.impl.rust", - "match": "\\bimpl\\b" - }, - "box": { - "comment": "Box storage modifier", - "name": "storage.modifier.box.rust", - "match": "\\bbox\\b" - }, - "const": { - "comment": "Const storage modifier", - "name": "storage.modifier.const.rust", - "match": "\\bconst\\b" - }, - "pub": { - "comment": "Visibility modifier", - "name": "storage.modifier.visibility.rust", - "match": "\\bpub\\b" - }, - "unsafe": { - "comment": "Unsafe code keyword", - "name": "keyword.other.unsafe.rust", - "match": "\\bunsafe\\b" - }, - "where": { - "comment": "Generic where clause", - "name": "keyword.other.where.rust", - "match": "\\bwhere\\b" - }, - "lifetime": { - "comment": "Named lifetime", - "name": "storage.modifier.lifetime.rust", - "match": "'([a-zA-Z_][a-zA-Z0-9_]*)\\b", + "match": "(\\\\)(?:(?:(x[0-7][0-7a-fA-F])|(u(\\{)[\\da-fA-F]{4,6}(\\}))|.))", "captures": { "1": { - "name": "entity.name.lifetime.rust" - } - } - }, - "ref_lifetime": { - "comment": "Reference with named lifetime", - "match": "&('([a-zA-Z_][a-zA-Z0-9_]*))\\b", - "captures": { - "1": { - "name": "storage.modifier.lifetime.rust" + "name": "constant.character.escape.backslash.rust" }, "2": { - "name": "entity.name.lifetime.rust" + "name": "constant.character.escape.bit.rust" + }, + "3": { + "name": "constant.character.escape.unicode.rust" + }, + "4": { + "name": "constant.character.escape.unicode.punctuation.rust" + }, + "5": { + "name": "constant.character.escape.unicode.punctuation.rust" } } }, - "core_types": { - "comment": "Built-in/core type", - "name": "storage.type.core.rust", - "match": "\\b(bool|char|usize|isize|u8|u16|u32|u64|u128|i8|i16|i32|i64|i128|f32|f64|str|Self|Option|Result)\\b" - }, - "core_vars": { - "comment": "Core type variant", - "name": "support.constant.core.rust", - "match": "\\b(Some|None|Ok|Err)\\b" - }, - "core_marker": { - "comment": "Core trait (marker)", - "name": "support.type.marker.rust", - "match": "\\b(Copy|Send|Sized|Sync)\\b" - }, - "core_traits": { - "comment": "Core trait", - "name": "support.type.core.rust", - "match": "\\b(Drop|Fn|FnMut|FnOnce|Clone|PartialEq|PartialOrd|Eq|Ord|AsRef|AsMut|Into|From|Default|Iterator|Extend|IntoIterator|DoubleEndedIterator|ExactSizeIterator)\\b" - }, - "std_types": { - "comment": "Standard library type", - "name": "storage.class.std.rust", - "match": "\\b(Box|String|Vec|Path|PathBuf)\\b" - }, - "std_traits": { - "comment": "Standard library trait", - "name": "support.type.std.rust", - "match": "\\b(ToOwned|ToString)\\b" - }, - "type": { - "comment": "A type", - "name": "entity.name.type.rust", - "match": "\\b([A-Za-z][_A-Za-z0-9]*|_[_A-Za-z0-9]+)\\b" - }, - "type_params": { - "comment": "Type parameters", - "name": "meta.type_params.rust", - "begin": "<(?![=<])", - "end": "(?", + "functions": { "patterns": [ { - "include": "#block_comment" + "comment": "pub as a function", + "match": "\\b(pub)(\\()", + "captures": { + "1": { + "name": "keyword.other.rust" + }, + "2": { + "name": "punctuation.brackets.round.rust" + } + } }, { - "include": "#line_comment" + "comment": "function definition", + "name": "meta.function.definition.rust", + "begin": "\\b(fn)\\s+((?:r#(?!crate|[Ss]elf|super))?[A-Za-z0-9_]+)((\\()|(<))", + "beginCaptures": { + "1": { + "name": "keyword.control.fn.rust" + }, + "2": { + "name": "entity.name.function.rust" + }, + "4": { + "name": "punctuation.brackets.round.rust" + }, + "5": { + "name": "punctuation.brackets.angle.rust" + } + }, + "end": "\\{|;", + "endCaptures": { + "0": { + "name": "punctuation.brackets.curly.rust" + } + }, + "patterns": [ + { + "include": "#block-comments" + }, + { + "include": "#comments" + }, + { + "include": "#keywords" + }, + { + "include": "#lvariables" + }, + { + "include": "#constants" + }, + { + "include": "#gtypes" + }, + { + "include": "#functions" + }, + { + "include": "#lifetimes" + }, + { + "include": "#macros" + }, + { + "include": "#namespaces" + }, + { + "include": "#punctuation" + }, + { + "include": "#strings" + }, + { + "include": "#types" + }, + { + "include": "#variables" + } + ] }, { - "include": "#sigils" + "comment": "function/method calls, chaining", + "name": "meta.function.call.rust", + "begin": "((?:r#(?!crate|[Ss]elf|super))?[A-Za-z0-9_]+)(\\()", + "beginCaptures": { + "1": { + "name": "entity.name.function.rust" + }, + "2": { + "name": "punctuation.brackets.round.rust" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.brackets.round.rust" + } + }, + "patterns": [ + { + "include": "#block-comments" + }, + { + "include": "#comments" + }, + { + "include": "#keywords" + }, + { + "include": "#lvariables" + }, + { + "include": "#constants" + }, + { + "include": "#gtypes" + }, + { + "include": "#functions" + }, + { + "include": "#lifetimes" + }, + { + "include": "#macros" + }, + { + "include": "#namespaces" + }, + { + "include": "#punctuation" + }, + { + "include": "#strings" + }, + { + "include": "#types" + }, + { + "include": "#variables" + } + ] }, { - "include": "#mut" + "comment": "function/method calls with turbofish", + "name": "meta.function.call.rust", + "begin": "((?:r#(?!crate|[Ss]elf|super))?[A-Za-z0-9_]+)(?=::<.*>\\()", + "beginCaptures": { + "1": { + "name": "entity.name.function.rust" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.brackets.round.rust" + } + }, + "patterns": [ + { + "include": "#block-comments" + }, + { + "include": "#comments" + }, + { + "include": "#keywords" + }, + { + "include": "#lvariables" + }, + { + "include": "#constants" + }, + { + "include": "#gtypes" + }, + { + "include": "#functions" + }, + { + "include": "#lifetimes" + }, + { + "include": "#macros" + }, + { + "include": "#namespaces" + }, + { + "include": "#punctuation" + }, + { + "include": "#strings" + }, + { + "include": "#types" + }, + { + "include": "#variables" + } + ] + } + ] + }, + "keywords": { + "patterns": [ + { + "comment": "control flow keywords", + "name": "keyword.control.rust", + "match": "\\b(async|await|break|continue|do|else|for|if|loop|match|move|return|try|where|while|yield)\\b" }, { - "include": "#dyn" + "comment": "storage keywords", + "name": "storage.type.rust", + "match": "\\b(const|enum|extern|let|macro|mod|struct|trait|type)\\b" }, { - "include": "#impl" + "comment": "storage modifiers", + "name": "storage.modifier.rust", + "match": "\\b(abstract|static)\\b" }, { - "include": "#lifetime" + "comment": "other keywords", + "name": "keyword.other.rust", + "match": "\\b(as|become|box|dyn|final|impl|in|override|priv|pub|ref|typeof|union|unsafe|unsized|use|virtual)\\b" }, { - "include": "#core_types" + "comment": "fn", + "name": "keyword.other.fn.rust", + "match": "\\bfn\\b" }, { - "include": "#core_marker" + "comment": "crate", + "name": "keyword.other.crate.rust", + "match": "\\bcrate\\b" }, { - "include": "#core_traits" + "comment": "mut", + "name": "storage.modifier.mut.rust", + "match": "\\bmut\\b" }, { - "include": "#std_types" + "comment": "math operators", + "name": "keyword.operator.math.rust", + "match": "(([+%]|(\\*(?!\\w)))(?!=))|(-(?!>))|(/(?!/))" }, { - "include": "#std_traits" + "comment": "logical operators", + "name": "keyword.operator.logical.rust", + "match": "(\\^|\\||\\|\\||&&|<<|>>|!)(?!=)" }, { - "include": "#type_params" + "comment": "logical AND, borrow references", + "name": "keyword.operator.borrow.and.rust", + "match": "&(?![&=])" + }, + { + "comment": "assignment operators", + "name": "keyword.operator.assignment.rust", + "match": "(-=|\\*=|/=|%=|\\^=|&=|\\|=|<<=|>>=)" + }, + { + "comment": "single equal", + "name": "keyword.operator.assignment.equal.rust", + "match": "(?])=(?!=|>)" + }, + { + "comment": "comparison operators", + "name": "keyword.operator.comparison.rust", + "match": "(=(=)?(?!>)|!=|<=|(?=)" + }, + { + "comment": "less than, greater than (special case)", + "match": "(?:\\b|(?:(\\))|(\\])|(\\})))[ \\t]+([<>])[ \\t]+(?:\\b|(?:(\\()|(\\[)|(\\{)))", + "captures": { + "1": { + "name": "punctuation.brackets.round.rust" + }, + "2": { + "name": "punctuation.brackets.square.rust" + }, + "3": { + "name": "punctuation.brackets.curly.rust" + }, + "4": { + "name": "keyword.operator.comparison.rust" + }, + "5": { + "name": "punctuation.brackets.round.rust" + }, + "6": { + "name": "punctuation.brackets.square.rust" + }, + "7": { + "name": "punctuation.brackets.curly.rust" + } + } + }, + { + "comment": "namespace operator", + "name": "keyword.operator.namespace.rust", + "match": "::" + }, + { + "comment": "dereference asterisk", + "match": "(\\*)(?=\\w+)", + "captures": { + "1": { + "name": "keyword.operator.dereference.rust" + } + } + }, + { + "comment": "subpattern binding", + "name": "keyword.operator.subpattern.rust", + "match": "@" + }, + { + "comment": "dot access", + "name": "keyword.operator.access.dot.rust", + "match": "\\.(?!\\.)" + }, + { + "comment": "ranges, range patterns", + "name": "keyword.operator.range.rust", + "match": "\\.{2}(=|\\.)?" + }, + { + "comment": "colon", + "name": "keyword.operator.key-value.rust", + "match": ":(?!:)" + }, + { + "comment": "dashrocket, skinny arrow", + "name": "keyword.operator.arrow.skinny.rust", + "match": "->" + }, + { + "comment": "hashrocket, fat arrow", + "name": "keyword.operator.arrow.fat.rust", + "match": "=>" + }, + { + "comment": "dollar macros", + "name": "keyword.operator.macro.dollar.rust", + "match": "\\$" + }, + { + "comment": "question mark operator, questionably sized, macro kleene matcher", + "name": "keyword.operator.question.rust", + "match": "\\?" + } + ] + }, + "interpolations": { + "comment": "curly brace interpolations", + "name": "meta.interpolation.rust", + "match": "({)[^\"{}]*(})", + "captures": { + "1": { + "name": "punctuation.definition.interpolation.rust" + }, + "2": { + "name": "punctuation.definition.interpolation.rust" + } + } + }, + "lifetimes": { + "patterns": [ + { + "comment": "named lifetime parameters", + "match": "(['])([a-zA-Z_][0-9a-zA-Z_]*)(?!['])\\b", + "captures": { + "1": { + "name": "punctuation.definition.lifetime.rust" + }, + "2": { + "name": "entity.name.type.lifetime.rust" + } + } + }, + { + "comment": "borrowing references to named lifetimes", + "match": "(\\&)(['])([a-zA-Z_][0-9a-zA-Z_]*)(?!['])\\b", + "captures": { + "1": { + "name": "keyword.operator.borrow.rust" + }, + "2": { + "name": "punctuation.definition.lifetime.rust" + }, + "3": { + "name": "entity.name.type.lifetime.rust" + } + } + } + ] + }, + "macros": { + "patterns": [ + { + "comment": "macros", + "name": "meta.macro.rust", + "match": "(([a-z_][A-Za-z0-9_]*!)|([A-Z_][A-Za-z0-9_]*!))", + "captures": { + "2": { + "name": "entity.name.function.macro.rust" + }, + "3": { + "name": "entity.name.type.macro.rust" + } + } + } + ] + }, + "namespaces": { + "patterns": [ + { + "comment": "namespace (non-type, non-function path segment)", + "match": "(?", + "endCaptures": { + "0": { + "name": "punctuation.brackets.angle.rust" + } + }, + "patterns": [ + { + "include": "#block-comments" + }, + { + "include": "#comments" + }, + { + "include": "#keywords" + }, + { + "include": "#lvariables" + }, + { + "include": "#lifetimes" + }, + { + "include": "#punctuation" + }, + { + "include": "#types" + }, + { + "include": "#variables" + } + ] + }, + { + "comment": "primitive types", + "name": "entity.name.type.primitive.rust", + "match": "\\b(bool|char|str)\\b" + }, + { + "comment": "trait declarations", + "match": "\\b(trait)\\s+([A-Z][A-Za-z0-9]*)\\b", + "captures": { + "1": { + "name": "storage.type.rust" + }, + "2": { + "name": "entity.name.type.trait.rust" + } + } + }, + { + "comment": "struct declarations", + "match": "\\b(struct)\\s+([A-Z][A-Za-z0-9]*)\\b", + "captures": { + "1": { + "name": "storage.type.rust" + }, + "2": { + "name": "entity.name.type.struct.rust" + } + } + }, + { + "comment": "enum declarations", + "match": "\\b(enum)\\s+([A-Z][A-Za-z0-9_]*)\\b", + "captures": { + "1": { + "name": "storage.type.rust" + }, + "2": { + "name": "entity.name.type.enum.rust" + } + } + }, + { + "comment": "type declarations", + "match": "\\b(type)\\s+([A-Z][A-Za-z0-9_]*)\\b", + "captures": { + "1": { + "name": "storage.type.rust" + }, + "2": { + "name": "entity.name.type.declaration.rust" + } + } + }, + { + "comment": "types", + "name": "entity.name.type.rust", + "match": "\\b[A-Z][A-Za-z0-9]*\\b(?!!)" + } + ] + }, + "gtypes": { + "patterns": [ + { + "comment": "option types", + "name": "entity.name.type.option.rust", + "match": "\\b(Some|None)\\b" + }, + { + "comment": "result types", + "name": "entity.name.type.result.rust", + "match": "\\b(Ok|Err)\\b" + } + ] + }, + "punctuation": { + "patterns": [ + { + "comment": "comma", + "name": "punctuation.comma.rust", + "match": "," + }, + { + "comment": "curly braces", + "name": "punctuation.brackets.curly.rust", + "match": "[{}]" + }, + { + "comment": "parentheses, round brackets", + "name": "punctuation.brackets.round.rust", + "match": "[()]" + }, + { + "comment": "semicolon", + "name": "punctuation.semi.rust", + "match": ";" + }, + { + "comment": "square brackets", + "name": "punctuation.brackets.square.rust", + "match": "[\\[\\]]" + }, + { + "comment": "angle brackets", + "name": "punctuation.brackets.angle.rust", + "match": "(?]" + } + ] + }, + "strings": { + "patterns": [ + { + "comment": "double-quoted strings and byte strings", + "name": "string.quoted.double.rust", + "begin": "(b?)(\")", + "beginCaptures": { + "1": { + "name": "string.quoted.byte.raw.rust" + }, + "2": { + "name": "punctuation.definition.string.rust" + } + }, + "end": "\"", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.rust" + } + }, + "patterns": [ + { + "include": "#escapes" + }, + { + "include": "#interpolations" + } + ] + }, + { + "comment": "double-quoted raw strings and raw byte strings", + "name": "string.quoted.double.rust", + "begin": "(b?r)(#*)(\")", + "beginCaptures": { + "1": { + "name": "string.quoted.byte.raw.rust" + }, + "2": { + "name": "punctuation.definition.string.raw.rust" + }, + "3": { + "name": "punctuation.definition.string.rust" + } + }, + "end": "(\")(\\2)", + "endCaptures": { + "1": { + "name": "punctuation.definition.string.rust" + }, + "2": { + "name": "punctuation.definition.string.raw.rust" + } + } + }, + { + "comment": "characters and bytes", + "name": "string.quoted.single.char.rust", + "begin": "(b)?(')", + "beginCaptures": { + "1": { + "name": "string.quoted.byte.raw.rust" + }, + "2": { + "name": "punctuation.definition.char.rust" + } + }, + "end": "'", + "endCaptures": { + "0": { + "name": "punctuation.definition.char.rust" + } + }, + "patterns": [ + { + "include": "#escapes" + } + ] + } + ] + }, + "lvariables": { + "patterns": [ + { + "comment": "self", + "name": "variable.language.self.rust", + "match": "\\b[Ss]elf\\b" + }, + { + "comment": "super", + "name": "variable.language.super.rust", + "match": "\\bsuper\\b" + } + ] + }, + "variables": { + "patterns": [ + { + "comment": "variables", + "name": "variable.other.rust", + "match": "\\b(?", - "t": "source.rust meta.type_params.rust", + "c": "<", + "t": "source.rust punctuation.brackets.angle.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "A", + "t": "source.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": ",", + "t": "source.rust punctuation.comma.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "B", + "t": "source.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": ">", + "t": "source.rust punctuation.brackets.angle.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -56,13 +100,13 @@ }, { "c": "where", - "t": "source.rust keyword.other.where.rust", + "t": "source.rust keyword.control.rust", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" } }, { @@ -88,7 +132,18 @@ } }, { - "c": ": ", + "c": ":", + "t": "source.rust keyword.operator.key-value.rust", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", "t": "source.rust", "r": { "dark_plus": "default: #D4D4D4", @@ -110,7 +165,18 @@ } }, { - "c": "{ }", + "c": "{", + "t": "source.rust punctuation.brackets.curly.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", "t": "source.rust", "r": { "dark_plus": "default: #D4D4D4", @@ -121,14 +187,25 @@ } }, { - "c": "impl", - "t": "source.rust storage.type.rust", + "c": "}", + "t": "source.rust punctuation.brackets.curly.rust", "r": { - "dark_plus": "storage.type: #569CD6", - "light_plus": "storage.type: #0000FF", - "dark_vs": "storage.type: #569CD6", - "light_vs": "storage.type: #0000FF", - "hc_black": "storage.type: #569CD6" + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "impl", + "t": "source.rust keyword.other.rust", + "r": { + "dark_plus": "keyword: #569CD6", + "light_plus": "keyword: #0000FF", + "dark_vs": "keyword: #569CD6", + "light_vs": "keyword: #0000FF", + "hc_black": "keyword: #569CD6" } }, { @@ -154,8 +231,52 @@ } }, { - "c": "", - "t": "source.rust meta.type_params.rust", + "c": "<", + "t": "source.rust punctuation.brackets.angle.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "A", + "t": "source.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": ",", + "t": "source.rust punctuation.comma.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "B", + "t": "source.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": ">", + "t": "source.rust punctuation.brackets.angle.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -177,13 +298,13 @@ }, { "c": "for", - "t": "source.rust storage.type.rust", + "t": "source.rust keyword.control.rust", "r": { - "dark_plus": "storage.type: #569CD6", - "light_plus": "storage.type: #0000FF", - "dark_vs": "storage.type: #569CD6", - "light_vs": "storage.type: #0000FF", - "hc_black": "storage.type: #569CD6" + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" } }, { @@ -221,13 +342,13 @@ }, { "c": "where", - "t": "source.rust keyword.other.where.rust", + "t": "source.rust keyword.control.rust", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" } }, { @@ -253,7 +374,18 @@ } }, { - "c": ": ", + "c": ":", + "t": "source.rust keyword.operator.key-value.rust", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", "t": "source.rust", "r": { "dark_plus": "default: #D4D4D4", @@ -275,7 +407,18 @@ } }, { - "c": "{ }", + "c": "{", + "t": "source.rust punctuation.brackets.curly.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", "t": "source.rust", "r": { "dark_plus": "default: #D4D4D4", @@ -286,14 +429,25 @@ } }, { - "c": "impl", - "t": "source.rust storage.type.rust", + "c": "}", + "t": "source.rust punctuation.brackets.curly.rust", "r": { - "dark_plus": "storage.type: #569CD6", - "light_plus": "storage.type: #0000FF", - "dark_vs": "storage.type: #569CD6", - "light_vs": "storage.type: #0000FF", - "hc_black": "storage.type: #569CD6" + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "impl", + "t": "source.rust keyword.other.rust", + "r": { + "dark_plus": "keyword: #569CD6", + "light_plus": "keyword: #0000FF", + "dark_vs": "keyword: #569CD6", + "light_vs": "keyword: #0000FF", + "hc_black": "keyword: #569CD6" } }, { @@ -319,8 +473,52 @@ } }, { - "c": "", - "t": "source.rust meta.type_params.rust", + "c": "<", + "t": "source.rust punctuation.brackets.angle.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "A", + "t": "source.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": ",", + "t": "source.rust punctuation.comma.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "B", + "t": "source.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": ">", + "t": "source.rust punctuation.brackets.angle.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -342,13 +540,13 @@ }, { "c": "for", - "t": "source.rust storage.type.rust", + "t": "source.rust keyword.control.rust", "r": { - "dark_plus": "storage.type: #569CD6", - "light_plus": "storage.type: #0000FF", - "dark_vs": "storage.type: #569CD6", - "light_vs": "storage.type: #0000FF", - "hc_black": "storage.type: #569CD6" + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" } }, { @@ -375,7 +573,7 @@ }, { "c": "{", - "t": "source.rust", + "t": "source.rust punctuation.brackets.curly.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -397,18 +595,18 @@ }, { "c": "fn", - "t": "source.rust keyword.other.fn.rust", + "t": "source.rust meta.function.definition.rust keyword.control.fn.rust", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" } }, { "c": " ", - "t": "source.rust", + "t": "source.rust meta.function.definition.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -419,7 +617,7 @@ }, { "c": "foo", - "t": "source.rust entity.name.function.rust", + "t": "source.rust meta.function.definition.rust entity.name.function.rust", "r": { "dark_plus": "entity.name.function: #DCDCAA", "light_plus": "entity.name.function: #795E26", @@ -429,8 +627,8 @@ } }, { - "c": "", - "t": "source.rust meta.type_params.rust", + "c": "<", + "t": "source.rust meta.function.definition.rust punctuation.brackets.angle.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -440,8 +638,19 @@ } }, { - "c": " -> C", - "t": "source.rust", + "c": "A", + "t": "source.rust meta.function.definition.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": ",", + "t": "source.rust meta.function.definition.rust punctuation.comma.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -450,9 +659,75 @@ "hc_black": "default: #FFFFFF" } }, + { + "c": "B", + "t": "source.rust meta.function.definition.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": ">", + "t": "source.rust meta.function.definition.rust punctuation.brackets.angle.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.rust meta.function.definition.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "->", + "t": "source.rust meta.function.definition.rust keyword.operator.arrow.skinny.rust", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.rust meta.function.definition.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "C", + "t": "source.rust meta.function.definition.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, { "c": " ", - "t": "source.rust", + "t": "source.rust meta.function.definition.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -463,18 +738,18 @@ }, { "c": "where", - "t": "source.rust keyword.other.where.rust", + "t": "source.rust meta.function.definition.rust keyword.control.rust", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" } }, { - "c": " A: B", - "t": "source.rust", + "c": " ", + "t": "source.rust meta.function.definition.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -484,7 +759,73 @@ } }, { - "c": " { }", + "c": "A", + "t": "source.rust meta.function.definition.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": ":", + "t": "source.rust meta.function.definition.rust keyword.operator.key-value.rust", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.rust meta.function.definition.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "B", + "t": "source.rust meta.function.definition.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": " ", + "t": "source.rust meta.function.definition.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "{", + "t": "source.rust meta.function.definition.rust punctuation.brackets.curly.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", "t": "source.rust", "r": { "dark_plus": "default: #D4D4D4", @@ -496,7 +837,18 @@ }, { "c": "}", - "t": "source.rust", + "t": "source.rust punctuation.brackets.curly.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "}", + "t": "source.rust punctuation.brackets.curly.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -507,13 +859,222 @@ }, { "c": "fn", - "t": "source.rust keyword.other.fn.rust", + "t": "source.rust meta.function.definition.rust keyword.control.fn.rust", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.rust meta.function.definition.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "foo", + "t": "source.rust meta.function.definition.rust entity.name.function.rust", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "<", + "t": "source.rust meta.function.definition.rust punctuation.brackets.angle.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "A", + "t": "source.rust meta.function.definition.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": ",", + "t": "source.rust meta.function.definition.rust punctuation.comma.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "B", + "t": "source.rust meta.function.definition.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": ">", + "t": "source.rust meta.function.definition.rust punctuation.brackets.angle.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.rust meta.function.definition.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "->", + "t": "source.rust meta.function.definition.rust keyword.operator.arrow.skinny.rust", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.rust meta.function.definition.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "C", + "t": "source.rust meta.function.definition.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": " ", + "t": "source.rust meta.function.definition.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "where", + "t": "source.rust meta.function.definition.rust keyword.control.rust", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.rust meta.function.definition.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "A", + "t": "source.rust meta.function.definition.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": ":", + "t": "source.rust meta.function.definition.rust keyword.operator.key-value.rust", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.rust meta.function.definition.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "B", + "t": "source.rust meta.function.definition.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": "{", + "t": "source.rust meta.function.definition.rust punctuation.brackets.curly.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" } }, { @@ -528,74 +1089,8 @@ } }, { - "c": "foo", - "t": "source.rust entity.name.function.rust", - "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.function: #DCDCAA" - } - }, - { - "c": "", - "t": "source.rust meta.type_params.rust", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " -> C", - "t": "source.rust", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.rust", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "where", - "t": "source.rust keyword.other.where.rust", - "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" - } - }, - { - "c": " A: B", - "t": "source.rust", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "{ }", - "t": "source.rust", + "c": "}", + "t": "source.rust punctuation.brackets.curly.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -628,6 +1123,28 @@ }, { "c": "Foo", + "t": "source.rust entity.name.type.struct.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": "<", + "t": "source.rust punctuation.brackets.angle.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "A", "t": "source.rust entity.name.type.rust", "r": { "dark_plus": "entity.name.type: #4EC9B0", @@ -638,8 +1155,30 @@ } }, { - "c": "", - "t": "source.rust meta.type_params.rust", + "c": ",", + "t": "source.rust punctuation.comma.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "B", + "t": "source.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": ">", + "t": "source.rust punctuation.brackets.angle.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -661,17 +1200,17 @@ }, { "c": "where", - "t": "source.rust keyword.other.where.rust", + "t": "source.rust keyword.control.rust", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" } }, { - "c": " A: B", + "c": " ", "t": "source.rust", "r": { "dark_plus": "default: #D4D4D4", @@ -682,7 +1221,29 @@ } }, { - "c": "{ }", + "c": "A", + "t": "source.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": ":", + "t": "source.rust keyword.operator.key-value.rust", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", "t": "source.rust", "r": { "dark_plus": "default: #D4D4D4", @@ -692,6 +1253,50 @@ "hc_black": "default: #FFFFFF" } }, + { + "c": "B", + "t": "source.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": "{", + "t": "source.rust punctuation.brackets.curly.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "}", + "t": "source.rust punctuation.brackets.curly.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, { "c": "trait", "t": "source.rust storage.type.rust", @@ -716,6 +1321,28 @@ }, { "c": "Foo", + "t": "source.rust entity.name.type.trait.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": "<", + "t": "source.rust punctuation.brackets.angle.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "A", "t": "source.rust entity.name.type.rust", "r": { "dark_plus": "entity.name.type: #4EC9B0", @@ -726,8 +1353,8 @@ } }, { - "c": "", - "t": "source.rust meta.type_params.rust", + "c": ",", + "t": "source.rust punctuation.comma.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -737,7 +1364,29 @@ } }, { - "c": " : C", + "c": "B", + "t": "source.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": ">", + "t": "source.rust punctuation.brackets.angle.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", "t": "source.rust", "r": { "dark_plus": "default: #D4D4D4", @@ -747,6 +1396,39 @@ "hc_black": "default: #FFFFFF" } }, + { + "c": ":", + "t": "source.rust keyword.operator.key-value.rust", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "C", + "t": "source.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, { "c": " ", "t": "source.rust", @@ -760,17 +1442,17 @@ }, { "c": "where", - "t": "source.rust keyword.other.where.rust", + "t": "source.rust keyword.control.rust", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" } }, { - "c": " A: B", + "c": " ", "t": "source.rust", "r": { "dark_plus": "default: #D4D4D4", @@ -781,7 +1463,29 @@ } }, { - "c": "{ }", + "c": "A", + "t": "source.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": ":", + "t": "source.rust keyword.operator.key-value.rust", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", "t": "source.rust", "r": { "dark_plus": "default: #D4D4D4", @@ -790,5 +1494,49 @@ "light_vs": "default: #000000", "hc_black": "default: #FFFFFF" } + }, + { + "c": "B", + "t": "source.rust entity.name.type.rust", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": "{", + "t": "source.rust punctuation.brackets.curly.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "}", + "t": "source.rust punctuation.brackets.curly.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } } ] \ No newline at end of file diff --git a/extensions/rust/test/colorize-results/test_rs.json b/extensions/rust/test/colorize-results/test_rs.json index 977fe6bd2..82615d435 100644 --- a/extensions/rust/test/colorize-results/test_rs.json +++ b/extensions/rust/test/colorize-results/test_rs.json @@ -1,18 +1,18 @@ [ { "c": "use", - "t": "source.rust keyword.other.rust", + "t": "source.rust meta.use.rust keyword.control.rust", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" } }, { - "c": " std", - "t": "source.rust", + "c": " ", + "t": "source.rust meta.use.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -21,9 +21,20 @@ "hc_black": "default: #FFFFFF" } }, + { + "c": "std", + "t": "source.rust meta.use.rust entity.name.namespace.rust", + "r": { + "dark_plus": "entity.name.namespace: #4EC9B0", + "light_plus": "entity.name.namespace: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.namespace: #4EC9B0" + } + }, { "c": "::", - "t": "source.rust keyword.operator.misc.rust", + "t": "source.rust meta.use.rust keyword.operator.namespace.rust", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -33,8 +44,19 @@ } }, { - "c": "io;", - "t": "source.rust", + "c": "io", + "t": "source.rust meta.use.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.rust meta.use.rust punctuation.semi.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -45,18 +67,18 @@ }, { "c": "fn", - "t": "source.rust keyword.other.fn.rust", + "t": "source.rust meta.function.definition.rust keyword.control.fn.rust", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" } }, { "c": " ", - "t": "source.rust", + "t": "source.rust meta.function.definition.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -67,7 +89,7 @@ }, { "c": "main", - "t": "source.rust entity.name.function.rust", + "t": "source.rust meta.function.definition.rust entity.name.function.rust", "r": { "dark_plus": "entity.name.function: #DCDCAA", "light_plus": "entity.name.function: #795E26", @@ -77,8 +99,30 @@ } }, { - "c": "() {", - "t": "source.rust", + "c": "()", + "t": "source.rust meta.function.definition.rust punctuation.brackets.round.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.rust meta.function.definition.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "{", + "t": "source.rust meta.function.definition.rust punctuation.brackets.curly.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -100,18 +144,18 @@ }, { "c": "println!", - "t": "source.rust support.function.std.rust", + "t": "source.rust meta.macro.rust entity.name.function.macro.rust", "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA" + "hc_black": "entity.name.function: #DCDCAA" } }, { "c": "(", - "t": "source.rust", + "t": "source.rust punctuation.brackets.round.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -121,7 +165,18 @@ } }, { - "c": "\"Guess the number!\"", + "c": "\"", + "t": "source.rust string.quoted.double.rust punctuation.definition.string.rust", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "Guess the number!", "t": "source.rust string.quoted.double.rust", "r": { "dark_plus": "string: #CE9178", @@ -132,8 +187,30 @@ } }, { - "c": ");", - "t": "source.rust", + "c": "\"", + "t": "source.rust string.quoted.double.rust punctuation.definition.string.rust", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ")", + "t": "source.rust punctuation.brackets.round.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.rust punctuation.semi.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -155,18 +232,18 @@ }, { "c": "println!", - "t": "source.rust support.function.std.rust", + "t": "source.rust meta.macro.rust entity.name.function.macro.rust", "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA" + "hc_black": "entity.name.function: #DCDCAA" } }, { "c": "(", - "t": "source.rust", + "t": "source.rust punctuation.brackets.round.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -176,7 +253,18 @@ } }, { - "c": "\"Please input your guess.\"", + "c": "\"", + "t": "source.rust string.quoted.double.rust punctuation.definition.string.rust", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "Please input your guess.", "t": "source.rust string.quoted.double.rust", "r": { "dark_plus": "string: #CE9178", @@ -187,8 +275,30 @@ } }, { - "c": ");", - "t": "source.rust", + "c": "\"", + "t": "source.rust string.quoted.double.rust punctuation.definition.string.rust", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ")", + "t": "source.rust punctuation.brackets.round.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.rust punctuation.semi.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -210,13 +320,13 @@ }, { "c": "let", - "t": "source.rust keyword.other.rust", + "t": "source.rust storage.type.rust", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" } }, { @@ -242,7 +352,29 @@ } }, { - "c": " guess ", + "c": " ", + "t": "source.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "guess", + "t": "source.rust variable.other.rust", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": " ", "t": "source.rust", "r": { "dark_plus": "default: #D4D4D4", @@ -254,7 +386,7 @@ }, { "c": "=", - "t": "source.rust keyword.operator.assignment.rust", + "t": "source.rust keyword.operator.assignment.equal.rust", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -276,18 +408,18 @@ }, { "c": "String", - "t": "source.rust storage.class.std.rust", + "t": "source.rust entity.name.type.rust", "r": { - "dark_plus": "storage: #569CD6", - "light_plus": "storage: #0000FF", - "dark_vs": "storage: #569CD6", - "light_vs": "storage: #0000FF", - "hc_black": "storage: #569CD6" + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" } }, { "c": "::", - "t": "source.rust keyword.operator.misc.rust", + "t": "source.rust keyword.operator.namespace.rust", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -298,139 +430,7 @@ }, { "c": "new", - "t": "source.rust entity.name.function.rust", - "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.function: #DCDCAA" - } - }, - { - "c": "();", - "t": "source.rust", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " io", - "t": "source.rust", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "::", - "t": "source.rust keyword.operator.misc.rust", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": "stdin", - "t": "source.rust entity.name.function.rust", - "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.function: #DCDCAA" - } - }, - { - "c": "().", - "t": "source.rust", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "read_line", - "t": "source.rust entity.name.function.rust", - "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "entity.name.function: #DCDCAA" - } - }, - { - "c": "(", - "t": "source.rust", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "&", - "t": "source.rust keyword.operator.sigil.rust", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": "mut", - "t": "source.rust storage.modifier.mut.rust", - "r": { - "dark_plus": "storage.modifier: #569CD6", - "light_plus": "storage.modifier: #0000FF", - "dark_vs": "storage.modifier: #569CD6", - "light_vs": "storage.modifier: #0000FF", - "hc_black": "storage.modifier: #569CD6" - } - }, - { - "c": " guess)", - "t": "source.rust", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " .", - "t": "source.rust", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "ok", - "t": "source.rust entity.name.function.rust", + "t": "source.rust meta.function.call.rust entity.name.function.rust", "r": { "dark_plus": "entity.name.function: #DCDCAA", "light_plus": "entity.name.function: #795E26", @@ -441,6 +441,28 @@ }, { "c": "()", + "t": "source.rust meta.function.call.rust punctuation.brackets.round.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.rust punctuation.semi.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", "t": "source.rust", "r": { "dark_plus": "default: #D4D4D4", @@ -451,8 +473,41 @@ } }, { - "c": " .", - "t": "source.rust", + "c": "io", + "t": "source.rust entity.name.namespace.rust", + "r": { + "dark_plus": "entity.name.namespace: #4EC9B0", + "light_plus": "entity.name.namespace: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.namespace: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.rust keyword.operator.namespace.rust", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "stdin", + "t": "source.rust meta.function.call.rust entity.name.function.rust", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "()", + "t": "source.rust meta.function.call.rust punctuation.brackets.round.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -462,8 +517,19 @@ } }, { - "c": "expect", - "t": "source.rust entity.name.function.rust", + "c": ".", + "t": "source.rust keyword.operator.access.dot.rust", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "read_line", + "t": "source.rust meta.function.call.rust entity.name.function.rust", "r": { "dark_plus": "entity.name.function: #DCDCAA", "light_plus": "entity.name.function: #795E26", @@ -474,6 +540,72 @@ }, { "c": "(", + "t": "source.rust meta.function.call.rust punctuation.brackets.round.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "&", + "t": "source.rust meta.function.call.rust keyword.operator.borrow.and.rust", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "mut", + "t": "source.rust meta.function.call.rust storage.modifier.mut.rust", + "r": { + "dark_plus": "storage.modifier: #569CD6", + "light_plus": "storage.modifier: #0000FF", + "dark_vs": "storage.modifier: #569CD6", + "light_vs": "storage.modifier: #0000FF", + "hc_black": "storage.modifier: #569CD6" + } + }, + { + "c": " ", + "t": "source.rust meta.function.call.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "guess", + "t": "source.rust meta.function.call.rust variable.other.rust", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.rust meta.function.call.rust punctuation.brackets.round.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", "t": "source.rust", "r": { "dark_plus": "default: #D4D4D4", @@ -484,8 +616,85 @@ } }, { - "c": "\"Failed to read line\"", - "t": "source.rust string.quoted.double.rust", + "c": ".", + "t": "source.rust keyword.operator.access.dot.rust", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "ok", + "t": "source.rust meta.function.call.rust entity.name.function.rust", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "()", + "t": "source.rust meta.function.call.rust punctuation.brackets.round.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.rust keyword.operator.access.dot.rust", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "expect", + "t": "source.rust meta.function.call.rust entity.name.function.rust", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.rust meta.function.call.rust punctuation.brackets.round.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "\"", + "t": "source.rust meta.function.call.rust string.quoted.double.rust punctuation.definition.string.rust", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -495,8 +704,41 @@ } }, { - "c": ");", - "t": "source.rust", + "c": "Failed to read line", + "t": "source.rust meta.function.call.rust string.quoted.double.rust", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "\"", + "t": "source.rust meta.function.call.rust string.quoted.double.rust punctuation.definition.string.rust", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ")", + "t": "source.rust meta.function.call.rust punctuation.brackets.round.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.rust punctuation.semi.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -518,18 +760,18 @@ }, { "c": "println!", - "t": "source.rust support.function.std.rust", + "t": "source.rust meta.macro.rust entity.name.function.macro.rust", "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA" + "hc_black": "entity.name.function: #DCDCAA" } }, { "c": "(", - "t": "source.rust", + "t": "source.rust punctuation.brackets.round.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -539,7 +781,18 @@ } }, { - "c": "\"You guessed: {}\"", + "c": "\"", + "t": "source.rust string.quoted.double.rust punctuation.definition.string.rust", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "You guessed: ", "t": "source.rust string.quoted.double.rust", "r": { "dark_plus": "string: #CE9178", @@ -550,7 +803,40 @@ } }, { - "c": ", guess);", + "c": "{}", + "t": "source.rust string.quoted.double.rust meta.interpolation.rust punctuation.definition.interpolation.rust", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "\"", + "t": "source.rust string.quoted.double.rust punctuation.definition.string.rust", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ",", + "t": "source.rust punctuation.comma.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", "t": "source.rust", "r": { "dark_plus": "default: #D4D4D4", @@ -560,9 +846,42 @@ "hc_black": "default: #FFFFFF" } }, + { + "c": "guess", + "t": "source.rust variable.other.rust", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.rust punctuation.brackets.round.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.rust punctuation.semi.rust", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, { "c": "}", - "t": "source.rust", + "t": "source.rust punctuation.brackets.curly.rust", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", diff --git a/extensions/search-result/package.json b/extensions/search-result/package.json index 7b43da243..9635a6c51 100644 --- a/extensions/search-result/package.json +++ b/extensions/search-result/package.json @@ -3,6 +3,7 @@ "displayName": "%displayName%", "description": "%description%", "version": "1.0.0", + "enableProposedApi": true, "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/search-result/src/extension.ts b/extensions/search-result/src/extension.ts index 0c0c9b85f..6bc5a573c 100644 --- a/extensions/search-result/src/extension.ts +++ b/extensions/search-result/src/extension.ts @@ -8,7 +8,8 @@ import * as pathUtils from 'path'; const FILE_LINE_REGEX = /^(\S.*):$/; const RESULT_LINE_REGEX = /^(\s+)(\d+)(:| )(\s+)(.*)$/; -const SEARCH_RESULT_SELECTOR = { language: 'search-result' }; +const ELISION_REGEX = /⟪ ([0-9]+) characters skipped ⟫/g; +const SEARCH_RESULT_SELECTOR = { language: 'search-result', exclusive: true }; const DIRECTIVES = ['# Query:', '# Flags:', '# Including:', '# Excluding:', '# ContextLines:']; const FLAGS = ['RegExp', 'CaseSensitive', 'IgnoreExcludeSettings', 'WordMatch']; @@ -80,12 +81,18 @@ export function activate(context: vscode.ExtensionContext) { return lineResult.allLocations; } - const translateRangeSidewaysBy = (r: vscode.Range, n: number) => - r.with({ start: new vscode.Position(r.start.line, Math.max(0, n - r.start.character)), end: new vscode.Position(r.end.line, Math.max(0, n - r.end.character)) }); + const location = lineResult.locations.find(l => l.originSelectionRange.contains(position)); + if (!location) { + return []; + } + const targetPos = new vscode.Position( + location.targetSelectionRange.start.line, + location.targetSelectionRange.start.character + (position.character - location.originSelectionRange.start.character) + ); return [{ - ...lineResult.location, - targetSelectionRange: translateRangeSidewaysBy(lineResult.location.targetSelectionRange!, position.character - 1) + ...location, + targetSelectionRange: new vscode.Range(targetPos, targetPos), }]; } }), @@ -93,7 +100,7 @@ export function activate(context: vscode.ExtensionContext) { vscode.languages.registerDocumentLinkProvider(SEARCH_RESULT_SELECTOR, { async provideDocumentLinks(document: vscode.TextDocument, token: vscode.CancellationToken): Promise { return parseSearchResults(document, token) - .filter(({ type }) => type === 'file') + .filter(isFileLine) .map(({ location }) => ({ range: location.originSelectionRange!, target: location.targetUri })); } }), @@ -162,7 +169,7 @@ function relativePathToUri(path: string, resultsUri: vscode.Uri): vscode.Uri | u } type ParsedSearchFileLine = { type: 'file', location: vscode.LocationLink, allLocations: vscode.LocationLink[], path: string }; -type ParsedSearchResultLine = { type: 'result', location: vscode.LocationLink, isContext: boolean, prefixRange: vscode.Range }; +type ParsedSearchResultLine = { type: 'result', locations: Required[], isContext: boolean, prefixRange: vscode.Range }; type ParsedSearchResults = Array; const isFileLine = (line: ParsedSearchResultLine | ParsedSearchFileLine): line is ParsedSearchFileLine => line.type === 'file'; const isResultLine = (line: ParsedSearchResultLine | ParsedSearchFileLine): line is ParsedSearchResultLine => line.type === 'result'; @@ -211,17 +218,35 @@ function parseSearchResults(document: vscode.TextDocument, token?: vscode.Cancel const lineNumber = +_lineNumber - 1; const resultStart = (indentation + _lineNumber + seperator + resultIndentation).length; const metadataOffset = (indentation + _lineNumber + seperator).length; + const targetRange = new vscode.Range(Math.max(lineNumber - 3, 0), 0, lineNumber + 3, line.length); - const location: vscode.LocationLink = { - targetRange: new vscode.Range(Math.max(lineNumber - 3, 0), 0, lineNumber + 3, line.length), - targetSelectionRange: new vscode.Range(lineNumber, metadataOffset, lineNumber, metadataOffset), - targetUri: currentTarget, - originSelectionRange: new vscode.Range(i, resultStart, i, line.length), - }; + let lastEnd = resultStart; + let offset = 0; + let locations: Required[] = []; + ELISION_REGEX.lastIndex = resultStart; + for (let match: RegExpExecArray | null; (match = ELISION_REGEX.exec(line));) { + locations.push({ + targetRange, + targetSelectionRange: new vscode.Range(lineNumber, offset, lineNumber, offset), + targetUri: currentTarget, + originSelectionRange: new vscode.Range(i, lastEnd, i, ELISION_REGEX.lastIndex - match[0].length), + }); - currentTargetLocations?.push(location); + offset += (ELISION_REGEX.lastIndex - lastEnd - match[0].length) + Number(match[1]); + lastEnd = ELISION_REGEX.lastIndex; + } - links[i] = { type: 'result', location, isContext: seperator === ' ', prefixRange: new vscode.Range(i, 0, i, metadataOffset) }; + if (lastEnd < line.length) { + locations.push({ + targetRange, + targetSelectionRange: new vscode.Range(lineNumber, offset, lineNumber, offset), + targetUri: currentTarget, + originSelectionRange: new vscode.Range(i, lastEnd, i, line.length), + }); + } + + currentTargetLocations?.push(...locations); + links[i] = { type: 'result', locations, isContext: seperator === ' ', prefixRange: new vscode.Range(i, 0, i, metadataOffset) }; } } diff --git a/extensions/search-result/syntaxes/generateTMLanguage.js b/extensions/search-result/syntaxes/generateTMLanguage.js index fb74d3696..c0170e514 100644 --- a/extensions/search-result/syntaxes/generateTMLanguage.js +++ b/extensions/search-result/syntaxes/generateTMLanguage.js @@ -83,6 +83,7 @@ const scopes = { meta: 'meta.resultLine.search', metaSingleLine: 'meta.resultLine.singleLine.search', metaMultiLine: 'meta.resultLine.multiLine.search', + elision: 'comment meta.resultLine.elision', prefix: { meta: 'constant.numeric.integer meta.resultLinePrefix.search', metaContext: 'meta.resultLinePrefix.contextLinePrefix.search', @@ -220,6 +221,10 @@ const plainText = [ '4': { name: [scopes.resultBlock.result.prefix.meta, scopes.resultBlock.result.prefix.metaContext].join(' ') }, '5': { name: scopes.resultBlock.result.prefix.lineNumber }, } + }, + { + match: '⟪ [0-9]+ characters skipped ⟫', + name: [scopes.resultBlock.meta, scopes.resultBlock.result.elision].join(' '), } ]; diff --git a/extensions/search-result/syntaxes/searchResult.tmLanguage.json b/extensions/search-result/syntaxes/searchResult.tmLanguage.json index e2687fe8a..e319191c4 100644 --- a/extensions/search-result/syntaxes/searchResult.tmLanguage.json +++ b/extensions/search-result/syntaxes/searchResult.tmLanguage.json @@ -263,6 +263,10 @@ "name": "meta.resultLinePrefix.lineNumber.search" } } + }, + { + "match": "⟪ [0-9]+ characters skipped ⟫", + "name": "meta.resultBlock.search comment meta.resultLine.elision" } ], "repository": { diff --git a/extensions/sql/build/update-grammar.js b/extensions/sql/build/update-grammar.js new file mode 100644 index 000000000..7f95e256b --- /dev/null +++ b/extensions/sql/build/update-grammar.js @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +var updateGrammar = require('../../../build/npm/update-grammar'); +updateGrammar.update('microsoft/vscode-mssql', 'syntaxes/SQL.plist', './syntaxes/sql.tmLanguage.json', undefined, 'main'); + + diff --git a/extensions/sql/cgmanifest.json b/extensions/sql/cgmanifest.json index 110a300af..3f0ac384a 100644 --- a/extensions/sql/cgmanifest.json +++ b/extensions/sql/cgmanifest.json @@ -14,4 +14,4 @@ } ], "version": 1 -} +} \ No newline at end of file diff --git a/extensions/sql/package.json b/extensions/sql/package.json index 5063c9d73..437d114c4 100644 --- a/extensions/sql/package.json +++ b/extensions/sql/package.json @@ -5,21 +5,32 @@ "version": "1.0.0", "publisher": "vscode", "license": "MIT", - "engines": { "vscode": "*" }, + "engines": { + "vscode": "*" + }, "scripts": { - "update-grammar": "node ../../build/npm/update-grammar.js microsoft/vscode-mssql syntaxes/SQL.plist ./syntaxes/sql.tmLanguage.json" + "update-grammar": "node ./build/update-grammar.js" }, "contributes": { - "languages": [{ - "id": "sql", - "extensions": [ ".sql", ".dsql" ], - "aliases": [ "SQL" ], - "configuration": "./language-configuration.json" - }], - "grammars": [{ - "language": "sql", - "scopeName": "source.sql", - "path": "./syntaxes/sql.tmLanguage.json" - }] + "languages": [ + { + "id": "sql", + "extensions": [ + ".sql", + ".dsql" + ], + "aliases": [ + "SQL" + ], + "configuration": "./language-configuration.json" + } + ], + "grammars": [ + { + "language": "sql", + "scopeName": "source.sql", + "path": "./syntaxes/sql.tmLanguage.json" + } + ] } } diff --git a/extensions/sql/syntaxes/sql.tmLanguage.json b/extensions/sql/syntaxes/sql.tmLanguage.json index 446d20081..76b4c39b5 100644 --- a/extensions/sql/syntaxes/sql.tmLanguage.json +++ b/extensions/sql/syntaxes/sql.tmLanguage.json @@ -17,7 +17,7 @@ "name": "text.bracketed" }, { - "match": "\\b(?i)(abort|abort_after_wait|absent|absolute|accent_sensitivity|acceptable_cursopt|acp|action|activation|address|admin|aes_128|aes_192|aes_256|affinity|after|aggregate|algorithm|all_constraints|all_errormsgs|all_indexes|all_levels|all_results|allow_connections|allow_dup_row|allow_encrypted_value_modifications|allow_page_locks|allow_row_locks|allow_snapshot_isolation|alter|altercolumn|always|anonymous|ansi_defaults|ansi_null_default|ansi_null_dflt_off|ansi_null_dflt_on|ansi_nulls|ansi_padding|ansi_warnings|appdomain|append|application|apply|arithabort|arithignore|assembly|asymmetric|asynchronous_commit|at|atan2|atomic|attach|attach_force_rebuild_log|attach_rebuild_log|audit|auth_realm|authentication|auto|auto_cleanup|auto_close|auto_create_statistics|auto_shrink|auto_update_statistics|auto_update_statistics_async|automated_backup_preference|automatic|autopilot|availability|availability_mode|backup_priority|base64|basic|batches|batchsize|before|between|bigint|binary|binding|bit|block|blocksize|bmk|break|broker|broker_instance|bucket_count|buffer|buffercount|bulk_logged|by|call|caller|card|case|cast|catalog|catch|cert|certificate|change_retention|change_tracking|change_tracking_context|changes|char|character|character_set|check_expiration|check_policy|checkconstraints|checkindex|checkpoint|cleanup_policy|clear|clear_port|close|codepage|collection|column_encryption_key|column_master_key|columnstore|columnstore_archive|colv_80_to_100|colv_100_to_80|commit_differential_base|committed|compatibility_level|compress_all_row_groups|compression|compression_delay|concat_null_yields_null|concatenate|configuration|connect|continue|continue_after_error|contract|contract_name|control|conversation|conversation_group_id|conversation_handle|copy|copy_only|count_rows|counter|create(\\s+or\\s+alter)?|credential|cross|cryptographic|cryptographic_provider|cube|cursor_close_on_commit|cursor_default|data|data_compression|data_flush_interval_seconds|data_mirroring|data_purity|data_source|database|database_name|database_snapshot|datafiletype|date_correlation_optimization|date|datefirst|dateformat|date_format|datetime|datetime2|datetimeoffset|days|db_chaining|dbid|dbidexec|dbo_only|deadlock_priority|deallocate|dec|decimal|declare(\\s+cursor)?|decrypt|decrypt_a|decryption|default_database|default_language|default_logon_domain|default_schema|definition|delay|delayed_durability|delimitedtext|density_vector|dependent|des|description|desired_state|desx|differential|digest|disable|disable_broker|disable_def_cnst_chk|disabled|disk|distinct|distributed|distribution|drop|drop_existing|dts_buffers|dump|durability|dynamic|edition|elements|else|emergency|empty|enable|enable_broker|enabled|encoding|encrypted|encrypted_value|encryption|encryption_type|end|endpoint|endpoint_url|enhancedintegrity|entry|error_broker_conversations|errorfile|estimateonly|event|except|exec|executable|execute|exists|expand|expiredate|expiry_date|explicit|external|external_access|failover|failover_mode|failure_condition_level|fast|fast_forward|fastfirstrow|federated_service_account|fetch|field_terminator|fieldterminator|file|filelistonly|filegroup|filename|filestream|filestream_log|filestream_on|filetable|file_format|filter|first_row|fips_flagger|fire_triggers|first|firstrow|float|flush_interval_seconds|fmtonly|following|force|force_failover_allow_data_loss|force_service_allow_data_loss|forced|forceplan|formatfile|format_options|format_type|formsof|forward_only|free_cursors|free_exec_context|fullscan|fulltext|fulltextall|fulltextkey|function|generated|get|geography|geometry|global|go|goto|governor|guid|hadoop|hardening|hash|hashed|header_limit|headeronly|health_check_timeout|hidden|hierarchyid|histogram|histogram_steps|hits_cursors|hits_exec_context|hours|http|identity|identity_value|if|ifnull|ignore_constraints|ignore_dup_key|ignore_dup_row|ignore_triggers|image|immediate|implicit_transactions|include|include_null_values|inflectional|init|initiator|insensitive|insert|instead|int|integer|integrated|intersect|intermediate|interval_length_minutes|into|inuse_cursors|inuse_exec_context|io|is|isabout|iso_week|isolation|job_tracker_location|json|keep|keep_nulls|keep_replication|keepdefaults|keepfixed|keepidentity|keepnulls|kerberos|key|key_path|key_source|key_store_provider_name|keyset|kill|kilobytes_per_batch|labelonly|langid|language|last|lastrow|legacy_cardinality_estimation|length|level|lifetime|lineage_80_to_100|lineage_100_to_80|listener_ip|listener_port|load|loadhistory|lob_compaction|local|local_service_name|locate|location|lock_escalation|lock_timeout|lockres|login|login_type|loop|manual|mark_in_use_for_removal|masked|master|max_queue_readers|max_duration|max_outstanding_io_per_volume|maxdop|maxerrors|maxlength|maxtransfersize|max_plans_per_query|max_storage_size_mb|mediadescription|medianame|mediapassword|memogroup|memory_optimized|merge|message|message_forward_size|message_forwarding|microsecond|millisecond|minutes|mirror_address|misses_cursors|misses_exec_context|mixed|modify|money|move|multi_user|must_change|name|namespace|nanosecond|native|native_compilation|nchar|ncharacter|never|new_account|new_broker|newname|next|no|no_browsetable|no_checksum|no_compression|no_infomsgs|no_triggers|no_truncate|nocount|noexec|noexpand|noformat|noinit|nolock|nonatomic|nondurable|none|norecompute|norecovery|noreset|norewind|noskip|not|notification|nounload|now|nowait|ntext|ntlm|numeric|numeric_roundabort|nvarchar|object|objid|oem|offline|old_account|online|operation_mode|open|openjson|optimistic|option|orc|out|outer|output|over|override|owner|ownership|pad_index|page|page_checksum|page_verify|pagecount|paglock|param|parameter_sniffing|parameter_type_expansion|parameterization|parquet|parseonly|partial|partition|partner|password|path|pause|percentage|permission_set|persisted|period|physical_only|plan_forcing_mode|policy|pool|population|ports|preceding|precision|predicate|presume_abort|primary|primary_role|print|prior|priority |priority_level|private|proc(edure)?|procedure_name|profile|provider|query_capture_mode|query_governor_cost_limit|query_optimizer_hotfixes|query_store|queue|quoted_identifier|raiserror|range|raw|rcfile|rc2|rc4|rc4_128|rdbms|read_committed_snapshot|read|read_only|read_write|readcommitted|readcommittedlock|readonly|readpast|readuncommitted|readwrite|real|rebuild|receive|recmodel_70backcomp|recompile|reconfigure|recovery|recursive|recursive_triggers|redo_queue|reject_sample_value|reject_type|reject_value|relative|remote|remote_data_archive|remote_proc_transactions|remote_service_name|remove|removed_cursors|removed_exec_context|reorganize|repeat|repeatable|repeatableread|replica|replicated|replnick_100_to_80|replnickarray_80_to_100|replnickarray_100_to_80|required|required_cursopt|resample|reset|resource|resource_manager_location|restart|restore|restricted_user|resume|retaindays|retention|return|revert|rewind|rewindonly|returns|robust|role|rollup|root|round_robin|route|row|rowdump|rowguidcol|rowlock|row_terminator|rows|rows_per_batch|rowsets_only|rowterminator|rowversion|rsa_1024|rsa_2048|rsa_3072|rsa_4096|rsa_512|safe|safety|sample|save|schema|schemabinding|scoped|scroll|scroll_locks|sddl|secexpr|secondary|secondary_only|secondary_role|secret|security|securityaudit|selective|self|send|sent|sequence|serde_method|serializable|server|service|service_broker|service_name|service_objective|session_timeout|session|sessions|seterror|setopts|sets|shard_map_manager|shard_map_name|sharded|shared_memory|show_statistics|showplan_all|showplan_text|showplan_xml|showplan_xml_with_recompile|shrinkdb|shutdown|sid|signature|simple|single_blob|single_clob|single_nclob|single_user|singleton|site|size_based_cleanup_mode|skip|smalldatetime|smallint|smallmoney|snapshot|snapshot_import|snapshotrestorephase|soap|softnuma|sort_in_tempdb|sorted_data|sorted_data_reorg|spatial|sql|sql_bigint|sql_binary|sql_bit|sql_char|sql_date|sql_decimal|sql_double|sql_float|sql_guid|sql_handle|sql_longvarbinary|sql_longvarchar|sql_numeric|sql_real|sql_smallint|sql_time|sql_timestamp|sql_tinyint|sql_tsi_day|sql_tsi_frac_second|sql_tsi_hour|sql_tsi_minute|sql_tsi_month|sql_tsi_quarter|sql_tsi_second|sql_tsi_week|sql_tsi_year|sql_type_date|sql_type_time|sql_type_timestamp|sql_varbinary|sql_varchar|sql_variant|sql_wchar|sql_wlongvarchar|ssl|ssl_port|standard|standby|start|start_date|started|stat_header|state|statement|static|statistics|statistics_incremental|statistics_norecompute|statistics_only|statman|stats_stream|status|stop|stop_on_error|stopat|stopatmark|stopbeforemark|stoplist|stopped|string_delimiter|subject|supplemental_logging|supported|suspend|symmetric|synchronous_commit|synonym|sysname|system|system_time|system_versioning|table|tableresults|tablock|tablockx|take|tape|target|target_index|target_partition|tcp|temporal_history_retention|text|textimage_on|then|thesaurus|throw|time|timeout|timestamp|tinyint|to|top|torn_page_detection|track_columns_updated|tran|transaction|transfer|triple_des|triple_des_3key|truncate|trustworthy|try|tsql|type|type_desc|type_warning|tzoffset|uid|unbounded|uncommitted|uniqueidentifier|unlimited|unload|unlock|unsafe|updlock|url|use|useplan|useroptions|use_type_default|using|utcdatetime|valid_xml|validation|value|values|varbinary|varchar|verbose|verifyonly|version|view_metadata|virtual_device|visiblity|waitfor|webmethod|weekday|weight|well_formed_xml|when|while|widechar|widechar_ansi|widenative|windows|with|within|witness|without|without_array_wrapper|workload|wsdl|xact_abort|xlock|xml|xmlschema|xquery|xsinil|zone)\\b", + "match": "\\b(?i)(abort|abort_after_wait|absent|absolute|accent_sensitivity|acceptable_cursopt|acp|action|activation|add|address|admin|aes_128|aes_192|aes_256|affinity|after|aggregate|algorithm|all_constraints|all_errormsgs|all_indexes|all_levels|all_results|allow_connections|allow_dup_row|allow_encrypted_value_modifications|allow_page_locks|allow_row_locks|allow_snapshot_isolation|alter|altercolumn|always|anonymous|ansi_defaults|ansi_null_default|ansi_null_dflt_off|ansi_null_dflt_on|ansi_nulls|ansi_padding|ansi_warnings|appdomain|append|application|apply|arithabort|arithignore|assembly|asymmetric|asynchronous_commit|at|atan2|atomic|attach|attach_force_rebuild_log|attach_rebuild_log|audit|auth_realm|authentication|auto|auto_cleanup|auto_close|auto_create_statistics|auto_shrink|auto_update_statistics|auto_update_statistics_async|automated_backup_preference|automatic|autopilot|availability|availability_mode|backup_priority|base64|basic|batches|batchsize|before|between|bigint|binary|binding|bit|block|blocksize|bmk|break|broker|broker_instance|bucket_count|buffer|buffercount|bulk_logged|by|call|caller|card|case|catalog|catch|cert|certificate|change_retention|change_tracking|change_tracking_context|changes|char|character|character_set|check_expiration|check_policy|checkconstraints|checkindex|checkpoint|cleanup_policy|clear|clear_port|close|clustered|codepage|collection|column_encryption_key|column_master_key|columnstore|columnstore_archive|colv_80_to_100|colv_100_to_80|commit_differential_base|committed|compatibility_level|compress_all_row_groups|compression|compression_delay|concat_null_yields_null|concatenate|configuration|connect|continue|continue_after_error|contract|contract_name|control|conversation|conversation_group_id|conversation_handle|copy|copy_only|count_rows|counter|create(\\s+or\\s+alter)?|credential|cross|cryptographic|cryptographic_provider|cube|cursor|cursor_close_on_commit|cursor_default|data|data_compression|data_flush_interval_seconds|data_mirroring|data_purity|data_source|database|database_name|database_snapshot|datafiletype|date_correlation_optimization|date|datefirst|dateformat|date_format|datetime|datetime2|datetimeoffset|days|db_chaining|dbid|dbidexec|dbo_only|deadlock_priority|deallocate|dec|decimal|declare|decrypt|decrypt_a|decryption|default_database|default_language|default_logon_domain|default_schema|definition|delay|delayed_durability|delimitedtext|density_vector|dependent|des|description|desired_state|desx|differential|digest|disable|disable_broker|disable_def_cnst_chk|disabled|disk|distinct|distributed|distribution|drop|drop_existing|dts_buffers|dump|durability|dynamic|edition|elements|else|emergency|empty|enable|enable_broker|enabled|encoding|encrypted|encrypted_value|encryption|encryption_type|end|endpoint|endpoint_url|enhancedintegrity|entry|error_broker_conversations|errorfile|estimateonly|event|except|exec|executable|execute|exists|expand|expiredate|expiry_date|explicit|external|external_access|failover|failover_mode|failure_condition_level|fast|fast_forward|fastfirstrow|federated_service_account|fetch|field_terminator|fieldterminator|file|filelistonly|filegroup|filename|filestream|filestream_log|filestream_on|filetable|file_format|filter|first_row|fips_flagger|fire_triggers|first|firstrow|float|flush_interval_seconds|fmtonly|following|force|force_failover_allow_data_loss|force_service_allow_data_loss|forced|forceplan|formatfile|format_options|format_type|formsof|forward_only|free_cursors|free_exec_context|fullscan|fulltext|fulltextall|fulltextkey|function|generated|get|geography|geometry|global|go|goto|governor|guid|hadoop|hardening|hash|hashed|header_limit|headeronly|health_check_timeout|hidden|hierarchyid|histogram|histogram_steps|hits_cursors|hits_exec_context|hours|http|identity|identity_value|if|ifnull|ignore_constraints|ignore_dup_key|ignore_dup_row|ignore_triggers|image|immediate|implicit_transactions|include|include_null_values|index|inflectional|init|initiator|insensitive|insert|instead|int|integer|integrated|intersect|intermediate|interval_length_minutes|into|inuse_cursors|inuse_exec_context|io|is|isabout|iso_week|isolation|job_tracker_location|json|keep|keep_nulls|keep_replication|keepdefaults|keepfixed|keepidentity|keepnulls|kerberos|key|key_path|key_source|key_store_provider_name|keyset|kill|kilobytes_per_batch|labelonly|langid|language|last|lastrow|legacy_cardinality_estimation|length|level|lifetime|lineage_80_to_100|lineage_100_to_80|listener_ip|listener_port|load|loadhistory|lob_compaction|local|local_service_name|locate|location|lock_escalation|lock_timeout|lockres|login|login_type|loop|manual|mark_in_use_for_removal|masked|master|matched|max_queue_readers|max_duration|max_outstanding_io_per_volume|maxdop|maxerrors|maxlength|maxtransfersize|max_plans_per_query|max_storage_size_mb|mediadescription|medianame|mediapassword|memogroup|memory_optimized|merge|message|message_forward_size|message_forwarding|microsecond|millisecond|minutes|mirror_address|misses_cursors|misses_exec_context|mixed|modify|money|move|multi_user|must_change|name|namespace|nanosecond|native|native_compilation|nchar|ncharacter|never|new_account|new_broker|newname|next|no|no_browsetable|no_checksum|no_compression|no_infomsgs|no_triggers|no_truncate|nocount|noexec|noexpand|noformat|noinit|nolock|nonatomic|nonclustered|nondurable|none|norecompute|norecovery|noreset|norewind|noskip|not|notification|nounload|now|nowait|ntext|ntlm|numeric|numeric_roundabort|nvarchar|object|objid|oem|offline|old_account|online|operation_mode|open|openjson|optimistic|option|orc|out|outer|output|over|override|owner|ownership|pad_index|page|page_checksum|page_verify|pagecount|paglock|param|parameter_sniffing|parameter_type_expansion|parameterization|parquet|parseonly|partial|partition|partner|password|path|pause|percentage|permission_set|persisted|period|physical_only|plan_forcing_mode|policy|pool|population|ports|preceding|precision|predicate|presume_abort|primary|primary_role|print|prior|priority |priority_level|private|proc(edure)?|procedure_name|profile|provider|query_capture_mode|query_governor_cost_limit|query_optimizer_hotfixes|query_store|queue|quoted_identifier|raiserror|range|raw|rcfile|rc2|rc4|rc4_128|rdbms|read_committed_snapshot|read|read_only|read_write|readcommitted|readcommittedlock|readonly|readpast|readuncommitted|readwrite|real|rebuild|receive|recmodel_70backcomp|recompile|reconfigure|recovery|recursive|recursive_triggers|redo_queue|reject_sample_value|reject_type|reject_value|relative|remote|remote_data_archive|remote_proc_transactions|remote_service_name|remove|removed_cursors|removed_exec_context|reorganize|repeat|repeatable|repeatableread|replica|replicated|replnick_100_to_80|replnickarray_80_to_100|replnickarray_100_to_80|required|required_cursopt|resample|reset|resource|resource_manager_location|restart|restore|restricted_user|resume|retaindays|retention|return|revert|rewind|rewindonly|returns|robust|role|rollup|root|round_robin|route|row|rowdump|rowguidcol|rowlock|row_terminator|rows|rows_per_batch|rowsets_only|rowterminator|rowversion|rsa_1024|rsa_2048|rsa_3072|rsa_4096|rsa_512|safe|safety|sample|save|schema|schemabinding|scoped|scroll|scroll_locks|sddl|secexpr|secondary|secondary_only|secondary_role|secret|security|securityaudit|selective|self|send|sent|sequence|serde_method|serializable|server|service|service_broker|service_name|service_objective|session_timeout|session|sessions|seterror|setopts|sets|shard_map_manager|shard_map_name|sharded|shared_memory|show_statistics|showplan_all|showplan_text|showplan_xml|showplan_xml_with_recompile|shrinkdb|shutdown|sid|signature|simple|single_blob|single_clob|single_nclob|single_user|singleton|site|size_based_cleanup_mode|skip|smalldatetime|smallint|smallmoney|snapshot|snapshot_import|snapshotrestorephase|soap|softnuma|sort_in_tempdb|sorted_data|sorted_data_reorg|spatial|sql|sql_bigint|sql_binary|sql_bit|sql_char|sql_date|sql_decimal|sql_double|sql_float|sql_guid|sql_handle|sql_longvarbinary|sql_longvarchar|sql_numeric|sql_real|sql_smallint|sql_time|sql_timestamp|sql_tinyint|sql_tsi_day|sql_tsi_frac_second|sql_tsi_hour|sql_tsi_minute|sql_tsi_month|sql_tsi_quarter|sql_tsi_second|sql_tsi_week|sql_tsi_year|sql_type_date|sql_type_time|sql_type_timestamp|sql_varbinary|sql_varchar|sql_variant|sql_wchar|sql_wlongvarchar|ssl|ssl_port|standard|standby|start|start_date|started|stat_header|state|statement|static|statistics|statistics_incremental|statistics_norecompute|statistics_only|statman|stats_stream|status|stop|stop_on_error|stopat|stopatmark|stopbeforemark|stoplist|stopped|string_delimiter|subject|supplemental_logging|supported|suspend|symmetric|synchronous_commit|synonym|sysname|system|system_time|system_versioning|table|tableresults|tablock|tablockx|take|tape|target|target_index|target_partition|tcp|temporal_history_retention|text|textimage_on|then|thesaurus|throw|time|timeout|timestamp|tinyint|to|top|torn_page_detection|track_columns_updated|tran|transaction|transfer|triple_des|triple_des_3key|truncate|trustworthy|try|tsql|type|type_desc|type_warning|tzoffset|uid|unbounded|uncommitted|unique|uniqueidentifier|unlimited|unload|unlock|unsafe|updlock|url|use|useplan|useroptions|use_type_default|using|utcdatetime|valid_xml|validation|value|values|varbinary|varchar|verbose|verifyonly|version|view_metadata|virtual_device|visiblity|waitfor|webmethod|weekday|weight|well_formed_xml|when|while|widechar|widechar_ansi|widenative|windows|with|within|witness|without|without_array_wrapper|workload|wsdl|xact_abort|xlock|xml|xmlschema|xquery|xsinil|zone)\\b", "name": "keyword.other.sql" }, { @@ -131,7 +131,7 @@ "match": "(?xi)\n\n\t\t\t\t# normal stuff, capture 1\n\t\t\t\t \\b(bigint|bigserial|bit|boolean|box|bytea|cidr|circle|date|double\\sprecision|inet|int|integer|line|lseg|macaddr|money|oid|path|point|polygon|real|serial|smallint|sysdate|text)\\b\n\n\t\t\t\t# numeric suffix, capture 2 + 3i\n\t\t\t\t|\\b(bit\\svarying|character\\s(?:varying)?|tinyint|var\\schar|float|interval)\\((\\d+)\\)\n\n\t\t\t\t# optional numeric suffix, capture 4 + 5i\n\t\t\t\t|\\b(char|number|varchar\\d?)\\b(?:\\((\\d+)\\))?\n\n\t\t\t\t# special case, capture 6 + 7i + 8i\n\t\t\t\t|\\b(numeric|decimal)\\b(?:\\((\\d+),(\\d+)\\))?\n\n\t\t\t\t# special case, captures 9, 10i, 11\n\t\t\t\t|\\b(times?)\\b(?:\\((\\d+)\\))?(\\swith(?:out)?\\stime\\szone\\b)?\n\n\t\t\t\t# special case, captures 12, 13, 14i, 15\n\t\t\t\t|\\b(timestamp)(?:(s|tz))?\\b(?:\\((\\d+)\\))?(\\s(with|without)\\stime\\szone\\b)?\n\n\t\t\t" }, { - "match": "(?i:\\b((?:primary|foreign)\\s+key|references|on\\sdelete(\\s+cascade)?|check|constraint)\\b)", + "match": "(?i:\\b((?:primary|foreign)\\s+key|references|on\\sdelete(\\s+cascade)?|nocheck|check|constraint|collate|default)\\b)", "name": "storage.modifier.sql" }, { @@ -139,7 +139,7 @@ "name": "constant.numeric.sql" }, { - "match": "(?i:\\b(select(\\s+distinct)?|insert\\s+(ignore\\s+)?into|update|delete|from|set|where|group\\sby|or|like|and|union(\\s+all)?|having|order\\sby|limit|(inner|cross)\\s+join|join|straight_join|full\\s+outer\\s+join|(left|right)(\\s+outer)?\\s+join|natural(\\s+(left|right)(\\s+outer)?)?\\s+join)\\b)", + "match": "(?i:\\b(select(\\s+distinct)?|insert\\s+(ignore\\s+)?into|update|delete|from|set|where|group\\s+by|or|like|and|union(\\s+all)?|having|order\\s+by|limit|(inner|cross)\\s+join|join|straight_join|full\\s+outer\\s+join|(left|right)(\\s+outer)?\\s+join|natural(\\s+(left|right)(\\s+outer)?)?\\s+join)\\b)", "name": "keyword.other.DML.sql" }, { @@ -239,7 +239,7 @@ "name": "support.function.security.sql" }, { - "match": "(?i)\\b(ascii|char|charindex|concat|difference|format|left|len|lower|ltrim|nchar|patindex|quotename|replace|replicate|reverse|rtrim|soundex|space|str|string_agg|string_escape|string_split|stuff|substring|translate|trim|unicode|upper)\\b", + "match": "(?i)\\b(ascii|char|charindex|concat|difference|format|left|len|lower|ltrim|nchar|nodes|patindex|quotename|replace|replicate|reverse|right|rtrim|soundex|space|str|string_agg|string_escape|string_split|stuff|substring|translate|trim|unicode|upper)\\b", "name": "support.function.string.sql" }, { @@ -516,4 +516,4 @@ ] } } -} +} \ No newline at end of file diff --git a/extensions/theme-abyss/package.json b/extensions/theme-abyss/package.json index bdeb6a2a6..9543824f3 100644 --- a/extensions/theme-abyss/package.json +++ b/extensions/theme-abyss/package.json @@ -9,10 +9,11 @@ "contributes": { "themes": [ { - "label": "Abyss", + "id": "Abyss", + "label": "%themeLabel%", "uiTheme": "vs-dark", "path": "./themes/abyss-color-theme.json" } ] } -} \ No newline at end of file +} diff --git a/extensions/theme-abyss/package.nls.json b/extensions/theme-abyss/package.nls.json index 48fbcd858..a25492d70 100644 --- a/extensions/theme-abyss/package.nls.json +++ b/extensions/theme-abyss/package.nls.json @@ -1,4 +1,5 @@ { "displayName": "Abyss Theme", - "description": "Abyss theme for Visual Studio Code" -} \ No newline at end of file + "description": "Abyss theme for Visual Studio Code", + "themeLabel": "Abyss" +} diff --git a/extensions/theme-abyss/themes/abyss-color-theme.json b/extensions/theme-abyss/themes/abyss-color-theme.json index 642a9e3d9..8e97bbb6d 100644 --- a/extensions/theme-abyss/themes/abyss-color-theme.json +++ b/extensions/theme-abyss/themes/abyss-color-theme.json @@ -172,14 +172,14 @@ "scope": "invalid", "settings": { "fontStyle": "", - "foreground": "#F8F8F0" + "foreground": "#A22D44" } }, { "name": "Invalid deprecated", "scope": "invalid.deprecated", "settings": { - "foreground": "#F8F8F0" + "foreground": "#A22D44" } }, { diff --git a/extensions/theme-defaults/package.json b/extensions/theme-defaults/package.json index 56d5fa3f4..75e158342 100644 --- a/extensions/theme-defaults/package.json +++ b/extensions/theme-defaults/package.json @@ -11,31 +11,31 @@ "themes": [ { "id": "Default Dark+", - "label": "Dark+ (default dark)", + "label": "%darkPlusColorThemeLabel%", "uiTheme": "vs-dark", "path": "./themes/dark_plus.json" }, { "id": "Default Light+", - "label": "Light+ (default light)", + "label": "%lightPlusColorThemeLabel%", "uiTheme": "vs", "path": "./themes/light_plus.json" }, { "id": "Visual Studio Dark", - "label": "Dark (Visual Studio)", + "label": "%darkColorThemeLabel%", "uiTheme": "vs-dark", "path": "./themes/dark_vs.json" }, { "id": "Visual Studio Light", - "label": "Light (Visual Studio)", + "label": "%lightColorThemeLabel%", "uiTheme": "vs", "path": "./themes/light_vs.json" }, { "id": "Default High Contrast", - "label": "High Contrast", + "label": "%hcColorThemeLabel%", "uiTheme": "hc-black", "path": "./themes/hc_black.json" } @@ -43,9 +43,9 @@ "iconThemes": [ { "id": "vs-minimal", - "label": "Minimal (Visual Studio Code)", + "label": "%minimalIconThemeLabel%", "path": "./fileicons/vs_minimal-icon-theme.json" } ] } -} \ No newline at end of file +} diff --git a/extensions/theme-defaults/package.nls.json b/extensions/theme-defaults/package.nls.json index f03211eed..4cf3da530 100644 --- a/extensions/theme-defaults/package.nls.json +++ b/extensions/theme-defaults/package.nls.json @@ -1,4 +1,10 @@ { "displayName": "Default Themes", - "description": "The default Visual Studio light and dark themes" -} \ No newline at end of file + "description": "The default Visual Studio light and dark themes", + "darkPlusColorThemeLabel": "Dark+ (default dark)", + "lightPlusColorThemeLabel": "Light+ (default light)", + "darkColorThemeLabel": "Dark (Visual Studio)", + "lightColorThemeLabel": "Light (Visual Studio)", + "hcColorThemeLabel": "High Contrast", + "minimalIconThemeLabel": "Minimal (Visual Studio Code)" +} diff --git a/extensions/theme-defaults/themes/hc_black_defaults.json b/extensions/theme-defaults/themes/hc_black_defaults.json index d0382cec2..ea03fb3fa 100644 --- a/extensions/theme-defaults/themes/hc_black_defaults.json +++ b/extensions/theme-defaults/themes/hc_black_defaults.json @@ -9,21 +9,14 @@ "statusBarItem.remoteBackground": "#00000000", "sideBarTitle.foreground": "#FFFFFF" }, - "settings": [ - { - "settings": { - "foreground": "#FFFFFF", - "background": "#000000" - } - }, + "tokenColors": [ { "scope": [ "meta.embedded", "source.groovy.embedded" ], "settings": { - "foreground": "#FFFFFF", - "background": "#000000" + "foreground": "#FFFFFF" } }, { diff --git a/extensions/theme-defaults/themes/light_defaults.json b/extensions/theme-defaults/themes/light_defaults.json index 0f82502db..05fce6cd1 100644 --- a/extensions/theme-defaults/themes/light_defaults.json +++ b/extensions/theme-defaults/themes/light_defaults.json @@ -21,9 +21,8 @@ "sideBarSectionHeader.background": "#0000", "sideBarSectionHeader.border": "#61616130", "tab.lastPinnedBorder": "#61616130", - "notebook.focusedCellBackground": "#c8ddf150", - "notebook.cellBorderColor": "#dae3e9", - "notebook.outputContainerBackgroundColor": "#c8ddf150" + "notebook.cellBorderColor": "#E8E8E8", + "statusBarItem.errorBackground": "#c72e0f" }, "semanticHighlighting": true } diff --git a/extensions/theme-kimbie-dark/package.json b/extensions/theme-kimbie-dark/package.json index 1031c34d2..7c7ff5ea7 100644 --- a/extensions/theme-kimbie-dark/package.json +++ b/extensions/theme-kimbie-dark/package.json @@ -9,10 +9,11 @@ "contributes": { "themes": [ { - "label": "Kimbie Dark", + "id": "Kimbie Dark", + "label": "%themeLabel%", "uiTheme": "vs-dark", "path": "./themes/kimbie-dark-color-theme.json" } ] } -} \ No newline at end of file +} diff --git a/extensions/theme-kimbie-dark/package.nls.json b/extensions/theme-kimbie-dark/package.nls.json index 85c736cee..0d96b6f4a 100644 --- a/extensions/theme-kimbie-dark/package.nls.json +++ b/extensions/theme-kimbie-dark/package.nls.json @@ -1,4 +1,5 @@ { "displayName": "Kimbie Dark Theme", - "description": "Kimbie dark theme for Visual Studio Code" -} \ No newline at end of file + "description": "Kimbie dark theme for Visual Studio Code", + "themeLabel": "Kimbie Dark" +} diff --git a/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json b/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json index 8997b80df..3453c53dc 100644 --- a/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json +++ b/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json @@ -390,7 +390,7 @@ }, { "name": "Invalid", - "scope": "invalid.illegal", + "scope": "invalid", "settings": { "foreground": "#dc3958" } diff --git a/extensions/theme-monokai-dimmed/package.json b/extensions/theme-monokai-dimmed/package.json index 66c4711d9..43d950eb8 100644 --- a/extensions/theme-monokai-dimmed/package.json +++ b/extensions/theme-monokai-dimmed/package.json @@ -11,10 +11,11 @@ "contributes": { "themes": [ { - "label": "Monokai Dimmed", + "id": "Monokai Dimmed", + "label": "%themeLabel%", "uiTheme": "vs-dark", "path": "./themes/dimmed-monokai-color-theme.json" } ] } -} \ No newline at end of file +} diff --git a/extensions/theme-monokai-dimmed/package.nls.json b/extensions/theme-monokai-dimmed/package.nls.json index 3d93898e2..47baa2267 100644 --- a/extensions/theme-monokai-dimmed/package.nls.json +++ b/extensions/theme-monokai-dimmed/package.nls.json @@ -1,4 +1,5 @@ { "displayName": "Monokai Dimmed Theme", - "description": "Monokai dimmed theme for Visual Studio Code" -} \ No newline at end of file + "description": "Monokai dimmed theme for Visual Studio Code", + "themeLabel": "Monokai Dimmed" +} diff --git a/extensions/theme-monokai/package.json b/extensions/theme-monokai/package.json index 13b2db10d..b21aded1b 100644 --- a/extensions/theme-monokai/package.json +++ b/extensions/theme-monokai/package.json @@ -11,10 +11,11 @@ "contributes": { "themes": [ { - "label": "Monokai", + "id": "Monokai", + "label": "%themeLabel%", "uiTheme": "vs-dark", "path": "./themes/monokai-color-theme.json" } ] } -} \ No newline at end of file +} diff --git a/extensions/theme-monokai/package.nls.json b/extensions/theme-monokai/package.nls.json index 8e8d73c75..a5a17dc57 100644 --- a/extensions/theme-monokai/package.nls.json +++ b/extensions/theme-monokai/package.nls.json @@ -1,4 +1,5 @@ { "displayName": "Monokai Theme", - "description": "Monokai theme for Visual Studio Code" -} \ No newline at end of file + "description": "Monokai theme for Visual Studio Code", + "themeLabel": "Monokai" +} diff --git a/extensions/theme-monokai/themes/monokai-color-theme.json b/extensions/theme-monokai/themes/monokai-color-theme.json index aa30b85b1..969455d01 100644 --- a/extensions/theme-monokai/themes/monokai-color-theme.json +++ b/extensions/theme-monokai/themes/monokai-color-theme.json @@ -36,7 +36,7 @@ "tab.border": "#1e1f1c", "tab.inactiveForeground": "#ccccc7", // needs to be bright so it's readable when another editor group is focused "tab.lastPinnedBorder": "#414339", - "widget.shadow": "#000000", + "widget.shadow": "#00000098", "progressBar.background": "#75715E", "badge.background": "#75715E", "badge.foreground": "#f8f8f2", @@ -284,14 +284,14 @@ "scope": "invalid", "settings": { "fontStyle": "", - "foreground": "#F8F8F0" + "foreground": "#F44747" } }, { "name": "Invalid deprecated", "scope": "invalid.deprecated", "settings": { - "foreground": "#F8F8F0" + "foreground": "#F44747" } }, { diff --git a/extensions/theme-quietlight/package.json b/extensions/theme-quietlight/package.json index 0263925ee..f2e66e7a9 100644 --- a/extensions/theme-quietlight/package.json +++ b/extensions/theme-quietlight/package.json @@ -11,10 +11,11 @@ "contributes": { "themes": [ { - "label": "Quiet Light", + "id": "Quiet Light", + "label": "%themeLabel%", "uiTheme": "vs", "path": "./themes/quietlight-color-theme.json" } ] } -} \ No newline at end of file +} diff --git a/extensions/theme-quietlight/package.nls.json b/extensions/theme-quietlight/package.nls.json index 1873df058..30354d629 100644 --- a/extensions/theme-quietlight/package.nls.json +++ b/extensions/theme-quietlight/package.nls.json @@ -1,4 +1,5 @@ { "displayName": "Quiet Light Theme", - "description": "Quiet light theme for Visual Studio Code" -} \ No newline at end of file + "description": "Quiet light theme for Visual Studio Code", + "themeLabel": "Quiet Light" +} diff --git a/extensions/theme-quietlight/themes/quietlight-color-theme.json b/extensions/theme-quietlight/themes/quietlight-color-theme.json index 2a12b3698..e395b99e3 100644 --- a/extensions/theme-quietlight/themes/quietlight-color-theme.json +++ b/extensions/theme-quietlight/themes/quietlight-color-theme.json @@ -45,6 +45,13 @@ "foreground": "#448C27" } }, + { + "name": "Invalid", + "scope": "invalid", + "settings": { + "foreground": "#cd3131" + } + }, { "name": "Invalid - Illegal", "scope": "invalid.illegal", @@ -409,16 +416,37 @@ }, { "name": "Extra: Diff From", - "scope": "meta.diff.header.from-file", + "scope": ["meta.diff.header.from-file", "punctuation.definition.from-file.diff"], "settings": { - "foreground": "#434343" + "foreground": "#4B69C6" } }, { "name": "Extra: Diff To", - "scope": "meta.diff.header.to-file", + "scope": ["meta.diff.header.to-file", "punctuation.definition.to-file.diff"], "settings": { - "foreground": "#434343" + "foreground": "#4B69C6" + } + }, + { + "name": "diff: deleted", + "scope": "markup.deleted.diff", + "settings": { + "foreground": "#C73D20" + } + }, + { + "name": "diff: changed", + "scope": "markup.changed.diff", + "settings": { + "foreground": "#9C5D27" + } + }, + { + "name": "diff: inserted", + "scope": "markup.inserted.diff", + "settings": { + "foreground": "#448C27" } }, { diff --git a/extensions/theme-red/package.json b/extensions/theme-red/package.json index ba751a33e..a9920fdfd 100644 --- a/extensions/theme-red/package.json +++ b/extensions/theme-red/package.json @@ -9,10 +9,11 @@ "contributes": { "themes": [ { - "label": "Red", + "id": "Red", + "label": "%themeLabel%", "uiTheme": "vs-dark", "path": "./themes/Red-color-theme.json" } ] } -} \ No newline at end of file +} diff --git a/extensions/theme-red/package.nls.json b/extensions/theme-red/package.nls.json index 680fde603..d58a547e8 100644 --- a/extensions/theme-red/package.nls.json +++ b/extensions/theme-red/package.nls.json @@ -1,4 +1,5 @@ { "displayName": "Red Theme", - "description": "Red theme for Visual Studio Code" -} \ No newline at end of file + "description": "Red theme for Visual Studio Code", + "themeLabel": "Red" +} diff --git a/extensions/theme-seti/cgmanifest.json b/extensions/theme-seti/cgmanifest.json index 20f54daa8..4548b9812 100644 --- a/extensions/theme-seti/cgmanifest.json +++ b/extensions/theme-seti/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "seti-ui", "repositoryUrl": "https://github.com/jesseweed/seti-ui", - "commitHash": "7ea773d195eac3f40261897b49a2499815e9346c" + "commitHash": "4bbf2132df28c71302e305077ce20a811bf7d64b" } }, "version": "0.1.0" diff --git a/extensions/theme-seti/icons/seti.woff b/extensions/theme-seti/icons/seti.woff index 021e527020083f1efad65391f16c21f9951fe533..82e4a516665550fa3a2f7da6d0ded7b7e6dbdb4a 100644 GIT binary patch delta 34284 zcmV)UK(N2;lmgtA0u*;oMn(Vu00000i(CK;00000(_oPlKYvGKZDDW#00D#m00Te( z016cOCE4a@YUZFG150EGkq001oj00LlED*ym&Z)0Hq0EILF00A-p00A-& zape+iVR&!=05*nb0000V0000W0&xlvZeeX@004%F0003V0005z-bxwFaBp*T004)m z000BB000G8vJ;2RlL!Gp0)EevO93Z;pZNul8wDnT8bJgAQnC!!004NL?bL-<99bAe z;f(|$B*fj_-QC??iMzWyF=9j+=hga6GIeUtV+^dXZqa|=eW6z^fDS-SVRE4ss#~cl z?x<=hUacx$uc34KdTmux`JURmUTOdJtGHI>TKrsFUaO1u))vJ&>gh?~ z8O&rBvzfzO<}sfIEMyUjS;A75vAo#7f|aadHEUSQI@Ys+jm32no7uwFV%^4ecCeFO z>}C&p*~fkkaF9bB<_Jw3S|UJKW_S z_j$lW9`TqbJmneBdBIDXc~$)7uX)2;-qFH)KJbxGe5REzeB~S8`N2=xijS|3|Gye? zsc-)8r}jKyDxbM(k+nv7b}O~YQ&{PsJlB;vk+oiA?I^N#5?MPd&wHhRi^$rb{C1VD zB5OC1wNYg4F0%FzS$m4Cy+qdDD);tLdEZxL-cMxSUt~T&WIj-2K1gIfSY$p#WIj}6 zK1^gjTx32%WIj@4K1yUhT4X*(WIk48K2BslUSvK&WIj=3K1pOgS!6y%WIk2--&L6= zGM_FopCK}zDKej>@_x2|%KJGY`{s)5nBKsDK>{}$VZ?VX}B_jKlitJk^ zvTwP_z7-<-R*LLfC9-d|$i6iyKmWBNjddc8^&*W8B8`nAjZGqr%_5C0B8{ygjcp>0 z?JD21L!`G;q_<0?w_BvQN9FtXinRBMwD*g&4~Vo6inI@jv=57aw2z3in?%}2McT(i z+Q&uOCq&vOMcSuC+NVX@XGGd(McU^?+UG^u7ev|@McS7{+LuMzS47%ZMcUUy+Sf(e zH$>VuMcTJS+P6j8cSPEEMcVg7+V@4;4@BAzMcR)<+K)xrPej^JMcU6q+RsJWFGSie zMcU0G?N=i0*COqIHzMt~BJFn~?G};tdy)1Bk@iQG|Jy%_oby@aoK}%@zKERjRpgv+ zBIkS;Ip>GSIX^|tX%ji;H<5FG7dht-k#qhOIp;5tbN&`N=O2-C+C|&HAMxN1c$}2I z3A`Lvc_-RuuYKv-x_a-f+uir-?!8{=O7~jV+9gZKmMnRH6A0Ugc)@n;gxDZru)&Z( z?8M*%#s;2wW`@LIhXewHV`j+s;lv&YTf&5dz`(@B1ZLppgy4DZH{Yr1E1CD;z28&c z?yjynb?PkN^8c1|l%qNLM-2RI&d3#W+jIMKIn-Q0{Wa9BBHu!!X(7Lgdfm~m-)To- z_Nt*|5w$yi?O7BgZZA9M3l6VN60D=)XxJHc+6j8$(4B`4-T8a+qp|JPTerS?+f{>Y z+XfnAI#)QxyKm@mP8C*s<$o|;)6*?e&zsJ0+cu#vfsiFQI^MnXHJA2!@X@}ylsl2T8T!Wq z*-{G$K8u=*xSccww-N6o%}#ssHR!i@p#G>oT0^7ZD(coy*b94I=vy}%0}H_|737D3 z{FN+4B*;d9i)7Fr4bQzocyd0meP%O5^9-|W3)@p;6Fy8w^=yxaj;C@$xUy|Jc4}Ha zG&y&FRI?oIC@Q*AEU9gks6?q)1&ZDk`X$9s9GhT4RX?y5%OvO;&X*Q^iIWtZZn8*S=5_0-nWHjT6fC25;v@!3?2ojf!m{^!d2js9Kz2Vuz-t z3nMmFDlo|*IfnSZlSWR55$x%x=SFTca!2@o*Kd09#hXse&!0ly8b60(v~zX*+r1vT zX*HM8)&jmUCvsYDn7c5S^G3ZW=_F#*>qG+>PZ-_-&{axIa@2a=C`jkSmqb=Yfe!juD zDa{1XyNZFNi@1 znxnMfSE*&Hj3Eszs@@615P{mZ3lyVc18N%@r4%KqwFBlH$j&R16Lsq5nQ%BYEJiJ# zS~_MZ$LY}-jO>paCOs>VM%3|MmysWN>eiKR6UK|044%1h@V9Pqbxm8^va%k3V#H4! z7EEO(5C~daKYh&Pib7bazGKNW*5*{ExoV+Z8WrH1ilD7jU?|h^w8){{O}V$^eji42 zGz3OVnNE&d&;=R@V6KH^u&CoFLDYmR0Srozh#(4v>o^R0@JmvLAIY153E^@t=*jz_ zY;QCKMwN1?I~)Pc42Ib}ETTStFs)?vWKsa;gpacMO1X9&^<_oFp1jf<^x)GM%|R0I zgyYX_D^RChJ$cP5rW%MM%0LDgz9|@R9PxAn5)BThtE;r%#{y#-6c1jDfjlu~RG3)s zq8^4WoeT0UTgPR0dHGu6`QpI#rNZh?h2W)yojdG&({%DtWpx%~p$nyd>pDEl7i$D& zi)f5iY=*BLwC9DY)NPo)B+f%apecmg8VE$pG4WKSP!5N1QyxT1+oKE^p6=>B^=ZVA z6rEUg0t>6v7&?z#-83+R8E#D-+*5N^JV<49s=4|@Aq9RpPAupmP>a+m(35_95T*^znv}kJ(5a{w0)=+EYpC7i ze%OI7MzYRMn>YmI`j%s})CcZ%Fu@du<+cHg8;fXrsbUd_6Ms1$htcV^YK72kTG?>h z@IXWe%pEHklzAS1keZ@$OK6|~6-qTzA$ia_P+xw*;*|i%pb%o?cfA+^&48LzByED` zV6$ekSl~oZ3_2MrjzSHgn}MZS4moB}GCu`ch}fhYI%Oa=#Q;Lm%5dsb*sL!$>i;hG zRv0|qR|TQW@SsQ7FM*1i>U-3PP zb>e_p)1V&7UxvvkGBtu#AaPbL#oBCA-N-MXk%1PM@$!hDJ=RLa#8h z9HwcMF;zuS0wYkaHuM){Ro*QEUI1Ppphc<6L01wSM)~jx6)}S&s!C}_FxiMoHfYnx zs5wvt5(emhR9|yc#np={=Dm`G5iNn-g~D7lfQvwm!m-e)cB;A1Xe`w~%Yk2of~kQ` zqH0+23@us2Y)gFw!>)Gt}SY(mHv^1 z?ul;q#I+|2ADSMZ2d=sb702&DC$3tb9wz0vy<1YO)!Q=Ml-2mhxw+hd++on(eaW@* zK_b=Jpr|bz_`oj!4@XhT37Y__m55}N40`FiC(<_vaQROBQ3uX{v$Zy#j1~{ZyA?;% zd@IaZ9_3a^i0iYo9#tDyvzD9Frvh%%IMZZ_nqiErgdo zaA{bORk3u;_5)60IW-sND!EIbUZB^}fQg{xaS@FsLlla3M~pz-wLEZdG`Ng#H{fEKA)*uoWTtuma~HD7gfmx&9}*BX}|cAlAwzm}l)#_D=p zC>I)DL7#bmbxy|A*bo}ao6GHa%@8XD;}~#<8H0*Jpy;Z8i}LlK)GSZxB2fJW^QXB@ zeZPeNYi>(!NA6NdwaF&PUO*CJr&>UN0zcVD0MrvK4h2Y(>I?wlDdd)typ=+C4kH7i zyDG(c)RQ%!7h3(|mS#K)MucYj(--Vfj?CQjYO~oa=gwyI%xs)du&AW?Ph5d+Bb;=JSQ5ktIAP2H{^u9+}@3>ddP zGX56#sOsdCeKElGkcn$fx|YW=LMw!vBpOl50yMahJ7_^Oa!0G1ez_M(TGM>o{HrD*(n#e|}|<)bSKO^Uy{ zp_0k(K}fs3%NS{SVKLjQ!}hBI0lUd(o@MNie%Sagb2GpV@0Avq4XoCNNtfC`RqtKdjw+n(v^j_`_yKc&pR}2U0-lah z*qy*>_*y`~6jIYlB%Xk(1}YMM4QJRp@F(OllqA+kxobHE5m6OOcFrmp^qZ*-*zPpt zlO#oAQ&CpDS<4Nl$~{3q9~2AF&GJfH30ta`FU|O&>xTKbHvVs1CCwGvZxufp@iOu1 z2^eCk4cc(#;6m{>%L>$g>HR=OQ=DjK9&`YER2J_*=On?#+X%&K^HxXX;Gz)mRAj-2vdO)mPt(;fl71@P_%JbA!wc&M2%g* zbtF|@VBZPfv5;yMTmq*sE*S=~U!#UjtmQRZ(fmr)Q88j1;iS}Ys2 zw!PT*i$@J1j6k9TxZTjXI6s#bv2uglp4`>a7R;sxUOuodjHnz`iKr$1Lg@^^YuJ{s zY=VTkLsG?SIK7q@kK`pFy1}q2X+!==^BYij1r5-5+e%qkS!@S0s_dM-KBz{OGMFPX zJYqX8Z#s$&3=CI)tVU(j9Wdvw(9Rp~ym1ZTjp5dvYu8olKLyybH6|eURLpu!I}x_Y zbYf=rG=_VL#a>p~%bo(lGJE=#MwOEBx7V**M>}uckd_;F~Yny|#UN%anTG?%UhRY!JEz=BSk{U%WVoSL+oZV1zDO-$38< zmZ~Ce0$A~X>%~6`FWA2xx}jAEW5FP^5d+2``K61O+Ea}ZDU`RG`HS9ss8JiK96fcx zC}g01pb7BoJ=@CB7GY}aqBpnNoAU81_)kF(*D?vgLG}v?jTa|$10qQxKhY5#{1rqF z0*rhA`?$i~RxDI<#~CKr^Ln)KiQ|uKJC5Ff$zD)@Ouzdc#@wX97`^RkM!$`=hI1di z_@U9o=XG^LcIRAp=5lUbdMFU!C%`w+sEXDwz$q6dVhzh7wlMj**MRh;*}}Yt;I*AM z4ZOL(ss5MQ>&_fb?Dba-M^|nM58v>b9i`rdQ^PCg-KkF$|7hyu@tqa3e`J2)P?Fzy z`LSYuRc{9?<4bR~{kPxMeX;b@re8aB=K9OJ<1_1LZafmsUNvg%>X*?itJmBx z^M&X?mz{Y1_+|TN#*g)mU%ru_YR})$38H^Qg|+eb&dd25fKOj3`_yTthHqwF^)dkn zrimmPuz)h-OFF>X>6L7_JA;0bIWu^6vxFso3(`Oa4qihOt6hKvgf3=^uV{;Y;x+YX z?#8pX?QMG^@SIvfm);{JsLBe=HU+J1u$ZX?kTUYR$(CX{_5<$gexWjZ{Hmx>j=ff~ zsDo9)!UAwr0tN~^+bGRmxo{byuUcI3Y>6)vJ&#MHm3REH-kQhI&5)MY3L9Y!S_pRA;2i>vCs8F~SjEzUHUE#~gZ zbZc=AbcJOKGepC=ZWsPOGgw_6Y{zu<)DGWiOEZNKuIkAU%oIPb>iHuLKW|$NyI|Gk z?i5V3SUFkek7O>H{DNROhmm+aZgnAyyyx9B*WZA0?|u(_W$nR#kN-=ql*;K}Wa5nyv5pdfHsZsl?4zfD;}h{ocptvp zJ9<@mbk;uMAm{AH;hlHDznzCS&YnH{#Y`&+br*k}`6T-k+Yvrav4;;*$kkpbw<49zV zsT77z$RwqYj_hVH0&UcPOKGT=3Pm`!WCUdgT1DL|2CXrRMgd&Ieb5+)Ot)@d-`(Fm z-)a;>UBktwSZ{5C6BoR(J%i_CZB~otV&d5UGX#;P3XO&=X!z<)Gqz(YKr7~!$k8=U zsis+&?q6G7QV|TNuuPdwtg-~0`vOAoR-T&-+e%DE?^8W3;OBN5P$Qb-}}JF-+8dzI{WbY z@Y3Vx#W%eLy|7WeV*7{2pE`OT8@ z`vE39O5+ddwNx2o7eH75_FLT6H9|v}jA~7Us2NCq@T&|c zNEAA=EAQI5t>2vv)oBg*Uvd8>SZGg5<_Dp2JgzzwG#bKz@?hU{9GkRyb$ods|I|MqywWlr0aJR)?4HR;8@q zR|Z;z1)8I_H}W%`S|va1t?gVICZ03hx;VnLTHZ5}abx^tkocC2MFt`YI!p|JFbC1| zPe1U$)7XFdz3+V*ho3%q*Ig$+P2h{%b;YO7$;U5{BQV3$(xbN-3KtoFtj>r!Yc$Ly zNySG8#Bv43pa!a4Hz{vdm)e(f`pKMQdHLz~l`Vj0`(~Voh{v4)eymh+>r?r9wYBAN ze>Th$!=9Sof5EW>hxc7?Cv!{Vf{Kiq=QMIZ)1D2`R_EID^IH=E(vfbFNkkO@Y)51s zl0jeMpuP-hpKtOB#r}nV4TjovZT(1XvEt<0_2MA1%GFZ&!Xu5T>ipEoyz|@Vx^tCc z|Dw(`X6GAd*RVF_8zQvha^M{>1;22mG3S`UmXd9ce{-{|nbTp4OhJ!XPML68n9HfC z(TN5v$gcgEko z@7MpYI;y?&IkXd5AH4Uzd(qNc-#Y&GUFY@3JpMkQB{vu3a-$582dOw~6Tp8iAi*ap z+fGJTL}%9*`|+3Fwzl@-;+4NSb^jE3`t-qzX9>FEi6_R-t*xD_ZwKfj2JLpG1SVd? zW1Yb$3R*z~WQRW~GXB5^K7dZ&KmOEHPdzpM)LnPoMa=PYGLZh=o4ZRx#8@p?yJ@?GaY-nw@*2+|K-hz|w94LRD z)2Bg2|KOf`u!pp_-Tk(=A>;IuSHa;uZ@cHbJeVgtB>DrBNdTrAbw=og(_eq6{khMz zAN*^w{i)gmU%%#>uRl=R)NS|$auWDh`q<0Tr_zx@_JDa(E_zb6%YpES2M`YL+Pl#d zc`^RU=l@!D$?pHgOkLO6cRv65%dmeb@TD7Xy-3%;{4eMYO^uu5o8Cb%Se@i;=ndny z;w>gI&ie{}g?PF5W#pBAEoZnPFTae&dYzYOopAd zq}6paU_1pcpo^n$I0>qz@MS1tsIq!4vdU*uxeLW7F$LMQA-R=e4Qp#yD-fmP5-rxs z^Cpjl&&5e@~FDualW3Ii7%#nfHJC8TPZI_)hA3m@G8l z@l1vtYZXQDLT+7y(V2U~*>eBR+h>(eBg(XZsZ?QH(~Z1lJ?UXvFra`SHiIjM%yd;~5ncSV`;xs^p zCjjW&VVm4NzIDDazK(x9f#-OF^XL7LE`AntZY#GhcL?rGCfKZof;2^9 z9i^#U5+X{B+Zilk!Mekd*OP9y^t|*J^;0rWL<&@-)dhwSzWGNiQ)KrxV zz5cx)Hgqlz0iF!5x#s@48ah7yRJEZxj>DlKSTtIz8|gq)&i(h@cWzvKQp}wMIh6JY zN8LbbMaeW0@>`$*W$-fZ9nHgT`-14A)%F2ZUHfQ)_uJ%dS~2|DaO&9uOyBF@t$! z^AE64(GOLH1H^v_;jNSbP{WM<2Ioq#ly^!+4Sv0=D?|5Q!0LG?z+^x;)Jf8Kp5jBL zKP;F3Q6&OFQLkwK0aJ|0IP}Su+;wS;XTshx#cBw!JIR0I9!Wdxa%TcRgA8^>sW)Gy zM=Yl?$W9`IlBr1RjsQDMki{AXVIqrWA(R~m9)RaWL97QQi*Tk>O&UhJh&{p&?1wIT zs%;w#j5@Hp7|0A`VJdb^PKsf{amqGBgvY8@FO(fqZMXI{rzqKV%kEcPbl`QN@B7ys zFiZlH;u?Rr(^(v@8HQp3JZWM|v1yQe9xNy@gD^4b4d_Ox%0u4hNdHkxkj8kSOhIJX z6(5FhyAarly1RBC-+TMTmL12p9=_+Izl44ny%D_w-G|;MZOwmlT;u=>X*72HusiI_ypT?6Me~U- zFY2e69{|0gE}RbJH7L_e1$ELK_195%Am#or>ZacOB#V)8nTiDHf6$gWN?^oxrFw74eZFB1rNl2tbxz>T9cAeK5cMyWE8-)X=l2!WUVFzU)Q&J-a+ zgA#wq-u1=#isb1Gyh{ZWHKj*1l0MRahusOuNLp+H zCAOnMe}V7-ZuG+uG_eDc%)%mdrbwr>>}Y24jQUN#2e)MAPd5}@xF$`@Flx#!!C1*W zoRQz`_ks{OxDRKbA9#B)Vbr%~124c)pzVLuZCw|N{dV>#;inC$_U055hc&BTe|H-Pj6=PA+A!gV#1XbkX%ZaM*QX<85}B)lwRC_yj6if@xFEX3D9+c6O9dr{M#aMjDV62JyL!utvaoc~h26xZzqxa6TG4+O3)^Q7T(9VB zvll4#RfrGj>lTj+Q&R(6#RkR(nU-HVnISqR8o3-aC(tQq(_(d0oF+VR6khg);V8@P zof_zowLRO{Eu$GtvwubHq_=Oao(OZ_+R61fVC+9aM36>PvDS-=y5p73=V`o-P_Slh z0IA+t26+QAa+FFqUs`|AX#~Na4B8}TF+JZ)qyr4Z>vEVm7XdM3VsRCv`3+qeBR_wf zogbV7@c8zYn1!?Ng5E+_pJ@*R;n3a{SJTM$yn@cBp@)TGm%1#|mK(ZI~@{ zdTSe|QFv7&AfzQ4)}@xB7&hot;C7wV3Z`L(N4u9^c{4B&t#mpS$~V6S-Lbr_vax$K znBKGpuLqntojaVnMWW0!S~lrp=KUKrUZz#js2M1#%@AXdot4A8c}ug?9j1vYtEkoM zX4&^J=&7QRx+s66=Cp3mm*~4H(P{!3DuAmQyF6;SK1N>%+pk!rL|5{H*)|J-Y|tAi z2$#59$DF8wyUnt#0$3o}bU;!Pyn7m?)X|snhd}{!j(|+^Y-vu=*)1SM3?`W2j7kVm zR3|FnQPryj)lx4hG;8g-Zn7&W^eWA0%G81HfiaLvb%uXOmeO(aMri2-B);X@o>y>T zri*(i2tu|qktu|65Z6pac%D$eaA{l2*@={i4LU~-()?rd3len;Wa_{Y#GkD z4&wb&P{JgiQe~2;6_HPnD?UXc%V5uWan*V!x*0u$>BHPyo;OXZ9_1P-wpv9)9A%7{ z=IpW!SA~&Xy_ij|@{0vG%ph1U!aQ=c0anELNfjg13PrV&S6~mL?K0PO{N%$7xM_T= z=Uq(5d4A84`P_$ckHC|lD8vCLlVvG(0S1%YDM)`;KMG?O)|)GPcIeXm4tP%!2+?htF=TR=ISRMA&;bSFpGwyzZ^iRRggI7hvaEVS5t zGkz2y_};kuyl?wilFwa{`}J(?fHdS~bQOQ3JnqPdL6T;&NUvXJmIHIbtSy7Bn&r53 zLC}e1`Mum9ElaP4UrST2($xdHrRQsdHjgYMiZbIoC{2yPS%pmHL!q^Ls4@r_zH_FAPzm+0b6^vSZ~=i%C)Ygy#6nPB5XBNI z)-*u#(0cqC=O+!sYo93Sim8IGQ5-PBxuRQ@$U3d60uVpvLIKMHEC|I|z-qX=oCh$b z>yE1{e1b3EO3nbc7&!+xrIKrBF~fg}CEMie9HIKiXtL6r|ZJ_0HNUqxmqa+0m z{UtwL6_m&oQ*woe)J;-C4%(ewl$8vCHbyNF+r0p2RDPo;z)~2VoP8a!R$+fC;e))c za5{=k7g{%Jo_^-Q?i-1@UBqKz|NGlFee(Em&}kUvv6joV29YFZ;U+FneYV`um8+b(o#Chl3L<_D zQe%~)%XO=^?ZB4sf-6oPyzp?}zCzpf#K$i~P9gD}@joNYGwjIJD(HXiarN--i;nKx z)SvT&q%n?MD~)gn=@N|aedDJed=RZX_>V`9y#L6Ne*md#jeqnHMEX^3T+?2%{|LJ{_2B#SVU!(~`4J)Zs4; zUcC#{P@&?rWcH+|YLvN-I^bJd!?lI}>zNx=!Qj`1E!`X|HHFtiYp%wLr@i_nl^Wb5 z2UqX7!onXmLDg#OBtX&ZYc-o!3QkOHPt#qSYIoMA_cjh6e2;%!3{({qU3+AoR%cYD zthACjo)v%r+mUk*+rQbk)91ZnP){NJoK9q!f=tADxEelU-|;>{-6eu3Dgjy8=(0;ikl2` z9iVL)Om#vRLFIoyV=+2GjSwI*E`w4IG}{au>rVjL8X6+A3}J?U_|gyVM@(PTrSEPx z2qfaS=ltk&M5s)og@Zd7vH^dyXBb>08>dXqHdVv)0OWAI5`!3Ap3p%B{}}8vBj*8~ z=TIh#{IDg{gc@!~bOek_&u!rMMUBQ{qcQ$d^v&@K`uKm%yMN6O|9$Au@nfjGDUX)O7NE6mZh!9LR2NA}G@BVK74)(|y_8Aq0cuWC zB-6BL=^Pz3WrBHU0LKB0G{C=EVE#d8v$bTN%{Q*x+Ng9dKXfAG+U)4!tqa{>e#K5t zt^pfAy>EZV_T77Ttnc4@doqRhZ(jpTCAC^k$2C>+%1>MqRqJ!rI(GK&UEXP_>dOAt z)V%JA?MLfA(~j-D0;Z)Y)bZc6k~{9ZBWdBi+jq$C(*U#3Gnk;*(C+i%_@(-69sXVx z2L<^N=azCiB!{J5<4G!V+UH@WLs6O$Dwh#N(yV^~{Tc?@s&OncycYlzdW%gj-hcJ+ zeS5deqAgqY?)!atH2!?ruV34HbRDqYLSl$`&+Yq?)hX26wpV_x;y2}YEt$jF%{$V2 zj~^I6j#|yzf9u!wqB&1(F42{(`)=Qj-Zr^w+uj|?L?$hetzdt?SbU0aLEMG}8 z$Q*y&LFNFTT!7PA>`p3wE|L@jN_@B(z)LZ{jJ2i-8ZyKN2yr-&%Ma7lq-*IiaS7I? zQ96mZ$x~!rWkAnhm~bjI&V=RxJOsS0Qq2{SWfvXOcZvok)EZbyo~zU*q+Tl4Xvo^d zW_7Ms%yR>DH|3a56}9}Jy<%`Od*q-?HBo;+x}uwEc|p)bCH<<s;~pqLeMUC`hpLT6bG;O*0)VxM&%{PSSQa3QlFT#0M7O7E%ib)Yc6cat z4D-Z6k13iVjR>tm&3ttV+X}^6TroB@W`hAD2=$>~=j^HmY2^GAN2HYFXX`kI>0W;V zowgTRxWn6>ktCeq4%9<09Nn{PdS+&N*Pe$r4~oQY&J`*Hucg<$xV%p4KXW-f7(ZHA zNL-AgaKUmKjXyhwA^#bF60polnv3J7>omdWO_QD{D@6L~x^wAH7a0kpZA=sDvs_Sg z&GD(mhCQXL@NmohU>@wCYPFo4x#)kw!JNnQdJvW>jxpU`Y|k91)MFn%{=E3$O0%fTE_tO z@S6|_+&Y*`REbbn+T3DQ$Ga?lOo61$- zh{cKnCYuHdMeL!(rizgU3Dg~Kp5hR(xtR!bFKBpS$&@*aojeAU!!ts#?Z*#W6lpRA z0A8kPbEX=39a@2?^j&{5OhDr!q-}}ZwVLf(Dg$wWOh6Kd5*FFI+X%fVZ}}7&AIP+^ zWz9A2QKiRBp##|$MW2%^a;4$I6jd%x1`A0EDpl1Yh^Y!A!BQe)00;~+#axC`*n~l5 z7;y~=WY}PnwO)s?$qPyqo`Y~`3ya7FS>{owj0))_BGfh{c&f>8s4$Ln0UduWk&CkON`VA6!*iHaIm{qY%)IzF#P%ITjcc(^ z02iSws{nCz{QExC!No5ZY)2DZEnPmdlB_`GqNG?cAy!FsTufD!b;>Hhr@T#J>}H|8 zh6w6zqd%k35)^;mL!sXUS`Q{DHLSYK3MEKj`4n8TjOdHy5X?v!%0U1uBhPX$HYkuA z@M@&{s9+G4GvbvEl{+HH%ajA6sE(;A^OcpUSaqSFier>PX*mIvLq%wGof>;C!Z?&G zJtazYOq#BKrqa{tMis8+e<=OWB%e&RLTHqn!MAZ8hP{6VZP^CRqa_Q@egciS3fBQd zu3ZlBP|^#g3mC~1XQgBtE<*o`x$oru zDfd5eFXUd5ncJJ9DqUt9Z04heJ&=We9=62k?YWFOcsjl->&&(HuxXJ^--_ zcclSzP)dW2T>AzXYk88Yb3U0?O2;A0)gbEmfiDB_i4-*>XvjK>psmSZ6$Q|bwiIq~ zJ2?oo0%NgR0z*Av1Qw;0QP@FXd9i!1+x=^BvFwenOII&mUM#6 z0-t8xQWd2;1TaMhE>Kyc5s=FJSut+UxKJ3X0TxF{NyQk!ZB`nx0&_H+A`iKi9YyT~ z10CZM5bi5Lh~T0QJgdkJD;QmYLWXe5X#DnkG_xCU5lBDV5L3)>?~db(V)`mXok&-0 z9e;lfYV)R`dze^7oydk#rkS!Q+(^Tk&{K_HOpLsWR3Jm4ayPV#0I~o}QNUe7a04yO z%;P!3bXDe3(^gE)62QcQnP!n5S5M|W*U8H@(!>JsW*1DMTdraoAX5kPOS4jz*+|WU zOtF0`d};u_Y;L{ zq>9j2_gsi~JzyIQSr$F-lg48rawD0+;iXA8?S6_>V6u}eMfhBFK64}UFkShYUYE}4 ze$r|Na{G(`?2UnhI&xErbVXsd)y_bIADQWsu2o8_={;YU!BD)?i_OrJfoR97R;7Q) zkfR@(@oU8ZI|D+li;d7Dbn`0l10(LP03Cn`21~@&12yqHS1c{8#xXZ+g`0V;4JQ=G zZ|Ql{uyq#4tEZZ+WRWkbiRv{^4dSi|=5xz;6ybVpEpKvXsX$g2meT8iZlAYJ5dLSd z`eKPJJwMIp=`SD)vvbEy3w2)pENDP_c`?gYw9E>6<7&{-J#1=6ABM@z>Gx_*>&KZch9;Pm(Kh_vC**pZgj- z7i`cZO*RST!ip?Lc5d$!ka2@P*qnnjd~0PH!A1g-lSGweR;A&-baR&Ua=ViXxxaEVf1tYI&8c{<{rgLy?aTO#8#cEtH z-e3Z>k$&8WSWG1p>i`@yB*RCbPZ^_hx?0#KL))vn8GMEOAn3hv^*?{2G*fg#dgMuh zPxf|_Rw+@dl?9cSr{`z7py2btuB2Ut_Lv%IPiUpEBG+W*GV_>k`N6TR@POHJ=}bTN z6&{P?nUb^90T|dCHg@c3NNg3G0jKHGU2FirQfvTvigf4tI#rlQ0e+}vbm90Z1LTTj zf$^rM8-Y<%Fa-V5G){k1jlsRzkpn$ThU>fgj&_N{?CthrU~(>fnfnZE&WFnmDBTki zU+@&z|A%vTqw$bxM^+31T;yyqcxlgsfn4MrXxsa{kgDP=(T?nx#Be2^GSv!x`R-t1 zvrEM(&n94=JKTQ+I90A;>Jcm|t?6PJ6y$G8fT^l+z696`s26_fhBGfyf@~lFqUK$d z#3Ij3(1Rds!PxiqsoEo`{7hwSJA5s$K>Rv#QCtJn<H=wto-$frrzmFb8e~dnZ{uKQgdKUc^`djp0(R1jZ(2oIIzLeT8 zU^QS1dw^&oT);7|;u@a9E!@VlcnJ^iHoT5^;r;k9z677dm*LCtEAgxFwfHsodVDj! z1-~A@5ue3x!oLnS*_-h_`0e<&@V)pw_yPPNeh7a*jQ=hEDE=6J41WTD68{N)0{VOPeVLoof`H>!X;Uj;o zGCh*_?m$aFb52$gh(w0M1~!ssvSZO*PI~LeUl#CWxgKG(4DZ1bTng6Fl7L_EC9j_Q zf>KZe+}4?#=M;Zbu@e=y8JP<#t5lXHFbD3U^(6SQ<=+b`pO7Fgbxk^1lZz-VH-v*Fcn@heyG_dRa}^+ZXc&*b zpIY;20wn*eip7Ia1g^jS1Zna`oR-pr4L8!7ENJ9)80wHo!QU>^2?b4jWU|kpfU;@| zx>$`g;XGTFN#A%cA^TuN*cwY4f}Vm25#ys+m^$U?TA5ra#Q00GA*aWK$wG%80Fw|w zCL;&<`=q`Vp&SkxOOts39UMO_dO#t05R+L!Suh4im%`ZL@7Bj( zKOraP__3oY5uk(n(#c9c0WZO@tvxM||9v*a>E{RJt@u10oTroGK}mnTc(5O#{Y+Hq z1kaZDy)0g3=L#~`-fCtsxHOpKTgb~Y?)&YYOeN3KH_)w zbb3DYybE>(H@xx8DFX6YT@M0W$SbD0e6g12*Bn>X^K0Yg@w2%wSIkv&vuVU-b0fW; z+yWrolW-Lc(uL3I;^&6+kxg)bCsnu1p93fm;9jB8C^%KIsEv~HQW^e!=XV}mT%226 zM7NADziu!D`;r_tSh34OM)SkF7jYEL~ zqxQ=9V}9xI5&me|JHK}75I&hZBKI@wWQ!}p^ZvGUf6;W~139E?*$!1v(C(klhE82{ z?MW!4GszE>cRWX!E%)crY+kq3yyiGNXJW#u3%!n-sJ3GSs(%1KK)}DI$}D)(3tFI& zxTSm5MkzVAbx>>h)8S}oJC*zNYM}}W>LrsfB#Z!Lnuu-1;RXv^G3U*uQd2C3^-xn7 z27RQ~Wz;2!ZB=0^BWAm3sg9Z#z8kK2x=9%mrStP7fKR4!o!n|}U+$NrRVQN`6&X(& zq!2H0v%UDzyg0WnZMc6%)Cs)FUS4u9q->khWU@E7+}Q$&l&NOrE&*vJa`snctVhBx zc)|cz$o)id+*`fy`44asn&fMqB4R2KD&BhQEEv_j-Hf&Now{rrW4n8?>b2I}?ezoe zt=78TQcXi^$#ojjimofuZ~!uC9>nSWgrB0EFNXPBUiv#Q7bt%-^*kr<$`zJG1%^}4Ih3DpoYx~lYN^W%kYv#7{Gww(!(bc$fpn$a-^PEZWdiQ_>+%!b6(|PMltK{v zKMp?fNjN~d@@e^H5)ykDZS$%zq2KHA zJvjlrkgFg%z{BajJdFWMH9d3ekuM%U_6Vq| z_Vmo$^znZe#~+`W!S}p4H}le$ZomDJN9MlJz4>nd}!o z^2^7MeVCx;|MwF!8UMHV%jtfkGUGrx_Btf;I>T%map8fkXq$fH70tk!yW*BBX4D{F zD0u4P6*uEk#%!ZMtZy;K-$+o~Xx3+DYKcAmo92Ic-|03Ya=)GXGwIQ3x&6(I2r$F^ zBu#1Y!*#SN4!gB%cjas$d~b9v)EjMX>5(lPO+%(txqW1s8Y4Gtlxs`VmFdzZ4gHC` zI$KJZdS|-Q(HLzC+Fp0CfCjCR+_pAbl9)QBd6e$vn5J$HrK>0D4T3%>jiL13z@wvf zr=NeMO0LZ({+;Z(K%+cO%-P&bLwbYAJf$TyUAi?6j6`mt?xpggzXbaRBxWKx}hb821Sz4F-m($>1NCTY$q`uB6tvqk!ts zU667rKv_F3XBpZOTxzmECRSNqlYmdf6~MpnFvD{Q4^>gnh>RDfpj^7RfPW03u*-i* z$(4aasv#A7(A-rG%TyPgNPi_@Kq~)R1_O*7gASn4!r=9SKyqJ}ng^qfbfSccEGoAG z6b$S$qT2b~WFIJ#=qaYF0N-^0S1f|u8p&L@rZnp2!#2ut*L*H_q6*G`M2XzJ z>--ykc6?o9e!hY3Z_MMl@w*#Kfa-r1Pd@z=N&o)(smVX0%{Rt3rzP)e%#ZWSJC~O? zmYy994#Qu!``tk}h~X`pnE6WYBL@Ka^m99MdvaIguFd5}NvbZTvmA)M7g$8nF_1b? zWtw+BSR;e}0`3fYsk5=Q*#ok9&;mgs6INuLuiq2{V83o425FE7c;UH2Zbg67bVJq3 z`9p3sc5KtAx%eZxrd8ZSPCd4rxauCtmo?RR>37Pl_O@2Lnth>vcRgr57=SErf2N)S3BP4cVOGz(FQOWj#^18i5#!C9xx{Yk-oo{~tlNXz6IEQJ= zVK&QCh0y@8yiPSBWC93HjTV4Onj6j7<3b~hazPw2)u`6t3rPNd_4O`rj$P%Qpw2n> zo^#*#N>}$@Nu?^4q>@zchg6cfUES4ox7%*JU)XN2!C=SyY#?^b+9Z$xLSh36gA-zA z3}ztqW)m156UYDwGdO=ZhIm8bELj|~VKT9^WJt`0SteO#hF!qf7W@6rm8uPy+3MEo zzRv4A-{YL``+wb%--oH0HY)SQV%3pe5!2XAjnhu)4RcFU3Rf6P6wUhN3C8AX@bPdg z!%rR}qsb<|B4F0h%w=`oQ&fr<8~k+FU310Sd~2ng2wRox$gh9Sms)MHI)G0BX-J+- zT@zawFwk5l6~2aQ2!*k{GGB3tDV4mx_4!z;Vc*en?OCC$3ll`W$b_LFff~p@yfN$HN{mNWUktm9$0iLo%nGh<}$F#!IK(v)@*;?ad^p;k_H!;+eTc=)+5Qa4x}?mB|`jmr$PSFCu|W1B;t4sA4oNa@gO8VQo* zN0-{r5DYn5+g~|V1q>IQ!3+`AA+AZLE0-7(r!tgd6c*9)uAKLzDq8O9 z(%j;%Z306KvDG}bLx(i1&F!l!&jk8|Ynt;LFLgqbnbKemSvGt$M=F$8w9dJnsbVXT z)y2@03Gpr~?lxfPnm8KWO@mnyfS4ziNG-R-PnwoZRk5eEaKAV=#bhhQ&7_Gq6<~ix zzKt(j{Oi9$GzvTMmwzeF{?T#zH_KTPiE*Jp+Qp&56@^<1|3l%QXun~aA-g)1GRJ`I zwN)|ek4LlUAp<91I)r?$_EEU&}1wRs8ADJ?;qXI-w=mM zwwO6~xl)Tufn_Bd+Z)4$&Q9-*DVu*=7}c_><&;b9=H-`#Niv@#B%BcP`h(bVvSykV z;}g9;TWSo~w>Qc`sWcb5mp9v`vSU@V+OSdMx6o}1Nm4TaKVb3?U&+#iEDN2Id4lE~ zGcG8FT(y*=GRY}MJIns(?)hO zT^^jf?C9?HYQ4&REOkyk64V>5rPbzQ6dCT$+C8uRp<8D%rYFjzQ8|bQ=?%@qXU$>r z>(BiSyKnMy{N&`f*je^7Z+_sPfA7Ly7H>H4lX`mDn1MexO895f494JLP zVw)_D&OYNeO`cXRznewkkA#1zhX1EoEXU?WDSw8PUMbnDiy?dTCT=VC|5{8=Zjl}< zv*&D=+4kffJGDP&+3Ypu;*4hgTNtGp^KbUbrdWTS>2FXWi=KOq`A!qmbYz8R~{|gg{F4~K%o#;2>b9n0_zfCqyM`PK8bWr0K;K;Q{KRiT$ z$3Mo2u6YplHT%N9KvTUuzqs;}<;D4O(RY0}R1PMqsi4CJwEKPnsE-%+y z_D^anoyvS?uGWc*OL2ewOy{z_&Y8ONXCCRW+3l^d>m5Ja@1H&H`B$IcvX7YMIC5)~ z-)yWk=>Jps2S6Iapyc?zQwl;qu2h#=Yn{tB4-ZZrJG?(Sx^wiz4J&hdffF=4lW#9C zH7_rfMx$bUc{7SvHs)e)Z_kV8D$;TsD_vS%?DV|7vwQyXQn!CsJpQ85e|Caar}@UT z(O6$tNb`>=&f{b_w|tJadLyaN3JDZ5XqFUQiyhR>@T6d}Dvvcav)3`pX*#wv=6kT|xNg z7#fXi@=?a>rD}DlR{L_dRI4pjt98ad^+nT{(l>*>R@*0YSkeEIA4jddcN%s*r3afb z+8X=kQ5O~v-}7YqS~AY5h?ErTVo7?$JOeUEU4A|oZTQtp|?nQ+9|ov7+km6U(|S;IaF zw-{rW)?o_b!9q!|m>1$fgTZmH8*)G?4|^KPxJa+R{yw{d{qEyjypnzY^RIf<=Pz7O zf8v*)xZ`=7}(;J_QG{(cLiuCq`2|qpUw--#pgr+4c1} zlwq{?8ItPkE@jpm(UIL+$t4lL)J*RS%2DXJ!M*$WrDR~`m-0LRdzW&f*F5BO8`b@l zZA*Vda9mz)+rDso>ASY;-_u+YpL4>f6!7pHz2;&2M=vEsG%)q0_by(#l)B5;^5Y`^ z9`V(!!r{Uhn%%ck&%L?uPSPfSweYVBpUtC>e+UGBo;7o`C(rT-^t|C15Ee88$C!@2 z{5$o`FhPdQtUTE1qyL5){Z=aNeW#bCc6)!rZoAh`aU6h3X#=b4qux+^>|q~&x4XN& z#`NRJ+8RpzBRzvg#WqW(JWTo-)_Ht>)KFT1ZNzX()6vpodcAKhtJuy$ zYs0UVN1MZAhw4$gIG%E(n^IT$I=i zRGS!)@hbVcZHp3Fe6i0KjmV@+sVaX@QcYu+?Vhl|!mvmt>4@zAy#`Z2eY+gHsY+=TBhYheOGG4l?;_psfx@H2{l8dvrttN`Hw*t);lCEXSNH*T zQj$xsFq%|0`FQ5ZmOI1c2w~I6km&}c_Tkmsdx|yP>tns+AW8~xJStBu(BOZ$`q%@M z@+_W8!}|=T;$Gj;8R>wu@d375nwjvD)22^O$@D05<`J5&;p&oR%et3z2@+1&`Jpdy zTa%n_ok}}u-0h5sl*jsLmw!@{;y71c%7EJ%*0<-G?8enRVfO0Cta)lx=A zM!&w;$mU3=;q`$Vr)lg}ON)P>F-e8+Z6ZYxE*kEzUiHaXw}r3+&mo&b#_x^o`k_Xe zM%9qr=fyOhvFBBrL9JTsCXtvFb7OQ@m)D}&RxpG)2ZX0P(t%NldHSBYfrx=1j zq|vC96ECw$i&8B8ykE`AeH&2`th^e-MO`UJRf+KZ)2C!rs_6BGRkVK)^w2qEO#U;y zslTD)zO1kZgn9JnGB;hP5~`}_nx5U=Kk4~iHQ7msHa&4N?bL{oMsAraqZ=i**=zKj zvajr_pJ+6j}Ce6G?v7SW4JtMm!)2{9O2kF-RtelmU4D7P1cIC-p zsi?x8v=)&CD3obS(#F`(s#~h!|DZshZoHv_dU2?Z9CymEJZ}5*1Ha>wIx>sZ7aJtV zfJn6jYpd7<(mjGj$GPsS=(WasdPfA#pXzab@5Kk7*^HAU{>6Xa{n^*O?*1<{uWE1Y z-?hEFCN?fU_?>IwcHHl8?68ggi4)&mUmm9idj8X-|DFUAc_*JxQ(xnJ|Dvn22<19a ze`=o%bsVQg7n<^KV*9Mw*8z0w05}y>aR{lHz+sI)c7fOdbz&o1s`|uVBB%{*3)c_I)H@<7Gb25ht2tgME&; zX%d^HaVURmBqgMW3>F0BZ&HSY%%cne`B0{_b#@R0iJkg<)H%{mQR9hak^&=>1&#no zGU4R_0TCM4lJp)8XqkIWwskOY{=KE+huW>74)qT8Qhs;X+l7S9b5oJH<_NhVU9EML z=@`cLZoAj&YAh>kx|uJvPS@Iz*xn4Q@gNIYFO>1VSzdH!J` z%vcA%3t4x-!m<1s6W6u?y;3Es#Y65pWjjVycyyszLh3S(B`3r-89I?ChnsR2yIC}+ zVgn(Hw)8zL>|=!=NJ0Xxipc0`c*5n8oeFr#apHdpY-VyF)y z>NrmkI^amU&@`(lRGvDV%xr1cv8nyo46}vJI%2%BAZ^B0(nEYBBZ~>^ZP*Dw5?@Pd z$15MOlStXdJa}y*VR+!0E_}`Dve;CUfBE;Zy%2=96H?bmU6W}hhhU$5PSV8ol+T^2mynooQxcyt>Wazx zZFoZRs?V9t`0?B5*C|f{o|XFVoOnEkkYw-5Q6(qOTKI6^>l9tb_P?TV(&2v{!@W*T z`vOg$`jkq>_XkHWN)& zcMKW_`bp*^bw-s0q;DguhEYw0TmVTx*Rx~vo*s!ywfT{8FlZPwjR~?fiBm#=cmC2Gr5`3A7ePYKR zQJ7kRHVpD3PQ4Ntw*9vy>Q9{ul<}z1;ryXBL=X^>g+h;zH~BC4be%bRb7?p~pj;<% zKZaAGJC!%z;vXs8mghGmUVwl7y#848n=-PZ@3H=LrDAF~>df2Pj%sV(qAefY0z9O66 z!m7As+R^;6ghVoRcOX1JmZs_XB&CRY%|RQ}>Vsxcng*sf*^>!8Iz4|=DZdmXRmW9c z91%;P>7q<3z_9`|2+QmFGlO__4y>c^*<3pzZZxbl{c3-_m7y5>y!7QZ< zYwI5ebTLlkG(o*2RgHg;CTBnzk$>psK3V0bHrrd3-hvS#7j>{8`_*VKS~^|Phs$>6$Ww2E{$jZ=~&Qr_`482qL(EDc-oHf%IGwo8{Dtl0w}r(ZY*#C7=dV*W4i zEC)bs1EpGrmxg>c>3gl-CMR8L{KELm$L-+cZKK6nWpD!-H%_9XkRPVfs@iW~y<1#) zQ(PV0v@L%xY@EO4{MIJDZSuuxt+M;#KG}ySZXK`0+da5dKa8+{Usg$ss|Pns8C-q) zrexkGZh8g1otxztO6SierAX6 zQ`6Y6Nf)$}eBG>T{?G-N4j(*=y|&inygR_#$31^%dD-!Jeez}2cRcSM)q_)$t33yK znQ#`@c#}WV_w5q5?l9}ibi%v%jORS~AYJjzR&Br5Y|>lny4dmdJ^Js&w)Yl&NT0lp ze(K{kUGSDx^%$-{R&DJa^X=I7Q+gfqFZsNgLK2ljV-JuKOy^$9R6_lrlN!l%Bhu0Yn?@!Ckurg*d}oiOA$KGNV^3Tb3ZTS zJ#K4Em}~}&3m*-vR{-p_JsLkN+=UK%H%Z%-s;PvtP);WI=wISGr`(K50eA|2ORdt@ zyRSa;?`15H)YjO#zGJyq5{ZvwWYKii%7<1u}KvxH!TwWVRikqV{gm=|E_$}u)U`%)F!<|F_cqL#uFjuba6>z|VQs75f77FmdGyE|f8y3yTJWmi zS#nFIm8_R&CX0A^P+e*6pYXgYGD3eEo%7jZxmUhwxiZfJccDV&^eeBpX7Wkfk4nDF zzjfW-nJa(d+9nO2@!2=re8u%ia8<+&WoE`FZ#h?voJi4t%RCH0+E-nE=4DZ{=O6Ak zK=3vfOGl1XGat@u)%C$Yi()TKwfULP|3qNN*w+4K&BAJ7P&lHouCR00>kfa9F*P0y zw^%mApilnM7cv6LG!(4Y84nt49J6u8zIgidht6|$e)1xEdyQ9g-J6<^pUus!KeWT=Y8xN2=yK2dDLenjf3e$N^wL}W$*b7I z8-w#V-5Suh_ik(zF7e@akZgaveTKC(1yi0a?+v9+(=apk`#K(%gw$?VGrVJ5A75ZE z$n^@M-=)!?qGnzaXT9An86tkEcR-^7tb=?a%B)uw69*#tZW)4X^!d}q1`NZ#@isGC-*@_r!AO{9A3fA;__Y&e}hh$_~Z7jleW z$OEkH4qAwB7{Iq)r+9ym!nf=7Zln7%KXsy)x~|>sthX0V9(v^X!e0~bt|DJg%Oc1tL-e#qXX9+a*f zl}HCLVDIX{EyLSRtFu0R z`4HruI@-d}sa=2gbe*Hlux@inXU1wq9;!Y#0D{rGBzn1A=lIQ03q)_1twZZ**L#ED8YdeZBk3KzoB zrl^y#8JVAKmI3o;>FI;+ehhj zNym6Of1D`GF0=}#3O@-sJLvRO2H$#8o8YmFZS6s?tp@pY+hcKlaGud=$XbnUq8G$^ z(`1X|HvE9cnSMfx##i$!XkN*H*fs9cNl1y$VJ&})MJM0I9$DM+PENe_2UbY<+D1oy z$L{ZZub(~jpu zvN7Q(ttW|gN;t4HQlPv~&2Oyhbw}(wpY9w|VyirQ^%bHr&u z{9?@|_Plbe2^1)08zO2nI84ku-LXUo5La(q+RS?SD2PN^EWMc-0x-vuA`pQ>^5$LVMr?o!$m{=ffIxN)4-q%_3ZE_f zZ-s9a{tGi%i;dU`_7e6=_LJYwYj1;ye5bew)S{csG9!{{a6j z{uuv#{_prR{A>Jg`1klll57<*FIu7}M&gJ#E>4Lv;s)_E;$7lB;$MhI#IK2uh>wfk z7LSQP5Pu=QCjLe|Z!8!sW5u}IIBWcv@mk|8#{I@G81FJZVSLK?H^%Q6e_($+Yy6e* z4bwF@&2#1j^Ct73nIAAeVSdW|ee=uaSIw`R-!Q)|$@G+qa!IbptL0hwQhAH~arsvH zfP6%LOnyTCj{IZ!f5>ksL83WO^J-0PslFPi>(oosPpH?ax2j)I?^5qpA61`Mf2f{O zf2IB(^$%9f>R7AR5$l9?jdg#$b*uFn>-E-KthZb5w|?FF4eQg^6V{ikKe7Iu^)>4` z>pxn5Z+*}DfoIY?}zRWNK;b=${y zA9cyFE8Obga@>>UD#s9;Uc35O2IQWR*5Lu*F4gXIuK<3B^GxPjC+!xU8EL4}ZL-x> zS&<@-y_8mWS0!jmKgEBY@ewLa?5C?>)n(J? z^;*CwQ@9azBWBa3qX;F;61XF`M?ItmAF1u#0XjzMAfmK4O7jH}>N-UCK;z1Q&p6Fa zH|pwphST1oCh|cl%Wxxydu`GKdPa|ULH-mf6M$>uY8cgEOS!xNEKW4sGBFG&?Ja)`Sk z4ymPaDyHC-Z~z6JlvNv`Gq`6o?)4MZ9*kj@(W?iCkP+1MZnoPUs4^m>vo19UyD1|o zFGs%tL;ve%o61BqH67o z2W17~)*0XErmPJB{4w#>F#uD0J4u^rGVCI;yP}$m8f4sN5?L*Wqh6}3w>umsZR+2o zoFK55e&c(FsE$5}FMvBmEAMP90Ogh^-9H|TNFk%@4YGf(fus%?Lk5VN);Pnf}z~rHc7WJ&C52@nP?Zr9EKDjVaPBx>XN*mma85Tone}k zsjJ9l?@>?hl2)HDst$5>5R+P`x5miLqxas6d*%&Gmo$Vm61``_mbV79w_uNJjJnx zM$>$_t1}W(tMV=;QWz&YJ}W^lIbvwy5l@+4pd;J zL5zFd9nia8eZ`wx8q7ZsEx-BD`J(5@{f$ zI4ff_a+EiJyp!+uqFYA8bf0wrv_eCOk)x*utUGEi@T1U2wfaXLwF~YI)&(lTV89h% zxZ8iHiHTw|s*=)s;bc6bwak@J+z9E0dsC9K8?@$+@0>Hf{COpO%Xb54d{CI~7 z9nzy|)cVAVBqobKIpxoXXv^|+0J-0D7VHWc8G!! z>gcRhhNuEl08^s6??QqlxuvZT-Iny)DSptvCd4+z$&~$ekzIbMO*ZXEOZRtmUL$|B zk(g+27jQEpAoS|Cl~GgTV{HXyL8DA^cNhL~UBr@Q+u5Miz)m)u>norTXd+fE&9B=% z1S=6^7|V2oksoWdx!ohu#0*mOyfO$UYXhRG5BW;Gg34%*Qc2yi3rZ!4qh(9pEyO%% zfHRCW@z^r@lGpg7bQ<UZAy%0o0fOs<8dKNvg=w3Xh17mn zhkP|QgI{8pu3?(D0lVcj%;2VN*}f5Avzb|Rn~SY-ZrM`fx7ZR8E(Tn!$s9DWR>XY7 z)G$-pZMwpXfTM$aOUOrTry;H-a0CHcT_p-T*x z10Go9@NgV?BJm6zO+z;VWXXbUCJbK10t8cv^F;5jYZw=S3}u-`z@W^|5o2`>eGg<%cG1v!hRcBDs&sw4Dm)75n~U~rAAazmUmdYx9b1;S)XSs zT)dyYz6_C1H1Zl4N0=@(7w}6X#?m64&y_b$XG3}_|)pe%V z+XX_=HHqj>5F((D<0$P*sCy2_m16~eTK5tM^$M2vGu$39Oo;E6DCi-^-jz2SRHUD& zDW95klD-_}dPjfV)YTWJcpvAp!P8jYLKjDGREy6t(m-`6I@3Y%`oo-#Cx(|=#Qv1- z{K~X6&i)A9fq_*H9YDB0+hY7z7<->Sk1}rBNG_Pn59OaEi&f7(?FQh-ls}C$oypFF%^GQZG=8x!cz(V-n8i=`3E3E zT`6><5FcdhlYr4?xR7ah(>bMQtd-GsTk_vPhojP8X5S=6rEvh3HDzPmK%<{U3zbG< z;*aUZ(NTlC@|{R)gf<(Nk+%UiVQ_aCdm1D}&;6(Lvof781~p3eILKH7of)R&zm4Z9 zTaPb8+H-&NJ=3Q#R%h&fa6C;T891S)5;$PUCeItp~XZhe|ja zk)g{pB$fq8Ge4iz0!+aET;3mW!$P1jS0zpSpX7gf;I+FBm40@{Pc#K{>XWln)Du8b z7dh87F#@_U;lS3ZrMg9QFnTlxdUm&{Q!Nf40R0SWh#Gc5>i)+H33;I&4fZFG3TOm$968f1$jxeMG7h@h05}-D$m<4sia1@P!Q?fhMMSNl13VIGwPS{Rp%P5eh zK)k780T;oO%5@!onvqpxk=5y1Au)_q6{uy2OwdSPP$fcgLWyX#fJjbYhAA6x-&c1# zbf*VDKi<;ZjcyLZt4VYidj3mNH(N4vsDT!ZFI*3)<16%&*og$G3Y{;|0OA$m;Y>$s zTJRWU{sJJd!@yZ+VHiA&5VSVK0W%`bu84?2j$K#7RUUDFRB#ijv;*H&;aCYb0!szN znCYpIG!`VK0l!DhiG#A3%A!!vT;xDYgr6)jhDcIjEnR0VkW5MJz@(1kI!}@1A_aiq zx=aK6VxA~hN>_3b2Gmu|!Uh@Uh;51}gj+phkpwaWx9MoHsC6?$6OnurDuCO)#nb{Y zgE7OQ;ektkU=WmLDBI+YPNKr3E=Oy8_-{FMFWfjo63D^`JmhE>yhgne2Y~1&8kb?> zfRYTK2-9;dbf52Ns2gfhSvSk#OH_hs!x_rOq_U9&7<#MW(a?*M2qoZ^(G5hUmed*| zG*J@|qIpG=(vpVqi6kp7aStW}=*h@;DsvW}Gc}uk2FZ)9A{D^hDh-N{kb4>v;0zg5 z8P_V4RHK<5dvHZWehs1b)|ulI=`uQk1Sm?2GR-*3)OW;4OfA0vvtz1UBGO%p)v{VkscRRuD)BRFtg!C+}p z7-ErsD~o!Q>kJgooS|@Nmu9#IxDW%DmhQxH0kusJA`ZnC%a&WD0WNEW08`0U$UX-( zP@5j4YAV;%`aDe>Q{?s4x-*DRL(fuo7_y{s5Ku4(PD;ljK5D~i!YoHBdQ0bnaUi$d zG=#R~6S)9WMR>&8O(R4y33G0)NK|8L(?XGd3SuxjH{wFfYUFLFx$Ggr{s%=6OJ!opS&f*dv?=6znmPVf;`lK>{ zgOIoidK8ip3NA7YQNMa7(%EzHd77ghvpg$=pA&=3;4aruwtBD(^t`F@R$x0R=$oeF zfif!=qvmUePt$OSMHm58SfH`sUa7Sv4K0f?Gqtxos8E}SNGl95MQG9xs!U%d`zzZ=!YW+8U&`3m}nzZ%O5^dpKYj|8d+5v zy+8Ul^~qS`zg4i{f63ZK`ZsJQ*>1NtOKXSl9>npswZBG=aFi!Fn|ij42ik>yqdy+8 zZ{Bt8PS*I2rKD$-tt+~%mCl=&JEv@?d}J}UBCmGz^0?I9KfbbMoKEe9Vv97mpPoF) z_75Nanj4V-5SEDR^J`o6db_zDl%24WmI5hFt2ExJ^t}3FXSfIxIiBSo{*D+F_6lbU zw-tV}@J{T%9RRk3Z0d-Vht)5Ca0Jq5kDLC1_uWBgxB*|9{?kaNBKs% zPPU!9SZh2jc<;~=q&U-QEvAq$39l`8!#NTnL@J&vEoW9V?+YM5b2ZnlaVIJ@ivU&Q z{#tjd(|z-5Q0uOpuX<5w#XQ>SCXrflOQmuWCZ=3Z4NDc3LrmN000`=TEkwp<$(=Jr z5=6FlqcllA#nr$K0!u6X{3cuZl_Yaq>8P+4n(*x-HMM93sa=kJ&-RcD*bKbgUehpx zt^2mFUs$FJ-F&(gJ4R)F$BUhiOvWHSy|G{$N;#!&qwSCcwoio?-7f63m4;=%nBDGp z0h+ZIyCq4wfKM8j?acgteL@iw;=%$Db!X0xx+k?qyC@Ehv2Em5-DTf7^VV*Uvoptz zpF4AZr_0&dV^{oZaZ!km-o&3hd&P-!r+;>-i_dF+ZmG+jn*7U;iF*%mV^+?u2rthc z%i^}`wVN($ld62vuU9ef{&+`5=%bUH!-8%ldTg|%ZJI(*)(AKK6Wv{-- z38pDevtBQoJoOXw_xPJ?`>n?x-`aZksslfaeg4UUS141-1=dsTS$V)WAJDf!kKWja zkL@2jcYJ;S*#7!|@$<}i%h|KhE28tCsWd9=@bk~JpMIA9ChufFJ$dIVUh&8;{nE@g zk?FX~AI26z4q-JMXGN8m*|4XISt2@#+F)cG^o9*K9;)6rQ~d9rf8^dB3F@-v2mT74+s;1R>F8y8eloavFb((nY)LE;h&46I<-N#@+2yYL zhKk&%mjTR_kVyblh`6@Z`qTMzW4;JuZZx-Dv0zAGO^PjueX`h!jXEj5wy0FC`S#q| zodxTE{@Up{tQ%^PR9$Adq1#oPi%Ae`gFxPu`6SarhYr?tJQp=7$W()^7PHW$r&2l{KHA;yRiH|L_UAU|sS6|zUE$u!#Qt*C!4e-YNzyn) zwqSP{KZt4GyyV+9%}YIGoubb~r}y{|=#MDw`pIvy8yLOuueQI-=DyqhDob}Jf6f;t zf3fxbOMdSgqF2}{{A}R~c$p$K1l<4I!88b8uCd|*AcIqll#HE~J|c39S>M!RtlTDl z;RGk9X?!L&PZ6_PI+fo)Bu>X#2wnP zZZyq;LC5|(r*xIZeX$X^-&iB%(p`7g;`Qpuh9|s!73sPfTXtG!{5hc32{!p?9jim6 zzSuI>8DHoo5o3YAN=91Ar@`(BSWQTON+TLbBW-g;Rx)0Vpxzz-)?=TBMev!&{tYxF z`kw#(bpHD>#=k)n`S$Ca@KBDDM+&^0ggViUgX}Uxtp(eH-6huFr4BhmmE#ddGEojp6EsRa}4pH0YIjgq-6i82hS*+_A z&U#n>-Xc1jVKZ%r`NDSLRN+D5kMAq|O5s-v9|XcXLL1gtg27W5t73mNIN&7vVmwGR zu=31v@kd{sT^*{8e1Yv2v9=bxN;Wk=Lv(YZHykxkF5npv0*sjx6RalaJMS`DjF?QNj*BZ;Z;hO)h@bQ*_JQ%SmEK#PlQ)0}d9JnHBgjcp4UQllE=- zAOGUyM$&hqeN28^D7?avzy37$2^a z?tLY;RFYdnyxc}rkY^z^V*`02TplehAT(C6 zp{_F&Lw!{3O$eVZYwM^oJuUh=EhK#5NBxv--F@rJ?iAOF^&2lcbcJC4bxwTEWim4O zt!H2Uf#z0&H@BMnaemo<_^~^VzxL%>TAzHhfB3jdg5lh;8?MTlih48c1<4FoAdgTR{uB004NLV_;-pV1B^>1S}gsB=cnkMg~+c0{}kd z0}zuagdh#Q!g-+V2dM=B%f*NMlU0PefAlX_FOV|0iPvaRpi|6n>UcifZ2}kfU zUcsw)4X@)3yotB)Hr~Ozcn`aH9}EpR1PCoU^pKchfWin31B;_@@Cb;QVU7isIED}K zAwI&#_ynKgGklIO@P8$~!q@l)-{L!bk00~lOjaL=l@jY`EJm+kndIh_-w}*I7d}z#KC+fYB zM1Aro=TpBZxH5dG5>BI|ecTAz;YyAy(DvTO6LY+uaHB*Uy?->iG3Y>K85)Uv3;0YTOgXCXbd!;LLPs({=+5;7Qf;&qz( zl9XIXw}0(+J$KTEjMP|=bHtraC^?$M1>`c~~!)51~P_%MV`6@ZvA5}%?q7Hc%m92Zm2gP)k_iR*?K23(S z)Sa(-<_A(LoiC_N#h4*xJuUJ+jH;5IHjc!$R4V%5SZc9I^mdd>-I^kCAbCDzf)dQw zWCiO^t10P0YF|vHp0a%EjV`89NmdNZjjS9@Nc~qza(~jEN+pj*DJDwJX#IV@8f>N_ ztH&gURw_z?8&*0hMm8gzjl$GpDvBjV)SBiyt4o=dW35e~#+V_~cHB<3d!mb4M?H;v z@P*-tgu)0GSZ;+-nH6nr$*C>dn_zrzY?kzw+r-!;L1>K|rJ2~3Vtt?JDy@@#WR-Ph zN<5^kQGce(Z0t%K6SwJP8T!=p-araRjE6d5;$c)}x~!tjU2K*Ke3*Eq$fqQna?g_b zd=m=KnPTNy!L_FlR^ux6vgb)WW0~^1uq=HlMR!|1EETOZRj#7wD<7M5i!wj!3O|=^ z*a}H{Y}HB@(tv|W3?l+C*o kOPvPd^SN;%TPU9`MwKs_)7f%V*)eH1HvR)@KcMCS0D=>oQ~&?~ delta 34045 zcmV)PK()WzmICaQ0u*;oMn(Vu00000ifjN200000(d3a7KYvDJZDDW#00D#m00TGx z015c`j8%MRYkLPFf#-QC^YL)?wHyAvZG#(B4XlT4l3^B4o`t6TKncVFmL3!oEFSD0F;Ky4c} z#T_-R#j7>t>veQ3U$4|Om+z_M^=ikjU&Xa1*W%|&d95wpTPce5G|-tY#lCKTG}4_O zG|`h@^rla7*F<0X(VqbfWDtWH!cc}WoDqy<6r&l#SjI7)2~1=XlbKTd4%3*<3}%+! zvzW~s<}#1@EMOsvSj-ZZvW(@dU}dp?6{}gpTGp|i4Qyl+n~Uofwz7@w#kzx?>|!^2 z*vmflbAW>!qM5@S;V8#A&IwL`a*ETO;VkDk&jl`WiOaNbg{xfSIybnqXWEk+rkP+C^mTsyyq}ZX#=cqw?ETyNj$n zMAjyewWrA1OJwaWvi1>K`>NdAPvw1ok@*0T`9P8RAd&fCk@*mj`B0JhFp>Fik@*Ob z`ACuZD3SSSk@*;r`B;(pIFb2yk@*CX`9zWVB$4@Kk@*yn`BahlG?Dpq<^NW7hRA%T z$b6Q_e74AZj>`MFD(~liiR_y%vTuRNzJ((D7K!XzEV6Hj$iAf_`<999TQ0J1g~+~@ zBKuZ}>{~6eZ;i;lwIci0sr>xci!?TfG&YJfHi%GJABnUdi?pAJw4aK!pNX`ei?m;ev|oy}Ux~C| zi?rW}v|B~mZ$;W!??l?~McN-k+8FL ziHQl!z~_YEdG7cAr>d`H-Ush}Pkp<)y6V)av;3Fee>q1vnuCADz|ZE4Trsyjw?CId z%>~q7L)|L!Ekv3Y@~f!V9S!@Pb`)l>8h=U_QM=QgMM2{BvU9%R@aiPNIvS3Konfb) zpcf9^dFarczbii)+g`nO>#Mh2J=nHwpfRR%g=4(?h92isVZ~SeN7FSu-7@vO=?u4R z6ABXuS%RYz-OFBkS+55lT{PcZ-MyOB8h19UU&okG$_Tmjw5AdMX0(ckP#u4(?|*87 zo*g2DnJ`>VpnEv&BO*(=lewFre>{*awUFSmsJV#SNmFne@lMk0v^QUaetQS%kNTrE zG#aj=ZViRKu-ApYb+a+B5ZqEhei+DK$x=juYy`MS2K~|S{40bf=M&p!HZwHOFw3^E zJvBDr!*o>7_IT)cDkp?1+oofurhnx_lXFKk%h8UaqASIc+E$55l!{fL=)TY|DTdYg#hMWCEUs#tP_8$4 zJ1+`m2yJUrYzv{!$JIvF;uI4*G&Nlqv8hslNe;;|#Q%ddaypD)Pe(mBa(|P1N>5u;uw8o+qM z@D6~kQeu*$*6T(=Iv>6yvMLIE7|^6S8uX%3FGvO==>(rSHhXL?x-6{ktlz#=@@*J# zHSlVI9miYR%`I`>&=uXwcYmsu757^2-`4GSXSzub-SwUC?AUSnRe#iW6s9Q#*A&I^ z9KUMkl_`U(K36owtA|ce~s^G_wXmS0_ag!?wVWs+xCDT}&Q<>(fg?4FF zfNv^-wo-wiOvlqAhjKUN-j@4)7|qcT7%gQwIc`B0Xdr;O7LviDj-Lck6Rrd>C_y5E zC>XBeFzCTANf~}5ZvrNS%e|l{?}M_v(GVC_%AxLX1T-@kW`Fari2A^^lG&3<0hkj$ z%H}KO+I7^I6%BjxN^j7EPhT_#Nx&11KeMesop$xqwXc|JAc`mh8D#jTV8C(2(-BBC zIH0bs(taNcjA>9j_! z!_GHNCm&T-XMZskx=^~V!^3>BMqsvx#%RT6_%(y}yik?84bzvzd1weUg>YK~frvRK zo{AL8;Sg@hgJ@}clmWxjUA?D1jTn-m6RS>OVYM1V=dr7s24*nBt*L{1YObohXQ^gR zMMRvrN-JTi>FWHGk>f>}dRB+r%v8iF(F|q_gPFQ87k?EwtxCo#M9aM)_ZzuC$UT$$ zD)LYnbzqiLp}L5gZ2{C?C8J)(8|$boM}L6~(gLYi&J;{Il}YCbi38 z)Q1WNL&=DeOGaIg&MC3mR7R(o zt3MP{;D7hz<07~pTGN3CgUt8g2B4XCryKgb+3rJIdQmrowsw**6;SQOf-VBJNUZ`r z>9+@A+Tg57>8l5wih3bXXs5e|+D-0<9q3{t>+H0NLqM)?IX+8$;BE&KOmSFl8^E}+ zh_;t17I8T7m-BHLoms0^2;HWY4Yv&sM1;WHv44_5ndbqiDJr*w1`1H2R5KNl2b}}; zeYc`7oP6WlEld<9`)DXHESeoUK;|3-3Q=o;2 zP0FED22xWDASA5}r%s2>`eLL0?_zI-!Q*{Z5XuY>dW8KFC>qwC+O>7LNMg-1^R}3( z$A5w6t0p(Z;l?~PqkiO1Y!CSr-=kP34yZK^>XH0qn4BV0BUl9zXVp@y%@#SgJ)jRc zK7*PL3};=^x?<_fNvmXN#KbD}3Ny=LnnoE@RRkq40_AE$e?eB|-6G%x;1vQ|l)4;r zCBb2o53f`aGdQBElx75zji_XUHjRv$1AkQ@VSrBcHAhuky{KZ|D>)d^63AUB%vA%p z2;?Xn3!Q4Gn+uJ`QvI_W_*E#F8rUSNh854yl10q6)K@@$0AFGPtpoucs-wN|#F<)~#T`#b9Rp0@$i?emxP>c{gQ#0CT zdy!8sm@T6Q@98d|>6$I!)_D#6Wc$@?zTJusa}yJxY00RzujYB0mOtF^J;$os1-sU% z9z59WHV+=mzHoa_S9UMMHJ`>m%0=+QLWyK=*~4G~j)6scFp~Jw&Apvpd4I<{exk!pk1IEG)>XSUP6=0VlDXnhSH4+@(-2(CcWxM9}iM zh(?nk3PrmkMxgH76ZIsT(3v0vRg=mPz!*)5B|zA8r9gyXPNB6d+QAQMQE=z@=Is5IbW)i_|P^;R?1^8P4mP zuR6NR#Kq`qjmr)@&&IoKy0whUw1_1FCa!X0xN})T4kpaV(RpE>zD!O&KeuQ^zVZ$p#y8NG5_)i6BO!pRwss5#%+&`zr{VOI{9Q@3~)VU;@VTL<#CMA3L&S6MwGGu z4Q}Ml+aO!WmRyj_^kG9zu3MGru8}H3w@(1=Hk$2@3y87T03virJK8h8I^-9DC5EHD z=uzg;4RvoR8vkxFp{Q>8=*mKq;%{!Kq_i=Wy!PGJnMaI@```@{aTLPFBf6 zt`0xeP*3XA3~LLHL8HNVrxOAePd`MDV20myuT=_+f^Ktt+jqBlbHUSMeKVa^YHnUK`(=iIW6F3cD3kaA(YFdfJ6HwJaMZ&M)40{Lugj|M_ z#5yT=EvFzNs$$8`StWyhGqnNRou+(}q)2Qk%8EB@x#3i~CkW_+Vgb5YUTG^~OV#qF z89#L0Fdx^(|DCI(xnldR;zuK1CSE-OLrk?n8_pbDD1Y8&S%EseAE;=G6V1$n4!~BV ztHRF*#Bw@9V1;lqwyl7ujjc<&H!Rw0fwhH&aj-Z0OdZ4+v#K%ZqGvHJs?^c)sv#6% zDp0~Qsc9oniLL>PHZCg!%~OM@u?x74q{<8IJK;MPQjLO3;1tFs!yxu+)zFExyk;w! zU#U7OMt_VWJe7CmK!17dg4b4yWuw-%7yEwks3C+ANOSs(1~j*V5vVyaYry7*-{1$UkX*0}8L8 z0s3xRDJv_B?O;ZgowL^m)u>Vib7Y1`Y{wN%M}N_Qf#HhPsEoP;=KK}fdBdGIt|7cJ z+`4n^x@!HW09&@k1mvEIS+8j)!Zw*s%Q$Sc|Pv6q0QZoMb`gQAQ z=gk|kQj9JduB;N3wxq~oL^W9wSuy3xGd5AgN6-bm?Gq(u0mogGdo_%h9G(i2)^9pA zpntE!C>xk;uskq@lI8+R-3X~QEQvaE!i;#*CQU9cz0MFjDtsHF;rp7LLYpcB}AHRbC6!dT{ zlMozazmU*)aY8pBk|gpI9nryGLF6F7xc9%0E6i=hLM3;cWr97gM+=`k@z}N#=zmR^ z>;=X2JMUr4O$v_e1Yd=Wg5E_C(-0wSq3aM@UeW6_{-bTH9bTQwbnt zFoUnqK@ zxgZP?L8`egl-92z4B<@M%ztw0?S)R?8^kl^re=DqI$swQA>GQCg$87d(RqDbAo<)w zxksfwUPNHC^3^%4vgZRQW6j;{;vk7P#>M&s&aG4nzD3G6531AhdL^vQZ zeqJRUj5vd4mB0_8|MUFGz-!K>pXixiaEA$&E0=d@oT>W%$HNF8lx} zy<FVhnzSEXw z3L#w8lOdQXeqPn{M;d7ykFh(0;W4u@^7944t}+-2BQ{j_-c*N%ZDdj$iSf_q=D)|Lk5f=e_Li&IqKK z2ct0RoUi0tfB1(pZ+qL3Km5b#x4!k|Rqc7tyJxPy0p;HP9{9@Iga01?*IX%;)4#~X z8zo{LB?4{4hksAoN6*A3<5TcHe1&)P>h$QGebPbBxsAg+?|^?h4{w}1ckam}N5E#v z@P$6Hb8TpMls*eAUe9f~45}YZ9`p0Qzk$2s=?{MJ8$a)lXfJ&g&)qsRGc(wHPft(h zct$6Lfwk7kP3P9Y+C3?$C56V3$Q)BC44sfkN*^8B&3|45+NhV&kd%pVXvqM|4z!B8 zRSZgF7L9^lcMbQYVong5Zr#4VyT5zB)hL9zhKo_L-r52uE_!o&2G7UZtQOD3#IgTZ z2r|o6X~=?xuii9cJEj7(Vs42XUE`D~f?AmFUt3*Ml)>RD!l9ouF4=H>#*!nehPJlj zB3i56bAPnkJ-XjzswNEo;NDWfHlxZu;gqI|wz%mOQSwz6VBqiGNU zT?!IFjm^1j6>IbpojFzWSAG|Fi(49IL0da8xb)zTeHZC0HUjsvpKmYk=kO#$+zpp zL1dMyrSio`8dKHzsg-%>x6XIxYQ_FVooUR@H_)zOZOS)9XvgKiJ75Za@k(RPF@r57 z+aCYMW>+((!xWi<9;j}Q9Q&FQ64O+-WZbRCHC~9=vQ3Jg&{x)8_>CW+I;rmtN zAHU`P`|ltB<$v9%{`R}ZFQV^^zx}|k{ae)|6D+VPgJ&@imr^#tuOZDFWs}Y_Tu7IzcKaT6nXm0!DF)o zUHQZl%IU=#(dAOf<(zX><~zz05n&OA7id>k7Ep@Rn> zI+L6nGbp=YKpEZAiiH^mv|$WPgl|*Z>SgE~8@p?yJ@?GaY-nw@*2+|K-iDL+94LR@ zGiN|W|KQ$xv4^yK?!M<9WSn{OYB;?2o_jCIgL$$;qCYU11YoLBXM|oj^YusCpZi?< z;lC!^pQ=6d^=q&F`a`u%-G*Nvr+|;8kG(8?DjgYQ511$Aq9;|m90-qi0O9biy&FxD z7vrCN{;yS+?Ede})ODSG=kuSx9GiavU$*h~OLYCq|AOAs)VMjm>74|F)k*F_ZyLWH zZ!w8+!B_Ar#LK-W_tD&c$UU3;YU;%m$*=%QG6F!&4KiPj z7-fi}(@Q}`>TyjyJyH+UWCFmNj2K9(GYgQ8BmlKhZv^K1D0P>1!W!;%UY37lGVHV^ zt*)a1<0*InT^xnONl-O~FGCqamDO{RRX&@_T_`?@DafV`$*mM?SX;wdfhZN1Xt7qF zH^JOC0u)DULbWJZFQ7h&a44u$8APO17`W&trtT^(Ayv!NaZSd*RZVxw%q)|^jS}_7x?ANx2uJV%^%WB&jA7CzmWzu<>-@w1?FTe*F?LvUv@!DclSq$v{X zC{5*(5K&^>&R`J>)*X(#o^-pV=cT`>pOSeZQlKKOE--}fgJ%sw5g;h*jK_8BEP0)! z8|u3Z(-4T4=Vr(MCaCG}^zJiUrj(v7n<@|yao*)=9*@s`e>;C)K_4kgpEm=frm9@% z_3!<#p>uf%@MLi9wGYnK(24PF9(>?|^W)-^V(t{kp|nRh z>IPCPN~V#J-$JRKmc^c+w?PzCfoDl0w4rJ@GE?(53Xy%(^0-U%=n5Ds;cG@hsUQ0WiL z<$qL(Kv2{x+JC?lV=@kXvL$z28snL;w@k4b0_;w*xJQ4|PP^Qhz|SCqT~X@Im+2A9 zX$-QH$e?5@(z+wS4ijXthC!IfqFD%K2Z9ISIZ+VnLCGSV=~R=3kuGA7@B{myi=JxR z1_PrG>@Ef}!&sP#9g~w{Sa6)O%@E1wmwxaH?-6!_me$2At_}0UhTypT%m}%BQ-v%`ur26H5k`sV6;#?=UoI9F3le;zd zw%kvVfJFsbA4c8Oo1bJcGA>h*0R0czGDiuF*sfHsFqkq< zutvgwB%L&AY(1URzVu}R;ZCyZh6A{9lm^67r^YB%2J$-%xC9~avL8lWnZ}tSL}*YV z`FnpesWAhW?M0lm6KrMa^c37n!*cx$yH}Asoq>0$V4|k9%$ID#g2K$2Nlq|OxSl$IUMOrBA{>G$B4%>3zwq6^ogX&FXM*(De&nTIp- zoBdu80tffu4DGk#EVeiLtqAj zV*GfqQ1o3@h`R5U1X)o{GY?Q{*)o4S`6>eoEnX07s$qDBq1Hg&5km4BqlAK-;&~Gk zyAYI;CRa5Ka+3)Y8r|hg0X8)ajG$E-$bO2AJds&72#-sIF=kpitt?xC5$E@L28A*j zsFL;CaMqwi#6}z#jQL8*;zfmNW~&ZYw~Y3+DZvhvk7S$>H6Q3rrxXnmDkXoj3SBa} z6umJk5tTB6u|-75^Xryw`_R*y*?c(&F{|b~rga)X3}|*hEs7@7Dd1A?Jc!OI#v%<@ zOnj6d$M=KIk8(-w(%frvZvcNS*91Yain_oGiTF9kf$)=78cWNNU?3TA9c6(YC=i@? zZ>8p9DiflKh|jW8Gr$VsMC!;?5`oaT0)#4}?DS3ROQ*2U7 zb!~cj0X2&AHRCctiJ?*PC_+kQ`S7mZvZ5?3U2<_Zap`aDoSRnk#lnB~nFH4=`r7P8 zihVWWgZjF~qr%kG09Ubru|cNgmriDgj)_LD0L=+>3fi<-9TleuPaK7pePKAta(kx+ zdSq?S_I1l>M$_zHQ9J4FTdOC++_!dWeGVA=j}Q^0(NwJU;-c<&r3-l)uO}3&nHxZ= zca}lkfQ%fa63&+vbQ*s_@F#;d$yrR#_Y&y<1M#{XX3j-G44GJ5MQMIRSH{RM9B1bT z=K(yvy=A#TzJD5S-(HZJb}Gq7<(W?8m)!RDWc&7ghizS5R%#Xkja>ABR4HwC!f4rC z*jsA`h*LoSh|I0dQ9=8ZBRaQj4&XKIF@YSvbFxu1G?^W0sg{5B)yT0z7(^Rp%becY zhG`UD)d&b_iH3EVr6`6CdKI``C$)lUnBmdxWmnw{3`8rPPKEN#Z$Wn~Z>wzV9u1~9 z?ZN8-XHMr1=WdZGGmVx_`j~nDMva$gl{9JwifS{&7-VPV@NVAH>~x1|qRJ|2^}1R1 zJq&uPD5NgRs5yVF8}udmu1d6;fQAa-YR0aJTCR`L7sB={mMPJdykNG?LLeLTW(vY3 z?$$9Us^D(3Y^wkk2sRy%lmzdd1}Sy)rTk$~0G%TslRR6R6LfY92oZw`W;mk~LKM}B z3V2lYYC*NsOA5_ed#;=8N(#M7Gnz7W;Co;UjT$t(N zUJ8Pc?M!3}VI0IYQxTpg6fj)c7IStYWnzQQkwdu+;Qy;~XR~bdPFvz~nH7^|v37>- z)QOnJBrTLd_ht%xW%vRpSEdI7LxU+9rhY{k$o0B4)E#+J9S5$Qf*>KI#194}wGBF+ z#EqRnBOreus|3?{XcfDmO78=e(&|r<`>-HUw_pZz^J1jX`(Svd`Yrf2gJsKb#&r@sld36^bBgC9l99McZYr>-fn>8F171R?j;| z$OV4Sk@?(*a*x52t0=?)7n5))b^!&G=P5{k4IhOu3+v65Jv(&ieh0j#3G&B(Y>1PX zGq7qX*NsI(Q-o?*%0Q!*`7m@iK7PreBdD&$>KPYITtZA$TLVxd!(s^#y3Ey2Izk>v<6ssINq;$jQmY_w&=1MelCqljfPsyl z0kPS9#}pAyBQk^VQ?I_B{O6sQlS5B&@-)Uz6V!a?OHX6;47?lfXf8he6h4KmW z%kq1S&1y~S@v4GWZcR3GWOxGP( zSNH^9zMY%}ZZUEWa7rcD&SHjt6HB(q*~@=KFaLqA!L^sZJzm-E0;7n6evKB{^A8hYau9%W5Jfv=t5^~V)^rEa}0JJe`f!OW^K%?>-Jpq=&@YL+tYg~3Z)+oz6hCmU@Co& zcM-Q@JxtzsEy?fA*Lo!~d)XEEf9xYqAheH6^t8;8jB-U7$9kHr-+^`}Hd;hUP=ihc zy>%pG;)`g+I+2J(E9ww`ZEcObmz+BXALoy*e*F3I;qBjl{PFLjKU#erz42$SrbqWZ zzWV(0t4}|^{rlhFzUh<49|xU=VIFI_Tx$?Xau#ml0@Y{B9bLJ~soNQjdY~ZU*CI7m zIl4l(YTFKM2`{?x^udb{_w6gSZBKmSa^w^e&l&$S(mcbCOs#@{?jBbU@4n>d&Q1L} zPe>Z$$hFc4hmbD82tP1>`r(Js%ESM7GUpcg)K|NWoA-s4BlojdwCdHndfbH^W_&`5~C0;4*e+nPHrb@o6iTAn&e zCVDk$W+8nk3D)I*%AMheM*@P;~8)eMX&8m9o-G z=6GHlv8bVXju~99ZpI=k{6m=O#oR@?1Gy_Cr@9T9z$ZJ@Z_lEpjisoDu4YI=Mh7C9 zLZ9*pA7vM1UPO8}>|~x5nefnWN(jS6YN~YF#C+)sxch?|NG4E2jBbGD_b6^M%yoda zWiZtVT?CbX1C7P#BsD^S$hZtjInZn~aI8N8WNT=M%rb--{^3hMcn~ptQJ22E-5`*N z-=6cMGZCROjTR2>V8{mi&7NU!k!+kcJ=;_b(*uyh@k$J0aCt%p75rnc(~O)4be=<* zEb_yaOcQFj9nldmDm}M>-xoC+i;c$kPtiBWE9et{H}C%STTyqsYdg9VeX$M~7VCd7 zK8NlK#vi-$=z|ZSkBlEjf#=X(e|&a7WU@d$;e9-)8`3p=U5bv7tTS#qmq^**g5aEDj3t zBhD@5c1R9Oy~dMNLy^6EtLq4G`jRAeSGet4Y_=W#SU7OQUoW zag(RWyvl%{!7$-eXq*Yn19%8{Tcw&SBFip1rtcIDOsF-mlss3dO-Q{|tkICQi_Pj> zt(fNq=x)j}pDJqkL3_pEWcJ8GmujMafOJJS)$)R%iAwrap$m_9rlofe-LbgpX^!o+ zc*k!Rb;dnVfclJX01j0fjplkOR0RNQ5uc5Xg0L(=7A2Wygo$oX)t0?kK<)5Q>KNw9 zgC0{fLmClUg_`;56t)$LwYXwzXv_u!L=fsjzs}oL4bsT@DUL`f$IsSr4AZ@T1UhXm zv~Y*FJ0nRr!yTxHUO2jE*YwQH^sYURZXOhg-JB~_23||AdvST4)PLr3dNBS-VIgrb zj=}}YX*B-qJcj&d{KtT0R?=J?KV7E@MsJ$*JXs;qPuHDGce=<(AZ=rsP@m<3qH9k~ zH8$)iU4@5R?g#T=2UV-(y?qYl9K&2l0_~SpkB&jpDOSSV` zx|&dec4SN)>aKgc2^haYQ&$~t*y+|1uzhkeFAK(LH+dZ(@L_H@*!5TEZp!^V+KTp} ztI@Bbcc6#R$I;VLx~*ne!^3PPt;DO7M4>D}JCTuO>1>jz9iu^C`tTQjaZ|FZv>rhf z@(D%uGdtFgnic7bf_q1x^JD^ZRvReN(39Q-fKtOnoQ4dthb9+N!Tli0?AXmjm}n9! z3nuG#;cwJUzk8W`YdBh#Y0fa*a-BldU50624m14LN<$Z&rR)Kj^_^C_oF*~I5IN{E zX^w6BC2J_-%i)E*m(68=bBJe6NPniaqyv`bazyPk+%665G*Mf+OxhWzXR-J}#1{LFY0O<#XuawRY68zg(ESm*kQ;E7B^H=YC)%e!f;+FRE{UMON>)P z_wk+Bjs+2+XB%=6DhEpi1Fsr{-NOrkaGbW{`G!Euu@NcGT%?7?xj7?JF|esz1&&y( zIAF4ApismfN^GhaX^=qO;pQn05u2NdK=*=%7nV$!!`R7VFgZLU1lxZ6s6~+`Qvl#) znl@*uk=LOWh)UmoHNylnK0?}-$X%=1uB9>%7sv!8fhb{-t-FoTi}IFFq49xC8(Y>~ z<6c#I%oIA1eNpr|xgu8@E=*D7;$*Opl%P^oErOV;FcK^!G6sOaAXCg`D1}WJWQGyf zkU)kFHd*WS2%Eg1RN*-YhqkbYT##iRh03UqP9j2W!?C7+Voj~%)7Y0WXc3gC5YT=` zSUxayW=WhQ5QV3j42KHiNEguY*AclWE3XttU^6_2NtMG462;7me?x5FLDYB+)(PMu zlw}nlu8x1-hdQ|U<$~>Kf~%z~W>%6Fs9cm3D<;G$sg8@Osj#wk@ww&5c5uShOxH-daV%vbAC;8uG(w)ey&H|!Apo7|Ujf0g@A?w@l1 zGxtL7C7HRsDXP+Cw!vmTYS;r=2uddtbs(4oSYIIMGM<%U#81PK@<`hLqh6;cdV>+b zwb3MhRZfOeCs9_g*}(jiZ#a~0>=0yaSEMez%y$4!==1{ljza0ZkP*#+1mpt{t8iBu zKnJBX=*YEifU%Y*sX7;uX{B@=!dwlao*(!!0G~)vGlGV!qX^oX3|3J9{b)Hgtzy6AaLc3-Zfw-B;flY<*JQG{CtqNd&KZLf?d0a3%AxIJx8IpcF##+^~Yt6)0o~w~WT`$VW4~0T+SvvkfuD4EOFhz9govLezYL);d7R)q@^n`jU@3~H1u8}4dh&Q`n3f*!Q+W?t5m|vQevdl(m9%PE` z8xdi;V)~ZFbix&aFB7UMfP$&qgF|wEf{?sv3kE}1AOAkW7sCy)Dc1*MF?f(DTq9M4 zzPjgPyz3#`V92uQ1)nq?6OkLq3=S_%x@q@Qqym$jWGTYuqw|>?nTP4h*Yvt{PWO{m zGmzV71YmCrB-D|cQlu*iv#oXp68y+apLDHKT21fyx(tTmm0oOyo(x1gR<$aBMTQ*x z(2QRv2G|)8a$RhM9-*68i60nocLnGGL@-z)wjQX7=ec5OVKt7qVJqCsYi&58IDSjd zn})5kI9@&7Y$c0)QB72@d3q3cO)#HZzM}}&YioIvJ4*$!y0Da94|My2ZG!MWgVh&H zWa;^7Mo)hMS(u$aZd$1G@*mHCHC8rO8tDgWUpRj7Wv5@?*gUGsgY>vwU-^DRex{|) z=U(A25-M{YQQ-9ky{Jb9Bk3tc@!7@r`iDRLk;hKN1U)pq{o#jE=)Cyr_nvPzsFE~~ z6@cGJ#SIm^=sPHnoR_|d^5Y-MH*S6>`x<{8O^?4d9^>Z3pYtTSDtB*x?(?~?!E?a| zP10nOP%f;p5+w89|DPV%>0QaQuSbD^> z%Mui=(JZqB%-ptwKB=+m^|Q57P#A0^AUR1?S!Pul?n^gkNiVlMsh|s_^GANdMT>PeB!CZu#c0V}EC5S;n#uBO zpxK#Vj>?!XqX#x4DwWDoXPN5l+_2LaC zKpW}DjfllmLa`3OK|?Zp1p1UQN~f!ZZ8EgIx|_jQ$Pa?vD_8%26G}5hH>5|NB=}Ts zCux-uwOUzFX?c2nrV9!_AM8rnWoVD7f%b$}3M+C=W-c?2`Ia9X-wF?yEtk&rV_)I1 zD4s1jI~{<5tzl!wo`%F$u^DihF5Sfj04&7@pr=T8uCG&tc@*G>YDO22pE5wMSQZ#> zYPt~^H3dV^FHPfrMAaDFs~tJevt+owyYFb1D9qk&KMp46(wDiV%uthD<<{q&r%}sgRnO>1jHGbk?5_yMjv}+&>if~t{>D;6?el;_gLl~z(bid?e0I-vY9h43=wqmL@{GtfC)JSF^G=n=bsRoL`$8)AC7e5+yBv-NuO`BaR zPI)!~^W5QoCcvq34O5R`QE5#V%b*~CLjp`yjq@eIRzSV*Q#YJ>nG$3J0T4Cssw5V9 zW`Z6BVGG8-w=cg~ll$Aq2ddBrA*e9RT;|lsUAzbbc>jo!DA)7?f;`zW1}Gn(E3aRy z5Kv?aQ5tH^JG^K>#cY1LYQkAfHDgfL%oHU{bh*?UYE<1S6{j3FgH-c{fn5Y0qbOlb22Y4gCnkX< zcf(PE9r^fG8`9n(QmKKKa3xL!H?p9i$97#h9AeD#D9$c1V4fQ6hDbS zi~loz3O|FN#s39=L28q3x){4R9Be}aIPUI1{Z%AghP`cR>nL+c@}wcT1AaH;??%&q zMt;<5Y+jT5(j?Mb+&=F$UWEq&+8B1+bQ8QVaI+292JSF|)6gX*D7_--&iY zLLRt&0uPrrCNpX2ZiwW$Zaf z3_5-=5Tkxq41JM)cKqf@jO0foWC3|-qY)HKI?aTkQLr?Sw=DINo|ybdkGt@Hkye== z$$NL8rJp$`D+xp*Ltz6O$urrp=q@L{b>uG#c(PoNFj|K9Uu5>9FZhyI&woKF zr~z*4Oipxo`i=eNK`>fQ3!E>F(F-_rZKRfg>It0s?h$H&z(@#HgivXkk5DBRg4p7>07JGvOxaTt_sHZIh4J$QEQs;*Qci#rLx4la4|9@Gf_{&anm;vv%KXbu zNHIC?%3MCN%A_{|MAGvqZueA55i5g)x;I$U$)>MsEZ|_W>xuzyjKUM}3x2Xy3w0W4v*2HyF@t_dbFi$JKMU$)_Q- zLFNEM;Zbu@e=m~=6<#t5l zXHFhJ3U?hlEDfeZ^7j1{W`^vApBJsk-@kdM+&U&(&wlN2K~KSii1AS@Or3J{ z8kt-v#Q00GA*aWK$wG%80+Sj+CL@RV`=q`Vp&SkxOOts39UMO@ zdO#t07?Wc`SuhGmm%-TK@7Bj(KPe~X#POpk5uk(n(#c9c0WZO@tvxM||3fy#>E{RJ zt@r{RT%eQeK}ml-HrS8QekLk)f@jP7UKX#ia|Ib|Z#A)J|B5;GnfXn4mR)wY_}N^TE9R=X*)-y^xshH^ZUK<)Nw|sz>B8r9@pD7^$R;?zld4yydc1Y|N9~pI$NbXaBm5&}@50)tL-rR4b5L9OLahohzKQ~)SI*T3%5tA#2ksFzH_kT3#} zX(F~2hZ`(x#hf>rN=>mC)DCq^WvW@ZOF&wQoc&c9>yhvao-n`_@*q(h_f{`_{sWwZCi$ADh?ok5inrc63r2Ns zH)E}Rr!U{e*zR7edad<#d;P$AtF>;oRMXH}a-GJsqU*{u9Dq!k2XXo!;ioC*i(&qj zm;MgS1`V?xf?_ZmGqwcD6 zLN&yUZtF7wj2wq@`@){#41YY=N+aNMt&7NV#Ilf5wkuDR5>9rdE!bqM+Vzw6+FC(#_u3BuLkJ4dTgoYx)fc}9P!IK5sO|8=)M8oX(^H5pab@-sKw z7{z)i@O^|o-b9pgRafV1+m4!cY$bUYyI#$#!p?mY~@b9s?qZepx^O<3; znfmH}{xjJxe&m-=9RDyu&HwKwX0q{b@t4zmJY}R_I>b68@;bw8`)%QYjBT5K;}y-o zn!ECrD`(UoUMP6#;*~e!)5dJ0Kdf&t#@~NPP}^wMXJ%@NJ^q{K1)t+KB67c#`!neY zX}SH)Oad@p{3J~U@xyhrDMq@rY**rJ0eNq9K13RAZiSI8w@gEgRk`hAno=S+JCtiO z(-qy)mJ9ufn>Sm6mwGk2(m@w(3aDOpuz&`wk=*VzTgsO@b9t2Rf|#Z}4yB7E=?#B^ zJ_y^P^m@RfqjsmCq{^YqCw`IaxnTJ5G@)g46AI}?A@h`$)O5+yI4}~q`Mjej0B;qA zr8~;|dg_tj3ekayOu1}H4Q=5Dj%MmU!wMt$qACp61po*jP&H#Fm<3o%kV?M`SdTgK zewh$yP|a37Ehw5n9>juasd+=C<0gNFB9%tX5w7&A2$Z*VW|uAL@sryg62{RZ@EnS@ zvSF%#JUmr~Ad#ZUKw@l`>ZYMsT3&!Wdx2Od(hlf4a*b}ok%|)U?(d7I|BoZ1A`8r(Zb;MfE z?%)($_=pm@>(zxf{_Oa=#{7SL13lQ7$8+O%HPxA-!8Y>+gj~v_J#i4^|-Zf zu;VS)ylpi))_ml;i$|rXHuIZPhvufQ9d3J7XZDhoUfg-|?6w`(%x&2nqvOMCX66n} zY4PsMuGz8e?8%))y>)-dZ0A*aXSLnl-X4ExdbQnI?daS0?%DQc&)z+MjTQX+qh(E5 zj>^6BGSmmTa~j6xw%pxv?^G{K=99kF!6fOdGg-{l%`y(swMRjwiOz3!lwlPM$;6~I zT+mL^0ZkVyq)VKe>FE}n8)aD&0M0>1NS)7?l3teDk?o=6b$fq_j4|}H^$yvNHQxXx zFE-V14%3#yY?h}AqXA%GooYbH1dxpyEdY}=H=41>g+>_Vf;eQVQLV!lkbFCDcBP3H z)(REZjWuq#l8sqN4{G^0l%_Z*I-!ENtUP`ap;A%iMraB(g9pLbP(iK`X$}GBqOxfS zc#7dse6X$0>STYm(wy-FqKdrgm~kcFOvwM;*Smm8a#d%7dGC$5H{KDMkr9zul~q}p zS(#byM?Sk%-BnVzT573ZNNSKkAX$78*pgu+Z14camH?YUHWo7iF<`M?8-~XkynxLN zvSq+pW6O&d8IKu{B@Z5KF=jr-US@_}K>j+}^WVs<7I=SVt2{gD^JZG4vzz8*8<|i_ig48f? zo?Tz7#t3V-OfRwxZ>B?(N#FVz8oNTfm4{+)v*9d=0GlUWo&Q0HH|4%O#Jp#S@iU3B zh#laTcD8@PRv!sUY^==O()4ke&=bMI2OUipnkWY2%lM5rMuxx#wyEMpco4c-$qwxP zLKbyo>@*$HT#dQ8a_Dl;VLU1?mzNsd%~Xb|ifZ-PQ$gqjdVzzdoX5j1F-XJV*hq=E zNi5pkc7-o9Gd?~~Y_8;!Zs&Ol#&J)Sk*>6 zmu*yvb|`Cu%1UK1w4*GX8y#CH9qD_oD$hsFs<(3fY}<0wL+@gPmKxfz%e_M?|aEywYn(HqF~;o zFG>3wrr`HFCR-(8VQeq296h}nrd7qOkRE@A;}!#wTwZf7sVi)nH%UgBsX{s!g9q=Y zvAc(JKXjYfRHs?vPZ!=*_-Nrba|BGi(UQ&JyQXYG@5F%F%%FUy22YmGqW^K@Fwb~^ zV3;&FI|o<{o@xF7Af{vnz^)>7gT>+IBA8#l!Z5qVidQ|@8v1l-vmQiB zha}TTkR(68+=7N+$kF=#>gg)rtKdXsh^Pi}O)_1%#F#jhp&YB&+#r*R#J*ZwGPoIp zKQB44AX8xfQ5dFelzhvvxh)-b|B`?0OJO*+l8zk-8VGAS<4l=AjT4SbL+({KuReYC zf+tnca@UsUmj<>8>@CD3^Vkj@(y+F6uCY85s0prVE^NNo2~B27gE?dq@X{}rM**vY^AOL6XxPSC$uu8&BJ3w6>ijufsc++O%6g@3C3ZE1!K zbO>UOo!D)uV%8gvW>GhybD4fHwBy<8jGD>tZDjDsy`%e>g~^l;_KTp&SRPWLCb;fD zx}U!;4wGytbL?_uE-nR@m27|RYz`OOyWQ8PY<_VxmsKsNTxvD0ydq4Jg(M;2giz7% z$Ci^d(zF<#?Dp7leYml+Sq@62`Ov+x(JGZ4tD4OX>vQ}zx@|E@<_v`GoBTspvve`b zLZ@V&q&dfoOV5e9+S<7T#i_+V$PR^>jH2qzy7YW3#wT4O1S40m_^-dF#?tuq9K=y{ zhi2ll#<20VXa9!XKlxdHYVw=x9Q)}vKKL)bd+{%eHx*`M_dmke)R=X%S2o3}YfpcJ z8d&t~v&?_;$;oHG^re3{Jo@PG{T{n`=be*R(!t)r0jut@)nn{n@5y8PdwXR4?|p8s za9EzHaT(VOrwiu`cj(yF*B`E8$!6}jc{wULn?Yj3f4~GHiS~kOCHjr{4cbs5&DzRZSi>1lW#pRVbm;KYZ)pli}JwMlui_3BCZ2OA6_Su^AXCCR2*`3X?>zz2) z>zzB{`PW|9wvUJffIi;+LLdsEH|z!l}4jtd}SkwS2yQlZ*R|w=PS~394lR3S!#E^y>om1 z%5tY$Jn@3je|3UpyYc$8Uf)<;O!JQ^PT{Zf&l9E83s)3wF1)z#tA!61K3@1_;kOH) zFMNT<;3!kXn)SAP*33QNXJGrSQD>ZoJYb7#nuQ_{cG!Pso%Wy)EkwI=Y&r|A-Q`8c&=Gm9zc%e!vszpbX0+unm$;wBR1{PqzA&lL z+i9< z*zbQn&c(~w_dfTESA6c`jr1pe>9cn|$BdVM?zvaI`4u0!Y2w~E)6JN$3Y%o9-bJ#c z!18_9bws%9DC~akxQ@Ni8_p>_Kkx^*RE~Ep$wz-Pm^?I_b7j}R*#Mi=1MM!+T})ie^8D>#|`e=&o3oI zC%=^6{Xe^u8{Nhcr&F))ukKhPg5&K<%l3uiOW(C!|K7&3_#G#VN&yeQ-fbMUfA~^T z2m@1Jdf(FJOR2kjz29 za`Pk4QV8_7;TRAMGy}(&j@|q_^~^9qhR3Yj-|eCQh8n$9D(yX{mxc!2VW-t?r8o{i zptO$F;Zb*}JoA8P&K z@OXD;-VWUPV#$$A_@rGa>CeBgb9{eJ82r+=;kZ$^9BbhPbr1Pg>X!UYRK>R_Fb(sQ zGL^9%1k#LOlLgpGYziP=7{Z86?x<}#kL+2Ya4rgg@IKF=QL)XEDUXt#g!LAmA2*a% zB8}!I4_jDlTNzUawt!;d*41=_WRL+G>AbC922F zhdn84l1i_B2CnylrX?38b`#YmMr5~2zGmB^M3z_Vvn3-k=~Al76I9a}=Cdd4FEcDw zNjf6lD`H7iCF4on0HZg8j&EH}`lrDZP|L2wZlF?HJqYyN+7=PX-g`(#d$90+;{IPK zJYD!&;TwhjyYSx&-z|I}I{|;mWmpqUDqDO!^Tf)XxpIW7X=KB6gH8LOYVJM7O78Zs z?r<38gE$nGL*v(ZuEuo%1w4xf((p9>skqm7v`0FiYJ7-ImS!frytE0DQ!+iuoOv|n zYpJ@V+4AaTU4n!Yb}r~k+|necTc^^t8h6@bBIU6@+Tov&q`=LWmotChj)t|J1tvRj zwOZzBM8+pvlbBNLwQ9ALk+R>bE!DGm(rI|D@5X5wd)3m?r%h5Je49v7giD4ytW|w7 znr$KMz;j6Vm+`w}yLP0WrcpIy_j@soXY6^^Mle?`c9O|Qdo?nROG{NhP3D$NjTv%O zC~g~RP_2Y>DyrM(Cr^Je1TjdXUMVMDW|fwtSpGS`nw5Jty!u&rEryG=QjV$;;rnM! z%c@k-YYnSt!D+vJ#F+e7dQ*Qx$9-904+``6@fB{mP9;=T&m~j7vwzC-y=t1oj%#r>Trz3q)r++xBJLU46`6Ky z=RZof=4a)sL`J?{R@II?RV)=%xSP&JWQhr7+LE*}HneJ%s`%e8(5D-3s-RXJs$(ad z^2<-y{zBhx`=o!4%wo;OCJ8biDlNn6DK-Grj$p@euKOx_t@EzlnSk@6dYs>P>7l2$ z;v|WG;dg)LHLrQ#^Nnj-+xz$I4A#ZwrH8(KUEGR$z0F;=**kgiTN^9m^w6LCDbjyW z0ZF@^PpGM{Y`%}s(OGbEov1gplZHADQKREb`8TnB*64rf05En4{E4YJgj7u6aK#_H zNbG<*F>{JQp!Dq`S&)~@vUrJMHnO>OFZN|h_9G)9M${%IUsisr zT=mLHquiaB$6oR^JKTsaeUvPnpt5-Vg#|KPErl(9s@Ghj7`i^5Dr`K+CAuIxO)kh< zt$q+KC3b&1E-8zwE5w>s_ZK>0*_Ohbm*R{!*kuc6OWET3`tFce z`$U8zV(L|TJ@U`4v4Po%RV32<=ZXDC%o6_4TkwB&m(~`yYD>PMZ7iajq1a$AW53G& zjQwZ!JtPm~Wxl`>=a^*ueU7+a5}TxPC~W8?q=)nu1>|p1hJ?(cNYzXONs6~1Z^79;m&|AK+f9!2rjc&%Gu}R zJ~ou7(9s^e$bm43eW^ZK4MWnt5MMJIY6o2;%%tPPd^arLR_Yz_eOBi>rLuiL-6&~ITGnOk?9pl_j0^{+z%Ik{-90eD7|Xlf)ZM1RHb1C54s*2(Lu+im+>!V zppNa?T0}fuzUXDMIB)(TANZ#KC}OA&BEmRN;ri}Ky3jPMDO8>soTO}N*s-boune<_O*3M=u^?^6 zR?t0xqM-o~~YR4fTu!(<2ImSGA9V6j&;F>OcrRlQRRFi-8_p!Yggtik> z*GOREQel=snvPy`1{#pVLX#wt^8Bq6BKFM4tO+mP?MimX%N#+Lo9ULkBS&DL{f?xG z?J1u-RWBhidF^|2l?yn40zlnm+Z3qBaq0q^}OKQeWyEYlcfYCJA&;n3ndEL!Mw) zaSLH_aNZUv)+;3mJ8bHitmYUr4)l}EN9v3!2}s{YRtTe-3b_c9fUal9=si6WmuvGw z<6zJ*Xc`k_YZ9l5B$cHH2QGhZo=$hhbrlSFoX0jvW4fvKe2Sn-pmiyb8c`nxpEgmY zN@4pmCT>nPH5INszUY^_LpUX*9ta%-1j5U&C@`o*v!PfeZ3?X{*CP>Ip}Q7Pp)`=P z)KQbK3M-Q(ha~t46Z^!DJ)$tR3T+tVN1S>!GHm;AOVpn_mnGv-z0H65!|U*XCn5`l z9wBe?-|*==^YrG@aK2BuPULap^ z`KISk4=X!zgW8JeE3$v-Ev!ykrX4LDBqWlly943*u{2H3Cn-hLYxY~1Rv$2n(lju= z$(~H$k?5I9`K2JKB71ur5lf)yqD(5lu>vy)=dA$!XxJq`wn=UEv2((>XD_sRTugP9 zXGK={k;1F;JZ$+YKe+0_OzSv6UYFSx)_7_NEY6UW=-+hnYKnhhHnTvrs})H!eGk$R z45vr)hwy%>JuZdraG6YJ8P}ZHv(uu*F@KD;#j2esFI+u+$Ekf)m&;vFh!SZSMKoD@2BGTU#ueJPKQhOI`3qH*2nLEU%k} zGD>R=r>rc`29|#bcjgzXVYHDhuN^ciG0 z_|-9HWxZ&KHSCxyEvqz1jQLJR0sAL$2YdHLaq?67U1{&D}D#YO`!En^S zya+eSlKgnS&^H|QXDLZoOaC~ei*X{S3Dza4YWxE$lw}er?>2wCTby*M@$=&gAGd>3cZ`Yd{1TjJ{ImK}L<^TKTxwzueQi!W8@DuWmH$UZ!I`*=0p>B24e z5rnP#vPxoHJ+x`c;MzO4B=fd#)63}X{47Tn|3<+pq=hBDVs=&GeBl616mPkr2`3*OYM9^ilagKBf{z_(-DPw92wU-msSg(ND6#vUM} zmChZOsf7ALC*`5fKjr;NdvA~3J$dBWXW91T%R(YQ*y-)w_R5*n)2o-)I*T+<77M$u zP2wV!B6OUL-kvY!{#MA_+tQdO*$mhfz7klk09a{jG=4_7i*5E!lD4Z=Qwe9WoJ@c2 z)xX3IPPq}20`MgKhnl7BcV2t;-^*AYn_Fia`i_-iNd(=%n%v6_)=Wc#ehGG~S6wfY zMblm{A6YBKZuypSR8w`=r_#lzJR-s&jr_6ZQ5Y^2e9S~?S? zKD4LfVCWG0_KBh;f(Gn$W4$@JyT5rdmYV$Ln|B1lPt*!mM8iln& zzi>=@s$%D^+Zi4(H69JOSvJE^PyW#tG6Kmo6s+4G_v>sNvvJ10aOTVhFK~8Y@&bDM zQLpH_Meq6V?8+DIRC)F)mUJ$C?M1s)mc457&CbvAFKmu_Y<_;@;az_|Kezcoi!S%P zpS1Ij{1?0P1uwqMpS*%Svf00I%k2Sud-vvc;W8h78_C8yXIMc~FyPtp-caf^12bd4 zr{hUUNDVrg;T>>&e33mbS1E{oK%+lJeY`Brx`PfGB7Ujl#N+PHqR&FR6xg9bwve4x zqt0%1!wWbhy(&*0kJf)I0nL^Ek#R-RB&n?pN`9&6k>)G{$EShuRiH*I$Cu!5$UX5^ zDaq*CyKwQ9y(QP@+_Q?NL)x36>bu9UuHUrVH6&?WJdTT#Cl&%MK`Q`f8-dxp!n<*B z(@v;tCTBIVBdD;6s>FJ1{Bmd+s(wY+}}N~V>nydOw&6RF;U z9~`1x4X4uwQL#F6K#oxhd4RRueiQ!refWm!6a`ZFcCFT_cYgXOPj*w+wOj3t*5aun zkDgfki(=wccLugB8KSit+mVb)9V2l{c0_Wtu|E>TUtHyf>P^Sbe9zG5YI_Djdk*qkpJW?Qg%vU*BI%kh{bJs@f> zCuP%a3Psir*}K$((zT-!=>P`oT^)F2csprz)@O5KLFaa&SppCw_^-obW!xw>*egG_ z(du42w>6htS3kk-n>@tdTE8xv+dOk|rL}eEfo#oA#CdwG< zy=*#KfWweBARndn^-yiB=ny@?et@~uVmVB>hWp&b(*RHs7$25al*{WGo zWF%zOjZOYeSbeeSO55^1Zj%vdk~UYhou)0;(cCicCT1l zld+RHQHe%RdcD)(VmR6oH8Pewv3tuiZVimz6Lsdp*Wb|$o*k5n!QAw>|X~`=h6xx^(=hr=EN2 zDgM-t{mI>Uv9t8{5qe$LF<#0aC(5!5&BE!zPe9K0+g+8x$DPzBc+O&5yWef8em>py zSe$vQ}er=moLvG+E%d1%KRerk~KH@zp#Fnpe^%c8&XV5>4WBSj%G3sdunP z*SEb>6K~_eRT93o(U#w~+rls%yXt>uSn?I`bU0asy(}>VZ`;q*>tFR&viF5s+xER- zAo-b|pb6zgcSNGI>f9Fvrnu^e?S#>t?EQZ$r%s7xdu8Q|h8Lc`FIupP3r@apGk2os zfhcnL8OL)fcdkCUdgFZgRi{oh+uV5dX`zI<+BSy0jSXhranZ5chq@H}?~+2NaDBec zOC(P!6qrvVcfT7KlXC8$Og1JQmh~jjP6>y0MhcYjsrij{z0Qbz`%~>BN^G~u!k&Np z%J$w?CuO(UzHr^iuX&C*BZyzDyTqPX4;nx*Qnn$Ydc7TcOWtV`!@}QcNq_Qa=cp;J z*kpIC98r_M{Z#DR>^9GFyd&p{RT(LfR((5#^Q3T*#wOWZ?^jAZzFP^~e_s0#WpM^O z3O(JrS=00w(y;1ev0VExZ;8P^*Gqp6L%oBAe8gxj*KipSVH5UW!g4vBlJZ19q&d#6z`O}uN z%8OaCLPoY}+n!I8-xVe`oZIEabiU&|rt2$T8AhPD4xiHcbTJ}f>aeoKEmCjLPDh4`xY8}Xd6Xf%yg<67gK@gv5ojW-z&7(Z{k z!}z%IN#kD|zis@1@r?0T#@9{P+%nIb7tLGDe{R0d{J8l^^Y_g!nO`x#W`5oLmL$_t zF3DxNDzBC2o$TK6Y!vla^s=;)x z0RDdqOy*lBttOorX{gN|vei{tks^=1lxAn3611h4;?8ky2k2U0EiPZzAC=Rj*Qd*O zJ7W}`(4+VW6(;u6QLyT=>GQfx;A|<}h`JH8>CjPx!et5Ef?K05(p!(z&Y+KuQ976> z?T*rX0ff2^Q8s_jxH8}iPP40xI{KdBwD+iqyr0T4+_~ZAnskBw&?R1wKZVK!VAwbt zjHy{zF$;N@>M44)%>{TO?9>;%lwtk0u+|KS8 zTWfeC(n5>}Q1g;h0po?ZE8>Bg8fRe&z6eK5&`DXf0GfY-dq(4KFHx=j7-kv0x^M^? zK~3*ugHB(S5!st{s5#h88BuvTdJ7;f=>DDKbhITqqimGz;uKkHV*oqiSqZS%$Ad9d zpI#|OjR+V^Gcrq%hl;ATJMNbih+AhYqnol80NlsKSH}SS?CvHls>!f}#N>)s(s5`P#vDcy zp;^d~HR_POpq8sH5_Mskl&P!8X75r@4@j%e7gc}zxjKkRtQXn1^$^jmT?NQX-OOXG zPi17(_1$F9)dPh-hQ~Jc&}f-9$>J0g}$FSqMBINcL-B*N=vAdxykVzV+fBS(4j$Gd;|elNOZG)(td2S6b-gcvz`YQQ?9)*?R+ zeN?M|)KLR)Z?G;<2?hhM0K-9#CMJr>s7gxjg_B9q-8sexYYRk)80d+E5JbG>Gg4M2Zh zmGz2bHe!NSC4(ZV}YCU2_5|c%robqd;R3eBWTso+s#0gr8)aj9G&K8AQ zGj!PFY}1oqotub1!m*+Wl8~~YlLnDlOAQ^< zP?Y8;NlEGTB^7^&58_$Wv%MkF7sOi&ctwyjyCb3zxt+U#(llt1Fr@(_ z0@2I-$ajioP-8k;pUD*JN!4r-v5)u2)Z%?~R@Mj0z>sR9=_5@Nu>hj#a@N$e+TYQM zLyM6T8%5&&sp+r|`ATdCzsN9M!!+*z_Q;PigPXQx`$mAxW@gcCF1E_KWlN1e zVoN}f7;v>FbI`zA5%Yf$+rms~x9AEl0uBuF1tFiVmE(lqDMBB!xZ~4_8rQ(VR4y|B z5N2`&8vr9GJkRcE!sFsjx|Ng0XaIjJ*hSS1`+97eGN~;= zJ5T!}&>g9cKBQAl4&c*eyqx3MAh_@fTt+`!dJrBUG(wUT!Hz~@VWy#v!c{=#b;DB} zM`%BR{0fQg6VsDoiUow{Xs-^ge;SI~c^(BZvqLJ6YF2Z!FxquHEF_sqTgl=FNRWpp2GWL1K z-pe@Mr(IZdQbaU{O8p!vMsFWxZFITDuhF;Z1v@>?Q&bytj(CFSr&taH^<^?7*(02< z;3+HY-E{1;(~kOGU1xf|RUjl>lZfsFAp-h1fzrN+y61mzQaRS`r*toIP_JNlKf~<- z!-P0%iGm(x>>YWtK}GtRn)0bxr|8RZu6NW;U43DS_i#QNJdH&ybaC`XwfGDp4OEAO zGaa0+Kg{WPVtA=V>`&>=FHcM3?2phL7+B@d0fgPNZN`6@vG?fnC{y%JoL73rS{Z$}CI5AFI4b=m_6=fG zIv#&TQ#Qs8H2N8|P-!F~{+MnY9W|&c-;T6AXtRG|8F?FU69#vev8O;n^xS_&KP%Ju zVo;-WkAsXgP?up!{#$sSvi0~fr0q7}HGLXmHO3wQ$I~>DffH&ffdhtY@Ejs*I#HP7 zuOnuzrxtgxd0?Y40xW<%#d44)Hibl^xBmm~HHcLry>T7U`IzmxX|+^`Vv%T-Ae|0lQ}c>h?lq9iUWr{yUeW|}<#%?9~ zU}C&qlcQ8g?y}c1)BFY9!yT$BUuGn{s9G)dE-J*fNebE6gHaU$^maoi+ko#I(YvkT z>Ajew3gG0;MDS*dxkQJaa_slRN$D4;C5GaTZI~I7)hh8>Jx@9FJxnob)5A6d#V>y{ z*1)WIG5ogmd~0ibHnyh0w5VDjJi28El|`UJ8TSM9wKCKJ#>-ULB&4ns>jJV` znhBkPvw2e>IVC*Kh$vmmYSZ*EG>$12SVUtP1SDA$dN2_&k5I6ohY(cGV}vb1pwZ3NSm<2V%a1@P!Q?lFC zMSO9?3c3zbCfH7B%P5ehK-_<)VF91OwjI*~4cRC_vdn-Irp290`h^)NQ&t2Bk#s5F ziyDztWRcbGSRpZtH5I58iA=@-->(uOIiW3_brPshdq1I@Cam#uu)K)bSPiN$f;|RE5rWX8^GY@o=W2H7$R5j52=_5YA!X zEH*I=9!3aSo8f>N5ocFKL?OqntKlk-I4ZaaRoa2?s&K4?8-b+)V$AeZNE!>0(tzKi z=EOl+Ol46hXfAS~{lQNb880NMu$Hc~7D%Qfc3@IRa-C<#asgp)xGvMcw3sK#mC}`5 zgaLIGv#>#iIbxe43gLfN&sZdZkicy^S}bba4ADd+AB76wc5gAY2+Ui|aAiVhg?1@M!2oNrV#cz~}~|QcHho4H25Ci3icVqDg5< zL-|CK6_>aN69M#O`n@MVV$CW$HU(B&L>MfV(kOE)i)kku98fhE6V`rI;QWi9L1C z(_AIjsxitIhXa4KfU|B9za-E{CA0NlSeX8nPlKugncEQ@IHh2)G${W;ODTQ{5uE7*h(&T0zzz5v8Ev^Z;PWXl}rB z6k#SLinL$Wg1w9H$2?ZCK za;RTD6Y1DF_&m)~k6E4-!q168W^kA5C|f;P272DqxF@ik6!cBg@j#gsi&68n!>4ID z#3GCUDlAY@aIe%_lZHmcn3>vJ9#p8!L!`w87$AQ%X$V!OFC^90@R%&>nmG*pX#%Jg z#%`T0!9=y7lJi_&%QfYh{V@BPzj0$~Vn9Q8oyzp0Jjq9BDJsTCV%fZH0eXv6B4KKy z4C)(6U}_$TDw$i5AU>v70QAEV1PubyNjS6-s^t%#sn0f4PK^wzjou&roBCud^WQ93 z@V|d#ts?y!Hj-@6>CV!!A-o51ye;joks}S|3A(19E#tm+;pmM=>>Kx-znj&+Z7Jzl zW$UU=bG7}(mG)`dDIZ&kt;m}@eq~(h?4MZOHqNB>VzEgY+)quOVEadpe$|ag00>LO zwT1QVTCLUC3Cd1bNlSs0rd1m6R=QqosXc#Of{7f@^5=eA^b321bA>w!KT&u)_TLVH zQ$jX%M9RbJ7dZlHw8u^F(EIK%w6d>#1>r&BQ-2lR1Pt1rwu@+HxU_|C3oHwNf6oI&C(?K6juW` z2rRAi^IL4?SCY(erK7^R(1dRvsi{RPNbPd$d$xyMwr1cBx(&k&w(sA*adCwzbnBUB z>=>1eT`zV*G8u#T%;utPDCLwo^_G7_64*W+T6DXx(`Fi${bF{f;{|BedhC`Y=>k4! zV74>!_X$N%hzpBAgq=A*>Ymgd?V{K}U^~bSI$+;E`{quUv$F>$&Yyjt-Qn!q!Bzje zxFp0!ZsE_IyXxfmGe5K3!RPfqyWC+fY^ zg+<{OeQ$MJ^x0DVXL#@Fm96g4P7QyZ?MBV?oyPxpWP8oow%1$V0$pzL`tyy`%=kC+DewW_ZhYt1+&Y#%WKiJgV-X-fvbk& ztf&$*8+KJOOGG zevWeilImp5nSVU8ne2aaM9%>^Uhgm@hq#aZlqgVV6E`(*RFeCQ;e9mCL_3X??slYh zDbr4@C>`s2T<5=O{9HEpnv7$k9?X6uI`OddX6;c;%QC+ZGQSthd?784&Dd`MW(c4nvX5<>~ z0#GRaR+YRpHEt&XuUr-RbEb7X(23SgkQ}nKIYmlzc`x!}cBSjSt|B*UWdI~4WD-CX zBCc&U|8ybUTqu9Sm>bRSR4f=0Sd(IlVxKIwV!cL+uPrK7YoRrNZg8K_KtRe3I#*LkDX*o{O3kWU9{AidpE=)6G^m_al(3Tn^_pi&{fd z+-C=^O4?h3y=Ys?2t+XosMOP?u*$A84PTT5nMx#>sE6n;52A2|@kaGj-4kA~igZ);Z9Ax?CIlZdfE zUnL{0}>iK89|3%eL@)*)}w3`6k9Ddq=2k3!K%O0PLfuEY)-jXRV`uZxbEPu$eZ+ zLSd(Hy6_P3$M+O|x$rB64*;PYp$+RS!Qd&3Rk1hfA99j?G48_?k;rT6x%k7c_CSYf zBi~)CNvy32uaYgz&k)_5=nY2=Sc`b?-XMR+vmA_bzQ~b|r@zQp(i#?r{RAo9hx8pg zZer7L!f%H{X2m;&&$hhH-b> zU22O+5(i_|n8vgQBUSp&wv)t;i0Mfp1}rNqGb`|6@YF5hCapX2KmLWu&7|)}`QhK%bsNZi%;hmQ=0hcO#mTI!EZnJ()TsC>%6hu z;E(ew#*f`~;?*zB(%R%By`v{w5)9`LZn`FGELAFv#$mkvKk#oAUQ60l?n@2L6dE~A zX8IWEoQBgtj9iP=PG@-Q=@6Pcjcb|%U#fuT08%;}F!%KAk8~#9snpGV!*YLxxHn=y zW#t=QYA%yL4P4Rmtc;WK7e^_LK~dvp7=Xo9^=&do0|$BfY{XEKjaV7OvNsZ%@?>aP zt$C~DgrV*DcC>o)F|yEmNNMiXT1}F=ZpmHThn-Y##rizv2Er`g&W~JY; zCBI49w!HLlY4g`i-pYUOO0?$(y)x}F!FKhRYT)S4GFDvo zGc1i}{{IJy*;rEm004NLV_;-pU;yIpH)9UQ^VKqT{J21W){Fbx1c;{y(p1ce|Ct;2Vq z><6g@0E_*GCX+FRx_|O7EH7j)k}&8oJTcBPt}}Ks{51kK95vuI`Zh8)@HiScL^$3# z@;iJxvOGjQm_K|#2tc$z{6T&}+CpkVkV5K1PD9#6LPXj{bVZg%ct)H@Hb`G| zoL6jDpjW{i+o7^@Ky>~L~?Ilf`GBfuIDKj&tj8|D2+uD^SonRqlW^U7ex7yp}x9a;eGnU7r zd2gOKHaFI5yz&3*1UA7?phSflBWz)eZ5+TsoQ6X zj|cD|9>T+T1drk|JdP*uB%Z?4cm~hnIXsUS@FHHq%XkIH@G4%z>v#ii;w`+5cknLW z!~6IEyZ8_s4Fn_zEjsj2m|zNx0R|Qhd+-Q|NSI-c1(rCDkMJ=*!Ke5PpW_RBiLZa~ zHNL^O_zvIW2mFYi@H2kFulNnW;}86azwkHy!3q4kfnz0iPE*Z&C+*xY9)^-9schNs zhIHB3IeFFuU5lO2bMGJu!KXA@nO)brBcdlUZR}zvnf;LD=+sdmCSg$uZN;?Cc9J|D z5LVKT&}!h3w)YNE)zX|+)P;M(m5;gySw^iY!b z)>jm?GI2z)Cr@dp+cgDs%V#QA8MVq&!voc;ptXyt%^1iRtu>K#Dpq;-so8(*EbHvi zs`FfGS~;adot^9VblLf2V$mh-Mm-l(%}rPIe9+@QPlk*#U8s8=rlfT~Ur?2svD3tQ zS`>X8bgepLJ;`mQbqdk*46)4gc2p?S+A?#XL^0u#vYGMm%B+#r6Eag}LOD@p!i!0; zrko_Lcsb>M;MLT|jK9`OkO_ZOX|

9BVbB_4mbUuuLaD8nPH#r6~t~y3$cO@EI94 zNIM#GQ#>nTWN5y#N@7M0%(!7Y@7X3rAjWx!y(_ zdh1x0UEwlO<7{GU{h;jFt%Mr-%u?;VosfuC_S$yLrLJ-*bD2+;vCn@X_XkjV;v$Z+ z&A10$)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(\\!)?(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.ts entity.name.function.ts" @@ -484,7 +494,7 @@ "patterns": [ { "name": "meta.var-single-variable.expr.ts", - "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.ts variable.other.constant.ts entity.name.function.ts" @@ -868,7 +878,7 @@ } }, { - "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.ts" @@ -1108,7 +1118,7 @@ "include": "#comment" }, { - "match": "(?x)(\\#?[_$[:alpha:]][_$[:alnum:]]*)(?:(\\?)|(\\!))?(?=\\s*\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(\\#?[_$[:alpha:]][_$[:alnum:]]*)(?:(\\?)|(\\!))?(?=\\s*\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "meta.definition.property.ts entity.name.function.ts" @@ -1248,6 +1258,9 @@ { "include": "#return-type" }, + { + "include": "#type-function-return-type" + }, { "include": "#decl-block" }, @@ -1431,7 +1444,7 @@ }, { "name": "meta.arrow.ts", - "begin": "(?x) (?:\n (? is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", + "begin": "(?x) (?:\n (? is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", "beginCaptures": { "1": { "name": "storage.modifier.async.ts" @@ -2466,7 +2479,7 @@ }, { "name": "string.regexp.ts", - "begin": "(?<=\\))\\s*\\/(?![\\/*])(?=(?:[^\\/\\\\\\[]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\])+\\/([gimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", + "begin": "(?<=\\))\\s*\\/(?![\\/*])(?=(?:[^\\/\\\\\\[]|\\\\.|\\[([^\\]\\\\]|\\\\.)*\\])+\\/([gimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", "beginCaptures": { "0": { "name": "punctuation.definition.string.begin.ts" @@ -2625,7 +2638,7 @@ }, { "name": "meta.object.member.ts", - "match": "(?x)(?:([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*:(\\s*\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/)*\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*:(\\s*\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/)*\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "0": { "name": "meta.object-literal.key.ts" @@ -2716,7 +2729,7 @@ "end": "(?=,|\\})", "patterns": [ { - "begin": "(?<=:)\\s*(async)?(?=\\s*(<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)\\(\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", + "begin": "(?<=:)\\s*(async)?(?=\\s*(<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)\\(\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", "beginCaptures": { "1": { "name": "storage.modifier.async.ts" @@ -2749,7 +2762,7 @@ ] }, { - "begin": "(?<=:)\\s*(async)?\\s*(\\()(?=\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", + "begin": "(?<=:)\\s*(async)?\\s*(\\()(?=\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", "beginCaptures": { "1": { "name": "storage.modifier.async.ts" @@ -2785,7 +2798,7 @@ ] }, { - "begin": "(?<=\\>)\\s*(\\()(?=\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", + "begin": "(?<=\\>)\\s*(\\()(?=\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", "beginCaptures": { "1": { "name": "meta.brace.round.ts" @@ -2973,7 +2986,7 @@ "paren-expression-possibly-arrow": { "patterns": [ { - "begin": "(?<=[(=,])\\s*(async)?(?=\\s*((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?\\(\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", + "begin": "(?<=[(=,])\\s*(async)?(?=\\s*((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?\\(\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", "beginCaptures": { "1": { "name": "storage.modifier.async.ts" @@ -3057,7 +3070,7 @@ } }, { - "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.ts" @@ -3677,7 +3690,7 @@ "include": "#object-identifiers" }, { - "match": "(?x)(?:(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", + "match": "(?x)(?:(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", "captures": { "1": { "name": "punctuation.accessor.ts" @@ -3889,7 +3902,7 @@ ] }, "possibly-arrow-return-type": { - "begin": "(?<=\\)|^)\\s*(:)(?=\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*=>)", + "begin": "(?<=\\)|^)\\s*(:)(?=\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*=>)", "beginCaptures": { "1": { "name": "meta.arrow.ts meta.return.type.arrow.ts keyword.operator.type.annotation.ts" @@ -4208,7 +4221,7 @@ }, "patterns": [ { - "match": "(?x)(?:(?)\n ))\n ))\n)) |\n(:\\s*(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))))", + "match": "(?x)(?:(?)\n ))\n ))\n)) |\n(:\\s*(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))))", "captures": { "1": { "name": "storage.modifier.ts" @@ -4726,7 +4739,7 @@ }, { "name": "string.regexp.ts", - "begin": "((?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(\\!)?(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.tsx entity.name.function.tsx" @@ -487,7 +497,7 @@ "patterns": [ { "name": "meta.var-single-variable.expr.tsx", - "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.tsx variable.other.constant.tsx entity.name.function.tsx" @@ -871,7 +881,7 @@ } }, { - "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.tsx" @@ -1111,7 +1121,7 @@ "include": "#comment" }, { - "match": "(?x)(\\#?[_$[:alpha:]][_$[:alnum:]]*)(?:(\\?)|(\\!))?(?=\\s*\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(\\#?[_$[:alpha:]][_$[:alnum:]]*)(?:(\\?)|(\\!))?(?=\\s*\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "meta.definition.property.tsx entity.name.function.tsx" @@ -1251,6 +1261,9 @@ { "include": "#return-type" }, + { + "include": "#type-function-return-type" + }, { "include": "#decl-block" }, @@ -1434,7 +1447,7 @@ }, { "name": "meta.arrow.tsx", - "begin": "(?x) (?:\n (? is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", + "begin": "(?x) (?:\n (? is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", "beginCaptures": { "1": { "name": "storage.modifier.async.tsx" @@ -2469,7 +2482,7 @@ }, { "name": "string.regexp.tsx", - "begin": "(?<=\\))\\s*\\/(?![\\/*])(?=(?:[^\\/\\\\\\[]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\])+\\/([gimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", + "begin": "(?<=\\))\\s*\\/(?![\\/*])(?=(?:[^\\/\\\\\\[]|\\\\.|\\[([^\\]\\\\]|\\\\.)*\\])+\\/([gimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", "beginCaptures": { "0": { "name": "punctuation.definition.string.begin.tsx" @@ -2628,7 +2641,7 @@ }, { "name": "meta.object.member.tsx", - "match": "(?x)(?:([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*:(\\s*\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/)*\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*:(\\s*\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/)*\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "0": { "name": "meta.object-literal.key.tsx" @@ -2719,7 +2732,7 @@ "end": "(?=,|\\})", "patterns": [ { - "begin": "(?<=:)\\s*(async)?(?=\\s*(<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)\\(\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", + "begin": "(?<=:)\\s*(async)?(?=\\s*(<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)\\(\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", "beginCaptures": { "1": { "name": "storage.modifier.async.tsx" @@ -2752,7 +2765,7 @@ ] }, { - "begin": "(?<=:)\\s*(async)?\\s*(\\()(?=\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", + "begin": "(?<=:)\\s*(async)?\\s*(\\()(?=\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", "beginCaptures": { "1": { "name": "storage.modifier.async.tsx" @@ -2788,7 +2801,7 @@ ] }, { - "begin": "(?<=\\>)\\s*(\\()(?=\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", + "begin": "(?<=\\>)\\s*(\\()(?=\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", "beginCaptures": { "1": { "name": "meta.brace.round.tsx" @@ -2976,7 +2989,7 @@ "paren-expression-possibly-arrow": { "patterns": [ { - "begin": "(?<=[(=,])\\s*(async)?(?=\\s*((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?\\(\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", + "begin": "(?<=[(=,])\\s*(async)?(?=\\s*((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?\\(\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", "beginCaptures": { "1": { "name": "storage.modifier.async.tsx" @@ -3060,7 +3073,7 @@ } }, { - "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.tsx" @@ -3628,7 +3641,7 @@ "include": "#object-identifiers" }, { - "match": "(?x)(?:(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", + "match": "(?x)(?:(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", "captures": { "1": { "name": "punctuation.accessor.tsx" @@ -3840,7 +3853,7 @@ ] }, "possibly-arrow-return-type": { - "begin": "(?<=\\)|^)\\s*(:)(?=\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*=>)", + "begin": "(?<=\\)|^)\\s*(:)(?=\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*=>)", "beginCaptures": { "1": { "name": "meta.arrow.tsx meta.return.type.arrow.tsx keyword.operator.type.annotation.tsx" @@ -4159,7 +4172,7 @@ }, "patterns": [ { - "match": "(?x)(?:(?)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))))", + "match": "(?x)(?:(?)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))))", "captures": { "1": { "name": "storage.modifier.tsx" @@ -4677,7 +4690,7 @@ }, { "name": "string.regexp.tsx", - "begin": "((? { public static readonly cancelledCommand: vscode.Command = { // Cancellation is not an error. Just show nothing until we can properly re-compute the code lens @@ -47,7 +47,7 @@ export abstract class TypeScriptBaseCodeLensProvider implements vscode.CodeLensP return this.onDidChangeCodeLensesEmitter.event; } - async provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): Promise { + async provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): Promise { const filepath = this.client.toOpenedFilePath(document); if (!filepath) { return []; diff --git a/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts b/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts index f06411ec5..4e5293e7f 100644 --- a/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts +++ b/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts @@ -19,11 +19,9 @@ const localize = nls.loadMessageBundle(); export default class TypeScriptImplementationsCodeLensProvider extends TypeScriptBaseCodeLensProvider { public async resolveCodeLens( - inputCodeLens: vscode.CodeLens, + codeLens: ReferencesCodeLens, token: vscode.CancellationToken, ): Promise { - const codeLens = inputCodeLens as ReferencesCodeLens; - const args = typeConverters.Position.toFileLocationRequestArgs(codeLens.file, codeLens.range.start); const response = await this.client.execute('implementation', args, token, { lowPriority: true, cancelOnResourceChange: codeLens.document }); if (response.type !== 'response' || !response.body) { diff --git a/extensions/typescript-language-features/src/languageFeatures/codeLens/referencesCodeLens.ts b/extensions/typescript-language-features/src/languageFeatures/codeLens/referencesCodeLens.ts index 8fe118de5..57a9e599c 100644 --- a/extensions/typescript-language-features/src/languageFeatures/codeLens/referencesCodeLens.ts +++ b/extensions/typescript-language-features/src/languageFeatures/codeLens/referencesCodeLens.ts @@ -26,8 +26,7 @@ export class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLens super(client, _cachedResponse); } - public async resolveCodeLens(inputCodeLens: vscode.CodeLens, token: vscode.CancellationToken): Promise { - const codeLens = inputCodeLens as ReferencesCodeLens; + public async resolveCodeLens(codeLens: ReferencesCodeLens, token: vscode.CancellationToken): Promise { const args = typeConverters.Position.toFileLocationRequestArgs(codeLens.file, codeLens.range.start); const response = await this.client.execute('references', args, token, { lowPriority: true, @@ -42,12 +41,9 @@ export class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLens } const locations = response.body.refs + .filter(reference => !reference.isDefinition) .map(reference => - typeConverters.Location.fromTextSpan(this.client.toResource(reference.file), reference)) - .filter(location => - // Exclude original definition from references - !(location.uri.toString() === codeLens.document.toString() && - location.range.start.isEqual(codeLens.range.start))); + typeConverters.Location.fromTextSpan(this.client.toResource(reference.file), reference)); codeLens.command = { title: this.getCodeLensLabel(locations), diff --git a/extensions/typescript-language-features/src/languageFeatures/completions.ts b/extensions/typescript-language-features/src/languageFeatures/completions.ts index 73f54f20c..e316dd6ff 100644 --- a/extensions/typescript-language-features/src/languageFeatures/completions.ts +++ b/extensions/typescript-language-features/src/languageFeatures/completions.ts @@ -62,6 +62,13 @@ class MyCompletionItem extends vscode.CompletionItem { // De-prioritze auto-imports // https://github.com/microsoft/vscode/issues/40311 this.sortText = '\uffff' + tsEntry.sortText; + + // Render "fancy" when source is a workspace path + const qualifierCandidate = vscode.workspace.asRelativePath(tsEntry.source); + if (qualifierCandidate !== tsEntry.source) { + this.label2 = { name: tsEntry.name, qualifier: qualifierCandidate }; + } + } else { this.sortText = tsEntry.sortText; } @@ -561,7 +568,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< type: response?.type ?? 'unknown', count: response?.type === 'response' && response.body ? response.body.entries.length : 0, updateGraphDurationMs: response?.type === 'response' ? response.performanceData?.updateGraphDurationMs : undefined, - createAutoImportProviderProgramDurationMs: response?.type === 'response' ? (response.performanceData as Proto.PerformanceData & { createAutoImportProviderProgramDurationMs?: number })?.createAutoImportProviderProgramDurationMs : undefined, + createAutoImportProviderProgramDurationMs: response?.type === 'response' ? response.performanceData?.createAutoImportProviderProgramDurationMs : undefined, includesPackageJsonImport: includesPackageJsonImport ? 'true' : undefined, }); } diff --git a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts index 03ebff6dd..05fa16cf9 100644 --- a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts +++ b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts @@ -13,18 +13,6 @@ import { isTypeScriptDocument } from '../utils/languageModeIds'; import { equals } from '../utils/objects'; import { ResourceMap } from '../utils/resourceMap'; -namespace Experimental { - // https://github.com/microsoft/TypeScript/pull/37871/ - export interface UserPreferences extends Proto.UserPreferences { - readonly provideRefactorNotApplicableReason?: boolean; - } - - // https://github.com/microsoft/TypeScript/issues/41208 - export interface FormatCodeSettings extends Proto.FormatCodeSettings { - readonly insertSpaceAfterOpeningAndBeforeClosingEmptyBraces?: boolean; - } -} - interface FileConfiguration { readonly formatOptions: Proto.FormatCodeSettings; readonly preferences: Proto.UserPreferences; @@ -141,7 +129,7 @@ export default class FileConfigurationManager extends Disposable { private getFormatOptions( document: vscode.TextDocument, options: vscode.FormattingOptions - ): Experimental.FormatCodeSettings { + ): Proto.FormatCodeSettings { const config = vscode.workspace.getConfiguration( isTypeScriptDocument(document) ? 'typescript.format' : 'javascript.format', document.uri); @@ -185,8 +173,9 @@ export default class FileConfigurationManager extends Disposable { isTypeScriptDocument(document) ? 'typescript.preferences' : 'javascript.preferences', document.uri); - const preferences: Experimental.UserPreferences = { + const preferences: Proto.UserPreferences = { quotePreference: this.getQuoteStylePreference(preferencesConfig), + // @ts-expect-error until TypeScript 4.2 API importModuleSpecifierPreference: getImportModuleSpecifierPreference(preferencesConfig), importModuleSpecifierEnding: getImportModuleSpecifierEndingPreference(preferencesConfig), allowTextChangesInNewFiles: document.uri.scheme === fileSchemes.file, @@ -210,6 +199,7 @@ export default class FileConfigurationManager extends Disposable { function getImportModuleSpecifierPreference(config: vscode.WorkspaceConfiguration) { switch (config.get('importModuleSpecifier')) { + case 'project-relative': return 'project-relative'; case 'relative': return 'relative'; case 'non-relative': return 'non-relative'; default: return undefined; diff --git a/extensions/typescript-language-features/src/languageFeatures/folding.ts b/extensions/typescript-language-features/src/languageFeatures/folding.ts index 5b18decb4..5dc6dbd1c 100644 --- a/extensions/typescript-language-features/src/languageFeatures/folding.ts +++ b/extensions/typescript-language-features/src/languageFeatures/folding.ts @@ -54,14 +54,24 @@ class TypeScriptFoldingProvider implements vscode.FoldingRangeProvider { } const start = range.start.line; - // workaround for #47240 - const end = (range.end.character > 0 && ['}', ']'].includes(document.getText(new vscode.Range(range.end.translate(0, -1), range.end)))) - ? Math.max(range.end.line - 1, range.start.line) - : range.end.line; - + const end = this.adjustFoldingEnd(range, document); return new vscode.FoldingRange(start, end, kind); } + private static readonly foldEndPairCharacters = ['}', ']', ')', '`']; + + private adjustFoldingEnd(range: vscode.Range, document: vscode.TextDocument) { + // workaround for #47240 + if (range.end.character > 0) { + const foldEndCharacter = document.getText(new vscode.Range(range.end.translate(0, -1), range.end)); + if (TypeScriptFoldingProvider.foldEndPairCharacters.includes(foldEndCharacter)) { + return Math.max(range.end.line - 1, range.start.line); + } + } + + return range.end.line; + } + private static getFoldingRangeKind(span: Proto.OutliningSpan): vscode.FoldingRangeKind | undefined { switch (span.kind) { case 'comment': return vscode.FoldingRangeKind.Comment; diff --git a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index 0965a5747..2af23b270 100644 --- a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -51,6 +51,9 @@ class ApplyCodeActionCommand implements Command { } } +type ApplyFixAllCodeAction_args = { + readonly action: VsCodeFixAllCodeAction; +}; class ApplyFixAllCodeAction implements Command { public static readonly ID = '_typescript.applyFixAllCodeAction'; @@ -61,14 +64,7 @@ class ApplyFixAllCodeAction implements Command { private readonly telemetryReporter: TelemetryReporter, ) { } - public async execute( - file: string, - tsAction: Proto.CodeFixAction, - ): Promise { - if (!tsAction.fixId) { - return; - } - + public async execute(args: ApplyFixAllCodeAction_args): Promise { /* __GDPR__ "quickFixAll.execute" : { "fixName" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }, @@ -78,25 +74,12 @@ class ApplyFixAllCodeAction implements Command { } */ this.telemetryReporter.logTelemetry('quickFixAll.execute', { - fixName: tsAction.fixName + fixName: args.action.tsAction.fixName }); - const args: Proto.GetCombinedCodeFixRequestArgs = { - scope: { - type: 'file', - args: { file } - }, - fixId: tsAction.fixId, - }; - - const response = await this.client.execute('getCombinedCodeFix', args, nulToken); - if (response.type !== 'response' || !response.body) { - return undefined; + if (args.action.combinedResponse) { + await applyCodeActionCommands(this.client, args.action.combinedResponse.body.commands, nulToken); } - - const edit = typeConverters.WorkspaceEdit.fromFileCodeEdits(this.client, response.body.changes); - await vscode.workspace.applyEdit(edit); - await applyCodeActionCommands(this.client, response.body.commands, nulToken); } } @@ -134,13 +117,25 @@ class VsCodeCodeAction extends vscode.CodeAction { constructor( public readonly tsAction: Proto.CodeFixAction, title: string, - kind: vscode.CodeActionKind, - public readonly isFixAll: boolean, + kind: vscode.CodeActionKind ) { super(title, kind); } } +class VsCodeFixAllCodeAction extends VsCodeCodeAction { + constructor( + tsAction: Proto.CodeFixAction, + public readonly file: string, + title: string, + kind: vscode.CodeActionKind + ) { + super(tsAction, title, kind); + } + + public combinedResponse?: Proto.GetCombinedCodeFixResponse; +} + class CodeActionSet { private readonly _actions = new Set(); private readonly _fixAllActions = new Map<{}, VsCodeCodeAction>(); @@ -202,7 +197,7 @@ class SupportedCodeActionProvider { } } -class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { +class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { public static readonly metadata: vscode.CodeActionProviderMetadata = { providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] @@ -257,6 +252,28 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { return allActions; } + public async resolveCodeAction(codeAction: VsCodeCodeAction, token: vscode.CancellationToken): Promise { + if (!(codeAction instanceof VsCodeFixAllCodeAction) || !codeAction.tsAction.fixId) { + return codeAction; + } + + const arg: Proto.GetCombinedCodeFixRequestArgs = { + scope: { + type: 'file', + args: { file: codeAction.file } + }, + fixId: codeAction.tsAction.fixId, + }; + + const response = await this.client.execute('getCombinedCodeFix', arg, token); + if (response.type === 'response') { + codeAction.combinedResponse = response; + codeAction.edit = typeConverters.WorkspaceEdit.fromFileCodeEdits(this.client, response.body.changes); + } + + return codeAction; + } + private async getFixesForDiagnostic( document: vscode.TextDocument, file: string, @@ -295,7 +312,7 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { diagnostic: vscode.Diagnostic, tsAction: Proto.CodeFixAction ): VsCodeCodeAction { - const codeAction = new VsCodeCodeAction(tsAction, tsAction.description, vscode.CodeActionKind.QuickFix, false); + const codeAction = new VsCodeCodeAction(tsAction, tsAction.description, vscode.CodeActionKind.QuickFix); codeAction.edit = getEditForCodeAction(this.client, tsAction); codeAction.diagnostics = [diagnostic]; codeAction.command = { @@ -328,14 +345,16 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { return results; } - const action = new VsCodeCodeAction( + const action = new VsCodeFixAllCodeAction( tsAction, + file, tsAction.fixAllDescription || localize('fixAllInFileLabel', '{0} (Fix all in file)', tsAction.description), - vscode.CodeActionKind.QuickFix, true); + vscode.CodeActionKind.QuickFix); + action.diagnostics = [diagnostic]; action.command = { command: ApplyFixAllCodeAction.ID, - arguments: [file, tsAction], + arguments: [{ action }], title: '' }; results.addFixAllAction(tsAction.fixId, action); @@ -370,7 +389,7 @@ function isPreferredFix( action: VsCodeCodeAction, allActions: readonly VsCodeCodeAction[] ): boolean { - if (action.isFixAll) { + if (action instanceof VsCodeFixAllCodeAction) { return false; } @@ -384,7 +403,7 @@ function isPreferredFix( return true; } - if (otherAction.isFixAll) { + if (otherAction instanceof VsCodeFixAllCodeAction) { return true; } diff --git a/extensions/typescript-language-features/src/languageFeatures/refactor.ts b/extensions/typescript-language-features/src/languageFeatures/refactor.ts index ab0186c8f..cc8c0a5d3 100644 --- a/extensions/typescript-language-features/src/languageFeatures/refactor.ts +++ b/extensions/typescript-language-features/src/languageFeatures/refactor.ts @@ -20,11 +20,6 @@ import FormattingOptionsManager from './fileConfigurationManager'; const localize = nls.loadMessageBundle(); -namespace Experimental { - export interface RefactorActionInfo extends Proto.RefactorActionInfo { - readonly notApplicableReason?: string; - } -} interface DidApplyRefactoringCommand_Args { readonly codeAction: InlinedCodeAction @@ -58,10 +53,13 @@ class DidApplyRefactoringCommand implements Command { const renameLocation = args.codeAction.renameLocation; if (renameLocation) { - await vscode.commands.executeCommand('editor.action.rename', [ - args.codeAction.document.uri, - typeConverters.Position.fromLocation(renameLocation) - ]); + // Disable renames in interactive playground https://github.com/microsoft/vscode/issues/75137 + if (args.codeAction.document.uri.scheme !== fileSchemes.walkThroughSnippet) { + await vscode.commands.executeCommand('editor.action.rename', [ + args.codeAction.document.uri, + typeConverters.Position.fromLocation(renameLocation) + ]); + } } } } @@ -354,7 +352,7 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider string | undefined, onCaseInsenitiveFileSystem: boolean ) { - this._pending = new ResourceMap(undefined, { + this._pending = new ResourceMap(pathNormalizer, { onCaseInsenitiveFileSystem }); } @@ -280,7 +281,7 @@ class PendingDiagnostics extends ResourceMap { .sort((a, b) => a.value - b.value) .map(entry => entry.resource); - const map = new ResourceMap(undefined, this.config); + const map = new ResourceMap(this._normalizePath, this.config); for (const resource of orderedResources) { map.set(resource, undefined); } @@ -367,7 +368,7 @@ export default class BufferSyncSupport extends Disposable { const pathNormalizer = (path: vscode.Uri) => this.client.normalizedPath(path); this.syncedBuffers = new SyncedBufferMap(pathNormalizer, { onCaseInsenitiveFileSystem }); this.pendingDiagnostics = new PendingDiagnostics(pathNormalizer, { onCaseInsenitiveFileSystem }); - this.synchronizer = new BufferSynchronizer(client, onCaseInsenitiveFileSystem); + this.synchronizer = new BufferSynchronizer(client, pathNormalizer, onCaseInsenitiveFileSystem); this.updateConfiguration(); vscode.workspace.onDidChangeConfiguration(this.updateConfiguration, this, this._disposables); @@ -482,7 +483,7 @@ export default class BufferSyncSupport extends Disposable { } } - public interuptGetErr(f: () => R): R { + public interruptGetErr(f: () => R): R { if (!this.pendingGetErr || this.client.configuration.enableProjectDiagnostics // `geterr` happens on seperate server so no need to cancel it. ) { diff --git a/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts b/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts index bc06d18ee..33845073e 100644 --- a/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts +++ b/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts @@ -3,10 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; +import * as nls from 'vscode-nls'; import type * as Proto from '../protocol'; import { TypeScriptServiceConfiguration } from '../utils/configuration'; +import { memoize } from '../utils/memoize'; import { TsServerProcess, TsServerProcessKind } from './server'; + +const localize = nls.loadMessageBundle(); + declare const Worker: any; declare type Worker = any; @@ -37,6 +43,11 @@ export class WorkerServerProcess implements TsServerProcess { args: readonly string[], ) { worker.addEventListener('message', (msg: any) => { + if (msg.data.type === 'log') { + this.output.appendLine(msg.data.body); + return; + } + for (const handler of this._onDataHandlers) { handler(msg.data); } @@ -44,6 +55,11 @@ export class WorkerServerProcess implements TsServerProcess { worker.postMessage(args); } + @memoize + private get output(): vscode.OutputChannel { + return vscode.window.createOutputChannel(localize('channelName', 'TypeScript Server Log')); + } + write(serverRequest: Proto.Request): void { this.worker.postMessage(serverRequest); } diff --git a/extensions/typescript-language-features/src/tsServer/spawner.ts b/extensions/typescript-language-features/src/tsServer/spawner.ts index a3ad0abb1..58fd431ed 100644 --- a/extensions/typescript-language-features/src/tsServer/spawner.ts +++ b/extensions/typescript-language-features/src/tsServer/spawner.ts @@ -128,7 +128,7 @@ export class TypeScriptServerSpawner { const apiVersion = version.apiVersion || API.defaultVersion; const canceller = cancellerFactory.create(kind, this._tracer); - const { args, tsServerLogFile } = this.getTsServerArgs(kind, configuration, version, apiVersion, pluginManager, canceller.cancellationPipeName); + const { args, tsServerLogFile, tsServerTraceDirectory } = this.getTsServerArgs(kind, configuration, version, apiVersion, pluginManager, canceller.cancellationPipeName); if (TypeScriptServerSpawner.isLoggingEnabled(configuration)) { if (tsServerLogFile) { @@ -138,6 +138,14 @@ export class TypeScriptServerSpawner { } } + if (configuration.enableTsServerTracing) { + if (tsServerTraceDirectory) { + this._logger.info(`<${kind}> Trace directory: ${tsServerTraceDirectory}`); + } else { + this._logger.error(`<${kind}> Could not create trace directory`); + } + } + this._logger.info(`<${kind}> Forking...`); const process = this._factory.fork(version.tsServerPath, args, kind, configuration, this._versionManager); this._logger.info(`<${kind}> Starting...`); @@ -173,9 +181,10 @@ export class TypeScriptServerSpawner { apiVersion: API, pluginManager: PluginManager, cancellationPipeName: string | undefined, - ): { args: string[], tsServerLogFile: string | undefined } { + ): { args: string[], tsServerLogFile: string | undefined, tsServerTraceDirectory: string | undefined } { const args: string[] = []; let tsServerLogFile: string | undefined; + let tsServerTraceDirectory: string | undefined; if (kind === TsServerProcessKind.Syntax) { if (apiVersion.gte(API.v401)) { @@ -216,6 +225,13 @@ export class TypeScriptServerSpawner { } } + if (configuration.enableTsServerTracing && !isWeb()) { + tsServerTraceDirectory = this._logDirectoryProvider.getNewLogDirectory(); + if (tsServerTraceDirectory) { + args.push('--traceDirectory', tsServerTraceDirectory); + } + } + if (!isWeb()) { const pluginPaths = this._pluginPathsProvider.getPluginPaths(); @@ -251,7 +267,7 @@ export class TypeScriptServerSpawner { args.push('--validateDefaultNpmLocation'); } - return { args, tsServerLogFile }; + return { args, tsServerLogFile, tsServerTraceDirectory }; } private static isLoggingEnabled(configuration: TypeScriptServiceConfiguration) { diff --git a/extensions/typescript-language-features/src/tsServer/versionStatus.ts b/extensions/typescript-language-features/src/tsServer/versionStatus.ts index 20b9debc2..1d4e12894 100644 --- a/extensions/typescript-language-features/src/tsServer/versionStatus.ts +++ b/extensions/typescript-language-features/src/tsServer/versionStatus.ts @@ -58,7 +58,6 @@ class ProjectStatusCommand implements Command { public async execute(): Promise { const info = this._delegate(); - const result = await vscode.window.showQuickPick(coalesce([ this.getProjectItem(info), this.getVersionItem(), @@ -73,7 +72,7 @@ class ProjectStatusCommand implements Command { private getVersionItem(): QuickPickItem { return { label: localize('projectQuickPick.version.label', "Select TypeScript Version..."), - description: this._client.apiVersion.displayName, + description: localize('projectQuickPick.version.description', "[current = {0}]", this._client.apiVersion.displayName), run: () => { this._client.showVersionPicker(); } @@ -169,7 +168,7 @@ export default class VersionStatus extends Disposable { const doc = vscode.window.activeTextEditor.document; if (isTypeScriptDocument(doc)) { - const file = this._client.normalizedPath(doc.uri); + const file = this._client.toOpenedFilePath(doc, { suppressAlertOnFailure: true }); if (file) { this._statusBarEntry.show(); if (!this._ready) { diff --git a/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts b/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts index d225ef602..76471a204 100644 --- a/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts +++ b/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts @@ -30,12 +30,6 @@ import * as typeConverters from './utils/typeConverters'; import TypingsStatus, { AtaProgressReporter } from './utils/typingsStatus'; import * as ProjectStatus from './utils/largeProjectStatus'; -namespace Experimental { - export interface Diagnostic extends Proto.Diagnostic { - readonly reportsDeprecated?: {} - } -} - // Style check diagnostics that can be reported as warnings const styleCheckDiagnostics = new Set([ ...errorCodes.variableDeclaredButNeverUsed, @@ -256,7 +250,7 @@ export default class TypeScriptServiceClientHost extends Disposable { return diagnostics.map(tsDiag => this.tsDiagnosticToVsDiagnostic(tsDiag, source)); } - private tsDiagnosticToVsDiagnostic(diagnostic: Experimental.Diagnostic, source: string): vscode.Diagnostic & { reportUnnecessary: any, reportDeprecated: any } { + private tsDiagnosticToVsDiagnostic(diagnostic: Proto.Diagnostic, source: string): vscode.Diagnostic & { reportUnnecessary: any, reportDeprecated: any } { const { start, end, text } = diagnostic; const range = new vscode.Range(typeConverters.Position.fromLocation(start), typeConverters.Position.fromLocation(end)); const converted = new vscode.Diagnostic(range, text, this.getDiagnosticSeverity(diagnostic)); diff --git a/extensions/typescript-language-features/src/typescriptService.ts b/extensions/typescript-language-features/src/typescriptService.ts index 58a78ad19..588925ec6 100644 --- a/extensions/typescript-language-features/src/typescriptService.ts +++ b/extensions/typescript-language-features/src/typescriptService.ts @@ -147,7 +147,9 @@ export interface ITypeScriptServiceClient { * * @return The normalized path or `undefined` if the document is not open on the server. */ - toOpenedFilePath(document: vscode.TextDocument): string | undefined; + toOpenedFilePath(document: vscode.TextDocument, options?: { + suppressAlertOnFailure?: boolean + }): string | undefined; /** * Checks if `resource` has a given capability. diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 2d9dad639..e4c8c840e 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -189,9 +189,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.tracer.updateConfiguration(); if (this.serverState.type === ServerState.Type.Running) { - if (this._configuration.checkJs !== oldConfiguration.checkJs - || this._configuration.experimentalDecorators !== oldConfiguration.experimentalDecorators - ) { + if (!this._configuration.implictProjectConfiguration.isEqualTo(oldConfiguration.implictProjectConfiguration)) { this.setCompilerOptionsForInferredProjects(this._configuration); } @@ -663,14 +661,14 @@ export default class TypeScriptServiceClient extends Disposable implements IType return this.normalizedPath(resource); } - public toOpenedFilePath(document: vscode.TextDocument): string | undefined { + public toOpenedFilePath(document: vscode.TextDocument, options: { suppressAlertOnFailure?: boolean } = {}): string | undefined { if (!this.bufferSyncSupport.ensureHasBuffer(document.uri)) { - if (!fileSchemes.disabledSchemes.has(document.uri.scheme)) { + if (!options.suppressAlertOnFailure && !fileSchemes.disabledSchemes.has(document.uri.scheme)) { console.error(`Unexpected resource ${document.uri}`); } return undefined; } - return this.toPath(document.uri) || undefined; + return this.toPath(document.uri); } public hasCapabilityForResource(resource: vscode.Uri, capability: ClientCapability): boolean { @@ -778,7 +776,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType } public interruptGetErr(f: () => R): R { - return this.bufferSyncSupport.interuptGetErr(f); + return this.bufferSyncSupport.interruptGetErr(f); } private fatalError(command: string, error: unknown): void { diff --git a/extensions/typescript-language-features/src/utils/configuration.ts b/extensions/typescript-language-features/src/utils/configuration.ts index f549ff6f9..c43a12107 100644 --- a/extensions/typescript-language-features/src/utils/configuration.ts +++ b/extensions/typescript-language-features/src/utils/configuration.ts @@ -7,7 +7,6 @@ import * as os from 'os'; import * as path from 'path'; import * as vscode from 'vscode'; import * as objects from '../utils/objects'; -import * as arrays from './arrays'; export enum TsServerLogLevel { Off, @@ -51,6 +50,43 @@ export const enum SeparateSyntaxServerConfiguration { Enabled, } +export class ImplicitProjectConfiguration { + + public readonly checkJs: boolean; + public readonly experimentalDecorators: boolean; + public readonly strictNullChecks: boolean; + public readonly strictFunctionTypes: boolean; + + constructor(configuration: vscode.WorkspaceConfiguration) { + this.checkJs = ImplicitProjectConfiguration.readCheckJs(configuration); + this.experimentalDecorators = ImplicitProjectConfiguration.readExperimentalDecorators(configuration); + this.strictNullChecks = ImplicitProjectConfiguration.readImplicitStrictNullChecks(configuration); + this.strictFunctionTypes = ImplicitProjectConfiguration.readImplicitStrictFunctionTypes(configuration); + } + + public isEqualTo(other: ImplicitProjectConfiguration): boolean { + return objects.equals(this, other); + } + + private static readCheckJs(configuration: vscode.WorkspaceConfiguration): boolean { + return configuration.get('js/ts.implicitProjectConfig.checkJs') + ?? configuration.get('javascript.implicitProjectConfig.checkJs', false); + } + + private static readExperimentalDecorators(configuration: vscode.WorkspaceConfiguration): boolean { + return configuration.get('js/ts.implicitProjectConfig.experimentalDecorators') + ?? configuration.get('javascript.implicitProjectConfig.experimentalDecorators', false); + } + + private static readImplicitStrictNullChecks(configuration: vscode.WorkspaceConfiguration): boolean { + return configuration.get('js/ts.implicitProjectConfig.strictNullChecks', false); + } + + private static readImplicitStrictFunctionTypes(configuration: vscode.WorkspaceConfiguration): boolean { + return configuration.get('js/ts.implicitProjectConfig.strictFunctionTypes', true); + } +} + export class TypeScriptServiceConfiguration { public readonly locale: string | null; public readonly globalTsdk: string | null; @@ -58,8 +94,7 @@ export class TypeScriptServiceConfiguration { public readonly npmLocation: string | null; public readonly tsServerLogLevel: TsServerLogLevel = TsServerLogLevel.Off; public readonly tsServerPluginPaths: readonly string[]; - public readonly checkJs: boolean; - public readonly experimentalDecorators: boolean; + public readonly implictProjectConfiguration: ImplicitProjectConfiguration; public readonly disableAutomaticTypeAcquisition: boolean; public readonly separateSyntaxServer: SeparateSyntaxServerConfiguration; public readonly enableProjectDiagnostics: boolean; @@ -67,6 +102,7 @@ export class TypeScriptServiceConfiguration { public readonly enablePromptUseWorkspaceTsdk: boolean; public readonly watchOptions: protocol.WatchOptions | undefined; public readonly includePackageJsonAutoImports: 'auto' | 'on' | 'off' | undefined; + public readonly enableTsServerTracing: boolean; public static loadFromWorkspace(): TypeScriptServiceConfiguration { return new TypeScriptServiceConfiguration(); @@ -81,8 +117,7 @@ export class TypeScriptServiceConfiguration { this.npmLocation = TypeScriptServiceConfiguration.readNpmLocation(configuration); this.tsServerLogLevel = TypeScriptServiceConfiguration.readTsServerLogLevel(configuration); this.tsServerPluginPaths = TypeScriptServiceConfiguration.readTsServerPluginPaths(configuration); - this.checkJs = TypeScriptServiceConfiguration.readCheckJs(configuration); - this.experimentalDecorators = TypeScriptServiceConfiguration.readExperimentalDecorators(configuration); + this.implictProjectConfiguration = new ImplicitProjectConfiguration(configuration); this.disableAutomaticTypeAcquisition = TypeScriptServiceConfiguration.readDisableAutomaticTypeAcquisition(configuration); this.separateSyntaxServer = TypeScriptServiceConfiguration.readUseSeparateSyntaxServer(configuration); this.enableProjectDiagnostics = TypeScriptServiceConfiguration.readEnableProjectDiagnostics(configuration); @@ -90,24 +125,11 @@ export class TypeScriptServiceConfiguration { this.enablePromptUseWorkspaceTsdk = TypeScriptServiceConfiguration.readEnablePromptUseWorkspaceTsdk(configuration); this.watchOptions = TypeScriptServiceConfiguration.readWatchOptions(configuration); this.includePackageJsonAutoImports = TypeScriptServiceConfiguration.readIncludePackageJsonAutoImports(configuration); + this.enableTsServerTracing = TypeScriptServiceConfiguration.readEnableTsServerTracing(configuration); } public isEqualTo(other: TypeScriptServiceConfiguration): boolean { - return this.locale === other.locale - && this.globalTsdk === other.globalTsdk - && this.localTsdk === other.localTsdk - && this.npmLocation === other.npmLocation - && this.tsServerLogLevel === other.tsServerLogLevel - && this.checkJs === other.checkJs - && this.experimentalDecorators === other.experimentalDecorators - && this.disableAutomaticTypeAcquisition === other.disableAutomaticTypeAcquisition - && arrays.equals(this.tsServerPluginPaths, other.tsServerPluginPaths) - && this.separateSyntaxServer === other.separateSyntaxServer - && this.enableProjectDiagnostics === other.enableProjectDiagnostics - && this.maxTsServerMemory === other.maxTsServerMemory - && objects.equals(this.watchOptions, other.watchOptions) - && this.enablePromptUseWorkspaceTsdk === other.enablePromptUseWorkspaceTsdk - && this.includePackageJsonAutoImports === other.includePackageJsonAutoImports; + return objects.equals(this, other); } private static fixPathPrefixes(inspectValue: string): string { @@ -145,14 +167,6 @@ export class TypeScriptServiceConfiguration { return configuration.get('typescript.tsserver.pluginPaths', []); } - private static readCheckJs(configuration: vscode.WorkspaceConfiguration): boolean { - return configuration.get('javascript.implicitProjectConfig.checkJs', false); - } - - private static readExperimentalDecorators(configuration: vscode.WorkspaceConfiguration): boolean { - return configuration.get('javascript.implicitProjectConfig.experimentalDecorators', false); - } - private static readNpmLocation(configuration: vscode.WorkspaceConfiguration): string | null { return configuration.get('typescript.npm', null); } @@ -198,4 +212,8 @@ export class TypeScriptServiceConfiguration { private static readEnablePromptUseWorkspaceTsdk(configuration: vscode.WorkspaceConfiguration): boolean { return configuration.get('typescript.enablePromptUseWorkspaceTsdk', false); } + + private static readEnableTsServerTracing(configuration: vscode.WorkspaceConfiguration): boolean { + return configuration.get('typescript.tsserver.enableTracing', false); + } } diff --git a/extensions/typescript-language-features/src/utils/resourceMap.ts b/extensions/typescript-language-features/src/utils/resourceMap.ts index ea4889ded..1b7285aaa 100644 --- a/extensions/typescript-language-features/src/utils/resourceMap.ts +++ b/extensions/typescript-language-features/src/utils/resourceMap.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import * as fileSchemes from '../utils/fileSchemes'; /** * Maps of file resources @@ -12,10 +13,18 @@ import * as vscode from 'vscode'; * file systems. */ export class ResourceMap { - private readonly _map = new Map(); + + private static readonly defaultPathNormalizer = (resource: vscode.Uri): string => { + if (resource.scheme === fileSchemes.file) { + return resource.fsPath; + } + return resource.toString(true); + }; + + private readonly _map = new Map(); constructor( - private readonly _normalizePath: (resource: vscode.Uri) => string | undefined = (resource) => resource.fsPath, + protected readonly _normalizePath: (resource: vscode.Uri) => string | undefined = ResourceMap.defaultPathNormalizer, protected readonly config: { readonly onCaseInsenitiveFileSystem: boolean, }, diff --git a/extensions/typescript-language-features/src/utils/tsconfig.ts b/extensions/typescript-language-features/src/utils/tsconfig.ts index f6802ca3c..9d1c309e5 100644 --- a/extensions/typescript-language-features/src/utils/tsconfig.ts +++ b/extensions/typescript-language-features/src/utils/tsconfig.ts @@ -32,17 +32,25 @@ export function inferredProjectCompilerOptions( jsx: 'preserve' as Proto.JsxEmit, }; - if (serviceConfig.checkJs) { + if (serviceConfig.implictProjectConfiguration.checkJs) { projectConfig.checkJs = true; if (projectType === ProjectType.TypeScript) { projectConfig.allowJs = true; } } - if (serviceConfig.experimentalDecorators) { + if (serviceConfig.implictProjectConfiguration.experimentalDecorators) { projectConfig.experimentalDecorators = true; } + if (serviceConfig.implictProjectConfiguration.strictNullChecks) { + projectConfig.strictNullChecks = true; + } + + if (serviceConfig.implictProjectConfiguration.strictFunctionTypes) { + projectConfig.strictFunctionTypes = true; + } + if (projectType === ProjectType.TypeScript) { projectConfig.sourceMap = true; } diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index ad1d70446..82681ca03 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -1014,4 +1014,38 @@ suite('vscode API - workspace', () => { } }); + + test('issue #110141 - TextEdit.setEndOfLine applies an edit and invalidates redo stack even when no change is made', async () => { + const file = await createRandomFile('hello\nworld'); + + const document = await vscode.workspace.openTextDocument(file); + await vscode.window.showTextDocument(document); + + // apply edit + { + const we = new vscode.WorkspaceEdit(); + we.insert(file, new vscode.Position(0, 5), '2'); + await vscode.workspace.applyEdit(we); + } + + // check the document + { + assert.equal(document.getText(), 'hello2\nworld'); + assert.equal(document.isDirty, true); + } + + // apply no-op edit + { + const we = new vscode.WorkspaceEdit(); + we.set(file, [vscode.TextEdit.setEndOfLine(vscode.EndOfLine.LF)]); + await vscode.workspace.applyEdit(we); + } + + // undo + { + await vscode.commands.executeCommand('undo'); + assert.equal(document.getText(), 'hello\nworld'); + assert.equal(document.isDirty, false); + } + }); }); diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 20586e5c5..9e9d4faf7 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -typescript@4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.3.tgz#153bbd468ef07725c1df9c77e8b453f8d36abba5" - integrity sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg== +typescript@4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.2.tgz#6369ef22516fe5e10304aae5a5c4862db55380e9" + integrity sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ== diff --git a/package.json b/package.json index 28f8a69a2..c856e311d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", - "version": "1.51.1", - "distro": "d32b7eb5181a656f005556e20c54bc5fd73fd080", + "version": "1.52.1", + "distro": "11fd6f1230738827638f000f641d01b2b178db20", "author": { "name": "Microsoft Corporation" }, @@ -37,14 +37,15 @@ "smoketest": "cd test/smoke && node test/index.js", "download-builtin-extensions": "node build/lib/builtInExtensions.js", "monaco-compile-check": "tsc -p src/tsconfig.monaco.json --noEmit", - "tsec-compile-check": "node_modules/tsec/bin/tsec -p src/tsconfig.json --noEmit", + "tsec-compile-check": "node node_modules/tsec/bin/tsec -p src/tsconfig.json --noEmit", "valid-layers-check": "node build/lib/layersChecker.js", "strict-function-types-watch": "tsc --watch -p src/tsconfig.json --noEmit --strictFunctionTypes", "update-distro": "node build/npm/update-distro.js", "web": "node resources/web/code-web.js", "compile-web": "gulp compile-web --max_old_space_size=4095", "watch-web": "gulp watch-web --max_old_space_size=4095", - "eslint": "eslint -c .eslintrc.json --rulesdir ./build/lib/eslint --ext .ts --ext .js ./src/vs ./extensions" + "eslint": "eslint -c .eslintrc.json --rulesdir ./build/lib/eslint --ext .ts --ext .js ./src/vs ./extensions", + "electron-rebuild": "electron-rebuild --arch=arm64 --force --version=11.0.2" }, "dependencies": { "applicationinsights": "1.0.8", @@ -54,10 +55,10 @@ "https-proxy-agent": "^2.2.3", "iconv-lite-umd": "0.6.8", "jschardet": "2.2.1", - "keytar": "^5.5.0", + "keytar": "7.2.0", "minimist": "^1.2.5", "native-is-elevated": "0.4.1", - "native-keymap": "2.2.0", + "native-keymap": "2.2.1", "native-watchdog": "1.3.0", "node-pty": "0.10.0-beta17", "spdlog": "^0.11.1", @@ -67,7 +68,8 @@ "vscode-nsfw": "1.2.9", "vscode-oniguruma": "1.3.1", "vscode-proxy-agent": "^0.5.2", - "vscode-ripgrep": "^1.9.0", + "vscode-regexpp": "^3.1.0", + "vscode-ripgrep": "^1.11.1", "vscode-sqlite3": "4.0.10", "vscode-textmate": "5.2.0", "xterm": "4.10.0-beta.4", @@ -110,7 +112,8 @@ "css-loader": "^3.2.0", "debounce": "^1.0.0", "deemon": "^1.4.0", - "electron": "9.3.3", + "electron": "9.3.5", + "electron-rebuild": "2.0.3", "eslint": "6.8.0", "eslint-plugin-jsdoc": "^19.1.0", "eslint-plugin-mocha": "8.0.0", @@ -157,7 +160,7 @@ "opn": "^6.0.0", "optimist": "0.3.5", "p-all": "^1.0.0", - "playwright": "1.3.0", + "playwright": "1.6.2", "pump": "^1.0.1", "queue": "3.0.6", "rcedit": "^1.1.0", @@ -167,13 +170,13 @@ "style-loader": "^1.0.0", "ts-loader": "^4.4.2", "tsec": "googleinterns/tsec", - "typescript": "^4.1.0-dev.20201018", + "typescript": "^4.2.0-dev.20201119", "typescript-formatter": "7.1.0", "underscore": "^1.8.2", "vinyl": "^2.0.0", "vinyl-fs": "^3.0.0", "vsce": "1.48.0", - "vscode-debugprotocol": "1.41.0", + "vscode-debugprotocol": "1.43.0", "vscode-nls-dev": "^3.3.1", "webpack": "^4.43.0", "webpack-cli": "^3.3.12", diff --git a/product.json b/product.json index 7cab6d1b9..207bcf8f5 100644 --- a/product.json +++ b/product.json @@ -25,13 +25,13 @@ "extensionAllowedProposedApi": [ "ms-vscode.vscode-js-profile-flame", "ms-vscode.vscode-js-profile-table", - "ms-vscode.references-view", - "ms-vscode.github-browser" + "ms-vscode.github-browser", + "ms-vscode.github-richnav" ], "builtInExtensions": [ { "name": "ms-vscode.node-debug", - "version": "1.44.14", + "version": "1.44.15", "repo": "https://github.com/microsoft/vscode-node-debug", "metadata": { "id": "b6ded8fb-a0a0-4c1c-acbd-ab2a3bc995a6", @@ -61,7 +61,7 @@ }, { "name": "ms-vscode.references-view", - "version": "0.0.71", + "version": "0.0.74", "repo": "https://github.com/microsoft/vscode-reference-view", "metadata": { "id": "dc489f46-520d-4556-ae85-1f9eab3c412d", @@ -91,7 +91,7 @@ }, { "name": "ms-vscode.js-debug", - "version": "1.51.0", + "version": "1.52.2", "repo": "https://github.com/microsoft/vscode-js-debug", "metadata": { "id": "25629058-ddac-4e17-abba-74678e126c5d", @@ -123,7 +123,7 @@ "webBuiltInExtensions": [ { "name": "ms-vscode.github-browser", - "version": "0.0.13", + "version": "0.0.14", "repo": "https://github.com/microsoft/vscode-github-browser", "metadata": { "id": "c1bcff4b-4ecb-466e-b8f6-b02788b5fb5a", diff --git a/remote/package.json b/remote/package.json index d353586d7..f68deac64 100644 --- a/remote/package.json +++ b/remote/package.json @@ -18,8 +18,9 @@ "vscode-nsfw": "1.2.9", "vscode-oniguruma": "1.3.1", "vscode-proxy-agent": "^0.5.2", - "vscode-ripgrep": "^1.9.0", + "vscode-ripgrep": "^1.11.1", "vscode-textmate": "5.2.0", + "vscode-regexpp": "^3.1.0", "xterm": "4.10.0-beta.4", "xterm-addon-search": "0.8.0-beta.3", "xterm-addon-unicode11": "0.3.0-beta.3", diff --git a/remote/yarn.lock b/remote/yarn.lock index 5ba96e1c5..22d8ebe3f 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -167,11 +167,16 @@ glob-parent@~5.1.0: dependencies: is-glob "^4.0.1" -graceful-fs@4.2.3, graceful-fs@^4.1.2, graceful-fs@^4.1.6: +graceful-fs@4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== +graceful-fs@^4.1.2, graceful-fs@^4.1.6: + version "4.2.4" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" + integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== + http-proxy-agent@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" @@ -279,7 +284,12 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -nan@^2.10.0, nan@^2.14.0: +nan@^2.10.0: + version "2.14.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" + integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== + +nan@^2.14.0: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== @@ -410,10 +420,15 @@ vscode-proxy-agent@^0.5.2: https-proxy-agent "^2.2.3" socks-proxy-agent "^4.0.1" -vscode-ripgrep@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.9.0.tgz#d6cdea4d290f3c2919472cdcfe2440d5fb1f99db" - integrity sha512-7jyAC/NNfvMPZgCVkyqIn0STYJ7wIk3PF2qA2cX1sEutx1g/e2VtgKAodXnfpreJq4993JT/BSIigOv/0lBSzg== +vscode-regexpp@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/vscode-regexpp/-/vscode-regexpp-3.1.0.tgz#42d059b6fffe99bd42939c0d013f632f0cad823f" + integrity sha512-pqtN65VC1jRLawfluX4Y80MMG0DHJydWhe5ZwMHewZD6sys4LbU6lHwFAHxeuaVE6Y6+xZOtAw+9hvq7/0ejkg== + +vscode-ripgrep@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.11.1.tgz#9fa3c0a96c2939d5a2389f71218bd1bb6eaa8679" + integrity sha512-oHJfpqeXuTQhOO+szqIObYOddwQ9o+lzd4PQLlTQN+sQ7ex8D1qqFip207O2iJyFc5oWE8Bekf4YHTibdbW66w== dependencies: https-proxy-agent "^4.0.0" proxy-from-env "^1.1.0" diff --git a/resources/linux/debian/postinst.template b/resources/linux/debian/postinst.template index 9f26b3509..c72fe5f9f 100755 --- a/resources/linux/debian/postinst.template +++ b/resources/linux/debian/postinst.template @@ -73,6 +73,6 @@ NdCFTW7wY0Fb1fWJ+/KTsC4= if [ "$WRITE_SOURCE" -eq "1" ]; then echo "### THIS FILE IS AUTOMATICALLY CONFIGURED ### # You may comment out this entry, but any other modifications may be lost. -deb [arch=amd64] http://packages.microsoft.com/repos/vscode stable main" > $CODE_SOURCE_PART +deb [arch=amd64,arm64,armhf] http://packages.microsoft.com/repos/@@REPOSITORY_NAME@@ stable main" > $CODE_SOURCE_PART fi fi diff --git a/resources/linux/rpm/dependencies.json b/resources/linux/rpm/dependencies.json index 7f95cd3e5..8a055565b 100644 --- a/resources/linux/rpm/dependencies.json +++ b/resources/linux/rpm/dependencies.json @@ -66,133 +66,120 @@ "libgbm.so.1()(64bit)" ], "aarch64": [ - "libpthread.so.0()(aarch64)", - "libpthread.so.0(GLIBC_2.2.5)(aarch64)", - "libpthread.so.0(GLIBC_2.3.2)(aarch64)", - "libpthread.so.0(GLIBC_2.3.3)(aarch64)", - "libgtk-3.so.0()(aarch64)", - "libgdk-x11-2.0.so.0()(aarch64)", - "libatk-1.0.so.0()(aarch64)", - "libgio-2.0.so.0()(aarch64)", - "libpangocairo-1.0.so.0()(aarch64)", - "libgdk_pixbuf-2.0.so.0()(aarch64)", - "libcairo.so.2()(aarch64)", - "libpango-1.0.so.0()(aarch64)", - "libfreetype.so.6()(aarch64)", - "libfontconfig.so.1()(aarch64)", - "libgobject-2.0.so.0()(aarch64)", - "libdbus-1.so.3()(aarch64)", - "libXi.so.6()(aarch64)", - "libXcursor.so.1()(aarch64)", - "libXdamage.so.1()(aarch64)", - "libXrandr.so.2()(aarch64)", - "libXcomposite.so.1()(aarch64)", - "libXext.so.6()(aarch64)", - "libXfixes.so.3()(aarch64)", - "libXrender.so.1()(aarch64)", - "libX11.so.6()(aarch64)", - "libXss.so.1()(aarch64)", - "libXtst.so.6()(aarch64)", - "libgmodule-2.0.so.0()(aarch64)", - "librt.so.1()(aarch64)", - "libglib-2.0.so.0()(aarch64)", - "libnss3.so()(aarch64)", - "libnssutil3.so()(aarch64)", - "libsmime3.so()(aarch64)", - "libnspr4.so()(aarch64)", - "libasound.so.2()(aarch64)", - "libcups.so.2()(aarch64)", - "libdl.so.2()(aarch64)", - "libexpat.so.1()(aarch64)", - "libstdc++.so.6()(aarch64)", - "libstdc++.so.6(GLIBCXX_3.4)(aarch64)", - "libstdc++.so.6(GLIBCXX_3.4.10)(aarch64)", - "libstdc++.so.6(GLIBCXX_3.4.11)(aarch64)", - "libstdc++.so.6(GLIBCXX_3.4.14)(aarch64)", - "libstdc++.so.6(GLIBCXX_3.4.15)(aarch64)", - "libstdc++.so.6(GLIBCXX_3.4.9)(aarch64)", - "libm.so.6()(aarch64)", - "libm.so.6(GLIBC_2.2.5)(aarch64)", - "libgcc_s.so.1()(aarch64)", - "libgcc_s.so.1(GCC_3.0)(aarch64)", - "libgcc_s.so.1(GCC_4.0.0)(aarch64)", - "libc.so.6()(aarch64)", - "libc.so.6(GLIBC_2.11)(aarch64)", - "libc.so.6(GLIBC_2.2.5)(aarch64)", - "libc.so.6(GLIBC_2.3)(aarch64)", - "libc.so.6(GLIBC_2.3.2)(aarch64)", - "libc.so.6(GLIBC_2.3.4)(aarch64)", - "libc.so.6(GLIBC_2.4)(aarch64)", - "libc.so.6(GLIBC_2.6)(aarch64)", - "libc.so.6(GLIBC_2.7)(aarch64)", - "libc.so.6(GLIBC_2.9)(aarch64)", - "libxcb.so.1()(aarch64)", - "libxkbfile.so.1()(aarch64)", - "libsecret-1.so.0()(aarch64)" + "libpthread.so.0()(64bit)", + "libpthread.so.0(GLIBC_2.17)(64bit)", + "libgtk-3.so.0()(64bit)", + "libgdk-x11-2.0.so.0()(64bit)", + "libatk-1.0.so.0()(64bit)", + "libgio-2.0.so.0()(64bit)", + "libpangocairo-1.0.so.0()(64bit)", + "libgdk_pixbuf-2.0.so.0()(64bit)", + "libcairo.so.2()(64bit)", + "libpango-1.0.so.0()(64bit)", + "libfreetype.so.6()(64bit)", + "libfontconfig.so.1()(64bit)", + "libgobject-2.0.so.0()(64bit)", + "libdbus-1.so.3()(64bit)", + "libXi.so.6()(64bit)", + "libXcursor.so.1()(64bit)", + "libXdamage.so.1()(64bit)", + "libXrandr.so.2()(64bit)", + "libXcomposite.so.1()(64bit)", + "libXext.so.6()(64bit)", + "libXfixes.so.3()(64bit)", + "libXrender.so.1()(64bit)", + "libX11.so.6()(64bit)", + "libXss.so.1()(64bit)", + "libXtst.so.6()(64bit)", + "libgmodule-2.0.so.0()(64bit)", + "librt.so.1()(64bit)", + "libglib-2.0.so.0()(64bit)", + "libnss3.so()(64bit)", + "libnssutil3.so()(64bit)", + "libsmime3.so()(64bit)", + "libnspr4.so()(64bit)", + "libasound.so.2()(64bit)", + "libcups.so.2()(64bit)", + "libdl.so.2()(64bit)", + "libexpat.so.1()(64bit)", + "libstdc++.so.6()(64bit)", + "libstdc++.so.6(GLIBCXX_3.4)(64bit)", + "libstdc++.so.6(GLIBCXX_3.4.10)(64bit)", + "libstdc++.so.6(GLIBCXX_3.4.11)(64bit)", + "libstdc++.so.6(GLIBCXX_3.4.14)(64bit)", + "libstdc++.so.6(GLIBCXX_3.4.15)(64bit)", + "libstdc++.so.6(GLIBCXX_3.4.9)(64bit)", + "libm.so.6()(64bit)", + "libm.so.6(GLIBC_2.17)(64bit)", + "libgcc_s.so.1()(64bit)", + "libgcc_s.so.1(GCC_3.0)(64bit)", + "libgcc_s.so.1(GCC_4.0.0)(64bit)", + "libc.so.6()(64bit)", + "libc.so.6(GLIBC_2.17)(64bit)", + "libxcb.so.1()(64bit)", + "libxkbfile.so.1()(64bit)", + "libsecret-1.so.0()(64bit)" ], "armv7hl": [ - "libpthread.so.0()(armv7hl)", - "libpthread.so.0(GLIBC_2.2.5)(armv7hl)", - "libpthread.so.0(GLIBC_2.3.2)(armv7hl)", - "libpthread.so.0(GLIBC_2.3.3)(armv7hl)", - "libgtk-3.so.0()(armv7hl)", - "libgdk-x11-2.0.so.0()(armv7hl)", - "libatk-1.0.so.0()(armv7hl)", - "libgio-2.0.so.0()(armv7hl)", - "libpangocairo-1.0.so.0()(armv7hl)", - "libgdk_pixbuf-2.0.so.0()(armv7hl)", - "libcairo.so.2()(armv7hl)", - "libpango-1.0.so.0()(armv7hl)", - "libfreetype.so.6()(armv7hl)", - "libfontconfig.so.1()(armv7hl)", - "libgobject-2.0.so.0()(armv7hl)", - "libdbus-1.so.3()(armv7hl)", - "libXi.so.6()(armv7hl)", - "libXcursor.so.1()(armv7hl)", - "libXdamage.so.1()(armv7hl)", - "libXrandr.so.2()(armv7hl)", - "libXcomposite.so.1()(armv7hl)", - "libXext.so.6()(armv7hl)", - "libXfixes.so.3()(armv7hl)", - "libXrender.so.1()(armv7hl)", - "libX11.so.6()(armv7hl)", - "libXss.so.1()(armv7hl)", - "libXtst.so.6()(armv7hl)", - "libgmodule-2.0.so.0()(armv7hl)", - "librt.so.1()(armv7hl)", - "libglib-2.0.so.0()(armv7hl)", - "libnss3.so()(armv7hl)", - "libnssutil3.so()(armv7hl)", - "libsmime3.so()(armv7hl)", - "libnspr4.so()(armv7hl)", - "libasound.so.2()(armv7hl)", - "libcups.so.2()(armv7hl)", - "libdl.so.2()(armv7hl)", - "libexpat.so.1()(armv7hl)", - "libstdc++.so.6()(armv7hl)", - "libstdc++.so.6(GLIBCXX_3.4)(armv7hl)", - "libstdc++.so.6(GLIBCXX_3.4.10)(armv7hl)", - "libstdc++.so.6(GLIBCXX_3.4.11)(armv7hl)", - "libstdc++.so.6(GLIBCXX_3.4.14)(armv7hl)", - "libstdc++.so.6(GLIBCXX_3.4.15)(armv7hl)", - "libstdc++.so.6(GLIBCXX_3.4.9)(armv7hl)", - "libm.so.6()(armv7hl)", - "libm.so.6(GLIBC_2.2.5)(armv7hl)", - "libgcc_s.so.1()(armv7hl)", - "libgcc_s.so.1(GCC_3.0)(armv7hl)", - "libgcc_s.so.1(GCC_4.0.0)(armv7hl)", - "libc.so.6()(armv7hl)", - "libc.so.6(GLIBC_2.11)(armv7hl)", - "libc.so.6(GLIBC_2.2.5)(armv7hl)", - "libc.so.6(GLIBC_2.3)(armv7hl)", - "libc.so.6(GLIBC_2.3.2)(armv7hl)", - "libc.so.6(GLIBC_2.3.4)(armv7hl)", - "libc.so.6(GLIBC_2.4)(armv7hl)", - "libc.so.6(GLIBC_2.6)(armv7hl)", - "libc.so.6(GLIBC_2.7)(armv7hl)", - "libc.so.6(GLIBC_2.9)(armv7hl)", - "libxcb.so.1()(armv7hl)", - "libxkbfile.so.1()(armv7hl)", - "libsecret-1.so.0()(armv7hl)" + "libpthread.so.0()", + "libpthread.so.0(GLIBC_2.4)", + "libpthread.so.0(GLIBC_2.11)", + "libpthread.so.0(GLIBC_2.12)", + "libgtk-3.so.0()", + "libgdk-x11-2.0.so.0()", + "libatk-1.0.so.0()", + "libgio-2.0.so.0()", + "libpangocairo-1.0.so.0()", + "libgdk_pixbuf-2.0.so.0()", + "libcairo.so.2()", + "libpango-1.0.so.0()", + "libfreetype.so.6()", + "libfontconfig.so.1()", + "libgobject-2.0.so.0()", + "libdbus-1.so.3()", + "libXi.so.6()", + "libXcursor.so.1()", + "libXdamage.so.1()", + "libXrandr.so.2()", + "libXcomposite.so.1()", + "libXext.so.6()", + "libXfixes.so.3()", + "libXrender.so.1()", + "libX11.so.6()", + "libXss.so.1()", + "libXtst.so.6()", + "libgmodule-2.0.so.0()", + "librt.so.1()", + "libglib-2.0.so.0()", + "libnss3.so()", + "libnssutil3.so()", + "libsmime3.so()", + "libnspr4.so()", + "libasound.so.2()", + "libcups.so.2()", + "libdl.so.2()", + "libexpat.so.1()", + "libstdc++.so.6()", + "libstdc++.so.6(GLIBCXX_3.4)", + "libstdc++.so.6(GLIBCXX_3.4.10)", + "libstdc++.so.6(GLIBCXX_3.4.11)", + "libstdc++.so.6(GLIBCXX_3.4.14)", + "libstdc++.so.6(GLIBCXX_3.4.15)", + "libstdc++.so.6(GLIBCXX_3.4.9)", + "libm.so.6()", + "libm.so.6(GLIBC_2.4)", + "libm.so.6(GLIBC_2.15)", + "libgcc_s.so.1()", + "libgcc_s.so.1(GCC_3.0)", + "libgcc_s.so.1(GCC_4.0.0)", + "libc.so.6()", + "libc.so.6(GLIBC_2.11)", + "libc.so.6(GLIBC_2.4)", + "libc.so.6(GLIBC_2.6)", + "libc.so.6(GLIBC_2.7)", + "libc.so.6(GLIBC_2.9)", + "libxcb.so.1()", + "libxkbfile.so.1()", + "libsecret-1.so.0()" ] } diff --git a/resources/linux/snap/snapcraft.yaml b/resources/linux/snap/snapcraft.yaml index 046158e88..c24d0af3e 100644 --- a/resources/linux/snap/snapcraft.yaml +++ b/resources/linux/snap/snapcraft.yaml @@ -6,6 +6,10 @@ description: | simplicity of a code editor with what developers need for the core edit-build-debug cycle. +architectures: + - build-on: amd64 + run-on: @@ARCHITECTURE@@ + grade: stable confinement: classic diff --git a/resources/web/code-web.js b/resources/web/code-web.js index 8658b737c..9be3bc2d8 100644 --- a/resources/web/code-web.js +++ b/resources/web/code-web.js @@ -386,6 +386,10 @@ async function handleRoot(req, res) { if (args['wrap-iframe']) { webConfigJSON._wrapWebWorkerExtHostInIframe = true; } + if (req.headers['x-forwarded-host']) { + // support for running in codespace => no iframe wrapping + delete webConfigJSON.webWorkerExtensionHostIframeSrc; + } const authSessionInfo = args['github-auth'] ? { id: uuid.v4(), diff --git a/scripts/code.bat b/scripts/code.bat index 7ef1fd33f..a05bea92d 100644 --- a/scripts/code.bat +++ b/scripts/code.bat @@ -22,7 +22,6 @@ set VSCODE_DEV=1 set VSCODE_CLI=1 set ELECTRON_ENABLE_LOGGING=1 set ELECTRON_ENABLE_STACK_DUMPING=1 -set VSCODE_LOGS= :: Launch Code diff --git a/scripts/code.sh b/scripts/code.sh index 7ab367c17..3095f3897 100755 --- a/scripts/code.sh +++ b/scripts/code.sh @@ -41,7 +41,6 @@ function code() { export VSCODE_CLI=1 export ELECTRON_ENABLE_STACK_DUMPING=1 export ELECTRON_ENABLE_LOGGING=1 - export VSCODE_LOGS= # Launch Code exec "$CODE" . --no-sandbox "$@" diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index b302ce28d..b82dbec11 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -46,21 +46,46 @@ else echo "Running integration tests with '$INTEGRATION_TEST_ELECTRON_PATH' as build." fi +if [ -z "$INTEGRATION_TEST_APP_NAME" ]; then + after_suite() { true; } +else + after_suite() { killall $INTEGRATION_TEST_APP_NAME || true; } +fi + # Integration tests in AMD ./scripts/test.sh --runGlob **/*.integrationTest.js "$@" +after_suite # Tests in the extension host "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $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 --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +after_suite + "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $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 --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +after_suite + "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +after_suite + "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/markdown-language-features/test-workspace --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +after_suite + #"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/typescript-language-features/test-workspace --extensionDevelopmentPath=$ROOT/extensions/typescript-language-features --extensionTestsPath=$ROOT/extensions/typescript-language-features/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +# after_suite + "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/emmet/out/test/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +after_suite + "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $(mktemp -d 2>/dev/null) --enable-proposed-api=vscode.git --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +after_suite + "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-notebook-tests/test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-notebook-tests --extensionTestsPath=$ROOT/extensions/vscode-notebook-tests/out/ --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +after_suite # Tests in commonJS (CSS, HTML) cd $ROOT/extensions/css-language-features/server && $ROOT/scripts/node-electron.sh test/index.js +after_suite + cd $ROOT/extensions/html-language-features/server && $ROOT/scripts/node-electron.sh test/index.js +after_suite rm -rf $VSCODEUSERDATADIR diff --git a/src/bootstrap-amd.js b/src/bootstrap-amd.js index 752e4ab63..58355aebd 100644 --- a/src/bootstrap-amd.js +++ b/src/bootstrap-amd.js @@ -18,7 +18,9 @@ loader.config({ catchError: true, nodeRequire: require, nodeMain: __filename, - 'vs/nls': nlsConfig + 'vs/nls': nlsConfig, + amdModulesPattern: /^vs\//, + recordStats: true }); // Running in Electron @@ -29,7 +31,7 @@ if (process.env['ELECTRON_RUN_AS_NODE'] || process.versions['electron']) { } // Pseudo NLS support -if (nlsConfig.pseudo) { +if (nlsConfig && nlsConfig.pseudo) { loader(['vs/nls'], function (nlsPlugin) { nlsPlugin.setPseudoTranslation(nlsConfig.pseudo); }); diff --git a/src/bootstrap-fork.js b/src/bootstrap-fork.js index 97a4d9934..68e2219ba 100644 --- a/src/bootstrap-fork.js +++ b/src/bootstrap-fork.js @@ -13,7 +13,7 @@ const bootstrapNode = require('./bootstrap-node'); bootstrapNode.removeGlobalNodeModuleLookupPaths(); // Enable ASAR in our forked processes -bootstrap.enableASARSupport(); +bootstrap.enableASARSupport(undefined); if (process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH']) { bootstrapNode.injectNodeModuleLookupPath(process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH']); @@ -81,7 +81,9 @@ function pipeLoggingToParent() { // to start the stacktrace where the console message was being written if (process.env.VSCODE_LOG_STACK === 'true') { const stack = new Error().stack; - argsArray.push({ __$stack: stack.split('\n').slice(3).join('\n') }); + if (stack) { + argsArray.push({ __$stack: stack.split('\n').slice(3).join('\n') }); + } } try { @@ -114,7 +116,9 @@ function pipeLoggingToParent() { */ function safeSend(arg) { try { - process.send(arg); + if (process.send) { + process.send(arg); + } } catch (error) { // Can happen if the parent channel is closed meanwhile } diff --git a/src/bootstrap-node.js b/src/bootstrap-node.js index 268624997..161d68d59 100644 --- a/src/bootstrap-node.js +++ b/src/bootstrap-node.js @@ -58,3 +58,73 @@ exports.removeGlobalNodeModuleLookupPaths = function () { return paths.slice(0, paths.length - commonSuffixLength); }; }; + +/** + * Helper to enable portable mode. + * + * @param {{ portable?: string; applicationName: string; }} product + * @returns {{ portableDataPath: string; isPortable: boolean; }} + */ +exports.configurePortable = function (product) { + const fs = require('fs'); + const path = require('path'); + + const appRoot = path.dirname(__dirname); + + /** + * @param {import('path')} path + */ + function getApplicationPath(path) { + if (process.env['VSCODE_DEV']) { + return appRoot; + } + + if (process.platform === 'darwin') { + return path.dirname(path.dirname(path.dirname(appRoot))); + } + + return path.dirname(path.dirname(appRoot)); + } + + /** + * @param {import('path')} path + */ + function getPortableDataPath(path) { + if (process.env['VSCODE_PORTABLE']) { + return process.env['VSCODE_PORTABLE']; + } + + if (process.platform === 'win32' || process.platform === 'linux') { + return path.join(getApplicationPath(path), 'data'); + } + + // @ts-ignore + const portableDataName = product.portable || `${product.applicationName}-portable-data`; + return path.join(path.dirname(getApplicationPath(path)), portableDataName); + } + + const portableDataPath = getPortableDataPath(path); + const isPortable = !('target' in product) && fs.existsSync(portableDataPath); + const portableTempPath = path.join(portableDataPath, 'tmp'); + const isTempPortable = isPortable && fs.existsSync(portableTempPath); + + if (isPortable) { + process.env['VSCODE_PORTABLE'] = portableDataPath; + } else { + delete process.env['VSCODE_PORTABLE']; + } + + if (isTempPortable) { + if (process.platform === 'win32') { + process.env['TMP'] = portableTempPath; + process.env['TEMP'] = portableTempPath; + } else { + process.env['TMPDIR'] = portableTempPath; + } + } + + return { + portableDataPath, + isPortable + }; +}; diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index 3fc0f419b..d7cc72d3b 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -25,7 +25,12 @@ const preloadGlobals = globals(); const sandbox = preloadGlobals.context.sandbox; const webFrame = preloadGlobals.webFrame; - const safeProcess = sandbox ? preloadGlobals.process : process; + const safeProcess = preloadGlobals.process; + const configuration = parseWindowConfiguration(); + + // Start to resolve process.env before anything gets load + // so that we can run loading and resolving in parallel + const whenEnvResolved = safeProcess.resolveEnv(configuration.userEnv); /** * @param {string[]} modulePaths @@ -33,18 +38,6 @@ * @param {{ forceEnableDeveloperKeybindings?: boolean, disallowReloadKeybinding?: boolean, removeDeveloperKeybindingsAfterLoad?: boolean, canModifyDOM?: (config: object) => void, beforeLoaderConfig?: (config: object, loaderConfig: object) => void, beforeRequire?: () => void }=} options */ function load(modulePaths, resultCallback, options) { - const args = parseURLQueryArgs(); - /** - * // configuration: INativeWindowConfiguration - * @type {{ - * zoomLevel?: number, - * extensionDevelopmentPath?: string[], - * extensionTestsPath?: string, - * userEnv?: { [key: string]: string | undefined }, - * appRoot?: string, - * nodeCachedDataDir?: string - * }} */ - const configuration = JSON.parse(args['config'] || '{}') || {}; // Apply zoom level early to avoid glitches const zoomLevel = configuration.zoomLevel; @@ -64,22 +57,15 @@ developerToolsUnbind = registerDeveloperKeybindings(options && options.disallowReloadKeybinding); } - // Correctly inherit the parent's environment (TODO@sandbox non-sandboxed only) - if (!sandbox) { - Object.assign(safeProcess.env, configuration.userEnv); - } - - // Enable ASAR support (TODO@sandbox non-sandboxed only) - if (!sandbox) { - globalThis.MonacoBootstrap.enableASARSupport(configuration.appRoot); - } + // Enable ASAR support + globalThis.MonacoBootstrap.enableASARSupport(configuration.appRoot); if (options && typeof options.canModifyDOM === 'function') { options.canModifyDOM(configuration); } - // Get the nls configuration into the process.env as early as possible (TODO@sandbox non-sandboxed only) - const nlsConfig = sandbox ? { availableLanguages: {} } : globalThis.MonacoBootstrap.setupNLS(); + // Get the nls configuration into the process.env as early as possible + const nlsConfig = globalThis.MonacoBootstrap.setupNLS(); let locale = nlsConfig.availableLanguages['*'] || 'en'; if (locale === 'zh-tw') { @@ -102,9 +88,14 @@ window['MonacoEnvironment'] = {}; + const baseUrl = sandbox ? + `${bootstrapLib.fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32', scheme: 'vscode-file', fallbackAuthority: 'vscode-app' })}/out` : + `${bootstrapLib.fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32' })}/out`; + const loaderConfig = { - baseUrl: `${bootstrapLib.fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32' })}/out`, - 'vs/nls': nlsConfig + baseUrl, + 'vs/nls': nlsConfig, + preferScriptTags: sandbox }; // Enable loading of node modules: @@ -150,17 +141,23 @@ options.beforeRequire(); } - require(modulePaths, result => { + require(modulePaths, async result => { try { + + // Wait for process environment being fully resolved + const perf = perfLib(); + perf.mark('willWaitForShellEnv'); + await whenEnvResolved; + perf.mark('didWaitForShellEnv'); + + // Callback only after process environment is resolved const callbackResult = resultCallback(result, configuration); - if (callbackResult && typeof callbackResult.then === 'function') { - callbackResult.then(() => { - if (developerToolsUnbind && options && options.removeDeveloperKeybindingsAfterLoad) { - developerToolsUnbind(); - } - }, error => { - onUnexpectedError(error, enableDeveloperTools); - }); + if (callbackResult instanceof Promise) { + await callbackResult; + + if (developerToolsUnbind && options && options.removeDeveloperKeybindingsAfterLoad) { + developerToolsUnbind(); + } } } catch (error) { onUnexpectedError(error, enableDeveloperTools); @@ -169,20 +166,30 @@ } /** - * @returns {{[param: string]: string }} + * Parses the contents of the `INativeWindowConfiguration` that + * is passed into the URL from the `electron-main` side. + * + * @returns {{ + * zoomLevel?: number, + * extensionDevelopmentPath?: string[], + * extensionTestsPath?: string, + * userEnv?: { [key: string]: string | undefined }, + * appRoot: string, + * nodeCachedDataDir?: string + * }} */ - function parseURLQueryArgs() { - const search = window.location.search || ''; - - return search.split(/[?&]/) + function parseWindowConfiguration() { + const rawConfiguration = (window.location.search || '').split(/[?&]/) .filter(function (param) { return !!param; }) .map(function (param) { return param.split('='); }) .filter(function (param) { return param.length === 2; }) .reduce(function (r, param) { r[param[0]] = decodeURIComponent(param[1]); return r; }, {}); + + return JSON.parse(rawConfiguration['config'] || '{}') || {}; } /** - * @param {boolean} disallowReloadKeybinding + * @param {boolean | undefined} disallowReloadKeybinding * @returns {() => void} */ function registerDeveloperKeybindings(disallowReloadKeybinding) { @@ -203,6 +210,7 @@ const TOGGLE_DEV_TOOLS_KB_ALT = '123'; // F12 const RELOAD_KB = (safeProcess.platform === 'darwin' ? 'meta-82' : 'ctrl-82'); // mac: Cmd-R, rest: Ctrl-R + /** @type {((e: any) => void) | undefined} */ let listener = function (e) { const key = extractKey(e); if (key === TOGGLE_DEV_TOOLS_KB || key === TOGGLE_DEV_TOOLS_KB_ALT) { @@ -255,8 +263,26 @@ return window.vscode; } + /** + * @return {{ mark: (name: string) => void }} + */ + function perfLib() { + globalThis.MonacoPerformanceMarks = globalThis.MonacoPerformanceMarks || []; + + return { + /** + * @param {string} name + */ + mark(name) { + globalThis.MonacoPerformanceMarks.push(name, Date.now()); + performance.mark(name); + } + }; + } + return { load, - globals + globals, + perfLib }; })); diff --git a/src/bootstrap.js b/src/bootstrap.js index 0cb6466ae..5392a5602 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -42,11 +42,11 @@ //#region Add support for using node_modules.asar /** - * @param {string} appRoot + * @param {string | undefined} appRoot */ function enableASARSupport(appRoot) { - if (!path || !Module) { - console.warn('enableASARSupport() is only available in node.js environments'); + if (!path || !Module || typeof process === 'undefined') { + console.warn('enableASARSupport() is only available in node.js environments'); // TODO@sandbox ASAR is currently non-sandboxed only return; } @@ -124,17 +124,14 @@ //#region NLS helpers /** - * @returns {{locale?: string, availableLanguages: {[lang: string]: string;}, pseudo?: boolean }} + * @returns {{locale?: string, availableLanguages: {[lang: string]: string;}, pseudo?: boolean } | undefined} */ function setupNLS() { - if (!path || !fs) { - console.warn('setupNLS() is only available in node.js environments'); - return; - } - // Get the nls configuration into the process.env as early as possible. + // Get the nls configuration as early as possible. + const process = safeProcess(); let nlsConfig = { availableLanguages: {} }; - if (process.env['VSCODE_NLS_CONFIG']) { + if (process && process.env['VSCODE_NLS_CONFIG']) { try { nlsConfig = JSON.parse(process.env['VSCODE_NLS_CONFIG']); } catch (e) { @@ -153,8 +150,7 @@ return; } - const bundleFile = path.join(nlsConfig._resolvedLanguagePackCoreLocation, `${bundle.replace(/\//g, '!')}.nls.json`); - fs.promises.readFile(bundleFile, 'utf8').then(function (content) { + safeReadNlsFile(nlsConfig._resolvedLanguagePackCoreLocation, `${bundle.replace(/\//g, '!')}.nls.json`).then(function (content) { const json = JSON.parse(content); bundles[bundle] = json; @@ -162,7 +158,7 @@ }).catch((error) => { try { if (nlsConfig._corruptedFile) { - fs.promises.writeFile(nlsConfig._corruptedFile, 'corrupted', 'utf8').catch(function (error) { console.error(error); }); + safeWriteNlsFile(nlsConfig._corruptedFile, 'corrupted').catch(function (error) { console.error(error); }); } } finally { cb(error, undefined); @@ -174,77 +170,73 @@ return nlsConfig; } - //#endregion + function safeGlobals() { + const globals = (typeof self === 'object' ? self : typeof global === 'object' ? global : {}); - - //#region Portable helpers + return globals.vscode; + } /** - * @param {{ portable: string; applicationName: string; }} product - * @returns {{ portableDataPath: string; isPortable: boolean; }} + * @returns {NodeJS.Process | undefined} */ - function configurePortable(product) { - if (!path || !fs) { - console.warn('configurePortable() is only available in node.js environments'); - return; + function safeProcess() { + if (typeof process !== 'undefined') { + return process; // Native environment (non-sandboxed) } - const appRoot = path.dirname(__dirname); + const globals = safeGlobals(); + if (globals) { + return globals.process; // Native environment (sandboxed) + } + } - function getApplicationPath() { - if (process.env['VSCODE_DEV']) { - return appRoot; - } + /** + * @returns {Electron.IpcRenderer | undefined} + */ + function safeIpcRenderer() { + const globals = safeGlobals(); + if (globals) { + return globals.ipcRenderer; + } + } - if (process.platform === 'darwin') { - return path.dirname(path.dirname(path.dirname(appRoot))); - } - - return path.dirname(path.dirname(appRoot)); + /** + * @param {string[]} pathSegments + * @returns {Promise} + */ + async function safeReadNlsFile(...pathSegments) { + const ipcRenderer = safeIpcRenderer(); + if (ipcRenderer) { + return ipcRenderer.invoke('vscode:readNlsFile', ...pathSegments); } - function getPortableDataPath() { - if (process.env['VSCODE_PORTABLE']) { - return process.env['VSCODE_PORTABLE']; - } - - if (process.platform === 'win32' || process.platform === 'linux') { - return path.join(getApplicationPath(), 'data'); - } - - // @ts-ignore - const portableDataName = product.portable || `${product.applicationName}-portable-data`; - return path.join(path.dirname(getApplicationPath()), portableDataName); + if (fs && path) { + return (await fs.promises.readFile(path.join(...pathSegments))).toString(); } - const portableDataPath = getPortableDataPath(); - const isPortable = !('target' in product) && fs.existsSync(portableDataPath); - const portableTempPath = path.join(portableDataPath, 'tmp'); - const isTempPortable = isPortable && fs.existsSync(portableTempPath); + throw new Error('Unsupported operation (read NLS files)'); + } - if (isPortable) { - process.env['VSCODE_PORTABLE'] = portableDataPath; - } else { - delete process.env['VSCODE_PORTABLE']; + /** + * @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 (isTempPortable) { - if (process.platform === 'win32') { - process.env['TMP'] = portableTempPath; - process.env['TEMP'] = portableTempPath; - } else { - process.env['TMPDIR'] = portableTempPath; - } + if (fs) { + return fs.promises.writeFile(path, content); } - return { - portableDataPath, - isPortable - }; + throw new Error('Unsupported operation (write NLS files)'); } //#endregion - + //#region ApplicationInsights @@ -267,7 +259,6 @@ return { enableASARSupport, avoidMonkeyPatchFromAppInsights, - configurePortable, setupNLS, fileUriFromPath }; diff --git a/src/buildfile.js b/src/buildfile.js index f6d1c647d..c0400de76 100644 --- a/src/buildfile.js +++ b/src/buildfile.js @@ -10,7 +10,7 @@ function entrypoint(name) { exports.base = [{ name: 'vs/base/common/worker/simpleWorker', include: ['vs/editor/common/services/editorSimpleWorker'], - prepend: ['vs/loader.js'], + prepend: ['vs/loader.js', 'vs/nls.js'], append: ['vs/base/worker/workerMain'], dest: 'vs/base/worker/workerMain.js' }]; diff --git a/src/cli.js b/src/cli.js index 872cd6673..5624cb93b 100644 --- a/src/cli.js +++ b/src/cli.js @@ -7,16 +7,17 @@ 'use strict'; const bootstrap = require('./bootstrap'); +const bootstrapNode = require('./bootstrap-node'); const product = require('../product.json'); // Avoid Monkey Patches from Application Insights bootstrap.avoidMonkeyPatchFromAppInsights(); // Enable portable support -bootstrap.configurePortable(product); +bootstrapNode.configurePortable(product); // Enable ASAR support -bootstrap.enableASARSupport(); +bootstrap.enableASARSupport(undefined); // Load CLI through AMD loader require('./bootstrap-amd').load('vs/code/node/cli'); diff --git a/src/main.js b/src/main.js index 2a9756ab2..0bc566099 100644 --- a/src/main.js +++ b/src/main.js @@ -15,6 +15,7 @@ const path = require('path'); const fs = require('fs'); const os = require('os'); const bootstrap = require('./bootstrap'); +const bootstrapNode = require('./bootstrap-node'); const paths = require('./paths'); /** @type {any} */ const product = require('../product.json'); @@ -25,10 +26,10 @@ const { app, protocol, crashReporter } = require('electron'); app.allowRendererProcessReuse = false; // Enable portable support -const portable = bootstrap.configurePortable(product); +const portable = bootstrapNode.configurePortable(product); // Enable ASAR support -bootstrap.enableASARSupport(); +bootstrap.enableASARSupport(undefined); // Set userData path before app 'ready' event const args = parseCLIArgs(); @@ -107,7 +108,7 @@ crashReporter.start({ // to ensure that no 'logs' folder is created on disk at a // location outside of the portable directory // (https://github.com/microsoft/vscode/issues/56651) -if (portable.isPortable) { +if (portable && portable.isPortable) { app.setAppLogsPath(path.join(userDataPath, 'logs')); } @@ -133,6 +134,15 @@ protocol.registerSchemesAsPrivileged([ corsEnabled: true, } }, + { + scheme: 'vscode-file', + privileges: { + secure: true, + standard: true, + supportFetchAPI: true, + corsEnabled: true + } + } ]); // Global app listeners @@ -145,7 +155,7 @@ const nodeCachedDataDir = getNodeCachedDir(); * Support user defined locale: load it early before app('ready') * to have more things running in parallel. * - * @type {Promise} nlsConfig | undefined + * @type {Promise | undefined} */ let nlsConfigurationPromise = undefined; @@ -359,7 +369,7 @@ function getArgvConfigPath() { /** * @param {NativeParsedArgs} cliArgs - * @returns {string} + * @returns {string | null} */ function getJSFlags(cliArgs) { const jsFlags = []; @@ -387,7 +397,7 @@ function getUserDataPath(cliArgs) { return path.join(portable.portableDataPath, 'user-data'); } - return path.resolve(cliArgs['user-data-dir'] || paths.getDefaultUserDataPath(process.platform)); + return path.resolve(cliArgs['user-data-dir'] || paths.getDefaultUserDataPath()); } /** @@ -468,12 +478,14 @@ function getNodeCachedDir() { } async ensureExists() { - try { - await mkdirp(this.value); + if (typeof this.value === 'string') { + try { + await mkdirp(this.value); - return this.value; - } catch (error) { - // ignore + return this.value; + } catch (error) { + // ignore + } } } diff --git a/src/paths.js b/src/paths.js index a88927d20..2042123d7 100644 --- a/src/paths.js +++ b/src/paths.js @@ -11,25 +11,38 @@ const path = require('path'); const os = require('os'); /** - * @param {string} platform * @returns {string} */ -function getAppDataPath(platform) { - switch (platform) { - case 'win32': return process.env['VSCODE_APPDATA'] || process.env['APPDATA'] || path.join(process.env['USERPROFILE'], 'AppData', 'Roaming'); - case 'darwin': return process.env['VSCODE_APPDATA'] || path.join(os.homedir(), 'Library', 'Application Support'); - case 'linux': return process.env['VSCODE_APPDATA'] || process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config'); - default: throw new Error('Platform not supported'); +function getDefaultUserDataPath() { + + // Support global VSCODE_APPDATA environment variable + let appDataPath = process.env['VSCODE_APPDATA']; + + // Otherwise check per platform + if (!appDataPath) { + switch (process.platform) { + case 'win32': + appDataPath = process.env['APPDATA']; + if (!appDataPath) { + const userProfile = process.env['USERPROFILE']; + if (typeof userProfile !== 'string') { + throw new Error('Windows: Unexpected undefined %USERPROFILE% environment variable'); + } + appDataPath = path.join(userProfile, 'AppData', 'Roaming'); + } + break; + case 'darwin': + appDataPath = path.join(os.homedir(), 'Library', 'Application Support'); + break; + case 'linux': + appDataPath = process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config'); + break; + default: + throw new Error('Platform not supported'); + } } + + return path.join(appDataPath, pkg.name); } -/** - * @param {string} platform - * @returns {string} - */ -function getDefaultUserDataPath(platform) { - return path.join(getAppDataPath(platform), pkg.name); -} - -exports.getAppDataPath = getAppDataPath; exports.getDefaultUserDataPath = getDefaultUserDataPath; diff --git a/src/vs/base/browser/contextmenu.ts b/src/vs/base/browser/contextmenu.ts index 2119700f8..fc2ee0320 100644 --- a/src/vs/base/browser/contextmenu.ts +++ b/src/vs/base/browser/contextmenu.ts @@ -5,7 +5,7 @@ import { IAction, IActionRunner, IActionViewItem } from 'vs/base/common/actions'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; -import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; +import { AnchorAlignment, AnchorAxisAlignment } from 'vs/base/browser/ui/contextview/contextview'; export interface IContextMenuEvent { readonly shiftKey?: boolean; @@ -26,6 +26,7 @@ export interface IContextMenuDelegate { actionRunner?: IActionRunner; autoSelectFirstItem?: boolean; anchorAlignment?: AnchorAlignment; + anchorAxisAlignment?: AnchorAxisAlignment; domForShadowRoot?: HTMLElement; } diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 430ef8093..b4740dade 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -661,6 +661,48 @@ export function isAncestor(testChild: Node | null, testAncestor: Node | null): b return false; } +const parentFlowToDataKey = 'parentFlowToElementId'; + +/** + * Set an explicit parent to use for nodes that are not part of the + * regular dom structure. + */ +export function setParentFlowTo(fromChildElement: HTMLElement, toParentElement: Element): void { + fromChildElement.dataset[parentFlowToDataKey] = toParentElement.id; +} + +function getParentFlowToElement(node: HTMLElement): HTMLElement | null { + const flowToParentId = node.dataset[parentFlowToDataKey]; + if (typeof flowToParentId === 'string') { + return document.getElementById(flowToParentId); + } + return null; +} + +/** + * Check if `testAncestor` is an ancessor of `testChild`, observing the explicit + * parents set by `setParentFlowTo`. + */ +export function isAncestorUsingFlowTo(testChild: Node, testAncestor: Node): boolean { + let node: Node | null = testChild; + while (node) { + if (node === testAncestor) { + return true; + } + + if (node instanceof HTMLElement) { + const flowToParentElement = getParentFlowToElement(node); + if (flowToParentElement) { + node = flowToParentElement; + continue; + } + } + node = node.parentNode; + } + + return false; +} + export function findParentWithClass(node: HTMLElement, clazz: string, stopAtClazzOrNode?: string | HTMLElement): HTMLElement | null { while (node && node.nodeType === node.ELEMENT_NODE) { if (node.classList.contains(clazz)) { @@ -1331,8 +1373,8 @@ export function safeInnerHtml(node: HTMLElement, value: string): void { const options = _extInsaneOptions({ allowedTags: ['a', 'button', 'blockquote', 'code', 'div', 'h1', 'h2', 'h3', 'input', 'label', 'li', 'p', 'pre', 'select', 'small', 'span', 'strong', 'textarea', 'ul', 'ol'], allowedAttributes: { - 'a': ['href'], - 'button': ['data-href'], + 'a': ['href', 'x-dispatch'], + 'button': ['data-href', 'x-dispatch'], 'input': ['type', 'placeholder', 'checked', 'required'], 'label': ['for'], 'select': ['required'], diff --git a/src/vs/base/browser/globalMouseMoveMonitor.ts b/src/vs/base/browser/globalMouseMoveMonitor.ts index 328ecaee0..4911b59e0 100644 --- a/src/vs/base/browser/globalMouseMoveMonitor.ts +++ b/src/vs/base/browser/globalMouseMoveMonitor.ts @@ -4,11 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import * as platform from 'vs/base/common/platform'; import { IframeUtils } from 'vs/base/browser/iframe'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { BrowserFeatures } from 'vs/base/browser/canIUse'; export interface IStandardMouseMoveEventData { leftButton: boolean; @@ -26,7 +24,7 @@ export interface IMouseMoveCallback { } export interface IOnStopCallback { - (): void; + (browserEvent?: MouseEvent | KeyboardEvent): void; } export function standardMouseMoveMerger(lastEvent: IStandardMouseMoveEventData | null, currentEvent: MouseEvent): IStandardMouseMoveEventData { @@ -52,7 +50,7 @@ export class GlobalMouseMoveMonitor implements I this._hooks.dispose(); } - public stopMonitoring(invokeStopCallback: boolean): void { + public stopMonitoring(invokeStopCallback: boolean, browserEvent?: MouseEvent | KeyboardEvent): void { if (!this.isMonitoring()) { // Not monitoring return; @@ -66,7 +64,7 @@ export class GlobalMouseMoveMonitor implements I this._onStopCallback = null; if (invokeStopCallback && onStopCallback) { - onStopCallback(); + onStopCallback(browserEvent); } } @@ -90,8 +88,8 @@ export class GlobalMouseMoveMonitor implements I this._onStopCallback = onStopCallback; const windowChain = IframeUtils.getSameOriginWindowChain(); - const mouseMove = platform.isIOS && BrowserFeatures.pointerEvents ? 'pointermove' : 'mousemove'; - const mouseUp = platform.isIOS && BrowserFeatures.pointerEvents ? 'pointerup' : 'mouseup'; + const mouseMove = 'mousemove'; + const mouseUp = 'mouseup'; const listenTo: (Document | ShadowRoot)[] = windowChain.map(element => element.window.document); const shadowRoot = dom.getShadowRoot(initialElement); diff --git a/src/vs/base/browser/hash.ts b/src/vs/base/browser/hash.ts new file mode 100644 index 000000000..bf48b614b --- /dev/null +++ b/src/vs/base/browser/hash.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 { VSBuffer } from 'vs/base/common/buffer'; +import { StringSHA1, toHexString } from 'vs/base/common/hash'; + +export async function sha1Hex(str: string): Promise { + + // Prefer to use browser's crypto module + if (globalThis?.crypto?.subtle) { + const hash = await globalThis.crypto.subtle.digest({ name: 'sha-1' }, VSBuffer.fromString(str).buffer); + + return toHexString(hash); + } + + // Otherwise fallback to `StringSHA1` + else { + const computer = new StringSHA1(); + computer.update(str); + + return computer.digest(); + } +} diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 2d601e975..a5cd4c5ec 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -28,7 +28,7 @@ export interface MarkedOptions extends marked.MarkedOptions { export interface MarkdownRenderOptions extends FormattedTextRenderOptions { codeBlockRenderer?: (modeId: string, value: string) => Promise; - codeBlockRenderCallback?: () => void; + asyncRenderCallback?: () => void; baseUrl?: URI; } @@ -177,8 +177,8 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende // ignore }); - if (options.codeBlockRenderCallback) { - promise.then(options.codeBlockRenderCallback); + if (options.asyncRenderCallback) { + promise.then(options.asyncRenderCallback); } return `

${escape(code)}
`; @@ -215,11 +215,15 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende // 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 insane after this so that we don't rely on + // marked for sanitization. markedOptions.sanitizer = (html: string): string => { const match = markdown.isTrusted ? html.match(/^()|(<\/\s*span>)$/) : undefined; return match ? html : ''; }; markedOptions.sanitize = true; + markedOptions.silent = true; + markedOptions.renderer = renderer; // values that are too long will freeze the UI @@ -240,6 +244,17 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende // signal that async code blocks can be now be inserted signalInnerHTML!(); + // signal size changes for image tags + if (options.asyncRenderCallback) { + for (const img of element.getElementsByTagName('img')) { + const listener = DOM.addDisposableListener(img, 'load', () => { + listener.dispose(); + options.asyncRenderCallback!(); + }); + } + } + + return element; } @@ -301,3 +316,76 @@ function getInsaneOptions(options: { readonly isTrusted?: boolean }): InsaneOpti }; } +/** + * Strips all markdown from `markdown`. For example `# Header` would be output as `Header`. + */ +export function renderMarkdownAsPlaintext(markdown: IMarkdownString) { + const renderer = new marked.Renderer(); + + renderer.code = (code: string): string => { + return code; + }; + renderer.blockquote = (quote: string): string => { + return quote; + }; + renderer.html = (_html: string): string => { + return ''; + }; + renderer.heading = (text: string, _level: 1 | 2 | 3 | 4 | 5 | 6, _raw: string): string => { + return text + '\n'; + }; + renderer.hr = (): string => { + return ''; + }; + renderer.list = (body: string, _ordered: boolean): string => { + return body; + }; + renderer.listitem = (text: string): string => { + return text + '\n'; + }; + renderer.paragraph = (text: string): string => { + return text + '\n'; + }; + renderer.table = (header: string, body: string): string => { + return header + body + '\n'; + }; + renderer.tablerow = (content: string): string => { + return content; + }; + renderer.tablecell = (content: string, _flags: { + header: boolean; + align: 'center' | 'left' | 'right' | null; + }): string => { + return content + ' '; + }; + renderer.strong = (text: string): string => { + return text; + }; + renderer.em = (text: string): string => { + return text; + }; + renderer.codespan = (code: string): string => { + return code; + }; + renderer.br = (): string => { + return '\n'; + }; + renderer.del = (text: string): string => { + return text; + }; + renderer.image = (_href: string, _title: string, _text: string): string => { + return ''; + }; + renderer.text = (text: string): string => { + return text; + }; + renderer.link = (_href: string, _title: string, text: string): string => { + return text; + }; + // values that are too long will freeze the UI + let value = markdown.value ?? ''; + if (value.length > 100_000) { + value = `${value.substr(0, 100_000)}…`; + } + return sanitizeRenderedMarkdown({ isTrusted: false }, marked.parse(value, { renderer })).toString(); +} diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 9d206dabe..788a83d55 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -60,6 +60,9 @@ export class ActionBar extends Disposable implements IActionRunner { protected focusedItem?: number; private focusTracker: DOM.IFocusTracker; + // Trigger Key Tracking + private triggerKeyDown: boolean = false; + // Elements domNode: HTMLElement; protected actionsList: HTMLElement; @@ -74,8 +77,8 @@ export class ActionBar extends Disposable implements IActionRunner { private _onDidRun = this._register(new Emitter()); readonly onDidRun = this._onDidRun.event; - private _onDidBeforeRun = this._register(new Emitter()); - readonly onDidBeforeRun = this._onDidBeforeRun.event; + private _onBeforeRun = this._register(new Emitter()); + readonly onBeforeRun = this._onBeforeRun.event; constructor(container: HTMLElement, options: IActionBarOptions = {}) { super(); @@ -96,7 +99,7 @@ export class ActionBar extends Disposable implements IActionRunner { } this._register(this._actionRunner.onDidRun(e => this._onDidRun.fire(e))); - this._register(this._actionRunner.onDidBeforeRun(e => this._onDidBeforeRun.fire(e))); + this._register(this._actionRunner.onBeforeRun(e => this._onBeforeRun.fire(e))); this._actionIds = []; this.viewItems = []; @@ -148,6 +151,8 @@ export class ActionBar extends Disposable implements IActionRunner { // Staying out of the else branch even if not triggered if (this._triggerKeys.keyDown) { this.doTrigger(event); + } else { + this.triggerKeyDown = true; } } else { eventHandled = false; @@ -164,7 +169,8 @@ export class ActionBar extends Disposable implements IActionRunner { // Run action on Enter/Space if (this.isTriggerKeyEvent(event)) { - if (!this._triggerKeys.keyDown) { + if (!this._triggerKeys.keyDown && this.triggerKeyDown) { + this.triggerKeyDown = false; this.doTrigger(event); } @@ -183,6 +189,7 @@ export class ActionBar extends Disposable implements IActionRunner { if (DOM.getActiveElement() === this.domNode || !DOM.isAncestor(DOM.getActiveElement(), this.domNode)) { this._onDidBlur.fire(); this.focusedItem = undefined; + this.triggerKeyDown = false; } })); diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index 27981af27..37b6b03fe 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -11,7 +11,7 @@ import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; -import { Codicon, registerIcon } from 'vs/base/common/codicons'; +import { Codicon, registerCodicon } from 'vs/base/common/codicons'; import 'vs/css!./breadcrumbsWidget'; export abstract class BreadcrumbsItem { @@ -56,7 +56,7 @@ export interface IBreadcrumbsItemEvent { payload: any; } -const breadcrumbSeparatorIcon = registerIcon('breadcrumb-separator', Codicon.chevronRight); +const breadcrumbSeparatorIcon = registerCodicon('breadcrumb-separator', Codicon.chevronRight); export class BreadcrumbsWidget { diff --git a/src/vs/base/browser/ui/button/button.css b/src/vs/base/browser/ui/button/button.css index 6d6823524..217c1656e 100644 --- a/src/vs/base/browser/ui/button/button.css +++ b/src/vs/base/browser/ui/button/button.css @@ -10,7 +10,6 @@ padding: 4px; text-align: center; cursor: pointer; - outline-offset: 2px !important; justify-content: center; align-items: center; } @@ -24,7 +23,15 @@ cursor: default; } -.monaco-button > .codicon { +.monaco-text-button > .codicon { margin: 0 0.2em; color: inherit !important; } + +.monaco-button-dropdown { + display: flex; +} + +.monaco-button-dropdown > .monaco-dropdown-button { + margin-left: 1px; +} diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index a3ca3ac97..b3697f9bf 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -9,10 +9,13 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; import { Event as BaseEvent, Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch'; import { renderCodicons } from 'vs/base/browser/codicons'; import { addDisposableListener, IFocusTracker, EventType, EventHelper, trackFocus, reset, removeTabIndexAndUpdateFocus } from 'vs/base/browser/dom'; +import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; +import { IAction, IActionRunner } from 'vs/base/common/actions'; +import { CSSIcon, Codicon } from 'vs/base/common/codicons'; export interface IButtonOptions extends IButtonStyles { readonly title?: boolean | string; @@ -36,7 +39,18 @@ const defaultOptions: IButtonStyles = { buttonForeground: Color.white }; -export class Button extends Disposable { +export interface IButton extends IDisposable { + readonly element: HTMLElement; + readonly onDidClick: BaseEvent; + label: string; + icon: CSSIcon; + enabled: boolean; + style(styles: IButtonStyles): void; + focus(): void; + hasFocus(): boolean; +} + +export class Button extends Disposable implements IButton { private _element: HTMLElement; private options: IButtonOptions; @@ -188,8 +202,8 @@ export class Button extends Disposable { } } - set icon(iconClassName: string) { - this._element.classList.add(iconClassName); + set icon(icon: CSSIcon) { + this._element.classList.add(...icon.classNames.split(' ')); } set enabled(value: boolean) { @@ -217,47 +231,124 @@ export class Button extends Disposable { } } -export class ButtonGroup extends Disposable { - private _buttons: Button[] = []; +export interface IButtonWithDropdownOptions extends IButtonOptions { + readonly contextMenuProvider: IContextMenuProvider; + readonly actions: IAction[]; + readonly actionRunner?: IActionRunner; +} - constructor(container: HTMLElement, count: number, options?: IButtonOptions) { +export class ButtonWithDropdown extends Disposable implements IButton { + + private readonly button: Button; + private readonly dropdownButton: Button; + + readonly element: HTMLElement; + readonly onDidClick: BaseEvent; + + constructor(container: HTMLElement, options: IButtonWithDropdownOptions) { super(); - this.create(container, count, options); + this.element = document.createElement('div'); + this.element.classList.add('monaco-button-dropdown'); + container.appendChild(this.element); + + this.button = this._register(new Button(this.element, options)); + this.onDidClick = this.button.onDidClick; + + this.dropdownButton = this._register(new Button(this.element, { ...options, title: false, supportCodicons: true })); + this.dropdownButton.element.classList.add('monaco-dropdown-button'); + this.dropdownButton.icon = Codicon.dropDownButton; + this._register(this.dropdownButton.onDidClick(() => { + options.contextMenuProvider.showContextMenu({ + getAnchor: () => this.dropdownButton.element, + getActions: () => options.actions, + actionRunner: options.actionRunner, + onHide: () => this.dropdownButton.element.setAttribute('aria-expanded', 'false') + }); + this.dropdownButton.element.setAttribute('aria-expanded', 'true'); + })); } - get buttons(): Button[] { + set label(value: string) { + this.button.label = value; + } + + set icon(icon: CSSIcon) { + this.button.icon = icon; + } + + set enabled(enabled: boolean) { + this.button.enabled = enabled; + this.dropdownButton.enabled = enabled; + } + + get enabled(): boolean { + return this.button.enabled; + } + + style(styles: IButtonStyles): void { + this.button.style(styles); + this.dropdownButton.style(styles); + } + + focus(): void { + this.button.focus(); + } + + hasFocus(): boolean { + return this.button.hasFocus() || this.dropdownButton.hasFocus(); + } +} + +export class ButtonBar extends Disposable { + + private _buttons: IButton[] = []; + + constructor(private readonly container: HTMLElement) { + super(); + } + + get buttons(): IButton[] { return this._buttons; } - private create(container: HTMLElement, count: number, options?: IButtonOptions): void { - for (let index = 0; index < count; index++) { - const button = this._register(new Button(container, options)); - this._buttons.push(button); - - // Implement keyboard access in buttons if there are multiple - if (count > 1) { - this._register(addDisposableListener(button.element, EventType.KEY_DOWN, e => { - const event = new StandardKeyboardEvent(e); - let eventHandled = true; - - // Next / Previous Button - let buttonIndexToFocus: number | undefined; - if (event.equals(KeyCode.LeftArrow)) { - buttonIndexToFocus = index > 0 ? index - 1 : this._buttons.length - 1; - } else if (event.equals(KeyCode.RightArrow)) { - buttonIndexToFocus = index === this._buttons.length - 1 ? 0 : index + 1; - } else { - eventHandled = false; - } - - if (eventHandled && typeof buttonIndexToFocus === 'number') { - this._buttons[buttonIndexToFocus].focus(); - EventHelper.stop(e, true); - } - - })); - } - } + addButton(options?: IButtonOptions): IButton { + const button = this._register(new Button(this.container, options)); + this.pushButton(button); + return button; } + + addButtonWithDropdown(options: IButtonWithDropdownOptions): IButton { + const button = this._register(new ButtonWithDropdown(this.container, options)); + this.pushButton(button); + return button; + } + + private pushButton(button: IButton): void { + this._buttons.push(button); + + const index = this._buttons.length - 1; + this._register(addDisposableListener(button.element, EventType.KEY_DOWN, e => { + const event = new StandardKeyboardEvent(e); + let eventHandled = true; + + // Next / Previous Button + let buttonIndexToFocus: number | undefined; + if (event.equals(KeyCode.LeftArrow)) { + buttonIndexToFocus = index > 0 ? index - 1 : this._buttons.length - 1; + } else if (event.equals(KeyCode.RightArrow)) { + buttonIndexToFocus = index === this._buttons.length - 1 ? 0 : index + 1; + } else { + eventHandled = false; + } + + if (eventHandled && typeof buttonIndexToFocus === 'number') { + this._buttons[buttonIndexToFocus].focus(); + EventHelper.stop(e, true); + } + + })); + + } + } diff --git a/src/vs/base/browser/ui/checkbox/checkbox.ts b/src/vs/base/browser/ui/checkbox/checkbox.ts index c3b0e243f..932a28602 100644 --- a/src/vs/base/browser/ui/checkbox/checkbox.ts +++ b/src/vs/base/browser/ui/checkbox/checkbox.ts @@ -11,12 +11,12 @@ import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { Codicon } from 'vs/base/common/codicons'; +import { Codicon, CSSIcon } from 'vs/base/common/codicons'; import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; export interface ICheckboxOpts extends ICheckboxStyles { readonly actionClassName?: string; - readonly icon?: Codicon; + readonly icon?: CSSIcon; readonly title: string; readonly isChecked: boolean; } diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 4c16209d6a9203bb2b9f9b2e160fc20e947e7f36..7fdf6ed7b9db8580b4a762998936d06b55c37b51 100644 GIT binary patch delta 5614 zcmZA53tX1<*$437^?=|25#iw|M^R29$U)8`qJjqmJRzQtgD4sT9#S*&F*7T3WYS#o zE$6J6(Ytb^+PmIz&ACo9=bW`N*UB8T|9>@RX3mwpzuT*KecqSP=X+oG!}IX)+}zjy zx~@N0`n|3guRV#LZGh|sP}j1$sq5-n!5M&WJP>kzMf+{bd)M#W3&cDEoG5E+ZCVy} z_T3$P?FW?C#&7ukaZnD=U!;+3tJiOeILb4~MPN`#duL12%Irr=fT%A4*TL0Io4Q=9 z@Cu)g;EUornpU?aJQ+3dPT<4ye1TtA=eqS9dp}+Td~^vIw(G$c6DQO_oHqpC`Rt3n zr9(?Thrbt}1m3$iz2V#V^>wdLygugR!Eia>liwE!^_TnMz!}swpet`eLBX|@C@IxHL zk8lW&;m3FaNAMJ$#xppIpW<0Ohv)Hg{1^TkFX0$|iI?$foL~yPiZ}3E{0@J>S-gd} z@eba_IlPDS_!B zLMM8pgsC+YQ}H-n#}|@@-{XGf(Ra`hQ>lBv|%z7pv z58!Hoxm9sp!E8_%%eYZ-t-)+kTy-$FDXwFf&5A1#=61z33A06Ub;8`CxK3fVD!k_5 z$v3!oVYVq8XS`E!LBniUT-q>qDK2uD9SSV0=58gKj60Q60L?uL&Y$18RV1m5yU4?X z4CBfDO5z!vHzSEI^Gn6L2J^CF zg@gH(Vy%O5{@lc>2lH#i`Uk_+;b0|%`Hf;tgn31=I>NjPRt_FmC}Cb#ESWGT6^kd# zn~G%==9FSVh54OgX@xnhSY%;-uUKwj&L|dMm_I0%V3@Ot#h6+A79SAnGR)hG6&l7# zd19@Gc}KBo!@R3lzhTZPR&tp46l*%ndBy4u^C!hR5A(ia#fSN`!^2p& z%-%Yo`~NjxbRXEj z6dNh5D7I8smtu2;^-^rJu-=Ld7j}?h>xK1EY{IaZXSnQ@fj^a7itILE*yD`%*+DT=cVZ0b!OaLj>CQyh3; zM=Op#u<43J5bPMmaR@d;aWI0-R2-3D$0`m>uvv;@6KuBP00o<)I7-3h^7q?ZUj2E- zafW%KTdX*n!ImgaX|UrJ=QY?;_0 zGd3%UVQf(n$GA+%2*y?=BN>+~Nn%`~#7W#XB~Ie5RN^G7XB7`5PPX2n#L3onB~G@k zR>DNJ9ZH-8?Ns6Y6a)S~lDK{!{l5&$0 zCn;}J;w0r}B~BvVuEa^iElQk(yF&?c&2ClV5i63HE@(7mPnt+(W@0RNPI${z$>Orb9}=_?Y5e3)XpG!kdggR@{HV z9(GCt58R5uKB2fNgFT|)q>XbIh#NIn=PnSpY_Lx$Zr)&>J3`#X!8&(@xS@l6T5)R! z`;6iy5B8|S2k3bwDs0qgK6G9}-21^ktGEkkX0dDLym=9a=YDi?)FeuXhG<*(Bq*O!ve!{!F79kV{>NX#3t z^|4!G55^vki-?;YcW=Ba{%C@CLUqEq5o;4;6PrgyjeK_0=A?k6*-6Wjo=-+{N^(c? zJ1J2qRVn*Zk(!swi&b1uU>!?QM{JLBohhRn^G zhceHNtr&YPYi`!AtV`MM?4s;t*$?Lg6w8^6K&p8hpOOHVbr8h11vZra*(ra8WEST z|F7bktLy6?d;5}>Z!gFV_Rdk+{DA!MNy!<)tMYMkxrBuzCg+Fq-*{IxE^jF+DJg1M z{wYt2i+Pft-krWleA6d54{m-}{{6hT^tryl-Q(v+iW(>HuGzkZMK;iW>Z=Dg~F2Ht~Q02(D#&YoNr*71BG5ODcG3`-MQ)MVZADgED@HzxO5g9$)M} z|JDEF&FkJjdH=x2P2lBz%71)~#!E<)&KQe8 z>5uuSz(sz%H*io=PzMiIOT2_(s>Dj5L`iE2l4$8DG13Zaq`9=KyW!i$zb`V8g>3Xk zZe47Xww})Df;eQ9R!f1>^F>c2=j7JG3 zpcE4^31yg!DVT;CxD&H58&#NtyD%5?a5wHjH5OnY7NG|BVlis51WWmx`>+hlJy?lV zcmNM#9oFL^Y{bLZgh%iXY{5UV6_4UEJdSO60#D&-?7%KOhv)GEUc?^MV=w-NSMYEA zhZ*rY4&Y53!eP9PBX|$TZ~`CUV|;>7aSET|G(N`}oW zD<&#Tg<{IWOi}oPajIe(!%S0npK-ckYQx;&aW{PD{fsjd^Bv|+g*3)W#WMgiQ{gYh zSxVd=I$QCCz*H%o6_`1Srv~OO#q$F*SHa^x)jY*R1#`FJ@q(GJkjQwC;*o=?Ry=$# z3lvyu%tFNj3A0G?Xu{Me9#RW-;J#H?A-l_e~JbFifq&LB=JD=Ne|I;t7Yj zPw}k7EK^v`xLip*;|j$}0JBnID&s08v3#|0FCuZD-~lD9#Ac0>){N_v1Tn5x63w_l zNk_(ql*BN)Kaf~%U^Xha-}kT*R(Z3@@!^x&)$MEgM&G`C_>5v*g4wB9ph!Qmle7hZXA~ z%-f2U5yo9LVvU4(*Ii29uw25tr&u^)jwzN<7@j0H2SnXlXD%O3NuN@CxP|7>!6q^CeHwu#&&nq?vm~RzZ1G$_9tR zfMFXewqjUc#ik6~M6o@?-lp&fufO$E?AfsXirpJFK(UX*HdXBCu&g-_dpm59VwZ;v zR_yn%%@npVhA7Seu%U|60Bm#e@P>l{Y?$JR02{72EWox<92;O;Dh?2^trSNI*a*d; z0=Bi{cmW%!IB38|DUKYl(Tc+dJhlxV5a$rs7{!SMwyol90^3fxxz)B;oL68wC{8Z0 z9TjI7*jUAB2DX#pTm#!#al(P^atpuzBfJx*IQGDHRUCj|;}u6C*aXF)2sTl1Jc8|} zI4Hp;DUM9A$%?}hY>MI-1>0S5pn^^1``c80`p+4AC{9_hX^Qg}Y){3>3pQQh9AhuV zX$-cv;#>yXM{z=f%}|`xVEZagZLpcj&2O+-ijy2{w&F|&o1@&(u>BP0JlOt<6CZ4@ z;_L@IKyeC$%~PBQVFxNshOmPaXGGY+?)vkF<09-3#laDFsNx6-o3A)b!VXg$D`5*1 z2Ta&P#ZeP>gu+e6kxKX$c9ar6utiFIc>e8ZJ|JntI7W#t<5(q47>kwM#(29DcT~qI z31A$rB$%;8Ni)U?N~h5g1a^huDgwJw zaVddarMRBJ-mfssUH8?BI}7XsirWk98U^>~>96`Pgxs@vA8y~ll9TNQUD*hdw&CfLUmt};HZxJkio zQ{1UwpHSScVBPD9dlu|diW?W~c7>yiPb+R=usd97;0+ftSoducS2I}mZ4+Fo>{61= z=)QU4;s)!!dEyEO`<&u32m8F@S_k`r;(`bJqT;Ft`;y|)SNCQ_xc3J@-HnLRod%_p|zohLQglJ)qH#N6Jd#Ag<%WB zwuK!JyAmEAUJ$-Ce0TVn7Je<#TkLF^(z4Ria#hRYtx{Umwz?8g7_m0uQtR^8dmC#5>&p_I$rqq|ph-`)Mk z)Y#OL)U~OHdj#~z=uwmAmsZ|$YR?@#&!>+}f2~(cuj*dcdgu4v+xtlGzxxd8v#QVb zJ}3J$WYlCF>|50LU}jwA=FDSR*;%z&JF}Z)$7QGI1mvvBd8uEzr{9|Xjr-5*e>^uX zw={Q4?%xBZ4>*zMmzSD1CT~;Tg@J(s^9C*(czICDpz6VXgBK2N7?L~W=+J8jK5pK) z72`IK_ZmNB{FCEPmBg3qozQqf#)R4l=St&C7ng1?JuOWYQQ6%~Geit@-)$hFciqx;XG{?8z=gRCx{{sO&gkb;x diff --git a/src/vs/base/browser/ui/codicons/codiconStyles.ts b/src/vs/base/browser/ui/codicons/codiconStyles.ts index 8229f2f1c..442811967 100644 --- a/src/vs/base/browser/ui/codicons/codiconStyles.ts +++ b/src/vs/base/browser/ui/codicons/codiconStyles.ts @@ -7,18 +7,7 @@ import 'vs/css!./codicon/codicon'; import 'vs/css!./codicon/codicon-modifications'; import 'vs/css!./codicon/codicon-animations'; -import { Codicon, iconRegistry } from 'vs/base/common/codicons'; - -export const CodiconStyles = new class { - onDidChange = iconRegistry.onDidRegister; - public getCSS(): string { - const rules = []; - for (let c of iconRegistry.all) { - rules.push(formatRule(c)); - } - return rules.join('\n'); - } -}; +import { Codicon } from 'vs/base/common/codicons'; export function formatRule(c: Codicon) { let def = c.definition; diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index 97e78cefe..ef695b6a5 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -31,6 +31,10 @@ export const enum AnchorPosition { BELOW, ABOVE } +export const enum AnchorAxisAlignment { + VERTICAL, HORIZONTAL +} + export interface IDelegate { getAnchor(): HTMLElement | IAnchor; render(container: HTMLElement): IDisposable | null; @@ -38,6 +42,7 @@ export interface IDelegate { layout?(): void; anchorAlignment?: AnchorAlignment; // default: left anchorPosition?: AnchorPosition; // default: below + anchorAxisAlignment?: AnchorAxisAlignment; // default: vertical canRelayout?: boolean; // default: true onDOMEvent?(e: Event, activeElement: HTMLElement): void; onHide?(data?: any): void; @@ -66,9 +71,15 @@ export const enum LayoutAnchorPosition { After } +export enum LayoutAnchorMode { + AVOID, + ALIGN +} + export interface ILayoutAnchor { offset: number; size: number; + mode?: LayoutAnchorMode; // default: AVOID position: LayoutAnchorPosition; } @@ -78,25 +89,26 @@ export interface ILayoutAnchor { * @returns The view offset within the viewport. */ export function layout(viewportSize: number, viewSize: number, anchor: ILayoutAnchor): number { - const anchorEnd = anchor.offset + anchor.size; + const layoutAfterAnchorBoundary = anchor.mode === LayoutAnchorMode.ALIGN ? anchor.offset : anchor.offset + anchor.size; + const layoutBeforeAnchorBoundary = anchor.mode === LayoutAnchorMode.ALIGN ? anchor.offset + anchor.size : anchor.offset; if (anchor.position === LayoutAnchorPosition.Before) { - if (viewSize <= viewportSize - anchorEnd) { - return anchorEnd; // happy case, lay it out after the anchor + if (viewSize <= viewportSize - layoutAfterAnchorBoundary) { + return layoutAfterAnchorBoundary; // happy case, lay it out after the anchor } - if (viewSize <= anchor.offset) { - return anchor.offset - viewSize; // ok case, lay it out before the anchor + if (viewSize <= layoutBeforeAnchorBoundary) { + return layoutBeforeAnchorBoundary - viewSize; // ok case, lay it out before the anchor } return Math.max(viewportSize - viewSize, 0); // sad case, lay it over the anchor } else { - if (viewSize <= anchor.offset) { - return anchor.offset - viewSize; // happy case, lay it out before the anchor + if (viewSize <= layoutBeforeAnchorBoundary) { + return layoutBeforeAnchorBoundary - viewSize; // happy case, lay it out before the anchor } - if (viewSize <= viewportSize - anchorEnd) { - return anchorEnd; // ok case, lay it out after the anchor + if (viewSize <= viewportSize - layoutAfterAnchorBoundary) { + return layoutAfterAnchorBoundary; // ok case, lay it out after the anchor } return 0; // sad case, lay it over the anchor @@ -270,28 +282,36 @@ export class ContextView extends Disposable { const anchorPosition = this.delegate!.anchorPosition || AnchorPosition.BELOW; const anchorAlignment = this.delegate!.anchorAlignment || AnchorAlignment.LEFT; + const anchorAxisAlignment = this.delegate!.anchorAxisAlignment || AnchorAxisAlignment.VERTICAL; - const verticalAnchor: ILayoutAnchor = { offset: around.top - window.pageYOffset, size: around.height, position: anchorPosition === AnchorPosition.BELOW ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After }; + let top: number; + let left: number; - let horizontalAnchor: ILayoutAnchor; + if (anchorAxisAlignment === AnchorAxisAlignment.VERTICAL) { + const verticalAnchor: ILayoutAnchor = { offset: around.top - window.pageYOffset, size: around.height, position: anchorPosition === AnchorPosition.BELOW ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After }; + const horizontalAnchor: ILayoutAnchor = { offset: around.left, size: around.width, position: anchorAlignment === AnchorAlignment.LEFT ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After, mode: LayoutAnchorMode.ALIGN }; - if (anchorAlignment === AnchorAlignment.LEFT) { - horizontalAnchor = { offset: around.left, size: 0, position: LayoutAnchorPosition.Before }; - } else { - horizontalAnchor = { offset: around.left + around.width, size: 0, position: LayoutAnchorPosition.After }; - } + top = layout(window.innerHeight, viewSizeHeight, verticalAnchor) + window.pageYOffset; - const top = layout(window.innerHeight, viewSizeHeight, verticalAnchor) + window.pageYOffset; - - // if view intersects vertically with anchor, shift it horizontally - if (Range.intersects({ start: top, end: top + viewSizeHeight }, { start: verticalAnchor.offset, end: verticalAnchor.offset + verticalAnchor.size })) { - horizontalAnchor.size = around.width; - if (anchorAlignment === AnchorAlignment.RIGHT) { - horizontalAnchor.offset = around.left; + // if view intersects vertically with anchor, we must avoid the anchor + if (Range.intersects({ start: top, end: top + viewSizeHeight }, { start: verticalAnchor.offset, end: verticalAnchor.offset + verticalAnchor.size })) { + horizontalAnchor.mode = LayoutAnchorMode.AVOID; } - } - const left = layout(window.innerWidth, viewSizeWidth, horizontalAnchor); + left = layout(window.innerWidth, viewSizeWidth, horizontalAnchor); + } else { + const horizontalAnchor: ILayoutAnchor = { offset: around.left, size: around.width, position: anchorAlignment === AnchorAlignment.LEFT ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After }; + const verticalAnchor: ILayoutAnchor = { offset: around.top, size: around.height, position: anchorPosition === AnchorPosition.BELOW ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After, mode: LayoutAnchorMode.ALIGN }; + + left = layout(window.innerWidth, viewSizeWidth, horizontalAnchor); + + // if view intersects horizontally with anchor, we must avoid the anchor + if (Range.intersects({ start: left, end: left + viewSizeWidth }, { start: horizontalAnchor.offset, end: horizontalAnchor.offset + horizontalAnchor.size })) { + verticalAnchor.mode = LayoutAnchorMode.AVOID; + } + + top = layout(window.innerHeight, viewSizeHeight, verticalAnchor) + window.pageYOffset; + } this.view.classList.remove('top', 'bottom', 'left', 'right'); this.view.classList.add(anchorPosition === AnchorPosition.BELOW ? 'bottom' : 'top'); diff --git a/src/vs/base/browser/ui/dialog/dialog.css b/src/vs/base/browser/ui/dialog/dialog.css index 1f1180b8f..7c5ffc1c3 100644 --- a/src/vs/base/browser/ui/dialog/dialog.css +++ b/src/vs/base/browser/ui/dialog/dialog.css @@ -148,7 +148,8 @@ width: fit-content; width: -moz-fit-content; padding: 5px 10px; - margin: 4px 5px; /* allows button focus outline to be visible */ overflow: hidden; text-overflow: ellipsis; + margin: 4px 5px; /* allows button focus outline to be visible */ + outline-offset: 2px !important; } diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts index 097e879e3..4d8a075df 100644 --- a/src/vs/base/browser/ui/dialog/dialog.ts +++ b/src/vs/base/browser/ui/dialog/dialog.ts @@ -11,13 +11,13 @@ import { domEvent } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Color } from 'vs/base/common/color'; -import { ButtonGroup, IButtonStyles } from 'vs/base/browser/ui/button/button'; +import { ButtonBar, IButtonStyles } from 'vs/base/browser/ui/button/button'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { isMacintosh, isLinux } from 'vs/base/common/platform'; import { SimpleCheckbox, ISimpleCheckboxStyles } from 'vs/base/browser/ui/checkbox/checkbox'; -import { Codicon, registerIcon } from 'vs/base/common/codicons'; +import { Codicon, registerCodicon } from 'vs/base/common/codicons'; import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; export interface IDialogInputOptions { @@ -60,10 +60,10 @@ interface ButtonMapEntry { readonly index: number; } -const dialogErrorIcon = registerIcon('dialog-error', Codicon.error); -const dialogWarningIcon = registerIcon('dialog-warning', Codicon.warning); -const dialogInfoIcon = registerIcon('dialog-info', Codicon.info); -const dialogCloseIcon = registerIcon('dialog-close', Codicon.close); +const dialogErrorIcon = registerCodicon('dialog-error', Codicon.error); +const dialogWarningIcon = registerCodicon('dialog-warning', Codicon.warning); +const dialogInfoIcon = registerCodicon('dialog-info', Codicon.info); +const dialogCloseIcon = registerCodicon('dialog-close', Codicon.close); export class Dialog extends Disposable { private readonly element: HTMLElement; @@ -74,7 +74,7 @@ export class Dialog extends Disposable { private readonly iconElement: HTMLElement; private readonly checkbox: SimpleCheckbox | undefined; private readonly toolbarContainer: HTMLElement; - private buttonGroup: ButtonGroup | undefined; + private buttonBar: ButtonBar | undefined; private styles: IDialogStyles | undefined; private focusToReturn: HTMLElement | undefined; private readonly inputs: InputBox[]; @@ -173,11 +173,12 @@ export class Dialog extends Disposable { return new Promise((resolve) => { clearNode(this.buttonsContainer); - const buttonGroup = this.buttonGroup = this._register(new ButtonGroup(this.buttonsContainer, this.buttons.length, { title: true })); + const buttonBar = this.buttonBar = this._register(new ButtonBar(this.buttonsContainer)); const buttonMap = this.rearrangeButtons(this.buttons, this.options.cancelId); // Handle button clicks - buttonGroup.buttons.forEach((button, index) => { + buttonMap.forEach((entry, index) => { + const button = this._register(buttonBar.addButton({ title: true })); button.label = mnemonicButtonLabel(buttonMap[index].label, true); this._register(button.onDidClick(e => { @@ -237,8 +238,8 @@ export class Dialog extends Disposable { } } - if (this.buttonGroup) { - for (const button of this.buttonGroup.buttons) { + if (this.buttonBar) { + for (const button of this.buttonBar.buttons) { focusableElements.push(button); if (button.hasFocus()) { focusedIndex = focusableElements.length - 1; @@ -349,7 +350,7 @@ export class Dialog extends Disposable { } else { buttonMap.forEach((value, index) => { if (value.index === 0) { - buttonGroup.buttons[index].focus(); + buttonBar.buttons[index].focus(); } }); } @@ -371,8 +372,8 @@ export class Dialog extends Disposable { this.element.style.backgroundColor = bgColor?.toString() ?? ''; this.element.style.border = border; - if (this.buttonGroup) { - this.buttonGroup.buttons.forEach(button => button.style(style)); + if (this.buttonBar) { + this.buttonBar.buttons.forEach(button => button.style(style)); } if (this.checkbox) { diff --git a/src/vs/base/browser/ui/dropdown/dropdown.css b/src/vs/base/browser/ui/dropdown/dropdown.css index fb87b8c5d..77060ee20 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.css +++ b/src/vs/base/browser/ui/dropdown/dropdown.css @@ -12,3 +12,7 @@ cursor: pointer; height: 100%; } + +.monaco-dropdown > .dropdown-label > .action-label.disabled { + cursor: default; +} diff --git a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts index 5713a8a93..ea32bdc07 100644 --- a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts +++ b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts @@ -13,6 +13,7 @@ import { Emitter } from 'vs/base/common/event'; import { ActionViewItem, BaseActionViewItem, IActionViewItemOptions, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IActionProvider, DropdownMenu, IDropdownMenuOptions, ILabelRenderer } from 'vs/base/browser/ui/dropdown/dropdown'; import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; +import { Codicon } from 'vs/base/common/codicons'; export interface IKeybindingProvider { (action: IAction): ResolvedKeybinding | undefined; @@ -35,6 +36,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { private menuActionsOrProvider: readonly IAction[] | IActionProvider; private dropdownMenu: DropdownMenu | undefined; private contextMenuProvider: IContextMenuProvider; + private actionItem: HTMLElement | null = null; private _onDidChangeVisibility = this._register(new Emitter()); readonly onDidChangeVisibility = this._onDidChangeVisibility.event; @@ -56,6 +58,8 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { } render(container: HTMLElement): void { + this.actionItem = container; + const labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable | null => { this.element = append(el, $('a.action-label')); @@ -115,6 +119,8 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { } }; } + + this.updateEnabled(); } setActionContext(newContext: unknown): void { @@ -134,6 +140,12 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { this.dropdownMenu.show(); } } + + protected updateEnabled(): void { + const disabled = !this.getAction().enabled; + this.actionItem?.classList.toggle('disabled', disabled); + this.element?.classList.toggle('disabled', disabled); + } } export interface IActionWithDropdownActionViewItemOptions extends IActionViewItemOptions { @@ -158,7 +170,7 @@ export class ActionWithDropdownActionViewItem extends ActionViewItem { super.render(container); if (this.element) { this.element.classList.add('action-dropdown-item'); - this.dropdownMenuActionViewItem = new DropdownMenuActionViewItem(new Action('dropdownAction', undefined), (this.options).menuActionsOrProvider, this.contextMenuProvider, { classNames: ['dropdown', 'codicon-chevron-down', ...(this.options).menuActionClassNames || []] }); + this.dropdownMenuActionViewItem = new DropdownMenuActionViewItem(new Action('dropdownAction', undefined), (this.options).menuActionsOrProvider, this.contextMenuProvider, { classNames: ['dropdown', ...Codicon.dropDownButton.classNamesArray, ...(this.options).menuActionClassNames || []] }); this.dropdownMenuActionViewItem.render(this.element); } } diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts index db736d5da..22d8a75e9 100644 --- a/src/vs/base/browser/ui/grid/grid.ts +++ b/src/vs/base/browser/ui/grid/grid.ts @@ -175,7 +175,7 @@ function getGridLocation(element: HTMLElement): number[] { } const index = indexInParent(parentElement); - const ancestor = parentElement.parentElement!.parentElement!.parentElement!; + const ancestor = parentElement.parentElement!.parentElement!.parentElement!.parentElement!; return [...getGridLocation(ancestor), index]; } @@ -215,6 +215,8 @@ export class Grid extends Disposable { get boundarySashes(): IBoundarySashes { return this.gridview.boundarySashes; } set boundarySashes(boundarySashes: IBoundarySashes) { this.gridview.boundarySashes = boundarySashes; } + set edgeSnapping(edgeSnapping: boolean) { this.gridview.edgeSnapping = edgeSnapping; } + get element(): HTMLElement { return this.gridview.element; } private didLayout = false; diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index b08b72a94..323f71fd6 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -170,6 +170,7 @@ class BranchNode implements ISplitView, IDisposable { private absoluteOffset: number = 0; private absoluteOrthogonalOffset: number = 0; + private absoluteOrthogonalSize: number = 0; private _styles: IGridViewStyles; get styles(): IGridViewStyles { return this._styles; } @@ -270,6 +271,24 @@ class BranchNode implements ISplitView, IDisposable { } } + private _edgeSnapping = false; + get edgeSnapping(): boolean { return this._edgeSnapping; } + set edgeSnapping(edgeSnapping: boolean) { + if (this._edgeSnapping === edgeSnapping) { + return; + } + + this._edgeSnapping = edgeSnapping; + + for (const child of this.children) { + if (child instanceof BranchNode) { + child.edgeSnapping = edgeSnapping; + } + } + + this.updateSplitviewEdgeSnappingEnablement(); + } + constructor( readonly orientation: Orientation, readonly layoutController: ILayoutController, @@ -277,6 +296,7 @@ class BranchNode implements ISplitView, IDisposable { readonly proportionalLayout: boolean, size: number = 0, orthogonalSize: number = 0, + edgeSnapping: boolean = false, childDescriptors?: INodeDescriptor[] ) { this._styles = styles; @@ -355,6 +375,7 @@ class BranchNode implements ISplitView, IDisposable { this._orthogonalSize = size; this.absoluteOffset = ctx.absoluteOffset + offset; this.absoluteOrthogonalOffset = ctx.absoluteOrthogonalOffset; + this.absoluteOrthogonalSize = ctx.absoluteOrthogonalSize; this.splitview.layout(ctx.orthogonalSize, { orthogonalSize: size, @@ -364,9 +385,7 @@ class BranchNode implements ISplitView, IDisposable { absoluteOrthogonalSize: ctx.absoluteSize }); - // Disable snapping on views which sit on the edges of the grid - this.splitview.startSnappingEnabled = this.absoluteOrthogonalOffset > 0; - this.splitview.endSnappingEnabled = this.absoluteOrthogonalOffset + ctx.orthogonalSize < ctx.absoluteOrthogonalSize; + this.updateSplitviewEdgeSnappingEnablement(); } setVisible(visible: boolean): void { @@ -607,6 +626,11 @@ class BranchNode implements ISplitView, IDisposable { }); } + private updateSplitviewEdgeSnappingEnablement(): void { + this.splitview.startSnappingEnabled = this._edgeSnapping || this.absoluteOrthogonalOffset > 0; + this.splitview.endSnappingEnabled = this._edgeSnapping || this.absoluteOrthogonalOffset + this._size < this.absoluteOrthogonalSize; + } + dispose(): void { for (const child of this.children) { child.dispose(); @@ -775,7 +799,7 @@ export interface INodeDescriptor { function flipNode(node: T, size: number, orthogonalSize: number): T { if (node instanceof BranchNode) { - const result = new BranchNode(orthogonal(node.orientation), node.layoutController, node.styles, node.proportionalLayout, size, orthogonalSize); + const result = new BranchNode(orthogonal(node.orientation), node.layoutController, node.styles, node.proportionalLayout, size, orthogonalSize, node.edgeSnapping); let totalSize = 0; @@ -863,6 +887,10 @@ export class GridView implements IDisposable { this.root.boundarySashes = fromAbsoluteBoundarySashes(boundarySashes, this.orientation); } + set edgeSnapping(edgeSnapping: boolean) { + this.root.edgeSnapping = edgeSnapping; + } + /** * The first layout controller makes sure layout only propagates * to the views after the very first call to gridview.layout() @@ -932,7 +960,7 @@ export class GridView implements IDisposable { grandParent.removeChild(parentIndex); - const newParent = new BranchNode(parent.orientation, parent.layoutController, this.styles, this.proportionalLayout, parent.size, parent.orthogonalSize); + const newParent = new BranchNode(parent.orientation, parent.layoutController, this.styles, this.proportionalLayout, parent.size, parent.orthogonalSize, grandParent.edgeSnapping); grandParent.addChild(newParent, parent.size, parentIndex); const newSibling = new LeafNode(parent.view, grandParent.orientation, this.layoutController, parent.size); @@ -1205,7 +1233,7 @@ export class GridView implements IDisposable { } as INodeDescriptor; }); - result = new BranchNode(orientation, this.layoutController, this.styles, this.proportionalLayout, node.size, orthogonalSize, children); + result = new BranchNode(orientation, this.layoutController, this.styles, this.proportionalLayout, node.size, orthogonalSize, undefined, children); } else { result = new LeafNode(deserializer.fromJSON(node.data), orientation, this.layoutController, orthogonalSize, node.size); } diff --git a/src/vs/base/browser/ui/hover/hover.css b/src/vs/base/browser/ui/hover/hover.css index a5390be41..c2e11fa89 100644 --- a/src/vs/base/browser/ui/hover/hover.css +++ b/src/vs/base/browser/ui/hover/hover.css @@ -87,7 +87,6 @@ .monaco-hover .monaco-tokenized-source { white-space: pre-wrap; - word-break: break-all; } .monaco-hover .hover-row.status-bar { diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index 18a32bf99..44c56834f 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -14,7 +14,7 @@ import { isMacintosh } from 'vs/base/common/platform'; import { IHoverDelegate, IHoverDelegateOptions, IHoverDelegateTarget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { AnchorPosition } from 'vs/base/browser/ui/contextview/contextview'; import { IMarkdownString } from 'vs/base/common/htmlContent'; -import { isString } from 'vs/base/common/types'; +import { isFunction, isString } from 'vs/base/common/types'; import { domEvent } from 'vs/base/browser/event'; export interface IIconLabelCreationOptions { @@ -24,8 +24,13 @@ export interface IIconLabelCreationOptions { hoverDelegate?: IHoverDelegate; } +export interface IIconLabelMarkdownString { + markdown: IMarkdownString | string | undefined | (() => Promise); + markdownNotSupportedFallback: string | undefined; +} + export interface IIconLabelValueOptions { - title?: string | IMarkdownString | Promise; + title?: string | IIconLabelMarkdownString; descriptionTitle?: string; hideIcon?: boolean; extraClasses?: string[]; @@ -93,6 +98,8 @@ export class IconLabel extends Disposable { private descriptionNode: FastLabelNode | HighlightedLabel | undefined; private descriptionNodeFactory: () => FastLabelNode | HighlightedLabel; + private labelContainer: HTMLElement; + private hoverDelegate: IHoverDelegate | undefined = undefined; private readonly customHovers: Map = new Map(); @@ -101,10 +108,10 @@ export class IconLabel extends Disposable { this.domNode = this._register(new FastLabelNode(dom.append(container, dom.$('.monaco-icon-label')))); - const labelContainer = dom.append(this.domNode.element, dom.$('.monaco-icon-label-container')); + this.labelContainer = dom.append(this.domNode.element, dom.$('.monaco-icon-label-container')); - const nameContainer = dom.append(labelContainer, dom.$('span.monaco-icon-name-container')); - this.descriptionContainer = this._register(new FastLabelNode(dom.append(labelContainer, dom.$('span.monaco-icon-description-container')))); + const nameContainer = dom.append(this.labelContainer, dom.$('span.monaco-icon-name-container')); + this.descriptionContainer = this._register(new FastLabelNode(dom.append(this.labelContainer, dom.$('span.monaco-icon-description-container')))); if (options?.supportHighlights) { this.nameNode = new LabelWithHighlights(nameContainer, !!options.supportCodicons); @@ -144,7 +151,7 @@ export class IconLabel extends Disposable { } this.domNode.className = classes.join(' '); - this.setupHover(this.domNode.element, options?.title); + this.setupHover(this.labelContainer, options?.title); this.nameNode.setLabel(label, options); @@ -164,7 +171,7 @@ export class IconLabel extends Disposable { } } - private setupHover(htmlElement: HTMLElement, tooltip: string | IMarkdownString | Promise | undefined): void { + private setupHover(htmlElement: HTMLElement, tooltip: string | IIconLabelMarkdownString | undefined): void { const previousCustomHover = this.customHovers.get(htmlElement); if (previousCustomHover) { previousCustomHover.dispose(); @@ -183,22 +190,39 @@ export class IconLabel extends Disposable { } } - private setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, tooltip: string | IMarkdownString | Promise | undefined): void { + private setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, markdownTooltip: string | IIconLabelMarkdownString): void { htmlElement.removeAttribute('title'); + let tooltip: () => Promise; + if (isString(markdownTooltip)) { + tooltip = async () => markdownTooltip; + } else if (isFunction(markdownTooltip.markdown)) { + tooltip = markdownTooltip.markdown; + } else { + const markdown = markdownTooltip.markdown; + tooltip = async () => markdown; + } // Testing has indicated that on Windows and Linux 500 ms matches the native hovers most closely. // On Mac, the delay is 1500. const hoverDelay = isMacintosh ? 1500 : 500; let hoverOptions: IHoverDelegateOptions | undefined; let mouseX: number | undefined; + let isHovering = false; function mouseOver(this: HTMLElement, e: MouseEvent): any { - let isHovering = true; + if (isHovering) { + return; + } + function mouseLeaveOrDown(this: HTMLElement, e: MouseEvent): any { + isHovering = false; + mouseLeaveDisposable.dispose(); + mouseDownDisposable.dispose(); + } + const mouseLeaveDisposable = domEvent(htmlElement, dom.EventType.MOUSE_LEAVE, true)(mouseLeaveOrDown.bind(htmlElement)); + const mouseDownDisposable = domEvent(htmlElement, dom.EventType.MOUSE_DOWN, true)(mouseLeaveOrDown.bind(htmlElement)); + isHovering = true; + function mouseMove(this: HTMLElement, e: MouseEvent): any { mouseX = e.x; } - function mouseLeave(this: HTMLElement, e: MouseEvent): any { - isHovering = false; - } - const mouseLeaveDisposable = domEvent(htmlElement, dom.EventType.MOUSE_LEAVE, true)(mouseLeave.bind(htmlElement)); const mouseMoveDisposable = domEvent(htmlElement, dom.EventType.MOUSE_MOVE, true)(mouseMove.bind(htmlElement)); setTimeout(async () => { if (isHovering && tooltip) { @@ -208,7 +232,7 @@ export class IconLabel extends Disposable { targetElements: [this], dispose: () => { } }; - const resolvedTooltip = await tooltip; + const resolvedTooltip = await tooltip(); if (resolvedTooltip) { hoverOptions = { text: resolvedTooltip, @@ -217,7 +241,8 @@ export class IconLabel extends Disposable { }; } } - if (hoverOptions) { + // awaiting the tooltip could take a while. Make sure we're still hovering. + if (hoverOptions && isHovering) { if (mouseX !== undefined) { (hoverOptions.target).x = mouseX + 10; } @@ -225,15 +250,20 @@ export class IconLabel extends Disposable { } } mouseMoveDisposable.dispose(); - mouseLeaveDisposable.dispose(); }, hoverDelay); } const mouseOverDisposable = this._register(domEvent(htmlElement, dom.EventType.MOUSE_OVER, true)(mouseOver.bind(htmlElement))); this.customHovers.set(htmlElement, mouseOverDisposable); } - private setupNativeHover(htmlElement: HTMLElement, tooltip: string | IMarkdownString | Promise | undefined): void { - htmlElement.title = isString(tooltip) ? tooltip : ''; + private setupNativeHover(htmlElement: HTMLElement, tooltip: string | IIconLabelMarkdownString | undefined): void { + let stringTooltip: string = ''; + if (isString(tooltip)) { + stringTooltip = tooltip; + } else if (tooltip?.markdownNotSupportedFallback) { + stringTooltip = tooltip.markdownNotSupportedFallback; + } + htmlElement.title = stringTooltip; } } diff --git a/src/vs/base/browser/ui/iconLabel/iconlabel.css b/src/vs/base/browser/ui/iconLabel/iconlabel.css index 9341febad..06224874c 100644 --- a/src/vs/base/browser/ui/iconLabel/iconlabel.css +++ b/src/vs/base/browser/ui/iconLabel/iconlabel.css @@ -60,12 +60,12 @@ } .monaco-icon-label.italic > .monaco-icon-label-container > .monaco-icon-name-container > .label-name, -.monaco-icon-label.italic > .monaco-icon-description-container > .label-description { +.monaco-icon-label.italic > .monaco-icon-label-container > .monaco-icon-description-container > .label-description { font-style: italic; } .monaco-icon-label.strikethrough > .monaco-icon-label-container > .monaco-icon-name-container > .label-name, -.monaco-icon-label.strikethrough > .monaco-icon-description-container > .label-description { +.monaco-icon-label.strikethrough > .monaco-icon-label-container > .monaco-icon-description-container > .label-description { text-decoration: line-through; } diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index 03db9d2a6..e2e8e849a 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -258,6 +258,10 @@ export class PagedList implements IThemable, IDisposable { return this.list.getSelection(); } + getSelectedElements(): T[] { + return this.getSelection().map(i => this.model.get(i)); + } + layout(height?: number, width?: number): void { this.list.layout(height, width); } diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index c527ca29f..26b198431 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -327,7 +327,6 @@ export class ListView implements ISpliceable, IDisposable { this.scrollable = new Scrollable(getOrDefault(options, o => o.smoothScrolling, false) ? 125 : 0, cb => scheduleAtNextAnimationFrame(cb)); this.scrollableElement = this.disposables.add(new SmoothScrollableElement(this.rowsContainer, { - alwaysConsumeMouseWheel: true, horizontal: ScrollbarVisibility.Auto, vertical: getOrDefault(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode), useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows), @@ -1323,6 +1322,9 @@ export class ListView implements ISpliceable, IDisposable { if (item.row) { const renderer = this.renderers.get(item.row.templateId); if (renderer) { + if (renderer.disposeElement) { + renderer.disposeElement(item.element, -1, item.row.templateData, undefined); + } renderer.disposeTemplate(item.row.templateData); } } diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 39d1bbed0..e530b43d3 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -318,10 +318,12 @@ class KeyboardController implements IDisposable { } private onEscape(e: StandardKeyboardEvent): void { - e.preventDefault(); - e.stopPropagation(); - this.list.setSelection([], e.browserEvent); - this.view.domNode.focus(); + if (this.list.getSelection().length) { + e.preventDefault(); + e.stopPropagation(); + this.list.setSelection([], e.browserEvent); + this.view.domNode.focus(); + } } dispose() { @@ -1460,6 +1462,8 @@ export class List implements ISpliceable, IThemable, IDisposable { this.view.setScrollTop(previousScrollTop + this.view.renderHeight - this.view.elementHeight(lastPageIndex)); if (this.view.getScrollTop() !== previousScrollTop) { + this.setFocus([]); + // Let the scroll event listener run setTimeout(() => this.focusNextPage(browserEvent, filter), 0); } @@ -1492,6 +1496,8 @@ export class List implements ISpliceable, IThemable, IDisposable { this.view.setScrollTop(scrollTop - this.view.renderHeight); if (this.view.getScrollTop() !== previousScrollTop) { + this.setFocus([]); + // Let the scroll event listener run setTimeout(() => this.focusPreviousPage(browserEvent, filter), 0); } diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 8b3da5df8..fc129fef5 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -18,7 +18,7 @@ import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable'; import { Event } from 'vs/base/common/event'; import { AnchorAlignment, layout, LayoutAnchorPosition } from 'vs/base/browser/ui/contextview/contextview'; import { isLinux, isMacintosh } from 'vs/base/common/platform'; -import { Codicon, registerIcon, stripCodicons } from 'vs/base/common/codicons'; +import { Codicon, registerCodicon, stripCodicons } from 'vs/base/common/codicons'; import { BaseActionViewItem, ActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { formatRule } from 'vs/base/browser/ui/codicons/codiconStyles'; import { isFirefox } from 'vs/base/browser/browser'; @@ -27,8 +27,8 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; export const MENU_MNEMONIC_REGEX = /\(&([^\s&])\)|(^|[^&])&([^\s&])/; export const MENU_ESCAPED_MNEMONIC_REGEX = /(&)?(&)([^\s&])/g; -const menuSelectionIcon = registerIcon('menu-selection', Codicon.check); -const menuSubmenuIcon = registerIcon('menu-submenu', Codicon.chevronRight); +const menuSelectionIcon = registerCodicon('menu-selection', Codicon.check); +const menuSubmenuIcon = registerCodicon('menu-submenu', Codicon.chevronRight); export enum Direction { Right, @@ -728,6 +728,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { if (this.item) { this.item.classList.add('monaco-submenu-item'); + this.item.tabIndex = 0; this.item.setAttribute('aria-haspopup', 'true'); this.updateAriaExpanded('false'); this.submenuIndicator = append(this.item, $('span.submenu-indicator' + menuSubmenuIcon.cssSelector)); @@ -777,6 +778,12 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { })); } + updateEnabled(): void { + // override on submenu entry + // native menus do not observe enablement on sumbenus + // we mimic that behavior + } + open(selectFirst?: boolean): void { this.cleanupExistingSubmenu(false); this.createSubmenu(selectFirst); @@ -1189,6 +1196,7 @@ ${formatRule(menuSubmenuIcon)} outline: 0; border: none; animation: fadeIn 0.083s linear; + -webkit-app-region: no-drag; } .context-view.monaco-menu-container :focus, diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index cb2b3fc0c..55858a589 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -21,11 +21,11 @@ import { asArray } from 'vs/base/common/arrays'; import { ScanCodeUtils, ScanCode } from 'vs/base/common/scanCode'; import { isMacintosh } from 'vs/base/common/platform'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { Codicon, registerIcon } from 'vs/base/common/codicons'; +import { Codicon, registerCodicon } from 'vs/base/common/codicons'; const $ = DOM.$; -const menuBarMoreIcon = registerIcon('menubar-more', Codicon.more); +const menuBarMoreIcon = registerCodicon('menubar-more', Codicon.more); export interface IMenuBarOptions { enableMnemonics?: boolean; @@ -115,7 +115,7 @@ export class MenuBar extends Disposable { this.menuUpdater = this._register(new RunOnceScheduler(() => this.update(), 200)); this.actionRunner = this._register(new ActionRunner()); - this._register(this.actionRunner.onDidBeforeRun(() => { + this._register(this.actionRunner.onBeforeRun(() => { this.setUnfocusedState(); })); diff --git a/src/vs/base/browser/ui/progressbar/progressbar.css b/src/vs/base/browser/ui/progressbar/progressbar.css index 64368d699..55e6a1a69 100644 --- a/src/vs/base/browser/ui/progressbar/progressbar.css +++ b/src/vs/base/browser/ui/progressbar/progressbar.css @@ -42,7 +42,10 @@ * The progress bit has a width: 2% (1/50) of the parent container. The animation moves it from 0% to 100% of * that container. Since translateX is relative to the progress bit size, we have to multiple it with * its relative size to the parent container: - * 50%: 50 * 50 = 2500% - * 100%: 50 * 100 - 50 (do not overflow): 4950% + * parent width: 5000% + * bit width: 100% + * translateX should be as follow: + * 50%: 5000% * 50% - 50% (set to center) = 2450% + * 100%: 5000% * 100% - 100% (do not overflow) = 4900% */ -@keyframes progress { from { transform: translateX(0%) scaleX(1) } 50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4950%) scaleX(1) } } +@keyframes progress { from { transform: translateX(0%) scaleX(1) } 50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4900%) scaleX(1) } } diff --git a/src/vs/base/browser/ui/progressbar/progressbar.ts b/src/vs/base/browser/ui/progressbar/progressbar.ts index 5e47bcb13..a8aa8b61e 100644 --- a/src/vs/base/browser/ui/progressbar/progressbar.ts +++ b/src/vs/base/browser/ui/progressbar/progressbar.ts @@ -58,6 +58,7 @@ export class ProgressBar extends Disposable { this.element = document.createElement('div'); this.element.classList.add('monaco-progress-container'); this.element.setAttribute('role', 'progressbar'); + this.element.setAttribute('aria-valuemin', '0'); container.appendChild(this.element); this.bit = document.createElement('div'); diff --git a/src/vs/base/browser/ui/sash/sash.css b/src/vs/base/browser/ui/sash/sash.css index ac9e628c9..db8977d69 100644 --- a/src/vs/base/browser/ui/sash/sash.css +++ b/src/vs/base/browser/ui/sash/sash.css @@ -60,19 +60,48 @@ height: var(--sash-size); } -.monaco-sash:not(.disabled).orthogonal-start::before, .monaco-sash:not(.disabled).orthogonal-end::after { - content: ' '; +.monaco-sash:not(.disabled).orthogonal-start::before, +.monaco-sash:not(.disabled).orthogonal-end::after { + content: " "; height: calc(var(--sash-size) * 2); width: calc(var(--sash-size) * 2); z-index: 100; display: block; - cursor: all-scroll; position: absolute; + cursor: all-scroll; + position: absolute; } -.monaco-sash.orthogonal-start.vertical::before { left: -calc(var(--sash-size) / 2); top: calc(var(--sash-size) * -1); } -.monaco-sash.orthogonal-end.vertical::after { left: -calc(var(--sash-size) / 2); bottom: calc(var(--sash-size) * -1); } -.monaco-sash.orthogonal-start.horizontal::before { top: -calc(var(--sash-size) / 2); left: calc(var(--sash-size) * -1); } -.monaco-sash.orthogonal-end.horizontal::after { top: -calc(var(--sash-size) / 2); right: calc(var(--sash-size) * -1); } +.monaco-sash.horizontal.orthogonal-edge-north:not(.disabled).orthogonal-start::before, +.monaco-sash.horizontal.orthogonal-edge-south:not(.disabled).orthogonal-end::after { + cursor: nwse-resize; +} + +.monaco-sash.horizontal.orthogonal-edge-north:not(.disabled).orthogonal-end::after, +.monaco-sash.horizontal.orthogonal-edge-south:not(.disabled).orthogonal-start::before { + cursor: nesw-resize; +} + +.monaco-sash.orthogonal-start.vertical::before { + left: -calc(var(--sash-size) / 2); + top: calc(var(--sash-size) * -1); +} +.monaco-sash.orthogonal-end.vertical::after { + left: -calc(var(--sash-size) / 2); + bottom: calc(var(--sash-size) * -1); +} +.monaco-sash.orthogonal-start.horizontal::before { + top: -calc(var(--sash-size) / 2); + left: calc(var(--sash-size) * -1); +} +.monaco-sash.orthogonal-end.horizontal::after { + top: -calc(var(--sash-size) / 2); + right: calc(var(--sash-size) * -1); +} + +.monaco-sash { + transition: background-color 0.1s ease-out; + background: transparent; +} /** Debug **/ diff --git a/src/vs/base/browser/ui/sash/sash.ts b/src/vs/base/browser/ui/sash/sash.ts index 3f7801ddb..fa0aa709d 100644 --- a/src/vs/base/browser/ui/sash/sash.ts +++ b/src/vs/base/browser/ui/sash/sash.ts @@ -37,11 +37,19 @@ export interface ISashEvent { altKey: boolean; } +export enum OrthogonalEdge { + North = 'north', + South = 'south', + East = 'east', + West = 'west' +} + export interface ISashOptions { readonly orientation: Orientation; readonly orthogonalStartSash?: Sash; readonly orthogonalEndSash?: Sash; readonly size?: number; + readonly orthogonalEdge?: OrthogonalEdge; } export interface IVerticalSashOptions extends ISashOptions { @@ -150,6 +158,10 @@ export class Sash extends Disposable { this.el = append(container, $('.monaco-sash')); + if (options.orthogonalEdge) { + this.el.classList.add(`orthogonal-edge-${options.orthogonalEdge}`); + } + if (isMacintosh) { this.el.classList.add('mac'); } diff --git a/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts b/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts index 23bfdb2c7..133ec9350 100644 --- a/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts +++ b/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts @@ -38,12 +38,14 @@ export interface AbstractScrollbarOptions { visibility: ScrollbarVisibility; extraScrollbarClassName: string; scrollable: Scrollable; + scrollByPage: boolean; } export abstract class AbstractScrollbar extends Widget { protected _host: ScrollbarHost; protected _scrollable: Scrollable; + protected _scrollByPage: boolean; private _lazyRender: boolean; protected _scrollbarState: ScrollbarState; private _visibilityController: ScrollbarVisibilityController; @@ -59,6 +61,7 @@ export abstract class AbstractScrollbar extends Widget { this._lazyRender = opts.lazyRender; this._host = opts.host; this._scrollable = opts.scrollable; + this._scrollByPage = opts.scrollByPage; this._scrollbarState = opts.scrollbarState; this._visibilityController = this._register(new ScrollbarVisibilityController(opts.visibility, 'visible scrollbar ' + opts.extraScrollbarClassName, 'invisible scrollbar ' + opts.extraScrollbarClassName)); this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()); @@ -210,7 +213,14 @@ export abstract class AbstractScrollbar extends Widget { offsetX = e.posx - domNodePosition.left; offsetY = e.posy - domNodePosition.top; } - this._setDesiredScrollPositionNow(this._scrollbarState.getDesiredScrollPositionFromOffset(this._mouseDownRelativePosition(offsetX, offsetY))); + + const offset = this._mouseDownRelativePosition(offsetX, offsetY); + this._setDesiredScrollPositionNow( + this._scrollByPage + ? this._scrollbarState.getDesiredScrollPositionFromOffsetPaged(offset) + : this._scrollbarState.getDesiredScrollPositionFromOffset(offset) + ); + if (e.leftButton) { e.preventDefault(); this._sliderMouseDown(e, () => { /*nothing to do*/ }); diff --git a/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts b/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts index 6e7f132e9..9d9a22683 100644 --- a/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts +++ b/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts @@ -9,11 +9,11 @@ import { ScrollableElementResolvedOptions } from 'vs/base/browser/ui/scrollbar/s import { ARROW_IMG_SIZE } from 'vs/base/browser/ui/scrollbar/scrollbarArrow'; import { ScrollbarState } from 'vs/base/browser/ui/scrollbar/scrollbarState'; import { INewScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable'; -import { Codicon, registerIcon } from 'vs/base/common/codicons'; +import { Codicon, registerCodicon } from 'vs/base/common/codicons'; -const scrollbarButtonLeftIcon = registerIcon('scrollbar-button-left', Codicon.triangleLeft); -const scrollbarButtonRightIcon = registerIcon('scrollbar-button-right', Codicon.triangleRight); +const scrollbarButtonLeftIcon = registerCodicon('scrollbar-button-left', Codicon.triangleLeft); +const scrollbarButtonRightIcon = registerCodicon('scrollbar-button-right', Codicon.triangleRight); export class HorizontalScrollbar extends AbstractScrollbar { @@ -33,7 +33,8 @@ export class HorizontalScrollbar extends AbstractScrollbar { ), visibility: options.horizontal, extraScrollbarClassName: 'horizontal', - scrollable: scrollable + scrollable: scrollable, + scrollByPage: options.scrollByPage }); if (options.horizontalHasArrows) { diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index 00bb9830d..115864f3d 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -361,6 +361,8 @@ export abstract class AbstractScrollableElement extends Widget { // console.log(`${Date.now()}, ${e.deltaY}, ${e.deltaX}`); + let didScroll = false; + if (e.deltaY || e.deltaX) { let deltaY = e.deltaY * this._options.mouseWheelScrollSensitivity; let deltaX = e.deltaX * this._options.mouseWheelScrollSensitivity; @@ -419,11 +421,12 @@ export abstract class AbstractScrollableElement extends Widget { } else { this._scrollable.setScrollPositionNow(desiredScrollPosition); } - this._shouldRender = true; + + didScroll = true; } } - if (this._options.alwaysConsumeMouseWheel || this._shouldRender) { + if (this._options.alwaysConsumeMouseWheel || didScroll) { e.preventDefault(); e.stopPropagation(); } @@ -614,7 +617,9 @@ function resolveOptions(opts: ScrollableElementCreationOptions): ScrollableEleme vertical: (typeof opts.vertical !== 'undefined' ? opts.vertical : ScrollbarVisibility.Auto), verticalScrollbarSize: (typeof opts.verticalScrollbarSize !== 'undefined' ? opts.verticalScrollbarSize : 10), verticalHasArrows: (typeof opts.verticalHasArrows !== 'undefined' ? opts.verticalHasArrows : false), - verticalSliderSize: (typeof opts.verticalSliderSize !== 'undefined' ? opts.verticalSliderSize : 0) + verticalSliderSize: (typeof opts.verticalSliderSize !== 'undefined' ? opts.verticalSliderSize : 0), + + scrollByPage: (typeof opts.scrollByPage !== 'undefined' ? opts.scrollByPage : false) }; result.horizontalSliderSize = (typeof opts.horizontalSliderSize !== 'undefined' ? opts.horizontalSliderSize : result.horizontalScrollbarSize); diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts b/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts index afb227be7..556d20839 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts @@ -114,6 +114,11 @@ export interface ScrollableElementCreationOptions { * Defaults to false. */ verticalHasArrows?: boolean; + /** + * Scroll gutter clicks move by page vs. jump to position. + * Defaults to false. + */ + scrollByPage?: boolean; } export interface ScrollableElementChangeOptions { @@ -146,4 +151,5 @@ export interface ScrollableElementResolvedOptions { verticalScrollbarSize: number; verticalSliderSize: number; verticalHasArrows: boolean; + scrollByPage: boolean; } diff --git a/src/vs/base/browser/ui/scrollbar/scrollbarState.ts b/src/vs/base/browser/ui/scrollbar/scrollbarState.ts index 48e20a5a0..d0d98b9a8 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollbarState.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollbarState.ts @@ -202,6 +202,28 @@ export class ScrollbarState { return Math.round(desiredSliderPosition / this._computedSliderRatio); } + /** + * Compute a desired `scrollPosition` from if offset is before or after the slider position. + * If offset is before slider, treat as a page up (or left). If after, page down (or right). + * `offset` and `_computedSliderPosition` are based on the same coordinate system. + * `_visibleSize` corresponds to a "page" of lines in the returned coordinate system. + */ + public getDesiredScrollPositionFromOffsetPaged(offset: number): number { + if (!this._computedIsNeeded) { + // no need for a slider + return 0; + } + + let correctedOffset = offset - this._arrowSize; // compensate if has arrows + let desiredScrollPosition = this._scrollPosition; + if (correctedOffset < this._computedSliderPosition) { + desiredScrollPosition -= this._visibleSize; // page up/left + } else { + desiredScrollPosition += this._visibleSize; // page down/right + } + return desiredScrollPosition; + } + /** * Compute a desired `scrollPosition` such that the slider moves by `delta`. */ diff --git a/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts b/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts index 296913a3f..12d2887b6 100644 --- a/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts +++ b/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts @@ -9,10 +9,10 @@ import { ScrollableElementResolvedOptions } from 'vs/base/browser/ui/scrollbar/s import { ARROW_IMG_SIZE } from 'vs/base/browser/ui/scrollbar/scrollbarArrow'; import { ScrollbarState } from 'vs/base/browser/ui/scrollbar/scrollbarState'; import { INewScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable'; -import { Codicon, registerIcon } from 'vs/base/common/codicons'; +import { Codicon, registerCodicon } from 'vs/base/common/codicons'; -const scrollbarButtonUpIcon = registerIcon('scrollbar-button-up', Codicon.triangleUp); -const scrollbarButtonDownIcon = registerIcon('scrollbar-button-down', Codicon.triangleDown); +const scrollbarButtonUpIcon = registerCodicon('scrollbar-button-up', Codicon.triangleUp); +const scrollbarButtonDownIcon = registerCodicon('scrollbar-button-down', Codicon.triangleDown); export class VerticalScrollbar extends AbstractScrollbar { @@ -33,7 +33,8 @@ export class VerticalScrollbar extends AbstractScrollbar { ), visibility: options.vertical, extraScrollbarClassName: 'vertical', - scrollable: scrollable + scrollable: scrollable, + scrollByPage: options.scrollByPage }); if (options.verticalHasArrows) { diff --git a/src/vs/base/browser/ui/selectBox/selectBox.ts b/src/vs/base/browser/ui/selectBox/selectBox.ts index 79f0da0e6..4315b8e8f 100644 --- a/src/vs/base/browser/ui/selectBox/selectBox.ts +++ b/src/vs/base/browser/ui/selectBox/selectBox.ts @@ -46,6 +46,7 @@ export interface ISelectBoxOptions { // Utilize optionItem interface to capture all option parameters export interface ISelectOptionItem { text: string; + detail?: string; decoratorRight?: string; description?: string; descriptionIsMarkdown?: boolean; diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.css b/src/vs/base/browser/ui/selectBox/selectBoxCustom.css index 536a94223..f385bf29d 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.css +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.css @@ -75,6 +75,15 @@ float: left; } +.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row > .option-detail { + text-overflow: ellipsis; + overflow: hidden; + padding-left: 3.5px; + white-space: nowrap; + float: left; + opacity: 0.7; +} + .monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row > .option-decorator-right { text-overflow: ellipsis; overflow: hidden; diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index 62438e1b3..841f2ee44 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -29,6 +29,7 @@ const SELECT_OPTION_ENTRY_TEMPLATE_ID = 'selectOption.entry.template'; interface ISelectListTemplateData { root: HTMLElement; text: HTMLElement; + detail: HTMLElement; decoratorRight: HTMLElement; disposables: IDisposable[]; } @@ -42,6 +43,7 @@ class SelectListRenderer implements IListRenderer { - const len = option.text.length + (!!option.decoratorRight ? option.decoratorRight.length : 0); + const detailLength = !!option.detail ? option.detail.length : 0; + const rightDecoratorLength = !!option.decoratorRight ? option.decoratorRight.length : 0; + + const len = option.text.length + detailLength + rightDecoratorLength; if (len > longestLength) { longest = index; longestLength = len; @@ -697,6 +706,10 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi accessibilityProvider: { getAriaLabel: element => { let label = element.text; + if (element.detail) { + label += `. ${element.detail}`; + } + if (element.decoratorRight) { label += `. ${element.decoratorRight}`; } diff --git a/src/vs/base/browser/ui/splitview/paneview.css b/src/vs/base/browser/ui/splitview/paneview.css index 06743287c..3ffad073b 100644 --- a/src/vs/base/browser/ui/splitview/paneview.css +++ b/src/vs/base/browser/ui/splitview/paneview.css @@ -38,23 +38,12 @@ width: 22px; } -.monaco-pane-view .pane > .pane-header > .twisties { - width: 20px; - display: flex; - align-items: center; - justify-content: center; - transform-origin: center; - color: inherit; - flex-shrink: 0; +.monaco-pane-view .pane > .pane-header > .codicon:first-of-type { + margin: 0 2px; } -.monaco-pane-view .pane.horizontal:not(.expanded) > .pane-header > .twisties { - margin-top: 2px; - margin-bottom: 2px; -} - -.monaco-pane-view .pane > .pane-header.expanded > .twisties::before { - transform: rotate(90deg); +.monaco-pane-view .pane.horizontal:not(.expanded) > .pane-header > .codicon:first-of-type { + margin: 2px; } /* TODO: actions should be part of the pane, but they aren't yet */ diff --git a/src/vs/base/browser/ui/splitview/splitview.css b/src/vs/base/browser/ui/splitview/splitview.css index e5baba1f3..3af3e9062 100644 --- a/src/vs/base/browser/ui/splitview/splitview.css +++ b/src/vs/base/browser/ui/splitview/splitview.css @@ -20,31 +20,36 @@ pointer-events: initial; } -.monaco-split-view2 > .split-view-container { +.monaco-split-view2 > .monaco-scrollable-element { + width: 100%; + height: 100%; +} + +.monaco-split-view2 > .monaco-scrollable-element > .split-view-container { width: 100%; height: 100%; white-space: nowrap; position: relative; } -.monaco-split-view2 > .split-view-container > .split-view-view { +.monaco-split-view2 > .monaco-scrollable-element > .split-view-container > .split-view-view { white-space: initial; position: absolute; } -.monaco-split-view2 > .split-view-container > .split-view-view:not(.visible) { +.monaco-split-view2 > .monaco-scrollable-element > .split-view-container > .split-view-view:not(.visible) { display: none; } -.monaco-split-view2.vertical > .split-view-container > .split-view-view { +.monaco-split-view2.vertical > .monaco-scrollable-element > .split-view-container > .split-view-view { width: 100%; } -.monaco-split-view2.horizontal > .split-view-container > .split-view-view { +.monaco-split-view2.horizontal > .monaco-scrollable-element > .split-view-container > .split-view-view { height: 100%; } -.monaco-split-view2.separator-border > .split-view-container > .split-view-view:not(:first-child)::before { +.monaco-split-view2.separator-border > .monaco-scrollable-element > .split-view-container > .split-view-view:not(:first-child)::before { content: ' '; position: absolute; top: 0; @@ -54,12 +59,12 @@ background-color: var(--separator-border); } -.monaco-split-view2.separator-border.horizontal > .split-view-container > .split-view-view:not(:first-child)::before { +.monaco-split-view2.separator-border.horizontal > .monaco-scrollable-element > .split-view-container > .split-view-view:not(:first-child)::before { height: 100%; width: 1px; } -.monaco-split-view2.separator-border.vertical > .split-view-container > .split-view-view:not(:first-child)::before { +.monaco-split-view2.separator-border.vertical > .monaco-scrollable-element > .split-view-container > .split-view-view:not(:first-child)::before { height: 1px; width: 100%; } diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 10c15797c..8bb893912 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -12,7 +12,9 @@ import { range, pushToStart, pushToEnd } from 'vs/base/common/arrays'; import { Sash, Orientation, ISashEvent as IBaseSashEvent, SashState } from 'vs/base/browser/ui/sash/sash'; import { Color } from 'vs/base/common/color'; import { domEvent } from 'vs/base/browser/event'; -import { $, append } from 'vs/base/browser/dom'; +import { $, append, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; +import { Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable'; export { Orientation } from 'vs/base/browser/ui/sash/sash'; export interface ISplitViewStyles { @@ -213,6 +215,8 @@ export class SplitView extends Disposable { readonly el: HTMLElement; private sashContainer: HTMLElement; private viewContainer: HTMLElement; + private scrollable: Scrollable; + private scrollableElement: SmoothScrollableElement; private size = 0; private layoutContext: TLayoutContext | undefined; private contentSize = 0; @@ -301,7 +305,20 @@ export class SplitView extends Disposable { container.appendChild(this.el); this.sashContainer = append(this.el, $('.sash-container')); - this.viewContainer = append(this.el, $('.split-view-container')); + this.viewContainer = $('.split-view-container'); + + this.scrollable = new Scrollable(125, scheduleAtNextAnimationFrame); + this.scrollableElement = this._register(new SmoothScrollableElement(this.viewContainer, { + vertical: this.orientation === Orientation.VERTICAL ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden, + horizontal: this.orientation === Orientation.HORIZONTAL ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden + }, this.scrollable)); + + this._register(this.scrollableElement.onScroll(e => { + this.viewContainer.scrollTop = e.scrollTop; + this.viewContainer.scrollLeft = e.scrollLeft; + })); + + append(this.el, this.scrollableElement.getDomNode()); this.style(options.styles || defaultStyles); @@ -897,6 +914,21 @@ export class SplitView extends Disposable { // Layout sashes this.sashItems.forEach(item => item.sash.layout()); this.updateSashEnablement(); + this.updateScrollableElement(); + } + + private updateScrollableElement(): void { + if (this.orientation === Orientation.VERTICAL) { + this.scrollableElement.setScrollDimensions({ + height: this.size, + scrollHeight: this.contentSize + }); + } else { + this.scrollableElement.setScrollDimensions({ + width: this.size, + scrollWidth: this.contentSize + }); + } } private updateSashEnablement(): void { diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index d2b361446..d782e1bcb 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -11,12 +11,12 @@ import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { Codicon, registerIcon } from 'vs/base/common/codicons'; +import { Codicon, CSSIcon, registerCodicon } from 'vs/base/common/codicons'; import { EventMultiplexer } from 'vs/base/common/event'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; -const toolBarMoreIcon = registerIcon('toolbar-more', Codicon.more); +const toolBarMoreIcon = registerCodicon('toolbar-more', Codicon.more); export interface IToolBarOptions { orientation?: ActionsOrientation; @@ -27,6 +27,7 @@ export interface IToolBarOptions { toggleMenuTitle?: string; anchorAlignmentProvider?: () => AnchorAlignment; renderDropdownAsChildElement?: boolean; + moreIcon?: CSSIcon; } /** @@ -72,7 +73,7 @@ export class ToolBar extends Disposable { actionViewItemProvider: this.options.actionViewItemProvider, actionRunner: this.actionRunner, keybindingProvider: this.options.getKeyBinding, - classNames: toolBarMoreIcon.classNamesArray, + classNames: (options.moreIcon ?? toolBarMoreIcon).classNames, anchorAlignmentProvider: this.options.anchorAlignmentProvider, menuAsChild: !!this.options.renderDropdownAsChildElement } diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index fa8ac3e84..4df19745c 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -400,15 +400,23 @@ class TreeRenderer implements IListRenderer } private renderTwistie(node: ITreeNode, templateData: ITreeListTemplateData) { + templateData.twistie.classList.remove(...treeItemExpandedIcon.classNamesArray); + + let twistieRendered = false; + if (this.renderer.renderTwistie) { - this.renderer.renderTwistie(node.element, templateData.twistie); + twistieRendered = this.renderer.renderTwistie(node.element, templateData.twistie); } if (node.collapsible && (!this.hideTwistiesOfChildlessElements || node.visibleChildrenCount > 0)) { - templateData.twistie.classList.add(...treeItemExpandedIcon.classNamesArray, 'collapsible'); + if (!twistieRendered) { + templateData.twistie.classList.add(...treeItemExpandedIcon.classNamesArray); + } + + templateData.twistie.classList.add('collapsible'); templateData.twistie.classList.toggle('collapsed', node.collapsed); } else { - templateData.twistie.classList.remove(...treeItemExpandedIcon.classNamesArray, 'collapsible', 'collapsed'); + templateData.twistie.classList.remove('collapsible', 'collapsed'); } if (node.collapsible) { @@ -811,7 +819,7 @@ class TypeFilterController implements IDisposable { const onDragOver = (event: DragEvent) => { event.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome) - const x = event.screenX - left; + const x = event.clientX - left; if (event.dataTransfer) { event.dataTransfer.dropEffect = 'none'; } @@ -954,6 +962,7 @@ export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions { readonly smoothScrolling?: boolean; readonly horizontalScrolling?: boolean; readonly expandOnlyOnDoubleClick?: boolean; + readonly expandOnlyOnTwistieClick?: boolean | ((e: any) => boolean); // e is T } export interface IAbstractTreeOptions extends IAbstractTreeOptionsUpdate, IListOptions { @@ -961,7 +970,6 @@ export interface IAbstractTreeOptions extends IAbstractTr readonly filter?: ITreeFilter; readonly dnd?: ITreeDragAndDrop; readonly keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter; - readonly expandOnlyOnTwistieClick?: boolean | ((e: T) => boolean); readonly additionalScrollHeight?: number; } @@ -1109,7 +1117,7 @@ class TreeNodeListMouseController extends MouseController< expandOnlyOnTwistieClick = !!this.tree.expandOnlyOnTwistieClick; } - if (expandOnlyOnTwistieClick && !onTwistie) { + if (expandOnlyOnTwistieClick && !onTwistie && e.browserEvent.detail !== 2) { return super.onViewPointer(e); } diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 970eddf5c..e4a93aef0 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -110,10 +110,11 @@ class AsyncDataTreeRenderer implements IT renderTwistie(element: IAsyncDataTreeNode, twistieElement: HTMLElement): boolean { if (element.slow) { twistieElement.classList.add(...treeItemLoadingIcon.classNamesArray); + return true; } else { twistieElement.classList.remove(...treeItemLoadingIcon.classNamesArray); + return false; } - return false; } disposeElement(node: ITreeNode, TFilterData>, index: number, templateData: IDataTreeListTemplateData, height: number | undefined): void { @@ -1053,10 +1054,11 @@ class CompressibleAsyncDataTreeRenderer i renderTwistie(element: IAsyncDataTreeNode, twistieElement: HTMLElement): boolean { if (element.slow) { twistieElement.classList.add(...treeItemLoadingIcon.classNamesArray); + return true; } else { twistieElement.classList.remove(...treeItemLoadingIcon.classNamesArray); + return false; } - return false; } disposeElement(node: ITreeNode, TFilterData>, index: number, templateData: IDataTreeListTemplateData, height: number | undefined): void { diff --git a/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts b/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts index 150f49ce5..3dc8ab961 100644 --- a/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts +++ b/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts @@ -38,8 +38,8 @@ export function compress(element: ICompressedTreeElement): ITreeElement>; - let children: ITreeElement[]; + let childrenIterator: Iterable>; + let children: ICompressedTreeElement[]; while (true) { [children, childrenIterator] = Iterable.consume(Iterable.from(element.children), 2); @@ -48,12 +48,11 @@ export function compress(element: ICompressedTreeElement): ITreeElement, TFilterData, TTemplateDat this.renderer.disposeTemplate(templateData.data); } - renderTwistie?(element: T, twistieElement: HTMLElement): void { + renderTwistie?(element: T, twistieElement: HTMLElement): boolean { if (this.renderer.renderTwistie) { - this.renderer.renderTwistie(element, twistieElement); + return this.renderer.renderTwistie(element, twistieElement); } + return false; } } diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index 085bb3d90..c17b41fbb 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -129,7 +129,7 @@ export interface ITreeModel { } export interface ITreeRenderer extends IListRenderer, TTemplateData> { - renderTwistie?(element: T, twistieElement: HTMLElement): void; + renderTwistie?(element: T, twistieElement: HTMLElement): boolean; onDidChangeTwistieState?: Event; } diff --git a/src/vs/base/browser/ui/tree/treeIcons.ts b/src/vs/base/browser/ui/tree/treeIcons.ts index bccd51889..9755ae59e 100644 --- a/src/vs/base/browser/ui/tree/treeIcons.ts +++ b/src/vs/base/browser/ui/tree/treeIcons.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Codicon, registerIcon } from 'vs/base/common/codicons'; +import { Codicon, registerCodicon } from 'vs/base/common/codicons'; -export const treeItemExpandedIcon = registerIcon('tree-item-expanded', Codicon.chevronDown); // collapsed is done with rotation +export const treeItemExpandedIcon = registerCodicon('tree-item-expanded', Codicon.chevronDown); // collapsed is done with rotation -export const treeFilterOnTypeOnIcon = registerIcon('tree-filter-on-type-on', Codicon.listFilter); -export const treeFilterOnTypeOffIcon = registerIcon('tree-filter-on-type-off', Codicon.listSelection); -export const treeFilterClearIcon = registerIcon('tree-filter-clear', Codicon.close); +export const treeFilterOnTypeOnIcon = registerCodicon('tree-filter-on-type-on', Codicon.listFilter); +export const treeFilterOnTypeOffIcon = registerCodicon('tree-filter-on-type-off', Codicon.listSelection); +export const treeFilterClearIcon = registerCodicon('tree-filter-clear', Codicon.close); -export const treeItemLoadingIcon = registerIcon('tree-item-loading', Codicon.loading); +export const treeItemLoadingIcon = registerCodicon('tree-item-loading', Codicon.loading); diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index cb4b12d5c..8be6f19af 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -36,7 +36,7 @@ export interface IAction extends IDisposable { export interface IActionRunner extends IDisposable { run(action: IAction, context?: any): Promise; readonly onDidRun: Event; - readonly onDidBeforeRun: Event; + readonly onBeforeRun: Event; } export interface IActionViewItem extends IDisposable { @@ -178,8 +178,8 @@ export interface IRunEvent { export class ActionRunner extends Disposable implements IActionRunner { - private _onDidBeforeRun = this._register(new Emitter()); - readonly onDidBeforeRun: Event = this._onDidBeforeRun.event; + private _onBeforeRun = this._register(new Emitter()); + readonly onBeforeRun: Event = this._onBeforeRun.event; private _onDidRun = this._register(new Emitter()); readonly onDidRun: Event = this._onDidRun.event; @@ -189,7 +189,7 @@ export class ActionRunner extends Disposable implements IActionRunner { return Promise.resolve(null); } - this._onDidBeforeRun.fire({ action: action }); + this._onBeforeRun.fire({ action: action }); try { const result = await this.runAction(action, context); @@ -235,6 +235,17 @@ export class Separator extends Action { } } +export class ActionWithMenuAction extends Action { + + get actions(): IAction[] { + return this._actions; + } + + constructor(id: string, private _actions: IAction[], label?: string, cssClass?: string, enabled?: boolean, actionCallback?: (event?: any) => Promise) { + super(id, label, cssClass, enabled, actionCallback); + } +} + export class SubmenuAction extends Action { get actions(): IAction[] { @@ -242,7 +253,7 @@ export class SubmenuAction extends Action { } constructor(id: string, label: string, private _actions: IAction[], cssClass?: string) { - super(id, label, cssClass, true); + super(id, label, cssClass, !!_actions?.length); } } diff --git a/src/vs/base/common/amd.ts b/src/vs/base/common/amd.ts index d8ce68b55..a0ee6f54d 100644 --- a/src/vs/base/common/amd.ts +++ b/src/vs/base/common/amd.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { mergeSort } from 'vs/base/common/arrays'; import { URI } from 'vs/base/common/uri'; /** @@ -18,3 +19,133 @@ export function getPathFromAmdModule(requirefn: typeof require, relativePath: st export function getUriFromAmdModule(requirefn: typeof require, relativePath: string): URI { return URI.parse(requirefn.toUrl(relativePath)); } + +export abstract class LoaderStats { + abstract get amdLoad(): [string, number][]; + abstract get amdInvoke(): [string, number][]; + abstract get nodeRequire(): [string, number][]; + abstract get nodeEval(): [string, number][]; + abstract get nodeRequireTotal(): number; + + + static get(): LoaderStats { + + + const amdLoadScript = new Map(); + const amdInvokeFactory = new Map(); + const nodeRequire = new Map(); + const nodeEval = new Map(); + + function mark(map: Map, stat: LoaderEvent) { + if (map.has(stat.detail)) { + // console.warn('BAD events, DOUBLE start', stat); + // map.delete(stat.detail); + return; + } + map.set(stat.detail, -stat.timestamp); + } + + function diff(map: Map, stat: LoaderEvent) { + let duration = map.get(stat.detail); + if (!duration) { + // console.warn('BAD events, end WITHOUT start', stat); + // map.delete(stat.detail); + return; + } + if (duration >= 0) { + // console.warn('BAD events, DOUBLE end', stat); + // map.delete(stat.detail); + return; + } + map.set(stat.detail, duration + stat.timestamp); + } + + const stats = mergeSort(require.getStats().slice(0), (a, b) => a.timestamp - b.timestamp); + + for (const stat of stats) { + switch (stat.type) { + case LoaderEventType.BeginLoadingScript: + mark(amdLoadScript, stat); + break; + case LoaderEventType.EndLoadingScriptOK: + case LoaderEventType.EndLoadingScriptError: + diff(amdLoadScript, stat); + break; + + case LoaderEventType.BeginInvokeFactory: + mark(amdInvokeFactory, stat); + break; + case LoaderEventType.EndInvokeFactory: + diff(amdInvokeFactory, stat); + break; + + case LoaderEventType.NodeBeginNativeRequire: + mark(nodeRequire, stat); + break; + case LoaderEventType.NodeEndNativeRequire: + diff(nodeRequire, stat); + break; + + case LoaderEventType.NodeBeginEvaluatingScript: + mark(nodeEval, stat); + break; + case LoaderEventType.NodeEndEvaluatingScript: + diff(nodeEval, stat); + break; + } + } + + let nodeRequireTotal = 0; + nodeRequire.forEach(value => nodeRequireTotal += value); + + function to2dArray(map: Map): [string, number][] { + let res: [string, number][] = []; + map.forEach((value, index) => res.push([index, value])); + return res; + } + + return { + amdLoad: to2dArray(amdLoadScript), + amdInvoke: to2dArray(amdInvokeFactory), + nodeRequire: to2dArray(nodeRequire), + nodeEval: to2dArray(nodeEval), + nodeRequireTotal + }; + } + + static toMarkdownTable(header: string[], rows: Array>): string { + let result = ''; + + let lengths: number[] = []; + header.forEach((cell, ci) => { + lengths[ci] = cell.length; + }); + rows.forEach(row => { + row.forEach((cell, ci) => { + if (typeof cell === 'undefined') { + cell = row[ci] = '-'; + } + const len = cell.toString().length; + lengths[ci] = Math.max(len, lengths[ci]); + }); + }); + + // header + header.forEach((cell, ci) => { result += `| ${cell + ' '.repeat(lengths[ci] - cell.toString().length)} `; }); + result += '|\n'; + header.forEach((_cell, ci) => { result += `| ${'-'.repeat(lengths[ci])} `; }); + result += '|\n'; + + // cells + rows.forEach(row => { + row.forEach((cell, ci) => { + if (typeof cell !== 'undefined') { + result += `| ${cell + ' '.repeat(lengths[ci] - cell.toString().length)} `; + } + }); + result += '|\n'; + }); + + return result; + } +} diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index bc0aa40a6..c4820d9cf 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -588,3 +588,17 @@ export function asArray(x: T | T[]): T[] { export function getRandomElement(arr: T[]): T | undefined { return arr[Math.floor(Math.random() * arr.length)]; } + +/** + * Returns the first mapped value of the array which is not undefined. + */ +export function mapFind(array: Iterable, mapFn: (value: T) => R | undefined): R | undefined { + for (const value of array) { + const mapped = mapFn(value); + if (mapped !== undefined) { + return mapped; + } + } + + return undefined; +} diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 632c1989f..167a47d15 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -445,6 +445,45 @@ export function first(promiseFactories: ITask>[], shouldStop: (t: return loop(); } +/** + * Returns the result of the first promise that matches the "shouldStop", + * running all promises in parallel. Supports cancelable promises. + */ +export function firstParallel(promiseList: Promise[], shouldStop?: (t: T) => boolean, defaultValue?: T | null): Promise; +export function firstParallel(promiseList: Promise[], shouldStop: (t: T) => t is R, defaultValue?: R | null): Promise; +export function firstParallel(promiseList: Promise[], shouldStop: (t: T) => boolean = t => !!t, defaultValue: T | null = null) { + if (promiseList.length === 0) { + return Promise.resolve(defaultValue); + } + + let todo = promiseList.length; + const finish = () => { + todo = -1; + for (const promise of promiseList) { + (promise as Partial>).cancel?.(); + } + }; + + return new Promise((resolve, reject) => { + for (const promise of promiseList) { + promise.then(result => { + if (--todo >= 0 && shouldStop(result)) { + finish(); + resolve(result); + } else if (todo === 0) { + resolve(defaultValue); + } + }) + .catch(err => { + if (--todo >= 0) { + finish(); + reject(err); + } + }); + } + }); +} + interface ILimitedTaskFactory { factory: ITask>; c: (value: T | Promise) => void; diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index 3c4fffd3b..8681df323 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -5,6 +5,7 @@ import { codiconStartMarker } from 'vs/base/common/codicon'; import { Emitter, Event } from 'vs/base/common/event'; +import { localize } from 'vs/nls'; export interface IIconRegistry { readonly all: IterableIterator; @@ -18,9 +19,12 @@ class Registry implements IIconRegistry { private readonly _onDidRegister = new Emitter(); public add(icon: Codicon) { - if (!this._icons.has(icon.id)) { + const existing = this._icons.get(icon.id); + if (!existing) { this._icons.set(icon.id, icon); this._onDidRegister.fire(icon); + } else if (icon.description) { + existing.description = icon.description; } else { console.error(`Duplicate registration of codicon ${icon.id}`); } @@ -43,11 +47,11 @@ const _registry = new Registry(); export const iconRegistry: IIconRegistry = _registry; -export function registerIcon(id: string, def: Codicon, description?: string) { - return new Codicon(id, def); +export function registerCodicon(id: string, def: Codicon, description?: string): Codicon { + return new Codicon(id, def, description); } -export class Codicon { +export class Codicon implements CSSIcon { constructor(public readonly id: string, public readonly definition: Codicon | IconDefinition, public description?: string) { _registry.add(this); } @@ -57,6 +61,12 @@ export class Codicon { public get cssSelector() { return '.codicon.codicon-' + this.id; } } +export interface CSSIcon { + readonly classNames: string; +} + + + interface IconDefinition { character: string; } @@ -484,8 +494,15 @@ export namespace Codicon { export const redo = new Codicon('redo', { character: '\\ebb0' }); export const checkAll = new Codicon('check-all', { character: '\\ebb1' }); export const pinnedDirty = new Codicon('pinned-dirty', { character: '\\ebb2' }); + export const passFilled = new Codicon('pass-filled', { character: '\\ebb3' }); + export const circleLargeFilled = new Codicon('circle-large-filled', { character: '\\ebb4' }); + export const circleLargeOutline = new Codicon('circle-large-outline', { character: '\\ebb5' }); + + export const dropDownButton = new Codicon('drop-down-button', Codicon.chevronDown.definition, localize('dropDownButton', 'Icon for drop down buttons.')); } +// common icons + diff --git a/src/vs/base/common/collections.ts b/src/vs/base/common/collections.ts index 8729b7303..f97dc23f6 100644 --- a/src/vs/base/common/collections.ts +++ b/src/vs/base/common/collections.ts @@ -76,7 +76,37 @@ export function fromMap(original: Map): IStringDictionary { return result; } +export function diffSets(before: Set, after: Set): { removed: T[], added: T[] } { + const removed: T[] = []; + const added: T[] = []; + for (let element of before) { + if (!after.has(element)) { + removed.push(element); + } + } + for (let element of after) { + if (!before.has(element)) { + added.push(element); + } + } + return { removed, added }; +} +export function diffMaps(before: Map, after: Map): { removed: V[], added: V[] } { + const removed: V[] = []; + const added: V[] = []; + for (let [index, value] of before) { + if (!after.has(index)) { + removed.push(value); + } + } + for (let [index, value] of after) { + if (!before.has(index)) { + added.push(value); + } + } + return { removed, added }; +} export class SetMap { private map = new Map>(); diff --git a/src/vs/base/common/diff/diff.ts b/src/vs/base/common/diff/diff.ts index d81c79eaa..97fdcbc00 100644 --- a/src/vs/base/common/diff/diff.ts +++ b/src/vs/base/common/diff/diff.ts @@ -880,9 +880,81 @@ export class LcsDiff { change.modifiedStart -= bestDelta; } + // There could be multiple longest common substrings. + // Give preference to the ones containing longer lines + if (this._hasStrings) { + for (let i = 1, len = changes.length; i < len; i++) { + const aChange = changes[i - 1]; + const bChange = changes[i]; + const matchedLength = bChange.originalStart - aChange.originalStart - aChange.originalLength; + const aOriginalStart = aChange.originalStart; + const bOriginalEnd = bChange.originalStart + bChange.originalLength; + const abOriginalLength = bOriginalEnd - aOriginalStart; + const aModifiedStart = aChange.modifiedStart; + const bModifiedEnd = bChange.modifiedStart + bChange.modifiedLength; + const abModifiedLength = bModifiedEnd - aModifiedStart; + // Avoid wasting a lot of time with these searches + if (matchedLength < 5 && abOriginalLength < 20 && abModifiedLength < 20) { + const t = this._findBetterContiguousSequence( + aOriginalStart, abOriginalLength, + aModifiedStart, abModifiedLength, + matchedLength + ); + if (t) { + const [originalMatchStart, modifiedMatchStart] = t; + if (originalMatchStart !== aChange.originalStart + aChange.originalLength || modifiedMatchStart !== aChange.modifiedStart + aChange.modifiedLength) { + // switch to another sequence that has a better score + aChange.originalLength = originalMatchStart - aChange.originalStart; + aChange.modifiedLength = modifiedMatchStart - aChange.modifiedStart; + bChange.originalStart = originalMatchStart + matchedLength; + bChange.modifiedStart = modifiedMatchStart + matchedLength; + bChange.originalLength = bOriginalEnd - bChange.originalStart; + bChange.modifiedLength = bModifiedEnd - bChange.modifiedStart; + } + } + } + } + } + return changes; } + private _findBetterContiguousSequence(originalStart: number, originalLength: number, modifiedStart: number, modifiedLength: number, desiredLength: number): [number, number] | null { + if (originalLength < desiredLength || modifiedLength < desiredLength) { + return null; + } + const originalMax = originalStart + originalLength - desiredLength + 1; + const modifiedMax = modifiedStart + modifiedLength - desiredLength + 1; + let bestScore = 0; + let bestOriginalStart = 0; + let bestModifiedStart = 0; + for (let i = originalStart; i < originalMax; i++) { + for (let j = modifiedStart; j < modifiedMax; j++) { + const score = this._contiguousSequenceScore(i, j, desiredLength); + if (score > 0 && score > bestScore) { + bestScore = score; + bestOriginalStart = i; + bestModifiedStart = j; + } + } + } + if (bestScore > 0) { + return [bestOriginalStart, bestModifiedStart]; + } + return null; + } + + private _contiguousSequenceScore(originalStart: number, modifiedStart: number, length: number): number { + let score = 0; + for (let l = 0; l < length; l++) { + if (!this.ElementsAreEqual(originalStart + l, modifiedStart + l)) { + return 0; + } + score += this._originalStringElements[originalStart + l].length; + } + return score; + } + private _OriginalIsBoundary(index: number): boolean { if (index <= 0 || index >= this._originalElementsOrHash.length - 1) { return true; diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index f70aa9d8d..6a69c0e6a 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -629,7 +629,7 @@ export class PauseableEmitter extends Emitter { if (this._mergeFn) { // use the merge function to create a single composite // event. make a copy in case firing pauses this emitter - const events = this._eventQueue.toArray(); + const events = Array.from(this._eventQueue); this._eventQueue.clear(); super.fire(this._mergeFn(events)); diff --git a/src/vs/base/common/hash.ts b/src/vs/base/common/hash.ts index 13a1847e0..b39581a84 100644 --- a/src/vs/base/common/hash.ts +++ b/src/vs/base/common/hash.ts @@ -106,8 +106,14 @@ function leftPad(value: string, length: number, char: string = '0'): string { return value; } -function toHexString(value: number, bitsize: number = 32): string { - return leftPad((value >>> 0).toString(16), bitsize / 4); +export function toHexString(buffer: ArrayBuffer): string; +export function toHexString(value: number, bitsize?: number): string; +export function toHexString(bufferOrValue: ArrayBuffer | number, bitsize: number = 32): string { + if (bufferOrValue instanceof ArrayBuffer) { + return Array.from(new Uint8Array(bufferOrValue)).map(b => b.toString(16).padStart(2, '0')).join(''); + } + + return leftPad((bufferOrValue >>> 0).toString(16), bitsize / 4); } /** diff --git a/src/vs/base/common/htmlContent.ts b/src/vs/base/common/htmlContent.ts index 32f463ff8..829a34800 100644 --- a/src/vs/base/common/htmlContent.ts +++ b/src/vs/base/common/htmlContent.ts @@ -21,52 +21,52 @@ export const enum MarkdownStringTextNewlineStyle { } export class MarkdownString implements IMarkdownString { - private readonly _isTrusted: boolean; - private readonly _supportThemeIcons: boolean; + + public value: string; + public isTrusted?: boolean; + public supportThemeIcons?: boolean; constructor( - private _value: string = '', + value: string = '', isTrustedOrOptions: boolean | { isTrusted?: boolean, supportThemeIcons?: boolean } = false, ) { - if (typeof this._value !== 'string') { + this.value = value; + if (typeof this.value !== 'string') { throw illegalArgument('value'); } if (typeof isTrustedOrOptions === 'boolean') { - this._isTrusted = isTrustedOrOptions; - this._supportThemeIcons = false; + this.isTrusted = isTrustedOrOptions; + this.supportThemeIcons = false; } else { - this._isTrusted = isTrustedOrOptions.isTrusted ?? false; - this._supportThemeIcons = isTrustedOrOptions.supportThemeIcons ?? false; + this.isTrusted = isTrustedOrOptions.isTrusted ?? undefined; + this.supportThemeIcons = isTrustedOrOptions.supportThemeIcons ?? false; } } - get value() { return this._value; } - get isTrusted() { return this._isTrusted; } - get supportThemeIcons() { return this._supportThemeIcons; } - appendText(value: string, newlineStyle: MarkdownStringTextNewlineStyle = MarkdownStringTextNewlineStyle.Paragraph): MarkdownString { // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash - this._value += (this._supportThemeIcons ? escapeCodicons(value) : value) + this.value += (this.supportThemeIcons ? escapeCodicons(value) : value) .replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&') + .replace(/([ \t]+)/g, (_match, g1) => ' '.repeat(g1.length)) + .replace(/^>/gm, '\\>') .replace(/\n/g, newlineStyle === MarkdownStringTextNewlineStyle.Break ? '\\\n' : '\n\n'); return this; } appendMarkdown(value: string): MarkdownString { - this._value += value; - + this.value += value; return this; } appendCodeblock(langId: string, code: string): MarkdownString { - this._value += '\n```'; - this._value += langId; - this._value += '\n'; - this._value += code; - this._value += '\n```\n'; + this.value += '\n```'; + this.value += langId; + this.value += '\n'; + this.value += code; + this.value += '\n```\n'; return this; } } diff --git a/src/vs/base/common/json.ts b/src/vs/base/common/json.ts index 933e370f9..8b464edbf 100644 --- a/src/vs/base/common/json.ts +++ b/src/vs/base/common/json.ts @@ -126,7 +126,7 @@ export interface Location { /** * Matches the locations path against a pattern consisting of strings (for properties) and numbers (for array indices). * '*' will match a single segment, of any property name or index. - * '**' will match a sequece of segments or no segment, of any property name or index. + * '**' will match a sequence of segments or no segment, of any property name or index. */ matches: (patterns: JSONPath) => boolean; /** diff --git a/src/vs/base/common/labels.ts b/src/vs/base/common/labels.ts index 5f3fab864..5c1aac3bc 100644 --- a/src/vs/base/common/labels.ts +++ b/src/vs/base/common/labels.ts @@ -95,6 +95,10 @@ function hasDriveLetter(path: string): boolean { return !!(isWindows && path && path[1] === ':'); } +export function extractDriveLetter(path: string): string | undefined { + return hasDriveLetter(path) ? path[0] : undefined; +} + export function normalizeDriveLetter(path: string): string { if (hasDriveLetter(path)) { return path.charAt(0).toUpperCase() + path.slice(1); diff --git a/src/vs/base/common/linkedList.ts b/src/vs/base/common/linkedList.ts index 8ca17bc73..ddeb494cc 100644 --- a/src/vs/base/common/linkedList.ts +++ b/src/vs/base/common/linkedList.ts @@ -131,12 +131,4 @@ export class LinkedList { node = node.next; } } - - toArray(): E[] { - const result: E[] = []; - for (let node = this._first; node !== Node.Undefined; node = node.next) { - result.push(node.element); - } - return result; - } } diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index deaf56955..0e08876e5 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -6,8 +6,6 @@ import { URI } from 'vs/base/common/uri'; import { CharCode } from 'vs/base/common/charCode'; import { compareSubstringIgnoreCase, compare, compareSubstring, compareIgnoreCase } from 'vs/base/common/strings'; -import { isLinux } from 'vs/base/common/platform'; -import { Schemas } from 'vs/base/common/network'; export function getOrSet(map: Map, key: K, value: V): V { let result = map.get(key); @@ -77,6 +75,57 @@ export class StringIterator implements IKeyIterator { } } +export class ConfigKeysIterator implements IKeyIterator { + + private _value!: string; + private _from!: number; + private _to!: number; + + constructor( + private readonly _caseSensitive: boolean = true + ) { } + + reset(key: string): this { + this._value = key; + this._from = 0; + this._to = 0; + return this.next(); + } + + hasNext(): boolean { + return this._to < this._value.length; + } + + next(): this { + // this._data = key.split(/[\\/]/).filter(s => !!s); + this._from = this._to; + let justSeps = true; + for (; this._to < this._value.length; this._to++) { + const ch = this._value.charCodeAt(this._to); + if (ch === CharCode.Period) { + if (justSeps) { + this._from++; + } else { + break; + } + } else { + justSeps = false; + } + } + return this; + } + + cmp(a: string): number { + return this._caseSensitive + ? compareSubstring(a, this._value, 0, a.length, this._from, this._to) + : compareSubstringIgnoreCase(a, this._value, 0, a.length, this._from, this._to); + } + + value(): string { + return this._value.substring(this._from, this._to); + } +} + export class PathIterator implements IKeyIterator { private _value!: string; @@ -140,7 +189,7 @@ export class UriIterator implements IKeyIterator { private _states: UriIteratorState[] = []; private _stateIdx: number = 0; - constructor(private readonly _ignorePathCasing: boolean | undefined) { } + constructor(private readonly _ignorePathCasing: (uri: URI) => boolean) { } reset(key: URI): this { this._value = key; @@ -152,10 +201,7 @@ export class UriIterator implements IKeyIterator { this._states.push(UriIteratorState.Authority); } if (this._value.path) { - this._pathIterator = new PathIterator(false, this._ignorePathCasing === undefined - ? key.scheme === Schemas.file && isLinux - : !this._ignorePathCasing - ); + this._pathIterator = new PathIterator(false, !this._ignorePathCasing(key)); this._pathIterator.reset(key.path); if (this._pathIterator.value()) { this._states.push(UriIteratorState.Path); @@ -231,7 +277,7 @@ class TernarySearchTreeNode { export class TernarySearchTree { - static forUris(ignorePathCasing?: boolean): TernarySearchTree { + static forUris(ignorePathCasing: (key: URI) => boolean = () => false): TernarySearchTree { return new TernarySearchTree(new UriIterator(ignorePathCasing)); } @@ -243,6 +289,10 @@ export class TernarySearchTree { return new TernarySearchTree(new StringIterator()); } + static forConfigKeys(): TernarySearchTree { + return new TernarySearchTree(new ConfigKeysIterator()); + } + private _iter: IKeyIterator; private _root: TernarySearchTreeNode | undefined; @@ -301,6 +351,10 @@ export class TernarySearchTree { } get(key: K): V | undefined { + return this._getNode(key)?.value; + } + + private _getNode(key: K) { const iter = this._iter.reset(key); let node = this._root; while (node) { @@ -319,7 +373,12 @@ export class TernarySearchTree { break; } } - return node ? node.value : undefined; + return node; + } + + has(key: K): boolean { + const node = this._getNode(key); + return !(node?.value === undefined && node?.mid === undefined); } delete(key: K): void { @@ -352,11 +411,18 @@ export class TernarySearchTree { stack.push([0, node]); node = node.mid; } else { - // remove element - node.value = undefined; + if (superStr) { + // remove children + node.left = undefined; + node.mid = undefined; + node.right = undefined; + } else { + // remove element + node.value = undefined; + } // clean up empty nodes - while (stack.length > 0 && (node.isEmpty() || superStr)) { + while (stack.length > 0 && node.isEmpty()) { let [dir, parent] = stack.pop()!; switch (dir) { case 1: parent.left = undefined; break; @@ -394,7 +460,7 @@ export class TernarySearchTree { return node && node.value || candidate; } - findSuperstr(key: K): Iterator | undefined { + findSuperstr(key: K): IterableIterator<[K, V]> | undefined { const iter = this._iter.reset(key); let node = this._root; while (node) { @@ -414,7 +480,7 @@ export class TernarySearchTree { if (!node.mid) { return undefined; } else { - return this._values(node.mid); + return this._entries(node.mid); } } } @@ -431,12 +497,6 @@ export class TernarySearchTree { yield* this._entries(this._root); } - private *_values(node: TernarySearchTreeNode): IterableIterator { - for (const [, value] of this._entries(node)) { - yield value; - } - } - private *_entries(node: TernarySearchTreeNode | undefined): IterableIterator<[K, V]> { if (node) { // left diff --git a/src/vs/base/common/marked/cgmanifest.json b/src/vs/base/common/marked/cgmanifest.json index f3477931d..15e641cb5 100644 --- a/src/vs/base/common/marked/cgmanifest.json +++ b/src/vs/base/common/marked/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "marked", "repositoryUrl": "https://github.com/markedjs/marked", - "commitHash": "529a8d4e185a8aa561e4d8d2891f8556b5717cd4" + "commitHash": "8cfa29ccd2a2759e8e60fe0d8d6df8c022beda4e" } }, "license": "MIT", - "version": "0.6.2" + "version": "1.1.0" } ], "version": 1 diff --git a/src/vs/base/common/marked/marked.js b/src/vs/base/common/marked/marked.js index fb133b90f..ce8d16410 100644 --- a/src/vs/base/common/marked/marked.js +++ b/src/vs/base/common/marked/marked.js @@ -12,7 +12,7 @@ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : - (global = global || self, global.marked = factory()); + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.marked = factory()); }(this, (function () { 'use strict'; function _defineProperties(target, props) { @@ -368,11 +368,28 @@ function checkSanitizeDeprecation(opt) { if (opt && opt.sanitize && !opt.silent) { - // VS CODE CHANGE - // Disable logging about sanitize options. We already use insane after running the sanitizer - - // 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'); + 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 + + + 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; } var helpers = { @@ -386,7 +403,8 @@ splitCells: splitCells, rtrim: rtrim, findClosingBracket: findClosingBracket, - checkSanitizeDeprecation: checkSanitizeDeprecation + checkSanitizeDeprecation: checkSanitizeDeprecation, + repeatString: repeatString }; var defaults$1 = defaults.defaults; @@ -620,7 +638,7 @@ // so it is seen as the next token. space = item.length; - item = item.replace(/^ *([*+-]|\d+[.)]) */, ''); // Outdent whatever the + item = item.replace(/^ *([*+-]|\d+[.)]) ?/, ''); // Outdent whatever the // list item contains. Hacky. if (~item.indexOf('\n ')) { @@ -1138,11 +1156,11 @@ block.gfm = merge$1({}, block.normal, { nptable: '^ *([^|\\n ].*\\|.*)\\n' // Header - + ' *([-:]+ *\\|[-| :]*)' // Align + + ' {0,3}([-:]+ *\\|[-| :]*)' // Align + '(?:\\n((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)', // Cells table: '^ *\\|(.+)\\n' // Header - + ' *\\|?( *[-:]+[-| :]*)' // Align + + ' {0,3}\\|?( *[-:]+[-| :]*)' // Align + '(?:\\n *((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)' // Cells }); @@ -1187,24 +1205,24 @@ start: /^(?:(\*\*(?=[*punctuation]))|\*\*)(?![\s])|__/, // (1) returns if starts w/ punctuation middle: /^\*\*(?:(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)|\*(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)*?\*)+?\*\*$|^__(?![\s])((?:(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)|_(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)*?_)+?)__$/, - endAst: /[^punctuation\s]\*\*(?!\*)|[punctuation]\*\*(?!\*)(?:(?=[punctuation\s]|$))/, + endAst: /[^punctuation\s]\*\*(?!\*)|[punctuation]\*\*(?!\*)(?:(?=[punctuation_\s]|$))/, // last char can't be punct, or final * must also be followed by punct (or endline) - endUnd: /[^\s]__(?!_)(?:(?=[punctuation\s])|$)/ // last char can't be a space, and final _ must preceed punct or \s (or endline) + endUnd: /[^\s]__(?!_)(?:(?=[punctuation*\s])|$)/ // last char can't be a space, and final _ must preceed punct or \s (or endline) }, em: { start: /^(?:(\*(?=[punctuation]))|\*)(?![*\s])|_/, // (1) returns if starts w/ punctuation middle: /^\*(?:(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)|\*(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)*?\*)+?\*$|^_(?![_\s])(?:(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)|_(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)*?_)+?_$/, - endAst: /[^punctuation\s]\*(?!\*)|[punctuation]\*(?!\*)(?:(?=[punctuation\s]|$))/, + endAst: /[^punctuation\s]\*(?!\*)|[punctuation]\*(?!\*)(?:(?=[punctuation_\s]|$))/, // last char can't be punct, or final * must also be followed by punct (or endline) - endUnd: /[^\s]_(?!_)(?:(?=[punctuation\s])|$)/ // last char can't be a space, and final _ must preceed punct or \s (or endline) + endUnd: /[^\s]_(?!_)(?:(?=[punctuation*\s])|$)/ // last char can't be a space, and final _ must preceed punct or \s (or endline) }, code: /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/, br: /^( {2,}|\\)\n(?!\s*$)/, del: noopTest$1, - text: /^(`+|[^`])(?:[\s\S]*?(?:(?=[\\ 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); + maskedSrc = maskedSrc.slice(0, match.index) + '[' + repeatString$1('a', match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex); } } } @@ -1660,7 +1688,7 @@ 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); + maskedSrc = maskedSrc.slice(0, match.index) + '[' + repeatString$1('a', match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.blockSkip.lastIndex); } while (src) { @@ -2073,6 +2101,15 @@ 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 */ @@ -2606,6 +2643,42 @@ } } }; + /** + * Parse Inline + */ + + + 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$2({}, marked.defaults, opt || {}); + checkSanitizeDeprecation$1(opt); + + try { + var tokens = Lexer_1.lexInline(src, opt); + + if (opt.walkTokens) { + marked.walkTokens(tokens, opt.walkTokens); + } + + return Parser_1.parseInline(tokens, opt); + } catch (e) { + e.message += '\nPlease report this to https://github.com/markedjs/marked.'; + + if (opt.silent) { + return '

An error occurred:

' + escape$2(e.message + '', true) + '
'; + } + + throw e; + } + }; /** * Expose */ diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index f475b10e5..4e12b577e 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -78,6 +78,12 @@ export namespace Schemas { * Scheme used for extension pages */ export const extension = 'extension'; + + /** + * Scheme used as a replacement of `file` scheme to load + * files with our custom protocol handler (desktop only). + */ + export const vscodeFileResource = 'vscode-file'; } class RemoteAuthoritiesImpl { @@ -132,6 +138,8 @@ export const RemoteAuthorities = new RemoteAuthoritiesImpl(); class FileAccessImpl { + private readonly FALLBACK_AUTHORITY = 'vscode-app'; + /** * Returns a URI to use in contexts where the browser is responsible * for loading (e.g. fetch()) or when used within the DOM. @@ -143,13 +151,43 @@ class FileAccessImpl { asBrowserUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI { const uri = this.toUri(uriOrModule, moduleIdToUrl); + // Handle remote URIs via `RemoteAuthorities` if (uri.scheme === Schemas.vscodeRemote) { return RemoteAuthorities.rewrite(uri); } + // Only convert the URI if we are in a native context and it has `file:` scheme + if (platform.isElectronSandboxed && platform.isNative && uri.scheme === Schemas.file) { + return this.toCodeFileUri(uri); + } + return uri; } + /** + * TODO@bpasero remove me eventually when vscode-file is adopted everywhere + */ + _asCodeFileUri(uri: URI): URI; + _asCodeFileUri(moduleId: string, moduleIdToUrl: { toUrl(moduleId: string): string }): URI; + _asCodeFileUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI { + const uri = this.toUri(uriOrModule, moduleIdToUrl); + + return this.toCodeFileUri(uri); + } + + private toCodeFileUri(uri: URI): URI { + return uri.with({ + scheme: Schemas.vscodeFileResource, + // We need to provide an authority here so that it can serve + // as origin for network and loading matters in chromium. + // If the URI is not coming with an authority already, we + // add our own + authority: uri.authority || this.FALLBACK_AUTHORITY, + query: null, + fragment: null + }); + } + /** * Returns the `file` URI to use in contexts where node.js * is responsible for loading. @@ -159,6 +197,19 @@ class FileAccessImpl { asFileUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI { const uri = this.toUri(uriOrModule, moduleIdToUrl); + // Only convert the URI if it is `vscode-file:` scheme + if (uri.scheme === Schemas.vscodeFileResource) { + return uri.with({ + scheme: Schemas.file, + // Only preserve the `authority` if it is different from + // our fallback authority. This ensures we properly preserve + // Windows UNC paths that come with their own authority. + authority: uri.authority !== this.FALLBACK_AUTHORITY ? uri.authority : null, + query: null, + fragment: null + }); + } + return uri; } diff --git a/src/vs/base/common/performance.js b/src/vs/base/common/performance.js index 5893db444..b10f9b1d6 100644 --- a/src/vs/base/common/performance.js +++ b/src/vs/base/common/performance.js @@ -12,7 +12,7 @@ function _factory(sharedObj) { sharedObj.MonacoPerformanceMarks = sharedObj.MonacoPerformanceMarks || []; const _dataLen = 2; - const _timeStamp = typeof console.timeStamp === 'function' ? console.timeStamp.bind(console) : () => { }; + const _nativeMark = typeof performance === 'object' && typeof performance.mark === 'function' ? performance.mark.bind(performance) : () => { }; function importEntries(entries) { sharedObj.MonacoPerformanceMarks.splice(0, 0, ...entries); @@ -55,7 +55,7 @@ function _factory(sharedObj) { function mark(name) { sharedObj.MonacoPerformanceMarks.push(name, Date.now()); - _timeStamp(name); + _nativeMark(name); } const exports = { diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index 3361d83be..61fa4d71f 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -33,8 +33,8 @@ export interface INodeProcess { versions?: { electron?: string; }; + sandboxed?: boolean; // Electron type?: string; - getuid(): number; cwd(): string; } declare const process: INodeProcess; @@ -55,11 +55,12 @@ if (typeof process !== 'undefined') { // Native environment (non-sandboxed) nodeProcess = process; } else if (typeof _globals.vscode !== 'undefined') { - // Native envionment (sandboxed) + // Native environment (sandboxed) nodeProcess = _globals.vscode.process; } const isElectronRenderer = typeof nodeProcess?.versions?.electron === 'string' && nodeProcess.type === 'renderer'; +export const isElectronSandboxed = isElectronRenderer && nodeProcess?.sandboxed; // Web environment if (typeof navigator === 'object' && !isElectronRenderer) { @@ -133,10 +134,6 @@ export const isIOS = _isIOS; export const platform = _platform; export const userAgent = _userAgent; -export function isRootUser(): boolean { - return !!nodeProcess && !_isWindows && (nodeProcess.getuid() === 0); -} - /** * The language used for the user interface. The format of * the string is all lower case (e.g. zh-tw for Traditional diff --git a/src/vs/base/common/process.ts b/src/vs/base/common/process.ts index 5e85d6721..4b9dde872 100644 --- a/src/vs/base/common/process.ts +++ b/src/vs/base/common/process.ts @@ -16,7 +16,16 @@ if (typeof process !== 'undefined') { // Native sandbox environment else if (typeof globals.vscode !== 'undefined') { - safeProcess = globals.vscode.process; + safeProcess = { + + // Supported + get platform(): 'win32' | 'linux' | 'darwin' { return globals.vscode.process.platform; }, + get env() { return globals.vscode.process.env; }, + nextTick(callback: (...args: any[]) => void): void { return setImmediate(callback); }, + + // Unsupported + cwd(): string { return globals.vscode.process.env['VSCODE_CWD'] || globals.vscode.process.execPath.substr(0, globals.vscode.process.execPath.lastIndexOf(globals.vscode.process.platform === 'win32' ? '\\' : '/')); } + }; } // Web environment @@ -29,8 +38,7 @@ else { // Unsupported get env() { return Object.create(null); }, - cwd(): string { return '/'; }, - getuid(): number { return -1; } + cwd(): string { return '/'; } }; } diff --git a/src/vs/base/common/resourceTree.ts b/src/vs/base/common/resourceTree.ts index 23f661e1b..96cdee54c 100644 --- a/src/vs/base/common/resourceTree.ts +++ b/src/vs/base/common/resourceTree.ts @@ -5,7 +5,7 @@ import { memoize } from 'vs/base/common/decorators'; import * as paths from 'vs/base/common/path'; -import { relativePath, joinPath } from 'vs/base/common/resources'; +import { IExtUri, extUri as defaultExtUri } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { PathIterator } from 'vs/base/common/map'; @@ -95,12 +95,12 @@ export class ResourceTree, C> { return obj instanceof Node; } - constructor(context: C, rootURI: URI = URI.file('/')) { + constructor(context: C, rootURI: URI = URI.file('/'), private extUri: IExtUri = defaultExtUri) { this.root = new Node(rootURI, '', context); } add(uri: URI, element: T): void { - const key = relativePath(this.root.uri, uri) || uri.fsPath; + const key = this.extUri.relativePath(this.root.uri, uri) || uri.path; const iterator = new PathIterator(false).reset(key); let node = this.root; let path = ''; @@ -113,7 +113,7 @@ export class ResourceTree, C> { if (!child) { child = new Node( - joinPath(this.root.uri, path), + this.extUri.joinPath(this.root.uri, path), path, this.root.context, iterator.hasNext() ? undefined : element, @@ -136,7 +136,7 @@ export class ResourceTree, C> { } delete(uri: URI): T | undefined { - const key = relativePath(this.root.uri, uri) || uri.fsPath; + const key = this.extUri.relativePath(this.root.uri, uri) || uri.path; const iterator = new PathIterator(false).reset(key); return this._delete(this.root, iterator); } @@ -168,7 +168,7 @@ export class ResourceTree, C> { } getNode(uri: URI): IResourceNode | undefined { - const key = relativePath(this.root.uri, uri) || uri.fsPath; + const key = this.extUri.relativePath(this.root.uri, uri) || uri.path; const iterator = new PathIterator(false).reset(key); let node = this.root; diff --git a/src/vs/base/common/resources.ts b/src/vs/base/common/resources.ts index 621c97368..82619c4b0 100644 --- a/src/vs/base/common/resources.ts +++ b/src/vs/base/common/resources.ts @@ -10,8 +10,6 @@ import { equalsIgnoreCase, compare as strCompare } from 'vs/base/common/strings' import { Schemas } from 'vs/base/common/network'; import { isWindows, isLinux } from 'vs/base/common/platform'; import { CharCode } from 'vs/base/common/charCode'; -import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob'; -import { TernarySearchTree } from 'vs/base/common/map'; export function originalFSPath(uri: URI): string { return uriToFsPath(uri, true); @@ -58,6 +56,11 @@ export interface IExtUri { */ getComparisonKey(uri: URI, ignoreFragment?: boolean): string; + /** + * Whether the casing of the path-component of the uri should be ignored. + */ + ignorePathCasing(uri: URI): boolean; + // --- path math basenameOrAuthority(resource: URI): string; @@ -161,6 +164,10 @@ export class ExtUri implements IExtUri { }).toString(); } + ignorePathCasing(uri: URI): boolean { + return this._ignorePathCasing(uri); + } + isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment: boolean = false): boolean { if (base.scheme === parentCandidate.scheme) { if (base.scheme === Schemas.file) { @@ -427,33 +434,6 @@ export namespace DataUri { } } -export class ResourceGlobMatcher { - - private readonly globalExpression: ParsedExpression; - private readonly expressionsByRoot: TernarySearchTree = TernarySearchTree.forUris<{ root: URI, expression: ParsedExpression }>(); - - constructor( - globalExpression: IExpression, - rootExpressions: { root: URI, expression: IExpression }[] - ) { - this.globalExpression = parse(globalExpression); - for (const expression of rootExpressions) { - this.expressionsByRoot.set(expression.root, { root: expression.root, expression: parse(expression.expression) }); - } - } - - matches(resource: URI): boolean { - const rootExpression = this.expressionsByRoot.findSubstr(resource); - if (rootExpression) { - const path = relativePath(rootExpression.root, resource); - if (path && !!rootExpression.expression(path)) { - return true; - } - } - return !!this.globalExpression(resource.path); - } -} - export function toLocalResource(resource: URI, authority: string | undefined, localScheme: string): URI { if (authority) { let path = resource.path; diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index 9e35fd6cb..84e9c52f2 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -69,6 +69,14 @@ export function count(value: string, character: string): number { return result; } +export function truncate(value: string, maxLength: number, suffix = '…'): string { + if (value.length <= maxLength) { + return value; + } + + return `${value.substr(0, maxLength)}${suffix}`; +} + /** * Removes all occurrences of needle from the beginning and end of haystack. * @param haystack string to trim @@ -208,6 +216,10 @@ export function regExpFlags(regexp: RegExp): string { + ((regexp as any /* standalone editor compilation */).unicode ? 'u' : ''); } +export function splitLines(str: string): string[] { + return str.split(/\r\n|\r|\n/); +} + /** * Returns first index of the string that is not whitespace. * If string is empty or contains only whitespaces, returns -1 @@ -879,9 +891,15 @@ export function getNLines(str: string, n = 1): string { n--; } while (n > 0 && idx >= 0); - return idx >= 0 ? - str.substr(0, idx) : - str; + if (idx === -1) { + return str; + } + + if (str[idx - 1] === '\r') { + idx--; + } + + return str.substr(0, idx); } /** diff --git a/src/vs/base/common/uri.ts b/src/vs/base/common/uri.ts index 5ac44dc84..8111602ef 100644 --- a/src/vs/base/common/uri.ts +++ b/src/vs/base/common/uri.ts @@ -345,7 +345,7 @@ export class URI implements UriComponents { */ static joinPath(uri: URI, ...pathFragment: string[]): URI { if (!uri.path) { - throw new Error(`[UriError]: cannot call joinPaths on URI without path`); + throw new Error(`[UriError]: cannot call joinPath on URI without path`); } let newPath: string; if (isWindows && uri.scheme === 'file') { diff --git a/src/vs/base/common/uuid.ts b/src/vs/base/common/uuid.ts index 57d9db69d..141fd83ce 100644 --- a/src/vs/base/common/uuid.ts +++ b/src/vs/base/common/uuid.ts @@ -17,8 +17,9 @@ for (let i = 0; i < 256; i++) { _hex.push(i.toString(16).padStart(2, '0')); } -// todo@joh node nodejs use `crypto#randomBytes`, see: https://nodejs.org/docs/latest/api/crypto.html#crypto_crypto_randombytes_size_callback -// todo@joh use browser-crypto +// todo@jrieken +// 1. node nodejs use`crypto#randomBytes`, see: https://nodejs.org/docs/latest/api/crypto.html#crypto_crypto_randombytes_size_callback +// 2. use browser-crypto const _fillRandomValues = function (bucket: Uint8Array): Uint8Array { for (let i = 0; i < bucket.length; i++) { bucket[i] = Math.floor(Math.random() * 256); diff --git a/src/vs/base/common/worker/simpleWorker.ts b/src/vs/base/common/worker/simpleWorker.ts index 19f154a79..1b81394ae 100644 --- a/src/vs/base/common/worker/simpleWorker.ts +++ b/src/vs/base/common/worker/simpleWorker.ts @@ -358,6 +358,10 @@ export class SimpleWorkerServer { delete loaderConfig.paths['vs']; } } + if (typeof loaderConfig.trustedTypesPolicy !== undefined) { + // don't use, it has been destroyed during serialize + delete loaderConfig['trustedTypesPolicy']; + } // Since this is in a web worker, enable catching errors loaderConfig.catchError = true; diff --git a/src/vs/base/node/paths.ts b/src/vs/base/node/paths.ts index 977eaf880..eaf03e6e4 100644 --- a/src/vs/base/node/paths.ts +++ b/src/vs/base/node/paths.ts @@ -5,12 +5,7 @@ import { FileAccess } from 'vs/base/common/network'; -interface IPaths { - getAppDataPath(platform: string): string; - getDefaultUserDataPath(platform: string): string; -} - const pathsPath = FileAccess.asFileUri('paths', require).fsPath; -const paths = require.__$__nodeRequire(pathsPath); -export const getAppDataPath = paths.getAppDataPath; +const paths = require.__$__nodeRequire<{ getDefaultUserDataPath(): string }>(pathsPath); + export const getDefaultUserDataPath = paths.getDefaultUserDataPath; diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts index bc8e97c26..b27c8734f 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -126,7 +126,8 @@ const enum ProtocolMessageType { Control = 2, Ack = 3, KeepAlive = 4, - Disconnect = 5 + Disconnect = 5, + ReplayRequest = 6 } export const enum ProtocolConstants { @@ -274,7 +275,11 @@ class ProtocolWriter { } public dispose(): void { - this.flush(); + try { + this.flush(); + } catch (err) { + // ignore error, since the socket could be already closed + } this._isDisposed = true; } @@ -601,6 +606,8 @@ export class PersistentProtocol implements IMessagePassingProtocol { private _outgoingKeepAliveTimeout: any | null; private _incomingKeepAliveTimeout: any | null; + private _lastReplayRequestTime: number; + private _socket: ISocket; private _socketWriter: ProtocolWriter; private _socketReader: ProtocolReader; @@ -642,6 +649,8 @@ export class PersistentProtocol implements IMessagePassingProtocol { this._outgoingKeepAliveTimeout = null; this._incomingKeepAliveTimeout = null; + this._lastReplayRequestTime = 0; + this._socketDisposables = []; this._socket = socket; this._socketWriter = new ProtocolWriter(this._socket); @@ -747,6 +756,8 @@ export class PersistentProtocol implements IMessagePassingProtocol { this._onSocketTimeout.flushBuffer(); this._socket.dispose(); + this._lastReplayRequestTime = 0; + this._socket = socket; this._socketWriter = new ProtocolWriter(this._socket); this._socketDisposables.push(this._socketWriter); @@ -792,17 +803,31 @@ export class PersistentProtocol implements IMessagePassingProtocol { if (msg.type === ProtocolMessageType.Regular) { if (msg.id > this._incomingMsgId) { if (msg.id !== this._incomingMsgId + 1) { - console.error(`PROTOCOL CORRUPTION, LAST SAW MSG ${this._incomingMsgId} AND HAVE NOW RECEIVED MSG ${msg.id}`); + // in case we missed some messages we ask the other party to resend them + const now = Date.now(); + if (now - this._lastReplayRequestTime > 10000) { + // send a replay request at most once every 10s + this._lastReplayRequestTime = now; + this._socketWriter.write(new ProtocolMessage(ProtocolMessageType.ReplayRequest, 0, 0, getEmptyBuffer())); + } + } else { + this._incomingMsgId = msg.id; + this._incomingMsgLastTime = Date.now(); + this._sendAckCheck(); + this._onMessage.fire(msg.data); } - this._incomingMsgId = msg.id; - this._incomingMsgLastTime = Date.now(); - this._sendAckCheck(); - this._onMessage.fire(msg.data); } } else if (msg.type === ProtocolMessageType.Control) { this._onControlMessage.fire(msg.data); } else if (msg.type === ProtocolMessageType.Disconnect) { this._onClose.fire(); + } else if (msg.type === ProtocolMessageType.ReplayRequest) { + // Send again all unacknowledged messages + const toSend = this._outgoingUnackMsg.toArray(); + for (let i = 0, len = toSend.length; i < len; i++) { + this._socketWriter.write(toSend[i]); + } + this._recvAckCheck(); } } diff --git a/src/vs/base/parts/quickinput/browser/media/quickInput.css b/src/vs/base/parts/quickinput/browser/media/quickInput.css index 29a3f02bc..4489a73ac 100644 --- a/src/vs/base/parts/quickinput/browser/media/quickInput.css +++ b/src/vs/base/parts/quickinput/browser/media/quickInput.css @@ -130,6 +130,11 @@ padding: 5px 5px 2px 5px; } +.quick-input-message > .codicon { + margin: 0 0.2em; + vertical-align: text-bottom; +} + .quick-input-progress.monaco-progress-container { position: relative; } diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index 0a4c9f1ba..bddbd8a6e 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -27,8 +27,10 @@ import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/lis import { List, IListOptions, IListStyles } from 'vs/base/browser/ui/list/listWidget'; import { IInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox'; import { Color } from 'vs/base/common/color'; -import { registerIcon, Codicon } from 'vs/base/common/codicons'; +import { registerCodicon, Codicon } from 'vs/base/common/codicons'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { escape } from 'vs/base/common/strings'; +import { renderCodicons } from 'vs/base/browser/codicons'; export interface IQuickInputOptions { idPrefix: string; @@ -70,7 +72,7 @@ const $ = dom.$; type Writeable = { -readonly [P in keyof T]: T[P] }; -const backButtonIcon = registerIcon('quick-input-back', Codicon.arrowLeft); +const backButtonIcon = registerCodicon('quick-input-back', Codicon.arrowLeft, localize('backButtonIcon', 'Icon for the back button in the quick input dialog.')); const backButton = { iconClass: backButtonIcon.classNames, @@ -413,6 +415,7 @@ class QuickPick extends QuickInput implements IQuickPi private _valueSelection: Readonly<[number, number]> | undefined; private valueSelectionUpdated = true; private _validationMessage: string | undefined; + private _lastValidationMessage: string | undefined; private _ok: boolean | 'default' = 'default'; private _customButton = false; private _customButtonLabel: string | undefined; @@ -961,12 +964,11 @@ class QuickPick extends QuickInput implements IQuickPi this.selectedItemsToConfirm = null; } } - if (this.validationMessage) { - this.ui.message.textContent = this.validationMessage; - this.showMessageDecoration(Severity.Error); - } else { - this.ui.message.textContent = null; - this.showMessageDecoration(Severity.Ignore); + const validationMessage = this.validationMessage || ''; + if (this._lastValidationMessage !== validationMessage) { + this._lastValidationMessage = validationMessage; + dom.reset(this.ui.message, ...renderCodicons(escape(validationMessage))); + this.showMessageDecoration(this.validationMessage ? Severity.Error : Severity.Ignore); } this.ui.customButton.label = this.customLabel || ''; this.ui.customButton.element.title = this.customHover || ''; @@ -996,6 +998,7 @@ class InputBox extends QuickInput implements IInputBox { private _prompt: string | undefined; private noValidationMessage = InputBox.noPromptMessage; private _validationMessage: string | undefined; + private _lastValidationMessage: string | undefined; private readonly onDidValueChangeEmitter = this._register(new Emitter()); private readonly onDidAcceptEmitter = this._register(new Emitter()); @@ -1097,13 +1100,11 @@ class InputBox extends QuickInput implements IInputBox { if (this.ui.inputBox.password !== this.password) { this.ui.inputBox.password = this.password; } - if (!this.validationMessage && this.ui.message.textContent !== this.noValidationMessage) { - this.ui.message.textContent = this.noValidationMessage; - this.showMessageDecoration(Severity.Ignore); - } - if (this.validationMessage && this.ui.message.textContent !== this.validationMessage) { - this.ui.message.textContent = this.validationMessage; - this.showMessageDecoration(Severity.Error); + const validationMessage = this.validationMessage || this.noValidationMessage; + if (this._lastValidationMessage !== validationMessage) { + this._lastValidationMessage = validationMessage; + dom.reset(this.ui.message, ...renderCodicons(validationMessage)); + this.showMessageDecoration(this.validationMessage ? Severity.Error : Severity.Ignore); } } } @@ -1528,7 +1529,7 @@ export class QuickInputController extends Disposable { ui.inputBox.showDecoration(Severity.Ignore); ui.visibleCount.setCount(0); ui.count.setCount(0); - ui.message.textContent = ''; + dom.reset(ui.message); ui.progressBar.stop(); ui.list.setElements([]); ui.list.matchOnDescription = false; @@ -1696,7 +1697,7 @@ export class QuickInputController extends Disposable { this.ui.container.style.backgroundColor = quickInputBackground ? quickInputBackground.toString() : ''; this.ui.container.style.color = quickInputForeground ? quickInputForeground.toString() : ''; this.ui.container.style.border = contrastBorder ? `1px solid ${contrastBorder}` : ''; - this.ui.container.style.boxShadow = widgetShadow ? `0 5px 8px ${widgetShadow}` : ''; + this.ui.container.style.boxShadow = widgetShadow ? `0 0 8px 2px ${widgetShadow}` : ''; this.ui.inputBox.style(this.styles.inputBox); this.ui.count.style(this.styles.countBadge); this.ui.ok.style(this.styles.button); diff --git a/src/vs/base/parts/sandbox/electron-browser/preload.js b/src/vs/base/parts/sandbox/electron-browser/preload.js index d0cb6d428..a1aa638f8 100644 --- a/src/vs/base/parts/sandbox/electron-browser/preload.js +++ b/src/vs/base/parts/sandbox/electron-browser/preload.js @@ -35,6 +35,17 @@ } }, + /** + * @param {string} channel + * @param {any[]} args + * @returns {Promise | undefined} + */ + invoke(channel, ...args) { + if (validateIPC(channel)) { + return ipcRenderer.invoke(channel, ...args); + } + }, + /** * @param {string} channel * @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener @@ -97,80 +108,48 @@ /** * 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 */ process: { get platform() { return process.platform; }, get env() { return process.env; }, get versions() { return process.versions; }, get type() { return 'renderer'; }, + get execPath() { return process.execPath; }, - _whenEnvResolved: undefined, - whenEnvResolved: - /** - * @returns when the shell environment has been resolved. - */ - function () { - if (!this._whenEnvResolved) { - this._whenEnvResolved = resolveEnv(); - } + /** + * @param {{[key: string]: string}} userEnv + * @returns {Promise} + */ + resolveEnv(userEnv) { + return resolveEnv(userEnv); + }, - return this._whenEnvResolved; - }, + /** + * @returns {Promise} + */ + getProcessMemoryInfo() { + return process.getProcessMemoryInfo(); + }, - nextTick: - /** - * Adds callback to the "next tick queue". This queue is fully drained - * after the current operation on the JavaScript stack runs to completion - * and before the event loop is allowed to continue. - * - * @param {Function} callback - * @param {any[]} args - */ - function nextTick(callback, ...args) { - return process.nextTick(callback, ...args); - }, - - cwd: - /** - * @returns the current working directory. - */ - function () { - return process.cwd(); - }, - - getuid: - /** - * @returns the numeric user identity of the process - */ - function () { - return process.getuid(); - }, - - getProcessMemoryInfo: - /** - * @returns {Promise} - */ - function () { - return process.getProcessMemoryInfo(); - }, - - on: - /** - * @param {string} type - * @param {() => void} callback - */ - function (type, callback) { - if (validateProcessEventType(type)) { - process.on(type, callback); - } + /** + * @param {string} type + * @param {() => void} callback + */ + on(type, callback) { + if (validateProcessEventType(type)) { + process.on(type, callback); } + } }, /** * Some information about the context we are running in. */ context: { - get sandbox() { return process.argv.includes('--enable-sandbox'); } + get sandbox() { return process.sandboxed; } } }; @@ -197,6 +176,7 @@ /** * @param {string} channel + * @returns {true | never} */ function validateIPC(channel) { if (!channel || !channel.startsWith('vscode:')) { @@ -218,32 +198,40 @@ return true; } + /** @type {Promise | undefined} */ + let resolvedEnv = undefined; + /** * If VSCode is not run from a terminal, we should resolve additional * shell specific environment from the OS shell to ensure we are seeing * all development related environment variables. We do this from the * main process because it may involve spawning a shell. + * + * @param {{[key: string]: string}} userEnv + * @returns {Promise} */ - function resolveEnv() { - return new Promise(function (resolve) { - const handle = setTimeout(function () { - console.warn('Preload: Unable to resolve shell environment in a reasonable time'); + function resolveEnv(userEnv) { + if (!resolvedEnv) { - // It took too long to fetch the shell environment, return - resolve(); - }, 3000); + // Apply `userEnv` directly + Object.assign(process.env, userEnv); - ipcRenderer.once('vscode:acceptShellEnv', function (event, shellEnv) { - clearTimeout(handle); + // Resolve `shellEnv` from the main side + resolvedEnv = new Promise(function (resolve) { + ipcRenderer.once('vscode:acceptShellEnv', function (event, shellEnv) { - // Assign all keys of the shell environment to our process environment - Object.assign(process.env, shellEnv); + // Assign all keys of the shell environment to our process environment + // But make sure that the user environment wins in the end + Object.assign(process.env, shellEnv, userEnv); - resolve(); + resolve(); + }); + + ipcRenderer.send('vscode:fetchShellEnv'); }); + } - ipcRenderer.send('vscode:fetchShellEnv'); - }); + return resolvedEnv; } //#endregion diff --git a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts index 923383b48..69f3a2a86 100644 --- a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts +++ b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts @@ -12,45 +12,45 @@ export interface ISandboxNodeProcess extends INodeProcess { * The process.platform property returns a string identifying the operating system platform * on which the Node.js process is running. */ - platform: 'win32' | 'linux' | 'darwin'; + readonly platform: 'win32' | 'linux' | 'darwin'; /** * The type will always be Electron renderer. */ - type: 'renderer'; + readonly type: 'renderer'; /** * A list of versions for the current node.js/electron configuration. */ - versions: { [key: string]: string | undefined }; + readonly versions: { [key: string]: string | undefined }; /** * The process.env property returns an object containing the user environment. */ - env: IProcessEnvironment; + readonly env: IProcessEnvironment; /** - * The current working directory. + * The `execPath` will be the location of the executable of this application. */ - cwd(): string; + readonly execPath: string; /** - * Returns the numeric user identity of the process. + * Resolve the true process environment to use and apply it to `process.env`. + * + * There are different layers of environment that will apply: + * - `process.env`: this is the actual environment of the process before this method + * - `shellEnv` : if the program was not started from a terminal, we resolve all shell + * variables to get the same experience as if the program was started from + * a terminal (Linux, macOS) + * - `userEnv` : this is instance specific environment, e.g. if the user started the program + * from a terminal and changed certain variables + * + * The order of overwrites is `process.env` < `shellEnv` < `userEnv`. + * + * It is critical that every process awaits this method early on startup to get the right + * set of environment in `process.env`. */ - getuid(): number; - - /** - * Allows to await resolving the full process environment by checking for the shell environment - * of the OS in certain cases (e.g. when the app is started from the Dock on macOS). - */ - whenEnvResolved(): Promise; - - /** - * Adds callback to the "next tick queue". This queue is fully drained - * after the current operation on the JavaScript stack runs to completion - * and before the event loop is allowed to continue. - */ - nextTick(callback: (...args: any[]) => void, ...args: any[]): void; + resolveEnv(userEnv: IProcessEnvironment): Promise; /** * A listener on the process. Only a small subset of listener types are allowed. diff --git a/src/vs/base/parts/storage/common/storage.ts b/src/vs/base/parts/storage/common/storage.ts index b6602b6a6..8b55819cc 100644 --- a/src/vs/base/parts/storage/common/storage.ts +++ b/src/vs/base/parts/storage/common/storage.ts @@ -43,9 +43,10 @@ export interface IStorageDatabase { export interface IStorage extends IDisposable { + readonly onDidChangeStorage: Event; + readonly items: Map; readonly size: number; - readonly onDidChangeStorage: Event; init(): Promise; @@ -61,6 +62,8 @@ export interface IStorage extends IDisposable { set(key: string, value: string | boolean | number | undefined | null): Promise; delete(key: string): Promise; + whenFlushed(): Promise; + close(): Promise; } @@ -86,6 +89,8 @@ export class Storage extends Disposable implements IStorage { private pendingDeletes = new Set(); private pendingInserts = new Map(); + private readonly whenFlushedCallbacks: Function[] = []; + constructor( protected readonly database: IStorageDatabase, private readonly options: IStorageOptions = Object.create(null) @@ -273,8 +278,12 @@ export class Storage extends Disposable implements IStorage { await this.database.close(() => this.cache); } + private get hasPending() { + return this.pendingInserts.size > 0 || this.pendingDeletes.size > 0; + } + private flushPending(): Promise { - if (this.pendingInserts.size === 0 && this.pendingDeletes.size === 0) { + if (!this.hasPending) { return Promise.resolve(); // return early if nothing to do } @@ -285,8 +294,23 @@ export class Storage extends Disposable implements IStorage { this.pendingDeletes = new Set(); this.pendingInserts = new Map(); - // Update in storage - return this.database.updateItems(updateRequest); + // Update in storage and release any + // waiters we have once done + return this.database.updateItems(updateRequest).finally(() => { + if (!this.hasPending) { + while (this.whenFlushedCallbacks.length) { + this.whenFlushedCallbacks.pop()?.(); + } + } + }); + } + + whenFlushed(): Promise { + if (!this.hasPending) { + return Promise.resolve(); // return early if nothing to do + } + + return new Promise(resolve => this.whenFlushedCallbacks.push(resolve)); } } diff --git a/src/vs/base/parts/storage/test/node/storage.test.ts b/src/vs/base/parts/storage/test/node/storage.test.ts index 3c3510390..e735634c2 100644 --- a/src/vs/base/parts/storage/test/node/storage.test.ts +++ b/src/vs/base/parts/storage/test/node/storage.test.ts @@ -45,11 +45,16 @@ suite('Storage Library', function () { changes.add(key); }); + await storage.whenFlushed(); // returns immediately when no pending updates + // Simple updates const set1Promise = storage.set('bar', 'foo'); const set2Promise = storage.set('barNumber', 55); const set3Promise = storage.set('barBoolean', true); + let flushPromiseResolved = false; + storage.whenFlushed().then(() => flushPromiseResolved = true); + equal(storage.get('bar'), 'foo'); equal(storage.getNumber('barNumber'), 55); equal(storage.getBoolean('barBoolean'), true); @@ -62,6 +67,7 @@ suite('Storage Library', function () { let setPromiseResolved = false; await Promise.all([set1Promise, set2Promise, set3Promise]).then(() => setPromiseResolved = true); equal(setPromiseResolved, true); + equal(flushPromiseResolved, true); changes = new Set(); @@ -166,6 +172,9 @@ suite('Storage Library', function () { const set1Promise = storage.set('foo', 'bar'); const set2Promise = storage.set('bar', 'foo'); + let flushPromiseResolved = false; + storage.whenFlushed().then(() => flushPromiseResolved = true); + equal(storage.get('foo'), 'bar'); equal(storage.get('bar'), 'foo'); @@ -175,6 +184,7 @@ suite('Storage Library', function () { await storage.close(); equal(setPromiseResolved, true); + equal(flushPromiseResolved, true); storage = new Storage(new SQLiteStorageDatabase(join(storageDir, 'storage.db'))); await storage.init(); @@ -226,6 +236,9 @@ suite('Storage Library', function () { const set2Promise = storage.set('foo', 'bar2'); const set3Promise = storage.set('foo', 'bar3'); + let flushPromiseResolved = false; + storage.whenFlushed().then(() => flushPromiseResolved = true); + equal(storage.get('foo'), 'bar3'); equal(changes.size, 1); ok(changes.has('foo')); @@ -233,6 +246,7 @@ suite('Storage Library', function () { let setPromiseResolved = false; await Promise.all([set1Promise, set2Promise, set3Promise]).then(() => setPromiseResolved = true); ok(setPromiseResolved); + ok(flushPromiseResolved); changes = new Set(); diff --git a/src/vs/base/test/browser/comparers.test.ts b/src/vs/base/test/browser/comparers.test.ts index 4c40ea2a0..77ec3adf6 100644 --- a/src/vs/base/test/browser/comparers.test.ts +++ b/src/vs/base/test/browser/comparers.test.ts @@ -197,7 +197,7 @@ suite('Comparers', () => { // name-only comparisons assert(compareFileNamesDefault('a', 'A') === compareLocale('a', 'A'), 'the same letter sorts by locale'); assert(compareFileNamesDefault('â', 'Â') === compareLocale('â', 'Â'), 'the same accented letter sorts by locale'); - assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileNamesDefault), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order'); + // assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileNamesDefault), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order'); assert.deepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileNamesDefault), ['email', 'Email', 'émail', 'Émail'].sort(compareLocale), 'the same base characters with different case or accents sort in locale order'); // numeric comparisons @@ -259,7 +259,7 @@ suite('Comparers', () => { // name-only comparisons assert(compareFileExtensionsDefault('a', 'A') === compareLocale('a', 'A'), 'the same letter of different case sorts by locale'); assert(compareFileExtensionsDefault('â', 'Â') === compareLocale('â', 'Â'), 'the same accented letter of different case sorts by locale'); - assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileExtensionsDefault), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order'); + // assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileExtensionsDefault), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order'); assert.deepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileExtensionsDefault), ['email', 'Email', 'émail', 'Émail'].sort((a, b) => a.localeCompare(b)), 'the same base characters with different case or accents sort in locale order'); // name plus extension comparisons diff --git a/src/vs/base/test/common/hash.test.ts b/src/vs/base/test/browser/hash.test.ts similarity index 85% rename from src/vs/base/test/common/hash.test.ts rename to src/vs/base/test/browser/hash.test.ts index b5074f4ff..68ee17eeb 100644 --- a/src/vs/base/test/common/hash.test.ts +++ b/src/vs/base/test/browser/hash.test.ts @@ -2,8 +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 { hash, StringSHA1 } from 'vs/base/common/hash'; +import { sha1Hex } from 'vs/base/browser/hash'; suite('Hash', () => { test('string', () => { @@ -71,28 +73,32 @@ suite('Hash', () => { }); - function checkSHA1(strings: string[], expected: string) { + async function checkSHA1(str: string, expected: string) { + + // Test with StringSHA1 const hash = new StringSHA1(); - for (const str of strings) { - hash.update(str); - } - const actual = hash.digest(); + hash.update(str); + let actual = hash.digest(); + assert.equal(actual, expected); + + // Test with crypto.subtle + actual = await sha1Hex(str); assert.equal(actual, expected); } test('sha1-1', () => { - checkSHA1(['\udd56'], '9bdb77276c1852e1fb067820472812fcf6084024'); + return checkSHA1('\udd56', '9bdb77276c1852e1fb067820472812fcf6084024'); }); test('sha1-2', () => { - checkSHA1(['\udb52'], '9bdb77276c1852e1fb067820472812fcf6084024'); + return checkSHA1('\udb52', '9bdb77276c1852e1fb067820472812fcf6084024'); }); test('sha1-3', () => { - checkSHA1(['\uda02ꑍ'], '9b483a471f22fe7e09d83f221871a987244bbd3f'); + return checkSHA1('\uda02ꑍ', '9b483a471f22fe7e09d83f221871a987244bbd3f'); }); test('sha1-4', () => { - checkSHA1(['hello'], 'aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d'); + return checkSHA1('hello', 'aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d'); }); }); diff --git a/src/vs/base/test/browser/markdownRenderer.test.ts b/src/vs/base/test/browser/markdownRenderer.test.ts index 78f82030b..f4bfa18d1 100644 --- a/src/vs/base/test/browser/markdownRenderer.test.ts +++ b/src/vs/base/test/browser/markdownRenderer.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as marked from 'vs/base/common/marked/marked'; -import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; +import { renderMarkdown, renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; import { MarkdownString, IMarkdownString } from 'vs/base/common/htmlContent'; import { URI } from 'vs/base/common/uri'; import { parse } from 'vs/base/common/marshalling'; @@ -57,7 +57,7 @@ suite('MarkdownRenderer', () => { mds.appendText('$(zap) $(not a theme icon) $(add)'); let result: HTMLElement = renderMarkdown(mds); - assert.strictEqual(result.innerHTML, `

$(zap) $(not a theme icon) $(add)

`); + assert.strictEqual(result.innerHTML, `

$(zap) $(not a theme icon) $(add)

`); }); test('render appendMarkdown', () => { @@ -85,7 +85,7 @@ suite('MarkdownRenderer', () => { mds.appendText('$(zap) $(not a theme icon) $(add)'); let result: HTMLElement = renderMarkdown(mds); - assert.strictEqual(result.innerHTML, `

$(zap) $(not a theme icon) $(add)

`); + assert.strictEqual(result.innerHTML, `

$(zap) $(not a theme icon) $(add)

`); }); test('render appendMarkdown with escaped icon', () => { @@ -115,4 +115,20 @@ suite('MarkdownRenderer', () => { assert.ok(data.documentUri.toString().startsWith('file:///c%3A/')); }); + 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 result: string = renderMarkdownAsPlaintext(markdown); + assert.strictEqual(result, expected); + }); + + test('test html, hr, image, link are rendered plaintext', () => { + const markdown = { value: '
html
\n\n---\n![image](imageLink)\n[text](textLink)' }; + const expected = '\ntext\n'; + const result: string = renderMarkdownAsPlaintext(markdown); + assert.strictEqual(result, expected); + }); + }); }); diff --git a/src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts b/src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts index 5ab03d9cd..4d3c0944a 100644 --- a/src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts +++ b/src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts @@ -18,8 +18,11 @@ suite('ScrollbarState', () => { assert.equal(actual.getSliderSize(), 20); assert.equal(actual.getSliderPosition(), 249); - assert.equal(actual.getDesiredScrollPositionFromOffset(259), 32849); + + // 259 is greater than 230 so page down, 32787 + 339 = 33126 + assert.equal(actual.getDesiredScrollPositionFromOffsetPaged(259), 33126); + actual.setScrollPosition(32849); assert.equal(actual.getArrowSize(), 0); assert.equal(actual.getScrollPosition(), 32849); @@ -41,8 +44,11 @@ suite('ScrollbarState', () => { assert.equal(actual.getSliderSize(), 20); assert.equal(actual.getSliderPosition(), 230); - assert.equal(actual.getDesiredScrollPositionFromOffset(240 + 12), 32811); + + // 240 + 12 = 252; greater than 230 so page down, 32787 + 339 = 33126 + assert.equal(actual.getDesiredScrollPositionFromOffsetPaged(240 + 12), 33126); + actual.setScrollPosition(32811); assert.equal(actual.getArrowSize(), 12); assert.equal(actual.getScrollPosition(), 32811); diff --git a/src/vs/base/test/browser/ui/splitview/splitview.test.ts b/src/vs/base/test/browser/ui/splitview/splitview.test.ts index 2620db0b7..c36a51fd2 100644 --- a/src/vs/base/test/browser/ui/splitview/splitview.test.ts +++ b/src/vs/base/test/browser/ui/splitview/splitview.test.ts @@ -91,7 +91,7 @@ suite('Splitview', () => { splitview.addView(view2, 20); splitview.addView(view3, 20); - let viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-container > .split-view-view'); + let viewQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-scrollable-element > .split-view-container > .split-view-view'); assert.equal(viewQuery.length, 3, 'split view should have 3 views'); let sashQuery = container.querySelectorAll('.monaco-split-view2 > .sash-container > .monaco-sash'); @@ -99,7 +99,7 @@ suite('Splitview', () => { splitview.removeView(2); - viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-container > .split-view-view'); + viewQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-scrollable-element > .split-view-container > .split-view-view'); assert.equal(viewQuery.length, 2, 'split view should have 2 views'); sashQuery = container.querySelectorAll('.monaco-split-view2 > .sash-container > .monaco-sash'); @@ -107,7 +107,7 @@ suite('Splitview', () => { splitview.removeView(0); - viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-container > .split-view-view'); + viewQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-scrollable-element > .split-view-container > .split-view-view'); assert.equal(viewQuery.length, 1, 'split view should have 1 view'); sashQuery = container.querySelectorAll('.monaco-split-view2 > .sash-container > .monaco-sash'); @@ -115,7 +115,7 @@ suite('Splitview', () => { splitview.removeView(0); - viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-container > .split-view-view'); + viewQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-scrollable-element > .split-view-container > .split-view-view'); assert.equal(viewQuery.length, 0, 'split view should have no views'); sashQuery = container.querySelectorAll('.monaco-split-view2 > .sash-container > .monaco-sash'); diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index a7d505b21..d1baf9b5b 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import * as async from 'vs/base/common/async'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; suite('Async', () => { @@ -651,41 +651,39 @@ suite('Async', () => { test('raceCancellation', async () => { const cts = new CancellationTokenSource(); - const now = Date.now(); - - const p = async.raceCancellation(async.timeout(100), cts.token); + let triggered = false; + const p = async.raceCancellation(async.timeout(100).then(() => triggered = true), cts.token); cts.cancel(); await p; - assert.ok(Date.now() - now < 100); + assert.ok(!triggered); }); test('raceTimeout', async () => { const cts = new CancellationTokenSource(); // timeout wins - let now = Date.now(); let timedout = false; + let triggered = false; - const p1 = async.raceTimeout(async.timeout(100), 1, () => timedout = true); + const p1 = async.raceTimeout(async.timeout(100).then(() => triggered = true), 1, () => timedout = true); cts.cancel(); await p1; - assert.ok(Date.now() - now < 100); + assert.ok(!triggered); assert.equal(timedout, true); // promise wins - now = Date.now(); timedout = false; - const p2 = async.raceTimeout(async.timeout(1), 100, () => timedout = true); + const p2 = async.raceTimeout(async.timeout(1).then(() => triggered = true), 100, () => timedout = true); cts.cancel(); await p2; - assert.ok(Date.now() - now < 100); + assert.ok(triggered); assert.equal(timedout, false); }); @@ -719,4 +717,63 @@ suite('Async', () => { assert.equal(counter.increment(), 2); assert.equal(counter.increment(), 3); }); + + test('firstParallel - simple', async () => { + const a = await async.firstParallel([ + Promise.resolve(1), + Promise.resolve(2), + Promise.resolve(3), + ], v => v === 2); + assert.equal(a, 2); + }); + + test('firstParallel - uses null default', async () => { + assert.equal(await async.firstParallel([Promise.resolve(1)], v => v === 2), null); + }); + + test('firstParallel - uses value default', async () => { + assert.equal(await async.firstParallel([Promise.resolve(1)], v => v === 2, 4), 4); + }); + + test('firstParallel - empty', async () => { + assert.equal(await async.firstParallel([], v => v === 2, 4), 4); + }); + + test('firstParallel - cancels', async () => { + let ct1: CancellationToken; + const p1 = async.createCancelablePromise(async (ct) => { + ct1 = ct; + await async.timeout(200, ct); + return 1; + }); + let ct2: CancellationToken; + const p2 = async.createCancelablePromise(async (ct) => { + ct2 = ct; + await async.timeout(2, ct); + return 2; + }); + + assert.equal(await async.firstParallel([p1, p2], v => v === 2, 4), 2); + assert.equal(ct1!.isCancellationRequested, true, 'should cancel a'); + assert.equal(ct2!.isCancellationRequested, true, 'should cancel b'); + }); + + test('firstParallel - rejection handling', async () => { + let ct1: CancellationToken; + const p1 = async.createCancelablePromise(async (ct) => { + ct1 = ct; + await async.timeout(200, ct); + return 1; + }); + let ct2: CancellationToken; + const p2 = async.createCancelablePromise(async (ct) => { + ct2 = ct; + await async.timeout(2, ct); + throw new Error('oh no'); + }); + + assert.equal(await async.firstParallel([p1, p2], v => v === 2, 4).catch(() => 'ok'), 'ok'); + assert.equal(ct1!.isCancellationRequested, true, 'should cancel a'); + assert.equal(ct2!.isCancellationRequested, true, 'should cancel b'); + }); }); diff --git a/src/vs/base/test/common/linkedList.test.ts b/src/vs/base/test/common/linkedList.test.ts index e63fda850..dbb057a21 100644 --- a/src/vs/base/test/common/linkedList.test.ts +++ b/src/vs/base/test/common/linkedList.test.ts @@ -14,7 +14,7 @@ suite('LinkedList', function () { assert.equal(list.size, elements.length); // assert toArray - assert.deepEqual(list.toArray(), elements); + assert.deepEqual(Array.from(list), elements); // assert Symbol.iterator (1) assert.deepEqual([...list], elements); diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts index 2615f098e..5781de52a 100644 --- a/src/vs/base/test/common/map.test.ts +++ b/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 { ResourceMap, TernarySearchTree, PathIterator, StringIterator, LinkedMap, Touch, LRUCache, UriIterator } from 'vs/base/common/map'; +import { ResourceMap, TernarySearchTree, PathIterator, StringIterator, LinkedMap, Touch, LRUCache, UriIterator, ConfigKeysIterator } from 'vs/base/common/map'; import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { extUriIgnorePathCase } from 'vs/base/common/resources'; @@ -368,7 +368,7 @@ suite('Map', () => { }); test('URIIterator', function () { - const iter = new UriIterator(false); + const iter = new UriIterator(() => false); iter.reset(URI.parse('file:///usr/bin/file.txt')); assert.equal(iter.value(), 'file'); @@ -549,13 +549,15 @@ suite('Map', () => { trie.set('foo', 1); trie.set('foobar', 2); trie.set('bar', 3); + trie.set('foobarbaz', 4); trie.deleteSuperstr('foo'); - assertTernarySearchTree(trie, ['bar', 3]); + assertTernarySearchTree(trie, ['foo', 1], ['bar', 3]); trie = new TernarySearchTree(new StringIterator()); trie.set('foo', 1); trie.set('foobar', 2); trie.set('bar', 3); + trie.set('foobarbaz', 4); trie.deleteSuperstr('fo'); assertTernarySearchTree(trie, ['bar', 3]); @@ -612,17 +614,17 @@ suite('Map', () => { map.set('/user/foo/flip/flop', 3); map.set('/usr/foo', 4); - let item: IteratorResult; + let item: IteratorResult<[string, number]>; let iter = map.findSuperstr('/user'); item = iter!.next(); - assert.equal(item.value, 2); + assert.equal(item.value[1], 2); assert.equal(item.done, false); item = iter!.next(); - assert.equal(item.value, 1); + assert.equal(item.value[1], 1); assert.equal(item.done, false); item = iter!.next(); - assert.equal(item.value, 3); + assert.equal(item.value[1], 3); assert.equal(item.done, false); item = iter!.next(); assert.equal(item.value, undefined); @@ -630,7 +632,7 @@ suite('Map', () => { iter = map.findSuperstr('/usr'); item = iter!.next(); - assert.equal(item.value, 4); + assert.equal(item.value[1], 4); assert.equal(item.done, false); item = iter!.next(); @@ -675,12 +677,12 @@ suite('Map', () => { map.set('/usr/foo', 4); map.deleteSuperstr('/user/foo'); assertTernarySearchTree(map, - ['/usr/foo', 4], + ['/user/foo', 2], ['/usr/foo', 4], ); }); test('TernarySearchTree (URI) - basics', function () { - let trie = new TernarySearchTree(new UriIterator(false)); + let trie = new TernarySearchTree(new UriIterator(() => false)); trie.set(URI.file('/user/foo/bar'), 1); trie.set(URI.file('/user/foo'), 2); @@ -700,7 +702,7 @@ suite('Map', () => { test('TernarySearchTree (URI) - lookup', function () { - const map = new TernarySearchTree(new UriIterator(false)); + const map = new TernarySearchTree(new UriIterator(() => false)); map.set(URI.parse('http://foo.bar/user/foo/bar'), 1); map.set(URI.parse('http://foo.bar/user/foo?query'), 2); map.set(URI.parse('http://foo.bar/user/foo?QUERY'), 3); @@ -715,25 +717,35 @@ suite('Map', () => { assert.equal(map.get(URI.parse('http://foo.bar/user/foo/bar/boo')), undefined); }); - test('TernarySearchTree (PathSegments) - superstr', function () { + test('TernarySearchTree (URI) - lookup, casing', function () { - const map = new TernarySearchTree(new UriIterator(false)); + const map = new TernarySearchTree(new UriIterator(uri => /^https?$/.test(uri.scheme))); + map.set(URI.parse('http://foo.bar/user/foo/bar'), 1); + assert.equal(map.get(URI.parse('http://foo.bar/USER/foo/bar')), 1); + + map.set(URI.parse('foo://foo.bar/user/foo/bar'), 1); + assert.equal(map.get(URI.parse('foo://foo.bar/USER/foo/bar')), undefined); + }); + + test('TernarySearchTree (URI) - superstr', function () { + + const map = new TernarySearchTree(new UriIterator(() => false)); map.set(URI.file('/user/foo/bar'), 1); map.set(URI.file('/user/foo'), 2); map.set(URI.file('/user/foo/flip/flop'), 3); map.set(URI.file('/usr/foo'), 4); - let item: IteratorResult; + let item: IteratorResult<[URI, number]>; let iter = map.findSuperstr(URI.file('/user'))!; item = iter.next(); - assert.equal(item.value, 2); + assert.equal(item.value[1], 2); assert.equal(item.done, false); item = iter.next(); - assert.equal(item.value, 1); + assert.equal(item.value[1], 1); assert.equal(item.done, false); item = iter.next(); - assert.equal(item.value, 3); + assert.equal(item.value[1], 3); assert.equal(item.done, false); item = iter.next(); assert.equal(item.value, undefined); @@ -741,7 +753,7 @@ suite('Map', () => { iter = map.findSuperstr(URI.file('/usr'))!; item = iter.next(); - assert.equal(item.value, 4); + assert.equal(item.value[1], 4); assert.equal(item.done, false); item = iter.next(); @@ -750,16 +762,16 @@ suite('Map', () => { iter = map.findSuperstr(URI.file('/'))!; item = iter.next(); - assert.equal(item.value, 2); + assert.equal(item.value[1], 2); assert.equal(item.done, false); item = iter.next(); - assert.equal(item.value, 1); + assert.equal(item.value[1], 1); assert.equal(item.done, false); item = iter.next(); - assert.equal(item.value, 3); + assert.equal(item.value[1], 3); assert.equal(item.done, false); item = iter.next(); - assert.equal(item.value, 4); + assert.equal(item.value[1], 4); assert.equal(item.done, false); item = iter.next(); assert.equal(item.value, undefined); @@ -771,6 +783,103 @@ suite('Map', () => { assert.equal(map.findSuperstr(URI.file('/userr')), undefined); }); + test('TernarySearchTree (ConfigKeySegments) - basics', function () { + let trie = new TernarySearchTree(new ConfigKeysIterator()); + + trie.set('config.foo.bar', 1); + trie.set('config.foo', 2); + trie.set('config.foo.flip.flop', 3); + + assert.equal(trie.get('config.foo.bar'), 1); + assert.equal(trie.get('config.foo'), 2); + assert.equal(trie.get('config.foo.flip.flop'), 3); + + assert.equal(trie.findSubstr('config.bar'), undefined); + assert.equal(trie.findSubstr('config.foo'), 2); + assert.equal(trie.findSubstr('config.foo.ba'), 2); + assert.equal(trie.findSubstr('config.foo.far.boo'), 2); + assert.equal(trie.findSubstr('config.foo.bar'), 1); + assert.equal(trie.findSubstr('config.foo.bar.far.boo'), 1); + }); + + test('TernarySearchTree (ConfigKeySegments) - lookup', function () { + + const map = new TernarySearchTree(new ConfigKeysIterator()); + map.set('config.foo.bar', 1); + map.set('config.foo', 2); + map.set('config.foo.flip.flop', 3); + + assert.equal(map.get('foo'), undefined); + assert.equal(map.get('config'), undefined); + assert.equal(map.get('config.foo'), 2); + assert.equal(map.get('config.foo.bar'), 1); + assert.equal(map.get('config.foo.bar.boo'), undefined); + }); + + test('TernarySearchTree (ConfigKeySegments) - superstr', function () { + + const map = new TernarySearchTree(new ConfigKeysIterator()); + map.set('config.foo.bar', 1); + map.set('config.foo', 2); + map.set('config.foo.flip.flop', 3); + map.set('boo', 4); + + let item: IteratorResult<[string, number]>; + let iter = map.findSuperstr('config'); + + item = iter!.next(); + assert.equal(item.value[1], 2); + assert.equal(item.done, false); + item = iter!.next(); + assert.equal(item.value[1], 1); + assert.equal(item.done, false); + item = iter!.next(); + assert.equal(item.value[1], 3); + assert.equal(item.done, false); + item = iter!.next(); + assert.equal(item.value, undefined); + assert.equal(item.done, true); + + assert.equal(map.findSuperstr('foo'), undefined); + assert.equal(map.findSuperstr('config.foo.no'), undefined); + assert.equal(map.findSuperstr('config.foop'), undefined); + }); + + + test('TernarySearchTree (ConfigKeySegments) - delete_superstr', function () { + + const map = new TernarySearchTree(new ConfigKeysIterator()); + map.set('config.foo.bar', 1); + map.set('config.foo', 2); + map.set('config.foo.flip.flop', 3); + map.set('boo', 4); + + assertTernarySearchTree(map, + ['config.foo.bar', 1], + ['config.foo', 2], + ['config.foo.flip.flop', 3], + ['boo', 4], + ); + + // not a segment + map.deleteSuperstr('config.fo'); + assertTernarySearchTree(map, + ['config.foo.bar', 1], + ['config.foo', 2], + ['config.foo.flip.flop', 3], + ['boo', 4], + ); + + // delete a segment + map.set('config.foo.bar', 1); + map.set('config.foo', 2); + map.set('config.foo.flip.flop', 3); + map.set('config.boo', 4); + map.deleteSuperstr('config.foo'); + assertTernarySearchTree(map, + ['config.foo', 2], ['boo', 4], + ); + }); test('ResourceMap - basics', function () { const map = new ResourceMap(); diff --git a/src/vs/base/test/common/markdownString.test.ts b/src/vs/base/test/common/markdownString.test.ts index f33f741f6..424c2bd46 100644 --- a/src/vs/base/test/common/markdownString.test.ts +++ b/src/vs/base/test/common/markdownString.test.ts @@ -8,12 +8,24 @@ import { MarkdownString } from 'vs/base/common/htmlContent'; suite('MarkdownString', () => { + test('Escape leading whitespace', function () { + const mds = new MarkdownString(); + mds.appendText('Hello\n Not a code block'); + assert.equal(mds.value, 'Hello\n\n    Not a code block'); + }); + + test('MarkdownString.appendText doesn\'t escape quote #109040', function () { + const mds = new MarkdownString(); + mds.appendText('> Text\n>More'); + assert.equal(mds.value, '\\> Text\n\n\\>More'); + }); + test('appendText', () => { const mds = new MarkdownString(); mds.appendText('# foo\n*bar*'); - assert.equal(mds.value, '\\# foo\n\n\\*bar\\*'); + assert.equal(mds.value, '\\# foo\n\n\\*bar\\*'); }); suite('ThemeIcons', () => { @@ -24,7 +36,7 @@ suite('MarkdownString', () => { const mds = new MarkdownString(undefined, { supportThemeIcons: true }); mds.appendText('$(zap) $(not a theme icon) $(add)'); - assert.equal(mds.value, '\\\\$\\(zap\\) $\\(not a theme icon\\) \\\\$\\(add\\)'); + assert.equal(mds.value, '\\\\$\\(zap\\) $\\(not a theme icon\\) \\\\$\\(add\\)'); }); test('appendMarkdown', () => { @@ -49,7 +61,7 @@ suite('MarkdownString', () => { const mds = new MarkdownString(undefined, { supportThemeIcons: false }); mds.appendText('$(zap) $(not a theme icon) $(add)'); - assert.equal(mds.value, '$\\(zap\\) $\\(not a theme icon\\) $\\(add\\)'); + assert.equal(mds.value, '$\\(zap\\) $\\(not a theme icon\\) $\\(add\\)'); }); test('appendMarkdown', () => { diff --git a/src/vs/base/test/common/network.test.ts b/src/vs/base/test/common/network.test.ts new file mode 100644 index 000000000..1729d4b1c --- /dev/null +++ b/src/vs/base/test/common/network.test.ts @@ -0,0 +1,70 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { URI } from 'vs/base/common/uri'; +import { FileAccess, Schemas } from 'vs/base/common/network'; +import { isEqual } from 'vs/base/common/resources'; +import { isElectronSandboxed } from 'vs/base/common/platform'; + +suite('network', () => { + const enableTest = isElectronSandboxed; + + (!enableTest ? test.skip : test)('FileAccess: URI (native)', () => { + + // asCodeUri() & asFileUri(): simple, without authority + let originalFileUri = URI.file('network.test.ts'); + let browserUri = FileAccess.asBrowserUri(originalFileUri); + assert.ok(browserUri.authority.length > 0); + let fileUri = FileAccess.asFileUri(browserUri); + assert.equal(fileUri.authority.length, 0); + assert(isEqual(originalFileUri, fileUri)); + + // asCodeUri() & asFileUri(): with authority + originalFileUri = URI.file('network.test.ts').with({ authority: 'test-authority' }); + browserUri = FileAccess.asBrowserUri(originalFileUri); + assert.equal(browserUri.authority, originalFileUri.authority); + fileUri = FileAccess.asFileUri(browserUri); + assert(isEqual(originalFileUri, fileUri)); + }); + + (!enableTest ? test.skip : test)('FileAccess: moduleId (native)', () => { + const browserUri = FileAccess.asBrowserUri('vs/base/test/node/network.test', require); + assert.equal(browserUri.scheme, Schemas.vscodeFileResource); + + const fileUri = FileAccess.asFileUri('vs/base/test/node/network.test', require); + assert.equal(fileUri.scheme, Schemas.file); + }); + + (!enableTest ? test.skip : test)('FileAccess: query and fragment is dropped (native)', () => { + let originalFileUri = URI.file('network.test.ts').with({ query: 'foo=bar', fragment: 'something' }); + let browserUri = FileAccess.asBrowserUri(originalFileUri); + assert.equal(browserUri.query, ''); + assert.equal(browserUri.fragment, ''); + }); + + (!enableTest ? test.skip : test)('FileAccess: query and fragment is kept if URI is already of same scheme (native)', () => { + let originalFileUri = URI.file('network.test.ts').with({ query: 'foo=bar', fragment: 'something' }); + let browserUri = FileAccess.asBrowserUri(originalFileUri.with({ scheme: Schemas.vscodeFileResource })); + assert.equal(browserUri.query, 'foo=bar'); + assert.equal(browserUri.fragment, 'something'); + + let fileUri = FileAccess.asFileUri(originalFileUri); + assert.equal(fileUri.query, 'foo=bar'); + assert.equal(fileUri.fragment, 'something'); + }); + + (!enableTest ? test.skip : test)('FileAccess: web', () => { + const originalHttpsUri = URI.file('network.test.ts').with({ scheme: 'https' }); + const browserUri = FileAccess.asBrowserUri(originalHttpsUri); + assert.equal(originalHttpsUri.toString(), browserUri.toString()); + }); + + test('FileAccess: remote URIs', () => { + const originalRemoteUri = URI.file('network.test.ts').with({ scheme: Schemas.vscodeRemote }); + const browserUri = FileAccess.asBrowserUri(originalRemoteUri); + assert.notEqual(originalRemoteUri.scheme, browserUri.scheme); + }); +}); diff --git a/src/vs/base/test/common/processes.test.ts b/src/vs/base/test/common/processes.test.ts index 4b49a3811..ee0d25c88 100644 --- a/src/vs/base/test/common/processes.test.ts +++ b/src/vs/base/test/common/processes.test.ts @@ -19,7 +19,6 @@ suite('Processes', () => { VSCODE_CLI: 'x', VSCODE_DEV: 'x', VSCODE_IPC_HOOK: 'x', - VSCODE_LOGS: 'x', VSCODE_NLS_CONFIG: 'x', VSCODE_PORTABLE: 'x', VSCODE_PID: 'x', diff --git a/src/vs/base/test/common/strings.test.ts b/src/vs/base/test/common/strings.test.ts index 543cb460b..d3366a1e5 100644 --- a/src/vs/base/test/common/strings.test.ts +++ b/src/vs/base/test/common/strings.test.ts @@ -417,4 +417,9 @@ suite('Strings', () => { test('getGraphemeBreakType', () => { assert.equal(strings.getGraphemeBreakType(0xBC1), strings.GraphemeBreakType.SpacingMark); }); + + test('truncate', () => { + assert.equal('hello world', strings.truncate('hello world', 100)); + assert.equal('hello…', strings.truncate('hello world', 5)); + }); }); diff --git a/src/vs/base/worker/workerMain.ts b/src/vs/base/worker/workerMain.ts index 71c6724e9..c39dba9a3 100644 --- a/src/vs/base/worker/workerMain.ts +++ b/src/vs/base/worker/workerMain.ts @@ -8,14 +8,20 @@ let MonacoEnvironment = (self).MonacoEnvironment; let monacoBaseUrl = MonacoEnvironment && MonacoEnvironment.baseUrl ? MonacoEnvironment.baseUrl : '../../../'; + const trustedTypesPolicy = self.trustedTypes?.createPolicy('amdLoader', { createScriptURL: value => value }); + if (typeof (self).define !== 'function' || !(self).define.amd) { - importScripts(monacoBaseUrl + 'vs/loader.js'); + let loaderSrc: string | TrustedScriptURL = monacoBaseUrl + 'vs/loader.js'; + if (trustedTypesPolicy) { + loaderSrc = trustedTypesPolicy.createScriptURL(loaderSrc); + } + importScripts(loaderSrc as string); } require.config({ baseUrl: monacoBaseUrl, catchError: true, - createTrustedScriptURL: (value: string) => value, + trustedTypesPolicy, }); let loadCode = function (moduleId: string) { diff --git a/src/vs/code/browser/workbench/workbench-dev.html b/src/vs/code/browser/workbench/workbench-dev.html index c3c259cfa..8b1869294 100644 --- a/src/vs/code/browser/workbench/workbench-dev.html +++ b/src/vs/code/browser/workbench/workbench-dev.html @@ -33,7 +33,14 @@ self.require = { baseUrl: `${window.location.origin}/static/out`, recordStats: true, - createTrustedScriptURL: value => value, + trustedTypesPolicy: window.trustedTypes?.createPolicy('amdLoader', { + createScriptURL(value) { + if(value.startsWith(window.location.origin)) { + return value; + } + throw new Error(`Invalid script url: ${value}`) + } + }), paths: { 'vscode-textmate': `${window.location.origin}/static/remote/web/node_modules/vscode-textmate/release/main`, 'vscode-oniguruma': `${window.location.origin}/static/remote/web/node_modules/vscode-oniguruma/release/main`, diff --git a/src/vs/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html index 300ceb2fb..1dbdce5d6 100644 --- a/src/vs/code/browser/workbench/workbench.html +++ b/src/vs/code/browser/workbench/workbench.html @@ -32,7 +32,14 @@ self.require = { baseUrl: `${window.location.origin}/static/out`, recordStats: true, - createTrustedScriptURL: value => value, + trustedTypesPolicy: window.trustedTypes?.createPolicy('amdLoader', { + createScriptURL(value) { + if(value.startsWith(window.location.origin)) { + return value; + } + throw new Error(`Invalid script url: ${value}`) + } + }), paths: { 'vscode-textmate': `${window.location.origin}/static/node_modules/vscode-textmate/release/main`, 'vscode-oniguruma': `${window.location.origin}/static/node_modules/vscode-oniguruma/release/main`, diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index 0ef8b9dc8..d1dba0721 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -17,6 +17,7 @@ import { isStandalone } from 'vs/base/browser/browser'; import { localize } from 'vs/nls'; import { Schemas } from 'vs/base/common/network'; import product from 'vs/platform/product/common/product'; +import { parseLogLevel } from 'vs/platform/log/common/log'; function doCreateUri(path: string, queryValues: Map): URI { let query: string | undefined = undefined; @@ -417,6 +418,7 @@ class WindowIndicator implements IWindowIndicator { let foundWorkspace = false; let workspace: IWorkspace; let payload = Object.create(null); + let logLevel: string | undefined = undefined; const query = new URL(document.location.href).searchParams; query.forEach((value, key) => { @@ -448,6 +450,11 @@ class WindowIndicator implements IWindowIndicator { console.error(error); // possible invalid JSON } break; + + // Log level + case 'logLevel': + logLevel = value; + break; } }); @@ -514,6 +521,7 @@ class WindowIndicator implements IWindowIndicator { // Finally create workbench create(document.body, { ...config, + logLevel: logLevel ? parseLogLevel(logLevel) : undefined, settingsSyncOptions, homeIndicator, windowIndicator, diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 88f3869f8..3dcc360b5 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -51,7 +51,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService, IUserDataSyncStoreManagementService, IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncStoreService, UserDataSyncStoreManagementService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; -import { UserDataSyncChannel, UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, UserDataSyncMachinesServiceChannel, UserDataSyncAccountServiceChannel, UserDataSyncStoreManagementServiceChannel, StorageKeysSyncRegistryChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; +import { UserDataSyncChannel, UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, UserDataSyncMachinesServiceChannel, UserDataSyncAccountServiceChannel, UserDataSyncStoreManagementServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { LoggerService } from 'vs/platform/log/node/loggerService'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; @@ -63,7 +63,6 @@ import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagemen import { UserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSyncResourceEnablementService'; import { IUserDataSyncAccountService, UserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService'; -import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { ExtensionTipsService } from 'vs/platform/extensionManagement/electron-sandbox/extensionTipsService'; import { UserDataSyncMachinesService, IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; @@ -72,6 +71,7 @@ import { ActiveWindowManager } from 'vs/platform/windows/common/windowTracker'; import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogAppender'; import { UserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataAutoSyncService'; import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; +import { ExtensionsStorageSyncService, IExtensionsStorageSyncService } from 'vs/platform/userDataSync/common/extensionsStorageSync'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -151,7 +151,6 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(IStorageService, storageService); disposables.add(toDisposable(() => storageService.flush())); - services.set(IStorageKeysSyncRegistryService, new SyncDescriptor(StorageKeysSyncRegistryService)); services.set(IEnvironmentService, environmentService); services.set(INativeEnvironmentService, environmentService); @@ -188,7 +187,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat appender: telemetryAppender, commonProperties: resolveCommonProperties(product.commit, product.version, configuration.machineId, product.msftInternalDomains, installSourcePath), sendErrorTelemetry: true, - piiPaths: extensionsPath ? [appRoot, extensionsPath] : [appRoot] + piiPaths: [appRoot, extensionsPath] }; telemetryService = new TelemetryService(config, configurationService); @@ -199,10 +198,6 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat } server.registerChannel('telemetryAppender', new TelemetryAppenderChannel(telemetryAppender)); - const storageKeysSyncRegistryService = accessor.get(IStorageKeysSyncRegistryService); - const storageKeysSyncChannel = new StorageKeysSyncRegistryChannel(storageKeysSyncRegistryService); - server.registerChannel('storageKeysSyncRegistryService', storageKeysSyncChannel); - services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)); services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService)); @@ -214,6 +209,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', client => client.ctx !== 'main'))); services.set(IGlobalExtensionEnablementService, new SyncDescriptor(GlobalExtensionEnablementService)); services.set(IIgnoredExtensionsManagementService, new SyncDescriptor(IgnoredExtensionsManagementService)); + services.set(IExtensionsStorageSyncService, new SyncDescriptor(ExtensionsStorageSyncService)); services.set(IUserDataSyncStoreManagementService, new SyncDescriptor(UserDataSyncStoreManagementService)); services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService)); services.set(IUserDataSyncMachinesService, new SyncDescriptor(UserDataSyncMachinesService)); diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js index 3dd6691d3..14c699461 100644 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ b/src/vs/code/electron-browser/workbench/workbench.js @@ -9,15 +9,12 @@ 'use strict'; (function () { + const bootstrapWindow = bootstrapWindowLib(); // Add a perf entry right from the top - const perf = perfLib(); + const perf = bootstrapWindow.perfLib(); perf.mark('renderer/started'); - // Load environment in parallel to workbench loading to avoid waterfall - const bootstrapWindow = bootstrapWindowLib(); - const whenEnvResolved = bootstrapWindow.globals().process.whenEnvResolved(); - // Load workbench main JS, CSS and NLS 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 @@ -31,12 +28,6 @@ // Mark start of workbench perf.mark('didLoadWorkbenchMain'); - performance.mark('workbench-start'); - - // Wait for process environment being fully resolved - await whenEnvResolved; - - perf.mark('main/startup'); // @ts-ignore return require('vs/workbench/electron-browser/desktop.main').main(configuration); @@ -58,23 +49,11 @@ //region Helpers - function perfLib() { - globalThis.MonacoPerformanceMarks = globalThis.MonacoPerformanceMarks || []; - - return { - /** - * @param {string} name - */ - mark(name) { - globalThis.MonacoPerformanceMarks.push(name, Date.now()); - } - }; - } - /** * @returns {{ * load: (modules: string[], resultCallback: (result, configuration: object) => any, options: object) => unknown, - * globals: () => typeof import('../../../base/parts/sandbox/electron-sandbox/globals') + * globals: () => typeof import('../../../base/parts/sandbox/electron-sandbox/globals'), + * perfLib: () => { mark: (name: string) => void } * }} */ function bootstrapWindowLib() { @@ -187,5 +166,5 @@ } //#endregion - + }()); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index cf79f58d5..759c85bc7 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { app, ipcMain as ipc, systemPreferences, contentTracing, protocol, IpcMainEvent, BrowserWindow, dialog, session } from 'electron'; -import { IProcessEnvironment, isWindows, isMacintosh } from 'vs/base/common/platform'; +import { app, ipcMain, systemPreferences, contentTracing, protocol, BrowserWindow, dialog, session } from 'electron'; +import { IProcessEnvironment, isWindows, isMacintosh, isLinux } from 'vs/base/common/platform'; import { WindowsMainService } from 'vs/platform/windows/electron-main/windowsMainService'; import { IWindowOpenable } from 'vs/platform/windows/common/windows'; import { OpenContext } from 'vs/platform/windows/node/window'; import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import { getShellEnvironment } from 'vs/code/node/shellEnv'; +import { resolveShellEnv } from 'vs/code/node/shellEnv'; import { IUpdateService } from 'vs/platform/update/common/update'; import { UpdateChannel } from 'vs/platform/update/electron-main/updateIpc'; import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron-main'; @@ -24,7 +24,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IStateService } from 'vs/platform/state/node/state'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IURLService } from 'vs/platform/url/common/url'; +import { IOpenURLOptions, IURLService } from 'vs/platform/url/common/url'; import { URLHandlerChannelClient, URLHandlerRouter } from 'vs/platform/url/common/urlIpc'; import { ITelemetryService, machineIdKey } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; @@ -35,6 +35,7 @@ import { getDelayedChannel, StaticRouter, createChannelReceiver, createChannelSe import product from 'vs/platform/product/common/product'; import { ProxyAuthHandler } from 'vs/code/electron-main/auth'; import { ProxyAuthHandler2 } from 'vs/code/electron-main/auth2'; +import { FileProtocolHandler } from 'vs/code/electron-main/protocol'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; import { URI } from 'vs/base/common/uri'; @@ -52,7 +53,7 @@ import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver'; import { IMenubarMainService, MenubarMainService } from 'vs/platform/menubar/electron-main/menubarMainService'; import { RunOnceScheduler } from 'vs/base/common/async'; import { registerContextMenuListener } from 'vs/base/parts/contextmenu/electron-main/contextmenu'; -import { sep, posix } from 'vs/base/common/path'; +import { sep, posix, join, isAbsolute } from 'vs/base/common/path'; import { joinPath } from 'vs/base/common/resources'; import { localize } from 'vs/nls'; import { Schemas } from 'vs/base/common/network'; @@ -72,7 +73,6 @@ import { INativeHostMainService, NativeHostMainService } from 'vs/platform/nativ import { ISharedProcessMainService, SharedProcessMainService } from 'vs/platform/ipc/electron-main/sharedProcessMainService'; import { IDialogMainService, DialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { coalesce } from 'vs/base/common/arrays'; import { mnemonicButtonLabel, getPathLabel } from 'vs/base/common/labels'; import { WebviewMainService } from 'vs/platform/webview/electron-main/webviewMainService'; import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService'; @@ -82,6 +82,14 @@ import { generateUuid } from 'vs/base/common/uuid'; import { VSBuffer } from 'vs/base/common/buffer'; import { EncryptionMainService, IEncryptionMainService } from 'vs/platform/encryption/electron-main/encryptionMainService'; import { ActiveWindowManager } from 'vs/platform/windows/common/windowTracker'; +import { IKeyboardLayoutMainService, KeyboardLayoutMainService } from 'vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; +import { DisplayMainService, IDisplayMainService } from 'vs/platform/display/electron-main/displayMainService'; +import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper'; +import { isEqualOrParent } from 'vs/base/common/extpath'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust'; +import { ExtensionUrlTrustService } from 'vs/platform/extensionManagement/node/extensionUrlTrustService'; export class CodeApplication extends Disposable { private windowsMainService: IWindowsMainService | undefined; @@ -96,7 +104,8 @@ export class CodeApplication extends Disposable { @IEnvironmentMainService private readonly environmentService: IEnvironmentMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IStateService private readonly stateService: IStateService + @IStateService private readonly stateService: IStateService, + @IFileService private readonly fileService: IFileService ) { super(); @@ -118,9 +127,7 @@ export class CodeApplication extends Disposable { // Accessibility change event app.on('accessibility-support-changed', (event, accessibilitySupportEnabled) => { - if (this.windowsMainService) { - this.windowsMainService.sendToAll('vscode:accessibilitySupportChanged', accessibilitySupportEnabled); - } + this.windowsMainService?.sendToAll('vscode:accessibilitySupportChanged', accessibilitySupportEnabled); }); // macOS dock activate @@ -128,8 +135,8 @@ export class CodeApplication extends Disposable { this.logService.trace('app#activate'); // Mac only event: open new window when we get activated - if (!hasVisibleWindows && this.windowsMainService) { - this.windowsMainService.openEmptyWindow({ context: OpenContext.DOCK }); + if (!hasVisibleWindows) { + this.windowsMainService?.openEmptyWindow({ context: OpenContext.DOCK }); } }); @@ -214,9 +221,7 @@ export class CodeApplication extends Disposable { contents.on('new-window', (event, url) => { event.preventDefault(); // prevent code that wants to open links - if (this.nativeHostMainService) { - this.nativeHostMainService.openExternal(undefined, url); - } + this.nativeHostMainService?.openExternal(undefined, url); }); session.defaultSession.setPermissionRequestHandler((webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback) => { @@ -247,62 +252,119 @@ export class CodeApplication extends Disposable { // Handle paths delayed in case more are coming! runningTimeout = setTimeout(() => { - if (this.windowsMainService) { - this.windowsMainService.open({ - context: OpenContext.DOCK /* can also be opening from finder while app is running */, - cli: this.environmentService.args, - urisToOpen: macOpenFileURIs, - gotoLineMode: false, - preferNewWindow: true /* dropping on the dock or opening from finder prefers to open in a new window */ - }); + this.windowsMainService?.open({ + context: OpenContext.DOCK /* can also be opening from finder while app is running */, + cli: this.environmentService.args, + urisToOpen: macOpenFileURIs, + gotoLineMode: false, + preferNewWindow: true /* dropping on the dock or opening from finder prefers to open in a new window */ + }); - macOpenFileURIs = []; - runningTimeout = null; - } + macOpenFileURIs = []; + runningTimeout = null; }, 100); }); app.on('new-window-for-tab', () => { - if (this.windowsMainService) { - this.windowsMainService.openEmptyWindow({ context: OpenContext.DESKTOP }); //macOS native tab "+" button - } + this.windowsMainService?.openEmptyWindow({ context: OpenContext.DESKTOP }); //macOS native tab "+" button }); - ipc.on('vscode:fetchShellEnv', async (event: IpcMainEvent) => { + //#region Bootstrap IPC Handlers + + ipcMain.on('vscode:fetchShellEnv', async event => { const webContents = event.sender; + const window = this.windowsMainService?.getWindowByWebContents(event.sender); - try { - const shellEnv = await getShellEnvironment(this.logService, this.environmentService); + let replied = false; - if (!webContents.isDestroyed()) { - webContents.send('vscode:acceptShellEnv', shellEnv); + function acceptShellEnv(env: NodeJS.ProcessEnv): void { + clearTimeout(shellEnvSlowWarningHandle); + clearTimeout(shellEnvTimeoutErrorHandle); + + if (!replied) { + replied = true; + + if (!webContents.isDestroyed()) { + webContents.send('vscode:acceptShellEnv', env); + } } - } catch (error) { - if (!webContents.isDestroyed()) { - webContents.send('vscode:acceptShellEnv', {}); - } - - this.logService.error('Error fetching shell env', error); } + + // Handle slow shell environment resolve calls: + // - a warning after 3s but continue to resolve + // - an error after 10s and stop trying to resolve + const cts = new CancellationTokenSource(); + const shellEnvSlowWarningHandle = setTimeout(() => window?.sendWhenReady('vscode:showShellEnvSlowWarning', cts.token), 3000); + const shellEnvTimeoutErrorHandle = setTimeout(function () { + cts.dispose(true); + window?.sendWhenReady('vscode:showShellEnvTimeoutError', CancellationToken.None); + acceptShellEnv({}); + }, 10000); + + // Prefer to use the args and env from the target window + // when resolving the shell env. It is possible that + // a first window was opened from the UI but a second + // from the CLI and that has implications for wether to + // resolve the shell environment or not. + let args: NativeParsedArgs; + let env: NodeJS.ProcessEnv; + if (window?.config) { + args = window.config; + env = { ...process.env, ...window.config.userEnv }; + } else { + args = this.environmentService.args; + env = process.env; + } + + // Resolve shell env + const shellEnv = await resolveShellEnv(this.logService, args, env); + acceptShellEnv(shellEnv); }); - ipc.on('vscode:toggleDevTools', (event: IpcMainEvent) => event.sender.toggleDevTools()); - ipc.on('vscode:openDevTools', (event: IpcMainEvent) => event.sender.openDevTools()); + ipcMain.handle('vscode:writeNlsFile', (event, path: unknown, data: unknown) => { + const uri = this.validateNlsPath([path]); + if (!uri || typeof data !== 'string') { + return Promise.reject('Invalid operation (vscode:writeNlsFile)'); + } - ipc.on('vscode:reloadWindow', (event: IpcMainEvent) => event.sender.reload()); + return this.fileService.writeFile(uri, VSBuffer.fromString(data)); + }); - // Some listeners after window opened - (async () => { - await this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen); + ipcMain.handle('vscode:readNlsFile', async (event, ...paths: unknown[]) => { + const uri = this.validateNlsPath(paths); + if (!uri) { + return Promise.reject('Invalid operation (vscode:readNlsFile)'); + } - // Keyboard layout changes (after window opened) - const nativeKeymap = await import('native-keymap'); - nativeKeymap.onDidChangeKeyboardLayout(() => { - if (this.windowsMainService) { - this.windowsMainService.sendToAll('vscode:keyboardLayoutChanged'); + return (await this.fileService.readFile(uri)).value.toString(); + }); + + ipcMain.on('vscode:toggleDevTools', event => event.sender.toggleDevTools()); + ipcMain.on('vscode:openDevTools', event => event.sender.openDevTools()); + + ipcMain.on('vscode:reloadWindow', event => event.sender.reload()); + + //#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.environmentService.cachedLanguagesPath, !isLinux)) { + return undefined; + } + + return URI.file(path); } private onUnexpectedError(err: Error): void { @@ -315,9 +377,7 @@ export class CodeApplication extends Disposable { }; // handle on client side - if (this.windowsMainService) { - this.windowsMainService.sendToFocused('vscode:reportError', JSON.stringify(friendlyError)); - } + this.windowsMainService?.sendToFocused('vscode:reportError', JSON.stringify(friendlyError)); } this.logService.error(`[uncaught exception in main]: ${err}`); @@ -354,6 +414,9 @@ export class CodeApplication extends Disposable { this.logService.error(error); } + // Setup Protocol Handler + const fileProtocolHandler = this._register(this.instantiationService.createInstance(FileProtocolHandler)); + // Create Electron IPC Server const electronIpcServer = new ElectronIPCServer(); @@ -376,7 +439,7 @@ export class CodeApplication extends Disposable { }); this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => { this._register(new RunOnceScheduler(async () => { - sharedProcess.spawn(await getShellEnvironment(this.logService, this.environmentService)); + sharedProcess.spawn(await resolveShellEnv(this.logService, this.environmentService.args, process.env)); }, 3000)).schedule(); }); @@ -391,15 +454,15 @@ export class CodeApplication extends Disposable { this._register(server); } - // Setup Auth Handler - if (this.configurationService.getValue('window.enableExperimentalProxyLoginDialog') !== true) { + // Setup Auth Handler (TODO@ben remove old auth handler eventually) + if (this.configurationService.getValue('window.enableExperimentalProxyLoginDialog') === false) { this._register(new ProxyAuthHandler()); } else { this._register(appInstantiationService.createInstance(ProxyAuthHandler2)); } // Open Windows - const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient)); + const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient, fileProtocolHandler)); // Post Open Windows Tasks appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor)); @@ -453,10 +516,13 @@ export class CodeApplication extends Disposable { services.set(IIssueMainService, new SyncDescriptor(IssueMainService, [machineId, this.userEnv])); services.set(IEncryptionMainService, new SyncDescriptor(EncryptionMainService, [machineId])); + services.set(IKeyboardLayoutMainService, new SyncDescriptor(KeyboardLayoutMainService)); + services.set(IDisplayMainService, new SyncDescriptor(DisplayMainService)); services.set(INativeHostMainService, new SyncDescriptor(NativeHostMainService)); services.set(IWebviewManagerService, new SyncDescriptor(WebviewMainService)); services.set(IWorkspacesService, new SyncDescriptor(WorkspacesService)); services.set(IMenubarMainService, new SyncDescriptor(MenubarMainService)); + services.set(IExtensionUrlTrustService, new SyncDescriptor(ExtensionUrlTrustService)); const storageMainService = new StorageMainService(this.logService, this.environmentService); services.set(IStorageMainService, storageMainService); @@ -474,7 +540,7 @@ export class CodeApplication extends Disposable { const channel = getDelayedChannel(sharedProcessReady.then(client => client.getChannel('telemetryAppender'))); const appender = new TelemetryAppenderClient(channel); const commonProperties = resolveCommonProperties(product.commit, product.version, machineId, product.msftInternalDomains, this.environmentService.installSourcePath); - const piiPaths = this.environmentService.extensionsPath ? [this.environmentService.appRoot, this.environmentService.extensionsPath] : [this.environmentService.appRoot]; + const piiPaths = [this.environmentService.appRoot, this.environmentService.extensionsPath]; const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, sendErrorTelemetry: true }; services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config])); @@ -502,14 +568,12 @@ export class CodeApplication extends Disposable { const path = await contentTracing.stopRecording(joinPath(this.environmentService.userHome, `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`).fsPath); if (!timeout) { - if (this.dialogMainService) { - this.dialogMainService.showMessageBox({ - type: 'info', - message: localize('trace.message', "Successfully created trace."), - detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path), - buttons: [localize('trace.ok', "OK")] - }, withNullAsUndefined(BrowserWindow.getFocusedWindow())); - } + this.dialogMainService?.showMessageBox({ + type: 'info', + message: localize('trace.message', "Successfully created trace."), + detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path), + buttons: [localize('trace.ok', "OK")] + }, withNullAsUndefined(BrowserWindow.getFocusedWindow())); } else { this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`); } @@ -525,7 +589,7 @@ export class CodeApplication extends Disposable { }); } - private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise>): ICodeWindow[] { + private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise>, fileProtocolHandler: FileProtocolHandler): ICodeWindow[] { // Register more Main IPC services const launchMainService = accessor.get(ILaunchMainService); @@ -545,6 +609,14 @@ export class CodeApplication extends Disposable { const encryptionChannel = createChannelReceiver(encryptionMainService); electronIpcServer.registerChannel('encryption', encryptionChannel); + const keyboardLayoutMainService = accessor.get(IKeyboardLayoutMainService); + const keyboardLayoutChannel = createChannelReceiver(keyboardLayoutMainService); + electronIpcServer.registerChannel('keyboardLayout', keyboardLayoutChannel); + + const displayMainService = accessor.get(IDisplayMainService); + const displayChannel = createChannelReceiver(displayMainService); + electronIpcServer.registerChannel('display', displayChannel); + const nativeHostMainService = this.nativeHostMainService = accessor.get(INativeHostMainService); const nativeHostChannel = createChannelReceiver(this.nativeHostMainService); electronIpcServer.registerChannel('nativeHost', nativeHostChannel); @@ -566,6 +638,10 @@ export class CodeApplication extends Disposable { const urlChannel = createChannelReceiver(urlService); electronIpcServer.registerChannel('url', urlChannel); + const extensionUrlTrustService = accessor.get(IExtensionUrlTrustService); + const extensionUrlTrustChannel = createChannelReceiver(extensionUrlTrustService); + electronIpcServer.registerChannel('extensionUrlTrust', extensionUrlTrustChannel); + const webviewManagerService = accessor.get(IWebviewManagerService); const webviewChannel = createChannelReceiver(webviewManagerService); electronIpcServer.registerChannel('webview', webviewChannel); @@ -579,8 +655,10 @@ export class CodeApplication extends Disposable { electronIpcServer.registerChannel('logger', loggerChannel); sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel)); - // ExtensionHost Debug broadcast service const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); + fileProtocolHandler.injectWindowsMainService(windowsMainService); + + // ExtensionHost Debug broadcast service electronIpcServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ElectronExtensionHostDebugBroadcastChannel(windowsMainService)); // Signal phase: ready (services set) @@ -591,29 +669,32 @@ export class CodeApplication extends Disposable { // Check for initial URLs to handle from protocol link invocations const pendingWindowOpenablesFromProtocolLinks: IWindowOpenable[] = []; - const pendingProtocolLinksToHandle = coalesce([ - + const pendingProtocolLinksToHandle = [ // Windows/Linux: protocol handler invokes CLI with --open-url ...this.environmentService.args['open-url'] ? this.environmentService.args._urls || [] : [], // macOS: open-url events ...((global).getOpenUrls() || []) as string[] - ].map(pendingUrlToHandle => { + ].map(url => { try { - return URI.parse(pendingUrlToHandle); - } catch (error) { - return undefined; + return { uri: URI.parse(url), url }; + } catch { + return null; } - })).filter(pendingUriToHandle => { - // if URI should be blocked, filter it out - if (this.shouldBlockURI(pendingUriToHandle)) { + }).filter((obj): obj is { uri: URI, url: string } => { + if (!obj) { return false; } - // filter out any protocol link that wants to open as window so that + // If URI should be blocked, filter it out + if (this.shouldBlockURI(obj.uri)) { + return false; + } + + // Filter out any protocol link that wants to open as window so that // we open the right set of windows on startup and not restore the // previous workspace too. - const windowOpenable = this.getWindowOpenableFromProtocolLink(pendingUriToHandle); + const windowOpenable = this.getWindowOpenableFromProtocolLink(obj.uri); if (windowOpenable) { pendingWindowOpenablesFromProtocolLinks.push(windowOpenable); @@ -627,8 +708,9 @@ export class CodeApplication extends Disposable { const app = this; const environmentService = this.environmentService; urlService.registerHandler({ - async handleURL(uri: URI): Promise { - // if URI should be blocked, behave as if it's handled + async handleURL(uri: URI, options?: IOpenURLOptions): Promise { + + // If URI should be blocked, behave as if it's handled if (app.shouldBlockURI(uri)) { return true; } @@ -658,7 +740,7 @@ export class CodeApplication extends Disposable { await window.ready(); - return urlService.open(uri); + return urlService.open(uri, options); } return false; @@ -666,11 +748,11 @@ export class CodeApplication extends Disposable { }); // Create a URL handler which forwards to the last active window - const activeWindowManager = new ActiveWindowManager({ + const activeWindowManager = this._register(new ActiveWindowManager({ onDidOpenWindow: nativeHostMainService.onDidOpenWindow, onDidFocusWindow: nativeHostMainService.onDidFocusWindow, getActiveWindowId: () => nativeHostMainService.getActiveWindowId(-1) - }); + })); const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id)); const urlHandlerRouter = new URLHandlerRouter(activeWindowRouter); const urlHandlerChannel = electronIpcServer.getChannel('urlHandler', urlHandlerRouter); @@ -682,7 +764,7 @@ export class CodeApplication extends Disposable { // Open our first window const args = this.environmentService.args; const macOpenFiles: string[] = (global).macOpenFiles; - const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP; + const context = isLaunchedFromCli(process.env) ? OpenContext.CLI : OpenContext.DESKTOP; const hasCliArgs = args._.length; const hasFolderURIs = !!args['folder-uri']; const hasFileURIs = !!args['file-uri']; @@ -828,12 +910,14 @@ export class CodeApplication extends Disposable { updateService.initialize(); } + // Start to fetch shell environment (if needed) after window has opened + resolveShellEnv(this.logService, this.environmentService.args, process.env); + // If enable-crash-reporter argv is undefined then this is a fresh start, // based on telemetry.enableCrashreporter settings, generate a UUID which // will be used as crash reporter id and also update the json file. try { - const fileService = accessor.get(IFileService); - const argvContent = await fileService.readFile(this.environmentService.argvResource); + const argvContent = await this.fileService.readFile(this.environmentService.argvResource); const argvString = argvContent.value.toString(); const argvJSON = JSON.parse(stripComments(argvString)); if (argvJSON['enable-crash-reporter'] === undefined) { @@ -850,14 +934,11 @@ export class CodeApplication extends Disposable { '}' ]; const newArgvString = argvString.substring(0, argvString.length - 2).concat(',\n', additionalArgvContent.join('\n')); - await fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(newArgvString)); + await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(newArgvString)); } } catch (error) { this.logService.error(error); } - - // Start to fetch shell environment after window has opened - getShellEnvironment(this.logService, this.environmentService); } private handleRemoteAuthorities(): void { diff --git a/src/vs/code/electron-main/auth2.ts b/src/vs/code/electron-main/auth2.ts index 1bce04457..1b84d4bc4 100644 --- a/src/vs/code/electron-main/auth2.ts +++ b/src/vs/code/electron-main/auth2.ts @@ -13,6 +13,7 @@ import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeH import { IEncryptionMainService } from 'vs/platform/encryption/electron-main/encryptionMainService'; import { generateUuid } from 'vs/base/common/uuid'; import product from 'vs/platform/product/common/product'; +import { CancellationToken } from 'vs/base/common/cancellation'; interface ElectronAuthenticationResponseDetails extends AuthenticationResponseDetails { firstAuthAttempt?: boolean; // https://github.com/electron/electron/blob/84a42a050e7d45225e69df5bd2d2bf9f1037ea41/shell/browser/login_handler.cc#L70 @@ -192,7 +193,7 @@ export class ProxyAuthHandler2 extends Disposable { password: this.sessionCredentials?.password ?? storedPassword, // prefer to show already used password (if any) over stored replyChannel: `vscode:proxyAuthResponse:${generateUuid()}` }; - window.sendWhenReady('vscode:openProxyAuthenticationDialog', payload); + window.sendWhenReady('vscode:openProxyAuthenticationDialog', CancellationToken.None, payload); this.state = ProxyAuthState.LoginDialogShown; // Handle reply diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 10f4a19f1..783ac6fb5 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -200,7 +200,7 @@ class CodeMain { VSCODE_IPC_HOOK: environmentService.mainIPCHandle }; - ['VSCODE_NLS_CONFIG', 'VSCODE_LOGS', 'VSCODE_PORTABLE'].forEach(key => { + ['VSCODE_NLS_CONFIG', 'VSCODE_PORTABLE'].forEach(key => { const value = process.env[key]; if (typeof value === 'string') { instanceEnvironment[key] = value; @@ -343,15 +343,7 @@ class CodeMain { private handleStartupDataDirError(environmentService: IEnvironmentMainService, error: NodeJS.ErrnoException): void { if (error.code === 'EACCES' || error.code === 'EPERM') { - const directories = [environmentService.userDataPath]; - - if (environmentService.extensionsPath) { - directories.push(environmentService.extensionsPath); - } - - if (XDG_RUNTIME_DIR) { - directories.push(XDG_RUNTIME_DIR); - } + const directories = coalesce([environmentService.userDataPath, environmentService.extensionsPath, XDG_RUNTIME_DIR]); this.showStartupWarningDialog( localize('startupDataDirError', "Unable to write program user data."), diff --git a/src/vs/code/electron-main/protocol.ts b/src/vs/code/electron-main/protocol.ts new file mode 100644 index 000000000..36d093199 --- /dev/null +++ b/src/vs/code/electron-main/protocol.ts @@ -0,0 +1,108 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { FileAccess, Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; +import { session } from 'electron'; +import { ILogService } from 'vs/platform/log/common/log'; +import { TernarySearchTree } from 'vs/base/common/map'; +import { isLinux } from 'vs/base/common/platform'; +import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; + +type ProtocolCallback = { (result: string | Electron.FilePathWithHeaders | { error: number }): void }; + +export class FileProtocolHandler extends Disposable { + + private readonly validRoots = TernarySearchTree.forUris(() => !isLinux); + + constructor( + @INativeEnvironmentService environmentService: INativeEnvironmentService, + @ILogService private readonly logService: ILogService + ) { + super(); + + const { defaultSession } = session; + + // Define an initial set of roots we allow loading from + // - appRoot : all files installed as part of the app + // - extensions : all files shipped from extensions + this.validRoots.set(URI.file(environmentService.appRoot), true); + this.validRoots.set(URI.file(environmentService.extensionsPath), true); + + // Register vscode-file:// handler + defaultSession.protocol.registerFileProtocol(Schemas.vscodeFileResource, (request, callback) => this.handleResourceRequest(request, callback as unknown as ProtocolCallback)); + + // Block any file:// access (sandbox only) + if (environmentService.args.__sandbox) { + defaultSession.protocol.interceptFileProtocol(Schemas.file, (request, callback) => this.handleFileRequest(request, callback as unknown as ProtocolCallback)); + } + + // Cleanup + this._register(toDisposable(() => { + defaultSession.protocol.unregisterProtocol(Schemas.vscodeFileResource); + if (environmentService.args.__sandbox) { + defaultSession.protocol.uninterceptProtocol(Schemas.file); + } + })); + } + + injectWindowsMainService(windowsMainService: IWindowsMainService): void { + this._register(windowsMainService.onWindowReady(window => { + if (window.config?.extensionDevelopmentPath || window.config?.extensionTestsPath) { + const disposables = new DisposableStore(); + disposables.add(Event.any(window.onClose, window.onDestroy)(() => disposables.dispose())); + + // Allow access to extension development path + if (window.config.extensionDevelopmentPath) { + for (const extensionDevelopmentPath of window.config.extensionDevelopmentPath) { + disposables.add(this.addValidRoot(URI.file(extensionDevelopmentPath))); + } + } + + // Allow access to extension tests path + if (window.config.extensionTestsPath) { + disposables.add(this.addValidRoot(URI.file(window.config.extensionTestsPath))); + } + } + })); + } + + private addValidRoot(root: URI): IDisposable { + if (!this.validRoots.get(root)) { + this.validRoots.set(root, true); + + return toDisposable(() => this.validRoots.delete(root)); + } + + return Disposable.None; + } + + private async handleFileRequest(request: Electron.ProtocolRequest, callback: ProtocolCallback) { + const uri = URI.parse(request.url); + + this.logService.error(`Refused to load resource ${uri.fsPath} from ${Schemas.file}: protocol`); + callback({ error: -3 /* ABORTED */ }); + } + + private async handleResourceRequest(request: Electron.ProtocolRequest, callback: ProtocolCallback) { + const uri = URI.parse(request.url); + + // Restore the `vscode-file` URI to a `file` URI so that we can + // ensure the root is valid and properly tell Chrome where the + // resource is at. + const fileUri = FileAccess.asFileUri(uri); + if (this.validRoots.findSubstr(fileUri)) { + return callback({ + path: fileUri.fsPath + }); + } + + this.logService.error(`${Schemas.vscodeFileResource}: Refused to load resource ${fileUri.fsPath}}`); + callback({ error: -3 /* ABORTED */ }); + } +} diff --git a/src/vs/code/electron-main/sharedProcess.ts b/src/vs/code/electron-main/sharedProcess.ts index c513c3c6c..dd721db92 100644 --- a/src/vs/code/electron-main/sharedProcess.ts +++ b/src/vs/code/electron-main/sharedProcess.ts @@ -60,8 +60,9 @@ export class SharedProcess implements ISharedProcess { windowId: this.window.id }; - const windowUrl = FileAccess - .asBrowserUri('vs/code/electron-browser/sharedProcess/sharedProcess.html', require) + const windowUrl = (this.environmentService.sandbox ? + FileAccess._asCodeFileUri('vs/code/electron-browser/sharedProcess/sharedProcess.html', require) : + FileAccess.asBrowserUri('vs/code/electron-browser/sharedProcess/sharedProcess.html', require)) .with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` }); this.window.loadURL(windowUrl.toString(true)); diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 79e1658c6..a9f47be81 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as os from 'os'; import * as path from 'vs/base/common/path'; -import * as objects from 'vs/base/common/objects'; import * as nls from 'vs/nls'; +import * as perf from 'vs/base/common/performance'; import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme, Event, Details } from 'electron'; @@ -23,7 +24,6 @@ import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; -import * as perf from 'vs/base/common/performance'; import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -33,8 +33,10 @@ import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IStorageMainService } from 'vs/platform/storage/node/storageMainService'; -import { IFileService } from 'vs/platform/files/common/files'; +import { ByteSize, IFileService } from 'vs/platform/files/common/files'; import { FileAccess, Schemas } from 'vs/base/common/network'; +import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper'; +import { CancellationToken } from 'vs/base/common/cancellation'; export interface IWindowCreationOptions { state: IWindowState; @@ -84,7 +86,7 @@ const enum ReadyState { export class CodeWindow extends Disposable implements ICodeWindow { - private static readonly MAX_URL_LENGTH = 2 * 1024 * 1024; // https://cs.chromium.org/chromium/src/url/url_constants.cc?l=32 + private static readonly MAX_URL_LENGTH = 2 * ByteSize.MB; // https://cs.chromium.org/chromium/src/url/url_constants.cc?l=32 private readonly _onLoad = this._register(new Emitter()); readonly onLoad = this._onLoad.event; @@ -110,8 +112,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { private readonly whenReadyCallbacks: { (window: ICodeWindow): void }[]; - private pendingLoadConfig?: INativeWindowConfiguration; - private marketplaceHeadersPromise: Promise; private readonly touchBarGroups: TouchBarSegmentedControl[]; @@ -150,7 +150,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // in case we are maximized or fullscreen, only show later after the call to maximize/fullscreen (see below) const isFullscreenOrMaximized = (this.windowState.mode === WindowMode.Maximized || this.windowState.mode === WindowMode.Fullscreen); - const windowConfig = this.configurationService.getValue('window'); + const windowConfig = this.configurationService.getValue('window'); const options: BrowserWindowConstructorOptions = { width: this.windowState.width, @@ -211,7 +211,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { options.tabbingIdentifier = product.nameShort; // this opts in to sierra tabs } - const useCustomTitleStyle = getTitleBarStyle(this.configurationService, this.environmentService, !!config.extensionDevelopmentPath) === 'custom'; + const useCustomTitleStyle = getTitleBarStyle(this.configurationService) === 'custom'; if (useCustomTitleStyle) { options.titleBarStyle = 'hidden'; this.hiddenTitleBarStyle = true; @@ -285,6 +285,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.registerListeners(); } + private pendingLoadConfig: INativeWindowConfiguration | undefined; + private currentConfig: INativeWindowConfiguration | undefined; get config(): INativeWindowConfiguration | undefined { return this.currentConfig; } @@ -296,11 +298,11 @@ export class CodeWindow extends Disposable implements ICodeWindow { get hasHiddenTitleBarStyle(): boolean { return !!this.hiddenTitleBarStyle; } - get isExtensionDevelopmentHost(): boolean { return !!(this.config && this.config.extensionDevelopmentPath); } + get isExtensionDevelopmentHost(): boolean { return !!(this.currentConfig?.extensionDevelopmentPath); } - get isExtensionTestHost(): boolean { return !!(this.config && this.config.extensionTestsPath); } + get isExtensionTestHost(): boolean { return !!(this.currentConfig?.extensionTestsPath); } - get isExtensionDevelopmentTestFromCli(): boolean { return this.isExtensionDevelopmentHost && this.isExtensionTestHost && !this.config?.debugId; } + get isExtensionDevelopmentTestFromCli(): boolean { return this.isExtensionDevelopmentHost && this.isExtensionTestHost && !this.currentConfig?.debugId; } setRepresentedFilename(filename: string): void { if (isMacintosh) { @@ -468,9 +470,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { return; // disposed } - // Notify renderers about displays changed - this.sendWhenReady('vscode:displayChanged'); - // Simple fullscreen doesn't resize automatically when the resolution changes so as a workaround // we need to detect when display metrics change or displays are added/removed and toggle the // fullscreen manually. @@ -520,11 +519,11 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Window Fullscreen this._win.on('enter-full-screen', () => { - this.sendWhenReady('vscode:enterFullScreen'); + this.sendWhenReady('vscode:enterFullScreen', CancellationToken.None); }); this._win.on('leave-full-screen', () => { - this.sendWhenReady('vscode:leaveFullScreen'); + this.sendWhenReady('vscode:leaveFullScreen', CancellationToken.None); }); // Window Failed to load @@ -680,6 +679,16 @@ export class CodeWindow extends Disposable implements ICodeWindow { load(config: INativeWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void { + // If this window was loaded before from the command line + // (as indicated by VSCODE_CLI environment), make sure to + // preserve that user environment in subsequent loads, + // unless the new configuration context was also a CLI + // (for https://github.com/microsoft/vscode/issues/108571) + const currentUserEnv = (this.currentConfig ?? this.pendingLoadConfig)?.userEnv; + if (currentUserEnv && isLaunchedFromCli(currentUserEnv) && !isLaunchedFromCli(config.userEnv)) { + config.userEnv = { ...currentUserEnv, ...config.userEnv }; // still allow to override certain environment as passed in + } + // If this is the first time the window is loaded, we associate the paths // directly with the window because we assume the loading will just work if (this._readyState === ReadyState.NONE) { @@ -738,10 +747,10 @@ export class CodeWindow extends Disposable implements ICodeWindow { this._onLoad.fire(); } - reload(configurationIn?: INativeWindowConfiguration, cli?: NativeParsedArgs): void { + reload(cli?: NativeParsedArgs): void { - // If config is not provided, copy our current one - const configuration = configurationIn ? configurationIn : objects.mixin({}, this.currentConfig); + // Copy our current config for reuse + const configuration = Object.assign({}, this.currentConfig); // Delete some properties we do not want during reload delete configuration.filesToOpenOrCreate; @@ -773,7 +782,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { windowConfiguration.logLevel = this.logService.getLevel(); // Set zoomlevel - const windowConfig = this.configurationService.getValue('window'); + const windowConfig = this.configurationService.getValue('window'); const zoomLevel = windowConfig?.zoomLevel; if (typeof zoomLevel === 'number') { windowConfiguration.zoomLevel = zoomLevel; @@ -799,6 +808,11 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Parts splash windowConfiguration.partsSplashPath = path.join(this.environmentService.userDataPath, 'rapid_render.json'); + // OS Info + windowConfiguration.os = { + release: os.release() + }; + // Config (combination of process.argv and window configuration) const environment = parseArgs(process.argv, OPTIONS); const config = Object.assign(environment, windowConfiguration) as unknown as { [key: string]: unknown }; @@ -814,10 +828,9 @@ export class CodeWindow extends Disposable implements ICodeWindow { // large depending on user configuration, so we can only remove it in that case. let configUrl = this.doGetUrl(config); if (configUrl.length > CodeWindow.MAX_URL_LENGTH) { - delete config.userEnv; this.logService.warn('Application URL exceeds maximum of 2MB and was shortened.'); - configUrl = this.doGetUrl(config); + configUrl = this.doGetUrl({ ...config, userEnv: undefined }); if (configUrl.length > CodeWindow.MAX_URL_LENGTH) { this.logService.error('Application URL exceeds maximum of 2MB and cannot be loaded.'); @@ -835,10 +848,10 @@ export class CodeWindow extends Disposable implements ICodeWindow { workbench = 'vs/code/electron-browser/workbench/workbench.html'; } - return FileAccess - .asBrowserUri(workbench, require) - .with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` }) - .toString(true); + return (this.environmentService.sandbox ? + FileAccess._asCodeFileUri(workbench, require) : + FileAccess.asBrowserUri(workbench, require)) + .with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` }).toString(true); } serializeWindowState(): IWindowState { @@ -1088,7 +1101,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } // Events - this.sendWhenReady(fullscreen ? 'vscode:enterFullScreen' : 'vscode:leaveFullScreen'); + this.sendWhenReady(fullscreen ? 'vscode:enterFullScreen' : 'vscode:leaveFullScreen', CancellationToken.None); // Respect configured menu bar visibility or default to toggle if not set if (this.currentMenuBarVisibility) { @@ -1116,7 +1129,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } private useNativeFullScreen(): boolean { - const windowConfig = this.configurationService.getValue('window'); + const windowConfig = this.configurationService.getValue('window'); if (!windowConfig || typeof windowConfig.nativeFullScreen !== 'boolean') { return true; // default } @@ -1133,7 +1146,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } private getMenuBarVisibility(): MenuBarVisibility { - let menuBarVisibility = getMenuBarVisibility(this.configurationService, this.environmentService, !!this.config?.extensionDevelopmentPath); + let menuBarVisibility = getMenuBarVisibility(this.configurationService); if (['visible', 'toggle', 'hidden'].indexOf(menuBarVisibility) < 0) { menuBarVisibility = 'default'; } @@ -1229,11 +1242,15 @@ export class CodeWindow extends Disposable implements ICodeWindow { } } - sendWhenReady(channel: string, ...args: any[]): void { + sendWhenReady(channel: string, token: CancellationToken, ...args: any[]): void { if (this.isReady) { this.send(channel, ...args); } else { - this.ready().then(() => this.send(channel, ...args)); + this.ready().then(() => { + if (!token.isCancellationRequested) { + this.send(channel, ...args); + } + }); } } @@ -1283,7 +1300,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { mode: 'buttons', segmentStyle: 'automatic', change: (selectedIndex) => { - this.sendWhenReady('vscode:runAction', { id: (control.segments[selectedIndex] as ITouchBarSegment).id, from: 'touchbar' }); + this.sendWhenReady('vscode:runAction', CancellationToken.None, { id: (control.segments[selectedIndex] as ITouchBarSegment).id, from: 'touchbar' }); } }); diff --git a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts index f48450b25..252260a57 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts @@ -150,6 +150,7 @@ export class IssueReporter extends Disposable { applyZoom(configuration.data.zoomLevel); this.applyStyles(configuration.data.styles); this.handleExtensionData(configuration.data.enabledExtensions); + this.updateExperimentsInfo(configuration.data.experiments); } render(): void { @@ -285,7 +286,7 @@ export class IssueReporter extends Disposable { this.render(); }); - (['includeSystemInfo', 'includeProcessInfo', 'includeWorkspaceInfo', 'includeExtensions', 'includeSearchedExtensions', 'includeSettingsSearchDetails'] as const).forEach(elementId => { + (['includeSystemInfo', 'includeProcessInfo', 'includeWorkspaceInfo', 'includeExtensions', 'includeExperiments'] as const).forEach(elementId => { this.addEventListener(elementId, 'click', (event: Event) => { event.stopPropagation(); this.issueReporterModel.update({ [elementId]: !this.issueReporterModel.getData()[elementId] }); @@ -693,8 +694,7 @@ export class IssueReporter extends Disposable { const processBlock = document.querySelector('.block-process'); const workspaceBlock = document.querySelector('.block-workspace'); const extensionsBlock = document.querySelector('.block-extensions'); - const searchedExtensionsBlock = document.querySelector('.block-searchedExtensions'); - const settingsSearchResultsBlock = document.querySelector('.block-settingsSearchResults'); + const experimentsBlock = document.querySelector('.block-experiments'); const problemSource = this.getElementById('problem-source')!; const descriptionTitle = this.getElementById('issue-description-label')!; @@ -707,8 +707,7 @@ export class IssueReporter extends Disposable { hide(processBlock); hide(workspaceBlock); hide(extensionsBlock); - hide(searchedExtensionsBlock); - hide(settingsSearchResultsBlock); + hide(experimentsBlock); hide(problemSource); hide(extensionSelector); @@ -716,6 +715,7 @@ export class IssueReporter extends Disposable { show(blockContainer); show(systemBlock); show(problemSource); + show(experimentsBlock); if (fileOnExtension) { show(extensionSelector); @@ -730,6 +730,7 @@ export class IssueReporter extends Disposable { show(processBlock); show(workspaceBlock); show(problemSource); + show(experimentsBlock); if (fileOnExtension) { show(extensionSelector); @@ -1084,6 +1085,14 @@ export class IssueReporter extends Disposable { } } + private updateExperimentsInfo(experimentInfo: string | undefined) { + this.issueReporterModel.update({ experimentInfo }); + const target = document.querySelector('.block-experiments .block-info'); + if (target) { + target.textContent = experimentInfo ? experimentInfo : localize('noCurrentExperiments', "No current experiments."); + } + } + private getExtensionTableHtml(extensions: IssueReporterExtensionData[]): HTMLTableElement { return $('table', undefined, $('tr', undefined, diff --git a/src/vs/code/electron-sandbox/issue/issueReporterModel.ts b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts index b529e48cd..f850e4d80 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterModel.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts @@ -19,8 +19,7 @@ export interface IssueReporterData { includeWorkspaceInfo: boolean; includeProcessInfo: boolean; includeExtensions: boolean; - includeSearchedExtensions: boolean; - includeSettingsSearchDetails: boolean; + includeExperiments: boolean; numberOfThemeExtesions?: number; allExtensions: IssueReporterExtensionData[]; @@ -31,6 +30,7 @@ export interface IssueReporterData { actualSearchResults?: ISettingSearchResult[]; query?: string; filterResultCount?: number; + experimentInfo?: string; } export class IssueReporterModel { @@ -43,8 +43,7 @@ export class IssueReporterModel { includeWorkspaceInfo: true, includeProcessInfo: true, includeExtensions: true, - includeSearchedExtensions: true, - includeSettingsSearchDetails: true, + includeExperiments: true, allExtensions: [] }; @@ -133,6 +132,12 @@ ${this.getInfos()} } } + if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) { + if (this._data.includeExperiments && this._data.experimentInfo) { + info += this.generateExperimentsInfoMd(); + } + } + return info; } @@ -150,7 +155,7 @@ ${this.getInfos()} |GPU Status|${Object.keys(this._data.systemInfo.gpuStatus).map(key => `${key}: ${this._data.systemInfo!.gpuStatus[key]}`).join('
')}| |Load (avg)|${this._data.systemInfo.load}| |Memory (System)|${this._data.systemInfo.memory}| -|Process Argv|${this._data.systemInfo.processArgs}| +|Process Argv|${this._data.systemInfo.processArgs.replace(/\\/g, '\\\\')}| |Screen Reader|${this._data.systemInfo.screenReader}| |VM|${this._data.systemInfo.vmHint}|`; @@ -203,6 +208,18 @@ ${this._data.processInfo} ${this._data.workspaceInfo}; \`\`\` + +`; + } + + private generateExperimentsInfoMd(): string { + return `
+A/B Experiments + +\`\`\` +${this._data.experimentInfo} +\`\`\` +
`; } diff --git a/src/vs/code/electron-sandbox/issue/issueReporterPage.ts b/src/vs/code/electron-sandbox/issue/issueReporterPage.ts index d7be46aeb..bf3c1353b 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterPage.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterPage.ts @@ -111,25 +111,15 @@ export default (): string => ` -
- - - -
- - - +
`; diff --git a/src/vs/code/electron-sandbox/issue/test/testReporterModel.test.ts b/src/vs/code/electron-sandbox/issue/test/testReporterModel.test.ts index b09f4fb76..215f66498 100644 --- a/src/vs/code/electron-sandbox/issue/test/testReporterModel.test.ts +++ b/src/vs/code/electron-sandbox/issue/test/testReporterModel.test.ts @@ -18,8 +18,7 @@ suite('IssueReporter', () => { includeWorkspaceInfo: true, includeProcessInfo: true, includeExtensions: true, - includeSearchedExtensions: true, - includeSettingsSearchDetails: true, + includeExperiments: true, issueType: 0 }); }); @@ -78,6 +77,58 @@ OS version: undefined |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.equal(issueReporterModel.serialize(), + ` +Issue Type: Bug + +undefined + +VS Code version: undefined +OS version: undefined + +
+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 +\`\`\` + +
+ `); }); @@ -191,6 +242,45 @@ Remote OS version: Linux x64 4.18.0 `); }); + 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.equal(issueReporterModel.serialize(), + ` +Issue Type: Bug + +undefined + +VS Code version: undefined +OS version: undefined + +
+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 normalize GitHub urls', () => { [ 'https://github.com/repo', diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts index 947a6e9f3..ef93a1c52 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts @@ -19,6 +19,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; import { MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; import { CodiconLabel } from 'vs/base/browser/ui/codicons/codiconLabel'; +import { ByteSize } from 'vs/platform/files/common/files'; const DEBUG_FLAGS_PATTERN = /\s--(inspect|debug)(-brk|port)?=(\d+)?/; const DEBUG_PORT_PATTERN = /\s--(inspect|debug)-port=(\d+)/; @@ -67,18 +68,19 @@ class ProcessExplorer { private getProcessList(rootProcess: ProcessItem, isLocal: boolean, totalMem: number): FormattedProcessItem[] { const processes: FormattedProcessItem[] = []; + const handledProcesses = new Set(); if (rootProcess) { - this.getProcessItem(processes, rootProcess, 0, isLocal, totalMem); + this.getProcessItem(processes, rootProcess, 0, isLocal, totalMem, handledProcesses); } return processes; } - private getProcessItem(processes: FormattedProcessItem[], item: ProcessItem, indent: number, isLocal: boolean, totalMem: number): void { + private getProcessItem(processes: FormattedProcessItem[], item: ProcessItem, indent: number, isLocal: boolean, totalMem: number, handledProcesses: Set): void { const isRoot = (indent === 0); - const MB = 1024 * 1024; + handledProcesses.add(item.pid); let name = item.name; if (isRoot) { @@ -95,7 +97,7 @@ class ProcessExplorer { const memory = this.data.platform === 'win32' ? item.mem : (totalMem * (item.mem / 100)); processes.push({ cpu: item.load, - memory: (memory / MB), + memory: (memory / ByteSize.MB), pid: item.pid.toFixed(0), name, formattedName, @@ -105,9 +107,11 @@ class ProcessExplorer { // Recurse into children if any if (Array.isArray(item.children)) { item.children.forEach(child => { - if (child) { - this.getProcessItem(processes, child, indent + 1, isLocal, totalMem); + if (!child || handledProcesses.has(child.pid)) { + return; // prevent loops } + + this.getProcessItem(processes, child, indent + 1, isLocal, totalMem, handledProcesses); }); } } diff --git a/src/vs/code/electron-sandbox/workbench/workbench.js b/src/vs/code/electron-sandbox/workbench/workbench.js index 3966a9d0e..6a46ef2b0 100644 --- a/src/vs/code/electron-sandbox/workbench/workbench.js +++ b/src/vs/code/electron-sandbox/workbench/workbench.js @@ -9,15 +9,12 @@ 'use strict'; (function () { + const bootstrapWindow = bootstrapWindowLib(); // Add a perf entry right from the top - const perf = perfLib(); + const perf = bootstrapWindow.perfLib(); perf.mark('renderer/started'); - // Load environment in parallel to workbench loading to avoid waterfall - const bootstrapWindow = bootstrapWindowLib(); - const whenEnvResolved = bootstrapWindow.globals().process.whenEnvResolved(); - // Load workbench main JS, CSS and NLS all in parallel. This is an // optimization to prevent a waterfall of loading to happen, because // we know for a fact that workbench.desktop.sandbox.main will depend on @@ -31,12 +28,6 @@ // Mark start of workbench perf.mark('didLoadWorkbenchMain'); - performance.mark('workbench-start'); - - // Wait for process environment being fully resolved - await whenEnvResolved; - - perf.mark('main/startup'); // @ts-ignore return require('vs/workbench/electron-sandbox/desktop.main').main(configuration); @@ -58,23 +49,11 @@ //region Helpers - function perfLib() { - globalThis.MonacoPerformanceMarks = globalThis.MonacoPerformanceMarks || []; - - return { - /** - * @param {string} name - */ - mark(name) { - globalThis.MonacoPerformanceMarks.push(name, Date.now()); - } - }; - } - /** * @returns {{ * load: (modules: string[], resultCallback: (result, configuration: object) => any, options: object) => unknown, - * globals: () => typeof import('../../../base/parts/sandbox/electron-sandbox/globals') + * globals: () => typeof import('../../../base/parts/sandbox/electron-sandbox/globals'), + * perfLib: () => { mark: (name: string) => void } * }} */ function bootstrapWindowLib() { diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 040144f4c..c6415a551 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -123,10 +123,6 @@ export async function main(argv: string[]): Promise { 'ELECTRON_NO_ATTACH_CONSOLE': '1' }; - if (args['force-user-env']) { - env['VSCODE_FORCE_USER_ENV'] = '1'; - } - delete env['ELECTRON_RUN_AS_NODE']; const processCallbacks: ((child: ChildProcess) => Promise)[] = []; @@ -157,41 +153,39 @@ export async function main(argv: string[]): Promise { // stdin. We do this because there is no reliable way to find out if data is piped to stdin. Just // checking for stdin being connected to a TTY is not enough (https://github.com/microsoft/vscode/issues/40351) - if (args._.length === 0) { - if (hasReadStdinArg) { - stdinFilePath = getStdinFilePath(); + if (hasReadStdinArg) { + stdinFilePath = getStdinFilePath(); - // returns a file path where stdin input is written into (write in progress). - try { - readFromStdin(stdinFilePath, !!verbose); // throws error if file can not be written + // returns a file path where stdin input is written into (write in progress). + try { + readFromStdin(stdinFilePath, !!verbose); // throws error if file can not be written - // Make sure to open tmp file - addArg(argv, stdinFilePath); + // Make sure to open tmp file + addArg(argv, stdinFilePath); - // Enable --wait to get all data and ignore adding this to history - addArg(argv, '--wait'); - addArg(argv, '--skip-add-to-recently-opened'); - args.wait = true; + // Enable --wait to get all data and ignore adding this to history + addArg(argv, '--wait'); + addArg(argv, '--skip-add-to-recently-opened'); + args.wait = true; - console.log(`Reading from stdin via: ${stdinFilePath}`); - } catch (e) { - console.log(`Failed to create file to read via stdin: ${e.toString()}`); - stdinFilePath = undefined; - } - } else { - - // If the user pipes data via stdin but forgot to add the "-" argument, help by printing a message - // if we detect that data flows into via stdin after a certain timeout. - processCallbacks.push(_ => stdinDataListener(1000).then(dataReceived => { - if (dataReceived) { - if (isWindows) { - console.log(`Run with '${product.applicationName} -' to read output from another program (e.g. 'echo Hello World | ${product.applicationName} -').`); - } else { - console.log(`Run with '${product.applicationName} -' to read from stdin (e.g. 'ps aux | grep code | ${product.applicationName} -').`); - } - } - })); + console.log(`Reading from stdin via: ${stdinFilePath}`); + } catch (e) { + console.log(`Failed to create file to read via stdin: ${e.toString()}`); + stdinFilePath = undefined; } + } else { + + // If the user pipes data via stdin but forgot to add the "-" argument, help by printing a message + // if we detect that data flows into via stdin after a certain timeout. + processCallbacks.push(_ => stdinDataListener(1000).then(dataReceived => { + if (dataReceived) { + if (isWindows) { + console.log(`Run with '${product.applicationName} -' to read output from another program (e.g. 'echo Hello World | ${product.applicationName} -').`); + } else { + console.log(`Run with '${product.applicationName} -' to read from stdin (e.g. 'ps aux | grep code | ${product.applicationName} -').`); + } + } + })); } } diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 426cde72b..84fa06d76 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -31,7 +31,7 @@ import { mkdirp, writeFile } from 'vs/base/node/pfs'; import { getBaseLabel } from 'vs/base/common/labels'; import { IStateService } from 'vs/platform/state/node/state'; import { StateService } from 'vs/platform/state/node/stateService'; -import { ILogService, getLogLevel } from 'vs/platform/log/common/log'; +import { ILogService, getLogLevel, LogLevel, ConsoleLogService, MultiplexLogService } from 'vs/platform/log/common/log'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { areSameExtensions, adoptToGalleryExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { URI } from 'vs/base/common/uri'; @@ -78,7 +78,7 @@ export class Main { @IInstantiationService private readonly instantiationService: IInstantiationService, @INativeEnvironmentService private readonly environmentService: INativeEnvironmentService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, - @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, ) { } async run(argv: NativeParsedArgs): Promise { @@ -93,7 +93,7 @@ export class Main { } else if (argv['locate-extension']) { await this.locateExtension(argv['locate-extension']); } else if (argv['telemetry']) { - console.log(buildTelemetryMessage(this.environmentService.appRoot, this.environmentService.extensionsPath ? this.environmentService.extensionsPath : undefined)); + console.log(buildTelemetryMessage(this.environmentService.appRoot, this.environmentService.extensionsPath)); } } @@ -126,13 +126,28 @@ export class Main { extensions.forEach(e => console.log(getId(e.manifest, showVersions))); } - private async installExtensions(extensions: string[], builtinExtensionIds: string[], isMachineScoped: boolean, force: boolean): Promise { + async installExtensions(extensions: string[], builtinExtensionIds: string[], isMachineScoped: boolean, force: boolean): Promise { const failed: string[] = []; const installedExtensionsManifests: IExtensionManifest[] = []; if (extensions.length) { console.log(localize('installingExtensions', "Installing extensions...")); } + const installed = await this.extensionManagementService.getInstalled(ExtensionType.User); + const checkIfNotInstalled = (id: string, version?: string): boolean => { + const installedExtension = installed.find(i => areSameExtensions(i.identifier, { id })); + if (installedExtension) { + if (!version && !force) { + console.log(localize('alreadyInstalled-checkAndUpdate', "Extension '{0}' v{1} is already installed. Use '--force' option to update to latest version or provide '@' to install a specific version, for example: '{2}@1.2.3'.", id, installedExtension.manifest.version, id)); + return false; + } + if (version && installedExtension.manifest.version === version) { + console.log(localize('alreadyInstalled', "Extension '{0}' is already installed.", `${id}@${version}`)); + return false; + } + } + return true; + }; const vsixs: string[] = []; const installExtensionInfos: InstallExtensionInfo[] = []; for (const extension of extensions) { @@ -140,12 +155,16 @@ export class Main { vsixs.push(extension); } else { const [id, version] = getIdAndVersion(extension); - installExtensionInfos.push({ id, version, installOptions: { isBuiltin: false, isMachineScoped } }); + if (checkIfNotInstalled(id, version)) { + installExtensionInfos.push({ id, version, installOptions: { isBuiltin: false, isMachineScoped } }); + } } } for (const extension of builtinExtensionIds) { const [id, version] = getIdAndVersion(extension); - installExtensionInfos.push({ id, version, installOptions: { isBuiltin: true, isMachineScoped: false } }); + if (checkIfNotInstalled(id, version)) { + installExtensionInfos.push({ id, version, installOptions: { isBuiltin: true, isMachineScoped: false } }); + } } if (vsixs.length) { @@ -162,28 +181,29 @@ export class Main { })); } - const [galleryExtensions, installed] = await Promise.all([ - this.getGalleryExtensions(installExtensionInfos), - this.extensionManagementService.getInstalled(ExtensionType.User) - ]); + if (installExtensionInfos.length) { - await Promise.all(installExtensionInfos.map(async extensionInfo => { - const gallery = galleryExtensions.get(extensionInfo.id.toLowerCase()); - if (gallery) { - try { - const manifest = await this.installFromGallery(extensionInfo, gallery, installed, force); - if (manifest) { - installedExtensionsManifests.push(manifest); + const galleryExtensions = await this.getGalleryExtensions(installExtensionInfos); + + await Promise.all(installExtensionInfos.map(async extensionInfo => { + const gallery = galleryExtensions.get(extensionInfo.id.toLowerCase()); + if (gallery) { + try { + const manifest = await this.installFromGallery(extensionInfo, gallery, installed, force); + if (manifest) { + installedExtensionsManifests.push(manifest); + } + } catch (err) { + console.error(err.message || err.stack || err); + failed.push(extensionInfo.id); } - } catch (err) { - console.error(err.message || err.stack || err); + } else { + console.error(`${notFound(extensionInfo.version ? `${extensionInfo.id}@${extensionInfo.version}` : extensionInfo.id)}\n${useId}`); failed.push(extensionInfo.id); } - } else { - console.error(`${notFound(extensionInfo.version ? `${extensionInfo.id}@${extensionInfo.version}` : extensionInfo.id)}\n${useId}`); - failed.push(extensionInfo.id); - } - })); + })); + + } if (installedExtensionsManifests.some(manifest => isLanguagePackExtension(manifest))) { await this.updateLocalizationsCache(); @@ -244,10 +264,6 @@ export class Main { console.log(localize('alreadyInstalled', "Extension '{0}' is already installed.", version ? `${id}@${version}` : id)); return null; } - if (!version && !force) { - console.log(localize('forceUpdate', "Extension '{0}' v{1} is already installed, but a newer version {2} is available in the marketplace. Use '--force' option to update to newer version.", id, installedExtension.manifest.version, galleryExtension.version)); - return null; - } console.log(localize('updateMessage', "Updating the extension '{0}' to the version {1}", id, galleryExtension.version)); } @@ -315,7 +331,7 @@ export class Main { return; } console.log(localize('uninstalling', "Uninstalling {0}...", id)); - await this.extensionManagementService.uninstall(extensionToUninstall, true); + await this.extensionManagementService.uninstall(extensionToUninstall); uninstalledExtensions.push(extensionToUninstall); console.log(localize('successUninstall', "Extension '{0}' was successfully uninstalled!", id)); } @@ -353,7 +369,13 @@ export async function main(argv: NativeParsedArgs): Promise { const disposables = new DisposableStore(); const environmentService = new NativeEnvironmentService(argv); - const logService: ILogService = new SpdLogService('cli', environmentService.logsPath, getLogLevel(environmentService)); + const logLevel = getLogLevel(environmentService); + const loggers: ILogService[] = []; + loggers.push(new SpdLogService('cli', environmentService.logsPath, logLevel)); + if (logLevel === LogLevel.Trace) { + loggers.push(new ConsoleLogService(logLevel)); + } + const logService = new MultiplexLogService(loggers); process.once('exit', () => logService.dispose()); logService.info('main', argv); @@ -403,7 +425,7 @@ export async function main(argv: NativeParsedArgs): Promise { appender: combinedAppender(...appenders), sendErrorTelemetry: false, commonProperties: resolveCommonProperties(product.commit, product.version, stateService.getItem('telemetry.machineId'), product.msftInternalDomains, installSourcePath), - piiPaths: extensionsPath ? [appRoot, extensionsPath] : [appRoot] + piiPaths: [appRoot, extensionsPath] }; services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config])); diff --git a/src/vs/code/node/shellEnv.ts b/src/vs/code/node/shellEnv.ts index 619886d0f..29ef8c143 100644 --- a/src/vs/code/node/shellEnv.ts +++ b/src/vs/code/node/shellEnv.ts @@ -7,9 +7,57 @@ import { spawn } from 'child_process'; import { generateUuid } from 'vs/base/common/uuid'; import { isWindows } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; -import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; +import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; -function getUnixShellEnvironment(logService: ILogService): Promise { +/** + * We need to get the environment from a user's shell. + * This should only be done when Code itself is not launched + * from within a shell. + */ +export async function resolveShellEnv(logService: ILogService, args: NativeParsedArgs, env: NodeJS.ProcessEnv): Promise { + + // Skip if --force-disable-user-env + if (args['force-disable-user-env']) { + logService.trace('resolveShellEnv(): skipped (--force-disable-user-env)'); + + return {}; + } + + // Skip on windows + else if (isWindows) { + logService.trace('resolveShellEnv(): skipped (Windows)'); + + return {}; + } + + // Skip if running from CLI already + else if (isLaunchedFromCli(env) && !args['force-user-env']) { + logService.trace('resolveShellEnv(): skipped (VSCODE_CLI is set)'); + + return {}; + } + + // Otherwise resolve (macOS, Linux) + else { + if (isLaunchedFromCli(env)) { + logService.trace('resolveShellEnv(): running (--force-user-env)'); + } else { + logService.trace('resolveShellEnv(): running (macOS/Linux)'); + } + + if (!unixShellEnvPromise) { + unixShellEnvPromise = doResolveUnixShellEnv(logService); + } + + return unixShellEnvPromise; + } +} + +let unixShellEnvPromise: Promise | undefined = undefined; + +async function doResolveUnixShellEnv(logService: ILogService): Promise { const promise = new Promise((resolve, reject) => { const runAsNode = process.env['ELECTRON_RUN_AS_NODE']; logService.trace('getUnixShellEnvironment#runAsNode', runAsNode); @@ -78,33 +126,11 @@ function getUnixShellEnvironment(logService: ILogService): Promise ({})); -} + try { + return await promise; + } catch (error) { + logService.error('getUnixShellEnvironment#error', toErrorMessage(error)); -let shellEnvPromise: Promise | undefined = undefined; - -/** - * We need to get the environment from a user's shell. - * This should only be done when Code itself is not launched - * from within a shell. - */ -export function getShellEnvironment(logService: ILogService, environmentService: INativeEnvironmentService): Promise { - if (!shellEnvPromise) { - if (environmentService.args['disable-user-env-probe']) { - logService.trace('getShellEnvironment: disable-user-env-probe set, skipping'); - shellEnvPromise = Promise.resolve({}); - } else if (isWindows) { - logService.trace('getShellEnvironment: running on Windows, skipping'); - shellEnvPromise = Promise.resolve({}); - } else if (process.env['VSCODE_CLI'] === '1' && process.env['VSCODE_FORCE_USER_ENV'] !== '1') { - logService.trace('getShellEnvironment: running on CLI, skipping'); - shellEnvPromise = Promise.resolve({}); - } else { - logService.trace('getShellEnvironment: running on Unix'); - shellEnvPromise = getUnixShellEnvironment(logService); - } + return {}; // ignore any errors } - - return shellEnvPromise; } diff --git a/src/vs/code/test/electron-main/nativeHelpers.test.ts b/src/vs/code/test/electron-main/nativeHelpers.test.ts deleted file mode 100644 index 1ce464480..000000000 --- a/src/vs/code/test/electron-main/nativeHelpers.test.ts +++ /dev/null @@ -1,40 +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 assert from 'assert'; -import { isWindows } from 'vs/base/common/platform'; - -suite('Windows Native Helpers', () => { - if (!isWindows) { - return; - } - - test('windows-mutex', async () => { - const mutex = await import('windows-mutex'); - assert.ok(mutex && typeof mutex.isActive === 'function', 'Unable to load windows-mutex dependency.'); - assert.ok(typeof mutex.isActive === 'function', 'Unable to load windows-mutex dependency.'); - }); - - test('windows-foreground-love', async () => { - const foregroundLove = await import('windows-foreground-love'); - assert.ok(foregroundLove && typeof foregroundLove.allowSetForegroundWindow === 'function', 'Unable to load windows-foreground-love dependency.'); - }); - - test('windows-process-tree', async () => { - const processTree = await import('windows-process-tree'); - assert.ok(processTree && typeof processTree.getProcessTree === 'function', 'Unable to load windows-process-tree dependency.'); - }); - - test('vscode-windows-ca-certs', async () => { - // @ts-ignore Windows only - const windowsCerts = await import('vscode-windows-ca-certs'); - assert.ok(windowsCerts, 'Unable to load vscode-windows-ca-certs dependency.'); - }); - - test('vscode-windows-registry', async () => { - const windowsRegistry = await import('vscode-windows-registry'); - assert.ok(windowsRegistry && typeof windowsRegistry.GetStringRegKey === 'function', 'Unable to load vscode-windows-registry dependency.'); - }); -}); diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index 1e7b0929d..06eecaa6a 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -355,13 +355,18 @@ class MouseDownOperation extends Disposable { e.buttons, createMouseMoveEventMerger(null), (e) => this._onMouseDownThenMove(e), - () => { + (browserEvent?: MouseEvent | KeyboardEvent) => { const position = this._findMousePosition(this._lastMouseEvent!, true); - this._viewController.emitMouseDrop({ - event: this._lastMouseEvent!, - target: (position ? this._createMouseTarget(this._lastMouseEvent!, true) : null) // Ignoring because position is unknown, e.g., Content View Zone - }); + if (browserEvent && browserEvent instanceof KeyboardEvent) { + // cancel + this._viewController.emitMouseDropCanceled(); + } else { + this._viewController.emitMouseDrop({ + event: this._lastMouseEvent!, + target: (position ? this._createMouseTarget(this._lastMouseEvent!, true) : null) // Ignoring because position is unknown, e.g., Content View Zone + }); + } this._stop(); } diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index f2be04e3a..37c1d8b05 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -18,6 +18,7 @@ import { ViewContext } from 'vs/editor/common/view/viewContext'; import { IViewModel } from 'vs/editor/common/viewModel/viewModel'; import { CursorColumns } from 'vs/editor/common/controller/cursorCommon'; import * as dom from 'vs/base/browser/dom'; +import { AtomicTabMoveOperations, Direction } from 'vs/editor/common/controller/cursorAtomicMoveOperations'; export interface IViewZoneData { viewZoneId: string; @@ -239,6 +240,7 @@ export class HitTestContext { public readonly layoutInfo: EditorLayoutInfo; public readonly viewDomNode: HTMLElement; public readonly lineHeight: number; + public readonly stickyTabStops: boolean; public readonly typicalHalfwidthCharacterWidth: number; public readonly lastRenderData: PointerHandlerLastRenderData; @@ -251,6 +253,7 @@ export class HitTestContext { this.layoutInfo = options.get(EditorOption.layoutInfo); this.viewDomNode = viewHelper.viewDomNode; this.lineHeight = options.get(EditorOption.lineHeight); + this.stickyTabStops = options.get(EditorOption.stickyTabStops); this.typicalHalfwidthCharacterWidth = options.get(EditorOption.fontInfo).typicalHalfwidthCharacterWidth; this.lastRenderData = lastRenderData; this._context = context; @@ -329,6 +332,14 @@ export class HitTestContext { return this._context.viewLayout.isAfterLines(mouseVerticalOffset); } + public isInTopPadding(mouseVerticalOffset: number): boolean { + return this._context.viewLayout.isInTopPadding(mouseVerticalOffset); + } + + public isInBottomPadding(mouseVerticalOffset: number): boolean { + return this._context.viewLayout.isInBottomPadding(mouseVerticalOffset); + } + public getVerticalOffsetForLineNumber(lineNumber: number): number { return this._context.viewLayout.getVerticalOffsetForLineNumber(lineNumber); } @@ -656,8 +667,12 @@ export class MouseTargetFactory { return null; } + if (ctx.isInTopPadding(request.mouseVerticalOffset)) { + return request.fulfill(MouseTargetType.CONTENT_EMPTY, new Position(1, 1), undefined, EMPTY_CONTENT_AFTER_LINES); + } + // Check if it is below any lines and any view zones - if (ctx.isAfterLines(request.mouseVerticalOffset)) { + if (ctx.isAfterLines(request.mouseVerticalOffset) || ctx.isInBottomPadding(request.mouseVerticalOffset)) { // This most likely indicates it happened after the last view-line const lineCount = ctx.model.getLineCount(); const maxLineColumn = ctx.model.getLineMaxColumn(lineCount); @@ -998,6 +1013,17 @@ export class MouseTargetFactory { }; } + private static _snapToSoftTabBoundary(position: Position, viewModel: IViewModel): Position { + const minColumn = viewModel.getLineMinColumn(position.lineNumber); + const lineContent = viewModel.getLineContent(position.lineNumber); + const { tabSize } = viewModel.getTextModelOptions(); + const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, position.column - minColumn, tabSize, Direction.Nearest); + if (newPosition !== -1) { + return new Position(position.lineNumber, newPosition + minColumn); + } + return position; + } + private static _doHitTest(ctx: HitTestContext, request: BareHitTestRequest): IHitTestResult { // State of the art (18.10.2012): // The spec says browsers should support document.caretPositionFromPoint, but nobody implemented it (http://dev.w3.org/csswg/cssom-view/) @@ -1016,24 +1042,24 @@ export class MouseTargetFactory { // Thank you browsers for making this so 'easy' :) + let result: IHitTestResult; if (typeof document.caretRangeFromPoint === 'function') { - - return this._doHitTestWithCaretRangeFromPoint(ctx, request); - + result = this._doHitTestWithCaretRangeFromPoint(ctx, request); } else if ((document).caretPositionFromPoint) { - - return this._doHitTestWithCaretPositionFromPoint(ctx, request.pos.toClientCoordinates()); - + result = this._doHitTestWithCaretPositionFromPoint(ctx, request.pos.toClientCoordinates()); } else if ((document.body).createTextRange) { - - return this._doHitTestWithMoveToPoint(ctx, request.pos.toClientCoordinates()); - + result = this._doHitTestWithMoveToPoint(ctx, request.pos.toClientCoordinates()); + } else { + result = { + position: null, + hitTarget: null + }; } - - return { - position: null, - hitTarget: null - }; + // Snap to the nearest soft tab boundary if atomic soft tabs are enabled. + if (result.position && ctx.stickyTabStops) { + result.position = this._snapToSoftTabBoundary(result.position, ctx.model); + } + return result; } } @@ -1047,7 +1073,7 @@ export function shadowCaretRangeFromPoint(shadowRoot: ShadowRoot, x: number, y: // Get the last child of the element until its firstChild is a text node // This assumes that the pointer is on the right of the line, out of the tokens // and that we want to get the offset of the last token of the line - while (el && el.firstChild && el.firstChild.nodeType !== el.firstChild.TEXT_NODE) { + while (el && el.firstChild && el.firstChild.nodeType !== el.firstChild.TEXT_NODE && el.lastChild && el.lastChild.firstChild) { el = el.lastChild; } diff --git a/src/vs/editor/browser/controller/pointerHandler.ts b/src/vs/editor/browser/controller/pointerHandler.ts index 83fd67d6f..b86fd84fd 100644 --- a/src/vs/editor/browser/controller/pointerHandler.ts +++ b/src/vs/editor/browser/controller/pointerHandler.ts @@ -182,7 +182,7 @@ export class PointerEventHandler extends MouseHandler { } public _onMouseDown(e: EditorMouseEvent): void { - if (e.target && this.viewHelper.linesContentDomNode.contains(e.target) && this._lastPointerType === 'touch') { + if ((e.browserEvent as any).pointerType === 'touch') { return; } diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 07eadee84..2f26618aa 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -276,6 +276,7 @@ export class TextAreaHandler extends ViewPart { this.textArea.setClassName(`inputarea ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME} ime-input`); this._viewController.compositionStart(); + this._context.model.onCompositionStart(); })); this._register(this._textAreaInput.onCompositionUpdate((e: ICompositionData) => { @@ -297,6 +298,7 @@ export class TextAreaHandler extends ViewPart { this.textArea.setClassName(`inputarea ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`); this._viewController.compositionEnd(); + this._context.model.onCompositionEnd(); })); this._register(this._textAreaInput.onFocus(() => { diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index e2d42baf4..bc1e26c1c 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -295,9 +295,12 @@ export class TextAreaInput extends Disposable { this._onType.fire(typeInput); } - // Due to isEdgeOrIE (where the textarea was not cleared initially) and isChrome (the textarea is not updated correctly when composition ends) + // Due to + // isEdgeOrIE (where the textarea was not cleared initially) + // and isChrome (the textarea is not updated correctly when composition ends) + // and isFirefox (the textare ais not updated correctly after inserting emojis) // we cannot assume the text at the end consists only of the composited text - if (browser.isEdge || browser.isChrome) { + if (browser.isEdge || browser.isChrome || browser.isFirefox) { this._textAreaState = TextAreaState.readFromTextArea(this._textArea); } diff --git a/src/vs/editor/browser/core/markdownRenderer.ts b/src/vs/editor/browser/core/markdownRenderer.ts index 375518d2c..e1eb76c8a 100644 --- a/src/vs/editor/browser/core/markdownRenderer.ts +++ b/src/vs/editor/browser/core/markdownRenderer.ts @@ -38,8 +38,8 @@ export class MarkdownRenderer { } }); - private readonly _onDidRenderCodeBlock = new Emitter(); - readonly onDidRenderCodeBlock = this._onDidRenderCodeBlock.event; + private readonly _onDidRenderAsync = new Emitter(); + readonly onDidRenderAsync = this._onDidRenderAsync.event; constructor( private readonly _options: IMarkdownRendererOptions, @@ -48,7 +48,7 @@ export class MarkdownRenderer { ) { } dispose(): void { - this._onDidRenderCodeBlock.dispose(); + this._onDidRenderAsync.dispose(); } render(markdown: IMarkdownString | undefined, options?: MarkdownRenderOptions, markedOptions?: MarkedOptions): IMarkdownRenderResult { @@ -103,7 +103,7 @@ export class MarkdownRenderer { return element; }, - codeBlockRenderCallback: () => this._onDidRenderCodeBlock.fire(), + asyncRenderCallback: () => this._onDidRenderAsync.fire(), actionHandler: { callback: (content) => this._openerService.open(content, { fromUserGesture: true }).catch(onUnexpectedError), disposeables diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 815fdf6b5..43305c283 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -494,6 +494,12 @@ export interface ICodeEditor extends editorCommon.IEditor { * @event */ onMouseDrop(listener: (e: IPartialEditorMouseEvent) => void): IDisposable; + /** + * An event emitted on a "mousedropcanceled". + * @internal + * @event + */ + onMouseDropCanceled(listener: () => void): IDisposable; /** * An event emitted on a "contextmenu". * @event @@ -678,10 +684,15 @@ export interface ICodeEditor extends editorCommon.IEditor { executeCommand(source: string | null | undefined, command: editorCommon.ICommand): void; /** - * Push an "undo stop" in the undo-redo stack. + * Create an "undo stop" in the undo-redo stack. */ pushUndoStop(): boolean; + /** + * Remove the "undo stop" in the undo-redo stack. + */ + popUndoStop(): boolean; + /** * Execute edits on the editor. * The edits will land on the undo-redo stack, but no "undo stop" will be pushed. diff --git a/src/vs/editor/browser/editorDom.ts b/src/vs/editor/browser/editorDom.ts index 4042ae826..3d9ffe64f 100644 --- a/src/vs/editor/browser/editorDom.ts +++ b/src/vs/editor/browser/editorDom.ts @@ -187,7 +187,7 @@ export class GlobalEditorMouseMoveMonitor extends Disposable { initialButtons: number, merger: EditorMouseEventMerger, mouseMoveCallback: (e: EditorMouseEvent) => void, - onStopCallback: () => void + onStopCallback: (browserEvent?: MouseEvent | KeyboardEvent) => void ): void { // Add a <> keydown event listener that will cancel the monitoring @@ -198,16 +198,16 @@ export class GlobalEditorMouseMoveMonitor extends Disposable { // Allow modifier keys return; } - this._globalMouseMoveMonitor.stopMonitoring(true); + this._globalMouseMoveMonitor.stopMonitoring(true, e.browserEvent); }, true); const myMerger: dom.IEventMerger = (lastEvent: EditorMouseEvent | null, currentEvent: MouseEvent): EditorMouseEvent => { return merger(lastEvent, new EditorMouseEvent(currentEvent, this._editorViewDomNode)); }; - this._globalMouseMoveMonitor.startMonitoring(initialElement, initialButtons, myMerger, mouseMoveCallback, () => { + this._globalMouseMoveMonitor.startMonitoring(initialElement, initialButtons, myMerger, mouseMoveCallback, (e) => { this._keydownListener!.dispose(); - onStopCallback(); + onStopCallback(e); }); } } diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index aaf9feaf3..893c2159a 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -4,8 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { IPosition } from 'vs/base/browser/ui/contextview/contextview'; -import { illegalArgument } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; @@ -409,47 +407,6 @@ export abstract class EditorAction2 extends Action2 { // --- Registration of commands and actions -export function registerLanguageCommand(id: string, handler: (accessor: ServicesAccessor, args: Args) => any) { - CommandsRegistry.registerCommand(id, (accessor, args) => handler(accessor, args || {})); -} - -interface IDefaultArgs { - resource: URI; - position: IPosition; - [name: string]: any; -} - -export function registerDefaultLanguageCommand(id: string, handler: (model: ITextModel, position: Position, args: IDefaultArgs) => any) { - registerLanguageCommand(id, function (accessor, args: IDefaultArgs) { - - const { resource, position } = args; - if (!(resource instanceof URI)) { - throw illegalArgument('resource'); - } - if (!Position.isIPosition(position)) { - throw illegalArgument('position'); - } - - const model = accessor.get(IModelService).getModel(resource); - if (model) { - const editorPosition = Position.lift(position); - return handler(model, editorPosition, args); - } - - return accessor.get(ITextModelService).createModelReference(resource).then(reference => { - return new Promise((resolve, reject) => { - try { - const result = handler(reference.object.textEditorModel, Position.lift(position), args); - resolve(result); - } catch (err) { - reject(err); - } - }).finally(() => { - reference.dispose(); - }); - }); - }); -} export function registerModelAndPositionCommand(id: string, handler: (model: ITextModel, position: Position, ...args: any[]) => any) { CommandsRegistry.registerCommand(id, function (accessor, ...args) { diff --git a/src/vs/editor/browser/services/abstractCodeEditorService.ts b/src/vs/editor/browser/services/abstractCodeEditorService.ts index f48a9cff8..3ebb6454b 100644 --- a/src/vs/editor/browser/services/abstractCodeEditorService.ts +++ b/src/vs/editor/browser/services/abstractCodeEditorService.ts @@ -31,6 +31,8 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC private readonly _onDidChangeTransientModelProperty: Emitter = this._register(new Emitter()); public readonly onDidChangeTransientModelProperty: Event = this._onDidChangeTransientModelProperty.event; + protected readonly _onDecorationTypeRegistered: Emitter = this._register(new Emitter()); + public onDecorationTypeRegistered: Event = this._onDecorationTypeRegistered.event; private readonly _codeEditors: { [editorId: string]: ICodeEditor; }; private readonly _diffEditors: { [editorId: string]: IDiffEditor; }; @@ -93,6 +95,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC abstract registerDecorationType(key: string, options: IDecorationRenderOptions, parentTypeKey?: string, editor?: ICodeEditor): void; abstract removeDecorationType(key: string): void; abstract resolveDecorationOptions(decorationTypeKey: string | undefined, writable: boolean): IModelDecorationOptions; + abstract resolveDecorationCSSRules(decorationTypeKey: string): CSSRuleList | null; private readonly _transientWatchers: { [uri: string]: ModelTransientSettingWatcher; } = {}; private readonly _modelProperties = new Map>(); diff --git a/src/vs/editor/browser/services/bulkEditService.ts b/src/vs/editor/browser/services/bulkEditService.ts index 7a1f3346e..a9e8b99dc 100644 --- a/src/vs/editor/browser/services/bulkEditService.ts +++ b/src/vs/editor/browser/services/bulkEditService.ts @@ -10,6 +10,8 @@ import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { isObject } from 'vs/base/common/types'; +import { UndoRedoSource } from 'vs/platform/undoRedo/common/undoRedo'; +import { CancellationToken } from 'vs/base/common/cancellation'; export const IBulkEditService = createDecorator('IWorkspaceEditService'); @@ -65,9 +67,13 @@ export class ResourceFileEdit extends ResourceEdit { export interface IBulkEditOptions { editor?: ICodeEditor; progress?: IProgress; + token?: CancellationToken; showPreview?: boolean; + suppressPreview?: boolean; label?: string; quotableLabel?: string; + undoRedoSource?: UndoRedoSource; + undoRedoGroupId?: number; } export interface IBulkEditResult { diff --git a/src/vs/editor/browser/services/codeEditorService.ts b/src/vs/editor/browser/services/codeEditorService.ts index 0174886d2..8665de002 100644 --- a/src/vs/editor/browser/services/codeEditorService.ts +++ b/src/vs/editor/browser/services/codeEditorService.ts @@ -23,6 +23,7 @@ export interface ICodeEditorService { readonly onDiffEditorRemove: Event; readonly onDidChangeTransientModelProperty: Event; + readonly onDecorationTypeRegistered: Event; addCodeEditor(editor: ICodeEditor): void; @@ -41,6 +42,7 @@ export interface ICodeEditorService { registerDecorationType(key: string, options: IDecorationRenderOptions, parentTypeKey?: string, editor?: ICodeEditor): void; removeDecorationType(key: string): void; resolveDecorationOptions(typeKey: string, writable: boolean): IModelDecorationOptions; + resolveDecorationCSSRules(decorationTypeKey: string): CSSRuleList | null; setModelProperty(resource: URI, key: string, value: any): void; getModelProperty(resource: URI, key: string): any; diff --git a/src/vs/editor/browser/services/codeEditorServiceImpl.ts b/src/vs/editor/browser/services/codeEditorServiceImpl.ts index 464c1a33d..c961bfd79 100644 --- a/src/vs/editor/browser/services/codeEditorServiceImpl.ts +++ b/src/vs/editor/browser/services/codeEditorServiceImpl.ts @@ -21,6 +21,10 @@ export class RefCountedStyleSheet { private readonly _styleSheet: HTMLStyleElement; private _refCount: number; + public get sheet() { + return this._styleSheet.sheet as CSSStyleSheet; + } + constructor(parent: CodeEditorServiceImpl, editorId: string, styleSheet: HTMLStyleElement) { this._parent = parent; this._editorId = editorId; @@ -53,6 +57,10 @@ export class RefCountedStyleSheet { export class GlobalStyleSheet { private readonly _styleSheet: HTMLStyleElement; + public get sheet() { + return this._styleSheet.sheet as CSSStyleSheet; + } + constructor(styleSheet: HTMLStyleElement) { this._styleSheet = styleSheet; } @@ -129,6 +137,7 @@ export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService { provider = new DecorationSubTypeOptionsProvider(this._themeService, styleSheet, providerArgs); } this._decorationOptionProviders.set(key, provider); + this._onDecorationTypeRegistered.fire(key); } provider.refCount++; } @@ -153,6 +162,14 @@ export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService { return provider.getOptions(this, writable); } + public resolveDecorationCSSRules(decorationTypeKey: string) { + const provider = this._decorationOptionProviders.get(decorationTypeKey); + if (!provider) { + return null; + } + return provider.resolveDecorationCSSRules(); + } + abstract getActiveCodeEditor(): ICodeEditor | null; abstract openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; } @@ -160,9 +177,10 @@ export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService { interface IModelDecorationOptionsProvider extends IDisposable { refCount: number; getOptions(codeEditorService: AbstractCodeEditorService, writable: boolean): IModelDecorationOptions; + resolveDecorationCSSRules(): CSSRuleList; } -class DecorationSubTypeOptionsProvider implements IModelDecorationOptionsProvider { +export class DecorationSubTypeOptionsProvider implements IModelDecorationOptionsProvider { private readonly _styleSheet: GlobalStyleSheet | RefCountedStyleSheet; public refCount: number; @@ -192,6 +210,10 @@ class DecorationSubTypeOptionsProvider implements IModelDecorationOptionsProvide return options; } + public resolveDecorationCSSRules(): CSSRuleList { + return this._styleSheet.sheet.cssRules; + } + public dispose(): void { if (this._beforeContentRules) { this._beforeContentRules.dispose(); @@ -213,7 +235,7 @@ interface ProviderArguments { } -class DecorationTypeOptionsProvider implements IModelDecorationOptionsProvider { +export class DecorationTypeOptionsProvider implements IModelDecorationOptionsProvider { private readonly _disposables = new DisposableStore(); private readonly _styleSheet: GlobalStyleSheet | RefCountedStyleSheet; @@ -295,6 +317,10 @@ class DecorationTypeOptionsProvider implements IModelDecorationOptionsProvider { }; } + public resolveDecorationCSSRules(): CSSRuleList { + return this._styleSheet.sheet.rules; + } + public dispose(): void { this._disposables.dispose(); this._styleSheet.unref(); diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index d898abd30..1360871f7 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -14,6 +14,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { ICommandService } from 'vs/platform/commands/common/commands'; import { IOpener, IOpenerService, IValidator, IExternalUriResolver, OpenOptions, ResolveExternalUriOptions, IResolvedExternalUri, IExternalOpener, matchesScheme } from 'vs/platform/opener/common/opener'; import { EditorOpenContext } from 'vs/platform/editor/common/editor'; +import { ResourceMap } from 'vs/base/common/map'; class CommandOpener implements IOpener { @@ -74,7 +75,14 @@ class EditorOpener implements IOpener { } await this._editorService.openCodeEditor( - { resource: target, options: { selection, context: options?.fromUserGesture ? EditorOpenContext.USER : EditorOpenContext.API } }, + { + resource: target, + options: { + selection, + context: options?.fromUserGesture ? EditorOpenContext.USER : EditorOpenContext.API, + ...options?.editorOptions + } + }, this._editorService.getFocusedCodeEditor(), options?.openToSide ); @@ -90,6 +98,7 @@ export class OpenerService implements IOpenerService { private readonly _openers = new LinkedList(); private readonly _validators = new LinkedList(); private readonly _resolvers = new LinkedList(); + private readonly _resolvedUriTargets = new ResourceMap(uri => uri.with({ path: null, fragment: null, query: null }).toString()); private _externalOpener: IExternalOpener; @@ -148,16 +157,18 @@ export class OpenerService implements IOpenerService { } async open(target: URI | string, options?: OpenOptions): Promise { - // check with contributed validators - for (const validator of this._validators.toArray()) { - if (!(await validator.shouldOpen(target))) { + const targetURI = typeof target === 'string' ? URI.parse(target) : target; + // validate against the original URI that this URI resolves to, if one exists + const validationTarget = this._resolvedUriTargets.get(targetURI) ?? target; + for (const validator of this._validators) { + if (!(await validator.shouldOpen(validationTarget))) { return false; } } // check with contributed openers - for (const opener of this._openers.toArray()) { + for (const opener of this._openers) { const handled = await opener.open(target, options); if (handled) { return true; @@ -168,9 +179,10 @@ export class OpenerService implements IOpenerService { } async resolveExternalUri(resource: URI, options?: ResolveExternalUriOptions): Promise { - for (const resolver of this._resolvers.toArray()) { + for (const resolver of this._resolvers) { const result = await resolver.resolveExternalUri(resource, options); if (result) { + this._resolvedUriTargets.set(result.resolved, resource); return result; } } @@ -180,7 +192,7 @@ export class OpenerService implements IOpenerService { private async _doOpenExternal(resource: URI | string, options: OpenOptions | undefined): Promise { - //todo@joh IExternalUriResolver should support `uri: URI | string` + //todo@jrieken IExternalUriResolver should support `uri: URI | string` const uri = typeof resource === 'string' ? URI.parse(resource) : resource; const { resolved } = await this.resolveExternalUri(uri, options); diff --git a/src/vs/editor/browser/view/domLineBreaksComputer.ts b/src/vs/editor/browser/view/domLineBreaksComputer.ts index 45c568d07..87c6461d5 100644 --- a/src/vs/editor/browser/view/domLineBreaksComputer.ts +++ b/src/vs/editor/browser/view/domLineBreaksComputer.ts @@ -3,13 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ILineBreaksComputerFactory, LineBreakData, ILineBreaksComputer } from 'vs/editor/common/viewModel/splitLinesCollection'; +import { ILineBreaksComputerFactory } from 'vs/editor/common/viewModel/splitLinesCollection'; import { WrappingIndent } from 'vs/editor/common/config/editorOptions'; import { FontInfo } from 'vs/editor/common/config/fontInfo'; import { createStringBuilder, IStringBuilder } from 'vs/editor/common/core/stringBuilder'; import { CharCode } from 'vs/base/common/charCode'; import * as strings from 'vs/base/common/strings'; import { Configuration } from 'vs/editor/browser/config/configuration'; +import { ILineBreaksComputer, LineBreakData } from 'vs/editor/common/viewModel/viewModel'; + +const ttPolicy = window.trustedTypes?.createPolicy('domLineBreaksComputer', { createHTML: value => value }); export class DOMLineBreaksComputerFactory implements ILineBreaksComputerFactory { @@ -107,7 +110,9 @@ function createLineBreaks(requests: string[], fontInfo: FontInfo, tabSize: numbe allCharOffsets[i] = tmp[0]; allVisibleColumns[i] = tmp[1]; } - containerDomNode.innerHTML = sb.build(); + const html = sb.build(); + const trustedhtml = ttPolicy ? ttPolicy.createHTML(html) : html; + containerDomNode.innerHTML = trustedhtml as unknown as string; containerDomNode.style.position = 'absolute'; containerDomNode.style.top = '10000'; diff --git a/src/vs/editor/browser/view/viewController.ts b/src/vs/editor/browser/view/viewController.ts index 519a26bb5..a218b7071 100644 --- a/src/vs/editor/browser/view/viewController.ts +++ b/src/vs/editor/browser/view/viewController.ts @@ -322,6 +322,10 @@ export class ViewController { this.userInputEvents.emitMouseDrop(e); } + public emitMouseDropCanceled(): void { + this.userInputEvents.emitMouseDropCanceled(); + } + public emitMouseWheel(e: IMouseWheelEvent): void { this.userInputEvents.emitMouseWheel(e); } diff --git a/src/vs/editor/browser/view/viewUserInputEvents.ts b/src/vs/editor/browser/view/viewUserInputEvents.ts index 22906bda6..2a58f89d5 100644 --- a/src/vs/editor/browser/view/viewUserInputEvents.ts +++ b/src/vs/editor/browser/view/viewUserInputEvents.ts @@ -26,6 +26,7 @@ export class ViewUserInputEvents { public onMouseUp: EventCallback | null = null; public onMouseDrag: EventCallback | null = null; public onMouseDrop: EventCallback | null = null; + public onMouseDropCanceled: EventCallback | null = null; public onMouseWheel: EventCallback | null = null; private readonly _coordinatesConverter: ICoordinatesConverter; @@ -88,6 +89,12 @@ export class ViewUserInputEvents { } } + public emitMouseDropCanceled(): void { + if (this.onMouseDropCanceled) { + this.onMouseDropCanceled(); + } + } + public emitMouseWheel(e: IMouseWheelEvent): void { if (this.onMouseWheel) { this.onMouseWheel(e); diff --git a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts index 098bc6c76..6208c89fb 100644 --- a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts +++ b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts @@ -23,7 +23,7 @@ export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay { protected _contentLeft: number; protected _contentWidth: number; protected _selectionIsEmpty: boolean; - protected _renderLineHightlightOnlyWhenFocus: boolean; + protected _renderLineHighlightOnlyWhenFocus: boolean; protected _focused: boolean; private _cursorLineNumbers: number[]; private _selections: Selection[]; @@ -37,7 +37,7 @@ export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay { const layoutInfo = options.get(EditorOption.layoutInfo); this._lineHeight = options.get(EditorOption.lineHeight); this._renderLineHighlight = options.get(EditorOption.renderLineHighlight); - this._renderLineHightlightOnlyWhenFocus = options.get(EditorOption.renderLineHighlightOnlyWhenFocus); + this._renderLineHighlightOnlyWhenFocus = options.get(EditorOption.renderLineHighlightOnlyWhenFocus); this._contentLeft = layoutInfo.contentLeft; this._contentWidth = layoutInfo.contentWidth; this._selectionIsEmpty = true; @@ -85,7 +85,7 @@ export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay { const layoutInfo = options.get(EditorOption.layoutInfo); this._lineHeight = options.get(EditorOption.lineHeight); this._renderLineHighlight = options.get(EditorOption.renderLineHighlight); - this._renderLineHightlightOnlyWhenFocus = options.get(EditorOption.renderLineHighlightOnlyWhenFocus); + this._renderLineHighlightOnlyWhenFocus = options.get(EditorOption.renderLineHighlightOnlyWhenFocus); this._contentLeft = layoutInfo.contentLeft; this._contentWidth = layoutInfo.contentWidth; return true; @@ -110,7 +110,7 @@ export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay { return true; } public onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean { - if (!this._renderLineHightlightOnlyWhenFocus) { + if (!this._renderLineHighlightOnlyWhenFocus) { return false; } @@ -170,33 +170,36 @@ export class CurrentLineHighlightOverlay extends AbstractLineHighlightOverlay { return ( (this._renderLineHighlight === 'line' || this._renderLineHighlight === 'all') && this._selectionIsEmpty - && (!this._renderLineHightlightOnlyWhenFocus || this._focused) + && (!this._renderLineHighlightOnlyWhenFocus || this._focused) ); } protected _shouldRenderOther(): boolean { return ( (this._renderLineHighlight === 'gutter' || this._renderLineHighlight === 'all') - && (!this._renderLineHightlightOnlyWhenFocus || this._focused) + && (!this._renderLineHighlightOnlyWhenFocus || this._focused) ); } } export class CurrentLineMarginHighlightOverlay extends AbstractLineHighlightOverlay { protected _renderOne(ctx: RenderingContext): string { - const className = 'current-line current-line-margin' + (this._shouldRenderOther() ? ' current-line-margin-both' : ''); + const className = 'current-line' + (this._shouldRenderMargin() ? ' current-line-margin' : '') + (this._shouldRenderOther() ? ' current-line-margin-both' : ''); return `
`; } - protected _shouldRenderThis(): boolean { + protected _shouldRenderMargin(): boolean { return ( (this._renderLineHighlight === 'gutter' || this._renderLineHighlight === 'all') - && (!this._renderLineHightlightOnlyWhenFocus || this._focused) + && (!this._renderLineHighlightOnlyWhenFocus || this._focused) ); } + protected _shouldRenderThis(): boolean { + return true; + } protected _shouldRenderOther(): boolean { return ( (this._renderLineHighlight === 'line' || this._renderLineHighlight === 'all') && this._selectionIsEmpty - && (!this._renderLineHightlightOnlyWhenFocus || this._focused) + && (!this._renderLineHighlightOnlyWhenFocus || this._focused) ); } } diff --git a/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts b/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts index 21193bac1..5a6df759b 100644 --- a/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts +++ b/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts @@ -56,6 +56,7 @@ export class EditorScrollbar extends ViewPart { mouseWheelScrollSensitivity: mouseWheelScrollSensitivity, fastScrollSensitivity: fastScrollSensitivity, scrollPredominantAxis: scrollPredominantAxis, + scrollByPage: scrollbar.scrollByPage, }; this.scrollbar = this._register(new SmoothScrollableElement(linesContent.domNode, scrollbarOptions, this._context.viewLayout.getScrollable())); diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 1be53aad7..7ca33211a 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -226,8 +226,7 @@ class MinimapLayout { * Compute a desired `scrollPosition` such that the slider moves by `delta`. */ public getDesiredScrollTopFromDelta(delta: number): number { - const desiredSliderPosition = this.sliderTop + delta; - return Math.round(desiredSliderPosition / this._computedSliderRatio); + return Math.round(this.scrollTop + delta / this._computedSliderRatio); } public getDesiredScrollTopFromTouchLocation(pageY: number): number { @@ -238,6 +237,7 @@ class MinimapLayout { options: MinimapOptions, viewportStartLineNumber: number, viewportEndLineNumber: number, + viewportStartLineNumberVerticalOffset: number, viewportHeight: number, viewportContainsWhitespaceGaps: boolean, lineCount: number, @@ -332,8 +332,10 @@ class MinimapLayout { } const endLineNumber = Math.min(lineCount, startLineNumber + minimapLinesFitting - 1); + const partialLine = (scrollTop - viewportStartLineNumberVerticalOffset) / lineHeight; + const sliderTopAligned = (viewportStartLineNumber - startLineNumber + partialLine) * minimapLineHeight / pixelRatio; - return new MinimapLayout(scrollTop, scrollHeight, true, computedSliderRatio, sliderTop, sliderHeight, startLineNumber, endLineNumber); + return new MinimapLayout(scrollTop, scrollHeight, true, computedSliderRatio, sliderTopAligned, sliderHeight, startLineNumber, endLineNumber); } } } @@ -505,6 +507,7 @@ interface IMinimapRenderingContext { readonly viewportStartLineNumber: number; readonly viewportEndLineNumber: number; + readonly viewportStartLineNumberVerticalOffset: number; readonly scrollTop: number; readonly scrollLeft: number; @@ -891,6 +894,7 @@ export class Minimap extends ViewPart implements IMinimapModel { viewportStartLineNumber: viewportStartLineNumber, viewportEndLineNumber: viewportEndLineNumber, + viewportStartLineNumberVerticalOffset: ctx.getVerticalOffsetForLineNumber(viewportStartLineNumber), scrollTop: ctx.scrollTop, scrollLeft: ctx.scrollLeft, @@ -1344,6 +1348,7 @@ class InnerMinimap extends Disposable { this._model.options, renderingCtx.viewportStartLineNumber, renderingCtx.viewportEndLineNumber, + renderingCtx.viewportStartLineNumberVerticalOffset, renderingCtx.viewportHeight, renderingCtx.viewportContainsWhitespaceGaps, this._model.getLineCount(), diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts index ef3f6e301..ec5077ea0 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts @@ -25,6 +25,7 @@ export class ViewCursors extends ViewPart { private _cursorStyle: TextEditorCursorStyle; private _cursorSmoothCaretAnimation: boolean; private _selectionIsEmpty: boolean; + private _isComposingInput: boolean; private _isVisible: boolean; @@ -49,6 +50,7 @@ export class ViewCursors extends ViewPart { this._cursorStyle = options.get(EditorOption.cursorStyle); this._cursorSmoothCaretAnimation = options.get(EditorOption.cursorSmoothCaretAnimation); this._selectionIsEmpty = true; + this._isComposingInput = false; this._isVisible = false; @@ -83,7 +85,16 @@ export class ViewCursors extends ViewPart { } // --- begin event handlers - + public onCompositionStart(e: viewEvents.ViewCompositionStartEvent): boolean { + this._isComposingInput = true; + this._updateBlinking(); + return true; + } + public onCompositionEnd(e: viewEvents.ViewCompositionEndEvent): boolean { + this._isComposingInput = false; + this._updateBlinking(); + return true; + } public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { const options = this._context.configuration.options; @@ -195,6 +206,10 @@ export class ViewCursors extends ViewPart { // ---- blinking logic private _getCursorBlinking(): TextEditorCursorBlinkingStyle { + if (this._isComposingInput) { + // avoid double cursors + return TextEditorCursorBlinkingStyle.Hidden; + } if (!this._editorHasFocus) { return TextEditorCursorBlinkingStyle.Hidden; } diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 17539558c..4c87ce2bb 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -37,7 +37,7 @@ import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents'; import * as modes from 'vs/editor/common/modes'; import { editorUnnecessaryCodeBorder, editorUnnecessaryCodeOpacity } from 'vs/editor/common/view/editorColorRegistry'; -import { editorErrorBorder, editorErrorForeground, editorHintBorder, editorHintForeground, editorInfoBorder, editorInfoForeground, editorWarningBorder, editorWarningForeground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; +import { editorErrorBorder, editorErrorForeground, editorHintBorder, editorHintForeground, editorInfoBorder, editorInfoForeground, editorWarningBorder, editorWarningForeground, editorForeground, editorErrorBackground, editorInfoBackground, editorWarningBackground } from 'vs/platform/theme/common/colorRegistry'; import { VerticalRevealType } from 'vs/editor/common/view/viewEvents'; import { IEditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; @@ -176,6 +176,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE private readonly _onMouseDrop: Emitter = this._register(new Emitter()); public readonly onMouseDrop: Event = this._onMouseDrop.event; + private readonly _onMouseDropCanceled: Emitter = this._register(new Emitter()); + public readonly onMouseDropCanceled: Event = this._onMouseDropCanceled.event; + private readonly _onContextMenu: Emitter = this._register(new Emitter()); public readonly onContextMenu: Event = this._onContextMenu.event; @@ -1024,6 +1027,8 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE if (this._triggerEditorCommand(source, handlerId, payload)) { return; } + + this._commandService.executeCommand(handlerId, payload); } private _startComposition(): void { @@ -1117,6 +1122,18 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return true; } + public popUndoStop(): boolean { + if (!this._modelData) { + return false; + } + if (this._configuration.options.get(EditorOption.readOnly)) { + // read only editor => sorry! + return false; + } + this._modelData.model.popStackElement(); + return true; + } + public executeEdits(source: string | null | undefined, edits: IIdentifiedSingleEditOperation[], endCursorState?: ICursorStateComputer | Selection[]): boolean { if (!this._modelData) { return false; @@ -1614,6 +1631,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE viewUserInputEvents.onMouseUp = (e) => this._onMouseUp.fire(e); viewUserInputEvents.onMouseDrag = (e) => this._onMouseDrag.fire(e); viewUserInputEvents.onMouseDrop = (e) => this._onMouseDrop.fire(e); + viewUserInputEvents.onMouseDropCanceled = (e) => this._onMouseDropCanceled.fire(e); viewUserInputEvents.onMouseWheel = (e) => this._onMouseWheel.fire(e); const view = new View( @@ -1716,6 +1734,7 @@ class EditorContextKeysManager extends Disposable { private readonly _editorTextFocus: IContextKey; private readonly _editorTabMovesFocus: IContextKey; private readonly _editorReadonly: IContextKey; + private readonly _inDiffEditor: IContextKey; private readonly _editorColumnSelection: IContextKey; private readonly _hasMultipleSelections: IContextKey; private readonly _hasNonEmptySelection: IContextKey; @@ -1738,6 +1757,7 @@ class EditorContextKeysManager extends Disposable { this._editorTextFocus = EditorContextKeys.editorTextFocus.bindTo(contextKeyService); this._editorTabMovesFocus = EditorContextKeys.tabMovesFocus.bindTo(contextKeyService); this._editorReadonly = EditorContextKeys.readOnly.bindTo(contextKeyService); + this._inDiffEditor = EditorContextKeys.inDiffEditor.bindTo(contextKeyService); this._editorColumnSelection = EditorContextKeys.columnSelection.bindTo(contextKeyService); this._hasMultipleSelections = EditorContextKeys.hasMultipleSelections.bindTo(contextKeyService); this._hasNonEmptySelection = EditorContextKeys.hasNonEmptySelection.bindTo(contextKeyService); @@ -1766,6 +1786,7 @@ class EditorContextKeysManager extends Disposable { this._editorTabMovesFocus.set(options.get(EditorOption.tabFocusMode)); this._editorReadonly.set(options.get(EditorOption.readOnly)); + this._inDiffEditor.set(options.get(EditorOption.inDiffEditor)); this._editorColumnSelection.set(options.get(EditorOption.columnSelection)); } @@ -1981,6 +2002,10 @@ registerThemingParticipant((theme, collector) => { if (errorForeground) { collector.addRule(`.monaco-editor .${ClassName.EditorErrorDecoration} { background: url("data:image/svg+xml,${getSquigglySVGData(errorForeground)}") repeat-x bottom left; }`); } + const errorBackground = theme.getColor(editorErrorBackground); + if (errorBackground) { + collector.addRule(`.monaco-editor .${ClassName.EditorErrorDecoration}::before { display: block; content: ''; width: 100%; height: 100%; background: ${errorBackground}; }`); + } const warningBorderColor = theme.getColor(editorWarningBorder); if (warningBorderColor) { @@ -1990,6 +2015,10 @@ registerThemingParticipant((theme, collector) => { if (warningForeground) { collector.addRule(`.monaco-editor .${ClassName.EditorWarningDecoration} { background: url("data:image/svg+xml,${getSquigglySVGData(warningForeground)}") repeat-x bottom left; }`); } + const warningBackground = theme.getColor(editorWarningBackground); + if (warningBackground) { + collector.addRule(`.monaco-editor .${ClassName.EditorWarningDecoration}::before { display: block; content: ''; width: 100%; height: 100%; background: ${warningBackground}; }`); + } const infoBorderColor = theme.getColor(editorInfoBorder); if (infoBorderColor) { @@ -1999,6 +2028,10 @@ registerThemingParticipant((theme, collector) => { if (infoForeground) { collector.addRule(`.monaco-editor .${ClassName.EditorInfoDecoration} { background: url("data:image/svg+xml,${getSquigglySVGData(infoForeground)}") repeat-x bottom left; }`); } + const infoBackground = theme.getColor(editorInfoBackground); + if (infoBackground) { + collector.addRule(`.monaco-editor .${ClassName.EditorInfoDecoration}::before { display: block; content: ''; width: 100%; height: 100%; background: ${infoBackground}; }`); + } const hintBorderColor = theme.getColor(editorHintBorder); if (hintBorderColor) { diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index ada211784..0730e4f00 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -20,7 +20,7 @@ import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { DiffReview } from 'vs/editor/browser/widget/diffReview'; -import { IDiffEditorOptions, IEditorOptions, EditorLayoutInfo, IComputedEditorOptions, EditorOption, EditorOptions, EditorFontLigatures } from 'vs/editor/common/config/editorOptions'; +import { IDiffEditorOptions, IEditorOptions, EditorLayoutInfo, EditorOption, EditorOptions, EditorFontLigatures, stringSet as validateStringSetOption, boolean as validateBooleanOption } from 'vs/editor/common/config/editorOptions'; 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'; @@ -33,13 +33,13 @@ import { OverviewRulerZone } from 'vs/editor/common/view/overviewZoneManager'; import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; import { RenderLineInput, renderViewLine } from 'vs/editor/common/viewLayout/viewLineRenderer'; import { IEditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout'; -import { InlineDecoration, InlineDecorationType, ViewLineRenderingData } from 'vs/editor/common/viewModel/viewModel'; +import { ILineBreaksComputer, InlineDecoration, InlineDecorationType, IViewModel, ViewLineRenderingData } from 'vs/editor/common/viewModel/viewModel'; 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 { INotificationService } from 'vs/platform/notification/common/notification'; import { defaultInsertColor, defaultRemoveColor, diffBorder, diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, scrollbarShadow, scrollbarSliderBackground, scrollbarSliderHoverBackground, scrollbarSliderActiveBackground, diffDiagonalFill } from 'vs/platform/theme/common/colorRegistry'; -import { IColorTheme, IThemeService, getThemeTypeSelector, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService, getThemeTypeSelector, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IDiffLinesChange, InlineDiffMargin } from 'vs/editor/browser/widget/inlineDiffMargin'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -48,8 +48,11 @@ import { EditorExtensionsRegistry, IDiffEditorContributionDescription } from 'vs import { onUnexpectedError } from 'vs/base/common/errors'; import { IEditorProgressService, IProgressRunner } from 'vs/platform/progress/common/progress'; import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver'; -import { Codicon, registerIcon } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor'; +import { IViewLineTokens } from 'vs/editor/common/core/lineTokens'; +import { FontInfo } from 'vs/editor/common/config/fontInfo'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; interface IEditorDiffDecorations { decorations: IModelDeltaDecoration[]; @@ -70,17 +73,9 @@ interface IEditorsZones { modified: IMyViewZone[]; } -export interface IDiffEditorWidgetStyle { - getEditorsDiffDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalWhitespaces: IEditorWhitespace[], modifiedWhitespaces: IEditorWhitespace[], originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorsDiffDecorationsWithZones; - setEnableSplitViewResizing(enableSplitViewResizing: boolean): void; - applyColors(theme: IColorTheme): boolean; - layout(): number; - dispose(): void; -} - class VisualEditorState { private _zones: string[]; - private inlineDiffMargins: InlineDiffMargin[]; + private _inlineDiffMargins: InlineDiffMargin[]; private _zonesMap: { [zoneId: string]: boolean; }; private _decorations: string[]; @@ -89,7 +84,7 @@ class VisualEditorState { private _clipboardService: IClipboardService ) { this._zones = []; - this.inlineDiffMargins = []; + this._inlineDiffMargins = []; this._zonesMap = {}; this._decorations = []; } @@ -102,8 +97,8 @@ class VisualEditorState { // (1) View zones if (this._zones.length > 0) { editor.changeViewZones((viewChangeAccessor: editorBrowser.IViewZoneChangeAccessor) => { - for (let i = 0, length = this._zones.length; i < length; i++) { - viewChangeAccessor.removeZone(this._zones[i]); + for (const zoneId of this._zones) { + viewChangeAccessor.removeZone(zoneId); } }); } @@ -120,25 +115,25 @@ class VisualEditorState { // view zones editor.changeViewZones((viewChangeAccessor: editorBrowser.IViewZoneChangeAccessor) => { - for (let i = 0, length = this._zones.length; i < length; i++) { - viewChangeAccessor.removeZone(this._zones[i]); + for (const zoneId of this._zones) { + viewChangeAccessor.removeZone(zoneId); } - for (let i = 0, length = this.inlineDiffMargins.length; i < length; i++) { - this.inlineDiffMargins[i].dispose(); + for (const inlineDiffMargin of this._inlineDiffMargins) { + inlineDiffMargin.dispose(); } this._zones = []; this._zonesMap = {}; - this.inlineDiffMargins = []; + this._inlineDiffMargins = []; for (let i = 0, length = newDecorations.zones.length; i < length; i++) { const viewZone = newDecorations.zones[i]; viewZone.suppressMouseDown = true; - let zoneId = viewChangeAccessor.addZone(viewZone); + const zoneId = viewChangeAccessor.addZone(viewZone); this._zones.push(zoneId); this._zonesMap[String(zoneId)] = true; if (newDecorations.zones[i].diff && viewZone.marginDomNode) { viewZone.suppressMouseDown = false; - this.inlineDiffMargins.push(new InlineDiffMargin(zoneId, viewZone.marginDomNode, editor, newDecorations.zones[i].diff!, this._contextMenuService, this._clipboardService)); + this._inlineDiffMargins.push(new InlineDiffMargin(zoneId, viewZone.marginDomNode, editor, newDecorations.zones[i].diff!, this._contextMenuService, this._clipboardService)); } } }); @@ -160,8 +155,9 @@ class VisualEditorState { let DIFF_EDITOR_ID = 0; -const diffInsertIcon = registerIcon('diff-insert', Codicon.add); -const diffRemoveIcon = registerIcon('diff-remove', Codicon.remove); +const diffInsertIcon = registerIcon('diff-insert', Codicon.add, nls.localize('diffInsertIcon', 'Line decoration for inserts in the diff editor.')); +const diffRemoveIcon = registerIcon('diff-remove', Codicon.remove, nls.localize('diffRemoveIcon', 'Line decoration for removals in the diff editor.')); +const ttPolicy = window.trustedTypes?.createPolicy('diffEditorWidget', { createHTML: value => value }); export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffEditor { @@ -178,7 +174,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE private readonly _onDidContentSizeChange: Emitter = this._register(new Emitter()); public readonly onDidContentSizeChange: Event = this._onDidContentSizeChange.event; - private readonly id: number; + private readonly _id: number; private _state: editorBrowser.DiffEditorState; private _updatingDiffProgress: IProgressRunner | null; @@ -189,12 +185,12 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE private readonly _elementSizeObserver: ElementSizeObserver; - private readonly originalEditor: CodeEditorWidget; + private readonly _originalEditor: CodeEditorWidget; private readonly _originalDomNode: HTMLElement; private readonly _originalEditorState: VisualEditorState; private _originalOverviewRuler: editorBrowser.IOverviewRuler | null; - private readonly modifiedEditor: CodeEditorWidget; + private readonly _modifiedEditor: CodeEditorWidget; private readonly _modifiedDomNode: HTMLElement; private readonly _modifiedEditorState: VisualEditorState; private _modifiedOverviewRuler: editorBrowser.IOverviewRuler | null; @@ -209,14 +205,14 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE private _ignoreTrimWhitespace: boolean; private _originalIsEditable: boolean; - private _originalCodeLens: boolean; - private _modifiedCodeLens: boolean; + private _diffCodeLens: boolean; + private _diffWordWrap: 'off' | 'on' | 'inherit'; private _renderSideBySide: boolean; private _maxComputationTime: number; private _renderIndicators: boolean; private _enableSplitViewResizing: boolean; - private _strategy!: IDiffEditorWidgetStyle; + private _strategy!: DiffEditorWidgetStyle; private readonly _updateDecorationsRunner: RunOnceScheduler; @@ -250,7 +246,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._themeService = themeService; this._notificationService = notificationService; - this.id = (++DIFF_EDITOR_ID); + this._id = (++DIFF_EDITOR_ID); this._state = editorBrowser.DiffEditorState.Idle; this._updatingDiffProgress = null; @@ -281,19 +277,14 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._renderIndicators = options.renderIndicators; } - this._originalIsEditable = false; - if (typeof options.originalEditable !== 'undefined') { - this._originalIsEditable = Boolean(options.originalEditable); - } + this._originalIsEditable = validateBooleanOption(options.originalEditable, false); + this._diffCodeLens = validateBooleanOption(options.diffCodeLens, false); + this._diffWordWrap = validateDiffWordWrap(options.diffWordWrap, 'inherit'); - this._originalCodeLens = false; - if (typeof options.originalCodeLens !== 'undefined') { - this._originalCodeLens = Boolean(options.originalCodeLens); - } - - this._modifiedCodeLens = false; - if (typeof options.modifiedCodeLens !== 'undefined') { - this._modifiedCodeLens = Boolean(options.modifiedCodeLens); + if (typeof options.isInEmbeddedEditor !== 'undefined') { + this._contextKeyService.createKey('isInEmbeddedDiffEditor', options.isInEmbeddedEditor); + } else { + this._contextKeyService.createKey('isInEmbeddedDiffEditor', false); } this._updateDecorationsRunner = this._register(new RunOnceScheduler(() => this._updateDecorations(), 0)); @@ -315,7 +306,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._overviewDomElement.appendChild(this._overviewViewportDomElement.domNode); this._register(dom.addStandardDisposableListener(this._overviewDomElement, 'mousedown', (e) => { - this.modifiedEditor.delegateVerticalScrollbarMouseDown(e); + this._modifiedEditor.delegateVerticalScrollbarMouseDown(e); })); this._containerDomElement.appendChild(this._overviewDomElement); @@ -362,8 +353,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE rightServices.set(IContextKeyService, rightContextKeyService); const rightScopedInstantiationService = instantiationService.createChild(rightServices); - this.originalEditor = this._createLeftHandSideEditor(options, leftScopedInstantiationService, leftContextKeyService); - this.modifiedEditor = this._createRightHandSideEditor(options, rightScopedInstantiationService, rightContextKeyService); + this._originalEditor = this._createLeftHandSideEditor(options, leftScopedInstantiationService, leftContextKeyService); + this._modifiedEditor = this._createRightHandSideEditor(options, rightScopedInstantiationService, rightContextKeyService); this._originalOverviewRuler = null; this._modifiedOverviewRuler = null; @@ -421,7 +412,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } public getContentHeight(): number { - return this.modifiedEditor.getContentHeight(); + return this._modifiedEditor.getContentHeight(); } private _setState(newState: editorBrowser.DiffEditorState): void { @@ -466,8 +457,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._overviewDomElement.removeChild(this._originalOverviewRuler.getDomNode()); this._originalOverviewRuler.dispose(); } - if (this.originalEditor.hasModel()) { - this._originalOverviewRuler = this.originalEditor.createOverviewRuler('original diffOverviewRuler')!; + if (this._originalEditor.hasModel()) { + this._originalOverviewRuler = this._originalEditor.createOverviewRuler('original diffOverviewRuler')!; this._overviewDomElement.appendChild(this._originalOverviewRuler.getDomNode()); } @@ -475,8 +466,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._overviewDomElement.removeChild(this._modifiedOverviewRuler.getDomNode()); this._modifiedOverviewRuler.dispose(); } - if (this.modifiedEditor.hasModel()) { - this._modifiedOverviewRuler = this.modifiedEditor.createOverviewRuler('modified diffOverviewRuler')!; + if (this._modifiedEditor.hasModel()) { + this._modifiedOverviewRuler = this._modifiedEditor.createOverviewRuler('modified diffOverviewRuler')!; this._overviewDomElement.appendChild(this._modifiedOverviewRuler.getDomNode()); } @@ -484,7 +475,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } private _createLeftHandSideEditor(options: editorBrowser.IDiffEditorConstructionOptions, instantiationService: IInstantiationService, contextKeyService: IContextKeyService): CodeEditorWidget { - const editor = this._createInnerEditor(instantiationService, this._originalDomNode, this._adjustOptionsForLeftHandSide(options, this._originalIsEditable, this._originalCodeLens)); + const editor = this._createInnerEditor(instantiationService, this._originalDomNode, this._adjustOptionsForLeftHandSide(options)); this._register(editor.onDidScrollChange((e) => { if (this._isHandlingScrollEvent) { @@ -494,56 +485,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE return; } this._isHandlingScrollEvent = true; - this.modifiedEditor.setScrollPosition({ - scrollLeft: e.scrollLeft, - scrollTop: e.scrollTop - }); - this._isHandlingScrollEvent = false; - - this._layoutOverviewViewport(); - })); - - this._register(editor.onDidChangeViewZones(() => { - this._onViewZonesChanged(); - })); - - this._register(editor.onDidChangeModelContent(() => { - if (this._isVisible) { - this._beginUpdateDecorationsSoon(); - } - })); - - const isInDiffLeftEditorKey = contextKeyService.createKey('isInDiffLeftEditor', undefined); - this._register(editor.onDidFocusEditorWidget(() => isInDiffLeftEditorKey.set(true))); - this._register(editor.onDidBlurEditorWidget(() => isInDiffLeftEditorKey.set(false))); - - this._register(editor.onDidContentSizeChange(e => { - const width = this.originalEditor.getContentWidth() + this.modifiedEditor.getContentWidth() + DiffEditorWidget.ONE_OVERVIEW_WIDTH; - const height = Math.max(this.modifiedEditor.getContentHeight(), this.originalEditor.getContentHeight()); - - this._onDidContentSizeChange.fire({ - contentHeight: height, - contentWidth: width, - contentHeightChanged: e.contentHeightChanged, - contentWidthChanged: e.contentWidthChanged - }); - })); - - return editor; - } - - private _createRightHandSideEditor(options: editorBrowser.IDiffEditorConstructionOptions, instantiationService: IInstantiationService, contextKeyService: IContextKeyService): CodeEditorWidget { - const editor = this._createInnerEditor(instantiationService, this._modifiedDomNode, this._adjustOptionsForRightHandSide(options, this._modifiedCodeLens)); - - this._register(editor.onDidScrollChange((e) => { - if (this._isHandlingScrollEvent) { - return; - } - if (!e.scrollTopChanged && !e.scrollLeftChanged && !e.scrollHeightChanged) { - return; - } - this._isHandlingScrollEvent = true; - this.originalEditor.setScrollPosition({ + this._modifiedEditor.setScrollPosition({ scrollLeft: e.scrollLeft, scrollTop: e.scrollTop }); @@ -557,8 +499,77 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE })); this._register(editor.onDidChangeConfiguration((e) => { - if (e.hasChanged(EditorOption.fontInfo) && editor.getModel()) { - this._onViewZonesChanged(); + if (!editor.getModel()) { + return; + } + if (e.hasChanged(EditorOption.fontInfo)) { + this._updateDecorationsRunner.schedule(); + } + if (e.hasChanged(EditorOption.wrappingInfo)) { + this._updateDecorationsRunner.cancel(); + this._updateDecorations(); + } + })); + + this._register(editor.onDidChangeModelContent(() => { + if (this._isVisible) { + this._beginUpdateDecorationsSoon(); + } + })); + + const isInDiffLeftEditorKey = contextKeyService.createKey('isInDiffLeftEditor', undefined); + this._register(editor.onDidFocusEditorWidget(() => isInDiffLeftEditorKey.set(true))); + this._register(editor.onDidBlurEditorWidget(() => isInDiffLeftEditorKey.set(false))); + + this._register(editor.onDidContentSizeChange(e => { + const width = this._originalEditor.getContentWidth() + this._modifiedEditor.getContentWidth() + DiffEditorWidget.ONE_OVERVIEW_WIDTH; + const height = Math.max(this._modifiedEditor.getContentHeight(), this._originalEditor.getContentHeight()); + + this._onDidContentSizeChange.fire({ + contentHeight: height, + contentWidth: width, + contentHeightChanged: e.contentHeightChanged, + contentWidthChanged: e.contentWidthChanged + }); + })); + + return editor; + } + + private _createRightHandSideEditor(options: editorBrowser.IDiffEditorConstructionOptions, instantiationService: IInstantiationService, contextKeyService: IContextKeyService): CodeEditorWidget { + const editor = this._createInnerEditor(instantiationService, this._modifiedDomNode, this._adjustOptionsForRightHandSide(options)); + + this._register(editor.onDidScrollChange((e) => { + if (this._isHandlingScrollEvent) { + return; + } + if (!e.scrollTopChanged && !e.scrollLeftChanged && !e.scrollHeightChanged) { + return; + } + this._isHandlingScrollEvent = true; + this._originalEditor.setScrollPosition({ + scrollLeft: e.scrollLeft, + scrollTop: e.scrollTop + }); + this._isHandlingScrollEvent = false; + + this._layoutOverviewViewport(); + })); + + this._register(editor.onDidChangeViewZones(() => { + this._onViewZonesChanged(); + })); + + this._register(editor.onDidChangeConfiguration((e) => { + if (!editor.getModel()) { + return; + } + if (e.hasChanged(EditorOption.fontInfo)) { + this._updateDecorationsRunner.schedule(); + } + if (e.hasChanged(EditorOption.wrappingInfo)) { + this._updateDecorationsRunner.cancel(); + this._updateDecorations(); } })); @@ -579,8 +590,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._register(editor.onDidBlurEditorWidget(() => isInDiffRightEditorKey.set(false))); this._register(editor.onDidContentSizeChange(e => { - const width = this.originalEditor.getContentWidth() + this.modifiedEditor.getContentWidth() + DiffEditorWidget.ONE_OVERVIEW_WIDTH; - const height = Math.max(this.modifiedEditor.getContentHeight(), this.originalEditor.getContentHeight()); + const width = this._originalEditor.getContentWidth() + this._modifiedEditor.getContentWidth() + DiffEditorWidget.ONE_OVERVIEW_WIDTH; + const height = Math.max(this._modifiedEditor.getContentHeight(), this._originalEditor.getContentHeight()); this._onDidContentSizeChange.fire({ contentHeight: height, @@ -619,10 +630,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._containerDomElement.removeChild(this._overviewDomElement); this._containerDomElement.removeChild(this._originalDomNode); - this.originalEditor.dispose(); + this._originalEditor.dispose(); this._containerDomElement.removeChild(this._modifiedDomNode); - this.modifiedEditor.dispose(); + this._modifiedEditor.dispose(); this._strategy.dispose(); @@ -641,7 +652,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE //------------ begin IDiffEditor methods public getId(): string { - return this.getEditorType() + ':' + this.id; + return this.getEditorType() + ':' + this._id; } public getEditorType(): string { @@ -660,11 +671,11 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } public getOriginalEditor(): editorBrowser.ICodeEditor { - return this.originalEditor; + return this._originalEditor; } public getModifiedEditor(): editorBrowser.ICodeEditor { - return this.modifiedEditor; + return this._modifiedEditor; } public updateOptions(newOptions: IDiffEditorOptions): void { @@ -706,18 +717,12 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._beginUpdateDecorations(); } - if (typeof newOptions.originalEditable !== 'undefined') { - this._originalIsEditable = Boolean(newOptions.originalEditable); - } - if (typeof newOptions.originalCodeLens !== 'undefined') { - this._originalCodeLens = Boolean(newOptions.originalCodeLens); - } - if (typeof newOptions.modifiedCodeLens !== 'undefined') { - this._modifiedCodeLens = Boolean(newOptions.modifiedCodeLens); - } + this._originalIsEditable = validateBooleanOption(newOptions.originalEditable, this._originalIsEditable); + this._diffCodeLens = validateBooleanOption(newOptions.diffCodeLens, this._diffCodeLens); + this._diffWordWrap = validateDiffWordWrap(newOptions.diffWordWrap, this._diffWordWrap); - this.modifiedEditor.updateOptions(this._adjustOptionsForRightHandSide(newOptions, this._modifiedCodeLens)); - this.originalEditor.updateOptions(this._adjustOptionsForLeftHandSide(newOptions, this._originalIsEditable, this._originalCodeLens)); + this._modifiedEditor.updateOptions(this._adjustOptionsForRightHandSide(newOptions)); + this._originalEditor.updateOptions(this._adjustOptionsForLeftHandSide(newOptions)); // enableSplitViewResizing if (typeof newOptions.enableSplitViewResizing !== 'undefined') { @@ -739,8 +744,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE public getModel(): editorCommon.IDiffEditorModel { return { - original: this.originalEditor.getModel()!, - modified: this.modifiedEditor.getModel()! + original: this._originalEditor.getModel()!, + modified: this._modifiedEditor.getModel()! }; } @@ -754,15 +759,15 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._cleanViewZonesAndDecorations(); // Update code editor models - this.originalEditor.setModel(model ? model.original : null); - this.modifiedEditor.setModel(model ? model.modified : null); + this._originalEditor.setModel(model ? model.original : null); + this._modifiedEditor.setModel(model ? model.modified : null); this._updateDecorationsRunner.cancel(); // this.originalEditor.onDidChangeModelOptions if (model) { - this.originalEditor.setScrollTop(0); - this.modifiedEditor.setScrollTop(0); + this._originalEditor.setScrollTop(0); + this._modifiedEditor.setScrollTop(0); } // Disable any diff computations that will come in @@ -785,59 +790,59 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } public getVisibleColumnFromPosition(position: IPosition): number { - return this.modifiedEditor.getVisibleColumnFromPosition(position); + return this._modifiedEditor.getVisibleColumnFromPosition(position); } public getStatusbarColumn(position: IPosition): number { - return this.modifiedEditor.getStatusbarColumn(position); + return this._modifiedEditor.getStatusbarColumn(position); } public getPosition(): Position | null { - return this.modifiedEditor.getPosition(); + return this._modifiedEditor.getPosition(); } public setPosition(position: IPosition): void { - this.modifiedEditor.setPosition(position); + this._modifiedEditor.setPosition(position); } public revealLine(lineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { - this.modifiedEditor.revealLine(lineNumber, scrollType); + this._modifiedEditor.revealLine(lineNumber, scrollType); } public revealLineInCenter(lineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { - this.modifiedEditor.revealLineInCenter(lineNumber, scrollType); + this._modifiedEditor.revealLineInCenter(lineNumber, scrollType); } public revealLineInCenterIfOutsideViewport(lineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { - this.modifiedEditor.revealLineInCenterIfOutsideViewport(lineNumber, scrollType); + this._modifiedEditor.revealLineInCenterIfOutsideViewport(lineNumber, scrollType); } public revealLineNearTop(lineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { - this.modifiedEditor.revealLineNearTop(lineNumber, scrollType); + this._modifiedEditor.revealLineNearTop(lineNumber, scrollType); } public revealPosition(position: IPosition, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { - this.modifiedEditor.revealPosition(position, scrollType); + this._modifiedEditor.revealPosition(position, scrollType); } public revealPositionInCenter(position: IPosition, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { - this.modifiedEditor.revealPositionInCenter(position, scrollType); + this._modifiedEditor.revealPositionInCenter(position, scrollType); } public revealPositionInCenterIfOutsideViewport(position: IPosition, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { - this.modifiedEditor.revealPositionInCenterIfOutsideViewport(position, scrollType); + this._modifiedEditor.revealPositionInCenterIfOutsideViewport(position, scrollType); } public revealPositionNearTop(position: IPosition, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { - this.modifiedEditor.revealPositionNearTop(position, scrollType); + this._modifiedEditor.revealPositionNearTop(position, scrollType); } public getSelection(): Selection | null { - return this.modifiedEditor.getSelection(); + return this._modifiedEditor.getSelection(); } public getSelections(): Selection[] | null { - return this.modifiedEditor.getSelections(); + return this._modifiedEditor.getSelections(); } public setSelection(range: IRange): void; @@ -845,60 +850,60 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE public setSelection(selection: ISelection): void; public setSelection(editorSelection: Selection): void; public setSelection(something: any): void { - this.modifiedEditor.setSelection(something); + this._modifiedEditor.setSelection(something); } public setSelections(ranges: readonly ISelection[]): void { - this.modifiedEditor.setSelections(ranges); + this._modifiedEditor.setSelections(ranges); } public revealLines(startLineNumber: number, endLineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { - this.modifiedEditor.revealLines(startLineNumber, endLineNumber, scrollType); + this._modifiedEditor.revealLines(startLineNumber, endLineNumber, scrollType); } public revealLinesInCenter(startLineNumber: number, endLineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { - this.modifiedEditor.revealLinesInCenter(startLineNumber, endLineNumber, scrollType); + this._modifiedEditor.revealLinesInCenter(startLineNumber, endLineNumber, scrollType); } public revealLinesInCenterIfOutsideViewport(startLineNumber: number, endLineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { - this.modifiedEditor.revealLinesInCenterIfOutsideViewport(startLineNumber, endLineNumber, scrollType); + this._modifiedEditor.revealLinesInCenterIfOutsideViewport(startLineNumber, endLineNumber, scrollType); } public revealLinesNearTop(startLineNumber: number, endLineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { - this.modifiedEditor.revealLinesNearTop(startLineNumber, endLineNumber, scrollType); + this._modifiedEditor.revealLinesNearTop(startLineNumber, endLineNumber, scrollType); } public revealRange(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth, revealVerticalInCenter: boolean = false, revealHorizontal: boolean = true): void { - this.modifiedEditor.revealRange(range, scrollType, revealVerticalInCenter, revealHorizontal); + this._modifiedEditor.revealRange(range, scrollType, revealVerticalInCenter, revealHorizontal); } public revealRangeInCenter(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { - this.modifiedEditor.revealRangeInCenter(range, scrollType); + this._modifiedEditor.revealRangeInCenter(range, scrollType); } public revealRangeInCenterIfOutsideViewport(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { - this.modifiedEditor.revealRangeInCenterIfOutsideViewport(range, scrollType); + this._modifiedEditor.revealRangeInCenterIfOutsideViewport(range, scrollType); } public revealRangeNearTop(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { - this.modifiedEditor.revealRangeNearTop(range, scrollType); + this._modifiedEditor.revealRangeNearTop(range, scrollType); } public revealRangeNearTopIfOutsideViewport(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { - this.modifiedEditor.revealRangeNearTopIfOutsideViewport(range, scrollType); + this._modifiedEditor.revealRangeNearTopIfOutsideViewport(range, scrollType); } public revealRangeAtTop(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { - this.modifiedEditor.revealRangeAtTop(range, scrollType); + this._modifiedEditor.revealRangeAtTop(range, scrollType); } public getSupportedActions(): editorCommon.IEditorAction[] { - return this.modifiedEditor.getSupportedActions(); + return this._modifiedEditor.getSupportedActions(); } public saveViewState(): editorCommon.IDiffEditorViewState { - let originalViewState = this.originalEditor.saveViewState(); - let modifiedViewState = this.modifiedEditor.saveViewState(); + const originalViewState = this._originalEditor.saveViewState(); + const modifiedViewState = this._modifiedEditor.saveViewState(); return { original: originalViewState, modified: modifiedViewState @@ -907,9 +912,9 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE public restoreViewState(s: editorCommon.IDiffEditorViewState): void { if (s.original && s.modified) { - let diffEditorState = s; - this.originalEditor.restoreViewState(diffEditorState.original); - this.modifiedEditor.restoreViewState(diffEditorState.modified); + const diffEditorState = s; + this._originalEditor.restoreViewState(diffEditorState.original); + this._modifiedEditor.restoreViewState(diffEditorState.modified); } } @@ -918,35 +923,35 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } public focus(): void { - this.modifiedEditor.focus(); + this._modifiedEditor.focus(); } public hasTextFocus(): boolean { - return this.originalEditor.hasTextFocus() || this.modifiedEditor.hasTextFocus(); + return this._originalEditor.hasTextFocus() || this._modifiedEditor.hasTextFocus(); } public onVisible(): void { this._isVisible = true; - this.originalEditor.onVisible(); - this.modifiedEditor.onVisible(); + this._originalEditor.onVisible(); + this._modifiedEditor.onVisible(); // Begin comparing this._beginUpdateDecorations(); } public onHide(): void { this._isVisible = false; - this.originalEditor.onHide(); - this.modifiedEditor.onHide(); + this._originalEditor.onHide(); + this._modifiedEditor.onHide(); // Remove all view zones & decorations this._cleanViewZonesAndDecorations(); } public trigger(source: string | null | undefined, handlerId: string, payload: any): void { - this.modifiedEditor.trigger(source, handlerId, payload); + this._modifiedEditor.trigger(source, handlerId, payload); } public changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => any): any { - return this.modifiedEditor.changeDecorations(callback); + return this._modifiedEditor.changeDecorations(callback); } //------------ end IDiffEditor methods @@ -970,8 +975,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE const height = this._elementSizeObserver.getHeight(); const reviewHeight = this._getReviewHeight(); - let freeSpace = DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH - 2 * DiffEditorWidget.ONE_OVERVIEW_WIDTH; - let layoutInfo = this.modifiedEditor.getLayoutInfo(); + const freeSpace = DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH - 2 * DiffEditorWidget.ONE_OVERVIEW_WIDTH; + const layoutInfo = this._modifiedEditor.getLayoutInfo(); if (layoutInfo) { this._originalOverviewRuler.setLayout({ top: 0, @@ -1021,8 +1026,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE private _beginUpdateDecorations(): void { this._beginUpdateDecorationsTimeout = -1; - const currentOriginalModel = this.originalEditor.getModel(); - const currentModifiedModel = this.modifiedEditor.getModel(); + const currentOriginalModel = this._originalEditor.getModel(); + const currentModifiedModel = this._modifiedEditor.getModel(); if (!currentOriginalModel || !currentModifiedModel) { return; } @@ -1031,7 +1036,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE // The best method would be to call cancel on the Promise, but this is not // yet supported, so using tokens for now. this._diffComputationToken++; - let currentToken = this._diffComputationToken; + const currentToken = this._diffComputationToken; this._setState(editorBrowser.DiffEditorState.ComputingDiff); if (!this._editorWorkerService.canComputeDiff(currentOriginalModel.uri, currentModifiedModel.uri)) { @@ -1048,8 +1053,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._editorWorkerService.computeDiff(currentOriginalModel.uri, currentModifiedModel.uri, this._ignoreTrimWhitespace, this._maxComputationTime).then((result) => { if (currentToken === this._diffComputationToken - && currentOriginalModel === this.originalEditor.getModel() - && currentModifiedModel === this.modifiedEditor.getModel() + && currentOriginalModel === this._originalEditor.getModel() + && currentModifiedModel === this._modifiedEditor.getModel() ) { this._setState(editorBrowser.DiffEditorState.DiffComputed); this._diffComputationResult = result; @@ -1058,8 +1063,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } }, (error) => { if (currentToken === this._diffComputationToken - && currentOriginalModel === this.originalEditor.getModel() - && currentModifiedModel === this.modifiedEditor.getModel() + && currentOriginalModel === this._originalEditor.getModel() + && currentModifiedModel === this._modifiedEditor.getModel() ) { this._setState(editorBrowser.DiffEditorState.DiffComputed); this._diffComputationResult = null; @@ -1069,40 +1074,38 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } private _cleanViewZonesAndDecorations(): void { - this._originalEditorState.clean(this.originalEditor); - this._modifiedEditorState.clean(this.modifiedEditor); + this._originalEditorState.clean(this._originalEditor); + this._modifiedEditorState.clean(this._modifiedEditor); } private _updateDecorations(): void { - if (!this.originalEditor.getModel() || !this.modifiedEditor.getModel() || !this._originalOverviewRuler || !this._modifiedOverviewRuler) { + if (!this._originalEditor.getModel() || !this._modifiedEditor.getModel() || !this._originalOverviewRuler || !this._modifiedOverviewRuler) { return; } const lineChanges = (this._diffComputationResult ? this._diffComputationResult.changes : []); - let foreignOriginal = this._originalEditorState.getForeignViewZones(this.originalEditor.getWhitespaces()); - let foreignModified = this._modifiedEditorState.getForeignViewZones(this.modifiedEditor.getWhitespaces()); + const foreignOriginal = this._originalEditorState.getForeignViewZones(this._originalEditor.getWhitespaces()); + const foreignModified = this._modifiedEditorState.getForeignViewZones(this._modifiedEditor.getWhitespaces()); - let diffDecorations = this._strategy.getEditorsDiffDecorations(lineChanges, this._ignoreTrimWhitespace, this._renderIndicators, foreignOriginal, foreignModified, this.originalEditor, this.modifiedEditor); + const diffDecorations = this._strategy.getEditorsDiffDecorations(lineChanges, this._ignoreTrimWhitespace, this._renderIndicators, foreignOriginal, foreignModified); try { this._currentlyChangingViewZones = true; - this._originalEditorState.apply(this.originalEditor, this._originalOverviewRuler, diffDecorations.original, false); - this._modifiedEditorState.apply(this.modifiedEditor, this._modifiedOverviewRuler, diffDecorations.modified, true); + this._originalEditorState.apply(this._originalEditor, this._originalOverviewRuler, diffDecorations.original, false); + this._modifiedEditorState.apply(this._modifiedEditor, this._modifiedOverviewRuler, diffDecorations.modified, true); } finally { this._currentlyChangingViewZones = false; } } private _adjustOptionsForSubEditor(options: editorBrowser.IDiffEditorConstructionOptions): editorBrowser.IDiffEditorConstructionOptions { - let clonedOptions: editorBrowser.IDiffEditorConstructionOptions = objects.deepClone(options || {}); + const clonedOptions: editorBrowser.IDiffEditorConstructionOptions = objects.deepClone(options || {}); clonedOptions.inDiffEditor = true; - clonedOptions.wordWrap = 'off'; - clonedOptions.wordWrapMinified = false; clonedOptions.automaticLayout = false; clonedOptions.scrollbar = clonedOptions.scrollbar || {}; clonedOptions.scrollbar.vertical = 'visible'; clonedOptions.folding = false; - clonedOptions.codeLens = false; + clonedOptions.codeLens = this._diffCodeLens; clonedOptions.fixedOverflowWidgets = true; clonedOptions.overflowWidgetsDomNode = options.overflowWidgetsDomNode; // clonedOptions.lineDecorationsWidth = '2ch'; @@ -1113,21 +1116,22 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE return clonedOptions; } - private _adjustOptionsForLeftHandSide(options: editorBrowser.IDiffEditorConstructionOptions, isEditable: boolean, isCodeLensEnabled: boolean): editorBrowser.IEditorConstructionOptions { - let result = this._adjustOptionsForSubEditor(options); - if (isCodeLensEnabled) { - result.codeLens = true; + private _adjustOptionsForLeftHandSide(options: editorBrowser.IDiffEditorConstructionOptions): editorBrowser.IEditorConstructionOptions { + const result = this._adjustOptionsForSubEditor(options); + if (!this._renderSideBySide) { + // never wrap hidden editor + result.wordWrapOverride1 = 'off'; + } else { + result.wordWrapOverride1 = this._diffWordWrap; } - result.readOnly = !isEditable; + result.readOnly = !this._originalIsEditable; result.extraEditorClassName = 'original-in-monaco-diff-editor'; return result; } - private _adjustOptionsForRightHandSide(options: editorBrowser.IDiffEditorConstructionOptions, isCodeLensEnabled: boolean): editorBrowser.IEditorConstructionOptions { - let result = this._adjustOptionsForSubEditor(options); - if (isCodeLensEnabled) { - result.codeLens = true; - } + private _adjustOptionsForRightHandSide(options: editorBrowser.IDiffEditorConstructionOptions): editorBrowser.IEditorConstructionOptions { + const result = this._adjustOptionsForSubEditor(options); + result.wordWrapOverride1 = this._diffWordWrap; result.revealHorizontalRightPadding = EditorOptions.revealHorizontalRightPadding.defaultValue + DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH; result.scrollbar!.verticalHasArrows = false; result.extraEditorClassName = 'modified-in-monaco-diff-editor'; @@ -1144,7 +1148,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE const height = this._elementSizeObserver.getHeight(); const reviewHeight = this._getReviewHeight(); - let splitPoint = this._strategy.layout(); + const splitPoint = this._strategy.layout(); this._originalDomNode.style.width = splitPoint + 'px'; this._originalDomNode.style.left = '0px'; @@ -1159,8 +1163,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._overviewViewportDomElement.setWidth(DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH); this._overviewViewportDomElement.setHeight(30); - this.originalEditor.layout({ width: splitPoint, height: (height - reviewHeight) }); - this.modifiedEditor.layout({ width: width - splitPoint - DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH, height: (height - reviewHeight) }); + this._originalEditor.layout({ width: splitPoint, height: (height - reviewHeight) }); + this._modifiedEditor.layout({ width: width - splitPoint - DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH, height: (height - reviewHeight) }); if (this._originalOverviewRuler || this._modifiedOverviewRuler) { this._layoutOverviewRulers(); @@ -1172,7 +1176,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } private _layoutOverviewViewport(): void { - let layout = this._computeOverviewViewport(); + const layout = this._computeOverviewViewport(); if (!layout) { this._overviewViewportDomElement.setTop(0); this._overviewViewportDomElement.setHeight(0); @@ -1183,20 +1187,20 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } private _computeOverviewViewport(): { height: number; top: number; } | null { - let layoutInfo = this.modifiedEditor.getLayoutInfo(); + const layoutInfo = this._modifiedEditor.getLayoutInfo(); if (!layoutInfo) { return null; } - let scrollTop = this.modifiedEditor.getScrollTop(); - let scrollHeight = this.modifiedEditor.getScrollHeight(); + const scrollTop = this._modifiedEditor.getScrollTop(); + const scrollHeight = this._modifiedEditor.getScrollHeight(); - let computedAvailableSize = Math.max(0, layoutInfo.height); - let computedRepresentableSize = Math.max(0, computedAvailableSize - 2 * 0); - let computedRatio = scrollHeight > 0 ? (computedRepresentableSize / scrollHeight) : 0; + const computedAvailableSize = Math.max(0, layoutInfo.height); + const computedRepresentableSize = Math.max(0, computedAvailableSize - 2 * 0); + const computedRatio = scrollHeight > 0 ? (computedRepresentableSize / scrollHeight) : 0; - let computedSliderSize = Math.max(0, Math.floor(layoutInfo.height * computedRatio)); - let computedSliderPosition = Math.floor(scrollTop * computedRatio); + const computedSliderSize = Math.max(0, Math.floor(layoutInfo.height * computedRatio)); + const computedSliderPosition = Math.floor(scrollTop * computedRatio); return { height: computedSliderSize, @@ -1223,16 +1227,16 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE }, getOriginalEditor: () => { - return this.originalEditor; + return this._originalEditor; }, getModifiedEditor: () => { - return this.modifiedEditor; + return this._modifiedEditor; } }; } - private _setStrategy(newStrategy: IDiffEditorWidgetStyle): void { + private _setStrategy(newStrategy: DiffEditorWidgetStyle): void { if (this._strategy) { this._strategy.dispose(); } @@ -1255,11 +1259,12 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE return null; } - let min = 0, max = lineChanges.length - 1; + let min = 0; + let max = lineChanges.length - 1; while (min < max) { - let mid = Math.floor((min + max) / 2); - let midStart = startLineNumberExtractor(lineChanges[mid]); - let midEnd = (mid + 1 <= max ? startLineNumberExtractor(lineChanges[mid + 1]) : Constants.MAX_SAFE_SMALL_INTEGER); + const mid = Math.floor((min + max) / 2); + const midStart = startLineNumberExtractor(lineChanges[mid]); + const midEnd = (mid + 1 <= max ? startLineNumberExtractor(lineChanges[mid + 1]) : Constants.MAX_SAFE_SMALL_INTEGER); if (lineNumber < midStart) { max = mid - 1; @@ -1275,19 +1280,19 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } private _getEquivalentLineForOriginalLineNumber(lineNumber: number): number { - let lineChange = this._getLineChangeAtOrBeforeLineNumber(lineNumber, (lineChange) => lineChange.originalStartLineNumber); + const lineChange = this._getLineChangeAtOrBeforeLineNumber(lineNumber, (lineChange) => lineChange.originalStartLineNumber); if (!lineChange) { return lineNumber; } - let originalEquivalentLineNumber = lineChange.originalStartLineNumber + (lineChange.originalEndLineNumber > 0 ? -1 : 0); - let modifiedEquivalentLineNumber = lineChange.modifiedStartLineNumber + (lineChange.modifiedEndLineNumber > 0 ? -1 : 0); - let lineChangeOriginalLength = (lineChange.originalEndLineNumber > 0 ? (lineChange.originalEndLineNumber - lineChange.originalStartLineNumber + 1) : 0); - let lineChangeModifiedLength = (lineChange.modifiedEndLineNumber > 0 ? (lineChange.modifiedEndLineNumber - lineChange.modifiedStartLineNumber + 1) : 0); + const originalEquivalentLineNumber = lineChange.originalStartLineNumber + (lineChange.originalEndLineNumber > 0 ? -1 : 0); + const modifiedEquivalentLineNumber = lineChange.modifiedStartLineNumber + (lineChange.modifiedEndLineNumber > 0 ? -1 : 0); + const lineChangeOriginalLength = (lineChange.originalEndLineNumber > 0 ? (lineChange.originalEndLineNumber - lineChange.originalStartLineNumber + 1) : 0); + const lineChangeModifiedLength = (lineChange.modifiedEndLineNumber > 0 ? (lineChange.modifiedEndLineNumber - lineChange.modifiedStartLineNumber + 1) : 0); - let delta = lineNumber - originalEquivalentLineNumber; + const delta = lineNumber - originalEquivalentLineNumber; if (delta <= lineChangeOriginalLength) { return modifiedEquivalentLineNumber + Math.min(delta, lineChangeModifiedLength); @@ -1297,19 +1302,19 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } private _getEquivalentLineForModifiedLineNumber(lineNumber: number): number { - let lineChange = this._getLineChangeAtOrBeforeLineNumber(lineNumber, (lineChange) => lineChange.modifiedStartLineNumber); + const lineChange = this._getLineChangeAtOrBeforeLineNumber(lineNumber, (lineChange) => lineChange.modifiedStartLineNumber); if (!lineChange) { return lineNumber; } - let originalEquivalentLineNumber = lineChange.originalStartLineNumber + (lineChange.originalEndLineNumber > 0 ? -1 : 0); - let modifiedEquivalentLineNumber = lineChange.modifiedStartLineNumber + (lineChange.modifiedEndLineNumber > 0 ? -1 : 0); - let lineChangeOriginalLength = (lineChange.originalEndLineNumber > 0 ? (lineChange.originalEndLineNumber - lineChange.originalStartLineNumber + 1) : 0); - let lineChangeModifiedLength = (lineChange.modifiedEndLineNumber > 0 ? (lineChange.modifiedEndLineNumber - lineChange.modifiedStartLineNumber + 1) : 0); + const originalEquivalentLineNumber = lineChange.originalStartLineNumber + (lineChange.originalEndLineNumber > 0 ? -1 : 0); + const modifiedEquivalentLineNumber = lineChange.modifiedStartLineNumber + (lineChange.modifiedEndLineNumber > 0 ? -1 : 0); + const lineChangeOriginalLength = (lineChange.originalEndLineNumber > 0 ? (lineChange.originalEndLineNumber - lineChange.originalStartLineNumber + 1) : 0); + const lineChangeModifiedLength = (lineChange.modifiedEndLineNumber > 0 ? (lineChange.modifiedEndLineNumber - lineChange.modifiedStartLineNumber + 1) : 0); - let delta = lineNumber - modifiedEquivalentLineNumber; + const delta = lineNumber - modifiedEquivalentLineNumber; if (delta <= lineChangeModifiedLength) { return originalEquivalentLineNumber + Math.min(delta, lineChangeOriginalLength); @@ -1345,15 +1350,15 @@ interface IDataSource { getContainerDomNode(): HTMLElement; relayoutEditors(): void; - getOriginalEditor(): editorBrowser.ICodeEditor; - getModifiedEditor(): editorBrowser.ICodeEditor; + getOriginalEditor(): CodeEditorWidget; + getModifiedEditor(): CodeEditorWidget; } -abstract class DiffEditorWidgetStyle extends Disposable implements IDiffEditorWidgetStyle { +abstract class DiffEditorWidgetStyle extends Disposable { - _dataSource: IDataSource; - _insertColor: Color | null; - _removeColor: Color | null; + protected _dataSource: IDataSource; + protected _insertColor: Color | null; + protected _removeColor: Color | null; constructor(dataSource: IDataSource) { super(); @@ -1363,15 +1368,15 @@ abstract class DiffEditorWidgetStyle extends Disposable implements IDiffEditorWi } public applyColors(theme: IColorTheme): boolean { - let newInsertColor = (theme.getColor(diffInserted) || defaultInsertColor).transparent(2); - let newRemoveColor = (theme.getColor(diffRemoved) || defaultRemoveColor).transparent(2); - let hasChanges = !newInsertColor.equals(this._insertColor) || !newRemoveColor.equals(this._removeColor); + const newInsertColor = (theme.getColor(diffInserted) || defaultInsertColor).transparent(2); + const newRemoveColor = (theme.getColor(diffRemoved) || defaultRemoveColor).transparent(2); + const hasChanges = !newInsertColor.equals(this._insertColor) || !newRemoveColor.equals(this._removeColor); this._insertColor = newInsertColor; this._removeColor = newRemoveColor; return hasChanges; } - public getEditorsDiffDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalWhitespaces: IEditorWhitespace[], modifiedWhitespaces: IEditorWhitespace[], originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorsDiffDecorationsWithZones { + public getEditorsDiffDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalWhitespaces: IEditorWhitespace[], modifiedWhitespaces: IEditorWhitespace[]): IEditorsDiffDecorationsWithZones { // Get view zones modifiedWhitespaces = modifiedWhitespaces.sort((a, b) => { return a.afterLineNumber - b.afterLineNumber; @@ -1379,11 +1384,11 @@ abstract class DiffEditorWidgetStyle extends Disposable implements IDiffEditorWi originalWhitespaces = originalWhitespaces.sort((a, b) => { return a.afterLineNumber - b.afterLineNumber; }); - let zones = this._getViewZones(lineChanges, originalWhitespaces, modifiedWhitespaces, originalEditor, modifiedEditor, renderIndicators); + const zones = this._getViewZones(lineChanges, originalWhitespaces, modifiedWhitespaces, renderIndicators); // Get decorations & overview ruler zones - let originalDecorations = this._getOriginalEditorDecorations(lineChanges, ignoreTrimWhitespace, renderIndicators, originalEditor, modifiedEditor); - let modifiedDecorations = this._getModifiedEditorDecorations(lineChanges, ignoreTrimWhitespace, renderIndicators, originalEditor, modifiedEditor); + const originalDecorations = this._getOriginalEditorDecorations(lineChanges, ignoreTrimWhitespace, renderIndicators); + const modifiedDecorations = this._getModifiedEditorDecorations(lineChanges, ignoreTrimWhitespace, renderIndicators); return { original: { @@ -1399,9 +1404,9 @@ abstract class DiffEditorWidgetStyle extends Disposable implements IDiffEditorWi }; } - protected abstract _getViewZones(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[], originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor, renderIndicators: boolean): IEditorsZones; - protected abstract _getOriginalEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorDiffDecorations; - protected abstract _getModifiedEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorDiffDecorations; + protected abstract _getViewZones(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[], renderIndicators: boolean): IEditorsZones; + protected abstract _getOriginalEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean): IEditorDiffDecorations; + protected abstract _getModifiedEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean): IEditorDiffDecorations; public abstract setEnableSplitViewResizing(enableSplitViewResizing: boolean): void; public abstract layout(): number; @@ -1410,6 +1415,7 @@ abstract class DiffEditorWidgetStyle extends Disposable implements IDiffEditorWi interface IMyViewZone { shouldNotShrink?: boolean; afterLineNumber: number; + afterColumn?: number; heightInLines: number; minWidthInPx?: number; domNode: HTMLElement | null; @@ -1442,22 +1448,37 @@ class ForeignViewZonesIterator { abstract class ViewZonesComputer { - private readonly lineChanges: editorCommon.ILineChange[]; - private readonly originalForeignVZ: IEditorWhitespace[]; - private readonly originalLineHeight: number; - private readonly modifiedForeignVZ: IEditorWhitespace[]; - private readonly modifiedLineHeight: number; + constructor( + private readonly _lineChanges: editorCommon.ILineChange[], + private readonly _originalForeignVZ: IEditorWhitespace[], + private readonly _modifiedForeignVZ: IEditorWhitespace[], + protected readonly _originalEditor: CodeEditorWidget, + protected readonly _modifiedEditor: CodeEditorWidget + ) { + } - constructor(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], originalLineHeight: number, modifiedForeignVZ: IEditorWhitespace[], modifiedLineHeight: number) { - this.lineChanges = lineChanges; - this.originalForeignVZ = originalForeignVZ; - this.originalLineHeight = originalLineHeight; - this.modifiedForeignVZ = modifiedForeignVZ; - this.modifiedLineHeight = modifiedLineHeight; + private static _getViewLineCount(editor: CodeEditorWidget, startLineNumber: number, endLineNumber: number): number { + const model = editor.getModel(); + const viewModel = editor._getViewModel(); + if (model && viewModel) { + const viewRange = getViewRange(model, viewModel, startLineNumber, endLineNumber); + return (viewRange.endLineNumber - viewRange.startLineNumber + 1); + } + + return (endLineNumber - startLineNumber + 1); } public getViewZones(): IEditorsZones { - let result: { original: IMyViewZone[]; modified: IMyViewZone[]; } = { + const originalLineHeight = this._originalEditor.getOption(EditorOption.lineHeight); + const modifiedLineHeight = this._modifiedEditor.getOption(EditorOption.lineHeight); + const originalHasWrapping = (this._originalEditor.getOption(EditorOption.wrappingInfo).wrappingColumn !== -1); + const modifiedHasWrapping = (this._modifiedEditor.getOption(EditorOption.wrappingInfo).wrappingColumn !== -1); + const hasWrapping = (originalHasWrapping || modifiedHasWrapping); + const originalModel = this._originalEditor.getModel()!; + const originalCoordinatesConverter = this._originalEditor._getViewModel()!.coordinatesConverter; + const modifiedCoordinatesConverter = this._modifiedEditor._getViewModel()!.coordinatesConverter; + + const result: { original: IMyViewZone[]; modified: IMyViewZone[]; } = { original: [], modified: [] }; @@ -1469,13 +1490,13 @@ abstract class ViewZonesComputer { let originalEndEquivalentLineNumber: number = 0; let modifiedEndEquivalentLineNumber: number = 0; - let sortMyViewZones = (a: IMyViewZone, b: IMyViewZone) => { + const sortMyViewZones = (a: IMyViewZone, b: IMyViewZone) => { return a.afterLineNumber - b.afterLineNumber; }; - let addAndCombineIfPossible = (destination: IMyViewZone[], item: IMyViewZone) => { + const addAndCombineIfPossible = (destination: IMyViewZone[], item: IMyViewZone) => { if (item.domNode === null && destination.length > 0) { - let lastItem = destination[destination.length - 1]; + const lastItem = destination[destination.length - 1]; if (lastItem.afterLineNumber === item.afterLineNumber && lastItem.domNode === null) { lastItem.heightInLines += item.heightInLines; return; @@ -1484,18 +1505,21 @@ abstract class ViewZonesComputer { destination.push(item); }; - let modifiedForeignVZ = new ForeignViewZonesIterator(this.modifiedForeignVZ); - let originalForeignVZ = new ForeignViewZonesIterator(this.originalForeignVZ); + const modifiedForeignVZ = new ForeignViewZonesIterator(this._modifiedForeignVZ); + const originalForeignVZ = new ForeignViewZonesIterator(this._originalForeignVZ); + + let lastOriginalLineNumber = 1; + let lastModifiedLineNumber = 1; // In order to include foreign view zones after the last line change, the for loop will iterate once more after the end of the `lineChanges` array - for (let i = 0, length = this.lineChanges.length; i <= length; i++) { - let lineChange = (i < length ? this.lineChanges[i] : null); + for (let i = 0, length = this._lineChanges.length; i <= length; i++) { + const lineChange = (i < length ? this._lineChanges[i] : null); if (lineChange !== null) { originalEquivalentLineNumber = lineChange.originalStartLineNumber + (lineChange.originalEndLineNumber > 0 ? -1 : 0); modifiedEquivalentLineNumber = lineChange.modifiedStartLineNumber + (lineChange.modifiedEndLineNumber > 0 ? -1 : 0); - lineChangeOriginalLength = (lineChange.originalEndLineNumber > 0 ? (lineChange.originalEndLineNumber - lineChange.originalStartLineNumber + 1) : 0); - lineChangeModifiedLength = (lineChange.modifiedEndLineNumber > 0 ? (lineChange.modifiedEndLineNumber - lineChange.modifiedStartLineNumber + 1) : 0); + lineChangeOriginalLength = (lineChange.originalEndLineNumber > 0 ? ViewZonesComputer._getViewLineCount(this._originalEditor, lineChange.originalStartLineNumber, lineChange.originalEndLineNumber) : 0); + lineChangeModifiedLength = (lineChange.modifiedEndLineNumber > 0 ? ViewZonesComputer._getViewLineCount(this._modifiedEditor, lineChange.modifiedStartLineNumber, lineChange.modifiedEndLineNumber) : 0); originalEndEquivalentLineNumber = Math.max(lineChange.originalStartLineNumber, lineChange.originalEndLineNumber); modifiedEndEquivalentLineNumber = Math.max(lineChange.modifiedStartLineNumber, lineChange.modifiedEndLineNumber); } else { @@ -1512,6 +1536,48 @@ abstract class ViewZonesComputer { // ---------------------------- PRODUCE VIEW ZONES + // [PRODUCE] View zones due to line mapping differences (equal lines but wrapped differently) + if (hasWrapping) { + let count: number; + if (lineChange) { + if (lineChange.originalEndLineNumber > 0) { + count = lineChange.originalStartLineNumber - lastOriginalLineNumber; + } else { + count = lineChange.modifiedStartLineNumber - lastModifiedLineNumber; + } + } else { + count = originalModel.getLineCount() - lastOriginalLineNumber; + } + + for (let i = 0; i < count; i++) { + const originalLineNumber = lastOriginalLineNumber + i; + const modifiedLineNumber = lastModifiedLineNumber + i; + + const originalViewLineCount = originalCoordinatesConverter.getModelLineViewLineCount(originalLineNumber); + const modifiedViewLineCount = modifiedCoordinatesConverter.getModelLineViewLineCount(modifiedLineNumber); + + if (originalViewLineCount < modifiedViewLineCount) { + stepOriginal.push({ + afterLineNumber: originalLineNumber, + heightInLines: modifiedViewLineCount - originalViewLineCount, + domNode: null, + marginDomNode: null + }); + } else if (originalViewLineCount > modifiedViewLineCount) { + stepModified.push({ + afterLineNumber: modifiedLineNumber, + heightInLines: originalViewLineCount - modifiedViewLineCount, + domNode: null, + marginDomNode: null + }); + } + } + if (lineChange) { + lastOriginalLineNumber = (lineChange.originalEndLineNumber > 0 ? lineChange.originalEndLineNumber : lineChange.originalStartLineNumber) + 1; + lastModifiedLineNumber = (lineChange.modifiedEndLineNumber > 0 ? lineChange.modifiedEndLineNumber : lineChange.modifiedStartLineNumber) + 1; + } + } + // [PRODUCE] View zone(s) in original-side due to foreign view zone(s) in modified-side while (modifiedForeignVZ.current && modifiedForeignVZ.current.afterLineNumber <= modifiedEndEquivalentLineNumber) { let viewZoneLineNumber: number; @@ -1528,7 +1594,7 @@ abstract class ViewZonesComputer { stepOriginal.push({ afterLineNumber: viewZoneLineNumber, - heightInLines: modifiedForeignVZ.current.height / this.modifiedLineHeight, + heightInLines: modifiedForeignVZ.current.height / modifiedLineHeight, domNode: null, marginDomNode: marginDomNode }); @@ -1545,21 +1611,21 @@ abstract class ViewZonesComputer { } stepModified.push({ afterLineNumber: viewZoneLineNumber, - heightInLines: originalForeignVZ.current.height / this.originalLineHeight, + heightInLines: originalForeignVZ.current.height / originalLineHeight, domNode: null }); originalForeignVZ.advance(); } if (lineChange !== null && isChangeOrInsert(lineChange)) { - let r = this._produceOriginalFromDiff(lineChange, lineChangeOriginalLength, lineChangeModifiedLength); + const r = this._produceOriginalFromDiff(lineChange, lineChangeOriginalLength, lineChangeModifiedLength); if (r) { stepOriginal.push(r); } } if (lineChange !== null && isChangeOrDelete(lineChange)) { - let r = this._produceModifiedFromDiff(lineChange, lineChangeOriginalLength, lineChangeModifiedLength); + const r = this._produceModifiedFromDiff(lineChange, lineChangeOriginalLength, lineChangeModifiedLength); if (r) { stepModified.push(r); } @@ -1578,11 +1644,11 @@ abstract class ViewZonesComputer { stepModified = stepModified.sort(sortMyViewZones); while (stepOriginalIndex < stepOriginal.length && stepModifiedIndex < stepModified.length) { - let original = stepOriginal[stepOriginalIndex]; - let modified = stepModified[stepModifiedIndex]; + const original = stepOriginal[stepOriginalIndex]; + const modified = stepModified[stepModifiedIndex]; - let originalDelta = original.afterLineNumber - originalEquivalentLineNumber; - let modifiedDelta = modified.afterLineNumber - modifiedEquivalentLineNumber; + const originalDelta = original.afterLineNumber - originalEquivalentLineNumber; + const modifiedDelta = modified.afterLineNumber - modifiedEquivalentLineNumber; if (originalDelta < modifiedDelta) { addAndCombineIfPossible(result.original, original); @@ -1646,14 +1712,14 @@ abstract class ViewZonesComputer { protected abstract _produceModifiedFromDiff(lineChange: editorCommon.ILineChange, lineChangeOriginalLength: number, lineChangeModifiedLength: number): IMyViewZone | null; } -export function createDecoration(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, options: ModelDecorationOptions) { +function createDecoration(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, options: ModelDecorationOptions) { return { range: new Range(startLineNumber, startColumn, endLineNumber, endColumn), options: options }; } -export const DECORATIONS = { +const DECORATIONS = { charDelete: ModelDecorationOptions.register({ className: 'char-delete' @@ -1678,7 +1744,7 @@ export const DECORATIONS = { }), lineInsertWithSign: ModelDecorationOptions.register({ className: 'line-insert', - linesDecorationsClassName: 'insert-sign ' + diffInsertIcon.classNames, + linesDecorationsClassName: 'insert-sign ' + ThemeIcon.asClassName(diffInsertIcon), marginClassName: 'line-insert', isWholeLine: true }), @@ -1690,7 +1756,7 @@ export const DECORATIONS = { }), lineDeleteWithSign: ModelDecorationOptions.register({ className: 'line-delete', - linesDecorationsClassName: 'delete-sign ' + diffRemoveIcon.classNames, + linesDecorationsClassName: 'delete-sign ' + ThemeIcon.asClassName(diffRemoveIcon), marginClassName: 'line-delete', isWholeLine: true @@ -1701,7 +1767,7 @@ export const DECORATIONS = { }; -export class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffEditorWidgetStyle, IVerticalSashLayoutProvider { +class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IVerticalSashLayoutProvider { static readonly MINIMUM_EDITOR_WIDTH = 100; @@ -1724,14 +1790,14 @@ export class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements this._sash.state = SashState.Disabled; } - this._sash.onDidStart(() => this.onSashDragStart()); - this._sash.onDidChange((e: ISashEvent) => this.onSashDrag(e)); - this._sash.onDidEnd(() => this.onSashDragEnd()); - this._sash.onDidReset(() => this.onSashReset()); + this._sash.onDidStart(() => this._onSashDragStart()); + this._sash.onDidChange((e: ISashEvent) => this._onSashDrag(e)); + this._sash.onDidEnd(() => this._onSashDragEnd()); + this._sash.onDidReset(() => this._onSashReset()); } public setEnableSplitViewResizing(enableSplitViewResizing: boolean): void { - let newDisableSash = (enableSplitViewResizing === false); + const newDisableSash = (enableSplitViewResizing === false); if (this._disableSash !== newDisableSash) { this._disableSash = newDisableSash; this._sash.state = this._disableSash ? SashState.Disabled : SashState.Enabled; @@ -1739,11 +1805,11 @@ export class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements } public layout(sashRatio: number | null = this._sashRatio): number { - let w = this._dataSource.getWidth(); - let contentWidth = w - DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH; + const w = this._dataSource.getWidth(); + const contentWidth = w - DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH; let sashPosition = Math.floor((sashRatio || 0.5) * contentWidth); - let midPoint = Math.floor(0.5 * contentWidth); + const midPoint = Math.floor(0.5 * contentWidth); sashPosition = this._disableSash ? midPoint : sashPosition || midPoint; @@ -1767,25 +1833,25 @@ export class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements return this._sashPosition; } - private onSashDragStart(): void { + private _onSashDragStart(): void { this._startSashPosition = this._sashPosition!; } - private onSashDrag(e: ISashEvent): void { - let w = this._dataSource.getWidth(); - let contentWidth = w - DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH; - let sashPosition = this.layout((this._startSashPosition! + (e.currentX - e.startX)) / contentWidth); + private _onSashDrag(e: ISashEvent): void { + const w = this._dataSource.getWidth(); + const contentWidth = w - DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH; + const sashPosition = this.layout((this._startSashPosition! + (e.currentX - e.startX)) / contentWidth); this._sashRatio = sashPosition / contentWidth; this._dataSource.relayoutEditors(); } - private onSashDragEnd(): void { + private _onSashDragEnd(): void { this._sash.layout(); } - private onSashReset(): void { + private _onSashReset(): void { this._sashRatio = 0.5; this._dataSource.relayoutEditors(); this._sash.layout(); @@ -1803,23 +1869,26 @@ export class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements return this._dataSource.getHeight(); } - protected _getViewZones(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[], originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorsZones { - let c = new SideBySideViewZonesComputer(lineChanges, originalForeignVZ, originalEditor.getOption(EditorOption.lineHeight), modifiedForeignVZ, modifiedEditor.getOption(EditorOption.lineHeight)); + protected _getViewZones(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[]): IEditorsZones { + const originalEditor = this._dataSource.getOriginalEditor(); + const modifiedEditor = this._dataSource.getModifiedEditor(); + const c = new SideBySideViewZonesComputer(lineChanges, originalForeignVZ, modifiedForeignVZ, originalEditor, modifiedEditor); return c.getViewZones(); } - protected _getOriginalEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorDiffDecorations { + protected _getOriginalEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean): IEditorDiffDecorations { + const originalEditor = this._dataSource.getOriginalEditor(); const overviewZoneColor = String(this._removeColor); - let result: IEditorDiffDecorations = { + const result: IEditorDiffDecorations = { decorations: [], overviewZones: [] }; - let originalModel = originalEditor.getModel()!; + const originalModel = originalEditor.getModel()!; + const originalViewModel = originalEditor._getViewModel()!; - for (let i = 0, length = lineChanges.length; i < length; i++) { - let lineChange = lineChanges[i]; + for (const lineChange of lineChanges) { if (isChangeOrDelete(lineChange)) { result.decorations.push({ @@ -1830,15 +1899,11 @@ export class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements result.decorations.push(createDecoration(lineChange.originalStartLineNumber, 1, lineChange.originalEndLineNumber, Constants.MAX_SAFE_SMALL_INTEGER, DECORATIONS.charDeleteWholeLine)); } - result.overviewZones.push(new OverviewRulerZone( - lineChange.originalStartLineNumber, - lineChange.originalEndLineNumber, - overviewZoneColor - )); + const viewRange = getViewRange(originalModel, originalViewModel, lineChange.originalStartLineNumber, lineChange.originalEndLineNumber); + result.overviewZones.push(new OverviewRulerZone(viewRange.startLineNumber, viewRange.endLineNumber, overviewZoneColor)); if (lineChange.charChanges) { - for (let j = 0, lengthJ = lineChange.charChanges.length; j < lengthJ; j++) { - let charChange = lineChange.charChanges[j]; + for (const charChange of lineChange.charChanges) { if (isChangeOrDelete(charChange)) { if (ignoreTrimWhitespace) { for (let lineNumber = charChange.originalStartLineNumber; lineNumber <= charChange.originalEndLineNumber; lineNumber++) { @@ -1868,18 +1933,19 @@ export class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements return result; } - protected _getModifiedEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorDiffDecorations { + protected _getModifiedEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean): IEditorDiffDecorations { + const modifiedEditor = this._dataSource.getModifiedEditor(); const overviewZoneColor = String(this._insertColor); - let result: IEditorDiffDecorations = { + const result: IEditorDiffDecorations = { decorations: [], overviewZones: [] }; - let modifiedModel = modifiedEditor.getModel()!; + const modifiedModel = modifiedEditor.getModel()!; + const modifiedViewModel = modifiedEditor._getViewModel()!; - for (let i = 0, length = lineChanges.length; i < length; i++) { - let lineChange = lineChanges[i]; + for (const lineChange of lineChanges) { if (isChangeOrInsert(lineChange)) { @@ -1890,15 +1956,12 @@ export class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements if (!isChangeOrDelete(lineChange) || !lineChange.charChanges) { result.decorations.push(createDecoration(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedEndLineNumber, Constants.MAX_SAFE_SMALL_INTEGER, DECORATIONS.charInsertWholeLine)); } - result.overviewZones.push(new OverviewRulerZone( - lineChange.modifiedStartLineNumber, - lineChange.modifiedEndLineNumber, - overviewZoneColor - )); + + const viewRange = getViewRange(modifiedModel, modifiedViewModel, lineChange.modifiedStartLineNumber, lineChange.modifiedEndLineNumber); + result.overviewZones.push(new OverviewRulerZone(viewRange.startLineNumber, viewRange.endLineNumber, overviewZoneColor)); if (lineChange.charChanges) { - for (let j = 0, lengthJ = lineChange.charChanges.length; j < lengthJ; j++) { - let charChange = lineChange.charChanges[j]; + for (const charChange of lineChange.charChanges) { if (isChangeOrInsert(charChange)) { if (ignoreTrimWhitespace) { for (let lineNumber = charChange.modifiedStartLineNumber; lineNumber <= charChange.modifiedEndLineNumber; lineNumber++) { @@ -1931,8 +1994,14 @@ export class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements class SideBySideViewZonesComputer extends ViewZonesComputer { - constructor(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], originalLineHeight: number, modifiedForeignVZ: IEditorWhitespace[], modifiedLineHeight: number) { - super(lineChanges, originalForeignVZ, originalLineHeight, modifiedForeignVZ, modifiedLineHeight); + constructor( + lineChanges: editorCommon.ILineChange[], + originalForeignVZ: IEditorWhitespace[], + modifiedForeignVZ: IEditorWhitespace[], + originalEditor: CodeEditorWidget, + modifiedEditor: CodeEditorWidget, + ) { + super(lineChanges, originalForeignVZ, modifiedForeignVZ, originalEditor, modifiedEditor); } protected _createOriginalMarginDomNodeForModifiedForeignViewZoneInAddedRegion(): HTMLDivElement | null { @@ -1962,18 +2031,18 @@ class SideBySideViewZonesComputer extends ViewZonesComputer { } } -class DiffEditorWidgetInline extends DiffEditorWidgetStyle implements IDiffEditorWidgetStyle { +class DiffEditorWidgetInline extends DiffEditorWidgetStyle { - private decorationsLeft: number; + private _decorationsLeft: number; constructor(dataSource: IDataSource, enableSplitViewResizing: boolean) { super(dataSource); - this.decorationsLeft = dataSource.getOriginalEditor().getLayoutInfo().decorationsLeft; + this._decorationsLeft = dataSource.getOriginalEditor().getLayoutInfo().decorationsLeft; this._register(dataSource.getOriginalEditor().onDidLayoutChange((layoutInfo: EditorLayoutInfo) => { - if (this.decorationsLeft !== layoutInfo.decorationsLeft) { - this.decorationsLeft = layoutInfo.decorationsLeft; + if (this._decorationsLeft !== layoutInfo.decorationsLeft) { + this._decorationsLeft = layoutInfo.decorationsLeft; dataSource.relayoutEditors(); } })); @@ -1983,21 +2052,26 @@ class DiffEditorWidgetInline extends DiffEditorWidgetStyle implements IDiffEdito // Nothing to do.. } - protected _getViewZones(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[], originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor, renderIndicators: boolean): IEditorsZones { - let computer = new InlineViewZonesComputer(lineChanges, originalForeignVZ, modifiedForeignVZ, originalEditor, modifiedEditor, renderIndicators); + protected _getViewZones(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[], renderIndicators: boolean): IEditorsZones { + const originalEditor = this._dataSource.getOriginalEditor(); + const modifiedEditor = this._dataSource.getModifiedEditor(); + const computer = new InlineViewZonesComputer(lineChanges, originalForeignVZ, modifiedForeignVZ, originalEditor, modifiedEditor, renderIndicators); return computer.getViewZones(); } - protected _getOriginalEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorDiffDecorations { + protected _getOriginalEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean): IEditorDiffDecorations { const overviewZoneColor = String(this._removeColor); - let result: IEditorDiffDecorations = { + const result: IEditorDiffDecorations = { decorations: [], overviewZones: [] }; - for (let i = 0, length = lineChanges.length; i < length; i++) { - let lineChange = lineChanges[i]; + const originalEditor = this._dataSource.getOriginalEditor(); + const originalModel = originalEditor.getModel()!; + const originalViewModel = originalEditor._getViewModel()!; + + for (const lineChange of lineChanges) { // Add overview zones in the overview ruler if (isChangeOrDelete(lineChange)) { @@ -2006,29 +2080,27 @@ class DiffEditorWidgetInline extends DiffEditorWidgetStyle implements IDiffEdito options: DECORATIONS.lineDeleteMargin }); - result.overviewZones.push(new OverviewRulerZone( - lineChange.originalStartLineNumber, - lineChange.originalEndLineNumber, - overviewZoneColor - )); + const viewRange = getViewRange(originalModel, originalViewModel, lineChange.originalStartLineNumber, lineChange.originalEndLineNumber); + result.overviewZones.push(new OverviewRulerZone(viewRange.startLineNumber, viewRange.endLineNumber, overviewZoneColor)); } } return result; } - protected _getModifiedEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorDiffDecorations { + protected _getModifiedEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean): IEditorDiffDecorations { + const modifiedEditor = this._dataSource.getModifiedEditor(); const overviewZoneColor = String(this._insertColor); - let result: IEditorDiffDecorations = { + const result: IEditorDiffDecorations = { decorations: [], overviewZones: [] }; - let modifiedModel = modifiedEditor.getModel()!; + const modifiedModel = modifiedEditor.getModel()!; + const modifiedViewModel = modifiedEditor._getViewModel()!; - for (let i = 0, length = lineChanges.length; i < length; i++) { - let lineChange = lineChanges[i]; + for (const lineChange of lineChanges) { // Add decorations & overview zones if (isChangeOrInsert(lineChange)) { @@ -2037,15 +2109,11 @@ class DiffEditorWidgetInline extends DiffEditorWidgetStyle implements IDiffEdito options: (renderIndicators ? DECORATIONS.lineInsertWithSign : DECORATIONS.lineInsert) }); - result.overviewZones.push(new OverviewRulerZone( - lineChange.modifiedStartLineNumber, - lineChange.modifiedEndLineNumber, - overviewZoneColor - )); + const viewRange = getViewRange(modifiedModel, modifiedViewModel, lineChange.modifiedStartLineNumber, lineChange.modifiedEndLineNumber); + result.overviewZones.push(new OverviewRulerZone(viewRange.startLineNumber, viewRange.endLineNumber, overviewZoneColor)); if (lineChange.charChanges) { - for (let j = 0, lengthJ = lineChange.charChanges.length; j < lengthJ; j++) { - let charChange = lineChange.charChanges[j]; + for (const charChange of lineChange.charChanges) { if (isChangeOrInsert(charChange)) { if (ignoreTrimWhitespace) { for (let lineNumber = charChange.modifiedStartLineNumber; lineNumber <= charChange.modifiedEndLineNumber; lineNumber++) { @@ -2079,34 +2147,59 @@ class DiffEditorWidgetInline extends DiffEditorWidgetStyle implements IDiffEdito public layout(): number { // An editor should not be smaller than 5px - return Math.max(5, this.decorationsLeft); + return Math.max(5, this._decorationsLeft); } } +interface InlineModifiedViewZone extends IMyViewZone { + shouldNotShrink: boolean; + afterLineNumber: number; + heightInLines: number; + minWidthInPx: number; + domNode: HTMLElement; + marginDomNode: HTMLElement; + diff: IDiffLinesChange; +} + class InlineViewZonesComputer extends ViewZonesComputer { - private readonly originalModel: ITextModel; - private readonly modifiedEditorOptions: IComputedEditorOptions; - private readonly modifiedEditorTabSize: number; - private readonly renderIndicators: boolean; + private readonly _originalModel: ITextModel; + private readonly _renderIndicators: boolean; + private readonly _pendingLineChange: editorCommon.ILineChange[]; + private readonly _pendingViewZones: InlineModifiedViewZone[]; + private readonly _lineBreaksComputer: ILineBreaksComputer; - constructor(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[], originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor, renderIndicators: boolean) { - super(lineChanges, originalForeignVZ, originalEditor.getOption(EditorOption.lineHeight), modifiedForeignVZ, modifiedEditor.getOption(EditorOption.lineHeight)); - this.originalModel = originalEditor.getModel()!; - this.modifiedEditorOptions = modifiedEditor.getOptions(); - this.modifiedEditorTabSize = modifiedEditor.getModel()!.getOptions().tabSize; - this.renderIndicators = renderIndicators; + constructor( + lineChanges: editorCommon.ILineChange[], + originalForeignVZ: IEditorWhitespace[], + modifiedForeignVZ: IEditorWhitespace[], + originalEditor: CodeEditorWidget, + modifiedEditor: CodeEditorWidget, + renderIndicators: boolean + ) { + super(lineChanges, originalForeignVZ, modifiedForeignVZ, originalEditor, modifiedEditor); + this._originalModel = originalEditor.getModel()!; + this._renderIndicators = renderIndicators; + this._pendingLineChange = []; + this._pendingViewZones = []; + this._lineBreaksComputer = this._modifiedEditor._getViewModel()!.createLineBreaksComputer(); + } + + public getViewZones(): IEditorsZones { + const result = super.getViewZones(); + this._finalize(result); + return result; } protected _createOriginalMarginDomNodeForModifiedForeignViewZoneInAddedRegion(): HTMLDivElement | null { - let result = document.createElement('div'); + const result = document.createElement('div'); result.className = 'inline-added-margin-view-zone'; return result; } protected _produceOriginalFromDiff(lineChange: editorCommon.ILineChange, lineChangeOriginalLength: number, lineChangeModifiedLength: number): IMyViewZone | null { - let marginDomNode = document.createElement('div'); + const marginDomNode = document.createElement('div'); marginDomNode.className = 'inline-added-margin-view-zone'; return { @@ -2118,57 +2211,17 @@ class InlineViewZonesComputer extends ViewZonesComputer { } protected _produceModifiedFromDiff(lineChange: editorCommon.ILineChange, lineChangeOriginalLength: number, lineChangeModifiedLength: number): IMyViewZone | null { - let decorations: InlineDecoration[] = []; - if (lineChange.charChanges) { - for (let j = 0, lengthJ = lineChange.charChanges.length; j < lengthJ; j++) { - let charChange = lineChange.charChanges[j]; - if (isChangeOrDelete(charChange)) { - decorations.push(new InlineDecoration( - new Range(charChange.originalStartLineNumber, charChange.originalStartColumn, charChange.originalEndLineNumber, charChange.originalEndColumn), - 'char-delete', - InlineDecorationType.Regular - )); - } - } - } - - let sb = createStringBuilder(10000); - let marginDomNode = document.createElement('div'); - const layoutInfo = this.modifiedEditorOptions.get(EditorOption.layoutInfo); - const fontInfo = this.modifiedEditorOptions.get(EditorOption.fontInfo); - const lineDecorationsWidth = layoutInfo.decorationsWidth; - - let lineHeight = this.modifiedEditorOptions.get(EditorOption.lineHeight); - const typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth; - let maxCharsPerLine = 0; - const originalContent: string[] = []; - for (let lineNumber = lineChange.originalStartLineNumber; lineNumber <= lineChange.originalEndLineNumber; lineNumber++) { - maxCharsPerLine = Math.max(maxCharsPerLine, this._renderOriginalLine(lineNumber - lineChange.originalStartLineNumber, this.originalModel, this.modifiedEditorOptions, this.modifiedEditorTabSize, lineNumber, decorations, sb)); - originalContent.push(this.originalModel.getLineContent(lineNumber)); - - if (this.renderIndicators) { - let index = lineNumber - lineChange.originalStartLineNumber; - const marginElement = document.createElement('div'); - marginElement.className = `delete-sign ${diffRemoveIcon.classNames}`; - marginElement.setAttribute('style', `position:absolute;top:${index * lineHeight}px;width:${lineDecorationsWidth}px;height:${lineHeight}px;right:0;`); - marginDomNode.appendChild(marginElement); - } - } - maxCharsPerLine += this.modifiedEditorOptions.get(EditorOption.scrollBeyondLastColumn); - - let domNode = document.createElement('div'); + const domNode = document.createElement('div'); domNode.className = `view-lines line-delete ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`; - domNode.innerHTML = sb.build(); - Configuration.applyFontInfoSlow(domNode, fontInfo); + const marginDomNode = document.createElement('div'); marginDomNode.className = 'inline-deleted-margin-view-zone'; - Configuration.applyFontInfoSlow(marginDomNode, fontInfo); - return { + const viewZone: InlineModifiedViewZone = { shouldNotShrink: true, afterLineNumber: (lineChange.modifiedEndLineNumber === 0 ? lineChange.modifiedStartLineNumber : lineChange.modifiedStartLineNumber - 1), heightInLines: lineChangeOriginalLength, - minWidthInPx: (maxCharsPerLine * typicalHalfwidthCharacterWidth), + minWidthInPx: 0, domNode: domNode, marginDomNode: marginDomNode, diff: { @@ -2176,31 +2229,199 @@ class InlineViewZonesComputer extends ViewZonesComputer { originalEndLineNumber: lineChange.originalEndLineNumber, modifiedStartLineNumber: lineChange.modifiedStartLineNumber, modifiedEndLineNumber: lineChange.modifiedEndLineNumber, - originalContent: originalContent + originalModel: this._originalModel, + viewLineCounts: null, } }; + + for (let lineNumber = lineChange.originalStartLineNumber; lineNumber <= lineChange.originalEndLineNumber; lineNumber++) { + this._lineBreaksComputer.addRequest(this._originalModel.getLineContent(lineNumber), null); + } + + this._pendingLineChange.push(lineChange); + this._pendingViewZones.push(viewZone); + + return viewZone; } - private _renderOriginalLine(count: number, originalModel: ITextModel, options: IComputedEditorOptions, tabSize: number, lineNumber: number, decorations: InlineDecoration[], sb: IStringBuilder): number { - const lineTokens = originalModel.getLineTokens(lineNumber); - const lineContent = lineTokens.getLineContent(); - const fontInfo = options.get(EditorOption.fontInfo); + private _finalize(result: IEditorsZones): void { + const modifiedEditorOptions = this._modifiedEditor.getOptions(); + const tabSize = this._modifiedEditor.getModel()!.getOptions().tabSize; + const fontInfo = modifiedEditorOptions.get(EditorOption.fontInfo); + const disableMonospaceOptimizations = modifiedEditorOptions.get(EditorOption.disableMonospaceOptimizations); + const typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth; + const scrollBeyondLastColumn = modifiedEditorOptions.get(EditorOption.scrollBeyondLastColumn); + const mightContainNonBasicASCII = this._originalModel.mightContainNonBasicASCII(); + const mightContainRTL = this._originalModel.mightContainRTL(); + const lineHeight = modifiedEditorOptions.get(EditorOption.lineHeight); + const layoutInfo = modifiedEditorOptions.get(EditorOption.layoutInfo); + const lineDecorationsWidth = layoutInfo.decorationsWidth; + const stopRenderingLineAfter = modifiedEditorOptions.get(EditorOption.stopRenderingLineAfter); + const renderWhitespace = modifiedEditorOptions.get(EditorOption.renderWhitespace); + const renderControlCharacters = modifiedEditorOptions.get(EditorOption.renderControlCharacters); + const fontLigatures = modifiedEditorOptions.get(EditorOption.fontLigatures); - const actualDecorations = LineDecoration.filter(decorations, lineNumber, 1, lineContent.length + 1); + const lineBreaks = this._lineBreaksComputer.finalize(); + let lineBreakIndex = 0; + + for (let i = 0; i < this._pendingLineChange.length; i++) { + const lineChange = this._pendingLineChange[i]; + const viewZone = this._pendingViewZones[i]; + const domNode = viewZone.domNode; + Configuration.applyFontInfoSlow(domNode, fontInfo); + + const marginDomNode = viewZone.marginDomNode; + Configuration.applyFontInfoSlow(marginDomNode, fontInfo); + + const decorations: InlineDecoration[] = []; + if (lineChange.charChanges) { + for (const charChange of lineChange.charChanges) { + if (isChangeOrDelete(charChange)) { + decorations.push(new InlineDecoration( + new Range(charChange.originalStartLineNumber, charChange.originalStartColumn, charChange.originalEndLineNumber, charChange.originalEndColumn), + 'char-delete', + InlineDecorationType.Regular + )); + } + } + } + const hasCharChanges = (decorations.length > 0); + + const sb = createStringBuilder(10000); + let maxCharsPerLine = 0; + let renderedLineCount = 0; + let viewLineCounts: number[] | null = null; + for (let lineNumber = lineChange.originalStartLineNumber; lineNumber <= lineChange.originalEndLineNumber; lineNumber++) { + const lineIndex = lineNumber - lineChange.originalStartLineNumber; + const lineTokens = this._originalModel.getLineTokens(lineNumber); + const lineContent = lineTokens.getLineContent(); + const lineBreakData = lineBreaks[lineBreakIndex++]; + const actualDecorations = LineDecoration.filter(decorations, lineNumber, 1, lineContent.length + 1); + + if (lineBreakData) { + let lastBreakOffset = 0; + for (const breakOffset of lineBreakData.breakOffsets) { + const viewLineTokens = lineTokens.sliceAndInflate(lastBreakOffset, breakOffset, 0); + const viewLineContent = lineContent.substring(lastBreakOffset, breakOffset); + maxCharsPerLine = Math.max(maxCharsPerLine, this._renderOriginalLine( + renderedLineCount++, + viewLineContent, + viewLineTokens, + LineDecoration.extractWrapped(actualDecorations, lastBreakOffset, breakOffset), + hasCharChanges, + mightContainNonBasicASCII, + mightContainRTL, + fontInfo, + disableMonospaceOptimizations, + lineHeight, + lineDecorationsWidth, + stopRenderingLineAfter, + renderWhitespace, + renderControlCharacters, + fontLigatures, + tabSize, + sb, + marginDomNode + )); + lastBreakOffset = breakOffset; + } + if (!viewLineCounts) { + viewLineCounts = []; + } + // make sure all lines before this one have an entry in `viewLineCounts` + while (viewLineCounts.length < lineIndex) { + viewLineCounts[viewLineCounts.length] = 1; + } + viewLineCounts[lineIndex] = lineBreakData.breakOffsets.length; + viewZone.heightInLines += (lineBreakData.breakOffsets.length - 1); + const marginDomNode2 = document.createElement('div'); + marginDomNode2.className = 'line-delete'; + result.original.push({ + afterLineNumber: lineNumber, + afterColumn: 0, + heightInLines: lineBreakData.breakOffsets.length - 1, + domNode: createFakeLinesDiv(), + marginDomNode: marginDomNode2 + }); + } else { + maxCharsPerLine = Math.max(maxCharsPerLine, this._renderOriginalLine( + renderedLineCount++, + lineContent, + lineTokens, + actualDecorations, + hasCharChanges, + mightContainNonBasicASCII, + mightContainRTL, + fontInfo, + disableMonospaceOptimizations, + lineHeight, + lineDecorationsWidth, + stopRenderingLineAfter, + renderWhitespace, + renderControlCharacters, + fontLigatures, + tabSize, + sb, + marginDomNode + )); + } + } + maxCharsPerLine += scrollBeyondLastColumn; + + const html = sb.build(); + const trustedhtml = ttPolicy ? ttPolicy.createHTML(html) : html; + domNode.innerHTML = trustedhtml as unknown as string; + viewZone.minWidthInPx = (maxCharsPerLine * typicalHalfwidthCharacterWidth); + + if (viewLineCounts) { + // make sure all lines have an entry in `viewLineCounts` + const cnt = lineChange.originalEndLineNumber - lineChange.originalStartLineNumber; + while (viewLineCounts.length <= cnt) { + viewLineCounts[viewLineCounts.length] = 1; + } + } + viewZone.diff.viewLineCounts = viewLineCounts; + } + + result.original.sort((a, b) => { + return a.afterLineNumber - b.afterLineNumber; + }); + } + + private _renderOriginalLine( + renderedLineCount: number, + lineContent: string, + lineTokens: IViewLineTokens, + decorations: LineDecoration[], + hasCharChanges: boolean, + mightContainNonBasicASCII: boolean, + mightContainRTL: boolean, + fontInfo: FontInfo, + disableMonospaceOptimizations: boolean, + lineHeight: number, + lineDecorationsWidth: number, + stopRenderingLineAfter: number, + renderWhitespace: 'selection' | 'none' | 'boundary' | 'trailing' | 'all', + renderControlCharacters: boolean, + fontLigatures: string, + tabSize: number, + sb: IStringBuilder, + marginDomNode: HTMLElement + ): number { sb.appendASCIIString('
'); - const isBasicASCII = ViewLineRenderingData.isBasicASCII(lineContent, originalModel.mightContainNonBasicASCII()); - const containsRTL = ViewLineRenderingData.containsRTL(lineContent, isBasicASCII, originalModel.mightContainRTL()); + const isBasicASCII = ViewLineRenderingData.isBasicASCII(lineContent, mightContainNonBasicASCII); + const containsRTL = ViewLineRenderingData.containsRTL(lineContent, isBasicASCII, mightContainRTL); const output = renderViewLine(new RenderLineInput( - (fontInfo.isMonospace && !options.get(EditorOption.disableMonospaceOptimizations)), + (fontInfo.isMonospace && !disableMonospaceOptimizations), fontInfo.canUseHalfwidthRightwardsArrow, lineContent, false, @@ -2208,40 +2429,61 @@ class InlineViewZonesComputer extends ViewZonesComputer { containsRTL, 0, lineTokens, - actualDecorations, + decorations, tabSize, 0, fontInfo.spaceWidth, fontInfo.middotWidth, fontInfo.wsmiddotWidth, - options.get(EditorOption.stopRenderingLineAfter), - options.get(EditorOption.renderWhitespace), - options.get(EditorOption.renderControlCharacters), - options.get(EditorOption.fontLigatures) !== EditorFontLigatures.OFF, + stopRenderingLineAfter, + renderWhitespace, + renderControlCharacters, + fontLigatures !== EditorFontLigatures.OFF, null // Send no selections, original line cannot be selected ), sb); sb.appendASCIIString('
'); + if (this._renderIndicators) { + const marginElement = document.createElement('div'); + marginElement.className = `delete-sign ${ThemeIcon.asClassName(diffRemoveIcon)}`; + marginElement.setAttribute('style', `position:absolute;top:${renderedLineCount * lineHeight}px;width:${lineDecorationsWidth}px;height:${lineHeight}px;right:0;`); + marginDomNode.appendChild(marginElement); + } + const absoluteOffsets = output.characterMapping.getAbsoluteOffsets(); return absoluteOffsets.length > 0 ? absoluteOffsets[absoluteOffsets.length - 1] : 0; } } -export function isChangeOrInsert(lineChange: editorCommon.IChange): boolean { +function validateDiffWordWrap(value: 'off' | 'on' | 'inherit' | undefined, defaultValue: 'off' | 'on' | 'inherit'): 'off' | 'on' | 'inherit' { + return validateStringSetOption<'off' | 'on' | 'inherit'>(value, defaultValue, ['off', 'on', 'inherit']); +} + +function isChangeOrInsert(lineChange: editorCommon.IChange): boolean { return lineChange.modifiedEndLineNumber > 0; } -export function isChangeOrDelete(lineChange: editorCommon.IChange): boolean { +function isChangeOrDelete(lineChange: editorCommon.IChange): boolean { return lineChange.originalEndLineNumber > 0; } function createFakeLinesDiv(): HTMLElement { - let r = document.createElement('div'); + const r = document.createElement('div'); r.className = 'diagonal-fill'; return r; } +function getViewRange(model: ITextModel, viewModel: IViewModel, startLineNumber: number, endLineNumber: number): Range { + const lineCount = model.getLineCount(); + startLineNumber = Math.min(lineCount, Math.max(1, startLineNumber)); + endLineNumber = Math.min(lineCount, Math.max(1, endLineNumber)); + return viewModel.coordinatesConverter.convertModelRangeToViewRange(new Range( + startLineNumber, model.getLineMinColumn(startLineNumber), + endLineNumber, model.getLineMaxColumn(endLineNumber) + )); +} + registerThemingParticipant((theme, collector) => { const added = theme.getColor(diffInserted); if (added) { diff --git a/src/vs/editor/browser/widget/diffReview.ts b/src/vs/editor/browser/widget/diffReview.ts index 693261364..fd53314e1 100644 --- a/src/vs/editor/browser/widget/diffReview.ts +++ b/src/vs/editor/browser/widget/diffReview.ts @@ -29,9 +29,10 @@ import { ViewLineRenderingData } from 'vs/editor/common/viewModel/viewModel'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { scrollbarShadow } from 'vs/platform/theme/common/colorRegistry'; -import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { Constants } from 'vs/base/common/uint'; -import { registerIcon, Codicon } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; const DIFF_LINES_PADDING = 3; @@ -73,9 +74,9 @@ class Diff { } } -const diffReviewInsertIcon = registerIcon('diff-review-insert', Codicon.add); -const diffReviewRemoveIcon = registerIcon('diff-review-remove', Codicon.remove); -const diffReviewCloseIcon = registerIcon('diff-review-close', Codicon.close); +const diffReviewInsertIcon = registerIcon('diff-review-insert', Codicon.add, nls.localize('diffReviewInsertIcon', 'Icon for \'Insert\' in diff review.')); +const diffReviewRemoveIcon = registerIcon('diff-review-remove', Codicon.remove, nls.localize('diffReviewRemoveIcon', 'Icon for \'Remove\' in diff review.')); +const diffReviewCloseIcon = registerIcon('diff-review-close', Codicon.close, nls.localize('diffReviewCloseIcon', 'Icon for \'Close\' in diff review.')); export class DiffReview extends Disposable { @@ -104,7 +105,7 @@ export class DiffReview extends Disposable { this.actionBarContainer.domNode )); - this._actionBar.push(new Action('diffreview.close', nls.localize('label.close', "Close"), 'close-diff-review ' + diffReviewCloseIcon.classNames, true, () => { + this._actionBar.push(new Action('diffreview.close', nls.localize('label.close', "Close"), 'close-diff-review ' + ThemeIcon.asClassName(diffReviewCloseIcon), true, () => { this.hide(); return Promise.resolve(null); }), { label: false, icon: true }); @@ -647,7 +648,7 @@ export class DiffReview extends Disposable { let rowClassName: string = 'diff-review-row'; let lineNumbersExtraClassName: string = ''; const spacerClassName: string = 'diff-review-spacer'; - let spacerIcon: Codicon | null = null; + let spacerIcon: ThemeIcon | null = null; switch (type) { case DiffEntryType.Insert: rowClassName = 'diff-review-row line-insert'; @@ -723,7 +724,7 @@ export class DiffReview extends Disposable { if (spacerIcon) { const spacerCodicon = document.createElement('span'); - spacerCodicon.className = spacerIcon.classNames; + spacerCodicon.className = ThemeIcon.asClassName(spacerIcon); spacerCodicon.innerText = '\u00a0\u00a0'; spacer.appendChild(spacerCodicon); } else { diff --git a/src/vs/editor/browser/widget/inlineDiffMargin.ts b/src/vs/editor/browser/widget/inlineDiffMargin.ts index 7ae64d75c..baf5aadce 100644 --- a/src/vs/editor/browser/widget/inlineDiffMargin.ts +++ b/src/vs/editor/browser/widget/inlineDiffMargin.ts @@ -14,13 +14,15 @@ import { Range } from 'vs/editor/common/core/range'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Codicon } from 'vs/base/common/codicons'; +import { ITextModel } from 'vs/editor/common/model'; export interface IDiffLinesChange { readonly originalStartLineNumber: number; readonly originalEndLineNumber: number; readonly modifiedStartLineNumber: number; readonly modifiedEndLineNumber: number; - readonly originalContent: string[]; + readonly originalModel: ITextModel; + viewLineCounts: number[] | null; } export class InlineDiffMargin extends Disposable { @@ -45,12 +47,12 @@ export class InlineDiffMargin extends Disposable { } constructor( - private _viewZoneId: string, - private _marginDomNode: HTMLElement, - public editor: CodeEditorWidget, - public diff: IDiffLinesChange, - private _contextMenuService: IContextMenuService, - private _clipboardService: IClipboardService + private readonly _viewZoneId: string, + private readonly _marginDomNode: HTMLElement, + public readonly editor: CodeEditorWidget, + public readonly diff: IDiffLinesChange, + private readonly _contextMenuService: IContextMenuService, + private readonly _clipboardService: IClipboardService ) { super(); @@ -79,7 +81,9 @@ export class InlineDiffMargin extends Disposable { undefined, true, async () => { - await this._clipboardService.writeText(diff.originalContent.join(lineFeed) + lineFeed); + const range = new Range(diff.originalStartLineNumber, 1, diff.originalEndLineNumber + 1, 1); + const deletedText = diff.originalModel.getValueInRange(range); + await this._clipboardService.writeText(deletedText); } )); @@ -92,7 +96,8 @@ export class InlineDiffMargin extends Disposable { undefined, true, async () => { - await this._clipboardService.writeText(diff.originalContent[currentLineNumberOffset]); + const lineContent = diff.originalModel.getLineContent(diff.originalStartLineNumber + currentLineNumberOffset); + await this._clipboardService.writeText(lineContent); } ); @@ -102,13 +107,15 @@ export class InlineDiffMargin extends Disposable { const readOnly = editor.getOption(EditorOption.readOnly); if (!readOnly) { actions.push(new Action('diff.inline.revertChange', nls.localize('diff.inline.revertChange.label', "Revert this change"), undefined, true, async () => { + const range = new Range(diff.originalStartLineNumber, 1, diff.originalEndLineNumber, diff.originalModel.getLineMaxColumn(diff.originalEndLineNumber)); + const deletedText = diff.originalModel.getValueInRange(range); if (diff.modifiedEndLineNumber === 0) { // deletion only const column = editor.getModel()!.getLineMaxColumn(diff.modifiedStartLineNumber); editor.executeEdits('diffEditor', [ { range: new Range(diff.modifiedStartLineNumber, column, diff.modifiedStartLineNumber, column), - text: lineFeed + diff.originalContent.join(lineFeed) + text: lineFeed + deletedText } ]); } else { @@ -116,7 +123,7 @@ export class InlineDiffMargin extends Disposable { editor.executeEdits('diffEditor', [ { range: new Range(diff.modifiedStartLineNumber, 1, diff.modifiedEndLineNumber, column), - text: diff.originalContent.join(lineFeed) + text: deletedText } ]); } @@ -189,6 +196,15 @@ export class InlineDiffMargin extends Disposable { const lineNumberOffset = Math.floor(offset / lineHeight); const newTop = lineNumberOffset * lineHeight; this._diffActions.style.top = `${newTop}px`; + if (this.diff.viewLineCounts) { + let acc = 0; + for (let i = 0; i < this.diff.viewLineCounts.length; i++) { + acc += this.diff.viewLineCounts[i]; + if (lineNumberOffset < acc) { + return i; + } + } + } return lineNumberOffset; } } diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 3bde5dfd8..bf1d608fc 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -502,6 +502,16 @@ const editorConfiguration: IConfigurationNode = { default: true, description: nls.localize('wordBasedSuggestions', "Controls whether completions should be computed based on words in the document.") }, + 'editor.wordBasedSuggestionsMode': { + enum: ['currentDocument', 'matchingDocuments', 'allDocuments'], + default: 'matchingDocuments', + enumDescriptions: [ + nls.localize('wordBasedSuggestionsMode.currentDocument', 'Only suggest words from the active document.'), + nls.localize('wordBasedSuggestionsMode.matchingDocuments', 'Suggest words from all open documents of the same language.'), + nls.localize('wordBasedSuggestionsMode.allDocuments', 'Suggest words from all open documents.') + ], + description: nls.localize('wordBasedSuggestionsMode', "Controls form what documents word based completions are computed.") + }, 'editor.semanticHighlighting.enabled': { enum: [true, false, 'configuredByTheme'], enumDescriptions: [ @@ -546,6 +556,16 @@ const editorConfiguration: IConfigurationNode = { type: 'boolean', default: false, description: nls.localize('codeLens', "Controls whether the editor shows CodeLens.") + }, + 'diffEditor.wordWrap': { + type: 'string', + enum: ['off', 'on', 'inherit'], + default: 'inherit', + markdownEnumDescriptions: [ + nls.localize('wordWrap.off', "Lines will never wrap."), + nls.localize('wordWrap.on', "Lines will wrap at the viewport width."), + nls.localize('wordWrap.inherit', "Lines will wrap according to the `#editor.wordWrap#` setting."), + ] } } }; diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index a49bf9ce5..1c6b481c7 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -144,9 +144,13 @@ export interface IEditorOptions { */ readOnly?: boolean; /** - * Rename matching regions on type. + * Enable linked editing. * Defaults to false. */ + linkedEditing?: boolean; + /** + * deprecated, use linkedEditing instead + */ renameOnType?: boolean; /** * Should the editor render validation decorations. @@ -260,6 +264,14 @@ export interface IEditorOptions { * Defaults to "off". */ wordWrap?: 'off' | 'on' | 'wordWrapColumn' | 'bounded'; + /** + * Override the `wordWrap` setting. + */ + wordWrapOverride1?: 'off' | 'on' | 'inherit'; + /** + * Override the `wordWrapOverride1` setting. + */ + wordWrapOverride2?: 'off' | 'on' | 'inherit'; /** * Control the wrapping of the editor. * When `wordWrap` = "off", the lines will never wrap. @@ -269,11 +281,6 @@ export interface IEditorOptions { * Defaults to 80. */ wordWrapColumn?: number; - /** - * Force word wrapping when the text appears to be of a minified/generated file. - * Defaults to true. - */ - wordWrapMinified?: boolean; /** * Control indentation of wrapped lines. Can be: 'none', 'same', 'indent' or 'deepIndent'. * Defaults to 'same' in vscode and to 'none' in monaco-editor. @@ -370,6 +377,10 @@ export interface IEditorOptions { * Suggest options. */ suggest?: ISuggestOptions; + /** + * Smart select opptions; + */ + smartSelect?: ISmartSelectOptions; /** * */ @@ -416,6 +427,11 @@ export interface IEditorOptions { * Defaults to advanced. */ autoIndent?: 'none' | 'keep' | 'brackets' | 'advanced' | 'full'; + /** + * Emulate selection behaviour of tab characters when using spaces for indentation. + * This means selection will stick to tab stops. + */ + stickyTabStops?: boolean; /** * Enable format on type. * Defaults to false. @@ -491,6 +507,14 @@ export interface IEditorOptions { * Defaults to true. */ codeLens?: boolean; + /** + * Code lens font family. Defaults to editor font family. + */ + codeLensFontFamily?: string; + /** + * Code lens font size. Default to 90% of the editor font size + */ + codeLensFontSize?: number; /** * Control the behavior and rendering of the code action lightbulb. */ @@ -644,15 +668,19 @@ export interface IDiffEditorOptions extends IEditorOptions { */ originalEditable?: boolean; /** - * Original editor should be have code lens enabled? + * Should the diff editor enable code lens? * Defaults to false. */ - originalCodeLens?: boolean; + diffCodeLens?: boolean; /** - * Modified editor should be have code lens enabled? - * Defaults to false. + * Is the diff editor inside another editor + * Defaults to false */ - modifiedCodeLens?: boolean; + isInEmbeddedEditor?: boolean; + /** + * Control the wrapping of the diff editor. + */ + diffWordWrap?: 'off' | 'on' | 'inherit'; } //#endregion @@ -828,18 +856,21 @@ class SimpleEditorOption implements IEditorOption extends SimpleEditorOption { - - public static boolean(value: any, defaultValue: boolean): boolean { - if (typeof value === 'undefined') { - return defaultValue; - } - if (value === 'false') { - // treat the string 'false' as false - return false; - } - return Boolean(value); +/** + * @internal + */ +export function boolean(value: any, defaultValue: boolean): boolean { + if (typeof value === 'undefined') { + return defaultValue; } + if (value === 'false') { + // treat the string 'false' as false + return false; + } + return Boolean(value); +} + +class EditorBooleanOption extends SimpleEditorOption { constructor(id: K1, name: PossibleKeyName, defaultValue: boolean, schema: IConfigurationPropertySchema | undefined = undefined) { if (typeof schema !== 'undefined') { @@ -850,7 +881,7 @@ class EditorBooleanOption extends SimpleEditorOption extends SimpleEditorOption extends SimpleEditorOption { - - public static stringSet(value: T | undefined, defaultValue: T, allowedValues: ReadonlyArray): T { - if (typeof value !== 'string') { - return defaultValue; - } - if (allowedValues.indexOf(value) === -1) { - return defaultValue; - } - return value; +/** + * @internal + */ +export function stringSet(value: T | undefined, defaultValue: T, allowedValues: ReadonlyArray): T { + if (typeof value !== 'string') { + return defaultValue; } + if (allowedValues.indexOf(value) === -1) { + return defaultValue; + } + return value; +} + +class EditorStringEnumOption extends SimpleEditorOption { private readonly _allowedValues: ReadonlyArray; @@ -975,7 +1009,7 @@ class EditorStringEnumOption extends } public validate(input: any): V { - return EditorStringEnumOption.stringSet(input, this.defaultValue, this._allowedValues); + return stringSet(input, this.defaultValue, this._allowedValues); } } @@ -1113,8 +1147,8 @@ class EditorComments extends BaseEditorOption } const input = _input as IEditorFindOptions; return { - cursorMoveOnType: EditorBooleanOption.boolean(input.cursorMoveOnType, this.defaultValue.cursorMoveOnType), - seedSearchStringFromSelection: EditorBooleanOption.boolean(input.seedSearchStringFromSelection, this.defaultValue.seedSearchStringFromSelection), + cursorMoveOnType: boolean(input.cursorMoveOnType, this.defaultValue.cursorMoveOnType), + seedSearchStringFromSelection: boolean(input.seedSearchStringFromSelection, this.defaultValue.seedSearchStringFromSelection), autoFindInSelection: typeof _input.autoFindInSelection === 'boolean' ? (_input.autoFindInSelection ? 'always' : 'never') - : EditorStringEnumOption.stringSet<'never' | 'always' | 'multiline'>(input.autoFindInSelection, this.defaultValue.autoFindInSelection, ['never', 'always', 'multiline']), - globalFindClipboard: EditorBooleanOption.boolean(input.globalFindClipboard, this.defaultValue.globalFindClipboard), - addExtraSpaceOnTop: EditorBooleanOption.boolean(input.addExtraSpaceOnTop, this.defaultValue.addExtraSpaceOnTop), - loop: EditorBooleanOption.boolean(input.loop, this.defaultValue.loop), + : stringSet<'never' | 'always' | 'multiline'>(input.autoFindInSelection, this.defaultValue.autoFindInSelection, ['never', 'always', 'multiline']), + globalFindClipboard: boolean(input.globalFindClipboard, this.defaultValue.globalFindClipboard), + addExtraSpaceOnTop: boolean(input.addExtraSpaceOnTop, this.defaultValue.addExtraSpaceOnTop), + loop: boolean(input.loop, this.defaultValue.loop), }; } } @@ -1406,14 +1440,14 @@ export class EditorFontLigatures extends BaseEditorOption(input.multiple, this.defaultValue.multiple!, ['peek', 'gotoAndPeek', 'goto']), - multipleDefinitions: input.multipleDefinitions ?? EditorStringEnumOption.stringSet(input.multipleDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']), - multipleTypeDefinitions: input.multipleTypeDefinitions ?? EditorStringEnumOption.stringSet(input.multipleTypeDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']), - multipleDeclarations: input.multipleDeclarations ?? EditorStringEnumOption.stringSet(input.multipleDeclarations, 'peek', ['peek', 'gotoAndPeek', 'goto']), - multipleImplementations: input.multipleImplementations ?? EditorStringEnumOption.stringSet(input.multipleImplementations, 'peek', ['peek', 'gotoAndPeek', 'goto']), - multipleReferences: input.multipleReferences ?? EditorStringEnumOption.stringSet(input.multipleReferences, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multiple: stringSet(input.multiple, this.defaultValue.multiple!, ['peek', 'gotoAndPeek', 'goto']), + multipleDefinitions: input.multipleDefinitions ?? stringSet(input.multipleDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multipleTypeDefinitions: input.multipleTypeDefinitions ?? stringSet(input.multipleTypeDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multipleDeclarations: input.multipleDeclarations ?? stringSet(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']), alternativeDefinitionCommand: EditorStringOption.string(input.alternativeDefinitionCommand, this.defaultValue.alternativeDefinitionCommand), alternativeTypeDefinitionCommand: EditorStringOption.string(input.alternativeTypeDefinitionCommand, this.defaultValue.alternativeTypeDefinitionCommand), alternativeDeclarationCommand: EditorStringOption.string(input.alternativeDeclarationCommand, this.defaultValue.alternativeDeclarationCommand), @@ -1722,9 +1756,9 @@ class EditorHover extends BaseEditorOption(input.size, this.defaultValue.size, ['proportional', 'fill', 'fit']), - side: EditorStringEnumOption.stringSet<'right' | 'left'>(input.side, this.defaultValue.side, ['right', 'left']), - showSlider: EditorStringEnumOption.stringSet<'always' | 'mouseover'>(input.showSlider, this.defaultValue.showSlider, ['always', 'mouseover']), - renderCharacters: EditorBooleanOption.boolean(input.renderCharacters, this.defaultValue.renderCharacters), + enabled: boolean(input.enabled, this.defaultValue.enabled), + size: stringSet<'proportional' | 'fill' | 'fit'>(input.size, this.defaultValue.size, ['proportional', 'fill', 'fit']), + side: stringSet<'right' | 'left'>(input.side, this.defaultValue.side, ['right', 'left']), + showSlider: stringSet<'always' | 'mouseover'>(input.showSlider, this.defaultValue.showSlider, ['always', 'mouseover']), + renderCharacters: boolean(input.renderCharacters, this.defaultValue.renderCharacters), scale: EditorIntOption.clampedInt(input.scale, 1, 1, 3), maxColumn: EditorIntOption.clampedInt(input.maxColumn, this.defaultValue.maxColumn, 1, 10000), }; @@ -2598,8 +2634,8 @@ class EditorParameterHints extends BaseEditorOption>; + +class SmartSelect extends BaseEditorOption { + + constructor() { + super( + EditorOption.smartSelect, 'smartSelect', + { + selectLeadingAndTrailingWhitespace: true + }, + { + 'editor.smartSelect.selectLeadingAndTrailingWhitespace': { + description: nls.localize('selectLeadingAndTrailingWhitespace', "Whether leading and trailing whitespace should always be selected."), + default: true, + type: 'boolean' + } + } + ); + } + + public validate(input: any): Readonly> { + if (!input || typeof input !== 'object') { + return this.defaultValue; + } + return { + selectLeadingAndTrailingWhitespace: boolean((input as ISmartSelectOptions).selectLeadingAndTrailingWhitespace, this.defaultValue.selectLeadingAndTrailingWhitespace) }; } } @@ -3552,6 +3646,8 @@ export const enum EditorOption { automaticLayout, autoSurround, codeLens, + codeLensFontFamily, + codeLensFontSize, colorDecorators, columnSelection, comments, @@ -3594,6 +3690,7 @@ export const enum EditorOption { lineHeight, lineNumbers, lineNumbersMinChars, + linkedEditing, links, matchBrackets, minimap, @@ -3634,7 +3731,9 @@ export const enum EditorOption { showFoldingControls, showUnused, snippetSuggestions, + smartSelect, smoothScrolling, + stickyTabStops, stopRenderingLineAfter, suggest, suggestFontSize, @@ -3650,7 +3749,8 @@ export const enum EditorOption { wordWrapBreakAfterCharacters, wordWrapBreakBeforeCharacters, wordWrapColumn, - wordWrapMinified, + wordWrapOverride1, + wordWrapOverride2, wrappingIndent, wrappingStrategy, showDeprecated, @@ -3776,10 +3876,25 @@ export const EditorOptions = { description: nls.localize('autoSurround', "Controls whether the editor should automatically surround selections when typing quotes or brackets.") } )), + stickyTabStops: register(new EditorBooleanOption( + EditorOption.stickyTabStops, 'stickyTabStops', false, + { description: nls.localize('stickyTabStops', "Emulate selection behaviour of tab characters when using spaces for indentation. Selection will stick to tab stops.") } + )), codeLens: register(new EditorBooleanOption( EditorOption.codeLens, 'codeLens', true, { description: nls.localize('codeLens', "Controls whether the editor shows CodeLens.") } )), + codeLensFontFamily: register(new EditorStringOption( + EditorOption.codeLensFontFamily, 'codeLensFontFamily', '', + { description: nls.localize('codeLensFontFamily', "Controls the font family for CodeLens.") } + )), + codeLensFontSize: register(new EditorIntOption(EditorOption.codeLensFontSize, 'codeLensFontSize', 0, 0, 100, { + type: 'number', + default: 0, + minimum: 0, + maximum: 100, + description: nls.localize('codeLensFontSize', "Controls the font size in pixels for CodeLens. When set to `0`, the 90% of `#editor.fontSize#` is used.") + })), colorDecorators: register(new EditorBooleanOption( EditorOption.colorDecorators, 'colorDecorators', true, { description: nls.localize('colorDecorators', "Controls whether the editor should render the inline color decorators and color picker.") } @@ -3914,7 +4029,7 @@ export const EditorOptions = { )), hover: register(new EditorHover()), inDiffEditor: register(new EditorBooleanOption( - EditorOption.inDiffEditor, 'inDiffEditor', false, + EditorOption.inDiffEditor, 'inDiffEditor', false )), letterSpacing: register(new EditorFloatOption( EditorOption.letterSpacing, 'letterSpacing', @@ -3929,6 +4044,10 @@ export const EditorOptions = { EditorOption.lineNumbersMinChars, 'lineNumbersMinChars', 5, 1, 300 )), + linkedEditing: register(new EditorBooleanOption( + EditorOption.linkedEditing, 'linkedEditing', false, + { description: nls.localize('linkedEditing', "Controls whether the editor has linked editing enabled. Depending on the language, related symbols, e.g. HTML tags, are updated while editing.") } + )), links: register(new EditorBooleanOption( EditorOption.links, 'links', true, { description: nls.localize('links', "Controls whether the editor should detect links and make them clickable.") } @@ -4030,7 +4149,7 @@ export const EditorOptions = { )), renameOnType: register(new EditorBooleanOption( EditorOption.renameOnType, 'renameOnType', false, - { description: nls.localize('renameOnType', "Controls whether the editor auto renames on type.") } + { description: nls.localize('renameOnType', "Controls whether the editor auto renames on type."), markdownDeprecationMessage: nls.localize('renameOnTypeDeprecate', "Deprecated, use `editor.linkedEditing` instead.") } )), renderControlCharacters: register(new EditorBooleanOption( EditorOption.renderControlCharacters, 'renderControlCharacters', false, @@ -4153,6 +4272,7 @@ export const EditorOptions = { description: nls.localize('snippetSuggestions', "Controls whether snippets are shown with other suggestions and how they are sorted.") } )), + smartSelect: register(new SmartSelect()), smoothScrolling: register(new EditorBooleanOption( EditorOption.smoothScrolling, 'smoothScrolling', false, { description: nls.localize('smoothScrolling', "Controls whether the editor will scroll using an animation.") } @@ -4170,7 +4290,7 @@ export const EditorOptions = { suggestLineHeight: register(new EditorIntOption( EditorOption.suggestLineHeight, 'suggestLineHeight', 0, 0, 1000, - { markdownDescription: nls.localize('suggestLineHeight', "Line height for the suggest widget. When set to `0`, the value of `#editor.lineHeight#` is used.") } + { markdownDescription: nls.localize('suggestLineHeight', "Line height for the suggest widget. When set to `0`, the value of `#editor.lineHeight#` is used. The minimum value is 8.") } )), suggestOnTriggerCharacters: register(new EditorBooleanOption( EditorOption.suggestOnTriggerCharacters, 'suggestOnTriggerCharacters', true, @@ -4279,8 +4399,15 @@ export const EditorOptions = { }, "Controls the wrapping column of the editor when `#editor.wordWrap#` is `wordWrapColumn` or `bounded`.") } )), - wordWrapMinified: register(new EditorBooleanOption( - EditorOption.wordWrapMinified, 'wordWrapMinified', true, + wordWrapOverride1: register(new EditorStringEnumOption( + EditorOption.wordWrapOverride1, 'wordWrapOverride1', + 'inherit' as 'off' | 'on' | 'inherit', + ['off', 'on', 'inherit'] as const + )), + wordWrapOverride2: register(new EditorStringEnumOption( + EditorOption.wordWrapOverride2, 'wordWrapOverride2', + 'inherit' as 'off' | 'on' | 'inherit', + ['off', 'on', 'inherit'] as const )), wrappingIndent: register(new EditorEnumOption( EditorOption.wrappingIndent, 'wrappingIndent', diff --git a/src/vs/editor/common/controller/cursor.ts b/src/vs/editor/common/controller/cursor.ts index 7a966ed22..2bb67894d 100644 --- a/src/vs/editor/common/controller/cursor.ts +++ b/src/vs/editor/common/controller/cursor.ts @@ -531,7 +531,7 @@ export class Cursor extends Disposable { } const closeChar = m[1]; - const autoClosingPairsCandidates = this.context.cursorConfig.autoClosingPairsClose2.get(closeChar); + const autoClosingPairsCandidates = this.context.cursorConfig.autoClosingPairs.autoClosingPairsCloseSingleChar.get(closeChar); if (!autoClosingPairsCandidates || autoClosingPairsCandidates.length !== 1) { return null; } diff --git a/src/vs/editor/common/controller/cursorAtomicMoveOperations.ts b/src/vs/editor/common/controller/cursorAtomicMoveOperations.ts new file mode 100644 index 000000000..4b3a23527 --- /dev/null +++ b/src/vs/editor/common/controller/cursorAtomicMoveOperations.ts @@ -0,0 +1,160 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { CursorColumns } from 'vs/editor/common/controller/cursorCommon'; + +export const enum Direction { + Left, + Right, + Nearest, +} + +export class AtomicTabMoveOperations { + /** + * Get the visible column at the position. If we get to a non-whitespace character first + * or past the end of string then return -1. + * + * **Note** `position` and the return value are 0-based. + */ + public static whitespaceVisibleColumn(lineContent: string, position: number, tabSize: number): [number, number, number] { + const lineLength = lineContent.length; + let visibleColumn = 0; + let prevTabStopPosition = -1; + let prevTabStopVisibleColumn = -1; + for (let i = 0; i < lineLength; i++) { + if (i === position) { + return [prevTabStopPosition, prevTabStopVisibleColumn, visibleColumn]; + } + if (visibleColumn % tabSize === 0) { + prevTabStopPosition = i; + prevTabStopVisibleColumn = visibleColumn; + } + const chCode = lineContent.charCodeAt(i); + switch (chCode) { + case CharCode.Space: + visibleColumn += 1; + break; + case CharCode.Tab: + // Skip to the next multiple of tabSize. + visibleColumn = CursorColumns.nextRenderTabStop(visibleColumn, tabSize); + break; + default: + return [-1, -1, -1]; + } + } + if (position === lineLength) { + return [prevTabStopPosition, prevTabStopVisibleColumn, visibleColumn]; + } + return [-1, -1, -1]; + } + + /** + * Return the position that should result from a move left, right or to the + * nearest tab, if atomic tabs are enabled. Left and right are used for the + * arrow key movements, nearest is used for mouse selection. It returns + * -1 if atomic tabs are not relevant and you should fall back to normal + * behaviour. + * + * **Note**: `position` and the return value are 0-based. + */ + public static atomicPosition(lineContent: string, position: number, tabSize: number, direction: Direction): number { + const lineLength = lineContent.length; + + // Get the 0-based visible column corresponding to the position, or return + // -1 if it is not in the initial whitespace. + const [prevTabStopPosition, prevTabStopVisibleColumn, visibleColumn] = AtomicTabMoveOperations.whitespaceVisibleColumn(lineContent, position, tabSize); + + if (visibleColumn === -1) { + return -1; + } + + // Is the output left or right of the current position. The case for nearest + // where it is the same as the current position is handled in the switch. + let left: boolean; + switch (direction) { + case Direction.Left: + left = true; + break; + case Direction.Right: + left = false; + break; + case Direction.Nearest: + // The code below assumes the output position is either left or right + // of the input position. If it is the same, return immediately. + if (visibleColumn % tabSize === 0) { + return position; + } + // Go to the nearest indentation. + left = visibleColumn % tabSize <= (tabSize / 2); + break; + } + + // If going left, we can just use the info about the last tab stop position and + // last tab stop visible column that we computed in the first walk over the whitespace. + if (left) { + if (prevTabStopPosition === -1) { + return -1; + } + // If the direction is left, we need to keep scanning right to ensure + // that targetVisibleColumn + tabSize is before non-whitespace. + // This is so that when we press left at the end of a partial + // indentation it only goes one character. For example ' foo' with + // tabSize 4, should jump from position 6 to position 5, not 4. + let currentVisibleColumn = prevTabStopVisibleColumn; + for (let i = prevTabStopPosition; i < lineLength; ++i) { + if (currentVisibleColumn === prevTabStopVisibleColumn + tabSize) { + // It is a full indentation. + return prevTabStopPosition; + } + + const chCode = lineContent.charCodeAt(i); + switch (chCode) { + case CharCode.Space: + currentVisibleColumn += 1; + break; + case CharCode.Tab: + currentVisibleColumn = CursorColumns.nextRenderTabStop(currentVisibleColumn, tabSize); + break; + default: + return -1; + } + } + if (currentVisibleColumn === prevTabStopVisibleColumn + tabSize) { + return prevTabStopPosition; + } + // It must have been a partial indentation. + return -1; + } + + // We are going right. + const targetVisibleColumn = CursorColumns.nextRenderTabStop(visibleColumn, tabSize); + + // We can just continue from where whitespaceVisibleColumn got to. + let currentVisibleColumn = visibleColumn; + for (let i = position; i < lineLength; i++) { + if (currentVisibleColumn === targetVisibleColumn) { + return i; + } + + const chCode = lineContent.charCodeAt(i); + switch (chCode) { + case CharCode.Space: + currentVisibleColumn += 1; + break; + case CharCode.Tab: + currentVisibleColumn = CursorColumns.nextRenderTabStop(currentVisibleColumn, tabSize); + break; + default: + return -1; + } + } + // This condition handles when the target column is at the end of the line. + if (currentVisibleColumn === targetVisibleColumn) { + return lineLength; + } + return -1; + } +} diff --git a/src/vs/editor/common/controller/cursorCommon.ts b/src/vs/editor/common/controller/cursorCommon.ts index 92d36e239..e35e89e06 100644 --- a/src/vs/editor/common/controller/cursorCommon.ts +++ b/src/vs/editor/common/controller/cursorCommon.ts @@ -14,7 +14,7 @@ import { ICommand, IConfiguration } from 'vs/editor/common/editorCommon'; import { ITextModel, TextModelResolvedOptions } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import { LanguageIdentifier } from 'vs/editor/common/modes'; -import { IAutoClosingPair, StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration'; +import { AutoClosingPairs, IAutoClosingPair } from 'vs/editor/common/modes/languageConfiguration'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel'; import { Constants } from 'vs/base/common/uint'; @@ -62,6 +62,7 @@ export class CursorConfiguration { public readonly tabSize: number; public readonly indentSize: number; public readonly insertSpaces: boolean; + public readonly stickyTabStops: boolean; public readonly pageSize: number; public readonly lineHeight: number; public readonly useTabStops: boolean; @@ -75,8 +76,7 @@ export class CursorConfiguration { public readonly autoClosingOvertype: EditorAutoClosingOvertypeStrategy; public readonly autoSurround: EditorAutoSurroundStrategy; public readonly autoIndent: EditorAutoIndentStrategy; - public readonly autoClosingPairsOpen2: Map; - public readonly autoClosingPairsClose2: Map; + public readonly autoClosingPairs: AutoClosingPairs; public readonly surroundingPairs: CharacterMap; public readonly shouldAutoCloseBefore: { quote: (ch: string) => boolean, bracket: (ch: string) => boolean }; @@ -114,6 +114,7 @@ export class CursorConfiguration { this.tabSize = modelOptions.tabSize; this.indentSize = modelOptions.indentSize; this.insertSpaces = modelOptions.insertSpaces; + this.stickyTabStops = options.get(EditorOption.stickyTabStops); this.lineHeight = options.get(EditorOption.lineHeight); this.pageSize = Math.max(1, Math.floor(layoutInfo.height / this.lineHeight) - 2); this.useTabStops = options.get(EditorOption.useTabStops); @@ -136,9 +137,7 @@ export class CursorConfiguration { bracket: CursorConfiguration._getShouldAutoClose(languageIdentifier, this.autoClosingBrackets) }; - const autoClosingPairs = LanguageConfigurationRegistry.getAutoClosingPairs(languageIdentifier.id); - this.autoClosingPairsOpen2 = autoClosingPairs.autoClosingPairsOpen; - this.autoClosingPairsClose2 = autoClosingPairs.autoClosingPairsClose; + this.autoClosingPairs = LanguageConfigurationRegistry.getAutoClosingPairs(languageIdentifier.id); let surroundingPairs = CursorConfiguration._getSurroundingPairs(languageIdentifier); if (surroundingPairs) { @@ -557,14 +556,14 @@ export class CursorColumns { } /** - * ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns) + * ATTENTION: This works with 0-based columns (as opposed to the regular 1-based columns) */ public static prevRenderTabStop(column: number, tabSize: number): number { return column - 1 - (column - 1) % tabSize; } /** - * ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns) + * ATTENTION: This works with 0-based columns (as opposed to the regular 1-based columns) */ public static prevIndentTabStop(column: number, indentSize: number): number { return column - 1 - (column - 1) % indentSize; diff --git a/src/vs/editor/common/controller/cursorDeleteOperations.ts b/src/vs/editor/common/controller/cursorDeleteOperations.ts index 1147e78ae..1a4a86d87 100644 --- a/src/vs/editor/common/controller/cursorDeleteOperations.ts +++ b/src/vs/editor/common/controller/cursorDeleteOperations.ts @@ -122,7 +122,7 @@ export class DeleteOperations { public static deleteLeft(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[]): [boolean, Array] { - if (this.isAutoClosingPairDelete(config.autoClosingBrackets, config.autoClosingQuotes, config.autoClosingPairsOpen2, model, selections)) { + if (this.isAutoClosingPairDelete(config.autoClosingBrackets, config.autoClosingQuotes, config.autoClosingPairs.autoClosingPairsOpenByEnd, model, selections)) { return this._runAutoClosingPairDelete(config, model, selections); } diff --git a/src/vs/editor/common/controller/cursorMoveCommands.ts b/src/vs/editor/common/controller/cursorMoveCommands.ts index bc9bbb1c3..909a2255d 100644 --- a/src/vs/editor/common/controller/cursorMoveCommands.ts +++ b/src/vs/editor/common/controller/cursorMoveCommands.ts @@ -412,7 +412,11 @@ export class CursorMoveCommands { const skipWrappingPointStop = hasMultipleCursors || !cursor.viewState.hasSelection(); let newViewState = MoveOperations.moveLeft(viewModel.cursorConfig, viewModel, cursor.viewState, inSelectionMode, noOfColumns); - if (skipWrappingPointStop && noOfColumns === 1 && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber) { + if (skipWrappingPointStop + && noOfColumns === 1 + && cursor.viewState.position.column === viewModel.getLineMinColumn(cursor.viewState.position.lineNumber) + && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber + ) { // moved over to the previous view line const newViewModelPosition = viewModel.coordinatesConverter.convertViewPositionToModelPosition(newViewState.position); if (newViewModelPosition.lineNumber === cursor.modelState.position.lineNumber) { @@ -445,7 +449,11 @@ export class CursorMoveCommands { const skipWrappingPointStop = hasMultipleCursors || !cursor.viewState.hasSelection(); let newViewState = MoveOperations.moveRight(viewModel.cursorConfig, viewModel, cursor.viewState, inSelectionMode, noOfColumns); - if (skipWrappingPointStop && noOfColumns === 1 && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber) { + if (skipWrappingPointStop + && noOfColumns === 1 + && cursor.viewState.position.column === viewModel.getLineMaxColumn(cursor.viewState.position.lineNumber) + && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber + ) { // moved over to the next view line const newViewModelPosition = viewModel.coordinatesConverter.convertViewPositionToModelPosition(newViewState.position); if (newViewModelPosition.lineNumber === cursor.modelState.position.lineNumber) { diff --git a/src/vs/editor/common/controller/cursorMoveOperations.ts b/src/vs/editor/common/controller/cursorMoveOperations.ts index 20ca28947..9229c2e35 100644 --- a/src/vs/editor/common/controller/cursorMoveOperations.ts +++ b/src/vs/editor/common/controller/cursorMoveOperations.ts @@ -8,6 +8,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import * as strings from 'vs/base/common/strings'; import { Constants } from 'vs/base/common/uint'; +import { AtomicTabMoveOperations, Direction } from 'vs/editor/common/controller/cursorAtomicMoveOperations'; export class CursorPosition { _cursorPositionBrand: void; @@ -35,8 +36,20 @@ export class MoveOperations { return new Position(lineNumber, column); } + public static leftPositionAtomicSoftTabs(model: ICursorSimpleModel, lineNumber: number, column: number, tabSize: number): Position { + const minColumn = model.getLineMinColumn(lineNumber); + const lineContent = model.getLineContent(lineNumber); + const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, column - minColumn, tabSize, Direction.Left); + if (newPosition === -1) { + return this.leftPosition(model, lineNumber, column); + } + return new Position(lineNumber, minColumn + newPosition); + } + public static left(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number): CursorPosition { - const pos = MoveOperations.leftPosition(model, lineNumber, column); + const pos = config.stickyTabStops + ? MoveOperations.leftPositionAtomicSoftTabs(model, lineNumber, column, config.tabSize) + : MoveOperations.leftPosition(model, lineNumber, column); return new CursorPosition(pos.lineNumber, pos.column, 0); } @@ -67,8 +80,20 @@ export class MoveOperations { return new Position(lineNumber, column); } + public static rightPositionAtomicSoftTabs(model: ICursorSimpleModel, lineNumber: number, column: number, tabSize: number, indentSize: number): Position { + const minColumn = model.getLineMinColumn(lineNumber); + const lineContent = model.getLineContent(lineNumber); + const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, column - minColumn, tabSize, Direction.Right); + if (newPosition === -1) { + return this.rightPosition(model, lineNumber, column); + } + return new Position(lineNumber, minColumn + newPosition); + } + public static right(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number): CursorPosition { - const pos = MoveOperations.rightPosition(model, lineNumber, column); + const pos = config.stickyTabStops + ? MoveOperations.rightPositionAtomicSoftTabs(model, lineNumber, column, config.tabSize, config.indentSize) + : MoveOperations.rightPosition(model, lineNumber, column); return new CursorPosition(pos.lineNumber, pos.column, 0); } diff --git a/src/vs/editor/common/controller/cursorTypeOperations.ts b/src/vs/editor/common/controller/cursorTypeOperations.ts index d383ffc63..75da04850 100644 --- a/src/vs/editor/common/controller/cursorTypeOperations.ts +++ b/src/vs/editor/common/controller/cursorTypeOperations.ts @@ -128,7 +128,7 @@ export class TypeOperations { if (text.charCodeAt(text.length - 1) === CharCode.CarriageReturn) { text = text.substr(0, text.length - 1); } - let lines = text.split(/\r\n|\r|\n/); + let lines = strings.splitLines(text); if (lines.length === selections.length) { return lines; } @@ -439,7 +439,7 @@ export class TypeOperations { return false; } - if (!config.autoClosingPairsClose2.has(ch)) { + if (!config.autoClosingPairs.autoClosingPairsCloseSingleChar.has(ch)) { return false; } @@ -498,31 +498,20 @@ export class TypeOperations { }); } - private static _autoClosingPairIsSymmetric(autoClosingPair: StandardAutoClosingPairConditional): boolean { - const { open, close } = autoClosingPair; - return (open.indexOf(close) >= 0 || close.indexOf(open) >= 0); - } + 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) || []; - private static _isBeforeClosingBrace(config: CursorConfiguration, autoClosingPair: StandardAutoClosingPairConditional, characterAfter: string) { - const otherAutoClosingPairs = config.autoClosingPairsClose2.get(characterAfter); - if (!otherAutoClosingPairs) { - return false; - } + const isBeforeStartingBrace = potentialStartingBraces.some(x => lineAfter.startsWith(x.open)); + const isBeforeClosingBrace = potentialClosingBraces.some(x => lineAfter.startsWith(x.close)); - const thisBraceIsSymmetric = TypeOperations._autoClosingPairIsSymmetric(autoClosingPair); - for (const otherAutoClosingPair of otherAutoClosingPairs) { - const otherBraceIsSymmetric = TypeOperations._autoClosingPairIsSymmetric(otherAutoClosingPair); - if (!thisBraceIsSymmetric && otherBraceIsSymmetric) { - continue; - } - return true; - } - - return false; + return !isBeforeStartingBrace && isBeforeClosingBrace; } private static _findAutoClosingPairOpen(config: CursorConfiguration, model: ITextModel, positions: Position[], ch: string): StandardAutoClosingPairConditional | null { - const autoClosingPairCandidates = config.autoClosingPairsOpen2.get(ch); + const autoClosingPairCandidates = config.autoClosingPairs.autoClosingPairsOpenByEnd.get(ch); if (!autoClosingPairCandidates) { return null; } @@ -548,7 +537,29 @@ export class TypeOperations { return autoClosingPair; } - private static _isAutoClosingOpenCharType(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, insertOpenCharacter: boolean): StandardAutoClosingPairConditional | null { + private static _findSubAutoClosingPairClose(config: CursorConfiguration, autoClosingPair: StandardAutoClosingPairConditional): string { + if (autoClosingPair.open.length <= 1) { + return ''; + } + const lastChar = autoClosingPair.close.charAt(autoClosingPair.close.length - 1); + // get candidates with the same last character as close + const subPairCandidates = config.autoClosingPairs.autoClosingPairsCloseByEnd.get(lastChar) || []; + let subPairMatch: StandardAutoClosingPairConditional | null = null; + for (const x of subPairCandidates) { + if (x.open !== autoClosingPair.open && autoClosingPair.open.includes(x.open) && autoClosingPair.close.endsWith(x.close)) { + if (!subPairMatch || x.open.length > subPairMatch.open.length) { + subPairMatch = x; + } + } + } + if (subPairMatch) { + return subPairMatch.close; + } else { + return ''; + } + } + + private static _getAutoClosingPairClose(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, insertOpenCharacter: boolean): string | null { const chIsQuote = isQuote(ch); const autoCloseConfig = chIsQuote ? config.autoClosingQuotes : config.autoClosingBrackets; if (autoCloseConfig === 'never') { @@ -560,6 +571,9 @@ export class TypeOperations { return null; } + const subAutoClosingPairClose = this._findSubAutoClosingPairClose(config, autoClosingPair); + let isSubAutoClosingPairPresent = true; + const shouldAutoCloseBefore = chIsQuote ? config.shouldAutoCloseBefore.quote : config.shouldAutoCloseBefore.bracket; for (let i = 0, len = selections.length; i < len; i++) { @@ -570,11 +584,16 @@ export class TypeOperations { const position = selection.getPosition(); const lineText = model.getLineContent(position.lineNumber); + const lineAfter = lineText.substring(position.column - 1); - // Only consider auto closing the pair if a space follows or if another autoclosed pair follows + if (!lineAfter.startsWith(subAutoClosingPairClose)) { + isSubAutoClosingPairPresent = false; + } + + // Only consider auto closing the pair if an allowed character follows or if another autoclosed pair closing brace follows if (lineText.length > position.column - 1) { const characterAfter = lineText.charAt(position.column - 1); - const isBeforeCloseBrace = TypeOperations._isBeforeClosingBrace(config, autoClosingPair, characterAfter); + const isBeforeCloseBrace = TypeOperations._isBeforeClosingBrace(config, lineAfter); if (!isBeforeCloseBrace && !shouldAutoCloseBefore(characterAfter)) { return null; @@ -612,14 +631,18 @@ export class TypeOperations { } } - return autoClosingPair; + if (isSubAutoClosingPairPresent) { + return autoClosingPair.close.substring(0, autoClosingPair.close.length - subAutoClosingPairClose.length); + } else { + return autoClosingPair.close; + } } - private static _runAutoClosingOpenCharType(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, insertOpenCharacter: boolean, autoClosingPair: StandardAutoClosingPairConditional): EditOperationResult { + private static _runAutoClosingOpenCharType(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, insertOpenCharacter: boolean, autoClosingPairClose: string): EditOperationResult { let commands: ICommand[] = []; for (let i = 0, len = selections.length; i < len; i++) { const selection = selections[i]; - commands[i] = new TypeWithAutoClosingCommand(selection, ch, insertOpenCharacter, autoClosingPair.close); + commands[i] = new TypeWithAutoClosingCommand(selection, ch, insertOpenCharacter, autoClosingPairClose); } return new EditOperationResult(EditOperationType.Typing, commands, { shouldPushStackElementBefore: true, @@ -794,9 +817,9 @@ export class TypeOperations { }); } - const autoClosingPairOpenCharType = this._isAutoClosingOpenCharType(config, model, selections, ch, false); - if (autoClosingPairOpenCharType) { - return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch, false, autoClosingPairOpenCharType); + const autoClosingPairClose = this._getAutoClosingPairClose(config, model, selections, ch, false); + if (autoClosingPairClose !== null) { + return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch, false, autoClosingPairClose); } return null; @@ -838,9 +861,9 @@ export class TypeOperations { } if (!isDoingComposition) { - const autoClosingPairOpenCharType = this._isAutoClosingOpenCharType(config, model, selections, ch, true); - if (autoClosingPairOpenCharType) { - return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch, true, autoClosingPairOpenCharType); + const autoClosingPairClose = this._getAutoClosingPairClose(config, model, selections, ch, true); + if (autoClosingPairClose) { + return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch, true, autoClosingPairClose); } } diff --git a/src/vs/editor/common/controller/cursorWordOperations.ts b/src/vs/editor/common/controller/cursorWordOperations.ts index c67d8f968..73820f9af 100644 --- a/src/vs/editor/common/controller/cursorWordOperations.ts +++ b/src/vs/editor/common/controller/cursorWordOperations.ts @@ -384,7 +384,7 @@ export class WordOperations { return selection; } - if (DeleteOperations.isAutoClosingPairDelete(ctx.autoClosingBrackets, ctx.autoClosingQuotes, ctx.autoClosingPairs.autoClosingPairsOpen, ctx.model, [ctx.selection])) { + if (DeleteOperations.isAutoClosingPairDelete(ctx.autoClosingBrackets, ctx.autoClosingQuotes, ctx.autoClosingPairs.autoClosingPairsOpenByEnd, ctx.model, [ctx.selection])) { const position = ctx.selection.getPosition(); return new Range(position.lineNumber, position.column - 1, position.lineNumber, position.column + 1); } @@ -438,6 +438,122 @@ export class WordOperations { return new Range(lineNumber, column, position.lineNumber, position.column); } + public static deleteInsideWord(wordSeparators: WordCharacterClassifier, model: ITextModel, selection: Selection): Range { + if (!selection.isEmpty()) { + return selection; + } + + const position = new Position(selection.positionLineNumber, selection.positionColumn); + + let r = this._deleteInsideWordWhitespace(model, position); + if (r) { + return r; + } + + return this._deleteInsideWordDetermineDeleteRange(wordSeparators, model, position); + } + + private static _charAtIsWhitespace(str: string, index: number): boolean { + const charCode = str.charCodeAt(index); + return (charCode === CharCode.Space || charCode === CharCode.Tab); + } + + private static _deleteInsideWordWhitespace(model: ICursorSimpleModel, position: Position): Range | null { + const lineContent = model.getLineContent(position.lineNumber); + const lineContentLength = lineContent.length; + + if (lineContentLength === 0) { + // empty line + return null; + } + + let leftIndex = Math.max(position.column - 2, 0); + if (!this._charAtIsWhitespace(lineContent, leftIndex)) { + // touches a non-whitespace character to the left + return null; + } + + let rightIndex = Math.min(position.column - 1, lineContentLength - 1); + if (!this._charAtIsWhitespace(lineContent, rightIndex)) { + // touches a non-whitespace character to the right + return null; + } + + // walk over whitespace to the left + while (leftIndex > 0 && this._charAtIsWhitespace(lineContent, leftIndex - 1)) { + leftIndex--; + } + + // walk over whitespace to the right + while (rightIndex + 1 < lineContentLength && this._charAtIsWhitespace(lineContent, rightIndex + 1)) { + rightIndex++; + } + + return new Range(position.lineNumber, leftIndex + 1, position.lineNumber, rightIndex + 2); + } + + private static _deleteInsideWordDetermineDeleteRange(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, position: Position): Range { + const lineContent = model.getLineContent(position.lineNumber); + const lineLength = lineContent.length; + if (lineLength === 0) { + // empty line + if (position.lineNumber > 1) { + return new Range(position.lineNumber - 1, model.getLineMaxColumn(position.lineNumber - 1), position.lineNumber, 1); + } else { + if (position.lineNumber < model.getLineCount()) { + return new Range(position.lineNumber, 1, position.lineNumber + 1, 1); + } else { + // empty model + return new Range(position.lineNumber, 1, position.lineNumber, 1); + } + } + } + + const touchesWord = (word: IFindWordResult) => { + return (word.start + 1 <= position.column && position.column <= word.end + 1); + }; + const createRangeWithPosition = (startColumn: number, endColumn: number) => { + startColumn = Math.min(startColumn, position.column); + endColumn = Math.max(endColumn, position.column); + return new Range(position.lineNumber, startColumn, position.lineNumber, endColumn); + }; + const deleteWordAndAdjacentWhitespace = (word: IFindWordResult) => { + let startColumn = word.start + 1; + let endColumn = word.end + 1; + let expandedToTheRight = false; + while (endColumn - 1 < lineLength && this._charAtIsWhitespace(lineContent, endColumn - 1)) { + expandedToTheRight = true; + endColumn++; + } + if (!expandedToTheRight) { + while (startColumn > 1 && this._charAtIsWhitespace(lineContent, startColumn - 2)) { + startColumn--; + } + } + return createRangeWithPosition(startColumn, endColumn); + }; + + const prevWordOnLine = WordOperations._findPreviousWordOnLine(wordSeparators, model, position); + if (prevWordOnLine && touchesWord(prevWordOnLine)) { + return deleteWordAndAdjacentWhitespace(prevWordOnLine); + } + const nextWordOnLine = WordOperations._findNextWordOnLine(wordSeparators, model, position); + if (nextWordOnLine && touchesWord(nextWordOnLine)) { + return deleteWordAndAdjacentWhitespace(nextWordOnLine); + } + if (prevWordOnLine && nextWordOnLine) { + return createRangeWithPosition(prevWordOnLine.end + 1, nextWordOnLine.start + 1); + } + if (prevWordOnLine) { + return createRangeWithPosition(prevWordOnLine.start + 1, prevWordOnLine.end + 1); + } + if (nextWordOnLine) { + return createRangeWithPosition(nextWordOnLine.start + 1, nextWordOnLine.end + 1); + } + + return createRangeWithPosition(1, lineLength + 1); + } + public static _deleteWordPartLeft(model: ICursorSimpleModel, selection: Selection): Range { if (!selection.isEmpty()) { return selection; diff --git a/src/vs/editor/common/editorContextKeys.ts b/src/vs/editor/common/editorContextKeys.ts index 487e6d4f0..a3a3b3de1 100644 --- a/src/vs/editor/common/editorContextKeys.ts +++ b/src/vs/editor/common/editorContextKeys.ts @@ -24,6 +24,7 @@ export namespace EditorContextKeys { export const textInputFocus = new RawContextKey('textInputFocus', false); export const readOnly = new RawContextKey('editorReadonly', false); + export const inDiffEditor = new RawContextKey('inDiffEditor', false); export const columnSelection = new RawContextKey('editorColumnSelection', false); export const writable = readOnly.toNegated(); export const hasNonEmptySelection = new RawContextKey('editorHasSelection', false); diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index fd5c9ccbb..8f4d53a84 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -695,6 +695,11 @@ export interface ITextModel { */ getEOL(): string; + /** + * Get the end of line sequence predominantly used in the text buffer. + */ + getEndOfLineSequence(): EndOfLineSequence; + /** * Get the minimum legal column for line at `lineNumber` */ @@ -850,7 +855,12 @@ export interface ITextModel { /** * @internal */ - hasSemanticTokens(): boolean; + hasCompleteSemanticTokens(): boolean; + + /** + * @internal + */ + hasSomeSemanticTokens(): boolean; /** * Flush all tokenization state. @@ -1089,12 +1099,17 @@ export interface ITextModel { detectIndentation(defaultInsertSpaces: boolean, defaultTabSize: number): void; /** - * Push a stack element onto the undo stack. This acts as an undo/redo point. - * The idea is to use `pushEditOperations` to edit the model and then to - * `pushStackElement` to create an undo/redo stop point. + * Close the current undo-redo element. + * This offers a way to create an undo/redo stop point. */ pushStackElement(): void; + /** + * Open the current undo-redo element. + * This offers a way to remove the current undo/redo stop point. + */ + popStackElement(): void; + /** * Push edit operations, basically editing the model. This is the preferred way * of editing the model. The edit operations will land on the undo stack. @@ -1138,7 +1153,7 @@ export interface ITextModel { _applyRedo(changes: TextChange[], eol: EndOfLineSequence, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void; /** - * Undo edit operations until the first previous stop point created by `pushStackElement`. + * Undo edit operations until the previous undo/redo point. * The inverse edit operations will be pushed on the redo stack. * @internal */ @@ -1151,7 +1166,7 @@ export interface ITextModel { canUndo(): boolean; /** - * Redo edit operations until the next stop point created by `pushStackElement`. + * Redo edit operations until the next undo/redo point. * The inverse edit operations will be pushed on the undo stack. * @internal */ diff --git a/src/vs/editor/common/model/editStack.ts b/src/vs/editor/common/model/editStack.ts index 0928d7d0e..d06f447b0 100644 --- a/src/vs/editor/common/model/editStack.ts +++ b/src/vs/editor/common/model/editStack.ts @@ -199,6 +199,12 @@ export class SingleModelEditStackElement implements IResourceUndoRedoElement { } } + public open(): void { + if (!(this._data instanceof SingleModelEditStackData)) { + this._data = SingleModelEditStackData.deserialize(this._data); + } + } + public undo(): void { if (URI.isUri(this.model)) { // don't have a model @@ -315,6 +321,10 @@ export class MultiModelEditStackElement implements IWorkspaceUndoRedoElement { this._isOpen = false; } + public open(): void { + // cannot reopen + } + public undo(): void { this._isOpen = false; @@ -386,6 +396,13 @@ export class EditStack { } } + public popStackElement(): void { + const lastElement = this._undoRedoService.getLastElement(this._model.uri); + if (isEditStackElement(lastElement)) { + lastElement.open(); + } + } + public clear(): void { this._undoRedoService.removeElements(this._model.uri); } diff --git a/src/vs/editor/common/model/mirrorTextModel.ts b/src/vs/editor/common/model/mirrorTextModel.ts index 6a1272552..9e14ea636 100644 --- a/src/vs/editor/common/model/mirrorTextModel.ts +++ b/src/vs/editor/common/model/mirrorTextModel.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { splitLines } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { Position } from 'vs/editor/common/core/position'; import { IRange } from 'vs/editor/common/core/range'; @@ -131,7 +132,7 @@ export class MirrorTextModel { // Nothing to insert return; } - let insertLines = insertText.split(/\r\n|\r|\n/); + let insertLines = splitLines(insertText); if (insertLines.length === 1) { // Inserting text on one line this._setLineText(position.lineNumber - 1, diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts index 62ab29299..2b73835b2 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts @@ -12,7 +12,7 @@ import { PieceTreeBase, StringBuffer } from 'vs/editor/common/model/pieceTreeTex import { SearchData } from 'vs/editor/common/model/textModelSearch'; import { countEOL, StringEOL } from 'vs/editor/common/model/tokensStore'; import { TextChange } from 'vs/editor/common/model/textChange'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; export interface IValidatedEditOperation { sortIndex: number; @@ -32,26 +32,24 @@ export interface IReverseSingleEditOperation extends IValidEditOperation { sortIndex: number; } -export class PieceTreeTextBuffer implements ITextBuffer, IDisposable { - private readonly _pieceTree: PieceTreeBase; +export class PieceTreeTextBuffer extends Disposable implements ITextBuffer { + private _pieceTree: PieceTreeBase; private readonly _BOM: string; private _mightContainRTL: boolean; private _mightContainUnusualLineTerminators: boolean; private _mightContainNonBasicASCII: boolean; - private readonly _onDidChangeContent: Emitter = new Emitter(); + private readonly _onDidChangeContent: Emitter = this._register(new Emitter()); public readonly onDidChangeContent: Event = this._onDidChangeContent.event; constructor(chunks: StringBuffer[], BOM: string, eol: '\r\n' | '\n', containsRTL: boolean, containsUnusualLineTerminators: boolean, isBasicASCII: boolean, eolNormalized: boolean) { + super(); this._BOM = BOM; this._mightContainNonBasicASCII = !isBasicASCII; this._mightContainRTL = containsRTL; this._mightContainUnusualLineTerminators = containsUnusualLineTerminators; this._pieceTree = new PieceTreeBase(chunks, eol, eolNormalized); } - dispose(): void { - this._onDidChangeContent.dispose(); - } // #region TextBuffer public equals(other: ITextBuffer): boolean { diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 24f25a4e5..35dd46640 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -387,6 +387,9 @@ export class TextModel extends Disposable implements model.ITextModel { this._isDisposed = true; super.dispose(); this._isDisposing = false; + // Manually release reference to previous text buffer to avoid large leaks + // in case someone leaks a TextModel reference + this._buffer = createTextBuffer('', this._options.defaultEOL); } private _assertNotDisposed(): void { @@ -830,6 +833,15 @@ export class TextModel extends Disposable implements model.ITextModel { return this._buffer.getEOL(); } + public getEndOfLineSequence(): model.EndOfLineSequence { + this._assertNotDisposed(); + return ( + this._buffer.getEOL() === '\n' + ? model.EndOfLineSequence.LF + : model.EndOfLineSequence.CRLF + ); + } + public getLineMinColumn(lineNumber: number): number { this._assertNotDisposed(); return 1; @@ -1213,6 +1225,10 @@ export class TextModel extends Disposable implements model.ITextModel { this._commandManager.pushStackElement(); } + public popStackElement(): void { + this._commandManager.popStackElement(); + } + public pushEOL(eol: model.EndOfLineSequence): void { const currentEOL = (this.getEOL() === '\n' ? model.EndOfLineSequence.LF : model.EndOfLineSequence.CRLF); if (currentEOL === eol) { @@ -1868,12 +1884,16 @@ export class TextModel extends Disposable implements model.ITextModel { }); } - public hasSemanticTokens(): boolean { + public hasCompleteSemanticTokens(): boolean { return this._tokens2.isComplete(); } + public hasSomeSemanticTokens(): boolean { + return !this._tokens2.isEmpty(); + } + public setPartialSemanticTokens(range: Range, tokens: MultilineTokens2[]): void { - if (this.hasSemanticTokens()) { + if (this.hasCompleteSemanticTokens()) { return; } const changedRange = this._tokens2.setPartial(range, tokens); diff --git a/src/vs/editor/common/model/tokensStore.ts b/src/vs/editor/common/model/tokensStore.ts index a49ef27a7..bbec0e369 100644 --- a/src/vs/editor/common/model/tokensStore.ts +++ b/src/vs/editor/common/model/tokensStore.ts @@ -884,6 +884,10 @@ export class TokensStore2 { this._isComplete = false; } + public isEmpty(): boolean { + return (this._pieces.length === 0); + } + public set(pieces: MultilineTokens2[] | null, isComplete: boolean): void { this._pieces = pieces || []; this._isComplete = isComplete; diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 0ab1352cf..fe0d7335d 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -19,7 +19,7 @@ import { TokenizationRegistryImpl } from 'vs/editor/common/modes/tokenizationReg import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IMarkerData } from 'vs/platform/markers/common/markers'; import { iconRegistry, Codicon } from 'vs/base/common/codicons'; - +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; /** * Open ended enum at runtime * @internal @@ -819,17 +819,32 @@ export interface DocumentHighlightProvider { } /** - * The rename provider interface defines the contract between extensions and - * the live-rename feature. + * The linked editing range provider interface defines the contract between extensions and + * the linked editing feature. */ -export interface OnTypeRenameProvider { - - wordPattern?: RegExp; +export interface LinkedEditingRangeProvider { /** - * Provide a list of ranges that can be live-renamed together. + * Provide a list of ranges that can be edited together. */ - provideOnTypeRenameRanges(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult<{ ranges: IRange[]; wordPattern?: RegExp; }>; + provideLinkedEditingRanges(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult; +} + +/** + * Represents a list of ranges that can be edited together along with a word pattern to describe valid contents. + */ +export interface LinkedEditingRanges { + /** + * A list of ranges that can be edited together. The ranges must have + * identical length and text content. The ranges cannot overlap + */ + ranges: IRange[]; + + /** + * An optional word pattern that describes valid contents for the given ranges. + * If no pattern is provided, the language configuration's word pattern will be used. + */ + wordPattern?: RegExp; } /** @@ -1359,7 +1374,10 @@ export interface WorkspaceEditMetadata { needsConfirmation: boolean; label: string; description?: string; - iconPath?: { id: string } | URI | { light: URI, dark: URI }; + /** + * @internal + */ + iconPath?: ThemeIcon | URI | { light: URI, dark: URI }; } export interface WorkspaceFileEditOptions { @@ -1367,6 +1385,10 @@ export interface WorkspaceFileEditOptions { ignoreIfNotExists?: boolean; ignoreIfExists?: boolean; recursive?: boolean; + copy?: boolean; + folder?: boolean; + skipTrashBin?: boolean; + maxSize?: number; } export interface WorkspaceFileEdit { @@ -1715,7 +1737,7 @@ export const DocumentHighlightProviderRegistry = new LanguageFeatureRegistry(); +export const LinkedEditingRangeProviderRegistry = new LanguageFeatureRegistry(); /** * @internal diff --git a/src/vs/editor/common/modes/languageConfiguration.ts b/src/vs/editor/common/modes/languageConfiguration.ts index 295fbdc01..a383c57e9 100644 --- a/src/vs/editor/common/modes/languageConfiguration.ts +++ b/src/vs/editor/common/modes/languageConfiguration.ts @@ -294,17 +294,32 @@ export class StandardAutoClosingPairConditional { * @internal */ export class AutoClosingPairs { + // it is useful to be able to get pairs using either end of open and close - public readonly autoClosingPairsOpen: Map; - public readonly autoClosingPairsClose: Map; + /** Key is first character of open */ + public readonly autoClosingPairsOpenByStart: Map; + /** Key is last character of open */ + public readonly autoClosingPairsOpenByEnd: Map; + /** Key is first character of close */ + public readonly autoClosingPairsCloseByStart: Map; + /** Key is last character of close */ + public readonly autoClosingPairsCloseByEnd: Map; + /** Key is close. Only has pairs that are a single character */ + public readonly autoClosingPairsCloseSingleChar: Map; constructor(autoClosingPairs: StandardAutoClosingPairConditional[]) { - this.autoClosingPairsOpen = new Map(); - this.autoClosingPairsClose = new Map(); + this.autoClosingPairsOpenByStart = new Map(); + this.autoClosingPairsOpenByEnd = new Map(); + this.autoClosingPairsCloseByStart = new Map(); + this.autoClosingPairsCloseByEnd = new Map(); + this.autoClosingPairsCloseSingleChar = new Map(); for (const pair of autoClosingPairs) { - appendEntry(this.autoClosingPairsOpen, pair.open.charAt(pair.open.length - 1), pair); - if (pair.close.length === 1) { - appendEntry(this.autoClosingPairsClose, pair.close, pair); + appendEntry(this.autoClosingPairsOpenByStart, pair.open.charAt(0), pair); + appendEntry(this.autoClosingPairsOpenByEnd, pair.open.charAt(pair.open.length - 1), pair); + appendEntry(this.autoClosingPairsCloseByStart, pair.close.charAt(0), pair); + appendEntry(this.autoClosingPairsCloseByEnd, pair.close.charAt(pair.close.length - 1), pair); + if (pair.close.length === 1 && pair.open.length === 1) { + appendEntry(this.autoClosingPairsCloseSingleChar, pair.close, pair); } } } diff --git a/src/vs/editor/common/modes/languageConfigurationRegistry.ts b/src/vs/editor/common/modes/languageConfigurationRegistry.ts index 2d7600d32..ccd20e376 100644 --- a/src/vs/editor/common/modes/languageConfigurationRegistry.ts +++ b/src/vs/editor/common/modes/languageConfigurationRegistry.ts @@ -622,6 +622,12 @@ export class LanguageConfigurationRegistryImpl { return null; } const scopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber, range.startColumn); + + if (scopedLineTokens.firstCharOffset) { + // this line has mixed languages and indentation rules will not work + return null; + } + const indentRulesSupport = this.getIndentRulesSupport(scopedLineTokens.languageId); if (!indentRulesSupport) { return null; diff --git a/src/vs/editor/common/modes/supports/indentRules.ts b/src/vs/editor/common/modes/supports/indentRules.ts index 687115bd4..3241358cc 100644 --- a/src/vs/editor/common/modes/supports/indentRules.ts +++ b/src/vs/editor/common/modes/supports/indentRules.ts @@ -12,6 +12,14 @@ export const enum IndentConsts { UNINDENT_MASK = 0b00001000, } +function resetGlobalRegex(reg: RegExp) { + if (reg.global) { + reg.lastIndex = 0; + } + + return true; +} + export class IndentRulesSupport { private readonly _indentationRules: IndentationRule; @@ -22,7 +30,7 @@ export class IndentRulesSupport { public shouldIncrease(text: string): boolean { if (this._indentationRules) { - if (this._indentationRules.increaseIndentPattern && this._indentationRules.increaseIndentPattern.test(text)) { + if (this._indentationRules.increaseIndentPattern && resetGlobalRegex(this._indentationRules.increaseIndentPattern) && this._indentationRules.increaseIndentPattern.test(text)) { return true; } // if (this._indentationRules.indentNextLinePattern && this._indentationRules.indentNextLinePattern.test(text)) { @@ -33,14 +41,14 @@ export class IndentRulesSupport { } public shouldDecrease(text: string): boolean { - if (this._indentationRules && this._indentationRules.decreaseIndentPattern && this._indentationRules.decreaseIndentPattern.test(text)) { + if (this._indentationRules && this._indentationRules.decreaseIndentPattern && resetGlobalRegex(this._indentationRules.decreaseIndentPattern) && this._indentationRules.decreaseIndentPattern.test(text)) { return true; } return false; } public shouldIndentNextLine(text: string): boolean { - if (this._indentationRules && this._indentationRules.indentNextLinePattern && this._indentationRules.indentNextLinePattern.test(text)) { + if (this._indentationRules && this._indentationRules.indentNextLinePattern && resetGlobalRegex(this._indentationRules.indentNextLinePattern) && this._indentationRules.indentNextLinePattern.test(text)) { return true; } @@ -49,7 +57,7 @@ export class IndentRulesSupport { public shouldIgnore(text: string): boolean { // the text matches `unIndentedLinePattern` - if (this._indentationRules && this._indentationRules.unIndentedLinePattern && this._indentationRules.unIndentedLinePattern.test(text)) { + if (this._indentationRules && this._indentationRules.unIndentedLinePattern && resetGlobalRegex(this._indentationRules.unIndentedLinePattern) && this._indentationRules.unIndentedLinePattern.test(text)) { return true; } diff --git a/src/vs/editor/common/modes/textToHtmlTokenizer.ts b/src/vs/editor/common/modes/textToHtmlTokenizer.ts index dd64e79de..c48666416 100644 --- a/src/vs/editor/common/modes/textToHtmlTokenizer.ts +++ b/src/vs/editor/common/modes/textToHtmlTokenizer.ts @@ -101,7 +101,7 @@ export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens function _tokenizeToString(text: string, tokenizationSupport: IReducedTokenizationSupport): string { let result = `
`; - let lines = text.split(/\r\n|\r|\n/); + let lines = strings.splitLines(text); let currentState = tokenizationSupport.getInitialState(); for (let i = 0, len = lines.length; i < len; i++) { let line = lines[i]; diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index 69592e735..68a3d1253 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -530,36 +530,30 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { private static readonly _suggestionsLimit = 10000; - public async textualSuggest(modelUrl: string, position: IPosition, wordDef: string, wordDefFlags: string): Promise<{ words: string[], duration: number } | null> { - const model = this._getModel(modelUrl); - if (!model) { - return null; - } + public async textualSuggest(modelUrls: string[], leadingWord: string | undefined, wordDef: string, wordDefFlags: string): Promise<{ words: string[], duration: number } | null> { const sw = new StopWatch(true); - const words: string[] = []; - const seen = new Set(); const wordDefRegExp = new RegExp(wordDef, wordDefFlags); + const seen = new Set(); - const wordAt = model.getWordAtPosition(position, wordDefRegExp); - if (wordAt) { - seen.add(model.getValueInRange(wordAt)); - } - - for (let word of model.words(wordDefRegExp)) { - if (seen.has(word)) { + outer: for (let url of modelUrls) { + const model = this._getModel(url); + if (!model) { continue; } - seen.add(word); - if (!isNaN(Number(word))) { - continue; - } - words.push(word); - if (seen.size > EditorSimpleWorker._suggestionsLimit) { - break; + + for (let word of model.words(wordDefRegExp)) { + if (word === leadingWord || !isNaN(Number(word))) { + continue; + } + seen.add(word); + if (seen.size > EditorSimpleWorker._suggestionsLimit) { + break outer; + } } } - return { words, duration: sw.elapsed() }; + + return { words: Array.from(seen), duration: sw.elapsed() }; } diff --git a/src/vs/editor/common/services/editorWorkerServiceImpl.ts b/src/vs/editor/common/services/editorWorkerServiceImpl.ts index 5976df3c2..dc6a0f706 100644 --- a/src/vs/editor/common/services/editorWorkerServiceImpl.ts +++ b/src/vs/editor/common/services/editorWorkerServiceImpl.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IntervalTimer } from 'vs/base/common/async'; +import { IntervalTimer, timeout } from 'vs/base/common/async'; import { Disposable, IDisposable, dispose, toDisposable, DisposableStore } 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/worker/defaultWorkerFactory'; -import { IPosition, Position } from 'vs/editor/common/core/position'; +import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { IChange } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; @@ -105,7 +105,7 @@ export class EditorWorkerServiceImpl extends Disposable implements IEditorWorker const sw = StopWatch.create(true); const result = this._workerManager.withWorker().then(client => client.computeMoreMinimalEdits(resource, edits)); result.finally(() => this._logService.trace('FORMAT#computeMoreMinimalEdits', resource.toString(true), sw.elapsed())); - return result; + return Promise.race([result, timeout(1000).then(() => edits)]); } else { return Promise.resolve(undefined); @@ -148,20 +148,47 @@ class WordBasedCompletionItemProvider implements modes.CompletionItemProvider { } async provideCompletionItems(model: ITextModel, position: Position): Promise { - const { wordBasedSuggestions } = this._configurationService.getValue<{ wordBasedSuggestions?: boolean }>(model.uri, position, 'editor'); - if (!wordBasedSuggestions) { + type WordBasedSuggestionsConfig = { + wordBasedSuggestions?: boolean, + wordBasedSuggestionsMode?: 'currentDocument' | 'matchingDocuments' | 'allDocuments' + }; + const config = this._configurationService.getValue(model.uri, position, 'editor'); + if (!config.wordBasedSuggestions) { return undefined; } - if (!canSyncModel(this._modelService, model.uri)) { - return undefined; // File too large + + const models: URI[] = []; + if (config.wordBasedSuggestionsMode === 'currentDocument') { + // only current file and only if not too large + if (canSyncModel(this._modelService, model.uri)) { + models.push(model.uri); + } + } else { + // either all files or files of same language + for (const candidate of this._modelService.getModels()) { + if (!canSyncModel(this._modelService, candidate.uri)) { + continue; + } + if (candidate === model) { + models.unshift(candidate.uri); + + } else if (config.wordBasedSuggestionsMode === 'allDocuments' || candidate.getLanguageIdentifier().id === model.getLanguageIdentifier().id) { + models.push(candidate.uri); + } + } } + if (models.length === 0) { + return undefined; // File too large, no other files + } + + const wordDefRegExp = LanguageConfigurationRegistry.getWordDefinition(model.getLanguageIdentifier().id); const word = model.getWordAtPosition(position); const replace = !word ? Range.fromPositions(position) : new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn); const insert = replace.setEndPosition(position.lineNumber, position.column); const client = await this._workerManager.withWorker(); - const data = await client.textualSuggest(model.uri, position); + const data = await client.textualSuggest(models, word?.word, wordDefRegExp); if (!data) { return undefined; } @@ -463,17 +490,11 @@ export class EditorWorkerClient extends Disposable { }); } - public textualSuggest(resource: URI, position: IPosition): Promise<{ words: string[], duration: number } | null> { - return this._withSyncedResources([resource]).then(proxy => { - let model = this._modelService.getModel(resource); - if (!model) { - return null; - } - let wordDefRegExp = LanguageConfigurationRegistry.getWordDefinition(model.getLanguageIdentifier().id); - let wordDef = wordDefRegExp.source; - let wordDefFlags = regExpFlags(wordDefRegExp); - return proxy.textualSuggest(resource.toString(), position, wordDef, wordDefFlags); - }); + public async textualSuggest(resources: URI[], leadingWord: string | undefined, wordDefRegExp: RegExp): Promise<{ words: string[], duration: number } | null> { + const proxy = await this._withSyncedResources(resources); + const wordDef = wordDefRegExp.source; + const wordDefFlags = regExpFlags(wordDefRegExp); + return proxy.textualSuggest(resources.map(r => r.toString()), leadingWord, wordDef, wordDefFlags); } computeWordRanges(resource: URI, range: IRange): Promise<{ [word: string]: IRange[] } | null> { diff --git a/src/vs/editor/common/services/getIconClasses.ts b/src/vs/editor/common/services/getIconClasses.ts index b20a0b7c5..60aca88ad 100644 --- a/src/vs/editor/common/services/getIconClasses.ts +++ b/src/vs/editor/common/services/getIconClasses.ts @@ -93,6 +93,6 @@ export function detectModeId(modelService: IModelService, modeService: IModeServ return modeService.getModeIdByFilepathOrFirstLine(resource); } -export function cssEscape(val: string): string { - return val.replace(/\s/g, '\\$&'); // make sure to not introduce CSS classes from files that contain whitespace +export 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. } diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 7836fa5de..866727686 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -790,6 +790,10 @@ class ModelSemanticColoring extends Disposable { } const provider = this._getSemanticColoringProvider(); if (!provider) { + if (this._currentDocumentResponse) { + // there are semantic tokens set + this._model.setSemanticTokens(null, false); + } return; } this._currentDocumentRequestCancellationTokenSource = new CancellationTokenSource(); diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 01bf8eabf..8c772e3c3 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -179,113 +179,119 @@ export enum EditorOption { automaticLayout = 9, autoSurround = 10, codeLens = 11, - colorDecorators = 12, - columnSelection = 13, - comments = 14, - contextmenu = 15, - copyWithSyntaxHighlighting = 16, - cursorBlinking = 17, - cursorSmoothCaretAnimation = 18, - cursorStyle = 19, - cursorSurroundingLines = 20, - cursorSurroundingLinesStyle = 21, - cursorWidth = 22, - disableLayerHinting = 23, - disableMonospaceOptimizations = 24, - dragAndDrop = 25, - emptySelectionClipboard = 26, - extraEditorClassName = 27, - fastScrollSensitivity = 28, - find = 29, - fixedOverflowWidgets = 30, - folding = 31, - foldingStrategy = 32, - foldingHighlight = 33, - unfoldOnClickAfterEndOfLine = 34, - fontFamily = 35, - fontInfo = 36, - fontLigatures = 37, - fontSize = 38, - fontWeight = 39, - formatOnPaste = 40, - formatOnType = 41, - glyphMargin = 42, - gotoLocation = 43, - hideCursorInOverviewRuler = 44, - highlightActiveIndentGuide = 45, - hover = 46, - inDiffEditor = 47, - letterSpacing = 48, - lightbulb = 49, - lineDecorationsWidth = 50, - lineHeight = 51, - lineNumbers = 52, - lineNumbersMinChars = 53, - links = 54, - matchBrackets = 55, - minimap = 56, - mouseStyle = 57, - mouseWheelScrollSensitivity = 58, - mouseWheelZoom = 59, - multiCursorMergeOverlapping = 60, - multiCursorModifier = 61, - multiCursorPaste = 62, - occurrencesHighlight = 63, - overviewRulerBorder = 64, - overviewRulerLanes = 65, - padding = 66, - parameterHints = 67, - peekWidgetDefaultFocus = 68, - definitionLinkOpensInPeek = 69, - quickSuggestions = 70, - quickSuggestionsDelay = 71, - readOnly = 72, - renameOnType = 73, - renderControlCharacters = 74, - renderIndentGuides = 75, - renderFinalNewline = 76, - renderLineHighlight = 77, - renderLineHighlightOnlyWhenFocus = 78, - renderValidationDecorations = 79, - renderWhitespace = 80, - revealHorizontalRightPadding = 81, - roundedSelection = 82, - rulers = 83, - scrollbar = 84, - scrollBeyondLastColumn = 85, - scrollBeyondLastLine = 86, - scrollPredominantAxis = 87, - selectionClipboard = 88, - selectionHighlight = 89, - selectOnLineNumbers = 90, - showFoldingControls = 91, - showUnused = 92, - snippetSuggestions = 93, - smoothScrolling = 94, - stopRenderingLineAfter = 95, - suggest = 96, - suggestFontSize = 97, - suggestLineHeight = 98, - suggestOnTriggerCharacters = 99, - suggestSelection = 100, - tabCompletion = 101, - tabIndex = 102, - unusualLineTerminators = 103, - useTabStops = 104, - wordSeparators = 105, - wordWrap = 106, - wordWrapBreakAfterCharacters = 107, - wordWrapBreakBeforeCharacters = 108, - wordWrapColumn = 109, - wordWrapMinified = 110, - wrappingIndent = 111, - wrappingStrategy = 112, - showDeprecated = 113, - editorClassName = 114, - pixelRatio = 115, - tabFocusMode = 116, - layoutInfo = 117, - wrappingInfo = 118 + codeLensFontFamily = 12, + codeLensFontSize = 13, + colorDecorators = 14, + columnSelection = 15, + comments = 16, + contextmenu = 17, + copyWithSyntaxHighlighting = 18, + cursorBlinking = 19, + cursorSmoothCaretAnimation = 20, + cursorStyle = 21, + cursorSurroundingLines = 22, + cursorSurroundingLinesStyle = 23, + cursorWidth = 24, + disableLayerHinting = 25, + disableMonospaceOptimizations = 26, + dragAndDrop = 27, + emptySelectionClipboard = 28, + extraEditorClassName = 29, + fastScrollSensitivity = 30, + find = 31, + fixedOverflowWidgets = 32, + folding = 33, + foldingStrategy = 34, + foldingHighlight = 35, + unfoldOnClickAfterEndOfLine = 36, + fontFamily = 37, + fontInfo = 38, + fontLigatures = 39, + fontSize = 40, + fontWeight = 41, + formatOnPaste = 42, + formatOnType = 43, + glyphMargin = 44, + gotoLocation = 45, + hideCursorInOverviewRuler = 46, + highlightActiveIndentGuide = 47, + hover = 48, + inDiffEditor = 49, + letterSpacing = 50, + lightbulb = 51, + lineDecorationsWidth = 52, + lineHeight = 53, + lineNumbers = 54, + lineNumbersMinChars = 55, + linkedEditing = 56, + links = 57, + matchBrackets = 58, + minimap = 59, + mouseStyle = 60, + mouseWheelScrollSensitivity = 61, + mouseWheelZoom = 62, + multiCursorMergeOverlapping = 63, + multiCursorModifier = 64, + multiCursorPaste = 65, + occurrencesHighlight = 66, + overviewRulerBorder = 67, + overviewRulerLanes = 68, + padding = 69, + parameterHints = 70, + peekWidgetDefaultFocus = 71, + definitionLinkOpensInPeek = 72, + quickSuggestions = 73, + quickSuggestionsDelay = 74, + readOnly = 75, + renameOnType = 76, + renderControlCharacters = 77, + renderIndentGuides = 78, + renderFinalNewline = 79, + renderLineHighlight = 80, + renderLineHighlightOnlyWhenFocus = 81, + renderValidationDecorations = 82, + renderWhitespace = 83, + revealHorizontalRightPadding = 84, + roundedSelection = 85, + rulers = 86, + scrollbar = 87, + scrollBeyondLastColumn = 88, + scrollBeyondLastLine = 89, + scrollPredominantAxis = 90, + selectionClipboard = 91, + selectionHighlight = 92, + selectOnLineNumbers = 93, + showFoldingControls = 94, + showUnused = 95, + snippetSuggestions = 96, + smartSelect = 97, + smoothScrolling = 98, + stickyTabStops = 99, + stopRenderingLineAfter = 100, + suggest = 101, + suggestFontSize = 102, + suggestLineHeight = 103, + suggestOnTriggerCharacters = 104, + suggestSelection = 105, + tabCompletion = 106, + tabIndex = 107, + unusualLineTerminators = 108, + useTabStops = 109, + wordSeparators = 110, + wordWrap = 111, + wordWrapBreakAfterCharacters = 112, + wordWrapBreakBeforeCharacters = 113, + wordWrapColumn = 114, + wordWrapOverride1 = 115, + wordWrapOverride2 = 116, + wrappingIndent = 117, + wrappingStrategy = 118, + showDeprecated = 119, + editorClassName = 120, + pixelRatio = 121, + tabFocusMode = 122, + layoutInfo = 123, + wrappingInfo = 124 } /** diff --git a/src/vs/editor/common/view/viewEvents.ts b/src/vs/editor/common/view/viewEvents.ts index 804d1b977..bcdc69bb3 100644 --- a/src/vs/editor/common/view/viewEvents.ts +++ b/src/vs/editor/common/view/viewEvents.ts @@ -11,6 +11,8 @@ import { ScrollType } from 'vs/editor/common/editorCommon'; import { IModelDecorationsChangedEvent } from 'vs/editor/common/model/textModelEvents'; export const enum ViewEventType { + ViewCompositionStart, + ViewCompositionEnd, ViewConfigurationChanged, ViewCursorStateChanged, ViewDecorationsChanged, @@ -29,6 +31,16 @@ export const enum ViewEventType { ViewZonesChanged, } +export class ViewCompositionStartEvent { + public readonly type = ViewEventType.ViewCompositionStart; + constructor() { } +} + +export class ViewCompositionEndEvent { + public readonly type = ViewEventType.ViewCompositionEnd; + constructor() { } +} + export class ViewConfigurationChangedEvent { public readonly type = ViewEventType.ViewConfigurationChanged; @@ -285,7 +297,9 @@ export class ViewZonesChangedEvent { } export type ViewEvent = ( - ViewConfigurationChangedEvent + ViewCompositionStartEvent + | ViewCompositionEndEvent + | ViewConfigurationChangedEvent | ViewCursorStateChangedEvent | ViewDecorationsChangedEvent | ViewFlushedEvent diff --git a/src/vs/editor/common/viewLayout/lineDecorations.ts b/src/vs/editor/common/viewLayout/lineDecorations.ts index 74428ae28..bd72baa14 100644 --- a/src/vs/editor/common/viewLayout/lineDecorations.ts +++ b/src/vs/editor/common/viewLayout/lineDecorations.ts @@ -42,6 +42,24 @@ export class LineDecoration { return true; } + public static extractWrapped(arr: LineDecoration[], startOffset: number, endOffset: number): LineDecoration[] { + if (arr.length === 0) { + return arr; + } + const startColumn = startOffset + 1; + const endColumn = endOffset + 1; + const lineLength = endOffset - startOffset; + const r = []; + let rLength = 0; + for (const dec of arr) { + if (dec.endColumn <= startColumn || dec.startColumn >= endColumn) { + continue; + } + r[rLength++] = new LineDecoration(Math.max(1, dec.startColumn - startColumn + 1), Math.min(lineLength + 1, dec.endColumn - startColumn + 1), dec.className, dec.type); + } + return r; + } + public static filter(lineDecorations: InlineDecoration[], lineNumber: number, minLineColumn: number, maxLineColumn: number): LineDecoration[] { if (lineDecorations.length === 0) { return []; diff --git a/src/vs/editor/common/viewLayout/linesLayout.ts b/src/vs/editor/common/viewLayout/linesLayout.ts index ceef064de..a564e9a88 100644 --- a/src/vs/editor/common/viewLayout/linesLayout.ts +++ b/src/vs/editor/common/viewLayout/linesLayout.ts @@ -546,6 +546,23 @@ export class LinesLayout { return verticalOffset > totalHeight; } + public isInTopPadding(verticalOffset: number): boolean { + if (this._paddingTop === 0) { + return false; + } + this._checkPendingChanges(); + return (verticalOffset < this._paddingTop); + } + + public isInBottomPadding(verticalOffset: number): boolean { + if (this._paddingBottom === 0) { + return false; + } + this._checkPendingChanges(); + const totalHeight = this.getLinesTotalHeight(); + return (verticalOffset >= totalHeight - this._paddingBottom); + } + /** * Find the first line number that is at or after vertical offset `verticalOffset`. * i.e. if getVerticalOffsetForLine(line) is x and getVerticalOffsetForLine(line + 1) is y, then diff --git a/src/vs/editor/common/viewLayout/viewLayout.ts b/src/vs/editor/common/viewLayout/viewLayout.ts index 0964eac91..24059d23a 100644 --- a/src/vs/editor/common/viewLayout/viewLayout.ts +++ b/src/vs/editor/common/viewLayout/viewLayout.ts @@ -364,6 +364,13 @@ export class ViewLayout extends Disposable implements IViewLayout { public isAfterLines(verticalOffset: number): boolean { return this._linesLayout.isAfterLines(verticalOffset); } + public isInTopPadding(verticalOffset: number): boolean { + return this._linesLayout.isInTopPadding(verticalOffset); + } + isInBottomPadding(verticalOffset: number): boolean { + return this._linesLayout.isInBottomPadding(verticalOffset); + } + public getLineNumberAtVerticalOffset(verticalOffset: number): number { return this._linesLayout.getLineNumberAtOrAfterVerticalOffset(verticalOffset); } diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index 1b75f0f7c..49b43c10e 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -127,7 +127,7 @@ export class RenderLineInput { this.containsRTL = containsRTL; this.fauxIndentLength = fauxIndentLength; this.lineTokens = lineTokens; - this.lineDecorations = lineDecorations; + this.lineDecorations = lineDecorations.sort(LineDecoration.compare); this.tabSize = tabSize; this.startVisibleColumn = startVisibleColumn; this.spaceWidth = spaceWidth; diff --git a/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts b/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts index 549496d00..67a3b90fa 100644 --- a/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts +++ b/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts @@ -7,8 +7,9 @@ import { CharCode } from 'vs/base/common/charCode'; import * as strings from 'vs/base/common/strings'; import { WrappingIndent, IComputedEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions'; import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier'; -import { ILineBreaksComputerFactory, LineBreakData, ILineBreaksComputer } from 'vs/editor/common/viewModel/splitLinesCollection'; +import { ILineBreaksComputerFactory } from 'vs/editor/common/viewModel/splitLinesCollection'; import { FontInfo } from 'vs/editor/common/config/fontInfo'; +import { ILineBreaksComputer, LineBreakData } from 'vs/editor/common/viewModel/viewModel'; const enum CharacterClass { NONE = 0, diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 3a3eab70a..50765f8ee 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -12,69 +12,11 @@ import { EndOfLinePreference, IActiveIndentGuideInfo, IModelDecoration, IModelDe import { ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModel'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { PrefixSumIndexOfResult } from 'vs/editor/common/viewModel/prefixSumComputer'; -import { ICoordinatesConverter, IOverviewRulerDecorations, ViewLineData } from 'vs/editor/common/viewModel/viewModel'; +import { ICoordinatesConverter, ILineBreaksComputer, IOverviewRulerDecorations, LineBreakData, ViewLineData } from 'vs/editor/common/viewModel/viewModel'; import { IDisposable } from 'vs/base/common/lifecycle'; import { FontInfo } from 'vs/editor/common/config/fontInfo'; import { EditorTheme } from 'vs/editor/common/view/viewContext'; -export class OutputPosition { - outputLineIndex: number; - outputOffset: number; - - constructor(outputLineIndex: number, outputOffset: number) { - this.outputLineIndex = outputLineIndex; - this.outputOffset = outputOffset; - } -} - -export class LineBreakData { - constructor( - public breakOffsets: number[], - public breakOffsetsVisibleColumn: number[], - public wrappedTextIndentLength: number - ) { } - - public static getInputOffsetOfOutputPosition(breakOffsets: number[], outputLineIndex: number, outputOffset: number): number { - if (outputLineIndex === 0) { - return outputOffset; - } else { - return breakOffsets[outputLineIndex - 1] + outputOffset; - } - } - - public static getOutputPositionOfInputOffset(breakOffsets: number[], inputOffset: number): OutputPosition { - let low = 0; - let high = breakOffsets.length - 1; - let mid = 0; - let midStart = 0; - - while (low <= high) { - mid = low + ((high - low) / 2) | 0; - - const midStop = breakOffsets[mid]; - midStart = mid > 0 ? breakOffsets[mid - 1] : 0; - - if (inputOffset < midStart) { - high = mid - 1; - } else if (inputOffset >= midStop) { - low = mid + 1; - } else { - break; - } - } - - return new OutputPosition(mid, inputOffset - midStart); - } -} - -export interface ILineBreaksComputer { - /** - * Pass in `previousLineBreakData` if the only difference is in breaking columns!!! - */ - addRequest(lineText: string, previousLineBreakData: LineBreakData | null): void; - finalize(): (LineBreakData | null)[]; -} - export interface ILineBreaksComputerFactory { createLineBreaksComputer(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent): ILineBreaksComputer; } @@ -174,6 +116,10 @@ export class CoordinatesConverter implements ICoordinatesConverter { public modelPositionIsVisible(modelPosition: Position): boolean { return this._lines.modelPositionIsVisible(modelPosition.lineNumber, modelPosition.column); } + + public getModelLineViewLineCount(modelLineNumber: number): number { + return this._lines.getModelLineViewLineCount(modelLineNumber); + } } const enum IndentGuideRepeatOption { @@ -473,6 +419,14 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return this.lines[modelLineNumber - 1].isVisible(); } + public getModelLineViewLineCount(modelLineNumber: number): number { + if (modelLineNumber < 1 || modelLineNumber > this.lines.length) { + // invalid arguments + return 1; + } + return this.lines[modelLineNumber - 1].getViewLineCount(); + } + public setTabSize(newTabSize: number): boolean { if (this.tabSize === newTabSize) { return false; @@ -1431,6 +1385,9 @@ export class IdentityCoordinatesConverter implements ICoordinatesConverter { return true; } + public getModelLineViewLineCount(modelLineNumber: number): number { + return 1; + } } export class IdentityLinesCollection implements IViewModelLinesCollection { diff --git a/src/vs/editor/common/viewModel/viewEventHandler.ts b/src/vs/editor/common/viewModel/viewEventHandler.ts index aad7a0b49..e73ba9679 100644 --- a/src/vs/editor/common/viewModel/viewEventHandler.ts +++ b/src/vs/editor/common/viewModel/viewEventHandler.ts @@ -33,10 +33,15 @@ export class ViewEventHandler extends Disposable { // --- begin event handlers + public onCompositionStart(e: viewEvents.ViewCompositionStartEvent): boolean { + return false; + } + public onCompositionEnd(e: viewEvents.ViewCompositionEndEvent): boolean { + return false; + } public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { return false; } - public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { return false; } @@ -94,6 +99,18 @@ export class ViewEventHandler extends Disposable { switch (e.type) { + case viewEvents.ViewEventType.ViewCompositionStart: + if (this.onCompositionStart(e)) { + shouldRender = true; + } + break; + + case viewEvents.ViewEventType.ViewCompositionEnd: + if (this.onCompositionEnd(e)) { + shouldRender = true; + } + break; + case viewEvents.ViewEventType.ViewConfigurationChanged: if (this.onConfigurationChanged(e)) { shouldRender = true; diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index 91ab8513c..125cb5880 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -61,6 +61,8 @@ export interface IViewLayout { getWhitespaces(): IEditorWhitespace[]; isAfterLines(verticalOffset: number): boolean; + isInTopPadding(verticalOffset: number): boolean; + isInBottomPadding(verticalOffset: number): boolean; getLineNumberAtVerticalOffset(verticalOffset: number): number; getVerticalOffsetForLineNumber(lineNumber: number): number; getWhitespaceAtVerticalOffset(verticalOffset: number): IViewWhitespaceViewportData | null; @@ -82,6 +84,65 @@ export interface ICoordinatesConverter { convertModelPositionToViewPosition(modelPosition: Position): Position; convertModelRangeToViewRange(modelRange: Range): Range; modelPositionIsVisible(modelPosition: Position): boolean; + getModelLineViewLineCount(modelLineNumber: number): number; +} + +export class OutputPosition { + outputLineIndex: number; + outputOffset: number; + + constructor(outputLineIndex: number, outputOffset: number) { + this.outputLineIndex = outputLineIndex; + this.outputOffset = outputOffset; + } +} + +export class LineBreakData { + constructor( + public breakOffsets: number[], + public breakOffsetsVisibleColumn: number[], + public wrappedTextIndentLength: number + ) { } + + public static getInputOffsetOfOutputPosition(breakOffsets: number[], outputLineIndex: number, outputOffset: number): number { + if (outputLineIndex === 0) { + return outputOffset; + } else { + return breakOffsets[outputLineIndex - 1] + outputOffset; + } + } + + public static getOutputPositionOfInputOffset(breakOffsets: number[], inputOffset: number): OutputPosition { + let low = 0; + let high = breakOffsets.length - 1; + let mid = 0; + let midStart = 0; + + while (low <= high) { + mid = low + ((high - low) / 2) | 0; + + const midStop = breakOffsets[mid]; + midStart = mid > 0 ? breakOffsets[mid - 1] : 0; + + if (inputOffset < midStart) { + high = mid - 1; + } else if (inputOffset >= midStop) { + low = mid + 1; + } else { + break; + } + } + + return new OutputPosition(mid, inputOffset - midStart); + } +} + +export interface ILineBreaksComputer { + /** + * Pass in `previousLineBreakData` if the only difference is in breaking columns!!! + */ + addRequest(lineText: string, previousLineBreakData: LineBreakData | null): void; + finalize(): (LineBreakData | null)[]; } export interface IViewModel extends ICursorSimpleModel { @@ -103,6 +164,8 @@ export interface IViewModel extends ICursorSimpleModel { setViewport(startLineNumber: number, endLineNumber: number, centeredLineNumber: number): void; tokenizeViewport(): void; setHasFocus(hasFocus: boolean): void; + onCompositionStart(): void; + onCompositionEnd(): void; onDidColorThemeChange(): void; getDecorationsInViewport(visibleRange: Range): ViewModelDecoration[]; @@ -142,6 +205,7 @@ export interface IViewModel extends ICursorSimpleModel { //#endregion + createLineBreaksComputer(): ILineBreaksComputer; //#region cursor getPrimaryCursorState(): CursorState; diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index f7f130f08..932f6917e 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -21,7 +21,7 @@ import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTok import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { ViewLayout } from 'vs/editor/common/viewLayout/viewLayout'; import { IViewModelLinesCollection, IdentityLinesCollection, SplitLinesCollection, ILineBreaksComputerFactory } from 'vs/editor/common/viewModel/splitLinesCollection'; -import { ICoordinatesConverter, IOverviewRulerDecorations, IViewModel, MinimapLinesRenderingData, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel'; +import { ICoordinatesConverter, ILineBreaksComputer, IOverviewRulerDecorations, IViewModel, MinimapLinesRenderingData, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel'; import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations'; import { RunOnceScheduler } from 'vs/base/common/async'; import * as platform from 'vs/base/common/platform'; @@ -153,6 +153,10 @@ export class ViewModel extends Disposable implements IViewModel { this._eventDispatcher.dispose(); } + public createLineBreaksComputer(): ILineBreaksComputer { + return this._lines.createLineBreaksComputer(); + } + public addViewEventHandler(eventHandler: ViewEventHandler): void { this._eventDispatcher.addViewEventHandler(eventHandler); } @@ -179,6 +183,14 @@ export class ViewModel extends Disposable implements IViewModel { this._eventDispatcher.emitOutgoingEvent(new FocusChangedEvent(!hasFocus, hasFocus)); } + public onCompositionStart(): void { + this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewCompositionStartEvent()); + } + + public onCompositionEnd(): void { + this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewCompositionEndEvent()); + } + public onDidColorThemeChange(): void { this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewThemeChangedEvent()); } diff --git a/src/vs/editor/contrib/codeAction/codeAction.ts b/src/vs/editor/contrib/codeAction/codeAction.ts index a488d79ca..25b7687bc 100644 --- a/src/vs/editor/contrib/codeAction/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/codeAction.ts @@ -9,7 +9,6 @@ import { illegalArgument, isPromiseCanceledError, onUnexpectedExternalError } fr import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { TextModelCancellationTokenSource } from 'vs/editor/browser/core/editorState'; -import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { ITextModel } from 'vs/editor/common/model'; @@ -17,6 +16,7 @@ import * as modes from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; import { CodeActionFilter, CodeActionKind, CodeActionTrigger, filtersAction, mayIncludeActionsOfKind } from './types'; import { IProgress, Progress } from 'vs/platform/progress/common/progress'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; export const codeActionCommandId = 'editor.action.codeAction'; export const refactorCommandId = 'editor.action.refactor'; @@ -223,8 +223,8 @@ function getDocumentation( return undefined; } -registerLanguageCommand('_executeCodeActionProvider', async function (accessor, args): Promise> { - const { resource, rangeOrSelection, kind, itemResolveCount } = args; +CommandsRegistry.registerCommand('_executeCodeActionProvider', async function (accessor, ...args): Promise> { + const [resource, rangeOrSelection, kind, itemResolveCount] = args; if (!(resource instanceof URI)) { throw illegalArgument(); } diff --git a/src/vs/editor/contrib/codeAction/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/codeActionMenu.ts index 186eacd2e..e4a05011d 100644 --- a/src/vs/editor/contrib/codeAction/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/codeActionMenu.ts @@ -35,10 +35,14 @@ class CodeActionAction extends Action { public readonly action: CodeAction, callback: () => Promise, ) { - super(action.command ? action.command.id : action.title, action.title, undefined, !action.disabled, callback); + super(action.command ? action.command.id : action.title, stripNewlines(action.title), undefined, !action.disabled, callback); } } +function stripNewlines(str: string): string { + return str.replace(/\r\n|\r|\n/g, ' '); +} + export interface CodeActionShowOptions { readonly includeDisabledActions: boolean; } @@ -224,3 +228,5 @@ export class CodeActionKeybindingResolver { }, undefined as ResolveCodeActionKeybinding | undefined); } } + + diff --git a/src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts b/src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts index a0092488f..b8785616b 100644 --- a/src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts @@ -90,6 +90,7 @@ function createCodeActionKeybinding(keycode: KeyCode, command: string, commandAr commandArgs, undefined, false, - null); + null, + false); } diff --git a/src/vs/editor/contrib/codelens/codeLensCache.ts b/src/vs/editor/contrib/codelens/codeLensCache.ts index 5f8c14765..c932cf496 100644 --- a/src/vs/editor/contrib/codelens/codeLensCache.ts +++ b/src/vs/editor/contrib/codelens/codeLensCache.ts @@ -9,7 +9,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { CodeLensModel } from 'vs/editor/contrib/codelens/codelens'; import { LRUCache } from 'vs/base/common/map'; import { CodeLensProvider, CodeLensList, CodeLens } from 'vs/editor/common/modes'; -import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { Range } from 'vs/editor/common/core/range'; import { runWhenIdle } from 'vs/base/common/async'; import { once } from 'vs/base/common/functional'; @@ -62,7 +62,7 @@ export class CodeLensCache implements ICodeLensCache { // store lens data on shutdown once(storageService.onWillSaveState)(e => { if (e.reason === WillSaveStateReason.SHUTDOWN) { - storageService.store(key, this._serialize(), StorageScope.WORKSPACE); + storageService.store(key, this._serialize(), StorageScope.WORKSPACE, StorageTarget.MACHINE); } }); } diff --git a/src/vs/editor/contrib/codelens/codelens.ts b/src/vs/editor/contrib/codelens/codelens.ts index fbcfb1991..f8a4f09b7 100644 --- a/src/vs/editor/contrib/codelens/codelens.ts +++ b/src/vs/editor/contrib/codelens/codelens.ts @@ -7,11 +7,12 @@ import { mergeSort } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { illegalArgument, onUnexpectedExternalError } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; -import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { ITextModel } from 'vs/editor/common/model'; import { CodeLensProvider, CodeLensProviderRegistry, CodeLens, CodeLensList } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { assertType } from 'vs/base/common/types'; export interface CodeLensItem { symbol: CodeLens; @@ -79,14 +80,12 @@ export async function getCodeLensModel(model: ITextModel, token: CancellationTok return result; } -registerLanguageCommand('_executeCodeLensProvider', function (accessor, args) { +CommandsRegistry.registerCommand('_executeCodeLensProvider', function (accessor, ...args: [URI, number | undefined | null]) { + let [uri, itemResolveCount] = args; + assertType(URI.isUri(uri)); + assertType(typeof itemResolveCount === 'number' || !itemResolveCount); - let { resource, itemResolveCount } = args; - if (!(resource instanceof URI)) { - throw illegalArgument(); - } - - const model = accessor.get(IModelService).getModel(resource); + const model = accessor.get(IModelService).getModel(uri); if (!model) { throw illegalArgument(); } @@ -99,7 +98,7 @@ registerLanguageCommand('_executeCodeLensProvider', function (accessor, args) { let resolve: Promise[] = []; for (const item of value.lenses) { - if (typeof itemResolveCount === 'undefined' || Boolean(item.symbol.command)) { + if (itemResolveCount === undefined || itemResolveCount === null || Boolean(item.symbol.command)) { result.push(item.symbol); } else if (itemResolveCount-- > 0 && item.provider.resolveCodeLens) { resolve.push(Promise.resolve(item.provider.resolveCodeLens(model, item.symbol, CancellationToken.None)).then(symbol => result.push(symbol || item.symbol))); diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index 360889a81..3fddfdff3 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -53,7 +53,7 @@ export class CodeLensContribution implements IEditorContribution { this._disposables.add(this._editor.onDidChangeModel(() => this._onModelChange())); this._disposables.add(this._editor.onDidChangeModelLanguage(() => this._onModelChange())); this._disposables.add(this._editor.onDidChangeConfiguration((e) => { - if (e.hasChanged(EditorOption.fontInfo)) { + if (e.hasChanged(EditorOption.fontInfo) || e.hasChanged(EditorOption.codeLensFontSize) || e.hasChanged(EditorOption.codeLensFontFamily)) { this._updateLensStyle(); } if (e.hasChanged(EditorOption.codeLens)) { @@ -77,21 +77,42 @@ export class CodeLensContribution implements IEditorContribution { this._disposables.dispose(); this._oldCodeLensModels.dispose(); this._currentCodeLensModel?.dispose(); + this._styleElement.remove(); + } + + private _getLayoutInfo() { + let fontSize = this._editor.getOption(EditorOption.codeLensFontSize); + let codeLensHeight: number; + if (!fontSize || fontSize < 5) { + fontSize = (this._editor.getOption(EditorOption.fontSize) * .9) | 0; + codeLensHeight = this._editor.getOption(EditorOption.lineHeight); + } else { + codeLensHeight = (fontSize * Math.max(1.3, this._editor.getOption(EditorOption.lineHeight) / this._editor.getOption(EditorOption.fontSize))) | 0; + } + return { codeLensHeight, fontSize }; } private _updateLensStyle(): void { - const options = this._editor.getOptions(); - const fontInfo = options.get(EditorOption.fontInfo); - const lineHeight = options.get(EditorOption.lineHeight); + const { codeLensHeight, fontSize } = this._getLayoutInfo(); + const fontFamily = this._editor.getOption(EditorOption.codeLensFontFamily); + const editorFontInfo = this._editor.getOption(EditorOption.fontInfo); - const height = Math.round(lineHeight * 1.1); - const fontSize = Math.round(fontInfo.fontSize * 0.9); - const newStyle = ` - .monaco-editor .codelens-decoration.${this._styleClassName} { height: ${height}px; line-height: ${lineHeight}px; font-size: ${fontSize}px; padding-right: ${Math.round(fontInfo.fontSize * 0.45)}px;} - .monaco-editor .codelens-decoration.${this._styleClassName} > a > .codicon { line-height: ${lineHeight}px; font-size: ${fontSize}px; } + let newStyle = ` + .monaco-editor .codelens-decoration.${this._styleClassName} { line-height: ${codeLensHeight}px; font-size: ${fontSize}px; padding-right: ${Math.round(fontSize * 0.5)}px; font-feature-settings: ${editorFontInfo.fontFeatureSettings} } + .monaco-editor .codelens-decoration.${this._styleClassName} span.codicon { line-height: ${codeLensHeight}px; font-size: ${fontSize}px; } `; + if (fontFamily) { + newStyle += `.monaco-editor .codelens-decoration.${this._styleClassName} { font-family: '${fontFamily}'}`; + } this._styleElement.textContent = newStyle; + + // + this._editor.changeViewZones(accessor => { + for (let lens of this._lenses) { + lens.updateHeight(codeLensHeight, accessor); + } + }); } private _localDispose(): void { @@ -165,7 +186,7 @@ export class CodeLensContribution implements IEditorContribution { // render lenses this._renderCodeLensSymbols(result); - this._resolveCodeLensesInViewportSoon(); + this._resolveCodeLensesInViewport(); }, onUnexpectedError); }, this._getCodeLensModelDelays.get(model)); @@ -199,11 +220,12 @@ export class CodeLensContribution implements IEditorContribution { }); }); - // Compute new `visible` code lenses - this._resolveCodeLensesInViewportSoon(); // Ask for all references again scheduler.schedule(); })); + this._localToDispose.add(this._editor.onDidFocusEditorWidget(() => { + scheduler.schedule(); + })); this._localToDispose.add(this._editor.onDidScrollChange(e => { if (e.scrollTopChanged && this._lenses.length > 0) { this._resolveCodeLensesInViewportSoon(); @@ -283,6 +305,7 @@ export class CodeLensContribution implements IEditorContribution { } const scrollState = StableEditorScrollState.capture(this._editor); + const layoutInfo = this._getLayoutInfo(); this._editor.changeDecorations(decorationsAccessor => { this._editor.changeViewZones(viewZoneAccessor => { @@ -304,7 +327,7 @@ export class CodeLensContribution implements IEditorContribution { groupsIndex++; codeLensIndex++; } else { - this._lenses.splice(codeLensIndex, 0, new CodeLensWidget(groups[groupsIndex], this._editor, this._styleClassName, helper, viewZoneAccessor, () => this._resolveCodeLensesInViewportSoon())); + this._lenses.splice(codeLensIndex, 0, new CodeLensWidget(groups[groupsIndex], this._editor, this._styleClassName, helper, viewZoneAccessor, layoutInfo.codeLensHeight, () => this._resolveCodeLensesInViewportSoon())); codeLensIndex++; groupsIndex++; } @@ -318,7 +341,7 @@ export class CodeLensContribution implements IEditorContribution { // Create extra symbols while (groupsIndex < groups.length) { - this._lenses.push(new CodeLensWidget(groups[groupsIndex], this._editor, this._styleClassName, helper, viewZoneAccessor, () => this._resolveCodeLensesInViewportSoon())); + this._lenses.push(new CodeLensWidget(groups[groupsIndex], this._editor, this._styleClassName, helper, viewZoneAccessor, layoutInfo.codeLensHeight, () => this._resolveCodeLensesInViewportSoon())); groupsIndex++; } diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index 215e48f22..f77b2dfc5 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -18,20 +18,20 @@ import { renderCodicons } from 'vs/base/browser/codicons'; class CodeLensViewZone implements IViewZone { - readonly heightInLines: number; readonly suppressMouseDown: boolean; readonly domNode: HTMLElement; afterLineNumber: number; + heightInPx: number; private _lastHeight?: number; - private readonly _onHeight: Function; + private readonly _onHeight: () => void; - constructor(afterLineNumber: number, onHeight: Function) { + constructor(afterLineNumber: number, heightInPx: number, onHeight: () => void) { this.afterLineNumber = afterLineNumber; - this._onHeight = onHeight; + this.heightInPx = heightInPx; - this.heightInLines = 1; + this._onHeight = onHeight; this.suppressMouseDown = true; this.domNode = document.createElement('div'); } @@ -179,8 +179,8 @@ export class CodeLensWidget { private readonly _editor: IActiveCodeEditor; private readonly _className: string; - private readonly _viewZone!: CodeLensViewZone; - private readonly _viewZoneId!: string; + private readonly _viewZone: CodeLensViewZone; + private readonly _viewZoneId: string; private _contentWidget?: CodeLensContentWidget; private _decorationIds: string[]; @@ -193,7 +193,8 @@ export class CodeLensWidget { className: string, helper: CodeLensHelper, viewZoneChangeAccessor: IViewZoneChangeAccessor, - updateCallback: Function + heightInPx: number, + updateCallback: () => void ) { this._editor = editor; this._className = className; @@ -224,7 +225,7 @@ export class CodeLensWidget { } }); - this._viewZone = new CodeLensViewZone(range!.startLineNumber - 1, updateCallback); + this._viewZone = new CodeLensViewZone(range!.startLineNumber - 1, heightInPx, updateCallback); this._viewZoneId = viewZoneChangeAccessor.addZone(this._viewZone); if (lenses.length > 0) { @@ -236,7 +237,9 @@ export class CodeLensWidget { private _createContentWidgetIfNecessary(): void { if (!this._contentWidget) { this._contentWidget = new CodeLensContentWidget(this._editor, this._className, this._viewZone.afterLineNumber + 1); - this._editor.addContentWidget(this._contentWidget!); + this._editor.addContentWidget(this._contentWidget); + } else { + this._editor.layoutContentWidget(this._contentWidget); } } @@ -277,6 +280,14 @@ export class CodeLensWidget { }); } + updateHeight(height: number, viewZoneChangeAccessor: IViewZoneChangeAccessor): void { + this._viewZone.heightInPx = height; + viewZoneChangeAccessor.layoutZone(this._viewZoneId); + if (this._contentWidget) { + this._editor.layoutContentWidget(this._contentWidget); + } + } + computeIfNecessary(model: ITextModel): CodeLensItem[] | null { if (!this._viewZone.domNode.hasAttribute('monaco-visible-view-zone')) { return null; diff --git a/src/vs/editor/contrib/colorPicker/color.ts b/src/vs/editor/contrib/colorPicker/color.ts index d6b490128..7a4383b16 100644 --- a/src/vs/editor/contrib/colorPicker/color.ts +++ b/src/vs/editor/contrib/colorPicker/color.ts @@ -6,11 +6,11 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { illegalArgument } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; -import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; import { ColorProviderRegistry, DocumentColorProvider, IColorInformation, IColorPresentation } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; export interface IColorData { @@ -36,9 +36,9 @@ export function getColorPresentations(model: ITextModel, colorInfo: IColorInform return Promise.resolve(provider.provideColorPresentations(model, colorInfo, token)); } -registerLanguageCommand('_executeDocumentColorProvider', function (accessor, args) { +CommandsRegistry.registerCommand('_executeDocumentColorProvider', function (accessor, ...args) { - const { resource } = args; + const [resource] = args; if (!(resource instanceof URI)) { throw illegalArgument(); } @@ -62,15 +62,16 @@ registerLanguageCommand('_executeDocumentColorProvider', function (accessor, arg }); -registerLanguageCommand('_executeColorPresentationProvider', function (accessor, args) { +CommandsRegistry.registerCommand('_executeColorPresentationProvider', function (accessor, ...args) { - const { resource, color, range } = args; - if (!(resource instanceof URI) || !Array.isArray(color) || color.length !== 4 || !Range.isIRange(range)) { + const [color, context] = args; + const { uri, range } = context; + if (!(uri instanceof URI) || !Array.isArray(color) || color.length !== 4 || !Range.isIRange(range)) { throw illegalArgument(); } const [red, green, blue, alpha] = color; - const model = accessor.get(IModelService).getModel(resource); + const model = accessor.get(IModelService).getModel(uri); if (!model) { throw illegalArgument(); } diff --git a/src/vs/editor/contrib/colorPicker/colorContributions.ts b/src/vs/editor/contrib/colorPicker/colorContributions.ts new file mode 100644 index 000000000..4a997061b --- /dev/null +++ b/src/vs/editor/contrib/colorPicker/colorContributions.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 color detector contribution +import 'vs/editor/contrib/colorPicker/colorDetector'; + +import { Disposable } from 'vs/base/common/lifecycle'; +import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { ModesHoverController } from 'vs/editor/contrib/hover/hover'; +import { Range } from 'vs/editor/common/core/range'; +import { HoverStartMode } from 'vs/editor/contrib/hover/hoverOperation'; + +export class ColorContribution extends Disposable implements IEditorContribution { + + public static readonly ID: string = 'editor.contrib.colorContribution'; + + static readonly RECOMPUTE_TIME = 1000; // ms + + constructor(private readonly _editor: ICodeEditor, + ) { + super(); + this._register(_editor.onMouseDown((e) => this.onMouseDown(e))); + } + + dispose(): void { + super.dispose(); + } + + private onMouseDown(mouseEvent: IEditorMouseEvent) { + const targetType = mouseEvent.target.type; + + if (targetType !== MouseTargetType.CONTENT_TEXT) { + return; + } + + const hoverOnColorDecorator = [...mouseEvent.target.element?.classList.values() || []].find(className => className.startsWith('ced-colorBox')); + if (!hoverOnColorDecorator) { + return; + } + + if (!mouseEvent.target.range) { + return; + } + + const hoverController = this._editor.getContribution(ModesHoverController.ID); + if (!hoverController.contentWidget.isColorPickerVisible()) { + const range = new Range(mouseEvent.target.range.startLineNumber, mouseEvent.target.range.startColumn + 1, mouseEvent.target.range.endLineNumber, mouseEvent.target.range.endColumn + 1); + hoverController.showContentHover(range, HoverStartMode.Delayed, false); + } + } +} + +registerEditorContribution(ColorContribution.ID, ColorContribution); diff --git a/src/vs/editor/contrib/colorPicker/colorDetector.ts b/src/vs/editor/contrib/colorPicker/colorDetector.ts index 4b678d0ed..ccd09304a 100644 --- a/src/vs/editor/contrib/colorPicker/colorDetector.ts +++ b/src/vs/editor/contrib/colorPicker/colorDetector.ts @@ -46,13 +46,13 @@ export class ColorDetector extends Disposable implements IEditorContribution { @IConfigurationService private readonly _configurationService: IConfigurationService ) { super(); - this._register(_editor.onDidChangeModel((e) => { + this._register(_editor.onDidChangeModel(() => { this._isEnabled = this.isEnabled(); this.onModelChanged(); })); - this._register(_editor.onDidChangeModelLanguage((e) => this.onModelChanged())); - this._register(ColorProviderRegistry.onDidChange((e) => this.onModelChanged())); - this._register(_editor.onDidChangeConfiguration((e) => { + this._register(_editor.onDidChangeModelLanguage(() => this.onModelChanged())); + this._register(ColorProviderRegistry.onDidChange(() => this.onModelChanged())); + this._register(_editor.onDidChangeConfiguration(() => { let prevIsEnabled = this._isEnabled; this._isEnabled = this.isEnabled(); if (prevIsEnabled !== this._isEnabled) { @@ -110,7 +110,7 @@ export class ColorDetector extends Disposable implements IEditorContribution { return; } - this._localToDispose.add(this._editor.onDidChangeModelContent((e) => { + this._localToDispose.add(this._editor.onDidChangeModelContent(() => { if (!this._timeoutTimer) { this._timeoutTimer = new TimeoutTimer(); this._timeoutTimer.cancelAndSet(() => { diff --git a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts index 4bcdaa397..454b3d339 100644 --- a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts @@ -335,7 +335,7 @@ export class ColorPickerWidget extends Widget { body: ColorPickerBody; - constructor(container: Node, private readonly model: ColorPickerModel, private pixelRatio: number, themeService: IThemeService) { + constructor(container: Node, readonly model: ColorPickerModel, private pixelRatio: number, themeService: IThemeService) { super(); this._register(onDidChangeZoomLevel(() => this.layout())); diff --git a/src/vs/editor/contrib/comment/comment.ts b/src/vs/editor/contrib/comment/comment.ts index 1dbe337e1..d09c10af9 100644 --- a/src/vs/editor/contrib/comment/comment.ts +++ b/src/vs/editor/contrib/comment/comment.ts @@ -7,6 +7,7 @@ import * as nls from 'vs/nls'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, IActionOptions, ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; +import { Range } from 'vs/editor/common/core/range'; import { ICommand } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { BlockCommentCommand } from 'vs/editor/contrib/comment/blockCommentCommand'; @@ -31,17 +32,38 @@ abstract class CommentLineAction extends EditorAction { const model = editor.getModel(); const commands: ICommand[] = []; - const selections = editor.getSelections(); const modelOptions = model.getOptions(); const commentsOptions = editor.getOption(EditorOption.comments); + const selections = editor.getSelections().map((selection, index) => ({ selection, index, ignoreFirstLine: false })); + selections.sort((a, b) => Range.compareRangesUsingStarts(a.selection, b.selection)); + + // Remove selections that would result in copying the same line + let prev = selections[0]; + for (let i = 1; i < selections.length; i++) { + const curr = selections[i]; + if (prev.selection.endLineNumber === curr.selection.startLineNumber) { + // these two selections would copy the same line + if (prev.index < curr.index) { + // prev wins + curr.ignoreFirstLine = true; + } else { + // curr wins + prev.ignoreFirstLine = true; + prev = curr; + } + } + } + + for (const selection of selections) { commands.push(new LineCommentCommand( - selection, + selection.selection, modelOptions.tabSize, this._type, commentsOptions.insertSpace, - commentsOptions.ignoreEmptyLines + commentsOptions.ignoreEmptyLines, + selection.ignoreFirstLine )); } diff --git a/src/vs/editor/contrib/comment/lineCommentCommand.ts b/src/vs/editor/contrib/comment/lineCommentCommand.ts index 8952727a9..0b81435aa 100644 --- a/src/vs/editor/contrib/comment/lineCommentCommand.ts +++ b/src/vs/editor/contrib/comment/lineCommentCommand.ts @@ -57,13 +57,15 @@ export class LineCommentCommand implements ICommand { private _selectionId: string | null; private _deltaColumn: number; private _moveEndPositionDown: boolean; + private _ignoreFirstLine: boolean; constructor( selection: Selection, tabSize: number, type: Type, insertSpace: boolean, - ignoreEmptyLines: boolean + ignoreEmptyLines: boolean, + ignoreFirstLine?: boolean ) { this._selection = selection; this._tabSize = tabSize; @@ -73,6 +75,7 @@ export class LineCommentCommand implements ICommand { this._deltaColumn = 0; this._moveEndPositionDown = false; this._ignoreEmptyLines = ignoreEmptyLines; + this._ignoreFirstLine = ignoreFirstLine || false; } /** @@ -108,7 +111,7 @@ export class LineCommentCommand implements ICommand { * Analyze lines and decide which lines are relevant and what the toggle should do. * Also, build up several offsets and lengths useful in the generation of editor operations. */ - public static _analyzeLines(type: Type, insertSpace: boolean, model: ISimpleModel, lines: ILinePreflightData[], startLineNumber: number, ignoreEmptyLines: boolean): IPreflightData { + public static _analyzeLines(type: Type, insertSpace: boolean, model: ISimpleModel, lines: ILinePreflightData[], startLineNumber: number, ignoreEmptyLines: boolean, ignoreFirstLine: boolean): IPreflightData { let onlyWhitespaceLines = true; let shouldRemoveComments: boolean; @@ -124,6 +127,12 @@ export class LineCommentCommand implements ICommand { const lineData = lines[i]; const lineNumber = startLineNumber + i; + if (lineNumber === startLineNumber && ignoreFirstLine) { + // first line ignored + lineData.ignore = true; + continue; + } + const lineContent = model.getLineContent(lineNumber); const lineContentStartOffset = strings.firstNonWhitespaceIndex(lineContent); @@ -178,7 +187,7 @@ export class LineCommentCommand implements ICommand { /** * Analyze all lines and decide exactly what to do => not supported | insert line comments | remove line comments */ - public static _gatherPreflightData(type: Type, insertSpace: boolean, model: ITextModel, startLineNumber: number, endLineNumber: number, ignoreEmptyLines: boolean): IPreflightData { + public static _gatherPreflightData(type: Type, insertSpace: boolean, model: ITextModel, startLineNumber: number, endLineNumber: number, ignoreEmptyLines: boolean, ignoreFirstLine: boolean): IPreflightData { const lines = LineCommentCommand._gatherPreflightCommentStrings(model, startLineNumber, endLineNumber); if (lines === null) { return { @@ -186,7 +195,7 @@ export class LineCommentCommand implements ICommand { }; } - return LineCommentCommand._analyzeLines(type, insertSpace, model, lines, startLineNumber, ignoreEmptyLines); + return LineCommentCommand._analyzeLines(type, insertSpace, model, lines, startLineNumber, ignoreEmptyLines, ignoreFirstLine); } /** @@ -323,6 +332,12 @@ export class LineCommentCommand implements ICommand { let s = this._selection; this._moveEndPositionDown = false; + if (s.startLineNumber === s.endLineNumber && this._ignoreFirstLine) { + builder.addEditOperation(new Range(s.startLineNumber, model.getLineMaxColumn(s.startLineNumber), s.startLineNumber + 1, 1), s.startLineNumber === model.getLineCount() ? '' : '\n'); + this._selectionId = builder.trackSelection(s); + return; + } + if (s.startLineNumber < s.endLineNumber && s.endColumn === 1) { this._moveEndPositionDown = true; s = s.setEndPosition(s.endLineNumber - 1, model.getLineMaxColumn(s.endLineNumber - 1)); @@ -334,7 +349,8 @@ export class LineCommentCommand implements ICommand { model, s.startLineNumber, s.endLineNumber, - this._ignoreEmptyLines + this._ignoreEmptyLines, + this._ignoreFirstLine ); if (data.supported) { diff --git a/src/vs/editor/contrib/comment/test/lineCommentCommand.test.ts b/src/vs/editor/contrib/comment/test/lineCommentCommand.test.ts index 0689b4209..2a4329ba9 100644 --- a/src/vs/editor/contrib/comment/test/lineCommentCommand.test.ts +++ b/src/vs/editor/contrib/comment/test/lineCommentCommand.test.ts @@ -91,7 +91,7 @@ suite('Editor Contrib - Line Comment Command', () => { ' ', ' c', '\t\td' - ]), createBasicLinePreflightData(['//', 'rem', '!@#', '!@#']), 1, true); + ]), createBasicLinePreflightData(['//', 'rem', '!@#', '!@#']), 1, true, false); if (!r.supported) { throw new Error(`unexpected`); } @@ -122,7 +122,7 @@ suite('Editor Contrib - Line Comment Command', () => { ' rem ', ' !@# c', '\t\t!@#d' - ]), createBasicLinePreflightData(['//', 'rem', '!@#', '!@#']), 1, true); + ]), createBasicLinePreflightData(['//', 'rem', '!@#', '!@#']), 1, true, false); if (!r.supported) { throw new Error(`unexpected`); } diff --git a/src/vs/editor/contrib/dnd/dnd.ts b/src/vs/editor/contrib/dnd/dnd.ts index b4bdd4336..b1b4a06b7 100644 --- a/src/vs/editor/contrib/dnd/dnd.ts +++ b/src/vs/editor/contrib/dnd/dnd.ts @@ -51,6 +51,7 @@ export class DragAndDropController extends Disposable implements IEditorContribu this._register(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(e))); this._register(this._editor.onMouseDrag((e: IEditorMouseEvent) => this._onEditorMouseDrag(e))); this._register(this._editor.onMouseDrop((e: IPartialEditorMouseEvent) => this._onEditorMouseDrop(e))); + this._register(this._editor.onMouseDropCanceled(() => this._onEditorMouseDropCanceled())); this._register(this._editor.onKeyDown((e: IKeyboardEvent) => this.onEditorKeyDown(e))); this._register(this._editor.onKeyUp((e: IKeyboardEvent) => this.onEditorKeyUp(e))); this._register(this._editor.onDidBlurEditorWidget(() => this.onEditorBlur())); @@ -144,6 +145,16 @@ export class DragAndDropController extends Disposable implements IEditorContribu } } + private _onEditorMouseDropCanceled() { + this._editor.updateOptions({ + mouseStyle: 'text' + }); + + this._removeDecoration(); + this._dragSelection = null; + this._mouseDown = false; + } + private _onEditorMouseDrop(mouseEvent: IPartialEditorMouseEvent): void { if (mouseEvent.target && (this._hitContent(mouseEvent.target) || this._hitMargin(mouseEvent.target)) && mouseEvent.target.position) { let newCursorPosition = new Position(mouseEvent.target.position.lineNumber, mouseEvent.target.position.column); diff --git a/src/vs/editor/contrib/find/findController.ts b/src/vs/editor/contrib/find/findController.ts index 4f7b574cd..9982068e5 100644 --- a/src/vs/editor/contrib/find/findController.ts +++ b/src/vs/editor/contrib/find/findController.ts @@ -22,22 +22,23 @@ import { IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/con import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; const SEARCH_STRING_MAX_LENGTH = 524288; -export function getSelectionSearchString(editor: ICodeEditor): string | null { +export function getSelectionSearchString(editor: ICodeEditor, seedSearchStringFromSelection: 'single' | 'multiple' = 'single'): string | null { if (!editor.hasModel()) { return null; } const selection = editor.getSelection(); // if selection spans multiple lines, default search string to empty - if (selection.startLineNumber === selection.endLineNumber) { + + if ((seedSearchStringFromSelection === 'single' && selection.startLineNumber === selection.endLineNumber) + || seedSearchStringFromSelection === 'multiple') { if (selection.isEmpty()) { const wordAtPosition = editor.getConfiguredWordAtPosition(selection.getStartPosition()); if (wordAtPosition) { @@ -61,7 +62,7 @@ export const enum FindStartFocusAction { export interface IFindStartOptions { forceRevealReplace: boolean; - seedSearchStringFromSelection: boolean; + seedSearchStringFromSelection: 'none' | 'single' | 'multiple'; seedSearchStringFromGlobalClipboard: boolean; shouldFocus: FindStartFocusAction; shouldAnimate: boolean; @@ -82,6 +83,10 @@ export class CommonFindController extends Disposable implements IEditorContribut private readonly _clipboardService: IClipboardService; protected readonly _contextKeyService: IContextKeyService; + get editor() { + return this._editor; + } + public static get(editor: ICodeEditor): CommonFindController { return editor.getContribution(CommonFindController.ID); } @@ -122,7 +127,7 @@ export class CommonFindController extends Disposable implements IEditorContribut if (shouldRestartFind) { this._start({ forceRevealReplace: false, - seedSearchStringFromSelection: false && this._editor.getOption(EditorOption.find).seedSearchStringFromSelection, + seedSearchStringFromSelection: 'none', seedSearchStringFromGlobalClipboard: false, shouldFocus: FindStartFocusAction.NoFocusChange, shouldAnimate: false, @@ -163,16 +168,16 @@ export class CommonFindController extends Disposable implements IEditorContribut private saveQueryState(e: FindReplaceStateChangedEvent) { if (e.isRegex) { - this._storageService.store('editor.isRegex', this._state.actualIsRegex, StorageScope.WORKSPACE); + this._storageService.store('editor.isRegex', this._state.actualIsRegex, StorageScope.WORKSPACE, StorageTarget.USER); } if (e.wholeWord) { - this._storageService.store('editor.wholeWord', this._state.actualWholeWord, StorageScope.WORKSPACE); + this._storageService.store('editor.wholeWord', this._state.actualWholeWord, StorageScope.WORKSPACE, StorageTarget.USER); } if (e.matchCase) { - this._storageService.store('editor.matchCase', this._state.actualMatchCase, StorageScope.WORKSPACE); + this._storageService.store('editor.matchCase', this._state.actualMatchCase, StorageScope.WORKSPACE, StorageTarget.USER); } if (e.preserveCase) { - this._storageService.store('editor.preserveCase', this._state.actualPreserveCase, StorageScope.WORKSPACE); + this._storageService.store('editor.preserveCase', this._state.actualPreserveCase, StorageScope.WORKSPACE, StorageTarget.USER); } } @@ -262,7 +267,7 @@ export class CommonFindController extends Disposable implements IEditorContribut this._state.change({ searchString: searchString }, false); } - public highlightFindOptions(): void { + public highlightFindOptions(ignoreWhenVisible: boolean = false): void { // overwritten in subclass } @@ -278,8 +283,8 @@ export class CommonFindController extends Disposable implements IEditorContribut isRevealed: true }; - if (opts.seedSearchStringFromSelection) { - let selectionSearchString = getSelectionSearchString(this._editor); + if (opts.seedSearchStringFromSelection === 'single') { + let selectionSearchString = getSelectionSearchString(this._editor, opts.seedSearchStringFromSelection); if (selectionSearchString) { if (this._state.isRegex) { stateChanges.searchString = strings.escapeRegExpCharacters(selectionSearchString); @@ -287,6 +292,11 @@ export class CommonFindController extends Disposable implements IEditorContribut stateChanges.searchString = selectionSearchString; } } + } else if (opts.seedSearchStringFromSelection === 'multiple' && !opts.updateSearchScope) { + let selectionSearchString = getSelectionSearchString(this._editor, opts.seedSearchStringFromSelection); + if (selectionSearchString) { + stateChanges.searchString = selectionSearchString; + } } if (!stateChanges.searchString && opts.seedSearchStringFromGlobalClipboard) { @@ -404,7 +414,6 @@ export class FindController extends CommonFindController implements IFindControl @IThemeService private readonly _themeService: IThemeService, @INotificationService private readonly _notificationService: INotificationService, @IStorageService _storageService: IStorageService, - @IStorageKeysSyncRegistryService private readonly _storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, @IClipboardService clipboardService: IClipboardService, ) { super(editor, _contextKeyService, _storageService, clipboardService); @@ -449,11 +458,11 @@ export class FindController extends CommonFindController implements IFindControl } } - public highlightFindOptions(): void { + public highlightFindOptions(ignoreWhenVisible: boolean = false): void { if (!this._widget) { this._createFindWidget(); } - if (this._state.isRevealed) { + if (this._state.isRevealed && !ignoreWhenVisible) { this._widget!.highlightFindOptions(); } else { this._findOptionsWidget!.highlightFindOptions(); @@ -461,9 +470,17 @@ export class FindController extends CommonFindController implements IFindControl } private _createFindWidget() { - this._widget = this._register(new FindWidget(this._editor, this, this._state, this._contextViewService, this._keybindingService, this._contextKeyService, this._themeService, this._storageService, this._notificationService, this._storageKeysSyncRegistryService)); + this._widget = this._register(new FindWidget(this._editor, this, this._state, this._contextViewService, this._keybindingService, this._contextKeyService, this._themeService, this._storageService, this._notificationService)); this._findOptionsWidget = this._register(new FindOptionsWidget(this._editor, this._state, this._keybindingService, this._themeService)); } + + saveViewState(): any { + return this._widget?.getViewState(); + } + + restoreViewState(state: any): void { + this._widget?.setViewState(state); + } } export class StartFindAction extends MultiEditorAction { @@ -493,7 +510,7 @@ export class StartFindAction extends MultiEditorAction { if (controller) { await controller.start({ forceRevealReplace: false, - seedSearchStringFromSelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection, + seedSearchStringFromSelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection ? 'single' : 'none', seedSearchStringFromGlobalClipboard: editor.getOption(EditorOption.find).globalFindClipboard, shouldFocus: FindStartFocusAction.FocusFindInput, shouldAnimate: true, @@ -523,12 +540,12 @@ export class StartFindWithSelectionAction extends EditorAction { }); } - public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + public async run(accessor: ServicesAccessor | null, editor: ICodeEditor): Promise { let controller = CommonFindController.get(editor); if (controller) { await controller.start({ forceRevealReplace: false, - seedSearchStringFromSelection: true, + seedSearchStringFromSelection: 'multiple', seedSearchStringFromGlobalClipboard: false, shouldFocus: FindStartFocusAction.NoFocusChange, shouldAnimate: true, @@ -546,7 +563,7 @@ export abstract class MatchFindAction extends EditorAction { if (controller && !this._run(controller)) { await controller.start({ forceRevealReplace: false, - seedSearchStringFromSelection: (controller.getState().searchString.length === 0) && editor.getOption(EditorOption.find).seedSearchStringFromSelection, + seedSearchStringFromSelection: (controller.getState().searchString.length === 0) && editor.getOption(EditorOption.find).seedSearchStringFromSelection ? 'single' : 'none', seedSearchStringFromGlobalClipboard: true, shouldFocus: FindStartFocusAction.NoFocusChange, shouldAnimate: true, @@ -578,7 +595,13 @@ export class NextMatchFindAction extends MatchFindAction { } protected _run(controller: CommonFindController): boolean { - return controller.moveToNextMatch(); + const result = controller.moveToNextMatch(); + if (result) { + controller.editor.pushUndoStop(); + return true; + } + + return false; } } @@ -599,7 +622,13 @@ export class NextMatchFindAction2 extends MatchFindAction { } protected _run(controller: CommonFindController): boolean { - return controller.moveToNextMatch(); + const result = controller.moveToNextMatch(); + if (result) { + controller.editor.pushUndoStop(); + return true; + } + + return false; } } @@ -659,7 +688,7 @@ export abstract class SelectionMatchFindAction extends EditorAction { if (!this._run(controller)) { await controller.start({ forceRevealReplace: false, - seedSearchStringFromSelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection, + seedSearchStringFromSelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection ? 'single' : 'none', seedSearchStringFromGlobalClipboard: false, shouldFocus: FindStartFocusAction.NoFocusChange, shouldAnimate: true, @@ -765,7 +794,7 @@ export class StartFindReplaceAction extends MultiEditorAction { if (controller) { await controller.start({ forceRevealReplace: true, - seedSearchStringFromSelection: seedSearchStringFromSelection, + seedSearchStringFromSelection: seedSearchStringFromSelection ? 'single' : 'none', seedSearchStringFromGlobalClipboard: editor.getOption(EditorOption.find).seedSearchStringFromSelection, shouldFocus: shouldFocus, shouldAnimate: true, diff --git a/src/vs/editor/contrib/find/findOptionsWidget.ts b/src/vs/editor/contrib/find/findOptionsWidget.ts index d541e0d06..660eed855 100644 --- a/src/vs/editor/contrib/find/findOptionsWidget.ts +++ b/src/vs/editor/contrib/find/findOptionsWidget.ts @@ -213,7 +213,7 @@ registerThemingParticipant((theme, collector) => { const widgetShadowColor = theme.getColor(widgetShadow); if (widgetShadowColor) { - collector.addRule(`.monaco-editor .findOptionsWidget { box-shadow: 0 2px 8px ${widgetShadowColor}; }`); + collector.addRule(`.monaco-editor .findOptionsWidget { box-shadow: 0 0 8px 2px ${widgetShadowColor}; }`); } const hcBorder = theme.getColor(contrastBorder); diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index 21fbb0060..597af6c32 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -31,23 +31,22 @@ import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contri import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { contrastBorder, editorFindMatch, editorFindMatchBorder, editorFindMatchHighlight, editorFindMatchHighlightBorder, editorFindRangeHighlight, editorFindRangeHighlightBorder, editorWidgetBackground, editorWidgetBorder, editorWidgetResizeBorder, errorForeground, inputActiveOptionBorder, inputActiveOptionBackground, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground, focusBorder } from 'vs/platform/theme/common/colorRegistry'; -import { IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/browser/contextScopedHistoryWidget'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; -import { Codicon, registerIcon } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; +import { registerIcon, widgetClose } from 'vs/platform/theme/common/iconRegistry'; -const findSelectionIcon = registerIcon('find-selection', Codicon.selection); -const findCollapsedIcon = registerIcon('find-collapsed', Codicon.chevronRight); -const findExpandedIcon = registerIcon('find-expanded', Codicon.chevronDown); +const findSelectionIcon = registerIcon('find-selection', Codicon.selection, nls.localize('findSelectionIcon', 'Icon for \'Find in Selection\' in the editor find widget.')); +const findCollapsedIcon = registerIcon('find-collapsed', Codicon.chevronRight, nls.localize('findCollapsedIcon', 'Icon to indicate that the editor find widget is collapsed.')); +const findExpandedIcon = registerIcon('find-expanded', Codicon.chevronDown, nls.localize('findExpandedIcon', 'Icon to indicate that the editor find widget is expanded.')); -export const findCloseIcon = registerIcon('find-close', Codicon.close); -export const findReplaceIcon = registerIcon('find-replace', Codicon.replace); -export const findReplaceAllIcon = registerIcon('find-replace-all', Codicon.replaceAll); -export const findPreviousMatchIcon = registerIcon('find-previous-match', Codicon.arrowUp); -export const findNextMatchIcon = registerIcon('find-next-match', Codicon.arrowDown); +export const findReplaceIcon = registerIcon('find-replace', Codicon.replace, nls.localize('findReplaceIcon', 'Icon for \'Replace\' in the editor find widget.')); +export const findReplaceAllIcon = registerIcon('find-replace-all', Codicon.replaceAll, nls.localize('findReplaceAllIcon', 'Icon for \'Replace All\' in the editor find widget.')); +export const findPreviousMatchIcon = registerIcon('find-previous-match', Codicon.arrowUp, nls.localize('findPreviousMatchIcon', 'Icon for \'Find Previous\' in the editor find widget.')); +export const findNextMatchIcon = registerIcon('find-next-match', Codicon.arrowDown, nls.localize('findNextMatchIcon', 'Icon for \'Find Next\' in the editor find widget.')); export interface IFindController { replace(): void; @@ -164,7 +163,6 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL themeService: IThemeService, storageService: IStorageService, notificationService: INotificationService, - storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { super(); this._codeEditor = codeEditor; @@ -176,7 +174,6 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._storageService = storageService; this._notificationService = notificationService; - storageKeysSyncRegistryService.registerStorageKey({ key: ctrlEnterReplaceAllWarningPromptedKey, version: 1 }); this._ctrlEnterReplaceAllWarningPrompted = !!storageService.getBoolean(ctrlEnterReplaceAllWarningPromptedKey, StorageScope.GLOBAL); this._isVisible = false; @@ -228,7 +225,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL if (this._isVisible) { let globalBufferTerm = await this._controller.getGlobalBufferTerm(); if (globalBufferTerm && globalBufferTerm !== this._state.searchString) { - this._state.change({ searchString: globalBufferTerm }, true); + this._state.change({ searchString: globalBufferTerm }, false); this._findInput.select(); } } @@ -383,7 +380,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL } private _delayedUpdateHistory() { - this._updateHistoryDelayer.trigger(this._updateHistory.bind(this)); + this._updateHistoryDelayer.trigger(this._updateHistory.bind(this)).then(undefined, onUnexpectedError); } private _updateHistory() { @@ -487,7 +484,15 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._toggleReplaceBtn.setEnabled(this._isVisible && canReplace); } + private _revealTimeouts: any[] = []; + private _reveal(): void { + this._revealTimeouts.forEach(e => { + clearTimeout(e); + }); + + this._revealTimeouts = []; + if (!this._isVisible) { this._isVisible = true; @@ -512,15 +517,15 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._tryUpdateWidgetWidth(); this._updateButtons(); - setTimeout(() => { + this._revealTimeouts.push(setTimeout(() => { this._domNode.classList.add('visible'); this._domNode.setAttribute('aria-hidden', 'false'); - }, 0); + }, 0)); // validate query again as it's being dismissed when we hide the find widget. - setTimeout(() => { + this._revealTimeouts.push(setTimeout(() => { this._findInput.validate(); - }, 200); + }, 200)); this._codeEditor.layoutOverlayWidget(this); @@ -555,6 +560,12 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL } private _hide(focusTheEditor: boolean): void { + this._revealTimeouts.forEach(e => { + clearTimeout(e); + }); + + this._revealTimeouts = []; + if (this._isVisible) { this._isVisible = false; @@ -571,7 +582,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL } } - private _layoutViewZone() { + private _layoutViewZone(targetScrollTop?: number) { const addExtraSpaceOnTop = this._codeEditor.getOption(EditorOption.find).addExtraSpaceOnTop; if (!addExtraSpaceOnTop) { @@ -591,7 +602,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL viewZone.heightInPx = this._getHeight(); this._viewZoneId = accessor.addZone(viewZone); // scroll top adjust to make sure the editor doesn't scroll when adding viewzone at the beginning. - this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() + viewZone.heightInPx); + this._codeEditor.setScrollTop(targetScrollTop || this._codeEditor.getScrollTop() + viewZone.heightInPx); }); } @@ -880,7 +891,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL ); this._ctrlEnterReplaceAllWarningPrompted = true; - this._storageService.store(ctrlEnterReplaceAllWarningPromptedKey, true, StorageScope.GLOBAL); + this._storageService.store(ctrlEnterReplaceAllWarningPromptedKey, true, StorageScope.GLOBAL, StorageTarget.USER); } @@ -1006,7 +1017,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL // Previous button this._prevBtn = this._register(new SimpleButton({ label: NLS_PREVIOUS_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.PreviousMatchFindAction), - className: findPreviousMatchIcon.classNames, + icon: findPreviousMatchIcon, onTrigger: () => { this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().then(undefined, onUnexpectedError); } @@ -1015,7 +1026,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL // Next button this._nextBtn = this._register(new SimpleButton({ label: NLS_NEXT_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.NextMatchFindAction), - className: findNextMatchIcon.classNames, + icon: findNextMatchIcon, onTrigger: () => { this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().then(undefined, onUnexpectedError); } @@ -1033,7 +1044,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL // Toggle selection button this._toggleSelectionFind = this._register(new Checkbox({ - icon: findSelectionIcon, + icon: ThemeIcon.asCSSIcon(findSelectionIcon), title: NLS_TOGGLE_SELECTION_FIND_TITLE + this._keybindingLabelFor(FIND_IDS.ToggleSearchScopeCommand), isChecked: false })); @@ -1066,7 +1077,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL // Close button this._closeBtn = this._register(new SimpleButton({ label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.CloseFindWidgetCommand), - className: findCloseIcon.classNames, + icon: widgetClose, onTrigger: () => { this._state.change({ isRevealed: false, searchScope: null }, false); }, @@ -1130,7 +1141,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL // Replace one button this._replaceBtn = this._register(new SimpleButton({ label: NLS_REPLACE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceOneAction), - className: findReplaceIcon.classNames, + icon: findReplaceIcon, onTrigger: () => { this._controller.replace(); }, @@ -1145,7 +1156,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL // Replace all button this._replaceAllBtn = this._register(new SimpleButton({ label: NLS_REPLACE_ALL_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceAllAction), - className: findReplaceAllIcon.classNames, + icon: findReplaceAllIcon, onTrigger: () => { this._controller.replaceAll(); } @@ -1255,11 +1266,35 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL const value = this._codeEditor.getOption(EditorOption.accessibilitySupport); this._findInput.setFocusInputOnOptionClick(value !== AccessibilitySupport.Enabled); } + + getViewState() { + let widgetViewZoneVisible = false; + if (this._viewZone && this._viewZoneId) { + widgetViewZoneVisible = this._viewZone.heightInPx > this._codeEditor.getScrollTop(); + } + + return { + widgetViewZoneVisible, + scrollTop: this._codeEditor.getScrollTop() + }; + } + + setViewState(state?: { widgetViewZoneVisible: boolean; scrollTop: number }) { + if (!state) { + return; + } + + if (state.widgetViewZoneVisible) { + // we should add the view zone + this._layoutViewZone(state.scrollTop); + } + } } export interface ISimpleButtonOpts { readonly label: string; - readonly className: string; + readonly className?: string; + readonly icon?: ThemeIcon; readonly onTrigger: () => void; readonly onKeyDown?: (e: IKeyboardEvent) => void; } @@ -1273,10 +1308,18 @@ export class SimpleButton extends Widget { super(); this._opts = opts; + let className = 'button'; + if (this._opts.className) { + className = className + ' ' + this._opts.className; + } + if (this._opts.icon) { + className = className + ' ' + ThemeIcon.asClassName(this._opts.icon); + } + this._domNode = document.createElement('div'); this._domNode.title = this._opts.label; this._domNode.tabIndex = 0; - this._domNode.className = 'button ' + this._opts.className; + this._domNode.className = className; this._domNode.setAttribute('role', 'button'); this._domNode.setAttribute('aria-label', this._opts.label); @@ -1318,11 +1361,11 @@ export class SimpleButton extends Widget { public setExpanded(expanded: boolean): void { this._domNode.setAttribute('aria-expanded', String(!!expanded)); if (expanded) { - this._domNode.classList.remove(...findCollapsedIcon.classNames.split(' ')); - this._domNode.classList.add(...findExpandedIcon.classNames.split(' ')); + this._domNode.classList.remove(...ThemeIcon.asClassNameArray(findCollapsedIcon)); + this._domNode.classList.add(...ThemeIcon.asClassNameArray(findExpandedIcon)); } else { - this._domNode.classList.remove(...findExpandedIcon.classNames.split(' ')); - this._domNode.classList.add(...findCollapsedIcon.classNames.split(' ')); + this._domNode.classList.remove(...ThemeIcon.asClassNameArray(findExpandedIcon)); + this._domNode.classList.add(...ThemeIcon.asClassNameArray(findCollapsedIcon)); } } } @@ -1345,7 +1388,7 @@ registerThemingParticipant((theme, collector) => { const widgetShadowColor = theme.getColor(widgetShadow); if (widgetShadowColor) { - collector.addRule(`.monaco-editor .find-widget { box-shadow: 0 2px 8px ${widgetShadowColor}; }`); + collector.addRule(`.monaco-editor .find-widget { box-shadow: 0 0 8px 2px ${widgetShadowColor}; }`); } const findMatchHighlightBorder = theme.getColor(editorFindMatchHighlightBorder); diff --git a/src/vs/editor/contrib/find/test/findController.test.ts b/src/vs/editor/contrib/find/test/findController.test.ts index ee0c48626..8d9216ea0 100644 --- a/src/vs/editor/contrib/find/test/findController.test.ts +++ b/src/vs/editor/contrib/find/test/findController.test.ts @@ -12,7 +12,7 @@ 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 { CommonFindController, FindStartFocusAction, IFindStartOptions, NextMatchFindAction, NextSelectionMatchFindAction, StartFindAction, StartFindReplaceAction } from 'vs/editor/contrib/find/findController'; +import { CommonFindController, FindStartFocusAction, IFindStartOptions, NextMatchFindAction, NextSelectionMatchFindAction, StartFindAction, StartFindReplaceAction, StartFindWithSelectionAction } from 'vs/editor/contrib/find/findController'; import { CONTEXT_FIND_INPUT_FOCUSED } from 'vs/editor/contrib/find/findModel'; import { withAsyncTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -61,14 +61,20 @@ suite('FindController', async () => { let serviceCollection = new ServiceCollection(); serviceCollection.set(IStorageService, { _serviceBrand: undefined, - onDidChangeStorage: Event.None, + onDidChangeTarget: Event.None, + onDidChangeValue: Event.None, onWillSaveState: Event.None, get: (key: string) => queryState[key], getBoolean: (key: string) => !!queryState[key], - getNumber: (key: string) => undefined, + getNumber: (key: string) => undefined!, store: (key: string, value: any) => { queryState[key] = value; return Promise.resolve(); }, - remove: () => undefined - } as any); + remove: () => undefined, + isNew: () => false, + flush: () => { return Promise.resolve(); }, + keys: () => [], + logStorage: () => { }, + migrate: () => { throw new Error(); } + } as IStorageService); if (platform.isMacintosh) { serviceCollection.set(IClipboardService, { @@ -272,7 +278,7 @@ suite('FindController', async () => { findController.setSearchString(testRegexString); await findController.start({ forceRevealReplace: false, - seedSearchStringFromSelection: false, + seedSearchStringFromSelection: 'none', seedSearchStringFromGlobalClipboard: false, shouldFocus: FindStartFocusAction.FocusFindInput, shouldAnimate: false, @@ -298,7 +304,7 @@ suite('FindController', async () => { let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); await findController.start({ forceRevealReplace: false, - seedSearchStringFromSelection: false, + seedSearchStringFromSelection: 'none', seedSearchStringFromGlobalClipboard: false, shouldFocus: FindStartFocusAction.NoFocusChange, shouldAnimate: false, @@ -428,6 +434,59 @@ suite('FindController', async () => { findController.dispose(); }); }); + + test('issue #47400, CMD+E supports feeding multiple line of text into the find widget', async () => { + await withAsyncTestCodeEditor([ + 'ABC', + 'ABC', + 'XYZ', + 'ABC', + 'ABC' + ], { serviceCollection: serviceCollection }, async (editor) => { + clipboardState = ''; + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let startFindAction = new StartFindAction(); + + // change selection + editor.setSelection(new Selection(1, 1, 1, 1)); + + // cmd+f - open find widget + await startFindAction.run(null, editor); + + editor.setSelection(new Selection(1, 1, 2, 4)); + let startFindWithSelectionAction = new StartFindWithSelectionAction(); + await startFindWithSelectionAction.run(null, editor); + let findState = findController.getState(); + + assert.deepEqual(findState.searchString.split(/\r\n|\r|\n/g), ['ABC', 'ABC']); + + editor.setSelection(new Selection(3, 1, 3, 1)); + await startFindWithSelectionAction.run(null, editor); + + findController.dispose(); + }); + }); + + test('issue #109756, CMD+E with empty cursor should always work', async () => { + await withAsyncTestCodeEditor([ + 'ABC', + 'ABC', + 'XYZ', + 'ABC', + 'ABC' + ], { serviceCollection: serviceCollection }, async (editor) => { + clipboardState = ''; + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + editor.setSelection(new Selection(1, 2, 1, 2)); + + let startFindWithSelectionAction = new StartFindWithSelectionAction(); + startFindWithSelectionAction.run(null, editor); + + let findState = findController.getState(); + assert.deepEqual(findState.searchString, 'ABC'); + findController.dispose(); + }); + }); }); suite('FindController query options persistence', async () => { @@ -438,14 +497,20 @@ suite('FindController query options persistence', async () => { let serviceCollection = new ServiceCollection(); serviceCollection.set(IStorageService, { _serviceBrand: undefined, - onDidChangeStorage: Event.None, + onDidChangeTarget: Event.None, + onDidChangeValue: Event.None, onWillSaveState: Event.None, get: (key: string) => queryState[key], getBoolean: (key: string) => !!queryState[key], - getNumber: (key: string) => undefined, + getNumber: (key: string) => undefined!, store: (key: string, value: any) => { queryState[key] = value; return Promise.resolve(); }, - remove: () => undefined - } as any); + remove: () => undefined, + isNew: () => false, + flush: () => { return Promise.resolve(); }, + keys: () => [], + logStorage: () => { }, + migrate: () => { throw new Error(); } + } as IStorageService); test('matchCase', async () => { await withAsyncTestCodeEditor([ @@ -524,9 +589,9 @@ suite('FindController query options persistence', async () => { ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, async (editor) => { // clipboardState = ''; let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); - const findConfig = { + const findConfig: IFindStartOptions = { forceRevealReplace: false, - seedSearchStringFromSelection: false, + seedSearchStringFromSelection: 'none', seedSearchStringFromGlobalClipboard: false, shouldFocus: FindStartFocusAction.NoFocusChange, shouldAnimate: false, @@ -558,7 +623,7 @@ suite('FindController query options persistence', async () => { await findController.start({ forceRevealReplace: false, - seedSearchStringFromSelection: false, + seedSearchStringFromSelection: 'none', seedSearchStringFromGlobalClipboard: false, shouldFocus: FindStartFocusAction.NoFocusChange, shouldAnimate: false, @@ -582,7 +647,7 @@ suite('FindController query options persistence', async () => { await findController.start({ forceRevealReplace: false, - seedSearchStringFromSelection: false, + seedSearchStringFromSelection: 'none', seedSearchStringFromGlobalClipboard: false, shouldFocus: FindStartFocusAction.NoFocusChange, shouldAnimate: false, @@ -607,7 +672,7 @@ suite('FindController query options persistence', async () => { await findController.start({ forceRevealReplace: false, - seedSearchStringFromSelection: false, + seedSearchStringFromSelection: 'none', seedSearchStringFromGlobalClipboard: false, shouldFocus: FindStartFocusAction.NoFocusChange, shouldAnimate: false, diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index 0bfa5f681..1d48e0611 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -32,7 +32,7 @@ import { InitializingRangeProvider, ID_INIT_PROVIDER } from 'vs/editor/contrib/f import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { onUnexpectedError } from 'vs/base/common/errors'; import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { registerColor, editorSelectionBackground, transparent, iconForeground } from 'vs/platform/theme/common/colorRegistry'; const CONTEXT_FOLDING_ENABLED = new RawContextKey('foldingEnabled', false); @@ -916,8 +916,8 @@ registerThemingParticipant((theme, collector) => { const editorFoldColor = theme.getColor(editorFoldForeground); if (editorFoldColor) { collector.addRule(` - .monaco-editor .cldr${foldingExpandedIcon.cssSelector}, - .monaco-editor .cldr${foldingCollapsedIcon.cssSelector} { + .monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingExpandedIcon)}, + .monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingCollapsedIcon)} { color: ${editorFoldColor} !important; } `); diff --git a/src/vs/editor/contrib/folding/foldingDecorations.ts b/src/vs/editor/contrib/folding/foldingDecorations.ts index c34e2c212..22601d129 100644 --- a/src/vs/editor/contrib/folding/foldingDecorations.ts +++ b/src/vs/editor/contrib/folding/foldingDecorations.ts @@ -7,18 +7,20 @@ import { TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationsChangeA import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IDecorationProvider } from 'vs/editor/contrib/folding/foldingModel'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { Codicon, registerIcon } from 'vs/base/common/codicons'; - -export const foldingExpandedIcon = registerIcon('folding-expanded', Codicon.chevronDown); -export const foldingCollapsedIcon = registerIcon('folding-collapsed', Codicon.chevronRight); +import { Codicon } from 'vs/base/common/codicons'; +import { localize } from 'vs/nls'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +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.')); export class FoldingDecorationProvider implements IDecorationProvider { private static readonly COLLAPSED_VISUAL_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, afterContentClassName: 'inline-folded', isWholeLine: true, - firstLineDecorationClassName: foldingCollapsedIcon.classNames + firstLineDecorationClassName: ThemeIcon.asClassName(foldingCollapsedIcon) }); private static readonly COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION = ModelDecorationOptions.register({ @@ -26,19 +28,19 @@ export class FoldingDecorationProvider implements IDecorationProvider { afterContentClassName: 'inline-folded', className: 'folded-background', isWholeLine: true, - firstLineDecorationClassName: foldingCollapsedIcon.classNames + firstLineDecorationClassName: ThemeIcon.asClassName(foldingCollapsedIcon) }); private static readonly EXPANDED_AUTO_HIDE_VISUAL_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, isWholeLine: true, - firstLineDecorationClassName: foldingExpandedIcon.classNames + firstLineDecorationClassName: ThemeIcon.asClassName(foldingExpandedIcon) }); private static readonly EXPANDED_VISUAL_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, isWholeLine: true, - firstLineDecorationClassName: 'alwaysShowFoldIcons ' + foldingExpandedIcon.classNames + firstLineDecorationClassName: 'alwaysShowFoldIcons ' + ThemeIcon.asClassName(foldingExpandedIcon) }); private static readonly HIDDEN_RANGE_DECORATION = ModelDecorationOptions.register({ diff --git a/src/vs/editor/contrib/format/formatActions.ts b/src/vs/editor/contrib/format/formatActions.ts index bedb20cdd..cdfdac89b 100644 --- a/src/vs/editor/contrib/format/formatActions.ts +++ b/src/vs/editor/contrib/format/formatActions.ts @@ -215,13 +215,12 @@ class FormatDocumentAction extends EditorAction { alias: 'Format Document', precondition: ContextKeyExpr.and(EditorContextKeys.notInCompositeEditor, EditorContextKeys.writable, EditorContextKeys.hasDocumentFormattingProvider), kbOpts: { - kbExpr: ContextKeyExpr.and(EditorContextKeys.editorTextFocus, EditorContextKeys.hasDocumentFormattingProvider), + kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_F, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_I }, weight: KeybindingWeight.EditorContrib }, contextMenuOpts: { - when: EditorContextKeys.hasDocumentFormattingProvider, group: '1_modification', order: 1.3 } @@ -249,12 +248,12 @@ class FormatSelectionAction extends EditorAction { alias: 'Format Selection', precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasDocumentSelectionFormattingProvider), kbOpts: { - kbExpr: ContextKeyExpr.and(EditorContextKeys.editorTextFocus, EditorContextKeys.hasDocumentSelectionFormattingProvider), + kbExpr: EditorContextKeys.editorTextFocus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_F), weight: KeybindingWeight.EditorContrib }, contextMenuOpts: { - when: ContextKeyExpr.and(EditorContextKeys.hasDocumentSelectionFormattingProvider, EditorContextKeys.hasNonEmptySelection), + when: EditorContextKeys.hasNonEmptySelection, group: '1_modification', order: 1.31 } diff --git a/src/vs/editor/contrib/gotoError/gotoError.ts b/src/vs/editor/contrib/gotoError/gotoError.ts index 001ed3f27..259773d90 100644 --- a/src/vs/editor/contrib/gotoError/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/gotoError.ts @@ -20,9 +20,10 @@ import { MarkerNavigationWidget } from './gotoErrorWidget'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { MenuId } from 'vs/platform/actions/common/actions'; import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; -import { Codicon, registerIcon } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IMarkerNavigationService, MarkerList } from 'vs/editor/contrib/gotoError/markerNavigationService'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; export class MarkerController implements IEditorContribution { @@ -199,7 +200,7 @@ export class NextMarkerAction extends MarkerNavigationAction { menuOpts: { menuId: MarkerNavigationWidget.TitleMenu, title: NextMarkerAction.LABEL, - icon: registerIcon('marker-navigation-next', Codicon.chevronDown), + icon: registerIcon('marker-navigation-next', Codicon.chevronDown, nls.localize('nextMarkerIcon', 'Icon for goto next marker.')), group: 'navigation', order: 1 } @@ -224,7 +225,7 @@ class PrevMarkerAction extends MarkerNavigationAction { menuOpts: { menuId: MarkerNavigationWidget.TitleMenu, title: NextMarkerAction.LABEL, - icon: registerIcon('marker-navigation-previous', Codicon.chevronUp), + icon: registerIcon('marker-navigation-previous', Codicon.chevronUp, nls.localize('previousMarkerIcon', 'Icon for goto previous marker.')), group: 'navigation', order: 2 } diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index 66bdc31f0..10a503f67 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -30,6 +30,7 @@ import { MenuId, IMenuService } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { splitLines } from 'vs/base/common/strings'; class MessageWidget { @@ -102,7 +103,7 @@ class MessageWidget { } } - const lines = message.split(/\r\n|\r|\n/g); + const lines = splitLines(message); this._lines = lines.length; this._longestLineLength = 0; for (const line of lines) { @@ -296,7 +297,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { protected _fillHead(container: HTMLElement): void { super._fillHead(container); - this._disposables.add(this._actionbarWidget!.actionRunner.onDidBeforeRun(e => this.editor.focus())); + this._disposables.add(this._actionbarWidget!.actionRunner.onBeforeRun(e => this.editor.focus())); const actions: IAction[] = []; const menu = this._menuService.createMenu(MarkerNavigationWidget.TitleMenu, this._contextKeyService); diff --git a/src/vs/editor/contrib/gotoSymbol/goToCommands.ts b/src/vs/editor/contrib/gotoSymbol/goToCommands.ts index 232aab8d0..835033505 100644 --- a/src/vs/editor/contrib/gotoSymbol/goToCommands.ts +++ b/src/vs/editor/contrib/gotoSymbol/goToCommands.ts @@ -162,6 +162,9 @@ abstract class SymbolNavigationAction extends EditorAction { if (!range) { range = reference.range; } + if (!range) { + return undefined; + } const targetEditor = await editorService.openCodeEditor({ resource: reference.uri, diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts index b7942a37a..ead7b894a 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts @@ -10,7 +10,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IContextKey, IContextKeyService, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ReferencesModel, OneReference } from '../referencesModel'; @@ -101,7 +101,7 @@ export abstract class ReferencesController implements IEditorContribution { this._disposables.add(this._widget.onDidClose(() => { modelPromise.cancel(); if (this._widget) { - this._storageService.store(storageKey, JSON.stringify(this._widget.layoutData), StorageScope.GLOBAL); + this._storageService.store(storageKey, JSON.stringify(this._widget.layoutData), StorageScope.GLOBAL, StorageTarget.MACHINE); this._widget = undefined; } this.closeWidget(); @@ -117,17 +117,17 @@ export abstract class ReferencesController implements IEditorContribution { if (event.source !== 'editor' || !this._configurationService.getValue('editor.stablePeek')) { // when stable peek is configured we don't close // the peek window on selecting the editor - this.openReference(element, false); + this.openReference(element, false, false); } break; case 'side': - this.openReference(element, true); + this.openReference(element, true, false); break; case 'goto': if (peekMode) { this._gotoReference(element); } else { - this.openReference(element, false); + this.openReference(element, false, true); } break; } @@ -285,7 +285,7 @@ export abstract class ReferencesController implements IEditorContribution { }); } - openReference(ref: Location, sideBySide: boolean): void { + openReference(ref: Location, sideBySide: boolean, pinned: boolean): void { // clear stage if (!sideBySide) { this.closeWidget(); @@ -294,7 +294,7 @@ export abstract class ReferencesController implements IEditorContribution { const { uri, range } = ref; this._editorService.openCodeEditor({ resource: uri, - options: { selection: range } + options: { selection: range, pinned } }, this._editor, sideBySide); } } @@ -404,7 +404,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const listService = accessor.get(IListService); const focus = listService.lastFocusedList?.getFocus(); if (Array.isArray(focus) && focus[0] instanceof OneReference) { - withController(accessor, controller => controller.openReference(focus[0], true)); + withController(accessor, controller => controller.openReference(focus[0], true, true)); } } }); @@ -413,6 +413,6 @@ CommandsRegistry.registerCommand('openReference', (accessor) => { const listService = accessor.get(IListService); const focus = listService.lastFocusedList?.getFocus(); if (Array.isArray(focus) && focus[0] instanceof OneReference) { - withController(accessor, controller => controller.openReference(focus[0], false)); + withController(accessor, controller => controller.openReference(focus[0], false, true)); } }); diff --git a/src/vs/editor/contrib/gotoSymbol/referencesModel.ts b/src/vs/editor/contrib/gotoSymbol/referencesModel.ts index 5c4bbe4d0..334928f3c 100644 --- a/src/vs/editor/contrib/gotoSymbol/referencesModel.ts +++ b/src/vs/editor/contrib/gotoSymbol/referencesModel.ts @@ -41,10 +41,20 @@ export class OneReference { } get ariaMessage(): string { - return localize( - 'aria.oneReference', "symbol in {0} on line {1} at column {2}", - basename(this.uri), this.range.startLineNumber, this.range.startColumn - ); + + const preview = this.parent.getPreview(this)?.preview(this.range); + + if (!preview) { + return localize( + 'aria.oneReference', "symbol in {0} on line {1} at column {2}", + basename(this.uri), this.range.startLineNumber, this.range.startColumn + ); + } else { + return localize( + 'aria.oneReference.preview', "symbol in {0} on line {1} at column {2}, {3}", + basename(this.uri), this.range.startLineNumber, this.range.startColumn, preview.value + ); + } } } diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index 92af0fba8..c57fbadbb 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -162,6 +162,15 @@ export class ModesHoverController implements IEditorContribution { return; } + + if ( + !this._isHoverSticky && targetType === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === ModesContentHoverWidget.ID + && this._contentWidget.value?.isColorPickerVisible() + ) { + // though the hover is not sticky, the color picker needs to. + return; + } + if (this._isHoverSticky && targetType === MouseTargetType.OVERLAY_WIDGET && mouseEvent.target.detail === ModesGlyphHoverWidget.ID) { // mouse moved on top of overlay hover widget return; diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 2cb7e7938..ad2d6b272 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -31,12 +31,12 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener'; import { MarkerController, NextMarkerAction } from 'vs/editor/contrib/gotoError/gotoError'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { CancelablePromise, createCancelablePromise, disposableTimeout } from 'vs/base/common/async'; import { getCodeActions, CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; import { QuickFixAction, QuickFixController } from 'vs/editor/contrib/codeAction/codeActionCommands'; import { CodeActionKind, CodeActionTrigger } from 'vs/editor/contrib/codeAction/types'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; +import { IIdentifiedSingleEditOperation, TrackedRangeStickiness } from 'vs/editor/common/model'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Constants } from 'vs/base/common/uint'; import { textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; @@ -251,6 +251,23 @@ export class ModesContentHoverWidget extends ContentHoverWidget { })); this._register(TokenizationRegistry.onDidChange((e) => { if (this.isVisible && this._lastRange && this._messages.length > 0) { + this._messages = this._messages.map(msg => { + // If a color hover is visible, we need to update the message that + // created it so that the color matches the last chosen color + if (msg instanceof ColorHover && !!this._lastRange?.intersectRanges(msg.range) && this._colorPicker?.model.color) { + const color = this._colorPicker.model.color; + const newColor = { + red: color.rgba.r / 255, + green: color.rgba.g / 255, + blue: color.rgba.b / 255, + alpha: color.rgba.a + }; + return new ColorHover(msg.range, newColor, msg.provider); + } else { + return msg; + } + }); + this._hover.contentsDomNode.textContent = ''; this._renderMessages(this._lastRange, this._messages); } @@ -406,15 +423,17 @@ export class ModesContentHoverWidget extends ContentHoverWidget { model.presentation.textEdit.range.endLineNumber, model.presentation.textEdit.range.endColumn ); - newRange = newRange.setEndPosition(newRange.endLineNumber, newRange.startColumn + model.presentation.textEdit.text.length); + const trackedRange = this._editor.getModel()!._setTrackedRange(null, newRange, TrackedRangeStickiness.GrowsOnlyWhenTypingAfter); + this._editor.pushUndoStop(); + this._editor.executeEdits('colorpicker', textEdits); + newRange = this._editor.getModel()!._getTrackedRange(trackedRange) || newRange; } else { textEdits = [{ identifier: null, range, text: model.presentation.label, forceMoveMarkers: false }]; newRange = range.setEndPosition(range.endLineNumber, range.startColumn + model.presentation.label.length); + this._editor.pushUndoStop(); + this._editor.executeEdits('colorpicker', textEdits); } - this._editor.pushUndoStop(); - this._editor.executeEdits('colorpicker', textEdits); - if (model.presentation.additionalTextEdits) { textEdits = [...model.presentation.additionalTextEdits as IIdentifiedSingleEditOperation[]]; this._editor.executeEdits('colorpicker', textEdits); @@ -461,7 +480,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { const markdownHoverElement = $('div.hover-row.markdown-hover'); const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents')); const renderer = markdownDisposeables.add(new MarkdownRenderer({ editor: this._editor }, this._modeService, this._openerService)); - markdownDisposeables.add(renderer.onDidRenderCodeBlock(() => { + markdownDisposeables.add(renderer.onDidRenderAsync(() => { hoverContentsElement.className = 'hover-contents code-hover-contents'; this._hover.onContentsChanged(); })); @@ -559,6 +578,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { return hoverElement; } + private recentMarkerCodeActionsInfo: { marker: IMarker, hasCodeActions: boolean } | undefined = undefined; private renderMarkerStatusbar(markerHover: MarkerHover): HTMLElement { const hoverElement = $('div.hover-row.status-bar'); const disposables = new DisposableStore(); @@ -577,24 +597,28 @@ export class ModesContentHoverWidget extends ContentHoverWidget { if (!this._editor.getOption(EditorOption.readOnly)) { const quickfixPlaceholderElement = dom.append(actionsElement, $('div')); - quickfixPlaceholderElement.style.opacity = '0'; - quickfixPlaceholderElement.style.transition = 'opacity 0.2s'; - setTimeout(() => quickfixPlaceholderElement.style.opacity = '1', 200); - quickfixPlaceholderElement.textContent = nls.localize('checkingForQuickFixes', "Checking for quick fixes..."); - disposables.add(toDisposable(() => quickfixPlaceholderElement.remove())); - + if (this.recentMarkerCodeActionsInfo) { + if (IMarkerData.makeKey(this.recentMarkerCodeActionsInfo.marker) === IMarkerData.makeKey(markerHover.marker)) { + if (!this.recentMarkerCodeActionsInfo.hasCodeActions) { + quickfixPlaceholderElement.textContent = nls.localize('noQuickFixes', "No quick fixes available"); + } + } else { + this.recentMarkerCodeActionsInfo = undefined; + } + } + const updatePlaceholderDisposable = disposables.add(disposableTimeout(() => quickfixPlaceholderElement.textContent = nls.localize('checkingForQuickFixes', "Checking for quick fixes..."), 64)); const codeActionsPromise = this.getCodeActions(markerHover.marker); disposables.add(toDisposable(() => codeActionsPromise.cancel())); codeActionsPromise.then(actions => { - quickfixPlaceholderElement.style.transition = ''; - quickfixPlaceholderElement.style.opacity = '1'; + updatePlaceholderDisposable.dispose(); + this.recentMarkerCodeActionsInfo = { marker: markerHover.marker, hasCodeActions: actions.validActions.length > 0 }; - if (!actions.validActions.length) { + if (!this.recentMarkerCodeActionsInfo.hasCodeActions) { actions.dispose(); quickfixPlaceholderElement.textContent = nls.localize('noQuickFixes', "No quick fixes available"); return; } - quickfixPlaceholderElement.remove(); + quickfixPlaceholderElement.style.display = 'none'; let showing = false; disposables.add(toDisposable(() => { diff --git a/src/vs/editor/contrib/indentation/indentation.ts b/src/vs/editor/contrib/indentation/indentation.ts index 11c8b67f9..720c8e769 100644 --- a/src/vs/editor/contrib/indentation/indentation.ts +++ b/src/vs/editor/contrib/indentation/indentation.ts @@ -84,7 +84,7 @@ export function getReindentEditOperations(model: ITextModel, startLineNumber: nu } if (currentLineText !== adjustedLineContent) { - indentEdits.push(EditOperation.replace(new Selection(startLineNumber, 1, startLineNumber, oldIndentation.length + 1), TextModel.normalizeIndentation(globalIndent, indentSize, insertSpaces))); + indentEdits.push(EditOperation.replaceMove(new Selection(startLineNumber, 1, startLineNumber, oldIndentation.length + 1), TextModel.normalizeIndentation(globalIndent, indentSize, insertSpaces))); } } else { globalIndent = strings.getLeadingWhitespace(currentLineText); @@ -115,7 +115,7 @@ export function getReindentEditOperations(model: ITextModel, startLineNumber: nu } if (oldIndentation !== idealIndentForNextLine) { - indentEdits.push(EditOperation.replace(new Selection(lineNumber, 1, lineNumber, oldIndentation.length + 1), TextModel.normalizeIndentation(idealIndentForNextLine, indentSize, insertSpaces))); + indentEdits.push(EditOperation.replaceMove(new Selection(lineNumber, 1, lineNumber, oldIndentation.length + 1), TextModel.normalizeIndentation(idealIndentForNextLine, indentSize, insertSpaces))); } // calculate idealIndentForNextLine @@ -472,7 +472,6 @@ export class AutoIndentOnPaste implements IEditorContribution { } const autoIndent = this.editor.getOption(EditorOption.autoIndent); const { tabSize, indentSize, insertSpaces } = model.getOptions(); - this.editor.pushUndoStop(); let textEdits: TextEdit[] = []; let indentConverter = { @@ -583,9 +582,12 @@ export class AutoIndentOnPaste implements IEditorContribution { } } - let cmd = new AutoIndentOnPasteCommand(textEdits, this.editor.getSelection()!); - this.editor.executeCommand('autoIndentOnPaste', cmd); - this.editor.pushUndoStop(); + if (textEdits.length > 0) { + this.editor.pushUndoStop(); + let cmd = new AutoIndentOnPasteCommand(textEdits, this.editor.getSelection()!); + this.editor.executeCommand('autoIndentOnPaste', cmd); + this.editor.pushUndoStop(); + } } private shouldIgnoreLine(model: ITextModel, lineNumber: number): boolean { diff --git a/src/vs/editor/contrib/linesOperations/copyLinesCommand.ts b/src/vs/editor/contrib/linesOperations/copyLinesCommand.ts index 6b76a75b7..d88851410 100644 --- a/src/vs/editor/contrib/linesOperations/copyLinesCommand.ts +++ b/src/vs/editor/contrib/linesOperations/copyLinesCommand.ts @@ -12,15 +12,17 @@ export class CopyLinesCommand implements ICommand { private readonly _selection: Selection; private readonly _isCopyingDown: boolean; + private readonly _noop: boolean; private _selectionDirection: SelectionDirection; private _selectionId: string | null; private _startLineNumberDelta: number; private _endLineNumberDelta: number; - constructor(selection: Selection, isCopyingDown: boolean) { + constructor(selection: Selection, isCopyingDown: boolean, noop?: boolean) { this._selection = selection; this._isCopyingDown = isCopyingDown; + this._noop = noop || false; this._selectionDirection = SelectionDirection.LTR; this._selectionId = null; this._startLineNumberDelta = 0; @@ -51,10 +53,14 @@ export class CopyLinesCommand implements ICommand { } } - if (!this._isCopyingDown) { - builder.addEditOperation(new Range(s.endLineNumber, model.getLineMaxColumn(s.endLineNumber), s.endLineNumber, model.getLineMaxColumn(s.endLineNumber)), '\n' + sourceText); + if (this._noop) { + builder.addEditOperation(new Range(s.endLineNumber, model.getLineMaxColumn(s.endLineNumber), s.endLineNumber + 1, 1), s.endLineNumber === model.getLineCount() ? '' : '\n'); } else { - builder.addEditOperation(new Range(s.startLineNumber, 1, s.startLineNumber, 1), sourceText + '\n'); + if (!this._isCopyingDown) { + builder.addEditOperation(new Range(s.endLineNumber, model.getLineMaxColumn(s.endLineNumber), s.endLineNumber, model.getLineMaxColumn(s.endLineNumber)), '\n' + sourceText); + } else { + builder.addEditOperation(new Range(s.startLineNumber, 1, s.startLineNumber, 1), sourceText + '\n'); + } } this._selectionId = builder.trackSelection(s); diff --git a/src/vs/editor/contrib/linesOperations/linesOperations.ts b/src/vs/editor/contrib/linesOperations/linesOperations.ts index bc7fd660d..9c365d507 100644 --- a/src/vs/editor/contrib/linesOperations/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/linesOperations.ts @@ -63,10 +63,7 @@ abstract class AbstractCopyLinesAction extends EditorAction { const commands: ICommand[] = []; for (const selection of selections) { - if (selection.ignore) { - continue; - } - commands.push(new CopyLinesCommand(selection.selection, this.down)); + commands.push(new CopyLinesCommand(selection.selection, this.down, selection.ignore)); } editor.pushUndoStop(); diff --git a/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts b/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts index d9600938c..681c8f027 100644 --- a/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts +++ b/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts @@ -9,7 +9,7 @@ import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { ICommand, ICursorStateComputerData, IEditOperationBuilder } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { IndentAction } from 'vs/editor/common/modes/languageConfiguration'; +import { CompleteEnterAction, IndentAction } from 'vs/editor/common/modes/languageConfiguration'; import { IIndentConverter, LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { IndentConsts } from 'vs/editor/common/modes/supports/indentRules'; import * as indentUtils from 'vs/editor/contrib/indentation/indentUtils'; @@ -135,7 +135,8 @@ export class MoveLinesCommand implements ICommand { // to s.startLineNumber builder.addEditOperation(new Range(s.startLineNumber, 1, s.startLineNumber, 1), insertingText + '\n'); - let ret = this.matchEnterRule(model, indentConverter, tabSize, s.startLineNumber, s.startLineNumber, insertingText); + let ret = this.matchEnterRuleMovingDown(model, indentConverter, tabSize, s.startLineNumber, movingLineNumber, insertingText); + // check if the line being moved before matches onEnter rules, if so let's adjust the indentation by onEnter rules. if (ret !== null) { if (ret !== 0) { @@ -229,31 +230,7 @@ export class MoveLinesCommand implements ICommand { }; } - private matchEnterRule(model: ITextModel, indentConverter: IIndentConverter, tabSize: number, line: number, oneLineAbove: number, oneLineAboveText?: string) { - let validPrecedingLine = oneLineAbove; - while (validPrecedingLine >= 1) { - // ship empty lines as empty lines just inherit indentation - let lineContent; - if (validPrecedingLine === oneLineAbove && oneLineAboveText !== undefined) { - lineContent = oneLineAboveText; - } else { - lineContent = model.getLineContent(validPrecedingLine); - } - - let nonWhitespaceIdx = strings.lastNonWhitespaceIndex(lineContent); - if (nonWhitespaceIdx >= 0) { - break; - } - validPrecedingLine--; - } - - if (validPrecedingLine < 1 || line > model.getLineCount()) { - return null; - } - - let maxColumn = model.getLineMaxColumn(validPrecedingLine); - let enter = LanguageConfigurationRegistry.getEnterAction(this._autoIndent, model, new Range(validPrecedingLine, maxColumn, validPrecedingLine, maxColumn)); - + private parseEnterResult(model: ITextModel, indentConverter: IIndentConverter, tabSize: number, line: number, enter: CompleteEnterAction | null) { if (enter) { let enterPrefix = enter.indentation; @@ -283,6 +260,72 @@ export class MoveLinesCommand implements ICommand { return null; } + /** + * + * @param model + * @param indentConverter + * @param tabSize + * @param line the line moving down + * @param futureAboveLineNumber the line which will be at the `line` position + * @param futureAboveLineText + */ + private matchEnterRuleMovingDown(model: ITextModel, indentConverter: IIndentConverter, tabSize: number, line: number, futureAboveLineNumber: number, futureAboveLineText: string) { + if (strings.lastNonWhitespaceIndex(futureAboveLineText) >= 0) { + // break + let maxColumn = model.getLineMaxColumn(futureAboveLineNumber); + let enter = LanguageConfigurationRegistry.getEnterAction(this._autoIndent, model, new Range(futureAboveLineNumber, maxColumn, futureAboveLineNumber, maxColumn)); + return this.parseEnterResult(model, indentConverter, tabSize, line, enter); + } else { + // go upwards, starting from `line - 1` + let validPrecedingLine = line - 1; + while (validPrecedingLine >= 1) { + let lineContent = model.getLineContent(validPrecedingLine); + let nonWhitespaceIdx = strings.lastNonWhitespaceIndex(lineContent); + + if (nonWhitespaceIdx >= 0) { + break; + } + + validPrecedingLine--; + } + + if (validPrecedingLine < 1 || line > model.getLineCount()) { + return null; + } + + let maxColumn = model.getLineMaxColumn(validPrecedingLine); + let enter = LanguageConfigurationRegistry.getEnterAction(this._autoIndent, model, new Range(validPrecedingLine, maxColumn, validPrecedingLine, maxColumn)); + return this.parseEnterResult(model, indentConverter, tabSize, line, enter); + } + } + + private matchEnterRule(model: ITextModel, indentConverter: IIndentConverter, tabSize: number, line: number, oneLineAbove: number, oneLineAboveText?: string) { + let validPrecedingLine = oneLineAbove; + while (validPrecedingLine >= 1) { + // ship empty lines as empty lines just inherit indentation + let lineContent; + if (validPrecedingLine === oneLineAbove && oneLineAboveText !== undefined) { + lineContent = oneLineAboveText; + } else { + lineContent = model.getLineContent(validPrecedingLine); + } + + let nonWhitespaceIdx = strings.lastNonWhitespaceIndex(lineContent); + if (nonWhitespaceIdx >= 0) { + break; + } + validPrecedingLine--; + } + + if (validPrecedingLine < 1 || line > model.getLineCount()) { + return null; + } + + let maxColumn = model.getLineMaxColumn(validPrecedingLine); + let enter = LanguageConfigurationRegistry.getEnterAction(this._autoIndent, model, new Range(validPrecedingLine, maxColumn, validPrecedingLine, maxColumn)); + return this.parseEnterResult(model, indentConverter, tabSize, line, enter); + } + private trimLeft(str: string) { return str.replace(/^\s+/, ''); } diff --git a/src/vs/editor/contrib/linesOperations/test/moveLinesCommand.test.ts b/src/vs/editor/contrib/linesOperations/test/moveLinesCommand.test.ts index dd0a86ac0..e8bdc29c8 100644 --- a/src/vs/editor/contrib/linesOperations/test/moveLinesCommand.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/moveLinesCommand.test.ts @@ -329,6 +329,7 @@ suite('Editor contrib - Move Lines Command honors Indentation Rules', () => { mode.dispose(); }); + test('move line should still work as before if there is no indentation rules', () => { testMoveLinesUpWithIndentCommand( null!, @@ -351,3 +352,55 @@ suite('Editor contrib - Move Lines Command honors Indentation Rules', () => { ); }); }); + +class EnterRulesMode extends MockMode { + private static readonly _id = new LanguageIdentifier('moveLinesEnterMode', 8); + constructor() { + super(EnterRulesMode._id); + this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), { + indentationRules: { + decreaseIndentPattern: /^\s*\[$/, + increaseIndentPattern: /^\s*\]$/, + }, + brackets: [ + ['{', '}'] + ] + })); + } +} + +suite('Editor - contrib - Move Lines Command honors onEnter Rules', () => { + + test('issue #54829. move block across block', () => { + let mode = new EnterRulesMode(); + + testMoveLinesDownWithIndentCommand( + mode.getLanguageIdentifier(), + + [ + 'if (true) {', + ' if (false) {', + ' if (1) {', + ' console.log(\'b\');', + ' }', + ' console.log(\'a\');', + ' }', + '}' + ], + new Selection(3, 9, 5, 10), + [ + 'if (true) {', + ' if (false) {', + ' console.log(\'a\');', + ' if (1) {', + ' console.log(\'b\');', + ' }', + ' }', + '}' + ], + new Selection(4, 9, 6, 10), + ); + + mode.dispose(); + }); +}); diff --git a/src/vs/editor/contrib/rename/onTypeRename.ts b/src/vs/editor/contrib/linkedEditing/linkedEditing.ts similarity index 81% rename from src/vs/editor/contrib/rename/onTypeRename.ts rename to src/vs/editor/contrib/linkedEditing/linkedEditing.ts index 105dfb2dd..441460812 100644 --- a/src/vs/editor/contrib/rename/onTypeRename.ts +++ b/src/vs/editor/contrib/linkedEditing/linkedEditing.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/onTypeRename'; import * as nls from 'vs/nls'; import { registerEditorContribution, registerModelAndPositionCommand, EditorAction, EditorCommand, ServicesAccessor, registerEditorAction, registerEditorCommand } from 'vs/editor/browser/editorExtensions'; import * as arrays from 'vs/base/common/arrays'; @@ -15,7 +14,7 @@ import { Position, IPosition } from 'vs/editor/common/core/position'; import { ITextModel, IModelDeltaDecoration, TrackedRangeStickiness, IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { OnTypeRenameProviderRegistry } from 'vs/editor/common/modes'; +import { LinkedEditingRangeProviderRegistry, LinkedEditingRanges } from 'vs/editor/common/modes'; import { first, createCancelablePromise, CancelablePromise, Delayer } from 'vs/base/common/async'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { ContextKeyExpr, RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -31,19 +30,21 @@ import { registerThemingParticipant } from 'vs/platform/theme/common/themeServic import { Color } from 'vs/base/common/color'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; -export const CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE = new RawContextKey('onTypeRenameInputVisible', false); +export const CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE = new RawContextKey('LinkedEditingInputVisible', false); -export class OnTypeRenameContribution extends Disposable implements IEditorContribution { +const DECORATION_CLASS_NAME = 'linked-editing-decoration'; - public static readonly ID = 'editor.contrib.onTypeRename'; +export class LinkedEditingContribution extends Disposable implements IEditorContribution { + + public static readonly ID = 'editor.contrib.linkedEditing'; private static readonly DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges, - className: 'on-type-rename-decoration' + className: DECORATION_CLASS_NAME }); - static get(editor: ICodeEditor): OnTypeRenameContribution { - return editor.getContribution(OnTypeRenameContribution.ID); + static get(editor: ICodeEditor): LinkedEditingContribution { + return editor.getContribution(LinkedEditingContribution.ID); } private _debounceDuration = 200; @@ -92,11 +93,11 @@ export class OnTypeRenameContribution extends Disposable implements IEditorContr this._register(this._editor.onDidChangeModel(() => this.reinitialize())); this._register(this._editor.onDidChangeConfiguration(e => { - if (e.hasChanged(EditorOption.renameOnType)) { + if (e.hasChanged(EditorOption.linkedEditing) || e.hasChanged(EditorOption.renameOnType)) { this.reinitialize(); } })); - this._register(OnTypeRenameProviderRegistry.onDidChange(() => this.reinitialize())); + this._register(LinkedEditingRangeProviderRegistry.onDidChange(() => this.reinitialize())); this._register(this._editor.onDidChangeModelLanguage(() => this.reinitialize())); this.reinitialize(); @@ -104,7 +105,7 @@ export class OnTypeRenameContribution extends Disposable implements IEditorContr private reinitialize() { const model = this._editor.getModel(); - const isEnabled = model !== null && this._editor.getOption(EditorOption.renameOnType) && OnTypeRenameProviderRegistry.has(model); + const isEnabled = model !== null && (this._editor.getOption(EditorOption.linkedEditing) || this._editor.getOption(EditorOption.renameOnType)) && LinkedEditingRangeProviderRegistry.has(model); if (isEnabled === this._enabled) { return; } @@ -219,9 +220,10 @@ export class OnTypeRenameContribution extends Disposable implements IEditorContr } try { + this._editor.popUndoStop(); this._ignoreChangeEvent = true; const prevEditOperationType = this._editor._getViewModel().getPrevEditOperationType(); - this._editor.executeEdits('onTypeRename', edits); + this._editor.executeEdits('linkedEditing', edits); this._editor._getViewModel().setPrevEditOperationType(prevEditOperationType); } finally { this._ignoreChangeEvent = false; @@ -282,7 +284,7 @@ export class OnTypeRenameContribution extends Disposable implements IEditorContr this._currentRequestModelVersion = modelVersionId; const request = createCancelablePromise(async token => { try { - const response = await getOnTypeRenameRanges(model, position, token); + const response = await getLinkedEditingRanges(model, position, token); if (request !== this._currentRequest) { return; } @@ -312,12 +314,12 @@ export class OnTypeRenameContribution extends Disposable implements IEditorContr } if (!foundReferenceRange) { - // Cannot do on type rename if the ranges are not where the cursor is... + // Cannot do linked editing if the ranges are not where the cursor is... this.clearRanges(); return; } - const decorations: IModelDeltaDecoration[] = ranges.map(range => ({ range: range, options: OnTypeRenameContribution.DECORATION })); + const decorations: IModelDeltaDecoration[] = ranges.map(range => ({ range: range, options: LinkedEditingContribution.DECORATION })); this._visibleContextKey.set(true); this._currentDecorations = this._editor.deltaDecorations(this._currentDecorations, decorations); } catch (err) { @@ -361,12 +363,12 @@ export class OnTypeRenameContribution extends Disposable implements IEditorContr // } } -export class OnTypeRenameAction extends EditorAction { +export class LinkedEditingAction extends EditorAction { constructor() { super({ - id: 'editor.action.onTypeRename', - label: nls.localize('onTypeRename.label', "On Type Rename Symbol"), - alias: 'On Type Rename Symbol', + id: 'editor.action.linkedEditing', + label: nls.localize('linkedEditing.label', "Start Linked Editing"), + alias: 'Start Linked Editing', precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasRenameProvider), kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -397,7 +399,7 @@ export class OnTypeRenameAction extends EditorAction { } run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { - const controller = OnTypeRenameContribution.get(editor); + const controller = LinkedEditingContribution.get(editor); if (controller) { return Promise.resolve(controller.updateRanges(true)); } @@ -405,9 +407,9 @@ export class OnTypeRenameAction extends EditorAction { } } -const OnTypeRenameCommand = EditorCommand.bindToContribution(OnTypeRenameContribution.get); -registerEditorCommand(new OnTypeRenameCommand({ - id: 'cancelOnTypeRenameInput', +const LinkedEditingCommand = EditorCommand.bindToContribution(LinkedEditingContribution.get); +registerEditorCommand(new LinkedEditingCommand({ + id: 'cancelLinkedEditingInput', precondition: CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE, handler: x => x.clearRanges(), kbOpts: { @@ -419,45 +421,31 @@ registerEditorCommand(new OnTypeRenameCommand({ })); -export function getOnTypeRenameRanges(model: ITextModel, position: Position, token: CancellationToken): Promise<{ - ranges: IRange[], - wordPattern?: RegExp -} | undefined | null> { - const orderedByScore = OnTypeRenameProviderRegistry.ordered(model); +function getLinkedEditingRanges(model: ITextModel, position: Position, token: CancellationToken): Promise { + const orderedByScore = LinkedEditingRangeProviderRegistry.ordered(model); - // in order of score ask the occurrences provider + // in order of score ask the linked editing range provider // until someone response with a good result - // (good = none empty array) - return first<{ - ranges: IRange[], - wordPattern?: RegExp - } | undefined>(orderedByScore.map(provider => () => { - return Promise.resolve(provider.provideOnTypeRenameRanges(model, position, token)).then((res) => { - if (!res) { - return undefined; - } - - return { - ranges: res.ranges, - wordPattern: res.wordPattern || provider.wordPattern - }; - }, (err) => { - onUnexpectedExternalError(err); + // (good = not null) + return first(orderedByScore.map(provider => async () => { + try { + return await provider.provideLinkedEditingRanges(model, position, token); + } catch (e) { + onUnexpectedExternalError(e); return undefined; - }); - + } }), result => !!result && arrays.isNonEmptyArray(result?.ranges)); } -export const editorOnTypeRenameBackground = registerColor('editor.onTypeRenameBackground', { dark: Color.fromHex('#f00').transparent(0.3), light: Color.fromHex('#f00').transparent(0.3), hc: Color.fromHex('#f00').transparent(0.3) }, nls.localize('editorOnTypeRenameBackground', 'Background color when the editor auto renames on type.')); +export const editorLinkedEditingBackground = registerColor('editor.linkedEditingBackground', { dark: Color.fromHex('#f00').transparent(0.3), light: Color.fromHex('#f00').transparent(0.3), hc: Color.fromHex('#f00').transparent(0.3) }, nls.localize('editorLinkedEditingBackground', 'Background color when the editor auto renames on type.')); registerThemingParticipant((theme, collector) => { - const editorOnTypeRenameBackgroundColor = theme.getColor(editorOnTypeRenameBackground); - if (editorOnTypeRenameBackgroundColor) { - collector.addRule(`.monaco-editor .on-type-rename-decoration { background: ${editorOnTypeRenameBackgroundColor}; border-left-color: ${editorOnTypeRenameBackgroundColor}; }`); + const editorLinkedEditingBackgroundColor = theme.getColor(editorLinkedEditingBackground); + if (editorLinkedEditingBackgroundColor) { + collector.addRule(`.monaco-editor .${DECORATION_CLASS_NAME} { background: ${editorLinkedEditingBackgroundColor}; border-left-color: ${editorLinkedEditingBackgroundColor}; }`); } }); -registerModelAndPositionCommand('_executeRenameOnTypeProvider', (model, position) => getOnTypeRenameRanges(model, position, CancellationToken.None)); +registerModelAndPositionCommand('_executeLinkedEditingProvider', (model, position) => getLinkedEditingRanges(model, position, CancellationToken.None)); -registerEditorContribution(OnTypeRenameContribution.ID, OnTypeRenameContribution); -registerEditorAction(OnTypeRenameAction); +registerEditorContribution(LinkedEditingContribution.ID, LinkedEditingContribution); +registerEditorAction(LinkedEditingAction); diff --git a/src/vs/editor/contrib/rename/test/onTypeRename.test.ts b/src/vs/editor/contrib/linkedEditing/test/linkedEditing.test..ts similarity index 91% rename from src/vs/editor/contrib/rename/test/onTypeRename.test.ts rename to src/vs/editor/contrib/linkedEditing/test/linkedEditing.test..ts index 05dba5e4b..9b00cb4b8 100644 --- a/src/vs/editor/contrib/rename/test/onTypeRename.test.ts +++ b/src/vs/editor/contrib/linkedEditing/test/linkedEditing.test..ts @@ -10,12 +10,13 @@ import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { Handler } from 'vs/editor/common/editorCommon'; import * as modes from 'vs/editor/common/modes'; -import { OnTypeRenameContribution } from 'vs/editor/contrib/rename/onTypeRename'; +import { LinkedEditingContribution } from 'vs/editor/contrib/linkedEditing/linkedEditing'; import { createTestCodeEditor, ITestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; import { ITextModel } from 'vs/editor/common/model'; import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/model/wordHelper'; +import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; const mockFile = URI.parse('test:somefile.ttt'); const mockFileSelector = { scheme: 'test' }; @@ -29,7 +30,12 @@ interface TestEditor { redo(): void; } -suite('On type rename', () => { +const languageIdentifier = new modes.LanguageIdentifier('linkedEditingTestLangage', 74); +LanguageConfigurationRegistry.register(languageIdentifier, { + wordPattern: /[a-zA-Z]+/ +}); + +suite('linked editing', () => { const disposables = new DisposableStore(); setup(() => { @@ -42,8 +48,8 @@ suite('On type rename', () => { function createMockEditor(text: string | string[]): ITestCodeEditor { const model = typeof text === 'string' - ? createTextModel(text, undefined, undefined, mockFile) - : createTextModel(text.join('\n'), undefined, undefined, mockFile); + ? createTextModel(text, undefined, languageIdentifier, mockFile) + : createTextModel(text.join('\n'), undefined, languageIdentifier, mockFile); const editor = createTestCodeEditor({ model }); disposables.add(model); @@ -55,18 +61,16 @@ suite('On type rename', () => { function testCase( name: string, - initialState: { text: string | string[], responseWordPattern?: RegExp, providerWordPattern?: RegExp }, + initialState: { text: string | string[], responseWordPattern?: RegExp }, operations: (editor: TestEditor) => Promise, expectedEndText: string | string[] ) { test(name, async () => { - disposables.add(modes.OnTypeRenameProviderRegistry.register(mockFileSelector, { - wordPattern: initialState.providerWordPattern, - provideOnTypeRenameRanges(model: ITextModel, pos: IPosition) { + disposables.add(modes.LinkedEditingRangeProviderRegistry.register(mockFileSelector, { + provideLinkedEditingRanges(model: ITextModel, pos: IPosition) { const wordAtPos = model.getWordAtPosition(pos); if (wordAtPos) { const matches = model.findMatches(wordAtPos.word, false, false, true, USUAL_WORD_SEPARATORS, false); - assert.ok(matches.length > 0); return { ranges: matches.map(m => m.range), wordPattern: initialState.responseWordPattern }; } return { ranges: [], wordPattern: initialState.responseWordPattern }; @@ -74,25 +78,25 @@ suite('On type rename', () => { })); const editor = createMockEditor(initialState.text); - editor.updateOptions({ renameOnType: true }); - const ontypeRenameContribution = editor.registerAndInstantiateContribution( - OnTypeRenameContribution.ID, - OnTypeRenameContribution + editor.updateOptions({ linkedEditing: true }); + const linkedEditingContribution = editor.registerAndInstantiateContribution( + LinkedEditingContribution.ID, + LinkedEditingContribution ); - ontypeRenameContribution.setDebounceDuration(0); + linkedEditingContribution.setDebounceDuration(0); const testEditor: TestEditor = { setPosition(pos: Position) { editor.setPosition(pos); - return ontypeRenameContribution.currentUpdateTriggerPromise; + return linkedEditingContribution.currentUpdateTriggerPromise; }, setSelection(sel: IRange) { editor.setSelection(sel); - return ontypeRenameContribution.currentUpdateTriggerPromise; + return linkedEditingContribution.currentUpdateTriggerPromise; }, trigger(source: string | null | undefined, handlerId: string, payload: any) { editor.trigger(source, handlerId, payload); - return ontypeRenameContribution.currentSyncTriggerPromise; + return linkedEditingContribution.currentSyncTriggerPromise; }, undo() { CoreEditingCommands.Undo.runEditorCommand(null, editor, null); @@ -247,7 +251,7 @@ suite('On type rename', () => { // testCase('Selection insert - across two boundary', state, async (editor) => { // const pos = new Position(1, 2); // await editor.setPosition(pos); - // await ontypeRenameContribution.updateLinkedUI(pos); + // await linkedEditingContribution.updateLinkedUI(pos); // await editor.setSelection(new Range(1, 4, 1, 9)); // await editor.trigger('keyboard', Handler.Type, { text: 'i' }); // }, ''); @@ -299,7 +303,7 @@ suite('On type rename', () => { const state3 = { ...state, - providerWordPattern: /[a-yA-Y]+/ + responseWordPattern: /[a-yA-Y]+/ }; testCase('Breakout with stop pattern - insert', state3, async (editor) => { @@ -334,7 +338,6 @@ suite('On type rename', () => { const state4 = { ...state, - providerWordPattern: /[a-yA-Y]+/, responseWordPattern: /[a-eA-E]+/ }; @@ -380,7 +383,7 @@ suite('On type rename', () => { // testCase('Delete - left all', state, async (editor) => { // const pos = new Position(1, 3); // await editor.setPosition(pos); - // await ontypeRenameContribution.updateLinkedUI(pos); + // await linkedEditingContribution.updateLinkedUI(pos); // await editor.trigger('keyboard', 'deleteAllLeft', {}); // }, '>'); @@ -390,7 +393,7 @@ suite('On type rename', () => { // testCase('Delete - left all then undo', state, async (editor) => { // const pos = new Position(1, 5); // await editor.setPosition(pos); - // await ontypeRenameContribution.updateLinkedUI(pos); + // await linkedEditingContribution.updateLinkedUI(pos); // await editor.trigger('keyboard', 'deleteAllLeft', {}); // editor.undo(); // }, '>'); diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index 283e8ef31..04e8b8aa0 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -47,7 +47,17 @@ function getHoverMessage(link: Link, useMetaKey: boolean): MarkdownString { : nls.localize('links.navigate.kb.alt', "alt + click"); if (link.url) { - const hoverMessage = new MarkdownString('', true).appendMarkdown(`[${label}](${link.url.toString()}) (${kb})`); + let nativeLabel = ''; + if (/^command:/i.test(link.url.toString())) { + // Don't show complete command arguments in the native tooltip + const match = link.url.toString().match(/^command:([^?#]+)/); + if (match) { + const commandId = match[1]; + const nativeLabelText = nls.localize('tooltip.explanation', "Execute command {0}", commandId); + nativeLabel = ` "${nativeLabelText}"`; + } + } + const hoverMessage = new MarkdownString('', true).appendMarkdown(`[${label}](${link.url.toString()}${nativeLabel}) (${kb})`); return hoverMessage; } else { return new MarkdownString().appendText(`${label} (${kb})`); diff --git a/src/vs/editor/contrib/message/messageController.css b/src/vs/editor/contrib/message/messageController.css index a3910415d..924349d11 100644 --- a/src/vs/editor/contrib/message/messageController.css +++ b/src/vs/editor/contrib/message/messageController.css @@ -8,6 +8,12 @@ z-index: 10000; } +.monaco-editor .monaco-editor-overlaymessage.below { + padding-bottom: 0; + padding-top: 8px; + z-index: 10000; +} + @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } @@ -37,3 +43,13 @@ border-width: 8px; position: absolute; } + +.monaco-editor .monaco-editor-overlaymessage:not(.below) .anchor.top, +.monaco-editor .monaco-editor-overlaymessage.below .anchor.below { + display: none; +} + +.monaco-editor .monaco-editor-overlaymessage.below .anchor.top { + display: inherit; + top: -8px; +} diff --git a/src/vs/editor/contrib/message/messageController.ts b/src/vs/editor/contrib/message/messageController.ts index 6bec24fd0..319f0d521 100644 --- a/src/vs/editor/contrib/message/messageController.ts +++ b/src/vs/editor/contrib/message/messageController.ts @@ -7,7 +7,7 @@ import 'vs/css!./messageController'; import * as nls from 'vs/nls'; import { TimeoutTimer } from 'vs/base/common/async'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { IDisposable, Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; @@ -20,7 +20,7 @@ import { inputValidationInfoBorder, inputValidationInfoBackground, inputValidati import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ColorScheme } from 'vs/platform/theme/common/theme'; -export class MessageController extends Disposable implements IEditorContribution { +export class MessageController implements IEditorContribution { public static readonly ID = 'editor.contrib.messageController'; @@ -32,21 +32,24 @@ export class MessageController extends Disposable implements IEditorContribution private readonly _editor: ICodeEditor; private readonly _visible: IContextKey; - private readonly _messageWidget = this._register(new MutableDisposable()); - private readonly _messageListeners = this._register(new DisposableStore()); + private readonly _messageWidget = new MutableDisposable(); + private readonly _messageListeners = new DisposableStore(); + private readonly _editorListener: IDisposable; constructor( editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService ) { - super(); + this._editor = editor; this._visible = MessageController.MESSAGE_VISIBLE.bindTo(contextKeyService); - this._register(this._editor.onDidAttemptReadOnlyEdit(() => this._onDidAttemptReadOnlyEdit())); + this._editorListener = this._editor.onDidAttemptReadOnlyEdit(() => this._onDidAttemptReadOnlyEdit()); } dispose(): void { - super.dispose(); + this._editorListener.dispose(); + this._messageListeners.dispose(); + this._messageWidget.dispose(); this._visible.reset(); } @@ -150,14 +153,18 @@ class MessageWidget implements IContentWidget { this._domNode = document.createElement('div'); this._domNode.classList.add('monaco-editor-overlaymessage'); + const anchorTop = document.createElement('div'); + anchorTop.classList.add('anchor', 'top'); + this._domNode.appendChild(anchorTop); + const message = document.createElement('div'); message.classList.add('message'); message.textContent = text; this._domNode.appendChild(message); - const anchor = document.createElement('div'); - anchor.classList.add('anchor'); - this._domNode.appendChild(anchor); + const anchorBottom = document.createElement('div'); + anchorBottom.classList.add('anchor', 'below'); + this._domNode.appendChild(anchorBottom); this._editor.addContentWidget(this); this._domNode.classList.add('fadeIn'); @@ -178,6 +185,11 @@ class MessageWidget implements IContentWidget { getPosition(): IContentWidgetPosition { return { position: this._position, preference: [ContentWidgetPositionPreference.ABOVE, ContentWidgetPositionPreference.BELOW] }; } + + afterRender(position: ContentWidgetPositionPreference | null): void { + this._domNode.classList.toggle('below', position === ContentWidgetPositionPreference.BELOW); + } + } registerEditorContribution(MessageController.ID, MessageController); @@ -186,7 +198,8 @@ registerThemingParticipant((theme, collector) => { const border = theme.getColor(inputValidationInfoBorder); if (border) { let borderWidth = theme.type === ColorScheme.HIGH_CONTRAST ? 2 : 1; - collector.addRule(`.monaco-editor .monaco-editor-overlaymessage .anchor { border-top-color: ${border}; }`); + collector.addRule(`.monaco-editor .monaco-editor-overlaymessage .anchor.below { border-top-color: ${border}; }`); + collector.addRule(`.monaco-editor .monaco-editor-overlaymessage .anchor.top { border-bottom-color: ${border}; }`); collector.addRule(`.monaco-editor .monaco-editor-overlaymessage .message { border: ${borderWidth}px solid ${border}; }`); } const background = theme.getColor(inputValidationInfoBackground); diff --git a/src/vs/editor/contrib/multicursor/multicursor.ts b/src/vs/editor/contrib/multicursor/multicursor.ts index 6a7c295e8..3606348fb 100644 --- a/src/vs/editor/contrib/multicursor/multicursor.ts +++ b/src/vs/editor/contrib/multicursor/multicursor.ts @@ -1016,6 +1016,11 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut }); this.decorations = this.editor.deltaDecorations(this.decorations, decorations); + + const currentFindState = CommonFindController.get(this.editor).getState(); + if (currentFindState.isRegex || currentFindState.matchCase || currentFindState.wholeWord) { + CommonFindController.get(this.editor).highlightFindOptions(true); + } } private static readonly _SELECTION_HIGHLIGHT_OVERVIEW = ModelDecorationOptions.register({ diff --git a/src/vs/editor/contrib/multicursor/test/multicursor.test.ts b/src/vs/editor/contrib/multicursor/test/multicursor.test.ts index 8b25ecac5..6e1e0a9be 100644 --- a/src/vs/editor/contrib/multicursor/test/multicursor.test.ts +++ b/src/vs/editor/contrib/multicursor/test/multicursor.test.ts @@ -61,7 +61,8 @@ suite('Multicursor selection', () => { let serviceCollection = new ServiceCollection(); serviceCollection.set(IStorageService, { _serviceBrand: undefined, - onDidChangeStorage: Event.None, + onDidChangeValue: Event.None, + onDidChangeTarget: Event.None, onWillSaveState: Event.None, get: (key: string) => queryState[key], getBoolean: (key: string) => !!queryState[key], @@ -70,8 +71,9 @@ suite('Multicursor selection', () => { remove: (key) => undefined, logStorage: () => undefined, migrate: (toWorkspace) => Promise.resolve(undefined), - flush: () => undefined, - isNew: () => true + flush: () => Promise.resolve(undefined), + isNew: () => true, + keys: () => [] } as IStorageService); test('issue #8817: Cursor position changes when you cancel multicursor', () => { diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts index 2d0d4bcd3..54b7a807b 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts @@ -20,17 +20,18 @@ import * as nls from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { editorHoverBackground, editorHoverBorder, textCodeBlockBackground, textLinkForeground, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; -import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ParameterHintsModel, TriggerContext } from 'vs/editor/contrib/parameterHints/parameterHintsModel'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; -import { registerIcon, Codicon } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; import { assertIsDefined } from 'vs/base/common/types'; import { ColorScheme } from 'vs/platform/theme/common/theme'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; const $ = dom.$; -const parameterHintsNextIcon = registerIcon('parameter-hints-next', Codicon.chevronDown); -const parameterHintsPreviousIcon = registerIcon('parameter-hints-previous', Codicon.chevronUp); +const parameterHintsNextIcon = registerIcon('parameter-hints-next', Codicon.chevronDown, nls.localize('parameterHintsNextIcon', 'Icon for show next parameter hint.')); +const parameterHintsPreviousIcon = registerIcon('parameter-hints-previous', Codicon.chevronUp, nls.localize('parameterHintsPreviousIcon', 'Icon for show previous parameter hint.')); export class ParameterHintsWidget extends Disposable implements IContentWidget { @@ -84,9 +85,9 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { wrapper.tabIndex = -1; const controls = dom.append(wrapper, $('.controls')); - const previous = dom.append(controls, $('.button' + parameterHintsPreviousIcon.cssSelector)); + const previous = dom.append(controls, $('.button' + ThemeIcon.asCSSSelector(parameterHintsPreviousIcon))); const overloads = dom.append(controls, $('.overloads')); - const next = dom.append(controls, $('.button' + parameterHintsNextIcon.cssSelector)); + const next = dom.append(controls, $('.button' + ThemeIcon.asCSSSelector(parameterHintsNextIcon))); const onPreviousClick = stop(domEvent(previous, 'click')); this._register(onPreviousClick(this.previous, this)); diff --git a/src/vs/editor/contrib/parameterHints/provideSignatureHelp.ts b/src/vs/editor/contrib/parameterHints/provideSignatureHelp.ts index 8aaf09f8b..d513ef4b9 100644 --- a/src/vs/editor/contrib/parameterHints/provideSignatureHelp.ts +++ b/src/vs/editor/contrib/parameterHints/provideSignatureHelp.ts @@ -5,12 +5,15 @@ import { first } from 'vs/base/common/async'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; -import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; -import { Position } from 'vs/editor/common/core/position'; +import { IPosition, Position } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { URI } from 'vs/base/common/uri'; +import { assertType } from 'vs/base/common/types'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; export const Context = { Visible: new RawContextKey('parameterHintsVisible', false), @@ -32,17 +35,29 @@ export function provideSignatureHelp( })); } -registerDefaultLanguageCommand('_executeSignatureHelpProvider', async (model, position, args) => { - const result = await provideSignatureHelp(model, position, { - triggerKind: modes.SignatureHelpTriggerKind.Invoke, - isRetrigger: false, - triggerCharacter: args['triggerCharacter'] - }, CancellationToken.None); +CommandsRegistry.registerCommand('_executeSignatureHelpProvider', async (accessor, ...args: [URI, IPosition, string?]) => { + const [uri, position, triggerCharacter] = args; + assertType(URI.isUri(uri)); + assertType(Position.isIPosition(position)); + assertType(typeof triggerCharacter === 'string' || !triggerCharacter); - if (!result) { - return undefined; + const ref = await accessor.get(ITextModelService).createModelReference(uri); + try { + + const result = await provideSignatureHelp(ref.object.textEditorModel, Position.lift(position), { + triggerKind: modes.SignatureHelpTriggerKind.Invoke, + isRetrigger: false, + triggerCharacter, + }, CancellationToken.None); + + if (!result) { + return undefined; + } + + setTimeout(() => result.dispose(), 0); + return result.value; + + } finally { + ref.dispose(); } - - setTimeout(() => result.dispose(), 0); - return result.value; }); diff --git a/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts b/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts index 039dfbd30..ef8cf91b8 100644 --- a/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts @@ -26,6 +26,20 @@ export interface IEditorNavigationQuickAccessOptions { canAcceptInBackground?: boolean; } +export interface IQuickAccessTextEditorContext { + + /** + * The current active editor. + */ + readonly editor: IEditor; + + /** + * If defined, allows to restore the original view state + * the text editor had before quick access opened. + */ + restoreViewState?: () => void; +} + /** * A reusable quick access provider for the editor with support * for adding decorations for navigating in the currently active file @@ -69,6 +83,7 @@ export abstract class AbstractEditorNavigationQuickAccessProvider implements IQu // With text control const editor = this.activeTextEditorControl; if (editor && this.canProvideWithTextEditor(editor)) { + const context: IQuickAccessTextEditorContext = { editor }; // Restore any view state if this picker was closed // without actually going to a line @@ -84,18 +99,20 @@ export abstract class AbstractEditorNavigationQuickAccessProvider implements IQu lastKnownEditorViewState = withNullAsUndefined(editor.saveViewState()); })); - disposables.add(once(token.onCancellationRequested)(() => { + context.restoreViewState = () => { if (lastKnownEditorViewState && editor === this.activeTextEditorControl) { editor.restoreViewState(lastKnownEditorViewState); } - })); + }; + + disposables.add(once(token.onCancellationRequested)(() => context.restoreViewState?.())); } // Clean up decorations on dispose disposables.add(toDisposable(() => this.clearDecorations(editor))); // Ask subclass for entries - disposables.add(this.provideWithTextEditor(editor, picker, token)); + disposables.add(this.provideWithTextEditor(context, picker, token)); } // Without text control @@ -116,14 +133,14 @@ export abstract class AbstractEditorNavigationQuickAccessProvider implements IQu /** * Subclasses to implement to provide picks for the picker when an editor is active. */ - protected abstract provideWithTextEditor(editor: IEditor, picker: IQuickPick, token: CancellationToken): IDisposable; + protected abstract provideWithTextEditor(context: IQuickAccessTextEditorContext, picker: IQuickPick, token: CancellationToken): IDisposable; /** * Subclasses to implement to provide picks for the picker when no editor is active. */ protected abstract provideWithoutTextEditor(picker: IQuickPick, token: CancellationToken): IDisposable; - protected gotoLocation(editor: IEditor, options: { range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean, preserveFocus?: boolean }): void { + protected gotoLocation({ editor }: IQuickAccessTextEditorContext, options: { range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean, preserveFocus?: boolean }): void { editor.setSelection(options.range); editor.revealRangeInCenter(options.range, ScrollType.Smooth); if (!options.preserveFocus) { diff --git a/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts index 0c17d7c0b..2885cc4fb 100644 --- a/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts @@ -9,7 +9,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { DisposableStore, IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IEditor, ScrollType } from 'vs/editor/common/editorCommon'; import { IRange } from 'vs/editor/common/core/range'; -import { AbstractEditorNavigationQuickAccessProvider } from 'vs/editor/contrib/quickAccess/editorNavigationQuickAccess'; +import { AbstractEditorNavigationQuickAccessProvider, IQuickAccessTextEditorContext } from 'vs/editor/contrib/quickAccess/editorNavigationQuickAccess'; import { IPosition } from 'vs/editor/common/core/position'; import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorOption, RenderLineNumbersType } from 'vs/editor/common/config/editorOptions'; @@ -33,7 +33,8 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor return Disposable.None; } - protected provideWithTextEditor(editor: IEditor, picker: IQuickPick, token: CancellationToken): IDisposable { + protected provideWithTextEditor(context: IQuickAccessTextEditorContext, picker: IQuickPick, token: CancellationToken): IDisposable { + const editor = context.editor; const disposables = new DisposableStore(); // Goto line once picked @@ -44,7 +45,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor return; } - this.gotoLocation(editor, { range: this.toRange(item.lineNumber, item.column), keyMods: picker.keyMods, preserveFocus: event.inBackground }); + this.gotoLocation(context, { range: this.toRange(item.lineNumber, item.column), keyMods: picker.keyMods, preserveFocus: event.inBackground }); if (!event.inBackground) { picker.hide(); diff --git a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts index ca85c3ce8..9fb3e3d3c 100644 --- a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts @@ -7,10 +7,10 @@ import { localize } from 'vs/nls'; import { IQuickPick, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { DisposableStore, IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IEditor, ScrollType } from 'vs/editor/common/editorCommon'; +import { ScrollType } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { AbstractEditorNavigationQuickAccessProvider, IEditorNavigationQuickAccessOptions } from 'vs/editor/contrib/quickAccess/editorNavigationQuickAccess'; +import { AbstractEditorNavigationQuickAccessProvider, IEditorNavigationQuickAccessOptions, IQuickAccessTextEditorContext } from 'vs/editor/contrib/quickAccess/editorNavigationQuickAccess'; import { DocumentSymbol, SymbolKinds, SymbolTag, DocumentSymbolProviderRegistry, SymbolKind } from 'vs/editor/common/modes'; import { OutlineModel, OutlineElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { trim, format } from 'vs/base/common/strings'; @@ -48,7 +48,8 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit return Disposable.None; } - protected provideWithTextEditor(editor: IEditor, picker: IQuickPick, token: CancellationToken): IDisposable { + protected provideWithTextEditor(context: IQuickAccessTextEditorContext, picker: IQuickPick, token: CancellationToken): IDisposable { + const editor = context.editor; const model = this.getModel(editor); if (!model) { return Disposable.None; @@ -56,16 +57,16 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit // Provide symbols from model if available in registry if (DocumentSymbolProviderRegistry.has(model)) { - return this.doProvideWithEditorSymbols(editor, model, picker, token); + return this.doProvideWithEditorSymbols(context, model, picker, token); } // Otherwise show an entry for a model without registry // But give a chance to resolve the symbols at a later // point if possible - return this.doProvideWithoutEditorSymbols(editor, model, picker, token); + return this.doProvideWithoutEditorSymbols(context, model, picker, token); } - private doProvideWithoutEditorSymbols(editor: IEditor, 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 @@ -82,7 +83,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit return; } - disposables.add(this.doProvideWithEditorSymbols(editor, model, picker, token)); + disposables.add(this.doProvideWithEditorSymbols(context, model, picker, token)); })(); return disposables; @@ -116,14 +117,15 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit return symbolProviderRegistryPromise; } - private doProvideWithEditorSymbols(editor: IEditor, model: ITextModel, picker: IQuickPick, token: CancellationToken): IDisposable { + private doProvideWithEditorSymbols(context: IQuickAccessTextEditorContext, model: ITextModel, picker: IQuickPick, token: CancellationToken): IDisposable { + const editor = context.editor; const disposables = new DisposableStore(); // Goto symbol once picked disposables.add(picker.onDidAccept(event => { const [item] = picker.selectedItems; if (item && item.range) { - this.gotoLocation(editor, { range: item.range.selection, keyMods: picker.keyMods, preserveFocus: event.inBackground }); + this.gotoLocation(context, { range: item.range.selection, keyMods: picker.keyMods, preserveFocus: event.inBackground }); if (!event.inBackground) { picker.hide(); @@ -134,7 +136,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit // Goto symbol side by side if enabled disposables.add(picker.onDidTriggerItemButton(({ item }) => { if (item && item.range) { - this.gotoLocation(editor, { range: item.range.selection, keyMods: picker.keyMods, forceSideBySide: true }); + this.gotoLocation(context, { range: item.range.selection, keyMods: picker.keyMods, forceSideBySide: true }); picker.hide(); } diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index 214d00ffc..415473087 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -358,7 +358,7 @@ registerModelAndPositionCommand('_executeDocumentRenameProvider', function (mode }); -//todo@joh use editor options world +//todo@jrieken use editor options world Registry.as(Extensions.Configuration).registerConfiguration({ id: 'editor', properties: { diff --git a/src/vs/editor/contrib/rename/renameInputField.ts b/src/vs/editor/contrib/rename/renameInputField.ts index c794e4ccc..a73fab128 100644 --- a/src/vs/editor/contrib/rename/renameInputField.ts +++ b/src/vs/editor/contrib/rename/renameInputField.ts @@ -100,7 +100,7 @@ export class RenameInputField implements IContentWidget { const widgetShadowColor = theme.getColor(widgetShadow); this._domNode.style.backgroundColor = String(theme.getColor(editorWidgetBackground) ?? ''); - this._domNode.style.boxShadow = widgetShadowColor ? ` 0 2px 8px ${widgetShadowColor}` : ''; + this._domNode.style.boxShadow = widgetShadowColor ? ` 0 0 8px 2px ${widgetShadowColor}` : ''; this._domNode.style.color = String(theme.getColor(inputForeground) ?? ''); this._input.style.backgroundColor = String(theme.getColor(inputBackground) ?? ''); diff --git a/src/vs/editor/contrib/smartSelect/smartSelect.ts b/src/vs/editor/contrib/smartSelect/smartSelect.ts index 714a27d7e..713d85032 100644 --- a/src/vs/editor/contrib/smartSelect/smartSelect.ts +++ b/src/vs/editor/contrib/smartSelect/smartSelect.ts @@ -23,9 +23,7 @@ import { WordSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/wordSe import { BracketSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/bracketSelections'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; -import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { Registry } from 'vs/platform/registry/common/platform'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; class SelectionRanges { @@ -50,49 +48,36 @@ class SelectionRanges { class SmartSelectController implements IEditorContribution { - public static readonly ID = 'editor.contrib.smartSelectController'; + static readonly ID = 'editor.contrib.smartSelectController'; static get(editor: ICodeEditor): SmartSelectController { return editor.getContribution(SmartSelectController.ID); } - private readonly _editor: ICodeEditor; - private _state?: SelectionRanges[]; private _selectionListener?: IDisposable; private _ignoreSelection: boolean = false; - constructor( - editor: ICodeEditor, - @ITextResourceConfigurationService private readonly _configService: ITextResourceConfigurationService, - ) { - this._editor = editor; - } + constructor(private readonly _editor: ICodeEditor) { } dispose(): void { this._selectionListener?.dispose(); } - run(forward: boolean): Promise | void { + async run(forward: boolean): Promise { if (!this._editor.hasModel()) { return; } const selections = this._editor.getSelections(); const model = this._editor.getModel(); - if (!modes.SelectionRangeRegistry.has(model)) { return; } - - let promise: Promise = Promise.resolve(undefined); - if (!this._state) { - const selectLeadingAndTrailingWhitespace = this._configService.getValue(model.uri, 'editor.smartSelect.selectLeadingAndTrailingWhitespace') ?? true; - - promise = provideSelectionRanges(model, selections.map(s => s.getPosition()), { selectLeadingAndTrailingWhitespace }, CancellationToken.None).then(ranges => { + await provideSelectionRanges(model, selections.map(s => s.getPosition()), this._editor.getOption(EditorOption.smartSelect), CancellationToken.None).then(ranges => { if (!arrays.isNonEmptyArray(ranges) || ranges.length !== selections.length) { // invalid result return; @@ -125,21 +110,18 @@ class SmartSelectController implements IEditorContribution { }); } - return promise.then(() => { - if (!this._state) { - // no state - return; - } - this._state = this._state.map(state => state.mov(forward)); - const selections = this._state.map(state => Selection.fromPositions(state.ranges[state.index].getStartPosition(), state.ranges[state.index].getEndPosition())); - this._ignoreSelection = true; - try { - this._editor.setSelections(selections); - } finally { - this._ignoreSelection = false; - } - - }); + if (!this._state) { + // no state + return; + } + this._state = this._state.map(state => state.mov(forward)); + const newSelections = this._state.map(state => Selection.fromPositions(state.ranges[state.index].getStartPosition(), state.ranges[state.index].getEndPosition())); + this._ignoreSelection = true; + try { + this._editor.setSelections(newSelections); + } finally { + this._ignoreSelection = false; + } } } @@ -186,21 +168,6 @@ class GrowSelectionAction extends AbstractSmartSelect { } } -//todo@jrieken use proper editor config instead. however, to keep the number -// of changes low use the quick config definition -Registry.as(Extensions.Configuration).registerConfiguration({ - id: 'editor', - properties: { - 'editor.smartSelect.selectLeadingAndTrailingWhitespace': { - scope: ConfigurationScope.LANGUAGE_OVERRIDABLE, - description: nls.localize('selectLeadingAndTrailingWhitespace', "Weather leading and trailing whitespace should always be selected."), - default: true, - type: 'boolean' - } - } -}); - - // renamed command id CommandsRegistry.registerCommandAlias('editor.action.smartSelect.grow', 'editor.action.smartSelect.expand'); @@ -241,7 +208,7 @@ export interface SelectionRangesOptions { selectLeadingAndTrailingWhitespace: boolean } -export function provideSelectionRanges(model: ITextModel, positions: Position[], options: SelectionRangesOptions, token: CancellationToken): Promise { +export async function provideSelectionRanges(model: ITextModel, positions: Position[], options: SelectionRangesOptions, token: CancellationToken): Promise { const providers = modes.SelectionRangeRegistry.all(model); @@ -271,66 +238,65 @@ export function provideSelectionRanges(model: ITextModel, positions: Position[], }, onUnexpectedExternalError)); } - return Promise.all(work).then(() => { + await Promise.all(work); - return allRawRanges.map(oneRawRanges => { + return allRawRanges.map(oneRawRanges => { - if (oneRawRanges.length === 0) { - return []; + if (oneRawRanges.length === 0) { + return []; + } + + // sort all by start/end position + oneRawRanges.sort((a, b) => { + if (Position.isBefore(a.getStartPosition(), b.getStartPosition())) { + return 1; + } else if (Position.isBefore(b.getStartPosition(), a.getStartPosition())) { + return -1; + } else if (Position.isBefore(a.getEndPosition(), b.getEndPosition())) { + return -1; + } else if (Position.isBefore(b.getEndPosition(), a.getEndPosition())) { + return 1; + } else { + return 0; } - - // sort all by start/end position - oneRawRanges.sort((a, b) => { - if (Position.isBefore(a.getStartPosition(), b.getStartPosition())) { - return 1; - } else if (Position.isBefore(b.getStartPosition(), a.getStartPosition())) { - return -1; - } else if (Position.isBefore(a.getEndPosition(), b.getEndPosition())) { - return -1; - } else if (Position.isBefore(b.getEndPosition(), a.getEndPosition())) { - return 1; - } else { - return 0; - } - }); - - // remove ranges that don't contain the former range or that are equal to the - // former range - let oneRanges: Range[] = []; - let last: Range | undefined; - for (const range of oneRawRanges) { - if (!last || (Range.containsRange(range, last) && !Range.equalsRange(range, last))) { - oneRanges.push(range); - last = range; - } - } - - if (!options.selectLeadingAndTrailingWhitespace) { - return oneRanges; - } - - // add ranges that expand trivia at line starts and ends whenever a range - // wraps onto the a new line - let oneRangesWithTrivia: Range[] = [oneRanges[0]]; - for (let i = 1; i < oneRanges.length; i++) { - const prev = oneRanges[i - 1]; - const cur = oneRanges[i]; - if (cur.startLineNumber !== prev.startLineNumber || cur.endLineNumber !== prev.endLineNumber) { - // add line/block range without leading/failing whitespace - const rangeNoWhitespace = new Range(prev.startLineNumber, model.getLineFirstNonWhitespaceColumn(prev.startLineNumber), prev.endLineNumber, model.getLineLastNonWhitespaceColumn(prev.endLineNumber)); - if (rangeNoWhitespace.containsRange(prev) && !rangeNoWhitespace.equalsRange(prev) && cur.containsRange(rangeNoWhitespace) && !cur.equalsRange(rangeNoWhitespace)) { - oneRangesWithTrivia.push(rangeNoWhitespace); - } - // add line/block range - const rangeFull = new Range(prev.startLineNumber, 1, prev.endLineNumber, model.getLineMaxColumn(prev.endLineNumber)); - if (rangeFull.containsRange(prev) && !rangeFull.equalsRange(rangeNoWhitespace) && cur.containsRange(rangeFull) && !cur.equalsRange(rangeFull)) { - oneRangesWithTrivia.push(rangeFull); - } - } - oneRangesWithTrivia.push(cur); - } - return oneRangesWithTrivia; }); + + // remove ranges that don't contain the former range or that are equal to the + // former range + let oneRanges: Range[] = []; + let last: Range | undefined; + for (const range of oneRawRanges) { + if (!last || (Range.containsRange(range, last) && !Range.equalsRange(range, last))) { + oneRanges.push(range); + last = range; + } + } + + if (!options.selectLeadingAndTrailingWhitespace) { + return oneRanges; + } + + // add ranges that expand trivia at line starts and ends whenever a range + // wraps onto the a new line + let oneRangesWithTrivia: Range[] = [oneRanges[0]]; + for (let i = 1; i < oneRanges.length; i++) { + const prev = oneRanges[i - 1]; + const cur = oneRanges[i]; + if (cur.startLineNumber !== prev.startLineNumber || cur.endLineNumber !== prev.endLineNumber) { + // add line/block range without leading/failing whitespace + const rangeNoWhitespace = new Range(prev.startLineNumber, model.getLineFirstNonWhitespaceColumn(prev.startLineNumber), prev.endLineNumber, model.getLineLastNonWhitespaceColumn(prev.endLineNumber)); + if (rangeNoWhitespace.containsRange(prev) && !rangeNoWhitespace.equalsRange(prev) && cur.containsRange(rangeNoWhitespace) && !cur.equalsRange(rangeNoWhitespace)) { + oneRangesWithTrivia.push(rangeNoWhitespace); + } + // add line/block range + const rangeFull = new Range(prev.startLineNumber, 1, prev.endLineNumber, model.getLineMaxColumn(prev.endLineNumber)); + if (rangeFull.containsRange(prev) && !rangeFull.equalsRange(rangeNoWhitespace) && cur.containsRange(rangeFull) && !cur.equalsRange(rangeFull)) { + oneRangesWithTrivia.push(rangeFull); + } + } + oneRangesWithTrivia.push(cur); + } + return oneRangesWithTrivia; }); } diff --git a/src/vs/editor/contrib/snippet/snippetSession.ts b/src/vs/editor/contrib/snippet/snippetSession.ts index 7f91f835c..90abafa25 100644 --- a/src/vs/editor/contrib/snippet/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/snippetSession.ts @@ -24,6 +24,7 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { ILabelService } from 'vs/platform/label/common/label'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { OvertypingCapturer } from 'vs/editor/contrib/suggest/suggestOvertypingCapturer'; +import { CharCode } from 'vs/base/common/charCode'; registerThemingParticipant((theme, collector) => { @@ -38,10 +39,6 @@ registerThemingParticipant((theme, collector) => { export class OneSnippet { - private readonly _editor: IActiveCodeEditor; - private readonly _snippet: TextmateSnippet; - private readonly _offset: number; - private _placeholderDecorations?: Map; private _placeholderGroups: Placeholder[][]; _placeholderGroupsIdx: number; @@ -54,12 +51,11 @@ export class OneSnippet { inactiveFinal: ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, className: 'finish-snippet-placeholder' }), }; - constructor(editor: IActiveCodeEditor, snippet: TextmateSnippet, offset: number) { - this._editor = editor; - this._snippet = snippet; - this._offset = offset; - - this._placeholderGroups = groupBy(snippet.placeholders, Placeholder.compareByIndex); + constructor( + private readonly _editor: IActiveCodeEditor, private readonly _snippet: TextmateSnippet, + private readonly _offset: number, private readonly _snippetLineLeadingWhitespace: string + ) { + this._placeholderGroups = groupBy(_snippet.placeholders, Placeholder.compareByIndex); this._placeholderGroupsIdx = -1; } @@ -113,8 +109,12 @@ export class OneSnippet { const id = this._placeholderDecorations!.get(placeholder)!; const range = this._editor.getModel().getDecorationRange(id)!; const currentValue = this._editor.getModel().getValueInRange(range); - - operations.push(EditOperation.replaceMove(range, placeholder.transform.resolve(currentValue))); + const transformedValueLines = placeholder.transform.resolve(currentValue).split(/\r\n|\r|\n/); + // fix indentation for transformed lines + for (let i = 1; i < transformedValueLines.length; i++) { + transformedValueLines[i] = this._editor.getModel().normalizeIndentation(this._snippetLineLeadingWhitespace + transformedValueLines[i]); + } + operations.push(EditOperation.replace(range, transformedValueLines.join(this._editor.getModel().getEOL()))); } } if (operations.length > 0) { @@ -300,7 +300,7 @@ export class OneSnippet { }); } - public getEnclosingRange(): Range | undefined { + getEnclosingRange(): Range | undefined { let result: Range | undefined; const model = this._editor.getModel(); for (const decorationId of this._placeholderDecorations!.values()) { @@ -333,32 +333,53 @@ const _defaultOptions: ISnippetSessionInsertOptions = { export class SnippetSession { - static adjustWhitespace(model: ITextModel, position: IPosition, snippet: TextmateSnippet, adjustIndentation: boolean, adjustNewlines: boolean): void { + static adjustWhitespace(model: ITextModel, position: IPosition, snippet: TextmateSnippet, adjustIndentation: boolean, adjustNewlines: boolean): string { const line = model.getLineContent(position.lineNumber); const lineLeadingWhitespace = getLeadingWhitespace(line, 0, position.column - 1); + // the snippet as inserted + let snippetTextString: string | undefined; + snippet.walk(marker => { - if (marker instanceof Text && !(marker.parent instanceof Choice)) { - // adjust indentation of text markers, except for choise elements - // which get adjusted when being selected - const lines = marker.value.split(/\r\n|\r|\n/); + // all text elements that are not inside choice + if (!(marker instanceof Text) || marker.parent instanceof Choice) { + return true; + } - if (adjustIndentation) { - for (let i = 1; i < lines.length; i++) { - let templateLeadingWhitespace = getLeadingWhitespace(lines[i]); - lines[i] = model.normalizeIndentation(lineLeadingWhitespace + templateLeadingWhitespace) + lines[i].substr(templateLeadingWhitespace.length); + const lines = marker.value.split(/\r\n|\r|\n/); + + if (adjustIndentation) { + // adjust indentation of snippet test + // -the snippet-start doesn't get extra-indented (lineLeadingWhitespace), only normalized + // -all N+1 lines get extra-indented and normalized + // -the text start get extra-indented and normalized when following a linebreak + const offset = snippet.offset(marker); + if (offset === 0) { + // snippet start + lines[0] = model.normalizeIndentation(lines[0]); + + } else { + // check if text start is after a linebreak + snippetTextString = snippetTextString ?? snippet.toString(); + let prevChar = snippetTextString.charCodeAt(offset - 1); + if (prevChar === CharCode.LineFeed || prevChar === CharCode.CarriageReturn) { + lines[0] = model.normalizeIndentation(lineLeadingWhitespace + lines[0]); } } - - if (adjustNewlines) { - const newValue = lines.join(model.getEOL()); - if (newValue !== marker.value) { - marker.parent.replace(marker, [new Text(newValue)]); - } + for (let i = 1; i < lines.length; i++) { + lines[i] = model.normalizeIndentation(lineLeadingWhitespace + lines[i]); } } + + const newValue = lines.join(model.getEOL()); + if (newValue !== marker.value) { + marker.parent.replace(marker, [new Text(newValue)]); + snippetTextString = undefined; + } return true; }); + + return lineLeadingWhitespace; } static adjustSelection(model: ITextModel, selection: Selection, overwriteBefore: number, overwriteAfter: number): Selection { @@ -443,7 +464,7 @@ export class SnippetSession { // happens when being asked for (default) or when this is a secondary // cursor and the leading whitespace is different const start = snippetSelection.getStartPosition(); - SnippetSession.adjustWhitespace( + const snippetLineLeadingWhitespace = SnippetSession.adjustWhitespace( model, start, snippet, adjustWhitespace || (idx > 0 && firstLineFirstNonWhitespace !== model.getLineFirstNonWhitespaceColumn(selection.positionLineNumber)), true @@ -467,7 +488,7 @@ export class SnippetSession { // the one with lowest start position edits[idx] = EditOperation.replace(snippetSelection, snippet.toString()); edits[idx].identifier = { major: idx, minor: 0 }; // mark the edit so only our undo edits will be used to generate end cursors - snippets[idx] = new OneSnippet(editor, snippet, offset); + snippets[idx] = new OneSnippet(editor, snippet, offset, snippetLineLeadingWhitespace); } return { edits, snippets }; diff --git a/src/vs/editor/contrib/snippet/snippetVariables.ts b/src/vs/editor/contrib/snippet/snippetVariables.ts index cd5d505bc..bdfe96e7c 100644 --- a/src/vs/editor/contrib/snippet/snippetVariables.ts +++ b/src/vs/editor/contrib/snippet/snippetVariables.ts @@ -10,7 +10,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { Selection } from 'vs/editor/common/core/selection'; import { VariableResolver, Variable, Text } from 'vs/editor/contrib/snippet/snippetParser'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; -import { getLeadingWhitespace, commonPrefixLength, isFalsyOrWhitespace } from 'vs/base/common/strings'; +import { getLeadingWhitespace, commonPrefixLength, isFalsyOrWhitespace, splitLines } from 'vs/base/common/strings'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { isSingleFolderWorkspaceIdentifier, toWorkspaceIdentifier, WORKSPACE_EXTENSION, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -111,7 +111,7 @@ export class SelectionBasedVariableResolver implements VariableResolver { return false; } if (marker instanceof Text) { - varLeadingWhitespace = getLeadingWhitespace(marker.value.split(/\r\n|\r|\n/).pop()!); + varLeadingWhitespace = getLeadingWhitespace(splitLines(marker.value).pop()!); } return true; }); diff --git a/src/vs/editor/contrib/snippet/test/snippetController2.test.ts b/src/vs/editor/contrib/snippet/test/snippetController2.test.ts index c430c2638..7ec294769 100644 --- a/src/vs/editor/contrib/snippet/test/snippetController2.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetController2.test.ts @@ -441,11 +441,18 @@ suite('SnippetController2', function () { }); test('leading TAB by snippets won\'t replace by spaces #101870', function () { - this.skip(); const ctrl = new SnippetController2(editor, logService, contextKeys); model.setValue(''); model.updateOptions({ insertSpaces: true, tabSize: 4 }); ctrl.insert('\tHello World\n\tNew Line'); assert.strictEqual(model.getValue(), ' Hello World\n New Line'); }); + + test('leading TAB by snippets won\'t replace by spaces #101870 (part 2)', function () { + const ctrl = new SnippetController2(editor, logService, contextKeys); + model.setValue(''); + model.updateOptions({ insertSpaces: true, tabSize: 4 }); + ctrl.insert('\tHello World\n\tNew Line\n${1:\tmore}'); + assert.strictEqual(model.getValue(), ' Hello World\n New Line\n more'); + }); }); diff --git a/src/vs/editor/contrib/snippet/test/snippetSession.test.ts b/src/vs/editor/contrib/snippet/test/snippetSession.test.ts index f72727ccd..458555cb0 100644 --- a/src/vs/editor/contrib/snippet/test/snippetSession.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetSession.test.ts @@ -561,6 +561,36 @@ suite('SnippetSession', function () { assertSelections(editor, new Selection(2, 1, 2, 1)); }); + test('Snippet tab stop selection issue #96545, snippets, transform adjacent to previous placeholder', function () { + editor.getModel()!.setValue(''); + editor.setSelection(new Selection(1, 1, 1, 1)); + const session = new SnippetSession(editor, '${1:{}${2:fff}${1/{/}/}'); + session.insert(); + + assertSelections(editor, new Selection(1, 1, 1, 2), new Selection(1, 5, 1, 6)); + session.next(); + + assert.equal(model.getValue(), '{fff}'); + assertSelections(editor, new Selection(1, 2, 1, 5)); + editor.trigger('test', 'type', { text: 'ggg' }); + session.next(); + + assert.equal(model.getValue(), '{ggg}'); + assert.equal(session.isAtLastPlaceholder, true); + assertSelections(editor, new Selection(1, 6, 1, 6)); + }); + + test('Snippet tab stop selection issue #96545', function () { + editor.getModel().setValue(''); + const session = new SnippetSession(editor, '${1:{}${2:fff}${1/[\\{]/}/}$0'); + session.insert(); + assert.equal(editor.getModel().getValue(), '{fff{'); + + assertSelections(editor, new Selection(1, 1, 1, 2), new Selection(1, 5, 1, 6)); + session.next(); + assertSelections(editor, new Selection(1, 2, 1, 5)); + }); + test('Snippet placeholder index incorrect after using 2+ snippets in a row that each end with a placeholder, #30769', function () { editor.getModel()!.setValue(''); editor.setSelection(new Selection(1, 1, 1, 1)); @@ -652,4 +682,52 @@ suite('SnippetSession', function () { new SnippetSession(editor, 'far', { overwriteBefore: 3, overwriteAfter: 0, adjustWhitespace: true, clipboardText: undefined, overtypingCapturer: undefined }).insert(); assert.equal(model.getValue(), 'console.far'); }); + + test('Tabs don\'t get replaced with spaces in snippet transformations #103818', function () { + const model = editor.getModel()!; + model.setValue('\n{\n \n}'); + model.updateOptions({ insertSpaces: true, tabSize: 2 }); + editor.setSelections([new Selection(1, 1, 1, 1), new Selection(3, 6, 3, 6)]); + const session = new SnippetSession(editor, [ + 'function animate () {', + '\tvar ${1:a} = 12;', + '\tconsole.log(${1/(.*)/\n\t\t$1\n\t/})', + '}' + ].join('\n')); + + session.insert(); + + assert.strictEqual(model.getValue(), [ + 'function animate () {', + ' var a = 12;', + ' console.log(a)', + '}', + '{', + ' function animate () {', + ' var a = 12;', + ' console.log(a)', + ' }', + '}', + ].join('\n')); + + editor.trigger('test', 'type', { text: 'bbb' }); + session.next(); + + assert.strictEqual(model.getValue(), [ + 'function animate () {', + ' var bbb = 12;', + ' console.log(', + ' bbb', + ' )', + '}', + '{', + ' function animate () {', + ' var bbb = 12;', + ' console.log(', + ' bbb', + ' )', + ' }', + '}', + ].join('\n')); + }); }); diff --git a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts index 0bb4cfe34..8e939a5ac 100644 --- a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts @@ -9,10 +9,11 @@ import { Selection } from 'vs/editor/common/core/selection'; import { SelectionBasedVariableResolver, CompositeSnippetVariableResolver, ModelBasedVariableResolver, ClipboardBasedVariableResolver, TimeBasedVariableResolver, WorkspaceBasedVariableResolver } from 'vs/editor/contrib/snippet/snippetVariables'; import { SnippetParser, Variable, VariableResolver } from 'vs/editor/contrib/snippet/snippetParser'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { Workspace, toWorkspaceFolders, IWorkspace, IWorkspaceContextService, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { toWorkspaceFolders, IWorkspace, IWorkspaceContextService, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ILabelService } from 'vs/platform/label/common/label'; import { mock } from 'vs/base/test/common/mock'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; +import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; suite('Snippet Variables Resolver', function () { diff --git a/src/vs/editor/contrib/suggest/resizable.ts b/src/vs/editor/contrib/suggest/resizable.ts index 25e1dcb3d..382f88289 100644 --- a/src/vs/editor/contrib/suggest/resizable.ts +++ b/src/vs/editor/contrib/suggest/resizable.ts @@ -6,7 +6,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { Dimension } from 'vs/base/browser/dom'; -import { Orientation, Sash, SashState } from 'vs/base/browser/ui/sash/sash'; +import { Orientation, OrthogonalEdge, Sash, SashState } from 'vs/base/browser/ui/sash/sash'; export interface IResizeEvent { @@ -43,8 +43,8 @@ export class ResizableHTMLElement { this.domNode = document.createElement('div'); this._eastSash = new Sash(this.domNode, { getVerticalSashLeft: () => this._size.width }, { orientation: Orientation.VERTICAL }); this._westSash = new Sash(this.domNode, { getVerticalSashLeft: () => 0 }, { orientation: Orientation.VERTICAL }); - this._northSash = new Sash(this.domNode, { getHorizontalSashTop: () => 0 }, { orientation: Orientation.HORIZONTAL }); - this._southSash = new Sash(this.domNode, { getHorizontalSashTop: () => this._size.height }, { orientation: Orientation.HORIZONTAL }); + this._northSash = new Sash(this.domNode, { getHorizontalSashTop: () => 0 }, { orientation: Orientation.HORIZONTAL, orthogonalEdge: OrthogonalEdge.North }); + this._southSash = new Sash(this.domNode, { getHorizontalSashTop: () => this._size.height }, { orientation: Orientation.HORIZONTAL, orthogonalEdge: OrthogonalEdge.South }); this._northSash.orthogonalStartSash = this._westSash; this._northSash.orthogonalEndSash = this._eastSash; diff --git a/src/vs/editor/contrib/suggest/suggest.ts b/src/vs/editor/contrib/suggest/suggest.ts index 650701dea..d6c466f33 100644 --- a/src/vs/editor/contrib/suggest/suggest.ts +++ b/src/vs/editor/contrib/suggest/suggest.ts @@ -6,7 +6,6 @@ import { onUnexpectedExternalError, canceled, isPromiseCanceledError } from 'vs/base/common/errors'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; import * as modes from 'vs/editor/common/modes'; import { Position, IPosition } from 'vs/editor/common/core/position'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -18,6 +17,10 @@ import { isDisposable, DisposableStore, IDisposable } from 'vs/base/common/lifec import { MenuId } from 'vs/platform/actions/common/actions'; import { SnippetParser } from 'vs/editor/contrib/snippet/snippetParser'; import { StopWatch } from 'vs/base/common/stopwatch'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { assertType } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; export const Context = { Visible: new RawContextKey('suggestWidgetVisible', false), @@ -341,31 +344,42 @@ export function getSuggestionComparator(snippetConfig: SnippetSortOrder): (a: Co return _snippetComparators.get(snippetConfig)!; } -registerDefaultLanguageCommand('_executeCompletionItemProvider', async (model, position, args) => { - - const result: modes.CompletionList = { - incomplete: false, - suggestions: [] - }; - - const resolving: Promise[] = []; - const maxItemsToResolve = args['maxItemsToResolve'] || 0; - - const completions = await provideSuggestionItems(model, position); - for (const item of completions.items) { - if (resolving.length < maxItemsToResolve) { - resolving.push(item.resolve(CancellationToken.None)); - } - result.incomplete = result.incomplete || item.container.incomplete; - result.suggestions.push(item.completion); - } +CommandsRegistry.registerCommand('_executeCompletionItemProvider', async (accessor, ...args: [URI, IPosition, string?, number?]) => { + const [uri, position, triggerCharacter, maxItemsToResolve] = args; + assertType(URI.isUri(uri)); + assertType(Position.isIPosition(position)); + assertType(typeof triggerCharacter === 'string' || !triggerCharacter); + assertType(typeof maxItemsToResolve === 'number' || !maxItemsToResolve); + const ref = await accessor.get(ITextModelService).createModelReference(uri); try { - await Promise.all(resolving); - return result; + + const result: modes.CompletionList = { + incomplete: false, + suggestions: [] + }; + + const resolving: Promise[] = []; + const completions = await provideSuggestionItems(ref.object.textEditorModel, Position.lift(position), undefined, { triggerCharacter, triggerKind: triggerCharacter ? modes.CompletionTriggerKind.TriggerCharacter : modes.CompletionTriggerKind.Invoke }); + for (const item of completions.items) { + if (resolving.length < (maxItemsToResolve ?? 0)) { + resolving.push(item.resolve(CancellationToken.None)); + } + result.incomplete = result.incomplete || item.container.incomplete; + result.suggestions.push(item.completion); + } + + try { + await Promise.all(resolving); + return result; + } finally { + setTimeout(() => completions.disposable.dispose(), 100); + } + } finally { - setTimeout(() => completions.disposable.dispose(), 100); + ref.dispose(); } + }); interface SuggestController extends IEditorContribution { diff --git a/src/vs/editor/contrib/suggest/suggestMemory.ts b/src/vs/editor/contrib/suggest/suggestMemory.ts index b2c98b605..dfb293635 100644 --- a/src/vs/editor/contrib/suggest/suggestMemory.ts +++ b/src/vs/editor/contrib/suggest/suggestMemory.ts @@ -5,7 +5,7 @@ import { LRUCache, TernarySearchTree } from 'vs/base/common/map'; -import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { ITextModel } from 'vs/editor/common/model'; import { IPosition } from 'vs/editor/common/core/position'; import { CompletionItemKind, completionKindFromString } from 'vs/editor/common/modes'; @@ -82,8 +82,7 @@ export class LRUMemory extends Memory { private _seq = 0; memorize(model: ITextModel, pos: IPosition, item: CompletionItem): void { - const { label } = item.completion; - const key = `${model.getLanguageIdentifier().language}/${label}`; + const key = `${model.getLanguageIdentifier().language}/${item.textLabel}`; this._cache.set(key, { touch: this._seq++, type: item.completion.kind, @@ -111,7 +110,7 @@ export class LRUMemory extends Memory { // consider only top items break; } - const key = `${model.getLanguageIdentifier().language}/${items[i].completion.label}`; + const key = `${model.getLanguageIdentifier().language}/${items[i].textLabel}`; const item = this._cache.peek(key); if (item && item.touch > seq && item.type === items[i].completion.kind && item.insertText === items[i].completion.insertText) { seq = item.touch; @@ -295,7 +294,7 @@ export class SuggestMemoryService implements ISuggestMemoryService { const share = this._configService.getValue('editor.suggest.shareSuggestSelections'); const scope = share ? StorageScope.GLOBAL : StorageScope.WORKSPACE; const raw = JSON.stringify(this._strategy); - this._storageService.store(`${SuggestMemoryService._storagePrefix}/${this._strategy.name}`, raw, scope); + this._storageService.store(`${SuggestMemoryService._storagePrefix}/${this._strategy.name}`, raw, scope, StorageTarget.MACHINE); } } } diff --git a/src/vs/editor/contrib/suggest/suggestModel.ts b/src/vs/editor/contrib/suggest/suggestModel.ts index 6cad7c8b6..9a88e1ccc 100644 --- a/src/vs/editor/contrib/suggest/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/suggestModel.ts @@ -20,7 +20,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { WordDistance } from 'vs/editor/contrib/suggest/wordDistance'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { isLowSurrogate, isHighSurrogate } from 'vs/base/common/strings'; +import { isLowSurrogate, isHighSurrogate, getLeadingWhitespace } from 'vs/base/common/strings'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ILogService } from 'vs/platform/log/common/log'; @@ -486,8 +486,14 @@ export class SuggestModel implements IDisposable { }).catch(onUnexpectedError); } + private _telemetryGate: number = 0; + private _reportDurationsTelemetry(durations: CompletionDurations): void { + if (this._telemetryGate++ % 230 !== 0) { + return; + } + setTimeout(() => { type Durations = { data: string; }; type DurationsClassification = { data: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' } }; @@ -553,7 +559,8 @@ export class SuggestModel implements IDisposable { return; } - if (ctx.leadingWord.startColumn < this._context.leadingWord.startColumn) { + if (getLeadingWhitespace(ctx.leadingLineContent) !== getLeadingWhitespace(this._context.leadingLineContent)) { + // cancel IntelliSense when line start changes // happens when the current word gets outdented this.cancel(); return; diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index a62c3c686..02f6233f2 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -11,7 +11,7 @@ import * as strings from 'vs/base/common/strings'; import * as dom from 'vs/base/browser/dom'; import { Event, Emitter } from 'vs/base/common/event'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IListEvent, IListMouseEvent, IListGestureEvent } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -22,7 +22,7 @@ import { CompletionModel } from './completionModel'; import { attachListStyler } from 'vs/platform/theme/common/styler'; import { IThemeService, IColorTheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { registerColor, editorWidgetBackground, listFocusBackground, activeContrastBorder, listHighlightForeground, editorForeground, editorWidgetBorder, focusBorder, textLinkForeground, textCodeBlockBackground } from 'vs/platform/theme/common/colorRegistry'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { TimeoutTimer, CancelablePromise, createCancelablePromise, disposableTimeout } from 'vs/base/common/async'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -32,6 +32,7 @@ import { getAriaId, ItemRenderer } from './suggestWidgetRenderer'; import { ResizableHTMLElement } from './resizable'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { IPosition } from 'vs/editor/common/core/position'; +import { clamp } from 'vs/base/common/numbers'; /** * Suggest widget colors @@ -82,7 +83,7 @@ class PersistedWidgetSize { } store(size: dom.Dimension) { - this._service.store(this._key, JSON.stringify(size), StorageScope.GLOBAL); + this._service.store(this._key, JSON.stringify(size), StorageScope.GLOBAL, StorageTarget.MACHINE); } reset(): void { @@ -95,49 +96,48 @@ export class SuggestWidget implements IDisposable { private static LOADING_MESSAGE: string = nls.localize('suggestWidget.loading', "Loading..."); private static NO_SUGGESTIONS_MESSAGE: string = nls.localize('suggestWidget.noSuggestions', "No suggestions."); - private state: State = State.Hidden; - private isAuto: boolean = false; - private loadingTimeout: IDisposable = Disposable.None; - private currentSuggestionDetails?: CancelablePromise; - private focusedItem?: CompletionItem; - private ignoreFocusEvents: boolean = false; - private completionModel?: CompletionModel; + private _state: State = State.Hidden; + private _isAuto: boolean = false; + private _loadingTimeout?: IDisposable; + private _currentSuggestionDetails?: CancelablePromise; + private _focusedItem?: CompletionItem; + private _ignoreFocusEvents: boolean = false; + private _completionModel?: CompletionModel; private _cappedHeight?: { wanted: number, capped: number }; + private _explainMode: boolean = false; readonly element: ResizableHTMLElement; - private readonly messageElement: HTMLElement; - private readonly listElement: HTMLElement; - private readonly list: List; - private readonly status: SuggestWidgetStatus; + private readonly _messageElement: HTMLElement; + private readonly _listElement: HTMLElement; + private readonly _list: List; + private readonly _status: SuggestWidgetStatus; private readonly _details: SuggestDetailsOverlay; private readonly _contentWidget: SuggestContentWidget; - - private readonly ctxSuggestWidgetVisible: IContextKey; - private readonly ctxSuggestWidgetDetailsVisible: IContextKey; - private readonly ctxSuggestWidgetMultipleSuggestions: IContextKey; - - private readonly showTimeout = new TimeoutTimer(); - private readonly _disposables = new DisposableStore(); - private readonly _persistedSize: PersistedWidgetSize; - private readonly onDidSelectEmitter = new Emitter(); - private readonly onDidFocusEmitter = new Emitter(); - private readonly onDidHideEmitter = new Emitter(); - private readonly onDidShowEmitter = new Emitter(); + private readonly _ctxSuggestWidgetVisible: IContextKey; + private readonly _ctxSuggestWidgetDetailsVisible: IContextKey; + private readonly _ctxSuggestWidgetMultipleSuggestions: IContextKey; - readonly onDidSelect: Event = this.onDidSelectEmitter.event; - readonly onDidFocus: Event = this.onDidFocusEmitter.event; - readonly onDidHide: Event = this.onDidHideEmitter.event; - readonly onDidShow: Event = this.onDidShowEmitter.event; + private readonly _showTimeout = new TimeoutTimer(); + private readonly _disposables = new DisposableStore(); - private detailsFocusBorderColor?: string; - private detailsBorderColor?: string; - private explainMode: boolean = false; + private readonly _onDidSelect = new Emitter(); + private readonly _onDidFocus = new Emitter(); + private readonly _onDidHide = new Emitter(); + private readonly _onDidShow = new Emitter(); + + readonly onDidSelect: Event = this._onDidSelect.event; + readonly onDidFocus: Event = this._onDidFocus.event; + readonly onDidHide: Event = this._onDidHide.event; + readonly onDidShow: Event = this._onDidShow.event; private readonly _onDetailsKeydown = new Emitter(); - public readonly onDetailsKeyDown: Event = this._onDetailsKeydown.event; + readonly onDetailsKeyDown: Event = this._onDetailsKeydown.event; + + private _detailsFocusBorderColor?: string; + private _detailsBorderColor?: string; constructor( private readonly editor: ICodeEditor, @@ -152,47 +152,55 @@ export class SuggestWidget implements IDisposable { this._contentWidget = new SuggestContentWidget(this, editor); this._persistedSize = new PersistedWidgetSize(_storageService, editor); - let persistedSize: dom.Dimension | undefined; - let currentSize: dom.Dimension | undefined; - let persistHeight = false; - let persistWidth = false; + class ResizeState { + constructor( + readonly persistedSize: dom.Dimension | undefined, + readonly currentSize: dom.Dimension, + public persistHeight = false, + public persistWidth = false, + ) { } + } + + let state: ResizeState | undefined; this._disposables.add(this.element.onDidWillResize(() => { this._contentWidget.lockPreference(); - persistedSize = this._persistedSize.restore(); - currentSize = this.element.size; + state = new ResizeState(this._persistedSize.restore(), this.element.size); })); this._disposables.add(this.element.onDidResize(e => { this._resize(e.dimension.width, e.dimension.height); - persistHeight = persistHeight || !!e.north || !!e.south; - persistWidth = persistWidth || !!e.east || !!e.west; - if (e.done) { + if (state) { + state.persistHeight = state.persistHeight || !!e.north || !!e.south; + state.persistWidth = state.persistWidth || !!e.east || !!e.west; + } + + if (!e.done) { + return; + } + + if (state) { // only store width or height value that have changed and also // only store changes that are above a certain threshold - const threshold = Math.floor(this.getLayoutInfo().itemHeight / 2); + const { itemHeight, defaultSize } = this.getLayoutInfo(); + const threshold = Math.round(itemHeight / 2); let { width, height } = this.element.size; - if (persistedSize && currentSize) { - if (!persistHeight || Math.abs(currentSize.height - height) <= threshold) { - height = persistedSize.height; - } - if (!persistWidth || Math.abs(currentSize.width - width) <= threshold) { - width = persistedSize.width; - } + if (!state.persistHeight || Math.abs(state.currentSize.height - height) <= threshold) { + height = state.persistedSize?.height ?? defaultSize.height; + } + if (!state.persistWidth || Math.abs(state.currentSize.width - width) <= threshold) { + width = state.persistedSize?.width ?? defaultSize.width; } this._persistedSize.store(new dom.Dimension(width, height)); - - // reset working state - this._contentWidget.unlockPreference(); - persistedSize = undefined; - currentSize = undefined; - persistHeight = false; - persistWidth = false; } + + // reset working state + this._contentWidget.unlockPreference(); + state = undefined; })); - this.messageElement = dom.append(this.element.domNode, dom.$('.message')); - this.listElement = dom.append(this.element.domNode, dom.$('.tree')); + this._messageElement = dom.append(this.element.domNode, dom.$('.message')); + this._listElement = dom.append(this.element.domNode, dom.$('.tree')); const details = instantiationService.createInstance(SuggestDetailsWidget, this.editor); details.onDidClose(this.toggleDetails, this, this._disposables); @@ -205,7 +213,7 @@ export class SuggestWidget implements IDisposable { this._disposables.add(renderer); this._disposables.add(renderer.onDidToggleDetails(() => this.toggleDetails())); - this.list = new List('SuggestWidget', this.listElement, { + this._list = new List('SuggestWidget', this._listElement, { getHeight: (_element: CompletionItem): number => this.getLayoutInfo().itemHeight, getTemplateId: (_element: CompletionItem): string => 'suggestion' }, [renderer], { @@ -232,22 +240,22 @@ export class SuggestWidget implements IDisposable { } }); - this.status = instantiationService.createInstance(SuggestWidgetStatus, this.element.domNode); + this._status = instantiationService.createInstance(SuggestWidgetStatus, this.element.domNode); const applyStatusBarStyle = () => this.element.domNode.classList.toggle('with-status-bar', this.editor.getOption(EditorOption.suggest).showStatusBar); applyStatusBarStyle(); - this._disposables.add(attachListStyler(this.list, _themeService, { + this._disposables.add(attachListStyler(this._list, _themeService, { listInactiveFocusBackground: editorSuggestWidgetSelectedBackground, listInactiveFocusOutline: activeContrastBorder })); - this._disposables.add(_themeService.onDidColorThemeChange(t => this.onThemeChange(t))); - this.onThemeChange(_themeService.getColorTheme()); + this._disposables.add(_themeService.onDidColorThemeChange(t => this._onThemeChange(t))); + this._onThemeChange(_themeService.getColorTheme()); - this._disposables.add(this.list.onMouseDown(e => this.onListMouseDownOrTap(e))); - this._disposables.add(this.list.onTap(e => this.onListMouseDownOrTap(e))); - this._disposables.add(this.list.onDidChangeSelection(e => this.onListSelection(e))); - this._disposables.add(this.list.onDidChangeFocus(e => this.onListFocus(e))); - this._disposables.add(this.editor.onDidChangeCursorSelection(() => this.onCursorSelectionChanged())); + this._disposables.add(this._list.onMouseDown(e => this._onListMouseDownOrTap(e))); + this._disposables.add(this._list.onTap(e => this._onListMouseDownOrTap(e))); + this._disposables.add(this._list.onDidChangeSelection(e => this._onListSelection(e))); + this._disposables.add(this._list.onDidChangeFocus(e => this._onListFocus(e))); + this._disposables.add(this.editor.onDidChangeCursorSelection(() => this._onCursorSelectionChanged())); this._disposables.add(this.editor.onDidChangeConfiguration(e => { if (e.hasChanged(EditorOption.suggest)) { applyStatusBarStyle(); @@ -255,31 +263,31 @@ export class SuggestWidget implements IDisposable { } })); - this.ctxSuggestWidgetVisible = SuggestContext.Visible.bindTo(_contextKeyService); - this.ctxSuggestWidgetDetailsVisible = SuggestContext.DetailsVisible.bindTo(_contextKeyService); - this.ctxSuggestWidgetMultipleSuggestions = SuggestContext.MultipleSuggestions.bindTo(_contextKeyService); + this._ctxSuggestWidgetVisible = SuggestContext.Visible.bindTo(_contextKeyService); + this._ctxSuggestWidgetDetailsVisible = SuggestContext.DetailsVisible.bindTo(_contextKeyService); + this._ctxSuggestWidgetMultipleSuggestions = SuggestContext.MultipleSuggestions.bindTo(_contextKeyService); this._disposables.add(dom.addStandardDisposableListener(this._details.widget.domNode, 'keydown', e => { this._onDetailsKeydown.fire(e); })); - this._disposables.add(this.editor.onMouseDown((e: IEditorMouseEvent) => this.onEditorMouseDown(e))); + this._disposables.add(this.editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e))); } dispose(): void { this._details.widget.dispose(); this._details.dispose(); - this.list.dispose(); - this.status.dispose(); + this._list.dispose(); + this._status.dispose(); this._disposables.dispose(); - this.loadingTimeout.dispose(); - this.showTimeout.dispose(); + this._loadingTimeout?.dispose(); + this._showTimeout.dispose(); this._contentWidget.dispose(); this.element.dispose(); } - private onEditorMouseDown(mouseEvent: IEditorMouseEvent): void { + private _onEditorMouseDown(mouseEvent: IEditorMouseEvent): void { if (this._details.widget.domNode.contains(mouseEvent.target.element)) { // Clicking inside details this._details.widget.domNode.focus(); @@ -291,13 +299,13 @@ export class SuggestWidget implements IDisposable { } } - private onCursorSelectionChanged(): void { - if (this.state !== State.Hidden) { + private _onCursorSelectionChanged(): void { + if (this._state !== State.Hidden) { this._contentWidget.layout(); } } - private onListMouseDownOrTap(e: IListMouseEvent | IListGestureEvent): void { + private _onListMouseDownOrTap(e: IListMouseEvent | IListGestureEvent): void { if (typeof e.element === 'undefined' || typeof e.index === 'undefined') { return; } @@ -306,83 +314,78 @@ export class SuggestWidget implements IDisposable { e.browserEvent.preventDefault(); e.browserEvent.stopPropagation(); - this.select(e.element, e.index); + this._select(e.element, e.index); } - private onListSelection(e: IListEvent): void { - if (!e.elements.length) { - return; + private _onListSelection(e: IListEvent): void { + if (e.elements.length) { + this._select(e.elements[0], e.indexes[0]); } - - this.select(e.elements[0], e.indexes[0]); } - private select(item: CompletionItem, index: number): void { - const completionModel = this.completionModel; - - if (!completionModel) { - return; + private _select(item: CompletionItem, index: number): void { + const completionModel = this._completionModel; + if (completionModel) { + this._onDidSelect.fire({ item, index, model: completionModel }); + this.editor.focus(); } - - this.onDidSelectEmitter.fire({ item, index, model: completionModel }); - this.editor.focus(); } - private onThemeChange(theme: IColorTheme) { + private _onThemeChange(theme: IColorTheme) { const backgroundColor = theme.getColor(editorSuggestWidgetBackground); if (backgroundColor) { this.element.domNode.style.backgroundColor = backgroundColor.toString(); - this.messageElement.style.backgroundColor = backgroundColor.toString(); + this._messageElement.style.backgroundColor = backgroundColor.toString(); this._details.widget.domNode.style.backgroundColor = backgroundColor.toString(); } const borderColor = theme.getColor(editorSuggestWidgetBorder); if (borderColor) { this.element.domNode.style.borderColor = borderColor.toString(); - this.messageElement.style.borderColor = borderColor.toString(); - this.status.element.style.borderTopColor = borderColor.toString(); + this._messageElement.style.borderColor = borderColor.toString(); + this._status.element.style.borderTopColor = borderColor.toString(); this._details.widget.domNode.style.borderColor = borderColor.toString(); - this.detailsBorderColor = borderColor.toString(); + this._detailsBorderColor = borderColor.toString(); } const focusBorderColor = theme.getColor(focusBorder); if (focusBorderColor) { - this.detailsFocusBorderColor = focusBorderColor.toString(); + this._detailsFocusBorderColor = focusBorderColor.toString(); } this._details.widget.borderWidth = theme.type === 'hc' ? 2 : 1; } - private onListFocus(e: IListEvent): void { - if (this.ignoreFocusEvents) { + private _onListFocus(e: IListEvent): void { + if (this._ignoreFocusEvents) { return; } if (!e.elements.length) { - if (this.currentSuggestionDetails) { - this.currentSuggestionDetails.cancel(); - this.currentSuggestionDetails = undefined; - this.focusedItem = undefined; + if (this._currentSuggestionDetails) { + this._currentSuggestionDetails.cancel(); + this._currentSuggestionDetails = undefined; + this._focusedItem = undefined; } this.editor.setAriaOptions({ activeDescendant: undefined }); return; } - if (!this.completionModel) { + if (!this._completionModel) { return; } const item = e.elements[0]; const index = e.indexes[0]; - if (item !== this.focusedItem) { + if (item !== this._focusedItem) { - this.currentSuggestionDetails?.cancel(); - this.currentSuggestionDetails = undefined; + this._currentSuggestionDetails?.cancel(); + this._currentSuggestionDetails = undefined; - this.focusedItem = item; + this._focusedItem = item; - this.list.reveal(index); + this._list.reveal(index); - this.currentSuggestionDetails = createCancelablePromise(async token => { + this._currentSuggestionDetails = createCancelablePromise(async token => { const loading = disposableTimeout(() => { if (this._isDetailsVisible()) { this.showDetails(true); @@ -394,16 +397,16 @@ export class SuggestWidget implements IDisposable { return result; }); - this.currentSuggestionDetails.then(() => { - if (index >= this.list.length || item !== this.list.element(index)) { + this._currentSuggestionDetails.then(() => { + if (index >= this._list.length || item !== this._list.element(index)) { return; } // item can have extra information, so re-render - this.ignoreFocusEvents = true; - this.list.splice(index, 1, [item]); - this.list.setFocus([index]); - this.ignoreFocusEvents = false; + this._ignoreFocusEvents = true; + this._list.splice(index, 1, [item]); + this._list.setFocus([index]); + this._ignoreFocusEvents = false; if (this._isDetailsVisible()) { this.showDetails(false); @@ -416,63 +419,63 @@ export class SuggestWidget implements IDisposable { } // emit an event - this.onDidFocusEmitter.fire({ item, index, model: this.completionModel }); + this._onDidFocus.fire({ item, index, model: this._completionModel }); } private _setState(state: State): void { - if (this.state === state) { + if (this._state === state) { return; } - this.state = state; + this._state = state; this.element.domNode.classList.toggle('frozen', state === State.Frozen); this.element.domNode.classList.remove('message'); switch (state) { case State.Hidden: - dom.hide(this.messageElement, this.listElement, this.status.element); + dom.hide(this._messageElement, this._listElement, this._status.element); this._details.hide(true); this._contentWidget.hide(); - this.ctxSuggestWidgetVisible.reset(); - this.ctxSuggestWidgetMultipleSuggestions.reset(); + this._ctxSuggestWidgetVisible.reset(); + this._ctxSuggestWidgetMultipleSuggestions.reset(); this.element.domNode.classList.remove('visible'); - this.list.splice(0, this.list.length); - this.focusedItem = undefined; + this._list.splice(0, this._list.length); + this._focusedItem = undefined; this._cappedHeight = undefined; - this.explainMode = false; + this._explainMode = false; break; case State.Loading: this.element.domNode.classList.add('message'); - this.messageElement.textContent = SuggestWidget.LOADING_MESSAGE; - dom.hide(this.listElement, this.status.element); - dom.show(this.messageElement); + this._messageElement.textContent = SuggestWidget.LOADING_MESSAGE; + dom.hide(this._listElement, this._status.element); + dom.show(this._messageElement); this._details.hide(); this._show(); - this.focusedItem = undefined; + this._focusedItem = undefined; break; case State.Empty: this.element.domNode.classList.add('message'); - this.messageElement.textContent = SuggestWidget.NO_SUGGESTIONS_MESSAGE; - dom.hide(this.listElement, this.status.element); - dom.show(this.messageElement); + this._messageElement.textContent = SuggestWidget.NO_SUGGESTIONS_MESSAGE; + dom.hide(this._listElement, this._status.element); + dom.show(this._messageElement); this._details.hide(); this._show(); - this.focusedItem = undefined; + this._focusedItem = undefined; break; case State.Open: - dom.hide(this.messageElement); - dom.show(this.listElement, this.status.element); + dom.hide(this._messageElement); + dom.show(this._listElement, this._status.element); this._show(); break; case State.Frozen: - dom.hide(this.messageElement); - dom.show(this.listElement, this.status.element); + dom.hide(this._messageElement); + dom.show(this._listElement, this._status.element); this._show(); break; case State.Details: - dom.hide(this.messageElement); - dom.show(this.listElement, this.status.element); + dom.hide(this._messageElement); + dom.show(this._listElement, this._status.element); this._details.show(); this._show(); break; @@ -482,176 +485,176 @@ export class SuggestWidget implements IDisposable { private _show(): void { this._contentWidget.show(); this._layout(this._persistedSize.restore()); - this.ctxSuggestWidgetVisible.set(true); + this._ctxSuggestWidgetVisible.set(true); - this.showTimeout.cancelAndSet(() => { + this._showTimeout.cancelAndSet(() => { this.element.domNode.classList.add('visible'); - this.onDidShowEmitter.fire(this); + this._onDidShow.fire(this); }, 100); } showTriggered(auto: boolean, delay: number) { - if (this.state !== State.Hidden) { + if (this._state !== State.Hidden) { return; } this._contentWidget.setPosition(this.editor.getPosition()); - this.isAuto = !!auto; + this._isAuto = !!auto; - if (!this.isAuto) { - this.loadingTimeout = disposableTimeout(() => this._setState(State.Loading), delay); + if (!this._isAuto) { + this._loadingTimeout = disposableTimeout(() => this._setState(State.Loading), delay); } } showSuggestions(completionModel: CompletionModel, selectionIndex: number, isFrozen: boolean, isAuto: boolean): void { this._contentWidget.setPosition(this.editor.getPosition()); - this.loadingTimeout.dispose(); + this._loadingTimeout?.dispose(); - this.currentSuggestionDetails?.cancel(); - this.currentSuggestionDetails = undefined; + this._currentSuggestionDetails?.cancel(); + this._currentSuggestionDetails = undefined; - if (this.completionModel !== completionModel) { - this.completionModel = completionModel; + if (this._completionModel !== completionModel) { + this._completionModel = completionModel; } - if (isFrozen && this.state !== State.Empty && this.state !== State.Hidden) { + if (isFrozen && this._state !== State.Empty && this._state !== State.Hidden) { this._setState(State.Frozen); return; } - const visibleCount = this.completionModel.items.length; + const visibleCount = this._completionModel.items.length; const isEmpty = visibleCount === 0; - this.ctxSuggestWidgetMultipleSuggestions.set(visibleCount > 1); + this._ctxSuggestWidgetMultipleSuggestions.set(visibleCount > 1); if (isEmpty) { this._setState(isAuto ? State.Hidden : State.Empty); - this.completionModel = undefined; + this._completionModel = undefined; return; } - this.focusedItem = undefined; - this.list.splice(0, this.list.length, this.completionModel.items); + this._focusedItem = undefined; + this._list.splice(0, this._list.length, this._completionModel.items); this._setState(isFrozen ? State.Frozen : State.Open); - this.list.reveal(selectionIndex, 0); - this.list.setFocus([selectionIndex]); + this._list.reveal(selectionIndex, 0); + this._list.setFocus([selectionIndex]); this._layout(this.element.size); // Reset focus border - if (this.detailsBorderColor) { - this._details.widget.domNode.style.borderColor = this.detailsBorderColor; + if (this._detailsBorderColor) { + this._details.widget.domNode.style.borderColor = this._detailsBorderColor; } } selectNextPage(): boolean { - switch (this.state) { + switch (this._state) { case State.Hidden: return false; case State.Details: this._details.widget.pageDown(); return true; case State.Loading: - return !this.isAuto; + return !this._isAuto; default: - this.list.focusNextPage(); + this._list.focusNextPage(); return true; } } selectNext(): boolean { - switch (this.state) { + switch (this._state) { case State.Hidden: return false; case State.Loading: - return !this.isAuto; + return !this._isAuto; default: - this.list.focusNext(1, true); + this._list.focusNext(1, true); return true; } } selectLast(): boolean { - switch (this.state) { + switch (this._state) { case State.Hidden: return false; case State.Details: this._details.widget.scrollBottom(); return true; case State.Loading: - return !this.isAuto; + return !this._isAuto; default: - this.list.focusLast(); + this._list.focusLast(); return true; } } selectPreviousPage(): boolean { - switch (this.state) { + switch (this._state) { case State.Hidden: return false; case State.Details: this._details.widget.pageUp(); return true; case State.Loading: - return !this.isAuto; + return !this._isAuto; default: - this.list.focusPreviousPage(); + this._list.focusPreviousPage(); return true; } } selectPrevious(): boolean { - switch (this.state) { + switch (this._state) { case State.Hidden: return false; case State.Loading: - return !this.isAuto; + return !this._isAuto; default: - this.list.focusPrevious(1, true); + this._list.focusPrevious(1, true); return false; } } selectFirst(): boolean { - switch (this.state) { + switch (this._state) { case State.Hidden: return false; case State.Details: this._details.widget.scrollTop(); return true; case State.Loading: - return !this.isAuto; + return !this._isAuto; default: - this.list.focusFirst(); + this._list.focusFirst(); return true; } } getFocusedItem(): ISelectedSuggestion | undefined { - if (this.state !== State.Hidden - && this.state !== State.Empty - && this.state !== State.Loading - && this.completionModel + if (this._state !== State.Hidden + && this._state !== State.Empty + && this._state !== State.Loading + && this._completionModel ) { return { - item: this.list.getFocusedElements()[0], - index: this.list.getFocus()[0], - model: this.completionModel + item: this._list.getFocusedElements()[0], + index: this._list.getFocus()[0], + model: this._completionModel }; } return undefined; } toggleDetailsFocus(): void { - if (this.state === State.Details) { + if (this._state === State.Details) { this._setState(State.Open); - if (this.detailsBorderColor) { - this._details.widget.domNode.style.borderColor = this.detailsBorderColor; + if (this._detailsBorderColor) { + this._details.widget.domNode.style.borderColor = this._detailsBorderColor; } - } else if (this.state === State.Open && this._isDetailsVisible()) { + } else if (this._state === State.Open && this._isDetailsVisible()) { this._setState(State.Details); - if (this.detailsFocusBorderColor) { - this._details.widget.domNode.style.borderColor = this.detailsFocusBorderColor; + if (this._detailsFocusBorderColor) { + this._details.widget.domNode.style.borderColor = this._detailsFocusBorderColor; } } } @@ -659,14 +662,14 @@ export class SuggestWidget implements IDisposable { toggleDetails(): void { if (this._isDetailsVisible()) { // hide details widget - this.ctxSuggestWidgetDetailsVisible.set(false); + this._ctxSuggestWidgetDetailsVisible.set(false); this._setDetailsVisible(false); this._details.hide(); this.element.domNode.classList.remove('shows-details'); - } else if (canExpandCompletionItem(this.list.getFocusedElements()[0]) && (this.state === State.Open || this.state === State.Details || this.state === State.Frozen)) { + } else if (canExpandCompletionItem(this._list.getFocusedElements()[0]) && (this._state === State.Open || this._state === State.Details || this._state === State.Frozen)) { // show details widget (iff possible) - this.ctxSuggestWidgetDetailsVisible.set(true); + this._ctxSuggestWidgetDetailsVisible.set(true); this._setDetailsVisible(true); this.showDetails(false); } @@ -677,7 +680,7 @@ export class SuggestWidget implements IDisposable { if (loading) { this._details.widget.renderLoading(); } else { - this._details.widget.renderItem(this.list.getFocusedElements()[0], this.explainMode); + this._details.widget.renderItem(this._list.getFocusedElements()[0], this._explainMode); } this._positionDetails(); this.editor.focus(); @@ -685,8 +688,8 @@ export class SuggestWidget implements IDisposable { } toggleExplainMode(): void { - if (this.list.getFocusedElements()[0] && this._isDetailsVisible()) { - this.explainMode = !this.explainMode; + if (this._list.getFocusedElements()[0] && this._isDetailsVisible()) { + this._explainMode = !this._explainMode; this.showDetails(false); } } @@ -696,13 +699,13 @@ export class SuggestWidget implements IDisposable { } hideWidget(): void { - this.loadingTimeout.dispose(); + this._loadingTimeout?.dispose(); this._setState(State.Hidden); - this.onDidHideEmitter.fire(this); + this._onDidHide.fire(this); } isFrozen(): boolean { - return this.state === State.Frozen; + return this._state === State.Frozen; } _afterRender(position: ContentWidgetPositionPreference | null) { @@ -712,7 +715,7 @@ export class SuggestWidget implements IDisposable { } return; } - if (this.state === State.Empty || this.state === State.Loading) { + if (this._state === State.Empty || this._state === State.Loading) { // no special positioning when widget isn't showing list return; } @@ -738,12 +741,12 @@ export class SuggestWidget implements IDisposable { const info = this.getLayoutInfo(); // status bar - this.status.element.style.lineHeight = `${info.itemHeight}px`; + this._status.element.style.lineHeight = `${info.itemHeight}px`; - if (this.state === State.Empty || this.state === State.Loading) { + if (this._state === State.Empty || this._state === State.Loading) { // showing a message only height = info.itemHeight + info.borderHeight; - width = 230; + width = info.defaultSize.width / 2; this.element.enableSashes(false, false, false, false); this.element.minSize = this.element.maxSize = new dom.Dimension(width, height); this._contentWidget.setPreference(ContentWidgetPositionPreference.BELOW); @@ -754,16 +757,16 @@ export class SuggestWidget implements IDisposable { // width math const maxWidth = bodyBox.width - info.borderHeight - 2 * info.horizontalPadding; if (width === undefined) { - width = 430; + width = info.defaultSize.width; } if (width > maxWidth) { width = maxWidth; } - const preferredWidth = this.completionModel ? this.completionModel.stats.pLabelLen * info.typicalHalfwidthCharacterWidth : width; + const preferredWidth = this._completionModel ? this._completionModel.stats.pLabelLen * info.typicalHalfwidthCharacterWidth : width; // height math - const fullHeight = info.statusBarHeight + this.list.contentHeight + info.borderHeight; - const preferredHeight = info.statusBarHeight + 12 * info.itemHeight + info.borderHeight; + const fullHeight = info.statusBarHeight + this._list.contentHeight + info.borderHeight; + const preferredHeight = info.defaultSize.height; const minHeight = info.itemHeight + info.statusBarHeight; const editorBox = dom.getDomNodePagePosition(this.editor.getDomNode()); const cursorBox = this.editor.getScrolledVisiblePosition(this.editor.getPosition()); @@ -819,8 +822,8 @@ export class SuggestWidget implements IDisposable { height = Math.min(maxHeight, height); const { statusBarHeight } = this.getLayoutInfo(); - this.list.layout(height - statusBarHeight, width); - this.listElement.style.height = `${height - statusBarHeight}px`; + this._list.layout(height - statusBarHeight, width); + this._listElement.style.height = `${height - statusBarHeight}px`; this.element.layout(height, width); this._contentWidget.layout(); @@ -835,8 +838,8 @@ export class SuggestWidget implements IDisposable { getLayoutInfo() { const fontInfo = this.editor.getOption(EditorOption.fontInfo); - const itemHeight = this.editor.getOption(EditorOption.suggestLineHeight) || fontInfo.lineHeight; - const statusBarHeight = !this.editor.getOption(EditorOption.suggest).showStatusBar || this.state === State.Empty || this.state === State.Loading ? 0 : itemHeight; + const itemHeight = clamp(this.editor.getOption(EditorOption.suggestLineHeight) || fontInfo.lineHeight, 8, 1000); + const statusBarHeight = !this.editor.getOption(EditorOption.suggest).showStatusBar || this._state === State.Empty || this._state === State.Loading ? 0 : itemHeight; const borderWidth = this._details.widget.borderWidth; const borderHeight = 2 * borderWidth; @@ -847,7 +850,8 @@ export class SuggestWidget implements IDisposable { borderHeight, typicalHalfwidthCharacterWidth: fontInfo.typicalHalfwidthCharacterWidth, verticalPadding: 22, - horizontalPadding: 14 + horizontalPadding: 14, + defaultSize: new dom.Dimension(430, statusBarHeight + 12 * itemHeight + borderHeight) }; } @@ -856,7 +860,7 @@ export class SuggestWidget implements IDisposable { } private _setDetailsVisible(value: boolean) { - this._storageService.store('expandSuggestionDocs', value, StorageScope.GLOBAL); + this._storageService.store('expandSuggestionDocs', value, StorageScope.GLOBAL, StorageTarget.USER); } } diff --git a/src/vs/editor/contrib/suggest/suggestWidgetDetails.ts b/src/vs/editor/contrib/suggest/suggestWidgetDetails.ts index 72500145f..9eed3c990 100644 --- a/src/vs/editor/contrib/suggest/suggestWidgetDetails.ts +++ b/src/vs/editor/contrib/suggest/suggestWidgetDetails.ts @@ -91,6 +91,7 @@ export class SuggestDetailsWidget { const lineHeightPx = `${lineHeight}px`; this.domNode.style.fontSize = fontSizePx; + this.domNode.style.lineHeight = lineHeightPx; this.domNode.style.fontWeight = fontWeight; this.domNode.style.fontFeatureSettings = fontInfo.fontFeatureSettings; this._type.style.fontFamily = fontFamily; @@ -127,9 +128,10 @@ export class SuggestDetailsWidget { if (explainMode) { let md = ''; - md += `score: ${item.score[0]}${item.word ? `, compared '${item.completion.filterText && (item.completion.filterText + ' (filterText)') || item.completion.label}' with '${item.word}'` : ' (no prefix)'}\n`; + md += `score: ${item.score[0]}${item.word ? `, compared '${item.completion.filterText && (item.completion.filterText + ' (filterText)') || typeof item.completion.label === 'string' ? item.completion.label : item.completion.label.name}' with '${item.word}'` : ' (no prefix)'}\n`; md += `distance: ${item.distance}, see localityBonus-setting\n`; md += `index: ${item.idx}, based on ${item.completion.sortText && `sortText: "${item.completion.sortText}"` || 'label'}\n`; + md += `commit characters: ${item.completion.commitCharacters}\n`; documentation = new MarkdownString().appendCodeblock('empty', md); detail = `Provider: ${item.provider._debugDisplayName}`; } @@ -168,7 +170,7 @@ export class SuggestDetailsWidget { const renderedContents = this._markdownRenderer.render(documentation); this._docs.appendChild(renderedContents.element); this._renderDisposeable.add(renderedContents); - this._renderDisposeable.add(this._markdownRenderer.onDidRenderCodeBlock(() => { + this._renderDisposeable.add(this._markdownRenderer.onDidRenderAsync(() => { this.layout(this._size.width, this._type.clientHeight + this._docs.clientHeight); this._onDidChangeContents.fire(this); })); diff --git a/src/vs/editor/contrib/suggest/suggestWidgetRenderer.ts b/src/vs/editor/contrib/suggest/suggestWidgetRenderer.ts index 66e6e51ce..26cdc77ae 100644 --- a/src/vs/editor/contrib/suggest/suggestWidgetRenderer.ts +++ b/src/vs/editor/contrib/suggest/suggestWidgetRenderer.ts @@ -11,7 +11,7 @@ import { IListRenderer } from 'vs/base/browser/ui/list/list'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { CompletionItem } from './suggest'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { CompletionItemKind, completionKindToCssClass, CompletionItemTag } from 'vs/editor/common/modes'; import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; @@ -21,32 +21,40 @@ import { URI } from 'vs/base/common/uri'; import { FileKind } from 'vs/platform/files/common/files'; import { flatten } from 'vs/base/common/arrays'; import { canExpandCompletionItem } from './suggestWidgetDetails'; -import { Codicon, registerIcon } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; export function getAriaId(index: number): string { return `suggest-aria-id:${index}`; } -export const suggestMoreInfoIcon = registerIcon('suggest-more-info', Codicon.chevronRight); +export const suggestMoreInfoIcon = registerIcon('suggest-more-info', Codicon.chevronRight, nls.localize('suggestMoreInfoIcon', 'Icon for more information in the suggest widget.')); -const colorRegExp = /^(#([\da-f]{3}){1,2}|(rgb|hsl)a\(\s*(\d{1,3}%?\s*,\s*){3}(1|0?\.\d+)\)|(rgb|hsl)\(\s*\d{1,3}%?(\s*,\s*\d{1,3}%?){2}\s*\))$/i; +const _completionItemColor = new class ColorExtractor { -function extractColor(item: CompletionItem, out: string[]): boolean { - const label = typeof item.completion.label === 'string' - ? item.completion.label - : item.completion.label.name; + private static _regexRelaxed = /(#([\da-fA-F]{3}){1,2}|(rgb|hsl)a\(\s*(\d{1,3}%?\s*,\s*){3}(1|0?\.\d+)\)|(rgb|hsl)\(\s*\d{1,3}%?(\s*,\s*\d{1,3}%?){2}\s*\))/; + private static _regexStrict = new RegExp(`^${ColorExtractor._regexRelaxed.source}$`, 'i'); - if (label.match(colorRegExp)) { - out[0] = label; - return true; + extract(item: CompletionItem, out: string[]): boolean { + if (item.textLabel.match(ColorExtractor._regexStrict)) { + out[0] = item.textLabel; + return true; + } + if (item.completion.detail && item.completion.detail.match(ColorExtractor._regexStrict)) { + out[0] = item.completion.detail; + return true; + } + if (typeof item.completion.documentation === 'string') { + const match = ColorExtractor._regexRelaxed.exec(item.completion.documentation); + if (match && (match.index === 0 || match.index + match[0].length === item.completion.documentation.length)) { + out[0] = match[0]; + return true; + } + } + return false; } - if (typeof item.completion.documentation === 'string' && item.completion.documentation.match(colorRegExp)) { - out[0] = item.completion.documentation; - return true; - } - return false; -} +}; export interface ISuggestionTemplateData { @@ -116,7 +124,7 @@ export class ItemRenderer implements IListRenderer { @@ -165,7 +173,7 @@ export class ItemRenderer implements IListRenderer { const _deleteWordRight = new DeleteWordRight(); const _deleteWordStartRight = new DeleteWordStartRight(); const _deleteWordEndRight = new DeleteWordEndRight(); + const _deleteInsideWord = new DeleteInsideWord(); function runEditorCommand(editor: ICodeEditor, command: EditorCommand): void { command.runEditorCommand(null, editor, null); @@ -88,6 +89,9 @@ suite('WordOperations', () => { function deleteWordEndRight(editor: ICodeEditor): void { runEditorCommand(editor, _deleteWordEndRight); } + function deleteInsideWord(editor: ICodeEditor): void { + _deleteInsideWord.run(null!, editor, null); + } test('cursorWordLeft - simple', () => { const EXPECTED = [ @@ -750,4 +754,119 @@ suite('WordOperations', () => { model.dispose(); mode.dispose(); }); + + test('deleteInsideWord - empty line', () => { + withTestCodeEditor([ + 'Line1', + '', + 'Line2' + ], {}, (editor, _) => { + const model = editor.getModel()!; + editor.setPosition(new Position(2, 1)); + deleteInsideWord(editor); + assert.equal(model.getValue(), 'Line1\nLine2'); + }); + }); + + test('deleteInsideWord - in whitespace 1', () => { + withTestCodeEditor([ + 'Just some text.' + ], {}, (editor, _) => { + const model = editor.getModel()!; + editor.setPosition(new Position(1, 6)); + deleteInsideWord(editor); + assert.equal(model.getValue(), 'Justsome text.'); + }); + }); + + test('deleteInsideWord - in whitespace 2', () => { + withTestCodeEditor([ + 'Just some text.' + ], {}, (editor, _) => { + const model = editor.getModel()!; + editor.setPosition(new Position(1, 6)); + deleteInsideWord(editor); + assert.equal(model.getValue(), 'Justsome text.'); + }); + }); + + test('deleteInsideWord - in whitespace 3', () => { + withTestCodeEditor([ + 'Just "some text.' + ], {}, (editor, _) => { + const model = editor.getModel()!; + editor.setPosition(new Position(1, 6)); + deleteInsideWord(editor); + assert.equal(model.getValue(), 'Just"some text.'); + deleteInsideWord(editor); + assert.equal(model.getValue(), '"some text.'); + deleteInsideWord(editor); + assert.equal(model.getValue(), 'some text.'); + deleteInsideWord(editor); + assert.equal(model.getValue(), 'text.'); + deleteInsideWord(editor); + assert.equal(model.getValue(), '.'); + deleteInsideWord(editor); + assert.equal(model.getValue(), ''); + deleteInsideWord(editor); + assert.equal(model.getValue(), ''); + }); + }); + + test('deleteInsideWord - in non-words', () => { + withTestCodeEditor([ + 'x=3+4+5+6' + ], {}, (editor, _) => { + const model = editor.getModel()!; + editor.setPosition(new Position(1, 7)); + deleteInsideWord(editor); + assert.equal(model.getValue(), 'x=3+45+6'); + deleteInsideWord(editor); + assert.equal(model.getValue(), 'x=3++6'); + deleteInsideWord(editor); + assert.equal(model.getValue(), 'x=36'); + deleteInsideWord(editor); + assert.equal(model.getValue(), 'x='); + deleteInsideWord(editor); + assert.equal(model.getValue(), 'x'); + deleteInsideWord(editor); + assert.equal(model.getValue(), ''); + deleteInsideWord(editor); + assert.equal(model.getValue(), ''); + }); + }); + + test('deleteInsideWord - in words 1', () => { + withTestCodeEditor([ + 'This is interesting' + ], {}, (editor, _) => { + const model = editor.getModel()!; + editor.setPosition(new Position(1, 7)); + deleteInsideWord(editor); + assert.equal(model.getValue(), 'This interesting'); + deleteInsideWord(editor); + assert.equal(model.getValue(), 'This'); + deleteInsideWord(editor); + assert.equal(model.getValue(), ''); + deleteInsideWord(editor); + assert.equal(model.getValue(), ''); + }); + }); + + test('deleteInsideWord - in words 2', () => { + withTestCodeEditor([ + 'This is interesting' + ], {}, (editor, _) => { + const model = editor.getModel()!; + editor.setPosition(new Position(1, 7)); + deleteInsideWord(editor); + assert.equal(model.getValue(), 'This interesting'); + deleteInsideWord(editor); + assert.equal(model.getValue(), 'This'); + deleteInsideWord(editor); + assert.equal(model.getValue(), ''); + deleteInsideWord(editor); + assert.equal(model.getValue(), ''); + }); + }); }); diff --git a/src/vs/editor/contrib/wordOperations/wordOperations.ts b/src/vs/editor/contrib/wordOperations/wordOperations.ts index c7db9649e..82dcbddc5 100644 --- a/src/vs/editor/contrib/wordOperations/wordOperations.ts +++ b/src/vs/editor/contrib/wordOperations/wordOperations.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorCommand, ICommandOptions, ServicesAccessor, registerEditorCommand } from 'vs/editor/browser/editorExtensions'; +import { EditorCommand, ICommandOptions, ServicesAccessor, registerEditorCommand, EditorAction, registerEditorAction } from 'vs/editor/browser/editorExtensions'; import { ReplaceCommand } from 'vs/editor/common/commands/replaceCommand'; import { CursorState } from 'vs/editor/common/controller/cursorCommon'; import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; @@ -480,6 +481,36 @@ export class DeleteWordRight extends DeleteWordRightCommand { } } +export class DeleteInsideWord extends EditorAction { + + constructor() { + super({ + id: 'deleteInsideWord', + precondition: EditorContextKeys.writable, + label: nls.localize('deleteInsideWord', "Delete Word"), + alias: 'Delete Word' + }); + } + + public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + if (!editor.hasModel()) { + return; + } + const wordSeparators = getMapForWordSeparators(editor.getOption(EditorOption.wordSeparators)); + const model = editor.getModel(); + const selections = editor.getSelections(); + + const commands = selections.map((sel) => { + const deleteRange = WordOperations.deleteInsideWord(wordSeparators, model, sel); + return new ReplaceCommand(deleteRange, ''); + }); + + editor.pushUndoStop(); + editor.executeCommands(this.id, commands); + editor.pushUndoStop(); + } +} + registerEditorCommand(new CursorWordStartLeft()); registerEditorCommand(new CursorWordEndLeft()); registerEditorCommand(new CursorWordLeft()); @@ -502,3 +533,4 @@ registerEditorCommand(new DeleteWordLeft()); registerEditorCommand(new DeleteWordStartRight()); registerEditorCommand(new DeleteWordEndRight()); registerEditorCommand(new DeleteWordRight()); +registerEditorAction(DeleteInsideWord); diff --git a/src/vs/editor/editor.all.ts b/src/vs/editor/editor.all.ts index a591facd8..904283213 100644 --- a/src/vs/editor/editor.all.ts +++ b/src/vs/editor/editor.all.ts @@ -14,7 +14,7 @@ import 'vs/editor/contrib/caretOperations/transpose'; import 'vs/editor/contrib/clipboard/clipboard'; import 'vs/editor/contrib/codeAction/codeActionContributions'; import 'vs/editor/contrib/codelens/codelensController'; -import 'vs/editor/contrib/colorPicker/colorDetector'; +import 'vs/editor/contrib/colorPicker/colorContributions'; import 'vs/editor/contrib/comment/comment'; import 'vs/editor/contrib/contextmenu/contextmenu'; import 'vs/editor/contrib/cursorUndo/cursorUndo'; @@ -31,10 +31,10 @@ import 'vs/editor/contrib/hover/hover'; import 'vs/editor/contrib/indentation/indentation'; import 'vs/editor/contrib/inPlaceReplace/inPlaceReplace'; import 'vs/editor/contrib/linesOperations/linesOperations'; +import 'vs/editor/contrib/linkedEditing/linkedEditing'; import 'vs/editor/contrib/links/links'; import 'vs/editor/contrib/multicursor/multicursor'; import 'vs/editor/contrib/parameterHints/parameterHints'; -import 'vs/editor/contrib/rename/onTypeRename'; import 'vs/editor/contrib/rename/rename'; import 'vs/editor/contrib/smartSelect/smartSelect'; import 'vs/editor/contrib/snippet/snippetController2'; diff --git a/src/vs/editor/editor.api.ts b/src/vs/editor/editor.api.ts index 19d57ab28..0e4c48c34 100644 --- a/src/vs/editor/editor.api.ts +++ b/src/vs/editor/editor.api.ts @@ -8,8 +8,6 @@ import { createMonacoBaseAPI } from 'vs/editor/common/standalone/standaloneBase' import { createMonacoEditorAPI } from 'vs/editor/standalone/browser/standaloneEditor'; import { createMonacoLanguagesAPI } from 'vs/editor/standalone/browser/standaloneLanguages'; -const global: any = self; - // Set defaults for standalone editor EditorOptions.wrappingIndent.defaultValue = WrappingIndent.None; EditorOptions.glyphMargin.defaultValue = false; @@ -34,10 +32,10 @@ export const Token = api.Token; export const editor = api.editor; export const languages = api.languages; -global.monaco = api; +self.monaco = api; -if (typeof global.require !== 'undefined' && typeof global.require.config === 'function') { - global.require.config({ +if (typeof self.require !== 'undefined' && typeof self.require.config === 'function') { + self.require.config({ ignoreDuplicateModules: [ 'vscode-languageserver-types', 'vscode-languageserver-types/main', diff --git a/src/vs/editor/standalone/browser/colorizer.ts b/src/vs/editor/standalone/browser/colorizer.ts index fdde96abc..22bad13e9 100644 --- a/src/vs/editor/standalone/browser/colorizer.ts +++ b/src/vs/editor/standalone/browser/colorizer.ts @@ -15,6 +15,8 @@ import { ViewLineRenderingData } from 'vs/editor/common/viewModel/viewModel'; import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; import { MonarchTokenizer } from 'vs/editor/standalone/common/monarch/monarchLexer'; +const ttPolicy = window.trustedTypes?.createPolicy('standaloneColorizer', { createHTML: value => value }); + export interface IColorizerOptions { tabSize?: number; } @@ -40,7 +42,8 @@ export class Colorizer { let text = domNode.firstChild ? domNode.firstChild.nodeValue : ''; domNode.className += ' ' + theme; let render = (str: string) => { - domNode.innerHTML = str; + const trustedhtml = ttPolicy ? ttPolicy.createHTML(str) : str; + domNode.innerHTML = trustedhtml as unknown as string; }; return this.colorize(modeService, text || '', mimeType, options).then(render, (err) => console.error(err)); } @@ -54,7 +57,7 @@ export class Colorizer { if (strings.startsWithUTF8BOM(text)) { text = text.substr(1); } - let lines = text.split(/\r\n|\r|\n/); + let lines = strings.splitLines(text); let language = modeService.getModeId(mimeType); if (!language) { return Promise.resolve(_fakeColorize(lines, tabSize)); diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 8248e2744..ba51dfd9e 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -332,7 +332,8 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { when: when, weight1: 1000, weight2: 0, - extensionId: null + extensionId: null, + isBuiltinExtension: false }); toDispose.add(toDisposable(() => { @@ -380,11 +381,11 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { if (!keybinding) { // This might be a removal keybinding item in user settings => accept it - result[resultLen++] = new ResolvedKeybindingItem(undefined, item.command, item.commandArgs, when, isDefault, null); + result[resultLen++] = new ResolvedKeybindingItem(undefined, item.command, item.commandArgs, when, isDefault, null, false); } else { const resolvedKeybindings = this.resolveKeybinding(keybinding); for (const resolvedKeybinding of resolvedKeybindings) { - result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybinding, item.command, item.commandArgs, when, isDefault, null); + result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybinding, item.command, item.commandArgs, when, isDefault, null, false); } } } diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index e06bd35a8..9344ee801 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -113,6 +113,10 @@ export interface IGlobalEditorOptions { * Defaults to true. */ wordBasedSuggestions?: boolean; + /** + * Controls whether word based completions should be included from opened documents of the same language or any language. + */ + wordBasedSuggestionsOnlySameLanguage?: boolean; /** * Controls whether the semanticHighlighting is shown for the languages that support it. * true: semanticHighlighting is enabled for all themes diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index fb2aaac02..6d5fc9c32 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -41,6 +41,7 @@ import { clearAllFontInfos } from 'vs/editor/browser/config/configuration'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { StandaloneThemeServiceImpl } from 'vs/editor/standalone/browser/standaloneThemeServiceImpl'; +import { splitLines } from 'vs/base/common/strings'; type Omit = Pick>; @@ -288,7 +289,7 @@ export function tokenize(text: string, languageId: string): Token[][] { modeService.triggerMode(languageId); let tokenizationSupport = getSafeTokenizationSupport(languageId); - let lines = text.split(/\r\n|\r|\n/); + let lines = splitLines(text); let result: Token[][] = []; let state = tokenizationSupport.getInitialState(); for (let i = 0, len = lines.length; i < len; i++) { diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index b95826e91..dbf225a6c 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -75,9 +75,11 @@ export function setLanguageConfiguration(languageId: string, configuration: Lang */ export class EncodedTokenizationSupport2Adapter implements modes.ITokenizationSupport { + private readonly _languageIdentifier: modes.LanguageIdentifier; private readonly _actual: EncodedTokensProvider; - constructor(actual: EncodedTokensProvider) { + constructor(languageIdentifier: modes.LanguageIdentifier, actual: EncodedTokensProvider) { + this._languageIdentifier = languageIdentifier; this._actual = actual; } @@ -86,6 +88,9 @@ export class EncodedTokenizationSupport2Adapter implements modes.ITokenizationSu } public tokenize(line: string, state: modes.IState, offsetDelta: number): TokenizationResult { + if (typeof this._actual.tokenize === 'function') { + return TokenizationSupport2Adapter.adaptTokenize(this._languageIdentifier.language, <{ tokenize(line: string, state: modes.IState): ILineTokens; }>this._actual, line, state, offsetDelta); + } throw new Error('Not supported!'); } @@ -114,7 +119,7 @@ export class TokenizationSupport2Adapter implements modes.ITokenizationSupport { return this._actual.getInitialState(); } - private _toClassicTokens(tokens: IToken[], language: string, offsetDelta: number): Token[] { + private static _toClassicTokens(tokens: IToken[], language: string, offsetDelta: number): Token[] { let result: Token[] = []; let previousStartIndex: number = 0; for (let i = 0, len = tokens.length; i < len; i++) { @@ -137,9 +142,9 @@ export class TokenizationSupport2Adapter implements modes.ITokenizationSupport { return result; } - public tokenize(line: string, state: modes.IState, offsetDelta: number): TokenizationResult { - let actualResult = this._actual.tokenize(line, state); - let tokens = this._toClassicTokens(actualResult.tokens, this._languageIdentifier.language, offsetDelta); + public static adaptTokenize(language: string, actual: { tokenize(line: string, state: modes.IState): ILineTokens; }, line: string, state: modes.IState, offsetDelta: number): TokenizationResult { + let actualResult = actual.tokenize(line, state); + let tokens = TokenizationSupport2Adapter._toClassicTokens(actualResult.tokens, language, offsetDelta); let endState: modes.IState; // try to save an object if possible @@ -152,6 +157,10 @@ export class TokenizationSupport2Adapter implements modes.ITokenizationSupport { return new TokenizationResult(tokens, endState); } + public tokenize(line: string, state: modes.IState, offsetDelta: number): TokenizationResult { + return TokenizationSupport2Adapter.adaptTokenize(this._languageIdentifier.language, this._actual, line, state, offsetDelta); + } + private _toBinaryTokens(tokens: IToken[], offsetDelta: number): Uint32Array { const languageId = this._languageIdentifier.id; const tokenTheme = this._standaloneThemeService.getColorTheme().tokenTheme; @@ -287,6 +296,10 @@ export interface EncodedTokensProvider { * Tokenize a line given the state at the beginning of the line. */ tokenizeEncoded(line: string, state: modes.IState): IEncodedLineTokens; + /** + * Tokenize a line given the state at the beginning of the line. + */ + tokenize?(line: string, state: modes.IState): ILineTokens; } function isEncodedTokensProvider(provider: TokensProvider | EncodedTokensProvider): provider is EncodedTokensProvider { @@ -307,7 +320,7 @@ export function setTokensProvider(languageId: string, provider: TokensProvider | } const create = (provider: TokensProvider | EncodedTokensProvider) => { if (isEncodedTokensProvider(provider)) { - return new EncodedTokenizationSupport2Adapter(provider); + return new EncodedTokenizationSupport2Adapter(languageIdentifier!, provider); } else { return new TokenizationSupport2Adapter(StaticServices.standaloneThemeService.get(), languageIdentifier!, provider); } @@ -392,10 +405,10 @@ export function registerDocumentHighlightProvider(languageId: string, provider: } /** - * Register an on type rename provider. + * Register an linked editing range provider. */ -export function registerOnTypeRenameProvider(languageId: string, provider: modes.OnTypeRenameProvider): IDisposable { - return modes.OnTypeRenameProviderRegistry.register(languageId, provider); +export function registerLinkedEditingRangeProvider(languageId: string, provider: modes.LinkedEditingRangeProvider): IDisposable { + return modes.LinkedEditingRangeProviderRegistry.register(languageId, provider); } /** @@ -566,7 +579,7 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { registerHoverProvider: registerHoverProvider, registerDocumentSymbolProvider: registerDocumentSymbolProvider, registerDocumentHighlightProvider: registerDocumentHighlightProvider, - registerOnTypeRenameProvider: registerOnTypeRenameProvider, + registerLinkedEditingRangeProvider: registerLinkedEditingRangeProvider, registerDefinitionProvider: registerDefinitionProvider, registerImplementationProvider: registerImplementationProvider, registerTypeDefinitionProvider: registerTypeDefinitionProvider, diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 6baca469b..4a0e6c930 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -54,7 +54,6 @@ import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; import { StandaloneQuickInputServiceImpl } from 'vs/editor/standalone/browser/quickInput/standaloneQuickInputServiceImpl'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; export interface IEditorOverrideServices { [index: string]: any; @@ -167,8 +166,6 @@ export module StaticServices { export const storageService = define(IStorageService, () => new InMemoryStorageService()); - export const storageSyncService = define(IStorageKeysSyncRegistryService, () => new StorageKeysSyncRegistryService()); - export const editorWorkerService = define(IEditorWorkerService, (o) => new EditorWorkerServiceImpl(modelService.get(o), resourceConfigurationService.get(o), logService.get(o))); } diff --git a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts index 54ca70238..544d7b07a 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts @@ -16,7 +16,7 @@ import { ColorIdentifier, Extensions, IColorRegistry } from 'vs/platform/theme/c import { Extensions as ThemingExtensions, ICssStyleCollector, IFileIconTheme, IThemingRegistry, ITokenStyle } from 'vs/platform/theme/common/themeService'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { ColorScheme } from 'vs/platform/theme/common/theme'; -import { CodiconStyles } from 'vs/base/browser/ui/codicons/codiconStyles'; +import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; const VS_THEME_NAME = 'vs'; const VS_DARK_THEME_NAME = 'vs-dark'; @@ -208,15 +208,18 @@ export class StandaloneThemeServiceImpl extends Disposable implements IStandalon this._knownThemes.set(VS_THEME_NAME, newBuiltInTheme(VS_THEME_NAME)); this._knownThemes.set(VS_DARK_THEME_NAME, newBuiltInTheme(VS_DARK_THEME_NAME)); this._knownThemes.set(HC_BLACK_THEME_NAME, newBuiltInTheme(HC_BLACK_THEME_NAME)); - this._codiconCSS = CodiconStyles.getCSS(); + + const iconRegistry = getIconRegistry(); + + this._codiconCSS = iconRegistry.getCSS(); this._themeCSS = ''; this._allCSS = `${this._codiconCSS}\n${this._themeCSS}`; this._globalStyleElement = null; this._styleElements = []; this.setTheme(VS_THEME_NAME); - CodiconStyles.onDidChange(() => { - this._codiconCSS = CodiconStyles.getCSS(); + iconRegistry.onDidChange(() => { + this._codiconCSS = iconRegistry.getCSS(); this._updateCSS(); }); } diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index a90e11dfd..cb202a3f8 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -2208,6 +2208,42 @@ suite('Editor Controller - Regression tests', () => { }); }); + test('issue #110376: multiple selections with wordwrap behave differently', () => { + // a single model line => 4 view lines + withTestCodeEditor([ + [ + 'just a sentence. just a ', + 'sentence. just a sentence.', + ].join('') + ], { wordWrap: 'wordWrapColumn', wordWrapColumn: 25 }, (editor, viewModel) => { + viewModel.setSelections('test', [ + new Selection(1, 1, 1, 16), + new Selection(1, 18, 1, 33), + new Selection(1, 35, 1, 50), + ]); + + moveLeft(editor, viewModel); + assertCursor(viewModel, [ + new Selection(1, 1, 1, 1), + new Selection(1, 18, 1, 18), + new Selection(1, 35, 1, 35), + ]); + + viewModel.setSelections('test', [ + new Selection(1, 1, 1, 16), + new Selection(1, 18, 1, 33), + new Selection(1, 35, 1, 50), + ]); + + moveRight(editor, viewModel); + assertCursor(viewModel, [ + new Selection(1, 16, 1, 16), + new Selection(1, 33, 1, 33), + new Selection(1, 50, 1, 50), + ]); + }); + }); + test('issue #98320: Multi-Cursor, Wrap lines and cursorSelectRight ==> cursors out of sync', () => { // a single model line => 4 view lines withTestCodeEditor([ @@ -4038,6 +4074,33 @@ suite('Editor Controller - Indentation Rules', () => { mode.dispose(); }); + test('issue #57197: indent rules regex should be stateless', () => { + usingCursor({ + text: [ + 'Project:', + ], + languageIdentifier: (new IndentRulesMode({ + decreaseIndentPattern: /^\s*}$/gm, + increaseIndentPattern: /^(?![^\S\n]*(?!--|––|——)(?:[-❍❑■⬜□☐▪▫–—≡→›✘xX✔✓☑+]|\[[ xX+-]?\])\s[^\n]*)[^\S\n]*(.+:)[^\S\n]*(?:(?=@[^\s*~(]+(?::\/\/[^\s*~(:]+)?(?:\([^)]*\))?)|$)/gm, + })).getLanguageIdentifier(), + modelOpts: { insertSpaces: false }, + editorOpts: { autoIndent: 'full' } + }, (editor, model, viewModel) => { + moveTo(editor, viewModel, 1, 9, false); + assertCursor(viewModel, new Selection(1, 9, 1, 9)); + + viewModel.type('\n', 'keyboard'); + model.forceTokenization(model.getLineCount()); + assertCursor(viewModel, new Selection(2, 2, 2, 2)); + + moveTo(editor, viewModel, 1, 9, false); + assertCursor(viewModel, new Selection(1, 9, 1, 9)); + viewModel.type('\n', 'keyboard'); + model.forceTokenization(model.getLineCount()); + assertCursor(viewModel, new Selection(2, 2, 2, 2)); + }); + }); + test('', () => { class JSONMode extends MockMode { private static readonly _id = new LanguageIdentifier('indentRulesMode', 4); @@ -4633,7 +4696,7 @@ suite('autoClosingPairs', () => { 'v|ar |c = \'|asd\';|', 'v|ar d = "|asd";|', 'v|ar e = /*3*/ 3;|', - 'v|ar f = /** 3 */3;|', + 'v|ar f = /** 3| */3;|', 'v|ar g = (3+5|);|', 'v|ar h = { |a: \'v|alue\' |};|', ]; @@ -4814,13 +4877,13 @@ suite('autoClosingPairs', () => { let autoClosePositions = [ 'var a |=| [|]|;|', - 'var b |=| |`asd`|;|', - 'var c |=| |\'asd\'|;|', - 'var d |=| |"asd"|;|', + 'var b |=| `asd`|;|', + 'var c |=| \'asd\'|;|', + 'var d |=| "asd"|;|', 'var e |=| /*3*/| 3;|', 'var f |=| /**| 3 */3;|', 'var g |=| (3+5)|;|', - 'var h |=| {| a:| |\'value\'| |}|;|', + 'var h |=| {| a:| \'value\'| |}|;|', ]; for (let i = 0, len = autoClosePositions.length; i < len; i++) { const lineNumber = i + 1; @@ -4863,6 +4926,51 @@ suite('autoClosingPairs', () => { mode.dispose(); }); + test('issue #72177: multi-character autoclose with conflicting patterns', () => { + const languageId = new LanguageIdentifier('autoClosingModeMultiChar', 5); + class AutoClosingModeMultiChar extends MockMode { + constructor() { + super(languageId); + this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), { + autoClosingPairs: [ + { open: '(', close: ')' }, + { open: '(*', close: '*)' }, + { open: '<@', close: '@>' }, + { open: '<@@', close: '@@>' }, + ], + })); + } + } + + const mode = new AutoClosingModeMultiChar(); + + usingCursor({ + text: [ + '', + ], + languageIdentifier: mode.getLanguageIdentifier() + }, (editor, model, viewModel) => { + viewModel.type('(', 'keyboard'); + assert.strictEqual(model.getLineContent(1), '()'); + viewModel.type('*', 'keyboard'); + assert.strictEqual(model.getLineContent(1), '(**)', `doesn't add entire close when already closed substring is there`); + + model.setValue('('); + viewModel.setSelections('test', [new Selection(1, 2, 1, 2)]); + viewModel.type('*', 'keyboard'); + assert.strictEqual(model.getLineContent(1), '(**)', `does add entire close if not already there`); + + model.setValue(''); + viewModel.type('<@', 'keyboard'); + assert.strictEqual(model.getLineContent(1), '<@@>'); + viewModel.type('@', 'keyboard'); + assert.strictEqual(model.getLineContent(1), '<@@@@>', `autocloses when before multi-character closing brace`); + viewModel.type('(', 'keyboard'); + assert.strictEqual(model.getLineContent(1), '<@@()@@>', `autocloses when before multi-character closing brace`); + }); + mode.dispose(); + }); + test('issue #55314: Do not auto-close when ending with open', () => { const languageId = new LanguageIdentifier('myElectricMode', 5); class ElectricMode extends MockMode { @@ -4916,7 +5024,7 @@ suite('autoClosingPairs', () => { ], languageIdentifier: mode.getLanguageIdentifier() }, (editor, model, viewModel) => { - assertType(editor, model, viewModel, 1, 12, '"', '""', `does not over type and will auto close`); + assertType(editor, model, viewModel, 1, 12, '"', '"', `does not over type and will not auto close`); }); mode.dispose(); }); @@ -5277,7 +5385,7 @@ suite('autoClosingPairs', () => { assert.equal(model.getValue(), 'console.log(\'it\\\');'); viewModel.type('\'', 'keyboard'); - assert.equal(model.getValue(), 'console.log(\'it\\\'\'\');'); + assert.equal(model.getValue(), 'console.log(\'it\\\'\');'); }); mode.dispose(); }); diff --git a/src/vs/editor/test/browser/controller/imeTester.ts b/src/vs/editor/test/browser/controller/imeTester.ts index 4a3f4e196..eb0958400 100644 --- a/src/vs/editor/test/browser/controller/imeTester.ts +++ b/src/vs/editor/test/browser/controller/imeTester.ts @@ -9,6 +9,7 @@ import { ISimpleModel, PagedScreenReaderStrategy, TextAreaState } from 'vs/edito import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { EndOfLinePreference } from 'vs/editor/common/model'; +import * as dom from 'vs/base/browser/dom'; // To run this test, open imeTester.html @@ -50,12 +51,13 @@ class TestView { } public paint(output: HTMLElement) { - let r = ''; + dom.clearNode(output); for (let i = 1; i <= this._model.getLineCount(); i++) { - let content = this._model.getModelLineContent(i); - r += content + '
'; + const textNode = document.createTextNode(this._model.getModelLineContent(i)); + output.appendChild(textNode); + const br = document.createElement('br'); + output.appendChild(br); } - output.innerHTML = r; } } @@ -69,7 +71,12 @@ function doCreateTest(description: string, inputStr: string, expectedStr: string let title = document.createElement('div'); title.className = 'title'; - title.innerHTML = description + '. Type ' + inputStr + ''; + const inputStrStrong = document.createElement('strong'); + inputStrStrong.innerText = inputStr; + + title.innerText = description + '. Type '; + title.appendChild(inputStrStrong); + container.appendChild(title); let startBtn = document.createElement('button'); @@ -140,7 +147,7 @@ function doCreateTest(description: string, inputStr: string, expectedStr: string check.innerText = '[BAD]'; check.className = 'check bad'; } - check.innerHTML += expected; + check.appendChild(document.createTextNode(expected)); }; handler.onType((e) => { diff --git a/src/vs/editor/test/browser/editorTestServices.ts b/src/vs/editor/test/browser/editorTestServices.ts index e9fba5fbb..55ceb3734 100644 --- a/src/vs/editor/test/browser/editorTestServices.ts +++ b/src/vs/editor/test/browser/editorTestServices.ts @@ -22,6 +22,7 @@ export class TestCodeEditorService extends AbstractCodeEditorService { public registerDecorationType(key: string, options: IDecorationRenderOptions, parentTypeKey?: string): void { } public removeDecorationType(key: string): void { } public resolveDecorationOptions(decorationTypeKey: string, writable: boolean): IModelDecorationOptions { return {}; } + public resolveDecorationCSSRules(decorationTypeKey: string): CSSRuleList | null { return null; } } export class TestCommandService implements ICommandService { diff --git a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts index bc5224169..9c8c6c6ed 100644 --- a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts +++ b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts @@ -149,25 +149,33 @@ suite('Decoration Render Options', () => { assert(readStyleSheet(styleSheet).indexOf(`{background:url('data:image/svg+xml;base64,PHN2ZyB4b+') center center no-repeat;}`) > 0); s.removeDecorationType('example'); + function assertBackground(url1: string, url2: string) { + const actual = readStyleSheet(styleSheet); + assert( + actual.indexOf(`{background:url('${url1}') center center no-repeat;}`) > 0 + || actual.indexOf(`{background:url('${url2}') center center no-repeat;}`) > 0 + ); + } + if (platform.isWindows) { // windows file path (used as string) s.registerDecorationType('example', { gutterIconPath: URI.file('c:\\files\\miles\\more.png') }); - assert(readStyleSheet(styleSheet).indexOf(`{background:url('file:///c:/files/miles/more.png') center center no-repeat;}`) > 0); + assertBackground('file:///c:/files/miles/more.png', 'vscode-file://vscode-app/c:/files/miles/more.png'); s.removeDecorationType('example'); // single quote must always be escaped/encoded s.registerDecorationType('example', { gutterIconPath: URI.file('c:\\files\\foo\\b\'ar.png') }); - assert(readStyleSheet(styleSheet).indexOf(`{background:url('file:///c:/files/foo/b%27ar.png') center center no-repeat;}`) > 0); + assertBackground('file:///c:/files/foo/b%27ar.png', 'vscode-file://vscode-app/c:/files/foo/b%27ar.png'); s.removeDecorationType('example'); } else { // unix file path (used as string) s.registerDecorationType('example', { gutterIconPath: URI.file('/Users/foo/bar.png') }); - assert(readStyleSheet(styleSheet).indexOf(`{background:url('file:///Users/foo/bar.png') center center no-repeat;}`) > 0); + assertBackground('file:///Users/foo/bar.png', 'vscode-file://vscode-app/Users/foo/bar.png'); s.removeDecorationType('example'); // single quote must always be escaped/encoded s.registerDecorationType('example', { gutterIconPath: URI.file('/Users/foo/b\'ar.png') }); - assert(readStyleSheet(styleSheet).indexOf(`{background:url('file:///Users/foo/b%27ar.png') center center no-repeat;}`) > 0); + assertBackground('file:///Users/foo/b%27ar.png', 'vscode-file://vscode-app/Users/foo/b%27ar.png'); s.removeDecorationType('example'); } diff --git a/src/vs/editor/test/common/controller/cursorAtomicMoveOperations.test.ts b/src/vs/editor/test/common/controller/cursorAtomicMoveOperations.test.ts new file mode 100644 index 000000000..e9c60b6e3 --- /dev/null +++ b/src/vs/editor/test/common/controller/cursorAtomicMoveOperations.test.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 assert from 'assert'; +import { AtomicTabMoveOperations, Direction } from 'vs/editor/common/controller/cursorAtomicMoveOperations'; + +suite('Cursor move command test', () => { + + test('Test whitespaceVisibleColumn', () => { + const testCases = [ + { + lineContent: ' ', + tabSize: 4, + expectedPrevTabStopPosition: [-1, 0, 0, 0, 0, 4, 4, 4, 4, -1], + expectedPrevTabStopVisibleColumn: [-1, 0, 0, 0, 0, 4, 4, 4, 4, -1], + expectedVisibleColumn: [0, 1, 2, 3, 4, 5, 6, 7, 8, -1], + }, + { + lineContent: ' ', + tabSize: 4, + expectedPrevTabStopPosition: [-1, 0, 0, -1], + expectedPrevTabStopVisibleColumn: [-1, 0, 0, -1], + expectedVisibleColumn: [0, 1, 2, -1], + }, + { + lineContent: '\t', + tabSize: 4, + expectedPrevTabStopPosition: [-1, 0, -1], + expectedPrevTabStopVisibleColumn: [-1, 0, -1], + expectedVisibleColumn: [0, 4, -1], + }, + { + lineContent: '\t ', + tabSize: 4, + expectedPrevTabStopPosition: [-1, 0, 1, -1], + expectedPrevTabStopVisibleColumn: [-1, 0, 4, -1], + expectedVisibleColumn: [0, 4, 5, -1], + }, + { + lineContent: ' \t\t ', + tabSize: 4, + expectedPrevTabStopPosition: [-1, 0, 0, 2, 3, -1], + expectedPrevTabStopVisibleColumn: [-1, 0, 0, 4, 8, -1], + expectedVisibleColumn: [0, 1, 4, 8, 9, -1], + }, + { + lineContent: ' \tA', + tabSize: 4, + expectedPrevTabStopPosition: [-1, 0, 0, -1, -1], + expectedPrevTabStopVisibleColumn: [-1, 0, 0, -1, -1], + expectedVisibleColumn: [0, 1, 4, -1, -1], + }, + { + lineContent: 'A', + tabSize: 4, + expectedPrevTabStopPosition: [-1, -1, -1], + expectedPrevTabStopVisibleColumn: [-1, -1, -1], + expectedVisibleColumn: [0, -1, -1], + }, + { + lineContent: '', + tabSize: 4, + expectedPrevTabStopPosition: [-1, -1], + expectedPrevTabStopVisibleColumn: [-1, -1], + expectedVisibleColumn: [0, -1], + }, + ]; + + for (const testCase of testCases) { + const maxPosition = testCase.expectedVisibleColumn.length; + for (let position = 0; position < maxPosition; position++) { + const actual = AtomicTabMoveOperations.whitespaceVisibleColumn(testCase.lineContent, position, testCase.tabSize); + const expected = [ + testCase.expectedPrevTabStopPosition[position], + testCase.expectedPrevTabStopVisibleColumn[position], + testCase.expectedVisibleColumn[position] + ]; + assert.deepStrictEqual(actual, expected); + } + } + }); + + test('Test atomicPosition', () => { + const testCases = [ + { + lineContent: ' ', + tabSize: 4, + expectedLeft: [-1, 0, 0, 0, 0, 4, 4, 4, 4, -1], + expectedRight: [4, 4, 4, 4, 8, 8, 8, 8, -1, -1], + expectedNearest: [0, 0, 0, 4, 4, 4, 4, 8, 8, -1], + }, + { + lineContent: ' \t', + tabSize: 4, + expectedLeft: [-1, 0, 0, -1], + expectedRight: [2, 2, -1, -1], + expectedNearest: [0, 0, 2, -1], + }, + { + lineContent: '\t ', + tabSize: 4, + expectedLeft: [-1, 0, -1, -1], + expectedRight: [1, -1, -1, -1], + expectedNearest: [0, 1, -1, -1], + }, + { + lineContent: ' \t ', + tabSize: 4, + expectedLeft: [-1, 0, 0, -1, -1], + expectedRight: [2, 2, -1, -1, -1], + expectedNearest: [0, 0, 2, -1, -1], + }, + { + lineContent: ' A', + tabSize: 4, + expectedLeft: [-1, 0, 0, 0, 0, 4, 4, 4, 4, -1, -1], + expectedRight: [4, 4, 4, 4, 8, 8, 8, 8, -1, -1, -1], + expectedNearest: [0, 0, 0, 4, 4, 4, 4, 8, 8, -1, -1], + }, + { + lineContent: ' foo', + tabSize: 4, + expectedLeft: [-1, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1], + expectedRight: [4, 4, 4, 4, -1, -1, -1, -1, -1, -1, -1], + expectedNearest: [0, 0, 0, 4, 4, -1, -1, -1, -1, -1, -1], + }, + ]; + + for (const testCase of testCases) { + for (const { direction, expected } of [ + { + direction: Direction.Left, + expected: testCase.expectedLeft, + }, + { + direction: Direction.Right, + expected: testCase.expectedRight, + }, + { + direction: Direction.Nearest, + expected: testCase.expectedNearest, + }, + ]) { + + const actual = expected.map((_, i) => AtomicTabMoveOperations.atomicPosition(testCase.lineContent, i, testCase.tabSize, direction)); + assert.deepStrictEqual(actual, expected); + } + } + }); +}); diff --git a/src/vs/editor/test/common/diff/diffComputer.test.ts b/src/vs/editor/test/common/diff/diffComputer.test.ts index a4d574c24..9361e05d6 100644 --- a/src/vs/editor/test/common/diff/diffComputer.test.ts +++ b/src/vs/editor/test/common/diff/diffComputer.test.ts @@ -852,4 +852,31 @@ suite('Editor Diff - DiffComputer', () => { ]; assertDiff(original, modified, expected, false, false, false); }); + + test('gives preference to matching longer lines', () => { + let original = [ + 'A', + 'A', + 'BB', + 'C', + ]; + let modified = [ + 'A', + 'BB', + 'A', + 'D', + 'E', + 'A', + 'C', + ]; + let expected = [ + createLineChange( + 2, 2, 1, 0 + ), + createLineChange( + 3, 0, 3, 6 + ) + ]; + assertDiff(original, modified, expected, false, false, false); + }); }); diff --git a/src/vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils.ts b/src/vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils.ts index 49841dabc..4f87bf638 100644 --- a/src/vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils.ts +++ b/src/vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CharCode } from 'vs/base/common/charCode'; +import { splitLines } from 'vs/base/common/strings'; import { Range } from 'vs/editor/common/core/range'; import { DefaultEndOfLine, ITextBuffer, ITextBufferBuilder, ValidAnnotatedEditOperation } from 'vs/editor/common/model'; @@ -34,7 +35,7 @@ export function getRandomString(minLength: number, maxLength: number): string { export function generateRandomEdits(chunks: string[], editCnt: number): ValidAnnotatedEditOperation[] { let lines: string[] = []; for (const chunk of chunks) { - let newLines = chunk.split(/\r\n|\r|\n/); + let newLines = splitLines(chunk); if (lines.length === 0) { lines.push(...newLines); } else { @@ -64,7 +65,7 @@ export function generateRandomEdits(chunks: string[], editCnt: number): ValidAnn export function generateSequentialInserts(chunks: string[], editCnt: number): ValidAnnotatedEditOperation[] { let lines: string[] = []; for (const chunk of chunks) { - let newLines = chunk.split(/\r\n|\r|\n/); + let newLines = splitLines(chunk); if (lines.length === 0) { lines.push(...newLines); } else { @@ -96,7 +97,7 @@ export function generateSequentialInserts(chunks: string[], editCnt: number): Va export function generateRandomReplaces(chunks: string[], editCnt: number, searchStringLen: number, replaceStringLen: number): ValidAnnotatedEditOperation[] { let lines: string[] = []; for (const chunk of chunks) { - let newLines = chunk.split(/\r\n|\r|\n/); + let newLines = splitLines(chunk); if (lines.length === 0) { lines.push(...newLines); } else { diff --git a/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts b/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts index ddec1facc..019b23f4f 100644 --- a/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts +++ b/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts @@ -14,6 +14,7 @@ import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeText import { NodeColor, SENTINEL, TreeNode } from 'vs/editor/common/model/pieceTreeTextBuffer/rbTreeBase'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { SearchData } from 'vs/editor/common/model/textModelSearch'; +import { splitLines } from 'vs/base/common/strings'; const alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n'; @@ -75,7 +76,7 @@ function trimLineFeed(text: string): string { //#region Assertion function testLinesContent(str: string, pieceTable: PieceTreeBase) { - let lines = str.split(/\r\n|\r|\n/); + let lines = splitLines(str); assert.equal(pieceTable.getLineCount(), lines.length); assert.equal(pieceTable.getLinesRawContent(), str); for (let i = 0; i < lines.length; i++) { @@ -997,7 +998,7 @@ suite('CRLF', () => { pieceTable.delete(2, 3); str = str.substring(0, 2) + str.substring(2 + 3); - let lines = str.split(/\r\n|\r|\n/); + let lines = splitLines(str); assert.equal(pieceTable.getLineCount(), lines.length); assertTreeInvariants(pieceTable); }); @@ -1012,7 +1013,7 @@ suite('CRLF', () => { pieceTable.delete(4, 1); str = str.substring(0, 4) + str.substring(4 + 1); - let lines = str.split(/\r\n|\r|\n/); + let lines = splitLines(str); assert.equal(pieceTable.getLineCount(), lines.length); assertTreeInvariants(pieceTable); }); @@ -1033,7 +1034,7 @@ suite('CRLF', () => { pieceTable.insert(3, '\r\r\r\n'); str = str.substring(0, 3) + '\r\r\r\n' + str.substring(3); - let lines = str.split(/\r\n|\r|\n/); + let lines = splitLines(str); assert.equal(pieceTable.getLineCount(), lines.length); assertTreeInvariants(pieceTable); }); @@ -1205,7 +1206,7 @@ suite('centralized lineStarts with CRLF', () => { pieceTable.delete(2, 3); str = str.substring(0, 2) + str.substring(2 + 3); - let lines = str.split(/\r\n|\r|\n/); + let lines = splitLines(str); assert.equal(pieceTable.getLineCount(), lines.length); assertTreeInvariants(pieceTable); }); @@ -1218,7 +1219,7 @@ suite('centralized lineStarts with CRLF', () => { pieceTable.delete(4, 1); str = str.substring(0, 4) + str.substring(4 + 1); - let lines = str.split(/\r\n|\r|\n/); + let lines = splitLines(str); assert.equal(pieceTable.getLineCount(), lines.length); assertTreeInvariants(pieceTable); }); @@ -1238,7 +1239,7 @@ suite('centralized lineStarts with CRLF', () => { pieceTable.insert(3, '\r\r\r\n'); str = str.substring(0, 3) + '\r\r\r\n' + str.substring(3); - let lines = str.split(/\r\n|\r|\n/); + let lines = splitLines(str); assert.equal(pieceTable.getLineCount(), lines.length); assertTreeInvariants(pieceTable); }); diff --git a/src/vs/editor/test/common/services/editorSimpleWorker.test.ts b/src/vs/editor/test/common/services/editorSimpleWorker.test.ts index 2b2a748c6..54f73ed0e 100644 --- a/src/vs/editor/test/common/services/editorSimpleWorker.test.ts +++ b/src/vs/editor/test/common/services/editorSimpleWorker.test.ts @@ -162,7 +162,7 @@ suite('EditorSimpleWorker', () => { 'f f' // 2 ]); - return worker.textualSuggest(model.uri.toString(), { lineNumber: 2, column: 2 }, '[a-z]+', 'img').then((result) => { + return worker.textualSuggest([model.uri.toString()], 'f', '[a-z]+', 'img').then((result) => { if (!result) { assert.ok(false); return; diff --git a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts index c4c1af552..910165a0e 100644 --- a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts +++ b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts @@ -68,6 +68,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { horizontalSliderSize: EditorOptions.scrollbar.defaultValue.horizontalSliderSize, verticalScrollbarSize: input.verticalScrollbarWidth, verticalSliderSize: EditorOptions.scrollbar.defaultValue.verticalSliderSize, + scrollByPage: EditorOptions.scrollbar.defaultValue.scrollByPage, }; options._write(EditorOption.scrollbar, scrollbarOptions); const lineNumbersOptions: InternalEditorRenderLineNumbersOptions = { @@ -78,7 +79,8 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { options._write(EditorOption.wordWrap, 'off'); options._write(EditorOption.wordWrapColumn, 80); - options._write(EditorOption.wordWrapMinified, true); + options._write(EditorOption.wordWrapOverride1, 'inherit'); + options._write(EditorOption.wordWrapOverride2, 'inherit'); options._write(EditorOption.accessibilitySupport, 'auto'); const actual = EditorLayoutInfoComputer.computeLayout(options, { diff --git a/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts b/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts index 509a0fe0b..670f42407 100644 --- a/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts +++ b/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts @@ -5,8 +5,9 @@ import * as assert from 'assert'; import { WrappingIndent, EditorOptions } from 'vs/editor/common/config/editorOptions'; import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; -import { ILineBreaksComputerFactory, LineBreakData } from 'vs/editor/common/viewModel/splitLinesCollection'; +import { ILineBreaksComputerFactory } from 'vs/editor/common/viewModel/splitLinesCollection'; import { FontInfo } from 'vs/editor/common/config/fontInfo'; +import { LineBreakData } from 'vs/editor/common/viewModel/viewModel'; function parseAnnotatedText(annotatedText: string): { text: string; indices: number[]; } { let text = ''; diff --git a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts index 2c5cca1cb..3156be0e1 100644 --- a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts +++ b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts @@ -14,8 +14,8 @@ import { TextModel } from 'vs/editor/common/model/textModel'; import * as modes from 'vs/editor/common/modes'; import { NULL_STATE } from 'vs/editor/common/modes/nullMode'; import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; -import { LineBreakData, ISimpleModel, SplitLine, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection'; -import { ViewLineData } from 'vs/editor/common/viewModel/viewModel'; +import { ISimpleModel, SplitLine, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection'; +import { LineBreakData, ViewLineData } from 'vs/editor/common/viewModel/viewModel'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; diff --git a/src/vs/loader.js b/src/vs/loader.js index 5a97ccfcc..76db97736 100644 --- a/src/vs/loader.js +++ b/src/vs/loader.js @@ -198,6 +198,9 @@ var AMDLoader; if (!obj || typeof obj !== 'object' || obj instanceof RegExp) { return obj; } + if (!Array.isArray(obj) && Object.getPrototypeOf(obj) !== Object.prototype) { + return obj; + } var result = Array.isArray(obj) ? [] : {}; Utilities.forEachProperty(obj, function (key, value) { if (value && typeof value === 'object') { @@ -618,37 +621,8 @@ var AMDLoader; }; return OnlyOnceScriptLoader; }()); - var trustedTypesPolyfill = new /** @class */(function () { - function class_1() { - } - class_1.prototype.installIfNeeded = function () { - if (typeof globalThis.trustedTypes !== 'undefined') { - return; // already defined - } - var _defaultRules = { - createHTML: function () { throw new Error('Policy\'s TrustedTypePolicyOptions did not specify a \'createHTML\' member'); }, - createScript: function () { throw new Error('Policy\'s TrustedTypePolicyOptions did not specify a \'createScript\' member'); }, - createScriptURL: function () { throw new Error('Policy\'s TrustedTypePolicyOptions did not specify a \'createScriptURL\' member'); }, - }; - globalThis.trustedTypes = { - createPolicy: function (name, rules) { - var _a, _b, _c; - return { - name: name, - createHTML: (_a = rules.createHTML) !== null && _a !== void 0 ? _a : _defaultRules.createHTML, - createScript: (_b = rules.createScript) !== null && _b !== void 0 ? _b : _defaultRules.createScript, - createScriptURL: (_c = rules.createScriptURL) !== null && _c !== void 0 ? _c : _defaultRules.createScriptURL, - }; - } - }; - }; - return class_1; - }()); - //#endregion var BrowserScriptLoader = /** @class */ (function () { function BrowserScriptLoader() { - // polyfill trustedTypes-support if missing - trustedTypesPolyfill.installIfNeeded(); } /** * Attach load / error listeners to a script element and remove them when either one has fired. @@ -673,7 +647,7 @@ var AMDLoader; BrowserScriptLoader.prototype.load = function (moduleManager, scriptSrc, callback, errorback) { if (/^node\|/.test(scriptSrc)) { var opts = moduleManager.getConfig().getOptionsLiteral(); - var nodeRequire = (opts.nodeRequire || AMDLoader.global.nodeRequire); + var nodeRequire = ensureRecordedNodeRequire(moduleManager.getRecorder(), (opts.nodeRequire || AMDLoader.global.nodeRequire)); var pieces = scriptSrc.split('|'); var moduleExports_1 = null; try { @@ -691,12 +665,9 @@ var AMDLoader; script.setAttribute('async', 'async'); script.setAttribute('type', 'text/javascript'); this.attachListeners(script, callback, errorback); - var createTrustedScriptURL = moduleManager.getConfig().getOptionsLiteral().createTrustedScriptURL; - if (createTrustedScriptURL) { - if (!this.scriptSourceURLPolicy) { - this.scriptSourceURLPolicy = trustedTypes.createPolicy('amdLoader', { createScriptURL: createTrustedScriptURL }); - } - scriptSrc = this.scriptSourceURLPolicy.createScriptURL(scriptSrc); + var trustedTypesPolicy = moduleManager.getConfig().getOptionsLiteral().trustedTypesPolicy; + if (trustedTypesPolicy) { + scriptSrc = trustedTypesPolicy.createScriptURL(scriptSrc); } script.setAttribute('src', scriptSrc); // Propagate CSP nonce to dynamically created script tag. @@ -711,16 +682,11 @@ var AMDLoader; }()); var WorkerScriptLoader = /** @class */ (function () { function WorkerScriptLoader() { - // polyfill trustedTypes-support if missing - trustedTypesPolyfill.installIfNeeded(); } WorkerScriptLoader.prototype.load = function (moduleManager, scriptSrc, callback, errorback) { - var createTrustedScriptURL = moduleManager.getConfig().getOptionsLiteral().createTrustedScriptURL; - if (createTrustedScriptURL) { - if (!this.scriptSourceURLPolicy) { - this.scriptSourceURLPolicy = trustedTypes.createPolicy('amdLoader', { createScriptURL: createTrustedScriptURL }); - } - scriptSrc = this.scriptSourceURLPolicy.createScriptURL(scriptSrc); + var trustedTypesPolicy = moduleManager.getConfig().getOptionsLiteral().trustedTypesPolicy; + if (trustedTypesPolicy) { + scriptSrc = trustedTypesPolicy.createScriptURL(scriptSrc); } try { importScripts(scriptSrc); @@ -815,7 +781,7 @@ var AMDLoader; NodeScriptLoader.prototype.load = function (moduleManager, scriptSrc, callback, errorback) { var _this = this; var opts = moduleManager.getConfig().getOptionsLiteral(); - var nodeRequire = (opts.nodeRequire || AMDLoader.global.nodeRequire); + var nodeRequire = ensureRecordedNodeRequire(moduleManager.getRecorder(), (opts.nodeRequire || AMDLoader.global.nodeRequire)); var nodeInstrumenter = (opts.nodeInstrumenter || function (c) { return c; }); this._init(nodeRequire); this._initNodeRequire(nodeRequire, moduleManager); @@ -1022,6 +988,24 @@ var AMDLoader; NodeScriptLoader._SUFFIX = '\n});'; return NodeScriptLoader; }()); + function ensureRecordedNodeRequire(recorder, _nodeRequire) { + if (_nodeRequire.__$__isRecorded) { + // it is already recorded + return _nodeRequire; + } + var nodeRequire = function nodeRequire(what) { + recorder.record(33 /* NodeBeginNativeRequire */, what); + try { + return _nodeRequire(what); + } + finally { + recorder.record(34 /* NodeEndNativeRequire */, what); + } + }; + nodeRequire.__$__isRecorded = true; + return nodeRequire; + } + AMDLoader.ensureRecordedNodeRequire = ensureRecordedNodeRequire; function createScriptLoader(env) { return new OnlyOnceScriptLoader(env); } @@ -1853,18 +1837,10 @@ var AMDLoader; }; function init() { if (typeof AMDLoader.global.require !== 'undefined' || typeof require !== 'undefined') { - var _nodeRequire_1 = (AMDLoader.global.require || require); - if (typeof _nodeRequire_1 === 'function' && typeof _nodeRequire_1.resolve === 'function') { + var _nodeRequire = (AMDLoader.global.require || require); + if (typeof _nodeRequire === 'function' && typeof _nodeRequire.resolve === 'function') { // re-expose node's require function - var nodeRequire = function (what) { - moduleManager.getRecorder().record(33 /* NodeBeginNativeRequire */, what); - try { - return _nodeRequire_1(what); - } - finally { - moduleManager.getRecorder().record(34 /* NodeEndNativeRequire */, what); - } - }; + var nodeRequire = AMDLoader.ensureRecordedNodeRequire(moduleManager.getRecorder(), _nodeRequire); AMDLoader.global.nodeRequire = nodeRequire; RequireFunc.nodeRequire = nodeRequire; RequireFunc.__$__nodeRequire = nodeRequire; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index b9a579f90..2cb6913f7 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1115,6 +1115,10 @@ declare namespace monaco.editor { * Defaults to true. */ wordBasedSuggestions?: boolean; + /** + * Controls whether word based completions should be included from opened documents of the same language or any language. + */ + wordBasedSuggestionsOnlySameLanguage?: boolean; /** * Controls whether the semanticHighlighting is shown for the languages that support it. * true: semanticHighlighting is enabled for all themes @@ -1685,6 +1689,10 @@ declare namespace monaco.editor { * @return EOL char sequence (e.g.: '\n' or '\r\n'). */ getEOL(): string; + /** + * Get the end of line sequence predominantly used in the text buffer. + */ + getEndOfLineSequence(): EndOfLineSequence; /** * Get the minimum legal column for line at `lineNumber` */ @@ -1882,11 +1890,15 @@ declare namespace monaco.editor { */ detectIndentation(defaultInsertSpaces: boolean, defaultTabSize: number): void; /** - * Push a stack element onto the undo stack. This acts as an undo/redo point. - * The idea is to use `pushEditOperations` to edit the model and then to - * `pushStackElement` to create an undo/redo stop point. + * Close the current undo-redo element. + * This offers a way to create an undo/redo stop point. */ pushStackElement(): void; + /** + * Open the current undo-redo element. + * This offers a way to remove the current undo/redo stop point. + */ + popStackElement(): void; /** * Push edit operations, basically editing the model. This is the preferred way * of editing the model. The edit operations will land on the undo stack. @@ -2683,9 +2695,13 @@ declare namespace monaco.editor { */ readOnly?: boolean; /** - * Rename matching regions on type. + * Enable linked editing. * Defaults to false. */ + linkedEditing?: boolean; + /** + * deprecated, use linkedEditing instead + */ renameOnType?: boolean; /** * Should the editor render validation decorations. @@ -2799,6 +2815,14 @@ declare namespace monaco.editor { * Defaults to "off". */ wordWrap?: 'off' | 'on' | 'wordWrapColumn' | 'bounded'; + /** + * Override the `wordWrap` setting. + */ + wordWrapOverride1?: 'off' | 'on' | 'inherit'; + /** + * Override the `wordWrapOverride1` setting. + */ + wordWrapOverride2?: 'off' | 'on' | 'inherit'; /** * Control the wrapping of the editor. * When `wordWrap` = "off", the lines will never wrap. @@ -2808,11 +2832,6 @@ declare namespace monaco.editor { * Defaults to 80. */ wordWrapColumn?: number; - /** - * Force word wrapping when the text appears to be of a minified/generated file. - * Defaults to true. - */ - wordWrapMinified?: boolean; /** * Control indentation of wrapped lines. Can be: 'none', 'same', 'indent' or 'deepIndent'. * Defaults to 'same' in vscode and to 'none' in monaco-editor. @@ -2909,6 +2928,10 @@ declare namespace monaco.editor { * Suggest options. */ suggest?: ISuggestOptions; + /** + * Smart select opptions; + */ + smartSelect?: ISmartSelectOptions; /** * */ @@ -2955,6 +2978,11 @@ declare namespace monaco.editor { * Defaults to advanced. */ autoIndent?: 'none' | 'keep' | 'brackets' | 'advanced' | 'full'; + /** + * Emulate selection behaviour of tab characters when using spaces for indentation. + * This means selection will stick to tab stops. + */ + stickyTabStops?: boolean; /** * Enable format on type. * Defaults to false. @@ -3030,6 +3058,14 @@ declare namespace monaco.editor { * Defaults to true. */ codeLens?: boolean; + /** + * Code lens font family. Defaults to editor font family. + */ + codeLensFontFamily?: string; + /** + * Code lens font size. Default to 90% of the editor font size + */ + codeLensFontSize?: number; /** * Control the behavior and rendering of the code action lightbulb. */ @@ -3177,15 +3213,19 @@ declare namespace monaco.editor { */ originalEditable?: boolean; /** - * Original editor should be have code lens enabled? + * Should the diff editor enable code lens? * Defaults to false. */ - originalCodeLens?: boolean; + diffCodeLens?: boolean; /** - * Modified editor should be have code lens enabled? - * Defaults to false. + * Is the diff editor inside another editor + * Defaults to false */ - modifiedCodeLens?: boolean; + isInEmbeddedEditor?: boolean; + /** + * Control the wrapping of the diff editor. + */ + diffWordWrap?: 'off' | 'on' | 'inherit'; } /** @@ -3656,6 +3696,11 @@ declare namespace monaco.editor { * Defaults to `horizontalScrollbarSize`. */ horizontalSliderSize?: number; + /** + * Scroll gutter clicks move by page vs jump to position. + * Defaults to false. + */ + scrollByPage?: boolean; } export interface InternalEditorScrollbarOptions { @@ -3671,6 +3716,7 @@ declare namespace monaco.editor { readonly horizontalSliderSize: number; readonly verticalScrollbarSize: number; readonly verticalSliderSize: number; + readonly scrollByPage: boolean; } /** @@ -3705,6 +3751,10 @@ declare namespace monaco.editor { * Enable or disable the suggest status bar. */ showStatusBar?: boolean; + /** + * Show details inline with the label. Defaults to true. + */ + showInlineDetails?: boolean; /** * Show method-suggestions. */ @@ -3817,6 +3867,12 @@ declare namespace monaco.editor { export type InternalSuggestOptions = Readonly>; + export interface ISmartSelectOptions { + selectLeadingAndTrailingWhitespace?: boolean; + } + + export type SmartSelectOptions = Readonly>; + /** * Describes how to indent wrapped lines. */ @@ -3859,113 +3915,119 @@ declare namespace monaco.editor { automaticLayout = 9, autoSurround = 10, codeLens = 11, - colorDecorators = 12, - columnSelection = 13, - comments = 14, - contextmenu = 15, - copyWithSyntaxHighlighting = 16, - cursorBlinking = 17, - cursorSmoothCaretAnimation = 18, - cursorStyle = 19, - cursorSurroundingLines = 20, - cursorSurroundingLinesStyle = 21, - cursorWidth = 22, - disableLayerHinting = 23, - disableMonospaceOptimizations = 24, - dragAndDrop = 25, - emptySelectionClipboard = 26, - extraEditorClassName = 27, - fastScrollSensitivity = 28, - find = 29, - fixedOverflowWidgets = 30, - folding = 31, - foldingStrategy = 32, - foldingHighlight = 33, - unfoldOnClickAfterEndOfLine = 34, - fontFamily = 35, - fontInfo = 36, - fontLigatures = 37, - fontSize = 38, - fontWeight = 39, - formatOnPaste = 40, - formatOnType = 41, - glyphMargin = 42, - gotoLocation = 43, - hideCursorInOverviewRuler = 44, - highlightActiveIndentGuide = 45, - hover = 46, - inDiffEditor = 47, - letterSpacing = 48, - lightbulb = 49, - lineDecorationsWidth = 50, - lineHeight = 51, - lineNumbers = 52, - lineNumbersMinChars = 53, - links = 54, - matchBrackets = 55, - minimap = 56, - mouseStyle = 57, - mouseWheelScrollSensitivity = 58, - mouseWheelZoom = 59, - multiCursorMergeOverlapping = 60, - multiCursorModifier = 61, - multiCursorPaste = 62, - occurrencesHighlight = 63, - overviewRulerBorder = 64, - overviewRulerLanes = 65, - padding = 66, - parameterHints = 67, - peekWidgetDefaultFocus = 68, - definitionLinkOpensInPeek = 69, - quickSuggestions = 70, - quickSuggestionsDelay = 71, - readOnly = 72, - renameOnType = 73, - renderControlCharacters = 74, - renderIndentGuides = 75, - renderFinalNewline = 76, - renderLineHighlight = 77, - renderLineHighlightOnlyWhenFocus = 78, - renderValidationDecorations = 79, - renderWhitespace = 80, - revealHorizontalRightPadding = 81, - roundedSelection = 82, - rulers = 83, - scrollbar = 84, - scrollBeyondLastColumn = 85, - scrollBeyondLastLine = 86, - scrollPredominantAxis = 87, - selectionClipboard = 88, - selectionHighlight = 89, - selectOnLineNumbers = 90, - showFoldingControls = 91, - showUnused = 92, - snippetSuggestions = 93, - smoothScrolling = 94, - stopRenderingLineAfter = 95, - suggest = 96, - suggestFontSize = 97, - suggestLineHeight = 98, - suggestOnTriggerCharacters = 99, - suggestSelection = 100, - tabCompletion = 101, - tabIndex = 102, - unusualLineTerminators = 103, - useTabStops = 104, - wordSeparators = 105, - wordWrap = 106, - wordWrapBreakAfterCharacters = 107, - wordWrapBreakBeforeCharacters = 108, - wordWrapColumn = 109, - wordWrapMinified = 110, - wrappingIndent = 111, - wrappingStrategy = 112, - showDeprecated = 113, - editorClassName = 114, - pixelRatio = 115, - tabFocusMode = 116, - layoutInfo = 117, - wrappingInfo = 118 + codeLensFontFamily = 12, + codeLensFontSize = 13, + colorDecorators = 14, + columnSelection = 15, + comments = 16, + contextmenu = 17, + copyWithSyntaxHighlighting = 18, + cursorBlinking = 19, + cursorSmoothCaretAnimation = 20, + cursorStyle = 21, + cursorSurroundingLines = 22, + cursorSurroundingLinesStyle = 23, + cursorWidth = 24, + disableLayerHinting = 25, + disableMonospaceOptimizations = 26, + dragAndDrop = 27, + emptySelectionClipboard = 28, + extraEditorClassName = 29, + fastScrollSensitivity = 30, + find = 31, + fixedOverflowWidgets = 32, + folding = 33, + foldingStrategy = 34, + foldingHighlight = 35, + unfoldOnClickAfterEndOfLine = 36, + fontFamily = 37, + fontInfo = 38, + fontLigatures = 39, + fontSize = 40, + fontWeight = 41, + formatOnPaste = 42, + formatOnType = 43, + glyphMargin = 44, + gotoLocation = 45, + hideCursorInOverviewRuler = 46, + highlightActiveIndentGuide = 47, + hover = 48, + inDiffEditor = 49, + letterSpacing = 50, + lightbulb = 51, + lineDecorationsWidth = 52, + lineHeight = 53, + lineNumbers = 54, + lineNumbersMinChars = 55, + linkedEditing = 56, + links = 57, + matchBrackets = 58, + minimap = 59, + mouseStyle = 60, + mouseWheelScrollSensitivity = 61, + mouseWheelZoom = 62, + multiCursorMergeOverlapping = 63, + multiCursorModifier = 64, + multiCursorPaste = 65, + occurrencesHighlight = 66, + overviewRulerBorder = 67, + overviewRulerLanes = 68, + padding = 69, + parameterHints = 70, + peekWidgetDefaultFocus = 71, + definitionLinkOpensInPeek = 72, + quickSuggestions = 73, + quickSuggestionsDelay = 74, + readOnly = 75, + renameOnType = 76, + renderControlCharacters = 77, + renderIndentGuides = 78, + renderFinalNewline = 79, + renderLineHighlight = 80, + renderLineHighlightOnlyWhenFocus = 81, + renderValidationDecorations = 82, + renderWhitespace = 83, + revealHorizontalRightPadding = 84, + roundedSelection = 85, + rulers = 86, + scrollbar = 87, + scrollBeyondLastColumn = 88, + scrollBeyondLastLine = 89, + scrollPredominantAxis = 90, + selectionClipboard = 91, + selectionHighlight = 92, + selectOnLineNumbers = 93, + showFoldingControls = 94, + showUnused = 95, + snippetSuggestions = 96, + smartSelect = 97, + smoothScrolling = 98, + stickyTabStops = 99, + stopRenderingLineAfter = 100, + suggest = 101, + suggestFontSize = 102, + suggestLineHeight = 103, + suggestOnTriggerCharacters = 104, + suggestSelection = 105, + tabCompletion = 106, + tabIndex = 107, + unusualLineTerminators = 108, + useTabStops = 109, + wordSeparators = 110, + wordWrap = 111, + wordWrapBreakAfterCharacters = 112, + wordWrapBreakBeforeCharacters = 113, + wordWrapColumn = 114, + wordWrapOverride1 = 115, + wordWrapOverride2 = 116, + wrappingIndent = 117, + wrappingStrategy = 118, + showDeprecated = 119, + editorClassName = 120, + pixelRatio = 121, + tabFocusMode = 122, + layoutInfo = 123, + wrappingInfo = 124 } export const EditorOptions: { acceptSuggestionOnCommitCharacter: IEditorOption; @@ -3979,7 +4041,10 @@ declare namespace monaco.editor { autoIndent: IEditorOption; automaticLayout: IEditorOption; autoSurround: IEditorOption; + stickyTabStops: IEditorOption; codeLens: IEditorOption; + codeLensFontFamily: IEditorOption; + codeLensFontSize: IEditorOption; colorDecorators: IEditorOption; columnSelection: IEditorOption; comments: IEditorOption; @@ -4022,6 +4087,7 @@ declare namespace monaco.editor { lineHeight: IEditorOption; lineNumbers: IEditorOption; lineNumbersMinChars: IEditorOption; + linkedEditing: IEditorOption; links: IEditorOption; matchBrackets: IEditorOption; minimap: IEditorOption; @@ -4063,6 +4129,7 @@ declare namespace monaco.editor { showUnused: IEditorOption; showDeprecated: IEditorOption; snippetSuggestions: IEditorOption; + smartSelect: IEditorOption; smoothScrolling: IEditorOption; stopRenderingLineAfter: IEditorOption; suggest: IEditorOption; @@ -4079,7 +4146,8 @@ declare namespace monaco.editor { wordWrapBreakAfterCharacters: IEditorOption; wordWrapBreakBeforeCharacters: IEditorOption; wordWrapColumn: IEditorOption; - wordWrapMinified: IEditorOption; + wordWrapOverride1: IEditorOption; + wordWrapOverride2: IEditorOption; wrappingIndent: IEditorOption; wrappingStrategy: IEditorOption; editorClassName: IEditorOption; @@ -4676,9 +4744,13 @@ declare namespace monaco.editor { */ executeCommand(source: string | null | undefined, command: ICommand): void; /** - * Push an "undo stop" in the undo-redo stack. + * Create an "undo stop" in the undo-redo stack. */ pushUndoStop(): boolean; + /** + * Remove the "undo stop" in the undo-redo stack. + */ + popUndoStop(): boolean; /** * Execute edits on the editor. * The edits will land on the undo-redo stack, but no "undo stop" will be pushed. @@ -4997,6 +5069,10 @@ declare namespace monaco.languages { * Tokenize a line given the state at the beginning of the line. */ tokenizeEncoded(line: string, state: IState): IEncodedLineTokens; + /** + * Tokenize a line given the state at the beginning of the line. + */ + tokenize?(line: string, state: IState): ILineTokens; } /** @@ -5040,9 +5116,9 @@ declare namespace monaco.languages { export function registerDocumentHighlightProvider(languageId: string, provider: DocumentHighlightProvider): IDisposable; /** - * Register an on type rename provider. + * Register an linked editing range provider. */ - export function registerOnTypeRenameProvider(languageId: string, provider: OnTypeRenameProvider): IDisposable; + export function registerLinkedEditingRangeProvider(languageId: string, provider: LinkedEditingRangeProvider): IDisposable; /** * Register a definition provider (used by e.g. go to definition). @@ -5786,18 +5862,30 @@ declare namespace monaco.languages { } /** - * The rename provider interface defines the contract between extensions and - * the live-rename feature. + * The linked editing range provider interface defines the contract between extensions and + * the linked editing feature. */ - export interface OnTypeRenameProvider { - wordPattern?: RegExp; + export interface LinkedEditingRangeProvider { /** - * Provide a list of ranges that can be live-renamed together. + * Provide a list of ranges that can be edited together. */ - provideOnTypeRenameRanges(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult<{ - ranges: IRange[]; - wordPattern?: RegExp; - }>; + provideLinkedEditingRanges(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult; + } + + /** + * Represents a list of ranges that can be edited together along with a word pattern to describe valid contents. + */ + export interface LinkedEditingRanges { + /** + * A list of ranges that can be edited together. The ranges must have + * identical length and text content. The ranges cannot overlap + */ + ranges: IRange[]; + /** + * An optional word pattern that describes valid contents for the given ranges. + * If no pattern is provided, the language configuration's word pattern will be used. + */ + wordPattern?: RegExp; } /** @@ -6195,12 +6283,6 @@ declare namespace monaco.languages { needsConfirmation: boolean; label: string; description?: string; - iconPath?: { - id: string; - } | Uri | { - light: Uri; - dark: Uri; - }; } export interface WorkspaceFileEditOptions { @@ -6208,6 +6290,10 @@ declare namespace monaco.languages { ignoreIfNotExists?: boolean; ignoreIfExists?: boolean; recursive?: boolean; + copy?: boolean; + folder?: boolean; + skipTrashBin?: boolean; + maxSize?: number; } export interface WorkspaceFileEdit { diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 9da3b3d19..1344ea900 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -16,10 +16,12 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; +import { isWindows, isLinux } from 'vs/base/common/platform'; export function createAndFillInContextMenuActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, isPrimaryGroup?: (group: string) => boolean): IDisposable { const groups = menu.getActions(options); - const useAlternativeActions = ModifierKeyEmitter.getInstance().keyStatus.altKey; + const modifierKeyEmitter = ModifierKeyEmitter.getInstance(); + const useAlternativeActions = modifierKeyEmitter.keyStatus.altKey || ((isWindows || isLinux) && modifierKeyEmitter.keyStatus.shiftKey); fillInActions(groups, target, useAlternativeActions, isPrimaryGroup); return asDisposable(groups); } @@ -102,7 +104,7 @@ export class MenuEntryActionViewItem extends ActionViewItem { let mouseOver = false; - let alternativeKeyDown = this._altKey.keyStatus.altKey; + let alternativeKeyDown = this._altKey.keyStatus.altKey || ((isWindows || isLinux) && this._altKey.keyStatus.shiftKey); const updateAltState = () => { const wantsAltCommand = mouseOver && alternativeKeyDown; @@ -116,7 +118,7 @@ export class MenuEntryActionViewItem extends ActionViewItem { if (this._action.alt) { this._register(this._altKey.event(value => { - alternativeKeyDown = value.altKey; + alternativeKeyDown = value.altKey || ((isWindows || isLinux) && value.shiftKey); updateAltState(); })); } diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index aa382fdd5..c06a3511b 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -95,7 +95,7 @@ export class MenuId { static readonly MenubarSwitchGroupMenu = new MenuId('MenubarSwitchGroupMenu'); static readonly MenubarTerminalMenu = new MenuId('MenubarTerminalMenu'); static readonly MenubarViewMenu = new MenuId('MenubarViewMenu'); - static readonly MenubarWebNavigationMenu = new MenuId('MenubarWebNavigationMenu'); + static readonly MenubarHomeMenu = new MenuId('MenubarHomeMenu'); static readonly OpenEditorsContext = new MenuId('OpenEditorsContext'); static readonly ProblemsPanelContext = new MenuId('ProblemsPanelContext'); static readonly SCMChangeContext = new MenuId('SCMChangeContext'); diff --git a/src/vs/platform/backup/electron-main/backupMainService.ts b/src/vs/platform/backup/electron-main/backupMainService.ts index a7e9a9ee3..42f5fb335 100644 --- a/src/vs/platform/backup/electron-main/backupMainService.ts +++ b/src/vs/platform/backup/electron-main/backupMainService.ts @@ -57,9 +57,6 @@ export class BackupMainService implements IBackupMainService { // read empty workspaces backups first if (backups.emptyWorkspaceInfos) { this.emptyWindows = await this.validateEmptyWorkspaces(backups.emptyWorkspaceInfos); - } else if (Array.isArray(backups.emptyWorkspaces)) { - // read legacy entries - this.emptyWindows = await this.validateEmptyWorkspaces(backups.emptyWorkspaces.map(emptyWindow => ({ backupFolder: emptyWindow }))); } // read workspace backups @@ -67,8 +64,6 @@ export class BackupMainService implements IBackupMainService { try { if (Array.isArray(backups.rootURIWorkspaces)) { rootWorkspaces = backups.rootURIWorkspaces.map(workspace => ({ workspace: { id: workspace.id, configPath: URI.parse(workspace.configURIPath) }, remoteAuthority: workspace.remoteAuthority })); - } else if (Array.isArray(backups.rootWorkspaces)) { - rootWorkspaces = backups.rootWorkspaces.map(workspace => ({ workspace: { id: workspace.id, configPath: URI.file(workspace.configPath) } })); } } catch (e) { // ignore URI parsing exceptions @@ -81,18 +76,6 @@ export class BackupMainService implements IBackupMainService { try { if (Array.isArray(backups.folderURIWorkspaces)) { workspaceFolders = backups.folderURIWorkspaces.map(folder => URI.parse(folder)); - } else if (Array.isArray(backups.folderWorkspaces)) { - // migrate legacy folder paths - workspaceFolders = []; - for (const folderPath of backups.folderWorkspaces) { - const oldFolderHash = this.getLegacyFolderHash(folderPath); - const folderUri = URI.file(folderPath); - const newFolderHash = this.getFolderHash(folderUri); - if (newFolderHash !== oldFolderHash) { - await this.moveBackupFolder(this.getBackupPath(newFolderHash), this.getBackupPath(oldFolderHash)); - } - workspaceFolders.push(folderUri); - } } } catch (e) { // ignore URI parsing exceptions @@ -174,23 +157,6 @@ export class BackupMainService implements IBackupMainService { } } - private async moveBackupFolder(backupPath: string, moveFromPath: string): Promise { - - // Target exists: make sure to convert existing backups to empty window backups - if (await exists(backupPath)) { - await this.convertToEmptyWindowBackup(backupPath); - } - - // When we have data to migrate from, move it over to the target location - if (await exists(moveFromPath)) { - try { - await rename(moveFromPath, backupPath); - } catch (ex) { - this.logService.error(`Backup: Could not move backup folder to new location: ${ex.toString()}`); - } - } - } - unregisterWorkspaceBackupSync(workspace: IWorkspaceIdentifier): void { const id = workspace.id; const index = this.workspaces.findIndex(workspace => workspace.workspace.id === id); @@ -475,8 +441,7 @@ export class BackupMainService implements IBackupMainService { return { rootURIWorkspaces: this.workspaces.map(workspace => ({ id: workspace.workspace.id, configURIPath: workspace.workspace.configPath.toString(), remoteAuthority: workspace.remoteAuthority })), folderURIWorkspaces: this.folders.map(folder => folder.toString()), - emptyWorkspaceInfos: this.emptyWindows, - emptyWorkspaces: this.emptyWindows.map(emptyWindow => emptyWindow.backupFolder) + emptyWorkspaceInfos: this.emptyWindows }; } @@ -496,8 +461,4 @@ export class BackupMainService implements IBackupMainService { return crypto.createHash('md5').update(key).digest('hex'); } - - protected getLegacyFolderHash(folderPath: string): string { - return crypto.createHash('md5').update(platform.isLinux ? folderPath : folderPath.toLowerCase()).digest('hex'); - } } diff --git a/src/vs/platform/backup/node/backup.ts b/src/vs/platform/backup/node/backup.ts index d40d01b18..b054be3ee 100644 --- a/src/vs/platform/backup/node/backup.ts +++ b/src/vs/platform/backup/node/backup.ts @@ -9,11 +9,6 @@ export interface IBackupWorkspacesFormat { rootURIWorkspaces: ISerializedWorkspace[]; folderURIWorkspaces: string[]; emptyWorkspaceInfos: IEmptyWindowBackupInfo[]; - - // deprecated - folderWorkspaces?: string[]; // use folderURIWorkspaces instead - emptyWorkspaces?: string[]; - rootWorkspaces?: { id: string, configPath: string }[]; // use rootURIWorkspaces instead } export interface IEmptyWindowBackupInfo { diff --git a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts index 4db502cca..42e2636d8 100644 --- a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts +++ b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts @@ -53,10 +53,6 @@ suite('BackupMainService', () => { getFolderHash(folderUri: URI): string { return super.getFolderHash(folderUri); } - - toLegacyBackupPath(folderPath: string): string { - return path.join(this.backupHome, super.getLegacyFolderHash(folderPath)); - } } function toWorkspace(path: string): IWorkspaceIdentifier { @@ -266,68 +262,6 @@ suite('BackupMainService', () => { assert.equal(1, fs.readdirSync(path.join(backupHome, emptyBackups[0].backupFolder!)).length); }); - suite('migrate path to URI', () => { - - test('migration folder path to URI makes sure to preserve existing backups', async () => { - let path1 = path.join(parentDir, 'folder1'); - let path2 = path.join(parentDir, 'FOLDER2'); - let uri1 = URI.file(path1); - let uri2 = URI.file(path2); - - if (!fs.existsSync(path1)) { - fs.mkdirSync(path1); - } - if (!fs.existsSync(path2)) { - fs.mkdirSync(path2); - } - const backupFolder1 = service.toLegacyBackupPath(path1); - if (!fs.existsSync(backupFolder1)) { - fs.mkdirSync(backupFolder1); - fs.mkdirSync(path.join(backupFolder1, Schemas.file)); - await pfs.writeFile(path.join(backupFolder1, Schemas.file, 'unsaved1.txt'), 'Legacy'); - } - const backupFolder2 = service.toLegacyBackupPath(path2); - if (!fs.existsSync(backupFolder2)) { - fs.mkdirSync(backupFolder2); - fs.mkdirSync(path.join(backupFolder2, Schemas.file)); - await pfs.writeFile(path.join(backupFolder2, Schemas.file, 'unsaved2.txt'), 'Legacy'); - } - - const workspacesJson = { rootWorkspaces: [], folderWorkspaces: [path1, path2], emptyWorkspaces: [] }; - await pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)); - await service.initialize(); - const content = await pfs.readFile(backupWorkspacesPath, 'utf-8'); - const json = (JSON.parse(content)); - assert.deepEqual(json.folderURIWorkspaces, [uri1.toString(), uri2.toString()]); - const newBackupFolder1 = service.toBackupPath(uri1); - assert.ok(fs.existsSync(path.join(newBackupFolder1, Schemas.file, 'unsaved1.txt'))); - const newBackupFolder2 = service.toBackupPath(uri2); - assert.ok(fs.existsSync(path.join(newBackupFolder2, Schemas.file, 'unsaved2.txt'))); - }); - - test('migrate storage file', async () => { - let folderPath = path.join(parentDir, 'f1'); - ensureFolderExists(URI.file(folderPath)); - const backupFolderPath = service.toLegacyBackupPath(folderPath); - await createBackupFolder(backupFolderPath); - - let workspacePath = path.join(parentDir, 'f2.code-workspace'); - const workspace = toWorkspace(workspacePath); - await ensureWorkspaceExists(workspace); - - const workspacesJson = { rootWorkspaces: [{ id: workspace.id, configPath: workspacePath }], folderWorkspaces: [folderPath], emptyWorkspaces: [] }; - await pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)); - await service.initialize(); - const content = await pfs.readFile(backupWorkspacesPath, 'utf-8'); - const json = (JSON.parse(content)); - assert.deepEqual(json.folderURIWorkspaces, [URI.file(folderPath).toString()]); - assert.deepEqual(json.rootURIWorkspaces, [{ id: workspace.id, configURIPath: URI.file(workspacePath).toString() }]); - - assertEqualUris(service.getWorkspaceBackups().map(window => window.workspace.configPath), [workspace.configPath]); - }); - }); - - suite('loadSync', () => { test('getFolderBackupPaths() should return [] when workspaces.json doesn\'t exist', () => { assertEqualUris(service.getFolderBackupPaths(), []); @@ -650,12 +584,12 @@ suite('BackupMainService', () => { const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); const json = (JSON.parse(buffer)); - assert.deepEqual(json.emptyWorkspaces, ['bar']); + assert.deepEqual(json.emptyWorkspaceInfos, [{ backupFolder: 'bar' }]); service.unregisterEmptyWindowBackupSync('bar'); const content = await pfs.readFile(backupWorkspacesPath, 'utf-8'); const json2 = (JSON.parse(content)); - assert.deepEqual(json2.emptyWorkspaces, []); + assert.deepEqual(json2.emptyWorkspaceInfos, []); }); test('should fail gracefully when removing a path that doesn\'t exist', async () => { diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 7f14056f1..d5b333f73 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -114,7 +114,7 @@ export interface IConfigurationService { inspect(key: string, overrides?: IConfigurationOverrides): IConfigurationValue; - reloadConfiguration(folder?: IWorkspaceFolder): Promise; + reloadConfiguration(target?: ConfigurationTarget | IWorkspaceFolder): Promise; keys(): { default: string[]; diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 3a7eaf968..07680c0f2 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -478,6 +478,9 @@ const configurationRegistry = new ConfigurationRegistry(); Registry.add(Extensions.Configuration, configurationRegistry); export function validateProperty(property: string): string | null { + if (!property.trim()) { + return nls.localize('config.property.empty', "Cannot register an empty property"); + } if (OVERRIDE_PROPERTY_PATTERN.test(property)) { return nls.localize('config.property.languageDefault', "Cannot register '{0}'. This matches property pattern '\\\\[.*\\\\]$' for describing language specific editor settings. Use 'configurationDefaults' contribution.", property); } diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index 1ff040dce..c743e36b8 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -7,9 +7,10 @@ import { ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { URI } from 'vs/base/common/uri'; -import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { join } from 'vs/base/common/path'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; suite('ConfigurationModel', () => { diff --git a/src/vs/platform/contextkey/browser/contextKeyService.ts b/src/vs/platform/contextkey/browser/contextKeyService.ts index b4d8e42cf..21320c0d6 100644 --- a/src/vs/platform/contextkey/browser/contextKeyService.ts +++ b/src/vs/platform/contextkey/browser/contextKeyService.ts @@ -4,7 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event, PauseableEmitter } from 'vs/base/common/event'; +import { Iterable } from 'vs/base/common/iterator'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { TernarySearchTree } from 'vs/base/common/map'; import { distinct } from 'vs/base/common/objects'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -90,10 +92,9 @@ class NullContext extends Context { } class ConfigAwareContextValuesContainer extends Context { - private static readonly _keyPrefix = 'config.'; - private readonly _values = new Map(); + private readonly _values = TernarySearchTree.forConfigKeys(); private readonly _listener: IDisposable; constructor( @@ -106,18 +107,26 @@ class ConfigAwareContextValuesContainer extends Context { this._listener = this._configurationService.onDidChangeConfiguration(event => { if (event.source === ConfigurationTarget.DEFAULT) { // new setting, reset everything - const allKeys = Array.from(this._values.keys()); + const allKeys = Array.from(Iterable.map(this._values, ([k]) => k)); this._values.clear(); emitter.fire(new ArrayContextKeyChangeEvent(allKeys)); } else { const changedKeys: string[] = []; for (const configKey of event.affectedKeys) { const contextKey = `config.${configKey}`; + + const cachedItems = this._values.findSuperstr(contextKey); + if (cachedItems !== undefined) { + changedKeys.push(...Iterable.map(cachedItems, ([key]) => key)); + this._values.deleteSuperstr(contextKey); + } + if (this._values.has(contextKey)) { - this._values.delete(contextKey); changedKeys.push(contextKey); + this._values.delete(contextKey); } } + emitter.fire(new ArrayContextKeyChangeEvent(changedKeys)); } }); @@ -149,6 +158,8 @@ class ConfigAwareContextValuesContainer extends Context { default: if (Array.isArray(configValue)) { value = JSON.stringify(configValue); + } else { + value = configValue; } } @@ -405,6 +416,9 @@ class ScopedContextKeyService extends AbstractContextKeyService { if (domNode) { this._domNode = domNode; + if (this._domNode.hasAttribute(KEYBINDING_CONTEXT_ATTR)) { + console.error('Element already has context attribute'); + } this._domNode.setAttribute(KEYBINDING_CONTEXT_ATTR, String(this._myContextId)); } } diff --git a/src/vs/platform/contextkey/common/contextkey.ts b/src/vs/platform/contextkey/common/contextkey.ts index dad27b174..ea7c83876 100644 --- a/src/vs/platform/contextkey/common/contextkey.ts +++ b/src/vs/platform/contextkey/common/contextkey.ts @@ -6,8 +6,9 @@ import { Event } from 'vs/base/common/event'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { isMacintosh, isLinux, isWindows, isWeb } from 'vs/base/common/platform'; +import { userAgent, isMacintosh, isLinux, isWindows, isWeb } from 'vs/base/common/platform'; +let _userAgent = userAgent || ''; const STATIC_VALUES = new Map(); STATIC_VALUES.set('false', false); STATIC_VALUES.set('true', true); @@ -16,6 +17,11 @@ STATIC_VALUES.set('isLinux', isLinux); STATIC_VALUES.set('isWindows', isWindows); STATIC_VALUES.set('isWeb', isWeb); STATIC_VALUES.set('isMacNative', isMacintosh && !isWeb); +STATIC_VALUES.set('isEdge', _userAgent.indexOf('Edg/') >= 0); +STATIC_VALUES.set('isFirefox', _userAgent.indexOf('Firefox') >= 0); +STATIC_VALUES.set('isChrome', _userAgent.indexOf('Chrome') >= 0); +STATIC_VALUES.set('isSafari', _userAgent.indexOf('Safari') >= 0); +STATIC_VALUES.set('isIPad', _userAgent.indexOf('iPad') >= 0); const hasOwnProperty = Object.prototype.hasOwnProperty; @@ -32,6 +38,10 @@ export const enum ContextKeyExprType { Or = 9, In = 10, NotIn = 11, + Greater = 12, + GreaterEquals = 13, + Smaller = 14, + SmallerEquals = 15, } export interface IContextKeyExprMapper { @@ -39,6 +49,10 @@ export interface IContextKeyExprMapper { mapNot(key: string): ContextKeyExpression; mapEquals(key: string, value: any): ContextKeyExpression; mapNotEquals(key: string, value: any): ContextKeyExpression; + mapGreater(key: string, value: any): ContextKeyExpression; + mapGreaterEquals(key: string, value: any): ContextKeyExpression; + mapSmaller(key: string, value: any): ContextKeyExpression; + mapSmallerEquals(key: string, value: any): ContextKeyExpression; mapRegex(key: string, regexp: RegExp | null): ContextKeyRegexExpr; mapIn(key: string, valueKey: string): ContextKeyInExpr; } @@ -57,7 +71,9 @@ export interface IContextKeyExpression { export type ContextKeyExpression = ( ContextKeyFalseExpr | ContextKeyTrueExpr | ContextKeyDefinedExpr | ContextKeyNotExpr | ContextKeyEqualsExpr | ContextKeyNotEqualsExpr | ContextKeyRegexExpr - | ContextKeyNotRegexExpr | ContextKeyAndExpr | ContextKeyOrExpr | ContextKeyInExpr | ContextKeyNotInExpr + | ContextKeyNotRegexExpr | ContextKeyAndExpr | ContextKeyOrExpr | ContextKeyInExpr + | ContextKeyNotInExpr | ContextKeyGreaterExpr | ContextKeyGreaterEqualsExpr + | ContextKeySmallerExpr | ContextKeySmallerEqualsExpr ); export abstract class ContextKeyExpr { @@ -102,6 +118,14 @@ export abstract class ContextKeyExpr { return ContextKeyOrExpr.create(expr); } + public static greater(key: string, value: any): ContextKeyExpression { + return ContextKeyGreaterExpr.create(key, value); + } + + public static less(key: string, value: any): ContextKeyExpression { + return ContextKeySmallerExpr.create(key, value); + } + public static deserialize(serialized: string | null | undefined, strict: boolean = false): ContextKeyExpression | undefined { if (!serialized) { return undefined; @@ -143,6 +167,26 @@ export abstract class ContextKeyExpr { return ContextKeyInExpr.create(pieces[0].trim(), pieces[1].trim()); } + if (/^[^<=>]+>=[^<=>]+$/.test(serializedOne)) { + const pieces = serializedOne.split('>='); + return ContextKeyGreaterEqualsExpr.create(pieces[0].trim(), pieces[1].trim()); + } + + if (/^[^<=>]+>[^<=>]+$/.test(serializedOne)) { + const pieces = serializedOne.split('>'); + return ContextKeyGreaterExpr.create(pieces[0].trim(), pieces[1].trim()); + } + + if (/^[^<=>]+<=[^<=>]+$/.test(serializedOne)) { + const pieces = serializedOne.split('<='); + return ContextKeySmallerEqualsExpr.create(pieces[0].trim(), pieces[1].trim()); + } + + if (/^[^<=>]+<[^<=>]+$/.test(serializedOne)) { + const pieces = serializedOne.split('<'); + return ContextKeySmallerExpr.create(pieces[0].trim(), pieces[1].trim()); + } + if (/^\!\s*/.test(serializedOne)) { return ContextKeyNotExpr.create(serializedOne.substr(1).trim()); } @@ -302,13 +346,7 @@ export class ContextKeyDefinedExpr implements IContextKeyExpression { if (other.type !== this.type) { return this.type - other.type; } - if (this.key < other.key) { - return -1; - } - if (this.key > other.key) { - return 1; - } - return 0; + return cmp1(this.key, other.key); } public equals(other: ContextKeyExpression): boolean { @@ -362,19 +400,7 @@ export class ContextKeyEqualsExpr implements IContextKeyExpression { if (other.type !== this.type) { return this.type - other.type; } - if (this.key < other.key) { - return -1; - } - if (this.key > other.key) { - return 1; - } - if (this.value < other.value) { - return -1; - } - if (this.value > other.value) { - return 1; - } - return 0; + return cmp2(this.key, this.value, other.key, other.value); } public equals(other: ContextKeyExpression): boolean { @@ -391,7 +417,7 @@ export class ContextKeyEqualsExpr implements IContextKeyExpression { } public serialize(): string { - return this.key + ' == \'' + this.value + '\''; + return `${this.key} == '${this.value}'`; } public keys(): string[] { @@ -422,19 +448,7 @@ export class ContextKeyInExpr implements IContextKeyExpression { if (other.type !== this.type) { return this.type - other.type; } - if (this.key < other.key) { - return -1; - } - if (this.key > other.key) { - return 1; - } - if (this.valueKey < other.valueKey) { - return -1; - } - if (this.valueKey > other.valueKey) { - return 1; - } - return 0; + return cmp2(this.key, this.valueKey, other.key, other.valueKey); } public equals(other: ContextKeyExpression): boolean { @@ -460,7 +474,7 @@ export class ContextKeyInExpr implements IContextKeyExpression { } public serialize(): string { - return this.key + ' in \'' + this.valueKey + '\''; + return `${this.key} in '${this.valueKey}'`; } public keys(): string[] { @@ -549,19 +563,7 @@ export class ContextKeyNotEqualsExpr implements IContextKeyExpression { if (other.type !== this.type) { return this.type - other.type; } - if (this.key < other.key) { - return -1; - } - if (this.key > other.key) { - return 1; - } - if (this.value < other.value) { - return -1; - } - if (this.value > other.value) { - return 1; - } - return 0; + return cmp2(this.key, this.value, other.key, other.value); } public equals(other: ContextKeyExpression): boolean { @@ -578,7 +580,7 @@ export class ContextKeyNotEqualsExpr implements IContextKeyExpression { } public serialize(): string { - return this.key + ' != \'' + this.value + '\''; + return `${this.key} != '${this.value}'`; } public keys(): string[] { @@ -613,13 +615,7 @@ export class ContextKeyNotExpr implements IContextKeyExpression { if (other.type !== this.type) { return this.type - other.type; } - if (this.key < other.key) { - return -1; - } - if (this.key > other.key) { - return 1; - } - return 0; + return cmp1(this.key, other.key); } public equals(other: ContextKeyExpression): boolean { @@ -634,7 +630,7 @@ export class ContextKeyNotExpr implements IContextKeyExpression { } public serialize(): string { - return '!' + this.key; + return `!${this.key}`; } public keys(): string[] { @@ -650,6 +646,200 @@ export class ContextKeyNotExpr implements IContextKeyExpression { } } +export class ContextKeyGreaterExpr implements IContextKeyExpression { + + public static create(key: string, value: any): ContextKeyExpression { + return new ContextKeyGreaterExpr(key, value); + } + + public readonly type = ContextKeyExprType.Greater; + + private constructor( + private readonly key: string, + private readonly value: any + ) { } + + public cmp(other: ContextKeyExpression): number { + if (other.type !== this.type) { + return this.type - other.type; + } + return cmp2(this.key, this.value, other.key, other.value); + } + + public equals(other: ContextKeyExpression): boolean { + if (other.type === this.type) { + return (this.key === other.key && this.value === other.value); + } + return false; + } + + public evaluate(context: IContext): boolean { + return (parseFloat(context.getValue(this.key)) > parseFloat(this.value)); + } + + public serialize(): string { + return `${this.key} > ${this.value}`; + } + + public keys(): string[] { + return [this.key]; + } + + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { + return mapFnc.mapGreater(this.key, this.value); + } + + public negate(): ContextKeyExpression { + return ContextKeySmallerEqualsExpr.create(this.key, this.value); + } +} + +export class ContextKeyGreaterEqualsExpr implements IContextKeyExpression { + + public static create(key: string, value: any): ContextKeyExpression { + return new ContextKeyGreaterEqualsExpr(key, value); + } + + public readonly type = ContextKeyExprType.GreaterEquals; + + private constructor( + private readonly key: string, + private readonly value: any + ) { } + + public cmp(other: ContextKeyExpression): number { + if (other.type !== this.type) { + return this.type - other.type; + } + return cmp2(this.key, this.value, other.key, other.value); + } + + public equals(other: ContextKeyExpression): boolean { + if (other.type === this.type) { + return (this.key === other.key && this.value === other.value); + } + return false; + } + + public evaluate(context: IContext): boolean { + return (parseFloat(context.getValue(this.key)) >= parseFloat(this.value)); + } + + public serialize(): string { + return `${this.key} >= ${this.value}`; + } + + public keys(): string[] { + return [this.key]; + } + + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { + return mapFnc.mapGreaterEquals(this.key, this.value); + } + + public negate(): ContextKeyExpression { + return ContextKeySmallerExpr.create(this.key, this.value); + } +} + +export class ContextKeySmallerExpr implements IContextKeyExpression { + + public static create(key: string, value: any): ContextKeyExpression { + return new ContextKeySmallerExpr(key, value); + } + + public readonly type = ContextKeyExprType.Smaller; + + private constructor( + private readonly key: string, + private readonly value: any + ) { + } + + public cmp(other: ContextKeyExpression): number { + if (other.type !== this.type) { + return this.type - other.type; + } + return cmp2(this.key, this.value, other.key, other.value); + } + + public equals(other: ContextKeyExpression): boolean { + if (other.type === this.type) { + return (this.key === other.key && this.value === other.value); + } + return false; + } + + public evaluate(context: IContext): boolean { + return (parseFloat(context.getValue(this.key)) < parseFloat(this.value)); + } + + public serialize(): string { + return `${this.key} < ${this.value}`; + } + + public keys(): string[] { + return [this.key]; + } + + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { + return mapFnc.mapSmaller(this.key, this.value); + } + + public negate(): ContextKeyExpression { + return ContextKeyGreaterEqualsExpr.create(this.key, this.value); + } +} + +export class ContextKeySmallerEqualsExpr implements IContextKeyExpression { + + public static create(key: string, value: any): ContextKeyExpression { + return new ContextKeySmallerEqualsExpr(key, value); + } + + public readonly type = ContextKeyExprType.SmallerEquals; + + private constructor( + private readonly key: string, + private readonly value: any + ) { + } + + public cmp(other: ContextKeyExpression): number { + if (other.type !== this.type) { + return this.type - other.type; + } + return cmp2(this.key, this.value, other.key, other.value); + } + + public equals(other: ContextKeyExpression): boolean { + if (other.type === this.type) { + return (this.key === other.key && this.value === other.value); + } + return false; + } + + public evaluate(context: IContext): boolean { + return (parseFloat(context.getValue(this.key)) <= parseFloat(this.value)); + } + + public serialize(): string { + return `${this.key} <= ${this.value}`; + } + + public keys(): string[] { + return [this.key]; + } + + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { + return mapFnc.mapSmallerEquals(this.key, this.value); + } + + public negate(): ContextKeyExpression { + return ContextKeyGreaterExpr.create(this.key, this.value); + } +} + export class ContextKeyRegexExpr implements IContextKeyExpression { public static create(key: string, regexp: RegExp | null): ContextKeyRegexExpr { @@ -1143,3 +1333,29 @@ export interface IContextKeyService { } export const SET_CONTEXT_COMMAND_ID = 'setContext'; + +function cmp1(key1: string, key2: string): number { + if (key1 < key2) { + return -1; + } + if (key1 > key2) { + return 1; + } + return 0; +} + +function cmp2(key1: string, value1: any, key2: string, value2: any): number { + if (key1 < key2) { + return -1; + } + if (key1 > key2) { + return 1; + } + if (value1 < value2) { + return -1; + } + if (value1 > value2) { + return 1; + } + return 0; +} diff --git a/src/vs/platform/contextkey/test/common/contextkey.test.ts b/src/vs/platform/contextkey/test/common/contextkey.test.ts index 2912df4b0..91a548be6 100644 --- a/src/vs/platform/contextkey/test/common/contextkey.test.ts +++ b/src/vs/platform/contextkey/test/common/contextkey.test.ts @@ -186,4 +186,84 @@ suite('ContextKeyExpr', () => { ); assert.equal(actual!.equals(expected!), true); }); + + test('Greater, GreaterEquals, Smaller, SmallerEquals evaluate', () => { + function checkEvaluate(expr: string, ctx: any, expected: any): void { + const _expr = ContextKeyExpr.deserialize(expr)!; + assert.equal(_expr.evaluate(createContext(ctx)), expected); + } + + checkEvaluate('a>1', {}, false); + checkEvaluate('a>1', { a: 0 }, false); + checkEvaluate('a>1', { a: 1 }, false); + checkEvaluate('a>1', { a: 2 }, true); + checkEvaluate('a>1', { a: '0' }, false); + checkEvaluate('a>1', { a: '1' }, false); + checkEvaluate('a>1', { a: '2' }, true); + checkEvaluate('a>1', { a: 'a' }, false); + + checkEvaluate('a>10', { a: 2 }, false); + checkEvaluate('a>10', { a: 11 }, true); + checkEvaluate('a>10', { a: '11' }, true); + checkEvaluate('a>10', { a: '2' }, false); + checkEvaluate('a>10', { a: '11' }, true); + + checkEvaluate('a>1.1', { a: 1 }, false); + checkEvaluate('a>1.1', { a: 2 }, true); + checkEvaluate('a>1.1', { a: 11 }, true); + checkEvaluate('a>1.1', { a: '1.1' }, false); + checkEvaluate('a>1.1', { a: '2' }, true); + checkEvaluate('a>1.1', { a: '11' }, true); + + checkEvaluate('a>b', { a: 'b' }, false); + checkEvaluate('a>b', { a: 'c' }, false); + checkEvaluate('a>b', { a: 1000 }, false); + + checkEvaluate('a >= 2', { a: '1' }, false); + checkEvaluate('a >= 2', { a: '2' }, true); + checkEvaluate('a >= 2', { a: '3' }, true); + + checkEvaluate('a < 2', { a: '1' }, true); + checkEvaluate('a < 2', { a: '2' }, false); + checkEvaluate('a < 2', { a: '3' }, false); + + checkEvaluate('a <= 2', { a: '1' }, true); + checkEvaluate('a <= 2', { a: '2' }, true); + checkEvaluate('a <= 2', { a: '3' }, false); + }); + + test('Greater, GreaterEquals, Smaller, SmallerEquals negate', () => { + function checkNegate(expr: string, expected: string): void { + const a = ContextKeyExpr.deserialize(expr)!; + const b = a.negate(); + assert.equal(b.serialize(), expected); + } + + checkNegate('a>1', 'a <= 1'); + checkNegate('a>1.1', 'a <= 1.1'); + checkNegate('a>b', 'a <= b'); + + checkNegate('a>=1', 'a < 1'); + checkNegate('a>=1.1', 'a < 1.1'); + checkNegate('a>=b', 'a < b'); + + checkNegate('a<1', 'a >= 1'); + checkNegate('a<1.1', 'a >= 1.1'); + checkNegate('a= b'); + + checkNegate('a<=1', 'a > 1'); + checkNegate('a<=1.1', 'a > 1.1'); + checkNegate('a<=b', 'a > b'); + }); + + test('issue #111899: context keys can use `<` or `>` ', () => { + const actual = ContextKeyExpr.deserialize('editorTextFocus && vim.active && vim.use')!; + assert.ok(actual.equals( + ContextKeyExpr.and( + ContextKeyExpr.has('editorTextFocus'), + ContextKeyExpr.has('vim.active'), + ContextKeyExpr.has('vim.use'), + )! + )); + }); }); diff --git a/src/vs/platform/contextview/browser/contextMenuHandler.ts b/src/vs/platform/contextview/browser/contextMenuHandler.ts index d307c2927..1788290cc 100644 --- a/src/vs/platform/contextview/browser/contextMenuHandler.ts +++ b/src/vs/platform/contextview/browser/contextMenuHandler.ts @@ -55,6 +55,7 @@ export class ContextMenuHandler { getAnchor: () => delegate.getAnchor(), canRelayout: false, anchorAlignment: delegate.anchorAlignment, + anchorAxisAlignment: delegate.anchorAxisAlignment, render: (container) => { let className = delegate.getMenuClassName ? delegate.getMenuClassName() : ''; @@ -79,7 +80,7 @@ export class ContextMenuHandler { const menuDisposables = new DisposableStore(); const actionRunner = delegate.actionRunner || new ActionRunner(); - actionRunner.onDidBeforeRun(this.onActionRun, this, menuDisposables); + actionRunner.onBeforeRun(this.onActionRun, this, menuDisposables); actionRunner.onDidRun(this.onDidActionRun, this, menuDisposables); menu = new Menu(container, actions, { actionViewItemProvider: delegate.getActionViewItem, diff --git a/src/vs/platform/contextview/browser/contextView.ts b/src/vs/platform/contextview/browser/contextView.ts index dcba62aa3..b33d652cf 100644 --- a/src/vs/platform/contextview/browser/contextView.ts +++ b/src/vs/platform/contextview/browser/contextView.ts @@ -6,7 +6,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IContextMenuDelegate } from 'vs/base/browser/contextmenu'; -import { AnchorAlignment, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; +import { AnchorAlignment, AnchorAxisAlignment, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; export const IContextViewService = createDecorator('contextViewService'); @@ -31,6 +31,7 @@ export interface IContextViewDelegate { onHide?(data?: any): void; focus?(): void; anchorAlignment?: AnchorAlignment; + anchorAxisAlignment?: AnchorAxisAlignment; } export const IContextMenuService = createDecorator('contextMenuService'); diff --git a/src/vs/platform/diagnostics/node/diagnosticsService.ts b/src/vs/platform/diagnostics/node/diagnosticsService.ts index b3b4815fc..2fe576f1b 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsService.ts @@ -18,6 +18,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Iterable } from 'vs/base/common/iterator'; import { Schemas } from 'vs/base/common/network'; +import { ByteSize } from 'vs/platform/files/common/files'; export const ID = 'diagnosticsService'; export const IDiagnosticsService = createDecorator(ID); @@ -163,12 +164,10 @@ function asSortedItems(items: Map): WorkspaceStatItem[] { } export function getMachineInfo(): IMachineInfo { - const MB = 1024 * 1024; - const GB = 1024 * MB; const machineInfo: IMachineInfo = { os: `${osLib.type()} ${osLib.arch()} ${osLib.release()}`, - memory: `${(osLib.totalmem() / GB).toFixed(2)}GB (${(osLib.freemem() / GB).toFixed(2)}GB free)`, + memory: `${(osLib.totalmem() / ByteSize.GB).toFixed(2)}GB (${(osLib.freemem() / ByteSize.GB).toFixed(2)}GB free)`, vmHint: `${Math.round((virtualMachineHint.value() * 100))}%`, }; @@ -238,9 +237,6 @@ export class DiagnosticsService implements IDiagnosticsService { } private formatEnvironment(info: IMainProcessInfo): string { - const MB = 1024 * 1024; - const GB = 1024 * MB; - const output: string[] = []; output.push(`Version: ${product.nameShort} ${product.version} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'})`); output.push(`OS Version: ${osLib.type()} ${osLib.arch()} ${osLib.release()}`); @@ -248,7 +244,7 @@ export class DiagnosticsService implements IDiagnosticsService { if (cpus && cpus.length > 0) { output.push(`CPUs: ${cpus[0].model} (${cpus.length} x ${cpus[0].speed})`); } - output.push(`Memory (System): ${(osLib.totalmem() / GB).toFixed(2)}GB (${(osLib.freemem() / GB).toFixed(2)}GB free)`); + output.push(`Memory (System): ${(osLib.totalmem() / ByteSize.GB).toFixed(2)}GB (${(osLib.freemem() / ByteSize.GB).toFixed(2)}GB free)`); if (!isWindows) { output.push(`Load (avg): ${osLib.loadavg().map(l => Math.round(l)).join(', ')}`); // only provided on Linux/macOS } @@ -493,8 +489,6 @@ export class DiagnosticsService implements IDiagnosticsService { private formatProcessItem(mainPid: number, mapPidToWindowTitle: Map, output: string[], item: ProcessItem, indent: number): void { const isRoot = (indent === 0); - const MB = 1024 * 1024; - // Format name with indent let name: string; if (isRoot) { @@ -508,7 +502,7 @@ export class DiagnosticsService implements IDiagnosticsService { } const memory = process.platform === 'win32' ? item.mem : (osLib.totalmem() * (item.mem / 100)); - output.push(`${item.load.toFixed(0).padStart(5, ' ')}\t${(memory / MB).toFixed(0).padStart(6, ' ')}\t${item.pid.toFixed(0).padStart(6, ' ')}\t${name}`); + output.push(`${item.load.toFixed(0).padStart(5, ' ')}\t${(memory / ByteSize.MB).toFixed(0).padStart(6, ' ')}\t${item.pid.toFixed(0).padStart(6, ' ')}\t${name}`); // Recurse into children if any if (Array.isArray(item.children)) { diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index 127c8fa95..8f7bb194d 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -22,6 +22,29 @@ export interface ICheckbox { checked?: boolean; } +export interface IConfirmDialogArgs { + confirmation: IConfirmation; +} + +export interface IShowDialogArgs { + severity: Severity; + message: string; + buttons: string[]; + options?: IDialogOptions; +} + +export interface IInputDialogArgs extends IShowDialogArgs { + inputs: IInput[], +} + +export interface IDialog { + confirmArgs?: IConfirmDialogArgs; + showArgs?: IShowDialogArgs; + inputArgs?: IInputDialogArgs; +} + +export type IDialogResult = IConfirmationResult | IInputResult | IShowResult; + export interface IConfirmation { title?: string; type?: DialogType; @@ -166,6 +189,40 @@ export interface IInput { value?: string; } +/** + * A handler to bring up modal dialogs. + */ +export interface IDialogHandler { + /** + * Ask the user for confirmation with a modal dialog. + */ + confirm(confirmation: IConfirmation): Promise; + + /** + * Present a modal dialog to the user. + * + * @returns A promise with the selected choice index. If the user refused to choose, + * then a promise with index of `cancelId` option is returned. If there is no such + * option then promise with index `0` is returned. + */ + show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise; + + /** + * Present a modal dialog to the user asking for input. + * + * @returns A promise with the selected choice index. If the user refused to choose, + * then a promise with index of `cancelId` option is returned. If there is no such + * option then promise with index `0` is returned. In addition, the values for the + * inputs are returned as well. + */ + input(severity: Severity, message: string, buttons: string[], inputs: IInput[], options?: IDialogOptions): Promise; + + /** + * Present the about dialog to the user. + */ + about(): Promise; +} + /** * A service to bring up modal dialogs. * @@ -218,20 +275,23 @@ export interface IFileDialogService { /** * The default path for a new file based on previously used files. * @param schemeFilter The scheme of the file path. If no filter given, the scheme of the current window is used. + * Falls back to user home in the absence of enough information to find a better URI. */ - defaultFilePath(schemeFilter?: string): URI | undefined; + defaultFilePath(schemeFilter?: string): Promise; /** * The default path for a new folder based on previously used folders. * @param schemeFilter The scheme of the folder path. If no filter given, the scheme of the current window is used. + * Falls back to user home in the absence of enough information to find a better URI. */ - defaultFolderPath(schemeFilter?: string): URI | undefined; + defaultFolderPath(schemeFilter?: string): Promise; /** * The default path for a new workspace based on previously used workspaces. * @param schemeFilter The scheme of the workspace path. If no filter given, the scheme of the current window is used. + * Falls back to user home in the absence of enough information to find a better URI. */ - defaultWorkspacePath(schemeFilter?: string, filename?: string): URI | undefined; + defaultWorkspacePath(schemeFilter?: string, filename?: string): Promise; /** * Shows a file-folder selection dialog and opens the selected entry. diff --git a/src/vs/editor/contrib/rename/media/onTypeRename.css b/src/vs/platform/display/common/displayMainService.ts similarity index 68% rename from src/vs/editor/contrib/rename/media/onTypeRename.css rename to src/vs/platform/display/common/displayMainService.ts index 16bb01785..44d79f8d5 100644 --- a/src/vs/editor/contrib/rename/media/onTypeRename.css +++ b/src/vs/platform/display/common/displayMainService.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-editor .on-type-rename-decoration { - border-left: 1px solid transparent; - /* So border can be transparent */ - background-clip: padding-box; +import { Event } from 'vs/base/common/event'; + +export interface IDisplayMainService { + readonly _serviceBrand: undefined; + readonly onDidDisplayChanged: Event; } diff --git a/src/vs/platform/display/electron-main/displayMainService.ts b/src/vs/platform/display/electron-main/displayMainService.ts new file mode 100644 index 000000000..e360fc486 --- /dev/null +++ b/src/vs/platform/display/electron-main/displayMainService.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IDisplayMainService as ICommonDisplayMainService } from 'vs/platform/display/common/displayMainService'; +import { Emitter } from 'vs/base/common/event'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { app, Display, screen } from 'electron'; +import { RunOnceScheduler } from 'vs/base/common/async'; + +export const IDisplayMainService = createDecorator('displayMainService'); + +export interface IDisplayMainService extends ICommonDisplayMainService { } + +export class DisplayMainService extends Disposable implements ICommonDisplayMainService { + + declare readonly _serviceBrand: undefined; + + private readonly _onDidDisplayChanged = this._register(new Emitter()); + readonly onDidDisplayChanged = this._onDidDisplayChanged.event; + + constructor() { + super(); + + const displayChangedScheduler = this._register(new RunOnceScheduler(() => { + this._onDidDisplayChanged.fire(); + }, 100)); + + app.whenReady().then(() => { + + const displayChangedListener = (event: Event, display: Display, changedMetrics?: string[]) => { + displayChangedScheduler.schedule(); + }; + + screen.on('display-metrics-changed', displayChangedListener); + this._register(toDisposable(() => screen.removeListener('display-metrics-changed', displayChangedListener))); + + screen.on('display-added', displayChangedListener); + this._register(toDisposable(() => screen.removeListener('display-added', displayChangedListener))); + + screen.on('display-removed', displayChangedListener); + this._register(toDisposable(() => screen.removeListener('display-removed', displayChangedListener))); + }); + } +} diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index 409bb7e19..e719e129c 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -72,12 +72,13 @@ export interface NativeParsedArgs { 'driver'?: string; 'driver-verbose'?: boolean; 'remote'?: string; - 'disable-user-env-probe'?: boolean; 'force'?: boolean; 'do-not-sync'?: boolean; 'force-user-env'?: boolean; + 'force-disable-user-env'?: boolean; 'sync'?: 'on' | 'off'; '__sandbox'?: boolean; + 'logsPath'?: string; // chromium command line args: https://electronjs.org/docs/all#supported-chrome-command-line-switches 'no-proxy-server'?: boolean; diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 21b4d719c..f4b344542 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -119,7 +119,7 @@ export interface INativeEnvironmentService extends IEnvironmentService { sharedIPCHandle: string; // --- Extensions - extensionsPath?: string; + extensionsPath: string; extensionsDownloadPath: string; builtinExtensionsPath: string; diff --git a/src/vs/platform/environment/electron-main/environmentMainService.ts b/src/vs/platform/environment/electron-main/environmentMainService.ts index 8240ed281..35b09d89d 100644 --- a/src/vs/platform/environment/electron-main/environmentMainService.ts +++ b/src/vs/platform/environment/electron-main/environmentMainService.ts @@ -19,6 +19,9 @@ export const IEnvironmentMainService = createDecorator( */ export interface IEnvironmentMainService extends INativeEnvironmentService { + // --- NLS cache path + cachedLanguagesPath: string; + // --- backup paths backupHome: string; backupWorkspacesPath: string; @@ -35,7 +38,10 @@ export interface IEnvironmentMainService extends INativeEnvironmentService { disableUpdates: boolean; } -export class EnvironmentMainService extends NativeEnvironmentService { +export class EnvironmentMainService extends NativeEnvironmentService implements IEnvironmentMainService { + + @memoize + get cachedLanguagesPath(): string { return join(this.userDataPath, 'clp'); } @memoize get backupHome(): string { return join(this.userDataPath, 'Backups'); } diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 149e6ffb4..7263bcbf7 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -43,8 +43,6 @@ export const OPTIONS: OptionDescriptions> = { 'goto': { type: 'boolean', cat: 'o', alias: 'g', args: 'file:line[:character]', description: localize('goto', "Open a file at the path on the specified line and character position.") }, 'new-window': { type: 'boolean', cat: 'o', alias: 'n', description: localize('newWindow', "Force to open a new window.") }, 'reuse-window': { type: 'boolean', cat: 'o', alias: 'r', description: localize('reuseWindow', "Force to open a file or folder in an already opened window.") }, - 'folder-uri': { type: 'string[]', cat: 'o', args: 'uri', description: localize('folderUri', "Opens a window with given folder uri(s)") }, - 'file-uri': { type: 'string[]', cat: 'o', args: 'uri', description: localize('fileUri', "Opens a window with given file uri(s)") }, 'wait': { type: 'boolean', cat: 'o', alias: 'w', description: localize('wait', "Wait for the files to be closed before returning.") }, 'waitMarkerFilePath': { type: 'string' }, 'locale': { type: 'string', cat: 'o', args: 'locale', description: localize('locale', "The locale to use (e.g. en-US or zh-TW).") }, @@ -57,7 +55,7 @@ export const OPTIONS: OptionDescriptions> = { 'list-extensions': { type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") }, 'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extension.") }, 'category': { type: 'string', cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extension.") }, - 'install-extension': { type: 'string[]', cat: 'e', args: 'extension-id[@version] | path-to-vsix', description: localize('installExtension', "Installs or updates the extension. Use `--force` argument to avoid prompts. The identifier of an extension is always `${publisher}.${name}`. To install a specific version provide `@${version}`. For example: 'vscode.csharp@1.2.3'.") }, + 'install-extension': { type: 'string[]', cat: 'e', args: 'extension-id[@version] | path-to-vsix', description: localize('installExtension', "Installs or updates the extension. The identifier of an extension is always `${publisher}.${name}`. Use `--force` argument to update to latest version. To install a specific version provide `@${version}`. For example: 'vscode.csharp@1.2.3'.") }, 'uninstall-extension': { type: 'string[]', cat: 'e', args: 'extension-id', description: localize('uninstallExtension', "Uninstalls an extension.") }, 'enable-proposed-api': { type: 'string[]', cat: 'e', args: 'extension-id', description: localize('experimentalApis', "Enables proposed API features for extensions. Can receive one or more extension IDs to enable individually.") }, @@ -79,6 +77,9 @@ export const OPTIONS: OptionDescriptions> = { 'telemetry': { type: 'boolean', cat: 't', description: localize('telemetry', "Shows all telemetry events which VS code collects.") }, 'remote': { type: 'string' }, + 'folder-uri': { type: 'string[]', cat: 'o', args: 'uri' }, + 'file-uri': { type: 'string[]', cat: 'o', args: 'uri' }, + 'locate-extension': { type: 'string[]' }, 'extensionDevelopmentPath': { type: 'string[]' }, 'extensionTestsPath': { type: 'string' }, @@ -96,7 +97,6 @@ export const OPTIONS: OptionDescriptions> = { 'disable-crash-reporter': { type: 'boolean' }, 'crash-reporter-directory': { type: 'string' }, 'crash-reporter-id': { type: 'string' }, - 'disable-user-env-probe': { type: 'boolean' }, 'skip-add-to-recently-opened': { type: 'boolean' }, 'unity-launch': { type: 'boolean' }, 'open-url': { type: 'boolean' }, @@ -110,8 +110,10 @@ export const OPTIONS: OptionDescriptions> = { 'trace-category-filter': { type: 'string' }, 'trace-options': { type: 'string' }, 'force-user-env': { type: 'boolean' }, + 'force-disable-user-env': { type: 'boolean' }, 'open-devtools': { type: 'boolean' }, '__sandbox': { type: 'boolean' }, + 'logsPath': { type: 'string' }, // chromium flags 'no-proxy-server': { type: 'boolean' }, diff --git a/src/vs/platform/environment/node/argvHelper.ts b/src/vs/platform/environment/node/argvHelper.ts index a3c16f98d..3ace20cfc 100644 --- a/src/vs/platform/environment/node/argvHelper.ts +++ b/src/vs/platform/environment/node/argvHelper.ts @@ -52,7 +52,7 @@ export function parseMainProcessArgv(processArgv: string[]): NativeParsedArgs { } // If called from CLI, don't report warnings as they are already reported. - let reportWarnings = !process.env['VSCODE_CLI']; + const reportWarnings = !isLaunchedFromCli(process.env); return parseAndValidate(args, reportWarnings); } @@ -60,7 +60,7 @@ export function parseMainProcessArgv(processArgv: string[]): NativeParsedArgs { * Use this to parse raw code CLI process.argv such as: `Electron cli.js . --verbose --wait` */ export function parseCLIProcessArgv(processArgv: string[]): NativeParsedArgs { - let [, , ...args] = processArgv; // remove the first non-option argument: it's always the app location + const [, , ...args] = processArgv; // remove the first non-option argument: it's always the app location return parseAndValidate(args, true); } @@ -78,3 +78,7 @@ export function addArg(argv: string[], ...args: string[]): string[] { return argv; } + +export function isLaunchedFromCli(env: NodeJS.ProcessEnv): boolean { + return env['VSCODE_CLI'] === '1'; +} diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 80f68fb1d..cef263622 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as os from 'os'; import { IDebugParams, IExtensionHostDebugParams, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import * as paths from 'vs/base/node/paths'; -import * as os from 'os'; import * as path from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import { memoize } from 'vs/base/common/decorators'; @@ -204,12 +204,11 @@ export class NativeEnvironmentService implements INativeEnvironmentService { get disableTelemetry(): boolean { return !!this._args['disable-telemetry']; } constructor(protected _args: NativeParsedArgs) { - if (!process.env['VSCODE_LOGS']) { + if (!_args.logsPath) { const key = toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, ''); - process.env['VSCODE_LOGS'] = path.join(this.userDataPath, 'logs', key); + _args.logsPath = path.join(this.userDataPath, 'logs', key); } - - this.logsPath = process.env['VSCODE_LOGS']!; + this.logsPath = _args.logsPath; } } @@ -246,5 +245,5 @@ export function parsePathArg(arg: string | undefined, process: NodeJS.Process): } export function parseUserDataDir(args: NativeParsedArgs, process: NodeJS.Process): string { - return parsePathArg(args['user-data-dir'], process) || path.resolve(paths.getDefaultUserDataPath(process.platform)); + return parsePathArg(args['user-data-dir'], process) || path.resolve(paths.getDefaultUserDataPath()); } diff --git a/src/vs/code/test/node/argv.test.ts b/src/vs/platform/environment/test/node/argv.test.ts similarity index 99% rename from src/vs/code/test/node/argv.test.ts rename to src/vs/platform/environment/test/node/argv.test.ts index 79ce7d224..61a4b4f2e 100644 --- a/src/vs/code/test/node/argv.test.ts +++ b/src/vs/platform/environment/test/node/argv.test.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 assert from 'assert'; import { formatOptions, Option } from 'vs/platform/environment/node/argv'; import { addArg } from 'vs/platform/environment/node/argvHelper'; diff --git a/src/vs/platform/environment/test/node/nativeModules.test.ts b/src/vs/platform/environment/test/node/nativeModules.test.ts new file mode 100644 index 000000000..b64282f62 --- /dev/null +++ b/src/vs/platform/environment/test/node/nativeModules.test.ts @@ -0,0 +1,105 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { isMacintosh, isWindows } from 'vs/base/common/platform'; + +function testErrorMessage(module: string): string { + return `Unable to load "${module}" dependency. It was probably not compiled for the right operating system architecture or had missing build tools.`; +} + +suite('Native Modules (all platforms)', () => { + + test('native-is-elevated', async () => { + const isElevated = await import('native-is-elevated'); + assert.ok(typeof isElevated === 'function', testErrorMessage('native-is-elevated ')); + }); + + test('native-keymap', async () => { + const keyMap = await import('native-keymap'); + assert.ok(typeof keyMap.getCurrentKeyboardLayout === 'function', testErrorMessage('native-keymap')); + }); + + test('native-watchdog', async () => { + const watchDog = await import('native-watchdog'); + assert.ok(typeof watchDog.start === 'function', testErrorMessage('native-watchdog')); + }); + + test('node-pty', async () => { + const nodePty = await import('node-pty'); + assert.ok(typeof nodePty.spawn === 'function', testErrorMessage('node-pty')); + }); + + test('spdlog', async () => { + const spdlog = await import('spdlog'); + assert.ok(typeof spdlog.createRotatingLogger === 'function', testErrorMessage('spdlog')); + }); + + test('v8-inspect-profiler', async () => { + const profiler = await import('v8-inspect-profiler'); + assert.ok(typeof profiler.startProfiling === 'function', testErrorMessage('v8-inspect-profiler')); + }); + + test('vscode-nsfw', async () => { + const nsfWatcher = await import('vscode-nsfw'); + assert.ok(typeof nsfWatcher === 'function', testErrorMessage('vscode-nsfw')); + }); + + test('vscode-sqlite3', async () => { + const sqlite3 = await import('vscode-sqlite3'); + assert.ok(typeof sqlite3.Database === 'function', testErrorMessage('vscode-sqlite3')); + }); +}); + +(!isMacintosh ? suite.skip : suite)('Native Modules (macOS)', () => { + + test('chokidar (fsevents)', async () => { + const chokidar = await import('chokidar'); + const watcher = chokidar.watch(__dirname); + assert.ok(watcher.options.useFsEvents, testErrorMessage('chokidar (fsevents)')); + + return watcher.close(); + }); +}); + +(!isWindows ? suite.skip : suite)('Native Modules (Windows)', () => { + + test('windows-mutex', async () => { + const mutex = await import('windows-mutex'); + assert.ok(mutex && typeof mutex.isActive === 'function', testErrorMessage('windows-mutex')); + assert.ok(typeof mutex.isActive === 'function', testErrorMessage('windows-mutex')); + }); + + test('windows-foreground-love', async () => { + const foregroundLove = await import('windows-foreground-love'); + assert.ok(typeof foregroundLove.allowSetForegroundWindow === 'function', testErrorMessage('windows-foreground-love')); + }); + + test('windows-process-tree', async () => { + const processTree = await import('windows-process-tree'); + assert.ok(typeof processTree.getProcessTree === 'function', testErrorMessage('windows-process-tree')); + }); + + test('vscode-windows-registry', async () => { + const windowsRegistry = await import('vscode-windows-registry'); + assert.ok(typeof windowsRegistry.GetStringRegKey === 'function', testErrorMessage('vscode-windows-registry')); + }); + + test('vscode-windows-ca-certs', async () => { + // @ts-ignore Windows only + const windowsCerts = await import('vscode-windows-ca-certs'); + const store = windowsCerts(); + 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/src/vs/platform/extensionManagement/common/extensionEnablementService.ts b/src/vs/platform/extensionManagement/common/extensionEnablementService.ts index afb3cf6e6..7b0a60eeb 100644 --- a/src/vs/platform/extensionManagement/common/extensionEnablementService.ts +++ b/src/vs/platform/extensionManagement/common/extensionEnablementService.ts @@ -7,7 +7,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IExtensionIdentifier, IGlobalExtensionEnablementService, DISABLED_EXTENSIONS_STORAGE_PATH } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage'; import { isUndefinedOrNull } from 'vs/base/common/types'; export class GlobalExtensionEnablementService extends Disposable implements IGlobalExtensionEnablementService { @@ -96,7 +96,7 @@ export class StorageManager extends Disposable { constructor(private storageService: IStorageService) { super(); - this._register(storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); + this._register(storageService.onDidChangeValue(e => this.onDidStorageChange(e))); } get(key: string, scope: StorageScope): IExtensionIdentifier[] { @@ -127,14 +127,14 @@ export class StorageManager extends Disposable { } } - private onDidStorageChange(workspaceStorageChangeEvent: IWorkspaceStorageChangeEvent): void { - if (workspaceStorageChangeEvent.scope === StorageScope.GLOBAL) { - if (!isUndefinedOrNull(this.storage[workspaceStorageChangeEvent.key])) { - const newValue = this._get(workspaceStorageChangeEvent.key, workspaceStorageChangeEvent.scope); - if (newValue !== this.storage[workspaceStorageChangeEvent.key]) { - const oldValues = this.get(workspaceStorageChangeEvent.key, workspaceStorageChangeEvent.scope); - delete this.storage[workspaceStorageChangeEvent.key]; - const newValues = this.get(workspaceStorageChangeEvent.key, workspaceStorageChangeEvent.scope); + private onDidStorageChange(storageChangeEvent: IStorageValueChangeEvent): void { + if (storageChangeEvent.scope === StorageScope.GLOBAL) { + if (!isUndefinedOrNull(this.storage[storageChangeEvent.key])) { + const newValue = this._get(storageChangeEvent.key, storageChangeEvent.scope); + if (newValue !== this.storage[storageChangeEvent.key]) { + const oldValues = this.get(storageChangeEvent.key, storageChangeEvent.scope); + delete this.storage[storageChangeEvent.key]; + const newValues = this.get(storageChangeEvent.key, storageChangeEvent.scope); const added = oldValues.filter(oldValue => !newValues.some(newValue => areSameExtensions(oldValue, newValue))); const removed = newValues.filter(newValue => !oldValues.some(oldValue => areSameExtensions(oldValue, newValue))); if (added.length || removed.length) { @@ -151,7 +151,8 @@ export class StorageManager extends Disposable { private _set(key: string, value: string | undefined, scope: StorageScope): void { if (value) { - this.storageService.store(key, value, scope); + // Enablement state is synced separately through extensions + this.storageService.store(key, value, scope, StorageTarget.MACHINE); } else { this.storageService.remove(key, scope); } diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index b9e1f47e8..3f0dd835c 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -19,7 +19,7 @@ import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId'; import { optional } from 'vs/platform/instantiation/common/instantiation'; import { joinPath } from 'vs/base/common/resources'; @@ -803,7 +803,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { export async function resolveMarketplaceHeaders(version: string, environmentService: IEnvironmentService, fileService: IFileService, storageService: { get: (key: string, scope: StorageScope) => string | undefined, - store: (key: string, value: string, scope: StorageScope) => void + store: (key: string, value: string, scope: StorageScope, target: StorageTarget) => void } | undefined): Promise<{ [key: string]: string; }> { const headers: IHeaders = { 'X-Market-Client-Id': `VSCode ${version}`, diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 0c20952bb..0e46a92d6 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -201,7 +201,8 @@ export class ExtensionManagementError extends Error { } } -export type InstallOptions = { isBuiltin?: boolean, isMachineScoped?: boolean }; +export type InstallOptions = { isBuiltin?: boolean, isMachineScoped?: boolean, donotIncludePackAndDependencies?: boolean }; +export type UninstallOptions = { donotIncludePack?: boolean, donotCheckDependents?: boolean }; export const IExtensionManagementService = createDecorator('extensionManagementService'); export interface IExtensionManagementService { @@ -218,7 +219,7 @@ export interface IExtensionManagementService { install(vsix: URI, options?: InstallOptions): Promise; canInstall(extension: IGalleryExtension): Promise; installFromGallery(extension: IGalleryExtension, options?: InstallOptions): Promise; - uninstall(extension: ILocalExtension, force?: boolean): Promise; + uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise; reinstallFromGallery(extension: ILocalExtension): Promise; getInstalled(type?: ExtensionType): Promise; getExtensionsReport(): Promise; diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index da3540454..75422f963 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -4,12 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension, IExtensionTipsService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { Event } from 'vs/base/common/event'; +import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension, IExtensionTipsService, InstallOptions, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { Emitter, Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IURITransformer, DefaultURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc'; import { cloneAndChange } from 'vs/base/common/objects'; import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { Disposable } from 'vs/base/common/lifecycle'; function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI { return URI.revive(transformer ? transformer.transformIncoming(uri) : uri); @@ -77,18 +78,31 @@ export class ExtensionManagementChannel implements IServerChannel { } } -export class ExtensionManagementChannelClient implements IExtensionManagementService { +export class ExtensionManagementChannelClient extends Disposable implements IExtensionManagementService { declare readonly _serviceBrand: undefined; + private readonly _onInstallExtension = this._register(new Emitter()); + readonly onInstallExtension = this._onInstallExtension.event; + + private readonly _onDidInstallExtension = this._register(new Emitter()); + readonly onDidInstallExtension = this._onDidInstallExtension.event; + + private readonly _onUninstallExtension = this._register(new Emitter()); + readonly onUninstallExtension = this._onUninstallExtension.event; + + private readonly _onDidUninstallExtension = this._register(new Emitter()); + readonly onDidUninstallExtension = this._onDidUninstallExtension.event; + constructor( private readonly channel: IChannel, - ) { } - - get onInstallExtension(): Event { return this.channel.listen('onInstallExtension'); } - get onDidInstallExtension(): Event { return Event.map(this.channel.listen('onDidInstallExtension'), i => ({ ...i, local: i.local ? transformIncomingExtension(i.local, null) : i.local })); } - get onUninstallExtension(): Event { return this.channel.listen('onUninstallExtension'); } - get onDidUninstallExtension(): Event { return this.channel.listen('onDidUninstallExtension'); } + ) { + super(); + this._register(this.channel.listen('onInstallExtension')(e => this._onInstallExtension.fire(e))); + this._register(this.channel.listen('onDidInstallExtension')(e => this._onDidInstallExtension.fire({ ...e, local: e.local ? transformIncomingExtension(e.local, null) : e.local }))); + this._register(this.channel.listen('onUninstallExtension')(e => this._onUninstallExtension.fire(e))); + this._register(this.channel.listen('onDidUninstallExtension')(e => this._onDidUninstallExtension.fire(e))); + } zip(extension: ILocalExtension): Promise { return Promise.resolve(this.channel.call('zip', [extension]).then(result => URI.revive(result))); @@ -114,8 +128,8 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer return Promise.resolve(this.channel.call('installFromGallery', [extension, installOptions])).then(local => transformIncomingExtension(local, null)); } - uninstall(extension: ILocalExtension, force = false): Promise { - return Promise.resolve(this.channel.call('uninstall', [extension!, force])); + uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise { + return Promise.resolve(this.channel.call('uninstall', [extension!, options])); } reinstallFromGallery(extension: ILocalExtension): Promise { diff --git a/src/vs/platform/extensionManagement/common/extensionUrlTrust.ts b/src/vs/platform/extensionManagement/common/extensionUrlTrust.ts new file mode 100644 index 000000000..b26395646 --- /dev/null +++ b/src/vs/platform/extensionManagement/common/extensionUrlTrust.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IExtensionUrlTrustService = createDecorator('extensionUrlTrustService'); + +export interface IExtensionUrlTrustService { + readonly _serviceBrand: undefined; + isExtensionUrlTrusted(extensionId: string, url: string): Promise; +} diff --git a/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts b/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts index 4072c4edf..7d17d9ef3 100644 --- a/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts +++ b/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts @@ -20,7 +20,7 @@ import { disposableTimeout, timeout } from 'vs/base/common/async'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionRecommendationNotificationService, RecommendationsNotificationResult, RecommendationSource } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; import { localize } from 'vs/nls'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; type ExeExtensionRecommendationsClassification = { extensionId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; @@ -241,7 +241,7 @@ export class ExtensionTipsService extends BaseExtensionTipsService { } private updateLastPromptedMediumExeTime(value: number): void { - this.storageService.store(lastPromptedMediumImpExeTimeStorageKey, value, StorageScope.GLOBAL); + this.storageService.store(lastPromptedMediumImpExeTimeStorageKey, value, StorageScope.GLOBAL, StorageTarget.MACHINE); } private getPromptedExecutableTips(): IStringDictionary { @@ -251,7 +251,7 @@ export class ExtensionTipsService extends BaseExtensionTipsService { private addToRecommendedExecutables(exeName: string, tips: IExecutableBasedExtensionTip[]) { const promptedExecutableTips = this.getPromptedExecutableTips(); promptedExecutableTips[exeName] = tips.map(({ extensionId }) => extensionId.toLowerCase()); - this.storageService.store(promptedExecutableTipsStorageKey, JSON.stringify(promptedExecutableTips), StorageScope.GLOBAL); + this.storageService.store(promptedExecutableTipsStorageKey, JSON.stringify(promptedExecutableTips), StorageScope.GLOBAL, StorageTarget.USER); } private groupByInstalled(recommendationsToSuggest: string[], local: ILocalExtension[]): { installed: string[], uninstalled: string[] } { diff --git a/src/vs/platform/extensionManagement/node/extensionDownloader.ts b/src/vs/platform/extensionManagement/node/extensionDownloader.ts index f11e0174b..822dd47b1 100644 --- a/src/vs/platform/extensionManagement/node/extensionDownloader.ts +++ b/src/vs/platform/extensionManagement/node/extensionDownloader.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; +import { rename } from 'vs/base/node/pfs'; import { IFileService, IFileStatWithMetadata } from 'vs/platform/files/common/files'; import { IExtensionGalleryService, IGalleryExtension, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -13,6 +14,7 @@ import { ExtensionIdentifierWithVersion, groupByExtension } from 'vs/platform/ex import { ILogService } from 'vs/platform/log/common/log'; import { generateUuid } from 'vs/base/common/uuid'; import * as semver from 'vs/base/common/semver/semver'; +import { isWindows } from 'vs/base/common/platform'; const ExtensionIdVersionRegex = /^([^.]+\..+)-(\d+\.\d+\.\d+)$/; @@ -36,8 +38,21 @@ export class ExtensionsDownloader extends Disposable { async downloadExtension(extension: IGalleryExtension, operation: InstallOperation): Promise { await this.cleanUpPromise; - const location = joinPath(this.extensionsDownloadDir, this.getName(extension)); - await this.download(extension, location, operation); + const vsixName = this.getName(extension); + const location = joinPath(this.extensionsDownloadDir, vsixName); + + // Download only if vsix does not exist + if (!await this.fileService.exists(location)) { + // Download to temporary location first only if vsix does not exist + const tempLocation = joinPath(this.extensionsDownloadDir, `.${vsixName}`); + if (!await this.fileService.exists(tempLocation)) { + await this.extensionGalleryService.download(extension, tempLocation, operation); + } + + // Rename temp location to original + await this.rename(tempLocation, location, Date.now() + (2 * 60 * 1000) /* Retry for 2 minutes */); + } + return location; } @@ -45,9 +60,15 @@ export class ExtensionsDownloader extends Disposable { // noop as caching is enabled always } - private async download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise { - if (!await this.fileService.exists(location)) { - await this.extensionGalleryService.download(extension, location, operation); + private async rename(from: URI, to: URI, retryUntil: number): Promise { + try { + await rename(from.fsPath, to.fsPath); + } catch (error) { + if (isWindows && error && error.code === 'EPERM' && Date.now() < retryUntil) { + this.logService.info(`Failed renaming ${from} to ${to} with 'EPERM' error. Trying again...`); + return this.rename(from, to, retryUntil); + } + throw error; } } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 11135f9eb..af1092c14 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -20,7 +20,8 @@ import { INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, ExtensionManagementError, - InstallOptions + InstallOptions, + UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, getGalleryExtensionId, getMaliciousExtensionsSet, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -297,11 +298,13 @@ export class ExtensionManagementService extends Disposable implements IExtension try { await this.extensionsDownloader.delete(URI.file(installableExtension.zipPath)); } catch (error) { /* Ignore */ } - try { - await this.installDependenciesAndPackExtensions(local, existingExtension, options); - } catch (error) { - try { await this.uninstall(local); } catch (error) { /* Ignore */ } - throw error; + if (!options.donotIncludePackAndDependencies) { + try { + await this.installDependenciesAndPackExtensions(local, existingExtension, options); + } catch (error) { + try { await this.uninstall(local); } catch (error) { /* Ignore */ } + throw error; + } } if (existingExtension && semver.neq(existingExtension.manifest.version, extension.version)) { @@ -471,7 +474,7 @@ export class ExtensionManagementService extends Disposable implements IExtension await Promise.all(extensionsToUninstall.map(local => this.uninstall(local))); } - async uninstall(extension: ILocalExtension): Promise { + async uninstall(extension: ILocalExtension, options: UninstallOptions = {}): Promise { this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id); const installed = await this.getInstalled(ExtensionType.User); const extensionToUninstall = installed.find(e => areSameExtensions(e.identifier, extension.identifier)); @@ -480,7 +483,7 @@ export class ExtensionManagementService extends Disposable implements IExtension } try { - await this.checkForDependenciesAndUninstall(extensionToUninstall, installed); + await this.checkForDependenciesAndUninstall(extensionToUninstall, installed, options); } catch (error) { throw this.joinErrors(error); } @@ -533,15 +536,11 @@ export class ExtensionManagementService extends Disposable implements IExtension }, new Error('')); } - private async checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[]): Promise { + private async checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[], options: UninstallOptions): Promise { try { await this.preUninstallExtension(extension); - const packedExtensions = this.getAllPackExtensionsToUninstall(extension, installed); - if (packedExtensions.length) { - await this.uninstallExtensions(extension, packedExtensions, installed); - } else { - await this.uninstallExtensions(extension, [], installed); - } + const packedExtensions = options.donotIncludePack ? [] : this.getAllPackExtensionsToUninstall(extension, installed); + await this.uninstallExtensions(extension, packedExtensions, installed, options); } catch (error) { await this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL)); throw error; @@ -549,10 +548,12 @@ export class ExtensionManagementService extends Disposable implements IExtension await this.postUninstallExtension(extension); } - private async uninstallExtensions(extension: ILocalExtension, otherExtensionsToUninstall: ILocalExtension[], installed: ILocalExtension[]): Promise { + private async uninstallExtensions(extension: ILocalExtension, otherExtensionsToUninstall: ILocalExtension[], installed: ILocalExtension[], options: UninstallOptions): Promise { const extensionsToUninstall = [extension, ...otherExtensionsToUninstall]; - for (const e of extensionsToUninstall) { - this.checkForDependents(e, extensionsToUninstall, installed, extension); + if (!options.donotCheckDependents) { + for (const e of extensionsToUninstall) { + this.checkForDependents(e, extensionsToUninstall, installed, extension); + } } await Promise.all([this.uninstallExtension(extension), ...otherExtensionsToUninstall.map(d => this.doUninstall(d))]); } diff --git a/src/vs/platform/extensionManagement/node/extensionUrlTrustService.ts b/src/vs/platform/extensionManagement/node/extensionUrlTrustService.ts new file mode 100644 index 000000000..8b638a63f --- /dev/null +++ b/src/vs/platform/extensionManagement/node/extensionUrlTrustService.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 * as crypto from 'crypto'; +import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IProductService } from 'vs/platform/product/common/productService'; + +export class ExtensionUrlTrustService implements IExtensionUrlTrustService { + + declare readonly _serviceBrand: undefined; + + private trustedExtensionUrlPublicKeys = new Map(); + + constructor( + @IProductService private readonly productService: IProductService, + @ILogService private readonly logService: ILogService + ) { } + + async isExtensionUrlTrusted(extensionId: string, url: string): Promise { + if (!this.productService.trustedExtensionUrlPublicKeys) { + this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'There are no configured trusted keys'); + return false; + } + + const match = /^(.*)#([^#]+)$/.exec(url); + + if (!match) { + this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Uri has no fragment', url); + return false; + } + + const [, originalUrl, fragment] = match; + + let keys = this.trustedExtensionUrlPublicKeys.get(extensionId); + + if (!keys) { + keys = this.productService.trustedExtensionUrlPublicKeys[extensionId]; + + if (!keys || keys.length === 0) { + this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Extension doesn\'t have any trusted keys', extensionId); + return false; + } + + this.trustedExtensionUrlPublicKeys.set(extensionId, [...keys]); + } + + const fragmentBuffer = Buffer.from(decodeURIComponent(fragment), 'base64'); + + if (fragmentBuffer.length <= 6) { + this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Uri fragment is not a signature', url); + return false; + } + + const timestampBuffer = fragmentBuffer.slice(0, 6); + const timestamp = fragmentBuffer.readUIntBE(0, 6); + const diff = Date.now() - timestamp; + + if (diff < 0 || diff > 3_600_000) { // 1 hour + this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Signed uri has expired', url); + return false; + } + + const signatureBuffer = fragmentBuffer.slice(6); + const verify = crypto.createVerify('SHA256'); + verify.write(timestampBuffer); + verify.write(Buffer.from(originalUrl)); + verify.end(); + + for (let i = 0; i < keys.length; i++) { + let key = keys[i]; + + if (key === null) { // failed to be parsed before + continue; + } else if (typeof key === 'string') { // needs to be parsed + try { + key = crypto.createPublicKey({ key: Buffer.from(key, 'base64'), format: 'der', type: 'spki' }); + keys[i] = key; + } catch (err) { + this.logService.warn('ExtensionUrlTrustService#isExtensionUrlTrusted', `Failed to parse trusted extension uri public key #${i + 1} for ${extensionId}:`, err); + keys[i] = null; + continue; + } + } + + if (verify.verify(key, signatureBuffer)) { + this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Signed uri is valid', url); + return true; + } + } + + this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Signed uri could not be verified', url); + return false; + } +} diff --git a/src/vs/platform/extensionManagement/node/extensionsScanner.ts b/src/vs/platform/extensionManagement/node/extensionsScanner.ts index aee65f8ed..adce023b9 100644 --- a/src/vs/platform/extensionManagement/node/extensionsScanner.ts +++ b/src/vs/platform/extensionManagement/node/extensionsScanner.ts @@ -49,7 +49,7 @@ export class ExtensionsScanner extends Disposable { ) { super(); this.systemExtensionsPath = environmentService.builtinExtensionsPath; - this.extensionsPath = environmentService.extensionsPath!; + this.extensionsPath = environmentService.extensionsPath; this.uninstalledPath = path.join(this.extensionsPath, '.obsolete'); this.uninstalledFileLimiter = new Queue(); } diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index b36a5aac6..23001b2a2 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -180,6 +180,7 @@ export class FileService extends Disposable implements IFileService { private async doResolveFile(resource: URI, options?: IResolveFileOptions): Promise; private async doResolveFile(resource: URI, options?: IResolveFileOptions): Promise { const provider = await this.withProvider(resource); + const isPathCaseSensitive = this.isPathCaseSensitive(provider); const resolveTo = options?.resolveTo; const resolveSingleChildDescendants = options?.resolveSingleChildDescendants; @@ -193,7 +194,7 @@ export class FileService extends Disposable implements IFileService { // lazy trie to check for recursive resolving if (!trie) { - trie = TernarySearchTree.forUris(); + trie = TernarySearchTree.forUris(() => !isPathCaseSensitive); trie.set(resource, true); if (isNonEmptyArray(resolveTo)) { resolveTo.forEach(uri => trie!.set(uri, true)); diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 2d2cdcfee..4ae154345 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -521,19 +521,19 @@ export class FileChangesEvent { switch (change.type) { case FileChangeType.ADDED: if (!this.added) { - this.added = TernarySearchTree.forUris(this.ignorePathCasing); + this.added = TernarySearchTree.forUris(() => this.ignorePathCasing); } this.added.set(change.resource, change); break; case FileChangeType.UPDATED: if (!this.updated) { - this.updated = TernarySearchTree.forUris(this.ignorePathCasing); + this.updated = TernarySearchTree.forUris(() => this.ignorePathCasing); } this.updated.set(change.resource, change); break; case FileChangeType.DELETED: if (!this.deleted) { - this.deleted = TernarySearchTree.forUris(this.ignorePathCasing); + this.deleted = TernarySearchTree.forUris(() => this.ignorePathCasing); } this.deleted.set(change.resource, change); break; @@ -971,33 +971,33 @@ export const FALLBACK_MAX_MEMORY_SIZE_MB = 4096; /** * Helper to format a raw byte size into a human readable label. */ -export class BinarySize { +export class ByteSize { static readonly KB = 1024; - static readonly MB = BinarySize.KB * BinarySize.KB; - static readonly GB = BinarySize.MB * BinarySize.KB; - static readonly TB = BinarySize.GB * BinarySize.KB; + static readonly MB = ByteSize.KB * ByteSize.KB; + static readonly GB = ByteSize.MB * ByteSize.KB; + static readonly TB = ByteSize.GB * ByteSize.KB; static formatSize(size: number): string { if (!isNumber(size)) { size = 0; } - if (size < BinarySize.KB) { + if (size < ByteSize.KB) { return localize('sizeB', "{0}B", size.toFixed(0)); } - if (size < BinarySize.MB) { - return localize('sizeKB', "{0}KB", (size / BinarySize.KB).toFixed(2)); + if (size < ByteSize.MB) { + return localize('sizeKB', "{0}KB", (size / ByteSize.KB).toFixed(2)); } - if (size < BinarySize.GB) { - return localize('sizeMB', "{0}MB", (size / BinarySize.MB).toFixed(2)); + if (size < ByteSize.GB) { + return localize('sizeMB', "{0}MB", (size / ByteSize.MB).toFixed(2)); } - if (size < BinarySize.TB) { - return localize('sizeGB', "{0}GB", (size / BinarySize.GB).toFixed(2)); + if (size < ByteSize.TB) { + return localize('sizeGB', "{0}GB", (size / ByteSize.GB).toFixed(2)); } - return localize('sizeTB', "{0}TB", (size / BinarySize.TB).toFixed(2)); + return localize('sizeTB', "{0}TB", (size / ByteSize.TB).toFixed(2)); } } diff --git a/src/vs/platform/files/node/watcher/nsfw/test/nsfwWatcherService.test.ts b/src/vs/platform/files/node/watcher/nsfw/test/nsfwWatcherService.test.ts index de85fd770..9ad083fc9 100644 --- a/src/vs/platform/files/node/watcher/nsfw/test/nsfwWatcherService.test.ts +++ b/src/vs/platform/files/node/watcher/nsfw/test/nsfwWatcherService.test.ts @@ -5,23 +5,27 @@ import * as assert from 'assert'; import * as platform from 'vs/base/common/platform'; -import { NsfwWatcherService } from 'vs/platform/files/node/watcher/nsfw/nsfwWatcherService'; import { IWatcherRequest } from 'vs/platform/files/node/watcher/nsfw/watcher'; -class TestNsfwWatcherService extends NsfwWatcherService { +suite('NSFW Watcher Service', async () => { - normalizeRoots(roots: string[]): string[] { + // Load `nsfwWatcherService` within the suite to prevent all tests + // from failing to start if `vscode-nsfw` was not properly installed + const { NsfwWatcherService } = await import('vs/platform/files/node/watcher/nsfw/nsfwWatcherService'); - // Work with strings as paths to simplify testing - const requests: IWatcherRequest[] = roots.map(r => { - return { path: r, excludes: [] }; - }); + class TestNsfwWatcherService extends NsfwWatcherService { - return this._normalizeRoots(requests).map(r => r.path); + normalizeRoots(roots: string[]): string[] { + + // Work with strings as paths to simplify testing + const requests: IWatcherRequest[] = roots.map(r => { + return { path: r, excludes: [] }; + }); + + return this._normalizeRoots(requests).map(r => r.path); + } } -} -suite('NSFW Watcher Service', () => { suite('_normalizeRoots', () => { test('should not impacts roots that don\'t overlap', () => { const service = new TestNsfwWatcherService(); diff --git a/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts b/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts index 1f88cadc0..895e5dfa9 100644 --- a/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts +++ b/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts @@ -142,7 +142,7 @@ export class ChokidarWatcherService extends Disposable implements IWatcherServic } if (this.verboseLogging) { - this.log(`Start watching with chockidar: ${realBasePath}, excludes: ${excludes.join(',')}, usePolling: ${usePolling ? 'true, interval ' + pollingInterval : 'false'}`); + this.log(`Start watching with chokidar: ${realBasePath}, excludes: ${excludes.join(',')}, usePolling: ${usePolling ? 'true, interval ' + pollingInterval : 'false'}`); } let chokidarWatcher: chokidar.FSWatcher | null = chokidar.watch(realBasePath, watcherOpts); diff --git a/src/vs/platform/files/node/watcher/unix/test/chockidarWatcherService.test.ts b/src/vs/platform/files/node/watcher/unix/test/chockidarWatcherService.test.ts index 4fe0f39ca..4c780ec6b 100644 --- a/src/vs/platform/files/node/watcher/unix/test/chockidarWatcherService.test.ts +++ b/src/vs/platform/files/node/watcher/unix/test/chockidarWatcherService.test.ts @@ -5,32 +5,36 @@ import * as assert from 'assert'; import * as platform from 'vs/base/common/platform'; -import { normalizeRoots } from 'vs/platform/files/node/watcher/unix/chokidarWatcherService'; import { IWatcherRequest } from 'vs/platform/files/node/watcher/unix/watcher'; -function newRequest(basePath: string, ignored: string[] = []): IWatcherRequest { - return { path: basePath, excludes: ignored }; -} +suite('Chokidar normalizeRoots', async () => { -function assertNormalizedRootPath(inputPaths: string[], expectedPaths: string[]) { - const requests = inputPaths.map(path => newRequest(path)); - const actual = normalizeRoots(requests); - assert.deepEqual(Object.keys(actual).sort(), expectedPaths); -} + // Load `chokidarWatcherService` within the suite to prevent all tests + // from failing to start if `chokidar` was not properly installed + const { normalizeRoots } = await import('vs/platform/files/node/watcher/unix/chokidarWatcherService'); -function assertNormalizedRequests(inputRequests: IWatcherRequest[], expectedRequests: { [path: string]: IWatcherRequest[] }) { - const actual = normalizeRoots(inputRequests); - const actualPath = Object.keys(actual).sort(); - const expectedPaths = Object.keys(expectedRequests).sort(); - assert.deepEqual(actualPath, expectedPaths); - for (let path of actualPath) { - let a = expectedRequests[path].sort((r1, r2) => r1.path.localeCompare(r2.path)); - let e = expectedRequests[path].sort((r1, r2) => r1.path.localeCompare(r2.path)); - assert.deepEqual(a, e); + function newRequest(basePath: string, ignored: string[] = []): IWatcherRequest { + return { path: basePath, excludes: ignored }; + } + + function assertNormalizedRootPath(inputPaths: string[], expectedPaths: string[]) { + const requests = inputPaths.map(path => newRequest(path)); + const actual = normalizeRoots(requests); + assert.deepEqual(Object.keys(actual).sort(), expectedPaths); + } + + function assertNormalizedRequests(inputRequests: IWatcherRequest[], expectedRequests: { [path: string]: IWatcherRequest[] }) { + const actual = normalizeRoots(inputRequests); + const actualPath = Object.keys(actual).sort(); + const expectedPaths = Object.keys(expectedRequests).sort(); + assert.deepEqual(actualPath, expectedPaths); + for (let path of actualPath) { + let a = expectedRequests[path].sort((r1, r2) => r1.path.localeCompare(r2.path)); + let e = expectedRequests[path].sort((r1, r2) => r1.path.localeCompare(r2.path)); + assert.deepEqual(a, e); + } } -} -suite('Chokidar normalizeRoots', () => { test('should not impacts roots that don\'t overlap', () => { if (platform.isWindows) { assertNormalizedRootPath(['C:\\a'], ['C:\\a']); diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/common/issue.ts index 9cbaae735..e981a34d5 100644 --- a/src/vs/platform/issue/common/issue.ts +++ b/src/vs/platform/issue/common/issue.ts @@ -55,6 +55,7 @@ export interface IssueReporterData extends WindowData { enabledExtensions: IssueReporterExtensionData[]; issueType?: IssueType; extensionId?: string; + experiments?: string; readonly issueTitle?: string; readonly issueBody?: string; } diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index ca72fa628..da98e2f94 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -55,7 +55,7 @@ export class IssueMainService implements ICommonIssueService { .then(result => { const [info, remoteData] = result; this.diagnosticsService.getSystemInfo(info, remoteData).then(msg => { - event.sender.send('vscode:issueSystemInfoResponse', msg); + this.safeSend(event, 'vscode:issueSystemInfoResponse', msg); }); }); }); @@ -86,7 +86,7 @@ export class IssueMainService implements ICommonIssueService { this.logService.error(`Listing processes failed: ${e}`); } - event.sender.send('vscode:listProcessesResponse', processes); + this.safeSend(event, 'vscode:listProcessesResponse', processes); }); ipcMain.on('vscode:issueReporterClipboard', (event: IpcMainEvent) => { @@ -102,14 +102,14 @@ export class IssueMainService implements ICommonIssueService { if (this._issueWindow) { this.dialogMainService.showMessageBox(messageOptions, this._issueWindow) .then(result => { - event.sender.send('vscode:issueReporterClipboardResponse', result.response === 0); + this.safeSend(event, 'vscode:issueReporterClipboardResponse', result.response === 0); }); } }); ipcMain.on('vscode:issuePerformanceInfoRequest', (event: IpcMainEvent) => { this.getPerformanceInfo().then(msg => { - event.sender.send('vscode:issuePerformanceInfoResponse', msg); + this.safeSend(event, 'vscode:issuePerformanceInfoResponse', msg); }); }); @@ -174,11 +174,17 @@ export class IssueMainService implements ICommonIssueService { ipcMain.on('vscode:windowsInfoRequest', (event: IpcMainEvent) => { this.launchMainService.getMainProcessInfo().then(info => { - event.sender.send('vscode:windowsInfoResponse', info.windows); + this.safeSend(event, 'vscode:windowsInfoResponse', info.windows); }); }); } + private safeSend(event: IpcMainEvent, channel: string, ...args: unknown[]): void { + if (!event.sender.isDestroyed()) { + event.sender.send(channel, ...args); + } + } + openReporter(data: IssueReporterData): Promise { return new Promise(_ => { if (!this._issueWindow) { @@ -203,18 +209,8 @@ export class IssueMainService implements ICommonIssueService { spellcheck: false, nativeWindowOpen: true, zoomFactor: zoomLevelToZoomFactor(data.zoomLevel), - ...this.environmentService.sandbox ? - - // Sandbox - { - sandbox: true, - contextIsolation: true - } : - - // No Sandbox - { - nodeIntegration: true - } + sandbox: true, + contextIsolation: true } }); @@ -269,18 +265,8 @@ export class IssueMainService implements ICommonIssueService { spellcheck: false, nativeWindowOpen: true, zoomFactor: zoomLevelToZoomFactor(data.zoomLevel), - ...this.environmentService.sandbox ? - - // Sandbox - { - sandbox: true, - contextIsolation: true - } : - - // No Sandbox - { - nodeIntegration: true - } + sandbox: true, + contextIsolation: true } }); @@ -288,7 +274,6 @@ export class IssueMainService implements ICommonIssueService { const windowConfiguration = { appRoot: this.environmentService.appRoot, - nodeCachedDataDir: this.environmentService.nodeCachedDataDir, windowId: this._processExplorerWindow.id, userEnv: this.userEnv, machineId: this.machineId, @@ -416,7 +401,6 @@ export class IssueMainService implements ICommonIssueService { const windowConfiguration = { appRoot: this.environmentService.appRoot, - nodeCachedDataDir: this.environmentService.nodeCachedDataDir, windowId: this._issueWindow.id, machineId: this.machineId, userEnv: this.userEnv, @@ -452,7 +436,7 @@ function toWindowUrl(modulePathToHtml: string, windowConfiguration: T): strin } return FileAccess - .asBrowserUri(modulePathToHtml, require) + ._asCodeFileUri(modulePathToHtml, require) .with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` }) .toString(true); } diff --git a/src/vs/platform/keybinding/common/keybindingResolver.ts b/src/vs/platform/keybinding/common/keybindingResolver.ts index 43a109d67..3582ce6d6 100644 --- a/src/vs/platform/keybinding/common/keybindingResolver.ts +++ b/src/vs/platform/keybinding/common/keybindingResolver.ts @@ -383,14 +383,9 @@ function printWhenExplanation(when: ContextKeyExpression | undefined): string { } function printSourceExplanation(kb: ResolvedKeybindingItem): string { - if (kb.isDefault) { - if (kb.extensionId) { - return `built-in extension ${kb.extensionId}`; - } - return `built-in`; - } - if (kb.extensionId) { - return `user extension ${kb.extensionId}`; - } - return `user`; + return ( + kb.extensionId + ? (kb.isBuiltinExtension ? `built-in extension ${kb.extensionId}` : `user extension ${kb.extensionId}`) + : (kb.isDefault ? `built-in` : `user`) + ); } diff --git a/src/vs/platform/keybinding/common/keybindingsRegistry.ts b/src/vs/platform/keybinding/common/keybindingsRegistry.ts index b4210f9e5..2bf215e08 100644 --- a/src/vs/platform/keybinding/common/keybindingsRegistry.ts +++ b/src/vs/platform/keybinding/common/keybindingsRegistry.ts @@ -17,6 +17,7 @@ export interface IKeybindingItem { weight1: number; weight2: number; extensionId: string | null; + isBuiltinExtension: boolean; } export interface IKeybindings { @@ -53,6 +54,7 @@ export interface IKeybindingRule2 { weight: number; when: ContextKeyExpression | undefined; extensionId?: string; + isBuiltinExtension?: boolean; } export const enum KeybindingWeight { @@ -164,7 +166,8 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry { when: rule.when, weight1: rule.weight, weight2: 0, - extensionId: rule.extensionId || null + extensionId: rule.extensionId || null, + isBuiltinExtension: rule.isBuiltinExtension || false }; } } @@ -223,7 +226,8 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry { when: when, weight1: weight1, weight2: weight2, - extensionId: null + extensionId: null, + isBuiltinExtension: false }); this._cachedMergedKeybindings = null; } diff --git a/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts b/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts index 0410c4698..6d2df1597 100644 --- a/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts +++ b/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts @@ -18,8 +18,9 @@ export class ResolvedKeybindingItem { public readonly when: ContextKeyExpression | undefined; public readonly isDefault: boolean; public readonly extensionId: string | null; + public readonly isBuiltinExtension: boolean; - constructor(resolvedKeybinding: ResolvedKeybinding | undefined, command: string | null, commandArgs: any, when: ContextKeyExpression | undefined, isDefault: boolean, extensionId: string | null) { + constructor(resolvedKeybinding: ResolvedKeybinding | undefined, command: string | null, commandArgs: any, when: ContextKeyExpression | undefined, isDefault: boolean, extensionId: string | null, isBuiltinExtension: boolean) { this.resolvedKeybinding = resolvedKeybinding; this.keypressParts = resolvedKeybinding ? removeElementsAfterNulls(resolvedKeybinding.getDispatchParts()) : []; this.bubble = (command ? command.charCodeAt(0) === CharCode.Caret : false); @@ -28,6 +29,7 @@ export class ResolvedKeybindingItem { this.when = when; this.isDefault = isDefault; this.extensionId = extensionId; + this.isBuiltinExtension = isBuiltinExtension; } } diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index b9af02e74..cbbb54344 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -192,7 +192,8 @@ suite('AbstractKeybindingService', () => { null, when, true, - null + null, + false ); } diff --git a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts index 0723eb637..2820e0ea4 100644 --- a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts +++ b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts @@ -28,7 +28,8 @@ suite('KeybindingResolver', () => { commandArgs, when, isDefault, - null + null, + false ); } diff --git a/src/vs/workbench/services/keybinding/common/dispatchConfig.ts b/src/vs/platform/keyboardLayout/common/dispatchConfig.ts similarity index 100% rename from src/vs/workbench/services/keybinding/common/dispatchConfig.ts rename to src/vs/platform/keyboardLayout/common/dispatchConfig.ts diff --git a/src/vs/platform/keyboardLayout/common/keyboardLayout.ts b/src/vs/platform/keyboardLayout/common/keyboardLayout.ts new file mode 100644 index 000000000..db0975fe9 --- /dev/null +++ b/src/vs/platform/keyboardLayout/common/keyboardLayout.ts @@ -0,0 +1,260 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { Event } from 'vs/base/common/event'; +import { ScanCode, ScanCodeUtils } from 'vs/base/common/scanCode'; +import { IKeyboardMapper } from 'vs/platform/keyboardLayout/common/keyboardMapper'; +import { DispatchConfig } from 'vs/platform/keyboardLayout/common/dispatchConfig'; +import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; + +export const IKeyboardLayoutService = createDecorator('keyboardLayoutService'); + +export interface IWindowsKeyMapping { + vkey: string; + value: string; + withShift: string; + withAltGr: string; + withShiftAltGr: string; +} +export interface IWindowsKeyboardMapping { + [code: string]: IWindowsKeyMapping; +} +export interface ILinuxKeyMapping { + value: string; + withShift: string; + withAltGr: string; + withShiftAltGr: string; +} +export interface ILinuxKeyboardMapping { + [code: string]: ILinuxKeyMapping; +} +export interface IMacKeyMapping { + value: string; + valueIsDeadKey: boolean; + withShift: string; + withShiftIsDeadKey: boolean; + withAltGr: string; + withAltGrIsDeadKey: boolean; + withShiftAltGr: string; + withShiftAltGrIsDeadKey: boolean; +} +export interface IMacKeyboardMapping { + [code: string]: IMacKeyMapping; +} + +export type IMacLinuxKeyMapping = IMacKeyMapping | ILinuxKeyMapping; +export type IMacLinuxKeyboardMapping = IMacKeyboardMapping | ILinuxKeyboardMapping; +export type IKeyboardMapping = IWindowsKeyboardMapping | ILinuxKeyboardMapping | IMacKeyboardMapping; + +/* __GDPR__FRAGMENT__ + "IKeyboardLayoutInfo" : { + "name" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "id": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "text": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } +*/ +export interface IWindowsKeyboardLayoutInfo { + name: string; + id: string; + text: string; +} + +/* __GDPR__FRAGMENT__ + "IKeyboardLayoutInfo" : { + "model" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "layout": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "variant": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "options": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "rules": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } +*/ +export interface ILinuxKeyboardLayoutInfo { + model: string; + layout: string; + variant: string; + options: string; + rules: string; +} + +/* __GDPR__FRAGMENT__ + "IKeyboardLayoutInfo" : { + "id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "lang": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "localizedName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } +*/ +export interface IMacKeyboardLayoutInfo { + id: string; + lang: string; + localizedName?: string; +} + +export type IKeyboardLayoutInfo = (IWindowsKeyboardLayoutInfo | ILinuxKeyboardLayoutInfo | IMacKeyboardLayoutInfo) & { isUserKeyboardLayout?: boolean; isUSStandard?: true }; + +export interface IKeyboardLayoutService { + readonly _serviceBrand: undefined; + + readonly onDidChangeKeyboardLayout: Event; + + getRawKeyboardMapping(): IKeyboardMapping | null; + getCurrentKeyboardLayout(): IKeyboardLayoutInfo | null; + getAllKeyboardLayouts(): IKeyboardLayoutInfo[]; + getKeyboardMapper(dispatchConfig: DispatchConfig): IKeyboardMapper; + validateCurrentKeyboardMapping(keyboardEvent: IKeyboardEvent): void; +} + +export function areKeyboardLayoutsEqual(a: IKeyboardLayoutInfo | null, b: IKeyboardLayoutInfo | null): boolean { + if (!a || !b) { + return false; + } + + if ((a).name && (b).name && (a).name === (b).name) { + return true; + } + + if ((a).id && (b).id && (a).id === (b).id) { + return true; + } + + if ((a).model && + (b).model && + (a).model === (b).model && + (a).layout === (b).layout + ) { + return true; + } + + return false; +} + +export function parseKeyboardLayoutDescription(layout: IKeyboardLayoutInfo | null): { label: string, description: string } { + if (!layout) { + return { label: '', description: '' }; + } + + if ((layout).name) { + // windows + let windowsLayout = layout; + return { + label: windowsLayout.text, + description: '' + }; + } + + if ((layout).id) { + let macLayout = layout; + if (macLayout.localizedName) { + return { + label: macLayout.localizedName, + description: '' + }; + } + + if (/^com\.apple\.keylayout\./.test(macLayout.id)) { + return { + label: macLayout.id.replace(/^com\.apple\.keylayout\./, '').replace(/-/, ' '), + description: '' + }; + } + if (/^.*inputmethod\./.test(macLayout.id)) { + return { + label: macLayout.id.replace(/^.*inputmethod\./, '').replace(/[-\.]/, ' '), + description: `Input Method (${macLayout.lang})` + }; + } + + return { + label: macLayout.lang, + description: '' + }; + } + + let linuxLayout = layout; + + return { + label: linuxLayout.layout, + description: '' + }; +} + +export function getKeyboardLayoutId(layout: IKeyboardLayoutInfo): string { + if ((layout).name) { + return (layout).name; + } + + if ((layout).id) { + return (layout).id; + } + + return (layout).layout; +} + +function windowsKeyMappingEquals(a: IWindowsKeyMapping, b: IWindowsKeyMapping): boolean { + if (!a && !b) { + return true; + } + if (!a || !b) { + return false; + } + return ( + a.vkey === b.vkey + && a.value === b.value + && a.withShift === b.withShift + && a.withAltGr === b.withAltGr + && a.withShiftAltGr === b.withShiftAltGr + ); +} + +export function windowsKeyboardMappingEquals(a: IWindowsKeyboardMapping | null, b: IWindowsKeyboardMapping | null): boolean { + if (!a && !b) { + return true; + } + if (!a || !b) { + return false; + } + for (let scanCode = 0; scanCode < ScanCode.MAX_VALUE; scanCode++) { + const strScanCode = ScanCodeUtils.toString(scanCode); + const aEntry = a[strScanCode]; + const bEntry = b[strScanCode]; + if (!windowsKeyMappingEquals(aEntry, bEntry)) { + return false; + } + } + return true; +} + +function macLinuxKeyMappingEquals(a: IMacLinuxKeyMapping, b: IMacLinuxKeyMapping): boolean { + if (!a && !b) { + return true; + } + if (!a || !b) { + return false; + } + return ( + a.value === b.value + && a.withShift === b.withShift + && a.withAltGr === b.withAltGr + && a.withShiftAltGr === b.withShiftAltGr + ); +} + +export function macLinuxKeyboardMappingEquals(a: IMacLinuxKeyboardMapping | null, b: IMacLinuxKeyboardMapping | null): boolean { + if (!a && !b) { + return true; + } + if (!a || !b) { + return false; + } + for (let scanCode = 0; scanCode < ScanCode.MAX_VALUE; scanCode++) { + const strScanCode = ScanCodeUtils.toString(scanCode); + const aEntry = a[strScanCode]; + const bEntry = b[strScanCode]; + if (!macLinuxKeyMappingEquals(aEntry, bEntry)) { + return false; + } + } + return true; +} diff --git a/src/vs/platform/keyboardLayout/common/keyboardLayoutMainService.ts b/src/vs/platform/keyboardLayout/common/keyboardLayoutMainService.ts new file mode 100644 index 000000000..4b6d3d468 --- /dev/null +++ b/src/vs/platform/keyboardLayout/common/keyboardLayoutMainService.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IKeyboardLayoutInfo, IKeyboardMapping } from 'vs/platform/keyboardLayout/common/keyboardLayout'; +import { Event } from 'vs/base/common/event'; + +export interface IKeyboardLayoutData { + keyboardLayoutInfo: IKeyboardLayoutInfo; + keyboardMapping: IKeyboardMapping; +} + +export interface IKeyboardLayoutMainService { + readonly _serviceBrand: undefined; + readonly onDidChangeKeyboardLayout: Event; + getKeyboardLayoutData(): Promise; +} diff --git a/src/vs/workbench/services/keybinding/common/keyboardMapper.ts b/src/vs/platform/keyboardLayout/common/keyboardMapper.ts similarity index 100% rename from src/vs/workbench/services/keybinding/common/keyboardMapper.ts rename to src/vs/platform/keyboardLayout/common/keyboardMapper.ts diff --git a/src/vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService.ts b/src/vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService.ts new file mode 100644 index 000000000..f28a15dca --- /dev/null +++ b/src/vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IKeyboardLayoutData, IKeyboardLayoutMainService as ICommonKeyboardLayoutMainService } from 'vs/platform/keyboardLayout/common/keyboardLayoutMainService'; +import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import * as nativeKeymap from 'native-keymap'; + +export const IKeyboardLayoutMainService = createDecorator('keyboardLayoutMainService'); + +export interface IKeyboardLayoutMainService extends ICommonKeyboardLayoutMainService { } + +export class KeyboardLayoutMainService extends Disposable implements ICommonKeyboardLayoutMainService { + + declare readonly _serviceBrand: undefined; + + private readonly _onDidChangeKeyboardLayout = this._register(new Emitter()); + readonly onDidChangeKeyboardLayout = this._onDidChangeKeyboardLayout.event; + + private _initPromise: Promise | null; + private _keyboardLayoutData: IKeyboardLayoutData | null; + + constructor() { + super(); + this._initPromise = null; + this._keyboardLayoutData = null; + } + + private _initialize(): Promise { + if (!this._initPromise) { + this._initPromise = this._doInitialize(); + } + return this._initPromise; + } + + private async _doInitialize(): Promise { + const nativeKeymapMod = await import('native-keymap'); + + this._keyboardLayoutData = readKeyboardLayoutData(nativeKeymapMod); + nativeKeymapMod.onDidChangeKeyboardLayout(() => { + this._keyboardLayoutData = readKeyboardLayoutData(nativeKeymapMod); + this._onDidChangeKeyboardLayout.fire(this._keyboardLayoutData); + }); + } + + public async getKeyboardLayoutData(): Promise { + await this._initialize(); + return this._keyboardLayoutData!; + } +} + +function readKeyboardLayoutData(nativeKeymapMod: typeof nativeKeymap): IKeyboardLayoutData { + const keyboardMapping = nativeKeymapMod.getKeyMap(); + const keyboardLayoutInfo = nativeKeymapMod.getCurrentKeyboardLayout(); + return { keyboardMapping, keyboardLayoutInfo }; +} diff --git a/src/vs/platform/launch/electron-main/launchMainService.ts b/src/vs/platform/launch/electron-main/launchMainService.ts index e646cc854..9b3d65663 100644 --- a/src/vs/platform/launch/electron-main/launchMainService.ts +++ b/src/vs/platform/launch/electron-main/launchMainService.ts @@ -19,6 +19,8 @@ import { BrowserWindow, ipcMain, Event as IpcEvent, app } from 'electron'; import { coalesce } from 'vs/base/common/arrays'; import { IDiagnosticInfoOptions, IDiagnosticInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; import { IMainProcessInfo, IWindowInfo } from 'vs/platform/launch/node/launch'; +import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper'; +import { CancellationToken } from 'vs/base/common/cancellation'; export const ID = 'launchMainService'; export const ILaunchMainService = createDecorator(ID); @@ -33,14 +35,14 @@ export interface IRemoteDiagnosticOptions { includeWorkspaceMetadata?: boolean; } -function parseOpenUrl(args: NativeParsedArgs): URI[] { +function parseOpenUrl(args: NativeParsedArgs): { uri: URI, url: string }[] { if (args['open-url'] && args._urls && args._urls.length > 0) { // --open-url must contain -- followed by the url(s) // process.argv is used over args._ as args._ are resolved to file paths at this point return coalesce(args._urls .map(url => { try { - return URI.parse(url); + return { uri: URI.parse(url), url }; } catch (err) { return null; } @@ -99,8 +101,8 @@ export class LaunchMainService implements ILaunchMainService { // Make sure a window is open, ready to receive the url event whenWindowReady.then(() => { - for (const url of urlsToOpen) { - this.urlService.open(url); + for (const { uri, url } of urlsToOpen) { + this.urlService.open(uri, { originalUrl: url }); } }); } @@ -112,7 +114,7 @@ export class LaunchMainService implements ILaunchMainService { } private startOpenWindow(args: NativeParsedArgs, userEnv: IProcessEnvironment): Promise { - const context = !!userEnv['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP; + const context = isLaunchedFromCli(userEnv) ? OpenContext.CLI : OpenContext.DESKTOP; let usedWindows: ICodeWindow[] = []; const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined; @@ -138,7 +140,7 @@ export class LaunchMainService implements ILaunchMainService { // Otherwise check for settings else { - const windowConfig = this.configurationService.getValue('window'); + const windowConfig = this.configurationService.getValue('window'); const openWithoutArgumentsInNewWindowConfig = windowConfig?.openWithoutArgumentsInNewWindow || 'default' /* default */; switch (openWithoutArgumentsInNewWindowConfig) { case 'on': @@ -247,7 +249,7 @@ export class LaunchMainService implements ILaunchMainService { folders: options.includeWorkspaceMetadata ? this.getFolderURIs(window) : undefined }; - window.sendWhenReady('vscode:getDiagnosticInfo', { replyChannel, args }); + window.sendWhenReady('vscode:getDiagnosticInfo', CancellationToken.None, { replyChannel, args }); ipcMain.once(replyChannel, (_: IpcEvent, data: IRemoteDiagnosticInfo) => { // No data is returned if getting the connection fails. diff --git a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts index 48ad05b7b..62db3f751 100644 --- a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts +++ b/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 { ipcMain as ipc, app, BrowserWindow } from 'electron'; +import { ipcMain, app, BrowserWindow } from 'electron'; import { ILogService } from 'vs/platform/log/common/log'; import { IStateService } from 'vs/platform/state/node/state'; import { Event, Emitter } from 'vs/base/common/event'; @@ -371,7 +371,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe // Only reload when the window has not vetoed this const veto = await this.unload(window, UnloadReason.RELOAD); if (!veto) { - window.reload(undefined, cli); + window.reload(cli); } } @@ -437,11 +437,11 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe const okChannel = `vscode:ok${oneTimeEventToken}`; const cancelChannel = `vscode:cancel${oneTimeEventToken}`; - ipc.once(okChannel, () => { + ipcMain.once(okChannel, () => { resolve(false); // no veto }); - ipc.once(cancelChannel, () => { + ipcMain.once(cancelChannel, () => { resolve(true); // veto }); @@ -468,7 +468,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe const oneTimeEventToken = this.oneTimeListenerTokenGenerator++; const replyChannel = `vscode:reply${oneTimeEventToken}`; - ipc.once(replyChannel, () => resolve()); + ipcMain.once(replyChannel, () => resolve()); window.send('vscode:onWillUnload', { replyChannel, reason }); }); diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index b1a865528..b553a0581 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -118,14 +118,15 @@ function createScopedContextKeyService(contextKeyService: IContextKeyService, wi return result; } -export const multiSelectModifierSettingKey = 'workbench.list.multiSelectModifier'; -export const openModeSettingKey = 'workbench.list.openMode'; -export const horizontalScrollingKey = 'workbench.list.horizontalScrolling'; -export const keyboardNavigationSettingKey = 'workbench.list.keyboardNavigation'; -export const automaticKeyboardNavigationSettingKey = 'workbench.list.automaticKeyboardNavigation'; +const multiSelectModifierSettingKey = 'workbench.list.multiSelectModifier'; +const openModeSettingKey = 'workbench.list.openMode'; +const horizontalScrollingKey = 'workbench.list.horizontalScrolling'; +const keyboardNavigationSettingKey = 'workbench.list.keyboardNavigation'; +const automaticKeyboardNavigationSettingKey = 'workbench.list.automaticKeyboardNavigation'; const treeIndentKey = 'workbench.tree.indent'; const treeRenderIndentGuidesKey = 'workbench.tree.renderIndentGuides'; const listSmoothScrolling = 'workbench.list.smoothScrolling'; +const treeExpandMode = 'workbench.tree.expandMode'; function useAltAsMultipleSelectionModifier(configurationService: IConfigurationService): boolean { return configurationService.getValue(multiSelectModifierSettingKey) === 'alt'; @@ -444,11 +445,11 @@ abstract class ResourceNavigator extends Disposable { private readonly openOnFocus: boolean; private openOnSingleClick: boolean; - private readonly _onDidOpen = new Emitter>(); - readonly onDidOpen: Event> = this._onDidOpen.event; + private readonly _onDidOpen = this._register(new Emitter>()); + readonly onDidOpen: Event> = this._onDidOpen.event; constructor( - private readonly widget: ListWidget, + protected readonly widget: ListWidget, options?: IResourceNavigatorOptions ) { super(); @@ -456,8 +457,8 @@ abstract class ResourceNavigator extends Disposable { this.openOnFocus = options?.openOnFocus ?? false; this._register(Event.filter(this.widget.onDidChangeSelection, e => e.browserEvent instanceof KeyboardEvent)(e => this.onSelectionFromKeyboard(e))); - this._register(this.widget.onPointer((e: { browserEvent: MouseEvent }) => this.onPointer(e.browserEvent))); - this._register(this.widget.onMouseDblClick((e: { browserEvent: MouseEvent }) => this.onMouseDblClick(e.browserEvent))); + this._register(this.widget.onPointer((e: { browserEvent: MouseEvent, element: T | undefined }) => this.onPointer(e.element, e.browserEvent))); + this._register(this.widget.onMouseDblClick((e: { browserEvent: MouseEvent, element: T | undefined }) => this.onMouseDblClick(e.element, e.browserEvent))); if (this.openOnFocus) { this._register(Event.filter(this.widget.onDidChangeFocus, e => e.browserEvent instanceof KeyboardEvent)(e => this.onFocusFromKeyboard(e))); @@ -478,10 +479,10 @@ abstract class ResourceNavigator extends Disposable { this.widget.setSelection(focus, event.browserEvent); const preserveFocus = typeof (event.browserEvent as SelectionKeyboardEvent).preserveFocus === 'boolean' ? (event.browserEvent as SelectionKeyboardEvent).preserveFocus! : true; - const pinned = false; + const pinned = !preserveFocus; const sideBySide = false; - this._open(preserveFocus, pinned, sideBySide, event.browserEvent); + this._open(this.getSelectedElement(), preserveFocus, pinned, sideBySide, event.browserEvent); } private onSelectionFromKeyboard(event: ITreeEvent): void { @@ -490,13 +491,13 @@ abstract class ResourceNavigator extends Disposable { } const preserveFocus = typeof (event.browserEvent as SelectionKeyboardEvent).preserveFocus === 'boolean' ? (event.browserEvent as SelectionKeyboardEvent).preserveFocus! : true; - const pinned = false; + const pinned = !preserveFocus; const sideBySide = false; - this._open(preserveFocus, pinned, sideBySide, event.browserEvent); + this._open(this.getSelectedElement(), preserveFocus, pinned, sideBySide, event.browserEvent); } - private onPointer(browserEvent: MouseEvent): void { + private onPointer(element: T | undefined, browserEvent: MouseEvent): void { if (!this.openOnSingleClick) { return; } @@ -512,10 +513,10 @@ abstract class ResourceNavigator extends Disposable { const pinned = isMiddleClick; const sideBySide = browserEvent.ctrlKey || browserEvent.metaKey || browserEvent.altKey; - this._open(preserveFocus, pinned, sideBySide, browserEvent); + this._open(element, preserveFocus, pinned, sideBySide, browserEvent); } - private onMouseDblClick(browserEvent?: MouseEvent): void { + private onMouseDblClick(element: T | undefined, browserEvent?: MouseEvent): void { if (!browserEvent) { return; } @@ -524,10 +525,14 @@ abstract class ResourceNavigator extends Disposable { const pinned = true; const sideBySide = (browserEvent.ctrlKey || browserEvent.metaKey || browserEvent.altKey); - this._open(preserveFocus, pinned, sideBySide, browserEvent); + this._open(element, preserveFocus, pinned, sideBySide, browserEvent); } - private _open(preserveFocus: boolean, pinned: boolean, sideBySide: boolean, browserEvent?: UIEvent): void { + private _open(element: T | undefined, preserveFocus: boolean, pinned: boolean, sideBySide: boolean, browserEvent?: UIEvent): void { + if (!element) { + return; + } + this._onDidOpen.fire({ editorOptions: { preserveFocus, @@ -535,27 +540,39 @@ abstract class ResourceNavigator extends Disposable { revealIfVisible: true }, sideBySide, - element: this.widget.getSelection()[0], + element, browserEvent }); } + + abstract getSelectedElement(): T | undefined; } -export class ListResourceNavigator extends ResourceNavigator { +export class ListResourceNavigator extends ResourceNavigator { + constructor( - list: List | PagedList, + protected readonly widget: List | PagedList, options?: IResourceNavigatorOptions ) { - super(list, options); + super(widget, options); + } + + getSelectedElement(): T | undefined { + return this.widget.getSelectedElements()[0]; } } class TreeResourceNavigator extends ResourceNavigator { + constructor( - tree: ObjectTree | CompressibleObjectTree | DataTree | AsyncDataTree | CompressibleAsyncDataTree, + protected readonly widget: ObjectTree | CompressibleObjectTree | DataTree | AsyncDataTree | CompressibleAsyncDataTree, options: IResourceNavigatorOptions ) { - super(tree, options); + super(widget, options); + } + + getSelectedElement(): T | undefined { + return this.widget.getSelection()[0] ?? undefined; } } @@ -590,7 +607,7 @@ export class WorkbenchObjectTree, TFilterData = void> private internals: WorkbenchTreeInternals; get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; } get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; } - get onDidOpen(): Event> { return this.internals.onDidOpen; } + get onDidOpen(): Event> { return this.internals.onDidOpen; } constructor( user: string, @@ -626,7 +643,7 @@ export class WorkbenchCompressibleObjectTree, TFilter private internals: WorkbenchTreeInternals; get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; } get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; } - get onDidOpen(): Event> { return this.internals.onDidOpen; } + get onDidOpen(): Event> { return this.internals.onDidOpen; } constructor( user: string, @@ -670,7 +687,7 @@ export class WorkbenchDataTree extends DataTree; get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; } get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; } - get onDidOpen(): Event> { return this.internals.onDidOpen; } + get onDidOpen(): Event> { return this.internals.onDidOpen; } constructor( user: string, @@ -715,7 +732,7 @@ export class WorkbenchAsyncDataTree extends Async private internals: WorkbenchTreeInternals; get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; } get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; } - get onDidOpen(): Event> { return this.internals.onDidOpen; } + get onDidOpen(): Event> { return this.internals.onDidOpen; } constructor( user: string, @@ -757,7 +774,7 @@ export class WorkbenchCompressibleAsyncDataTree e private internals: WorkbenchTreeInternals; get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; } get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; } - get onDidOpen(): Event> { return this.internals.onDidOpen; } + get onDidOpen(): Event> { return this.internals.onDidOpen; } constructor( user: string, @@ -831,7 +848,8 @@ function workbenchTreeDataPreamble(treeExpandMode) === 'doubleClick') } as TOptions }; } @@ -847,7 +865,7 @@ class WorkbenchTreeInternals { private styler: IDisposable | undefined; private navigator: TreeResourceNavigator; - get onDidOpen(): Event> { return this.navigator.onDidOpen; } + get onDidOpen(): Event> { return this.navigator.onDidOpen; } constructor( private tree: WorkbenchObjectTree | WorkbenchCompressibleObjectTree | WorkbenchDataTree | WorkbenchAsyncDataTree | WorkbenchCompressibleAsyncDataTree, @@ -933,6 +951,9 @@ class WorkbenchTreeInternals { if (e.affectsConfiguration(openModeSettingKey)) { newOptions = { ...newOptions, expandOnlyOnDoubleClick: configurationService.getValue(openModeSettingKey) === 'doubleClick' }; } + if (e.affectsConfiguration(treeExpandMode) && options.expandOnlyOnTwistieClick === undefined) { + newOptions = { ...newOptions, expandOnlyOnTwistieClick: configurationService.getValue<'singleClick' | 'doubleClick'>(treeExpandMode) === 'doubleClick' }; + } if (Object.keys(newOptions).length > 0) { tree.updateOptions(newOptions); } @@ -1036,6 +1057,12 @@ configurationRegistry.registerConfiguration({ 'type': 'boolean', 'default': true, markdownDescription: localize('automatic keyboard navigation setting', "Controls whether keyboard navigation in lists and trees is automatically triggered simply by typing. If set to `false`, keyboard navigation is only triggered when executing the `list.toggleKeyboardNavigation` command, for which you can assign a keyboard shortcut.") + }, + [treeExpandMode]: { + type: 'string', + enum: ['singleClick', 'doubleClick'], + default: 'singleClick', + description: localize('expand mode', "Controls how tree folders are expanded when clicking the folder names."), } } }); diff --git a/src/vs/platform/log/common/fileLogService.ts b/src/vs/platform/log/common/fileLogService.ts index a6318446b..7f2665b51 100644 --- a/src/vs/platform/log/common/fileLogService.ts +++ b/src/vs/platform/log/common/fileLogService.ts @@ -5,7 +5,7 @@ import { ILogService, LogLevel, AbstractLogService, ILoggerService, ILogger } from 'vs/platform/log/common/log'; import { URI } from 'vs/base/common/uri'; -import { FileOperationError, FileOperationResult, IFileService, whenProviderRegistered } from 'vs/platform/files/common/files'; +import { ByteSize, FileOperationError, FileOperationResult, IFileService, whenProviderRegistered } from 'vs/platform/files/common/files'; import { Queue } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { dirname, joinPath, basename } from 'vs/base/common/resources'; @@ -13,7 +13,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { BufferLogService } from 'vs/platform/log/common/bufferLog'; -const MAX_FILE_SIZE = 1024 * 1024 * 5; +const MAX_FILE_SIZE = 5 * ByteSize.MB; export class FileLogService extends AbstractLogService implements ILogService { diff --git a/src/vs/platform/log/common/log.ts b/src/vs/platform/log/common/log.ts index add6b7666..67462e8f9 100644 --- a/src/vs/platform/log/common/log.ts +++ b/src/vs/platform/log/common/log.ts @@ -419,23 +419,43 @@ export function getLogLevel(environmentService: IEnvironmentService): LogLevel { return LogLevel.Trace; } if (typeof environmentService.logLevel === 'string') { - const logLevel = environmentService.logLevel.toLowerCase(); - switch (logLevel) { - case 'trace': - return LogLevel.Trace; - case 'debug': - return LogLevel.Debug; - case 'info': - return LogLevel.Info; - case 'warn': - return LogLevel.Warning; - case 'error': - return LogLevel.Error; - case 'critical': - return LogLevel.Critical; - case 'off': - return LogLevel.Off; + const logLevel = parseLogLevel(environmentService.logLevel.toLowerCase()); + if (logLevel !== undefined) { + return logLevel; } } return DEFAULT_LOG_LEVEL; } + +export function parseLogLevel(logLevel: string): LogLevel | undefined { + switch (logLevel) { + case 'trace': + return LogLevel.Trace; + case 'debug': + return LogLevel.Debug; + case 'info': + return LogLevel.Info; + case 'warn': + return LogLevel.Warning; + case 'error': + return LogLevel.Error; + case 'critical': + return LogLevel.Critical; + case 'off': + return LogLevel.Off; + } + return undefined; +} + +export function LogLevelToString(logLevel: LogLevel): string { + switch (logLevel) { + case LogLevel.Trace: return 'trace'; + case LogLevel.Debug: return 'debug'; + case LogLevel.Info: return 'info'; + case LogLevel.Warning: return 'warn'; + case LogLevel.Error: return 'error'; + case LogLevel.Critical: return 'critical'; + case LogLevel.Off: return 'off'; + } +} + diff --git a/src/vs/platform/log/node/spdlogService.ts b/src/vs/platform/log/node/spdlogService.ts index c32ab07af..b011f2423 100644 --- a/src/vs/platform/log/node/spdlogService.ts +++ b/src/vs/platform/log/node/spdlogService.ts @@ -6,6 +6,7 @@ import * as path from 'vs/base/common/path'; import { ILogService, LogLevel, AbstractLogService } from 'vs/platform/log/common/log'; import * as spdlog from 'spdlog'; +import { ByteSize } from 'vs/platform/files/common/files'; async function createSpdLogLogger(processName: string, logsFolder: string): Promise { // Do not crash if spdlog cannot be loaded @@ -13,7 +14,7 @@ async function createSpdLogLogger(processName: string, logsFolder: string): Prom const _spdlog = await import('spdlog'); _spdlog.setAsyncMode(8192, 500); const logfilePath = path.join(logsFolder, `${processName}.log`); - return _spdlog.createRotatingLoggerAsync(processName, logfilePath, 1024 * 1024 * 5, 6); + return _spdlog.createRotatingLoggerAsync(processName, logfilePath, 5 * ByteSize.MB, 6); } catch (e) { console.error(e); } diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 4ecb37251..3039b01a1 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -24,6 +24,7 @@ import { IStateService } from 'vs/platform/state/node/state'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService'; +import { CancellationToken } from 'vs/base/common/cancellation'; const telemetryFrom = 'menu'; @@ -82,7 +83,7 @@ export class Menubar { this.menubarMenus = Object.create(null); this.keybindings = Object.create(null); - if (isMacintosh || getTitleBarStyle(this.configurationService, this.environmentService) === 'native') { + if (isMacintosh || getTitleBarStyle(this.configurationService) === 'native') { this.restoreCachedMenubarData(); } @@ -416,7 +417,7 @@ export class Menubar { private shouldDrawMenu(menuId: string): boolean { // We need to draw an empty menu to override the electron default - if (!isMacintosh && getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { + if (!isMacintosh && getTitleBarStyle(this.configurationService) === 'custom') { return false; } @@ -754,10 +755,10 @@ export class Menubar { if (invocation.type === 'commandId') { const runActionPayload: INativeRunActionInWindowRequest = { id: invocation.commandId, from: 'menu' }; - activeWindow.sendWhenReady('vscode:runAction', runActionPayload); + activeWindow.sendWhenReady('vscode:runAction', CancellationToken.None, runActionPayload); } else { const runKeybindingPayload: INativeRunKeybindingInWindowRequest = { userSettingsLabel: invocation.userSettingsLabel }; - activeWindow.sendWhenReady('vscode:runKeybinding', runKeybindingPayload); + activeWindow.sendWhenReady('vscode:runKeybinding', CancellationToken.None, runKeybindingPayload); } } else { this.logService.trace('menubar#runActionInRenderer: no active window found', invocation); diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index c71a369e9..e24efa83c 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -10,7 +10,7 @@ import { OpenContext } from 'vs/platform/windows/node/window'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IOpenedWindow, IOpenWindowOptions, IWindowOpenable, IOpenEmptyWindowOptions, IColorScheme } from 'vs/platform/windows/common/windows'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; -import { isMacintosh, isWindows, isRootUser, isLinux } from 'vs/base/common/platform'; +import { isMacintosh, isWindows, isLinux } from 'vs/base/common/platform'; import { ICommonNativeHostService, IOSProperties, IOSStatistics } from 'vs/platform/native/common/native'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; @@ -364,7 +364,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain if (isWindows) { isAdmin = (await import('native-is-elevated'))(); } else { - isAdmin = isRootUser(); + isAdmin = process.getuid() === 0; } return isAdmin; @@ -550,7 +550,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain async reload(windowId: number | undefined, options?: { disableExtensions?: boolean }): Promise { const window = this.windowById(windowId); if (window) { - return this.lifecycleMainService.reload(window, options?.disableExtensions ? { _: [], 'disable-extensions': true } : undefined); + return this.lifecycleMainService.reload(window, options?.disableExtensions !== undefined ? { _: [], 'disable-extensions': options?.disableExtensions } : undefined); } } @@ -615,11 +615,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain const window = this.windowById(windowId); if (window) { const contents = window.win.webContents; - if (isMacintosh && window.hasHiddenTitleBarStyle && !window.isFullScreen && !contents.isDevToolsOpened()) { - contents.openDevTools({ mode: 'undocked' }); // due to https://github.com/electron/electron/issues/3647 - } else { - contents.toggleDevTools(); - } + contents.toggleDevTools(); } } diff --git a/src/vs/platform/notification/common/notification.ts b/src/vs/platform/notification/common/notification.ts index cc3cb1077..74c263638 100644 --- a/src/vs/platform/notification/common/notification.ts +++ b/src/vs/platform/notification/common/notification.ts @@ -114,6 +114,8 @@ export interface INotificationActions { /** * Primary actions show up as buttons as part of the message and will close * the notification once clicked. + * + * Pass `ActionWithMenuAction` for an action that has additional menu actions. */ readonly primary?: ReadonlyArray; @@ -209,19 +211,13 @@ export interface INotificationHandle { close(): void; } -export interface IPromptChoice { +interface IBasePromptChoice { /** * Label to show for the choice to the user. */ readonly label: string; - /** - * Primary choices show up as buttons in the notification below the message. - * Secondary choices show up under the gear icon in the header of the notification. - */ - readonly isSecondary?: boolean; - /** * Whether to keep the notification open after the choice was selected * by the user. By default, will close the notification upon click. @@ -234,6 +230,28 @@ export interface IPromptChoice { run: () => void; } +export interface IPromptChoice extends IBasePromptChoice { + + /** + * Primary choices show up as buttons in the notification below the message. + * Secondary choices show up under the gear icon in the header of the notification. + */ + readonly isSecondary?: boolean; +} + +export interface IPromptChoiceWithMenu extends IPromptChoice { + + /** + * Additional choices those will be shown in the dropdown menu for this choice. + */ + readonly menu: IBasePromptChoice[]; + + /** + * Menu is not supported on secondary choices + */ + readonly isSecondary: false | undefined; +} + export interface IPromptOptions extends INotificationProperties { /** @@ -327,7 +345,7 @@ export interface INotificationService { * * @returns a handle on the notification to e.g. hide it or update message, buttons, etc. */ - prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions): INotificationHandle; + prompt(severity: Severity, message: string, choices: (IPromptChoice | IPromptChoiceWithMenu)[], options?: IPromptOptions): INotificationHandle; /** * Shows a status message in the status area with the provided text. diff --git a/src/vs/platform/opener/common/opener.ts b/src/vs/platform/opener/common/opener.ts index 977cb79b8..d82651f93 100644 --- a/src/vs/platform/opener/common/opener.ts +++ b/src/vs/platform/opener/common/opener.ts @@ -7,6 +7,7 @@ import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { equalsIgnoreCase, startsWithIgnoreCase } from 'vs/base/common/strings'; +import { IEditorOptions } from 'vs/platform/editor/common/editor'; export const IOpenerService = createDecorator('openerService'); @@ -18,6 +19,11 @@ type OpenInternalOptions = { */ readonly openToSide?: boolean; + /** + * Extra editor options to apply in case an editor is used to open. + */ + readonly editorOptions?: IEditorOptions; + /** * Signals that the editor to open was triggered through a user * action, such as keyboard or mouse usage. diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 2bea85740..56fbc68ad 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -20,7 +20,7 @@ if (isWeb || typeof require === 'undefined' || typeof require.__$__nodeRequire ! // Running out of sources if (Object.keys(product).length === 0) { Object.assign(product, { - version: '1.51.0-dev', + version: '1.52.0-dev', nameShort: isWeb ? 'Code Web - OSS Dev' : 'Code - OSS Dev', nameLong: isWeb ? 'Code Web - OSS Dev' : 'Code - OSS Dev', applicationName: 'code-oss', @@ -32,7 +32,6 @@ if (isWeb || typeof require === 'undefined' || typeof require.__$__nodeRequire ! extensionAllowedProposedApi: [ 'ms-vscode.vscode-js-profile-flame', 'ms-vscode.vscode-js-profile-table', - 'ms-vscode.references-view', 'ms-vscode.github-browser' ], }); diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts index 333e5b24b..74a8c744a 100644 --- a/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts @@ -80,6 +80,7 @@ export interface IProductConfiguration { readonly remoteExtensionTips?: { [remoteName: string]: IRemoteExtensionTip; }; readonly extensionKeywords?: { [extension: string]: readonly string[]; }; readonly keymapExtensionTips?: readonly string[]; + readonly trustedExtensionUrlPublicKeys?: { [id: string]: string[]; }; readonly crashReporter?: { readonly companyName: string; diff --git a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts index 18b7dc26e..a1d27c5ac 100644 --- a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts @@ -11,7 +11,7 @@ import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecyc import { or, matchesPrefix, matchesWords, matchesContiguousSubString } from 'vs/base/common/filters'; import { withNullAsUndefined } from 'vs/base/common/types'; import { LRUCache } from 'vs/base/common/map'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -21,7 +21,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; export interface ICommandQuickPick extends IPickerQuickAccessItem { commandId: string; @@ -204,14 +203,9 @@ export class CommandsHistory extends Disposable { constructor( @IStorageService private readonly storageService: IStorageService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { super(); - // opt-in to syncing - storageKeysSyncRegistryService.registerStorageKey({ key: CommandsHistory.PREF_KEY_CACHE, version: 1 }); - storageKeysSyncRegistryService.registerStorageKey({ key: CommandsHistory.PREF_KEY_COUNTER, version: 1 }); - this.updateConfiguration(); this.load(); @@ -279,8 +273,8 @@ export class CommandsHistory extends Disposable { const serializedCache: ISerializedCommandHistory = { usesLRU: true, entries: [] }; CommandsHistory.cache.forEach((value, key) => serializedCache.entries.push({ key, value })); - storageService.store(CommandsHistory.PREF_KEY_CACHE, JSON.stringify(serializedCache), StorageScope.GLOBAL); - storageService.store(CommandsHistory.PREF_KEY_COUNTER, CommandsHistory.counter, StorageScope.GLOBAL); + storageService.store(CommandsHistory.PREF_KEY_CACHE, JSON.stringify(serializedCache), StorageScope.GLOBAL, StorageTarget.USER); + storageService.store(CommandsHistory.PREF_KEY_COUNTER, CommandsHistory.counter, StorageScope.GLOBAL, StorageTarget.USER); } static getConfiguredCommandHistoryLength(configurationService: IConfigurationService): number { diff --git a/src/vs/platform/remote/common/tunnel.ts b/src/vs/platform/remote/common/tunnel.ts index 52dfacbc4..980c3203a 100644 --- a/src/vs/platform/remote/common/tunnel.ts +++ b/src/vs/platform/remote/common/tunnel.ts @@ -26,8 +26,12 @@ export interface TunnelOptions { label?: string; } +export interface TunnelCreationOptions { + elevationRequired?: boolean; +} + export interface ITunnelProvider { - forwardPort(tunnelOptions: TunnelOptions): Promise | undefined; + forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise | undefined; } export interface ITunnelService { @@ -56,8 +60,14 @@ export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address: }; } +export const LOCALHOST_ADDRESSES = ['localhost', '127.0.0.1', '0:0:0:0:0:0:0:1', '::1']; export function isLocalhost(host: string): boolean { - return host === 'localhost' || host === '127.0.0.1'; + return LOCALHOST_ADDRESSES.indexOf(host) >= 0; +} + +export const ALL_INTERFACES_ADDRESSES = ['0.0.0.0', '0:0:0:0:0:0:0:0', '::']; +export function isAllInterfaces(host: string): boolean { + return ALL_INTERFACES_ADDRESSES.indexOf(host) >= 0; } function getOtherLocalhost(host: string): string | undefined { @@ -198,6 +208,10 @@ export abstract class AbstractTunnelService implements ITunnelService { } protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort?: number): Promise | undefined; + + protected isPortPrivileged(port: number): boolean { + return port < 1024; + } } export class TunnelService extends AbstractTunnelService { @@ -209,7 +223,10 @@ export class TunnelService extends AbstractTunnelService { } if (this._tunnelProvider) { - const tunnel = this._tunnelProvider.forwardPort({ remoteAddress: { host: remoteHost, port: remotePort } }); + const preferredLocalPort = localPort === undefined ? remotePort : localPort; + const tunnelOptions = { remoteAddress: { host: remoteHost, port: remotePort }, localAddressPort: localPort }; + const creationInfo = { elevationRequired: this.isPortPrivileged(preferredLocalPort) }; + const tunnel = this._tunnelProvider.forwardPort(tunnelOptions, creationInfo); if (tunnel) { this.addTunnelToMap(remoteHost, remotePort, tunnel); } diff --git a/src/vs/platform/remote/node/tunnelService.ts b/src/vs/platform/remote/node/tunnelService.ts index 0a27aabb5..19ec1efd6 100644 --- a/src/vs/platform/remote/node/tunnelService.ts +++ b/src/vs/platform/remote/node/tunnelService.ts @@ -146,7 +146,10 @@ export class TunnelService extends AbstractTunnelService { } if (this._tunnelProvider) { - const tunnel = this._tunnelProvider.forwardPort({ remoteAddress: { host: remoteHost, port: remotePort }, localAddressPort: localPort }); + const preferredLocalPort = localPort === undefined ? remotePort : localPort; + const creationInfo = { elevationRequired: this.isPortPrivileged(preferredLocalPort) }; + const tunnelOptions = { remoteAddress: { host: remoteHost, port: remotePort }, localAddressPort: localPort }; + const tunnel = this._tunnelProvider.forwardPort(tunnelOptions, creationInfo); if (tunnel) { this.addTunnelToMap(remoteHost, remotePort, tunnel); } diff --git a/src/vs/platform/serviceMachineId/common/serviceMachineId.ts b/src/vs/platform/serviceMachineId/common/serviceMachineId.ts index 97abad373..c760a13bb 100644 --- a/src/vs/platform/serviceMachineId/common/serviceMachineId.ts +++ b/src/vs/platform/serviceMachineId/common/serviceMachineId.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { IFileService } from 'vs/platform/files/common/files'; -import { StorageScope } from 'vs/platform/storage/common/storage'; +import { StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { isUUID, generateUuid } from 'vs/base/common/uuid'; import { VSBuffer } from 'vs/base/common/buffer'; export async function getServiceMachineId(environmentService: IEnvironmentService, fileService: IFileService, storageService: { get: (key: string, scope: StorageScope, fallbackValue?: string | undefined) => string | undefined, - store: (key: string, value: string, scope: StorageScope) => void + store: (key: string, value: string, scope: StorageScope, target: StorageTarget) => void } | undefined): Promise { let uuid: string | null = storageService ? storageService.get('storage.serviceMachineId', StorageScope.GLOBAL) || null : null; if (uuid) { @@ -34,7 +34,7 @@ export async function getServiceMachineId(environmentService: IEnvironmentServic } } if (storageService) { - storageService.store('storage.serviceMachineId', uuid, StorageScope.GLOBAL); + storageService.store('storage.serviceMachineId', uuid, StorageScope.GLOBAL, StorageTarget.MACHINE); } return uuid; } diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts index ab3fd347b..c667dd88f 100644 --- a/src/vs/platform/storage/browser/storageService.ts +++ b/src/vs/platform/storage/browser/storageService.ts @@ -5,7 +5,7 @@ import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; -import { IWorkspaceStorageChangeEvent, IStorageService, StorageScope, IWillSaveStateEvent, WillSaveStateReason, logStorage, IS_NEW_KEY } from 'vs/platform/storage/common/storage'; +import { StorageScope, logStorage, IS_NEW_KEY, AbstractStorageService } from 'vs/platform/storage/common/storage'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; import { IFileService, FileChangeType } from 'vs/platform/files/common/files'; @@ -16,15 +16,7 @@ import { runWhenIdle, RunOnceScheduler } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; -export class BrowserStorageService extends Disposable implements IStorageService { - - declare readonly _serviceBrand: undefined; - - private readonly _onDidChangeStorage = this._register(new Emitter()); - readonly onDidChangeStorage = this._onDidChangeStorage.event; - - private readonly _onWillSaveState = this._register(new Emitter()); - readonly onWillSaveState = this._onWillSaveState.event; +export class BrowserStorageService extends AbstractStorageService { private globalStorage: IStorage | undefined; private workspaceStorage: IStorage | undefined; @@ -40,6 +32,10 @@ export class BrowserStorageService extends Disposable implements IStorageService private readonly periodicFlushScheduler = this._register(new RunOnceScheduler(() => this.doFlushWhenIdle(), 5000 /* every 5s */)); private runWhenIdleDisposable: IDisposable | undefined = undefined; + get hasPendingUpdate(): boolean { + return (!!this.globalStorageDatabase && this.globalStorageDatabase.hasPendingUpdate) || (!!this.workspaceStorageDatabase && this.workspaceStorageDatabase.hasPendingUpdate); + } + constructor( @IEnvironmentService private readonly environmentService: IEnvironmentService, @IFileService private readonly fileService: IFileService @@ -66,13 +62,13 @@ export class BrowserStorageService extends Disposable implements IStorageService this.workspaceStorageDatabase = this._register(new FileStorageDatabase(this.workspaceStorageFile, false /* do not watch for external changes */, this.fileService)); this.workspaceStorage = this._register(new Storage(this.workspaceStorageDatabase)); - this._register(this.workspaceStorage.onDidChangeStorage(key => this._onDidChangeStorage.fire({ key, scope: StorageScope.WORKSPACE }))); + this._register(this.workspaceStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.WORKSPACE, key))); // Global Storage this.globalStorageFile = joinPath(stateRoot, 'global.json'); this.globalStorageDatabase = this._register(new FileStorageDatabase(this.globalStorageFile, true /* watch for external changes */, this.fileService)); this.globalStorage = this._register(new Storage(this.globalStorageDatabase)); - this._register(this.globalStorage.onDidChangeStorage(key => this._onDidChangeStorage.fire({ key, scope: StorageScope.GLOBAL }))); + this._register(this.globalStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.GLOBAL, key))); // Init both await Promise.all([ @@ -122,11 +118,11 @@ export class BrowserStorageService extends Disposable implements IStorageService return this.getStorage(scope).getNumber(key, fallbackValue); } - store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): void { + protected doStore(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): void { this.getStorage(scope).set(key, value); } - remove(key: string, scope: StorageScope): void { + protected doRemove(key: string, scope: StorageScope): void { this.getStorage(scope).delete(key); } @@ -149,6 +145,13 @@ export class BrowserStorageService extends Disposable implements IStorageService throw new Error('Migrating storage is currently unsupported in Web'); } + protected async doFlush(): Promise { + await Promise.all([ + this.getStorage(StorageScope.GLOBAL).whenFlushed(), + this.getStorage(StorageScope.WORKSPACE).whenFlushed() + ]); + } + private doFlushWhenIdle(): void { // Dispose any previous idle runner @@ -175,14 +178,6 @@ export class BrowserStorageService extends Disposable implements IStorageService }); } - get hasPendingUpdate(): boolean { - return (!!this.globalStorageDatabase && this.globalStorageDatabase.hasPendingUpdate) || (!!this.workspaceStorageDatabase && this.workspaceStorageDatabase.hasPendingUpdate); - } - - flush(): void { - this._onWillSaveState.fire({ reason: WillSaveStateReason.NONE }); - } - close(): void { // We explicitly do not close our DBs because writing data onBeforeUnload() // can result in unexpected results. Namely, it seems that - even though this @@ -195,10 +190,6 @@ export class BrowserStorageService extends Disposable implements IStorageService this.dispose(); } - isNew(scope: StorageScope): boolean { - return this.getBoolean(IS_NEW_KEY, scope) === true; - } - dispose(): void { dispose(this.runWhenIdleDisposable); this.runWhenIdleDisposable = undefined; diff --git a/src/vs/platform/storage/common/storage.ts b/src/vs/platform/storage/common/storage.ts index 6611f1dae..3d176f5b6 100644 --- a/src/vs/platform/storage/common/storage.ts +++ b/src/vs/platform/storage/common/storage.ts @@ -4,18 +4,27 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Event, Emitter, PauseableEmitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; export const IS_NEW_KEY = '__$__isNewStorageMarker'; +const TARGET_KEY = '__$__targetStorageMarker'; export const IStorageService = createDecorator('storageService'); export enum WillSaveStateReason { - NONE = 0, - SHUTDOWN = 1 + + /** + * No specific reason to save state. + */ + NONE, + + /** + * A hint that the workbench is about to shutdown. + */ + SHUTDOWN } export interface IWillSaveStateEvent { @@ -29,7 +38,12 @@ export interface IStorageService { /** * Emitted whenever data is updated or deleted. */ - readonly onDidChangeStorage: Event; + readonly onDidChangeValue: Event; + + /** + * Emitted whenever target of a storage entry changes. + */ + readonly onDidChangeTarget: Event; /** * Emitted when the storage is about to persist. This is the right time @@ -48,44 +62,49 @@ export interface IStorageService { /** * Retrieve an element stored with the given key from storage. Use - * the provided defaultValue if the element is null or undefined. + * the provided `defaultValue` if the element is `null` or `undefined`. * - * The scope argument allows to define the scope of the storage - * operation to either the current workspace only or all workspaces. + * @param scope allows to define the scope of the storage operation + * to either the current workspace only or all workspaces. */ get(key: string, scope: StorageScope, fallbackValue: string): string; get(key: string, scope: StorageScope, fallbackValue?: string): string | undefined; /** * Retrieve an element stored with the given key from storage. Use - * the provided defaultValue if the element is null or undefined. The element - * will be converted to a boolean. + * the provided `defaultValue` if the element is `null` or `undefined`. + * The element will be converted to a `boolean`. * - * The scope argument allows to define the scope of the storage - * operation to either the current workspace only or all workspaces. + * @param scope allows to define the scope of the storage operation + * to either the current workspace only or all workspaces. */ getBoolean(key: string, scope: StorageScope, fallbackValue: boolean): boolean; getBoolean(key: string, scope: StorageScope, fallbackValue?: boolean): boolean | undefined; /** * Retrieve an element stored with the given key from storage. Use - * the provided defaultValue if the element is null or undefined. The element - * will be converted to a number using parseInt with a base of 10. + * the provided `defaultValue` if the element is `null` or `undefined`. + * The element will be converted to a `number` using `parseInt` with a + * base of `10`. * - * The scope argument allows to define the scope of the storage - * operation to either the current workspace only or all workspaces. + * @param scope allows to define the scope of the storage operation + * to either the current workspace only or all workspaces. */ getNumber(key: string, scope: StorageScope, fallbackValue: number): number; getNumber(key: string, scope: StorageScope, fallbackValue?: number): number | undefined; /** - * Store a value under the given key to storage. The value will be converted to a string. - * Storing either undefined or null will remove the entry under the key. + * Store a value under the given key to storage. The value will be + * converted to a `string`. Storing either `undefined` or `null` will + * remove the entry under the key. * - * The scope argument allows to define the scope of the storage - * operation to either the current workspace only or all workspaces. + * @param scope allows to define the scope of the storage operation + * to either the current workspace only or all workspaces. + * + * @param target allows to define the target of the storage operation + * to either the current machine or user. */ - store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): void; + store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope, target: StorageTarget): void; /** * Delete an element stored under the provided key from storage. @@ -95,6 +114,22 @@ export interface IStorageService { */ remove(key: string, scope: StorageScope): void; + /** + * Returns all the keys used in the storage for the provided `scope` + * and `target`. + * + * Note: this will NOT return all keys stored in the storage layer. + * Some keys may not have an associated `StorageTarget` and thus + * will be excluded from the results. + * + * @param scope allows to define the scope for the keys + * to either the current workspace only or all workspaces. + * + * @param target allows to define the target for the keys + * to either the current machine or user. + */ + keys(scope: StorageScope, target: StorageTarget): string[]; + /** * Log the contents of the storage to the console. */ @@ -108,16 +143,18 @@ export interface IStorageService { /** * Whether the storage for the given scope was created during this session or * existed before. - * */ isNew(scope: StorageScope): boolean; /** * Allows to flush state, e.g. in cases where a shutdown is - * imminent. This will send out the onWillSaveState to ask + * imminent. This will send out the `onWillSaveState` to ask * everyone for latest state. + * + * @returns a `Promise` that can be awaited on when all updates + * to the underlying storage have been flushed. */ - flush(): void; + flush(): Promise; } export const enum StorageScope { @@ -133,21 +170,247 @@ export const enum StorageScope { WORKSPACE } -export interface IWorkspaceStorageChangeEvent { +export const enum StorageTarget { + + /** + * The stored data is user specific and applies across machines. + */ + USER, + + /** + * The stored data is machine specific. + */ + MACHINE +} + +export interface IStorageValueChangeEvent { + + /** + * The scope for the storage entry that changed + * or was removed. + */ + readonly scope: StorageScope; + + /** + * The `key` of the storage entry that was changed + * or was removed. + */ readonly key: string; + + /** + * The `target` can be `undefined` if a key is being + * removed. + */ + readonly target: StorageTarget | undefined; +} + +export interface IStorageTargetChangeEvent { + + /** + * The scope for the target that changed. Listeners + * should use `keys(scope, target)` to get an updated + * list of keys for the given `scope` and `target`. + */ readonly scope: StorageScope; } -export class InMemoryStorageService extends Disposable implements IStorageService { +interface IKeyTargets { + [key: string]: StorageTarget +} + +export abstract class AbstractStorageService extends Disposable implements IStorageService { declare readonly _serviceBrand: undefined; - private readonly _onDidChangeStorage = this._register(new Emitter()); - readonly onDidChangeStorage = this._onDidChangeStorage.event; + private readonly _onDidChangeValue = this._register(new PauseableEmitter()); + readonly onDidChangeValue = this._onDidChangeValue.event; - protected readonly _onWillSaveState = this._register(new Emitter()); + private readonly _onDidChangeTarget = this._register(new PauseableEmitter()); + readonly onDidChangeTarget = this._onDidChangeTarget.event; + + private readonly _onWillSaveState = this._register(new Emitter()); readonly onWillSaveState = this._onWillSaveState.event; + protected emitDidChangeValue(scope: StorageScope, key: string): void { + + // Specially handle `TARGET_KEY` + if (key === TARGET_KEY) { + + // Clear our cached version which is now out of date + if (scope === StorageScope.GLOBAL) { + this._globalKeyTargets = undefined; + } else if (scope === StorageScope.WORKSPACE) { + this._workspaceKeyTargets = undefined; + } + + // Emit as `didChangeTarget` event + this._onDidChangeTarget.fire({ scope }); + } + + // Emit any other key to outside + else { + this._onDidChangeValue.fire({ scope, key, target: this.getKeyTargets(scope)[key] }); + } + } + + protected emitWillSaveState(reason: WillSaveStateReason): void { + this._onWillSaveState.fire({ reason }); + } + + store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope, target: StorageTarget): void { + + // We remove the key for undefined/null values + if (isUndefinedOrNull(value)) { + this.remove(key, scope); + return; + } + + // Update our datastructures but send events only after + this.withPausedEmitters(() => { + + // Update key-target map + this.updateKeyTarget(key, scope, target); + + // Store actual value + this.doStore(key, value, scope); + }); + } + + remove(key: string, scope: StorageScope): void { + + // Update our datastructures but send events only after + this.withPausedEmitters(() => { + + // Update key-target map + this.updateKeyTarget(key, scope, undefined); + + // Remove actual key + this.doRemove(key, scope); + }); + } + + private withPausedEmitters(fn: Function): void { + + // Pause emitters + this._onDidChangeValue.pause(); + this._onDidChangeTarget.pause(); + + try { + fn(); + } finally { + + // Resume emitters + this._onDidChangeValue.resume(); + this._onDidChangeTarget.resume(); + } + } + + keys(scope: StorageScope, target: StorageTarget): string[] { + const keys: string[] = []; + + const keyTargets = this.getKeyTargets(scope); + for (const key of Object.keys(keyTargets)) { + const keyTarget = keyTargets[key]; + if (keyTarget === target) { + keys.push(key); + } + } + + return keys; + } + + private updateKeyTarget(key: string, scope: StorageScope, target: StorageTarget | undefined): void { + + // Add + const keyTargets = this.getKeyTargets(scope); + if (typeof target === 'number') { + if (keyTargets[key] !== target) { + keyTargets[key] = target; + this.doStore(TARGET_KEY, JSON.stringify(keyTargets), scope); + } + } + + // Remove + else { + if (typeof keyTargets[key] === 'number') { + delete keyTargets[key]; + this.doStore(TARGET_KEY, JSON.stringify(keyTargets), scope); + } + } + } + + private _workspaceKeyTargets: IKeyTargets | undefined = undefined; + private get workspaceKeyTargets(): IKeyTargets { + if (!this._workspaceKeyTargets) { + this._workspaceKeyTargets = this.loadKeyTargets(StorageScope.WORKSPACE); + } + + return this._workspaceKeyTargets; + } + + private _globalKeyTargets: IKeyTargets | undefined = undefined; + private get globalKeyTargets(): IKeyTargets { + if (!this._globalKeyTargets) { + this._globalKeyTargets = this.loadKeyTargets(StorageScope.GLOBAL); + } + + return this._globalKeyTargets; + } + + private getKeyTargets(scope: StorageScope): IKeyTargets { + return scope === StorageScope.GLOBAL ? this.globalKeyTargets : this.workspaceKeyTargets; + } + + private loadKeyTargets(scope: StorageScope): { [key: string]: StorageTarget } { + const keysRaw = this.get(TARGET_KEY, scope); + if (keysRaw) { + try { + return JSON.parse(keysRaw); + } catch (error) { + // Fail gracefully + } + } + + return Object.create(null); + } + + isNew(scope: StorageScope): boolean { + return this.getBoolean(IS_NEW_KEY, scope) === true; + } + + flush(): Promise { + + // Signal event to collect changes + this._onWillSaveState.fire({ reason: WillSaveStateReason.NONE }); + + // Await flush + return this.doFlush(); + } + + // --- abstract + + abstract get(key: string, scope: StorageScope, fallbackValue: string): string; + abstract get(key: string, scope: StorageScope, fallbackValue?: string): string | undefined; + + abstract getBoolean(key: string, scope: StorageScope, fallbackValue: boolean): boolean; + abstract getBoolean(key: string, scope: StorageScope, fallbackValue?: boolean): boolean | undefined; + + abstract getNumber(key: string, scope: StorageScope, fallbackValue: number): number; + abstract getNumber(key: string, scope: StorageScope, fallbackValue?: number): number | undefined; + + protected abstract doStore(key: string, value: string | boolean | number, scope: StorageScope): void; + + protected abstract doRemove(key: string, scope: StorageScope): void; + + protected abstract doFlush(): Promise; + + abstract migrate(toWorkspace: IWorkspaceInitializationPayload): Promise; + + abstract logStorage(): void; +} + +export class InMemoryStorageService extends AbstractStorageService { + private readonly globalCache = new Map(); private readonly workspaceCache = new Map(); @@ -188,12 +451,7 @@ export class InMemoryStorageService extends Disposable implements IStorageServic return parseInt(value, 10); } - store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): Promise { - - // We remove the key for undefined/null values - if (isUndefinedOrNull(value)) { - return this.remove(key, scope); - } + protected doStore(key: string, value: string | boolean | number, scope: StorageScope): void { // Otherwise, convert to String and store const valueStr = String(value); @@ -201,28 +459,24 @@ export class InMemoryStorageService extends Disposable implements IStorageServic // Return early if value already set const currentValue = this.getCache(scope).get(key); if (currentValue === valueStr) { - return Promise.resolve(); + return; } // Update in cache this.getCache(scope).set(key, valueStr); // Events - this._onDidChangeStorage.fire({ scope, key }); - - return Promise.resolve(); + this.emitDidChangeValue(scope, key); } - remove(key: string, scope: StorageScope): Promise { + protected doRemove(key: string, scope: StorageScope): void { const wasDeleted = this.getCache(scope).delete(key); if (!wasDeleted) { - return Promise.resolve(); // Return early if value already deleted + return; // Return early if value already deleted } // Events - this._onDidChangeStorage.fire({ scope, key }); - - return Promise.resolve(); + this.emitDidChangeValue(scope, key); } logStorage(): void { @@ -233,13 +487,7 @@ export class InMemoryStorageService extends Disposable implements IStorageServic // not supported } - flush(): void { - this._onWillSaveState.fire({ reason: WillSaveStateReason.NONE }); - } - - isNew(): boolean { - return true; // always new when in-memory - } + async doFlush(): Promise { } async close(): Promise { } } diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts index 096b9e234..97cb9b161 100644 --- a/src/vs/platform/storage/node/storageService.ts +++ b/src/vs/platform/storage/node/storageService.ts @@ -3,10 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { Emitter } from 'vs/base/common/event'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; -import { IWorkspaceStorageChangeEvent, IStorageService, StorageScope, IWillSaveStateEvent, WillSaveStateReason, logStorage, IS_NEW_KEY } from 'vs/platform/storage/common/storage'; +import { StorageScope, WillSaveStateReason, logStorage, IS_NEW_KEY, AbstractStorageService } from 'vs/platform/storage/common/storage'; import { SQLiteStorageDatabase, ISQLiteStorageDatabaseLoggingOptions } from 'vs/base/parts/storage/node/storage'; import { Storage, IStorageDatabase, IStorage, StorageHint } from 'vs/base/parts/storage/common/storage'; import { mark } from 'vs/base/common/performance'; @@ -17,19 +16,11 @@ import { IWorkspaceInitializationPayload, isWorkspaceIdentifier, isSingleFolderW import { assertIsDefined } from 'vs/base/common/types'; import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; -export class NativeStorageService extends Disposable implements IStorageService { - - declare readonly _serviceBrand: undefined; +export class NativeStorageService extends AbstractStorageService { private static readonly WORKSPACE_STORAGE_NAME = 'state.vscdb'; private static readonly WORKSPACE_META_NAME = 'workspace.json'; - private readonly _onDidChangeStorage = this._register(new Emitter()); - readonly onDidChangeStorage = this._onDidChangeStorage.event; - - private readonly _onWillSaveState = this._register(new Emitter()); - readonly onWillSaveState = this._onWillSaveState.event; - private readonly globalStorage = new Storage(this.globalStorageDatabase); private workspaceStoragePath: string | undefined; @@ -54,11 +45,7 @@ export class NativeStorageService extends Disposable implements IStorageService private registerListeners(): void { // Global Storage change events - this._register(this.globalStorage.onDidChangeStorage(key => this.handleDidChangeStorage(key, StorageScope.GLOBAL))); - } - - private handleDidChangeStorage(key: string, scope: StorageScope): void { - this._onDidChangeStorage.fire({ key, scope }); + this._register(this.globalStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.GLOBAL, key))); } initialize(payload?: IWorkspaceInitializationPayload): Promise { @@ -134,7 +121,7 @@ export class NativeStorageService extends Disposable implements IStorageService // Create new this.workspaceStoragePath = workspaceStoragePath; this.workspaceStorage = new Storage(new SQLiteStorageDatabase(workspaceStoragePath, { logging: workspaceLoggingOptions }), { hint }); - this.workspaceStorageListener = this.workspaceStorage.onDidChangeStorage(key => this.handleDidChangeStorage(key, StorageScope.WORKSPACE)); + this.workspaceStorageListener = this.workspaceStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.WORKSPACE, key)); return this.workspaceStorage; } @@ -201,11 +188,11 @@ export class NativeStorageService extends Disposable implements IStorageService return this.getStorage(scope).getNumber(key, fallbackValue); } - store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): void { + protected doStore(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): void { this.getStorage(scope).set(key, value); } - remove(key: string, scope: StorageScope): void { + protected doRemove(key: string, scope: StorageScope): void { this.getStorage(scope).delete(key); } @@ -213,6 +200,19 @@ export class NativeStorageService extends Disposable implements IStorageService return assertIsDefined(scope === StorageScope.GLOBAL ? this.globalStorage : this.workspaceStorage); } + protected async doFlush(): Promise { + const promises: Promise[] = []; + if (this.globalStorage) { + promises.push(this.globalStorage.whenFlushed()); + } + + if (this.workspaceStorage) { + promises.push(this.workspaceStorage.whenFlushed()); + } + + await Promise.all(promises); + } + private doFlushWhenIdle(): void { // Dispose any previous idle runner @@ -229,10 +229,6 @@ export class NativeStorageService extends Disposable implements IStorageService }); } - flush(): void { - this._onWillSaveState.fire({ reason: WillSaveStateReason.NONE }); - } - async close(): Promise { // Stop periodic scheduler and idle runner as we now collect state normally @@ -241,7 +237,7 @@ export class NativeStorageService extends Disposable implements IStorageService this.runWhenIdleDisposable = undefined; // Signal as event so that clients can still store data - this._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); + this.emitWillSaveState(WillSaveStateReason.SHUTDOWN); // Do it await Promise.all([ @@ -277,8 +273,4 @@ export class NativeStorageService extends Disposable implements IStorageService // Recreate and init workspace storage return this.createWorkspaceStorage(newWorkspaceStoragePath).init(); } - - isNew(scope: StorageScope): boolean { - return this.getBoolean(IS_NEW_KEY, scope) === true; - } } diff --git a/src/vs/platform/storage/test/common/storageService.test.ts b/src/vs/platform/storage/test/common/storageService.test.ts new file mode 100644 index 000000000..886efb2f8 --- /dev/null +++ b/src/vs/platform/storage/test/common/storageService.test.ts @@ -0,0 +1,198 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { strictEqual, ok, equal } from 'assert'; +import { StorageScope, InMemoryStorageService, StorageTarget, IStorageValueChangeEvent, IStorageTargetChangeEvent } from 'vs/platform/storage/common/storage'; + +suite('StorageService', function () { + + test('Get Data, Integer, Boolean (global, in-memory)', () => { + storeData(StorageScope.GLOBAL); + }); + + test('Get Data, Integer, Boolean (workspace, in-memory)', () => { + storeData(StorageScope.WORKSPACE); + }); + + function storeData(scope: StorageScope): void { + const storage = new InMemoryStorageService(); + + let storageValueChangeEvents: IStorageValueChangeEvent[] = []; + storage.onDidChangeValue(e => storageValueChangeEvents.push(e)); + + strictEqual(storage.get('test.get', scope, 'foobar'), 'foobar'); + strictEqual(storage.get('test.get', scope, ''), ''); + strictEqual(storage.getNumber('test.getNumber', scope, 5), 5); + strictEqual(storage.getNumber('test.getNumber', scope, 0), 0); + strictEqual(storage.getBoolean('test.getBoolean', scope, true), true); + strictEqual(storage.getBoolean('test.getBoolean', scope, false), false); + + storage.store('test.get', 'foobar', scope, StorageTarget.MACHINE); + strictEqual(storage.get('test.get', scope, (undefined)!), 'foobar'); + let storageValueChangeEvent = storageValueChangeEvents.find(e => e.key === 'test.get'); + equal(storageValueChangeEvent?.scope, scope); + equal(storageValueChangeEvent?.key, 'test.get'); + storageValueChangeEvents = []; + + storage.store('test.get', '', scope, StorageTarget.MACHINE); + strictEqual(storage.get('test.get', scope, (undefined)!), ''); + storageValueChangeEvent = storageValueChangeEvents.find(e => e.key === 'test.get'); + equal(storageValueChangeEvent!.scope, scope); + equal(storageValueChangeEvent!.key, 'test.get'); + + storage.store('test.getNumber', 5, scope, StorageTarget.MACHINE); + strictEqual(storage.getNumber('test.getNumber', scope, (undefined)!), 5); + + storage.store('test.getNumber', 0, scope, StorageTarget.MACHINE); + strictEqual(storage.getNumber('test.getNumber', scope, (undefined)!), 0); + + storage.store('test.getBoolean', true, scope, StorageTarget.MACHINE); + strictEqual(storage.getBoolean('test.getBoolean', scope, (undefined)!), true); + + storage.store('test.getBoolean', false, scope, StorageTarget.MACHINE); + strictEqual(storage.getBoolean('test.getBoolean', scope, (undefined)!), false); + + strictEqual(storage.get('test.getDefault', scope, 'getDefault'), 'getDefault'); + strictEqual(storage.getNumber('test.getNumberDefault', scope, 5), 5); + strictEqual(storage.getBoolean('test.getBooleanDefault', scope, true), true); + } + + test('Remove Data (global, in-memory)', () => { + removeData(StorageScope.GLOBAL); + }); + + test('Remove Data (workspace, in-memory)', () => { + removeData(StorageScope.WORKSPACE); + }); + + function removeData(scope: StorageScope): void { + const storage = new InMemoryStorageService(); + + let storageValueChangeEvents: IStorageValueChangeEvent[] = []; + storage.onDidChangeValue(e => storageValueChangeEvents.push(e)); + + storage.store('test.remove', 'foobar', scope, StorageTarget.MACHINE); + strictEqual('foobar', storage.get('test.remove', scope, (undefined)!)); + + storage.remove('test.remove', scope); + ok(!storage.get('test.remove', scope, (undefined)!)); + let storageValueChangeEvent = storageValueChangeEvents.find(e => e.key === 'test.remove'); + equal(storageValueChangeEvent?.scope, scope); + equal(storageValueChangeEvent?.key, 'test.remove'); + } + + test('Keys (in-memory)', () => { + const storage = new InMemoryStorageService(); + + let storageTargetEvent: IStorageTargetChangeEvent | undefined = undefined; + storage.onDidChangeTarget(e => storageTargetEvent = e); + + let storageValueChangeEvent: IStorageValueChangeEvent | undefined = undefined; + storage.onDidChangeValue(e => storageValueChangeEvent = e); + + // Empty + for (const scope of [StorageScope.WORKSPACE, StorageScope.GLOBAL]) { + for (const target of [StorageTarget.MACHINE, StorageTarget.USER]) { + strictEqual(storage.keys(scope, target).length, 0); + } + } + + // Add values + for (const scope of [StorageScope.WORKSPACE, StorageScope.GLOBAL]) { + for (const target of [StorageTarget.MACHINE, StorageTarget.USER]) { + storageTargetEvent = Object.create(null); + storageValueChangeEvent = Object.create(null); + + storage.store('test.target1', 'value1', scope, target); + strictEqual(storage.keys(scope, target).length, 1); + equal(storageTargetEvent?.scope, scope); + equal(storageValueChangeEvent?.key, 'test.target1'); + equal(storageValueChangeEvent?.scope, scope); + equal(storageValueChangeEvent?.target, target); + + storageTargetEvent = undefined; + storageValueChangeEvent = Object.create(null); + + storage.store('test.target1', 'otherValue1', scope, target); + strictEqual(storage.keys(scope, target).length, 1); + equal(storageTargetEvent, undefined); + equal(storageValueChangeEvent?.key, 'test.target1'); + equal(storageValueChangeEvent?.scope, scope); + equal(storageValueChangeEvent?.target, target); + + storage.store('test.target2', 'value2', scope, target); + storage.store('test.target3', 'value3', scope, target); + + strictEqual(storage.keys(scope, target).length, 3); + } + } + + // Remove values + for (const scope of [StorageScope.WORKSPACE, StorageScope.GLOBAL]) { + for (const target of [StorageTarget.MACHINE, StorageTarget.USER]) { + const keysLength = storage.keys(scope, target).length; + + storage.store('test.target4', 'value1', scope, target); + strictEqual(storage.keys(scope, target).length, keysLength + 1); + + storageTargetEvent = Object.create(null); + storageValueChangeEvent = Object.create(null); + + storage.remove('test.target4', scope); + strictEqual(storage.keys(scope, target).length, keysLength); + equal(storageTargetEvent?.scope, scope); + equal(storageValueChangeEvent?.key, 'test.target4'); + equal(storageValueChangeEvent?.scope, scope); + } + } + + // Remove all + for (const scope of [StorageScope.WORKSPACE, StorageScope.GLOBAL]) { + for (const target of [StorageTarget.MACHINE, StorageTarget.USER]) { + const keys = storage.keys(scope, target); + + for (const key of keys) { + storage.remove(key, scope); + } + + strictEqual(storage.keys(scope, target).length, 0); + } + } + + // Adding undefined or null removes value + for (const scope of [StorageScope.WORKSPACE, StorageScope.GLOBAL]) { + for (const target of [StorageTarget.MACHINE, StorageTarget.USER]) { + storage.store('test.target1', 'value1', scope, target); + strictEqual(storage.keys(scope, target).length, 1); + + storageTargetEvent = Object.create(null); + + storage.store('test.target1', undefined, scope, target); + strictEqual(storage.keys(scope, target).length, 0); + equal(storageTargetEvent?.scope, scope); + + storage.store('test.target1', '', scope, target); + strictEqual(storage.keys(scope, target).length, 1); + + storage.store('test.target1', null, scope, target); + strictEqual(storage.keys(scope, target).length, 0); + } + } + + // Target change + storageTargetEvent = undefined; + storage.store('test.target5', 'value1', StorageScope.GLOBAL, StorageTarget.MACHINE); + ok(storageTargetEvent); + storageTargetEvent = undefined; + storage.store('test.target5', 'value1', StorageScope.GLOBAL, StorageTarget.USER); + ok(storageTargetEvent); + storageTargetEvent = undefined; + storage.store('test.target5', 'value1', StorageScope.GLOBAL, StorageTarget.MACHINE); + ok(storageTargetEvent); + storageTargetEvent = undefined; + storage.store('test.target5', 'value1', StorageScope.GLOBAL, StorageTarget.MACHINE); + ok(!storageTargetEvent); // no change in target + }); +}); diff --git a/src/vs/platform/storage/test/node/storageService.test.ts b/src/vs/platform/storage/test/node/storageService.test.ts index a87802a84..e0a410ef6 100644 --- a/src/vs/platform/storage/test/node/storageService.test.ts +++ b/src/vs/platform/storage/test/node/storageService.test.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { strictEqual, ok, equal } from 'assert'; -import { StorageScope, InMemoryStorageService } from 'vs/platform/storage/common/storage'; +import { equal } from 'assert'; +import { StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { NativeStorageService } from 'vs/platform/storage/node/storageService'; import { generateUuid } from 'vs/base/common/uuid'; import { join } from 'vs/base/common/path'; @@ -16,66 +16,7 @@ import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { InMemoryStorageDatabase } from 'vs/base/parts/storage/common/storage'; import { URI } from 'vs/base/common/uri'; -suite('StorageService', function () { - - test('Remove Data (global, in-memory)', () => { - removeData(StorageScope.GLOBAL); - }); - - test('Remove Data (workspace, in-memory)', () => { - removeData(StorageScope.WORKSPACE); - }); - - function removeData(scope: StorageScope): void { - const storage = new InMemoryStorageService(); - - storage.store('test.remove', 'foobar', scope); - strictEqual('foobar', storage.get('test.remove', scope, (undefined)!)); - - storage.remove('test.remove', scope); - ok(!storage.get('test.remove', scope, (undefined)!)); - } - - test('Get Data, Integer, Boolean (global, in-memory)', () => { - storeData(StorageScope.GLOBAL); - }); - - test('Get Data, Integer, Boolean (workspace, in-memory)', () => { - storeData(StorageScope.WORKSPACE); - }); - - function storeData(scope: StorageScope): void { - const storage = new InMemoryStorageService(); - - strictEqual(storage.get('test.get', scope, 'foobar'), 'foobar'); - strictEqual(storage.get('test.get', scope, ''), ''); - strictEqual(storage.getNumber('test.getNumber', scope, 5), 5); - strictEqual(storage.getNumber('test.getNumber', scope, 0), 0); - strictEqual(storage.getBoolean('test.getBoolean', scope, true), true); - strictEqual(storage.getBoolean('test.getBoolean', scope, false), false); - - storage.store('test.get', 'foobar', scope); - strictEqual(storage.get('test.get', scope, (undefined)!), 'foobar'); - - storage.store('test.get', '', scope); - strictEqual(storage.get('test.get', scope, (undefined)!), ''); - - storage.store('test.getNumber', 5, scope); - strictEqual(storage.getNumber('test.getNumber', scope, (undefined)!), 5); - - storage.store('test.getNumber', 0, scope); - strictEqual(storage.getNumber('test.getNumber', scope, (undefined)!), 0); - - storage.store('test.getBoolean', true, scope); - strictEqual(storage.getBoolean('test.getBoolean', scope, (undefined)!), true); - - storage.store('test.getBoolean', false, scope); - strictEqual(storage.getBoolean('test.getBoolean', scope, (undefined)!), false); - - strictEqual(storage.get('test.getDefault', scope, 'getDefault'), 'getDefault'); - strictEqual(storage.getNumber('test.getNumberDefault', scope, 5), 5); - strictEqual(storage.getBoolean('test.getBooleanDefault', scope, true), true); - } +suite('NativeStorageService', function () { function uniqueStorageDir(): string { const id = generateUuid(); @@ -111,9 +52,13 @@ suite('StorageService', function () { const storage = new NativeStorageService(new InMemoryStorageDatabase(), new NullLogService(), new StorageTestEnvironmentService(URI.file(storageDir), storageDir)); await storage.initialize({ id: String(Date.now()) }); - storage.store('bar', 'foo', StorageScope.WORKSPACE); - storage.store('barNumber', 55, StorageScope.WORKSPACE); - storage.store('barBoolean', true, StorageScope.GLOBAL); + storage.store('bar', 'foo', StorageScope.WORKSPACE, StorageTarget.MACHINE); + storage.store('barNumber', 55, StorageScope.WORKSPACE, StorageTarget.MACHINE); + storage.store('barBoolean', true, StorageScope.GLOBAL, StorageTarget.MACHINE); + + equal(storage.get('bar', StorageScope.WORKSPACE), 'foo'); + equal(storage.getNumber('barNumber', StorageScope.WORKSPACE), 55); + equal(storage.getBoolean('barBoolean', StorageScope.GLOBAL), true); await storage.migrate({ id: String(Date.now() + 100) }); diff --git a/src/vs/platform/telemetry/browser/errorTelemetry.ts b/src/vs/platform/telemetry/browser/errorTelemetry.ts index 6ecc99a21..6ef338fd8 100644 --- a/src/vs/platform/telemetry/browser/errorTelemetry.ts +++ b/src/vs/platform/telemetry/browser/errorTelemetry.ts @@ -5,7 +5,7 @@ import { toDisposable } from 'vs/base/common/lifecycle'; import { globals } from 'vs/base/common/platform'; -import BaseErrorTelemetry, { ErrorEvent } from '../common/errorTelemetry'; +import BaseErrorTelemetry, { ErrorEvent } from 'vs/platform/telemetry/common/errorTelemetry'; export default class ErrorTelemetry extends BaseErrorTelemetry { protected installErrorListeners(): void { diff --git a/src/vs/platform/telemetry/node/appInsightsAppender.ts b/src/vs/platform/telemetry/node/appInsightsAppender.ts index b288c282a..0001e1772 100644 --- a/src/vs/platform/telemetry/node/appInsightsAppender.ts +++ b/src/vs/platform/telemetry/node/appInsightsAppender.ts @@ -4,11 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as appInsights from 'applicationinsights'; +import { onUnexpectedError } from 'vs/base/common/errors'; import { mixin } from 'vs/base/common/objects'; import { ITelemetryAppender, validateTelemetryData } from 'vs/platform/telemetry/common/telemetryUtils'; -function getClient(aiKey: string): appInsights.TelemetryClient { - +async function getClient(aiKey: string): Promise { + const appInsights = await import('applicationinsights'); let client: appInsights.TelemetryClient; if (appInsights.defaultClient) { client = new appInsights.TelemetryClient(aiKey); @@ -36,7 +37,8 @@ function getClient(aiKey: string): appInsights.TelemetryClient { export class AppInsightsAppender implements ITelemetryAppender { - private _aiClient?: appInsights.TelemetryClient; + private _aiClient: string | appInsights.TelemetryClient | undefined; + private _asyncAIClient: Promise | null; constructor( private _eventPrefix: string, @@ -47,11 +49,37 @@ export class AppInsightsAppender implements ITelemetryAppender { this._defaultData = Object.create(null); } - if (typeof aiKeyOrClientFactory === 'string') { - this._aiClient = getClient(aiKeyOrClientFactory); - } else if (typeof aiKeyOrClientFactory === 'function') { + if (typeof aiKeyOrClientFactory === 'function') { this._aiClient = aiKeyOrClientFactory(); + } else { + this._aiClient = aiKeyOrClientFactory; } + this._asyncAIClient = null; + } + + private _withAIClient(callback: (aiClient: appInsights.TelemetryClient) => void): void { + if (!this._aiClient) { + return; + } + + if (typeof this._aiClient !== 'string') { + callback(this._aiClient); + return; + } + + if (!this._asyncAIClient) { + this._asyncAIClient = getClient(this._aiClient); + } + + this._asyncAIClient.then( + (aiClient) => { + callback(aiClient); + }, + (err) => { + onUnexpectedError(err); + console.error(err); + } + ); } log(eventName: string, data?: any): void { @@ -61,22 +89,24 @@ export class AppInsightsAppender implements ITelemetryAppender { data = mixin(data, this._defaultData); data = validateTelemetryData(data); - this._aiClient.trackEvent({ + this._withAIClient((aiClient) => aiClient.trackEvent({ name: this._eventPrefix + '/' + eventName, properties: data.properties, measurements: data.measurements - }); + })); } flush(): Promise { if (this._aiClient) { return new Promise(resolve => { - this._aiClient!.flush({ - callback: () => { - // all data flushed - this._aiClient = undefined; - resolve(undefined); - } + this._withAIClient((aiClient) => { + aiClient.flush({ + callback: () => { + // all data flushed + this._aiClient = undefined; + resolve(undefined); + } + }); }); }); } diff --git a/src/vs/platform/telemetry/node/errorTelemetry.ts b/src/vs/platform/telemetry/node/errorTelemetry.ts index 60f1633ee..562eb44b9 100644 --- a/src/vs/platform/telemetry/node/errorTelemetry.ts +++ b/src/vs/platform/telemetry/node/errorTelemetry.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; -import BaseErrorTelemetry from '../common/errorTelemetry'; +import BaseErrorTelemetry from 'vs/platform/telemetry/common/errorTelemetry'; export default class ErrorTelemetry extends BaseErrorTelemetry { protected installErrorListeners(): void { diff --git a/src/vs/platform/theme/browser/checkbox.ts b/src/vs/platform/theme/browser/checkbox.ts deleted file mode 100644 index c479126ee..000000000 --- a/src/vs/platform/theme/browser/checkbox.ts +++ /dev/null @@ -1,26 +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 { CheckboxActionViewItem } from 'vs/base/browser/ui/checkbox/checkbox'; -import { IAction } from 'vs/base/common/actions'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { attachCheckboxStyler } from 'vs/platform/theme/common/styler'; -import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; - -export class ThemableCheckboxActionViewItem extends CheckboxActionViewItem { - - constructor(context: any, action: IAction, options: IBaseActionViewItemOptions | undefined, private readonly themeService: IThemeService) { - super(context, action, options); - } - - render(container: HTMLElement): void { - super.render(container); - if (this.checkbox) { - this.disposables.add(attachCheckboxStyler(this.checkbox, this.themeService)); - } - } - -} - diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index c9de28ba8..b5239353a 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -196,7 +196,7 @@ export const textBlockQuoteBorder = registerColor('textBlockQuote.border', { lig export const textCodeBlockBackground = registerColor('textCodeBlock.background', { light: '#dcdcdc66', dark: '#0a0a0a66', hc: Color.black }, nls.localize('textCodeBlockBackground', "Background color for code blocks in text.")); // ----- widgets -export const widgetShadow = registerColor('widget.shadow', { dark: '#000000', light: '#A8A8A8', hc: null }, nls.localize('widgetShadow', 'Shadow color of widgets such as find/replace inside the editor.')); +export const widgetShadow = registerColor('widget.shadow', { dark: transparent(Color.black, .36), light: transparent(Color.black, .16), hc: null }, nls.localize('widgetShadow', 'Shadow color of widgets such as find/replace inside the editor.')); export const inputBackground = registerColor('input.background', { dark: '#3C3C3C', light: Color.white, hc: Color.black }, nls.localize('inputBoxBackground', "Input box background.")); export const inputForeground = registerColor('input.foreground', { dark: foreground, light: foreground, hc: foreground }, nls.localize('inputBoxForeground', "Input box foreground.")); @@ -243,18 +243,23 @@ export const scrollbarSliderActiveBackground = registerColor('scrollbarSlider.ac export const progressBarBackground = registerColor('progressBar.background', { dark: Color.fromHex('#0E70C0'), light: Color.fromHex('#0E70C0'), hc: contrastBorder }, nls.localize('progressBarBackground', "Background color of the progress bar that can show for long running operations.")); +export const editorErrorBackground = registerColor('editorError.background', { dark: null, light: null, hc: 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', { dark: '#F48771', light: '#E51400', hc: null }, nls.localize('editorError.foreground', 'Foreground color of error squigglies in the editor.')); export const editorErrorBorder = registerColor('editorError.border', { dark: null, light: null, hc: Color.fromHex('#E47777').transparent(0.8) }, nls.localize('errorBorder', 'Border color of error boxes in the editor.')); +export const editorWarningBackground = registerColor('editorWarning.background', { dark: null, light: null, hc: 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', { dark: '#CCA700', light: '#E9A700', hc: null }, nls.localize('editorWarning.foreground', 'Foreground color of warning squigglies in the editor.')); export const editorWarningBorder = registerColor('editorWarning.border', { dark: null, light: null, hc: Color.fromHex('#FFCC00').transparent(0.8) }, nls.localize('warningBorder', 'Border color of warning boxes in the editor.')); +export const editorInfoBackground = registerColor('editorInfo.background', { dark: null, light: null, hc: 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', { dark: '#75BEFF', light: '#75BEFF', hc: null }, nls.localize('editorInfo.foreground', 'Foreground color of info squigglies in the editor.')); export const editorInfoBorder = registerColor('editorInfo.border', { dark: null, light: null, hc: Color.fromHex('#75BEFF').transparent(0.8) }, nls.localize('infoBorder', 'Border color of info boxes in the editor.')); export const editorHintForeground = registerColor('editorHint.foreground', { dark: Color.fromHex('#eeeeee').transparent(0.7), light: '#6c6c6c', hc: null }, nls.localize('editorHint.foreground', 'Foreground color of hint squigglies in the editor.')); export const editorHintBorder = registerColor('editorHint.border', { dark: null, light: null, hc: Color.fromHex('#eeeeee').transparent(0.8) }, nls.localize('hintBorder', 'Border color of hint boxes in the editor.')); +export const sashHoverBorder = registerColor('sash.hoverBorder', { dark: null, light: null, hc: null }, nls.localize('sashActiveBorder', "Border color of active sashes.")); + /** * Editor background color. * Because of bug https://monacotools.visualstudio.com/DefaultCollection/Monaco/_workitems/edit/13254 @@ -353,7 +358,7 @@ export const diffDiagonalFill = registerColor('diffEditor.diagonalFill', { dark: */ export const listFocusBackground = registerColor('list.focusBackground', { dark: '#062F4A', light: '#D6EBFF', hc: 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, hc: 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 listActiveSelectionBackground = registerColor('list.activeSelectionBackground', { dark: '#094771', light: '#0074E8', hc: null }, nls.localize('listActiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); +export const listActiveSelectionBackground = registerColor('list.activeSelectionBackground', { dark: '#094771', light: '#0060C0', hc: null }, nls.localize('listActiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); export const listActiveSelectionForeground = registerColor('list.activeSelectionForeground', { dark: Color.white, light: Color.white, hc: null }, 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 listInactiveSelectionBackground = registerColor('list.inactiveSelectionBackground', { dark: '#37373D', light: '#E4E6F1', hc: null }, 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, hc: 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.")); diff --git a/src/vs/platform/theme/common/iconRegistry.ts b/src/vs/platform/theme/common/iconRegistry.ts index da0df3677..a19a41a84 100644 --- a/src/vs/platform/theme/common/iconRegistry.ts +++ b/src/vs/platform/theme/common/iconRegistry.ts @@ -12,6 +12,7 @@ import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/plat import { RunOnceScheduler } from 'vs/base/common/async'; import * as Codicons from 'vs/base/common/codicons'; + // ------ API types @@ -29,14 +30,14 @@ export interface IconDefinition { export interface IconContribution { id: string; - description: string; + description: string | undefined; deprecationMessage?: string; defaults: IconDefaults; } export interface IIconRegistry { - readonly onDidChangeSchema: Event; + readonly onDidChange: Event; /** * Register a icon to the registry. @@ -44,7 +45,7 @@ export interface IIconRegistry { * @param defaults The default values * @description the description */ - registerIcon(id: string, defaults: IconDefaults, description: string): ThemeIcon; + registerIcon(id: string, defaults: IconDefaults, description?: string): ThemeIcon; /** * Register a icon to the registry. @@ -71,12 +72,17 @@ export interface IIconRegistry { */ getIconReferenceSchema(): IJSONSchema; + /** + * The CSS for all icons + */ + getCSS(): string; + } class IconRegistry implements IIconRegistry { - private readonly _onDidChangeSchema = new Emitter(); - readonly onDidChangeSchema: Event = this._onDidChangeSchema.event; + private readonly _onDidChange = new Emitter(); + readonly onDidChange: Event = this._onDidChange.event; private iconsById: { [key: string]: IconContribution }; private iconSchema: IJSONSchema & { properties: IJSONSchemaMap } = { @@ -101,8 +107,18 @@ class IconRegistry implements IIconRegistry { } public registerIcon(id: string, defaults: IconDefaults, description?: string, deprecationMessage?: string): ThemeIcon { - if (!description) { - description = localize('icon.defaultDescription', 'Icon with identifier \'{0}\'', id); + const existing = this.iconsById[id]; + if (existing) { + if (description && !existing.description) { + existing.description = description; + this.iconSchema.properties[id].markdownDescription = `${description} $(${id})`; + const enumIndex = this.iconReferenceSchema.enum.indexOf(id); + if (enumIndex !== -1) { + this.iconReferenceSchema.enumDescriptions[enumIndex] = description; + } + this._onDidChange.fire(); + } + return existing; } let iconContribution: IconContribution = { id, description, defaults, deprecationMessage }; this.iconsById[id] = iconContribution; @@ -110,12 +126,14 @@ class IconRegistry implements IIconRegistry { if (deprecationMessage) { propertySchema.deprecationMessage = deprecationMessage; } - propertySchema.markdownDescription = `${description}: $(${id})`; + if (description) { + propertySchema.markdownDescription = `${description}: $(${id})`; + } this.iconSchema.properties[id] = propertySchema; this.iconReferenceSchema.enum.push(id); - this.iconReferenceSchema.enumDescriptions.push(description); + this.iconReferenceSchema.enumDescriptions.push(description || ''); - this._onDidChangeSchema.fire(); + this._onDidChange.fire(); return { id }; } @@ -128,7 +146,7 @@ class IconRegistry implements IIconRegistry { this.iconReferenceSchema.enum.splice(index, 1); this.iconReferenceSchema.enumDescriptions.splice(index, 1); } - this._onDidChangeSchema.fire(); + this._onDidChange.fire(); } public getIcons(): IconContribution[] { @@ -147,6 +165,29 @@ class IconRegistry implements IIconRegistry { return this.iconReferenceSchema; } + public getCSS() { + const rules = []; + for (let id in this.iconsById) { + const rule = this.formatRule(id); + if (rule) { + rules.push(rule); + } + } + return rules.join('\n'); + } + + private formatRule(id: string): string | undefined { + let definition = this.iconsById[id].defaults; + while (ThemeIcon.isThemeIcon(definition)) { + const c = this.iconsById[definition.id]; + if (!c) { + return undefined; + } + definition = c.defaults; + } + return `.codicon-${id}:before { content: '${definition.character}'; }`; + } + public toString() { const sorter = (i1: IconContribution, i2: IconContribution) => { const isThemeIcon1 = ThemeIcon.isThemeIcon(i1.defaults); @@ -169,7 +210,7 @@ class IconRegistry implements IIconRegistry { const contributions = Object.keys(this.iconsById).map(key => this.iconsById[key]); for (const i of contributions.sort(sorter)) { - reference.push(`||${i.id}|${ThemeIcon.isThemeIcon(i.defaults) ? i.defaults.id : ''}|`); + reference.push(`||${i.id}|${ThemeIcon.isThemeIcon(i.defaults) ? i.defaults.id : ''}|${i.description || ''}|`); if (!ThemeIcon.isThemeIcon((i.defaults))) { docCss.push(`.codicon-${i.id}:before { content: "${i.defaults.character}" }`); @@ -183,7 +224,7 @@ class IconRegistry implements IIconRegistry { const iconRegistry = new IconRegistry(); platform.Registry.add(Extensions.IconContribution, iconRegistry); -export function registerIcon(id: string, defaults: IconDefaults, description?: string, deprecationMessage?: string): ThemeIcon { +export function registerIcon(id: string, defaults: IconDefaults, description: string, deprecationMessage?: string): ThemeIcon { return iconRegistry.registerIcon(id, defaults, description, deprecationMessage); } @@ -193,20 +234,19 @@ export function getIconRegistry(): IIconRegistry { function initialize() { for (const icon of Codicons.iconRegistry.all) { - registerIcon(icon.id, icon.definition); + iconRegistry.registerIcon(icon.id, icon.definition, icon.description); } - Codicons.iconRegistry.onDidRegister(icon => registerIcon(icon.id, icon.definition)); + Codicons.iconRegistry.onDidRegister(icon => iconRegistry.registerIcon(icon.id, icon.definition, icon.description)); } initialize(); - export const iconsSchemaId = 'vscode://schemas/icons'; let schemaRegistry = platform.Registry.as(JSONExtensions.JSONContribution); schemaRegistry.registerSchema(iconsSchemaId, iconRegistry.getIconSchema()); const delayer = new RunOnceScheduler(() => schemaRegistry.notifySchemaChanged(iconsSchemaId), 200); -iconRegistry.onDidChangeSchema(() => { +iconRegistry.onDidChange(() => { if (!delayer.isScheduled()) { delayer.schedule(); } @@ -214,3 +254,11 @@ iconRegistry.onDidChangeSchema(() => { //setTimeout(_ => console.log(iconRegistry.toString()), 5000); + + +// common icons + +export const widgetClose = registerIcon('widget-close', Codicons.Codicon.close, localize('widgetClose', 'Icon for the close action in widgets.')); + +export const gotoPreviousLocation = registerIcon('goto-previous-location', Codicons.Codicon.arrowUp, localize('previousChangeIcon', 'Icon for goto previous editor location.')); +export const gotoNextLocation = registerIcon('goto-next-location', Codicons.Codicon.arrowDown, localize('nextChangeIcon', 'Icon for goto next editor location.')); diff --git a/src/vs/platform/theme/common/themeService.ts b/src/vs/platform/theme/common/themeService.ts index 0ae9c30fe..f82c45767 100644 --- a/src/vs/platform/theme/common/themeService.ts +++ b/src/vs/platform/theme/common/themeService.ts @@ -11,6 +11,7 @@ import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { Event, Emitter } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ColorScheme } from 'vs/platform/theme/common/theme'; +import { CSSIcon } from 'vs/base/common/codicons'; export const IThemeService = createDecorator('themeService'); @@ -18,6 +19,12 @@ export interface ThemeColor { id: string; } +export namespace ThemeColor { + export function isThemeColor(obj: any): obj is ThemeColor { + return obj && typeof obj === 'object' && typeof (obj).id === 'string'; + } +} + export function themeColorFromId(id: ColorIdentifier) { return { id }; } @@ -29,8 +36,8 @@ export interface ThemeIcon { } export namespace ThemeIcon { - export function isThemeIcon(obj: any): obj is ThemeIcon | { id: string } { - return obj && typeof obj === 'object' && typeof (obj).id === 'string'; + export function isThemeIcon(obj: any): obj is ThemeIcon { + return obj && typeof obj === 'object' && typeof (obj).id === 'string' && (typeof (obj).color === 'undefined' || ThemeColor.isThemeColor((obj).color)); } const _regexFromString = /^\$\(([a-z.]+\/)?([a-z-~]+)\)$/i; @@ -41,26 +48,67 @@ export namespace ThemeIcon { return undefined; } let [, owner, name] = match; - if (!owner) { - owner = `codicon/`; + if (!owner || owner === 'codicon/') { + return { id: name }; } return { id: owner + name }; } + export function modify(icon: ThemeIcon, modifier: 'disabled' | 'spin' | undefined): ThemeIcon { + let id = icon.id; + const tildeIndex = id.lastIndexOf('~'); + if (tildeIndex !== -1) { + id = id.substring(0, tildeIndex); + } + if (modifier) { + id = `${id}~${modifier}`; + } + return { id }; + } + + export function isEqual(ti1: ThemeIcon, ti2: ThemeIcon): boolean { + return ti1.id === ti2.id && ti1.color?.id === ti2.color?.id; + } + const _regexAsClassName = /^(codicon\/)?([a-z-]+)(~[a-z]+)?$/i; - export function asClassName(icon: ThemeIcon): string | undefined { - // todo@martin,joh -> this should go into the ThemeService + export function asClassNameArray(icon: ThemeIcon): string[] { const match = _regexAsClassName.exec(icon.id); if (!match) { - return undefined; + return ['codicon', 'codicon-error']; } let [, , name, modifier] = match; - let className = `codicon codicon-${name}`; + let className = `codicon-${name}`; if (modifier) { - className += ` ${modifier.substr(1)}`; + return ['codicon', className, modifier.substr(1)]; } - return className; + return ['codicon', className]; + } + + + export function asClassName(icon: ThemeIcon): string { + return asClassNameArray(icon).join(' '); + } + + export function asCSSSelector(icon: ThemeIcon): string { + return '.' + asClassNameArray(icon).join('.'); + } + + export function asCSSIcon(icon: ThemeIcon): CSSIcon { + return { + classNames: asClassName(icon) + }; + } + + export function asCodiconLabel(icon: ThemeIcon): string { + return '$(' + icon.id + ')'; + } + + export function revive(icon: any): ThemeIcon | undefined { + if (ThemeIcon.isThemeIcon(icon)) { + return { id: icon.id, color: icon.color ? { id: icon.color.id } : undefined }; + } + return undefined; } } diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index ab705dbca..cf7a8df0f 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -522,8 +522,9 @@ function createDefaultTokenClassificationRegistry(): TokenClassificationRegistry registerTokenType('typeParameter', nls.localize('typeParameter', "Style for type parameters."), [['entity.name.type.parameter']]); registerTokenType('function', nls.localize('function', "Style for functions"), [['entity.name.function'], ['support.function']]); - registerTokenType('member', nls.localize('member', "Style for member"), [['entity.name.function.member'], ['support.function']]); - registerTokenType('macro', nls.localize('macro', "Style for macros."), [['entity.name.other.preprocessor.macro']]); + registerTokenType('member', nls.localize('member', "Style for member functions"), [], 'method', 'Deprecated use `method` instead'); + registerTokenType('method', nls.localize('method', "Style for method (member functions)"), [['entity.name.function.member'], ['support.function']]); + registerTokenType('macro', nls.localize('macro', "Style for macros."), [['entity.name.function.preprocessor']]); registerTokenType('variable', nls.localize('variable', "Style for variables."), [['variable.other.readwrite'], ['entity.name.variable']]); registerTokenType('parameter', nls.localize('parameter', "Style for parameters."), [['variable.parameter']]); diff --git a/src/vs/platform/theme/electron-main/themeMainService.ts b/src/vs/platform/theme/electron-main/themeMainService.ts index 7bbacdb3d..1d07cf7bf 100644 --- a/src/vs/platform/theme/electron-main/themeMainService.ts +++ b/src/vs/platform/theme/electron-main/themeMainService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { isWindows, isMacintosh } from 'vs/base/common/platform'; -import { ipcMain as ipc, nativeTheme } from 'electron'; +import { ipcMain, nativeTheme } from 'electron'; import { IStateService } from 'vs/platform/state/node/state'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -28,7 +28,7 @@ export class ThemeMainService implements IThemeMainService { declare readonly _serviceBrand: undefined; constructor(@IStateService private stateService: IStateService) { - ipc.on('vscode:changeColorTheme', (e: Event, windowId: number, broadcast: string) => { + ipcMain.on('vscode:changeColorTheme', (e: Event, windowId: number, broadcast: string) => { // Theme changes if (typeof broadcast === 'string') { this.storeBackgroundColor(JSON.parse(broadcast)); diff --git a/src/vs/platform/undoRedo/common/undoRedo.ts b/src/vs/platform/undoRedo/common/undoRedo.ts index c70e013dd..021003bab 100644 --- a/src/vs/platform/undoRedo/common/undoRedo.ts +++ b/src/vs/platform/undoRedo/common/undoRedo.ts @@ -81,6 +81,27 @@ export class UndoRedoGroup { public static None = new UndoRedoGroup(); } +export class UndoRedoSource { + private static _ID = 0; + + public readonly id: number; + private order: number; + + constructor() { + this.id = UndoRedoSource._ID++; + this.order = 1; + } + + public nextOrder(): number { + if (this.id === 0) { + return 0; + } + return this.order++; + } + + public static None = new UndoRedoSource(); +} + export interface IUndoRedoService { readonly _serviceBrand: undefined; @@ -100,7 +121,7 @@ export interface IUndoRedoService { * Add a new element to the `undo` stack. * This will destroy the `redo` stack. */ - pushElement(element: IUndoRedoElement, group?: UndoRedoGroup): void; + pushElement(element: IUndoRedoElement, group?: UndoRedoGroup, source?: UndoRedoSource): void; /** * Get the last pushed element for a resource. @@ -133,9 +154,9 @@ export interface IUndoRedoService { */ restoreSnapshot(snapshot: ResourceEditStackSnapshot): void; - canUndo(resource: URI): boolean; - undo(resource: URI): Promise | void; + canUndo(resource: URI | UndoRedoSource): boolean; + undo(resource: URI | UndoRedoSource): Promise | void; - canRedo(resource: URI): boolean; - redo(resource: URI): Promise | void; + canRedo(resource: URI | UndoRedoSource): boolean; + redo(resource: URI | UndoRedoSource): Promise | void; } diff --git a/src/vs/platform/undoRedo/common/undoRedoService.ts b/src/vs/platform/undoRedo/common/undoRedoService.ts index 2a5bdb84d..2fb802fbc 100644 --- a/src/vs/platform/undoRedo/common/undoRedoService.ts +++ b/src/vs/platform/undoRedo/common/undoRedoService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { IUndoRedoService, IWorkspaceUndoRedoElement, UndoRedoElementType, IUndoRedoElement, IPastFutureElements, ResourceEditStackSnapshot, UriComparisonKeyComputer, IResourceUndoRedoElement, UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo'; +import { IUndoRedoService, IWorkspaceUndoRedoElement, UndoRedoElementType, IUndoRedoElement, IPastFutureElements, ResourceEditStackSnapshot, UriComparisonKeyComputer, IResourceUndoRedoElement, UndoRedoGroup, UndoRedoSource } from 'vs/platform/undoRedo/common/undoRedo'; import { URI } from 'vs/base/common/uri'; import { onUnexpectedError } from 'vs/base/common/errors'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -34,9 +34,11 @@ class ResourceStackElement { public readonly strResources: string[]; public readonly groupId: number; public readonly groupOrder: number; + public readonly sourceId: number; + public readonly sourceOrder: number; public isValid: boolean; - constructor(actual: IUndoRedoElement, resourceLabel: string, strResource: string, groupId: number, groupOrder: number) { + constructor(actual: IUndoRedoElement, resourceLabel: string, strResource: string, groupId: number, groupOrder: number, sourceId: number, sourceOrder: number) { this.actual = actual; this.label = actual.label; this.resourceLabel = resourceLabel; @@ -45,6 +47,8 @@ class ResourceStackElement { this.strResources = [this.strResource]; this.groupId = groupId; this.groupOrder = groupOrder; + this.sourceId = sourceId; + this.sourceOrder = sourceOrder; this.isValid = true; } @@ -130,16 +134,20 @@ class WorkspaceStackElement { public readonly strResources: string[]; public readonly groupId: number; public readonly groupOrder: number; + public readonly sourceId: number; + public readonly sourceOrder: number; public removedResources: RemovedResources | null; public invalidatedResources: RemovedResources | null; - constructor(actual: IWorkspaceUndoRedoElement, resourceLabels: string[], strResources: string[], groupId: number, groupOrder: number) { + constructor(actual: IWorkspaceUndoRedoElement, resourceLabels: string[], strResources: string[], groupId: number, groupOrder: number, sourceId: number, sourceOrder: number) { this.actual = actual; this.label = actual.label; this.resourceLabels = resourceLabels; this.strResources = strResources; this.groupId = groupId; this.groupOrder = groupOrder; + this.sourceId = sourceId; + this.sourceOrder = sourceOrder; this.removedResources = null; this.invalidatedResources = null; } @@ -490,11 +498,11 @@ export class UndoRedoService implements IUndoRedoService { console.log(str.join('\n')); } - public pushElement(element: IUndoRedoElement, group: UndoRedoGroup = UndoRedoGroup.None): void { + public pushElement(element: IUndoRedoElement, group: UndoRedoGroup = UndoRedoGroup.None, source: UndoRedoSource = UndoRedoSource.None): void { if (element.type === UndoRedoElementType.Resource) { const resourceLabel = getResourceLabel(element.resource); const strResource = this.getUriComparisonKey(element.resource); - this._pushElement(new ResourceStackElement(element, resourceLabel, strResource, group.id, group.nextOrder())); + this._pushElement(new ResourceStackElement(element, resourceLabel, strResource, group.id, group.nextOrder(), source.id, source.nextOrder())); } else { const seen = new Set(); const resourceLabels: string[] = []; @@ -512,9 +520,9 @@ export class UndoRedoService implements IUndoRedoService { } if (resourceLabels.length === 1) { - this._pushElement(new ResourceStackElement(element, resourceLabels[0], strResources[0], group.id, group.nextOrder())); + this._pushElement(new ResourceStackElement(element, resourceLabels[0], strResources[0], group.id, group.nextOrder(), source.id, source.nextOrder())); } else { - this._pushElement(new WorkspaceStackElement(element, resourceLabels, strResources, group.id, group.nextOrder())); + this._pushElement(new WorkspaceStackElement(element, resourceLabels, strResources, group.id, group.nextOrder(), source.id, source.nextOrder())); } } if (DEBUG) { @@ -558,7 +566,7 @@ export class UndoRedoService implements IUndoRedoService { for (const _element of individualArr) { const resourceLabel = getResourceLabel(_element.resource); const strResource = this.getUriComparisonKey(_element.resource); - const element = new ResourceStackElement(_element, resourceLabel, strResource, 0, 0); + const element = new ResourceStackElement(_element, resourceLabel, strResource, 0, 0, 0, 0); individualMap.set(element.strResource, element); } @@ -577,7 +585,7 @@ export class UndoRedoService implements IUndoRedoService { for (const _element of individualArr) { const resourceLabel = getResourceLabel(_element.resource); const strResource = this.getUriComparisonKey(_element.resource); - const element = new ResourceStackElement(_element, resourceLabel, strResource, 0, 0); + const element = new ResourceStackElement(_element, resourceLabel, strResource, 0, 0, 0, 0); individualMap.set(element.strResource, element); } @@ -657,8 +665,37 @@ export class UndoRedoService implements IUndoRedoService { return { past: [], future: [] }; } - public canUndo(resource: URI): boolean { - const strResource = this.getUriComparisonKey(resource); + private _findClosestUndoElementWithSource(sourceId: number): [StackElement | null, string | null] { + if (!sourceId) { + return [null, null]; + } + + // find an element with the sourceId and with the highest sourceOrder ready to be undone + let matchedElement: StackElement | null = null; + let matchedStrResource: string | null = null; + + for (const [strResource, editStack] of this._editStacks) { + const candidate = editStack.getClosestPastElement(); + if (!candidate) { + continue; + } + if (candidate.sourceId === sourceId) { + if (!matchedElement || candidate.sourceOrder > matchedElement.sourceOrder) { + matchedElement = candidate; + matchedStrResource = strResource; + } + } + } + + return [matchedElement, matchedStrResource]; + } + + public canUndo(resourceOrSource: URI | UndoRedoSource): boolean { + if (resourceOrSource instanceof UndoRedoSource) { + const [, matchedStrResource] = this._findClosestUndoElementWithSource(resourceOrSource.id); + return matchedStrResource ? true : false; + } + const strResource = this.getUriComparisonKey(resourceOrSource); if (this._editStacks.has(strResource)) { const editStack = this._editStacks.get(strResource)!; return editStack.hasPastElements(); @@ -774,7 +811,7 @@ export class UndoRedoService implements IUndoRedoService { if (element.canSplit()) { this._splitPastWorkspaceElement(element, ignoreResources); this._notificationService.info(message); - return new WorkspaceVerificationError(this.undo(strResource)); + return new WorkspaceVerificationError(this._undo(strResource)); } else { // Cannot safely split this workspace element => flush all undo/redo stacks for (const strResource of element.strResources) { @@ -922,7 +959,7 @@ export class UndoRedoService implements IUndoRedoService { if (result.choice === 1) { // choice: undo this file this._splitPastWorkspaceElement(element, null); - return this.undo(strResource); + return this._undo(strResource); } // choice: undo in all files @@ -1007,12 +1044,22 @@ export class UndoRedoService implements IUndoRedoService { const [, matchedStrResource] = this._findClosestUndoElementInGroup(groupId); if (matchedStrResource) { - return this.undo(matchedStrResource); + return this._undo(matchedStrResource); } } - public undo(resource: URI | string): Promise | void { - const strResource = typeof resource === 'string' ? resource : this.getUriComparisonKey(resource); + public undo(resourceOrSource: URI | UndoRedoSource): Promise | void { + if (resourceOrSource instanceof UndoRedoSource) { + const [, matchedStrResource] = this._findClosestUndoElementWithSource(resourceOrSource.id); + return matchedStrResource ? this._undo(matchedStrResource, resourceOrSource.id) : undefined; + } + if (typeof resourceOrSource === 'string') { + return this._undo(resourceOrSource); + } + return this._undo(this.getUriComparisonKey(resourceOrSource)); + } + + private _undo(strResource: string, sourceId: number = 0): Promise | void { if (!this._editStacks.has(strResource)) { return; } @@ -1028,10 +1075,15 @@ export class UndoRedoService implements IUndoRedoService { const [matchedElement, matchedStrResource] = this._findClosestUndoElementInGroup(element.groupId); if (element !== matchedElement && matchedStrResource) { // there is an element in the same group that should be undone before this one - return this.undo(matchedStrResource); + return this._undo(matchedStrResource); } } + if (element.sourceId !== sourceId) { + // Hit a different source, prompt for confirmation + return this._confirmDifferentSourceAndContinueUndo(strResource, element); + } + try { if (element.type === UndoRedoElementType.Workspace) { return this._workspaceUndo(strResource, element); @@ -1045,8 +1097,59 @@ export class UndoRedoService implements IUndoRedoService { } } - public canRedo(resource: URI): boolean { - const strResource = this.getUriComparisonKey(resource); + private async _confirmDifferentSourceAndContinueUndo(strResource: string, element: StackElement): Promise { + const result = await this._dialogService.show( + Severity.Info, + nls.localize('confirmDifferentSource', "Would you like to undo '{0}'?", element.label), + [ + nls.localize('confirmDifferentSource.ok', "Undo"), + nls.localize('cancel', "Cancel"), + ], + { + cancelId: 1 + } + ); + + if (result.choice === 1) { + // choice: cancel + return; + } + + // choice: undo + return this._undo(strResource, element.sourceId); + } + + private _findClosestRedoElementWithSource(sourceId: number): [StackElement | null, string | null] { + if (!sourceId) { + return [null, null]; + } + + // find an element with sourceId and with the lowest sourceOrder ready to be redone + let matchedElement: StackElement | null = null; + let matchedStrResource: string | null = null; + + for (const [strResource, editStack] of this._editStacks) { + const candidate = editStack.getClosestFutureElement(); + if (!candidate) { + continue; + } + if (candidate.sourceId === sourceId) { + if (!matchedElement || candidate.sourceOrder < matchedElement.sourceOrder) { + matchedElement = candidate; + matchedStrResource = strResource; + } + } + } + + return [matchedElement, matchedStrResource]; + } + + public canRedo(resourceOrSource: URI | UndoRedoSource): boolean { + if (resourceOrSource instanceof UndoRedoSource) { + const [, matchedStrResource] = this._findClosestRedoElementWithSource(resourceOrSource.id); + return matchedStrResource ? true : false; + } + const strResource = this.getUriComparisonKey(resourceOrSource); if (this._editStacks.has(strResource)) { const editStack = this._editStacks.get(strResource)!; return editStack.hasFutureElements(); @@ -1058,7 +1161,7 @@ export class UndoRedoService implements IUndoRedoService { if (element.canSplit()) { this._splitFutureWorkspaceElement(element, ignoreResources); this._notificationService.info(message); - return new WorkspaceVerificationError(this.redo(strResource)); + return new WorkspaceVerificationError(this._redo(strResource)); } else { // Cannot safely split this workspace element => flush all undo/redo stacks for (const strResource of element.strResources) { @@ -1230,12 +1333,22 @@ export class UndoRedoService implements IUndoRedoService { const [, matchedStrResource] = this._findClosestRedoElementInGroup(groupId); if (matchedStrResource) { - return this.redo(matchedStrResource); + return this._redo(matchedStrResource); } } - public redo(resource: URI | string): Promise | void { - const strResource = typeof resource === 'string' ? resource : this.getUriComparisonKey(resource); + public redo(resourceOrSource: URI | UndoRedoSource | string): Promise | void { + if (resourceOrSource instanceof UndoRedoSource) { + const [, matchedStrResource] = this._findClosestRedoElementWithSource(resourceOrSource.id); + return matchedStrResource ? this._redo(matchedStrResource) : undefined; + } + if (typeof resourceOrSource === 'string') { + return this._redo(resourceOrSource); + } + return this._redo(this.getUriComparisonKey(resourceOrSource)); + } + + private _redo(strResource: string): Promise | void { if (!this._editStacks.has(strResource)) { return; } @@ -1251,7 +1364,7 @@ export class UndoRedoService implements IUndoRedoService { const [matchedElement, matchedStrResource] = this._findClosestRedoElementInGroup(element.groupId); if (element !== matchedElement && matchedStrResource) { // there is an element in the same group that should be redone before this one - return this.redo(matchedStrResource); + return this._redo(matchedStrResource); } } diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts index ce1033b12..2e18cc8cd 100644 --- a/src/vs/platform/update/electron-main/updateService.darwin.ts +++ b/src/vs/platform/update/electron-main/updateService.darwin.ts @@ -56,7 +56,8 @@ export class DarwinUpdateService extends AbstractUpdateService { } protected buildUpdateFeedUrl(quality: string): string | undefined { - const url = createUpdateURL('darwin', quality); + const assetID = process.arch === 'x64' ? 'darwin' : 'darwin-arm64'; + const url = createUpdateURL(assetID, quality); try { electron.autoUpdater.setFeedURL({ url }); } catch (e) { diff --git a/src/vs/platform/url/common/url.ts b/src/vs/platform/url/common/url.ts index 2069fc48e..5202226dd 100644 --- a/src/vs/platform/url/common/url.ts +++ b/src/vs/platform/url/common/url.ts @@ -18,6 +18,8 @@ export interface IOpenURLOptions { * might be shown to the user. */ trusted?: boolean; + + originalUrl?: string; } export interface IURLHandler { diff --git a/src/vs/platform/url/common/urlIpc.ts b/src/vs/platform/url/common/urlIpc.ts index 08d19ffd5..52fad0e24 100644 --- a/src/vs/platform/url/common/urlIpc.ts +++ b/src/vs/platform/url/common/urlIpc.ts @@ -19,7 +19,7 @@ export class URLHandlerChannel implements IServerChannel { call(_: unknown, command: string, arg?: any): Promise { switch (command) { - case 'handleURL': return this.handler.handleURL(URI.revive(arg)); + case 'handleURL': return this.handler.handleURL(URI.revive(arg[0]), arg[1]); } throw new Error(`Call not found: ${command}`); @@ -31,7 +31,7 @@ export class URLHandlerChannelClient implements IURLHandler { constructor(private channel: IChannel) { } handleURL(uri: URI, options?: IOpenURLOptions): Promise { - return this.channel.call('handleURL', uri.toJSON()); + return this.channel.call('handleURL', [uri.toJSON(), options]); } } diff --git a/src/vs/platform/url/electron-main/electronUrlListener.ts b/src/vs/platform/url/electron-main/electronUrlListener.ts index efc15e029..50ca2ea24 100644 --- a/src/vs/platform/url/electron-main/electronUrlListener.ts +++ b/src/vs/platform/url/electron-main/electronUrlListener.ts @@ -34,13 +34,13 @@ function uriFromRawUrl(url: string): URI | null { */ export class ElectronURLListener { - private uris: URI[] = []; + private uris: { uri: URI, url: string }[] = []; private retryCount = 0; private flushDisposable: IDisposable = Disposable.None; private disposables = new DisposableStore(); constructor( - initialUrisToHandle: URI[], + initialUrisToHandle: { uri: URI, url: string }[], private readonly urlService: IURLService, windowsMainService: IWindowsMainService, environmentService: IEnvironmentMainService @@ -64,8 +64,15 @@ export class ElectronURLListener { return url; }); - const onOpenUrl = Event.filter(Event.map(onOpenElectronUrl, uriFromRawUrl), (uri): uri is URI => !!uri); - onOpenUrl(this.urlService.open, this.urlService, this.disposables); + this.disposables.add(onOpenElectronUrl(url => { + const uri = uriFromRawUrl(url); + + if (!uri) { + return; + } + + this.urlService.open(uri, { originalUrl: url }); + })); // Send initial links to the window once it has loaded const isWindowReady = windowsMainService.getWindows() @@ -84,13 +91,13 @@ export class ElectronURLListener { return; } - const uris: URI[] = []; + const uris: { uri: URI, url: string }[] = []; - for (const uri of this.uris) { - const handled = await this.urlService.open(uri); + for (const obj of this.uris) { + const handled = await this.urlService.open(obj.uri, { originalUrl: obj.url }); if (!handled) { - uris.push(uri); + uris.push(obj); } } diff --git a/src/vs/platform/userDataSync/common/extensionsStorageSync.ts b/src/vs/platform/userDataSync/common/extensionsStorageSync.ts new file mode 100644 index 000000000..37846e053 --- /dev/null +++ b/src/vs/platform/userDataSync/common/extensionsStorageSync.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 { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; + +export interface IExtensionIdWithVersion { + id: string; + version: string; +} + +export const IExtensionsStorageSyncService = createDecorator('IExtensionsStorageSyncService'); + +export interface IExtensionsStorageSyncService { + + _serviceBrand: any; + + readonly onDidChangeExtensionsStorage: Event; + setKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion, keys: string[]): void; + getKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion): string[] | undefined; + +} + +const EXTENSION_KEYS_ID_VERSION_REGEX = /^extensionKeys\/([^.]+\..+)@(\d+\.\d+\.\d+(-.*)?)$/; + +export class ExtensionsStorageSyncService extends Disposable implements IExtensionsStorageSyncService { + + declare readonly _serviceBrand: undefined; + + private static toKey(extension: IExtensionIdWithVersion): string { + return `extensionKeys/${extension.id}@${extension.version}`; + } + + private static fromKey(key: string): IExtensionIdWithVersion | undefined { + const matches = EXTENSION_KEYS_ID_VERSION_REGEX.exec(key); + if (matches && matches[1]) { + return { id: matches[1], version: matches[2] }; + } + return undefined; + } + + private readonly _onDidChangeExtensionsStorage = this._register(new Emitter()); + readonly onDidChangeExtensionsStorage = this._onDidChangeExtensionsStorage.event; + + private readonly extensionsWithKeysForSync = new Set(); + + constructor( + @IStorageService private readonly storageService: IStorageService, + ) { + super(); + this.initialize(); + this._register(this.storageService.onDidChangeValue(e => this.onDidChangeStorageValue(e))); + } + + private initialize(): void { + const keys = this.storageService.keys(StorageScope.GLOBAL, StorageTarget.MACHINE); + for (const key of keys) { + const extensionIdWithVersion = ExtensionsStorageSyncService.fromKey(key); + if (extensionIdWithVersion) { + this.extensionsWithKeysForSync.add(extensionIdWithVersion.id.toLowerCase()); + } + } + } + + private onDidChangeStorageValue(e: IStorageValueChangeEvent): void { + if (e.scope !== StorageScope.GLOBAL) { + return; + } + + // State of extension with keys for sync has changed + if (this.extensionsWithKeysForSync.has(e.key.toLowerCase())) { + this._onDidChangeExtensionsStorage.fire(); + return; + } + + // Keys for sync of an extension has changed + const extensionIdWithVersion = ExtensionsStorageSyncService.fromKey(e.key); + if (extensionIdWithVersion) { + this.extensionsWithKeysForSync.add(extensionIdWithVersion.id.toLowerCase()); + this._onDidChangeExtensionsStorage.fire(); + return; + } + } + + setKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion, keys: string[]): void { + this.storageService.store(ExtensionsStorageSyncService.toKey(extensionIdWithVersion), JSON.stringify(keys), StorageScope.GLOBAL, StorageTarget.MACHINE); + } + + getKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion): string[] | undefined { + const keysForSyncValue = this.storageService.get(ExtensionsStorageSyncService.toKey(extensionIdWithVersion), StorageScope.GLOBAL); + return keysForSyncValue ? JSON.parse(keysForSyncValue) : undefined; + } +} diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index f34669f83..e716e1355 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -21,12 +21,12 @@ import { URI } from 'vs/base/common/uri'; import { format } from 'vs/base/common/jsonFormatter'; import { applyEdits } from 'vs/base/common/jsonEdit'; import { compare } from 'vs/base/common/strings'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { getErrorMessage } from 'vs/base/common/errors'; import { forEach, IStringDictionary } from 'vs/base/common/collections'; +import { IExtensionsStorageSyncService } from 'vs/platform/userDataSync/common/extensionsStorageSync'; interface IExtensionResourceMergeResult extends IAcceptResult { readonly added: ISyncExtension[]; @@ -105,7 +105,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse @IConfigurationService configurationService: IConfigurationService, @IUserDataSyncResourceEnablementService userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService, @ITelemetryService telemetryService: ITelemetryService, - @IStorageKeysSyncRegistryService private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, + @IExtensionsStorageSyncService private readonly extensionsStorageSyncService: IExtensionsStorageSyncService, ) { super(SyncResource.Extensions, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncResourceEnablementService, telemetryService, logService, configurationService); this._register( @@ -114,16 +114,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse Event.filter(this.extensionManagementService.onDidInstallExtension, (e => !!e.gallery)), Event.filter(this.extensionManagementService.onDidUninstallExtension, (e => !e.error)), this.extensionEnablementService.onDidChangeEnablement, - this.storageKeysSyncRegistryService.onDidChangeExtensionStorageKeys, - Event.filter(this.storageService.onDidChangeStorage, e => e.scope === StorageScope.GLOBAL - && this.storageKeysSyncRegistryService.extensionsStorageKeys.some(([extensionIdentifier]) => areSameExtensions(extensionIdentifier, { id: e.key })))), + this.extensionsStorageSyncService.onDidChangeExtensionsStorage), () => undefined, 500)(() => this.triggerLocalChange())); } protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise { const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? await parseAndMigrateExtensions(remoteUserData.syncData, this.extensionManagementService) : null; - const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : []; - const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? await parseAndMigrateExtensions(lastSyncUserData.syncData!, this.extensionManagementService) : null; + const skippedExtensions: ISyncExtension[] = lastSyncUserData?.skippedExtensions || []; + const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData?.syncData ? await parseAndMigrateExtensions(lastSyncUserData.syncData, this.extensionManagementService) : null; const installedExtensions = await this.extensionManagementService.getInstalled(); const localExtensions = this.getLocalExtensions(installedExtensions); @@ -352,7 +350,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const extensionsToRemove = installedExtensions.filter(({ identifier, isBuiltin }) => !isBuiltin && removed.some(r => areSameExtensions(identifier, r))); await Promise.all(extensionsToRemove.map(async extensionToRemove => { this.logService.trace(`${this.syncResourceLogLabel}: Uninstalling local extension...`, extensionToRemove.identifier.id); - await this.extensionManagementService.uninstall(extensionToRemove); + await this.extensionManagementService.uninstall(extensionToRemove, { donotIncludePack: true, donotCheckDependents: true }); this.logService.info(`${this.syncResourceLogLabel}: Uninstalled local extension.`, extensionToRemove.identifier.id); removeFromSkipped.push(extensionToRemove.identifier); })); @@ -365,9 +363,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse // Builtin Extension Sync: Enablement & State if (installedExtension && installedExtension.isBuiltin) { if (e.state && installedExtension.manifest.version === e.version) { - const extensionState = JSON.parse(this.storageService.get(e.identifier.id, StorageScope.GLOBAL) || '{}'); - forEach(e.state, ({ key, value }) => extensionState[key] = value); - this.storageService.store(e.identifier.id, JSON.stringify(extensionState), StorageScope.GLOBAL); + this.updateExtensionState(e.state, e.identifier.id, installedExtension.manifest.version); } if (e.disabled) { this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id); @@ -393,9 +389,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse (installedExtension ? installedExtension.manifest.version === e.version /* Installed and has same version */ : !!extension /* Installable */) ) { - const extensionState = JSON.parse(this.storageService.get(e.identifier.id, StorageScope.GLOBAL) || '{}'); - forEach(e.state, ({ key, value }) => extensionState[key] = value); - this.storageService.store(e.identifier.id, JSON.stringify(extensionState), StorageScope.GLOBAL); + this.updateExtensionState(e.state, e.identifier.id, installedExtension?.manifest.version); } if (extension) { @@ -413,7 +407,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse // Install only if the extension does not exist if (!installedExtension) { this.logService.trace(`${this.syncResourceLogLabel}: Installing extension...`, e.identifier.id, extension.version); - await this.extensionManagementService.installFromGallery(extension, { isMachineScoped: false } /* pass options to prevent install and sync dialog in web */); + await this.extensionManagementService.installFromGallery(extension, { isMachineScoped: false, donotIncludePackAndDependencies: true } /* pass options to prevent install and sync dialog in web */); this.logService.info(`${this.syncResourceLogLabel}: Installed extension.`, e.identifier.id, extension.version); removeFromSkipped.push(extension.identifier); } @@ -442,6 +436,17 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return newSkippedExtensions; } + private updateExtensionState(state: IStringDictionary, id: string, version?: string): void { + const extensionState = JSON.parse(this.storageService.get(id, StorageScope.GLOBAL) || '{}'); + const keys = version ? this.extensionsStorageSyncService.getKeysForSync({ id, version }) : undefined; + if (keys) { + keys.forEach(key => extensionState[key] = state[key]); + } else { + forEach(state, ({ key, value }) => extensionState[key] = value); + } + this.storageService.store(id, JSON.stringify(extensionState), StorageScope.GLOBAL, StorageTarget.MACHINE); + } + private parseExtensions(syncData: ISyncData): ISyncExtension[] { return JSON.parse(syncData.content); } @@ -457,10 +462,10 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse if (!isBuiltin) { syncExntesion.installed = true; } - const keys = this.storageKeysSyncRegistryService.getExtensioStorageKeys({ id: identifier.id, version: manifest.version }); - if (keys) { - const extensionStorageValue = this.storageService.get(identifier.id, StorageScope.GLOBAL) || '{}'; - try { + try { + const keys = this.extensionsStorageSyncService.getKeysForSync({ id: identifier.id, version: manifest.version }); + if (keys) { + const extensionStorageValue = this.storageService.get(identifier.id, StorageScope.GLOBAL) || '{}'; const extensionStorageState = JSON.parse(extensionStorageValue); syncExntesion.state = Object.keys(extensionStorageState).reduce((state: IStringDictionary, key) => { if (keys.includes(key)) { @@ -468,9 +473,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } return state; }, {}); - } catch (error) { - this.logService.info(`${this.syncResourceLogLabel}: Error while parsing extension state`, getErrorMessage(error)); } + } catch (error) { + this.logService.info(`${this.syncResourceLogLabel}: Error while parsing extension state`, getErrorMessage(error)); } return syncExntesion; }); @@ -499,6 +504,11 @@ export class ExtensionsInitializer extends AbstractInitializer { return; } + await this.initializeRemoteExtensions(remoteExtensions); + } + + protected async initializeRemoteExtensions(remoteExtensions: ISyncExtension[]): Promise { + const newlyEnabledExtensions: ILocalExtension[] = []; const installedExtensions = await this.extensionManagementService.getInstalled(); const newExtensionsToSync = new Map(); const installedExtensionsToSync: ISyncExtension[] = []; @@ -528,10 +538,13 @@ export class ExtensionsInitializer extends AbstractInitializer { try { const extensionToSync = newExtensionsToSync.get(galleryExtension.identifier.id.toLowerCase())!; if (extensionToSync.state) { - this.storageService.store(extensionToSync.identifier.id, JSON.stringify(extensionToSync.state), StorageScope.GLOBAL); + this.storageService.store(extensionToSync.identifier.id, JSON.stringify(extensionToSync.state), StorageScope.GLOBAL, StorageTarget.MACHINE); } this.logService.trace(`Installing extension...`, galleryExtension.identifier.id); - await this.extensionManagementService.installFromGallery(galleryExtension, { isMachineScoped: false } /* pass options to prevent install and sync dialog in web */); + const local = await this.extensionManagementService.installFromGallery(galleryExtension, { isMachineScoped: false } /* pass options to prevent install and sync dialog in web */); + if (!toDisable.some(identifier => areSameExtensions(identifier, galleryExtension.identifier))) { + newlyEnabledExtensions.push(local); + } this.logService.info(`Installed extension.`, galleryExtension.identifier.id); } catch (error) { this.logService.error(error); @@ -551,11 +564,11 @@ export class ExtensionsInitializer extends AbstractInitializer { if (extensionToSync.state) { const extensionState = JSON.parse(this.storageService.get(extensionToSync.identifier.id, StorageScope.GLOBAL) || '{}'); forEach(extensionToSync.state, ({ key, value }) => extensionState[key] = value); - this.storageService.store(extensionToSync.identifier.id, JSON.stringify(extensionState), StorageScope.GLOBAL); + this.storageService.store(extensionToSync.identifier.id, JSON.stringify(extensionState), StorageScope.GLOBAL, StorageTarget.MACHINE); } } + + return newlyEnabledExtensions; } } - - diff --git a/src/vs/platform/userDataSync/common/globalStateMerge.ts b/src/vs/platform/userDataSync/common/globalStateMerge.ts index 8af4c4de7..e519854a9 100644 --- a/src/vs/platform/userDataSync/common/globalStateMerge.ts +++ b/src/vs/platform/userDataSync/common/globalStateMerge.ts @@ -6,24 +6,22 @@ import * as objects from 'vs/base/common/objects'; import { IStorageValue } from 'vs/platform/userDataSync/common/userDataSync'; import { IStringDictionary } from 'vs/base/common/collections'; -import { IStorageKey } from 'vs/platform/userDataSync/common/storageKeys'; import { ILogService } from 'vs/platform/log/common/log'; export interface IMergeResult { local: { added: IStringDictionary, removed: string[], updated: IStringDictionary }; remote: IStringDictionary | null; - skipped: string[]; } -export function merge(localStorage: IStringDictionary, remoteStorage: IStringDictionary | null, baseStorage: IStringDictionary | null, storageKeys: ReadonlyArray, previouslySkipped: string[], logService: ILogService): IMergeResult { +export function merge(localStorage: IStringDictionary, remoteStorage: IStringDictionary | null, baseStorage: IStringDictionary | null, storageKeys: { machine: ReadonlyArray, unregistered: ReadonlyArray }, logService: ILogService): IMergeResult { if (!remoteStorage) { - return { remote: Object.keys(localStorage).length > 0 ? localStorage : null, local: { added: {}, removed: [], updated: {} }, skipped: [] }; + return { remote: Object.keys(localStorage).length > 0 ? localStorage : null, local: { added: {}, removed: [], updated: {} } }; } const localToRemote = compare(localStorage, remoteStorage); if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) { // No changes found between local and remote. - return { remote: null, local: { added: {}, removed: [], updated: {} }, skipped: [] }; + return { remote: null, local: { added: {}, removed: [], updated: {} } }; } const baseToRemote = baseStorage ? compare(baseStorage, remoteStorage) : { added: Object.keys(remoteStorage).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; @@ -31,26 +29,48 @@ export function merge(localStorage: IStringDictionary, remoteStor const local: { added: IStringDictionary, removed: string[], updated: IStringDictionary } = { added: {}, removed: [], updated: {} }; const remote: IStringDictionary = objects.deepClone(remoteStorage); - const skipped: string[] = []; + + // Added in local + for (const key of baseToLocal.added.values()) { + // Skip if local was not synced before and remote also has the key + // In this case, remote gets precedence + if (!baseStorage && baseToRemote.added.has(key)) { + continue; + } else { + remote[key] = localStorage[key]; + } + } + + // Updated in local + for (const key of baseToLocal.updated.values()) { + remote[key] = localStorage[key]; + } + + // Removed in local + for (const key of baseToLocal.removed.values()) { + // Do not remove from remote if key is not registered. + if (storageKeys.unregistered.includes(key)) { + continue; + } + delete remote[key]; + } // Added in remote for (const key of baseToRemote.added.values()) { const remoteValue = remoteStorage[key]; - const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0]; - if (!storageKey) { - skipped.push(key); - logService.trace(`GlobalState: Skipped adding ${key} in local storage as it is not registered.`); + if (storageKeys.machine.includes(key)) { + logService.info(`GlobalState: Skipped adding ${key} in local storage because it is declared as machine scoped.`); continue; } - if (storageKey.version !== remoteValue.version) { - logService.info(`GlobalState: Skipped adding ${key} in local storage. Local version '${storageKey.version}' and remote version '${remoteValue.version} are not same.`); + // Skip if the value is also added in local from the time it is last synced + if (baseStorage && baseToLocal.added.has(key)) { continue; } const localValue = localStorage[key]; if (localValue && localValue.value === remoteValue.value) { continue; } - if (baseToLocal.added.has(key)) { + if (localValue) { local.updated[key] = remoteValue; } else { local.added[key] = remoteValue; @@ -60,14 +80,12 @@ export function merge(localStorage: IStringDictionary, remoteStor // Updated in Remote for (const key of baseToRemote.updated.values()) { const remoteValue = remoteStorage[key]; - const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0]; - if (!storageKey) { - skipped.push(key); - logService.trace(`GlobalState: Skipped updating ${key} in local storage as is not registered.`); + if (storageKeys.machine.includes(key)) { + logService.info(`GlobalState: Skipped updating ${key} in local storage because it is declared as machine scoped.`); continue; } - if (storageKey.version !== remoteValue.version) { - logService.info(`GlobalState: Skipped updating ${key} in local storage. Local version '${storageKey.version}' and remote version '${remoteValue.version} are not same.`); + // Skip if the value is also updated or removed in local + if (baseToLocal.updated.has(key) || baseToLocal.removed.has(key)) { continue; } const localValue = localStorage[key]; @@ -79,67 +97,18 @@ export function merge(localStorage: IStringDictionary, remoteStor // Removed in remote for (const key of baseToRemote.removed.values()) { - const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0]; - if (!storageKey) { - logService.trace(`GlobalState: Skipped removing ${key} in local storage. It is not registered to sync.`); + if (storageKeys.machine.includes(key)) { + logService.trace(`GlobalState: Skipped removing ${key} in local storage because it is declared as machine scoped.`); + continue; + } + // Skip if the value is also updated or removed in local + if (baseToLocal.updated.has(key) || baseToLocal.removed.has(key)) { continue; } local.removed.push(key); } - // Added in local - for (const key of baseToLocal.added.values()) { - if (!baseToRemote.added.has(key)) { - remote[key] = localStorage[key]; - } - } - - // Updated in local - for (const key of baseToLocal.updated.values()) { - if (baseToRemote.updated.has(key) || baseToRemote.removed.has(key)) { - continue; - } - const remoteValue = remote[key]; - const localValue = localStorage[key]; - if (localValue.version < remoteValue.version) { - logService.info(`GlobalState: Skipped updating ${key} in remote storage. Local version '${localValue.version}' and remote version '${remoteValue.version} are not same.`); - continue; - } - remote[key] = localValue; - } - - // Removed in local - for (const key of baseToLocal.removed.values()) { - // do not remove from remote if it is updated in remote - if (baseToRemote.updated.has(key)) { - continue; - } - - const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0]; - // do not remove from remote if storage key is not found - if (!storageKey) { - skipped.push(key); - logService.trace(`GlobalState: Skipped removing ${key} in remote storage. It is not registered to sync.`); - continue; - } - - const remoteValue = remote[key]; - // do not remove from remote if local data version is old - if (storageKey.version < remoteValue.version) { - logService.info(`GlobalState: Skipped updating ${key} in remote storage. Local version '${storageKey.version}' and remote version '${remoteValue.version} are not same.`); - continue; - } - - // add to local if it was skipped before - if (previouslySkipped.indexOf(key) !== -1) { - local.added[key] = remote[key]; - continue; - } - - delete remote[key]; - } - - return { local, remote: areSame(remote, remoteStorage) ? null : remote, skipped }; + return { local, remote: areSame(remote, remoteStorage) ? null : remote }; } function compare(from: IStringDictionary, to: IStringDictionary): { added: Set, removed: Set, updated: Set } { diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index c1f4daa7b..7c69ad463 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -21,29 +21,34 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { URI } from 'vs/base/common/uri'; import { format } from 'vs/base/common/jsonFormatter'; import { applyEdits } from 'vs/base/common/jsonEdit'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { IStorageKeysSyncRegistryService, IStorageKey } from 'vs/platform/userDataSync/common/storageKeys'; -import { equals } from 'vs/base/common/arrays'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { CancellationToken } from 'vs/base/common/cancellation'; const argvStoragePrefx = 'globalState.argv.'; const argvProperties: string[] = ['locale']; +type StorageKeys = { machine: string[], user: string[], unregistered: string[] }; + interface IGlobalStateResourceMergeResult extends IAcceptResult { readonly local: { added: IStringDictionary, removed: string[], updated: IStringDictionary }; readonly remote: IStringDictionary | null; } export interface IGlobalStateResourcePreview extends IResourcePreview { - readonly skippedStorageKeys: string[]; readonly localUserData: IGlobalState; readonly previewResult: IGlobalStateResourceMergeResult; + readonly storageKeys: StorageKeys; } -interface ILastSyncUserData extends IRemoteUserData { - skippedStorageKeys: string[] | undefined; -} - +/** + * Synchronises global state that includes + * - Global storage with user scope + * - Locale from argv properties + * + * Global storage is synced without checking version just like other resources (settings, keybindings). + * If there is a change in format of the value of a storage key which requires migration then + * Owner of that key should remove that key from user scope and replace that with new user scoped key. + */ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { private static readonly GLOBAL_STATE_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'globalState', path: `/globalState.json` }); @@ -63,23 +68,22 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs @ITelemetryService telemetryService: ITelemetryService, @IConfigurationService configurationService: IConfigurationService, @IStorageService private readonly storageService: IStorageService, - @IStorageKeysSyncRegistryService private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, ) { super(SyncResource.GlobalState, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncResourceEnablementService, telemetryService, logService, configurationService); - this._register(this.fileService.watch(this.extUri.dirname(this.environmentService.argvResource))); + this._register(fileService.watch(this.extUri.dirname(this.environmentService.argvResource))); this._register( Event.any( /* Locale change */ - Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource)), - /* Storage change */ - Event.filter(this.storageService.onDidChangeStorage, e => storageKeysSyncRegistryService.storageKeys.some(({ key }) => e.key === key)), - /* Storage key registered */ - this.storageKeysSyncRegistryService.onDidChangeStorageKeys + Event.filter(fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource)), + /* Global storage with user target has changed */ + Event.filter(storageService.onDidChangeValue, e => e.scope === StorageScope.GLOBAL && e.target !== undefined ? e.target === StorageTarget.USER : storageService.keys(StorageScope.GLOBAL, StorageTarget.USER).includes(e.key)), + /* Storage key target has changed */ + this.storageService.onDidChangeTarget )((() => this.triggerLocalChange())) ); } - protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, token: CancellationToken): Promise { + protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null; const lastSyncGlobalState: IGlobalState | null = lastSyncUserData && lastSyncUserData.syncData ? JSON.parse(lastSyncUserData.syncData.content) : null; @@ -91,7 +95,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs this.logService.trace(`${this.syncResourceLogLabel}: Remote ui state does not exist. Synchronizing ui state for the first time.`); } - const { local, remote, skipped } = merge(localGloablState.storage, remoteGlobalState ? remoteGlobalState.storage : null, lastSyncGlobalState ? lastSyncGlobalState.storage : null, this.getSyncStorageKeys(), lastSyncUserData?.skippedStorageKeys || [], this.logService); + const storageKeys = this.getStorageKeys(lastSyncGlobalState); + const { local, remote } = merge(localGloablState.storage, remoteGlobalState ? remoteGlobalState.storage : null, lastSyncGlobalState ? lastSyncGlobalState.storage : null, storageKeys, this.logService); const previewResult: IGlobalStateResourceMergeResult = { content: null, local, @@ -101,7 +106,6 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs }; return [{ - skippedStorageKeys: skipped, localResource: this.localResource, localContent: this.format(localGloablState), localUserData: localGloablState, @@ -112,6 +116,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs localChange: previewResult.localChange, remoteChange: previewResult.remoteChange, acceptedResource: this.acceptedResource, + storageKeys }]; } @@ -152,7 +157,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs private async acceptRemote(resourcePreview: IGlobalStateResourcePreview): Promise { if (resourcePreview.remoteContent !== null) { const remoteGlobalState: IGlobalState = JSON.parse(resourcePreview.remoteContent); - const { local, remote } = merge(resourcePreview.localUserData.storage, remoteGlobalState.storage, null, this.getSyncStorageKeys(), resourcePreview.skippedStorageKeys, this.logService); + const { local, remote } = merge(resourcePreview.localUserData.storage, remoteGlobalState.storage, null, resourcePreview.storageKeys, this.logService); return { content: resourcePreview.remoteContent, local, @@ -171,8 +176,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } } - protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, resourcePreviews: [IGlobalStateResourcePreview, IGlobalStateResourceMergeResult][], force: boolean): Promise { - let { localUserData, skippedStorageKeys } = resourcePreviews[0][0]; + protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: [IGlobalStateResourcePreview, IGlobalStateResourceMergeResult][], force: boolean): Promise { + let { localUserData } = resourcePreviews[0][0]; let { local, remote, localChange, remoteChange } = resourcePreviews[0][1]; if (localChange === Change.None && remoteChange === Change.None) { @@ -195,10 +200,10 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs this.logService.info(`${this.syncResourceLogLabel}: Updated remote ui state`); } - if (lastSyncUserData?.ref !== remoteUserData.ref || !equals(lastSyncUserData.skippedStorageKeys, skippedStorageKeys)) { + if (lastSyncUserData?.ref !== remoteUserData.ref) { // update last sync this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized ui state...`); - await this.updateLastSyncUserData(remoteUserData, { skippedStorageKeys }); + await this.updateLastSyncUserData(remoteUserData); this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized ui state`); } } @@ -267,10 +272,10 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs storage[`${argvStoragePrefx}${argvProperty}`] = { version: 1, value: argvValue[argvProperty] }; } } - for (const { key, version } of this.storageKeysSyncRegistryService.storageKeys) { + for (const key of this.storageService.keys(StorageScope.GLOBAL, StorageTarget.USER)) { const value = this.storageService.get(key, StorageScope.GLOBAL); if (value) { - storage[key] = { version, value }; + storage[key] = { version: 1, value }; } } return { storage }; @@ -317,7 +322,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs if (updatedStorageKeys.length) { this.logService.trace(`${this.syncResourceLogLabel}: Updating global state...`); for (const key of Object.keys(updatedStorage)) { - this.storageService.store(key, updatedStorage[key], StorageScope.GLOBAL); + this.storageService.store(key, updatedStorage[key], StorageScope.GLOBAL, StorageTarget.USER); } this.logService.info(`${this.syncResourceLogLabel}: Updated global state`, Object.keys(updatedStorage)); } @@ -336,8 +341,12 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } } - private getSyncStorageKeys(): IStorageKey[] { - return [...this.storageKeysSyncRegistryService.storageKeys, ...argvProperties.map(argvProprety => ({ key: `${argvStoragePrefx}${argvProprety}`, version: 1 }))]; + private getStorageKeys(lastSyncGlobalState: IGlobalState | null): StorageKeys { + const user = this.storageService.keys(StorageScope.GLOBAL, StorageTarget.USER); + const machine = this.storageService.keys(StorageScope.GLOBAL, StorageTarget.MACHINE); + const registered = [...user, ...machine]; + const unregistered = lastSyncGlobalState?.storage ? Object.keys(lastSyncGlobalState.storage).filter(key => !key.startsWith(argvStoragePrefx) && !registered.includes(key) && this.storageService.get(key, StorageScope.GLOBAL) !== undefined) : []; + return { user, machine, unregistered }; } } @@ -385,7 +394,7 @@ export class GlobalStateInitializer extends AbstractInitializer { if (Object.keys(storage).length) { for (const key of Object.keys(storage)) { - this.storageService.store(key, storage[key], StorageScope.GLOBAL); + this.storageService.store(key, storage[key], StorageScope.GLOBAL, StorageTarget.USER); } } } diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 80f54b97a..7f9363670 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -23,6 +23,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { URI } from 'vs/base/common/uri'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { VSBuffer } from 'vs/base/common/buffer'; +import { Event } from 'vs/base/common/event'; interface ISyncContent { mac?: string; @@ -35,6 +36,10 @@ interface IKeybindingsResourcePreview extends IFileResourcePreview { previewResult: IMergeResult; } +interface ILastSyncUserData extends IRemoteUserData { + platformSpecific?: boolean; +} + export function getKeybindingsContentFromSyncContent(syncContent: string, platformSpecific: boolean): string | null { const parsed = JSON.parse(syncContent); if (!platformSpecific) { @@ -72,11 +77,12 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem @ITelemetryService telemetryService: ITelemetryService, ) { super(environmentService.keybindingsResource, SyncResource.Keybindings, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncResourceEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); + this._register(Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('settingsSync.keybindingsPerPlatform'))(() => this.triggerLocalChange())); } - protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { + protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, token: CancellationToken): Promise { const remoteContent = remoteUserData.syncData ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null; - const lastSyncContent: string | null = lastSyncUserData && lastSyncUserData.syncData ? this.getKeybindingsContentFromSyncContent(lastSyncUserData.syncData.content) : null; + const lastSyncContent: string | null = lastSyncUserData ? this.getKeybindingsContentFromLastSyncUserData(lastSyncUserData) : null; // Get file content last to get the latest const fileContent = await this.getLocalFileContent(); @@ -204,7 +210,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem if (localChange !== Change.None) { this.logService.trace(`${this.syncResourceLogLabel}: Updating local keybindings...`); if (fileContent) { - await this.backupLocal(this.toSyncContent(fileContent.value.toString(), null)); + await this.backupLocal(this.toSyncContent(fileContent.value.toString())); } await this.updateLocalFileContent(content || '[]', fileContent, force); this.logService.info(`${this.syncResourceLogLabel}: Updated local keybindings`); @@ -212,7 +218,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem if (remoteChange !== Change.None) { this.logService.trace(`${this.syncResourceLogLabel}: Updating remote keybindings...`); - const remoteContents = this.toSyncContent(content || '[]', remoteUserData.syncData ? remoteUserData.syncData.content : null); + const remoteContents = this.toSyncContent(content || '[]', remoteUserData.syncData?.content); remoteUserData = await this.updateRemoteUserData(remoteContents, force ? null : remoteUserData.ref); this.logService.info(`${this.syncResourceLogLabel}: Updated remote keybindings`); } @@ -224,15 +230,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem if (lastSyncUserData?.ref !== remoteUserData.ref) { this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized keybindings...`); - const lastSyncContent = content !== null ? this.toSyncContent(content, null) : remoteUserData.syncData?.content; - await this.updateLastSyncUserData({ - ref: remoteUserData.ref, - syncData: lastSyncContent ? { - version: remoteUserData.syncData ? remoteUserData.syncData.version : this.version, - machineId: remoteUserData.syncData!.machineId, - content: lastSyncContent - } : null - }); + await this.updateLastSyncUserData(remoteUserData, { platformSpecific: this.syncKeybindingsPerPlatform() }); this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized keybindings`); } @@ -281,6 +279,19 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem return null; } + private getKeybindingsContentFromLastSyncUserData(lastSyncUserData: ILastSyncUserData): string | null { + if (!lastSyncUserData.syncData) { + return null; + } + + // Return null if there is a change in platform specific property from last time sync. + if (lastSyncUserData.platformSpecific !== undefined && lastSyncUserData.platformSpecific !== this.syncKeybindingsPerPlatform()) { + return null; + } + + return this.getKeybindingsContentFromSyncContent(lastSyncUserData.syncData.content); + } + private getKeybindingsContentFromSyncContent(syncContent: string): string | null { try { return getKeybindingsContentFromSyncContent(syncContent, this.syncKeybindingsPerPlatform()); @@ -290,7 +301,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem } } - private toSyncContent(keybindingsContent: string, syncContent: string | null): string { + private toSyncContent(keybindingsContent: string, syncContent?: string): string { let parsed: ISyncContent = {}; try { parsed = JSON.parse(syncContent || '{}'); diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 6b583f9c8..d4e31ee05 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -13,7 +13,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { localize } from 'vs/nls'; import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancellationToken } from 'vs/base/common/cancellation'; import { updateIgnoredSettings, merge, getIgnoredSettings, isEmpty } from 'vs/platform/userDataSync/common/settingsMerge'; import { edit } from 'vs/platform/userDataSync/common/content'; @@ -198,6 +198,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement await this.backupLocal(JSON.stringify(this.toSettingsSyncContent(fileContent.value.toString()))); } await this.updateLocalFileContent(content, fileContent, force); + await this.configurationService.reloadConfiguration(ConfigurationTarget.USER_LOCAL); this.logService.info(`${this.syncResourceLogLabel}: Updated local settings`); } diff --git a/src/vs/platform/userDataSync/common/storageKeys.ts b/src/vs/platform/userDataSync/common/storageKeys.ts deleted file mode 100644 index 5ffe0786f..000000000 --- a/src/vs/platform/userDataSync/common/storageKeys.ts +++ /dev/null @@ -1,134 +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 { Event, Emitter } from 'vs/base/common/event'; -import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; - -export interface IStorageKey { - - readonly key: string; - readonly version: number; - -} - -export interface IExtensionIdWithVersion { - id: string; - version: string; -} - -export namespace ExtensionIdWithVersion { - - const EXTENSION_ID_VERSION_REGEX = /^([^.]+\..+)@(\d+\.\d+\.\d+(-.*)?)$/; - - export function toKey(extension: IExtensionIdWithVersion): string { - return `${extension.id}@${extension.version}`; - } - - export function fromKey(key: string): IExtensionIdWithVersion | undefined { - const matches = EXTENSION_ID_VERSION_REGEX.exec(key); - if (matches && matches[1]) { - return { id: matches[1], version: matches[2] }; - } - return undefined; - } -} - -export const IStorageKeysSyncRegistryService = createDecorator('IStorageKeysSyncRegistryService'); - -export interface IStorageKeysSyncRegistryService { - - _serviceBrand: any; - - /** - * All registered storage keys - */ - readonly storageKeys: ReadonlyArray; - - /** - * Event that is triggered when storage keys are changed - */ - readonly onDidChangeStorageKeys: Event>; - - /** - * Register a storage key that has to be synchronized during sync. - */ - registerStorageKey(key: IStorageKey): void; - - /** - * All registered extensions storage keys - */ - readonly extensionsStorageKeys: ReadonlyArray<[IExtensionIdentifierWithVersion, ReadonlyArray]>; - - /** - * Event that is triggered when extension storage keys are changed - */ - onDidChangeExtensionStorageKeys: Event<[IExtensionIdWithVersion, ReadonlyArray]>; - - /** - * Register storage keys that has to be synchronized for the given extension. - */ - registerExtensionStorageKeys(extension: IExtensionIdWithVersion, keys: string[]): void; - - /** - * Returns storage keys of the given extension that has to be synchronized. - */ - getExtensioStorageKeys(extension: IExtensionIdWithVersion): ReadonlyArray | undefined; -} - -export abstract class AbstractStorageKeysSyncRegistryService extends Disposable implements IStorageKeysSyncRegistryService { - - declare readonly _serviceBrand: undefined; - - protected readonly _storageKeys = new Map(); - get storageKeys(): ReadonlyArray { return [...this._storageKeys.values()]; } - - protected readonly _onDidChangeStorageKeys: Emitter> = this._register(new Emitter>()); - readonly onDidChangeStorageKeys = this._onDidChangeStorageKeys.event; - - protected readonly _extensionsStorageKeys = new Map(); - get extensionsStorageKeys() { - const result: [IExtensionIdWithVersion, ReadonlyArray][] = []; - this._extensionsStorageKeys.forEach((keys, extension) => result.push([ExtensionIdWithVersion.fromKey(extension)!, keys])); - return result; - } - protected readonly _onDidChangeExtensionStorageKeys = this._register(new Emitter<[IExtensionIdWithVersion, ReadonlyArray]>()); - readonly onDidChangeExtensionStorageKeys = this._onDidChangeExtensionStorageKeys.event; - - constructor() { - super(); - this._register(toDisposable(() => this._storageKeys.clear())); - } - - getExtensioStorageKeys(extension: IExtensionIdWithVersion): ReadonlyArray | undefined { - return this._extensionsStorageKeys.get(ExtensionIdWithVersion.toKey(extension)); - } - - protected updateExtensionStorageKeys(extension: IExtensionIdWithVersion, keys: string[]): void { - this._extensionsStorageKeys.set(ExtensionIdWithVersion.toKey(extension), keys); - this._onDidChangeExtensionStorageKeys.fire([extension, keys]); - } - - abstract registerStorageKey(key: IStorageKey): void; - abstract registerExtensionStorageKeys(extension: IExtensionIdWithVersion, keys: string[]): void; -} - -export class StorageKeysSyncRegistryService extends AbstractStorageKeysSyncRegistryService { - - _serviceBrand: any; - - registerStorageKey(storageKey: IStorageKey): void { - if (!this._storageKeys.has(storageKey.key)) { - this._storageKeys.set(storageKey.key, storageKey); - this._onDidChangeStorageKeys.fire(this.storageKeys); - } - } - - registerExtensionStorageKeys(extension: IExtensionIdWithVersion, keys: string[]): void { - this.updateExtensionStorageKeys(extension, keys); - } - -} diff --git a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts index cb2342a2e..e1960cf0d 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts @@ -11,7 +11,7 @@ import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/use import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; import { localize } from 'vs/nls'; @@ -55,7 +55,7 @@ export class UserDataAutoSyncEnablementService extends Disposable implements _IU @IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService, ) { super(); - this._register(storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); + this._register(storageService.onDidChangeValue(e => this.onDidStorageChange(e))); } isEnabled(defaultEnablement?: boolean): boolean { @@ -73,15 +73,15 @@ export class UserDataAutoSyncEnablementService extends Disposable implements _IU } setEnablement(enabled: boolean): void { - this.storageService.store(enablementKey, enabled, StorageScope.GLOBAL); + this.storageService.store(enablementKey, enabled, StorageScope.GLOBAL, StorageTarget.MACHINE); } - private onDidStorageChange(workspaceStorageChangeEvent: IWorkspaceStorageChangeEvent): void { - if (workspaceStorageChangeEvent.scope !== StorageScope.GLOBAL) { + private onDidStorageChange(storageChangeEvent: IStorageValueChangeEvent): void { + if (storageChangeEvent.scope !== StorageScope.GLOBAL) { return; } - if (enablementKey === workspaceStorageChangeEvent.key) { + if (enablementKey === storageChangeEvent.key) { this._onDidChangeEnablement.fire(this.isEnabled()); return; } @@ -110,7 +110,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto } private set syncUrl(syncUrl: URI | undefined) { if (syncUrl) { - this.storageService.store(storeUrlKey, syncUrl.toString(), StorageScope.GLOBAL); + this.storageService.store(storeUrlKey, syncUrl.toString(), StorageScope.GLOBAL, StorageTarget.MACHINE); } else { this.storageService.remove(storeUrlKey, StorageScope.GLOBAL); } @@ -325,7 +325,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto } private async disableMachineEventually(): Promise { - this.storageService.store(disableMachineEventuallyKey, true, StorageScope.GLOBAL); + this.storageService.store(disableMachineEventuallyKey, true, StorageScope.GLOBAL, StorageTarget.MACHINE); await timeout(1000 * 60 * 10); // Return if got stopped meanwhile. @@ -526,7 +526,7 @@ class AutoSync extends Disposable { // Update local session id if (manifest && manifest.session !== sessionId) { - this.storageService.store(sessionIdKey, manifest.session, StorageScope.GLOBAL); + this.storageService.store(sessionIdKey, manifest.session, StorageScope.GLOBAL, StorageTarget.MACHINE); } // Return if cancellation is requested diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 35a10cfd1..6d9d58d1b 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -220,7 +220,9 @@ export enum UserDataSyncErrorCode { TooManyRequestsAndRetryAfter = 'TooManyRequestsAndRetryAfter', /* 429 + Retry-After */ // Local Errors - ConnectionRefused = 'ConnectionRefused', + RequestFailed = 'RequestFailed', + RequestCanceled = 'RequestCanceled', + RequestTimeout = 'RequestTimeout', NoRef = 'NoRef', TurnedOff = 'TurnedOff', SessionExpired = 'SessionExpired', @@ -252,7 +254,7 @@ export class UserDataSyncError extends Error { } export class UserDataSyncStoreError extends UserDataSyncError { - constructor(message: string, code: UserDataSyncErrorCode, readonly operationId: string | undefined) { + constructor(message: string, readonly url: string, code: UserDataSyncErrorCode, readonly operationId: string | undefined) { super(message, code, undefined, operationId); } } diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index c9088b8d7..f67dfb1c0 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -9,11 +9,9 @@ import { IUserDataSyncService, IUserDataSyncUtilService, IUserDataAutoSyncServic import { URI } from 'vs/base/common/uri'; import { IStringDictionary } from 'vs/base/common/collections'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; -import { IStorageKeysSyncRegistryService, IStorageKey, IExtensionIdWithVersion, AbstractStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { ILogService } from 'vs/platform/log/common/log'; import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; -import { IExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagement'; export class UserDataSyncChannel implements IServerChannel { @@ -175,68 +173,6 @@ export class UserDataSyncUtilServiceClient implements IUserDataSyncUtilService { } -type StorageKeysSyncRegistryServiceInitData = { storageKeys: ReadonlyArray, extensionsStorageKeys: ReadonlyArray<[IExtensionIdWithVersion, ReadonlyArray]> }; - -export class StorageKeysSyncRegistryChannel implements IServerChannel { - - constructor(private readonly service: IStorageKeysSyncRegistryService) { } - - listen(_: unknown, event: string): Event { - switch (event) { - case 'onDidChangeStorageKeys': return this.service.onDidChangeStorageKeys; - case 'onDidChangeExtensionStorageKeys': return this.service.onDidChangeExtensionStorageKeys; - } - throw new Error(`Event not found: ${event}`); - } - - call(context: any, command: string, args?: any): Promise { - switch (command) { - case '_getInitialData': return Promise.resolve({ storageKeys: this.service.storageKeys, extensionsStorageKeys: this.service.extensionsStorageKeys }); - case 'registerStorageKey': return Promise.resolve(this.service.registerStorageKey(args[0])); - case 'registerExtensionStorageKeys': return Promise.resolve(this.service.registerExtensionStorageKeys(args[0], args[1])); - } - throw new Error('Invalid call'); - } -} - -export class StorageKeysSyncRegistryChannelClient extends AbstractStorageKeysSyncRegistryService { - - declare readonly _serviceBrand: undefined; - - constructor(private readonly channel: IChannel) { - super(); - this.channel.call('_getInitialData').then(({ storageKeys, extensionsStorageKeys }) => { - this.updateStorageKeys(storageKeys); - this.updateExtensionsStorageKeys(extensionsStorageKeys); - this._register(this.channel.listen>('onDidChangeStorageKeys')(storageKeys => this.updateStorageKeys(storageKeys))); - this._register(this.channel.listen<[IExtensionIdentifierWithVersion, string[]]>('onDidChangeExtensionStorageKeys')(e => this.updateExtensionStorageKeys(e[0], e[1]))); - }); - } - - private async updateStorageKeys(storageKeys: ReadonlyArray): Promise { - this._storageKeys.clear(); - for (const storageKey of storageKeys) { - this._storageKeys.set(storageKey.key, storageKey); - } - this._onDidChangeStorageKeys.fire(this.storageKeys); - } - - private async updateExtensionsStorageKeys(extensionStorageKeys: ReadonlyArray<[IExtensionIdentifierWithVersion, ReadonlyArray]>): Promise { - for (const [extension, keys] of extensionStorageKeys) { - this.updateExtensionStorageKeys(extension, [...keys]); - } - } - - registerStorageKey(storageKey: IStorageKey): void { - this.channel.call('registerStorageKey', [storageKey]); - } - - registerExtensionStorageKeys(extension: IExtensionIdentifierWithVersion, keys: string[]): void { - this.channel.call('registerExtensionStorageKeys', [extension, keys]); - } - -} - export class UserDataSyncMachinesServiceChannel implements IServerChannel { constructor(private readonly service: IUserDataSyncMachinesService) { } diff --git a/src/vs/platform/userDataSync/common/userDataSyncMachines.ts b/src/vs/platform/userDataSync/common/userDataSyncMachines.ts index 1b9d608b7..f50ad491a 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncMachines.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncMachines.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IUserDataSyncStoreService, IUserData, IUserDataSyncLogService, IUserDataManifest } from 'vs/platform/userDataSync/common/userDataSync'; import { localize } from 'vs/nls'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -103,7 +103,7 @@ export class UserDataSyncMachinesService extends Disposable implements IUserData machine.name = name; await this.writeMachinesData(machineData); if (machineData.machines.some(({ id }) => id === currentMachineId)) { - this.storageService.store(currentMachineNameKey, name, StorageScope.GLOBAL); + this.storageService.store(currentMachineNameKey, name, StorageScope.GLOBAL, StorageTarget.MACHINE); } } } diff --git a/src/vs/platform/userDataSync/common/userDataSyncResourceEnablementService.ts b/src/vs/platform/userDataSync/common/userDataSyncResourceEnablementService.ts index a84f21744..4fd2b3c33 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncResourceEnablementService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncResourceEnablementService.ts @@ -6,7 +6,7 @@ import { IUserDataSyncResourceEnablementService, ALL_SYNC_RESOURCES, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; -import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; type SyncEnablementClassification = { @@ -28,7 +28,7 @@ export class UserDataSyncResourceEnablementService extends Disposable implements @ITelemetryService private readonly telemetryService: ITelemetryService, ) { super(); - this._register(storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); + this._register(storageService.onDidChangeValue(e => this.onDidStorageChange(e))); } isResourceEnabled(resource: SyncResource): boolean { @@ -39,13 +39,13 @@ export class UserDataSyncResourceEnablementService extends Disposable implements if (this.isResourceEnabled(resource) !== enabled) { const resourceEnablementKey = getEnablementKey(resource); this.telemetryService.publicLog2<{ enabled: boolean }, SyncEnablementClassification>(resourceEnablementKey, { enabled }); - this.storageService.store(resourceEnablementKey, enabled, StorageScope.GLOBAL); + this.storageService.store(resourceEnablementKey, enabled, StorageScope.GLOBAL, StorageTarget.MACHINE); } } - private onDidStorageChange(workspaceStorageChangeEvent: IWorkspaceStorageChangeEvent): void { - if (workspaceStorageChangeEvent.scope === StorageScope.GLOBAL) { - const resourceKey = ALL_SYNC_RESOURCES.filter(resourceKey => getEnablementKey(resourceKey) === workspaceStorageChangeEvent.key)[0]; + private onDidStorageChange(storageChangeEvent: IStorageValueChangeEvent): void { + if (storageChangeEvent.scope === StorageScope.GLOBAL) { + const resourceKey = ALL_SYNC_RESOURCES.filter(resourceKey => getEnablementKey(resourceKey) === storageChangeEvent.key)[0]; if (resourceKey) { this._onDidChangeResourceEnablement.fire([resourceKey, this.isResourceEnabled(resourceKey)]); return; diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 50ea1f334..2cf4ac375 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -5,7 +5,7 @@ import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncResource, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncErrorCode, - UserDataSyncError, ISyncResourceHandle, IUserDataManifest, ISyncTask, IResourcePreview, IManualSyncTask, ISyncResourcePreview, HEADER_EXECUTION_ID, MergeState, Change, IUserDataSyncStoreManagementService + UserDataSyncError, ISyncResourceHandle, IUserDataManifest, ISyncTask, IResourcePreview, IManualSyncTask, ISyncResourcePreview, HEADER_EXECUTION_ID, MergeState, Change, IUserDataSyncStoreManagementService, UserDataSyncStoreError } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -16,7 +16,7 @@ import { GlobalStateSynchroniser } from 'vs/platform/userDataSync/common/globalS import { toErrorMessage } from 'vs/base/common/errorMessage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { equals } from 'vs/base/common/arrays'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { URI } from 'vs/base/common/uri'; import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { isEqual } from 'vs/base/common/resources'; @@ -30,6 +30,7 @@ import { isPromiseCanceledError } from 'vs/base/common/errors'; type SyncErrorClassification = { code: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; service: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + url?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; resource?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; executionId?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; }; @@ -118,9 +119,9 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } manifest = await this.userDataSyncStoreService.manifest(syncHeaders); } catch (error) { - error = UserDataSyncError.toUserDataSyncError(error); - this.telemetryService.publicLog2<{ code: string, service: string, resource?: string, executionId?: string }, SyncErrorClassification>('sync/error', { code: error.code, resource: error.resource, executionId, service: this.userDataSyncStoreManagementService.userDataSyncStore!.url.toString() }); - throw error; + const userDataSyncError = UserDataSyncError.toUserDataSyncError(error); + this.reportUserDataSyncError(userDataSyncError, executionId); + throw userDataSyncError; } let executed = false; @@ -156,9 +157,9 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ try { manifest = await this.userDataSyncStoreService.manifest(syncHeaders); } catch (error) { - error = UserDataSyncError.toUserDataSyncError(error); - this.telemetryService.publicLog2<{ code: string, service: string, resource?: string, executionId?: string }, SyncErrorClassification>('sync/error', { code: error.code, resource: error.resource, executionId, service: this.userDataSyncStoreManagementService.userDataSyncStore!.url.toString() }); - throw error; + const userDataSyncError = UserDataSyncError.toUserDataSyncError(error); + this.reportUserDataSyncError(userDataSyncError, executionId); + throw userDataSyncError; } return new ManualSyncTask(executionId, manifest, syncHeaders, this.synchronisers, this.logService); @@ -202,9 +203,9 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.logService.info(`Sync done. Took ${new Date().getTime() - startTime}ms`); this.updateLastSyncTime(); } catch (error) { - error = UserDataSyncError.toUserDataSyncError(error); - this.telemetryService.publicLog2<{ code: string, service: string, resource?: string, executionId?: string }, SyncErrorClassification>('sync/error', { code: error.code, resource: error.resource, executionId, service: this.userDataSyncStoreManagementService.userDataSyncStore!.url.toString() }); - throw error; + const userDataSyncError = UserDataSyncError.toUserDataSyncError(error); + this.reportUserDataSyncError(userDataSyncError, executionId); + throw userDataSyncError; } finally { this.updateStatus(); this._onSyncErrors.fire(this._syncErrors); @@ -365,7 +366,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ private updateLastSyncTime(): void { if (this.status === SyncStatus.Idle) { this._lastSyncTime = new Date().getTime(); - this.storageService.store(LAST_SYNC_TIME_KEY, this._lastSyncTime, StorageScope.GLOBAL); + this.storageService.store(LAST_SYNC_TIME_KEY, this._lastSyncTime, StorageScope.GLOBAL, StorageTarget.MACHINE); this._onDidChangeLastSyncTime.fire(this._lastSyncTime); } } @@ -390,6 +391,11 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.logService.error(`${source}: ${toErrorMessage(e)}`); } + private reportUserDataSyncError(userDataSyncError: UserDataSyncError, executionId: string) { + this.telemetryService.publicLog2<{ code: string, service: string, url?: string, resource?: string, executionId?: string }, SyncErrorClassification>('sync/error', + { code: userDataSyncError.code, url: userDataSyncError instanceof UserDataSyncStoreError ? userDataSyncError.url : undefined, resource: userDataSyncError.resource, executionId, service: this.userDataSyncStoreManagementService.userDataSyncStore!.url.toString() }); + } + private computeConflicts(): [SyncResource, IResourcePreview[]][] { return this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts) .map(s => ([s.resource, s.conflicts.map(toStrictResourcePreview)])); diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index fe6f311f9..409586895 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -14,13 +14,14 @@ import { IProductService, ConfigurationSyncStore } from 'vs/platform/product/com import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { generateUuid } from 'vs/base/common/uuid'; import { isWeb } from 'vs/base/common/platform'; import { Emitter, Event } from 'vs/base/common/event'; import { createCancelablePromise, timeout, CancelablePromise } from 'vs/base/common/async'; import { isString, isObject, isArray } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; +import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors'; const SYNC_SERVICE_URL_TYPE = 'sync.store.url.type'; const SYNC_PREVIOUS_STORE = 'sync.previous.store'; @@ -67,10 +68,10 @@ export abstract class AbstractUserDataSyncStoreManagementService extends Disposa const syncStore = value as ConfigurationSyncStore; const canSwitch = !!syncStore.canSwitch && !configuredStore?.url; const type: UserDataSyncStoreType | undefined = canSwitch ? this.storageService.get(SYNC_SERVICE_URL_TYPE, StorageScope.GLOBAL) as UserDataSyncStoreType : undefined; - const url = configuredStore?.url - || type === 'insiders' ? syncStore.insidersUrl - : type === 'stable' ? syncStore.stableUrl - : syncStore.url; + const url = configuredStore?.url || + (type === 'insiders' ? syncStore.insidersUrl + : type === 'stable' ? syncStore.stableUrl + : syncStore.url); return { url: URI.parse(url), type, @@ -111,7 +112,7 @@ export class UserDataSyncStoreManagementService extends AbstractUserDataSyncStor const syncStore = this.productService[CONFIGURATION_SYNC_STORE_KEY]; if (syncStore) { - this.storageService.store(SYNC_PREVIOUS_STORE, JSON.stringify(syncStore), StorageScope.GLOBAL); + this.storageService.store(SYNC_PREVIOUS_STORE, JSON.stringify(syncStore), StorageScope.GLOBAL, StorageTarget.MACHINE); } else { this.storageService.remove(SYNC_PREVIOUS_STORE, StorageScope.GLOBAL); } @@ -122,7 +123,7 @@ export class UserDataSyncStoreManagementService extends AbstractUserDataSyncStor if (type === this.userDataSyncStore.defaultType) { this.storageService.remove(SYNC_SERVICE_URL_TYPE, StorageScope.GLOBAL); } else { - this.storageService.store(SYNC_SERVICE_URL_TYPE, type, StorageScope.GLOBAL); + this.storageService.store(SYNC_SERVICE_URL_TYPE, type, StorageScope.GLOBAL, StorageTarget.MACHINE); } this.updateUserDataSyncStore(); } @@ -207,7 +208,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync } if (this._donotMakeRequestsUntil) { - this.storageService.store(DONOT_MAKE_REQUESTS_UNTIL_KEY, this._donotMakeRequestsUntil.getTime(), StorageScope.GLOBAL); + this.storageService.store(DONOT_MAKE_REQUESTS_UNTIL_KEY, this._donotMakeRequestsUntil.getTime(), StorageScope.GLOBAL, StorageTarget.MACHINE); this.resetDonotMakeRequestsUntilPromise = createCancelablePromise(token => timeout(this._donotMakeRequestsUntil!.getTime() - Date.now(), token).then(() => this.setDonotMakeRequestsUntil(undefined))); } else { this.storageService.remove(DONOT_MAKE_REQUESTS_UNTIL_KEY, StorageScope.GLOBAL); @@ -225,7 +226,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync const uri = joinPath(this.userDataSyncStoreUrl, 'resource', resource); const headers: IHeaders = {}; - const context = await this.request({ type: 'GET', url: uri.toString(), headers }, [], CancellationToken.None); + const context = await this.request(uri.toString(), { type: 'GET', headers }, [], CancellationToken.None); const result = await asJson<{ url: string, created: number }[]>(context) || []; return result.map(({ url, created }) => ({ ref: relativePath(uri, uri.with({ path: url }))!, created: created * 1000 /* Server returns in seconds */ })); @@ -240,7 +241,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync const headers: IHeaders = {}; headers['Cache-Control'] = 'no-cache'; - const context = await this.request({ type: 'GET', url, headers }, [], CancellationToken.None); + const context = await this.request(url, { type: 'GET', headers }, [], CancellationToken.None); const content = await asText(context); return content; } @@ -253,7 +254,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync const url = joinPath(this.userDataSyncStoreUrl, 'resource', resource).toString(); const headers: IHeaders = {}; - await this.request({ type: 'DELETE', url, headers }, [], CancellationToken.None); + await this.request(url, { type: 'DELETE', headers }, [], CancellationToken.None); } async read(resource: ServerResource, oldValue: IUserData | null, headers: IHeaders = {}): Promise { @@ -269,7 +270,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync headers['If-None-Match'] = oldValue.ref; } - const context = await this.request({ type: 'GET', url, headers }, [304], CancellationToken.None); + const context = await this.request(url, { type: 'GET', headers }, [304], CancellationToken.None); if (context.res.statusCode === 304) { // There is no new value. Hence return the old value. @@ -278,7 +279,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync const ref = context.res.headers['etag']; if (!ref) { - throw new UserDataSyncStoreError('Server did not return the ref', UserDataSyncErrorCode.NoRef, context.res.headers[HEADER_OPERATION_ID]); + throw new UserDataSyncStoreError('Server did not return the ref', url, UserDataSyncErrorCode.NoRef, context.res.headers[HEADER_OPERATION_ID]); } const content = await asText(context); return { ref, content }; @@ -296,11 +297,11 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync headers['If-Match'] = ref; } - const context = await this.request({ type: 'POST', url, data, headers }, [], CancellationToken.None); + const context = await this.request(url, { type: 'POST', data, headers }, [], CancellationToken.None); const newRef = context.res.headers['etag']; if (!newRef) { - throw new UserDataSyncStoreError('Server did not return the ref', UserDataSyncErrorCode.NoRef, context.res.headers[HEADER_OPERATION_ID]); + throw new UserDataSyncStoreError('Server did not return the ref', url, UserDataSyncErrorCode.NoRef, context.res.headers[HEADER_OPERATION_ID]); } return newRef; } @@ -314,7 +315,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync headers = { ...headers }; headers['Content-Type'] = 'application/json'; - const context = await this.request({ type: 'GET', url, headers }, [], CancellationToken.None); + const context = await this.request(url, { type: 'GET', headers }, [], CancellationToken.None); const manifest = await asJson(context); const currentSessionId = this.storageService.get(USER_SESSION_ID_KEY, StorageScope.GLOBAL); @@ -331,7 +332,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync if (manifest) { // update session - this.storageService.store(USER_SESSION_ID_KEY, manifest.session, StorageScope.GLOBAL); + this.storageService.store(USER_SESSION_ID_KEY, manifest.session, StorageScope.GLOBAL, StorageTarget.MACHINE); } return manifest; @@ -345,7 +346,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync const url = joinPath(this.userDataSyncStoreUrl, 'resource').toString(); const headers: IHeaders = { 'Content-Type': 'text/plain' }; - await this.request({ type: 'DELETE', url, headers }, [], CancellationToken.None); + await this.request(url, { type: 'DELETE', headers }, [], CancellationToken.None); // clear cached session. this.clearSession(); @@ -356,13 +357,13 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync this.storageService.remove(MACHINE_SESSION_ID_KEY, StorageScope.GLOBAL); } - private async request(options: IRequestOptions, successCodes: number[], token: CancellationToken): Promise { + private async request(url: string, options: IRequestOptions, successCodes: number[], token: CancellationToken): Promise { if (!this.authToken) { - throw new UserDataSyncStoreError('No Auth Token Available', UserDataSyncErrorCode.Unauthorized, undefined); + throw new UserDataSyncStoreError('No Auth Token Available', url, UserDataSyncErrorCode.Unauthorized, undefined); } if (this._donotMakeRequestsUntil && Date.now() < this._donotMakeRequestsUntil.getTime()) { - throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too many requests (429).`, UserDataSyncErrorCode.TooManyRequestsAndRetryAfter, undefined); + throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because of too many requests (429).`, url, UserDataSyncErrorCode.TooManyRequestsAndRetryAfter, undefined); } this.setDonotMakeRequestsUntil(undefined); @@ -377,21 +378,23 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync // Add session headers this.addSessionHeaders(options.headers); - this.logService.trace('Sending request to server', { url: options.url, type: options.type, headers: { ...options.headers, ...{ authorization: undefined } } }); + this.logService.trace('Sending request to server', { url, type: options.type, headers: { ...options.headers, ...{ authorization: undefined } } }); let context; try { - context = await this.session.request(options, token); + context = await this.session.request(url, options, token); } catch (e) { if (!(e instanceof UserDataSyncStoreError)) { - e = new UserDataSyncStoreError(`Connection refused for the request '${options.url?.toString()}'.`, UserDataSyncErrorCode.ConnectionRefused, undefined); + const code = isPromiseCanceledError(e) ? UserDataSyncErrorCode.RequestCanceled + : getErrorMessage(e).startsWith('XHR timeout') ? UserDataSyncErrorCode.RequestTimeout : UserDataSyncErrorCode.RequestFailed; + e = new UserDataSyncStoreError(`Connection refused for the request '${url}'.`, url, code, undefined); } - this.logService.info('Request failed', options.url); + this.logService.info('Request failed', url); throw e; } const operationId = context.res.headers[HEADER_OPERATION_ID]; - const requestInfo = { url: options.url, status: context.res.statusCode, 'execution-id': options.headers[HEADER_EXECUTION_ID], 'operation-id': operationId }; + const requestInfo = { url, status: context.res.statusCode, 'execution-id': options.headers[HEADER_EXECUTION_ID], 'operation-id': operationId }; const isSuccess = isSuccessContext(context) || (context.res.statusCode && successCodes.indexOf(context.res.statusCode) !== -1); if (isSuccess) { this.logService.trace('Request succeeded', requestInfo); @@ -402,43 +405,43 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync if (context.res.statusCode === 401) { this.authToken = undefined; this._onTokenFailed.fire(); - throw new UserDataSyncStoreError(`Request '${options.url?.toString()}' failed because of Unauthorized (401).`, UserDataSyncErrorCode.Unauthorized, operationId); + throw new UserDataSyncStoreError(`Request '${url}' failed because of Unauthorized (401).`, url, UserDataSyncErrorCode.Unauthorized, operationId); } this._onTokenSucceed.fire(); if (context.res.statusCode === 409) { - throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of Conflict (409). There is new data for this resource. Make the request again with latest data.`, UserDataSyncErrorCode.Conflict, operationId); + throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because of Conflict (409). There is new data for this resource. Make the request again with latest data.`, url, UserDataSyncErrorCode.Conflict, operationId); } if (context.res.statusCode === 410) { - throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because the requested resource is not longer available (410).`, UserDataSyncErrorCode.Gone, operationId); + throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because the requested resource is not longer available (410).`, url, UserDataSyncErrorCode.Gone, operationId); } if (context.res.statusCode === 412) { - throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of Precondition Failed (412). There is new data for this resource. Make the request again with latest data.`, UserDataSyncErrorCode.PreconditionFailed, operationId); + throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because of Precondition Failed (412). There is new data for this resource. Make the request again with latest data.`, url, UserDataSyncErrorCode.PreconditionFailed, operationId); } if (context.res.statusCode === 413) { - throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too large payload (413).`, UserDataSyncErrorCode.TooLarge, operationId); + throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because of too large payload (413).`, url, UserDataSyncErrorCode.TooLarge, operationId); } if (context.res.statusCode === 426) { - throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed with status Upgrade Required (426). Please upgrade the client and try again.`, UserDataSyncErrorCode.UpgradeRequired, operationId); + throw new UserDataSyncStoreError(`${options.type} request '${url}' failed with status Upgrade Required (426). Please upgrade the client and try again.`, url, UserDataSyncErrorCode.UpgradeRequired, operationId); } if (context.res.statusCode === 429) { const retryAfter = context.res.headers['retry-after']; if (retryAfter) { this.setDonotMakeRequestsUntil(new Date(Date.now() + (parseInt(retryAfter) * 1000))); - throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too many requests (429).`, UserDataSyncErrorCode.TooManyRequestsAndRetryAfter, operationId); + throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because of too many requests (429).`, url, UserDataSyncErrorCode.TooManyRequestsAndRetryAfter, operationId); } else { - throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too many requests (429).`, UserDataSyncErrorCode.TooManyRequests, operationId); + throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because of too many requests (429).`, url, UserDataSyncErrorCode.TooManyRequests, operationId); } } if (!isSuccess) { - throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, operationId); + throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, url, UserDataSyncErrorCode.Unknown, operationId); } return context; @@ -448,7 +451,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync let machineSessionId = this.storageService.get(MACHINE_SESSION_ID_KEY, StorageScope.GLOBAL); if (machineSessionId === undefined) { machineSessionId = generateUuid(); - this.storageService.store(MACHINE_SESSION_ID_KEY, machineSessionId, StorageScope.GLOBAL); + this.storageService.store(MACHINE_SESSION_ID_KEY, machineSessionId, StorageScope.GLOBAL, StorageTarget.MACHINE); } headers['X-Machine-Session-Id'] = machineSessionId; @@ -490,18 +493,20 @@ export class RequestsSession { private readonly logService: IUserDataSyncLogService, ) { } - request(options: IRequestOptions, token: CancellationToken): Promise { + request(url: string, options: IRequestOptions, token: CancellationToken): Promise { if (this.isExpired()) { this.reset(); } + options.url = url; + if (this.requests.length >= this.limit) { this.logService.info('Too many requests', ...this.requests); - throw new UserDataSyncStoreError(`Too many requests. Only ${this.limit} requests allowed in ${this.interval / (1000 * 60)} minutes.`, UserDataSyncErrorCode.LocalTooManyRequests, undefined); + throw new UserDataSyncStoreError(`Too many requests. Only ${this.limit} requests allowed in ${this.interval / (1000 * 60)} minutes.`, url, UserDataSyncErrorCode.LocalTooManyRequests, undefined); } this.startTime = this.startTime || new Date(); - this.requests.push(options.url!); + this.requests.push(url); return this.requestService.request(options, token); } diff --git a/src/vs/platform/userDataSync/test/common/globalStateMerge.test.ts b/src/vs/platform/userDataSync/test/common/globalStateMerge.test.ts index e89763a4d..27fb0a235 100644 --- a/src/vs/platform/userDataSync/test/common/globalStateMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/globalStateMerge.test.ts @@ -9,11 +9,11 @@ import { NullLogService } from 'vs/platform/log/common/log'; suite('GlobalStateMerge', () => { - test('merge when local and remote are same with one value', async () => { + test('merge when local and remote are same with one value and local is not synced yet', async () => { const local = { 'a': { version: 1, value: 'a' } }; const remote = { 'a': { version: 1, value: 'a' } }; - const actual = merge(local, remote, null, [{ key: 'a', version: 1 }], [], new NullLogService()); + const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService()); assert.deepEqual(actual.local.added, {}); assert.deepEqual(actual.local.updated, {}); @@ -21,11 +21,11 @@ suite('GlobalStateMerge', () => { assert.deepEqual(actual.remote, null); }); - test('merge when local and remote are same with multiple entries', async () => { + test('merge when local and remote are same with multiple entries and local is not synced yet', async () => { const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; const remote = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; - const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService()); assert.deepEqual(actual.local.added, {}); assert.deepEqual(actual.local.updated, {}); @@ -33,11 +33,11 @@ suite('GlobalStateMerge', () => { assert.deepEqual(actual.remote, null); }); - test('merge when local and remote are same with multiple entries in different order', async () => { + test('merge when local and remote are same with multiple entries in different order and local is not synced yet', async () => { const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; - const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService()); assert.deepEqual(actual.local.added, {}); assert.deepEqual(actual.local.updated, {}); @@ -50,7 +50,7 @@ suite('GlobalStateMerge', () => { const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; const base = { 'b': { version: 1, value: 'a' } }; - const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + const actual = merge(local, remote, base, { machine: [], unregistered: [] }, new NullLogService()); assert.deepEqual(actual.local.added, {}); assert.deepEqual(actual.local.updated, {}); @@ -58,11 +58,11 @@ suite('GlobalStateMerge', () => { assert.deepEqual(actual.remote, null); }); - test('merge when a new entry is added to remote', async () => { + test('merge when a new entry is added to remote and local has not synced yet', async () => { const local = { 'a': { version: 1, value: 'a' } }; const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; - const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService()); assert.deepEqual(actual.local.added, { 'b': { version: 1, value: 'b' } }); assert.deepEqual(actual.local.updated, {}); @@ -70,11 +70,11 @@ suite('GlobalStateMerge', () => { assert.deepEqual(actual.remote, null); }); - test('merge when multiple new entries are added to remote', async () => { + test('merge when multiple new entries are added to remote and local is not synced yet', async () => { const local = {}; const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; - const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService()); assert.deepEqual(actual.local.added, { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }); assert.deepEqual(actual.local.updated, {}); @@ -86,7 +86,7 @@ suite('GlobalStateMerge', () => { const local = { 'a': { version: 1, value: 'a' } }; const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; - const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + const actual = merge(local, remote, local, { machine: [], unregistered: [] }, new NullLogService()); assert.deepEqual(actual.local.added, { 'b': { version: 1, value: 'b' } }); assert.deepEqual(actual.local.updated, {}); @@ -98,7 +98,7 @@ suite('GlobalStateMerge', () => { const local = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; const remote = { 'a': { version: 1, value: 'a' } }; - const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + const actual = merge(local, remote, local, { machine: [], unregistered: [] }, new NullLogService()); assert.deepEqual(actual.local.added, {}); assert.deepEqual(actual.local.updated, {}); @@ -110,7 +110,7 @@ suite('GlobalStateMerge', () => { const local = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; const remote = {}; - const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + const actual = merge(local, remote, local, { machine: [], unregistered: [] }, new NullLogService()); assert.deepEqual(actual.local.added, {}); assert.deepEqual(actual.local.updated, {}); @@ -122,7 +122,7 @@ suite('GlobalStateMerge', () => { const local = { 'a': { version: 1, value: 'a' } }; const remote = { 'a': { version: 1, value: 'b' } }; - const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + const actual = merge(local, remote, local, { machine: [], unregistered: [] }, new NullLogService()); assert.deepEqual(actual.local.added, {}); assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } }); @@ -134,7 +134,7 @@ suite('GlobalStateMerge', () => { const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; const remote = { 'a': { version: 1, value: 'd' }, 'c': { version: 1, value: 'c' } }; - const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService()); + const actual = merge(local, remote, local, { machine: [], unregistered: [] }, new NullLogService()); assert.deepEqual(actual.local.added, { 'c': { version: 1, value: 'c' } }); assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'd' } }); @@ -142,11 +142,11 @@ suite('GlobalStateMerge', () => { assert.deepEqual(actual.remote, null); }); - test('merge when new entries are added to local', async () => { + test('merge when new entries are added to local and local is not synced yet', async () => { const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; const remote = { 'a': { version: 1, value: 'a' } }; - const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService()); + const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService()); assert.deepEqual(actual.local.added, {}); assert.deepEqual(actual.local.updated, {}); @@ -158,7 +158,7 @@ suite('GlobalStateMerge', () => { const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' }, 'c': { version: 1, value: 'c' } }; const remote = { 'a': { version: 1, value: 'a' } }; - const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService()); + const actual = merge(local, remote, remote, { machine: [], unregistered: [] }, new NullLogService()); assert.deepEqual(actual.local.added, {}); assert.deepEqual(actual.local.updated, {}); @@ -170,7 +170,7 @@ suite('GlobalStateMerge', () => { const local = { 'a': { version: 1, value: 'a' } }; const remote = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; - const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService()); + const actual = merge(local, remote, remote, { machine: [], unregistered: [] }, new NullLogService()); assert.deepEqual(actual.local.added, {}); assert.deepEqual(actual.local.updated, {}); @@ -182,7 +182,7 @@ suite('GlobalStateMerge', () => { const local = { 'a': { version: 1, value: 'b' } }; const remote = { 'a': { version: 1, value: 'a' } }; - const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService()); + const actual = merge(local, remote, remote, { machine: [], unregistered: [] }, new NullLogService()); assert.deepEqual(actual.local.added, {}); assert.deepEqual(actual.local.updated, {}); @@ -194,7 +194,7 @@ suite('GlobalStateMerge', () => { const local = { 'a': { version: 1, value: 'd' }, 'b': { version: 1, value: 'b' } }; const remote = { 'a': { version: 1, value: 'a' }, 'c': { version: 1, value: 'c' } }; - const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService()); + const actual = merge(local, remote, remote, { machine: [], unregistered: [] }, new NullLogService()); assert.deepEqual(actual.local.added, {}); assert.deepEqual(actual.local.updated, {}); @@ -202,11 +202,11 @@ suite('GlobalStateMerge', () => { assert.deepEqual(actual.remote, local); }); - test('merge when local and remote with one entry but different value', async () => { + test('merge when local and remote with one entry but different value and local is not synced yet', async () => { const local = { 'a': { version: 1, value: 'a' } }; const remote = { 'a': { version: 1, value: 'b' } }; - const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService()); + const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService()); assert.deepEqual(actual.local.added, {}); assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } }); @@ -219,12 +219,12 @@ suite('GlobalStateMerge', () => { const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'd' } }; const remote = { 'a': { version: 1, value: 'a' }, 'c': { version: 1, value: 'c' } }; - const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService()); + const actual = merge(local, remote, base, { machine: [], unregistered: [] }, new NullLogService()); assert.deepEqual(actual.local.added, { 'c': { version: 1, value: 'c' } }); assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, ['b']); - assert.deepEqual(actual.remote, null); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, { 'a': { version: 1, value: 'a' }, 'c': { version: 1, value: 'c' }, 'b': { version: 1, value: 'd' } }); }); test('merge with single entry and local is empty', async () => { @@ -232,32 +232,32 @@ suite('GlobalStateMerge', () => { const local = {}; const remote = { 'a': { version: 1, value: 'b' } }; - const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService()); + const actual = merge(local, remote, base, { machine: [], unregistered: [] }, new NullLogService()); assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } }); + assert.deepEqual(actual.local.updated, {}); assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, null); + assert.deepEqual(actual.remote, local); }); - test('merge when local and remote has moved forwareded with conflicts', async () => { + test('merge when local and remote has moved forward with conflicts', async () => { const base = { 'a': { version: 1, value: 'a' } }; const local = { 'a': { version: 1, value: 'd' } }; const remote = { 'a': { version: 1, value: 'b' } }; - const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService()); + const actual = merge(local, remote, base, { machine: [], unregistered: [] }, new NullLogService()); assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } }); + assert.deepEqual(actual.local.updated, {}); assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, null); + assert.deepEqual(actual.remote, local); }); - test('merge when a new entry is added to remote but not a registered key', async () => { + test('merge when a new entry is added to remote but scoped to machine locally and local is not synced yet', async () => { const local = { 'a': { version: 1, value: 'a' } }; const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; - const actual = merge(local, remote, null, [{ key: 'a', version: 1 }], [], new NullLogService()); + const actual = merge(local, remote, null, { machine: ['b'], unregistered: [] }, new NullLogService()); assert.deepEqual(actual.local.added, {}); assert.deepEqual(actual.local.updated, {}); @@ -265,23 +265,11 @@ suite('GlobalStateMerge', () => { assert.deepEqual(actual.remote, null); }); - test('merge when a new entry is added to remote but different version', async () => { - const local = { 'a': { version: 1, value: 'a' } }; - const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } }; - - const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); - - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, null); - }); - - test('merge when an entry is updated to remote but not a registered key', async () => { + test('merge when an entry is updated to remote but scoped to machine locally', async () => { const local = { 'a': { version: 1, value: 'a' } }; const remote = { 'a': { version: 1, value: 'b' } }; - const actual = merge(local, remote, local, [], [], new NullLogService()); + const actual = merge(local, remote, local, { machine: ['a'], unregistered: [] }, new NullLogService()); assert.deepEqual(actual.local.added, {}); assert.deepEqual(actual.local.updated, {}); @@ -289,92 +277,43 @@ suite('GlobalStateMerge', () => { assert.deepEqual(actual.remote, null); }); - test('merge when a new entry is updated to remote but different version', async () => { + test('merge when a local value is removed and scoped to machine locally', async () => { + const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; + const local = { 'a': { version: 1, value: 'a' } }; + const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; + + const actual = merge(local, remote, base, { machine: ['b'], unregistered: [] }, new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, local); + }); + + test('merge when local moved forwared by changing a key to machine scope', async () => { + const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; + const remote = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; + const local = { 'a': { version: 1, value: 'a' } }; + + const actual = merge(local, remote, base, { machine: ['b'], unregistered: [] }, new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, local); + }); + + test('merge should not remove remote keys if not registered', async () => { const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; - const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } }; + const base = { 'a': { version: 1, value: 'a' }, 'c': { version: 1, value: 'c' } }; + const remote = { 'a': { version: 1, value: 'a' }, 'c': { version: 1, value: 'c' } }; - const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + const actual = merge(local, remote, base, { machine: [], unregistered: ['c'] }, new NullLogService()); assert.deepEqual(actual.local.added, {}); assert.deepEqual(actual.local.updated, {}); assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, null); - }); - - test('merge when a local value is update with lower version', async () => { - const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'c' } }; - const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } }; - - const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); - - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, null); - }); - - test('merge when a local value is update with higher version', async () => { - const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 2, value: 'c' } }; - const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; - - const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 2 }], [], new NullLogService()); - - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, local); - }); - - test('merge when a local value is removed but not registered', async () => { - const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; - const local = { 'a': { version: 1, value: 'a' } }; - const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } }; - - const actual = merge(local, remote, base, [{ key: 'a', version: 1 }], [], new NullLogService()); - - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, null); - }); - - test('merge when a local value is removed with lower version', async () => { - const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; - const local = { 'a': { version: 1, value: 'a' } }; - const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } }; - - const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); - - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, null); - }); - - test('merge when a local value is removed with higher version', async () => { - const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; - const local = { 'a': { version: 1, value: 'a' } }; - const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; - - const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 2 }], [], new NullLogService()); - - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, local); - }); - - test('merge when a local value is not yet registered', async () => { - const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; - const local = { 'a': { version: 1, value: 'a' } }; - const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; - - const actual = merge(local, remote, base, [{ key: 'a', version: 1 }], [], new NullLogService()); - - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, null); + assert.deepEqual(actual.remote, { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' }, 'c': { version: 1, value: 'c' } }); }); }); diff --git a/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts b/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts index 2d5594c01..324469b7d 100644 --- a/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts @@ -10,10 +10,9 @@ import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { IFileService } from 'vs/platform/files/common/files'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { GlobalStateSynchroniser } from 'vs/platform/userDataSync/common/globalStateSync'; import { VSBuffer } from 'vs/base/common/buffer'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; suite('GlobalStateSync', () => { @@ -28,17 +27,11 @@ suite('GlobalStateSync', () => { setup(async () => { testClient = disposableStore.add(new UserDataSyncClient(server)); await testClient.setUp(true); - let storageKeysSyncRegistryService = testClient.instantiationService.get(IStorageKeysSyncRegistryService); - storageKeysSyncRegistryService.registerStorageKey({ key: 'a', version: 1 }); - storageKeysSyncRegistryService.registerStorageKey({ key: 'b', version: 1 }); testObject = (testClient.instantiationService.get(IUserDataSyncService) as UserDataSyncService).getSynchroniser(SyncResource.GlobalState) as GlobalStateSynchroniser; disposableStore.add(toDisposable(() => testClient.instantiationService.get(IUserDataSyncStoreService).clear())); client2 = disposableStore.add(new UserDataSyncClient(server)); await client2.setUp(true); - storageKeysSyncRegistryService = client2.instantiationService.get(IStorageKeysSyncRegistryService); - storageKeysSyncRegistryService.registerStorageKey({ key: 'a', version: 1 }); - storageKeysSyncRegistryService.registerStorageKey({ key: 'b', version: 1 }); }); teardown(() => disposableStore.clear()); @@ -72,7 +65,7 @@ suite('GlobalStateSync', () => { test('when global state is created after first sync', async () => { await testObject.sync(await testClient.manifest()); - updateStorage('a', 'value1', testClient); + updateUserStorage('a', 'value1', testClient); let lastSyncUserData = await testObject.getLastSyncUserData(); const manifest = await testClient.manifest(); @@ -91,7 +84,8 @@ suite('GlobalStateSync', () => { }); test('first time sync - outgoing to server (no state)', async () => { - updateStorage('a', 'value1', testClient); + updateUserStorage('a', 'value1', testClient); + updateMachineStorage('b', 'value1', testClient); await updateLocale(testClient); await testObject.sync(await testClient.manifest()); @@ -105,7 +99,7 @@ suite('GlobalStateSync', () => { }); test('first time sync - incoming from server (no state)', async () => { - updateStorage('a', 'value1', client2); + updateUserStorage('a', 'value1', client2); await updateLocale(client2); await client2.sync(); @@ -118,10 +112,10 @@ suite('GlobalStateSync', () => { }); test('first time sync when storage exists', async () => { - updateStorage('a', 'value1', client2); + updateUserStorage('a', 'value1', client2); await client2.sync(); - updateStorage('b', 'value2', testClient); + updateUserStorage('b', 'value2', testClient); await testObject.sync(await testClient.manifest()); assert.equal(testObject.status, SyncStatus.Idle); assert.deepEqual(testObject.conflicts, []); @@ -136,10 +130,10 @@ suite('GlobalStateSync', () => { }); test('first time sync when storage exists - has conflicts', async () => { - updateStorage('a', 'value1', client2); + updateUserStorage('a', 'value1', client2); await client2.sync(); - updateStorage('a', 'value2', client2); + updateUserStorage('a', 'value2', client2); await testObject.sync(await testClient.manifest()); assert.equal(testObject.status, SyncStatus.Idle); @@ -154,10 +148,10 @@ suite('GlobalStateSync', () => { }); test('sync adding a storage value', async () => { - updateStorage('a', 'value1', testClient); + updateUserStorage('a', 'value1', testClient); await testObject.sync(await testClient.manifest()); - updateStorage('b', 'value2', testClient); + updateUserStorage('b', 'value2', testClient); await testObject.sync(await testClient.manifest()); assert.equal(testObject.status, SyncStatus.Idle); assert.deepEqual(testObject.conflicts, []); @@ -172,10 +166,10 @@ suite('GlobalStateSync', () => { }); test('sync updating a storage value', async () => { - updateStorage('a', 'value1', testClient); + updateUserStorage('a', 'value1', testClient); await testObject.sync(await testClient.manifest()); - updateStorage('a', 'value2', testClient); + updateUserStorage('a', 'value2', testClient); await testObject.sync(await testClient.manifest()); assert.equal(testObject.status, SyncStatus.Idle); assert.deepEqual(testObject.conflicts, []); @@ -189,8 +183,8 @@ suite('GlobalStateSync', () => { }); test('sync removing a storage value', async () => { - updateStorage('a', 'value1', testClient); - updateStorage('b', 'value2', testClient); + updateUserStorage('a', 'value1', testClient); + updateUserStorage('b', 'value2', testClient); await testObject.sync(await testClient.manifest()); removeStorage('b', testClient); @@ -218,9 +212,14 @@ suite('GlobalStateSync', () => { await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'en' }))); } - function updateStorage(key: string, value: string, client: UserDataSyncClient): void { + function updateUserStorage(key: string, value: string, client: UserDataSyncClient): void { const storageService = client.instantiationService.get(IStorageService); - storageService.store(key, value, StorageScope.GLOBAL); + storageService.store(key, value, StorageScope.GLOBAL, StorageTarget.USER); + } + + function updateMachineStorage(key: string, value: string, client: UserDataSyncClient): void { + const storageService = client.instantiationService.get(IStorageService); + storageService.store(key, value, StorageScope.GLOBAL, StorageTarget.MACHINE); } function removeStorage(key: string, client: UserDataSyncClient): void { diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 6e821b1ba..322046976 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -36,10 +36,10 @@ import { IUserDataSyncAccountService, UserDataSyncAccountService } from 'vs/plat import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService'; -import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { IUserDataSyncMachinesService, UserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; import { UserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataAutoSyncService'; import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; +import { ExtensionsStorageSyncService, IExtensionsStorageSyncService } from 'vs/platform/userDataSync/common/extensionsStorageSync'; export class UserDataSyncClient extends Disposable { @@ -104,9 +104,9 @@ export class UserDataSyncClient extends Disposable { this.instantiationService.stub(IUserDataSyncBackupStoreService, this.instantiationService.createInstance(UserDataSyncBackupStoreService)); this.instantiationService.stub(IUserDataSyncUtilService, new TestUserDataSyncUtilService()); this.instantiationService.stub(IUserDataSyncResourceEnablementService, this.instantiationService.createInstance(UserDataSyncResourceEnablementService)); - this.instantiationService.stub(IStorageKeysSyncRegistryService, this.instantiationService.createInstance(StorageKeysSyncRegistryService)); this.instantiationService.stub(IGlobalExtensionEnablementService, this.instantiationService.createInstance(GlobalExtensionEnablementService)); + this.instantiationService.stub(IExtensionsStorageSyncService, this.instantiationService.createInstance(ExtensionsStorageSyncService)); this.instantiationService.stub(IIgnoredExtensionsManagementService, this.instantiationService.createInstance(IgnoredExtensionsManagementService)); this.instantiationService.stub(IExtensionManagementService, >{ async getInstalled() { return []; }, diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts b/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts index 3277d22be..2c7d1734d 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts @@ -464,10 +464,10 @@ suite('UserDataSyncRequestsSession', () => { test('too many requests are thrown when limit exceeded', async () => { const testObject = new RequestsSession(1, 500, requestService, new NullLogService()); - await testObject.request({}, CancellationToken.None); + await testObject.request('url', {}, CancellationToken.None); try { - await testObject.request({}, CancellationToken.None); + await testObject.request('url', {}, CancellationToken.None); } catch (error) { assert.ok(error instanceof UserDataSyncStoreError); assert.equal((error).code, UserDataSyncErrorCode.LocalTooManyRequests); @@ -478,19 +478,19 @@ suite('UserDataSyncRequestsSession', () => { test('requests are handled after session is expired', async () => { const testObject = new RequestsSession(1, 500, requestService, new NullLogService()); - await testObject.request({}, CancellationToken.None); + await testObject.request('url', {}, CancellationToken.None); await timeout(600); - await testObject.request({}, CancellationToken.None); + await testObject.request('url', {}, CancellationToken.None); }); test('too many requests are thrown after session is expired', async () => { const testObject = new RequestsSession(1, 500, requestService, new NullLogService()); - await testObject.request({}, CancellationToken.None); + await testObject.request('url', {}, CancellationToken.None); await timeout(600); - await testObject.request({}, CancellationToken.None); + await testObject.request('url', {}, CancellationToken.None); try { - await testObject.request({}, CancellationToken.None); + await testObject.request('url', {}, CancellationToken.None); } catch (error) { assert.ok(error instanceof UserDataSyncStoreError); assert.equal((error).code, UserDataSyncErrorCode.LocalTooManyRequests); diff --git a/src/vs/platform/webview/common/webviewManagerService.ts b/src/vs/platform/webview/common/webviewManagerService.ts index 8963865f3..476201f8f 100644 --- a/src/vs/platform/webview/common/webviewManagerService.ts +++ b/src/vs/platform/webview/common/webviewManagerService.ts @@ -11,6 +11,14 @@ import { IWebviewPortMapping } from 'vs/platform/webview/common/webviewPortMappi export const IWebviewManagerService = createDecorator('webviewManagerService'); +export interface WebviewWebContentsId { + readonly webContentsId: number; +} + +export interface WebviewWindowId { + readonly windowId: number; +} + export interface IWebviewManagerService { _serviceBrand: unknown; @@ -20,7 +28,7 @@ export interface IWebviewManagerService { didLoadResource(requestId: number, content: VSBuffer | undefined): void; - setIgnoreMenuShortcuts(webContentsId: number, enabled: boolean): Promise; + setIgnoreMenuShortcuts(id: WebviewWebContentsId | WebviewWindowId, enabled: boolean): Promise; } export interface RegisterWebviewMetadata { diff --git a/src/vs/platform/webview/electron-main/webviewMainService.ts b/src/vs/platform/webview/electron-main/webviewMainService.ts index c3b49724a..03a6e306c 100644 --- a/src/vs/platform/webview/electron-main/webviewMainService.ts +++ b/src/vs/platform/webview/electron-main/webviewMainService.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { webContents } from 'electron'; +import { WebContents, webContents } from 'electron'; import { VSBuffer } from 'vs/base/common/buffer'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { IRequestService } from 'vs/platform/request/common/request'; -import { IWebviewManagerService, RegisterWebviewMetadata } from 'vs/platform/webview/common/webviewManagerService'; +import { IWebviewManagerService, RegisterWebviewMetadata, WebviewWebContentsId, WebviewWindowId } from 'vs/platform/webview/common/webviewManagerService'; import { WebviewPortMappingProvider } from 'vs/platform/webview/electron-main/webviewPortMappingProvider'; import { WebviewProtocolProvider } from 'vs/platform/webview/electron-main/webviewProtocolProvider'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; @@ -26,7 +26,7 @@ export class WebviewMainService extends Disposable implements IWebviewManagerSer @IFileService fileService: IFileService, @IRequestService requestService: IRequestService, @ITunnelService tunnelService: ITunnelService, - @IWindowsMainService windowsMainService: IWindowsMainService, + @IWindowsMainService private readonly windowsMainService: IWindowsMainService, ) { super(); this.protocolProvider = this._register(new WebviewProtocolProvider(fileService, requestService, windowsMainService)); @@ -70,11 +70,24 @@ export class WebviewMainService extends Disposable implements IWebviewManagerSer }); } - public async setIgnoreMenuShortcuts(webContentsId: number, enabled: boolean): Promise { - const contents = webContents.fromId(webContentsId); - if (!contents) { - throw new Error(`Invalid webContentsId: ${webContentsId}`); + public async setIgnoreMenuShortcuts(id: WebviewWebContentsId | WebviewWindowId, enabled: boolean): Promise { + let contents: WebContents | undefined; + + if (typeof (id as WebviewWindowId).windowId === 'number') { + const { windowId } = (id as WebviewWindowId); + const window = this.windowsMainService.getWindowById(windowId); + if (!window) { + throw new Error(`Invalid windowId: ${windowId}`); + } + contents = window.win.webContents; + } else { + const { webContentsId } = (id as WebviewWebContentsId); + contents = webContents.fromId(webContentsId); + if (!contents) { + throw new Error(`Invalid webContentsId: ${webContentsId}`); + } } + if (!contents.isDestroyed()) { contents.setIgnoreMenuShortcuts(enabled); } diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 9d614c541..40c952136 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { isMacintosh, isLinux, isWeb, IProcessEnvironment } from 'vs/base/common/platform'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; @@ -85,8 +84,8 @@ export function isFileToOpen(uriToOpen: IWindowOpenable): uriToOpen is IFileToOp export type MenuBarVisibility = 'default' | 'visible' | 'toggle' | 'hidden' | 'compact'; -export function getMenuBarVisibility(configurationService: IConfigurationService, environment: IEnvironmentService, isExtensionDevelopment = environment.isExtensionDevelopment): MenuBarVisibility { - const titleBarStyle = getTitleBarStyle(configurationService, environment, isExtensionDevelopment); +export function getMenuBarVisibility(configurationService: IConfigurationService): MenuBarVisibility { + const titleBarStyle = getTitleBarStyle(configurationService); const menuBarVisibility = configurationService.getValue('window.menuBarVisibility'); if (titleBarStyle === 'native' && menuBarVisibility === 'compact') { @@ -104,7 +103,7 @@ export interface IWindowSettings { openFilesInNewWindow: 'on' | 'off' | 'default'; openFoldersInNewWindow: 'on' | 'off' | 'default'; openWithoutArgumentsInNewWindow: 'on' | 'off'; - restoreWindows: 'all' | 'folders' | 'one' | 'none'; + restoreWindows: 'preserve' | 'all' | 'folders' | 'one' | 'none'; restoreFullscreen: boolean; zoomLevel: number; titleBarStyle: 'native' | 'custom'; @@ -119,17 +118,12 @@ export interface IWindowSettings { enableExperimentalProxyLoginDialog: boolean; } -export function getTitleBarStyle(configurationService: IConfigurationService, environment: IEnvironmentService, isExtensionDevelopment = environment.isExtensionDevelopment): 'native' | 'custom' { +export function getTitleBarStyle(configurationService: IConfigurationService): 'native' | 'custom' { if (isWeb) { return 'custom'; } - const configuration = configurationService.getValue('window'); - - const isDev = !environment.isBuilt || isExtensionDevelopment; - if (isMacintosh && isDev) { - return 'native'; // not enabled when developing due to https://github.com/electron/electron/issues/3647 - } + const configuration = configurationService.getValue('window'); if (configuration) { const useNativeTabs = isMacintosh && configuration.nativeTabs === true; @@ -230,6 +224,10 @@ export interface IWindowConfiguration { filesToDiff?: IPath[]; } +export interface IOSConfiguration { + release: string; +} + export interface INativeWindowConfiguration extends IWindowConfiguration, NativeParsedArgs { mainPid: number; @@ -256,6 +254,8 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native userEnv: IProcessEnvironment; filesToWait?: IPathsToWaitFor; + + os: IOSConfiguration; } /** diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index e1f5be82b..83eccca73 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -12,8 +12,9 @@ import { IProcessEnvironment } from 'vs/base/common/platform'; import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { URI } from 'vs/base/common/uri'; -import { Rectangle, BrowserWindow } from 'electron'; +import { Rectangle, BrowserWindow, WebContents } from 'electron'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { CancellationToken } from 'vs/base/common/cancellation'; export interface IWindowState { width?: number; @@ -33,6 +34,11 @@ export const enum WindowMode { export interface ICodeWindow extends IDisposable { + readonly onLoad: Event; + readonly onReady: Event; + readonly onClose: Event; + readonly onDestroy: Event; + readonly whenClosedOrLoaded: Promise; readonly id: number; @@ -59,7 +65,7 @@ export interface ICodeWindow extends IDisposable { addTabbedWindow(window: ICodeWindow): void; load(config: INativeWindowConfiguration, isReload?: boolean): void; - reload(configuration?: INativeWindowConfiguration, cli?: NativeParsedArgs): void; + reload(cli?: NativeParsedArgs): void; focus(options?: { force: boolean }): void; close(): void; @@ -67,7 +73,7 @@ export interface ICodeWindow extends IDisposable { getBounds(): Rectangle; send(channel: string, ...args: any[]): void; - sendWhenReady(channel: string, ...args: any[]): void; + sendWhenReady(channel: string, token: CancellationToken, ...args: any[]): void; readonly isFullScreen: boolean; toggleFullScreen(): void; @@ -113,6 +119,7 @@ export interface IWindowsMainService { getLastActiveWindow(): ICodeWindow | undefined; getWindowById(windowId: number): ICodeWindow | undefined; + getWindowByWebContents(webContents: WebContents): ICodeWindow | undefined; getWindows(): ICodeWindow[]; getWindowCount(): number; } diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index b87cc3744..6bcedb5c7 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -3,18 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as fs from 'fs'; +import { statSync, unlink } from 'fs'; import { basename, normalize, join, posix } from 'vs/base/common/path'; import { localize } from 'vs/nls'; -import * as arrays from 'vs/base/common/arrays'; -import { mixin } from 'vs/base/common/objects'; +import { coalesce, distinct, firstOrDefault } from 'vs/base/common/arrays'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { IStateService } from 'vs/platform/state/node/state'; import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window'; -import { screen, BrowserWindow, MessageBoxOptions, Display, app } from 'electron'; +import { screen, BrowserWindow, MessageBoxOptions, Display, app, WebContents } from 'electron'; import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; @@ -40,6 +39,7 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { isWindowsDriveLetter, toSlashes, parseLineAndColumnAware } from 'vs/base/common/extpath'; import { CharCode } from 'vs/base/common/charCode'; import { getPathLabel } from 'vs/base/common/labels'; +import { CancellationToken } from 'vs/base/common/cancellation'; export interface IWindowState { workspace?: IWorkspaceIdentifier; @@ -59,7 +59,7 @@ interface INewWindowState extends ISingleWindowState { hasDefaultState?: boolean; } -type RestoreWindowsSetting = 'all' | 'folders' | 'one' | 'none'; +type RestoreWindowsSetting = 'preserve' | 'all' | 'folders' | 'one' | 'none'; interface IOpenBrowserWindowOptions { userEnv?: IProcessEnvironment; @@ -72,7 +72,7 @@ interface IOpenBrowserWindowOptions { initialStartup?: boolean; - fileInputs?: IFileInputs; + filesToOpen?: IFilesToOpen; forceNewWindow?: boolean; forceNewTabbedWindow?: boolean; @@ -87,7 +87,7 @@ interface IPathParseOptions { remoteAuthority?: string; } -interface IFileInputs { +interface IFilesToOpen { filesToOpenOrCreate: IPath[]; filesToDiff: IPath[]; filesToWait?: IPathsToWaitFor; @@ -307,7 +307,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic currentWindowsState.lastPluginDevelopmentHostWindow = this.toWindowState(extensionHostWindow); } - // 3.) All windows (except extension host) for N >= 2 to support restoreWindows: all or for auto update + // 3.) All windows (except extension host) for N >= 2 to support `restoreWindows: all` or for auto update // // Careful here: asking a window for its window state after it has been closed returns bogus values (width: 0, height: 0) // so if we ever want to persist the UI state of the last closed window (window count === 1), it has @@ -385,17 +385,17 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic this.logService.trace('windowsManager#open'); openConfig = this.validateOpenConfig(openConfig); - const pathsToOpen = this.getPathsToOpen(openConfig); - this.logService.trace('windowsManager#open pathsToOpen', pathsToOpen); - const foldersToAdd: IFolderPathToOpen[] = []; const foldersToOpen: IFolderPathToOpen[] = []; const workspacesToOpen: IWorkspacePathToOpen[] = []; const workspacesToRestore: IWorkspacePathToOpen[] = []; - const emptyToRestore: IEmptyWindowBackupInfo[] = []; // empty windows with backupPath + const emptyToRestore: IEmptyWindowBackupInfo[] = []; + let filesToOpen: IFilesToOpen | undefined; + let emptyToOpen = 0; - let emptyToOpen: number = 0; - let fileInputs: IFileInputs | undefined; // collect all file inputs + // Identify things to open from open config + const pathsToOpen = this.getPathsToOpen(openConfig); + this.logService.trace('windowsManager#open pathsToOpen', pathsToOpen); for (const path of pathsToOpen) { if (isFolderPathToOpen(path)) { if (openConfig.addMode) { @@ -408,10 +408,10 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } else if (isWorkspacePathToOpen(path)) { workspacesToOpen.push(path); } else if (path.fileUri) { - if (!fileInputs) { - fileInputs = { filesToOpenOrCreate: [], filesToDiff: [], remoteAuthority: path.remoteAuthority }; + if (!filesToOpen) { + filesToOpen = { filesToOpenOrCreate: [], filesToDiff: [], remoteAuthority: path.remoteAuthority }; } - fileInputs.filesToOpenOrCreate.push(path); + filesToOpen.filesToOpenOrCreate.push(path); } else if (path.backupPath) { emptyToRestore.push({ backupFolder: basename(path.backupPath), remoteAuthority: path.remoteAuthority }); } else { @@ -421,14 +421,14 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // When run with --diff, take the files to open as files to diff // if there are exactly two files provided. - if (fileInputs && openConfig.diffMode && fileInputs.filesToOpenOrCreate.length === 2) { - fileInputs.filesToDiff = fileInputs.filesToOpenOrCreate; - fileInputs.filesToOpenOrCreate = []; + if (openConfig.diffMode && filesToOpen?.filesToOpenOrCreate.length === 2) { + filesToOpen.filesToDiff = filesToOpen.filesToOpenOrCreate; + filesToOpen.filesToOpenOrCreate = []; } // When run with --wait, make sure we keep the paths to wait for - if (fileInputs && openConfig.waitMarkerFileURI) { - fileInputs.filesToWait = { paths: [...fileInputs.filesToDiff, ...fileInputs.filesToOpenOrCreate], waitMarkerFileUri: openConfig.waitMarkerFileURI }; + if (filesToOpen && openConfig.waitMarkerFileURI) { + filesToOpen.filesToWait = { paths: [...filesToOpen.filesToDiff, ...filesToOpen.filesToOpenOrCreate], waitMarkerFileUri: openConfig.waitMarkerFileURI }; } // @@ -447,52 +447,61 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } // Open based on config - const usedWindows = this.doOpen(openConfig, workspacesToOpen, foldersToOpen, emptyToRestore, emptyToOpen, fileInputs, foldersToAdd); + const { windows: usedWindows, filesOpenedInWindow } = this.doOpen(openConfig, workspacesToOpen, foldersToOpen, emptyToRestore, emptyToOpen, filesToOpen, foldersToAdd); this.logService.trace(`windowsManager#open used window count ${usedWindows.length} (workspacesToOpen: ${workspacesToOpen.length}, foldersToOpen: ${foldersToOpen.length}, emptyToRestore: ${emptyToRestore.length}, emptyToOpen: ${emptyToOpen})`); // Make sure to pass focus to the most relevant of the windows if we open multiple if (usedWindows.length > 1) { - const focusLastActive = this.windowsState.lastActiveWindow && !openConfig.forceEmpty && !openConfig.cli._.length && !openConfig.cli['file-uri'] && !openConfig.cli['folder-uri'] && !(openConfig.urisToOpen && openConfig.urisToOpen.length); - let focusLastOpened = true; - let focusLastWindow = true; - // 1.) focus last active window if we are not instructed to open any paths - if (focusLastActive) { - const lastActiveWindow = usedWindows.filter(window => this.windowsState.lastActiveWindow && window.backupPath === this.windowsState.lastActiveWindow.backupPath); - if (lastActiveWindow.length) { - lastActiveWindow[0].focus(); - focusLastOpened = false; - focusLastWindow = false; - } + // 1.) focus window we opened files in always with highest priority + if (filesOpenedInWindow) { + filesOpenedInWindow.focus(); } - // 2.) if instructed to open paths, focus last window which is not restored - if (focusLastOpened) { - for (let i = usedWindows.length - 1; i >= 0; i--) { - const usedWindow = usedWindows[i]; - if ( - (usedWindow.openedWorkspace && workspacesToRestore.some(workspace => usedWindow.openedWorkspace && workspace.workspace.id === usedWindow.openedWorkspace.id)) || // skip over restored workspace - (usedWindow.backupPath && emptyToRestore.some(empty => usedWindow.backupPath && empty.backupFolder === basename(usedWindow.backupPath))) // skip over restored empty window - ) { - continue; + // Otherwise, find a good window based on open params + else { + const focusLastActive = this.windowsState.lastActiveWindow && !openConfig.forceEmpty && !openConfig.cli._.length && !openConfig.cli['file-uri'] && !openConfig.cli['folder-uri'] && !(openConfig.urisToOpen && openConfig.urisToOpen.length); + let focusLastOpened = true; + let focusLastWindow = true; + + // 2.) focus last active window if we are not instructed to open any paths + if (focusLastActive) { + const lastActiveWindow = usedWindows.filter(window => this.windowsState.lastActiveWindow && window.backupPath === this.windowsState.lastActiveWindow.backupPath); + if (lastActiveWindow.length) { + lastActiveWindow[0].focus(); + focusLastOpened = false; + focusLastWindow = false; } - - usedWindow.focus(); - focusLastWindow = false; - break; } - } - // 3.) finally, always ensure to have at least last used window focused - if (focusLastWindow) { - usedWindows[usedWindows.length - 1].focus(); + // 3.) if instructed to open paths, focus last window which is not restored + if (focusLastOpened) { + for (let i = usedWindows.length - 1; i >= 0; i--) { + const usedWindow = usedWindows[i]; + if ( + (usedWindow.openedWorkspace && workspacesToRestore.some(workspace => usedWindow.openedWorkspace && workspace.workspace.id === usedWindow.openedWorkspace.id)) || // skip over restored workspace + (usedWindow.backupPath && emptyToRestore.some(empty => usedWindow.backupPath && empty.backupFolder === basename(usedWindow.backupPath))) // skip over restored empty window + ) { + continue; + } + + usedWindow.focus(); + focusLastWindow = false; + break; + } + } + + // 4.) finally, always ensure to have at least last used window focused + if (focusLastWindow) { + usedWindows[usedWindows.length - 1].focus(); + } } } // Remember in recent document list (unless this opens for extension development) // Also do not add paths when files are opened for diffing, only if opened individually - const isDiff = fileInputs && fileInputs.filesToDiff.length > 0; + const isDiff = filesToOpen && filesToOpen.filesToDiff.length > 0; if (!usedWindows.some(window => window.isExtensionDevelopmentHost) && !isDiff && !openConfig.noRecentEntry) { const recents: IRecent[] = []; for (let pathToOpen of pathsToOpen) { @@ -513,7 +522,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // process can continue. We do this by deleting the waitMarkerFilePath. const waitMarkerFileURI = openConfig.waitMarkerFileURI; if (openConfig.context === OpenContext.CLI && waitMarkerFileURI && usedWindows.length === 1 && usedWindows[0]) { - usedWindows[0].whenClosedOrLoaded.then(() => fs.unlink(waitMarkerFileURI.fsPath, _error => undefined)); + usedWindows[0].whenClosedOrLoaded.then(() => unlink(waitMarkerFileURI.fsPath, () => undefined)); } return usedWindows; @@ -535,10 +544,11 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic foldersToOpen: IFolderPathToOpen[], emptyToRestore: IEmptyWindowBackupInfo[], emptyToOpen: number, - fileInputs: IFileInputs | undefined, + filesToOpen: IFilesToOpen | undefined, foldersToAdd: IFolderPathToOpen[] - ) { + ): { windows: ICodeWindow[], filesOpenedInWindow: ICodeWindow | undefined } { const usedWindows: ICodeWindow[] = []; + let filesOpenedInWindow: ICodeWindow | undefined = undefined; // Settings can decide if files/folders open in new window or not let { openFolderInNewWindow, openFilesInNewWindow } = this.shouldOpenNewWindow(openConfig); @@ -554,13 +564,13 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // Handle files to open/diff or to create when we dont open a folder and we do not restore any folder/untitled from hot-exit const potentialWindowsCount = foldersToOpen.length + workspacesToOpen.length + emptyToRestore.length; - if (potentialWindowsCount === 0 && fileInputs) { + if (potentialWindowsCount === 0 && filesToOpen) { // Find suitable window or folder path to open files in - const fileToCheck = fileInputs.filesToOpenOrCreate[0] || fileInputs.filesToDiff[0]; + const fileToCheck = filesToOpen.filesToOpenOrCreate[0] || filesToOpen.filesToDiff[0]; // only look at the windows with correct authority - const windows = WindowsMainService.WINDOWS.filter(window => fileInputs && window.remoteAuthority === fileInputs.remoteAuthority); + const windows = WindowsMainService.WINDOWS.filter(window => filesToOpen && window.remoteAuthority === filesToOpen.remoteAuthority); const bestWindowOrFolder = findBestWindowOrFolderForFile({ windows, @@ -587,46 +597,52 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic else { // Do open files - usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, bestWindowOrFolder, fileInputs)); + const window = this.doOpenFilesInExistingWindow(openConfig, bestWindowOrFolder, filesToOpen); + usedWindows.push(window); - // Reset these because we handled them - fileInputs = undefined; + // Reset `filesToOpen` because we handled them and also remember window we used + filesToOpen = undefined; + filesOpenedInWindow = window; } } // Finally, if no window or folder is found, just open the files in an empty window else { - usedWindows.push(this.openInBrowserWindow({ + const window = this.openInBrowserWindow({ userEnv: openConfig.userEnv, cli: openConfig.cli, initialStartup: openConfig.initialStartup, - fileInputs, + filesToOpen, forceNewWindow: true, - remoteAuthority: fileInputs.remoteAuthority, + remoteAuthority: filesToOpen.remoteAuthority, forceNewTabbedWindow: openConfig.forceNewTabbedWindow - })); + }); + usedWindows.push(window); - // Reset these because we handled them - fileInputs = undefined; + // Reset `filesToOpen` because we handled them and also remember window we used + filesToOpen = undefined; + filesOpenedInWindow = window; } } // Handle workspaces to open (instructed and to restore) - const allWorkspacesToOpen = arrays.distinct(workspacesToOpen, workspace => workspace.workspace.id); // prevent duplicates + const allWorkspacesToOpen = distinct(workspacesToOpen, workspace => workspace.workspace.id); // prevent duplicates if (allWorkspacesToOpen.length > 0) { // Check for existing instances - const windowsOnWorkspace = arrays.coalesce(allWorkspacesToOpen.map(workspaceToOpen => findWindowOnWorkspace(WindowsMainService.WINDOWS, workspaceToOpen.workspace))); + const windowsOnWorkspace = coalesce(allWorkspacesToOpen.map(workspaceToOpen => findWindowOnWorkspace(WindowsMainService.WINDOWS, workspaceToOpen.workspace))); if (windowsOnWorkspace.length > 0) { const windowOnWorkspace = windowsOnWorkspace[0]; - const fileInputsForWindow = (fileInputs?.remoteAuthority === windowOnWorkspace.remoteAuthority) ? fileInputs : undefined; + const filesToOpenInWindow = (filesToOpen?.remoteAuthority === windowOnWorkspace.remoteAuthority) ? filesToOpen : undefined; // Do open files - usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnWorkspace, fileInputsForWindow)); + const window = this.doOpenFilesInExistingWindow(openConfig, windowOnWorkspace, filesToOpenInWindow); + usedWindows.push(window); - // Reset these because we handled them - if (fileInputsForWindow) { - fileInputs = undefined; + // Reset `filesToOpen` because we handled them and also remember window we used + if (filesToOpenInWindow) { + filesToOpen = undefined; + filesOpenedInWindow = window; } openFolderInNewWindow = true; // any other folders to open must open in new window then @@ -639,14 +655,16 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } const remoteAuthority = workspaceToOpen.remoteAuthority; - const fileInputsForWindow = (fileInputs?.remoteAuthority === remoteAuthority) ? fileInputs : undefined; + const filesToOpenInWindow = (filesToOpen?.remoteAuthority === remoteAuthority) ? filesToOpen : undefined; // Do open folder - usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, workspaceToOpen, openFolderInNewWindow, fileInputsForWindow)); + const window = this.doOpenFolderOrWorkspace(openConfig, workspaceToOpen, openFolderInNewWindow, filesToOpenInWindow); + usedWindows.push(window); - // Reset these because we handled them - if (fileInputsForWindow) { - fileInputs = undefined; + // Reset `filesToOpen` because we handled them and also remember window we used + if (filesToOpenInWindow) { + filesToOpen = undefined; + filesOpenedInWindow = window; } openFolderInNewWindow = true; // any other folders to open must open in new window then @@ -654,21 +672,23 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } // Handle folders to open (instructed and to restore) - const allFoldersToOpen = arrays.distinct(foldersToOpen, folder => extUriBiasedIgnorePathCase.getComparisonKey(folder.folderUri)); // prevent duplicates + const allFoldersToOpen = distinct(foldersToOpen, folder => extUriBiasedIgnorePathCase.getComparisonKey(folder.folderUri)); // prevent duplicates if (allFoldersToOpen.length > 0) { // Check for existing instances - const windowsOnFolderPath = arrays.coalesce(allFoldersToOpen.map(folderToOpen => findWindowOnWorkspace(WindowsMainService.WINDOWS, folderToOpen.folderUri))); + const windowsOnFolderPath = coalesce(allFoldersToOpen.map(folderToOpen => findWindowOnWorkspace(WindowsMainService.WINDOWS, folderToOpen.folderUri))); if (windowsOnFolderPath.length > 0) { const windowOnFolderPath = windowsOnFolderPath[0]; - const fileInputsForWindow = fileInputs?.remoteAuthority === windowOnFolderPath.remoteAuthority ? fileInputs : undefined; + const filesToOpenInWindow = filesToOpen?.remoteAuthority === windowOnFolderPath.remoteAuthority ? filesToOpen : undefined; // Do open files - usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnFolderPath, fileInputsForWindow)); + const window = this.doOpenFilesInExistingWindow(openConfig, windowOnFolderPath, filesToOpenInWindow); + usedWindows.push(window); - // Reset these because we handled them - if (fileInputsForWindow) { - fileInputs = undefined; + // Reset `filesToOpen` because we handled them and also remember window we used + if (filesToOpenInWindow) { + filesToOpen = undefined; + filesOpenedInWindow = window; } openFolderInNewWindow = true; // any other folders to open must open in new window then @@ -682,14 +702,16 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } const remoteAuthority = folderToOpen.remoteAuthority; - const fileInputsForWindow = (fileInputs?.remoteAuthority === remoteAuthority) ? fileInputs : undefined; + const filesToOpenInWindow = (filesToOpen?.remoteAuthority === remoteAuthority) ? filesToOpen : undefined; // Do open folder - usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, folderToOpen, openFolderInNewWindow, fileInputsForWindow)); + const window = this.doOpenFolderOrWorkspace(openConfig, folderToOpen, openFolderInNewWindow, filesToOpenInWindow); + usedWindows.push(window); - // Reset these because we handled them - if (fileInputsForWindow) { - fileInputs = undefined; + // Reset `filesToOpen` because we handled them and also remember window we used + if (filesToOpenInWindow) { + filesToOpen = undefined; + filesOpenedInWindow = window; } openFolderInNewWindow = true; // any other folders to open must open in new window then @@ -697,26 +719,28 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } // Handle empty to restore - const allEmptyToRestore = arrays.distinct(emptyToRestore, info => info.backupFolder); // prevent duplicates + const allEmptyToRestore = distinct(emptyToRestore, info => info.backupFolder); // prevent duplicates if (allEmptyToRestore.length > 0) { allEmptyToRestore.forEach(emptyWindowBackupInfo => { const remoteAuthority = emptyWindowBackupInfo.remoteAuthority; - const fileInputsForWindow = (fileInputs?.remoteAuthority === remoteAuthority) ? fileInputs : undefined; + const filesToOpenInWindow = (filesToOpen?.remoteAuthority === remoteAuthority) ? filesToOpen : undefined; - usedWindows.push(this.openInBrowserWindow({ + const window = this.openInBrowserWindow({ userEnv: openConfig.userEnv, cli: openConfig.cli, initialStartup: openConfig.initialStartup, - fileInputs: fileInputsForWindow, + filesToOpen: filesToOpenInWindow, remoteAuthority, forceNewWindow: true, forceNewTabbedWindow: openConfig.forceNewTabbedWindow, emptyWindowBackupInfo - })); + }); + usedWindows.push(window); - // Reset these because we handled them - if (fileInputsForWindow) { - fileInputs = undefined; + // Reset `filesToOpen` because we handled them and also remember window we used + if (filesToOpenInWindow) { + filesToOpen = undefined; + filesOpenedInWindow = window; } openFolderInNewWindow = true; // any other folders to open must open in new window then @@ -724,42 +748,48 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } // Handle empty to open (only if no other window opened) - if (usedWindows.length === 0 || fileInputs) { - if (fileInputs && !emptyToOpen) { + if (usedWindows.length === 0 || filesToOpen) { + if (filesToOpen && !emptyToOpen) { emptyToOpen++; } - const remoteAuthority = fileInputs ? fileInputs.remoteAuthority : (openConfig.cli && openConfig.cli.remote || undefined); + const remoteAuthority = filesToOpen ? filesToOpen.remoteAuthority : (openConfig.cli && openConfig.cli.remote || undefined); for (let i = 0; i < emptyToOpen; i++) { - usedWindows.push(this.doOpenEmpty(openConfig, openFolderInNewWindow, remoteAuthority, fileInputs)); + const window = this.doOpenEmpty(openConfig, openFolderInNewWindow, remoteAuthority, filesToOpen); + usedWindows.push(window); - // Reset these because we handled them - fileInputs = undefined; - openFolderInNewWindow = true; // any other window to open must open in new window then + // Reset `filesToOpen` because we handled them and also remember window we used + if (filesToOpen) { + filesToOpen = undefined; + filesOpenedInWindow = window; + } + + // any other window to open must open in new window then + openFolderInNewWindow = true; } } - return arrays.distinct(usedWindows); + return { windows: distinct(usedWindows), filesOpenedInWindow }; } - private doOpenFilesInExistingWindow(configuration: IOpenConfiguration, window: ICodeWindow, fileInputs?: IFileInputs): ICodeWindow { + private doOpenFilesInExistingWindow(configuration: IOpenConfiguration, window: ICodeWindow, filesToOpen?: IFilesToOpen): ICodeWindow { this.logService.trace('windowsManager#doOpenFilesInExistingWindow'); window.focus(); // make sure window has focus const params: { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[], filesToWait?: IPathsToWaitFor, termProgram?: string } = {}; - if (fileInputs) { - params.filesToOpenOrCreate = fileInputs.filesToOpenOrCreate; - params.filesToDiff = fileInputs.filesToDiff; - params.filesToWait = fileInputs.filesToWait; + if (filesToOpen) { + params.filesToOpenOrCreate = filesToOpen.filesToOpenOrCreate; + params.filesToDiff = filesToOpen.filesToDiff; + params.filesToWait = filesToOpen.filesToWait; } if (configuration.userEnv) { params.termProgram = configuration.userEnv['TERM_PROGRAM']; } - window.sendWhenReady('vscode:openFiles', params); + window.sendWhenReady('vscode:openFiles', CancellationToken.None, params); return window; } @@ -768,12 +798,12 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic window.focus(); // make sure window has focus const request: IAddFoldersRequest = { foldersToAdd }; - window.sendWhenReady('vscode:addFolders', request); + window.sendWhenReady('vscode:addFolders', CancellationToken.None, request); return window; } - private doOpenEmpty(openConfig: IOpenConfiguration, forceNewWindow: boolean, remoteAuthority: string | undefined, fileInputs: IFileInputs | undefined, windowToUse?: ICodeWindow): ICodeWindow { + private doOpenEmpty(openConfig: IOpenConfiguration, forceNewWindow: boolean, remoteAuthority: string | undefined, filesToOpen: IFilesToOpen | undefined, windowToUse?: ICodeWindow): ICodeWindow { if (!forceNewWindow && !windowToUse && typeof openConfig.contextWindowId === 'number') { windowToUse = this.getWindowById(openConfig.contextWindowId); // fix for https://github.com/microsoft/vscode/issues/97172 } @@ -785,12 +815,12 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic remoteAuthority, forceNewWindow, forceNewTabbedWindow: openConfig.forceNewTabbedWindow, - fileInputs, + filesToOpen, windowToUse }); } - private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IPathToOpen, forceNewWindow: boolean, fileInputs: IFileInputs | undefined, windowToUse?: ICodeWindow): ICodeWindow { + private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IPathToOpen, forceNewWindow: boolean, filesToOpen: IFilesToOpen | undefined, windowToUse?: ICodeWindow): ICodeWindow { if (!forceNewWindow && !windowToUse && typeof openConfig.contextWindowId === 'number') { windowToUse = this.getWindowById(openConfig.contextWindowId); // fix for https://github.com/microsoft/vscode/issues/49587 } @@ -801,7 +831,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic initialStartup: openConfig.initialStartup, workspace: folderOrWorkspace.workspace, folderUri: folderOrWorkspace.folderUri, - fileInputs, + filesToOpen, remoteAuthority: folderOrWorkspace.remoteAuthority, forceNewWindow, forceNewTabbedWindow: openConfig.forceNewTabbedWindow, @@ -812,6 +842,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic private getPathsToOpen(openConfig: IOpenConfiguration): IPathToOpen[] { let windowsToOpen: IPathToOpen[]; let isCommandLineOrAPICall = false; + let restoredWindows = false; // Extract paths: from API if (openConfig.urisToOpen && openConfig.urisToOpen.length > 0) { @@ -833,11 +864,12 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // Extract windows: from previous session else { windowsToOpen = this.doGetWindowsFromLastSession(); + restoredWindows = true; } // Convert multiple folders into workspace (if opened via API or CLI) // This will ensure to open these folders in one window instead of multiple - // If we are in addMode, we should not do this because in that case all + // If we are in `addMode`, we should not do this because in that case all // folders should be added to the existing window. if (!openConfig.addMode && isCommandLineOrAPICall) { const foldersToOpen = windowsToOpen.filter(path => !!path.folderUri); @@ -853,6 +885,15 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } } + // Check for `window.startup` setting to include all windows + // from the previous session if this is the initial startup and we have + // not restored windows already otherwise. + // Use `unshift` to ensure any new window to open comes last + // for proper focus treatment. + if (openConfig.initialStartup && !restoredWindows && this.configurationService.getValue('window')?.restoreWindows === 'preserve') { + windowsToOpen.unshift(...this.doGetWindowsFromLastSession().filter(window => window.workspace || window.folderUri || window.backupPath)); + } + return windowsToOpen; } @@ -914,7 +955,6 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } } - // file uris const fileUris = cli['file-uri']; if (fileUris) { @@ -947,9 +987,9 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } private doGetWindowsFromLastSession(): IPathToOpen[] { - const restoreWindows = this.getRestoreWindowsSetting(); + const restoreWindowsSetting = this.getRestoreWindowsSetting(); - switch (restoreWindows) { + switch (restoreWindowsSetting) { // none: we always open an empty window case 'none': @@ -960,9 +1000,12 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // folders: restore last opened folders only case 'one': case 'all': + case 'preserve': case 'folders': + + // Collect previously opened windows const openedWindows: IWindowState[] = []; - if (restoreWindows !== 'one') { + if (restoreWindowsSetting !== 'one') { openedWindows.push(...this.windowsState.openedWindows); } if (this.windowsState.lastActiveWindow) { @@ -971,17 +1014,25 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const windowsToOpen: IPathToOpen[] = []; for (const openedWindow of openedWindows) { - if (openedWindow.workspace) { // Workspaces + + // Workspaces + if (openedWindow.workspace) { const pathToOpen = this.parseUri({ workspaceUri: openedWindow.workspace.configPath }, { remoteAuthority: openedWindow.remoteAuthority }); if (pathToOpen?.workspace) { windowsToOpen.push(pathToOpen); } - } else if (openedWindow.folderUri) { // Folders + } + + // Folders + else if (openedWindow.folderUri) { const pathToOpen = this.parseUri({ folderUri: openedWindow.folderUri }, { remoteAuthority: openedWindow.remoteAuthority }); if (pathToOpen?.folderUri) { windowsToOpen.push(pathToOpen); } - } else if (restoreWindows !== 'folders' && openedWindow.backupPath) { // Empty window, potentially editors open to be restored + } + + // Empty window, potentially editors open to be restored + else if (restoreWindowsSetting !== 'folders' && openedWindow.backupPath) { windowsToOpen.push({ backupPath: openedWindow.backupPath, remoteAuthority: openedWindow.remoteAuthority }); } } @@ -1002,10 +1053,10 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic if (this.lifecycleMainService.wasRestarted) { restoreWindows = 'all'; // always reopen all windows when an update was applied } else { - const windowConfig = this.configurationService.getValue('window'); + const windowConfig = this.configurationService.getValue('window'); restoreWindows = windowConfig?.restoreWindows || 'all'; // by default restore all windows - if (!['all', 'folders', 'one', 'none'].includes(restoreWindows)) { + if (!['preserve', 'all', 'folders', 'one', 'none'].includes(restoreWindows)) { restoreWindows = 'all'; // by default restore all windows } } @@ -1142,7 +1193,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic let candidate = normalize(anyPath); try { - const candidateStat = fs.statSync(candidate); + const candidateStat = statSync(candidate); if (candidateStat.isFile()) { // Workspace (unless disabled via flag) @@ -1197,7 +1248,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic private shouldOpenNewWindow(openConfig: IOpenConfiguration): { openFolderInNewWindow: boolean; openFilesInNewWindow: boolean; } { // let the user settings override how folders are open in a new window or same window unless we are forced - const windowConfig = this.configurationService.getValue('window'); + const windowConfig = this.configurationService.getValue('window'); const openFolderInNewWindowConfig = windowConfig?.openFoldersInNewWindow || 'default' /* default */; const openFilesInNewWindowConfig = windowConfig?.openFilesInNewWindow || 'off' /* default */; @@ -1348,7 +1399,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow { // Build INativeWindowConfiguration from config and options - const configuration: INativeWindowConfiguration = mixin({}, options.cli); // inherit all properties from CLI + const configuration = { ...options.cli } as INativeWindowConfiguration; configuration.appRoot = this.environmentService.appRoot; configuration.machineId = this.machineId; configuration.nodeCachedDataDir = this.environmentService.nodeCachedDataDir; @@ -1360,11 +1411,11 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic configuration.folderUri = options.folderUri; configuration.remoteAuthority = options.remoteAuthority; - const fileInputs = options.fileInputs; - if (fileInputs) { - configuration.filesToOpenOrCreate = fileInputs.filesToOpenOrCreate; - configuration.filesToDiff = fileInputs.filesToDiff; - configuration.filesToWait = fileInputs.filesToWait; + const filesToOpen = options.filesToOpen; + if (filesToOpen) { + configuration.filesToOpenOrCreate = filesToOpen.filesToOpenOrCreate; + configuration.filesToDiff = filesToOpen.filesToDiff; + configuration.filesToWait = filesToOpen.filesToWait; } // if we know the backup folder upfront (for empty windows to restore), we can set it @@ -1385,18 +1436,18 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // New window if (!window) { - const windowConfig = this.configurationService.getValue('window'); + const windowConfig = this.configurationService.getValue('window'); const state = this.getNewWindowState(configuration); // Window state is not from a previous session: only allow fullscreen if we inherit it or user wants fullscreen let allowFullscreen: boolean; if (state.hasDefaultState) { - allowFullscreen = (windowConfig?.newWindowDimensions && ['fullscreen', 'inherit', 'offset'].indexOf(windowConfig.newWindowDimensions) >= 0); + allowFullscreen = !!(windowConfig?.newWindowDimensions && ['fullscreen', 'inherit', 'offset'].indexOf(windowConfig.newWindowDimensions) >= 0); } // Window state is from a previous session: only allow fullscreen when we got updated or user wants to restore else { - allowFullscreen = this.lifecycleMainService.wasRestarted || windowConfig?.restoreFullscreen; + allowFullscreen = !!(this.lifecycleMainService.wasRestarted || windowConfig?.restoreFullscreen); if (allowFullscreen && isMacintosh && WindowsMainService.WINDOWS.some(win => win.isFullScreen)) { // macOS: Electron does not allow to restore multiple windows in @@ -1581,7 +1632,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic state.y = Math.round(displayToUse.bounds.y + (displayToUse.bounds.height / 2) - (state.height! / 2)); // Check for newWindowDimensions setting and adjust accordingly - const windowConfig = this.configurationService.getValue('window'); + const windowConfig = this.configurationService.getValue('window'); let ensureNoOverlap = true; if (windowConfig?.newWindowDimensions) { if (windowConfig.newWindowDimensions === 'maximized') { @@ -1659,7 +1710,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const focusedWindow = this.getFocusedWindow() || this.getLastActiveWindow(); if (focusedWindow) { - focusedWindow.sendWhenReady(channel, ...args); + focusedWindow.sendWhenReady(channel, CancellationToken.None, ...args); } } @@ -1669,14 +1720,23 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic continue; // do not send if we are instructed to ignore it } - window.sendWhenReady(channel, payload); + window.sendWhenReady(channel, CancellationToken.None, payload); } } getWindowById(windowId: number): ICodeWindow | undefined { const res = WindowsMainService.WINDOWS.filter(window => window.id === windowId); - return arrays.firstOrDefault(res); + return firstOrDefault(res); + } + + getWindowByWebContents(webContents: WebContents): ICodeWindow | undefined { + const browserWindow = BrowserWindow.fromWebContents(webContents); + if (!browserWindow) { + return undefined; + } + + return this.getWindowById(browserWindow.id); } getWindows(): ICodeWindow[] { diff --git a/src/vs/code/test/electron-main/windowsStateStorage.test.ts b/src/vs/platform/windows/test/electron-main/windowsStateStorage.test.ts similarity index 100% rename from src/vs/code/test/electron-main/windowsStateStorage.test.ts rename to src/vs/platform/windows/test/electron-main/windowsStateStorage.test.ts diff --git a/src/vs/platform/workspace/common/workspace.ts b/src/vs/platform/workspace/common/workspace.ts index c0c34d74f..72ca615f9 100644 --- a/src/vs/platform/workspace/common/workspace.ts +++ b/src/vs/platform/workspace/common/workspace.ts @@ -144,13 +144,14 @@ export interface IWorkspaceFolder extends IWorkspaceFolderData { export class Workspace implements IWorkspace { - private _foldersMap: TernarySearchTree = TernarySearchTree.forUris(); + private _foldersMap: TernarySearchTree = TernarySearchTree.forUris(this._ignorePathCasing); private _folders!: WorkspaceFolder[]; constructor( private _id: string, - folders: WorkspaceFolder[] = [], - private _configuration: URI | null = null + folders: WorkspaceFolder[], + private _configuration: URI | null, + private _ignorePathCasing: (key: URI) => boolean, ) { this.folders = folders; } @@ -158,6 +159,7 @@ export class Workspace implements IWorkspace { update(workspace: Workspace) { this._id = workspace.id; this._configuration = workspace.configuration; + this._ignorePathCasing = workspace._ignorePathCasing; this.folders = workspace.folders; } @@ -195,7 +197,7 @@ export class Workspace implements IWorkspace { } private updateFoldersMap(): void { - this._foldersMap = TernarySearchTree.forUris(); + this._foldersMap = TernarySearchTree.forUris(this._ignorePathCasing); for (const folder of this.folders) { this._foldersMap.set(folder.uri, folder); } diff --git a/src/vs/platform/workspace/test/common/testWorkspace.ts b/src/vs/platform/workspace/test/common/testWorkspace.ts index 75762fed4..0cdb093ab 100644 --- a/src/vs/platform/workspace/test/common/testWorkspace.ts +++ b/src/vs/platform/workspace/test/common/testWorkspace.ts @@ -4,8 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { Workspace, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { isWindows } from 'vs/base/common/platform'; +import { Workspace as BaseWorkspace, toWorkspaceFolder, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { isLinux, isWindows } from 'vs/base/common/platform'; + +export class Workspace extends BaseWorkspace { + constructor( + id: string, + folders: WorkspaceFolder[] = [], + configuration: URI | null = null, + ignorePathCasing: (key: URI) => boolean = () => !isLinux + ) { + super(id, folders, configuration, ignorePathCasing); + } +} const wsUri = URI.file(isWindows ? 'C:\\testWorkspace' : '/testWorkspace'); export const TestWorkspace = testWorkspace(wsUri); diff --git a/src/vs/platform/workspace/test/common/workspace.test.ts b/src/vs/platform/workspace/test/common/workspace.test.ts index 10e257276..1d3f78f7b 100644 --- a/src/vs/platform/workspace/test/common/workspace.test.ts +++ b/src/vs/platform/workspace/test/common/workspace.test.ts @@ -8,7 +8,7 @@ import * as path from 'vs/base/common/path'; import { Workspace, toWorkspaceFolders, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { URI } from 'vs/base/common/uri'; import { IRawFileWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; -import { isWindows } from 'vs/base/common/platform'; +import { isLinux, isWindows } from 'vs/base/common/platform'; suite('Workspace', () => { @@ -27,7 +27,7 @@ suite('Workspace', () => { test('getFolder returns the folder with given uri', () => { const expected = new WorkspaceFolder({ uri: testFolderUri, name: '', index: 2 }); - let testObject = new Workspace('', [new WorkspaceFolder({ uri: mainFolderUri, name: '', index: 0 }), expected, new WorkspaceFolder({ uri: URI.file('/src/code'), name: '', index: 2 })]); + let testObject = new Workspace('', [new WorkspaceFolder({ uri: mainFolderUri, name: '', index: 0 }), expected, new WorkspaceFolder({ uri: URI.file('/src/code'), name: '', index: 2 })], null, () => !isLinux); const actual = testObject.getFolder(expected.uri); @@ -36,7 +36,7 @@ suite('Workspace', () => { test('getFolder returns the folder if the uri is sub', () => { const expected = new WorkspaceFolder({ uri: testFolderUri, name: '', index: 0 }); - let testObject = new Workspace('', [expected, new WorkspaceFolder({ uri: mainFolderUri, name: '', index: 1 }), new WorkspaceFolder({ uri: URI.file('/src/code'), name: '', index: 2 })]); + let testObject = new Workspace('', [expected, new WorkspaceFolder({ uri: mainFolderUri, name: '', index: 1 }), new WorkspaceFolder({ uri: URI.file('/src/code'), name: '', index: 2 })], null, () => !isLinux); const actual = testObject.getFolder(URI.file(path.join(fileFolder, 'test/a'))); @@ -45,7 +45,7 @@ suite('Workspace', () => { test('getFolder returns the closest folder if the uri is sub', () => { const expected = new WorkspaceFolder({ uri: testFolderUri, name: '', index: 2 }); - let testObject = new Workspace('', [new WorkspaceFolder({ uri: mainFolderUri, name: '', index: 0 }), new WorkspaceFolder({ uri: URI.file('/src/code'), name: '', index: 1 }), expected]); + let testObject = new Workspace('', [new WorkspaceFolder({ uri: mainFolderUri, name: '', index: 0 }), new WorkspaceFolder({ uri: URI.file('/src/code'), name: '', index: 1 }), expected], null, () => !isLinux); const actual = testObject.getFolder(URI.file(path.join(fileFolder, 'test/a'))); @@ -54,7 +54,7 @@ suite('Workspace', () => { test('getFolder returns the folder even if the uri has query path', () => { const expected = new WorkspaceFolder({ uri: testFolderUri, name: '', index: 2 }); - let testObject = new Workspace('', [new WorkspaceFolder({ uri: mainFolderUri, name: '', index: 0 }), new WorkspaceFolder({ uri: URI.file('/src/code'), name: '', index: 1 }), expected]); + let testObject = new Workspace('', [new WorkspaceFolder({ uri: mainFolderUri, name: '', index: 0 }), new WorkspaceFolder({ uri: URI.file('/src/code'), name: '', index: 1 }), expected], null, () => !isLinux); const actual = testObject.getFolder(URI.file(path.join(fileFolder, 'test/a')).with({ query: 'somequery' })); @@ -62,7 +62,7 @@ suite('Workspace', () => { }); test('getFolder returns null if the uri is not sub', () => { - let testObject = new Workspace('', [new WorkspaceFolder({ uri: testFolderUri, name: '', index: 0 }), new WorkspaceFolder({ uri: URI.file('/src/code'), name: '', index: 1 })]); + let testObject = new Workspace('', [new WorkspaceFolder({ uri: testFolderUri, name: '', index: 0 }), new WorkspaceFolder({ uri: URI.file('/src/code'), name: '', index: 1 })], null, () => !isLinux); const actual = testObject.getFolder(URI.file(path.join(fileFolder, 'main/a'))); diff --git a/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts index b6321d734..7a5baa225 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts @@ -330,53 +330,57 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa }); // Recent Workspaces - if (this.getRecentlyOpened().workspaces.length > 0) { + try { + if (this.getRecentlyOpened().workspaces.length > 0) { - // The user might have meanwhile removed items from the jump list and we have to respect that - // so we need to update our list of recent paths with the choice of the user to not add them again - // Also: Windows will not show our custom category at all if there is any entry which was removed - // by the user! See https://github.com/microsoft/vscode/issues/15052 - let toRemove: URI[] = []; - for (let item of app.getJumpListSettings().removedItems) { - const args = item.args; - if (args) { - const match = /^--(folder|file)-uri\s+"([^"]+)"$/.exec(args); - if (match) { - toRemove.push(URI.parse(match[2])); + // The user might have meanwhile removed items from the jump list and we have to respect that + // so we need to update our list of recent paths with the choice of the user to not add them again + // Also: Windows will not show our custom category at all if there is any entry which was removed + // by the user! See https://github.com/microsoft/vscode/issues/15052 + let toRemove: URI[] = []; + for (let item of app.getJumpListSettings().removedItems) { + const args = item.args; + if (args) { + const match = /^--(folder|file)-uri\s+"([^"]+)"$/.exec(args); + if (match) { + toRemove.push(URI.parse(match[2])); + } } } + this.removeRecentlyOpened(toRemove); + + // Add entries + jumpList.push({ + type: 'custom', + name: nls.localize('recentFolders', "Recent Workspaces"), + items: arrays.coalesce(this.getRecentlyOpened().workspaces.slice(0, 7 /* limit number of entries here */).map(recent => { + const workspace = isRecentWorkspace(recent) ? recent.workspace : recent.folderUri; + const title = recent.label ? splitName(recent.label).name : this.getSimpleWorkspaceLabel(workspace, this.environmentService.untitledWorkspacesHome); + + let description; + let args; + if (isSingleFolderWorkspaceIdentifier(workspace)) { + description = nls.localize('folderDesc', "{0} {1}", getBaseLabel(workspace), getPathLabel(dirname(workspace), this.environmentService)); + args = `--folder-uri "${workspace.toString()}"`; + } else { + description = nls.localize('workspaceDesc', "{0} {1}", getBaseLabel(workspace.configPath), getPathLabel(dirname(workspace.configPath), this.environmentService)); + args = `--file-uri "${workspace.configPath.toString()}"`; + } + + return { + type: 'task', + title, + description, + program: process.execPath, + args, + iconPath: 'explorer.exe', // simulate folder icon + iconIndex: 0 + }; + })) + }); } - this.removeRecentlyOpened(toRemove); - - // Add entries - jumpList.push({ - type: 'custom', - name: nls.localize('recentFolders', "Recent Workspaces"), - items: arrays.coalesce(this.getRecentlyOpened().workspaces.slice(0, 7 /* limit number of entries here */).map(recent => { - const workspace = isRecentWorkspace(recent) ? recent.workspace : recent.folderUri; - const title = recent.label ? splitName(recent.label).name : this.getSimpleWorkspaceLabel(workspace, this.environmentService.untitledWorkspacesHome); - - let description; - let args; - if (isSingleFolderWorkspaceIdentifier(workspace)) { - description = nls.localize('folderDesc', "{0} {1}", getBaseLabel(workspace), getPathLabel(dirname(workspace), this.environmentService)); - args = `--folder-uri "${workspace.toString()}"`; - } else { - description = nls.localize('workspaceDesc', "{0} {1}", getBaseLabel(workspace.configPath), getPathLabel(dirname(workspace.configPath), this.environmentService)); - args = `--file-uri "${workspace.configPath.toString()}"`; - } - - return { - type: 'task', - title, - description, - program: process.execPath, - args, - iconPath: 'explorer.exe', // simulate folder icon - iconIndex: 0 - }; - })) - }); + } catch (error) { + this.logService.warn('updateWindowsJumpList#recentWorkspaces', error); // https://github.com/microsoft/vscode/issues/111177 } // Recent @@ -387,7 +391,7 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa try { app.setJumpList(jumpList); } catch (error) { - this.logService.warn('#setJumpList', error); // since setJumpList is relatively new API, make sure to guard for errors + this.logService.warn('updateWindowsJumpList#setJumpList', error); // since setJumpList is relatively new API, make sure to guard for errors } } diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 99ba257db..e1c2dd30c 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -791,8 +791,8 @@ declare module 'vscode' { /** * A reference to a named icon. Currently, [File](#ThemeIcon.File), [Folder](#ThemeIcon.Folder), - * and [codicons](https://microsoft.github.io/vscode-codicons/dist/codicon.html) are supported. - * Using a theme icon is preferred over a custom icon as it gives theme authors the possibility to change the icons. + * and [ThemeIcon ids](https://code.visualstudio.com/api/references/icons-in-labels#icon-listing) are supported. + * Using a theme icon is preferred over a custom icon as it gives product theme authors the possibility to change the icons. * * *Note* that theme icons can also be rendered inside labels and descriptions. Places that support theme icons spell this out * and they use the `$()`-syntax, for instance `quickPick.label = "Hello World $(globe)"`. @@ -809,7 +809,7 @@ declare module 'vscode' { static readonly Folder: ThemeIcon; /** - * The id of the icon. The available icons are listed in https://microsoft.github.io/vscode-codicons/dist/codicon.html. + * The id of the icon. The available icons are listed in https://code.visualstudio.com/api/references/icons-in-labels#icon-listing. */ readonly id: string; @@ -820,7 +820,7 @@ declare module 'vscode' { /** * Creates a reference to a theme icon. - * @param id id of the icon. The available icons are listed in https://microsoft.github.io/vscode-codicons/dist/codicon.html. + * @param id id of the icon. The available icons are listed in https://code.visualstudio.com/api/references/icons-in-labels#icon-listing. * @param color optional `ThemeColor` for the icon. The color is currently only used in [TreeItem](#TreeItem). */ constructor(id: string, color?: ThemeColor); @@ -1878,8 +1878,9 @@ declare module 'vscode' { /** * A relative pattern is a helper to construct glob patterns that are matched - * relatively to a base path. The base path can either be an absolute file path - * or a [workspace folder](#WorkspaceFolder). + * relatively to a base file path. The base path can either be an absolute file + * path as string or uri or a [workspace folder](#WorkspaceFolder), which is the + * preferred way of creating the relative pattern. */ export class RelativePattern { @@ -1898,14 +1899,28 @@ declare module 'vscode' { pattern: string; /** - * Creates a new relative pattern object with a base path and pattern to match. This pattern - * will be matched on file paths relative to the base path. + * Creates a new relative pattern object with a base file path and pattern to match. This pattern + * will be matched on file paths relative to the base. * - * @param base A base file path to which this pattern will be matched against relatively. - * @param pattern A file glob pattern like `*.{ts,js}` that will be matched on file paths - * relative to the base path. + * Example: + * ```ts + * const folder = vscode.workspace.workspaceFolders?.[0]; + * if (folder) { + * + * // Match any TypeScript file in the root of this workspace folder + * const pattern1 = new vscode.RelativePattern(folder, '*.ts'); + * + * // Match any TypeScript file in `someFolder` inside this workspace folder + * const pattern2 = new vscode.RelativePattern(folder, 'someFolder/*.ts'); + * } + * ``` + * + * @param base A base to which this pattern will be matched against relatively. It is recommended + * to pass in a [workspace folder](#WorkspaceFolder) if the pattern should match inside the workspace. + * Otherwise, a uri or string should only be used if the pattern is for a file path outside the workspace. + * @param pattern A file glob pattern like `*.{ts,js}` that will be matched on paths relative to the base. */ - constructor(base: WorkspaceFolder | string, pattern: string) + constructor(base: WorkspaceFolder | Uri | string, pattern: string) } /** @@ -4325,6 +4340,12 @@ declare module 'vscode' { * [Folding](https://code.visualstudio.com/docs/editor/codebasics#_folding) in the editor. */ export interface FoldingRangeProvider { + + /** + * An optional event to signal that the folding ranges from this provider have changed. + */ + onDidChangeFoldingRanges?: Event; + /** * Returns a list of folding ranges or null and undefined if the provider * does not want to participate or was cancelled. @@ -4520,6 +4541,50 @@ declare module 'vscode' { provideCallHierarchyOutgoingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult; } + /** + * Represents a list of ranges that can be edited together along with a word pattern to describe valid range contents. + */ + export class LinkedEditingRanges { + /** + * Create a new linked editing ranges object. + * + * @param ranges A list of ranges that can be edited together + * @param wordPattern An optional word pattern that describes valid contents for the given ranges + */ + constructor(ranges: Range[], wordPattern?: RegExp); + + /** + * A list of ranges that can be edited together. The ranges must have + * identical length and text content. The ranges cannot overlap. + */ + readonly ranges: Range[]; + + /** + * An optional word pattern that describes valid contents for the given ranges. + * If no pattern is provided, the language configuration's word pattern will be used. + */ + readonly wordPattern?: RegExp; + } + + /** + * The linked editing range provider interface defines the contract between extensions and + * the linked editing feature. + */ + export interface LinkedEditingRangeProvider { + /** + * For a given position in a document, returns the range of the symbol at the position and all ranges + * that have the same content. A change to one of the ranges can be applied to all other ranges if the new content + * is valid. An optional word pattern can be returned with the result to describe valid contents. + * If no result-specific word pattern is provided, the word pattern from the language configuration is used. + * + * @param document The document in which the provider was invoked. + * @param position The position at which the provider was invoked. + * @param token A cancellation token. + * @return A list of ranges that can be edited together + */ + provideLinkedEditingRanges(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; + } + /** * A tuple of two characters, like a pair of * opening and closing brackets. @@ -5339,7 +5404,7 @@ declare module 'vscode' { * * `My text $(icon-name) contains icons like $(icon-name) this one.` * - * Where the icon-name is taken from the [codicon](https://microsoft.github.io/vscode-codicons/dist/codicon.html) icon set, e.g. + * Where the icon-name is taken from the ThemeIcon [icon set](https://code.visualstudio.com/api/references/icons-in-labels#icon-listing), e.g. * `light-bulb`, `thumbsup`, `zap` etc. */ text: string; @@ -5525,6 +5590,72 @@ declare module 'vscode' { tooltip?: string; } + /** + * A file decoration represents metadata that can be rendered with a file. + */ + export class FileDecoration { + + /** + * A very short string that represents this decoration. + */ + badge?: string; + + /** + * A human-readable tooltip for this decoration. + */ + tooltip?: string; + + /** + * The color of this decoration. + */ + color?: ThemeColor; + + /** + * A flag expressing that this decoration should be + * propagated to its parents. + */ + propagate?: boolean; + + /** + * Creates a new decoration. + * + * @param badge A letter that represents the decoration. + * @param tooltip The tooltip of the decoration. + * @param color The color of the decoration. + */ + constructor(badge?: string, tooltip?: string, color?: ThemeColor); + } + + /** + * The decoration provider interfaces defines the contract between extensions and + * file decorations. + */ + export interface FileDecorationProvider { + + /** + * An optional event to signal that decorations for one or many files have changed. + * + * *Note* that this event should be used to propagate information about children. + * + * @see [EventEmitter](#EventEmitter) + */ + onDidChangeFileDecorations?: Event; + + /** + * Provide decorations for a given uri. + * + * *Note* that this function is only called when a file gets rendered in the UI. + * This means a decoration from a descendent that propagates upwards must be signaled + * to the editor via the [onDidChangeFileDecorations](#FileDecorationProvider.onDidChangeFileDecorations)-event. + * + * @param uri The uri of the file to provide a decoration for. + * @param token A cancellation token. + * @returns A decoration or a thenable that resolves to such. + */ + provideFileDecoration(uri: Uri, token: CancellationToken): ProviderResult; + } + + /** * In a remote window the extension kind describes if an extension * runs where the UI (window) runs or if an extension runs remotely. @@ -6180,14 +6311,13 @@ declare module 'vscode' { */ export class CustomExecution { /** - * Constructs a CustomExecution task object. The callback will be executed the task is run, at which point the + * Constructs a CustomExecution task object. The callback will be executed when the task is run, at which point the * extension should return the Pseudoterminal it will "run in". The task should wait to do further execution until * [Pseudoterminal.open](#Pseudoterminal.open) is called. Task cancellation should be handled using * [Pseudoterminal.close](#Pseudoterminal.close). When the task is complete fire * [Pseudoterminal.onDidClose](#Pseudoterminal.onDidClose). - * @param process The [Pseudoterminal](#Pseudoterminal) to be used by the task to display output. * @param callback The callback that will be called when the task is started by a user. Any ${} style variables that - * were in the task definition will be resolved and passed into the callback. + * were in the task definition will be resolved and passed into the callback as `resolvedDefinition`. */ constructor(callback: (resolvedDefinition: TaskDefinition) => Thenable); } @@ -6413,9 +6543,9 @@ declare module 'vscode' { readonly execution: TaskExecution; /** - * The process's exit code. + * The process's exit code. Will be `undefined` when the task is terminated. */ - readonly exitCode: number; + readonly exitCode: number | undefined; } export interface TaskFilter { @@ -6860,6 +6990,21 @@ declare module 'vscode' { * @param options Defines if existing files should be overwritten. */ copy(source: Uri, target: Uri, options?: { overwrite?: boolean }): Thenable; + + /** + * Check if a given file system supports writing files. + * + * Keep in mind that just because a file system supports writing, that does + * not mean that writes will always succeed. There may be permissions issues + * or other errors that prevent writing a file. + * + * @param scheme The scheme of the filesystem, for example `file` or `git`. + * + * @return `true` if the file system supports writing, `false` if it does not + * support writing (i.e. it is readonly), and `undefined` if VS Code does not + * know about the filesystem. + */ + isWritableFileSystem(scheme: string): boolean | undefined; } /** @@ -8524,6 +8669,14 @@ declare module 'vscode' { */ export function registerTerminalLinkProvider(provider: TerminalLinkProvider): Disposable; + /** + * Register a file decoration provider. + * + * @param provider A [FileDecorationProvider](#FileDecorationProvider). + * @return A [disposable](#Disposable) that unregisters the provider. + */ + export function registerFileDecorationProvider(provider: FileDecorationProvider): Disposable; + /** * The currently active color theme as configured in the settings. The active * theme can be changed via the `workbench.colorTheme` setting. @@ -8700,13 +8853,34 @@ declare module 'vscode' { * @return Parent of `element`. */ getParent?(element: T): ProviderResult; + + /** + * Called only on hover to resolve the [TreeItem](#TreeItem.tooltip) property if it is undefined. + * Only properties that were undefined can be resolved in `resolveTreeItem`. + * Functionality may be expanded later to include being called to resolve other missing + * properties on selection and/or on open. + * + * Will only ever be called once per TreeItem. + * + * onDidChangeTreeData should not be triggered from within resolveTreeItem. + * + * *Note* that this function is called when tree items are already showing in the UI. + * Because of that, no property that changes the presentation (label, description, command, etc.) + * can be changed. + * + * @param element The object associated with the TreeItem + * @param item Undefined properties of `item` should be set then `item` should be returned. + * @return The resolved tree item or a thenable that resolves to such. It is OK to return the given + * `item`. When no result is returned, the given `item` will be used. + */ + resolveTreeItem?(item: TreeItem, element: T): ProviderResult; } export class TreeItem { /** * A human-readable string describing this item. When `falsy`, it is derived from [resourceUri](#TreeItem.resourceUri). */ - label?: string; + label?: string | TreeItemLabel; /** * Optional id for the tree item that has to be unique across tree. The id is used to preserve the selection and expansion state of the tree item. @@ -8739,10 +8913,14 @@ declare module 'vscode' { /** * The tooltip text when you hover over this item. */ - tooltip?: string | undefined; + tooltip?: string | MarkdownString | undefined; /** * The [command](#Command) that should be executed when the tree item is selected. + * + * Please use `vscode.open` or `vscode.diff` as command IDs when the tree item is opening + * something in the editor. Using these commands ensures that the resulting editor will + * appear consistent with how other built-in trees open editors. */ command?: Command; @@ -8782,7 +8960,7 @@ declare module 'vscode' { * @param label A human-readable string describing this item * @param collapsibleState [TreeItemCollapsibleState](#TreeItemCollapsibleState) of the tree item. Default is [TreeItemCollapsibleState.None](#TreeItemCollapsibleState.None) */ - constructor(label: string, collapsibleState?: TreeItemCollapsibleState); + constructor(label: string | TreeItemLabel, collapsibleState?: TreeItemCollapsibleState); /** * @param resourceUri The [uri](#Uri) of the resource representing this item. @@ -8809,6 +8987,23 @@ declare module 'vscode' { Expanded = 2 } + /** + * Label describing the [Tree item](#TreeItem) + */ + export interface TreeItemLabel { + + /** + * A human-readable string describing the [Tree item](#TreeItem). + */ + label: string; + + /** + * Ranges in the label to highlight. A range is defined as a tuple of two number where the + * first is the inclusive start index and the second the exclusive end index + */ + highlights?: [number, number][]; + } + /** * Value-object describing what options a terminal should use. */ @@ -9924,6 +10119,8 @@ declare module 'vscode' { * flags to ignore certain kinds of events can be provided. To stop listening to events the watcher must be disposed. * * *Note* that only files within the current [workspace folders](#workspace.workspaceFolders) can be watched. + * *Note* that when watching for file changes such as '**​/*.js', notifications will not be sent when a parent folder is + * moved or deleted (this is a known limitation of the current implementation and may change in the future). * * @param globPattern A [glob pattern](#GlobPattern) that is applied to the absolute paths of created, changed, * and deleted files. Use a [relative pattern](#RelativePattern) to limit events to a certain [workspace folder](#WorkspaceFolder). @@ -10691,6 +10888,19 @@ declare module 'vscode' { */ export function registerCallHierarchyProvider(selector: DocumentSelector, provider: CallHierarchyProvider): Disposable; + /** + * Register a linked editing range provider. + * + * Multiple providers can be registered for a language. In that case providers are sorted + * by their [score](#languages.match) and the best-matching provider that has a result is used. Failure + * of the selected provider will cause a failure of the whole operation. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider A linked editing range provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerLinkedEditingRangeProvider(selector: DocumentSelector, provider: LinkedEditingRangeProvider): Disposable; + /** * Set a [language configuration](#LanguageConfiguration) for a language. * @@ -10699,6 +10909,7 @@ declare module 'vscode' { * @return A [disposable](#Disposable) that unsets this configuration. */ export function setLanguageConfiguration(language: string, configuration: LanguageConfiguration): Disposable; + } /** diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 4076358f4..19e8d8f10 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -223,6 +223,13 @@ declare module 'vscode' { } + export interface TunnelCreationOptions { + /** + * True when the local operating system will require elevation to use the requested local port. + */ + elevationRequired?: boolean; + } + export type ResolverResult = ResolvedAuthority & ResolvedOptions & TunnelInformation; export class RemoteAuthorityResolverError extends Error { @@ -238,8 +245,11 @@ declare module 'vscode' { * Can be optionally implemented if the extension can forward ports better than the core. * When not implemented, the core will use its default forwarding logic. * When implemented, the core will use this to forward ports. + * + * To enable the "Change Local Port" action on forwarded ports, make sure to set the `localAddress` of + * the returned `Tunnel` to a `{ port: number, host: string; }` and not a string. */ - tunnelFactory?: (tunnelOptions: TunnelOptions) => Thenable | undefined; + tunnelFactory?: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => Thenable | undefined; /** * Provides filtering for candidate ports. @@ -741,71 +751,6 @@ declare module 'vscode' { //#endregion - //#region file-decorations: https://github.com/microsoft/vscode/issues/54938 - - - export class FileDecoration { - - /** - * A very short string that represents this decoration. - */ - badge?: string; - - /** - * A human-readable tooltip for this decoration. - */ - tooltip?: string; - - /** - * The color of this decoration. - */ - color?: ThemeColor; - - /** - * A flag expressing that this decoration should be - * propagated to its parents. - */ - propagate?: boolean; - - /** - * Creates a new decoration. - * - * @param badge A letter that represents the decoration. - * @param tooltip The tooltip of the decoration. - * @param color The color of the decoration. - */ - constructor(badge?: string, tooltip?: string, color?: ThemeColor); - } - - /** - * The decoration provider interfaces defines the contract between extensions and - * file decorations. - */ - export interface FileDecorationProvider { - - /** - * An event to signal decorations for one or many files have changed. - * - * @see [EventEmitter](#EventEmitter - */ - onDidChange: Event; - - /** - * Provide decorations for a given uri. - * - * @param uri The uri of the file to provide a decoration for. - * @param token A cancellation token. - * @returns A decoration or a thenable that resolves to such. - */ - provideFileDecoration(uri: Uri, token: CancellationToken): ProviderResult; - } - - export namespace window { - export function registerDecorationProvider(provider: FileDecorationProvider): Disposable; - } - - //#endregion - //#region debug /** @@ -824,46 +769,9 @@ declare module 'vscode' { // Properties: see details [here](https://microsoft.github.io/debug-adapter-protocol/specification#Base_Protocol_Variable). } - // deprecated debug API - - export interface DebugConfigurationProvider { - /** - * Deprecated, use DebugAdapterDescriptorFactory.provideDebugAdapter instead. - * @deprecated Use DebugAdapterDescriptorFactory.createDebugAdapterDescriptor instead - */ - debugAdapterExecutable?(folder: WorkspaceFolder | undefined, token?: CancellationToken): ProviderResult; - } - //#endregion - //#region LogLevel: https://github.com/microsoft/vscode/issues/85992 - /** - * @deprecated DO NOT USE, will be removed - */ - export enum LogLevel { - Trace = 1, - Debug = 2, - Info = 3, - Warning = 4, - Error = 5, - Critical = 6, - Off = 7 - } - - export namespace env { - /** - * @deprecated DO NOT USE, will be removed - */ - export const logLevel: LogLevel; - - /** - * @deprecated DO NOT USE, will be removed - */ - export const onDidChangeLogLevel: Event; - } - - //#endregion //#region @joaomoreno: SCM validation @@ -1008,45 +916,8 @@ declare module 'vscode' { //#endregion //#region Tree View: https://github.com/microsoft/vscode/issues/61313 - /** - * Label describing the [Tree item](#TreeItem) - */ - export interface TreeItemLabel { - - /** - * A human-readable string describing the [Tree item](#TreeItem). - */ - label: string; - - /** - * Ranges in the label to highlight. A range is defined as a tuple of two number where the - * first is the inclusive start index and the second the exclusive end index - */ - highlights?: [number, number][]; - - } - - // https://github.com/microsoft/vscode/issues/100741 - export interface TreeDataProvider { - resolveTreeItem?(element: T, item: TreeItem2): TreeItem2 | Thenable; - } - - export class TreeItem2 extends TreeItem { - /** - * Label describing this item. When `falsy`, it is derived from [resourceUri](#TreeItem.resourceUri). - */ - label?: string | TreeItemLabel | /* for compilation */ any; - - /** - * Content to be shown when you hover over the tree item. - */ - tooltip?: string | MarkdownString | /* for compilation */ any; - - /** - * @param label Label describing this item - * @param collapsibleState [TreeItemCollapsibleState](#TreeItemCollapsibleState) of the tree item. Default is [TreeItemCollapsibleState.None](#TreeItemCollapsibleState.None) - */ - constructor(label: TreeItemLabel, collapsibleState?: TreeItemCollapsibleState); + export interface TreeView extends Disposable { + reveal(element: T | undefined, options?: { select?: boolean, focus?: boolean, expand?: boolean | number }): Thenable; } //#endregion @@ -1112,45 +983,6 @@ declare module 'vscode' { //#endregion - //#region OnTypeRename: https://github.com/microsoft/vscode/issues/88424 - - /** - * The rename provider interface defines the contract between extensions and - * the live-rename feature. - */ - export interface OnTypeRenameProvider { - /** - * Provide a list of ranges that can be live renamed together. - * - * @param document The document in which the command was invoked. - * @param position The position at which the command was invoked. - * @param token A cancellation token. - * @return A list of ranges that can be live-renamed togehter. The ranges must have - * identical length and contain identical text content. The ranges cannot overlap. Optional a word pattern - * that overrides the word pattern defined when registering the provider. Live rename stops as soon as the renamed content - * no longer matches the word pattern. - */ - provideOnTypeRenameRanges(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<{ ranges: Range[]; wordPattern?: RegExp; }>; - } - - namespace languages { - /** - * Register a rename provider that works on type. - * - * Multiple providers can be registered for a language. In that case providers are sorted - * by their [score](#languages.match) and the best-matching provider is used. Failure - * of the selected provider will cause a failure of the whole operation. - * - * @param selector A selector that defines the documents this provider is applicable to. - * @param provider An on type rename provider. - * @param wordPattern Word pattern for this provider. - * @return A [disposable](#Disposable) that unregisters this provider when being disposed. - */ - export function registerOnTypeRenameProvider(selector: DocumentSelector, provider: OnTypeRenameProvider, wordPattern?: RegExp): Disposable; - } - - //#endregion - //#region Custom editor move https://github.com/microsoft/vscode/issues/86146 // TODO: Also for custom editor @@ -1403,6 +1235,12 @@ declare module 'vscode' { * The document's current run state */ runState?: NotebookRunState; + + /** + * Whether the document is trusted, default to true + * When false, insecure outputs like HTML, JavaScript, SVG will not be rendered. + */ + trusted?: boolean; } export interface NotebookDocumentContentOptions { @@ -1824,6 +1662,13 @@ declare module 'vscode' { dispose(): void; } + export interface NotebookDocumentShowOptions { + viewColumn?: ViewColumn; + preserveFocus?: boolean; + preview?: boolean; + selection?: NotebookCellRange; + } + export namespace notebook { export function registerNotebookContentProvider( @@ -1891,6 +1736,7 @@ declare module 'vscode' { export const onDidChangeActiveNotebookEditor: Event; export const onDidChangeNotebookEditorSelection: Event; export const onDidChangeNotebookEditorVisibleRanges: Event; + export function showNotebookDocument(document: NotebookDocument, options?: NotebookDocumentShowOptions): Promise; } //#endregion @@ -2138,36 +1984,338 @@ declare module 'vscode' { } //#endregion - //#region https://github.com/microsoft/vscode/issues/91697 - - export interface FileSystem { + //#region https://github.com/microsoft/vscode/issues/107467 + /* + General activation events: + - `onLanguage:*` most test extensions will want to activate when their + language is opened to provide code lenses. + - `onTests:*` new activation event very simiular to `workspaceContains`, + but only fired when the user wants to run tests or opens the test explorer. + */ + export namespace test { /** - * Check if a given file system supports writing files. - * - * Keep in mind that just because a file system supports writing, that does - * not mean that writes will always succeed. There may be permissions issues - * or other errors that prevent writing a file. - * - * @param scheme The scheme of the filesystem, for example `file` or `git`. - * - * @return `true` if the file system supports writing, `false` if it does not - * support writing (i.e. it is readonly), and `undefined` if VS Code does not - * know about the filesystem. + * Registers a provider that discovers tests for the given document + * selectors. It is activated when either tests need to be enumerated, or + * a document matching the selector is opened. */ - isWritableFileSystem(scheme: string): boolean | undefined; + export function registerTestProvider(testProvider: TestProvider): Disposable; + + /** + * Runs tests with the given options. If no options are given, then + * all tests are run. Returns the resulting test run. + */ + export function runTests(options: TestRunOptions): Thenable; + + /** + * Returns an observer that retrieves tests in the given workspace folder. + */ + export function createWorkspaceTestObserver(workspaceFolder: WorkspaceFolder): TestObserver; + + /** + * Returns an observer that retrieves tests in the given text document. + */ + export function createDocumentTestObserver(document: TextDocument): TestObserver; } + export interface TestObserver { + /** + * List of tests returned by test provider for files in the workspace. + */ + readonly tests: ReadonlyArray; + /** + * An event that fires when an existing test in the collection changes, or + * null if a top-level test was added or removed. When fired, the consumer + * should check the test item and all its children for changes. + */ + readonly onDidChangeTest: Event; + + /** + * An event the fires when all test providers have signalled that the tests + * the observer references have been discovered. Providers may continue to + * watch for changes and cause {@link onDidChangeTest} to fire as files + * change, until the observer is disposed. + * + * @todo as below + */ + readonly onDidDiscoverInitialTests: Event; + + /** + * Dispose of the observer, allowing VS Code to eventually tell test + * providers that they no longer need to update tests. + */ + dispose(): void; + } + + export interface TestChangeEvent { + /** + * List of all tests that are newly added. + */ + readonly added: ReadonlyArray; + + /** + * List of existing tests that have updated. + */ + readonly updated: ReadonlyArray; + + /** + * List of existing tests that have been removed. + */ + readonly removed: ReadonlyArray; + + /** + * Highest node in the test tree under which changes were made. This can + * be easily plugged into events like the TreeDataProvider update event. + */ + readonly commonChangeAncestor: TestItem | null; + } + + /** + * Tree of tests returned from the provide methods in the {@link TestProvider}. + */ + export interface TestHierarchy { + /** + * Root node for tests. The `testRoot` instance must not be replaced over + * the lifespan of the TestHierarchy, since you will need to reference it + * in `onDidChangeTest` when a test is added or removed. + */ + readonly root: T; + + /** + * An event that fires when an existing test under the `root` changes. + * This can be a result of a state change in a test run, a property update, + * or an update to its children. Changes made to tests will not be visible + * to {@link TestObserver} instances until this event is fired. + * + * This will signal a change recursively to all children of the given node. + * For example, firing the event with the {@link testRoot} will refresh + * all tests. + */ + readonly onDidChangeTest: Event; + + /** + * An event that should be fired when all tests that are currently defined + * have been discovered. The provider should continue to watch for changes + * and fire `onDidChangeTest` until the hierarchy is disposed. + * + * @todo can this be covered by existing progress apis? Or return a promise + */ + readonly onDidDiscoverInitialTests: Event; + + /** + * Dispose will be called when there are no longer observers interested + * in the hierarchy. + */ + dispose(): void; + } + + /** + * Discovers and provides tests. It's expected that the TestProvider will + * ambiently listen to {@link vscode.window.onDidChangeVisibleTextEditors} to + * provide test information about the open files for use in code lenses and + * other file-specific UI. + * + * Additionally, the UI may request it to discover tests for the workspace + * via `addWorkspaceTests`. + * + * @todo rename from provider + */ + export interface TestProvider { + /** + * Requests that tests be provided for the given workspace. This will + * generally be called when tests need to be enumerated for the + * workspace. + * + * It's guaranteed that this method will not be called again while + * there is a previous undisposed watcher for the given workspace folder. + */ + createWorkspaceTestHierarchy?(workspace: WorkspaceFolder): TestHierarchy; + + /** + * Requests that tests be provided for the given document. This will + * be called when tests need to be enumerated for a single open file, + * for instance by code lens UI. + */ + createDocumentTestHierarchy?(document: TextDocument): TestHierarchy; + + /** + * Starts a test run. This should cause {@link onDidChangeTest} to + * fire with update test states during the run. + * @todo this will eventually need to be able to return a summary report, coverage for example. + */ + runTests?(options: TestRunOptions, cancellationToken: CancellationToken): ProviderResult; + } + + /** + * Options given to `TestProvider.runTests` + */ + export interface TestRunOptions { + /** + * Array of specific tests to run. The {@link TestProvider.testRoot} may + * be provided as an indication to run all tests. + */ + tests: T[]; + + /** + * Whether or not tests in this run should be debugged. + */ + debug: boolean; + } + + /** + * A test item is an item shown in the "test explorer" view. It encompasses + * both a suite and a test, since they have almost or identical capabilities. + */ + export interface TestItem { + /** + * Display name describing the test case. + */ + label: string; + + /** + * Optional description that appears next to the label. + */ + description?: string; + + /** + * Whether this test item can be run individually, defaults to `true` + * if not provided. + * + * In some cases, like Go's tests, test can have children but these + * children cannot be run independently. + */ + runnable?: boolean; + + /** + * Whether this test item can be debugged. Defaults to `false` if not provided. + */ + debuggable?: boolean; + + /** + * VS Code location. + */ + location?: Location; + + /** + * Optional list of nested tests for this item. + */ + children?: TestItem[]; + + /** + * Test run state. Will generally be {@link TestRunState.Unset} by + * default. + */ + state: TestState; + } + + export enum TestRunState { + // Initial state + Unset = 0, + // Test is currently running + Running = 1, + // Test run has passed + Passed = 2, + // Test run has failed (on an assertion) + Failed = 3, + // Test run has been skipped + Skipped = 4, + // Test run failed for some other reason (compilation error, timeout, etc) + Errored = 5 + } + + /** + * TestState includes a test and its run state. This is included in the + * {@link TestItem} and is immutable; it should be replaced in th TestItem + * in order to update it. This allows consumers to quickly and easily check + * for changes via object identity. + */ + export class TestState { + /** + * Current state of the test. + */ + readonly runState: TestRunState; + + /** + * Optional duration of the test run, in milliseconds. + */ + readonly duration?: number; + + /** + * Associated test run message. Can, for example, contain assertion + * failure information if the test fails. + */ + readonly messages: ReadonlyArray>; + + /** + * @param state Run state to hold in the test state + * @param messages List of associated messages for the test + * @param duration Length of time the test run took, if appropriate. + */ + constructor(runState: TestRunState, messages?: TestMessage[], duration?: number); + } + + /** + * Represents the severity of test messages. + */ + export enum TestMessageSeverity { + Error = 0, + Warning = 1, + Information = 2, + Hint = 3 + } + + /** + * Message associated with the test state. Can be linked to a specific + * source range -- useful for assertion failures, for example. + */ + export interface TestMessage { + /** + * Human-readable message text to display. + */ + message: string | MarkdownString; + + /** + * Message severity. Defaults to "Error", if not provided. + */ + severity?: TestMessageSeverity; + + /** + * Expected test output. If given with `actual`, a diff view will be shown. + */ + expectedOutput?: string; + + /** + * Actual test output. If given with `actual`, a diff view will be shown. + */ + actualOutput?: string; + + /** + * Associated file location. + */ + location?: Location; + } //#endregion - //#region https://github.com/microsoft/vscode/issues/108929 FoldingRangeProvider.onDidChangeFoldingRanges @aeschli - export interface FoldingRangeProvider2 extends FoldingRangeProvider { + //#region Statusbar Item Background Color (https://github.com/microsoft/vscode/issues/110214) + + /** + * A status bar item is a status bar contribution that can + * show text and icons and run a command on click. + */ + export interface StatusBarItem { /** - * An optional event to signal that the folding ranges from this provider have changed. + * The background color for this entry. + * + * Note: only `new ThemeColor('statusBarItem.errorBackground')` is + * supported for now. More background colors may be supported in the + * future. + * + * Note: when a background color is set, the statusbar may override + * the `color` choice to ensure the entry is readable in all themes. */ - onDidChangeFoldingRanges?: Event; - + backgroundColor: ThemeColor | undefined; } + //#endregion } diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index a4df85236..3b4c8a66c 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -64,6 +64,7 @@ import './mainThreadLabelService'; import './mainThreadTunnelService'; import './mainThreadAuthentication'; import './mainThreadTimeline'; +import './mainThreadTesting'; import 'vs/workbench/api/common/apiCommands'; export class ExtensionPoints implements IWorkbenchContribution { diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 02240e28b..c39650404 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -10,11 +10,10 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { IAuthenticationService, AllowedExtension, readAllowedExtensions, getAuthenticationProviderActivationEvent } from 'vs/workbench/services/authentication/browser/authenticationService'; import { ExtHostAuthenticationShape, ExtHostContext, IExtHostContext, MainContext, MainThreadAuthenticationShape } from '../common/extHost.protocol'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import Severity from 'vs/base/common/severity'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { fromNow } from 'vs/base/common/date'; import { ActivationKind, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -23,7 +22,7 @@ import { IEncryptionService } from 'vs/workbench/services/encryption/common/encr import { IProductService } from 'vs/platform/product/common/productService'; import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials'; -const VSO_ALLOWED_EXTENSIONS = ['github.vscode-pull-request-github', 'github.vscode-pull-request-github-insiders', 'vscode.git', 'ms-vsonline.vsonline', 'vscode.github-browser', 'ms-vscode.github-browser']; +const VSO_ALLOWED_EXTENSIONS = ['github.vscode-pull-request-github', 'github.vscode-pull-request-github-insiders', 'vscode.git', 'ms-vsonline.vsonline', 'vscode.github-browser', 'ms-vscode.github-browser', 'github.codespaces']; interface IAccountUsage { extensionId: string; @@ -70,7 +69,7 @@ function addAccountUsage(storageService: IStorageService, providerId: string, ac }); } - storageService.store(accountKey, JSON.stringify(usages), StorageScope.GLOBAL); + storageService.store(accountKey, JSON.stringify(usages), StorageScope.GLOBAL, StorageTarget.MACHINE); } export class MainThreadAuthenticationProvider extends Disposable { @@ -83,7 +82,6 @@ export class MainThreadAuthenticationProvider extends Disposable { public readonly label: string, public readonly supportsMultipleAccounts: boolean, private readonly notificationService: INotificationService, - private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, private readonly storageService: IStorageService, private readonly quickInputService: IQuickInputService, private readonly dialogService: IDialogService @@ -100,9 +98,15 @@ export class MainThreadAuthenticationProvider extends Disposable { } public manageTrustedExtensions(accountName: string) { + const allowedExtensions = readAllowedExtensions(this.storageService, this.id, accountName); + + if (!allowedExtensions.length) { + this.dialogService.show(Severity.Info, nls.localize('noTrustedExtensions', "This account has not been used by any extensions."), []); + return; + } + const quickPick = this.quickInputService.createQuickPick<{ label: string, description: string, extension: AllowedExtension }>(); quickPick.canSelectMany = true; - const allowedExtensions = readAllowedExtensions(this.storageService, this.id, accountName); const usages = readAccountUsages(this.storageService, this.id, accountName); const items = allowedExtensions.map(extension => { const usage = usages.find(usage => extension.id === usage.extensionId); @@ -122,7 +126,7 @@ export class MainThreadAuthenticationProvider extends Disposable { quickPick.onDidAccept(() => { const updatedAllowedList = quickPick.selectedItems.map(item => item.extension); - this.storageService.store(`${this.id}-${accountName}`, JSON.stringify(updatedAllowedList), StorageScope.GLOBAL); + this.storageService.store(`${this.id}-${accountName}`, JSON.stringify(updatedAllowedList), StorageScope.GLOBAL, StorageTarget.USER); quickPick.dispose(); }); @@ -153,8 +157,6 @@ export class MainThreadAuthenticationProvider extends Disposable { } else { this._accounts.set(session.account.label, [session.id]); } - - this.storageKeysSyncRegistryService.registerStorageKey({ key: `${this.id}-${session.account.label}`, version: 1 }); } async signOut(accountName: string): Promise { @@ -171,6 +173,7 @@ export class MainThreadAuthenticationProvider extends Disposable { if (result.confirmed) { sessionsForAccount?.forEach(sessionId => this.logout(sessionId)); removeAccountUsage(this.storageService, this.id, accountName); + this.storageService.remove(`${this.id}-${accountName}`, StorageScope.GLOBAL); } } @@ -220,7 +223,6 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu @IDialogService private readonly dialogService: IDialogService, @IStorageService private readonly storageService: IStorageService, @INotificationService private readonly notificationService: INotificationService, - @IStorageKeysSyncRegistryService private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, @IQuickInputService private readonly quickInputService: IQuickInputService, @IExtensionService private readonly extensionService: IExtensionService, @@ -259,7 +261,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu } async $registerAuthenticationProvider(id: string, label: string, supportsMultipleAccounts: boolean): Promise { - const provider = new MainThreadAuthenticationProvider(this._proxy, id, label, supportsMultipleAccounts, this.notificationService, this.storageKeysSyncRegistryService, this.storageService, this.quickInputService, this.dialogService); + const provider = new MainThreadAuthenticationProvider(this._proxy, id, label, supportsMultipleAccounts, this.notificationService, this.storageService, this.quickInputService, this.dialogService); await provider.initialize(); this.authenticationService.registerAuthenticationProvider(id, provider); } @@ -383,10 +385,10 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu const allowList = readAllowedExtensions(this.storageService, providerId, accountName); if (!allowList.find(allowed => allowed.id === extensionId)) { allowList.push({ id: extensionId, name: extensionName }); - this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL); + this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL, StorageTarget.USER); } - this.storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.GLOBAL); + this.storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.GLOBAL, StorageTarget.MACHINE); quickPick.dispose(); resolve(session); @@ -435,7 +437,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu if (allow) { addAccountUsage(this.storageService, providerId, accountName, extensionId, extensionName); allowList.push({ id: extensionId, name: extensionName }); - this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL); + this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL, StorageTarget.USER); } return allow; @@ -458,10 +460,11 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu const allowList = readAllowedExtensions(this.storageService, providerId, accountName); if (!allowList.find(allowed => allowed.id === extensionId)) { allowList.push({ id: extensionId, name: extensionName }); - this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL); + this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL, StorageTarget.USER); } - this.storageService.store(`${extensionName}-${providerId}`, sessionId, StorageScope.GLOBAL); + this.storageService.store(`${extensionName}-${providerId}`, sessionId, StorageScope.GLOBAL, StorageTarget.MACHINE); + addAccountUsage(this.storageService, providerId, accountName, extensionId, extensionName); } private getFullKey(extensionId: string): string { diff --git a/src/vs/workbench/api/browser/mainThreadBulkEdits.ts b/src/vs/workbench/api/browser/mainThreadBulkEdits.ts index b6ee8cacf..70fb045d7 100644 --- a/src/vs/workbench/api/browser/mainThreadBulkEdits.ts +++ b/src/vs/workbench/api/browser/mainThreadBulkEdits.ts @@ -37,8 +37,14 @@ export class MainThreadBulkEdits implements MainThreadBulkEditsShape { dispose(): void { } - $tryApplyWorkspaceEdit(dto: IWorkspaceEditDto): Promise { + $tryApplyWorkspaceEdit(dto: IWorkspaceEditDto, undoRedoGroupId?: number): Promise { const edits = reviveWorkspaceEditDto2(dto); - return this._bulkEditService.apply(edits).then(() => true, _err => false); + return this._bulkEditService.apply(edits, { + // having a undoRedoGroupId means that this is a nested workspace edit, + // e.g one from a onWill-handler and for now we need to forcefully suppress + // refactor previewing, see: https://github.com/microsoft/vscode/issues/111873#issuecomment-738739852 + undoRedoGroupId, + suppressPreview: typeof undoRedoGroupId === 'number' ? true : undefined + }).then(() => true, _err => false); } } diff --git a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts index 8ea573366..1256682d4 100644 --- a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts +++ b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts @@ -14,7 +14,7 @@ import { IActiveCodeEditor, IViewZone } from 'vs/editor/browser/editorBrowser'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { isEqual } from 'vs/base/common/resources'; -// todo@joh move these things back into something like contrib/insets +// todo@jrieken move these things back into something like contrib/insets class EditorWebviewZone implements IViewZone { readonly domNode: HTMLElement; @@ -73,7 +73,7 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape { async $createEditorInset(handle: number, id: string, uri: UriComponents, line: number, height: number, options: modes.IWebviewOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): Promise { let editor: IActiveCodeEditor | undefined; - id = id.substr(0, id.indexOf(',')); //todo@joh HACK + id = id.substr(0, id.indexOf(',')); //todo@jrieken HACK for (const candidate of this._editorService.listCodeEditors()) { if (candidate.getId() === id && candidate.hasModel() && isEqual(candidate.getModel().uri, URI.revive(uri))) { diff --git a/src/vs/workbench/api/browser/mainThreadCommands.ts b/src/vs/workbench/api/browser/mainThreadCommands.ts index ff817619f..38f6ac669 100644 --- a/src/vs/workbench/api/browser/mainThreadCommands.ts +++ b/src/vs/workbench/api/browser/mainThreadCommands.ts @@ -34,23 +34,23 @@ export class MainThreadCommands implements MainThreadCommandsShape { this._generateCommandsDocumentationRegistration.dispose(); } - private _generateCommandsDocumentation(): Promise { - return this._proxy.$getContributedCommandHandlerDescriptions().then(result => { - // add local commands - const commands = CommandsRegistry.getCommands(); - for (const [id, command] of commands) { - if (command.description) { - result[id] = command.description; - } - } + private async _generateCommandsDocumentation(): Promise { + const result = await this._proxy.$getContributedCommandHandlerDescriptions(); - // print all as markdown - const all: string[] = []; - for (let id in result) { - all.push('`' + id + '` - ' + _generateMarkdown(result[id])); + // add local commands + const commands = CommandsRegistry.getCommands(); + for (const [id, command] of commands) { + if (command.description) { + result[id] = command.description; } - console.log(all.join('\n')); - }); + } + + // print all as markdown + const all: string[] = []; + for (let id in result) { + all.push('`' + id + '` - ' + _generateMarkdown(result[id])); + } + console.log(all.join('\n')); } $registerCommand(id: string): void { diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index b6b328412..8dba884cb 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -21,6 +21,8 @@ import { ViewContainer, IViewContainersRegistry, Extensions as ViewExtensions, V import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { Codicon } from 'vs/base/common/codicons'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; +import { localize } from 'vs/nls'; export class MainThreadCommentThread implements modes.CommentThread { @@ -351,6 +353,9 @@ export class MainThreadCommentController { } } + +const commentsViewIcon = registerIcon('comments-view-icon', Codicon.commentDiscussion, localize('commentsViewIcon', 'View icon of the comments view.')); + @extHostNamedCustomer(MainContext.MainThreadComments) export class MainThreadComments extends Disposable implements MainThreadCommentsShape { private readonly _proxy: ExtHostCommentsShape; @@ -472,7 +477,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [COMMENTS_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), storageId: COMMENTS_VIEW_TITLE, hideIfEmpty: true, - icon: Codicon.commentDiscussion.classNames, + icon: commentsViewIcon, order: 10, }, ViewContainerLocation.Panel); @@ -482,7 +487,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments canToggleVisibility: false, ctorDescriptor: new SyncDescriptor(CommentsPanel), canMoveView: true, - containerIcon: Codicon.commentDiscussion.classNames, + containerIcon: commentsViewIcon, focusCommand: { id: 'workbench.action.focusCommentsPanel' } diff --git a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts index 410b6b061..bcf31a1a7 100644 --- a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts @@ -23,8 +23,7 @@ import { IUndoRedoService, UndoRedoElementType } from 'vs/platform/undoRedo/comm import { MainThreadWebviewPanels } from 'vs/workbench/api/browser/mainThreadWebviewPanels'; import { MainThreadWebviews, reviveWebviewExtension } from 'vs/workbench/api/browser/mainThreadWebviews'; import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; -import { editorGroupToViewColumn } from 'vs/workbench/api/common/shared/editor'; -import { IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; +import { editorGroupToViewColumn, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; import { CustomEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; import { CustomDocumentBackupData } from 'vs/workbench/contrib/customEditor/browser/customEditorInputFactory'; import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; @@ -604,6 +603,10 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod this._proxy.$backup(this._editorResource.toJSON(), this.viewType, token))); this._hotExitState = pendingState; + token.onCancellationRequested(() => { + pendingState.operation.cancel(); + }); + try { const backupId = await pendingState.operation; // Make sure state has not changed in the meantime diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index 54fd5da63..f1906f16a 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -82,7 +82,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb // RPC methods (MainThreadDebugServiceShape) public $registerDebugTypes(debugTypes: string[]) { - this._toDispose.add(this.debugService.getConfigurationManager().registerDebugAdapterFactory(debugTypes, this)); + this._toDispose.add(this.debugService.getAdapterManager().registerDebugAdapterFactory(debugTypes, this)); } public $startBreakpointEvents(): void { @@ -155,7 +155,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb return Promise.resolve(); } - public $registerDebugConfigurationProvider(debugType: string, providerTriggerKind: DebugConfigurationProviderTriggerKind, hasProvide: boolean, hasResolve: boolean, hasResolve2: boolean, hasProvideDebugAdapter: boolean, handle: number): Promise { + public $registerDebugConfigurationProvider(debugType: string, providerTriggerKind: DebugConfigurationProviderTriggerKind, hasProvide: boolean, hasResolve: boolean, hasResolve2: boolean, handle: number): Promise { const provider = { type: debugType, @@ -176,12 +176,6 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb return this._proxy.$resolveDebugConfigurationWithSubstitutedVariables(handle, folder, config, token); }; } - if (hasProvideDebugAdapter) { - console.info('DebugConfigurationProvider.debugAdapterExecutable is deprecated and will be removed soon; please use DebugAdapterDescriptorFactory.createDebugAdapterDescriptor instead.'); - provider.debugAdapterExecutable = (folder) => { - return this._proxy.$legacyDebugAdapterExecutable(handle, folder); - }; - } this._debugConfigurationProviders.set(handle, provider); this._toDispose.add(this.debugService.getConfigurationManager().registerDebugConfigurationProvider(provider)); @@ -205,7 +199,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb } }; this._debugAdapterDescriptorFactories.set(handle, provider); - this._toDispose.add(this.debugService.getConfigurationManager().registerDebugAdapterDescriptorFactory(provider)); + this._toDispose.add(this.debugService.getAdapterManager().registerDebugAdapterDescriptorFactory(provider)); return Promise.resolve(undefined); } @@ -214,7 +208,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb const provider = this._debugAdapterDescriptorFactories.get(handle); if (provider) { this._debugAdapterDescriptorFactories.delete(handle); - this.debugService.getConfigurationManager().unregisterDebugAdapterDescriptorFactory(provider); + this.debugService.getAdapterManager().unregisterDebugAdapterDescriptorFactory(provider); } } diff --git a/src/vs/workbench/api/browser/mainThreadDialogs.ts b/src/vs/workbench/api/browser/mainThreadDialogs.ts index ff3b66e93..afa4240f3 100644 --- a/src/vs/workbench/api/browser/mainThreadDialogs.ts +++ b/src/vs/workbench/api/browser/mainThreadDialogs.ts @@ -23,12 +23,20 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape { // } - $showOpenDialog(options?: MainThreadDialogOpenOptions): Promise { - return Promise.resolve(this._fileDialogService.showOpenDialog(MainThreadDialogs._convertOpenOptions(options))); + async $showOpenDialog(options?: MainThreadDialogOpenOptions): Promise { + const convertedOptions = MainThreadDialogs._convertOpenOptions(options); + if (!convertedOptions.defaultUri) { + convertedOptions.defaultUri = await this._fileDialogService.defaultFilePath(); + } + return Promise.resolve(this._fileDialogService.showOpenDialog(convertedOptions)); } - $showSaveDialog(options?: MainThreadDialogSaveOptions): Promise { - return Promise.resolve(this._fileDialogService.showSaveDialog(MainThreadDialogs._convertSaveOptions(options))); + async $showSaveDialog(options?: MainThreadDialogSaveOptions): Promise { + const convertedOptions = MainThreadDialogs._convertSaveOptions(options); + if (!convertedOptions.defaultUri) { + convertedOptions.defaultUri = await this._fileDialogService.defaultFilePath(); + } + return Promise.resolve(this._fileDialogService.showSaveDialog(convertedOptions)); } private static _convertOpenOptions(options?: MainThreadDialogOpenOptions): IOpenDialogOptions { @@ -38,7 +46,8 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape { canSelectFolders: options?.canSelectFolders, canSelectMany: options?.canSelectMany, defaultUri: options?.defaultUri ? URI.revive(options.defaultUri) : undefined, - title: options?.title || undefined + title: options?.title || undefined, + availableFileSystems: [] }; if (options?.filters) { result.filters = []; diff --git a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts index c92a58c27..242521214 100644 --- a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts @@ -19,9 +19,8 @@ import { MainThreadDocuments } from 'vs/workbench/api/browser/mainThreadDocument import { MainThreadTextEditor } from 'vs/workbench/api/browser/mainThreadEditor'; import { MainThreadTextEditors } from 'vs/workbench/api/browser/mainThreadEditors'; import { ExtHostContext, ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta, IExtHostContext, IModelAddedData, ITextEditorAddData, MainContext } from 'vs/workbench/api/common/extHost.protocol'; -import { EditorViewColumn, editorGroupToViewColumn } from 'vs/workbench/api/common/shared/editor'; import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; -import { IEditorPane } from 'vs/workbench/common/editor'; +import { editorGroupToViewColumn, EditorGroupColumn, IEditorPane } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; @@ -31,41 +30,8 @@ import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/commo import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; +import { diffSets, diffMaps } from 'vs/base/common/collections'; -namespace delta { - - export function ofSets(before: Set, after: Set): { removed: T[], added: T[] } { - const removed: T[] = []; - const added: T[] = []; - for (let element of before) { - if (!after.has(element)) { - removed.push(element); - } - } - for (let element of after) { - if (!before.has(element)) { - added.push(element); - } - } - return { removed, added }; - } - - export function ofMaps(before: Map, after: Map): { removed: V[], added: V[] } { - const removed: V[] = []; - const added: V[] = []; - for (let [index, value] of before) { - if (!after.has(index)) { - removed.push(value); - } - } - for (let [index, value] of after) { - if (!before.has(index)) { - added.push(value); - } - } - return { removed, added }; - } -} class TextEditorSnapshot { @@ -118,8 +84,8 @@ class DocumentAndEditorState { undefined, after.activeEditor ); } - const documentDelta = delta.ofSets(before.documents, after.documents); - const editorDelta = delta.ofMaps(before.textEditors, after.textEditors); + const documentDelta = diffSets(before.documents, after.documents); + const editorDelta = diffMaps(before.textEditors, after.textEditors); const oldActiveEditor = before.activeEditor !== after.activeEditor ? before.activeEditor : undefined; const newActiveEditor = before.activeEditor !== after.activeEditor ? after.activeEditor : undefined; @@ -443,7 +409,7 @@ export class MainThreadDocumentsAndEditors { }; } - private _findEditorPosition(editor: MainThreadTextEditor): EditorViewColumn | undefined { + private _findEditorPosition(editor: MainThreadTextEditor): EditorGroupColumn | undefined { for (const editorPane of this._editorService.visibleEditorPanes) { if (editor.matches(editorPane)) { return editorGroupToViewColumn(this._editorGroupService, editorPane.group); diff --git a/src/vs/workbench/api/browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts index 738493d39..1723f6328 100644 --- a/src/vs/workbench/api/browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -14,18 +14,14 @@ import { ISelection } from 'vs/editor/common/core/selection'; import { IDecorationOptions, IDecorationRenderOptions, ILineChange } from 'vs/editor/common/editorCommon'; import { ISingleEditOperation } from 'vs/editor/common/model'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IEditorOptions, ITextEditorOptions, IResourceEditorInput, EditorActivation } from 'vs/platform/editor/common/editor'; +import { ITextEditorOptions, IResourceEditorInput, EditorActivation } from 'vs/platform/editor/common/editor'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/browser/mainThreadDocumentsAndEditors'; import { MainThreadTextEditor } from 'vs/workbench/api/browser/mainThreadEditor'; import { ExtHostContext, ExtHostEditorsShape, IApplyEditsOptions, IExtHostContext, ITextDocumentShowOptions, ITextEditorConfigurationUpdate, ITextEditorPositionData, IUndoStopOptions, MainThreadTextEditorsShape, TextEditorRevealType, IWorkspaceEditDto, WorkspaceEditType } from 'vs/workbench/api/common/extHost.protocol'; -import { EditorViewColumn, editorGroupToViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor'; +import { editorGroupToViewColumn, EditorGroupColumn, viewColumnToEditorGroup } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { openEditorWith } from 'vs/workbench/services/editor/common/editorOpenWith'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { revive } from 'vs/base/common/marshalling'; @@ -161,7 +157,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { return this._documentsAndEditors.findTextEditorIdFor(editor); } - async $tryShowEditor(id: string, position?: EditorViewColumn): Promise { + async $tryShowEditor(id: string, position?: EditorGroupColumn): Promise { const mainThreadEditor = this._documentsAndEditors.getEditor(id); if (mainThreadEditor) { const model = mainThreadEditor.getModel(); @@ -297,59 +293,6 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { // --- commands -CommandsRegistry.registerCommand('_workbench.open', async function (accessor: ServicesAccessor, args: [URI, IEditorOptions, EditorViewColumn, string?]) { - const editorService = accessor.get(IEditorService); - const editorGroupService = accessor.get(IEditorGroupsService); - const openerService = accessor.get(IOpenerService); - - const [resource, options, position, label] = args; - - if (options || typeof position === 'number') { - // use editor options or editor view column as a hint to use the editor service for opening - await editorService.openEditor({ resource, options, label }, viewColumnToEditorGroup(editorGroupService, position)); - return; - } - - if (resource && resource.scheme === 'command') { - // do not allow to execute commands from here - return; - - } - // finally, delegate to opener service - await openerService.open(resource); -}); - -CommandsRegistry.registerCommand('_workbench.openWith', (accessor: ServicesAccessor, args: [URI, string, ITextEditorOptions | undefined, EditorViewColumn | undefined]) => { - const editorService = accessor.get(IEditorService); - const editorGroupsService = accessor.get(IEditorGroupsService); - const configurationService = accessor.get(IConfigurationService); - const quickInputService = accessor.get(IQuickInputService); - - const [resource, id, options, position] = args; - - const group = editorGroupsService.getGroup(viewColumnToEditorGroup(editorGroupsService, position)) ?? editorGroupsService.activeGroup; - const textOptions: ITextEditorOptions = options ? { ...options, override: false } : { override: false }; - - const input = editorService.createEditorInput({ resource }); - return openEditorWith(input, id, textOptions, group, editorService, configurationService, quickInputService); -}); - - -CommandsRegistry.registerCommand('_workbench.diff', async function (accessor: ServicesAccessor, args: [URI, URI, string, string, IEditorOptions, EditorViewColumn]) { - const editorService = accessor.get(IEditorService); - const editorGroupService = accessor.get(IEditorGroupsService); - - let [leftResource, rightResource, label, description, options, position] = args; - - if (!options || typeof options !== 'object') { - options = { - preserveFocus: false - }; - } - - await editorService.openEditor({ leftResource, rightResource, label, description, options }, viewColumnToEditorGroup(editorGroupService, position)); -}); - CommandsRegistry.registerCommand('_workbench.revertAllDirty', async function (accessor: ServicesAccessor) { const environmentService = accessor.get(IEnvironmentService); if (!environmentService.extensionTestsLocationURI) { diff --git a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts index ca2c751ed..81905c6b1 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts @@ -54,11 +54,13 @@ export class MainThreadFileSystemEventService { // BEFORE file operation - workingCopyFileService.addFileOperationParticipant({ - participate: (files, operation, progress, timeout, token) => { - return proxy.$onWillRunFileOperation(operation, files, timeout, token); + this._listener.add(workingCopyFileService.addFileOperationParticipant({ + participate: async (files, operation, undoRedoGroupId, isUndoing, _progress, timeout, token) => { + if (!isUndoing) { + return proxy.$onWillRunFileOperation(operation, files, undoRedoGroupId, timeout, token); + } } - }); + })); // AFTER file operation this._listener.add(workingCopyFileService.onDidRunWorkingCopyFileOperation(e => proxy.$onDidRunFileOperation(e.operation, e.files))); @@ -75,7 +77,7 @@ Registry.as(Extensions.Configuration).registerConfigurat properties: { 'files.participants.timeout': { type: 'number', - default: 5000, + default: 60000, markdownDescription: localize('files.participants.timeout', "Timeout in milliseconds after which file participants for create, rename, and delete are cancelled. Use `0` to disable participants."), } } diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 64a3f19a3..ddea8e0e9 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -165,16 +165,15 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha $registerCodeLensSupport(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void { const provider = { - provideCodeLenses: (model: ITextModel, token: CancellationToken): Promise => { - return this._proxy.$provideCodeLenses(handle, model.uri, token).then(listDto => { - if (!listDto) { - return undefined; - } - return { - lenses: listDto.lenses, - dispose: () => listDto.cacheId && this._proxy.$releaseCodeLenses(handle, listDto.cacheId) - }; - }); + provideCodeLenses: async (model: ITextModel, token: CancellationToken): Promise => { + const listDto = await this._proxy.$provideCodeLenses(handle, model.uri, token); + if (!listDto) { + return undefined; + } + return { + lenses: listDto.lenses, + dispose: () => listDto.cacheId && this._proxy.$releaseCodeLenses(handle, listDto.cacheId) + }; }, resolveCodeLens: (_model: ITextModel, codeLens: modes.CodeLens, token: CancellationToken): Promise => { return this._proxy.$resolveCodeLens(handle, codeLens, token); @@ -261,14 +260,12 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha })); } - // --- on type rename + // --- linked editing - $registerOnTypeRenameProvider(handle: number, selector: IDocumentFilterDto[], wordPattern?: IRegExpDto): void { - const revivedWordPattern = wordPattern ? MainThreadLanguageFeatures._reviveRegExp(wordPattern) : undefined; - this._registrations.set(handle, modes.OnTypeRenameProviderRegistry.register(selector, { - wordPattern: revivedWordPattern, - provideOnTypeRenameRanges: async (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise<{ ranges: IRange[]; wordPattern?: RegExp; } | undefined> => { - const res = await this._proxy.$provideOnTypeRenameRanges(handle, model.uri, position, token); + $registerLinkedEditingRangeProvider(handle: number, selector: IDocumentFilterDto[]): void { + this._registrations.set(handle, modes.LinkedEditingRangeProviderRegistry.register(selector, { + provideLinkedEditingRanges: async (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise => { + const res = await this._proxy.$provideLinkedEditingRanges(handle, model.uri, position, token); if (res) { return { ranges: res.ranges, diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 93215c290..cbf34422d 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -5,6 +5,7 @@ import * as DOM from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { diffMaps, diffSets } from 'vs/base/common/collections'; import { Emitter } from 'vs/base/common/event'; import { IRelativePattern } from 'vs/base/common/glob'; import { combinedDisposable, Disposable, DisposableStore, dispose, IDisposable, IReference } from 'vs/base/common/lifecycle'; @@ -14,8 +15,11 @@ import { IExtUri } from 'vs/base/common/resources'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { EditorActivation, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { ILogService } from 'vs/platform/log/common/log'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { viewColumnToEditorGroup } from 'vs/workbench/common/editor'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; @@ -23,44 +27,14 @@ import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/com import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, DisplayOrderKey, ICellEditOperation, ICellRange, IEditor, IMainCellDto, INotebookDecorationRenderOptions, INotebookDocumentFilter, INotebookEditorModel, INotebookExclusiveDocumentFilter, NotebookCellOutputsSplice, NotebookCellsChangeType, NOTEBOOK_DISPLAY_ORDER, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroup, IEditorGroupsService, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { openEditorWith } from 'vs/workbench/services/editor/common/editorOpenWith'; +import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookCellStatusBarEntryDto, INotebookDocumentsAndEditorsDelta, INotebookModelAddedData, MainContext, MainThreadNotebookShape, NotebookEditorRevealType, NotebookExtensionDescription } from '../common/extHost.protocol'; +import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookCellStatusBarEntryDto, INotebookDocumentsAndEditorsDelta, INotebookDocumentShowOptions, INotebookModelAddedData, MainContext, MainThreadNotebookShape, NotebookEditorRevealType, NotebookExtensionDescription } from '../common/extHost.protocol'; class DocumentAndEditorState { - static ofSets(before: Set, after: Set): { removed: T[], added: T[] } { - const removed: T[] = []; - const added: T[] = []; - before.forEach(element => { - if (!after.has(element)) { - removed.push(element); - } - }); - after.forEach(element => { - if (!before.has(element)) { - added.push(element); - } - }); - return { removed, added }; - } - - static ofMaps(before: Map, after: Map): { removed: V[], added: V[] } { - const removed: V[] = []; - const added: V[] = []; - before.forEach((value, index) => { - if (!after.has(index)) { - removed.push(value); - } - }); - after.forEach((value, index) => { - if (!before.has(index)) { - added.push(value); - } - }); - return { removed, added }; - } - static compute(before: DocumentAndEditorState | undefined, after: DocumentAndEditorState): INotebookDocumentsAndEditorsDelta { if (!before) { const apiEditors = []; @@ -75,8 +49,8 @@ class DocumentAndEditorState { visibleEditors: [...after.visibleEditors].map(editor => editor[0]) }; } - const documentDelta = DocumentAndEditorState.ofSets(before.documents, after.documents); - const editorDelta = DocumentAndEditorState.ofMaps(before.textEditors, after.textEditors); + const documentDelta = diffSets(before.documents, after.documents); + const editorDelta = diffMaps(before.textEditors, after.textEditors); const addedAPIEditors = editorDelta.added.map(add => ({ id: add.getId(), documentUri: add.uri!, @@ -89,7 +63,7 @@ class DocumentAndEditorState { // const oldActiveEditor = before.activeEditor !== after.activeEditor ? before.activeEditor : undefined; const newActiveEditor = before.activeEditor !== after.activeEditor ? after.activeEditor : undefined; - const visibleEditorDelta = DocumentAndEditorState.ofMaps(before.visibleEditors, after.visibleEditors); + const visibleEditorDelta = diffMaps(before.visibleEditors, after.visibleEditors); return { addedDocuments: documentDelta.added.map((e: NotebookTextModel): INotebookModelAddedData => { @@ -152,7 +126,10 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo @INotebookService private _notebookService: INotebookService, @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, + @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService, + @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IAccessibilityService private readonly accessibilityService: IAccessibilityService, + @IQuickInputService private readonly quickInputService: IQuickInputService, @ILogService private readonly logService: ILogService, @INotebookCellStatusBarService private readonly cellStatusBarService: INotebookCellStatusBarService, @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, @@ -171,7 +148,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo if (!textModel) { return false; } - this._notebookService.transformEditsOutputs(textModel, cellEdits); return textModel.applyEdits(modelVersionId, cellEdits, true, undefined, () => undefined, undefined); } @@ -413,9 +389,9 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo const editors = new Map(); this._notebookService.listNotebookEditors().forEach(editor => { - if (editor.hasModel()) { + if (editor.textModel) { editors.set(editor.getId(), editor); - documentEditorsMap.set(editor.textModel!.uri.toString(), editor); + documentEditorsMap.set(editor.textModel.uri.toString(), editor); } }); @@ -434,7 +410,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo documents.add(document); }); - if (!activeEditor && focusedNotebookEditor && focusedNotebookEditor.hasModel()) { + if (!activeEditor && focusedNotebookEditor && focusedNotebookEditor.textModel) { activeEditor = focusedNotebookEditor.getId(); } @@ -479,8 +455,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo const edits: ICellEditOperation[] = [ { editType: CellEditType.Replace, index: 0, count: mainthreadTextModel.cells.length, cells: data.cells } ]; - - this._notebookService.transformEditsOutputs(mainthreadTextModel, edits); await new Promise(resolve => { DOM.scheduleAtNextAnimationFrame(() => { const ret = mainthreadTextModel!.applyEdits(mainthreadTextModel!.versionId, edits, true, undefined, () => undefined, undefined); @@ -606,7 +580,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo return; } - this._notebookService.transformSpliceOutputs(textModel, splices); const cell = textModel.cells.find(cell => cell.handle === cellHandle); if (!cell) { @@ -662,8 +635,11 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo const editor = this._notebookService.listNotebookEditors().find(editor => editor.getId() === id); if (editor && editor.isNotebookEditor) { const notebookEditor = editor as INotebookEditor; + if (!notebookEditor.hasModel()) { + return; + } const viewModel = notebookEditor.viewModel; - const cell = viewModel?.viewCells[range.start]; + const cell = viewModel.viewCells[range.start]; if (!cell) { return; } @@ -726,6 +702,51 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo return uri; } + + async $tryShowNotebookDocument(resource: UriComponents, viewType: string, options: INotebookDocumentShowOptions): Promise { + const editorOptions: ITextEditorOptions = { + preserveFocus: options.preserveFocus, + pinned: options.pinned, + // selection: options.selection, + // 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, + override: false, + }; + + const columnArg = viewColumnToEditorGroup(this._editorGroupService, options.position); + + let group: IEditorGroup | undefined = undefined; + + if (columnArg === SIDE_GROUP) { + const direction = preferredSideBySideGroupDirection(this.configurationService); + + let neighbourGroup = this.editorGroupsService.findGroup({ direction }); + if (!neighbourGroup) { + neighbourGroup = this.editorGroupsService.addGroup(this.editorGroupsService.activeGroup, direction); + } + group = neighbourGroup; + } else { + group = this.editorGroupsService.getGroup(viewColumnToEditorGroup(this.editorGroupsService, columnArg)) ?? this.editorGroupsService.activeGroup; + } + + const input = this.editorService.createEditorInput({ resource: URI.revive(resource), options: editorOptions }); + + // TODO: handle options.selection + const editorPane = await openEditorWith(input, viewType, options, group, this.editorService, this.configurationService, this.quickInputService); + const notebookEditor = (editorPane as unknown as { isNotebookEditor?: boolean })?.isNotebookEditor ? (editorPane!.getControl() as INotebookEditor) : undefined; + + if (notebookEditor) { + if (notebookEditor.viewModel && options.selection && notebookEditor.viewModel.viewCells[options.selection.start]) { + const focusedCell = notebookEditor.viewModel.viewCells[options.selection.start]; + notebookEditor.revealInCenterIfOutsideViewport(focusedCell); + notebookEditor.selectElement(focusedCell); + } + return notebookEditor.getId(); + } else { + throw new Error(`Notebook Editor creation failure for documenet ${resource}`); + } + } } diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 32dbe8049..8f1e25ebb 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -68,7 +68,8 @@ class MainThreadSCMResource implements ISCMResource { readonly sourceUri: URI, readonly resourceGroup: ISCMResourceGroup, readonly decorations: ISCMResourceDecorations, - readonly contextValue: string | undefined + readonly contextValue: string | undefined, + readonly command: Command | undefined ) { } open(preserveFocus: boolean): Promise { @@ -201,7 +202,7 @@ class MainThreadSCMProvider implements ISCMProvider { for (const [start, deleteCount, rawResources] of groupSlices) { const resources = rawResources.map(rawResource => { - const [handle, sourceUri, icons, tooltip, strikeThrough, faded, contextValue] = rawResource; + const [handle, sourceUri, icons, tooltip, strikeThrough, faded, contextValue, command] = rawResource; const icon = icons[0]; const iconDark = icons[1] || icon; const decorations = { @@ -220,7 +221,8 @@ class MainThreadSCMProvider implements ISCMProvider { URI.revive(sourceUri), group, decorations, - contextValue || undefined + contextValue || undefined, + command ); }); diff --git a/src/vs/workbench/api/browser/mainThreadStatusBar.ts b/src/vs/workbench/api/browser/mainThreadStatusBar.ts index d50bc8f08..a847df3bd 100644 --- a/src/vs/workbench/api/browser/mainThreadStatusBar.ts +++ b/src/vs/workbench/api/browser/mainThreadStatusBar.ts @@ -27,7 +27,7 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape { this.entries.clear(); } - $setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: Command | undefined, color: string | ThemeColor | undefined, alignment: MainThreadStatusBarAlignment, priority: number | undefined, accessibilityInformation: IAccessibilityInformation): void { + $setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: Command | undefined, color: string | ThemeColor | undefined, backgroundColor: string | ThemeColor | undefined, alignment: MainThreadStatusBarAlignment, priority: number | undefined, accessibilityInformation: IAccessibilityInformation): void { // if there are icons in the text use the tooltip for the aria label let ariaLabel: string; let role: string | undefined = undefined; @@ -37,7 +37,7 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape { } else { ariaLabel = text ? text.replace(MainThreadStatusBar.CODICON_REGEXP, (_match, codiconName) => codiconName) : ''; } - const entry: IStatusbarEntry = { text, tooltip, command, color, ariaLabel, role }; + const entry: IStatusbarEntry = { text, tooltip, command, color, backgroundColor, ariaLabel, role }; if (typeof priority === 'undefined') { priority = 0; diff --git a/src/vs/workbench/api/browser/mainThreadStorage.ts b/src/vs/workbench/api/browser/mainThreadStorage.ts index 57abf0e86..5cb658e91 100644 --- a/src/vs/workbench/api/browser/mainThreadStorage.ts +++ b/src/vs/workbench/api/browser/mainThreadStorage.ts @@ -3,17 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { MainThreadStorageShape, MainContext, IExtHostContext, ExtHostStorageShape, ExtHostContext } from '../common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { IExtensionIdWithVersion, IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; +import { IExtensionIdWithVersion, IExtensionsStorageSyncService } from 'vs/platform/userDataSync/common/extensionsStorageSync'; @extHostNamedCustomer(MainContext.MainThreadStorage) export class MainThreadStorage implements MainThreadStorageShape { private readonly _storageService: IStorageService; - private readonly _storageKeysSyncRegistryService: IStorageKeysSyncRegistryService; + private readonly _extensionsStorageSyncService: IExtensionsStorageSyncService; private readonly _proxy: ExtHostStorageShape; private readonly _storageListener: IDisposable; private readonly _sharedStorageKeysToWatch: Map = new Map(); @@ -21,13 +21,13 @@ export class MainThreadStorage implements MainThreadStorageShape { constructor( extHostContext: IExtHostContext, @IStorageService storageService: IStorageService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, + @IExtensionsStorageSyncService extensionsStorageSyncService: IExtensionsStorageSyncService, ) { this._storageService = storageService; - this._storageKeysSyncRegistryService = storageKeysSyncRegistryService; + this._extensionsStorageSyncService = extensionsStorageSyncService; this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostStorage); - this._storageListener = this._storageService.onDidChangeStorage(e => { + this._storageListener = this._storageService.onDidChangeValue(e => { const shared = e.scope === StorageScope.GLOBAL; if (shared && this._sharedStorageKeysToWatch.has(e.key)) { try { @@ -66,7 +66,8 @@ export class MainThreadStorage implements MainThreadStorageShape { let jsonValue: string; try { jsonValue = JSON.stringify(value); - this._storageService.store(key, jsonValue, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE); + // Extension state is synced separately through extensions + this._storageService.store(key, jsonValue, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE, StorageTarget.MACHINE); } catch (err) { return Promise.reject(err); } @@ -74,6 +75,6 @@ export class MainThreadStorage implements MainThreadStorageShape { } $registerExtensionStorageKeysToSync(extension: IExtensionIdWithVersion, keys: string[]): void { - this._storageKeysSyncRegistryService.registerExtensionStorageKeys(extension, keys); + this._extensionsStorageSyncService.setKeysForSync(extension, keys); } } diff --git a/src/vs/workbench/api/browser/mainThreadTask.ts b/src/vs/workbench/api/browser/mainThreadTask.ts index 391cd0cef..2f4cb073c 100644 --- a/src/vs/workbench/api/browser/mainThreadTask.ts +++ b/src/vs/workbench/api/browser/mainThreadTask.ts @@ -53,7 +53,7 @@ namespace TaskProcessStartedDTO { } namespace TaskProcessEndedDTO { - export function from(value: TaskExecution, exitCode: number): TaskProcessEndedDTO { + export function from(value: TaskExecution, exitCode: number | undefined): TaskProcessEndedDTO { return { id: value.id, exitCode @@ -429,7 +429,7 @@ export class MainThreadTask implements MainThreadTaskShape { } else if (event.kind === TaskEventKind.ProcessStarted) { this._proxy.$onDidStartTaskProcess(TaskProcessStartedDTO.from(task.getTaskExecution(), event.processId!)); } else if (event.kind === TaskEventKind.ProcessEnded) { - this._proxy.$onDidEndTaskProcess(TaskProcessEndedDTO.from(task.getTaskExecution(), event.exitCode!)); + this._proxy.$onDidEndTaskProcess(TaskProcessEndedDTO.from(task.getTaskExecution(), event.exitCode)); } else if (event.kind === TaskEventKind.End) { this._proxy.$OnDidEndTask(TaskExecutionDTO.from(task.getTaskExecution())); } diff --git a/src/vs/workbench/api/browser/mainThreadTesting.ts b/src/vs/workbench/api/browser/mainThreadTesting.ts new file mode 100644 index 000000000..52185f8dd --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadTesting.ts @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { getTestSubscriptionKey, RunTestsRequest, RunTestsResult, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; +import { ITestService } from 'vs/workbench/contrib/testing/common/testService'; +import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { ExtHostContext, ExtHostTestingResource, ExtHostTestingShape, IExtHostContext, MainContext, MainThreadTestingShape } from '../common/extHost.protocol'; +import { URI, UriComponents } from 'vs/base/common/uri'; + +@extHostNamedCustomer(MainContext.MainThreadTesting) +export class MainThreadTesting extends Disposable implements MainThreadTestingShape { + private readonly proxy: ExtHostTestingShape; + private readonly testSubscriptions = new Map(); + + constructor( + extHostContext: IExtHostContext, + @ITestService private readonly testService: ITestService, + ) { + super(); + this.proxy = extHostContext.getProxy(ExtHostContext.ExtHostTesting); + this._register(this.testService.onShouldSubscribe(args => this.proxy.$subscribeToTests(args.resource, args.uri))); + this._register(this.testService.onShouldUnsubscribe(args => this.proxy.$unsubscribeFromTests(args.resource, args.uri))); + } + + /** + * @inheritdoc + */ + public $registerTestProvider(id: string) { + this.testService.registerTestController(id, { + runTests: req => this.proxy.$runTestsForProvider(req), + }); + } + + /** + * @inheritdoc + */ + public $unregisterTestProvider(id: string) { + this.testService.unregisterTestController(id); + } + + /** + * @inheritdoc + */ + $subscribeToDiffs(resource: ExtHostTestingResource, uriComponents: UriComponents): void { + const uri = URI.revive(uriComponents); + const disposable = this.testService.subscribeToDiffs(resource, uri, + diff => this.proxy.$acceptDiff(resource, uriComponents, diff)); + this.testSubscriptions.set(getTestSubscriptionKey(resource, uri), disposable); + } + + /** + * @inheritdoc + */ + public $unsubscribeFromDiffs(resource: ExtHostTestingResource, uriComponents: UriComponents): void { + const key = getTestSubscriptionKey(resource, URI.revive(uriComponents)); + this.testSubscriptions.get(key)?.dispose(); + this.testSubscriptions.delete(key); + } + + /** + * @inheritdoc + */ + public $publishDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void { + this.testService.publishDiff(resource, URI.revive(uri), diff); + } + + public $runTests(req: RunTestsRequest): Promise { + return this.testService.runTests(req); + } + + public dispose() { + // no-op + } +} diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index 7d0d71cc4..8e32acd7f 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -52,14 +52,14 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie }); } - $reveal(treeViewId: string, item: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Promise { - this.logService.trace('MainThreadTreeViews#$reveal', treeViewId, item, parentChain, options); + $reveal(treeViewId: string, itemInfo: { item: ITreeItem, parentChain: ITreeItem[] } | undefined, options: IRevealOptions): Promise { + this.logService.trace('MainThreadTreeViews#$reveal', treeViewId, itemInfo?.item, itemInfo?.parentChain, options); return this.viewsService.openView(treeViewId, options.focus) .then(() => { const viewer = this.getTreeView(treeViewId); - if (viewer) { - return this.reveal(viewer, this._dataProviders.get(treeViewId)!, item, parentChain, options); + if (viewer && itemInfo) { + return this.reveal(viewer, this._dataProviders.get(treeViewId)!, itemInfo.item, itemInfo.parentChain, options); } return undefined; }); @@ -235,10 +235,14 @@ class TreeViewDataProvider implements ITreeViewDataProvider { private updateTreeItem(current: ITreeItem, treeItem: ITreeItem): void { treeItem.children = treeItem.children ? treeItem.children : undefined; if (current) { - const properties = distinct([...Object.keys(current), ...Object.keys(treeItem)]); + const properties = distinct([...Object.keys(current instanceof ResolvableTreeItem ? current.asTreeItem() : current), + ...Object.keys(treeItem)]); for (const property of properties) { (current)[property] = (treeItem)[property]; } + if (current instanceof ResolvableTreeItem) { + current.resetResolve(); + } } } } diff --git a/src/vs/workbench/api/browser/mainThreadTunnelService.ts b/src/vs/workbench/api/browser/mainThreadTunnelService.ts index 2030fe217..ac9b83562 100644 --- a/src/vs/workbench/api/browser/mainThreadTunnelService.ts +++ b/src/vs/workbench/api/browser/mainThreadTunnelService.ts @@ -6,8 +6,8 @@ import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostContext, ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { IRemoteExplorerService, MakeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService'; -import { ITunnelProvider, ITunnelService, TunnelOptions } from 'vs/platform/remote/common/tunnel'; +import { IRemoteExplorerService, makeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { Disposable } from 'vs/base/common/lifecycle'; import type { TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver'; @@ -26,8 +26,8 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun this._register(tunnelService.onTunnelClosed(() => this._proxy.$onDidTunnelsChange())); } - async $openTunnel(tunnelOptions: TunnelOptions): Promise { - const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remoteAddress, tunnelOptions.localAddressPort, tunnelOptions.label); + async $openTunnel(tunnelOptions: TunnelOptions, source: string): Promise { + const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remoteAddress, tunnelOptions.localAddressPort, tunnelOptions.label, source); if (tunnel) { return TunnelDto.fromServiceTunnel(tunnel); } @@ -47,8 +47,8 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun }); } - async $registerCandidateFinder(): Promise { - this.remoteExplorerService.registerCandidateFinder(() => this._proxy.$findCandidatePorts()); + async $onFoundNewCandidates(candidates: { host: string, port: number, detail: string }[]): Promise { + this.remoteExplorerService.onFoundNewCandidates(candidates); } async $tunnelServiceReady(): Promise { @@ -57,19 +57,17 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun async $setTunnelProvider(): Promise { const tunnelProvider: ITunnelProvider = { - forwardPort: (tunnelOptions: TunnelOptions) => { - const forward = this._proxy.$forwardPort(tunnelOptions); + forwardPort: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => { + const forward = this._proxy.$forwardPort(tunnelOptions, tunnelCreationOptions); if (forward) { return forward.then(tunnel => { return { tunnelRemotePort: tunnel.remoteAddress.port, tunnelRemoteHost: tunnel.remoteAddress.host, - localAddress: typeof tunnel.localAddress === 'string' ? tunnel.localAddress : MakeAddress(tunnel.localAddress.host, tunnel.localAddress.port), + localAddress: typeof tunnel.localAddress === 'string' ? tunnel.localAddress : makeAddress(tunnel.localAddress.host, tunnel.localAddress.port), tunnelLocalPort: typeof tunnel.localAddress !== 'string' ? tunnel.localAddress.port : undefined, - dispose: (silent: boolean) => { - if (!silent) { - this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port }); - } + dispose: (silent?: boolean) => { + this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port }, silent); } }; }); @@ -80,22 +78,6 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun this.tunnelService.setTunnelProvider(tunnelProvider); } - async $setCandidateFilter(): Promise { - this._register(this.remoteExplorerService.setCandidateFilter(async (candidates: { host: string, port: number, detail: string }[]): Promise<{ host: string, port: number, detail: string }[]> => { - const filters: boolean[] = await this._proxy.$filterCandidates(candidates); - const filteredCandidates: { host: string, port: number, detail: string }[] = []; - if (filters.length !== candidates.length) { - return candidates; - } - for (let i = 0; i < candidates.length; i++) { - if (filters[i]) { - filteredCandidates.push(candidates[i]); - } - } - return filteredCandidates; - })); - } - dispose(): void { } diff --git a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts index 561e3c219..6428e6f48 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts @@ -9,8 +9,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { MainThreadWebviews, reviveWebviewExtension, reviveWebviewOptions } from 'vs/workbench/api/browser/mainThreadWebviews'; import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; -import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor'; -import { IEditorInput } from 'vs/workbench/common/editor'; +import { editorGroupToViewColumn, EditorGroupColumn, IEditorInput, viewColumnToEditorGroup } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { WebviewIcons } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInput'; @@ -150,7 +149,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc handle: extHostProtocol.WebviewHandle, viewType: string, title: string, - showOptions: { viewColumn?: EditorViewColumn, preserveFocus?: boolean; }, + showOptions: { viewColumn?: EditorGroupColumn, preserveFocus?: boolean; }, options: WebviewInputOptions ): void { const mainThreadShowOptions: ICreateWebViewShowOptions = Object.create(null); diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index d6a1d810a..6be78f4ec 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -8,8 +8,8 @@ import { forEach } from 'vs/base/common/collections'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as resources from 'vs/base/common/resources'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation } from 'vs/workbench/common/views'; -import { TreeViewPane } from 'vs/workbench/browser/parts/views/treeView'; +import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation, testViewIcon } from 'vs/workbench/common/views'; +import { CustomTreeView, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { coalesce, } from 'vs/base/common/arrays'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -30,9 +30,8 @@ import { IWorkbenchActionRegistry, Extensions as ActionExtensions, CATEGORIES } import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { Codicon } from 'vs/base/common/codicons'; -import { CustomTreeView } from 'vs/workbench/contrib/views/browser/treeView'; import { WebviewViewPane } from 'vs/workbench/contrib/webviewView/browser/webviewViewPane'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; export interface IUserFriendlyViewsContainerDescriptor { id: string; @@ -321,7 +320,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { private registerTestViewContainer(): void { const title = localize('test', "Test"); - const icon = Codicon.beaker.classNames; + const icon = testViewIcon; this.registerCustomViewContainer(TEST_VIEW_CONTAINER_ID, title, icon, TEST_VIEW_CONTAINER_ORDER, undefined, ViewContainerLocation.Sidebar); } @@ -356,7 +355,9 @@ class ViewsExtensionHandler implements IWorkbenchContribution { private registerCustomViewContainers(containers: IUserFriendlyViewsContainerDescriptor[], extension: IExtensionDescription, order: number, existingViewContainers: ViewContainer[], location: ViewContainerLocation): number { containers.forEach(descriptor => { - const icon = resources.joinPath(extension.extensionLocation, descriptor.icon); + const themeIcon = ThemeIcon.fromString(descriptor.icon); + + const icon = themeIcon || resources.joinPath(extension.extensionLocation, descriptor.icon); const id = `workbench.view.extension.${descriptor.id}`; const viewContainer = this.registerCustomViewContainer(id, descriptor.title, icon, order++, extension.identifier, location); @@ -376,7 +377,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { return order; } - private registerCustomViewContainer(id: string, title: string, icon: URI | string, order: number, extensionId: ExtensionIdentifier | undefined, location: ViewContainerLocation): ViewContainer { + private registerCustomViewContainer(id: string, title: string, icon: URI | ThemeIcon, order: number, extensionId: ExtensionIdentifier | undefined, location: ViewContainerLocation): ViewContainer { let viewContainer = this.viewContainersRegistry.get(id); if (!viewContainer) { @@ -470,7 +471,11 @@ class ViewsExtensionHandler implements IWorkbenchContribution { ? container.viewOrderDelegate.getOrder(item.group) : undefined; - const icon = item.icon ? resources.joinPath(extension.description.extensionLocation, item.icon) : undefined; + let icon: ThemeIcon | URI | undefined; + if (typeof item.icon === 'string') { + icon = ThemeIcon.fromString(item.icon) || resources.joinPath(extension.description.extensionLocation, item.icon); + } + const initialVisibility = this.convertInitialVisibility(item.visibility); const type = this.getViewType(item.type); diff --git a/src/vs/workbench/api/common/apiCommands.ts b/src/vs/workbench/api/common/apiCommands.ts index eaf1c90ec..7670549c6 100644 --- a/src/vs/workbench/api/common/apiCommands.ts +++ b/src/vs/workbench/api/common/apiCommands.ts @@ -3,20 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type * as vscode from 'vscode'; -import { URI } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { CommandsRegistry, ICommandService, ICommandHandler } from 'vs/platform/commands/common/commands'; -import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; -import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; -import { EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IOpenWindowOptions, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; -import { IWorkspacesService, hasWorkspaceFileExtension, IRecent } from 'vs/platform/workspaces/common/workspaces'; -import { Schemas } from 'vs/base/common/network'; +import { IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; +import { IWorkspacesService, IRecent } from 'vs/platform/workspaces/common/workspaces'; import { ILogService } from 'vs/platform/log/common/log'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IViewDescriptorService, IViewsService, ViewVisibilityState } from 'vs/workbench/common/views'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; // ----------------------------------------------------------------- // The following commands are registered on both sides separately. @@ -35,41 +31,6 @@ function adjustHandler(handler: (executor: ICommandsExecutor, ...args: any[]) => }; } -interface IOpenFolderAPICommandOptions { - forceNewWindow?: boolean; - forceReuseWindow?: boolean; - noRecentEntry?: boolean; -} - -export class OpenFolderAPICommand { - public static readonly ID = 'vscode.openFolder'; - public static execute(executor: ICommandsExecutor, uri?: URI, forceNewWindow?: boolean): Promise; - public static execute(executor: ICommandsExecutor, uri?: URI, options?: IOpenFolderAPICommandOptions): Promise; - public static execute(executor: ICommandsExecutor, uri?: URI, arg: boolean | IOpenFolderAPICommandOptions = {}): Promise { - if (typeof arg === 'boolean') { - arg = { forceNewWindow: arg }; - } - if (!uri) { - return executor.executeCommand('_files.pickFolderAndOpen', { forceNewWindow: arg.forceNewWindow }); - } - const options: IOpenWindowOptions = { forceNewWindow: arg.forceNewWindow, forceReuseWindow: arg.forceReuseWindow, noRecentEntry: arg.noRecentEntry }; - uri = URI.revive(uri); - const uriToOpen: IWindowOpenable = (hasWorkspaceFileExtension(uri) || uri.scheme === Schemas.untitled) ? { workspaceUri: uri } : { folderUri: uri }; - return executor.executeCommand('_files.windowOpen', [uriToOpen], options); - } -} -CommandsRegistry.registerCommand({ - id: OpenFolderAPICommand.ID, - handler: adjustHandler(OpenFolderAPICommand.execute), - description: { - description: 'Open a folder or workspace in the current window or new window depending on the newWindow argument. Note that opening in the same window will shutdown the current extension host process and start a new one on the given folder/workspace unless the newWindow parameter is set to true.', - args: [ - { name: 'uri', description: '(optional) Uri of the folder or workspace file to open. If not provided, a native dialog will ask the user for the folder', constraint: (value: any) => value === undefined || value instanceof URI }, - { name: 'options', description: '(optional) Options. Object with the following properties: `forceNewWindow `: Whether to open the folder/workspace in a new window or the same. Defaults to opening in the same window. `noRecentEntry`: Wheter the opened URI will appear in the \'Open Recent\' list. Defaults to true. Note, for backward compatibility, options can also be of type boolean, representing the `forceNewWindow` setting.', constraint: (value: any) => value === undefined || typeof value === 'object' || typeof value === 'boolean' } - ] - } -}); - interface INewWindowAPICommandOptions { reuseWindow?: boolean; remoteAuthority?: string; @@ -96,67 +57,6 @@ CommandsRegistry.registerCommand({ } }); -export class DiffAPICommand { - public static readonly ID = 'vscode.diff'; - public static execute(executor: ICommandsExecutor, left: URI, right: URI, label: string, options?: typeConverters.TextEditorOpenOptions): Promise { - return executor.executeCommand('_workbench.diff', [ - left, right, - label, - undefined, - typeConverters.TextEditorOpenOptions.from(options), - options ? typeConverters.ViewColumn.from(options.viewColumn) : undefined - ]); - } -} -CommandsRegistry.registerCommand(DiffAPICommand.ID, adjustHandler(DiffAPICommand.execute)); - -export class OpenAPICommand { - public static readonly ID = 'vscode.open'; - public static execute(executor: ICommandsExecutor, resource: URI, columnOrOptions?: vscode.ViewColumn | typeConverters.TextEditorOpenOptions, label?: string): Promise { - let options: ITextEditorOptions | undefined; - let position: EditorViewColumn | undefined; - - if (columnOrOptions) { - if (typeof columnOrOptions === 'number') { - position = typeConverters.ViewColumn.from(columnOrOptions); - } else { - options = typeConverters.TextEditorOpenOptions.from(columnOrOptions); - position = typeConverters.ViewColumn.from(columnOrOptions.viewColumn); - } - } - - return executor.executeCommand('_workbench.open', [ - resource, - options, - position, - label - ]); - } -} -CommandsRegistry.registerCommand(OpenAPICommand.ID, adjustHandler(OpenAPICommand.execute)); - -export class OpenWithAPICommand { - public static readonly ID = 'vscode.openWith'; - public static execute(executor: ICommandsExecutor, resource: URI, viewType: string, columnOrOptions?: vscode.ViewColumn | typeConverters.TextEditorOpenOptions): Promise { - let options: ITextEditorOptions | undefined; - let position: EditorViewColumn | undefined; - - if (typeof columnOrOptions === 'number') { - position = typeConverters.ViewColumn.from(columnOrOptions); - } else if (typeof columnOrOptions !== 'undefined') { - options = typeConverters.TextEditorOpenOptions.from(columnOrOptions); - } - - return executor.executeCommand('_workbench.openWith', [ - resource, - viewType, - options, - position - ]); - } -} -CommandsRegistry.registerCommand(OpenWithAPICommand.ID, adjustHandler(OpenWithAPICommand.execute)); - CommandsRegistry.registerCommand('_workbench.removeFromRecentlyOpened', function (accessor: ServicesAccessor, uri: URI) { const workspacesService = accessor.get(IWorkspacesService); return workspacesService.removeRecentlyOpened([uri]); @@ -219,38 +119,6 @@ CommandsRegistry.registerCommand('_workbench.getRecentlyOpened', async function return workspacesService.getRecentlyOpened(); }); -export class SetEditorLayoutAPICommand { - public static readonly ID = 'vscode.setEditorLayout'; - public static execute(executor: ICommandsExecutor, layout: EditorGroupLayout): Promise { - return executor.executeCommand('layoutEditorGroups', layout); - } -} -CommandsRegistry.registerCommand({ - id: SetEditorLayoutAPICommand.ID, - handler: adjustHandler(SetEditorLayoutAPICommand.execute), - description: { - description: 'Set Editor Layout', - args: [{ - name: 'args', - schema: { - 'type': 'object', - 'required': ['groups'], - 'properties': { - 'orientation': { - 'type': 'number', - 'default': 0, - 'enum': [0, 1] - }, - 'groups': { - '$ref': '#/definitions/editorGroupsSchema', // defined in keybindingService.ts ... - 'default': [{}, {}], - } - } - } - }] - } -}); - CommandsRegistry.registerCommand('_extensionTests.setLogLevel', function (accessor: ServicesAccessor, level: number) { const logService = accessor.get(ILogService); const environmentService = accessor.get(IEnvironmentService); @@ -260,6 +128,13 @@ CommandsRegistry.registerCommand('_extensionTests.setLogLevel', function (access } }); +CommandsRegistry.registerCommand('_workbench.openExternal', function (accessor: ServicesAccessor, uri: UriComponents, options: { allowTunneling?: boolean }) { + // TODO: discuss martin, ben where to put this + const openerService = accessor.get(IOpenerService); + openerService.open(URI.revive(uri), options); +}); + + CommandsRegistry.registerCommand('_extensionTests.getLogLevel', function (accessor: ServicesAccessor) { const logService = accessor.get(ILogService); @@ -304,3 +179,31 @@ CommandsRegistry.registerCommand({ args: [] } }); + + +// ----------------------------------------------------------------- +// The following commands are registered on the renderer but as API +// command. DO NOT USE this unless you have understood what this +// means +// ----------------------------------------------------------------- + + +class OpenAPICommand { + public static readonly ID = 'vscode.open'; + public static execute(executor: ICommandsExecutor, resource: URI): Promise { + + return executor.executeCommand('_workbench.open', resource); + } +} +CommandsRegistry.registerCommand(OpenAPICommand.ID, adjustHandler(OpenAPICommand.execute)); + +class DiffAPICommand { + public static readonly ID = 'vscode.diff'; + public static execute(executor: ICommandsExecutor, left: URI, right: URI, label: string, options?: typeConverters.TextEditorOpenOptions): Promise { + return executor.executeCommand('_workbench.diff', [ + left, right, + label, + ]); + } +} +CommandsRegistry.registerCommand(DiffAPICommand.ID, adjustHandler(DiffAPICommand.execute)); diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index 83c0ab29e..aab5275fd 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -28,6 +28,10 @@ const configurationEntrySchema: IJSONSchema = { properties: { description: nls.localize('vscode.extension.contributes.configuration.properties', 'Description of the configuration properties.'), type: 'object', + propertyNames: { + pattern: '\\S+', + patternErrorMessage: nls.localize('vscode.extension.contributes.configuration.property.empty', 'Property should not be empty.'), + }, additionalProperties: { anyOf: [ { $ref: 'http://json-schema.org/draft-07/schema#' }, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 284c6aff8..fa880fcfc 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -51,7 +51,7 @@ import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { throwProposedApiError, checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { originalFSPath } from 'vs/base/common/resources'; import { values } from 'vs/base/common/collections'; @@ -81,6 +81,7 @@ import { ExtHostCustomEditors } from 'vs/workbench/api/common/extHostCustomEdito import { ExtHostWebviewPanels } from 'vs/workbench/api/common/extHostWebviewPanels'; import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits'; import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; +import { ExtHostTesting } from 'vs/workbench/api/common/extHostTesting'; export interface IExtensionApiFactory { (extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode; @@ -152,6 +153,7 @@ 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, extHostDocumentsAndEditors, extHostWorkspace)); // Check that no named customers are missing const expected: ProxyIdentifier[] = values(ExtHostContext); @@ -201,40 +203,50 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I })(); const authentication: typeof vscode.authentication = { - registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable { - return extHostAuthentication.registerAuthenticationProvider(provider); - }, - get onDidChangeAuthenticationProviders(): Event { - return extHostAuthentication.onDidChangeAuthenticationProviders; - }, - getProviderIds(): Thenable> { - return extHostAuthentication.getProviderIds(); - }, - get providerIds(): string[] { - return extHostAuthentication.providerIds; - }, - get providers(): ReadonlyArray { - return extHostAuthentication.providers; - }, getSession(providerId: string, scopes: string[], options?: vscode.AuthenticationGetSessionOptions) { return extHostAuthentication.getSession(extension, providerId, scopes, options as any); }, - logout(providerId: string, sessionId: string): Thenable { - return extHostAuthentication.logout(providerId, sessionId); - }, get onDidChangeSessions(): Event { return extHostAuthentication.onDidChangeSessions; }, + registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable { + checkProposedApiEnabled(extension); + return extHostAuthentication.registerAuthenticationProvider(provider); + }, + get onDidChangeAuthenticationProviders(): Event { + checkProposedApiEnabled(extension); + return extHostAuthentication.onDidChangeAuthenticationProviders; + }, + getProviderIds(): Thenable> { + checkProposedApiEnabled(extension); + return extHostAuthentication.getProviderIds(); + }, + get providerIds(): string[] { + checkProposedApiEnabled(extension); + return extHostAuthentication.providerIds; + }, + get providers(): ReadonlyArray { + checkProposedApiEnabled(extension); + return extHostAuthentication.providers; + }, + logout(providerId: string, sessionId: string): Thenable { + checkProposedApiEnabled(extension); + return extHostAuthentication.logout(providerId, sessionId); + }, getPassword(key: string): Thenable { + checkProposedApiEnabled(extension); return extHostAuthentication.getPassword(extension, key); }, setPassword(key: string, value: string): Thenable { + checkProposedApiEnabled(extension); return extHostAuthentication.setPassword(extension, key, value); }, deletePassword(key: string): Thenable { + checkProposedApiEnabled(extension); return extHostAuthentication.deletePassword(extension, key); }, get onDidChangePassword(): Event { + checkProposedApiEnabled(extension); return extHostAuthentication.onDidChangePassword; } }; @@ -293,14 +305,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I get appName() { return initData.environment.appName; }, get appRoot() { return initData.environment.appRoot?.fsPath ?? ''; }, get uriScheme() { return initData.environment.appUriScheme; }, - get logLevel() { - checkProposedApiEnabled(extension); - return typeConverters.LogLevel.to(extHostLogService.getLevel()); - }, - get onDidChangeLogLevel() { - checkProposedApiEnabled(extension); - return Event.map(extHostLogService.onDidChangeLogLevel, l => typeConverters.LogLevel.to(l)); - }, get clipboard(): vscode.Clipboard { return extHostClipboard; }, @@ -333,6 +337,25 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ? extHostTypes.ExtensionKind.Workspace : extHostTypes.ExtensionKind.UI; + const test: typeof vscode.test = { + registerTestProvider(provider) { + checkProposedApiEnabled(extension); + return extHostTesting.registerTestProvider(provider); + }, + createDocumentTestObserver(document) { + checkProposedApiEnabled(extension); + return extHostTesting.createTextDocumentTestObserver(document); + }, + createWorkspaceTestObserver(workspaceFolder) { + checkProposedApiEnabled(extension); + return extHostTesting.createWorkspaceTestObserver(workspaceFolder); + }, + runTests(provider) { + checkProposedApiEnabled(extension); + return extHostTesting.runTests(provider); + }, + }; + // namespace: extensions const extensions: typeof vscode.extensions = { getExtension(extensionId: string): Extension | undefined { @@ -397,9 +420,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerDocumentHighlightProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable { return extHostLanguageFeatures.registerDocumentHighlightProvider(extension, checkSelector(selector), provider); }, - registerOnTypeRenameProvider(selector: vscode.DocumentSelector, provider: vscode.OnTypeRenameProvider, stopPattern?: RegExp): vscode.Disposable { - checkProposedApiEnabled(extension); - return extHostLanguageFeatures.registerOnTypeRenameProvider(extension, checkSelector(selector), provider, stopPattern); + registerLinkedEditingRangeProvider(selector: vscode.DocumentSelector, provider: vscode.LinkedEditingRangeProvider): vscode.Disposable { + return extHostLanguageFeatures.registerLinkedEditingRangeProvider(extension, checkSelector(selector), provider); }, registerReferenceProvider(selector: vscode.DocumentSelector, provider: vscode.ReferenceProvider): vscode.Disposable { return extHostLanguageFeatures.registerReferenceProvider(extension, checkSelector(selector), provider); @@ -569,7 +591,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I priority = priority; } - return extHostStatusBar.createStatusBarEntry(id, name, alignment, priority, accessibilityInformation); + return extHostStatusBar.createStatusBarEntry(id, name, alignment, priority, accessibilityInformation, extension); }, setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable): vscode.Disposable { return extHostStatusBar.setStatusBarMessage(text, timeoutOrThenable); @@ -617,9 +639,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerCustomEditorProvider: (viewType: string, provider: vscode.CustomTextEditorProvider | vscode.CustomReadonlyEditorProvider, options: { webviewOptions?: vscode.WebviewPanelOptions, supportsMultipleEditorsPerDocument?: boolean } = {}) => { return extHostCustomEditors.registerCustomEditorProvider(extension, viewType, provider, options); }, - registerDecorationProvider(provider: vscode.FileDecorationProvider) { - checkProposedApiEnabled(extension); - return extHostDecorations.registerDecorationProvider(provider, extension.identifier); + registerFileDecorationProvider(provider: vscode.FileDecorationProvider) { + return extHostDecorations.registerFileDecorationProvider(provider, extension.identifier); }, registerUriHandler(handler: vscode.UriHandler) { return extHostUrls.registerUriHandler(extension.identifier, handler); @@ -667,6 +688,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostNotebook.onDidChangeNotebookEditorVisibleRanges(listener, thisArgs, disposables); }, + showNotebookDocument(document, options?) { + checkProposedApiEnabled(extension); + return extHostNotebook.showNotebookDocument(document, options); + } }; // namespace: workspace @@ -835,7 +860,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, openTunnel: (forward: vscode.TunnelOptions) => { checkProposedApiEnabled(extension); - return extHostTunnelService.openTunnel(forward).then(value => { + return extHostTunnelService.openTunnel(extension, forward).then(value => { if (!value) { throw new Error('cannot open tunnel'); } @@ -870,14 +895,13 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I } }; - const comment: typeof vscode.comments = { + // namespace: comments + const comments: typeof vscode.comments = { createCommentController(id: string, label: string) { return extHostComment.createCommentController(extension, id, label); } }; - const comments = comment; - // namespace: debug const debug: typeof vscode.debug = { get activeDebugSession() { @@ -1014,6 +1038,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostNotebook.registerNotebookKernelProvider(extension, selector, provider); }, createNotebookEditorDecorationType(options: vscode.NotebookDecorationRenderOptions): vscode.NotebookEditorDecorationType { + checkProposedApiEnabled(extension); return extHostNotebook.createNotebookEditorDecorationType(options); }, get activeNotebookEditor(): vscode.NotebookEditor | undefined { @@ -1067,40 +1092,46 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // namespaces authentication, commands, + comments, debug, env, extensions, languages, - scm, - comment, - comments, - tasks, notebook, + scm, + tasks, + test, window, workspace, // types Breakpoint: extHostTypes.Breakpoint, + CallHierarchyIncomingCall: extHostTypes.CallHierarchyIncomingCall, + CallHierarchyItem: extHostTypes.CallHierarchyItem, + CallHierarchyOutgoingCall: extHostTypes.CallHierarchyOutgoingCall, CancellationTokenSource: CancellationTokenSource, CodeAction: extHostTypes.CodeAction, CodeActionKind: extHostTypes.CodeActionKind, CodeActionTrigger: extHostTypes.CodeActionTrigger, CodeLens: extHostTypes.CodeLens, - CodeInset: extHostTypes.CodeInset, Color: extHostTypes.Color, ColorInformation: extHostTypes.ColorInformation, ColorPresentation: extHostTypes.ColorPresentation, - CommentThreadCollapsibleState: extHostTypes.CommentThreadCollapsibleState, + ColorThemeKind: extHostTypes.ColorThemeKind, CommentMode: extHostTypes.CommentMode, + CommentThreadCollapsibleState: extHostTypes.CommentThreadCollapsibleState, CompletionItem: extHostTypes.CompletionItem, CompletionItemKind: extHostTypes.CompletionItemKind, CompletionItemTag: extHostTypes.CompletionItemTag, CompletionList: extHostTypes.CompletionList, CompletionTriggerKind: extHostTypes.CompletionTriggerKind, ConfigurationTarget: extHostTypes.ConfigurationTarget, + CustomExecution: extHostTypes.CustomExecution, DebugAdapterExecutable: extHostTypes.DebugAdapterExecutable, - DebugAdapterServer: extHostTypes.DebugAdapterServer, - DebugAdapterNamedPipeServer: extHostTypes.DebugAdapterNamedPipeServer, DebugAdapterInlineImplementation: extHostTypes.DebugAdapterInlineImplementation, + DebugAdapterNamedPipeServer: extHostTypes.DebugAdapterNamedPipeServer, + DebugAdapterServer: extHostTypes.DebugAdapterServer, + DebugConfigurationProviderTriggerKind: extHostTypes.DebugConfigurationProviderTriggerKind, + DebugConsoleMode: extHostTypes.DebugConsoleMode, DecorationRangeBehavior: extHostTypes.DecorationRangeBehavior, Diagnostic: extHostTypes.Diagnostic, DiagnosticRelatedInformation: extHostTypes.DiagnosticRelatedInformation, @@ -1117,9 +1148,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I EventEmitter: Emitter, ExtensionKind: extHostTypes.ExtensionKind, ExtensionMode: extHostTypes.ExtensionMode, - ExtensionRuntime: extHostTypes.ExtensionRuntime, - CustomExecution: extHostTypes.CustomExecution, FileChangeType: extHostTypes.FileChangeType, + FileDecoration: extHostTypes.FileDecoration, FileSystemError: extHostTypes.FileSystemError, FileType: files.FileType, FoldingRange: extHostTypes.FoldingRange, @@ -1128,7 +1158,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I Hover: extHostTypes.Hover, IndentAction: languageConfiguration.IndentAction, Location: extHostTypes.Location, - LogLevel: extHostTypes.LogLevel, MarkdownString: extHostTypes.MarkdownString, OverviewRulerLane: OverviewRulerLane, ParameterInformation: extHostTypes.ParameterInformation, @@ -1138,23 +1167,20 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I QuickInputButtons: extHostTypes.QuickInputButtons, Range: extHostTypes.Range, RelativePattern: extHostTypes.RelativePattern, - ResolvedAuthority: extHostTypes.ResolvedAuthority, - RemoteAuthorityResolverError: extHostTypes.RemoteAuthorityResolverError, - SemanticTokensLegend: extHostTypes.SemanticTokensLegend, - SemanticTokensBuilder: extHostTypes.SemanticTokensBuilder, - SemanticTokens: extHostTypes.SemanticTokens, - SemanticTokensEdits: extHostTypes.SemanticTokensEdits, - SemanticTokensEdit: extHostTypes.SemanticTokensEdit, Selection: extHostTypes.Selection, SelectionRange: extHostTypes.SelectionRange, + SemanticTokens: extHostTypes.SemanticTokens, + SemanticTokensBuilder: extHostTypes.SemanticTokensBuilder, + SemanticTokensEdit: extHostTypes.SemanticTokensEdit, + SemanticTokensEdits: extHostTypes.SemanticTokensEdits, + SemanticTokensLegend: extHostTypes.SemanticTokensLegend, ShellExecution: extHostTypes.ShellExecution, ShellQuoting: extHostTypes.ShellQuoting, - SignatureHelpTriggerKind: extHostTypes.SignatureHelpTriggerKind, SignatureHelp: extHostTypes.SignatureHelp, + SignatureHelpTriggerKind: extHostTypes.SignatureHelpTriggerKind, SignatureInformation: extHostTypes.SignatureInformation, SnippetString: extHostTypes.SnippetString, SourceBreakpoint: extHostTypes.SourceBreakpoint, - SourceControlInputBoxValidationType: extHostTypes.SourceControlInputBoxValidationType, StandardTokenType: extHostTypes.StandardTokenType, StatusBarAlignment: extHostTypes.StatusBarAlignment, SymbolInformation: extHostTypes.SymbolInformation, @@ -1174,29 +1200,80 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ThemeColor: extHostTypes.ThemeColor, ThemeIcon: extHostTypes.ThemeIcon, TreeItem: extHostTypes.TreeItem, - TreeItem2: extHostTypes.TreeItem, TreeItemCollapsibleState: extHostTypes.TreeItemCollapsibleState, + UIKind: UIKind, Uri: URI, ViewColumn: extHostTypes.ViewColumn, WorkspaceEdit: extHostTypes.WorkspaceEdit, - // proposed - CallHierarchyOutgoingCall: extHostTypes.CallHierarchyOutgoingCall, - CallHierarchyIncomingCall: extHostTypes.CallHierarchyIncomingCall, - CallHierarchyItem: extHostTypes.CallHierarchyItem, - DebugConsoleMode: extHostTypes.DebugConsoleMode, - DebugConfigurationProviderTriggerKind: extHostTypes.DebugConfigurationProviderTriggerKind, - FileDecoration: extHostTypes.FileDecoration, - UIKind: UIKind, - ColorThemeKind: extHostTypes.ColorThemeKind, - TimelineItem: extHostTypes.TimelineItem, - CellKind: extHostTypes.CellKind, - CellOutputKind: extHostTypes.CellOutputKind, - NotebookCellRunState: extHostTypes.NotebookCellRunState, - NotebookRunState: extHostTypes.NotebookRunState, - NotebookCellStatusBarAlignment: extHostTypes.NotebookCellStatusBarAlignment, - NotebookEditorRevealType: extHostTypes.NotebookEditorRevealType, - NotebookCellOutput: extHostTypes.NotebookCellOutput, - NotebookCellOutputItem: extHostTypes.NotebookCellOutputItem, + // proposed api types + get RemoteAuthorityResolverError() { + // checkProposedApiEnabled(extension); + return extHostTypes.RemoteAuthorityResolverError; + }, + get ResolvedAuthority() { + // checkProposedApiEnabled(extension); + return extHostTypes.ResolvedAuthority; + }, + get SourceControlInputBoxValidationType() { + // checkProposedApiEnabled(extension); + return extHostTypes.SourceControlInputBoxValidationType; + }, + get ExtensionRuntime() { + // checkProposedApiEnabled(extension); + return extHostTypes.ExtensionRuntime; + }, + get TimelineItem() { + // checkProposedApiEnabled(extension); + return extHostTypes.TimelineItem; + }, + get CellKind() { + // checkProposedApiEnabled(extension); + return extHostTypes.CellKind; + }, + get CellOutputKind() { + // checkProposedApiEnabled(extension); + return extHostTypes.CellOutputKind; + }, + get NotebookCellRunState() { + // checkProposedApiEnabled(extension); + return extHostTypes.NotebookCellRunState; + }, + get NotebookRunState() { + // checkProposedApiEnabled(extension); + return extHostTypes.NotebookRunState; + }, + get NotebookCellStatusBarAlignment() { + // checkProposedApiEnabled(extension); + return extHostTypes.NotebookCellStatusBarAlignment; + }, + get NotebookEditorRevealType() { + // checkProposedApiEnabled(extension); + return extHostTypes.NotebookEditorRevealType; + }, + get NotebookCellOutput() { + // checkProposedApiEnabled(extension); + return extHostTypes.NotebookCellOutput; + }, + get NotebookCellOutputItem() { + // checkProposedApiEnabled(extension); + return extHostTypes.NotebookCellOutputItem; + }, + get LinkedEditingRanges() { + // checkProposedApiEnabled(extension); + return extHostTypes.LinkedEditingRanges; + }, + get TestRunState() { + // checkProposedApiEnabled(extension); + return extHostTypes.TestRunState; + }, + get TestMessageSeverity() { + // checkProposedApiEnabled(extension); + return extHostTypes.TestMessageSeverity; + }, + get TestState() { + // checkProposedApiEnabled(extension); + return extHostTypes.TestState; + }, }; }; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 77ef65778..b97a962f6 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -36,7 +36,6 @@ import * as statusbar from 'vs/workbench/services/statusbar/common/statusbar'; import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings'; import { ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; -import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import * as tasks from 'vs/workbench/api/common/shared/tasks'; import { IRevealOptions, ITreeItem } from 'vs/workbench/common/views'; import { IAdapterDescriptor, IConfig, IDebugSessionReplMode } from 'vs/workbench/contrib/debug/common/debug'; @@ -45,10 +44,10 @@ import { ITerminalDimensions, IShellLaunchConfig, ITerminalLaunchError } from 'v import { ActivationKind, ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions'; import { createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId, IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import * as search from 'vs/workbench/services/search/common/search'; -import { SaveReason } from 'vs/workbench/common/editor'; +import { EditorGroupColumn, SaveReason } from 'vs/workbench/common/editor'; import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; -import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; +import { TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline'; import { revive } from 'vs/base/common/marshalling'; import { IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, INotebookKernelInfoDto2, TransientMetadata, INotebookCellStatusBarEntry, ICellRange, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -57,7 +56,8 @@ import { Dto } from 'vs/base/common/types'; import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/api/common/extHostTypes'; import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility'; -import { IExtensionIdWithVersion } from 'vs/platform/userDataSync/common/storageKeys'; +import { IExtensionIdWithVersion } from 'vs/platform/userDataSync/common/extensionsStorageSync'; +import { RunTestForProviderRequest, RunTestsRequest, RunTestsResult, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; @@ -263,21 +263,21 @@ export interface IApplyEditsOptions extends IUndoStopOptions { } export interface ITextDocumentShowOptions { - position?: EditorViewColumn; + position?: EditorGroupColumn; preserveFocus?: boolean; pinned?: boolean; selection?: IRange; } export interface MainThreadBulkEditsShape extends IDisposable { - $tryApplyWorkspaceEdit(workspaceEditDto: IWorkspaceEditDto): Promise; + $tryApplyWorkspaceEdit(workspaceEditDto: IWorkspaceEditDto, undoRedoGroupId?: number): Promise; } export interface MainThreadTextEditorsShape extends IDisposable { $tryShowTextDocument(resource: UriComponents, options: ITextDocumentShowOptions): Promise; $registerTextEditorDecorationType(key: string, options: editorCommon.IDecorationRenderOptions): void; $removeTextEditorDecorationType(key: string): void; - $tryShowEditor(id: string, position: EditorViewColumn): Promise; + $tryShowEditor(id: string, position: EditorGroupColumn): Promise; $tryHideEditor(id: string): Promise; $trySetOptions(id: string, options: ITextEditorConfigurationUpdate): Promise; $trySetDecorations(id: string, key: string, ranges: editorCommon.IDecorationOptions[]): Promise; @@ -292,7 +292,7 @@ export interface MainThreadTextEditorsShape extends IDisposable { export interface MainThreadTreeViewsShape extends IDisposable { $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean; }): void; $refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem; }): Promise; - $reveal(treeViewId: string, treeItem: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Promise; + $reveal(treeViewId: string, itemInfo: { item: ITreeItem, parentChain: ITreeItem[] } | undefined, options: IRevealOptions): Promise; $setMessage(treeViewId: string, message: string): void; $setTitle(treeViewId: string, title: string, description: string | undefined): void; } @@ -384,7 +384,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerHoverProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerEvaluatableExpressionProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void; - $registerOnTypeRenameProvider(handle: number, selector: IDocumentFilterDto[], stopPattern: IRegExpDto | undefined): void; + $registerLinkedEditingRangeProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void; $registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto, displayName: string, supportsResolve: boolean): void; $registerDocumentFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void; @@ -565,7 +565,7 @@ export interface MainThreadQuickOpenShape extends IDisposable { } export interface MainThreadStatusBarShape extends IDisposable { - $setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: ICommandDto | undefined, color: string | ThemeColor | undefined, alignment: statusbar.StatusbarAlignment, priority: number | undefined, accessibilityInformation: IAccessibilityInformation | undefined): void; + $setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: ICommandDto | undefined, color: string | ThemeColor | undefined, backgroundColor: string | ThemeColor | undefined, alignment: statusbar.StatusbarAlignment, priority: number | undefined, accessibilityInformation: IAccessibilityInformation | undefined): void; $dispose(id: number): void; } @@ -597,7 +597,7 @@ export interface ExtHostEditorInsetsShape { export type WebviewHandle = string; export interface WebviewPanelShowOptions { - readonly viewColumn?: EditorViewColumn; + readonly viewColumn?: EditorGroupColumn; readonly preserveFocus?: boolean; } @@ -661,7 +661,7 @@ export interface WebviewPanelViewStateData { [handle: string]: { readonly active: boolean; readonly visible: boolean; - readonly position: EditorViewColumn; + readonly position: EditorGroupColumn; }; } @@ -673,11 +673,11 @@ export interface ExtHostWebviewsShape { export interface ExtHostWebviewPanelsShape { $onDidChangeWebviewPanelViewStates(newState: WebviewPanelViewStateData): void; $onDidDisposeWebviewPanel(handle: WebviewHandle): Promise; - $deserializeWebviewPanel(newWebviewHandle: WebviewHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; + $deserializeWebviewPanel(newWebviewHandle: WebviewHandle, viewType: string, title: string, state: any, position: EditorGroupColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; } export interface ExtHostCustomEditorsShape { - $resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions, cancellation: CancellationToken): Promise; + $resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewHandle, viewType: string, title: string, position: EditorGroupColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions, cancellation: CancellationToken): Promise; $createCustomDocument(resource: UriComponents, viewType: string, backupId: string | undefined, cancellation: CancellationToken): Promise<{ editable: boolean }>; $disposeCustomDocument(resource: UriComponents, viewType: string): Promise; @@ -741,6 +741,13 @@ export enum NotebookEditorRevealType { InCenterIfOutsideViewport = 2, } +export interface INotebookDocumentShowOptions { + position?: EditorGroupColumn; + preserveFocus?: boolean; + pinned?: boolean; + selection?: ICellRange; +} + export type INotebookCellStatusBarEntryDto = Dto; export interface MainThreadNotebookShape extends IDisposable { @@ -760,6 +767,7 @@ export interface MainThreadNotebookShape extends IDisposable { $postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise; $setStatusBarEntry(id: number, statusBarEntry: INotebookCellStatusBarEntryDto): Promise; $tryOpenDocument(uriComponents: UriComponents, viewType?: string): Promise; + $tryShowNotebookDocument(uriComponents: UriComponents, viewType: string, options: INotebookDocumentShowOptions): Promise; $tryRevealRange(id: string, range: ICellRange, revealType: NotebookEditorRevealType): Promise; $registerNotebookEditorDecorationType(key: string, options: INotebookDecorationRenderOptions): void; $removeNotebookEditorDecorationType(key: string): void; @@ -866,7 +874,8 @@ export type SCMRawResource = [ string /*tooltip*/, boolean /*strike through*/, boolean /*faded*/, - string /*context value*/ + string /*context value*/, + ICommandDto | undefined /*command*/ ]; export type SCMRawResourceSplice = [ @@ -920,7 +929,7 @@ export interface MainThreadDebugServiceShape extends IDisposable { $acceptDAMessage(handle: number, message: DebugProtocol.ProtocolMessage): void; $acceptDAError(handle: number, name: string, message: string, stack: string | undefined): void; $acceptDAExit(handle: number, code: number | undefined, signal: string | undefined): void; - $registerDebugConfigurationProvider(type: string, triggerKind: DebugConfigurationProviderTriggerKind, hasProvideMethod: boolean, hasResolveMethod: boolean, hasResolve2Method: boolean, hasProvideDaMethod: boolean, handle: number): Promise; + $registerDebugConfigurationProvider(type: string, triggerKind: DebugConfigurationProviderTriggerKind, hasProvideMethod: boolean, hasResolveMethod: boolean, hasResolve2Method: boolean, handle: number): Promise; $registerDebugAdapterDescriptorFactory(type: string, handle: number): Promise; $unregisterDebugConfigurationProvider(handle: number): void; $unregisterDebugAdapterDescriptorFactory(handle: number): void; @@ -946,13 +955,12 @@ export interface MainThreadWindowShape extends IDisposable { } export interface MainThreadTunnelServiceShape extends IDisposable { - $openTunnel(tunnelOptions: TunnelOptions): Promise; + $openTunnel(tunnelOptions: TunnelOptions, source: string | undefined): Promise; $closeTunnel(remote: { host: string, port: number }): Promise; $getTunnels(): Promise; - $registerCandidateFinder(): Promise; $setTunnelProvider(): Promise; - $setCandidateFilter(): Promise; $tunnelServiceReady(): Promise; + $onFoundNewCandidates(candidates: { host: string, port: number, detail: string }[]): Promise; } export interface MainThreadTimelineShape extends IDisposable { @@ -1006,10 +1014,10 @@ export interface ITextEditorAddData { options: IResolvedTextEditorConfiguration; selections: ISelection[]; visibleRanges: IRange[]; - editorPosition: EditorViewColumn | undefined; + editorPosition: EditorGroupColumn | undefined; } export interface ITextEditorPositionData { - [id: string]: EditorViewColumn; + [id: string]: EditorGroupColumn; } export interface IEditorPropertiesChangeData { options: IResolvedTextEditorConfiguration | null; @@ -1139,7 +1147,7 @@ export interface SourceTargetPair { export interface ExtHostFileSystemEventServiceShape { $onFileEvent(events: FileSystemEvents): void; - $onWillRunFileOperation(operation: files.FileOperation, files: SourceTargetPair[], timeout: number, token: CancellationToken): Promise; + $onWillRunFileOperation(operation: files.FileOperation, files: SourceTargetPair[], undoRedoGroupId: number | undefined, timeout: number, token: CancellationToken): Promise; $onDidRunFileOperation(operation: files.FileOperation, files: SourceTargetPair[]): void; } @@ -1307,7 +1315,7 @@ export interface IWorkspaceCellEditDto { export interface IWorkspaceEditDto { edits: Array; - // todo@joh reject should go into rename + // todo@jrieken reject should go into rename rejectReason?: string; } @@ -1395,6 +1403,11 @@ export interface ILanguageWordDefinitionDto { regexFlags: string } +export interface ILinkedEditingRangesDto { + ranges: IRange[]; + wordPattern?: IRegExpDto; +} + export interface ExtHostLanguageFeaturesShape { $provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise; $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise; @@ -1407,7 +1420,7 @@ export interface ExtHostLanguageFeaturesShape { $provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideEvaluatableExpression(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; - $provideOnTypeRenameRanges(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<{ ranges: IRange[]; wordPattern?: IRegExpDto; } | undefined>; + $provideLinkedEditingRanges(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise; $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise; $resolveCodeAction(handle: number, id: ChainedCacheId, token: CancellationToken): Promise; @@ -1602,7 +1615,6 @@ export interface ExtHostDebugServiceShape { $resolveDebugConfiguration(handle: number, folder: UriComponents | undefined, debugConfiguration: IConfig, token: CancellationToken): Promise; $resolveDebugConfigurationWithSubstitutedVariables(handle: number, folder: UriComponents | undefined, debugConfiguration: IConfig, token: CancellationToken): Promise; $provideDebugConfigurations(handle: number, folder: UriComponents | undefined, token: CancellationToken): Promise; - $legacyDebugAdapterExecutable(handle: number, folderUri: UriComponents | undefined): Promise; // TODO@AW legacy $provideDebugAdapter(handle: number, session: IDebugSessionDto): Promise; $acceptDebugSessionStarted(session: IDebugSessionDto): void; $acceptDebugSessionTerminated(session: IDebugSessionDto): void; @@ -1709,7 +1721,6 @@ export interface ExtHostNotebookShape { $resolveNotebookKernel(handle: number, editorId: string, uri: UriComponents, kernelId: string, token: CancellationToken): Promise; $executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise; $cancelNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise; - $executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise; $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise; $saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise; $backup(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise; @@ -1738,10 +1749,8 @@ export interface MainThreadThemingShape extends IDisposable { } export interface ExtHostTunnelServiceShape { - $findCandidatePorts(): Promise<{ host: string, port: number, detail: string }[]>; - $filterCandidates(candidates: { host: string, port: number, detail: string }[]): Promise; - $forwardPort(tunnelOptions: TunnelOptions): Promise | undefined; - $closeTunnel(remote: { host: string, port: number }): Promise; + $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise | undefined; + $closeTunnel(remote: { host: string, port: number }, silent?: boolean): Promise; $onDidTunnelsChange(): Promise; } @@ -1749,6 +1758,28 @@ export interface ExtHostTimelineShape { $getTimeline(source: string, uri: UriComponents, options: TimelineOptions, token: CancellationToken, internalOptions?: InternalTimelineOptions): Promise; } +export const enum ExtHostTestingResource { + Workspace, + TextDocument +} + +export interface ExtHostTestingShape { + $runTestsForProvider(req: RunTestForProviderRequest): Promise; + $subscribeToTests(resource: ExtHostTestingResource, uri: UriComponents): void; + $unsubscribeFromTests(resource: ExtHostTestingResource, uri: UriComponents): void; + + $acceptDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void; +} + +export interface MainThreadTestingShape { + $registerTestProvider(id: string): void; + $unregisterTestProvider(id: string): void; + $subscribeToDiffs(resource: ExtHostTestingResource, uri: UriComponents): void; + $unsubscribeFromDiffs(resource: ExtHostTestingResource, uri: UriComponents): void; + $publishDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void; + $runTests(req: RunTestsRequest): Promise; +} + // --- proxy identifiers export const MainContext = { @@ -1798,7 +1829,8 @@ export const MainContext = { MainThreadNotebook: createMainId('MainThreadNotebook'), MainThreadTheming: createMainId('MainThreadTheming'), MainThreadTunnelService: createMainId('MainThreadTunnelService'), - MainThreadTimeline: createMainId('MainThreadTimeline') + MainThreadTimeline: createMainId('MainThreadTimeline'), + MainThreadTesting: createMainId('MainThreadTesting'), }; export const ExtHostContext = { @@ -1841,5 +1873,6 @@ export const ExtHostContext = { ExtHostTheming: createMainId('ExtHostTheming'), ExtHostTunnelService: createMainId('ExtHostTunnelService'), ExtHostAuthentication: createMainId('ExtHostAuthentication'), - ExtHostTimeline: createMainId('ExtHostTimeline') + ExtHostTimeline: createMainId('ExtHostTimeline'), + ExtHostTesting: createMainId('ExtHostTesting'), }; diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 26300e195..08b2543a4 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import type * as vscode from 'vscode'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as types from 'vs/workbench/api/common/extHostTypes'; @@ -12,77 +12,17 @@ import { IRawColorInfo, IWorkspaceEditDto, ICallHierarchyItemDto, IIncomingCallD import * as modes from 'vs/editor/common/modes'; import * as search from 'vs/workbench/contrib/search/common/search'; import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; -import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { CustomCodeAction } from 'vs/workbench/api/common/extHostLanguageFeatures'; -import { ICommandsExecutor, OpenFolderAPICommand, DiffAPICommand, OpenAPICommand, RemoveFromRecentlyOpenedAPICommand, SetEditorLayoutAPICommand, OpenIssueReporter, OpenIssueReporterArgs } from './apiCommands'; -import { EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { ICommandsExecutor, RemoveFromRecentlyOpenedAPICommand, OpenIssueReporter, OpenIssueReporterArgs } from './apiCommands'; import { isFalsyOrEmpty } from 'vs/base/common/arrays'; import { IRange } from 'vs/editor/common/core/range'; import { IPosition } from 'vs/editor/common/core/position'; import { TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; //#region --- NEW world -export class ApiCommandArgument { - - static readonly Uri = new ApiCommandArgument('uri', 'Uri of a text document', v => URI.isUri(v), v => v); - static readonly Position = new ApiCommandArgument('position', 'A position in a text document', v => types.Position.isPosition(v), typeConverters.Position.from); - static readonly Range = new ApiCommandArgument('range', 'A range in a text document', v => types.Range.isRange(v), typeConverters.Range.from); - - static readonly CallHierarchyItem = new ApiCommandArgument('item', 'A call hierarchy item', v => v instanceof types.CallHierarchyItem, typeConverters.CallHierarchyItem.to); - - constructor( - readonly name: string, - readonly description: string, - readonly validate: (v: V) => boolean, - readonly convert: (v: V) => O - ) { } -} - -export class ApiCommandResult { - - constructor( - readonly description: string, - readonly convert: (v: V, apiArgs: any[]) => O - ) { } -} - -export class ApiCommand { - - constructor( - readonly id: string, - readonly internalId: string, - readonly description: string, - readonly args: ApiCommandArgument[], - readonly result: ApiCommandResult - ) { } - - register(commands: ExtHostCommands): IDisposable { - - return commands.registerCommand(false, this.id, async (...apiArgs) => { - - const internalArgs = this.args.map((arg, i) => { - if (!arg.validate(apiArgs[i])) { - throw new Error(`Invalid argument '${arg.name}' when running '${this.id}', receieved: ${apiArgs[i]}`); - } - return arg.convert(apiArgs[i]); - }); - - const internalResult = await commands.executeCommand(this.internalId, ...internalArgs); - return this.result.convert(internalResult, apiArgs); - }, undefined, this._getCommandHandlerDesc()); - } - - private _getCommandHandlerDesc(): ICommandHandlerDescription { - return { - description: this.description, - args: this.args, - returns: this.result.description - }; - } -} - - const newCommands: ApiCommand[] = [ // -- document highlights new ApiCommand( @@ -189,7 +129,7 @@ const newCommands: ApiCommand[] = [ // -- symbol search new ApiCommand( 'vscode.executeWorkspaceSymbolProvider', '_executeWorkspaceSymbolProvider', 'Execute all workspace symbol providers.', - [new ApiCommandArgument('query', 'Search string', v => typeof v === 'string', v => v)], + [ApiCommandArgument.String.with('query', 'Search string')], new ApiCommandResult<[search.IWorkspaceSymbolProvider, search.IWorkspaceSymbol[]][], types.SymbolInformation[]>('A promise that resolves to an array of SymbolInformation-instances.', value => { const result: types.SymbolInformation[] = []; if (Array.isArray(value)) { @@ -219,7 +159,7 @@ const newCommands: ApiCommand[] = [ // --- rename new ApiCommand( 'vscode.executeDocumentRenameProvider', '_executeDocumentRenameProvider', 'Execute rename provider.', - [ApiCommandArgument.Uri, ApiCommandArgument.Position, new ApiCommandArgument('newName', 'The new symbol name', v => typeof v === 'string', v => v)], + [ApiCommandArgument.Uri, ApiCommandArgument.Position, ApiCommandArgument.String.with('newName', 'The new symbol name')], new ApiCommandResult('A promise that resolves to a WorkspaceEdit.', value => { if (!value) { return undefined; @@ -233,9 +173,169 @@ const newCommands: ApiCommand[] = [ // --- links new ApiCommand( 'vscode.executeLinkProvider', '_executeLinkProvider', 'Execute document link provider.', - [ApiCommandArgument.Uri, new ApiCommandArgument('linkResolveCount', '(optional) Number of links that should be resolved, only when links are unresolved.', v => typeof v === 'number' || typeof v === 'undefined', v => v)], + [ApiCommandArgument.Uri, ApiCommandArgument.Number.with('linkResolveCount', 'Number of links that should be resolved, only when links are unresolved.').optional()], new ApiCommandResult('A promise that resolves to an array of DocumentLink-instances.', value => value.map(typeConverters.DocumentLink.to)) - ) + ), + // --- completions + new ApiCommand( + 'vscode.executeCompletionItemProvider', '_executeCompletionItemProvider', 'Execute completion item provider.', + [ + ApiCommandArgument.Uri, + ApiCommandArgument.Position, + ApiCommandArgument.String.with('triggerCharacter', 'Trigger completion when the user types the character, like `,` or `(`').optional(), + ApiCommandArgument.Number.with('itemResolveCount', 'Number of completions to resolve (too large numbers slow down completions)').optional() + ], + new ApiCommandResult('A promise that resolves to a CompletionList-instance.', (value, _args, converter) => { + if (!value) { + return new types.CompletionList([]); + } + const items = value.suggestions.map(suggestion => typeConverters.CompletionItem.to(suggestion, converter)); + return new types.CompletionList(items, value.incomplete); + }) + ), + // --- signature help + new ApiCommand( + 'vscode.executeSignatureHelpProvider', '_executeSignatureHelpProvider', 'Execute signature help provider.', + [ApiCommandArgument.Uri, ApiCommandArgument.Position, ApiCommandArgument.String.with('triggerCharacter', 'Trigger signature help when the user types the character, like `,` or `(`').optional()], + new ApiCommandResult('A promise that resolves to SignatureHelp.', value => { + if (value) { + return typeConverters.SignatureHelp.to(value); + } + return undefined; + }) + ), + // --- code lens + new ApiCommand( + 'vscode.executeCodeLensProvider', '_executeCodeLensProvider', 'Execute code lens provider.', + [ApiCommandArgument.Uri, ApiCommandArgument.Number.with('itemResolveCount', 'Number of lenses that should be resolved and returned. Will only return resolved lenses, will impact performance)').optional()], + new ApiCommandResult('A promise that resolves to an array of CodeLens-instances.', (value, _args, converter) => { + return tryMapWith(item => { + return new types.CodeLens(typeConverters.Range.to(item.range), item.command && converter.fromInternal(item.command)); + })(value); + }) + ), + // --- code actions + new ApiCommand( + 'vscode.executeCodeActionProvider', '_executeCodeActionProvider', 'Execute code action provider.', + [ + ApiCommandArgument.Uri, + new ApiCommandArgument('rangeOrSelection', 'Range in a text document. Some refactoring provider requires Selection object.', v => types.Range.isRange(v), v => types.Selection.isSelection(v) ? typeConverters.Selection.from(v) : typeConverters.Range.from(v)), + ApiCommandArgument.String.with('kind', 'Code action kind to return code actions for').optional(), + ApiCommandArgument.Number.with('itemResolveCount', 'Number of code actions to resolve (too large numbers slow down code actions)').optional() + ], + new ApiCommandResult('A promise that resolves to an array of Command-instances.', (value, _args, converter) => { + return tryMapWith((codeAction) => { + if (codeAction._isSynthetic) { + if (!codeAction.command) { + throw new Error('Synthetic code actions must have a command'); + } + return converter.fromInternal(codeAction.command); + } else { + const ret = new types.CodeAction( + codeAction.title, + codeAction.kind ? new types.CodeActionKind(codeAction.kind) : undefined + ); + if (codeAction.edit) { + ret.edit = typeConverters.WorkspaceEdit.to(codeAction.edit); + } + if (codeAction.command) { + ret.command = converter.fromInternal(codeAction.command); + } + ret.isPreferred = codeAction.isPreferred; + return ret; + } + })(value); + }) + ), + // --- colors + new ApiCommand( + 'vscode.executeDocumentColorProvider', '_executeDocumentColorProvider', 'Execute document color provider.', + [ApiCommandArgument.Uri], + new ApiCommandResult('A promise that resolves to an array of ColorInformation objects.', result => { + if (result) { + return result.map(ci => new types.ColorInformation(typeConverters.Range.to(ci.range), typeConverters.Color.to(ci.color))); + } + return []; + }) + ), + new ApiCommand( + 'vscode.executeColorPresentationProvider', '_executeColorPresentationProvider', 'Execute color presentation provider.', + [ + new ApiCommandArgument('color', 'The color to show and insert', v => v instanceof types.Color, typeConverters.Color.from), + new ApiCommandArgument<{ uri: URI, range: types.Range; }, { uri: URI, range: IRange; }>('context', 'Context object with uri and range', _v => true, v => ({ uri: v.uri, range: typeConverters.Range.from(v.range) })), + ], + new ApiCommandResult('A promise that resolves to an array of ColorPresentation objects.', result => { + if (result) { + return result.map(typeConverters.ColorPresentation.to); + } + return []; + }) + ), + // --- notebooks + new ApiCommand( + 'vscode.resolveNotebookContentProviders', '_resolveNotebookContentProvider', 'Resolve Notebook Content Providers', + [ + new ApiCommandArgument('viewType', '', v => typeof v === 'string', v => v), + new ApiCommandArgument('displayName', '', v => typeof v === 'string', v => v), + new ApiCommandArgument('options', '', v => typeof v === 'object', v => v), + ], + new ApiCommandResult<{ + viewType: string; + displayName: string; + options: { transientOutputs: boolean; transientMetadata: TransientMetadata }; + filenamePattern: (string | types.RelativePattern | { include: string | types.RelativePattern, exclude: string | types.RelativePattern })[] + }[], { + viewType: string; + displayName: string; + filenamePattern: vscode.NotebookFilenamePattern[]; + options: vscode.NotebookDocumentContentOptions; + }[] | undefined>('A promise that resolves to an array of NotebookContentProvider static info objects.', tryMapWith(item => { + return { + viewType: item.viewType, + displayName: item.displayName, + options: { transientOutputs: item.options.transientOutputs, transientMetadata: item.options.transientMetadata }, + filenamePattern: item.filenamePattern.map(pattern => typeConverters.NotebookExclusiveDocumentPattern.to(pattern)) + }; + })) + ), + // --- open'ish commands + new ApiCommand( + 'vscode.open', '_workbench.open', 'Opens the provided resource in the editor. Can be a text or binary file, or a http(s) url. If you need more control over the options for opening a text file, use vscode.window.showTextDocument instead.', + [ + ApiCommandArgument.Uri, + new ApiCommandArgument('columnOrOptions', 'Either the column in which to open or editor options, see vscode.TextDocumentShowOptions', + v => v === undefined || typeof v === 'number' || typeof v === 'object', + v => !v ? v : typeof v === 'number' ? [v, undefined] : [typeConverters.ViewColumn.from(v.viewColumn), typeConverters.TextEditorOpenOptions.from(v)] + ).optional(), + ApiCommandArgument.String.with('label', '').optional() + ], + ApiCommandResult.Void + ), + new ApiCommand( + 'vscode.openWith', '_workbench.openWith', 'Opens the provided resource with a specific editor.', + [ + ApiCommandArgument.Uri.with('resource', 'Resource to open'), + ApiCommandArgument.String.with('viewId', 'Custom editor view id or \'default\' to use VS Code\'s default editor'), + new ApiCommandArgument('columnOrOptions', 'Either the column in which to open or editor options, see vscode.TextDocumentShowOptions', + v => v === undefined || typeof v === 'number' || typeof v === 'object', + v => !v ? v : typeof v === 'number' ? [v, undefined] : [typeConverters.ViewColumn.from(v.viewColumn), typeConverters.TextEditorOpenOptions.from(v)], + ).optional() + ], + ApiCommandResult.Void + ), + new ApiCommand( + 'vscode.diff', '_workbench.diff', 'Opens the provided resources in the diff editor to compare their contents.', + [ + ApiCommandArgument.Uri.with('left', 'Left-hand side resource of the diff editor'), + ApiCommandArgument.Uri.with('right', 'Rigth-hand side resource of the diff editor'), + ApiCommandArgument.String.with('title', 'Human readable title for the diff editor').optional(), + new ApiCommandArgument('columnOrOptions', 'Either the column in which to open or editor options, see vscode.TextDocumentShowOptions', + v => v === undefined || typeof v === 'object', + v => v && [typeConverters.ViewColumn.from(v.viewColumn), typeConverters.TextEditorOpenOptions.from(v)] + ).optional(), + ], + ApiCommandResult.Void + ), ]; //#endregion @@ -246,7 +346,7 @@ const newCommands: ApiCommand[] = [ export class ExtHostApiCommands { static register(commands: ExtHostCommands) { - newCommands.forEach(command => command.register(commands)); + newCommands.forEach(commands.registerApiCommand, commands); return new ExtHostApiCommands(commands).registerCommands(); } @@ -258,66 +358,10 @@ export class ExtHostApiCommands { } registerCommands() { - this._register('vscode.executeSignatureHelpProvider', this._executeSignatureHelpProvider, { - description: 'Execute signature help provider.', - args: [ - { name: 'uri', description: 'Uri of a text document', constraint: URI }, - { name: 'position', description: 'Position in a text document', constraint: types.Position }, - { name: 'triggerCharacter', description: '(optional) Trigger signature help when the user types the character, like `,` or `(`', constraint: (value: any) => value === undefined || typeof value === 'string' } - ], - returns: 'A promise that resolves to SignatureHelp.' - }); - this._register('vscode.executeCompletionItemProvider', this._executeCompletionItemProvider, { - description: 'Execute completion item provider.', - args: [ - { name: 'uri', description: 'Uri of a text document', constraint: URI }, - { name: 'position', description: 'Position in a text document', constraint: types.Position }, - { name: 'triggerCharacter', description: '(optional) Trigger completion when the user types the character, like `,` or `(`', constraint: (value: any) => value === undefined || typeof value === 'string' }, - { name: 'itemResolveCount', description: '(optional) Number of completions to resolve (too large numbers slow down completions)', constraint: (value: any) => value === undefined || typeof value === 'number' } - ], - returns: 'A promise that resolves to a CompletionList-instance.' - }); - this._register('vscode.executeCodeActionProvider', this._executeCodeActionProvider, { - description: 'Execute code action provider.', - args: [ - { name: 'uri', description: 'Uri of a text document', constraint: URI }, - { name: 'rangeOrSelection', description: 'Range in a text document. Some refactoring provider requires Selection object.', constraint: types.Range }, - { name: 'kind', description: '(optional) Code action kind to return code actions for', constraint: (value: any) => !value || typeof value.value === 'string' }, - { name: 'itemResolveCount', description: '(optional) Number of code actions to resolve (too large numbers slow down code actions)', constraint: (value: any) => value === undefined || typeof value === 'number' } - ], - returns: 'A promise that resolves to an array of Command-instances.' - }); - this._register('vscode.executeCodeLensProvider', this._executeCodeLensProvider, { - description: 'Execute CodeLens provider.', - args: [ - { name: 'uri', description: 'Uri of a text document', constraint: URI }, - { name: 'itemResolveCount', description: '(optional) Number of lenses that should be resolved and returned. Will only return resolved lenses, will impact performance)', constraint: (value: any) => value === undefined || typeof value === 'number' } - ], - returns: 'A promise that resolves to an array of CodeLens-instances.' - }); - this._register('vscode.executeDocumentColorProvider', this._executeDocumentColorProvider, { - description: 'Execute document color provider.', - args: [ - { name: 'uri', description: 'Uri of a text document', constraint: URI }, - ], - returns: 'A promise that resolves to an array of ColorInformation objects.' - }); - this._register('vscode.executeColorPresentationProvider', this._executeColorPresentationProvider, { - description: 'Execute color presentation provider.', - args: [ - { name: 'color', description: 'The color to show and insert', constraint: types.Color }, - { name: 'context', description: 'Context object with uri and range' } - ], - returns: 'A promise that resolves to an array of ColorPresentation objects.' - }); - this._register('vscode.resolveNotebookContentProviders', this._resolveNotebookContentProviders, { - description: 'Resolve Notebook Content Providers', - args: [], - returns: 'A promise that resolves to an array of NotebookContentProvider static info objects.' - }); + // ----------------------------------------------------------------- // The following commands are registered on both sides separately. @@ -333,32 +377,6 @@ export class ExtHostApiCommands { }; }; - this._register(OpenFolderAPICommand.ID, adjustHandler(OpenFolderAPICommand.execute), { - description: 'Open a folder or workspace in the current window or new window depending on the newWindow argument. Note that opening in the same window will shutdown the current extension host process and start a new one on the given folder/workspace unless the newWindow parameter is set to true.', - args: [ - { name: 'uri', description: '(optional) Uri of the folder or workspace file to open. If not provided, a native dialog will ask the user for the folder', constraint: (value: any) => value === undefined || URI.isUri(value) }, - { name: 'options', description: '(optional) Options. Object with the following properties: `forceNewWindow `: Whether to open the folder/workspace in a new window or the same. Defaults to opening in the same window. `noRecentEntry`: Whether the opened URI will appear in the \'Open Recent\' list. Defaults to true. Note, for backward compatibility, options can also be of type boolean, representing the `forceNewWindow` setting.', constraint: (value: any) => value === undefined || typeof value === 'object' || typeof value === 'boolean' } - ] - }); - - this._register(DiffAPICommand.ID, adjustHandler(DiffAPICommand.execute), { - description: 'Opens the provided resources in the diff editor to compare their contents.', - args: [ - { name: 'left', description: 'Left-hand side resource of the diff editor', constraint: URI }, - { name: 'right', description: 'Right-hand side resource of the diff editor', constraint: URI }, - { name: 'title', description: '(optional) Human readable title for the diff editor', constraint: (v: any) => v === undefined || typeof v === 'string' }, - { name: 'options', description: '(optional) Editor options, see vscode.TextDocumentShowOptions' } - ] - }); - - this._register(OpenAPICommand.ID, adjustHandler(OpenAPICommand.execute), { - description: 'Opens the provided resource in the editor. Can be a text or binary file, or a http(s) url. If you need more control over the options for opening a text file, use vscode.window.showTextDocument instead.', - args: [ - { name: 'resource', description: 'Resource to open', constraint: URI }, - { name: 'columnOrOptions', description: '(optional) Either the column in which to open or editor options, see vscode.TextDocumentShowOptions', constraint: (v: any) => v === undefined || typeof v === 'number' || typeof v === 'object' } - ] - }); - this._register(RemoveFromRecentlyOpenedAPICommand.ID, adjustHandler(RemoveFromRecentlyOpenedAPICommand.execute), { description: 'Removes an entry with the given path from the recently opened list.', args: [ @@ -366,13 +384,6 @@ export class ExtHostApiCommands { ] }); - this._register(SetEditorLayoutAPICommand.ID, adjustHandler(SetEditorLayoutAPICommand.execute), { - description: 'Sets the editor layout. The layout is described as object with an initial (optional) orientation (0 = horizontal, 1 = vertical) and an array of editor groups within. Each editor group can have a size and another array of editor groups that will be laid out orthogonal to the orientation. If editor group sizes are provided, their sum must be 1 to be applied per row or column. Example for a 2x2 grid: `{ orientation: 0, groups: [{ groups: [{}, {}], size: 0.5 }, { groups: [{}, {}], size: 0.5 }] }`', - args: [ - { name: 'layout', description: 'The editor layout to set.', constraint: (value: EditorGroupLayout) => typeof value === 'object' && Array.isArray(value.groups) } - ] - }); - this._register(OpenIssueReporter.ID, adjustHandler(OpenIssueReporter.execute), { description: 'Opens the issue reporter with the provided extension id as the selected source', args: [ @@ -383,132 +394,16 @@ export class ExtHostApiCommands { // --- command impl + /** + * @deprecated use the ApiCommand instead + */ private _register(id: string, handler: (...args: any[]) => any, description?: ICommandHandlerDescription): void { const disposable = this._commands.registerCommand(false, id, handler, this, description); this._disposables.add(disposable); } - private _executeSignatureHelpProvider(resource: URI, position: types.Position, triggerCharacter: string): Promise { - const args = { - resource, - position: position && typeConverters.Position.from(position), - triggerCharacter - }; - return this._commands.executeCommand('_executeSignatureHelpProvider', args).then(value => { - if (value) { - return typeConverters.SignatureHelp.to(value); - } - return undefined; - }); - } - - private _executeCompletionItemProvider(resource: URI, position: types.Position, triggerCharacter: string, maxItemsToResolve: number): Promise { - const args = { - resource, - position: position && typeConverters.Position.from(position), - triggerCharacter, - maxItemsToResolve - }; - return this._commands.executeCommand('_executeCompletionItemProvider', args).then(result => { - if (result) { - const items = result.suggestions.map(suggestion => typeConverters.CompletionItem.to(suggestion, this._commands.converter)); - return new types.CompletionList(items, result.incomplete); - } - return undefined; - }); - } - - private _executeDocumentColorProvider(resource: URI): Promise { - const args = { - resource - }; - return this._commands.executeCommand('_executeDocumentColorProvider', args).then(result => { - if (result) { - return result.map(ci => ({ range: typeConverters.Range.to(ci.range), color: typeConverters.Color.to(ci.color) })); - } - return []; - }); - } - - private _executeColorPresentationProvider(color: types.Color, context: { uri: URI, range: types.Range; }): Promise { - const args = { - resource: context.uri, - color: typeConverters.Color.from(color), - range: typeConverters.Range.from(context.range), - }; - return this._commands.executeCommand('_executeColorPresentationProvider', args).then(result => { - if (result) { - return result.map(typeConverters.ColorPresentation.to); - } - return []; - }); - } - private _executeCodeActionProvider(resource: URI, rangeOrSelection: types.Range | types.Selection, kind?: string, itemResolveCount?: number): Promise<(vscode.CodeAction | vscode.Command | undefined)[] | undefined> { - const args = { - resource, - rangeOrSelection: types.Selection.isSelection(rangeOrSelection) - ? typeConverters.Selection.from(rangeOrSelection) - : typeConverters.Range.from(rangeOrSelection), - kind, - itemResolveCount, - }; - return this._commands.executeCommand('_executeCodeActionProvider', args) - .then(tryMapWith(codeAction => { - if (codeAction._isSynthetic) { - if (!codeAction.command) { - throw new Error('Synthetic code actions must have a command'); - } - return this._commands.converter.fromInternal(codeAction.command); - } else { - const ret = new types.CodeAction( - codeAction.title, - codeAction.kind ? new types.CodeActionKind(codeAction.kind) : undefined - ); - if (codeAction.edit) { - ret.edit = typeConverters.WorkspaceEdit.to(codeAction.edit); - } - if (codeAction.command) { - ret.command = this._commands.converter.fromInternal(codeAction.command); - } - ret.isPreferred = codeAction.isPreferred; - return ret; - } - })); - } - - private _executeCodeLensProvider(resource: URI, itemResolveCount: number): Promise { - const args = { resource, itemResolveCount }; - return this._commands.executeCommand('_executeCodeLensProvider', args) - .then(tryMapWith(item => { - return new types.CodeLens( - typeConverters.Range.to(item.range), - item.command ? this._commands.converter.fromInternal(item.command) : undefined); - })); - } - - private _resolveNotebookContentProviders(): Promise<{ - viewType: string; - displayName: string; - filenamePattern: vscode.NotebookFilenamePattern[]; - options: vscode.NotebookDocumentContentOptions; - }[] | undefined> { - return this._commands.executeCommand<{ - viewType: string; - displayName: string; - options: { transientOutputs: boolean; transientMetadata: TransientMetadata }; - filenamePattern: (string | types.RelativePattern | { include: string | types.RelativePattern, exclude: string | types.RelativePattern })[] - }[]>('_resolveNotebookContentProvider') - .then(tryMapWith(item => { - return { - viewType: item.viewType, - displayName: item.displayName, - options: { transientOutputs: item.options.transientOutputs, transientMetadata: item.options.transientMetadata }, - filenamePattern: item.filenamePattern.map(pattern => typeConverters.NotebookExclusiveDocumentPattern.to(pattern)) - }; - })); - } } function tryMapWith(f: (x: T) => R) { diff --git a/src/vs/workbench/api/common/extHostCommands.ts b/src/vs/workbench/api/common/extHostCommands.ts index c20a7a42b..649612e38 100644 --- a/src/vs/workbench/api/common/extHostCommands.ts +++ b/src/vs/workbench/api/common/extHostCommands.ts @@ -14,12 +14,13 @@ import * as modes from 'vs/editor/common/modes'; import type * as vscode from 'vscode'; import { ILogService } from 'vs/platform/log/common/log'; import { revive } from 'vs/base/common/marshalling'; -import { Range } from 'vs/editor/common/core/range'; -import { Position } from 'vs/editor/common/core/position'; +import { IRange, Range } from 'vs/editor/common/core/range'; +import { IPosition, Position } from 'vs/editor/common/core/position'; import { URI } from 'vs/base/common/uri'; import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { ISelection } from 'vs/editor/common/core/selection'; interface CommandHandler { callback: Function; @@ -36,18 +37,32 @@ export class ExtHostCommands implements ExtHostCommandsShape { readonly _serviceBrand: undefined; private readonly _commands = new Map(); + private readonly _apiCommands = new Map(); + private readonly _proxy: MainThreadCommandsShape; - private readonly _converter: CommandsConverter; private readonly _logService: ILogService; private readonly _argumentProcessors: ArgumentProcessor[]; + readonly converter: CommandsConverter; + constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, @ILogService logService: ILogService ) { this._proxy = extHostRpc.getProxy(MainContext.MainThreadCommands); this._logService = logService; - this._converter = new CommandsConverter(this, logService); + this.converter = new CommandsConverter( + this, + id => { + // API commands that have no return type (void) can be + // converted to their internal command and don't need + // any indirection commands + const candidate = this._apiCommands.get(id); + return candidate?.result === ApiCommandResult.Void + ? candidate : undefined; + }, + logService + ); this._argumentProcessors = [ { processArgument(a) { @@ -77,14 +92,38 @@ export class ExtHostCommands implements ExtHostCommandsShape { ]; } - get converter(): CommandsConverter { - return this._converter; - } - registerArgumentProcessor(processor: ArgumentProcessor): void { this._argumentProcessors.push(processor); } + registerApiCommand(apiCommand: ApiCommand): extHostTypes.Disposable { + + + const registration = this.registerCommand(false, apiCommand.id, async (...apiArgs) => { + + const internalArgs = apiCommand.args.map((arg, i) => { + if (!arg.validate(apiArgs[i])) { + throw new Error(`Invalid argument '${arg.name}' when running '${apiCommand.id}', receieved: ${apiArgs[i]}`); + } + return arg.convert(apiArgs[i]); + }); + + const internalResult = await this.executeCommand(apiCommand.internalId, ...internalArgs); + return apiCommand.result.convert(internalResult, apiArgs, this.converter); + }, undefined, { + description: apiCommand.description, + args: apiCommand.args, + returns: apiCommand.result.description + }); + + this._apiCommands.set(apiCommand.id, apiCommand); + + return new extHostTypes.Disposable(() => { + registration.dispose(); + this._apiCommands.delete(apiCommand.id); + }); + } + registerCommand(global: boolean, id: string, callback: (...args: any[]) => T | Thenable, thisArg?: any, description?: ICommandHandlerDescription): extHostTypes.Disposable { this._logService.trace('ExtHostCommands#registerCommand', id); @@ -214,6 +253,8 @@ export class ExtHostCommands implements ExtHostCommandsShape { } } +export interface IExtHostCommands extends ExtHostCommands { } +export const IExtHostCommands = createDecorator('IExtHostCommands'); export class CommandsConverter { @@ -224,6 +265,7 @@ export class CommandsConverter { // --- conversion between internal and api commands constructor( private readonly _commands: ExtHostCommands, + private readonly _lookupApiCommand: (id: string) => ApiCommand | undefined, private readonly _logService: ILogService ) { this._delegatingCommandId = `_vscode_delegate_cmd_${Date.now().toString(36)}`; @@ -245,7 +287,20 @@ export class CommandsConverter { tooltip: command.tooltip }; - if (command.command && isNonEmptyArray(command.arguments)) { + if (!command.command) { + // falsy command id -> return converted command but don't attempt any + // argument or API-command dance since this command won't run anyways + return result; + } + + const apiCommand = this._lookupApiCommand(command.command); + if (apiCommand) { + // API command with return-value can be converted inplace + result.id = apiCommand.internalId; + result.arguments = apiCommand.args.map((arg, i) => arg.convert(command.arguments && command.arguments[i])); + + + } else if (isNonEmptyArray(command.arguments)) { // we have a contributed command with arguments. that // means we don't want to send the arguments around @@ -293,5 +348,55 @@ export class CommandsConverter { } -export interface IExtHostCommands extends ExtHostCommands { } -export const IExtHostCommands = createDecorator('IExtHostCommands'); + +export class ApiCommandArgument { + + static readonly Uri = new ApiCommandArgument('uri', 'Uri of a text document', v => URI.isUri(v), v => v); + static readonly Position = new ApiCommandArgument('position', 'A position in a text document', v => extHostTypes.Position.isPosition(v), extHostTypeConverter.Position.from); + static readonly Range = new ApiCommandArgument('range', 'A range in a text document', v => extHostTypes.Range.isRange(v), extHostTypeConverter.Range.from); + static readonly Selection = new ApiCommandArgument('selection', 'A selection in a text document', v => extHostTypes.Selection.isSelection(v), extHostTypeConverter.Selection.from); + static readonly Number = new ApiCommandArgument('number', '', v => typeof v === 'number', v => v); + static readonly String = new ApiCommandArgument('string', '', v => typeof v === 'string', v => v); + + static readonly CallHierarchyItem = new ApiCommandArgument('item', 'A call hierarchy item', v => v instanceof extHostTypes.CallHierarchyItem, extHostTypeConverter.CallHierarchyItem.to); + + constructor( + readonly name: string, + readonly description: string, + readonly validate: (v: V) => boolean, + readonly convert: (v: V) => O + ) { } + + optional(): ApiCommandArgument { + return new ApiCommandArgument( + this.name, `(optional) ${this.description}`, + value => value === undefined || value === null || this.validate(value), + value => value === undefined ? undefined : value === null ? null : this.convert(value) + ); + } + + with(name: string | undefined, description: string | undefined): ApiCommandArgument { + return new ApiCommandArgument(name ?? this.name, description ?? this.description, this.validate, this.convert); + } +} + +export class ApiCommandResult { + + static readonly Void = new ApiCommandResult('no result', v => v); + + constructor( + readonly description: string, + readonly convert: (v: V, apiArgs: any[], cmdConverter: CommandsConverter) => O + ) { } +} + +export class ApiCommand { + + constructor( + readonly id: string, + readonly internalId: string, + readonly description: string, + readonly args: ApiCommandArgument[], + readonly result: ApiCommandResult + ) { } +} diff --git a/src/vs/workbench/api/common/extHostCustomEditors.ts b/src/vs/workbench/api/common/extHostCustomEditors.ts index f3b3cbd84..becd0887e 100644 --- a/src/vs/workbench/api/common/extHostCustomEditors.ts +++ b/src/vs/workbench/api/common/extHostCustomEditors.ts @@ -15,7 +15,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import { ExtHostWebviews, toExtensionData } from 'vs/workbench/api/common/extHostWebview'; import { ExtHostWebviewPanels } from 'vs/workbench/api/common/extHostWebviewPanels'; -import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; +import { EditorGroupColumn } from 'vs/workbench/common/editor'; import type * as vscode from 'vscode'; import { Cache } from './cache'; import * as extHostProtocol from './extHost.protocol'; @@ -252,7 +252,7 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor handle: extHostProtocol.WebviewHandle, viewType: string, title: string, - position: EditorViewColumn, + position: EditorGroupColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions, cancellation: CancellationToken, ): Promise { diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index b724e831c..4026a1d2b 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -23,7 +23,6 @@ import { ExtHostConfigProvider, IExtHostConfiguration } from '../common/extHostC import { convertToVSCPaths, convertToDAPaths, isDebuggerMainContribution } from 'vs/workbench/contrib/debug/common/debugUtils'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { ISignService } from 'vs/platform/sign/common/sign'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; @@ -97,7 +96,6 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E private readonly _onDidChangeBreakpoints: Emitter; - private _aexCommands: Map; private _debugAdapters: Map; private _debugAdaptersTrackers: Map; @@ -105,14 +103,12 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E private _signService: ISignService | undefined; - constructor( @IExtHostRpcService extHostRpcService: IExtHostRpcService, - @IExtHostWorkspace private _workspaceService: IExtHostWorkspace, + @IExtHostWorkspace protected _workspaceService: IExtHostWorkspace, @IExtHostExtensionService private _extensionService: IExtHostExtensionService, @IExtHostDocumentsAndEditors private _editorsService: IExtHostDocumentsAndEditors, @IExtHostConfiguration protected _configurationService: IExtHostConfiguration, - @IExtHostCommands private _commandService: IExtHostCommands ) { this._configProviderHandleCounter = 0; this._configProviders = []; @@ -123,7 +119,6 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E this._trackerFactoryHandleCounter = 0; this._trackerFactories = []; - this._aexCommands = new Map(); this._debugAdapters = new Map(); this._debugAdaptersTrackers = new Map(); @@ -182,7 +177,6 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E private registerAllDebugTypes(extensionRegistry: ExtensionDescriptionRegistry) { const debugTypes: string[] = []; - this._aexCommands.clear(); for (const ed of extensionRegistry.getAllExtensionDescriptions()) { if (ed.contributes) { @@ -191,9 +185,6 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E for (const dbg of debuggers) { if (isDebuggerMainContribution(dbg)) { debugTypes.push(dbg.type); - if (dbg.adapterExecutableCommand) { - this._aexCommands.set(dbg.type, dbg.adapterExecutableCommand); - } } } } @@ -312,10 +303,6 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E return new Disposable(() => { }); } - if (provider.debugAdapterExecutable) { - console.error('DebugConfigurationProvider.debugAdapterExecutable is deprecated and will be removed soon; please use DebugAdapterDescriptorFactory.createDebugAdapterDescriptor instead.'); - } - const handle = this._configProviderHandleCounter++; this._configProviders.push({ type, handle, provider }); @@ -323,7 +310,6 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E !!provider.provideDebugConfigurations, !!provider.resolveDebugConfiguration, !!provider.resolveDebugConfigurationWithSubstitutedVariables, - !!provider.debugAdapterExecutable, // TODO@AW: deprecated handle); return new Disposable(() => { @@ -651,26 +637,6 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E }); } - // TODO@AW deprecated and legacy - public $legacyDebugAdapterExecutable(configProviderHandle: number, folderUri: UriComponents | undefined): Promise { - return asPromise(async () => { - const provider = this.getConfigProviderByHandle(configProviderHandle); - if (!provider) { - throw new Error('no DebugConfigurationProvider found'); - } - if (!provider.debugAdapterExecutable) { - throw new Error('DebugConfigurationProvider has no method debugAdapterExecutable'); - } - const folder = await this.getFolder(folderUri); - return provider.debugAdapterExecutable(folder, CancellationToken.None); - }).then(executable => { - if (!executable) { - throw new Error('nothing returned from DebugConfigurationProvider.debugAdapterExecutable'); - } - return this.convertToDto(executable); - }); - } - public async $provideDebugAdapter(adapterFactoryHandle: number, sessionDto: IDebugSessionDto): Promise { const adapterDescriptorFactory = this.getAdapterDescriptorFactoryByHandle(adapterFactoryHandle); if (!adapterDescriptorFactory) { @@ -830,18 +796,6 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E return Promise.resolve(new DebugAdapterServer(serverPort)); } - // TODO@AW legacy - const pair = this._configProviders.filter(p => p.type === session.type).pop(); - if (pair && pair.provider.debugAdapterExecutable) { - const func = pair.provider.debugAdapterExecutable; - return asPromise(() => func(session.workspaceFolder, CancellationToken.None)).then(executable => { - if (executable) { - return executable; - } - return undefined; - }); - } - if (adapterDescriptorFactory) { const extensionRegistry = await this._extensionService.getExtensionRegistry(); return asPromise(() => adapterDescriptorFactory.createDebugAdapterDescriptor(session, this.daExecutableFromPackage(session, extensionRegistry))).then(daDescriptor => { @@ -852,17 +806,6 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E }); } - // try deprecated command based extension API "adapterExecutableCommand" to determine the executable - // TODO@AW legacy - const aex = this._aexCommands.get(session.type); - if (aex) { - const folder = session.workspaceFolder; - const rootFolder = folder ? folder.uri.toString() : undefined; - return this._commandService.executeCommand(aex, rootFolder).then((ae: any) => { - return new DebugAdapterExecutable(ae.command, ae.args || []); - }); - } - // fallback: use executable information from package.json const extensionRegistry = await this._extensionService.getExtensionRegistry(); return Promise.resolve(this.daExecutableFromPackage(session, extensionRegistry)); @@ -987,7 +930,7 @@ export class ExtHostDebugConsole implements vscode.DebugConsole { export class ExtHostVariableResolverService extends AbstractVariableResolverService { - constructor(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors | undefined, configurationService: ExtHostConfigProvider, env?: IProcessEnvironment) { + constructor(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors | undefined, configurationService: ExtHostConfigProvider, env?: IProcessEnvironment, workspaceService?: IExtHostWorkspace) { super({ getFolderUri: (folderName: string): URI | undefined => { const found = folders.filter(f => f.name === folderName); @@ -999,7 +942,7 @@ export class ExtHostVariableResolverService extends AbstractVariableResolverServ getWorkspaceFolderCount: (): number => { return folders.length; }, - getConfigurationValue: (folderUri: URI, section: string): string | undefined => { + getConfigurationValue: (folderUri: URI | undefined, section: string): string | undefined => { return configurationService.getConfiguration(undefined, folderUri).get(section); }, getExecPath: (): string | undefined => { @@ -1014,6 +957,18 @@ export class ExtHostVariableResolverService extends AbstractVariableResolverServ } return undefined; }, + getWorkspaceFolderPathForFile: (): string | undefined => { + if (editorService && workspaceService) { + const activeEditor = editorService.activeEditor(); + if (activeEditor) { + const ws = workspaceService.getWorkspaceFolder(activeEditor.document.uri); + if (ws) { + return path.normalize(ws.uri.fsPath); + } + } + } + return undefined; + }, getSelectedText: (): string | undefined => { if (editorService) { const activeEditor = editorService.activeEditor(); @@ -1032,7 +987,7 @@ export class ExtHostVariableResolverService extends AbstractVariableResolverServ } return undefined; } - }, undefined, env, !editorService); + }, undefined, env); } } @@ -1118,10 +1073,9 @@ export class WorkerExtHostDebugService extends ExtHostDebugServiceBase { @IExtHostWorkspace workspaceService: IExtHostWorkspace, @IExtHostExtensionService extensionService: IExtHostExtensionService, @IExtHostDocumentsAndEditors editorsService: IExtHostDocumentsAndEditors, - @IExtHostConfiguration configurationService: IExtHostConfiguration, - @IExtHostCommands commandService: IExtHostCommands + @IExtHostConfiguration configurationService: IExtHostConfiguration ) { - super(extHostRpcService, workspaceService, extensionService, editorsService, configurationService, commandService); + super(extHostRpcService, workspaceService, extensionService, editorsService, configurationService); } protected createVariableResolver(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider): AbstractVariableResolverService { diff --git a/src/vs/workbench/api/common/extHostDecorations.ts b/src/vs/workbench/api/common/extHostDecorations.ts index 39ba91473..ea4858374 100644 --- a/src/vs/workbench/api/common/extHostDecorations.ts +++ b/src/vs/workbench/api/common/extHostDecorations.ts @@ -37,12 +37,12 @@ export class ExtHostDecorations implements ExtHostDecorationsShape { this._proxy = extHostRpc.getProxy(MainContext.MainThreadDecorations); } - registerDecorationProvider(provider: vscode.FileDecorationProvider, extensionId: ExtensionIdentifier): vscode.Disposable { + registerFileDecorationProvider(provider: vscode.FileDecorationProvider, extensionId: ExtensionIdentifier): vscode.Disposable { const handle = ExtHostDecorations._handlePool++; this._provider.set(handle, { provider, extensionId }); this._proxy.$registerDecorationProvider(handle, extensionId.value); - const listener = provider.onDidChange(e => { + const listener = provider.onDidChangeFileDecorations && provider.onDidChangeFileDecorations(e => { if (!e) { this._proxy.$onDidChange(handle, null); return; @@ -75,7 +75,7 @@ export class ExtHostDecorations implements ExtHostDecorationsShape { }); return new Disposable(() => { - listener.dispose(); + listener?.dispose(); this._proxy.$unregisterDecorationProvider(handle); this._provider.delete(handle); }); diff --git a/src/vs/workbench/api/common/extHostDocumentContentProviders.ts b/src/vs/workbench/api/common/extHostDocumentContentProviders.ts index eba5e7845..e4d2b49a4 100644 --- a/src/vs/workbench/api/common/extHostDocumentContentProviders.ts +++ b/src/vs/workbench/api/common/extHostDocumentContentProviders.ts @@ -13,6 +13,7 @@ import { ExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors'; import { Schemas } from 'vs/base/common/network'; import { ILogService } from 'vs/platform/log/common/log'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { splitLines } from 'vs/base/common/strings'; export class ExtHostDocumentContentProvider implements ExtHostDocumentContentProvidersShape { @@ -61,7 +62,7 @@ export class ExtHostDocumentContentProvider implements ExtHostDocumentContentPro } // create lines and compare - const lines = value.split(/\r\n|\r|\n/); + const lines = splitLines(value); // broadcast event when content changed if (!document.equalLines(lines)) { diff --git a/src/vs/workbench/api/common/extHostFileSystemEventService.ts b/src/vs/workbench/api/common/extHostFileSystemEventService.ts index c576f8f5e..0503a131b 100644 --- a/src/vs/workbench/api/common/extHostFileSystemEventService.ts +++ b/src/vs/workbench/api/common/extHostFileSystemEventService.ts @@ -178,23 +178,23 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ }; } - async $onWillRunFileOperation(operation: FileOperation, files: SourceTargetPair[], timeout: number, token: CancellationToken): Promise { + async $onWillRunFileOperation(operation: FileOperation, files: SourceTargetPair[], undoRedoGroupId: number | undefined, timeout: number, token: CancellationToken): Promise { switch (operation) { case FileOperation.MOVE: - await this._fireWillEvent(this._onWillRenameFile, { files: files.map(f => ({ oldUri: URI.revive(f.source!), newUri: URI.revive(f.target) })) }, timeout, token); + await this._fireWillEvent(this._onWillRenameFile, { files: files.map(f => ({ oldUri: URI.revive(f.source!), newUri: URI.revive(f.target) })) }, undoRedoGroupId, timeout, token); break; case FileOperation.DELETE: - await this._fireWillEvent(this._onWillDeleteFile, { files: files.map(f => URI.revive(f.target)) }, timeout, token); + await this._fireWillEvent(this._onWillDeleteFile, { files: files.map(f => URI.revive(f.target)) }, undoRedoGroupId, timeout, token); break; case FileOperation.CREATE: - await this._fireWillEvent(this._onWillCreateFile, { files: files.map(f => URI.revive(f.target)) }, timeout, token); + await this._fireWillEvent(this._onWillCreateFile, { files: files.map(f => URI.revive(f.target)) }, undoRedoGroupId, timeout, token); break; default: //ignore, dont send } } - private async _fireWillEvent(emitter: AsyncEmitter, data: Omit, timeout: number, token: CancellationToken): Promise { + private async _fireWillEvent(emitter: AsyncEmitter, data: Omit, undoRedoGroupId: number | undefined, timeout: number, token: CancellationToken): Promise { const edits: WorkspaceEdit[] = []; @@ -222,7 +222,7 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors); dto.edits = dto.edits.concat(edits); } - return this._mainThreadBulkEdits.$tryApplyWorkspaceEdit(dto); + return this._mainThreadBulkEdits.$tryApplyWorkspaceEdit(dto, undoRedoGroupId); } } } diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index b26a4ef04..49cde88e1 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -32,7 +32,6 @@ import { IdGenerator } from 'vs/base/common/idGenerator'; import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; import { Cache } from './cache'; import { StopWatch } from 'vs/base/common/stopwatch'; -import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; // --- adapter @@ -117,65 +116,57 @@ class CodeLensAdapter { private readonly _provider: vscode.CodeLensProvider ) { } - provideCodeLenses(resource: URI, token: CancellationToken): Promise { + async provideCodeLenses(resource: URI, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); - return asPromise(() => this._provider.provideCodeLenses(doc, token)).then(lenses => { - - if (!lenses || token.isCancellationRequested) { - return undefined; - } - - const cacheId = this._cache.add(lenses); - const disposables = new DisposableStore(); - this._disposables.set(cacheId, disposables); - - const result: extHostProtocol.ICodeLensListDto = { - cacheId, - lenses: [], - }; - - for (let i = 0; i < lenses.length; i++) { - result.lenses.push({ - cacheId: [cacheId, i], - range: typeConvert.Range.from(lenses[i].range), - command: this._commands.toInternal(lenses[i].command, disposables) - }); - } - - return result; - }); + const lenses = await this._provider.provideCodeLenses(doc, token); + if (!lenses || token.isCancellationRequested) { + return undefined; + } + const cacheId = this._cache.add(lenses); + const disposables = new DisposableStore(); + this._disposables.set(cacheId, disposables); + const result: extHostProtocol.ICodeLensListDto = { + cacheId, + lenses: [], + }; + for (let i = 0; i < lenses.length; i++) { + result.lenses.push({ + cacheId: [cacheId, i], + range: typeConvert.Range.from(lenses[i].range), + command: this._commands.toInternal(lenses[i].command, disposables) + }); + } + return result; } - resolveCodeLens(symbol: extHostProtocol.ICodeLensDto, token: CancellationToken): Promise { + async resolveCodeLens(symbol: extHostProtocol.ICodeLensDto, token: CancellationToken): Promise { const lens = symbol.cacheId && this._cache.get(...symbol.cacheId); if (!lens) { - return Promise.resolve(undefined); + return undefined; } - let resolve: Promise; + let resolvedLens: vscode.CodeLens | undefined | null; if (typeof this._provider.resolveCodeLens !== 'function' || lens.isResolved) { - resolve = Promise.resolve(lens); + resolvedLens = lens; } else { - resolve = asPromise(() => this._provider.resolveCodeLens!(lens, token)); + resolvedLens = await this._provider.resolveCodeLens(lens, token); + } + if (!resolvedLens) { + resolvedLens = lens; } - return resolve.then(newLens => { - if (token.isCancellationRequested) { - return undefined; - } - - const disposables = symbol.cacheId && this._disposables.get(symbol.cacheId[0]); - if (!disposables) { - // We've already been disposed of - return undefined; - } - - newLens = newLens || lens; - symbol.command = this._commands.toInternal(newLens.command || CodeLensAdapter._badCmd, disposables); - return symbol; - }); + if (token.isCancellationRequested) { + return undefined; + } + const disposables = symbol.cacheId && this._disposables.get(symbol.cacheId[0]); + if (!disposables) { + // disposed in the meantime + return undefined; + } + symbol.command = this._commands.toInternal(resolvedLens.command ?? CodeLensAdapter._badCmd, disposables); + return symbol; } releaseCodeLenses(cachedId: number): void { @@ -320,18 +311,18 @@ class DocumentHighlightAdapter { } } -class OnTypeRenameAdapter { +class LinkedEditingRangeAdapter { constructor( private readonly _documents: ExtHostDocuments, - private readonly _provider: vscode.OnTypeRenameProvider + private readonly _provider: vscode.LinkedEditingRangeProvider ) { } - provideOnTypeRenameRanges(resource: URI, position: IPosition, token: CancellationToken): Promise<{ ranges: IRange[]; wordPattern?: RegExp; } | undefined> { + provideLinkedEditingRanges(resource: URI, position: IPosition, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); const pos = typeConvert.Position.to(position); - return asPromise(() => this._provider.provideOnTypeRenameRanges(doc, pos, token)).then(value => { + return asPromise(() => this._provider.provideLinkedEditingRanges(doc, pos, token)).then(value => { if (value && Array.isArray(value.ranges)) { return { ranges: coalesce(value.ranges.map(typeConvert.Range.from)), @@ -1329,7 +1320,7 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov | SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter | TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter | SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter - | OnTypeRenameAdapter; + | LinkedEditingRangeAdapter; class AdapterData { constructor( @@ -1571,18 +1562,17 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._withAdapter(handle, DocumentHighlightAdapter, adapter => adapter.provideDocumentHighlights(URI.revive(resource), position, token), undefined); } - // --- on type rename + // --- linked editing - registerOnTypeRenameProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.OnTypeRenameProvider, wordPattern?: RegExp): vscode.Disposable { - const handle = this._addNewAdapter(new OnTypeRenameAdapter(this._documents, provider), extension); - const serializedWordPattern = wordPattern ? ExtHostLanguageFeatures._serializeRegExp(wordPattern) : undefined; - this._proxy.$registerOnTypeRenameProvider(handle, this._transformDocumentSelector(selector), serializedWordPattern); + registerLinkedEditingRangeProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.LinkedEditingRangeProvider): vscode.Disposable { + const handle = this._addNewAdapter(new LinkedEditingRangeAdapter(this._documents, provider), extension); + this._proxy.$registerLinkedEditingRangeProvider(handle, this._transformDocumentSelector(selector)); return this._createDisposable(handle); } - $provideOnTypeRenameRanges(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<{ ranges: IRange[]; wordPattern?: extHostProtocol.IRegExpDto; } | undefined> { - return this._withAdapter(handle, OnTypeRenameAdapter, async adapter => { - const res = await adapter.provideOnTypeRenameRanges(URI.revive(resource), position, token); + $provideLinkedEditingRanges(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise { + return this._withAdapter(handle, LinkedEditingRangeAdapter, async adapter => { + const res = await adapter.provideLinkedEditingRanges(URI.revive(resource), position, token); if (res) { return { ranges: res.ranges, @@ -1814,7 +1804,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._withAdapter(handle, ColorProviderAdapter, adapter => adapter.provideColorPresentations(URI.revive(resource), colorInfo, token), undefined); } - registerFoldingRangeProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.FoldingRangeProvider2): vscode.Disposable { + registerFoldingRangeProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.FoldingRangeProvider): vscode.Disposable { const handle = this._nextHandle(); const eventHandle = typeof provider.onDidChangeFoldingRanges === 'function' ? this._nextHandle() : undefined; @@ -1823,8 +1813,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF let result = this._createDisposable(handle); if (eventHandle !== undefined) { - checkProposedApiEnabled(extension); - const subscription = provider.onDidChangeFoldingRanges!(_ => this._proxy.$emitFoldingRangeEvent(eventHandle)); + const subscription = provider.onDidChangeFoldingRanges!(() => this._proxy.$emitFoldingRangeEvent(eventHandle)); result = Disposable.from(result, subscription); } diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 0846a2d55..bb136d1c0 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -9,7 +9,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as UUID from 'vs/base/common/uuid'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { ExtHostNotebookShape, ICommandDto, IMainContext, IModelAddedData, INotebookDocumentPropertiesChangeData, INotebookDocumentsAndEditorsDelta, INotebookEditorPropertiesChangeData, MainContext, MainThreadBulkEditsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostNotebookShape, ICommandDto, IMainContext, IModelAddedData, INotebookDocumentPropertiesChangeData, INotebookDocumentsAndEditorsDelta, INotebookDocumentShowOptions, INotebookEditorPropertiesChangeData, MainContext, MainThreadBulkEditsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; import { ILogService } from 'vs/platform/log/common/log'; import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; @@ -214,7 +214,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private readonly _proxy: MainThreadNotebookShape; private readonly _mainThreadBulkEdits: MainThreadBulkEditsShape; private readonly _notebookContentProviders = new Map(); - private readonly _notebookKernels = new Map(); private readonly _notebookKernelProviders = new Map(); private readonly _documents = new ResourceMap(); private readonly _editors = new Map(); @@ -414,6 +413,35 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN return callback(provider, document); } + async showNotebookDocument(notebookDocument: vscode.NotebookDocument, options?: vscode.NotebookDocumentShowOptions): Promise { + let resolvedOptions: INotebookDocumentShowOptions; + if (typeof options === 'object') { + resolvedOptions = { + position: typeConverters.ViewColumn.from(options.viewColumn), + preserveFocus: options.preserveFocus, + selection: options.selection, + pinned: typeof options.preview === 'boolean' ? !options.preview : undefined + }; + } else { + resolvedOptions = { + preserveFocus: false + }; + } + + const editorId = await this._proxy.$tryShowNotebookDocument(notebookDocument.uri, notebookDocument.viewType, resolvedOptions); + const editor = editorId && this._editors.get(editorId)?.editor; + + if (editor) { + return editor; + } + + if (editorId) { + throw new Error(`Could NOT open editor for "${notebookDocument.toString()}" because another editor opened in the meantime.`); + } else { + throw new Error(`Could NOT open editor for "${notebookDocument.toString()}".`); + } + } + async $provideNotebookKernels(handle: number, uri: UriComponents, token: CancellationToken): Promise { return this._withAdapter(handle, uri, (adapter, document) => { return adapter.provideKernels(document, token); @@ -487,28 +515,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN }); } - async $executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise { - const document = this._documents.get(URI.revive(uri)); - - if (!document || document.notebookDocument.viewType !== viewType) { - return; - } - - const kernelInfo = this._notebookKernels.get(kernelId); - - if (!kernelInfo) { - return; - } - - const cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined; - - if (cell) { - return withToken(token => (kernelInfo!.kernel.executeCell as any)(document.notebookDocument, cell.cell, token)); - } else { - return withToken(token => (kernelInfo!.kernel.executeAllCells as any)(document.notebookDocument, token)); - } - } - async $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise { const document = this._documents.get(URI.revive(uri)); if (!document) { diff --git a/src/vs/workbench/api/common/extHostQuickOpen.ts b/src/vs/workbench/api/common/extHostQuickOpen.ts index 4adf04afd..2f2fd4cdb 100644 --- a/src/vs/workbench/api/common/extHostQuickOpen.ts +++ b/src/vs/workbench/api/common/extHostQuickOpen.ts @@ -448,7 +448,11 @@ function getIconUris(iconPath: QuickInputButton['iconPath']): { dark: URI, light } const dark = getDarkIconUri(iconPath as URI | { light: URI; dark: URI; }); const light = getLightIconUri(iconPath as URI | { light: URI; dark: URI; }); - return { dark, light }; + // Tolerate strings: https://github.com/microsoft/vscode/issues/110432#issuecomment-726144556 + return { + dark: typeof dark === 'string' ? URI.file(dark) : dark, + light: typeof light === 'string' ? URI.file(light) : light + }; } function getLightIconUri(iconPath: URI | { light: URI; dark: URI; }) { diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 83739e744..058061633 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -17,6 +17,7 @@ import { ISplice } from 'vs/base/common/sequence'; import { ILogService } from 'vs/platform/log/common/log'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; type ProviderHandle = number; type GroupHandle = number; @@ -90,6 +91,49 @@ function compareResourceStatesDecorations(a: vscode.SourceControlResourceDecorat return result; } +function compareCommands(a: vscode.Command, b: vscode.Command): number { + if (a.command !== b.command) { + return a.command < b.command ? -1 : 1; + } + + if (a.title !== b.title) { + return a.title < b.title ? -1 : 1; + } + + if (a.tooltip !== b.tooltip) { + if (a.tooltip !== undefined && b.tooltip !== undefined) { + return a.tooltip < b.tooltip ? -1 : 1; + } else if (a.tooltip !== undefined) { + return 1; + } else if (b.tooltip !== undefined) { + return -1; + } + } + + if (a.arguments === b.arguments) { + return 0; + } else if (!a.arguments) { + return -1; + } else if (!b.arguments) { + return 1; + } else if (a.arguments.length !== b.arguments.length) { + return a.arguments.length - b.arguments.length; + } + + for (let i = 0; i < a.arguments.length; i++) { + const aArg = a.arguments[i]; + const bArg = b.arguments[i]; + + if (aArg === bArg) { + continue; + } + + return aArg < bArg ? -1 : 1; + } + + return 0; +} + function compareResourceStates(a: vscode.SourceControlResourceState, b: vscode.SourceControlResourceState): number { let result = comparePaths(a.resourceUri.fsPath, b.resourceUri.fsPath, true); @@ -97,6 +141,18 @@ function compareResourceStates(a: vscode.SourceControlResourceState, b: vscode.S return result; } + if (a.command && b.command) { + result = compareCommands(a.command, b.command); + } else if (a.command) { + return 1; + } else if (b.command) { + return -1; + } + + if (result !== 0) { + return result; + } + if (a.decorations && b.decorations) { result = compareResourceStatesDecorations(a.decorations, b.decorations); } else if (a.decorations) { @@ -166,17 +222,13 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { private _validateInput: IValidateInput | undefined; get validateInput(): IValidateInput | undefined { - if (!this._extension.enableProposedApi) { - throw new Error(`[${this._extension.identifier.value}]: Proposed API is only available when running out of dev or with the following command line switch: --enable-proposed-api ${this._extension.identifier.value}`); - } + checkProposedApiEnabled(this._extension); return this._validateInput; } set validateInput(fn: IValidateInput | undefined) { - if (!this._extension.enableProposedApi) { - throw new Error(`[${this._extension.identifier.value}]: Proposed API is only available when running out of dev or with the following command line switch: --enable-proposed-api ${this._extension.identifier.value}`); - } + checkProposedApiEnabled(this._extension); if (fn && typeof fn !== 'function') { throw new Error(`[${this._extension.identifier.value}]: Invalid SCM input box validation function`); @@ -223,8 +275,9 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG private _resourceHandlePool: number = 0; private _resourceStates: vscode.SourceControlResourceState[] = []; - private _resourceStatesMap: Map = new Map(); - private _resourceStatesCommandsMap: Map = new Map(); + private _resourceStatesMap = new Map(); + private _resourceStatesCommandsMap = new Map(); + private _resourceStatesDisposablesMap = new Map(); private readonly _onDidUpdateResourceStates = new Emitter(); readonly onDidUpdateResourceStates = this._onDidUpdateResourceStates.event; @@ -302,9 +355,16 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG const lightIconUri = r.decorations && getIconResource(r.decorations.light) || iconUri; const darkIconUri = r.decorations && getIconResource(r.decorations.dark) || iconUri; const icons: UriComponents[] = []; + let command: ICommandDto | undefined; if (r.command) { - this._resourceStatesCommandsMap.set(handle, r.command); + if (r.command.command === 'vscode.open' || r.command.command === 'vscode.diff') { + const disposables = new DisposableStore(); + command = this._commands.converter.toInternal(r.command, disposables); + this._resourceStatesDisposablesMap.set(handle, disposables); + } else { + this._resourceStatesCommandsMap.set(handle, r.command); + } } if (lightIconUri) { @@ -320,7 +380,7 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG const faded = r.decorations && !!r.decorations.faded; const contextValue = r.contextValue || ''; - const rawResource = [handle, sourceUri, icons, tooltip, strikeThrough, faded, contextValue] as SCMRawResource; + const rawResource = [handle, sourceUri, icons, tooltip, strikeThrough, faded, contextValue, command] as SCMRawResource; return { rawResource, handle }; }); @@ -340,6 +400,8 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG for (const handle of handlesToDelete) { this._resourceStatesMap.delete(handle); this._resourceStatesCommandsMap.delete(handle); + this._resourceStatesDisposablesMap.get(handle)?.dispose(); + this._resourceStatesDisposablesMap.delete(handle); } } diff --git a/src/vs/workbench/api/common/extHostStatusBar.ts b/src/vs/workbench/api/common/extHostStatusBar.ts index edbaec0f6..a89e9fe30 100644 --- a/src/vs/workbench/api/common/extHostStatusBar.ts +++ b/src/vs/workbench/api/common/extHostStatusBar.ts @@ -10,10 +10,21 @@ import { MainContext, MainThreadStatusBarShape, IMainContext, ICommandDto } from import { localize } from 'vs/nls'; import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; export class ExtHostStatusBarEntry implements vscode.StatusBarItem { private static ID_GEN = 0; + private static ALLOWED_BACKGROUND_COLORS = (() => { + const map = new Map(); + + // https://github.com/microsoft/vscode/issues/110214 + map.set('statusBarItem.errorBackground', new ThemeColor('statusBarItem.errorForeground')); + + return map; + })(); + private _id: number; private _alignment: number; private _priority?: number; @@ -26,6 +37,7 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { private _text: string = ''; private _tooltip?: string; private _color?: string | ThemeColor; + private _backgroundColor?: ThemeColor; private readonly _internalCommandRegistration = new DisposableStore(); private _command?: { readonly fromApi: string | vscode.Command, @@ -36,8 +48,9 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { private _proxy: MainThreadStatusBarShape; private _commands: CommandsConverter; private _accessibilityInformation?: vscode.AccessibilityInformation; + private _extension?: IExtensionDescription; - constructor(proxy: MainThreadStatusBarShape, commands: CommandsConverter, id: string, name: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number, accessibilityInformation?: vscode.AccessibilityInformation) { + constructor(proxy: MainThreadStatusBarShape, commands: CommandsConverter, id: string, name: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number, accessibilityInformation?: vscode.AccessibilityInformation, extension?: IExtensionDescription) { this._id = ExtHostStatusBarEntry.ID_GEN++; this._proxy = proxy; this._commands = commands; @@ -46,6 +59,7 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { this._alignment = alignment; this._priority = priority; this._accessibilityInformation = accessibilityInformation; + this._extension = extension; } public get id(): number { @@ -72,6 +86,14 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { return this._color; } + public get backgroundColor(): ThemeColor | undefined { + if (this._extension) { + checkProposedApiEnabled(this._extension); + } + + return this._backgroundColor; + } + public get command(): string | vscode.Command | undefined { return this._command?.fromApi; } @@ -95,6 +117,19 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { this.update(); } + public set backgroundColor(color: ThemeColor | undefined) { + if (this._extension) { + checkProposedApiEnabled(this._extension); + } + + if (color && !ExtHostStatusBarEntry.ALLOWED_BACKGROUND_COLORS.has(color.id)) { + color = undefined; + } + + this._backgroundColor = color; + this.update(); + } + public set command(command: string | vscode.Command | undefined) { if (this._command?.fromApi === command) { return; @@ -144,9 +179,15 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { this._timeoutHandle = setTimeout(() => { this._timeoutHandle = undefined; + // If a background color is set, the foreground is determined + let color = this._color; + if (this._backgroundColor) { + color = ExtHostStatusBarEntry.ALLOWED_BACKGROUND_COLORS.get(this._backgroundColor.id); + } + // Set to status bar - this._proxy.$setEntry(this.id, this._statusId, this._statusName, this.text, this.tooltip, this._command?.internal, this.color, - this._alignment === ExtHostStatusBarAlignment.Left ? MainThreadStatusBarAlignment.LEFT : MainThreadStatusBarAlignment.RIGHT, + this._proxy.$setEntry(this.id, this._statusId, this._statusName, this._text, this._tooltip, this._command?.internal, color, + this._backgroundColor, this._alignment === ExtHostStatusBarAlignment.Left ? MainThreadStatusBarAlignment.LEFT : MainThreadStatusBarAlignment.RIGHT, this._priority, this._accessibilityInformation); }, 0); } @@ -207,8 +248,8 @@ export class ExtHostStatusBar { this._statusMessage = new StatusBarMessage(this); } - createStatusBarEntry(id: string, name: string, alignment?: ExtHostStatusBarAlignment, priority?: number, accessibilityInformation?: vscode.AccessibilityInformation): vscode.StatusBarItem { - return new ExtHostStatusBarEntry(this._proxy, this._commands, id, name, alignment, priority, accessibilityInformation); + createStatusBarEntry(id: string, name: string, alignment?: ExtHostStatusBarAlignment, priority?: number, accessibilityInformation?: vscode.AccessibilityInformation, extension?: IExtensionDescription): vscode.StatusBarItem { + return new ExtHostStatusBarEntry(this._proxy, this._commands, id, name, alignment, priority, accessibilityInformation, extension); } setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable): Disposable { diff --git a/src/vs/workbench/api/common/extHostStorage.ts b/src/vs/workbench/api/common/extHostStorage.ts index 0c38bb510..01712256f 100644 --- a/src/vs/workbench/api/common/extHostStorage.ts +++ b/src/vs/workbench/api/common/extHostStorage.ts @@ -7,7 +7,7 @@ import { MainContext, MainThreadStorageShape, ExtHostStorageShape } from './extH import { Emitter } from 'vs/base/common/event'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IExtensionIdWithVersion } from 'vs/platform/userDataSync/common/storageKeys'; +import { IExtensionIdWithVersion } from 'vs/platform/userDataSync/common/extensionsStorageSync'; export interface IStorageChangeEvent { shared: boolean; diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts index f0bb1dfac..9b0753a57 100644 --- a/src/vs/workbench/api/common/extHostTask.ts +++ b/src/vs/workbench/api/common/extHostTask.ts @@ -188,6 +188,10 @@ export namespace CustomExecutionDTO { customExecution: 'customExecution' }; } + + export function to(taskId: string, providedCustomExeutions: Map): types.CustomExecution | undefined { + return providedCustomExeutions.get(taskId); + } } @@ -274,15 +278,17 @@ export namespace TaskDTO { }; return result; } - export async function to(value: tasks.TaskDTO | undefined, workspace: IExtHostWorkspaceProvider): Promise { + export async function to(value: tasks.TaskDTO | undefined, workspace: IExtHostWorkspaceProvider, providedCustomExeutions: Map): Promise { if (value === undefined || value === null) { return undefined; } - let execution: types.ShellExecution | types.ProcessExecution | undefined; + let execution: types.ShellExecution | types.ProcessExecution | types.CustomExecution | undefined; if (ProcessExecutionDTO.is(value.execution)) { execution = ProcessExecutionDTO.to(value.execution); } else if (ShellExecutionDTO.is(value.execution)) { execution = ShellExecutionDTO.to(value.execution); + } else if (CustomExecutionDTO.is(value.execution)) { + execution = CustomExecutionDTO.to(value._id, providedCustomExeutions); } const definition: vscode.TaskDefinition | undefined = TaskDefinitionDTO.to(value.definition); let scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder | undefined; @@ -354,13 +360,6 @@ class TaskExecutionImpl implements vscode.TaskExecution { } export namespace TaskExecutionDTO { - export async function to(value: tasks.TaskExecutionDTO, tasks: ExtHostTaskBase, workspaceProvider: IExtHostWorkspaceProvider): Promise { - const task = await TaskDTO.to(value.task, workspaceProvider); - if (!task) { - throw new Error('Unexpected: Task cannot be created.'); - } - return new TaskExecutionImpl(tasks, value.id, task); - } export function from(value: vscode.TaskExecution): tasks.TaskExecutionDTO { return { id: (value as TaskExecutionImpl)._id, @@ -447,7 +446,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape, IExtHostTask return this._proxy.$fetchTasks(TaskFilterDTO.from(filter)).then(async (values) => { const result: vscode.Task[] = []; for (let value of values) { - const task = await TaskDTO.to(value, this._workspaceProvider); + const task = await TaskDTO.to(value, this._workspaceProvider, this._providedCustomExecutions2); if (task) { result.push(task); } @@ -573,7 +572,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape, IExtHostTask throw new Error(`Unexpected: Task of type [${taskDTO.definition.type}] cannot be resolved by provider of type [${handler.type}].`); } - const task = await TaskDTO.to(taskDTO, this._workspaceProvider); + const task = await TaskDTO.to(taskDTO, this._workspaceProvider, this._providedCustomExecutions2); if (!task) { throw new Error('Unexpected: Task cannot be resolved.'); } @@ -631,7 +630,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape, IExtHostTask return result; } const createdResult: Promise = new Promise(async (resolve, reject) => { - const taskToCreate = task ? task : await TaskDTO.to(execution.task, this._workspaceProvider); + const taskToCreate = task ? task : await TaskDTO.to(execution.task, this._workspaceProvider, this._providedCustomExecutions2); if (!taskToCreate) { reject('Unexpected: Task does not exist.'); } else { @@ -705,6 +704,10 @@ export class WorkerExtHostTask extends ExtHostTaskBase { } public async executeTask(extension: IExtensionDescription, task: vscode.Task): Promise { + if (!task.execution) { + throw new Error('Tasks to execute must include an execution'); + } + const dto = TaskDTO.from(task, extension); if (dto === undefined) { throw new Error('Task is not valid'); diff --git a/src/vs/workbench/api/common/extHostTesting.ts b/src/vs/workbench/api/common/extHostTesting.ts new file mode 100644 index 000000000..02fc32cbf --- /dev/null +++ b/src/vs/workbench/api/common/extHostTesting.ts @@ -0,0 +1,794 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { mapFind } from 'vs/base/common/arrays'; +import { disposableTimeout } from 'vs/base/common/async'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { throttle } from 'vs/base/common/decorators'; +import { Emitter } from 'vs/base/common/event'; +import { once } from 'vs/base/common/functional'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { isDefined } from 'vs/base/common/types'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; +import { ExtHostTestingResource, ExtHostTestingShape, MainContext, MainThreadTestingShape } from 'vs/workbench/api/common/extHost.protocol'; +import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { TestItem } from 'vs/workbench/api/common/extHostTypeConverters'; +import { Disposable, RequiredTestItem } from 'vs/workbench/api/common/extHostTypes'; +import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; +import { AbstractIncrementalTestCollection, EMPTY_TEST_RESULT, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, RunTestForProviderRequest, RunTestsResult, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; +import type * as vscode from 'vscode'; + +const getTestSubscriptionKey = (resource: ExtHostTestingResource, uri: URI) => `${resource}:${uri.toString()}`; + +export class ExtHostTesting implements ExtHostTestingShape { + private readonly providers = new Map(); + private readonly proxy: MainThreadTestingShape; + private readonly ownedTests = new OwnedTestCollection(); + private readonly testSubscriptions = new Map(); + + private workspaceObservers: WorkspaceFolderTestObserverFactory; + private textDocumentObservers: TextDocumentTestObserverFactory; + + constructor(@IExtHostRpcService rpc: IExtHostRpcService, @IExtHostDocumentsAndEditors private readonly documents: IExtHostDocumentsAndEditors, @IExtHostWorkspace private readonly workspace: IExtHostWorkspace) { + this.proxy = rpc.getProxy(MainContext.MainThreadTesting); + this.workspaceObservers = new WorkspaceFolderTestObserverFactory(this.proxy); + this.textDocumentObservers = new TextDocumentTestObserverFactory(this.proxy, documents); + } + + /** + * Implements vscode.test.registerTestProvider + */ + public registerTestProvider(provider: vscode.TestProvider): vscode.Disposable { + const providerId = generateUuid(); + this.providers.set(providerId, provider); + this.proxy.$registerTestProvider(providerId); + + return new Disposable(() => { + this.providers.delete(providerId); + this.proxy.$unregisterTestProvider(providerId); + }); + } + + /** + * Implements vscode.test.createTextDocumentTestObserver + */ + public createTextDocumentTestObserver(document: vscode.TextDocument) { + return this.textDocumentObservers.checkout(document.uri); + } + + /** + * Implements vscode.test.createWorkspaceTestObserver + */ + public createWorkspaceTestObserver(workspaceFolder: vscode.WorkspaceFolder) { + return this.workspaceObservers.checkout(workspaceFolder.uri); + } + + /** + * Implements vscode.test.runTests + */ + public async runTests(req: vscode.TestRunOptions) { + await this.proxy.$runTests({ + tests: req.tests + // Find workspace items first, then owned tests, then document tests. + // If a test instance exists in both the workspace and document, prefer + // the workspace because it's less ephemeral. + .map(test => this.workspaceObservers.getMirroredTestDataByReference(test) + ?? mapFind(this.testSubscriptions.values(), c => c.collection.getTestByReference(test)) + ?? this.textDocumentObservers.getMirroredTestDataByReference(test)) + .filter(isDefined) + .map(item => ({ providerId: item.providerId, testId: item.id })), + debug: req.debug + }); + } + + /** + * Handles a request to read tests for a file, or workspace. + * @override + */ + public $subscribeToTests(resource: ExtHostTestingResource, uriComponents: UriComponents) { + const uri = URI.revive(uriComponents); + const subscriptionKey = getTestSubscriptionKey(resource, uri); + if (this.testSubscriptions.has(subscriptionKey)) { + return; + } + + let method: undefined | ((p: vscode.TestProvider) => vscode.TestHierarchy | undefined); + if (resource === ExtHostTestingResource.TextDocument) { + const document = this.documents.getDocument(uri); + if (document) { + method = p => p.createDocumentTestHierarchy?.(document.document); + } + } else { + const folder = this.workspace.getWorkspaceFolder(uri, false); + if (folder) { + method = p => p.createWorkspaceTestHierarchy?.(folder); + } + } + + if (!method) { + return; + } + + const disposable = new DisposableStore(); + const collection = disposable.add(this.ownedTests.createForHierarchy(diff => this.proxy.$publishDiff(resource, uriComponents, diff))); + for (const [id, provider] of this.providers) { + try { + const hierarchy = method(provider); + if (!hierarchy) { + continue; + } + + disposable.add(hierarchy); + collection.addRoot(hierarchy.root, id); + hierarchy.onDidChangeTest(e => collection.onItemChange(e, id)); + } catch (e) { + console.error(e); + } + } + + this.testSubscriptions.set(subscriptionKey, { store: disposable, collection }); + } + + /** + * Disposes of a previous subscription to tests. + * @override + */ + public $unsubscribeFromTests(resource: ExtHostTestingResource, uriComponents: UriComponents) { + const uri = URI.revive(uriComponents); + const subscriptionKey = getTestSubscriptionKey(resource, uri); + this.testSubscriptions.get(subscriptionKey)?.store.dispose(); + this.testSubscriptions.delete(subscriptionKey); + } + + /** + * Receives a test update from the main thread. Called (eventually) whenever + * tests change. + * @override + */ + public $acceptDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void { + if (resource === ExtHostTestingResource.TextDocument) { + this.textDocumentObservers.acceptDiff(URI.revive(uri), diff); + } else { + this.workspaceObservers.acceptDiff(URI.revive(uri), diff); + } + } + + /** + * Runs tests with the given set of IDs. Allows for test from multiple + * providers to be run. + * @override + */ + public async $runTestsForProvider(req: RunTestForProviderRequest): Promise { + const provider = this.providers.get(req.providerId); + if (!provider || !provider.runTests) { + return EMPTY_TEST_RESULT; + } + + const tests = req.ids.map(id => this.ownedTests.getTestById(id)?.actual).filter(isDefined); + if (!tests.length) { + return EMPTY_TEST_RESULT; + } + + await provider.runTests({ tests, debug: req.debug }, CancellationToken.None); + return EMPTY_TEST_RESULT; + } +} + +const keyMap: { [K in keyof Omit]: null } = { + label: null, + location: null, + state: null, + debuggable: null, + description: null, + runnable: null +}; + +const simpleProps = Object.keys(keyMap) as ReadonlyArray; + +const itemEqualityComparator = (a: vscode.TestItem) => { + const values: unknown[] = []; + for (const prop of simpleProps) { + values.push(a[prop]); + } + + return (b: vscode.TestItem) => { + for (let i = 0; i < simpleProps.length; i++) { + if (values[i] !== b[simpleProps[i]]) { + return false; + } + } + + return true; + }; +}; + +/** + * @private + */ +export interface OwnedCollectionTestItem extends InternalTestItem { + actual: vscode.TestItem; + previousChildren: Set; + previousEquals: (v: vscode.TestItem) => boolean; +} + +/** + * @private + */ +export class OwnedTestCollection { + protected readonly testIdToInternal = new Map(); + + /** + * Gets test information by ID, if it was defined and still exists in this + * extension host. + */ + public getTestById(id: string) { + return this.testIdToInternal.get(id); + } + + /** + * Creates a new test collection for a specific hierarchy for a workspace + * or document observation. + */ + public createForHierarchy(publishDiff: (diff: TestsDiff) => void = () => undefined) { + return new SingleUseTestCollection(this.testIdToInternal, publishDiff); + } +} + +/** + * Maintains tests created and registered for a single set of hierarchies + * for a workspace or document. + * @private + */ +export class SingleUseTestCollection implements IDisposable { + protected readonly testItemToInternal = new Map(); + protected diff: TestsDiff = []; + private disposed = false; + + constructor(private readonly testIdToInternal: Map, private readonly publishDiff: (diff: TestsDiff) => void) { } + + /** + * Adds a new root node to the collection. + */ + public addRoot(item: vscode.TestItem, providerId: string) { + this.addItem(item, providerId, null); + this.throttleSendDiff(); + } + + /** + * Gets test information by its reference, if it was defined and still exists + * in this extension host. + */ + public getTestByReference(item: vscode.TestItem) { + return this.testItemToInternal.get(item); + } + + /** + * Should be called when an item change is fired on the test provider. + */ + public onItemChange(item: vscode.TestItem, providerId: string) { + const existing = this.testItemToInternal.get(item); + if (!existing) { + if (!this.disposed) { + console.warn(`Received a TestProvider.onDidChangeTest for a test that wasn't seen before as a child.`); + } + return; + } + + this.addItem(item, providerId, existing.parent); + this.throttleSendDiff(); + } + + /** + * Gets a diff of all changes that have been made, and clears the diff queue. + */ + public collectDiff() { + const diff = this.diff; + this.diff = []; + return diff; + } + + public dispose() { + for (const item of this.testItemToInternal.values()) { + this.testIdToInternal.delete(item.id); + } + + this.testIdToInternal.clear(); + this.diff = []; + this.disposed = true; + } + + protected getId(): string { + return generateUuid(); + } + + private addItem(actual: vscode.TestItem, providerId: string, parent: string | null) { + let internal = this.testItemToInternal.get(actual); + if (!internal) { + internal = { + actual, + id: this.getId(), + parent, + item: TestItem.from(actual), + providerId, + previousChildren: new Set(), + previousEquals: itemEqualityComparator(actual), + }; + + this.testItemToInternal.set(actual, internal); + this.testIdToInternal.set(internal.id, internal); + this.diff.push([TestDiffOpType.Add, { id: internal.id, parent, providerId, item: internal.item }]); + } else if (!internal.previousEquals(actual)) { + internal.item = TestItem.from(actual); + internal.previousEquals = itemEqualityComparator(actual); + this.diff.push([TestDiffOpType.Update, { id: internal.id, parent, providerId, item: internal.item }]); + } + + // If there are children, track which ones are deleted + // and recursively and/update them. + if (actual.children) { + const deletedChildren = internal.previousChildren; + const currentChildren = new Set(); + for (const child of actual.children) { + const c = this.addItem(child, providerId, internal.id); + deletedChildren.delete(c.id); + currentChildren.add(c.id); + } + + for (const child of deletedChildren) { + this.removeItembyId(child); + } + + internal.previousChildren = currentChildren; + } + + + return internal; + } + + private removeItembyId(id: string) { + this.diff.push([TestDiffOpType.Remove, id]); + + const queue = [this.testIdToInternal.get(id)]; + while (queue.length) { + const item = queue.pop(); + if (!item) { + continue; + } + + this.testIdToInternal.delete(item.id); + this.testItemToInternal.delete(item.actual); + for (const child of item.previousChildren) { + queue.push(this.testIdToInternal.get(child)); + } + } + } + + @throttle(200) + protected throttleSendDiff() { + const diff = this.collectDiff(); + if (diff.length) { + this.publishDiff(diff); + } + } +} + +/** + * @private + */ +interface MirroredCollectionTestItem extends IncrementalTestCollectionItem { + revived: vscode.TestItem; + depth: number; + wrapped?: vscode.TestItem; +} + +class MirroredChangeCollector extends IncrementalChangeCollector { + private readonly added = new Set(); + private readonly updated = new Set(); + private readonly removed = new Set(); + + private readonly alreadyRemoved = new Set(); + + public get isEmpty() { + return this.added.size === 0 && this.removed.size === 0 && this.updated.size === 0; + } + + constructor(private readonly collection: MirroredTestCollection, private readonly emitter: Emitter) { + super(); + } + + /** + * @override + */ + public add(node: MirroredCollectionTestItem): void { + this.added.add(node); + } + + /** + * @override + */ + public update(node: MirroredCollectionTestItem): void { + Object.assign(node.revived, TestItem.to(node.item)); + if (!this.added.has(node)) { + this.updated.add(node); + } + } + + /** + * @override + */ + public remove(node: MirroredCollectionTestItem): void { + if (this.added.has(node)) { + this.added.delete(node); + return; + } + + this.updated.delete(node); + + if (node.parent && this.alreadyRemoved.has(node.parent)) { + this.alreadyRemoved.add(node.id); + return; + } + + this.removed.add(node); + } + + /** + * @override + */ + public getChangeEvent(): vscode.TestChangeEvent { + const { collection, added, updated, removed } = this; + return { + get added() { return [...added].map(collection.getPublicTestItem, collection); }, + get updated() { return [...updated].map(collection.getPublicTestItem, collection); }, + get removed() { return [...removed].map(collection.getPublicTestItem, collection); }, + get commonChangeAncestor() { + let ancestorPath: MirroredCollectionTestItem[] | undefined; + const buildAncestorPath = (node: MirroredCollectionTestItem | undefined) => { + if (!node) { + return undefined; + } + + // add the node and all its parents to the list of ancestors. If + // the node is detached, do not return a path (its parent will + // also have been passed to remove() and be present) + const path: MirroredCollectionTestItem[] = new Array(node.depth + 1); + for (let i = node.depth; i >= 0; i--) { + if (!node) { + return undefined; // detached child + } + + path[node.depth] = node; + node = node.parent ? collection.getMirroredTestDataById(node.parent) : undefined; + } + + return path; + }; + + const addAncestorPath = (node: MirroredCollectionTestItem) => { + // fast path: if the common ancestor is already the root, no more work to do + if (ancestorPath && ancestorPath.length === 0) { + return; + } + + const thisPath = buildAncestorPath(node); + if (!thisPath) { + return; + } + + if (!ancestorPath) { + ancestorPath = thisPath; + return; + } + + // removes node from the path to the ancestor that don't match + // the corresponding node in *this* path. + for (let i = ancestorPath.length - 1; i >= 0; i--) { + if (ancestorPath[i] !== thisPath[i]) { + ancestorPath.pop(); + } + } + }; + + const addParentAncestor = (node: MirroredCollectionTestItem) => { + if (ancestorPath && ancestorPath.length === 0) { + // no-op + } else if (node.parent === null) { + ancestorPath = []; + } else { + const parent = collection.getMirroredTestDataById(node.parent); + if (parent) { + addAncestorPath(parent); + } + } + }; + + for (const node of added) { addParentAncestor(node); } + for (const node of updated) { addAncestorPath(node); } + for (const node of removed) { addParentAncestor(node); } + + const ancestor = ancestorPath && ancestorPath[ancestorPath.length - 1]; + return ancestor ? collection.getPublicTestItem(ancestor) : null; + }, + }; + } + + public complete() { + if (!this.isEmpty) { + this.emitter.fire(this.getChangeEvent()); + } + } +} + +/** + * Maintains tests in this extension host sent from the main thread. + * @private + */ +export class MirroredTestCollection extends AbstractIncrementalTestCollection { + private changeEmitter = new Emitter(); + + /** + * Change emitter that fires with the same sematics as `TestObserver.onDidChangeTests`. + */ + public readonly onDidChangeTests = this.changeEmitter.event; + + /** + * Gets a list of root test items. + */ + public get rootTestItems() { + return this.getAllAsTestItem([...this.roots]); + } + + /** + * Translates the item IDs to TestItems for exposure to extensions. + */ + public getAllAsTestItem(itemIds: Iterable): vscode.TestItem[] { + let output: vscode.TestItem[] = []; + for (const itemId of itemIds) { + const item = this.items.get(itemId); + if (item) { + output.push(this.getPublicTestItem(item)); + } + } + + return output; + } + + /** + * + * If the test ID exists, returns its underlying ID. + */ + public getMirroredTestDataById(itemId: string) { + return this.items.get(itemId); + } + + /** + * If the test item is a mirrored test item, returns its underlying ID. + */ + public getMirroredTestDataByReference(item: vscode.TestItem) { + const id = getMirroredItemId(item); + return id ? this.items.get(id) : undefined; + } + + /** + * @override + */ + protected createItem(item: InternalTestItem, parent?: MirroredCollectionTestItem): MirroredCollectionTestItem { + return { ...item, revived: TestItem.to(item.item), depth: parent ? parent.depth + 1 : 0, children: new Set() }; + } + + /** + * @override + */ + protected createChangeCollector() { + return new MirroredChangeCollector(this, this.changeEmitter); + } + + /** + * Gets the public test item instance for the given mirrored record. + */ + public getPublicTestItem(item: MirroredCollectionTestItem): vscode.TestItem { + if (!item.wrapped) { + item.wrapped = new ExtHostTestItem(item, this); + } + + return item.wrapped; + } +} + +const getMirroredItemId = (item: vscode.TestItem) => { + return (item as any)[MirroredItemId] as string | undefined; +}; + +const MirroredItemId = Symbol('MirroredItemId'); + +class ExtHostTestItem implements vscode.TestItem, RequiredTestItem { + readonly #internal: MirroredCollectionTestItem; + readonly #collection: MirroredTestCollection; + + public get label() { return this.#internal.revived.label; } + public get description() { return this.#internal.revived.description; } + public get state() { return this.#internal.revived.state; } + public get location() { return this.#internal.revived.location; } + public get runnable() { return this.#internal.revived.runnable ?? true; } + public get debuggable() { return this.#internal.revived.debuggable ?? false; } + public get children() { + return this.#collection.getAllAsTestItem(this.#internal.children); + } + + get [MirroredItemId]() { return this.#internal.id; } + + constructor(internal: MirroredCollectionTestItem, collection: MirroredTestCollection) { + this.#internal = internal; + this.#collection = collection; + } + + public toJSON() { + const serialized: RequiredTestItem = { + label: this.label, + description: this.description, + state: this.state, + location: this.location, + runnable: this.runnable, + debuggable: this.debuggable, + children: this.children.map(c => (c as ExtHostTestItem).toJSON()), + }; + + return serialized; + } +} + +interface IObserverData { + observers: number; + tests: MirroredTestCollection; + listener: IDisposable; + pendingDeletion?: IDisposable; +} + +abstract class AbstractTestObserverFactory { + private readonly resources = new Map(); + + public checkout(resourceUri: URI): vscode.TestObserver { + const resourceKey = resourceUri.toString(); + const resource = this.resources.get(resourceKey) ?? this.createObserverData(resourceUri); + + resource.observers++; + + return { + onDidChangeTest: resource.tests.onDidChangeTests, + onDidDiscoverInitialTests: new Emitter().event, // todo@connor4312 + get tests() { + return resource.tests.rootTestItems; + }, + dispose: once(() => { + if (!--resource.observers) { + resource.pendingDeletion = this.eventuallyDispose(resourceUri); + } + }), + }; + } + + /** + * Gets the internal test data by its reference, in any observer. + */ + public getMirroredTestDataByReference(ref: vscode.TestItem) { + for (const { tests } of this.resources.values()) { + const v = tests.getMirroredTestDataByReference(ref); + if (v) { + return v; + } + } + + return undefined; + } + + /** + * Called when no observers are listening for the resource any more. Should + * defer unlistening on the resource, and return a disposiable + * to halt the process in case new listeners come in. + */ + protected eventuallyDispose(resourceUri: URI) { + return disposableTimeout(() => this.unlisten(resourceUri), 10 * 1000); + } + + /** + * Starts listening to test information for the given resource. + */ + protected abstract listen(resourceUri: URI, onDiff: (diff: TestsDiff) => void): Disposable; + + private createObserverData(resourceUri: URI): IObserverData { + const tests = new MirroredTestCollection(); + const listener = this.listen(resourceUri, diff => tests.apply(diff)); + const data: IObserverData = { observers: 0, tests, listener }; + this.resources.set(resourceUri.toString(), data); + return data; + } + + /** + * Called when a resource is no longer in use. + */ + protected unlisten(resourceUri: URI) { + const key = resourceUri.toString(); + const resource = this.resources.get(key); + if (resource) { + resource.observers = -1; + resource.pendingDeletion?.dispose(); + resource.listener.dispose(); + this.resources.delete(key); + } + } +} + +class WorkspaceFolderTestObserverFactory extends AbstractTestObserverFactory { + private diffListeners = new Map void>(); + + constructor(private readonly proxy: MainThreadTestingShape) { + super(); + } + + /** + * Publishees the diff for the workspace folder with the given uri. + */ + public acceptDiff(resourceUri: URI, diff: TestsDiff) { + this.diffListeners.get(resourceUri.toString())?.(diff); + } + + /** + * @override + */ + public listen(resourceUri: URI, onDiff: (diff: TestsDiff) => void) { + this.proxy.$subscribeToDiffs(ExtHostTestingResource.Workspace, resourceUri); + + const uriString = resourceUri.toString(); + this.diffListeners.set(uriString, onDiff); + + return new Disposable(() => { + this.proxy.$unsubscribeFromDiffs(ExtHostTestingResource.Workspace, resourceUri); + this.diffListeners.delete(uriString); + }); + } +} + +class TextDocumentTestObserverFactory extends AbstractTestObserverFactory { + private diffListeners = new Map void>(); + + constructor(private readonly proxy: MainThreadTestingShape, private documents: IExtHostDocumentsAndEditors) { + super(); + } + + /** + * Publishees the diff for the document with the given uri. + */ + public acceptDiff(resourceUri: URI, diff: TestsDiff) { + this.diffListeners.get(resourceUri.toString())?.(diff); + } + + /** + * @override + */ + public listen(resourceUri: URI, onDiff: (diff: TestsDiff) => void) { + const document = this.documents.getDocument(resourceUri); + if (!document) { + return new Disposable(() => undefined); + } + + const uriString = resourceUri.toString(); + this.diffListeners.set(uriString, onDiff); + + const disposeListener = this.documents.onDidRemoveDocuments(evt => { + if (evt.some(delta => delta.document.uri.toString() === uriString)) { + this.unlisten(resourceUri); + } + }); + + this.proxy.$subscribeToDiffs(ExtHostTestingResource.TextDocument, resourceUri); + return new Disposable(() => { + this.proxy.$unsubscribeFromDiffs(ExtHostTestingResource.TextDocument, resourceUri); + disposeListener.dispose(); + this.diffListeners.delete(uriString); + }); + } +} diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index d7194fc23..63d307018 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -17,7 +17,6 @@ import { TreeItemCollapsibleState, ThemeIcon, MarkdownString as MarkdownStringTy import { isUndefinedOrNull, isString } from 'vs/base/common/types'; import { equals, coalesce } from 'vs/base/common/arrays'; import { ILogService } from 'vs/platform/log/common/log'; -import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { MarkdownString } from 'vs/workbench/api/common/extHostTypeConverters'; import { IMarkdownString } from 'vs/base/common/htmlContent'; @@ -33,7 +32,6 @@ function toTreeItemLabel(label: any, extension: IExtensionDescription): ITreeIte if (label && typeof label === 'object' && typeof label.label === 'string') { - checkProposedApiEnabled(extension); let highlights: [number, number][] | undefined = undefined; if (Array.isArray(label.highlights)) { highlights = (<[number, number][]>label.highlights).filter((highlight => highlight.length === 2 && typeof highlight[0] === 'number' && typeof highlight[1] === 'number')); @@ -183,7 +181,7 @@ type TreeData = { message: boolean, element: T | Root | false }; interface TreeNode extends IDisposable { item: ITreeItem; - extensionItem: vscode.TreeItem2; + extensionItem: vscode.TreeItem; parent: TreeNode | Root; children?: TreeNode[]; } @@ -293,7 +291,7 @@ class ExtHostTreeView extends Disposable { return this.elements.get(treeItemHandle); } - reveal(element: T, options?: IRevealOptions): Promise { + reveal(element: T | undefined, options?: IRevealOptions): Promise { options = options ? options : { select: true, focus: false }; const select = isUndefinedOrNull(options.select) ? true : options.select; const focus = isUndefinedOrNull(options.focus) ? false : options.focus; @@ -302,10 +300,15 @@ class ExtHostTreeView extends Disposable { if (typeof this.dataProvider.getParent !== 'function') { return Promise.reject(new Error(`Required registered TreeDataProvider to implement 'getParent' method to access 'reveal' method`)); } - return this.refreshPromise - .then(() => this.resolveUnknownParentChain(element)) - .then(parentChain => this.resolveTreeNode(element, parentChain[parentChain.length - 1]) - .then(treeNode => this.proxy.$reveal(this.viewId, treeNode.item, parentChain.map(p => p.item), { select, focus, expand })), error => this.logService.error(error)); + + if (element) { + return this.refreshPromise + .then(() => this.resolveUnknownParentChain(element)) + .then(parentChain => this.resolveTreeNode(element, parentChain[parentChain.length - 1]) + .then(treeNode => this.proxy.$reveal(this.viewId, { item: treeNode.item, parentChain: parentChain.map(p => p.item) }, { select, focus, expand })), error => this.logService.error(error)); + } else { + return this.proxy.$reveal(this.viewId, undefined, { select, focus, expand }); + } } private _message: string = ''; @@ -375,7 +378,7 @@ class ExtHostTreeView extends Disposable { if (element) { const node = this.nodes.get(element); if (node) { - const resolve = await this.dataProvider.resolveTreeItem(element, node.extensionItem); + const resolve = await this.dataProvider.resolveTreeItem(node.extensionItem, element) ?? node.extensionItem; // Resolvable elements. Currently only tooltip. node.item.tooltip = this.getTooltip(resolve.tooltip); return node.item; @@ -565,13 +568,12 @@ class ExtHostTreeView extends Disposable { private getTooltip(tooltip?: string | vscode.MarkdownString): string | IMarkdownString | undefined { if (MarkdownStringType.isMarkdownString(tooltip)) { - checkProposedApiEnabled(this.extension); return MarkdownString.from(tooltip); } return tooltip; } - private createTreeNode(element: T, extensionTreeItem: vscode.TreeItem2, parent: TreeNode | Root): TreeNode { + private createTreeNode(element: T, extensionTreeItem: vscode.TreeItem, parent: TreeNode | Root): TreeNode { const disposable = new DisposableStore(); const handle = this.createHandle(element, extensionTreeItem, parent); const icon = this.getLightIconPath(extensionTreeItem); @@ -600,7 +602,7 @@ class ExtHostTreeView extends Disposable { }; } - private getThemeIcon(extensionTreeItem: vscode.TreeItem2): ThemeIcon | undefined { + private getThemeIcon(extensionTreeItem: vscode.TreeItem): ThemeIcon | undefined { return extensionTreeItem.iconPath instanceof ThemeIcon ? extensionTreeItem.iconPath : undefined; } diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index ca249267d..29a50ae27 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -6,10 +6,11 @@ import { ExtHostTunnelServiceShape, MainContext, MainThreadTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import * as vscode from 'vscode'; -import { RemoteTunnel, TunnelOptions } from 'vs/platform/remote/common/tunnel'; +import { RemoteTunnel, TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; export interface TunnelDto { remoteAddress: { port: number, host: string }; @@ -32,7 +33,7 @@ export interface Tunnel extends vscode.Disposable { export interface IExtHostTunnelService extends ExtHostTunnelServiceShape { readonly _serviceBrand: undefined; - openTunnel(forward: TunnelOptions): Promise; + openTunnel(extension: IExtensionDescription, forward: TunnelOptions): Promise; getTunnels(): Promise; onDidChangeTunnels: vscode.Event; setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise; @@ -51,23 +52,17 @@ export class ExtHostTunnelService implements IExtHostTunnelService { this._proxy = extHostRpc.getProxy(MainContext.MainThreadTunnelService); } - async openTunnel(forward: TunnelOptions): Promise { + async openTunnel(extension: IExtensionDescription, forward: TunnelOptions): Promise { return undefined; } async getTunnels(): Promise { return []; } - async $findCandidatePorts(): Promise<{ host: string, port: number; detail: string; }[]> { - return []; - } - async $filterCandidates(candidates: { host: string, port: number, detail: string }[]): Promise { - return candidates.map(() => true); - } async setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise { await this._proxy.$tunnelServiceReady(); return { dispose: () => { } }; } - $forwardPort(tunnelOptions: TunnelOptions): Promise | undefined { return undefined; } + $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise | undefined { return undefined; } async $closeTunnel(remote: { host: string, port: number }): Promise { } async $onDidTunnelsChange(): Promise { } } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index c0eb2a21f..d80dfae4b 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -7,13 +7,12 @@ import * as modes from 'vs/editor/common/modes'; import * as types from './extHostTypes'; import * as search from 'vs/workbench/contrib/search/common/search'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; -import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { IDecorationOptions, IThemeDecorationRenderOptions, IDecorationRenderOptions, IContentDecorationRenderOptions } from 'vs/editor/common/editorCommon'; import { EndOfLineSequence, TrackedRangeStickiness } from 'vs/editor/common/model'; import type * as vscode from 'vscode'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ProgressLocation as MainProgressLocation } from 'vs/platform/progress/common/progress'; -import { SaveReason } from 'vs/workbench/common/editor'; +import { EditorGroupColumn, SaveReason } from 'vs/workbench/common/editor'; import { IPosition } from 'vs/editor/common/core/position'; import * as editorRange from 'vs/editor/common/core/range'; import { ISelection } from 'vs/editor/common/core/selection'; @@ -33,6 +32,7 @@ import { RenderLineNumbersType } from 'vs/editor/common/config/editorOptions'; import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; import { CellOutputKind, IDisplayOutput, INotebookDecorationRenderOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ITestItem, ITestState } from 'vs/workbench/contrib/testing/common/testCollection'; export interface PositionLike { line: number; @@ -220,7 +220,7 @@ export namespace DiagnosticSeverity { } export namespace ViewColumn { - export function from(column?: vscode.ViewColumn): EditorViewColumn { + export function from(column?: vscode.ViewColumn): EditorGroupColumn { if (typeof column === 'number' && column >= types.ViewColumn.One) { return column - 1; // adjust zero index (ViewColumn.ONE => 0) } @@ -232,12 +232,12 @@ export namespace ViewColumn { return ACTIVE_GROUP; // default is always the active group } - export function to(position: EditorViewColumn): vscode.ViewColumn { + export function to(position: EditorGroupColumn): vscode.ViewColumn { if (typeof position === 'number' && position >= 0) { return position + 1; // adjust to index (ViewColumn.ONE => 1) } - throw new Error(`invalid 'EditorViewColumn'`); + throw new Error(`invalid 'EditorGroupColumn'`); } } @@ -274,8 +274,8 @@ export namespace MarkdownString { if (isCodeblock(markup)) { const { language, value } = markup; res = { value: '```' + language + '\n' + value + '\n```\n' }; - } else if (htmlContent.isMarkdownString(markup)) { - res = markup; + } else if (types.MarkdownString.isMarkdownString(markup)) { + res = { value: markup.value, isTrusted: markup.isTrusted, supportThemeIcons: markup.supportThemeIcons }; } else if (typeof markup === 'string') { res = { value: markup }; } else { @@ -343,7 +343,7 @@ export namespace MarkdownString { return result; } - export function fromStrict(value: string | types.MarkdownString): undefined | string | htmlContent.IMarkdownString { + export function fromStrict(value: string | vscode.MarkdownString): undefined | string | htmlContent.IMarkdownString { if (!value) { return undefined; } @@ -1183,6 +1183,7 @@ export namespace FoldingRangeKind { export interface TextEditorOpenOptions extends vscode.TextDocumentShowOptions { background?: boolean; + override?: boolean; } export namespace TextEditorOpenOptions { @@ -1194,6 +1195,7 @@ export namespace TextEditorOpenOptions { inactive: options.background, preserveFocus: options.preserveFocus, selection: typeof options.selection === 'object' ? Range.from(options.selection) : undefined, + override: typeof options.override === 'boolean' ? false : undefined }; } @@ -1253,50 +1255,6 @@ export namespace LanguageSelector { } } -export namespace LogLevel { - export function from(extLevel: types.LogLevel): _MainLogLevel { - switch (extLevel) { - case types.LogLevel.Trace: - return _MainLogLevel.Trace; - case types.LogLevel.Debug: - return _MainLogLevel.Debug; - case types.LogLevel.Info: - return _MainLogLevel.Info; - case types.LogLevel.Warning: - return _MainLogLevel.Warning; - case types.LogLevel.Error: - return _MainLogLevel.Error; - case types.LogLevel.Critical: - return _MainLogLevel.Critical; - case types.LogLevel.Off: - return _MainLogLevel.Off; - default: - return _MainLogLevel.Info; - } - } - - export function to(mainLevel: _MainLogLevel): types.LogLevel { - switch (mainLevel) { - case _MainLogLevel.Trace: - return types.LogLevel.Trace; - case _MainLogLevel.Debug: - return types.LogLevel.Debug; - case _MainLogLevel.Info: - return types.LogLevel.Info; - case _MainLogLevel.Warning: - return types.LogLevel.Warning; - case _MainLogLevel.Error: - return types.LogLevel.Error; - case _MainLogLevel.Critical: - return types.LogLevel.Critical; - case _MainLogLevel.Off: - return types.LogLevel.Off; - default: - return types.LogLevel.Info; - } - } -} - export namespace NotebookCellOutput { export function from(output: types.NotebookCellOutput): IDisplayOutput { return output.toJSON(); @@ -1395,3 +1353,64 @@ export namespace NotebookDecorationRenderOptions { }; } } + +export namespace TestState { + export function from(item: vscode.TestState): ITestState { + return { + runState: item.runState, + duration: item.duration, + messages: item.messages.map(message => ({ + message: MarkdownString.fromStrict(message.message) || '', + severity: message.severity, + expectedOutput: message.expectedOutput, + actualOutput: message.actualOutput, + location: message.location ? location.from(message.location) : undefined, + })), + }; + } + + export function to(item: ITestState): vscode.TestState { + return new types.TestState( + item.runState, + item.messages.map(message => ({ + message: typeof message.message === 'string' ? message.message : MarkdownString.to(message.message), + severity: message.severity, + expectedOutput: message.expectedOutput, + actualOutput: message.actualOutput, + location: message.location && location.to({ + range: message.location.range, + uri: URI.revive(message.location.uri) + }), + })), + item.duration, + ); + } +} + + +export namespace TestItem { + export function from(item: vscode.TestItem): ITestItem { + return { + label: item.label, + location: item.location ? location.from(item.location) : undefined, + debuggable: item.debuggable, + description: item.description, + runnable: item.runnable, + state: TestState.from(item.state), + }; + } + + export function to(item: ITestItem): vscode.TestItem { + return { + label: item.label, + location: item.location && location.to({ + range: item.location.range, + uri: URI.revive(item.location.uri) + }), + debuggable: item.debuggable, + description: item.description, + runnable: item.runnable, + state: TestState.to(item.state), + }; + } +} diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 6eb858eee..069c56583 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4,10 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { coalesceInPlace, equals } from 'vs/base/common/arrays'; -import { escapeCodicons } from 'vs/base/common/codicons'; import { illegalArgument } from 'vs/base/common/errors'; import { IRelativePattern } from 'vs/base/common/glob'; -import { isMarkdownString } from 'vs/base/common/htmlContent'; +import { isMarkdownString, MarkdownString as BaseMarkdownString } from 'vs/base/common/htmlContent'; import { ResourceMap } from 'vs/base/common/map'; import { isStringArray } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; @@ -1276,54 +1275,10 @@ export class CodeLens { } } - -export class CodeInset { - - range: Range; - height?: number; - - constructor(range: Range, height?: number) { - this.range = range; - this.height = height; - } -} - - @es5ClassCompat -export class MarkdownString { +export class MarkdownString implements vscode.MarkdownString { - value: string; - isTrusted?: boolean; - readonly supportThemeIcons?: boolean; - - constructor(value?: string, supportThemeIcons: boolean = false) { - this.value = value ?? ''; - this.supportThemeIcons = supportThemeIcons; - } - - appendText(value: string): MarkdownString { - // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash - this.value += (this.supportThemeIcons ? escapeCodicons(value) : value) - .replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&') - .replace(/\n/, '\n\n'); - - return this; - } - - appendMarkdown(value: string): MarkdownString { - this.value += value; - - return this; - } - - appendCodeblock(code: string, language: string = ''): MarkdownString { - this.value += '\n```'; - this.value += language; - this.value += '\n'; - this.value += code; - this.value += '\n```\n'; - return this; - } + readonly #delegate: BaseMarkdownString; static isMarkdownString(thing: any): thing is vscode.MarkdownString { if (thing instanceof MarkdownString) { @@ -1331,15 +1286,55 @@ export class MarkdownString { } return thing && thing.appendCodeblock && thing.appendMarkdown && thing.appendText && (thing.value !== undefined); } + + constructor(value?: string, supportThemeIcons: boolean = false) { + this.#delegate = new BaseMarkdownString(value, { supportThemeIcons }); + } + + get value(): string { + return this.#delegate.value; + } + set value(value: string) { + this.#delegate.value = value; + } + + get isTrusted(): boolean | undefined { + return this.#delegate.isTrusted; + } + + set isTrusted(value: boolean | undefined) { + this.#delegate.isTrusted = value; + } + + get supportThemeIcons(): boolean | undefined { + return this.#delegate.supportThemeIcons; + } + + appendText(value: string): vscode.MarkdownString { + this.#delegate.appendText(value); + return this; + } + + appendMarkdown(value: string): vscode.MarkdownString { + this.#delegate.appendMarkdown(value); + return this; + } + + appendCodeblock(value: string, language?: string): vscode.MarkdownString { + this.#delegate.appendCodeblock(language ?? '', value); + return this; + } + + } @es5ClassCompat export class ParameterInformation { label: string | [number, number]; - documentation?: string | MarkdownString; + documentation?: string | vscode.MarkdownString; - constructor(label: string | [number, number], documentation?: string | MarkdownString) { + constructor(label: string | [number, number], documentation?: string | vscode.MarkdownString) { this.label = label; this.documentation = documentation; } @@ -1349,11 +1344,11 @@ export class ParameterInformation { export class SignatureInformation { label: string; - documentation?: string | MarkdownString; + documentation?: string | vscode.MarkdownString; parameters: ParameterInformation[]; activeParameter?: number; - constructor(label: string, documentation?: string | MarkdownString) { + constructor(label: string, documentation?: string | vscode.MarkdownString) { this.label = label; this.documentation = documentation; this.parameters = []; @@ -1439,7 +1434,7 @@ export class CompletionItem implements vscode.CompletionItem { kind?: CompletionItemKind; tags?: CompletionItemTag[]; detail?: string; - documentation?: string | MarkdownString; + documentation?: string | vscode.MarkdownString; sortText?: string; filterText?: string; preselect?: boolean; @@ -2218,13 +2213,18 @@ export enum ConfigurationTarget { @es5ClassCompat export class RelativePattern implements IRelativePattern { base: string; - baseFolder?: URI; - pattern: string; - constructor(base: vscode.WorkspaceFolder | string, pattern: string) { + // expose a `baseFolder: URI` property as a workaround for the short-coming + // of `IRelativePattern` only supporting `base: string` which always translates + // to a `file://` URI. With `baseFolder` we can support non-file based folders + // in searches + // (https://github.com/microsoft/vscode/commit/6326543b11cf4998c8fd1564cab5c429a2416db3) + readonly baseFolder?: URI; + + constructor(base: vscode.WorkspaceFolder | URI | string, pattern: string) { if (typeof base !== 'string') { - if (!base || !URI.isUri(base.uri)) { + if (!base || !URI.isUri(base) && !URI.isUri(base.uri)) { throw illegalArgument('base'); } } @@ -2234,7 +2234,11 @@ export class RelativePattern implements IRelativePattern { } if (typeof base === 'string') { + this.baseFolder = URI.file(base); this.base = base; + } else if (URI.isUri(base)) { + this.baseFolder = base; + this.base = base.fsPath; } else { this.baseFolder = base.uri; this.base = base.uri.fsPath; @@ -2369,16 +2373,6 @@ export class EvaluatableExpression implements vscode.EvaluatableExpression { } } -export enum LogLevel { - Trace = 1, - Debug = 2, - Info = 3, - Warning = 4, - Error = 5, - Critical = 6, - Off = 7 -} - //#region file api export enum FileChangeType { @@ -2749,8 +2743,8 @@ export enum ExtensionKind { export class FileDecoration { static validate(d: FileDecoration): void { - if (d.badge && d.badge.length !== 1) { - throw new Error(`The 'badge'-property must be undefined or a single character`); + if (d.badge && d.badge.length !== 1 && d.badge.length !== 2) { + throw new Error(`The 'badge'-property must be undefined or a short character`); } if (!d.color && !d.badge && !d.tooltip) { throw new Error(`The decoration is empty`); @@ -2920,3 +2914,64 @@ export enum StandardTokenType { String = 2, RegEx = 4 } + + +export class LinkedEditingRanges { + constructor(public readonly ranges: Range[], public readonly wordPattern?: RegExp) { + } +} + +//#region Testing +export enum TestRunState { + Unset = 0, + Running = 1, + Passed = 2, + Failed = 3, + Skipped = 4, + Errored = 5 +} + +export enum TestMessageSeverity { + Error = 0, + Warning = 1, + Information = 2, + Hint = 3 +} + +@es5ClassCompat +export class TestState { + #runState: TestRunState; + #duration?: number; + #messages: ReadonlyArray>; + + public get runState() { + return this.#runState; + } + + public get duration() { + return this.#duration; + } + + public get messages() { + return this.#messages; + } + + constructor(runState: TestRunState, messages: vscode.TestMessage[] = [], duration?: number) { + this.#runState = runState; + this.#messages = Object.freeze(messages.map(m => Object.freeze(m))); + this.#duration = duration; + } +} + +type AllowedUndefined = 'description' | 'location'; + +/** + * Test item without any optional properties. Only some properties are + * permitted to be undefined, but they must still exist. + */ +export type RequiredTestItem = { + [K in keyof Required]: K extends AllowedUndefined ? vscode.TestItem[K] : Required[K] +}; + + +//#endregion diff --git a/src/vs/workbench/api/common/extHostWebviewPanels.ts b/src/vs/workbench/api/common/extHostWebviewPanels.ts index d29fdc631..797702b88 100644 --- a/src/vs/workbench/api/common/extHostWebviewPanels.ts +++ b/src/vs/workbench/api/common/extHostWebviewPanels.ts @@ -12,7 +12,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { convertWebviewOptions, ExtHostWebview, ExtHostWebviews, toExtensionData } from 'vs/workbench/api/common/extHostWebview'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; -import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; +import { EditorGroupColumn } from 'vs/workbench/common/editor'; import type * as vscode from 'vscode'; import * as extHostProtocol from './extHost.protocol'; import * as extHostTypes from './extHostTypes'; @@ -273,7 +273,7 @@ export class ExtHostWebviewPanels implements extHostProtocol.ExtHostWebviewPanel viewType: string, title: string, state: any, - position: EditorViewColumn, + position: EditorGroupColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions ): Promise { const entry = this._serializers.get(viewType); diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 7ba5fbca0..3e0de07e3 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -10,17 +10,18 @@ import { Emitter, Event } from 'vs/base/common/event'; import { TernarySearchTree } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; import { Counter } from 'vs/base/common/numbers'; -import { isLinux } from 'vs/base/common/platform'; import { basename, basenameOrAuthority, dirname, isEqual, relativePath } from 'vs/base/common/resources'; import { compare } from 'vs/base/common/strings'; import { withUndefinedAsNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { Severity } from 'vs/platform/notification/common/notification'; import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { Range, RelativePattern } from 'vs/workbench/api/common/extHostTypes'; @@ -59,6 +60,11 @@ function delta(oldFolders: vscode.WorkspaceFolder[], newFolders: vscode.Workspac return arrayDelta(oldSortedFolders, newSortedFolders, compare); } +function ignorePathCasing(uri: URI, extHostFileSystemInfo: IExtHostFileSystemInfo): boolean { + const capabilities = extHostFileSystemInfo.getCapabilities(uri.scheme); + return !(capabilities && (capabilities & FileSystemProviderCapabilities.PathCaseSensitive)); +} + interface MutableWorkspaceFolder extends vscode.WorkspaceFolder { name: string; index: number; @@ -66,7 +72,7 @@ interface MutableWorkspaceFolder extends vscode.WorkspaceFolder { class ExtHostWorkspaceImpl extends Workspace { - static toExtHostWorkspace(data: IWorkspaceData | null, previousConfirmedWorkspace?: ExtHostWorkspaceImpl, previousUnconfirmedWorkspace?: ExtHostWorkspaceImpl): { workspace: ExtHostWorkspaceImpl | null, added: vscode.WorkspaceFolder[], removed: vscode.WorkspaceFolder[] } { + static toExtHostWorkspace(data: IWorkspaceData | null, previousConfirmedWorkspace: ExtHostWorkspaceImpl | undefined, previousUnconfirmedWorkspace: ExtHostWorkspaceImpl | undefined, extHostFileSystemInfo: IExtHostFileSystemInfo): { workspace: ExtHostWorkspaceImpl | null, added: vscode.WorkspaceFolder[], removed: vscode.WorkspaceFolder[] } { if (!data) { return { workspace: null, added: [], removed: [] }; } @@ -99,7 +105,7 @@ class ExtHostWorkspaceImpl extends Workspace { // make sure to restore sort order based on index newWorkspaceFolders.sort((f1, f2) => f1.index < f2.index ? -1 : 1); - const workspace = new ExtHostWorkspaceImpl(id, name, newWorkspaceFolders, configuration ? URI.revive(configuration) : null, !!isUntitled); + const workspace = new ExtHostWorkspaceImpl(id, name, newWorkspaceFolders, configuration ? URI.revive(configuration) : null, !!isUntitled, uri => ignorePathCasing(uri, extHostFileSystemInfo)); const { added, removed } = delta(oldWorkspace ? oldWorkspace.workspaceFolders : [], workspace.workspaceFolders, compareWorkspaceFolderByUri); return { workspace, added, removed }; @@ -117,10 +123,11 @@ class ExtHostWorkspaceImpl extends Workspace { } private readonly _workspaceFolders: vscode.WorkspaceFolder[] = []; - private readonly _structure = TernarySearchTree.forUris(!isLinux); + private readonly _structure: TernarySearchTree; - constructor(id: string, private _name: string, folders: vscode.WorkspaceFolder[], configuration: URI | null, private _isUntitled: boolean) { - super(id, folders.map(f => new WorkspaceFolder(f)), configuration); + constructor(id: string, private _name: string, folders: vscode.WorkspaceFolder[], configuration: URI | null, private _isUntitled: boolean, ignorePathCasing: (key: URI) => boolean) { + super(id, folders.map(f => new WorkspaceFolder(f)), configuration, ignorePathCasing); + this._structure = TernarySearchTree.forUris(ignorePathCasing); // setup the workspace folder data structure folders.forEach(folder => { @@ -170,22 +177,25 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac private readonly _proxy: MainThreadWorkspaceShape; private readonly _messageService: MainThreadMessageServiceShape; + private readonly _extHostFileSystemInfo: IExtHostFileSystemInfo; private readonly _activeSearchCallbacks: ((match: IRawFileMatch2) => any)[] = []; constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, @IExtHostInitDataService initData: IExtHostInitDataService, + @IExtHostFileSystemInfo extHostFileSystemInfo: IExtHostFileSystemInfo, @ILogService logService: ILogService, ) { this._logService = logService; + this._extHostFileSystemInfo = extHostFileSystemInfo; this._requestIdProvider = new Counter(); this._barrier = new Barrier(); this._proxy = extHostRpc.getProxy(MainContext.MainThreadWorkspace); this._messageService = extHostRpc.getProxy(MainContext.MainThreadMessageService); const data = initData.workspace; - this._confirmedWorkspace = data ? new ExtHostWorkspaceImpl(data.id, data.name, [], data.configuration ? URI.revive(data.configuration) : null, !!data.isUntitled) : undefined; + this._confirmedWorkspace = data ? new ExtHostWorkspaceImpl(data.id, data.name, [], data.configuration ? URI.revive(data.configuration) : null, !!data.isUntitled, uri => ignorePathCasing(uri, extHostFileSystemInfo)) : undefined; } $initializeWorkspace(data: IWorkspaceData | null): void { @@ -391,13 +401,13 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac configuration: this._actualWorkspace.configuration, folders, isUntitled: this._actualWorkspace.isUntitled - } as IWorkspaceData, this._actualWorkspace).workspace || undefined; + } as IWorkspaceData, this._actualWorkspace, undefined, this._extHostFileSystemInfo).workspace || undefined; } } $acceptWorkspaceData(data: IWorkspaceData | null): void { - const { workspace, added, removed } = ExtHostWorkspaceImpl.toExtHostWorkspace(data, this._confirmedWorkspace, this._unconfirmedWorkspace); + const { workspace, added, removed } = ExtHostWorkspaceImpl.toExtHostWorkspace(data, this._confirmedWorkspace, this._unconfirmedWorkspace, this._extHostFileSystemInfo); // Update our workspace object. We have a confirmed workspace, so we drop our // unconfirmed workspace. @@ -457,17 +467,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac } : options.previewOptions; - let includePattern: string | undefined; - let folder: URI | undefined; - if (options.include) { - if (typeof options.include === 'string') { - includePattern = options.include; - } else { - includePattern = options.include.pattern; - folder = (options.include as RelativePattern).baseFolder || URI.file(options.include.base); - } - } - + const { includePattern, folder } = parseSearchInclude(options.include); const excludePattern = (typeof options.exclude === 'string') ? options.exclude : options.exclude ? options.exclude.pattern : undefined; const queryOptions: ITextQueryBuilderOptions = { @@ -562,14 +562,12 @@ function parseSearchInclude(include: RelativePattern | string | undefined): { in includePattern = include; } else { includePattern = include.pattern; - - // include.base must be an absolute path includeFolder = include.baseFolder || URI.file(include.base); } } return { - includePattern: includePattern, + includePattern, folder: includeFolder }; } diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 2a393e99c..32027cbe2 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -73,19 +73,19 @@ const apiMenus: IAPIMenu[] = [ id: MenuId.DebugToolBar, description: localize('menus.debugToolBar', "The debug toolbar menu") }, - { - key: 'menuBar/webNavigation', - id: MenuId.MenubarWebNavigationMenu, - description: localize('menus.webNavigation', "The top level navigational menu (web only)"), - proposed: true, - supportsSubmenus: false - }, { key: 'menuBar/file', id: MenuId.MenubarFileMenu, description: localize('menus.file', "The top level file menu"), proposed: true }, + { + key: 'menuBar/home', + id: MenuId.MenubarHomeMenu, + description: localize('menus.home', "The home indicator context menu (web only)"), + proposed: true, + supportsSubmenus: false + }, { key: 'scm/title', id: MenuId.SCMTitle, @@ -461,7 +461,7 @@ namespace schema { type: 'string' }, enablement: { - description: localize('vscode.extension.contributes.commandType.precondition', '(Optional) Condition which must be true to enable the command'), + description: localize('vscode.extension.contributes.commandType.precondition', '(Optional) Condition which must be true to enable the command in the UI (menu and keybindings). Does not prevent executing the command by other means, like the `executeCommand`-api.'), type: 'string' }, icon: { @@ -586,6 +586,10 @@ submenusExtensionPoint.setHandler(extensions => { collector.warn(localize('submenuId.invalid.id', "`{0}` is not a valid submenu identifier", entry.value.id)); return; } + if (_submenus.has(entry.value.id)) { + collector.warn(localize('submenuId.duplicate.id', "The `{0}` submenu was already previously registered.", entry.value.id)); + return; + } if (!entry.value.label) { collector.warn(localize('submenuId.invalid.label', "`{0}` is not a valid submenu label", entry.value.label)); return; @@ -616,6 +620,7 @@ submenusExtensionPoint.setHandler(extensions => { const _apiMenusByKey = new Map(Iterable.map(Iterable.from(apiMenus), menu => ([menu.key, menu]))); const _menuRegistrations = new DisposableStore(); +const _submenuMenuItems = new Map>(); const menusExtensionPoint = ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: (schema.IUserFriendlyMenuItem | schema.IUserFriendlySubmenuItem)[] }>({ extensionPoint: 'menus', @@ -627,6 +632,7 @@ menusExtensionPoint.setHandler(extensions => { // remove all previous menu registrations _menuRegistrations.clear(); + _submenuMenuItems.clear(); const items: { id: MenuId, item: IMenuItem | ISubmenuItem }[] = []; @@ -694,6 +700,20 @@ menusExtensionPoint.setHandler(extensions => { continue; } + let submenuRegistrations = _submenuMenuItems.get(menu.id.id); + + if (!submenuRegistrations) { + submenuRegistrations = new Set(); + _submenuMenuItems.set(menu.id.id, submenuRegistrations); + } + + if (submenuRegistrations.has(submenu.id.id)) { + collector.warn(localize('submenuItem.duplicate', "The `{0}` submenu was already contributed to the `{1}` menu.", menuItem.submenu, entry.key)); + continue; + } + + submenuRegistrations.add(submenu.id.id); + item = { submenu: submenu.id, icon: submenu.icon, title: submenu.label, group: undefined, order: undefined, when: undefined }; } diff --git a/src/vs/workbench/api/common/shared/editor.ts b/src/vs/workbench/api/common/shared/editor.ts deleted file mode 100644 index 247c9b178..000000000 --- a/src/vs/workbench/api/common/shared/editor.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 { IEditorGroupsService, IEditorGroup, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { GroupIdentifier } from 'vs/workbench/common/editor'; -import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; - -export type EditorViewColumn = number; - -export function viewColumnToEditorGroup(editorGroupService: IEditorGroupsService, position?: EditorViewColumn): GroupIdentifier { - if (typeof position !== 'number' || position === ACTIVE_GROUP) { - return ACTIVE_GROUP; // prefer active group when position is undefined or passed in as such - } - - const groups = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE); - - let candidate = groups[position]; - if (candidate) { - return candidate.id; // found direct match - } - - let firstGroup = groups[0]; - if (groups.length === 1 && firstGroup.count === 0) { - return firstGroup.id; // first editor should always open in first group independent from position provided - } - - return SIDE_GROUP; // open to the side if group not found or we are instructed to -} - -export function editorGroupToViewColumn(editorGroupService: IEditorGroupsService, editorGroup: IEditorGroup | GroupIdentifier): EditorViewColumn { - const group = (typeof editorGroup === 'number') ? editorGroupService.getGroup(editorGroup) : editorGroup; - if (!group) { - throw new Error('Invalid group provided'); - } - - return editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE).indexOf(group); -} \ No newline at end of file diff --git a/src/vs/workbench/api/common/shared/tasks.ts b/src/vs/workbench/api/common/shared/tasks.ts index 17396177e..4de859f67 100644 --- a/src/vs/workbench/api/common/shared/tasks.ts +++ b/src/vs/workbench/api/common/shared/tasks.ts @@ -113,7 +113,7 @@ export interface TaskProcessStartedDTO { export interface TaskProcessEndedDTO { id: string; - exitCode: number; + exitCode: number | undefined; } diff --git a/src/vs/workbench/api/node/extHostCLIServer.ts b/src/vs/workbench/api/node/extHostCLIServer.ts index b3857616f..bb5ec1cd7 100644 --- a/src/vs/workbench/api/node/extHostCLIServer.ts +++ b/src/vs/workbench/api/node/extHostCLIServer.ts @@ -15,7 +15,7 @@ import { ILogService } from 'vs/platform/log/common/log'; export interface OpenCommandPipeArgs { type: 'open'; fileURIs?: string[]; - folderURIs: string[]; + folderURIs?: string[]; forceNewWindow?: boolean; diffMode?: boolean; addMode?: boolean; @@ -24,6 +24,11 @@ export interface OpenCommandPipeArgs { waitMarkerFilePath?: string; } +export interface OpenExternalCommandPipeArgs { + type: 'openExternal'; + uris: string[]; +} + export interface StatusPipeArgs { type: 'status'; } @@ -34,6 +39,8 @@ export interface RunCommandPipeArgs { args: any[]; } +export type PipeCommand = OpenCommandPipeArgs | StatusPipeArgs | RunCommandPipeArgs | OpenExternalCommandPipeArgs; + export interface ICommandsExecuter { executeCommand(id: string, ...args: any[]): Promise; } @@ -73,11 +80,14 @@ export class CLIServerBase { req.setEncoding('utf8'); req.on('data', (d: string) => chunks.push(d)); req.on('end', () => { - const data: OpenCommandPipeArgs | StatusPipeArgs | RunCommandPipeArgs | any = JSON.parse(chunks.join('')); + const data: PipeCommand | any = JSON.parse(chunks.join('')); switch (data.type) { case 'open': this.open(data, res); break; + case 'openExternal': + this.openExternal(data, res); + break; case 'status': this.getStatus(data, res); break; @@ -133,6 +143,14 @@ export class CLIServerBase { res.end(); } + private openExternal(data: OpenExternalCommandPipeArgs, res: http.ServerResponse) { + for (const uri of data.uris) { + this._commands.executeCommand('_workbench.openExternal', URI.parse(uri), { allowTunneling: true }); + } + res.writeHead(200); + res.end(); + } + private async getStatus(data: StatusPipeArgs, res: http.ServerResponse) { try { const status = await this._commands.executeCommand('_issues.getSystemStatus'); diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index c68c242a4..ff76f109b 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -14,7 +14,6 @@ import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensi import { IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { IAdapterDescriptor } from 'vs/workbench/contrib/debug/common/debug'; import { IExtHostConfiguration, ExtHostConfigProvider } from '../common/extHostConfiguration'; -import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; @@ -24,13 +23,14 @@ import { SignService } from 'vs/platform/sign/node/signService'; import { hasChildProcesses, prepareCommand, runInExternalTerminal } from 'vs/workbench/contrib/debug/node/terminals'; import { IDisposable } from 'vs/base/common/lifecycle'; import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver'; +import { createCancelablePromise, firstParallel } from 'vs/base/common/async'; export class ExtHostDebugService extends ExtHostDebugServiceBase { readonly _serviceBrand: undefined; - private _integratedTerminalInstance?: vscode.Terminal; + private _integratedTerminalInstances = new DebugTerminalCollection(); private _terminalDisposedListener: IDisposable | undefined; constructor( @@ -39,10 +39,9 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { @IExtHostExtensionService extensionService: IExtHostExtensionService, @IExtHostDocumentsAndEditors editorsService: IExtHostDocumentsAndEditors, @IExtHostConfiguration configurationService: IExtHostConfiguration, - @IExtHostTerminalService private _terminalService: IExtHostTerminalService, - @IExtHostCommands commandService: IExtHostCommands + @IExtHostTerminalService private _terminalService: IExtHostTerminalService ) { - super(extHostRpcService, workspaceService, extensionService, editorsService, configurationService, commandService); + super(extHostRpcService, workspaceService, extensionService, editorsService, configurationService); } protected createDebugAdapter(adapter: IAdapterDescriptor, session: ExtHostDebugSession): AbstractDebugAdapter | undefined { @@ -76,40 +75,44 @@ 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 => { - if (this._integratedTerminalInstance && this._integratedTerminalInstance === terminal) { - this._integratedTerminalInstance = undefined; - } + this._integratedTerminalInstances.onTerminalClosed(terminal); }); } - let needNewTerminal = true; // be pessimistic - if (this._integratedTerminalInstance) { - const pid = await this._integratedTerminalInstance.processId; - needNewTerminal = await hasChildProcesses(pid); // if no processes running in terminal reuse terminal - } - const configProvider = await this._configurationService.getConfigProvider(); const shell = this._terminalService.getDefaultShell(true, configProvider); + const shellArgs = this._terminalService.getDefaultShellArgs(true, configProvider); + + const shellConfig = JSON.stringify({ shell, shellArgs }); + let terminal = await this._integratedTerminalInstances.checkout(shellConfig); + let cwdForPrepareCommand: string | undefined; + let giveShellTimeToInitialize = false; - if (needNewTerminal || !this._integratedTerminalInstance) { - + if (!terminal) { const options: vscode.TerminalOptions = { shellPath: shell, - // shellArgs: this._terminalService._getDefaultShellArgs(configProvider), + shellArgs: shellArgs, cwd: args.cwd, name: args.title || nls.localize('debug.terminal.title', "debuggee"), }; - this._integratedTerminalInstance = this._terminalService.createTerminalFromOptions(options, true); + giveShellTimeToInitialize = true; + terminal = this._terminalService.createTerminalFromOptions(options, true); + this._integratedTerminalInstances.insert(terminal, shellConfig); + } else { cwdForPrepareCommand = args.cwd; } - const terminal = this._integratedTerminalInstance; - terminal.show(); - const shellProcessId = await this._integratedTerminalInstance.processId; + const shellProcessId = await terminal.processId; + + if (giveShellTimeToInitialize) { + // give a new terminal some time to initialize the shell + await new Promise(resolve => setTimeout(resolve, 1000)); + } + const command = prepareCommand(shell, args.args, cwdForPrepareCommand, args.env); terminal.sendText(command, true); @@ -123,6 +126,49 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { } protected createVariableResolver(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider): AbstractVariableResolverService { - return new ExtHostVariableResolverService(folders, editorService, configurationService, process.env as env.IProcessEnvironment); + return new ExtHostVariableResolverService(folders, editorService, configurationService, process.env as env.IProcessEnvironment, this._workspaceService); + } +} + +class DebugTerminalCollection { + /** + * Delay before a new terminal is a candidate for reuse. See #71850 + */ + private static minUseDelay = 1000; + + private _terminalInstances = new Map(); + + public async checkout(config: string) { + const entries = [...this._terminalInstances.keys()]; + const promises = entries.map((terminal) => createCancelablePromise(async ct => { + const pid = await terminal.processId; + if (await hasChildProcesses(pid)) { + return null; + } + + // important: date check and map operations must be synchronous + const now = Date.now(); + const termInfo = this._terminalInstances.get(terminal); + if (!termInfo || termInfo.lastUsedAt + DebugTerminalCollection.minUseDelay > now || ct.isCancellationRequested) { + return null; + } + + if (termInfo.config !== config) { + return null; + } + + termInfo.lastUsedAt = now; + return terminal; + })); + + return await firstParallel(promises, (t): t is vscode.Terminal => !!t); + } + + public insert(terminal: vscode.Terminal, termConfig: string) { + this._terminalInstances.set(terminal, { lastUsedAt: Date.now(), config: termConfig }); + } + + public onTerminalClosed(terminal: vscode.Terminal) { + this._terminalInstances.delete(terminal); } } diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index 01fc86aec..9d5951b84 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -50,6 +50,10 @@ export class ExtHostTask extends ExtHostTaskBase { } public async executeTask(extension: IExtensionDescription, task: vscode.Task): Promise { + if (!task.execution) { + throw new Error('Tasks to execute must include an execution'); + } + const tTask = (task as types.Task); // We have a preserved ID. So the task didn't change. if (tTask._id !== undefined) { diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index cdc252bc1..01a34ff3f 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -23,6 +23,7 @@ import { BaseExtHostTerminalService, ExtHostTerminal } from 'vs/workbench/api/co import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { withNullAsUndefined } from 'vs/base/common/types'; export class ExtHostTerminalService extends BaseExtHostTerminalService { @@ -56,7 +57,15 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { public createTerminalFromOptions(options: vscode.TerminalOptions, isFeatureTerminal?: boolean): vscode.Terminal { const terminal = new ExtHostTerminal(this._proxy, options, options.name); this._terminals.push(terminal); - terminal.create(options.shellPath, options.shellArgs, options.cwd, options.env, /*options.waitOnExit*/ undefined, options.strictEnv, options.hideFromUser, isFeatureTerminal); + terminal.create( + withNullAsUndefined(options.shellPath), + withNullAsUndefined(options.shellArgs), + withNullAsUndefined(options.cwd), + withNullAsUndefined(options.env), + /*options.waitOnExit*/ undefined, + withNullAsUndefined(options.strictEnv), + withNullAsUndefined(options.hideFromUser), + withNullAsUndefined(isFeatureTerminal)); return terminal; } diff --git a/src/vs/workbench/api/node/extHostTunnelService.ts b/src/vs/workbench/api/node/extHostTunnelService.ts index ffd3bd982..922451d10 100644 --- a/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -16,7 +16,8 @@ import { isLinux } from 'vs/base/common/platform'; import { IExtHostTunnelService, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { asPromise } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; -import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; +import { TunnelOptions, TunnelCreationOptions } from 'vs/platform/remote/common/tunnel'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; class ExtensionTunnel implements vscode.Tunnel { private _onDispose: Emitter = new Emitter(); @@ -36,9 +37,9 @@ class ExtensionTunnel implements vscode.Tunnel { export class ExtHostTunnelService extends Disposable implements IExtHostTunnelService { readonly _serviceBrand: undefined; private readonly _proxy: MainThreadTunnelServiceShape; - private _forwardPortProvider: ((tunnelOptions: TunnelOptions) => Thenable | undefined) | undefined; + private _forwardPortProvider: ((tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => Thenable | undefined) | undefined; private _showCandidatePort: (host: string, port: number, detail: string) => Thenable = () => { return Promise.resolve(true); }; - private _extensionTunnels: Map> = new Map(); + private _extensionTunnels: Map> = new Map(); private _onDidChangeTunnels: Emitter = new Emitter(); onDidChangeTunnels: vscode.Event = this._onDidChangeTunnels.event; @@ -53,8 +54,8 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe } } - async openTunnel(forward: TunnelOptions): Promise { - const tunnel = await this._proxy.$openTunnel(forward); + async openTunnel(extension: IExtensionDescription, forward: TunnelOptions): Promise { + const tunnel = await this._proxy.$openTunnel(forward, extension.displayName); if (tunnel) { const disposableTunnel: vscode.Tunnel = new ExtensionTunnel(tunnel.remoteAddress, tunnel.localAddress, () => { return this._proxy.$closeTunnel(tunnel.remoteAddress); @@ -69,21 +70,25 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe return this._proxy.$getTunnels(); } - registerCandidateFinder(): Promise { - return this._proxy.$registerCandidateFinder(); - } - - $filterCandidates(candidates: { host: string, port: number, detail: string }[]): Promise { - return Promise.all(candidates.map(candidate => { - return this._showCandidatePort(candidate.host, candidate.port, candidate.detail); - })); + registerCandidateFinder(): void { + // Every two seconds, scan to see if the candidate ports have changed; + if (isLinux) { + let oldPorts: { host: string, port: number, detail: string }[] | undefined = undefined; + setInterval(async () => { + const newPorts = await this.findCandidatePorts(); + if (!oldPorts || (JSON.stringify(oldPorts) !== JSON.stringify(newPorts))) { + oldPorts = newPorts; + this._proxy.$onFoundNewCandidates(oldPorts.filter(async (candidate) => await this._showCandidatePort(candidate.host, candidate.port, candidate.detail))); + return; + } + }, 2000); + } } async setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise { if (provider) { if (provider.showCandidatePort) { this._showCandidatePort = provider.showCandidatePort; - await this._proxy.$setCandidateFilter(); } if (provider.tunnelFactory) { this._forwardPortProvider = provider.tunnelFactory; @@ -98,11 +103,14 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe }); } - async $closeTunnel(remote: { host: string, port: number }): Promise { + async $closeTunnel(remote: { host: string, port: number }, silent?: boolean): Promise { if (this._extensionTunnels.has(remote.host)) { const hostMap = this._extensionTunnels.get(remote.host)!; if (hostMap.has(remote.port)) { - hostMap.get(remote.port)!.dispose(); + if (silent) { + hostMap.get(remote.port)!.disposeListener.dispose(); + } + hostMap.get(remote.port)!.tunnel.dispose(); hostMap.delete(remote.port); } } @@ -112,16 +120,16 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe this._onDidChangeTunnels.fire(); } - $forwardPort(tunnelOptions: TunnelOptions): Promise | undefined { + $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise | undefined { if (this._forwardPortProvider) { - const providedPort = this._forwardPortProvider!(tunnelOptions); + const providedPort = this._forwardPortProvider(tunnelOptions, tunnelCreationOptions); if (providedPort !== undefined) { return asPromise(() => providedPort).then(tunnel => { if (!this._extensionTunnels.has(tunnelOptions.remoteAddress.host)) { this._extensionTunnels.set(tunnelOptions.remoteAddress.host, new Map()); } - this._extensionTunnels.get(tunnelOptions.remoteAddress.host)!.set(tunnelOptions.remoteAddress.port, tunnel); - this._register(tunnel.onDidDispose(() => this._proxy.$closeTunnel(tunnel.remoteAddress))); + const disposeListener = this._register(tunnel.onDidDispose(() => this._proxy.$closeTunnel(tunnel.remoteAddress))); + this._extensionTunnels.get(tunnelOptions.remoteAddress.host)!.set(tunnelOptions.remoteAddress.port, { tunnel, disposeListener }); return Promise.resolve(TunnelDto.fromApiTunnel(tunnel)); }); } @@ -130,11 +138,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe } - async $findCandidatePorts(): Promise<{ host: string, port: number, detail: string }[]> { - if (!isLinux) { - return []; - } - + async findCandidatePorts(): Promise<{ host: string, port: number, detail: string }[]> { const ports: { host: string, port: number, detail: string }[] = []; let tcp: string = ''; let tcp6: string = ''; @@ -189,15 +193,19 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe return ports; } - private getSockets(stdout: string) { + private getSockets(stdout: string): { pid: number, socket: number }[] { const lines = stdout.trim().split('\n'); - return lines.map(line => { + const mapped: { pid: number, socket: number }[] = []; + lines.forEach(line => { const match = /\/proc\/(\d+)\/fd\/\d+ -> socket:\[(\d+)\]/.exec(line)!; - return { - pid: parseInt(match[1], 10), - socket: parseInt(match[2], 10) - }; + if (match && match.length >= 3) { + mapped.push({ + pid: parseInt(match[1], 10), + socket: parseInt(match[2], 10) + }); + } }); + return mapped; } private loadListeningPorts(...stdouts: string[]): { socket: number, ip: string, port: number }[] { diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 35f9395f7..cdd2cdfc3 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -8,7 +8,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Action } from 'vs/base/common/actions'; import { SyncActionDescriptor, MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions, CATEGORIES } from 'vs/workbench/common/actions'; -import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { IEditorGroupsService, GroupOrientation } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -21,7 +21,6 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { InEditorZenModeContext, IsCenteredLayoutContext, EditorAreaVisibleContext } from 'vs/workbench/common/editor'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { SideBarVisibleContext } from 'vs/workbench/common/viewlet'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IViewDescriptorService, IViewsService, FocusedViewContext, ViewContainerLocation, IViewDescriptor } from 'vs/workbench/common/views'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -76,7 +75,7 @@ export class ToggleActivityBarVisibilityAction extends Action2 { const visibility = layoutService.isVisible(Parts.ACTIVITYBAR_PART); const newVisibilityValue = !visibility; - configurationService.updateValue(ToggleActivityBarVisibilityAction.activityBarVisibleKey, newVisibilityValue, ConfigurationTarget.USER); + configurationService.updateValue(ToggleActivityBarVisibilityAction.activityBarVisibleKey, newVisibilityValue); } } @@ -196,7 +195,7 @@ export class ToggleSidebarPositionAction extends Action { const position = this.layoutService.getSideBarPosition(); const newPositionValue = (position === Position.LEFT) ? 'right' : 'left'; - return this.configurationService.updateValue(ToggleSidebarPositionAction.sidebarPositionConfigurationKey, newPositionValue, ConfigurationTarget.USER); + return this.configurationService.updateValue(ToggleSidebarPositionAction.sidebarPositionConfigurationKey, newPositionValue); } static getLabel(layoutService: IWorkbenchLayoutService): string { @@ -317,7 +316,7 @@ export class ToggleStatusbarVisibilityAction extends Action { const visibility = this.layoutService.isVisible(Parts.STATUSBAR_PART); const newVisibilityValue = !visibility; - return this.configurationService.updateValue(ToggleStatusbarVisibilityAction.statusbarVisibleKey, newVisibilityValue, ConfigurationTarget.USER); + return this.configurationService.updateValue(ToggleStatusbarVisibilityAction.statusbarVisibleKey, newVisibilityValue); } } @@ -419,14 +418,13 @@ export class ToggleMenuBarAction extends Action { constructor( id: string, label: string, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IEnvironmentService private readonly environmentService: IEnvironmentService + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(id, label); } run(): Promise { - let currentVisibilityValue = getMenuBarVisibility(this.configurationService, this.environmentService); + let currentVisibilityValue = getMenuBarVisibility(this.configurationService); if (typeof currentVisibilityValue !== 'string') { currentVisibilityValue = 'default'; } diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index 5c8d8c70d..b71489ac3 100644 --- a/src/vs/workbench/browser/actions/listCommands.ts +++ b/src/vs/workbench/browser/actions/listCommands.ts @@ -356,19 +356,13 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ // List if (focused instanceof List || focused instanceof PagedList) { - const list = focused; - - list.focusPreviousPage(); - list.reveal(list.getFocus()[0]); + focused.focusPreviousPage(); } // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { - const list = focused; - const fakeKeyboardEvent = new KeyboardEvent('keydown'); - list.focusPreviousPage(fakeKeyboardEvent); - list.reveal(list.getFocus()[0]); + focused.focusPreviousPage(fakeKeyboardEvent); } // Ensure DOM Focus @@ -386,19 +380,13 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ // List if (focused instanceof List || focused instanceof PagedList) { - const list = focused; - - list.focusNextPage(); - list.reveal(list.getFocus()[0]); + focused.focusNextPage(); } // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { - const list = focused; - const fakeKeyboardEvent = new KeyboardEvent('keydown'); - list.focusNextPage(fakeKeyboardEvent); - list.reveal(list.getFocus()[0]); + focused.focusNextPage(fakeKeyboardEvent); } // Ensure DOM Focus diff --git a/src/vs/workbench/browser/actions/navigationActions.ts b/src/vs/workbench/browser/actions/navigationActions.ts index 7344a3a29..5833c3291 100644 --- a/src/vs/workbench/browser/actions/navigationActions.ts +++ b/src/vs/workbench/browser/actions/navigationActions.ts @@ -12,13 +12,10 @@ import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/bro import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { IPanel } from 'vs/workbench/common/panel'; -import { Action2, MenuId, registerAction2, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions, CATEGORIES } from 'vs/workbench/common/actions'; import { Direction } from 'vs/base/browser/ui/grid/grid'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { isAncestor } from 'vs/base/browser/dom'; @@ -275,29 +272,6 @@ export class FocusPreviousPart extends Action { } } -class GoHomeContributor implements IWorkbenchContribution { - - constructor( - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService - ) { - const homeIndicator = environmentService.options?.homeIndicator; - if (homeIndicator) { - registerAction2(class extends Action2 { - constructor() { - super({ - id: `workbench.actions.goHome`, - title: nls.localize('goHome', "Go Home"), - menu: { id: MenuId.MenubarWebNavigationMenu } - }); - } - async run(): Promise { - window.location.href = homeIndicator.href; - } - }); - } - } -} - // --- Actions Registration const actionsRegistry = Registry.as(Extensions.WorkbenchActions); @@ -308,6 +282,3 @@ actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateLeftAc actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateRightAction, undefined), 'View: Navigate to the View on the Right', CATEGORIES.View.value); actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(FocusNextPart, { primary: KeyCode.F6 }), 'View: Focus Next Part', CATEGORIES.View.value); actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(FocusPreviousPart, { primary: KeyMod.Shift | KeyCode.F6 }), 'View: Focus Previous Part', CATEGORIES.View.value); - -const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(GoHomeContributor, LifecyclePhase.Ready); diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index 700fb6ce0..b73573760 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -141,7 +141,7 @@ export class OpenWorkspaceConfigFileAction extends Action { async run(): Promise { const configuration = this.workspaceContextService.getWorkspace().configuration; if (configuration) { - await this.editorService.openEditor({ resource: configuration }); + await this.editorService.openEditor({ resource: configuration, options: { pinned: true } }); } } } diff --git a/src/vs/workbench/browser/actions/workspaceCommands.ts b/src/vs/workbench/browser/actions/workspaceCommands.ts index 24dc2f9d6..7f43e0fb0 100644 --- a/src/vs/workbench/browser/actions/workspaceCommands.ts +++ b/src/vs/workbench/browser/actions/workspaceCommands.ts @@ -9,7 +9,7 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/commo import * as resources from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { FileKind } from 'vs/platform/files/common/files'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -18,6 +18,10 @@ import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { URI } from 'vs/base/common/uri'; +import { Schemas } from 'vs/base/common/network'; +import { IOpenWindowOptions, IWindowOpenable } from 'vs/platform/windows/common/windows'; +import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; export const ADD_ROOT_FOLDER_COMMAND_ID = 'addRootFolder'; export const ADD_ROOT_FOLDER_LABEL = nls.localize('addFolderToWorkspace', "Add Folder to Workspace..."); @@ -61,7 +65,7 @@ CommandsRegistry.registerCommand({ title: nls.localize('addFolderToWorkspaceTitle', "Add Folder to Workspace"), canSelectFolders: true, canSelectMany: true, - defaultUri: dialogsService.defaultFolderPath() + defaultUri: await dialogsService.defaultFolderPath() }); if (!folders || !folders.length) { @@ -116,3 +120,46 @@ CommandsRegistry.registerCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, async functio return; }); + +// API Command registration + +interface IOpenFolderAPICommandOptions { + forceNewWindow?: boolean; + forceReuseWindow?: boolean; + noRecentEntry?: boolean; +} + +CommandsRegistry.registerCommand({ + id: 'vscode.openFolder', + handler: (accessor: ServicesAccessor, uri?: URI, arg?: boolean | IOpenFolderAPICommandOptions) => { + const commandService = accessor.get(ICommandService); + + // Be compatible to previous args by converting to options + if (typeof arg === 'boolean') { + arg = { forceNewWindow: arg }; + } + + // Without URI, ask to pick a folder or workpsace to open + if (!uri) { + return commandService.executeCommand('_files.pickFolderAndOpen', { forceNewWindow: arg?.forceNewWindow }); + } + + uri = URI.revive(uri); + + const options: IOpenWindowOptions = { + forceNewWindow: arg?.forceNewWindow, + forceReuseWindow: arg?.forceReuseWindow, + noRecentEntry: arg?.noRecentEntry + }; + + const uriToOpen: IWindowOpenable = (hasWorkspaceFileExtension(uri) || uri.scheme === Schemas.untitled) ? { workspaceUri: uri } : { folderUri: uri }; + return commandService.executeCommand('_files.windowOpen', [uriToOpen], options); + }, + description: { + description: 'Open a folder or workspace in the current window or new window depending on the newWindow argument. Note that opening in the same window will shutdown the current extension host process and start a new one on the given folder/workspace unless the newWindow parameter is set to true.', + args: [ + { name: 'uri', description: '(optional) Uri of the folder or workspace file to open. If not provided, a native dialog will ask the user for the folder', constraint: (value: any) => value === undefined || value instanceof URI }, + { name: 'options', description: '(optional) Options. Object with the following properties: `forceNewWindow `: Whether to open the folder/workspace in a new window or the same. Defaults to opening in the same window. `noRecentEntry`: Wheter the opened URI will appear in the \'Open Recent\' list. Defaults to true. Note, for backward compatibility, options can also be of type boolean, representing the `forceNewWindow` setting.', constraint: (value: any) => value === undefined || typeof value === 'object' || typeof value === 'boolean' } + ] + } +}); diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 53f39bc84..baab13434 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -16,7 +16,7 @@ import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd'; import { DragMouseEvent } from 'vs/base/browser/mouseEvent'; import { normalizeDriveLetter } from 'vs/base/common/labels'; import { MIME_BINARY } from 'vs/base/common/mime'; -import { isWindows, isWeb } from 'vs/base/common/platform'; +import { isWindows } from 'vs/base/common/platform'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorIdentifier, GroupIdentifier } from 'vs/workbench/common/editor'; @@ -27,7 +27,6 @@ import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsSe import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { isStandalone } from 'vs/base/browser/browser'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { Emitter } from 'vs/base/common/event'; @@ -320,8 +319,7 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources: event.dataTransfer.setData(DataTransfers.TEXT, sources.map(source => source.resource.scheme === Schemas.file ? normalize(normalizeDriveLetter(source.resource.fsPath)) : source.resource.toString()).join(lineDelimiter)); // Download URL: enables support to drag a tab as file to desktop (only single file supported) - // Disabled for PWA web due to: https://github.com/microsoft/vscode/issues/83441 - if (!sources[0].isDirectory && (!isWeb || !isStandalone)) { + if (!sources[0].isDirectory) { event.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [MIME_BINARY, basename(sources[0].resource), FileAccess.asBrowserUri(sources[0].resource).toString()].join(':')); } diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 13f62c492..460594ee0 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -5,7 +5,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; -import { EventType, addDisposableListener, isAncestor, getClientArea, Dimension, position, size, IDimension } from 'vs/base/browser/dom'; +import { EventType, addDisposableListener, getClientArea, Dimension, position, size, IDimension, isAncestorUsingFlowTo } from 'vs/base/browser/dom'; import { onDidChangeFullscreen, isFullscreen } from 'vs/base/browser/browser'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -16,7 +16,7 @@ import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; import { PanelRegistry, Extensions as PanelExtensions } from 'vs/workbench/browser/panel'; import { Position, Parts, PanelOpensMaximizedOptions, IWorkbenchLayoutService, positionFromString, positionToString, panelOpensMaximizedFromString } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; @@ -41,7 +41,6 @@ import { INotificationService, NotificationsFilter } from 'vs/platform/notificat import { IThemeService } from 'vs/platform/theme/common/themeService'; import { WINDOW_ACTIVE_BORDER, WINDOW_INACTIVE_BORDER } from 'vs/workbench/common/theme'; import { LineNumbersType } from 'vs/editor/common/config/editorOptions'; -import { ActivitybarPart } from 'vs/workbench/browser/parts/activitybar/activitybarPart'; import { URI } from 'vs/base/common/uri'; import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; @@ -91,21 +90,6 @@ enum Classes { WINDOW_BORDER = 'border' } -interface PanelActivityState { - id: string; - name?: string; - pinned: boolean; - order: number; - visible: boolean; -} - -interface SideBarActivityState { - id: string; - pinned: boolean; - order: number; - visible: boolean; -} - export abstract class Layout extends Disposable implements IWorkbenchLayoutService { declare readonly _serviceBrand: undefined; @@ -319,7 +303,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._register(addDisposableListener(this.container, EventType.SCROLL, () => this.container.scrollTop = 0)); // Menubar visibility changes - if ((isWindows || isLinux || isWeb) && getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { + if ((isWindows || isLinux || isWeb) && getTitleBarStyle(this.configurationService) === 'custom') { this._register(this.titleService.onMenubarVisibilityChange(visible => this.onMenubarToggled(visible))); } @@ -357,8 +341,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } + // Change edge snapping accordingly + this.workbenchGrid.edgeSnapping = this.state.fullscreen; + // Changing fullscreen state of the window has an impact on custom title bar visibility, so we need to update - if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { + if (getTitleBarStyle(this.configurationService) === 'custom') { // Propagate to grid this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); @@ -407,7 +394,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // Menubar visibility - const newMenubarVisibility = getMenuBarVisibility(this.configurationService, this.environmentService); + const newMenubarVisibility = getMenuBarVisibility(this.configurationService); this.setMenubarVisibility(newMenubarVisibility, !!skipLayout); // Centered Layout @@ -451,7 +438,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } private updateWindowBorder(skipLayout: boolean = false) { - if (isWeb || getTitleBarStyle(this.configurationService, this.environmentService) !== 'custom') { + if (isWeb || getTitleBarStyle(this.configurationService) !== 'custom') { return; } @@ -495,7 +482,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.state.fullscreen = isFullscreen(); // Menubar visibility - this.state.menuBar.visibility = getMenuBarVisibility(this.configurationService, this.environmentService); + this.state.menuBar.visibility = getMenuBarVisibility(this.configurationService); // Activity bar visibility this.state.activityBar.hidden = !this.configurationService.getValue(Settings.ACTIVITYBAR_VISIBLE); @@ -582,167 +569,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const { views } = defaultLayout; if (views?.length) { this.state.views.defaults = views.map(v => v.id); - - return; - } - - // TODO@eamodio Everything below here is deprecated and will be removed once Codespaces migrates - - const { sidebar } = defaultLayout; - if (sidebar) { - if (sidebar.visible !== undefined) { - if (sidebar.visible) { - storageService.remove(Storage.SIDEBAR_HIDDEN, StorageScope.WORKSPACE); - } else { - storageService.store(Storage.SIDEBAR_HIDDEN, true, StorageScope.WORKSPACE); - } - } - - if (sidebar.containers?.length) { - const sidebarState: SideBarActivityState[] = []; - - let order = -1; - for (const container of sidebar.containers.sort((a, b) => (a.order ?? 1) - (b.order ?? 1))) { - let viewletId; - switch (container.id) { - case 'explorer': - viewletId = 'workbench.view.explorer'; - break; - case 'run': - viewletId = 'workbench.view.debug'; - break; - case 'scm': - viewletId = 'workbench.view.scm'; - break; - case 'search': - viewletId = 'workbench.view.search'; - break; - case 'extensions': - viewletId = 'workbench.view.extensions'; - break; - case 'remote': - viewletId = 'workbench.view.remote'; - break; - default: - viewletId = `workbench.view.extension.${container.id}`; - } - - if (container.active) { - storageService.store(SidebarPart.activeViewletSettingsKey, viewletId, StorageScope.WORKSPACE); - } - - if (container.order !== undefined || (container.active === undefined && container.visible !== undefined)) { - order = container.order ?? (order + 1); - const state: SideBarActivityState = { - id: viewletId, - order: order, - pinned: (container.active || container.visible) ?? true, - visible: (container.active || container.visible) ?? true - }; - - sidebarState.push(state); - } - - if (container.views !== undefined) { - const viewsState: { id: string, isHidden?: boolean, order?: number }[] = []; - const viewsWorkspaceState: { [id: string]: { collapsed: boolean, isHidden?: boolean, size?: number } } = {}; - - for (const view of container.views) { - if (view.order !== undefined || view.visible !== undefined) { - viewsState.push({ - id: view.id, - isHidden: view.visible === undefined ? undefined : !view.visible, - order: view.order === undefined ? undefined : view.order - }); - } - - if (view.collapsed !== undefined) { - viewsWorkspaceState[view.id] = { - collapsed: view.collapsed, - isHidden: view.visible === undefined ? undefined : !view.visible, - }; - } - } - - storageService.store(`${viewletId}.state.hidden`, JSON.stringify(viewsState), StorageScope.GLOBAL); - storageService.store(`${viewletId}.state`, JSON.stringify(viewsWorkspaceState), StorageScope.WORKSPACE); - } - } - - if (sidebarState.length) { - storageService.store(ActivitybarPart.PINNED_VIEW_CONTAINERS, JSON.stringify(sidebarState), StorageScope.GLOBAL); - } - } - } - - const { panel } = defaultLayout; - if (panel) { - if (panel.visible !== undefined) { - if (panel.visible) { - storageService.store(Storage.PANEL_HIDDEN, false, StorageScope.WORKSPACE); - } else { - storageService.remove(Storage.PANEL_HIDDEN, StorageScope.WORKSPACE); - } - } - - if (panel.containers?.length) { - const panelState: PanelActivityState[] = []; - - let order = -1; - for (const container of panel.containers.sort((a, b) => (a.order ?? 1) - (b.order ?? 1))) { - let name; - let panelId = container.id; - switch (panelId) { - case 'terminal': - name = 'Terminal'; - panelId = 'workbench.panel.terminal'; - break; - case 'debug': - name = 'Debug Console'; - panelId = 'workbench.panel.repl'; - break; - case 'problems': - name = 'Problems'; - panelId = 'workbench.panel.markers'; - break; - case 'output': - name = 'Output'; - panelId = 'workbench.panel.output'; - break; - case 'comments': - name = 'Comments'; - panelId = 'workbench.panel.comments'; - break; - case 'refactor': - name = 'Refactor Preview'; - panelId = 'refactorPreview'; - break; - default: - continue; - } - - if (container.active) { - storageService.store(PanelPart.activePanelSettingsKey, panelId, StorageScope.WORKSPACE); - } - - if (container.order !== undefined || (container.active === undefined && container.visible !== undefined)) { - order = container.order ?? (order + 1); - const state: PanelActivityState = { - id: panelId, - name: name, - order: order, - pinned: (container.active || container.visible) ?? true, - visible: (container.active || container.visible) ?? true - }; - - panelState.push(state); - } - } - - if (panelState.length) { - storageService.store(PanelPart.PINNED_PANELS, JSON.stringify(panelState), StorageScope.GLOBAL); - } - } } } @@ -750,7 +576,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const initialFilesToOpen = this.getInitialFilesToOpen(); // Only restore editors if we are not instructed to open files initially - this.state.editor.restoreEditors = initialFilesToOpen === undefined; + // or when `window.restoreWindows` setting is explicitly set to `preserve` + const forceRestoreEditors = this.configurationService.getValue('window.restoreWindows') === 'preserve'; + this.state.editor.restoreEditors = !!forceRestoreEditors || initialFilesToOpen === undefined; // Files to open, diff or create if (initialFilesToOpen !== undefined) { @@ -996,7 +824,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const container = this.getContainer(part); - return !!container && isAncestor(activeElement, container); + return !!container && isAncestorUsingFlowTo(activeElement, container); } focusPart(part: Parts): void { @@ -1050,7 +878,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi isVisible(part: Parts): boolean { switch (part) { case Parts.TITLEBAR_PART: - if (getTitleBarStyle(this.configurationService, this.environmentService) === 'native') { + if (getTitleBarStyle(this.configurationService) === 'native') { return false; } else if (!this.state.fullscreen && !isWeb) { return true; @@ -1246,7 +1074,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // State if (this.state.zenMode.active) { - this.storageService.store(Storage.ZEN_MODE_ENABLED, true, StorageScope.WORKSPACE); + this.storageService.store(Storage.ZEN_MODE_ENABLED, true, StorageScope.WORKSPACE, StorageTarget.USER); // Exit zen mode on shutdown unless configured to keep this.state.zenMode.transitionDisposables.add(this.storageService.onWillSaveState(e => { @@ -1310,6 +1138,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.container.prepend(workbenchGrid.element); this.container.setAttribute('role', 'application'); this.workbenchGrid = workbenchGrid; + this.workbenchGrid.edgeSnapping = this.state.fullscreen; [titleBar, editorPart, activityBar, panelPart, sideBar, statusBar].forEach((part: Part) => { this._register(part.onDidVisibilityChange((visible) => { @@ -1331,18 +1160,18 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi ? grid.getViewCachedVisibleSize(this.sideBarPartView) : grid.getViewSize(this.sideBarPartView).width; - this.storageService.store(Storage.SIDEBAR_SIZE, sideBarSize, StorageScope.GLOBAL); + this.storageService.store(Storage.SIDEBAR_SIZE, sideBarSize, StorageScope.GLOBAL, StorageTarget.MACHINE); const panelSize = this.state.panel.hidden ? grid.getViewCachedVisibleSize(this.panelPartView) : (this.state.panel.position === Position.BOTTOM ? grid.getViewSize(this.panelPartView).height : grid.getViewSize(this.panelPartView).width); - this.storageService.store(Storage.PANEL_SIZE, panelSize, StorageScope.GLOBAL); - this.storageService.store(Storage.PANEL_DIMENSION, positionToString(this.state.panel.position), StorageScope.GLOBAL); + this.storageService.store(Storage.PANEL_SIZE, panelSize, StorageScope.GLOBAL, StorageTarget.MACHINE); + this.storageService.store(Storage.PANEL_DIMENSION, positionToString(this.state.panel.position), StorageScope.GLOBAL, StorageTarget.MACHINE); const gridSize = grid.getViewSize(); - this.storageService.store(Storage.GRID_WIDTH, gridSize.width, StorageScope.GLOBAL); - this.storageService.store(Storage.GRID_HEIGHT, gridSize.height, StorageScope.GLOBAL); + this.storageService.store(Storage.GRID_WIDTH, gridSize.width, StorageScope.GLOBAL, StorageTarget.MACHINE); + this.storageService.store(Storage.GRID_HEIGHT, gridSize.height, StorageScope.GLOBAL, StorageTarget.MACHINE); })); } @@ -1373,7 +1202,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi centerEditorLayout(active: boolean, skipLayout?: boolean): void { this.state.editor.centered = active; - this.storageService.store(Storage.CENTERED_LAYOUT_ENABLED, active, StorageScope.WORKSPACE); + this.storageService.store(Storage.CENTERED_LAYOUT_ENABLED, active, StorageScope.WORKSPACE, StorageTarget.USER); let smartActive = active; const activeEditor = this.editorService.activeEditor; @@ -1486,7 +1315,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Remember in settings if (hidden) { - this.storageService.store(Storage.EDITOR_HIDDEN, true, StorageScope.WORKSPACE); + this.storageService.store(Storage.EDITOR_HIDDEN, true, StorageScope.WORKSPACE, StorageTarget.USER); } else { this.storageService.remove(Storage.EDITOR_HIDDEN, StorageScope.WORKSPACE); } @@ -1547,7 +1376,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Remember in settings const defaultHidden = this.contextService.getWorkbenchState() === WorkbenchState.EMPTY; if (hidden !== defaultHidden) { - this.storageService.store(Storage.SIDEBAR_HIDDEN, hidden ? 'true' : 'false', StorageScope.WORKSPACE); + this.storageService.store(Storage.SIDEBAR_HIDDEN, hidden ? 'true' : 'false', StorageScope.WORKSPACE, StorageTarget.USER); } else { this.storageService.remove(Storage.SIDEBAR_HIDDEN, StorageScope.WORKSPACE); } @@ -1612,14 +1441,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // Remember in settings if (!hidden) { - this.storageService.store(Storage.PANEL_HIDDEN, 'false', StorageScope.WORKSPACE); + this.storageService.store(Storage.PANEL_HIDDEN, 'false', StorageScope.WORKSPACE, StorageTarget.USER); } else { this.storageService.remove(Storage.PANEL_HIDDEN, StorageScope.WORKSPACE); // Remember this setting only when panel is hiding if (this.state.panel.wasLastMaximized) { - this.storageService.store(Storage.PANEL_LAST_IS_MAXIMIZED, true, StorageScope.WORKSPACE); + this.storageService.store(Storage.PANEL_LAST_IS_MAXIMIZED, true, StorageScope.WORKSPACE, StorageTarget.USER); } else { this.storageService.remove(Storage.PANEL_LAST_IS_MAXIMIZED, StorageScope.WORKSPACE); @@ -1637,10 +1466,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (!this.state.panel.hidden) { if (this.state.panel.position === Position.BOTTOM) { this.state.panel.lastNonMaximizedHeight = size.height; - this.storageService.store(Storage.PANEL_LAST_NON_MAXIMIZED_HEIGHT, this.state.panel.lastNonMaximizedHeight, StorageScope.GLOBAL); + this.storageService.store(Storage.PANEL_LAST_NON_MAXIMIZED_HEIGHT, this.state.panel.lastNonMaximizedHeight, StorageScope.GLOBAL, StorageTarget.MACHINE); } else { this.state.panel.lastNonMaximizedWidth = size.width; - this.storageService.store(Storage.PANEL_LAST_NON_MAXIMIZED_WIDTH, this.state.panel.lastNonMaximizedWidth, StorageScope.GLOBAL); + this.storageService.store(Storage.PANEL_LAST_NON_MAXIMIZED_WIDTH, this.state.panel.lastNonMaximizedWidth, StorageScope.GLOBAL, StorageTarget.MACHINE); } } @@ -1715,7 +1544,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.state.panel.position = position; // Save panel position - this.storageService.store(Storage.PANEL_POSITION, newPositionValue, StorageScope.WORKSPACE); + this.storageService.store(Storage.PANEL_POSITION, newPositionValue, StorageScope.WORKSPACE, StorageTarget.USER); // Adjust CSS const panelContainer = assertIsDefined(panelPart.getContainer()); diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index b32718632..cf008e43f 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -220,6 +220,10 @@ body.web { opacity: 1 !important; } +.monaco-workbench input[type="checkbox"]:focus { + outline-offset: 2px; +} + .monaco-workbench [tabindex="0"]:active, .monaco-workbench [tabindex="-1"]:active, .monaco-workbench select:active, diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index fca512810..49bbae451 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -10,15 +10,14 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; import { Action, IAction, Separator, SubmenuAction } from 'vs/base/common/actions'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { dispose } from 'vs/base/common/lifecycle'; -import { SyncActionDescriptor, IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IMenuService, MenuId, IMenu, registerAction2, Action2, IAction2Options } from 'vs/platform/actions/common/actions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { Registry } from 'vs/platform/registry/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ActivityAction, ActivityActionViewItem, ICompositeBar, ICompositeBarColors, ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions'; -import { CATEGORIES, Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; +import { CATEGORIES } from 'vs/workbench/common/actions'; import { IActivity } from 'vs/workbench/common/activity'; import { ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_ACTIVE_FOCUS_BORDER, ACTIVITY_BAR_ACTIVE_BACKGROUND, ACTIVITY_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; @@ -26,44 +25,31 @@ import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/bro import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { Codicon } from 'vs/base/common/codicons'; -import { isMacintosh } from 'vs/base/common/platform'; +import { isMacintosh, isWeb } from 'vs/base/common/platform'; import { getCurrentAuthenticationSessionInfo, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; import { AuthenticationSession } from 'vs/editor/common/modes'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IProductService } from 'vs/platform/product/common/productService'; -import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; +import { AnchorAlignment, AnchorAxisAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { getTitleBarStyle } from 'vs/platform/windows/common/windows'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; export class ViewContainerActivityAction extends ActivityAction { private static readonly preventDoubleClickDelay = 300; - private readonly viewletService: IViewletService; - private readonly layoutService: IWorkbenchLayoutService; - private readonly telemetryService: ITelemetryService; - private readonly configurationService: IConfigurationService; - - private lastRun: number; + private lastRun = 0; constructor( activity: IActivity, - @IViewletService viewletService: IViewletService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @ITelemetryService telemetryService: ITelemetryService, - @IConfigurationService configurationService: IConfigurationService + @IViewletService private readonly viewletService: IViewletService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(activity); - - this.lastRun = 0; - this.viewletService = viewletService; - this.layoutService = layoutService; - this.telemetryService = telemetryService; - this.configurationService = configurationService; } updateActivity(activity: IActivity): void { @@ -105,6 +91,7 @@ export class ViewContainerActivityAction extends ActivityAction { this.logAction('show'); await this.viewletService.openViewlet(this.activity.id, true); + return this.activate(); } @@ -117,21 +104,18 @@ export class ViewContainerActivityAction extends ActivityAction { } } -export const ACCOUNTS_VISIBILITY_PREFERENCE_KEY = 'workbench.activity.showAccounts'; +class MenuActivityActionViewItem extends ActivityActionViewItem { -export class AccountsActionViewItem extends ActivityActionViewItem { constructor( + private readonly menuId: MenuId, action: ActivityAction, colors: (theme: IColorTheme) => ICompositeBarColors, @IThemeService themeService: IThemeService, - @IContextMenuService protected contextMenuService: IContextMenuService, - @IMenuService protected menuService: IMenuService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IAuthenticationService private readonly authenticationService: IAuthenticationService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IStorageService private readonly storageService: IStorageService, - @IProductService private readonly productService: IProductService, - @IConfigurationService private readonly configurationService: IConfigurationService, + @IMenuService protected readonly menuService: IMenuService, + @IContextMenuService protected readonly contextMenuService: IContextMenuService, + @IContextKeyService protected readonly contextKeyService: IContextKeyService, + @IConfigurationService protected readonly configurationService: IConfigurationService, + @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService ) { super(action, { draggable: false, colors, icon: true }, themeService); } @@ -161,12 +145,103 @@ export class AccountsActionViewItem extends ActivityActionViewItem { })); } - private async getActions(accountsMenu: IMenu) { + protected async showContextMenu(e?: MouseEvent): Promise { + const disposables = new DisposableStore(); + + const menu = disposables.add(this.menuService.createMenu(this.menuId, this.contextKeyService)); + const actions = await this.resolveActions(menu, disposables); + + const isUsingCustomMenu = isWeb || (getTitleBarStyle(this.configurationService) !== 'native' && !isMacintosh); // see #40262 + const position = this.configurationService.getValue('workbench.sideBar.location'); + + this.contextMenuService.showContextMenu({ + getAnchor: () => isUsingCustomMenu ? this.container : e || this.container, + anchorAlignment: isUsingCustomMenu ? (position === 'left' ? AnchorAlignment.RIGHT : AnchorAlignment.LEFT) : undefined, + anchorAxisAlignment: isUsingCustomMenu ? AnchorAxisAlignment.HORIZONTAL : AnchorAxisAlignment.VERTICAL, + getActions: () => actions, + onHide: () => disposables.dispose() + }); + } + + protected async resolveActions(menu: IMenu, disposables: DisposableStore): Promise { + const actions: IAction[] = []; + + disposables.add(createAndFillInActionBarActions(menu, undefined, { primary: [], secondary: actions })); + + return actions; + } +} + +export class HomeActivityActionViewItem extends MenuActivityActionViewItem { + + static readonly HOME_BAR_VISIBILITY_PREFERENCE = 'workbench.activity.showHomeIndicator'; + + constructor( + private readonly goHomeHref: string, + action: ActivityAction, + colors: (theme: IColorTheme) => ICompositeBarColors, + @IThemeService themeService: IThemeService, + @IMenuService menuService: IMenuService, + @IContextMenuService contextMenuService: IContextMenuService, + @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService configurationService: IConfigurationService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IStorageService private readonly storageService: IStorageService + ) { + super(MenuId.MenubarHomeMenu, action, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService); + } + + protected async resolveActions(homeMenu: IMenu, disposables: DisposableStore): Promise { + const actions = []; + + // Go Home + actions.push(disposables.add(new Action('goHome', nls.localize('goHome', "Go Home"), undefined, true, async () => window.location.href = this.goHomeHref))); + actions.push(disposables.add(new Separator())); + + // Contributed + const contributedActions = await super.resolveActions(homeMenu, disposables); + actions.push(...contributedActions); + + // Hide + if (contributedActions.length > 0) { + actions.push(disposables.add(new Separator())); + } + actions.push(disposables.add(new Action('hide', nls.localize('hide', "Hide"), undefined, true, async () => { + this.storageService.store(HomeActivityActionViewItem.HOME_BAR_VISIBILITY_PREFERENCE, false, StorageScope.GLOBAL, StorageTarget.USER); + }))); + + return actions; + } +} + +export class AccountsActivityActionViewItem extends MenuActivityActionViewItem { + + static readonly ACCOUNTS_VISIBILITY_PREFERENCE_KEY = 'workbench.activity.showAccounts'; + + constructor( + action: ActivityAction, + colors: (theme: IColorTheme) => ICompositeBarColors, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IMenuService menuService: IMenuService, + @IContextKeyService contextKeyService: IContextKeyService, + @IAuthenticationService private readonly authenticationService: IAuthenticationService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IStorageService private readonly storageService: IStorageService, + @IProductService private readonly productService: IProductService, + @IConfigurationService configurationService: IConfigurationService, + ) { + super(MenuId.AccountsContext, action, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService); + } + + protected async resolveActions(accountsMenu: IMenu, disposables: DisposableStore): Promise { + await super.resolveActions(accountsMenu, disposables); + const otherCommands = accountsMenu.getActions(); const providers = this.authenticationService.getProviderIds(); - const allSessions = providers.map(async id => { + const allSessions = providers.map(async providerId => { try { - const sessions = await this.authenticationService.getSessions(id); + const sessions = await this.authenticationService.getSessions(providerId); const groupedSessions: { [label: string]: AuthenticationSession[] } = {}; sessions.forEach(session => { @@ -177,14 +252,9 @@ export class AccountsActionViewItem extends ActivityActionViewItem { } }); - return { - providerId: id, - sessions: groupedSessions - }; + return { providerId, sessions: groupedSessions }; } catch { - return { - providerId: id - }; + return { providerId }; } }); @@ -196,132 +266,67 @@ export class AccountsActionViewItem extends ActivityActionViewItem { if (sessionInfo.sessions) { Object.keys(sessionInfo.sessions).forEach(accountName => { - const hasEmbedderAccountSession = sessionInfo.sessions[accountName].some(session => session.id === (authenticationSession?.id)); - const manageExtensionsAction = new Action(`configureSessions${accountName}`, nls.localize('manageTrustedExtensions', "Manage Trusted Extensions"), '', true, _ => { + const manageExtensionsAction = disposables.add(new Action(`configureSessions${accountName}`, nls.localize('manageTrustedExtensions', "Manage Trusted Extensions"), '', true, () => { return this.authenticationService.manageTrustedExtensionsForAccount(sessionInfo.providerId, accountName); - }); - const signOutAction = new Action('signOut', nls.localize('signOut', "Sign Out"), '', true, _ => { - return this.authenticationService.signOutOfAccount(sessionInfo.providerId, accountName); - }); + })); - const actions = [manageExtensionsAction]; + const signOutAction = disposables.add(new Action('signOut', nls.localize('signOut', "Sign Out"), '', true, () => { + return this.authenticationService.signOutOfAccount(sessionInfo.providerId, accountName); + })); + + const providerSubMenuActions = [manageExtensionsAction]; + + const hasEmbedderAccountSession = sessionInfo.sessions[accountName].some(session => session.id === (authenticationSession?.id)); if (!hasEmbedderAccountSession || authenticationSession?.canSignOut) { - actions.push(signOutAction); + providerSubMenuActions.push(signOutAction); } - const menu = new SubmenuAction('activitybar.submenu', `${accountName} (${providerDisplayName})`, actions); - menus.push(menu); + const providerSubMenu = disposables.add(new SubmenuAction('activitybar.submenu', `${accountName} (${providerDisplayName})`, providerSubMenuActions)); + menus.push(providerSubMenu); }); } else { - const menu = new Action('providerUnavailable', nls.localize('authProviderUnavailable', '{0} is currently unavailable', providerDisplayName)); - menus.push(menu); + const providerUnavailableAction = disposables.add(new Action('providerUnavailable', nls.localize('authProviderUnavailable', '{0} is currently unavailable', providerDisplayName))); + menus.push(providerUnavailableAction); } }); if (menus.length && otherCommands.length) { - menus.push(new Separator()); + menus.push(disposables.add(new Separator())); } otherCommands.forEach((group, i) => { const actions = group[1]; menus = menus.concat(actions); if (i !== otherCommands.length - 1) { - menus.push(new Separator()); + menus.push(disposables.add(new Separator())); } }); if (menus.length) { - menus.push(new Separator()); + menus.push(disposables.add(new Separator())); } - menus.push(new Action('hide', nls.localize('hide', "Hide"), undefined, true, _ => { - this.storageService.store(ACCOUNTS_VISIBILITY_PREFERENCE_KEY, false, StorageScope.GLOBAL); - return Promise.resolve(); - })); + menus.push(disposables.add(new Action('hide', nls.localize('hide', "Hide"), undefined, true, async () => { + this.storageService.store(AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, false, StorageScope.GLOBAL, StorageTarget.USER); + }))); return menus; } - - private async showContextMenu(e?: MouseEvent): Promise { - const accountsActions: IAction[] = []; - const accountsMenu = this.menuService.createMenu(MenuId.AccountsContext, this.contextKeyService); - const actionsDisposable = createAndFillInActionBarActions(accountsMenu, undefined, { primary: [], secondary: accountsActions }); - const native = getTitleBarStyle(this.configurationService, this.environmentService) === 'native'; - const position = this.configurationService.getValue('workbench.sideBar.location'); - - const containerPosition = DOM.getDomNodePagePosition(this.container); - const location = { x: containerPosition.left + (position === 'left' ? containerPosition.width : 0), y: containerPosition.top }; - const actions = await this.getActions(accountsMenu); - this.contextMenuService.showContextMenu({ - getAnchor: () => !native ? location : e || this.container, - anchorAlignment: !native ? (position === 'left' ? AnchorAlignment.RIGHT : AnchorAlignment.LEFT) : undefined, - getActions: () => actions, - onHide: () => { - accountsMenu.dispose(); - dispose(actionsDisposable); - } - }); - } } -export class GlobalActivityActionViewItem extends ActivityActionViewItem { +export class GlobalActivityActionViewItem extends MenuActivityActionViewItem { constructor( action: ActivityAction, colors: (theme: IColorTheme) => ICompositeBarColors, @IThemeService themeService: IThemeService, - @IMenuService private readonly menuService: IMenuService, - @IContextMenuService protected readonly contextMenuService: IContextMenuService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IEnvironmentService private readonly environmentService: IEnvironmentService + @IMenuService menuService: IMenuService, + @IContextMenuService contextMenuService: IContextMenuService, + @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService configurationService: IConfigurationService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService ) { - super(action, { draggable: false, colors, icon: true }, themeService); - } - - render(container: HTMLElement): void { - super.render(container); - - // Context menus are triggered on mouse down so that an item can be picked - // and executed with releasing the mouse over it - - this._register(DOM.addDisposableListener(this.container, DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => { - DOM.EventHelper.stop(e, true); - this.showContextMenu(e); - })); - - this._register(DOM.addDisposableListener(this.container, DOM.EventType.KEY_UP, (e: KeyboardEvent) => { - let event = new StandardKeyboardEvent(e); - if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { - DOM.EventHelper.stop(e, true); - this.showContextMenu(); - } - })); - - this._register(DOM.addDisposableListener(this.container, TouchEventType.Tap, (e: GestureEvent) => { - DOM.EventHelper.stop(e, true); - this.showContextMenu(); - })); - } - - private showContextMenu(e?: MouseEvent): void { - const globalActivityActions: IAction[] = []; - const globalActivityMenu = this.menuService.createMenu(MenuId.GlobalActivity, this.contextKeyService); - const actionsDisposable = createAndFillInActionBarActions(globalActivityMenu, undefined, { primary: [], secondary: globalActivityActions }); - const native = getTitleBarStyle(this.configurationService, this.environmentService) === 'native'; - const position = this.configurationService.getValue('workbench.sideBar.location'); - - const containerPosition = DOM.getDomNodePagePosition(this.container); - const location = { x: containerPosition.left + (position === 'left' ? containerPosition.width : 0), y: containerPosition.top + containerPosition.height }; - this.contextMenuService.showContextMenu({ - getAnchor: () => !native ? location : e || this.container, - anchorAlignment: !native ? (position === 'left' ? AnchorAlignment.RIGHT : AnchorAlignment.LEFT) : undefined, - getActions: () => globalActivityActions, - onHide: () => { - globalActivityMenu.dispose(); - dispose(actionsDisposable); - } - }); + super(MenuId.GlobalActivity, action, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService); } } @@ -338,106 +343,62 @@ export class PlaceHolderToggleCompositePinnedAction extends ToggleCompositePinne } } -class SwitchSideBarViewAction extends Action { +class SwitchSideBarViewAction extends Action2 { constructor( - id: string, - name: string, - @IViewletService private readonly viewletService: IViewletService, - @IActivityBarService private readonly activityBarService: IActivityBarService + desc: Readonly, + private readonly offset: number ) { - super(id, name); + super(desc); } - async run(offset: number): Promise { - const visibleViewletIds = this.activityBarService.getVisibleViewContainerIds(); + async run(accessor: ServicesAccessor): Promise { + const activityBarService = accessor.get(IActivityBarService); + const viewletService = accessor.get(IViewletService); - const activeViewlet = this.viewletService.getActiveViewlet(); + const visibleViewletIds = activityBarService.getVisibleViewContainerIds(); + + const activeViewlet = viewletService.getActiveViewlet(); if (!activeViewlet) { return; } let targetViewletId: string | undefined; for (let i = 0; i < visibleViewletIds.length; i++) { if (visibleViewletIds[i] === activeViewlet.getId()) { - targetViewletId = visibleViewletIds[(i + visibleViewletIds.length + offset) % visibleViewletIds.length]; + targetViewletId = visibleViewletIds[(i + visibleViewletIds.length + this.offset) % visibleViewletIds.length]; break; } } - await this.viewletService.openViewlet(targetViewletId, true); + await viewletService.openViewlet(targetViewletId, true); } } -export class PreviousSideBarViewAction extends SwitchSideBarViewAction { - - static readonly ID = 'workbench.action.previousSideBarView'; - static readonly LABEL = nls.localize('previousSideBarView', 'Previous Side Bar View'); - - constructor( - id: string, - name: string, - @IViewletService viewletService: IViewletService, - @IActivityBarService activityBarService: IActivityBarService - ) { - super(id, name, viewletService, activityBarService); - } - - run(): Promise { - return super.run(-1); - } -} - -export class NextSideBarViewAction extends SwitchSideBarViewAction { - - static readonly ID = 'workbench.action.nextSideBarView'; - static readonly LABEL = nls.localize('nextSideBarView', 'Next Side Bar View'); - - constructor( - id: string, - name: string, - @IViewletService viewletService: IViewletService, - @IActivityBarService activityBarService: IActivityBarService - ) { - super(id, name, viewletService, activityBarService); - } - - run(): Promise { - return super.run(1); - } -} - -export class HomeAction extends Action { - - constructor( - private readonly href: string, - name: string, - icon: Codicon - ) { - super('workbench.action.home', name, icon.classNames); - } - - async run(event: MouseEvent): Promise { - let openInNewWindow = false; - if (isMacintosh) { - openInNewWindow = event.metaKey; - } else { - openInNewWindow = event.ctrlKey; - } - - if (openInNewWindow) { - DOM.windowOpenNoOpener(this.href); - } else { - window.location.href = this.href; +registerAction2( + class PreviousSideBarViewAction extends SwitchSideBarViewAction { + constructor() { + super({ + id: 'workbench.action.previousSideBarView', + title: { value: nls.localize('previousSideBarView', "Previous Side Bar View"), original: 'Previous Side Bar View' }, + category: CATEGORIES.View, + f1: true + }, -1); } } -} +); -export class HomeActionViewItem extends ActionViewItem { - - constructor(action: IAction) { - super(undefined, action, { icon: true, label: false, useEventAsContext: true }); +registerAction2( + class NextSideBarViewAction extends SwitchSideBarViewAction { + constructor() { + super({ + id: 'workbench.action.nextSideBarView', + title: { value: nls.localize('nextSideBarView', "Next Side Bar View"), original: 'Next Side Bar View' }, + category: CATEGORIES.View, + f1: true + }, 1); + } } -} +); registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const activityBarBackgroundColor = theme.getColor(ACTIVITY_BAR_BACKGROUND); @@ -510,6 +471,7 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = left: 9px; height: 32px; width: 32px; + z-index: 1; } .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active:before, @@ -549,7 +511,3 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = } } }); - -const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(SyncActionDescriptor.from(PreviousSideBarViewAction), 'View: Previous Side Bar View', CATEGORIES.View.value); -registry.registerWorkbenchAction(SyncActionDescriptor.from(NextSideBarViewAction), 'View: Next Side Bar View', CATEGORIES.View.value); diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 2a3a93d01..39a3ca76e 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -8,24 +8,24 @@ import * as nls from 'vs/nls'; import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { GLOBAL_ACTIVITY_ID, IActivity, ACCOUNTS_ACTIVITY_ID } from 'vs/workbench/common/activity'; import { Part } from 'vs/workbench/browser/part'; -import { GlobalActivityActionViewItem, ViewContainerActivityAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewContainerActivityAction, AccountsActionViewItem, HomeAction, HomeActionViewItem, ACCOUNTS_VISIBILITY_PREFERENCE_KEY } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; +import { GlobalActivityActionViewItem, ViewContainerActivityAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewContainerActivityAction, AccountsActivityActionViewItem, HomeActivityActionViewItem } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; import { IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { ToggleActivityBarVisibilityAction, ToggleMenuBarAction, ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions'; -import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_ACTIVE_BACKGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BORDER } from 'vs/workbench/common/theme'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbench/browser/parts/compositeBar'; import { Dimension, createCSSRule, asCSSUrl, addDisposableListener, EventType } from 'vs/base/browser/dom'; -import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity } from 'vs/workbench/browser/parts/compositeBarActions'; import { IViewDescriptorService, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewContainerModel, ViewContainerLocation, IViewsService } from 'vs/workbench/common/views'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { isUndefinedOrNull, assertIsDefined, isString } from 'vs/base/common/types'; +import { assertIsDefined } from 'vs/base/common/types'; import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Schemas } from 'vs/base/common/network'; @@ -34,7 +34,6 @@ import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menuba import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { getMenuBarVisibility } from 'vs/platform/windows/common/windows'; import { isWeb } from 'vs/base/common/platform'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { Before2D } from 'vs/workbench/browser/dnd'; import { Codicon, iconRegistry } from 'vs/base/common/codicons'; import { Action, Separator } from 'vs/base/common/actions'; @@ -43,41 +42,45 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { CATEGORIES } from 'vs/workbench/common/actions'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; interface IPlaceholderViewContainer { - id: string; - name?: string; - iconUrl?: UriComponents; - iconCSS?: string; - views?: { when?: string }[]; + readonly id: string; + readonly name?: string; + readonly iconUrl?: UriComponents; + readonly themeIcon?: ThemeIcon; + readonly views?: { when?: string }[]; } interface IPinnedViewContainer { - id: string; - pinned: boolean; - order?: number; - visible: boolean; + readonly id: string; + readonly pinned: boolean; + readonly order?: number; + readonly visible: boolean; } interface ICachedViewContainer { - id: string; + readonly id: string; name?: string; - icon?: URI | string; - pinned: boolean; - order?: number; + icon?: URI | ThemeIcon; + readonly pinned: boolean; + readonly order?: number; visible: boolean; views?: { when?: string }[]; } +const settingsViewBarIcon = registerIcon('settings-view-bar-icon', Codicon.settingsGear, nls.localize('settingsViewBarIcon', 'Settings icon in the view bar.')); +const accountsViewBarIcon = registerIcon('accounts-view-bar-icon', Codicon.account, nls.localize('accountsViewBarIcon', 'Accounts icon in the view bar.')); + export class ActivitybarPart extends Part implements IActivityBarService { declare readonly _serviceBrand: undefined; - private static readonly ACTION_HEIGHT = 48; - static readonly PINNED_VIEW_CONTAINERS = 'workbench.activity.pinnedViewlets2'; + private static readonly PINNED_VIEW_CONTAINERS = 'workbench.activity.pinnedViewlets2'; private static readonly PLACEHOLDER_VIEW_CONTAINERS = 'workbench.activity.placeholderViewlets'; - private static readonly HOME_BAR_VISIBILITY_PREFERENCE = 'workbench.activity.showHomeIndicator'; + private static readonly ACTION_HEIGHT = 48; private static readonly ACCOUNTS_ACTION_INDEX = 0; + //#region IView readonly minimumWidth: number = 48; @@ -100,17 +103,17 @@ export class ActivitybarPart extends Part implements IActivityBarService { private globalActivityAction: ActivityAction | undefined; private globalActivityActionBar: ActionBar | undefined; - private readonly globalActivity: ICompositeActivity[] = []; private globalActivitiesContainer: HTMLElement | undefined; + private readonly globalActivity: ICompositeActivity[] = []; private accountsActivityAction: ActivityAction | undefined; - private accountsActivity: ICompositeActivity[] = []; + private readonly accountsActivity: ICompositeActivity[] = []; private readonly compositeActions = new Map(); private readonly viewContainerDisposables = new Map(); - private readonly keyboardNavigationDisposables = new DisposableStore(); + private readonly keyboardNavigationDisposables = this._register(new DisposableStore()); private readonly location = ViewContainerLocation.Sidebar; @@ -125,27 +128,36 @@ export class ActivitybarPart extends Part implements IActivityBarService { @IContextKeyService private readonly contextKeyService: IContextKeyService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { super(Parts.ACTIVITYBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); - storageKeysSyncRegistryService.registerStorageKey({ key: ActivitybarPart.PINNED_VIEW_CONTAINERS, version: 1 }); - storageKeysSyncRegistryService.registerStorageKey({ key: ActivitybarPart.HOME_BAR_VISIBILITY_PREFERENCE, version: 1 }); - storageKeysSyncRegistryService.registerStorageKey({ key: ACCOUNTS_VISIBILITY_PREFERENCE_KEY, version: 1 }); - - this.migrateFromOldCachedViewContainersValue(); - for (const cachedViewContainer of this.cachedViewContainers) { - if (environmentService.remoteAuthority // In remote window, hide activity bar entries until registered. - || this.shouldBeHidden(cachedViewContainer.id, cachedViewContainer) + if ( + environmentService.remoteAuthority || // In remote window, hide activity bar entries until registered + this.shouldBeHidden(cachedViewContainer.id, cachedViewContainer) ) { cachedViewContainer.visible = false; } } + this.compositeBar = this.createCompositeBar(); + + this.onDidRegisterViewContainers(this.getViewContainers()); + + this.registerListeners(); + } + + private createCompositeBar() { const cachedItems = this.cachedViewContainers - .map(v => ({ id: v.id, name: v.name, visible: v.visible, order: v.order, pinned: v.pinned })); - this.compositeBar = this._register(this.instantiationService.createInstance(CompositeBar, cachedItems, { + .map(container => ({ + id: container.id, + name: container.name, + visible: container.visible, + order: container.order, + pinned: container.pinned + })); + + return this._register(this.instantiationService.createInstance(CompositeBar, cachedItems, { icon: true, orientation: ActionsOrientation.VERTICAL, preventLoopNavigation: true, @@ -154,8 +166,9 @@ export class ActivitybarPart extends Part implements IActivityBarService { getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction, getOnCompositeClickAction: (compositeId: string) => new Action(compositeId, '', '', true, () => this.viewsService.isViewContainerVisible(compositeId) ? Promise.resolve(this.viewsService.closeViewContainer(compositeId)) : this.viewsService.openViewContainer(compositeId)), getContextMenuActions: () => { - const menuBarVisibility = getMenuBarVisibility(this.configurationService, this.environmentService); const actions = []; + + // Home if (this.homeBarContainer) { actions.push(new Action( 'toggleHomeBarAction', @@ -166,22 +179,26 @@ export class ActivitybarPart extends Part implements IActivityBarService { )); } + // Menu + const menuBarVisibility = getMenuBarVisibility(this.configurationService); if (menuBarVisibility === 'compact' || (menuBarVisibility === 'hidden' && isWeb)) { actions.push(this.instantiationService.createInstance(ToggleMenuBarAction, ToggleMenuBarAction.ID, menuBarVisibility === 'compact' ? nls.localize('hideMenu', "Hide Menu") : nls.localize('showMenu', "Show Menu"))); } - const toggleAccountsVisibilityAction = new Action( + // Accounts + actions.push(new Action( 'toggleAccountsVisibility', this.accountsVisibilityPreference ? nls.localize('hideAccounts', "Hide Accounts") : nls.localize('showAccounts', "Show Accounts"), undefined, true, async () => { this.accountsVisibilityPreference = !this.accountsVisibilityPreference; } - ); - - actions.push(toggleAccountsVisibilityAction); + )); actions.push(new Separator()); + // Toggle Sidebar actions.push(this.instantiationService.createInstance(ToggleSidebarPositionAction, ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.getLabel(this.layoutService))); + + // Toggle Activity Bar actions.push(new Action( ToggleActivityBarVisibilityAction.ID, nls.localize('hideActivitBar', "Hide Activity Bar"), @@ -204,19 +221,12 @@ export class ActivitybarPart extends Part implements IActivityBarService { colors: (theme: IColorTheme) => this.getActivitybarItemColors(theme), overflowActionSize: ActivitybarPart.ACTION_HEIGHT })); - - this.onDidRegisterViewContainers(this.getViewContainers()); - this.registerListeners(); - } - - focusActivityBar(): void { - this.compositeBar.focus(); } private getContextMenuActionsForComposite(compositeId: string): Action[] { - const viewContainer = this.viewDescriptorService.getViewContainerById(compositeId)!; - const actions = []; + + const viewContainer = this.viewDescriptorService.getViewContainerById(compositeId)!; const defaultLocation = this.viewDescriptorService.getDefaultViewContainerLocation(viewContainer)!; if (defaultLocation !== this.viewDescriptorService.getViewContainerLocation(viewContainer)) { actions.push(new Action('resetLocationAction', nls.localize('resetLocation', "Reset Location"), undefined, true, async () => { @@ -253,13 +263,13 @@ export class ActivitybarPart extends Part implements IActivityBarService { disposables.clear(); this.onDidRegisterExtensions(); this.compositeBar.onDidChange(() => this.saveCachedViewContainers(), this, disposables); - this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e), this, disposables); + this.storageService.onDidChangeValue(e => this.onDidStorageValueChange(e), this, disposables); })); // Register for configuration changes this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('window.menuBarVisibility')) { - if (getMenuBarVisibility(this.configurationService, this.environmentService) === 'compact') { + if (getMenuBarVisibility(this.configurationService) === 'compact') { this.installMenubar(); } else { this.uninstallMenubar(); @@ -277,6 +287,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { if (from === this.location) { this.onDidDeregisterViewContainer(container); } + if (to === this.location) { this.onDidRegisterViewContainers([container]); } @@ -306,6 +317,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { private onDidViewContainerVisible(id: string): void { const viewContainer = this.getViewContainer(id); if (viewContainer) { + // Update the composite bar by adding this.compositeBar.addComposite(viewContainer); this.compositeBar.activateComposite(viewContainer.id); @@ -313,7 +325,8 @@ export class ActivitybarPart extends Part implements IActivityBarService { if (viewContainer.hideIfEmpty) { const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); if (viewContainerModel.activeViewDescriptors.length === 0) { - this.hideComposite(viewContainer.id); // Update the composite bar by hiding + // Update the composite bar by hiding + this.hideComposite(viewContainer.id); } } } @@ -339,6 +352,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { if (typeof priority !== 'number') { priority = 0; } + const activity: ICompositeActivity = { badge, clazz, priority }; const activityCache = activityId === GLOBAL_ACTIVITY_ID ? this.globalActivity : this.accountsActivity; @@ -387,16 +401,18 @@ export class ActivitybarPart extends Part implements IActivityBarService { private getCumulativeNumberBadge(activityCache: ICompositeActivity[], priority: number): NumberBadge { const numberActivities = activityCache.filter(activity => activity.badge instanceof NumberBadge && activity.priority === priority); - let number = numberActivities.reduce((result, activity) => { return result + (activity.badge).number; }, 0); - let descriptorFn = (): string => { + const number = numberActivities.reduce((result, activity) => { return result + (activity.badge).number; }, 0); + const descriptorFn = (): string => { return numberActivities.reduce((result, activity, index) => { result = result + (activity.badge).getDescription(); if (index < numberActivities.length - 1) { - result = result + '\n'; + result = `${result}\n`; } + return result; }, ''); }; + return new NumberBadge(number, descriptorFn); } @@ -414,11 +430,19 @@ export class ActivitybarPart extends Part implements IActivityBarService { } private installMenubar() { + if (this.menuBar) { + return; // prevent menu bar from installing twice #110720 + } + this.menuBarContainer = document.createElement('div'); this.menuBarContainer.classList.add('menubar'); const content = assertIsDefined(this.content); - content.prepend(this.menuBarContainer); + if (this.homeBarContainer) { + content.insertBefore(this.menuBarContainer, this.homeBarContainer.nextSibling); + } else { + content.prepend(this.menuBarContainer); + } // Menubar: install a custom menu bar depending on configuration this.menuBar = this._register(this.instantiationService.createInstance(CustomMenubarControl)); @@ -442,12 +466,12 @@ export class ActivitybarPart extends Part implements IActivityBarService { codicon = Codicon.code; } - this.createHomeBar(homeIndicator.href, homeIndicator.title, codicon); + this.createHomeBar(homeIndicator.href, codicon); this.onDidChangeHomeBarVisibility(); } // Install menubar if compact - if (getMenuBarVisibility(this.configurationService, this.environmentService) === 'compact') { + if (getMenuBarVisibility(this.configurationService) === 'compact') { this.installMenubar(); } @@ -456,11 +480,11 @@ export class ActivitybarPart extends Part implements IActivityBarService { // Global action bar this.globalActivitiesContainer = document.createElement('div'); - this.globalActivitiesContainer.classList.add('global-activity'); this.content.appendChild(this.globalActivitiesContainer); this.createGlobalActivityActionBar(this.globalActivitiesContainer); + // Keyboard Navigation this.registerKeyboardNavigationListeners(); return this.content; @@ -528,23 +552,19 @@ export class ActivitybarPart extends Part implements IActivityBarService { } })); } - - - } - private createHomeBar(href: string, title: string, icon: Codicon): void { + private createHomeBar(href: string, icon: Codicon): void { this.homeBarContainer = document.createElement('div'); this.homeBarContainer.setAttribute('aria-label', nls.localize('homeIndicator', "Home")); this.homeBarContainer.setAttribute('role', 'toolbar'); this.homeBarContainer.classList.add('home-bar'); this.homeBar = this._register(new ActionBar(this.homeBarContainer, { + actionViewItemProvider: action => this.instantiationService.createInstance(HomeActivityActionViewItem, href, action as ActivityAction, (theme: IColorTheme) => this.getActivitybarItemColors(theme)), orientation: ActionsOrientation.VERTICAL, - animated: false, ariaLabel: nls.localize('home', "Home"), - actionViewItemProvider: action => new HomeActionViewItem(action), - allowContextMenu: true, + animated: false, preventLoopNavigation: true, ignoreOrientationForPreviousAndNextKey: true })); @@ -552,35 +572,15 @@ export class ActivitybarPart extends Part implements IActivityBarService { const homeBarIconBadge = document.createElement('div'); homeBarIconBadge.classList.add('home-bar-icon-badge'); this.homeBarContainer.appendChild(homeBarIconBadge); - this.homeBar.push(this._register(this.instantiationService.createInstance(HomeAction, href, title, icon))); + + this.homeBar.push(this._register(new ActivityAction({ + id: 'workbench.actions.home', + name: nls.localize('home', "Home"), + cssClass: icon.classNames + }))); const content = assertIsDefined(this.content); - content.prepend(this.homeBarContainer); - } - - updateStyles(): void { - super.updateStyles(); - - const container = assertIsDefined(this.getContainer()); - const background = this.getColor(ACTIVITY_BAR_BACKGROUND) || ''; - container.style.backgroundColor = background; - - const borderColor = this.getColor(ACTIVITY_BAR_BORDER) || this.getColor(contrastBorder) || ''; - container.classList.toggle('bordered', !!borderColor); - container.style.borderColor = borderColor ? borderColor : ''; - } - - private getActivitybarItemColors(theme: IColorTheme): ICompositeBarColors { - return { - activeForegroundColor: theme.getColor(ACTIVITY_BAR_FOREGROUND), - inactiveForegroundColor: theme.getColor(ACTIVITY_BAR_INACTIVE_FOREGROUND), - activeBorderColor: theme.getColor(ACTIVITY_BAR_ACTIVE_BORDER), - activeBackground: theme.getColor(ACTIVITY_BAR_ACTIVE_BACKGROUND), - badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND), - badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND), - dragAndDropBorder: theme.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BORDER), - activeBackgroundColor: undefined, inactiveBackgroundColor: undefined, activeBorderBottomColor: undefined, - }; + content.appendChild(this.homeBarContainer); } private createGlobalActivityActionBar(container: HTMLElement): void { @@ -591,7 +591,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { } if (action.id === 'workbench.actions.accounts') { - return this.instantiationService.createInstance(AccountsActionViewItem, action as ActivityAction, (theme: IColorTheme) => this.getActivitybarItemColors(theme)); + return this.instantiationService.createInstance(AccountsActivityActionViewItem, action as ActivityAction, (theme: IColorTheme) => this.getActivitybarItemColors(theme)); } throw new Error(`No view item for action '${action.id}'`); @@ -603,18 +603,18 @@ export class ActivitybarPart extends Part implements IActivityBarService { ignoreOrientationForPreviousAndNextKey: true })); - this.globalActivityAction = new ActivityAction({ + this.globalActivityAction = this._register(new ActivityAction({ id: 'workbench.actions.manage', name: nls.localize('manage', "Manage"), - cssClass: Codicon.settingsGear.classNames - }); + cssClass: ThemeIcon.asClassName(settingsViewBarIcon) + })); if (this.accountsVisibilityPreference) { - this.accountsActivityAction = new ActivityAction({ + this.accountsActivityAction = this._register(new ActivityAction({ id: 'workbench.actions.accounts', name: nls.localize('accounts', "Accounts"), - cssClass: Codicon.account.classNames - }); + cssClass: ThemeIcon.asClassName(accountsViewBarIcon) + })); this.globalActivityActionBar.push(this.accountsActivityAction, { index: ActivitybarPart.ACCOUNTS_ACTION_INDEX }); } @@ -628,11 +628,11 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.globalActivityActionBar.pull(ActivitybarPart.ACCOUNTS_ACTION_INDEX); this.accountsActivityAction = undefined; } else { - this.accountsActivityAction = new ActivityAction({ + this.accountsActivityAction = this._register(new ActivityAction({ id: 'workbench.actions.accounts', name: nls.localize('accounts', "Accounts"), cssClass: Codicon.account.classNames - }); + })); this.globalActivityActionBar.push(this.accountsActivityAction, { index: ActivitybarPart.ACCOUNTS_ACTION_INDEX }); } } @@ -723,7 +723,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { return ActivitybarPart.toActivity(id, name, icon, focusCommand?.id || id); } - private static toActivity(id: string, name: string, icon: URI | string | undefined, keybindingId: string | undefined): IActivity { + private static toActivity(id: string, name: string, icon: URI | ThemeIcon | undefined, keybindingId: string | undefined): IActivity { let cssClass: string | undefined = undefined; let iconUrl: URI | undefined = undefined; if (URI.isUri(icon)) { @@ -736,9 +736,10 @@ export class ActivitybarPart extends Part implements IActivityBarService { -webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%; -webkit-mask-size: 24px; `); - } else if (isString(icon)) { - cssClass = icon; + } else if (ThemeIcon.isThemeIcon(icon)) { + cssClass = ThemeIcon.asClassName(icon); } + return { id, name, cssClass, iconUrl, keybindingId }; } @@ -810,6 +811,35 @@ export class ActivitybarPart extends Part implements IActivityBarService { .map(v => v.id); } + focusActivityBar(): void { + this.compositeBar.focus(); + } + + updateStyles(): void { + super.updateStyles(); + + const container = assertIsDefined(this.getContainer()); + const background = this.getColor(ACTIVITY_BAR_BACKGROUND) || ''; + container.style.backgroundColor = background; + + const borderColor = this.getColor(ACTIVITY_BAR_BORDER) || this.getColor(contrastBorder) || ''; + container.classList.toggle('bordered', !!borderColor); + container.style.borderColor = borderColor ? borderColor : ''; + } + + private getActivitybarItemColors(theme: IColorTheme): ICompositeBarColors { + return { + activeForegroundColor: theme.getColor(ACTIVITY_BAR_FOREGROUND), + inactiveForegroundColor: theme.getColor(ACTIVITY_BAR_INACTIVE_FOREGROUND), + activeBorderColor: theme.getColor(ACTIVITY_BAR_ACTIVE_BORDER), + activeBackground: theme.getColor(ACTIVITY_BAR_ACTIVE_BACKGROUND), + badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND), + badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND), + dragAndDropBorder: theme.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BORDER), + activeBackgroundColor: undefined, inactiveBackgroundColor: undefined, activeBorderBottomColor: undefined, + }; + } + layout(width: number, height: number): void { if (!this.layoutService.isVisible(Parts.ACTIVITYBAR_PART)) { return; @@ -834,6 +864,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { private getViewContainer(id: string): ViewContainer | undefined { const viewContainer = this.viewDescriptorService.getViewContainerById(id); + return viewContainer && this.viewDescriptorService.getViewContainerLocation(viewContainer) === this.location ? viewContainer : undefined; } @@ -841,7 +872,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { return this.viewDescriptorService.getViewContainersByLocation(this.location); } - private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { + private onDidStorageValueChange(e: IStorageValueChangeEvent): void { if (e.key === ActivitybarPart.PINNED_VIEW_CONTAINERS && e.scope === StorageScope.GLOBAL && this.pinnedViewContainersValue !== this.getStoredPinnedViewContainersValue() /* This checks if current window changed the value or not */) { this._pinnedViewContainersValue = undefined; @@ -870,11 +901,11 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.compositeBar.setCompositeBarItems(newCompositeItems); } - if (e.key === ActivitybarPart.HOME_BAR_VISIBILITY_PREFERENCE && e.scope === StorageScope.GLOBAL) { + if (e.key === HomeActivityActionViewItem.HOME_BAR_VISIBILITY_PREFERENCE && e.scope === StorageScope.GLOBAL) { this.onDidChangeHomeBarVisibility(); } - if (e.key === ACCOUNTS_VISIBILITY_PREFERENCE_KEY && e.scope === StorageScope.GLOBAL) { + if (e.key === AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY && e.scope === StorageScope.GLOBAL) { this.toggleAccountsActivity(); } } @@ -917,12 +948,13 @@ export class ActivitybarPart extends Part implements IActivityBarService { const cachedViewContainer = this._cachedViewContainers.filter(cached => cached.id === placeholderViewContainer.id)[0]; if (cachedViewContainer) { cachedViewContainer.name = placeholderViewContainer.name; - cachedViewContainer.icon = placeholderViewContainer.iconCSS ? placeholderViewContainer.iconCSS : + cachedViewContainer.icon = placeholderViewContainer.themeIcon ? ThemeIcon.revive(placeholderViewContainer.themeIcon) : placeholderViewContainer.iconUrl ? URI.revive(placeholderViewContainer.iconUrl) : undefined; cachedViewContainer.views = placeholderViewContainer.views; } } } + return this._cachedViewContainers; } @@ -933,10 +965,11 @@ export class ActivitybarPart extends Part implements IActivityBarService { visible, order }))); + this.setPlaceholderViewContainers(cachedViewContainers.map(({ id, icon, name, views }) => ({ id, iconUrl: URI.isUri(icon) ? icon : undefined, - iconCSS: isString(icon) ? icon : undefined, + themeIcon: ThemeIcon.isThemeIcon(icon) ? icon : undefined, name, views }))); @@ -971,7 +1004,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { } private setStoredPinnedViewContainersValue(value: string): void { - this.storageService.store(ActivitybarPart.PINNED_VIEW_CONTAINERS, value, StorageScope.GLOBAL); + this.storageService.store(ActivitybarPart.PINNED_VIEW_CONTAINERS, value, StorageScope.GLOBAL, StorageTarget.USER); } private getPlaceholderViewContainers(): IPlaceholderViewContainer[] { @@ -1003,37 +1036,23 @@ export class ActivitybarPart extends Part implements IActivityBarService { } private setStoredPlaceholderViewContainersValue(value: string): void { - this.storageService.store(ActivitybarPart.PLACEHOLDER_VIEW_CONTAINERS, value, StorageScope.GLOBAL); + this.storageService.store(ActivitybarPart.PLACEHOLDER_VIEW_CONTAINERS, value, StorageScope.GLOBAL, StorageTarget.MACHINE); } private get homeBarVisibilityPreference(): boolean { - return this.storageService.getBoolean(ActivitybarPart.HOME_BAR_VISIBILITY_PREFERENCE, StorageScope.GLOBAL, true); + return this.storageService.getBoolean(HomeActivityActionViewItem.HOME_BAR_VISIBILITY_PREFERENCE, StorageScope.GLOBAL, true); } private set homeBarVisibilityPreference(value: boolean) { - this.storageService.store(ActivitybarPart.HOME_BAR_VISIBILITY_PREFERENCE, value, StorageScope.GLOBAL); + this.storageService.store(HomeActivityActionViewItem.HOME_BAR_VISIBILITY_PREFERENCE, value, StorageScope.GLOBAL, StorageTarget.USER); } private get accountsVisibilityPreference(): boolean { - return this.storageService.getBoolean(ACCOUNTS_VISIBILITY_PREFERENCE_KEY, StorageScope.GLOBAL, true); + return this.storageService.getBoolean(AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, StorageScope.GLOBAL, true); } private set accountsVisibilityPreference(value: boolean) { - this.storageService.store(ACCOUNTS_VISIBILITY_PREFERENCE_KEY, value, StorageScope.GLOBAL); - } - - private migrateFromOldCachedViewContainersValue(): void { - const value = this.storageService.get('workbench.activity.pinnedViewlets', StorageScope.GLOBAL); - if (value !== undefined) { - const storedStates: Array = JSON.parse(value); - const cachedViewContainers = storedStates.map(c => { - const serialized: ICachedViewContainer = typeof c === 'string' /* migration from pinned states to composites states */ ? { id: c, pinned: true, order: undefined, visible: true, name: undefined, icon: undefined, views: undefined } : c; - serialized.visible = isUndefinedOrNull(serialized.visible) ? true : serialized.visible; - return serialized; - }); - this.storeCachedViewContainersState(cachedViewContainers); - this.storageService.remove('workbench.activity.pinnedViewlets', StorageScope.GLOBAL); - } + this.storageService.store(AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, value, StorageScope.GLOBAL, StorageTarget.USER); } toJSON(): object { diff --git a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css index 39fa9ebde..57d6f2a85 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css @@ -9,10 +9,6 @@ margin-bottom: 4px; } -.monaco-workbench .activitybar > .content > .home-bar > .monaco-action-bar .action-item { - margin-bottom: 0; -} - .monaco-workbench .activitybar > .content .composite-bar > .monaco-action-bar .action-item::before, .monaco-workbench .activitybar > .content .composite-bar > .monaco-action-bar .action-item::after { position: absolute; diff --git a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css index ced2d8158..4f2d86b17 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css @@ -51,10 +51,6 @@ position: relative; width: 100%; height: 48px; - display: flex; - align-items: center; - justify-content: center; - order: -1; } .monaco-workbench .activitybar > .content > .home-bar > .home-bar-icon-badge { @@ -88,10 +84,6 @@ margin-bottom: auto; } -.monaco-workbench .activitybar > .content > .composite-bar-excess { - height: 100%; -} - /** Menu Bar */ .monaco-workbench .activitybar .menubar { diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 5c481fafa..1350ddfe5 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -18,7 +18,7 @@ import { Composite, CompositeRegistry } from 'vs/workbench/browser/composite'; import { IComposite } from 'vs/workbench/common/composite'; import { CompositeProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -199,7 +199,7 @@ export abstract class CompositePart extends Part { // Store in preferences const id = this.activeComposite.getId(); if (id !== this.defaultCompositeId) { - this.storageService.store(this.activeCompositeSettingsKey, id, StorageScope.WORKSPACE); + this.storageService.store(this.activeCompositeSettingsKey, id, StorageScope.WORKSPACE, StorageTarget.USER); } else { this.storageService.remove(this.activeCompositeSettingsKey, StorageScope.WORKSPACE); } diff --git a/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts b/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts new file mode 100644 index 000000000..b5fc1d1fa --- /dev/null +++ b/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts @@ -0,0 +1,76 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IDialogHandler, IDialogResult, IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IDialogsModel, IDialogViewItem } from 'vs/workbench/common/dialogs'; +import { BrowserDialogHandler } from 'vs/workbench/browser/parts/dialogs/dialogHandler'; +import { DialogService } from 'vs/workbench/services/dialogs/common/dialogService'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; + +export class DialogHandlerContribution extends Disposable implements IWorkbenchContribution { + private impl: IDialogHandler; + + private model: IDialogsModel; + private currentDialog: IDialogViewItem | undefined; + + constructor( + @IDialogService private dialogService: IDialogService, + @ILogService logService: ILogService, + @ILayoutService layoutService: ILayoutService, + @IThemeService themeService: IThemeService, + @IKeybindingService keybindingService: IKeybindingService, + @IProductService productService: IProductService, + @IClipboardService clipboardService: IClipboardService + ) { + super(); + + this.impl = new BrowserDialogHandler(logService, layoutService, themeService, keybindingService, productService, clipboardService); + + this.model = (this.dialogService as DialogService).model; + + this._register(this.model.onDidShowDialog(() => { + if (!this.currentDialog) { + this.processDialogs(); + } + })); + + this.processDialogs(); + } + + private async processDialogs(): Promise { + while (this.model.dialogs.length) { + this.currentDialog = this.model.dialogs[0]; + + let result: IDialogResult | undefined = undefined; + if (this.currentDialog.args.confirmArgs) { + const args = this.currentDialog.args.confirmArgs; + result = await this.impl.confirm(args.confirmation); + } else if (this.currentDialog.args.inputArgs) { + const args = this.currentDialog.args.inputArgs; + result = await this.impl.input(args.severity, args.message, args.buttons, args.inputs, args.options); + } else if (this.currentDialog.args.showArgs) { + const args = this.currentDialog.args.showArgs; + result = await this.impl.show(args.severity, args.message, args.buttons, args.options); + } else { + await this.impl.about(); + } + + this.currentDialog.close(result); + this.currentDialog = undefined; + } + } +} + +const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchRegistry.registerWorkbenchContribution(DialogHandlerContribution, LifecyclePhase.Starting); diff --git a/src/vs/workbench/services/dialogs/browser/dialogService.ts b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts similarity index 91% rename from src/vs/workbench/services/dialogs/browser/dialogService.ts rename to src/vs/workbench/browser/parts/dialogs/dialogHandler.ts index 85d83f37d..fe65e6dad 100644 --- a/src/vs/workbench/services/dialogs/browser/dialogService.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { IDialogService, IDialogOptions, IConfirmation, IConfirmationResult, DialogType, IShowResult, IInputResult, ICheckbox, IInput } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogOptions, IConfirmation, IConfirmationResult, DialogType, IShowResult, IInputResult, ICheckbox, IInput, IDialogHandler } from 'vs/platform/dialogs/common/dialogs'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ILogService } from 'vs/platform/log/common/log'; import Severity from 'vs/base/common/severity'; @@ -17,12 +17,9 @@ import { EventHelper } from 'vs/base/browser/dom'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IProductService } from 'vs/platform/product/common/productService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { fromNow } from 'vs/base/common/date'; -export class DialogService implements IDialogService { - - declare readonly _serviceBrand: undefined; +export class BrowserDialogHandler implements IDialogHandler { private static readonly ALLOWABLE_COMMANDS = [ 'copy', @@ -91,7 +88,7 @@ export class DialogService implements IDialogService { keyEventProcessor: (event: StandardKeyboardEvent) => { const resolved = this.keybindingService.softDispatch(event, this.layoutService.container); if (resolved && resolved.commandId) { - if (DialogService.ALLOWABLE_COMMANDS.indexOf(resolved.commandId) === -1) { + if (BrowserDialogHandler.ALLOWABLE_COMMANDS.indexOf(resolved.commandId) === -1) { EventHelper.stop(event, true); } } @@ -144,5 +141,3 @@ export class DialogService implements IDialogService { } } } - -registerSingleton(IDialogService, DialogService, true); diff --git a/src/vs/workbench/browser/parts/editor/binaryEditor.ts b/src/vs/workbench/browser/parts/editor/binaryEditor.ts index e9bbf8c7c..2de9f55a9 100644 --- a/src/vs/workbench/browser/parts/editor/binaryEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryEditor.ts @@ -20,7 +20,7 @@ import { dispose, IDisposable, Disposable, DisposableStore } from 'vs/base/commo import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; -import { BinarySize } from 'vs/platform/files/common/files'; +import { ByteSize } from 'vs/platform/files/common/files'; export interface IOpenCallbacks { openInternal: (input: EditorInput, options: EditorOptions | undefined) => Promise; @@ -182,7 +182,7 @@ interface ResourceViewerDelegate { class ResourceViewer { - private static readonly MAX_OPEN_INTERNAL_SIZE = BinarySize.MB * 200; // max size until we offer an action to open internally + private static readonly MAX_OPEN_INTERNAL_SIZE = ByteSize.MB * 200; // max size until we offer an action to open internally static show( descriptor: IResourceDescriptor, @@ -211,7 +211,7 @@ class FileTooLargeFileView { scrollbar: DomScrollableElement, delegate: ResourceViewerDelegate ) { - const size = BinarySize.formatSize(descriptorSize); + const size = ByteSize.formatSize(descriptorSize); delegate.metadataClb(size); clearNode(container); @@ -233,7 +233,7 @@ class FileSeemsBinaryFileView { scrollbar: DomScrollableElement, delegate: ResourceViewerDelegate ) { - delegate.metadataClb(typeof descriptor.size === 'number' ? BinarySize.formatSize(descriptor.size) : ''); + delegate.metadataClb(typeof descriptor.size === 'number' ? ByteSize.formatSize(descriptor.size) : ''); clearNode(container); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index b743f504b..541ea9b2c 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -491,7 +491,7 @@ export class BreadcrumbsControl { if (element instanceof FileElement) { if (element.kind === FileKind.FILE) { // open file in any editor - this._editorService.openEditor({ resource: element.uri, options: { pinned: pinned } }, group); + this._editorService.openEditor({ resource: element.uri, options: { pinned } }, group); } else { // show next picker let items = this._widget.getItems(); @@ -508,7 +508,8 @@ export class BreadcrumbsControl { resource: model.uri, options: { selection: Range.collapseToStart(element.symbol.selectionRange), - selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport + selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport, + pinned } }, withUndefinedAsNull(this._getActiveCodeEditor()), group === SIDE_GROUP); } @@ -753,13 +754,14 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ // open symbol in editor return editors.openEditor({ resource: outlineElement.uri, - options: { selection: Range.collapseToStart(element.symbol.selectionRange) } + options: { selection: Range.collapseToStart(element.symbol.selectionRange), pinned: true } }, SIDE_GROUP); } else if (element && URI.isUri(element.resource)) { // open file in editor return editors.openEditor({ resource: element.resource, + options: { pinned: true } }, SIDE_GROUP); } else { diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index d1585c4da..001398ec0 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -99,7 +99,7 @@ export abstract class BreadcrumbsPicker { this._treeContainer = document.createElement('div'); this._treeContainer.style.background = color ? color.toString() : ''; this._treeContainer.style.paddingTop = '2px'; - this._treeContainer.style.boxShadow = `0px 5px 8px ${this._themeService.getColorTheme().getColor(widgetShadow)}`; + this._treeContainer.style.boxShadow = `0 0 8px 2px ${this._themeService.getColorTheme().getColor(widgetShadow)}`; this._domNode.appendChild(this._treeContainer); this._layoutInfo = { maxHeight, width, arrowSize, arrowOffset, inputHeight: 0 }; @@ -436,7 +436,6 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { } protected _getTargetFromEvent(element: any): any | undefined { - // todo@joh if (element && !IWorkspaceFolder.isIWorkspaceFolder(element) && !(element as IFileStat).isDirectory) { return new FileElement((element as IFileStat).resource, FileKind.FILE); } diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 05f02021e..fdb0a57ac 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -55,6 +55,8 @@ import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/pl import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess, AllEditorsByAppearanceQuickAccess, AllEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { FileAccess } from 'vs/base/common/network'; +import { Codicon } from 'vs/base/common/codicons'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; // Register String Editor Registry.as(EditorExtensions.Editors).registerEditor( @@ -238,27 +240,27 @@ export abstract class AbstractSideBySideEditorInputFactory implements IEditorInp const secondaryInput = secondaryInputFactory.deserialize(instantiationService, deserialized.secondarySerialized); if (primaryInput && secondaryInput) { - return this.createEditorInput(deserialized.name, deserialized.description, secondaryInput, primaryInput); + return this.createEditorInput(instantiationService, deserialized.name, deserialized.description, secondaryInput, primaryInput); } } return undefined; } - protected abstract createEditorInput(name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput; + protected abstract createEditorInput(instantiationService: IInstantiationService, name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput; } class SideBySideEditorInputFactory extends AbstractSideBySideEditorInputFactory { - protected createEditorInput(name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput { + protected createEditorInput(instantiationService: IInstantiationService, name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput { return new SideBySideEditorInput(name, description, secondaryInput, primaryInput); } } class DiffEditorInputFactory extends AbstractSideBySideEditorInputFactory { - protected createEditorInput(name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput { - return new DiffEditorInput(name, description, secondaryInput, primaryInput); + protected createEditorInput(instantiationService: IInstantiationService, name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput { + return instantiationService.createInstance(DiffEditorInput, name, description, secondaryInput, primaryInput, undefined); } } @@ -462,6 +464,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands. MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.SHOW_EDITORS_IN_GROUP, title: nls.localize('showOpenedEditors', "Show Opened Editors") }, group: '3_open', order: 10 }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All") }, group: '5_close', order: 10 }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeAllSaved', "Close Saved") }, group: '5_close', order: 20 }); +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.TOGGLE_KEEP_EDITORS_COMMAND_ID, title: nls.localize('toggleKeepEditors', "Keep Editors Open"), toggled: ContextKeyExpr.not('config.workbench.editor.enablePreview') }, group: '7_settings', order: 10 }); interface IEditorToolItem { id: string; title: string; icon?: { dark?: URI; light?: URI; } | ThemeIcon; } @@ -494,14 +497,14 @@ appendEditorToolItem( { id: SplitEditorAction.ID, title: nls.localize('splitEditorRight', "Split Editor Right"), - icon: { id: 'codicon/split-horizontal' } + icon: Codicon.splitHorizontal }, ContextKeyExpr.not('splitEditorsVertically'), 100000, // towards the end { id: editorCommands.SPLIT_EDITOR_DOWN, title: nls.localize('splitEditorDown', "Split Editor Down"), - icon: { id: 'codicon/split-vertical' } + icon: Codicon.splitVertical } ); @@ -509,14 +512,14 @@ appendEditorToolItem( { id: SplitEditorAction.ID, title: nls.localize('splitEditorDown', "Split Editor Down"), - icon: { id: 'codicon/split-vertical' } + icon: Codicon.splitVertical }, ContextKeyExpr.has('splitEditorsVertically'), 100000, // towards the end { id: editorCommands.SPLIT_EDITOR_RIGHT, title: nls.localize('splitEditorRight', "Split Editor Right"), - icon: { id: 'codicon/split-horizontal' } + icon: Codicon.splitHorizontal } ); @@ -525,14 +528,14 @@ appendEditorToolItem( { id: editorCommands.CLOSE_EDITOR_COMMAND_ID, title: nls.localize('close', "Close"), - icon: { id: 'codicon/close' } + icon: Codicon.close }, ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ActiveEditorDirtyContext.toNegated(), ActiveEditorStickyContext.toNegated()), 1000000, // towards the far end { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All"), - icon: { id: 'codicon/close-all' } + icon: Codicon.closeAll } ); @@ -541,14 +544,14 @@ appendEditorToolItem( { id: editorCommands.CLOSE_EDITOR_COMMAND_ID, title: nls.localize('close', "Close"), - icon: { id: 'codicon/close-dirty' } + icon: Codicon.closeDirty }, ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ActiveEditorDirtyContext, ActiveEditorStickyContext.toNegated()), 1000000, // towards the far end { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All"), - icon: { id: 'codicon/close-all' } + icon: Codicon.closeAll } ); @@ -557,14 +560,14 @@ appendEditorToolItem( { id: editorCommands.UNPIN_EDITOR_COMMAND_ID, title: nls.localize('unpin', "Unpin"), - icon: { id: 'codicon/pinned' } + icon: Codicon.pinned }, ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ActiveEditorDirtyContext.toNegated(), ActiveEditorStickyContext), 1000000, // towards the far end { id: editorCommands.CLOSE_EDITOR_COMMAND_ID, title: nls.localize('close', "Close"), - icon: { id: 'codicon/close' } + icon: Codicon.close } ); @@ -573,23 +576,28 @@ appendEditorToolItem( { id: editorCommands.UNPIN_EDITOR_COMMAND_ID, title: nls.localize('unpin', "Unpin"), - icon: { id: 'codicon/pinned-dirty' } + icon: Codicon.pinnedDirty }, ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ActiveEditorDirtyContext, ActiveEditorStickyContext), 1000000, // towards the far end { id: editorCommands.CLOSE_EDITOR_COMMAND_ID, title: nls.localize('close', "Close"), - icon: { id: 'codicon/close' } + icon: Codicon.close } ); +const previousChangeIcon = registerIcon('diff-editor-previous-change', Codicon.arrowUp, nls.localize('previousChangeIcon', 'Icon for the previous change action in the diff editor.')); +const nextChangeIcon = registerIcon('diff-editor-next-change', Codicon.arrowDown, nls.localize('nextChangeIcon', 'Icon for the next change action in the diff editor.')); +const toggleWhitespace = registerIcon('diff-editor-toggle-whitespace', Codicon.whitespace, nls.localize('toggleWhitespace', 'Icon for the toggle whitespace action in the diff editor.')); + + // Diff Editor Title Menu: Previous Change appendEditorToolItem( { id: editorCommands.GOTO_PREVIOUS_CHANGE, title: nls.localize('navigate.prev.label', "Previous Change"), - icon: { id: 'codicon/arrow-up' } + icon: previousChangeIcon }, TextCompareEditorActiveContext, 10 @@ -600,7 +608,7 @@ appendEditorToolItem( { id: editorCommands.GOTO_NEXT_CHANGE, title: nls.localize('navigate.next.label', "Next Change"), - icon: { id: 'codicon/arrow-down' } + icon: nextChangeIcon }, TextCompareEditorActiveContext, 11 @@ -611,7 +619,7 @@ appendEditorToolItem( { id: editorCommands.TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, title: nls.localize('ignoreTrimWhitespace.label', "Ignore Leading/Trailing Whitespace Differences"), - icon: { id: 'codicon/whitespace' } + icon: toggleWhitespace }, ContextKeyExpr.and(TextCompareEditorActiveContext, ContextKeyExpr.notEquals('config.diffEditor.ignoreTrimWhitespace', true)), 20 @@ -622,7 +630,7 @@ appendEditorToolItem( { id: editorCommands.TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, title: nls.localize('showTrimWhitespace.label', "Show Leading/Trailing Whitespace Differences"), - icon: { id: 'codicon/whitespace~disabled' } + icon: ThemeIcon.modify(toggleWhitespace, 'disabled') }, ContextKeyExpr.and(TextCompareEditorActiveContext, ContextKeyExpr.notEquals('config.diffEditor.ignoreTrimWhitespace', false)), 20 diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index ef35a87e7..c05ce6d79 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -38,7 +38,8 @@ export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = { openSideBySideDirection: 'right', closeEmptyGroups: true, labelFormat: 'default', - splitSizing: 'distribute' + splitSizing: 'distribute', + splitOnDragAndDrop: true }; export function impactsEditorPartOptions(event: IConfigurationChangeEvent): boolean { @@ -64,12 +65,12 @@ export interface IEditorOpeningEvent extends IEditorIdentifier { /** * The options used when opening the editor. */ - options?: IEditorOptions; + readonly options?: IEditorOptions; /** * Context indicates how the editor open event is initialized. */ - context?: OpenEditorContext; + readonly context?: OpenEditorContext; /** * Allows to prevent the opening of an editor by providing a callback diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 4b62e9478..dcece5343 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -7,22 +7,26 @@ import * as nls from 'vs/nls'; import { isObject, isString, isUndefined, isNumber, withNullAsUndefined } from 'vs/base/common/types'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { TextCompareEditorVisibleContext, EditorInput, IEditorIdentifier, IEditorCommandsContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, CloseDirection, IEditorInput, IVisibleEditorPane, ActiveEditorStickyContext, EditorsOrder } from 'vs/workbench/common/editor'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { TextCompareEditorVisibleContext, EditorInput, IEditorIdentifier, IEditorCommandsContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, CloseDirection, IEditorInput, IVisibleEditorPane, ActiveEditorStickyContext, EditorsOrder, viewColumnToEditorGroup, EditorGroupColumn } from 'vs/workbench/common/editor'; +import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; -import { URI } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { IListService } from 'vs/platform/list/browser/listService'; +import { IListService, IOpenEvent } from 'vs/platform/list/browser/listService'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { distinct, coalesce } from 'vs/base/common/arrays'; import { IEditorGroupsService, IEditorGroup, GroupDirection, GroupLocation, GroupsOrder, preferredSideBySideGroupDirection, EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { openEditorWith } from 'vs/workbench/services/editor/common/editorOpenWith'; export const CLOSE_SAVED_EDITORS_COMMAND_ID = 'workbench.action.closeUnmodifiedEditors'; export const CLOSE_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeEditorsInGroup'; @@ -36,6 +40,7 @@ export const CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeOt export const MOVE_ACTIVE_EDITOR_COMMAND_ID = 'moveActiveEditor'; export const LAYOUT_EDITOR_GROUPS_COMMAND_ID = 'layoutEditorGroups'; export const KEEP_EDITOR_COMMAND_ID = 'workbench.action.keepEditor'; +export const TOGGLE_KEEP_EDITORS_COMMAND_ID = 'workbench.action.toggleKeepEditors'; export const SHOW_EDITORS_IN_GROUP = 'workbench.action.showEditorsInGroup'; export const PIN_EDITOR_COMMAND_ID = 'workbench.action.pinEditor'; @@ -44,6 +49,9 @@ export const UNPIN_EDITOR_COMMAND_ID = 'workbench.action.unpinEditor'; export const TOGGLE_DIFF_SIDE_BY_SIDE = 'toggle.diff.renderSideBySide'; export const GOTO_NEXT_CHANGE = 'workbench.action.compareEditor.nextChange'; export const GOTO_PREVIOUS_CHANGE = 'workbench.action.compareEditor.previousChange'; +export const DIFF_FOCUS_PRIMARY_SIDE = 'workbench.action.compareEditor.focusPrimarySide'; +export const DIFF_FOCUS_SECONDARY_SIDE = 'workbench.action.compareEditor.focusSecondarySide'; +export const DIFF_FOCUS_OTHER_SIDE = 'workbench.action.compareEditor.focusOtherSide'; export const TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE = 'toggle.diff.ignoreTrimWhitespace'; export const SPLIT_EDITOR_UP = 'workbench.action.splitEditorUp'; @@ -58,6 +66,10 @@ export const FOCUS_BELOW_GROUP_WITHOUT_WRAP_COMMAND_ID = 'workbench.action.focus export const OPEN_EDITOR_AT_INDEX_COMMAND_ID = 'workbench.action.openEditorAtIndex'; +export const API_OPEN_EDITOR_COMMAND_ID = '_workbench.open'; +export const API_OPEN_DIFF_EDITOR_COMMAND_ID = '_workbench.diff'; +export const API_OPEN_WITH_EDITOR_COMMAND_ID = '_workbench.openWith'; + export interface ActiveEditorMoveArguments { to: 'first' | 'last' | 'left' | 'right' | 'up' | 'down' | 'center' | 'position' | 'previous' | 'next'; by: 'tab' | 'group'; @@ -227,13 +239,45 @@ function moveActiveEditorToGroup(args: ActiveEditorMoveArguments, control: IVisi } function registerEditorGroupsLayoutCommand(): void { - CommandsRegistry.registerCommand(LAYOUT_EDITOR_GROUPS_COMMAND_ID, (accessor: ServicesAccessor, args: EditorGroupLayout) => { - if (!args || typeof args !== 'object') { + + function applyEditorLayout(accessor: ServicesAccessor, layout: EditorGroupLayout): void { + if (!layout || typeof layout !== 'object') { return; } const editorGroupService = accessor.get(IEditorGroupsService); - editorGroupService.applyLayout(args); + editorGroupService.applyLayout(layout); + } + + CommandsRegistry.registerCommand(LAYOUT_EDITOR_GROUPS_COMMAND_ID, (accessor: ServicesAccessor, args: EditorGroupLayout) => { + applyEditorLayout(accessor, args); + }); + + // API Command + CommandsRegistry.registerCommand({ + id: 'vscode.setEditorLayout', + handler: (accessor: ServicesAccessor, args: EditorGroupLayout) => applyEditorLayout(accessor, args), + description: { + description: 'Set Editor Layout', + args: [{ + name: 'args', + schema: { + 'type': 'object', + 'required': ['groups'], + 'properties': { + 'orientation': { + 'type': 'number', + 'default': 0, + 'enum': [0, 1] + }, + 'groups': { + '$ref': '#/definitions/editorGroupsSchema', + 'default': [{}, {}] + } + } + } + }] + } }); } @@ -265,30 +309,68 @@ function registerDiffEditorCommands(): void { handler: accessor => navigateInDiffEditor(accessor, false) }); - function navigateInDiffEditor(accessor: ServicesAccessor, next: boolean): void { + function getActiveTextDiffEditor(accessor: ServicesAccessor): TextDiffEditor | undefined { const editorService = accessor.get(IEditorService); - const candidates = [editorService.activeEditorPane, ...editorService.visibleEditorPanes].filter(editor => editor instanceof TextDiffEditor); - if (candidates.length > 0) { - const navigator = (candidates[0]).getDiffNavigator(); + for (const editor of [editorService.activeEditorPane, ...editorService.visibleEditorPanes]) { + if (editor instanceof TextDiffEditor) { + return editor; + } + } + + return undefined; + } + + function navigateInDiffEditor(accessor: ServicesAccessor, next: boolean): void { + const activeTextDiffEditor = getActiveTextDiffEditor(accessor); + + if (activeTextDiffEditor) { + const navigator = activeTextDiffEditor.getDiffNavigator(); if (navigator) { next ? navigator.next() : navigator.previous(); } } } + enum FocusTextDiffEditorMode { + Original, + Modified, + Toggle + } + + function focusInDiffEditor(accessor: ServicesAccessor, mode: FocusTextDiffEditorMode): void { + const activeTextDiffEditor = getActiveTextDiffEditor(accessor); + + if (activeTextDiffEditor) { + switch (mode) { + case FocusTextDiffEditorMode.Original: + activeTextDiffEditor.getControl()?.getOriginalEditor().focus(); + break; + case FocusTextDiffEditorMode.Modified: + activeTextDiffEditor.getControl()?.getModifiedEditor().focus(); + break; + case FocusTextDiffEditorMode.Toggle: + if (activeTextDiffEditor.getControl()?.getModifiedEditor().hasWidgetFocus()) { + return focusInDiffEditor(accessor, FocusTextDiffEditorMode.Original); + } else { + return focusInDiffEditor(accessor, FocusTextDiffEditorMode.Modified); + } + } + } + } + function toggleDiffSideBySide(accessor: ServicesAccessor): void { const configurationService = accessor.get(IConfigurationService); const newValue = !configurationService.getValue('diffEditor.renderSideBySide'); - configurationService.updateValue('diffEditor.renderSideBySide', newValue, ConfigurationTarget.USER); + configurationService.updateValue('diffEditor.renderSideBySide', newValue); } function toggleDiffIgnoreTrimWhitespace(accessor: ServicesAccessor): void { const configurationService = accessor.get(IConfigurationService); const newValue = !configurationService.getValue('diffEditor.ignoreTrimWhitespace'); - configurationService.updateValue('diffEditor.ignoreTrimWhitespace', newValue, ConfigurationTarget.USER); + configurationService.updateValue('diffEditor.ignoreTrimWhitespace', newValue); } KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -299,6 +381,30 @@ function registerDiffEditorCommands(): void { handler: accessor => toggleDiffSideBySide(accessor) }); + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: DIFF_FOCUS_PRIMARY_SIDE, + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: undefined, + handler: accessor => focusInDiffEditor(accessor, FocusTextDiffEditorMode.Modified) + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: DIFF_FOCUS_SECONDARY_SIDE, + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: undefined, + handler: accessor => focusInDiffEditor(accessor, FocusTextDiffEditorMode.Original) + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: DIFF_FOCUS_OTHER_SIDE, + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: undefined, + handler: accessor => focusInDiffEditor(accessor, FocusTextDiffEditorMode.Toggle) + }); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: TOGGLE_DIFF_SIDE_BY_SIDE, @@ -320,6 +426,88 @@ function registerDiffEditorCommands(): void { }); } +function registerOpenEditorAPICommands(): void { + + function mixinContext(context: IOpenEvent | undefined, options: ITextEditorOptions | undefined, column: EditorGroupColumn | undefined): [ITextEditorOptions | undefined, EditorGroupColumn | undefined] { + if (!context) { + return [options, column]; + } + + return [ + { ...context.editorOptions, ...(options ?? Object.create(null)) }, + context.sideBySide ? SIDE_GROUP : column + ]; + } + + CommandsRegistry.registerCommand(API_OPEN_EDITOR_COMMAND_ID, async function (accessor: ServicesAccessor, resourceArg: UriComponents, columnAndOptions?: [EditorGroupColumn?, ITextEditorOptions?], label?: string, context?: IOpenEvent) { + const editorService = accessor.get(IEditorService); + const editorGroupService = accessor.get(IEditorGroupsService); + const openerService = accessor.get(IOpenerService); + + const resource = URI.revive(resourceArg); + const [columnArg, optionsArg] = columnAndOptions ?? []; + + // use editor options or editor view column as a hint to use the editor service for opening + if (optionsArg || typeof columnArg === 'number') { + const [options, column] = mixinContext(context, optionsArg, columnArg); + + await editorService.openEditor({ resource, options, label }, viewColumnToEditorGroup(editorGroupService, column)); + } + + // do not allow to execute commands from here + else if (resource.scheme === 'command') { + return; + } + + // finally, delegate to opener service + else { + await openerService.open(resource, { openToSide: context?.sideBySide, editorOptions: context?.editorOptions }); + } + }); + + CommandsRegistry.registerCommand(API_OPEN_DIFF_EDITOR_COMMAND_ID, async function (accessor: ServicesAccessor, leftResource: UriComponents, rightResource: UriComponents, label?: string, columnAndOptions?: [EditorGroupColumn?, ITextEditorOptions?], context?: IOpenEvent) { + const editorService = accessor.get(IEditorService); + const editorGroupService = accessor.get(IEditorGroupsService); + + const [columnArg, optionsArg] = columnAndOptions ?? []; + const [options, column] = mixinContext(context, optionsArg, columnArg); + + await editorService.openEditor({ + leftResource: URI.revive(leftResource), + rightResource: URI.revive(rightResource), + label, + options + }, viewColumnToEditorGroup(editorGroupService, column)); + }); + + CommandsRegistry.registerCommand(API_OPEN_WITH_EDITOR_COMMAND_ID, (accessor: ServicesAccessor, resource: UriComponents, id: string, columnAndOptions?: [EditorGroupColumn?, ITextEditorOptions?]) => { + const editorService = accessor.get(IEditorService); + const editorGroupsService = accessor.get(IEditorGroupsService); + const configurationService = accessor.get(IConfigurationService); + const quickInputService = accessor.get(IQuickInputService); + + const [columnArg, optionsArg] = columnAndOptions ?? []; + let group: IEditorGroup | undefined = undefined; + + if (columnArg === SIDE_GROUP) { + const direction = preferredSideBySideGroupDirection(configurationService); + + let neighbourGroup = editorGroupsService.findGroup({ direction }); + if (!neighbourGroup) { + neighbourGroup = editorGroupsService.addGroup(editorGroupsService.activeGroup, direction); + } + group = neighbourGroup; + } else { + group = editorGroupsService.getGroup(viewColumnToEditorGroup(editorGroupsService, columnArg)) ?? editorGroupsService.activeGroup; + } + + const textOptions: ITextEditorOptions = optionsArg ? { ...optionsArg, override: false } : { override: false }; + + const input = editorService.createEditorInput({ resource: URI.revive(resource) }); + return openEditorWith(input, id, textOptions, group, editorService, configurationService, quickInputService); + }); +} + function registerOpenEditorAtIndexCommands(): void { const openEditorAtIndex: ICommandHandler = (accessor: ServicesAccessor, editorIndex: number): void => { const editorService = accessor.get(IEditorService); @@ -710,6 +898,31 @@ function registerOtherEditorCommands(): void { } }); + CommandsRegistry.registerCommand({ + id: TOGGLE_KEEP_EDITORS_COMMAND_ID, + handler: accessor => { + const configurationService = accessor.get(IConfigurationService); + const notificationService = accessor.get(INotificationService); + const openerService = accessor.get(IOpenerService); + + // Update setting + const currentSetting = configurationService.getValue('workbench.editor.enablePreview'); + const newSetting = currentSetting === true ? false : true; + configurationService.updateValue('workbench.editor.enablePreview', newSetting); + + // Inform user + notificationService.prompt( + Severity.Info, + newSetting ? + nls.localize('enablePreview', "Preview editors have been enabled in settings.") : + nls.localize('disablePreview', "Preview editors have been disabled in settings."), + [{ + label: nls.localize('learnMode', "Learn More"), run: () => openerService.open('https://go.microsoft.com/fwlink/?linkid=2147473') + }] + ); + } + }); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: PIN_EDITOR_COMMAND_ID, weight: KeybindingWeight.WorkbenchContrib, @@ -877,6 +1090,7 @@ export function setup(): void { registerActiveEditorMoveCommand(); registerEditorGroupsLayoutCommand(); registerDiffEditorCommands(); + registerOpenEditorAPICommands(); registerOpenEditorAtIndexCommands(); registerCloseEditorCommands(); registerOtherEditorCommands(); diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 9c48c54fe..50f571563 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -12,7 +12,7 @@ import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { IEditorIdentifier, EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { isMacintosh, isWeb } from 'vs/base/common/platform'; -import { GroupDirection, MergeGroupMode, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { GroupDirection, IEditorGroupsService, MergeGroupMode, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService'; import { toDisposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -25,6 +25,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { localize } from 'vs/nls'; +import { ByteSize } from 'vs/platform/files/common/files'; interface IDropOperation { splitDirection?: GroupDirection; @@ -34,7 +35,7 @@ class DropOverlay extends Themable { private static readonly OVERLAY_ID = 'monaco-workbench-editor-drop-overlay'; - private static readonly MAX_FILE_UPLOAD_SIZE = 100 * 1024 * 1024; // 100mb + private static readonly MAX_FILE_UPLOAD_SIZE = 100 * ByteSize.MB; private container: HTMLElement | undefined; private overlay: HTMLElement | undefined; @@ -54,7 +55,8 @@ class DropOverlay extends Themable { @IInstantiationService private instantiationService: IInstantiationService, @IFileDialogService private readonly fileDialogService: IFileDialogService, @IEditorService private readonly editorService: IEditorService, - @INotificationService private readonly notificationService: INotificationService + @INotificationService private readonly notificationService: INotificationService, + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService ) { super(themeService); @@ -143,8 +145,14 @@ class DropOverlay extends Themable { } } - // Position overlay - this.positionOverlay(e.offsetX, e.offsetY, isDraggingGroup); + // Position overlay and conditionally enable or disable + // editor group splitting support based on setting and + // keymodifiers used. + let splitOnDragAndDrop = !!this.editorGroupService.partOptions.splitOnDragAndDrop; + if (this.isToggleSplitOperation(e)) { + splitOnDragAndDrop = !splitOnDragAndDrop; + } + this.positionOverlay(e.offsetX, e.offsetY, isDraggingGroup, splitOnDragAndDrop); // Make sure to stop any running cleanup scheduler to remove the overlay if (this.cleanupOverlayScheduler.isScheduled()) { @@ -319,7 +327,7 @@ class DropOverlay extends Themable { // Try to come up with a good file path for the untitled // editor by asking the file dialog service for the default let proposedFilePath: URI | undefined = undefined; - const defaultFilePath = this.fileDialogService.defaultFilePath(); + const defaultFilePath = await this.fileDialogService.defaultFilePath(); if (defaultFilePath) { proposedFilePath = joinPath(defaultFilePath, name); } @@ -362,24 +370,33 @@ class DropOverlay extends Themable { return (e.ctrlKey && !isMacintosh) || (e.altKey && isMacintosh); } - private positionOverlay(mousePosX: number, mousePosY: number, isDraggingGroup: boolean): void { + private isToggleSplitOperation(e: DragEvent): boolean { + return (e.altKey && !isMacintosh) || (e.shiftKey && isMacintosh); + } + + private positionOverlay(mousePosX: number, mousePosY: number, isDraggingGroup: boolean, enableSplitting: boolean): void { const preferSplitVertically = this.accessor.partOptions.openSideBySideDirection === 'right'; const editorControlWidth = this.groupView.element.clientWidth; const editorControlHeight = this.groupView.element.clientHeight - this.getOverlayOffsetHeight(); let edgeWidthThresholdFactor: number; - if (isDraggingGroup) { - edgeWidthThresholdFactor = preferSplitVertically ? 0.3 : 0.1; // give larger threshold when dragging group depending on preferred split direction - } else { - edgeWidthThresholdFactor = 0.1; // 10% threshold to split if dragging editors - } - let edgeHeightThresholdFactor: number; - if (isDraggingGroup) { - edgeHeightThresholdFactor = preferSplitVertically ? 0.1 : 0.3; // give larger threshold when dragging group depending on preferred split direction + if (enableSplitting) { + if (isDraggingGroup) { + edgeWidthThresholdFactor = preferSplitVertically ? 0.3 : 0.1; // give larger threshold when dragging group depending on preferred split direction + } else { + edgeWidthThresholdFactor = 0.1; // 10% threshold to split if dragging editors + } + + if (isDraggingGroup) { + edgeHeightThresholdFactor = preferSplitVertically ? 0.1 : 0.3; // give larger threshold when dragging group depending on preferred split direction + } else { + edgeHeightThresholdFactor = 0.1; // 10% threshold to split if dragging editors + } } else { - edgeHeightThresholdFactor = 0.1; // 10% threshold to split if dragging editors + edgeWidthThresholdFactor = 0; + edgeHeightThresholdFactor = 0; } const edgeWidthThreshold = editorControlWidth * edgeWidthThresholdFactor; diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index d01c17c4a..a212456dd 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -1742,28 +1742,17 @@ class EditorOpeningEvent implements IEditorOpeningEvent { private override: (() => Promise) | undefined = undefined; constructor( - private _group: GroupIdentifier, - private _editor: EditorInput, + public readonly groupId: GroupIdentifier, + public readonly editor: EditorInput, private _options: EditorOptions | undefined, - private _context: OpenEditorContext | undefined + public readonly context: OpenEditorContext | undefined ) { } - get groupId(): GroupIdentifier { - return this._group; - } - - get editor(): EditorInput { - return this._editor; - } - get options(): EditorOptions | undefined { return this._options; } - get context(): OpenEditorContext | undefined { - return this._context; - } prevent(callback: () => Promise): void { this.override = callback; @@ -1775,9 +1764,9 @@ class EditorOpeningEvent implements IEditorOpeningEvent { } export interface EditorReplacement { - editor: EditorInput; - replacement: EditorInput; - options?: EditorOptions; + readonly editor: EditorInput; + readonly replacement: EditorInput; + readonly options?: EditorOptions; } registerThemingParticipant((theme, collector, environment) => { diff --git a/src/vs/workbench/browser/parts/editor/editorPane.ts b/src/vs/workbench/browser/parts/editor/editorPane.ts index 07a33a40c..92dc1975b 100644 --- a/src/vs/workbench/browser/parts/editor/editorPane.ts +++ b/src/vs/workbench/browser/parts/editor/editorPane.ts @@ -9,7 +9,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorGroup, IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { LRUCache, Touch } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; @@ -156,7 +156,7 @@ export abstract class EditorPane extends Composite implements IEditorPane { let editorMemento = EditorPane.EDITOR_MEMENTOS.get(mementoKey); if (!editorMemento) { - editorMemento = new EditorMemento(this.getId(), key, this.getMemento(StorageScope.WORKSPACE), limit, editorGroupService); + editorMemento = new EditorMemento(this.getId(), key, this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE), limit, editorGroupService); EditorPane.EDITOR_MEMENTOS.set(mementoKey, editorMemento); } @@ -220,18 +220,7 @@ export class EditorMemento implements IEditorMemento { // Automatically clear when editor input gets disposed if any if (resourceOrEditor instanceof EditorInput) { - const editor = resourceOrEditor; - - if (!this.editorDisposables) { - this.editorDisposables = new Map(); - } - - if (!this.editorDisposables.has(editor)) { - this.editorDisposables.set(editor, Event.once(resourceOrEditor.onDispose)(() => { - this.clearEditorState(resource); - this.editorDisposables?.delete(editor); - })); - } + this.clearEditorStateOnDispose(resource, resourceOrEditor); } } @@ -240,7 +229,7 @@ export class EditorMemento implements IEditorMemento { loadEditorState(group: IEditorGroup, resourceOrEditor: URI | EditorInput, fallbackToOtherGroupState?: boolean): T | undefined { const resource = this.doGetResource(resourceOrEditor); if (!resource || !group) { - return undefined; // we are not in a good state to load any state for a resource + return; // we are not in a good state to load any state for a resource } const cache = this.doLoad(); @@ -261,12 +250,16 @@ export class EditorMemento implements IEditorMemento { } } - return undefined; + return; } clearEditorState(resource: URI, group?: IEditorGroup): void; clearEditorState(editor: EditorInput, group?: IEditorGroup): void; clearEditorState(resourceOrEditor: URI | EditorInput, group?: IEditorGroup): void { + if (resourceOrEditor instanceof EditorInput) { + this.editorDisposables?.delete(resourceOrEditor); + } + const resource = this.doGetResource(resourceOrEditor); if (resource) { const cache = this.doLoad(); @@ -286,6 +279,19 @@ export class EditorMemento implements IEditorMemento { } } + clearEditorStateOnDispose(resource: URI, editor: EditorInput): void { + if (!this.editorDisposables) { + this.editorDisposables = new Map(); + } + + if (!this.editorDisposables.has(editor)) { + this.editorDisposables.set(editor, Event.once(editor.onDispose)(() => { + this.clearEditorState(resource); + this.editorDisposables?.delete(editor); + })); + } + } + moveEditorState(source: URI, target: URI, comparer: IExtUri): void { const cache = this.doLoad(); @@ -342,7 +348,7 @@ export class EditorMemento implements IEditorMemento { saveState(): void { const cache = this.doLoad(); - // Cleanup once during shutdown + // Cleanup once during session if (!this.cleanedUp) { this.cleanUp(); this.cleanedUp = true; diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index e6e216a5e..828156890 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -19,7 +19,7 @@ import { IEditorGroupsAccessor, IEditorGroupView, getEditorPartOptions, impactsE import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; import { EditorDropTarget, IEditorDropTargetDelegate } from 'vs/workbench/browser/parts/editor/editorDropTarget'; import { IEditorDropService } from 'vs/workbench/services/editor/browser/editorDropService'; @@ -148,8 +148,8 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro this.gridWidgetView = new GridWidgetView(); - this.workspaceMemento = this.getMemento(StorageScope.WORKSPACE); - this.globalMemento = this.getMemento(StorageScope.GLOBAL); + this.workspaceMemento = this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); + this.globalMemento = this.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); this._whenRestored = new Promise(resolve => (this.whenRestoredResolve = resolve)); diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index bfa6f87dc..1d85f0ad5 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/editorstatus'; import * as nls from 'vs/nls'; import { runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; -import { format, compare } from 'vs/base/common/strings'; +import { format, compare, splitLines } from 'vs/base/common/strings'; import { extname, basename, isEqual } from 'vs/base/common/resources'; import { areFunctions, withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; @@ -346,12 +346,12 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { [{ label: nls.localize('screenReaderDetectedExplanation.answerYes', "Yes"), run: () => { - this.configurationService.updateValue('editor.accessibilitySupport', 'on', ConfigurationTarget.USER); + this.configurationService.updateValue('editor.accessibilitySupport', 'on'); } }, { label: nls.localize('screenReaderDetectedExplanation.answerNo', "No"), run: () => { - this.configurationService.updateValue('editor.accessibilitySupport', 'off', ConfigurationTarget.USER); + this.configurationService.updateValue('editor.accessibilitySupport', 'off'); } }], { sticky: true } @@ -918,7 +918,7 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable { this.currentMarker = this.getMarker(); if (this.hasToUpdateStatus(previousMarker, this.currentMarker)) { if (this.currentMarker) { - const line = this.currentMarker.message.split(/\r\n|\r|\n/g)[0]; + const line = splitLines(this.currentMarker.message)[0]; const text = `${this.getType(this.currentMarker)} ${line}`; if (!this.statusBarEntryAccessor.value) { this.statusBarEntryAccessor.value = this.statusbarService.addEntry({ text: '', ariaLabel: '' }, 'statusbar.currentProblem', nls.localize('currentProblem', "Current Problem"), StatusbarAlignment.LEFT); @@ -1046,12 +1046,19 @@ export class ChangeModeAction extends Action { @IQuickInputService private readonly quickInputService: IQuickInputService, @IPreferencesService private readonly preferencesService: IPreferencesService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @ITextFileService private readonly textFileService: ITextFileService + @ITextFileService private readonly textFileService: ITextFileService, + @ICommandService private readonly commandService: ICommandService ) { super(actionId, actionLabel); } async run(): Promise { + const activeEditorPane = this.editorService.activeEditorPane as unknown as { isNotebookEditor?: boolean } | undefined; + if (activeEditorPane?.isNotebookEditor) { + // it's inside notebook editor + return this.commandService.executeCommand('notebook.cell.changeLanguage'); + } + const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl); if (!activeTextEditorControl) { await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); @@ -1143,7 +1150,7 @@ export class ChangeModeAction extends Action { // User decided to configure settings for current language if (pick === configureModeSettings) { - this.preferencesService.openGlobalSettings(true, { editSetting: `[${withUndefinedAsNull(currentModeId)}]` }); + this.preferencesService.openGlobalSettings(true, { revealSetting: { key: `[${withUndefinedAsNull(currentModeId)}]`, edit: true } }); return; } diff --git a/src/vs/workbench/browser/parts/editor/editorWidgets.ts b/src/vs/workbench/browser/parts/editor/editorWidgets.ts index 5d7217725..02601bc97 100644 --- a/src/vs/workbench/browser/parts/editor/editorWidgets.ts +++ b/src/vs/workbench/browser/parts/editor/editorWidgets.ts @@ -38,6 +38,8 @@ export class FloatingClickWidget extends Widget implements IOverlayWidget { super(); this._domNode = $('.floating-click-widget'); + this._domNode.style.padding = '10px'; + this._domNode.style.cursor = 'pointer'; if (keyBindingAction) { const keybinding = keybindingService.lookupKeybinding(keyBindingAction); diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts index 2733dfb40..285653b99 100644 --- a/src/vs/workbench/browser/parts/editor/editorsObserver.ts +++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts @@ -5,7 +5,7 @@ import { IEditorInput, IEditorInputFactoryRegistry, IEditorIdentifier, GroupIdentifier, Extensions, IEditorPartOptionsChangeEvent, EditorsOrder, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; import { Event, Emitter } from 'vs/base/common/event'; import { IEditorGroupsService, IEditorGroup, GroupChangeKind, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -339,7 +339,7 @@ export class EditorsObserver extends Disposable { if (this.mostRecentEditorsMap.isEmpty()) { this.storageService.remove(EditorsObserver.STORAGE_KEY, StorageScope.WORKSPACE); } else { - this.storageService.store(EditorsObserver.STORAGE_KEY, JSON.stringify(this.serialize()), StorageScope.WORKSPACE); + this.storageService.store(EditorsObserver.STORAGE_KEY, JSON.stringify(this.serialize()), StorageScope.WORKSPACE, StorageTarget.MACHINE); } } diff --git a/src/vs/workbench/browser/parts/editor/media/breadcrumbscontrol.css b/src/vs/workbench/browser/parts/editor/media/breadcrumbscontrol.css index 98ddd9e96..3b8bac456 100644 --- a/src/vs/workbench/browser/parts/editor/media/breadcrumbscontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/breadcrumbscontrol.css @@ -21,7 +21,7 @@ padding-right: 6px; } -/* todo@joh move somewhere else */ +/* breadcrumbs-picker-style */ .monaco-workbench .monaco-breadcrumbs-picker .arrow { position: absolute; diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index bed3f2094..bcd835f4c 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -589,8 +589,8 @@ export class TabsTitleControl extends TitleControl { const tabActionRunner = new EditorCommandsContextActionRunner({ groupId: this.group.id, editorIndex: index }); - const tabActionBar = new ActionBar(tabActionsContainer, { ariaLabel: localize('ariaLabelTabActions', "Tab actions"), actionRunner: tabActionRunner, }); - tabActionBar.onDidBeforeRun(e => { + const tabActionBar = new ActionBar(tabActionsContainer, { ariaLabel: localize('ariaLabelTabActions', "Tab actions"), actionRunner: tabActionRunner }); + tabActionBar.onBeforeRun(e => { if (e.action.id === this.closeEditorAction.id) { this.blockRevealActiveTabOnce(); } diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 115d6ee98..4557a2c48 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -24,7 +24,6 @@ import { ScrollType, IDiffEditorViewState, IDiffEditorModel } from 'vs/editor/co import { DisposableStore } from 'vs/base/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { URI } from 'vs/base/common/uri'; -import { Event } from 'vs/base/common/event'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -32,6 +31,7 @@ import { EditorActivation, IEditorOptions } from 'vs/platform/editor/common/edit import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { isEqual } from 'vs/base/common/resources'; import { multibyteAwareBtoa } from 'vs/base/browser/dom'; +import { IFileService } from 'vs/platform/files/common/files'; /** * The text editor that leverages the diff text editor for the editing experience. @@ -62,9 +62,28 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @IEditorService editorService: IEditorService, @IThemeService themeService: IThemeService, - @IEditorGroupsService editorGroupService: IEditorGroupsService + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IFileService private readonly fileService: IFileService ) { super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService); + + // Listen to file system provider changes + this._register(this.fileService.onDidChangeFileSystemProviderCapabilities(e => this.onDidFileSystemProviderChange(e.scheme))); + this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(e => this.onDidFileSystemProviderChange(e.scheme))); + } + + private onDidFileSystemProviderChange(scheme: string): void { + const control = this.getControl(); + const input = this.input; + + if (control && input instanceof DiffEditorInput) { + if (input.originalInput.resource?.scheme === scheme || input.modifiedInput.resource?.scheme === scheme) { + control.updateOptions({ + readOnly: input.modifiedInput.isReadonly(), + originalEditable: !input.originalInput.isReadonly() + }); + } + } } protected onWillCloseEditorInGroup(editor: IEditorInput): void { @@ -175,7 +194,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan const originalInput = input.originalInput; const modifiedInput = input.modifiedInput; - const binaryDiffInput = new DiffEditorInput(input.getName(), input.getDescription(), originalInput, modifiedInput, true); + const binaryDiffInput = this.instantiationService.createInstance(DiffEditorInput, input.getName(), input.getDescription(), originalInput, modifiedInput, true); // Forward binary flag to input if supported const fileEditorInputFactory = Registry.as(EditorInputExtensions.EditorInputFactories).getFileEditorInputFactory(); @@ -217,16 +236,17 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan // Handle diff editor specially by merging in diffEditor configuration if (isObject(configuration.diffEditor)) { - // User settings defines `diffEditor.codeLens`, but there is also `editor.codeLens`. - // Due to the mixin, the two settings cannot be distinguished anymore. - // - // So we map `diffEditor.codeLens` to `diffEditor.originalCodeLens` and `diffEditor.modifiedCodeLens`. const diffEditorConfiguration = objects.deepClone(configuration.diffEditor); - diffEditorConfiguration.originalCodeLens = diffEditorConfiguration.codeLens; - diffEditorConfiguration.modifiedCodeLens = diffEditorConfiguration.codeLens; + + // User settings defines `diffEditor.codeLens`, but here we rename that to `diffEditor.diffCodeLens` to avoid collisions with `editor.codeLens`. + diffEditorConfiguration.diffCodeLens = diffEditorConfiguration.codeLens; delete diffEditorConfiguration.codeLens; - objects.mixin(editorConfiguration, diffEditorConfiguration); + // User settings defines `diffEditor.wordWrap`, but here we rename that to `diffEditor.diffWordWrap` to avoid collisions with `editor.wordWrap`. + diffEditorConfiguration.diffWordWrap = <'off' | 'on' | 'inherit' | undefined>diffEditorConfiguration.wordWrap; + delete diffEditorConfiguration.wordWrap; + + Object.assign(editorConfiguration, diffEditorConfiguration); } return editorConfiguration; @@ -309,12 +329,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan // Otherwise save it else { - super.saveTextEditorViewState(resource); - - // Make sure to clean up when the input gets disposed - Event.once(input.onDispose)(() => { - super.clearTextEditorViewState([resource], this.group); - }); + super.saveTextEditorViewState(resource, input); } } diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index f2a77542e..9cae4d214 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -67,6 +67,7 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa @IEditorGroupsService protected editorGroupService: IEditorGroupsService ) { super(id, telemetryService, themeService, storageService); + this._instantiationService = instantiationService; this.editorMemento = this.getEditorMemento(editorGroupService, BaseTextEditor.TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100); @@ -223,13 +224,27 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa return this.editorControl; } - protected saveTextEditorViewState(resource: URI): void { + getViewState(): IEditorViewState | undefined { + const resource = this.input?.resource; + if (resource) { + return withNullAsUndefined(this.retrieveTextEditorViewState(resource)); + } + + return undefined; + } + + + protected saveTextEditorViewState(resource: URI, cleanUpOnDispose?: IEditorInput): void { const editorViewState = this.retrieveTextEditorViewState(resource); if (!editorViewState || !this.group) { return; } this.editorMemento.saveEditorState(this.group, resource, editorViewState); + + if (cleanUpOnDispose) { + this.editorMemento.clearEditorStateOnDispose(resource, cleanUpOnDispose); + } } protected shouldRestoreTextEditorViewState(editor: IEditorInput, context?: IEditorOpenContext): boolean { @@ -243,15 +258,6 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa return true; } - getViewState(): IEditorViewState | undefined { - const resource = this.input?.resource; - if (resource) { - return withNullAsUndefined(this.retrieveTextEditorViewState(resource)); - } - - return undefined; - } - protected retrieveTextEditorViewState(resource: URI): IEditorViewState | null { const control = this.getControl(); if (!isCodeEditor(control)) { @@ -284,9 +290,9 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa } protected clearTextEditorViewState(resources: URI[], group?: IEditorGroup): void { - resources.forEach(resource => { + for (const resource of resources) { this.editorMemento.clearEditorState(resource, group); - }); + } } private updateEditorConfiguration(configuration?: IEditorConfiguration): void { diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index a99323c2e..2bb2640dc 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -16,7 +16,6 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { Event } from 'vs/base/common/event'; import { ScrollType, IEditor } from 'vs/editor/common/editorCommon'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -158,12 +157,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor { // Otherwise save it else { - super.saveTextEditorViewState(resource); - - // Make sure to clean up when the input gets disposed - Event.once(input.onDispose)(() => { - super.clearTextEditorViewState([resource]); - }); + super.saveTextEditorViewState(resource, input); } } } diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsList.css b/src/vs/workbench/browser/parts/notifications/media/notificationsList.css index c6be6928d..8bd0a4022 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsList.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsList.css @@ -105,18 +105,27 @@ overflow: hidden; } +.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-buttons-container > .monaco-button-dropdown, +.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-buttons-container > .monaco-button { + margin: 4px 5px; /* allows button focus outline to be visible */ +} + .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-buttons-container .monaco-button { + outline-offset: 2px !important; +} + +.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-buttons-container .monaco-text-button { width: fit-content; width: -moz-fit-content; padding: 5px 10px; - margin: 4px 5px; /* allows button focus outline to be visible */ + display: inline-block; /* to enable ellipsis in text overflow */ font-size: 12px; overflow: hidden; text-overflow: ellipsis; } -.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-buttons-container .monaco-text-button { - display: inline-block; /* to enable ellipsis in text overflow */ +.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-buttons-container .monaco-dropdown-button { + padding: 5px } /** Notification: Progress */ diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css b/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css index f1ab3c2cf..987f9a368 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css @@ -26,7 +26,7 @@ } .monaco-workbench > .notifications-toasts .notification-toast-container > .notification-toast { - margin: 5px; /* enables separation and drop shadows around toasts */ + margin: 8px; /* enables separation and drop shadows around toasts */ 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; diff --git a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts index 477b90cfc..7aaa25068 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts @@ -12,14 +12,16 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { CLEAR_NOTIFICATION, EXPAND_NOTIFICATION, COLLAPSE_NOTIFICATION, CLEAR_ALL_NOTIFICATIONS, HIDE_NOTIFICATIONS_CENTER } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { Codicon, registerIcon } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; -const clearIcon = registerIcon('notifications-clear', Codicon.close); -const clearAllIcon = registerIcon('notifications-clear-all', Codicon.clearAll); -const hideIcon = registerIcon('notifications-hide', Codicon.chevronDown); -const expandIcon = registerIcon('notifications-expand', Codicon.chevronUp); -const collapseIcon = registerIcon('notifications-collapse', Codicon.chevronDown); -const configureIcon = registerIcon('notifications-configure', Codicon.gear); +const clearIcon = registerIcon('notifications-clear', Codicon.close, localize('clearIcon', 'Icon for the clear action in notifications.')); +const clearAllIcon = registerIcon('notifications-clear-all', Codicon.clearAll, localize('clearAllIcon', 'Icon for the clear all action in notifications.')); +const hideIcon = registerIcon('notifications-hide', Codicon.chevronDown, localize('hideIcon', 'Icon for the hide action in notifications.')); +const expandIcon = registerIcon('notifications-expand', Codicon.chevronUp, localize('expandIcon', 'Icon for the expand action in notifications.')); +const collapseIcon = registerIcon('notifications-collapse', Codicon.chevronDown, localize('collapseIcon', 'Icon for the collapse action in notifications.')); +const configureIcon = registerIcon('notifications-configure', Codicon.gear, localize('configureIcon', 'Icon for the configure action in notifications.')); export class ClearNotificationAction extends Action { @@ -31,7 +33,7 @@ export class ClearNotificationAction extends Action { label: string, @ICommandService private readonly commandService: ICommandService ) { - super(id, label, clearIcon.classNames); + super(id, label, ThemeIcon.asClassName(clearIcon)); } async run(notification: INotificationViewItem): Promise { @@ -49,7 +51,7 @@ export class ClearAllNotificationsAction extends Action { label: string, @ICommandService private readonly commandService: ICommandService ) { - super(id, label, clearAllIcon.classNames); + super(id, label, ThemeIcon.asClassName(clearAllIcon)); } async run(): Promise { @@ -67,7 +69,7 @@ export class HideNotificationsCenterAction extends Action { label: string, @ICommandService private readonly commandService: ICommandService ) { - super(id, label, hideIcon.classNames); + super(id, label, ThemeIcon.asClassName(hideIcon)); } async run(): Promise { @@ -85,7 +87,7 @@ export class ExpandNotificationAction extends Action { label: string, @ICommandService private readonly commandService: ICommandService ) { - super(id, label, expandIcon.classNames); + super(id, label, ThemeIcon.asClassName(expandIcon)); } async run(notification: INotificationViewItem): Promise { @@ -103,7 +105,7 @@ export class CollapseNotificationAction extends Action { label: string, @ICommandService private readonly commandService: ICommandService ) { - super(id, label, collapseIcon.classNames); + super(id, label, ThemeIcon.asClassName(collapseIcon)); } async run(notification: INotificationViewItem): Promise { @@ -121,7 +123,7 @@ export class ConfigureNotificationAction extends Action { label: string, public readonly configurationActions: ReadonlyArray ) { - super(id, label, configureIcon.classNames); + super(id, label, ThemeIcon.asClassName(configureIcon)); } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index 6e8b4e940..d4074bbeb 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -251,7 +251,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente protected updateStyles(): void { if (this.notificationsCenterContainer && this.notificationsCenterHeader) { const widgetShadowColor = this.getColor(widgetShadow); - this.notificationsCenterContainer.style.boxShadow = widgetShadowColor ? `0 0px 8px ${widgetShadowColor}` : ''; + this.notificationsCenterContainer.style.boxShadow = widgetShadowColor ? `0 0 8px 2px ${widgetShadowColor}` : ''; const borderColor = this.getColor(NOTIFICATIONS_CENTER_BORDER); this.notificationsCenterContainer.style.border = borderColor ? `1px solid ${borderColor}` : ''; diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts index a935e757a..20b125013 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts @@ -15,20 +15,20 @@ import { IListService, WorkbenchList } from 'vs/platform/list/browser/listServic // Center export const SHOW_NOTIFICATIONS_CENTER = 'notifications.showList'; export const HIDE_NOTIFICATIONS_CENTER = 'notifications.hideList'; -export const TOGGLE_NOTIFICATIONS_CENTER = 'notifications.toggleList'; +const TOGGLE_NOTIFICATIONS_CENTER = 'notifications.toggleList'; // Toasts -export const HIDE_NOTIFICATION_TOAST = 'notifications.hideToasts'; -export const FOCUS_NOTIFICATION_TOAST = 'notifications.focusToasts'; -export const FOCUS_NEXT_NOTIFICATION_TOAST = 'notifications.focusNextToast'; -export const FOCUS_PREVIOUS_NOTIFICATION_TOAST = 'notifications.focusPreviousToast'; -export const FOCUS_FIRST_NOTIFICATION_TOAST = 'notifications.focusFirstToast'; -export const FOCUS_LAST_NOTIFICATION_TOAST = 'notifications.focusLastToast'; +const HIDE_NOTIFICATION_TOAST = 'notifications.hideToasts'; +const FOCUS_NOTIFICATION_TOAST = 'notifications.focusToasts'; +const FOCUS_NEXT_NOTIFICATION_TOAST = 'notifications.focusNextToast'; +const FOCUS_PREVIOUS_NOTIFICATION_TOAST = 'notifications.focusPreviousToast'; +const FOCUS_FIRST_NOTIFICATION_TOAST = 'notifications.focusFirstToast'; +const FOCUS_LAST_NOTIFICATION_TOAST = 'notifications.focusLastToast'; // Notification export const COLLAPSE_NOTIFICATION = 'notification.collapse'; export const EXPAND_NOTIFICATION = 'notification.expand'; -export const TOGGLE_NOTIFICATION = 'notification.toggle'; +const TOGGLE_NOTIFICATION = 'notification.toggle'; export const CLEAR_NOTIFICATION = 'notification.clear'; export const CLEAR_ALL_NOTIFICATIONS = 'notifications.clearAll'; @@ -159,12 +159,20 @@ export function registerNotificationCommands(center: INotificationsCenterControl }); // Hide Toasts - KeybindingsRegistry.registerCommandAndKeybindingRule({ + CommandsRegistry.registerCommand(HIDE_NOTIFICATION_TOAST, accessor => toasts.hide()); + + KeybindingsRegistry.registerKeybindingRule({ id: HIDE_NOTIFICATION_TOAST, - weight: KeybindingWeight.WorkbenchContrib + 50, + weight: KeybindingWeight.WorkbenchContrib - 50, // lower when not focused (e.g. let editor suggest win over this command) when: NotificationsToastsVisibleContext, - primary: KeyCode.Escape, - handler: accessor => toasts.hide() + primary: KeyCode.Escape + }); + + KeybindingsRegistry.registerKeybindingRule({ + id: HIDE_NOTIFICATION_TOAST, + weight: KeybindingWeight.WorkbenchContrib + 100, // higher when focused + when: ContextKeyExpr.and(NotificationsToastsVisibleContext, NotificationFocusedContext), + primary: KeyCode.Escape }); // Focus Toasts diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index 22f915231..6eb5f2cee 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -482,7 +482,7 @@ export class NotificationsToasts extends Themable implements INotificationsToast toast.style.background = backgroundColor ? backgroundColor : ''; const widgetShadowColor = this.getColor(widgetShadow); - toast.style.boxShadow = widgetShadowColor ? `0 0px 8px ${widgetShadowColor}` : ''; + toast.style.boxShadow = widgetShadowColor ? `0 0 8px 2px ${widgetShadowColor}` : ''; const borderColor = this.getColor(NOTIFICATIONS_TOAST_BORDER); toast.style.border = borderColor ? `1px solid ${borderColor}` : ''; diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index a78fd3199..51272ab8a 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -8,11 +8,11 @@ import { clearNode, addDisposableListener, EventType, EventHelper, $ } from 'vs/ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; -import { ButtonGroup } from 'vs/base/browser/ui/button/button'; +import { ButtonBar } from 'vs/base/browser/ui/button/button'; import { attachButtonStyler, attachProgressBarStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IAction, IActionRunner } from 'vs/base/common/actions'; +import { ActionRunner, ActionWithMenuAction, IAction, IActionRunner } from 'vs/base/common/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { dispose, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -285,7 +285,8 @@ export class NotificationTemplateRenderer extends Disposable { @IOpenerService private readonly openerService: IOpenerService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService private readonly themeService: IThemeService, - @IKeybindingService private readonly keybindingService: IKeybindingService + @IKeybindingService private readonly keybindingService: IKeybindingService, + @IContextMenuService private readonly contextMenuService: IContextMenuService, ) { super(); @@ -441,27 +442,40 @@ export class NotificationTemplateRenderer extends Disposable { const primaryActions = notification.actions ? notification.actions.primary : undefined; if (notification.expanded && isNonEmptyArray(primaryActions)) { - const buttonGroup = new ButtonGroup(this.template.buttonsContainer, primaryActions.length, { title: true /* assign titles to buttons in case they overflow */ }); - buttonGroup.buttons.forEach((button, index) => { - const action = primaryActions[index]; - button.label = action.label; - - this.inputDisposables.add(button.onDidClick(e => { - EventHelper.stop(e, true); - + const that = this; + const actionRunner: IActionRunner = new class extends ActionRunner { + protected async runAction(action: IAction): Promise { // Run action - this.actionRunner.run(action, notification); + that.actionRunner.run(action, notification); // Hide notification (unless explicitly prevented) if (!(action instanceof ChoiceAction) || !action.keepOpen) { notification.close(); } + } + }(); + const buttonToolbar = this.inputDisposables.add(new ButtonBar(this.template.buttonsContainer)); + for (const action of primaryActions) { + const buttonOptions = { title: true, /* assign titles to buttons in case they overflow */ }; + const dropdownActions = action instanceof ChoiceAction ? action.menu + : action instanceof ActionWithMenuAction ? action.actions : undefined; + const button = this.inputDisposables.add( + dropdownActions + ? buttonToolbar.addButtonWithDropdown({ + ...buttonOptions, + contextMenuProvider: this.contextMenuService, + actions: dropdownActions, + actionRunner + }) + : buttonToolbar.addButton(buttonOptions)); + button.label = action.label; + this.inputDisposables.add(button.onDidClick(e => { + EventHelper.stop(e, true); + actionRunner.run(action); })); this.inputDisposables.add(attachButtonStyler(button, this.themeService)); - }); - - this.inputDisposables.add(buttonGroup); + } } } diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index 96d407d05..27fc613ff 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -84,10 +84,6 @@ justify-content: center; } -.monaco-workbench .part.panel > .composite.title > .composite-bar-excess { - width: 100px; -} - .monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar { line-height: 27px; /* matches panel titles in settings */ height: 35px; diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 41674f53f..74619ecec 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -18,11 +18,13 @@ import { IActivity } from 'vs/workbench/common/activity'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ActivePanelContext, PanelPositionContext } from 'vs/workbench/common/panel'; import { ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; -import { Codicon, registerIcon } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; -const maximizeIcon = registerIcon('panel-maximize', Codicon.chevronUp); -const restoreIcon = registerIcon('panel-restore', Codicon.chevronDown); -const closeIcon = registerIcon('panel-close', Codicon.close); +const maximizeIcon = registerIcon('panel-maximize', Codicon.chevronUp, nls.localize('maximizeIcon', 'Icon to maximize a panel.')); +const restoreIcon = registerIcon('panel-restore', Codicon.chevronDown, nls.localize('restoreIcon', 'Icon to restore a panel.')); +const closeIcon = registerIcon('panel-close', Codicon.close, nls.localize('closeIcon', 'Icon to close a panel.')); export class ClosePanelAction extends Action { @@ -34,7 +36,7 @@ export class ClosePanelAction extends Action { name: string, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { - super(id, name, closeIcon.classNames); + super(id, name, ThemeIcon.asClassName(closeIcon)); } async run(): Promise { @@ -106,11 +108,11 @@ export class ToggleMaximizedPanelAction extends Action { @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IEditorGroupsService editorGroupsService: IEditorGroupsService ) { - super(id, label, layoutService.isPanelMaximized() ? restoreIcon.classNames : maximizeIcon.classNames); + super(id, label, layoutService.isPanelMaximized() ? ThemeIcon.asClassName(restoreIcon) : ThemeIcon.asClassName(maximizeIcon)); this.toDispose.add(editorGroupsService.onDidLayout(() => { const maximized = this.layoutService.isPanelMaximized(); - this.class = maximized ? restoreIcon.classNames : maximizeIcon.classNames; + this.class = maximized ? ThemeIcon.asClassName(restoreIcon) : ThemeIcon.asClassName(maximizeIcon); this.label = maximized ? ToggleMaximizedPanelAction.RESTORE_LABEL : ToggleMaximizedPanelAction.MAXIMIZE_LABEL; })); } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index e7480b64c..332a9ab4c 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -13,7 +13,7 @@ import { CompositePart, ICompositeTitleLabel } from 'vs/workbench/browser/parts/ import { Panel, PanelRegistry, Extensions as PanelExtensions, PanelDescriptor } from 'vs/workbench/browser/panel'; import { IPanelService, IPanelIdentifier } from 'vs/workbench/services/panel/common/panelService'; import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; -import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -37,7 +37,6 @@ import { ViewContainer, IViewDescriptorService, IViewContainerModel, ViewContain import { MenuId } from 'vs/platform/actions/common/actions'; import { ViewMenuActions, ViewContainerMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { Before2D, CompositeDragAndDropObserver, ICompositeDragAndDrop, toggleDropEffect } from 'vs/workbench/browser/dnd'; import { IActivity } from 'vs/workbench/common/activity'; @@ -118,7 +117,6 @@ export class PanelPart extends CompositePart implements IPanelService { @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IExtensionService private readonly extensionService: IExtensionService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { super( notificationService, @@ -140,7 +138,6 @@ export class PanelPart extends CompositePart implements IPanelService { ); this.panelRegistry = Registry.as(PanelExtensions.Panels); - storageKeysSyncRegistryService.registerStorageKey({ key: PanelPart.PINNED_PANELS, version: 1 }); this.dndHandler = new CompositeDragAndDrop(this.viewDescriptorService, ViewContainerLocation.Panel, (id: string, focus?: boolean) => (this.openPanel(id, focus) as Promise).then(panel => panel || null), @@ -338,7 +335,7 @@ export class PanelPart extends CompositePart implements IPanelService { disposables.clear(); this.onDidRegisterExtensions(); this.compositeBar.onDidChange(() => this.saveCachedPanels(), this, disposables); - this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e), this, disposables); + this.storageService.onDidChangeValue(e => this.onDidStorageValueChange(e), this, disposables); })); } @@ -670,7 +667,7 @@ export class PanelPart extends CompositePart implements IPanelService { return this.toolBar.getItemsWidth(); } - private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { + private onDidStorageValueChange(e: IStorageValueChangeEvent): void { if (e.key === PanelPart.PINNED_PANELS && e.scope === StorageScope.GLOBAL && this.cachedPanelsValue !== this.getStoredCachedPanelsValue() /* This checks if current window changed the value or not */) { this._cachedPanelsValue = undefined; @@ -760,7 +757,7 @@ export class PanelPart extends CompositePart implements IPanelService { } private setStoredCachedViewletsValue(value: string): void { - this.storageService.store(PanelPart.PINNED_PANELS, value, StorageScope.GLOBAL); + this.storageService.store(PanelPart.PINNED_PANELS, value, StorageScope.GLOBAL, StorageTarget.USER); } private getPlaceholderViewContainers(): IPlaceholderViewContainer[] { @@ -792,7 +789,7 @@ export class PanelPart extends CompositePart implements IPanelService { } private setStoredPlaceholderViewContainersValue(value: string): void { - this.storageService.store(PanelPart.PLACEHOLDER_VIEW_CONTAINERS, value, StorageScope.WORKSPACE); + this.storageService.store(PanelPart.PLACEHOLDER_VIEW_CONTAINERS, value, StorageScope.WORKSPACE, StorageTarget.MACHINE); } private getViewContainer(panelId: string): ViewContainer | undefined { diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index ced7b6480..759039699 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -23,7 +23,7 @@ import { isThemeColor } from 'vs/editor/common/editorCommon'; import { Color } from 'vs/base/common/color'; import { EventHelper, createStyleSheet, addDisposableListener, EventType, hide, show, isAncestor, appendChildren } from 'vs/base/browser/dom'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage'; import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { coalesce } from 'vs/base/common/arrays'; @@ -32,7 +32,6 @@ import { ToggleStatusbarVisibilityAction } from 'vs/workbench/browser/actions/la import { assertIsDefined } from 'vs/base/common/types'; import { Emitter } from 'vs/base/common/event'; import { Command } from 'vs/editor/common/modes'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -102,10 +101,10 @@ class StatusbarViewModel extends Disposable { } private registerListeners(): void { - this._register(this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); + this._register(this.storageService.onDidChangeValue(e => this.onDidStorageValueChange(e))); } - private onDidStorageChange(event: IWorkspaceStorageChangeEvent): void { + private onDidStorageValueChange(event: IStorageValueChangeEvent): void { if (event.key === StatusbarViewModel.HIDDEN_ENTRIES_KEY && event.scope === StorageScope.GLOBAL) { // Keep current hidden entries @@ -275,7 +274,7 @@ class StatusbarViewModel extends Disposable { private saveState(): void { if (this.hidden.size > 0) { - this.storageService.store(StatusbarViewModel.HIDDEN_ENTRIES_KEY, JSON.stringify(Array.from(this.hidden.values())), StorageScope.GLOBAL); + this.storageService.store(StatusbarViewModel.HIDDEN_ENTRIES_KEY, JSON.stringify(Array.from(this.hidden.values())), StorageScope.GLOBAL, StorageTarget.USER); } else { this.storageService.remove(StatusbarViewModel.HIDDEN_ENTRIES_KEY, StorageScope.GLOBAL); } @@ -405,12 +404,9 @@ export class StatusbarPart extends Part implements IStatusbarService { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IContextMenuService private contextMenuService: IContextMenuService, @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, ) { super(Parts.STATUSBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); - storageKeysSyncRegistryService.registerStorageKey({ key: StatusbarViewModel.HIDDEN_ENTRIES_KEY, version: 1 }); - this.registerListeners(); } diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 1f3b424ac..99fcf2b6e 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -11,7 +11,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IAction, Action, SubmenuAction, Separator } from 'vs/base/common/actions'; import * as DOM from 'vs/base/browser/dom'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { isMacintosh, isWeb, isIOS } from 'vs/base/common/platform'; +import { isMacintosh, isWeb, isIOS, isNative } from 'vs/base/common/platform'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -21,7 +21,7 @@ import { MENUBAR_SELECTION_FOREGROUND, MENUBAR_SELECTION_BACKGROUND, MENUBAR_SEL import { URI } from 'vs/base/common/uri'; import { ILabelService } from 'vs/platform/label/common/label'; import { IUpdateService, StateType } from 'vs/platform/update/common/update'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -122,7 +122,7 @@ export abstract class MenubarControl extends Disposable { this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e))); // Listen to update service - this.updateService.onStateChange(() => this.updateMenubar()); + this.updateService.onStateChange(() => this.onUpdateStateChange()); // Listen for changes in recently opened menu this._register(this.workspacesService.onRecentlyOpenedChange(() => { this.onRecentlyOpenedChange(); })); @@ -148,6 +148,14 @@ export abstract class MenubarControl extends Disposable { return label; } + protected onUpdateStateChange(): void { + this.updateMenubar(); + } + + protected onUpdateKeybindings(): void { + this.updateMenubar(); + } + protected getOpenRecentActions(): (Separator | IAction & { uri: URI })[] { if (!this.recentlyOpened) { return []; @@ -191,13 +199,27 @@ export abstract class MenubarControl extends Disposable { if (event.affectsConfiguration('editor.accessibilitySupport')) { this.notifyUserOfCustomMenubarAccessibility(); } + + // Since we try not update when hidden, we should + // try to update the recently opened list on visibility changes + if (event.affectsConfiguration('window.menuBarVisibility')) { + this.onRecentlyOpenedChange(); + } } - private onRecentlyOpenedChange(): void { - this.workspacesService.getRecentlyOpened().then(recentlyOpened => { - this.recentlyOpened = recentlyOpened; - this.updateMenubar(); - }); + private get menubarHidden(): boolean { + return isMacintosh && isNative ? false : getMenuBarVisibility(this.configurationService) === 'hidden'; + } + + protected onRecentlyOpenedChange(): void { + + // Do not update recently opened when the menubar is hidden #108712 + if (!this.menubarHidden) { + this.workspacesService.getRecentlyOpened().then(recentlyOpened => { + this.recentlyOpened = recentlyOpened; + this.updateMenubar(); + }); + } } private createOpenRecentMenuAction(recent: IRecent): IAction & { uri: URI } { @@ -241,7 +263,7 @@ export abstract class MenubarControl extends Disposable { } const hasBeenNotified = this.storageService.getBoolean('menubar/accessibleMenubarNotified', StorageScope.GLOBAL, false); - const usingCustomMenubar = getTitleBarStyle(this.configurationService, this.environmentService) === 'custom'; + const usingCustomMenubar = getTitleBarStyle(this.configurationService) === 'custom'; if (hasBeenNotified || usingCustomMenubar || !this.accessibilityService.isScreenReaderOptimized()) { return; @@ -257,7 +279,7 @@ export abstract class MenubarControl extends Disposable { } ]); - this.storageService.store('menubar/accessibleMenubarNotified', true, StorageScope.GLOBAL); + this.storageService.store('menubar/accessibleMenubarNotified', true, StorageScope.GLOBAL, StorageTarget.USER); } } @@ -266,6 +288,7 @@ export class CustomMenubarControl extends MenubarControl { private container: HTMLElement | undefined; private alwaysOnMnemonics: boolean = false; private focusInsideMenubar: boolean = false; + private visible: boolean = true; private readonly _onVisibilityChange: Emitter; private readonly _onFocusStateChange: Emitter; @@ -475,7 +498,7 @@ export class CustomMenubarControl extends MenubarControl { } private get currentMenubarVisibility(): MenuBarVisibility { - return getMenuBarVisibility(this.configurationService, this.environmentService); + return getMenuBarVisibility(this.configurationService); } private get currentDisableMenuBarAltFocus(): boolean { @@ -530,6 +553,12 @@ export class CustomMenubarControl extends MenubarControl { return currentSidebarLocation === 'right' ? Direction.Left : Direction.Right; } + private onDidVisibilityChange(visible: boolean): void { + this.visible = visible; + this.onRecentlyOpenedChange(); + this._onVisibilityChange.fire(visible); + } + private setupCustomMenubar(firstTime: boolean): void { // If there is no container, we cannot setup the menubar if (!this.container) { @@ -554,7 +583,7 @@ export class CustomMenubarControl extends MenubarControl { } })); - this._register(this.menubar.onVisibilityChange(e => this._onVisibilityChange.fire(e))); + this._register(this.menubar.onVisibilityChange(e => this.onDidVisibilityChange(e))); // Before we focus the menubar, stop updates to it so that focus-related context keys will work this._register(DOM.addDisposableListener(this.container, DOM.EventType.FOCUS_IN, () => { @@ -645,29 +674,15 @@ export class CustomMenubarControl extends MenubarControl { visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics, - compactMode: this.currentCompactMenuMode, - getCompactMenuActions: () => { - if (!isWeb) { - return []; // only for web - } - - const webNavigationActions: IAction[] = []; - const webNavigationMenu = this.menuService.createMenu(MenuId.MenubarWebNavigationMenu, this.contextKeyService); - for (const groups of webNavigationMenu.getActions()) { - const [, actions] = groups; - for (const action of actions) { - action.label = mnemonicMenuLabel(this.calculateActionLabel(action)); - webNavigationActions.push(action); - } - } - webNavigationMenu.dispose(); - - return webNavigationActions; - } + compactMode: this.currentCompactMenuMode }; } protected onDidChangeWindowFocus(hasFocus: boolean): void { + if (!this.visible) { + return; + } + super.onDidChangeWindowFocus(hasFocus); if (this.container) { @@ -682,6 +697,30 @@ export class CustomMenubarControl extends MenubarControl { } } + protected onUpdateStateChange(): void { + if (!this.visible) { + return; + } + + super.onUpdateStateChange(); + } + + protected onRecentlyOpenedChange(): void { + if (!this.visible) { + return; + } + + super.onRecentlyOpenedChange(); + } + + protected onUpdateKeybindings(): void { + if (!this.visible) { + return; + } + + super.onUpdateKeybindings(); + } + protected registerListeners(): void { super.registerListeners(); diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 1dc512aa7..4783cb522 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -53,8 +53,8 @@ export class TitlebarPart extends Part implements ITitleService { readonly minimumWidth: number = 0; readonly maximumWidth: number = Number.POSITIVE_INFINITY; - get minimumHeight(): number { return isMacintosh && !isWeb ? 22 / getZoomFactor() : (30 / (this.currentMenubarVisibility === 'hidden' ? getZoomFactor() : 1)); } - get maximumHeight(): number { return isMacintosh && !isWeb ? 22 / getZoomFactor() : (30 / (this.currentMenubarVisibility === 'hidden' ? getZoomFactor() : 1)); } + get minimumHeight(): number { return 30 / (this.currentMenubarVisibility === 'hidden' ? getZoomFactor() : 1); } + get maximumHeight(): number { return this.minimumHeight; } //#endregion @@ -100,7 +100,7 @@ export class TitlebarPart extends Part implements ITitleService { this.contextMenu = this._register(menuService.createMenu(MenuId.TitleBarContext, contextKeyService)); - this.titleBarStyle = getTitleBarStyle(this.configurationService, this.environmentService); + this.titleBarStyle = getTitleBarStyle(this.configurationService); this.registerListeners(); } @@ -461,13 +461,13 @@ export class TitlebarPart extends Part implements ITitleService { } protected get currentMenubarVisibility(): MenuBarVisibility { - return getMenuBarVisibility(this.configurationService, this.environmentService); + return getMenuBarVisibility(this.configurationService); } updateLayout(dimension: Dimension): void { this.lastLayoutDimensions = dimension; - if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { + if (getTitleBarStyle(this.configurationService) === 'custom') { // Only prevent zooming behavior on macOS or when the menubar is not visible if ((!isWeb && isMacintosh) || this.currentMenubarVisibility === 'hidden') { this.title.style.zoom = `${1 / getZoomFactor()}`; diff --git a/src/vs/workbench/contrib/views/browser/media/views.css b/src/vs/workbench/browser/parts/views/media/views.css similarity index 98% rename from src/vs/workbench/contrib/views/browser/media/views.css rename to src/vs/workbench/browser/parts/views/media/views.css index 8b30fccc5..8f2df0476 100644 --- a/src/vs/workbench/contrib/views/browser/media/views.css +++ b/src/vs/workbench/browser/parts/views/media/views.css @@ -64,11 +64,15 @@ } .monaco-workbench .pane > .pane-body > .welcome-view .monaco-button { - max-width: 260px; margin-left: auto; margin-right: auto; } +.monaco-workbench .pane > .pane-body.wide > .welcome-view .monaco-button { + margin-left: inherit; + max-width: 260px; +} + .monaco-workbench .pane > .pane-body .welcome-view-content { padding: 0 20px 0 20px; box-sizing: border-box; diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 3669114bf..dbd8ffce1 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -3,20 +3,55 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { toDisposable } from 'vs/base/common/lifecycle'; +import 'vs/css!./media/views'; +import { toDisposable, IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { MenuId } from 'vs/platform/actions/common/actions'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ITreeView, ITreeViewDescriptor, IViewsRegistry, Extensions, IViewDescriptorService } from 'vs/workbench/common/views'; +import { MenuId, IMenuService, MenuItemAction, registerAction2, Action2, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { IContextKeyService, ContextKeyExpr, ContextKeyEqualsExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ITreeView, ITreeViewDescriptor, IViewsRegistry, Extensions, IViewDescriptorService, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeItemLabel, ViewContainer, ViewContainerLocation, ResolvableTreeItem } from 'vs/workbench/common/views'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { Registry } from 'vs/platform/registry/common/platform'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { Event, Emitter } from 'vs/base/common/event'; +import { IAction, ActionRunner, IActionViewItemProvider } from 'vs/base/common/actions'; +import { MenuEntryActionViewItem, createAndFillInContextMenuActions, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IProgressService } from 'vs/platform/progress/common/progress'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import * as DOM from 'vs/base/browser/dom'; +import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { URI } from 'vs/base/common/uri'; +import { dirname, basename } from 'vs/base/common/resources'; +import { FileKind } from 'vs/platform/files/common/files'; +import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; +import { localize } from 'vs/nls'; +import { timeout } from 'vs/base/common/async'; +import { textLinkForeground, textCodeBlockBackground, focusBorder, listFilterMatchHighlight, listFilterMatchHighlightBorder } from 'vs/platform/theme/common/colorRegistry'; +import { isString } from 'vs/base/common/types'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list'; +import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; +import { FuzzyScore, createMatches } from 'vs/base/common/filters'; +import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults'; +import { isFalsyOrWhitespace } from 'vs/base/common/strings'; +import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; +import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; +import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { IIconLabelMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabel'; +import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; +import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { Codicon } from 'vs/base/common/codicons'; export class TreeViewPane extends ViewPane { @@ -87,3 +122,1050 @@ export class TreeViewPane extends ViewPane { this.treeView.setVisibility(this.isBodyVisible()); } } + +class Root implements ITreeItem { + label = { label: 'root' }; + handle = '0'; + parentHandle: string | undefined = undefined; + collapsibleState = TreeItemCollapsibleState.Expanded; + children: ITreeItem[] | undefined = undefined; +} + +const noDataProviderMessage = localize('no-dataprovider', "There is no data provider registered that can provide view data."); + +class Tree extends WorkbenchAsyncDataTree { } + +export class TreeView extends Disposable implements ITreeView { + + private isVisible: boolean = false; + private _hasIconForParentNode = false; + private _hasIconForLeafNode = false; + + private readonly collapseAllContextKey: RawContextKey; + private readonly collapseAllContext: IContextKey; + private readonly collapseAllToggleContextKey: RawContextKey; + private readonly collapseAllToggleContext: IContextKey; + private readonly refreshContextKey: RawContextKey; + private readonly refreshContext: IContextKey; + + private focused: boolean = false; + private domNode!: HTMLElement; + private treeContainer!: HTMLElement; + private _messageValue: string | undefined; + private _canSelectMany: boolean = false; + private messageElement!: HTMLDivElement; + private tree: Tree | undefined; + private treeLabels: ResourceLabels | undefined; + + private root: ITreeItem; + private elementsToRefresh: ITreeItem[] = []; + + private readonly _onDidExpandItem: Emitter = this._register(new Emitter()); + readonly onDidExpandItem: Event = this._onDidExpandItem.event; + + private readonly _onDidCollapseItem: Emitter = this._register(new Emitter()); + readonly onDidCollapseItem: Event = this._onDidCollapseItem.event; + + private _onDidChangeSelection: Emitter = this._register(new Emitter()); + readonly onDidChangeSelection: Event = this._onDidChangeSelection.event; + + private readonly _onDidChangeVisibility: Emitter = this._register(new Emitter()); + readonly onDidChangeVisibility: Event = this._onDidChangeVisibility.event; + + private readonly _onDidChangeActions: Emitter = this._register(new Emitter()); + readonly onDidChangeActions: Event = this._onDidChangeActions.event; + + private readonly _onDidChangeWelcomeState: Emitter = this._register(new Emitter()); + readonly onDidChangeWelcomeState: Event = this._onDidChangeWelcomeState.event; + + private readonly _onDidChangeTitle: Emitter = this._register(new Emitter()); + readonly onDidChangeTitle: Event = this._onDidChangeTitle.event; + + private readonly _onDidChangeDescription: Emitter = this._register(new Emitter()); + readonly onDidChangeDescription: Event = this._onDidChangeDescription.event; + + private readonly _onDidCompleteRefresh: Emitter = this._register(new Emitter()); + + constructor( + readonly id: string, + private _title: string, + @IThemeService private readonly themeService: IThemeService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @ICommandService private readonly commandService: ICommandService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IProgressService protected readonly progressService: IProgressService, + @IContextMenuService private readonly contextMenuService: IContextMenuService, + @IKeybindingService private readonly keybindingService: IKeybindingService, + @INotificationService private readonly notificationService: INotificationService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IHoverService private readonly hoverService: IHoverService, + @IContextKeyService contextKeyService: IContextKeyService + ) { + super(); + this.root = new Root(); + this.collapseAllContextKey = new RawContextKey(`treeView.${this.id}.enableCollapseAll`, false); + this.collapseAllContext = this.collapseAllContextKey.bindTo(contextKeyService); + this.collapseAllToggleContextKey = new RawContextKey(`treeView.${this.id}.toggleCollapseAll`, false); + this.collapseAllToggleContext = this.collapseAllToggleContextKey.bindTo(contextKeyService); + this.refreshContextKey = new RawContextKey(`treeView.${this.id}.enableRefresh`, false); + this.refreshContext = this.refreshContextKey.bindTo(contextKeyService); + + this._register(this.themeService.onDidFileIconThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); + this._register(this.themeService.onDidColorThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('explorer.decorations')) { + this.doRefresh([this.root]); /** soft refresh **/ + } + })); + this._register(this.viewDescriptorService.onDidChangeLocation(({ views, from, to }) => { + if (views.some(v => v.id === this.id)) { + this.tree?.updateOptions({ overrideStyles: { listBackground: this.viewLocation === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND } }); + } + })); + this.registerActions(); + + this.create(); + } + + get viewContainer(): ViewContainer { + return this.viewDescriptorService.getViewContainerByViewId(this.id)!; + } + + get viewLocation(): ViewContainerLocation { + return this.viewDescriptorService.getViewLocationById(this.id)!; + } + + private _dataProvider: ITreeViewDataProvider | undefined; + get dataProvider(): ITreeViewDataProvider | undefined { + return this._dataProvider; + } + + set dataProvider(dataProvider: ITreeViewDataProvider | undefined) { + if (this.tree === undefined) { + this.createTree(); + } + + if (dataProvider) { + const self = this; + this._dataProvider = new class implements ITreeViewDataProvider { + private _isEmpty: boolean = true; + private _onDidChangeEmpty: Emitter = new Emitter(); + public onDidChangeEmpty: Event = this._onDidChangeEmpty.event; + + get isTreeEmpty(): boolean { + return this._isEmpty; + } + + async getChildren(node?: ITreeItem): Promise { + let children: ITreeItem[]; + if (node && node.children) { + children = node.children; + } else { + node = node ?? self.root; + children = await (node instanceof Root ? dataProvider.getChildren() : dataProvider.getChildren(node)); + node.children = children; + } + if (node instanceof Root) { + const oldEmpty = this._isEmpty; + this._isEmpty = children.length === 0; + if (oldEmpty !== this._isEmpty) { + this._onDidChangeEmpty.fire(); + } + } + return children; + } + }; + if (this._dataProvider.onDidChangeEmpty) { + this._register(this._dataProvider.onDidChangeEmpty(() => this._onDidChangeWelcomeState.fire())); + } + this.updateMessage(); + this.refresh(); + } else { + this._dataProvider = undefined; + this.updateMessage(); + } + + this._onDidChangeWelcomeState.fire(); + } + + private _message: string | undefined; + get message(): string | undefined { + return this._message; + } + + set message(message: string | undefined) { + this._message = message; + this.updateMessage(); + this._onDidChangeWelcomeState.fire(); + } + + get title(): string { + return this._title; + } + + set title(name: string) { + this._title = name; + this._onDidChangeTitle.fire(this._title); + } + + private _description: string | undefined; + get description(): string | undefined { + return this._description; + } + + set description(description: string | undefined) { + this._description = description; + this._onDidChangeDescription.fire(this._description); + } + + get canSelectMany(): boolean { + return this._canSelectMany; + } + + set canSelectMany(canSelectMany: boolean) { + this._canSelectMany = canSelectMany; + } + + get hasIconForParentNode(): boolean { + return this._hasIconForParentNode; + } + + get hasIconForLeafNode(): boolean { + return this._hasIconForLeafNode; + } + + get visible(): boolean { + return this.isVisible; + } + + get showCollapseAllAction(): boolean { + return !!this.collapseAllContext.get(); + } + + set showCollapseAllAction(showCollapseAllAction: boolean) { + this.collapseAllContext.set(showCollapseAllAction); + } + + get showRefreshAction(): boolean { + return !!this.refreshContext.get(); + } + + set showRefreshAction(showRefreshAction: boolean) { + this.refreshContext.set(showRefreshAction); + } + + private registerActions() { + const that = this; + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.treeView.${that.id}.refresh`, + title: localize('refresh', "Refresh"), + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', that.id), that.refreshContextKey), + group: 'navigation', + order: Number.MAX_SAFE_INTEGER - 1, + }, + icon: Codicon.refresh + }); + } + async run(): Promise { + return that.refresh(); + } + })); + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.treeView.${that.id}.collapseAll`, + title: localize('collapseAll', "Collapse All"), + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', that.id), that.collapseAllContextKey), + group: 'navigation', + order: Number.MAX_SAFE_INTEGER, + }, + precondition: that.collapseAllToggleContextKey, + icon: Codicon.collapseAll + }); + } + async run(): Promise { + if (that.tree) { + return new CollapseAllAction(that.tree, true).run(); + } + } + })); + } + + setVisibility(isVisible: boolean): void { + isVisible = !!isVisible; + if (this.isVisible === isVisible) { + return; + } + + this.isVisible = isVisible; + + if (this.tree) { + if (this.isVisible) { + DOM.show(this.tree.getHTMLElement()); + } else { + DOM.hide(this.tree.getHTMLElement()); // make sure the tree goes out of the tabindex world by hiding it + } + + if (this.isVisible && this.elementsToRefresh.length) { + this.doRefresh(this.elementsToRefresh); + this.elementsToRefresh = []; + } + } + + this._onDidChangeVisibility.fire(this.isVisible); + } + + focus(reveal: boolean = true): void { + if (this.tree && this.root.children && this.root.children.length > 0) { + // Make sure the current selected element is revealed + const selectedElement = this.tree.getSelection()[0]; + if (selectedElement && reveal) { + this.tree.reveal(selectedElement, 0.5); + } + + // Pass Focus to Viewer + this.tree.domFocus(); + } else if (this.tree) { + this.tree.domFocus(); + } else { + this.domNode.focus(); + } + } + + show(container: HTMLElement): void { + DOM.append(container, this.domNode); + } + + private create() { + this.domNode = DOM.$('.tree-explorer-viewlet-tree-view'); + this.messageElement = DOM.append(this.domNode, DOM.$('.message')); + this.treeContainer = DOM.append(this.domNode, DOM.$('.customview-tree')); + this.treeContainer.classList.add('file-icon-themable-tree', 'show-file-icons'); + const focusTracker = this._register(DOM.trackFocus(this.domNode)); + this._register(focusTracker.onDidFocus(() => this.focused = true)); + this._register(focusTracker.onDidBlur(() => this.focused = false)); + } + + private createTree() { + const actionViewItemProvider = (action: IAction) => { + if (action instanceof MenuItemAction) { + return this.instantiationService.createInstance(MenuEntryActionViewItem, action); + } else if (action instanceof SubmenuItemAction) { + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action); + } + + return undefined; + }; + const treeMenus = this._register(this.instantiationService.createInstance(TreeMenus, this.id)); + this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this)); + const dataSource = this.instantiationService.createInstance(TreeDataSource, this, (task: Promise) => this.progressService.withProgress({ location: this.id }, () => task)); + const aligner = new Aligner(this.themeService); + const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner); + const widgetAriaLabel = this._title; + + this.tree = this._register(this.instantiationService.createInstance(Tree, this.id, this.treeContainer, new TreeViewDelegate(), [renderer], + dataSource, { + identityProvider: new TreeViewIdentityProvider(), + accessibilityProvider: { + getAriaLabel(element: ITreeItem): string { + if (element.accessibilityInformation) { + return element.accessibilityInformation.label; + } + + if (isString(element.tooltip)) { + return element.tooltip; + } else { + let buildAriaLabel: string = ''; + if (element.label) { + buildAriaLabel += element.label.label + ' '; + } + if (element.description) { + buildAriaLabel += element.description; + } + return buildAriaLabel; + } + }, + getRole(element: ITreeItem): string | undefined { + return element.accessibilityInformation?.role ?? 'treeitem'; + }, + getWidgetAriaLabel(): string { + return widgetAriaLabel; + } + }, + keyboardNavigationLabelProvider: { + getKeyboardNavigationLabel: (item: ITreeItem) => { + return item.label ? item.label.label : (item.resourceUri ? basename(URI.revive(item.resourceUri)) : undefined); + } + }, + expandOnlyOnTwistieClick: (e: ITreeItem) => !!e.command, + collapseByDefault: (e: ITreeItem): boolean => { + return e.collapsibleState !== TreeItemCollapsibleState.Expanded; + }, + multipleSelectionSupport: this.canSelectMany, + overrideStyles: { + listBackground: this.viewLocation === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND + } + }) as WorkbenchAsyncDataTree); + treeMenus.setContextKeyService(this.tree.contextKeyService); + aligner.tree = this.tree; + const actionRunner = new MultipleSelectionActionRunner(this.notificationService, () => this.tree!.getSelection()); + renderer.actionRunner = actionRunner; + + this.tree.contextKeyService.createKey(this.id, true); + this._register(this.tree.onContextMenu(e => this.onContextMenu(treeMenus, e, actionRunner))); + this._register(this.tree.onDidChangeSelection(e => this._onDidChangeSelection.fire(e.elements))); + this._register(this.tree.onDidChangeCollapseState(e => { + if (!e.node.element) { + return; + } + + const element: ITreeItem = Array.isArray(e.node.element.element) ? e.node.element.element[0] : e.node.element.element; + if (e.node.collapsed) { + this._onDidCollapseItem.fire(element); + } else { + this._onDidExpandItem.fire(element); + } + })); + this.tree.setInput(this.root).then(() => this.updateContentAreas()); + + this._register(this.tree.onDidOpen(e => { + if (!e.browserEvent) { + return; + } + const selection = this.tree!.getSelection(); + const command = selection.length === 1 ? selection[0].command : undefined; + if (command) { + let args = command.arguments || []; + if (command.id === API_OPEN_EDITOR_COMMAND_ID || command.id === API_OPEN_DIFF_EDITOR_COMMAND_ID) { + // Some commands owned by us should receive the + // `IOpenEvent` as context to open properly + args = [...args, e]; + } + + this.commandService.executeCommand(command.id, ...args); + } + })); + + } + + private onContextMenu(treeMenus: TreeMenus, treeEvent: ITreeContextMenuEvent, actionRunner: MultipleSelectionActionRunner): void { + this.hoverService.hideHover(); + const node: ITreeItem | null = treeEvent.element; + if (node === null) { + return; + } + const event: UIEvent = treeEvent.browserEvent; + + event.preventDefault(); + event.stopPropagation(); + + this.tree!.setFocus([node]); + const actions = treeMenus.getResourceContextActions(node); + if (!actions.length) { + return; + } + this.contextMenuService.showContextMenu({ + getAnchor: () => treeEvent.anchor, + + getActions: () => actions, + + getActionViewItem: (action) => { + const keybinding = this.keybindingService.lookupKeybinding(action.id); + if (keybinding) { + return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel() }); + } + return undefined; + }, + + onHide: (wasCancelled?: boolean) => { + if (wasCancelled) { + this.tree!.domFocus(); + } + }, + + getActionsContext: () => ({ $treeViewId: this.id, $treeItemHandle: node.handle }), + + actionRunner + }); + } + + protected updateMessage(): void { + if (this._message) { + this.showMessage(this._message); + } else if (!this.dataProvider) { + this.showMessage(noDataProviderMessage); + } else { + this.hideMessage(); + } + this.updateContentAreas(); + } + + private showMessage(message: string): void { + this.messageElement.classList.remove('hide'); + this.resetMessageElement(); + this._messageValue = message; + if (!isFalsyOrWhitespace(this._message)) { + this.messageElement.textContent = this._messageValue; + } + this.layout(this._height, this._width); + } + + private hideMessage(): void { + this.resetMessageElement(); + this.messageElement.classList.add('hide'); + this.layout(this._height, this._width); + } + + private resetMessageElement(): void { + DOM.clearNode(this.messageElement); + } + + private _height: number = 0; + private _width: number = 0; + layout(height: number, width: number) { + if (height && width) { + this._height = height; + this._width = width; + const treeHeight = height - DOM.getTotalHeight(this.messageElement); + this.treeContainer.style.height = treeHeight + 'px'; + if (this.tree) { + this.tree.layout(treeHeight, width); + } + } + } + + getOptimalWidth(): number { + if (this.tree) { + const parentNode = this.tree.getHTMLElement(); + const childNodes = ([] as HTMLElement[]).slice.call(parentNode.querySelectorAll('.outline-item-label > a')); + return DOM.getLargestChildWidth(parentNode, childNodes); + } + return 0; + } + + async refresh(elements?: ITreeItem[]): Promise { + if (this.dataProvider && this.tree) { + if (this.refreshing) { + await Event.toPromise(this._onDidCompleteRefresh.event); + } + if (!elements) { + elements = [this.root]; + // remove all waiting elements to refresh if root is asked to refresh + this.elementsToRefresh = []; + } + for (const element of elements) { + element.children = undefined; // reset children + } + if (this.isVisible) { + return this.doRefresh(elements); + } else { + if (this.elementsToRefresh.length) { + const seen: Set = new Set(); + this.elementsToRefresh.forEach(element => seen.add(element.handle)); + for (const element of elements) { + if (!seen.has(element.handle)) { + this.elementsToRefresh.push(element); + } + } + } else { + this.elementsToRefresh.push(...elements); + } + } + } + return undefined; + } + + async expand(itemOrItems: ITreeItem | ITreeItem[]): Promise { + const tree = this.tree; + if (tree) { + itemOrItems = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems]; + await Promise.all(itemOrItems.map(element => { + return tree.expand(element, false); + })); + } + } + + setSelection(items: ITreeItem[]): void { + if (this.tree) { + this.tree.setSelection(items); + } + } + + setFocus(item: ITreeItem): void { + if (this.tree) { + this.focus(); + this.tree.setFocus([item]); + } + } + + async reveal(item: ITreeItem): Promise { + if (this.tree) { + return this.tree.reveal(item); + } + } + + private refreshing: boolean = false; + private async doRefresh(elements: ITreeItem[]): Promise { + const tree = this.tree; + if (tree && this.visible) { + this.refreshing = true; + await Promise.all(elements.map(element => tree.updateChildren(element, true, true))); + this.refreshing = false; + this._onDidCompleteRefresh.fire(); + this.updateContentAreas(); + if (this.focused) { + this.focus(false); + } + this.updateCollapseAllToggle(); + } + } + + private updateCollapseAllToggle() { + if (this.showCollapseAllAction) { + this.collapseAllToggleContext.set(!!this.root.children && (this.root.children.length > 0) && + this.root.children.some(value => value.collapsibleState !== TreeItemCollapsibleState.None)); + } + } + + private updateContentAreas(): void { + const isTreeEmpty = !this.root.children || this.root.children.length === 0; + // Hide tree container only when there is a message and tree is empty and not refreshing + if (this._messageValue && isTreeEmpty && !this.refreshing) { + this.treeContainer.classList.add('hide'); + this.domNode.setAttribute('tabindex', '0'); + } else { + this.treeContainer.classList.remove('hide'); + this.domNode.removeAttribute('tabindex'); + } + } +} + +class TreeViewIdentityProvider implements IIdentityProvider { + getId(element: ITreeItem): { toString(): string; } { + return element.handle; + } +} + +class TreeViewDelegate implements IListVirtualDelegate { + + getHeight(element: ITreeItem): number { + return TreeRenderer.ITEM_HEIGHT; + } + + getTemplateId(element: ITreeItem): string { + return TreeRenderer.TREE_TEMPLATE_ID; + } +} + +class TreeDataSource implements IAsyncDataSource { + + constructor( + private treeView: ITreeView, + private withProgress: (task: Promise) => Promise + ) { + } + + hasChildren(element: ITreeItem): boolean { + return !!this.treeView.dataProvider && (element.collapsibleState !== TreeItemCollapsibleState.None); + } + + async getChildren(element: ITreeItem): Promise { + if (this.treeView.dataProvider) { + return this.withProgress(this.treeView.dataProvider.getChildren(element)); + } + return []; + } +} + +// todo@jrieken,sandy make this proper and contributable from extensions +registerThemingParticipant((theme, collector) => { + + const matchBackgroundColor = theme.getColor(listFilterMatchHighlight); + if (matchBackgroundColor) { + collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; background-color: ${matchBackgroundColor}; }`); + collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; background-color: ${matchBackgroundColor}; }`); + } + const matchBorderColor = theme.getColor(listFilterMatchHighlightBorder); + if (matchBorderColor) { + collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${matchBorderColor}; box-sizing: border-box; }`); + collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${matchBorderColor}; box-sizing: border-box; }`); + } + const link = theme.getColor(textLinkForeground); + if (link) { + collector.addRule(`.tree-explorer-viewlet-tree-view > .message a { color: ${link}; }`); + } + const focusBorderColor = theme.getColor(focusBorder); + if (focusBorderColor) { + collector.addRule(`.tree-explorer-viewlet-tree-view > .message a:focus { outline: 1px solid ${focusBorderColor}; outline-offset: -1px; }`); + } + const codeBackground = theme.getColor(textCodeBlockBackground); + if (codeBackground) { + collector.addRule(`.tree-explorer-viewlet-tree-view > .message code { background-color: ${codeBackground}; }`); + } +}); + +interface ITreeExplorerTemplateData { + elementDisposable: IDisposable; + container: HTMLElement; + resourceLabel: IResourceLabel; + icon: HTMLElement; + actionBar: ActionBar; +} + +class TreeRenderer extends Disposable implements ITreeRenderer { + static readonly ITEM_HEIGHT = 22; + static readonly TREE_TEMPLATE_ID = 'treeExplorer'; + + private _actionRunner: MultipleSelectionActionRunner | undefined; + private _hoverDelegate: IHoverDelegate; + + constructor( + private treeViewId: string, + private menus: TreeMenus, + private labels: ResourceLabels, + private actionViewItemProvider: IActionViewItemProvider, + private aligner: Aligner, + @IThemeService private readonly themeService: IThemeService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @ILabelService private readonly labelService: ILabelService, + @IHoverService private readonly hoverService: IHoverService + ) { + super(); + this._hoverDelegate = { + showHover: (options: IHoverDelegateOptions): IDisposable | undefined => { + return this.hoverService.showHover(options); + } + }; + } + + get templateId(): string { + return TreeRenderer.TREE_TEMPLATE_ID; + } + + set actionRunner(actionRunner: MultipleSelectionActionRunner) { + this._actionRunner = actionRunner; + } + + renderTemplate(container: HTMLElement): ITreeExplorerTemplateData { + container.classList.add('custom-view-tree-node-item'); + + const icon = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon')); + + const resourceLabel = this.labels.create(container, { supportHighlights: true, hoverDelegate: this._hoverDelegate }); + const actionsContainer = DOM.append(resourceLabel.element, DOM.$('.actions')); + const actionBar = new ActionBar(actionsContainer, { + actionViewItemProvider: this.actionViewItemProvider + }); + + return { resourceLabel, icon, actionBar, container, elementDisposable: Disposable.None }; + } + + private getHover(label: string | undefined, resource: URI | null, node: ITreeItem): string | IIconLabelMarkdownString | undefined { + if (!(node instanceof ResolvableTreeItem) || !node.hasResolve) { + if (resource) { + return undefined; + } else if (!node.tooltip) { + return label; + } else if (!isString(node.tooltip)) { + return { markdown: node.tooltip, markdownNotSupportedFallback: resource ? undefined : renderMarkdownAsPlaintext(node.tooltip) }; // Passing undefined as the fallback for a resource falls back to the old native hover + } else { + return node.tooltip; + } + } + + return { + markdown: (): Promise => { + return new Promise(async (resolve) => { + await node.resolve(); + resolve(node.tooltip); + }); + }, + markdownNotSupportedFallback: resource ? undefined : '' // Passing undefined as the fallback for a resource falls back to the old native hover + }; + } + + renderElement(element: ITreeNode, index: number, templateData: ITreeExplorerTemplateData): void { + templateData.elementDisposable.dispose(); + const node = element.element; + const resource = node.resourceUri ? URI.revive(node.resourceUri) : null; + const treeItemLabel: ITreeItemLabel | undefined = node.label ? node.label : (resource ? { label: basename(resource) } : undefined); + const description = isString(node.description) ? node.description : resource && node.description === true ? this.labelService.getUriLabel(dirname(resource), { relative: true }) : undefined; + const label = treeItemLabel ? treeItemLabel.label : undefined; + const matches = (treeItemLabel && treeItemLabel.highlights && label) ? treeItemLabel.highlights.map(([start, end]) => { + if (start < 0) { + start = label.length + start; + } + if (end < 0) { + end = label.length + end; + } + if ((start >= label.length) || (end > label.length)) { + return ({ start: 0, end: 0 }); + } + if (start > end) { + const swap = start; + start = end; + end = swap; + } + return ({ start, end }); + }) : undefined; + const icon = this.themeService.getColorTheme().type === ColorScheme.LIGHT ? node.icon : node.iconDark; + const iconUrl = icon ? URI.revive(icon) : null; + const title = this.getHover(label, resource, node); + + // reset + templateData.actionBar.clear(); + templateData.icon.style.color = ''; + + if (resource || this.isFileKindThemeIcon(node.themeIcon)) { + const fileDecorations = this.configurationService.getValue<{ colors: boolean, badges: boolean }>('explorer.decorations'); + const labelResource = resource ? resource : URI.parse('missing:_icon_resource'); + templateData.resourceLabel.setResource({ name: label, description, resource: labelResource }, { + fileKind: this.getFileKind(node), + title, + hideIcon: !!iconUrl, + fileDecorations, + extraClasses: ['custom-view-tree-node-item-resourceLabel'], + matches: matches ? matches : createMatches(element.filterData), + strikethrough: treeItemLabel?.strikethrough + }); + } else { + templateData.resourceLabel.setResource({ name: label, description }, { + title, + hideIcon: true, + extraClasses: ['custom-view-tree-node-item-resourceLabel'], + matches: matches ? matches : createMatches(element.filterData), + strikethrough: treeItemLabel?.strikethrough + }); + } + + if (iconUrl) { + templateData.icon.className = 'custom-view-tree-node-item-icon'; + templateData.icon.style.backgroundImage = DOM.asCSSUrl(iconUrl); + } else { + let iconClass: string | undefined; + if (node.themeIcon && !this.isFileKindThemeIcon(node.themeIcon)) { + iconClass = ThemeIcon.asClassName(node.themeIcon); + if (node.themeIcon.color) { + templateData.icon.style.color = this.themeService.getColorTheme().getColor(node.themeIcon.color.id)?.toString() ?? ''; + } + } + templateData.icon.className = iconClass ? `custom-view-tree-node-item-icon ${iconClass}` : ''; + templateData.icon.style.backgroundImage = ''; + } + + templateData.actionBar.context = { $treeViewId: this.treeViewId, $treeItemHandle: node.handle }; + templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false }); + if (this._actionRunner) { + templateData.actionBar.actionRunner = this._actionRunner; + } + this.setAlignment(templateData.container, node); + const disposableStore = new DisposableStore(); + templateData.elementDisposable = disposableStore; + disposableStore.add(this.themeService.onDidFileIconThemeChange(() => this.setAlignment(templateData.container, node))); + } + + private setAlignment(container: HTMLElement, treeItem: ITreeItem) { + container.parentElement!.classList.toggle('align-icon-with-twisty', this.aligner.alignIconWithTwisty(treeItem)); + } + + private isFileKindThemeIcon(icon: ThemeIcon | undefined): boolean { + if (icon) { + return icon.id === FileThemeIcon.id || icon.id === FolderThemeIcon.id; + } else { + return false; + } + } + + private getFileKind(node: ITreeItem): FileKind { + if (node.themeIcon) { + switch (node.themeIcon.id) { + case FileThemeIcon.id: + return FileKind.FILE; + case FolderThemeIcon.id: + return FileKind.FOLDER; + } + } + return node.collapsibleState === TreeItemCollapsibleState.Collapsed || node.collapsibleState === TreeItemCollapsibleState.Expanded ? FileKind.FOLDER : FileKind.FILE; + } + + disposeElement(resource: ITreeNode, index: number, templateData: ITreeExplorerTemplateData): void { + templateData.elementDisposable.dispose(); + } + + disposeTemplate(templateData: ITreeExplorerTemplateData): void { + templateData.resourceLabel.dispose(); + templateData.actionBar.dispose(); + templateData.elementDisposable.dispose(); + } +} + +class Aligner extends Disposable { + private _tree: WorkbenchAsyncDataTree | undefined; + + constructor(private themeService: IThemeService) { + super(); + } + + set tree(tree: WorkbenchAsyncDataTree) { + this._tree = tree; + } + + public alignIconWithTwisty(treeItem: ITreeItem): boolean { + if (treeItem.collapsibleState !== TreeItemCollapsibleState.None) { + return false; + } + if (!this.hasIcon(treeItem)) { + return false; + } + + if (this._tree) { + const parent: ITreeItem = this._tree.getParentElement(treeItem) || this._tree.getInput(); + if (this.hasIcon(parent)) { + return !!parent.children && parent.children.some(c => c.collapsibleState !== TreeItemCollapsibleState.None && !this.hasIcon(c)); + } + return !!parent.children && parent.children.every(c => c.collapsibleState === TreeItemCollapsibleState.None || !this.hasIcon(c)); + } else { + return false; + } + } + + private hasIcon(node: ITreeItem): boolean { + const icon = this.themeService.getColorTheme().type === ColorScheme.LIGHT ? node.icon : node.iconDark; + if (icon) { + return true; + } + if (node.resourceUri || node.themeIcon) { + const fileIconTheme = this.themeService.getFileIconTheme(); + const isFolder = node.themeIcon ? node.themeIcon.id === FolderThemeIcon.id : node.collapsibleState !== TreeItemCollapsibleState.None; + if (isFolder) { + return fileIconTheme.hasFileIcons && fileIconTheme.hasFolderIcons; + } + return fileIconTheme.hasFileIcons; + } + return false; + } +} + +class MultipleSelectionActionRunner extends ActionRunner { + + constructor(notificationService: INotificationService, private getSelectedResources: (() => ITreeItem[])) { + super(); + this._register(this.onDidRun(e => { + if (e.error) { + notificationService.error(localize('command-error', 'Error running command {1}: {0}. This is likely caused by the extension that contributes {1}.', e.error.message, e.action.id)); + } + })); + } + + runAction(action: IAction, context: TreeViewItemHandleArg): Promise { + const selection = this.getSelectedResources(); + let selectionHandleArgs: TreeViewItemHandleArg[] | undefined = undefined; + let actionInSelected: boolean = false; + if (selection.length > 1) { + selectionHandleArgs = selection.map(selected => { + if (selected.handle === context.$treeItemHandle) { + actionInSelected = true; + } + return { $treeViewId: context.$treeViewId, $treeItemHandle: selected.handle }; + }); + } + + if (!actionInSelected) { + selectionHandleArgs = undefined; + } + + return action.run(...[context, selectionHandleArgs]); + } +} + +class TreeMenus extends Disposable implements IDisposable { + private contextKeyService: IContextKeyService | undefined; + + constructor( + private id: string, + @IMenuService private readonly menuService: IMenuService + ) { + super(); + } + + getResourceActions(element: ITreeItem): IAction[] { + return this.getActions(MenuId.ViewItemContext, { key: 'viewItem', value: element.contextValue }).primary; + } + + getResourceContextActions(element: ITreeItem): IAction[] { + return this.getActions(MenuId.ViewItemContext, { key: 'viewItem', value: element.contextValue }).secondary; + } + + public setContextKeyService(service: IContextKeyService) { + this.contextKeyService = service; + } + + private getActions(menuId: MenuId, context: { key: string, value?: string }): { primary: IAction[]; secondary: IAction[]; } { + if (!this.contextKeyService) { + return { primary: [], secondary: [] }; + } + const contextKeyService = this.contextKeyService.createScoped(); + contextKeyService.createKey('view', this.id); + contextKeyService.createKey(context.key, context.value); + + const menu = this.menuService.createMenu(menuId, contextKeyService); + const primary: IAction[] = []; + const secondary: IAction[] = []; + const result = { primary, secondary }; + createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, g => /^inline/.test(g)); + + menu.dispose(); + contextKeyService.dispose(); + + return result; + } +} + +export class CustomTreeView extends TreeView { + + private activated: boolean = false; + + constructor( + id: string, + title: string, + @IThemeService themeService: IThemeService, + @IInstantiationService instantiationService: IInstantiationService, + @ICommandService commandService: ICommandService, + @IConfigurationService configurationService: IConfigurationService, + @IProgressService progressService: IProgressService, + @IContextMenuService contextMenuService: IContextMenuService, + @IKeybindingService keybindingService: IKeybindingService, + @INotificationService notificationService: INotificationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IContextKeyService contextKeyService: IContextKeyService, + @IHoverService hoverService: IHoverService, + @IExtensionService private readonly extensionService: IExtensionService, + ) { + super(id, title, themeService, instantiationService, commandService, configurationService, progressService, contextMenuService, keybindingService, notificationService, viewDescriptorService, hoverService, contextKeyService); + } + + setVisibility(isVisible: boolean): void { + super.setVisibility(isVisible); + if (this.visible) { + this.activate(); + } + } + + private activate() { + if (!this.activated) { + this.progressService.withProgress({ location: this.id }, () => this.extensionService.activateByEvent(`onView:${this.id}`)) + .then(() => timeout(2000)) + .then(() => { + this.updateMessage(); + }); + this.activated = true; + } + } +} + diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 4fa93931e..410c67244 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -18,15 +18,15 @@ import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; +import { IThemeService, Themable, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { PaneView, IPaneViewOptions, IPaneOptions, Pane, IPaneStyles } from 'vs/base/browser/ui/splitview/paneview'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewPaneContainer, IViewsRegistry, IViewContentDescriptor, IAddedViewDescriptorRef, IViewDescriptorRef, IViewContainerModel } from 'vs/workbench/common/views'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewPaneContainer, IViewsRegistry, IViewContentDescriptor, IAddedViewDescriptorRef, IViewDescriptorRef, IViewContainerModel, defaultViewIcon } from 'vs/workbench/common/views'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { assertIsDefined, isString } from 'vs/base/common/types'; +import { assertIsDefined } from 'vs/base/common/types'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -50,6 +50,8 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { URI } from 'vs/base/common/uri'; import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; +import { Codicon } from 'vs/base/common/codicons'; export interface IPaneColors extends IColorMapping { dropBackground?: ColorIdentifier; @@ -70,6 +72,9 @@ type WelcomeActionClassification = { uri: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; }; +const viewPaneContainerExpandedIcon = registerIcon('view-pane-container-expanded', Codicon.chevronDown, nls.localize('viewPaneContainerExpandedIcon', 'Icon for an expanded view pane container.')); +const viewPaneContainerCollapsedIcon = registerIcon('view-pane-container-collapsed', Codicon.chevronRight, nls.localize('viewPaneContainerCollapsedIcon', 'Icon for a collapsed view pane container.')); + const viewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); interface IItem { @@ -263,7 +268,10 @@ export abstract class ViewPane extends Pane implements IView { if (changed) { this._onDidChangeBodyVisibility.fire(expanded); } - + if (this.twistiesContainer) { + this.twistiesContainer.classList.remove(...ThemeIcon.asClassNameArray(this.getTwistyIcon(!expanded))); + this.twistiesContainer.classList.add(...ThemeIcon.asClassNameArray(this.getTwistyIcon(expanded))); + } return changed; } @@ -288,7 +296,7 @@ export abstract class ViewPane extends Pane implements IView { protected renderHeader(container: HTMLElement): void { this.headerContainer = container; - this.renderTwisties(container); + this.twistiesContainer = append(container, $(ThemeIcon.asCSSSelector(this.getTwistyIcon(this.isExpanded())))); this.renderHeaderTitle(container, this.title); @@ -316,8 +324,8 @@ export abstract class ViewPane extends Pane implements IView { this.updateActionsVisibility(); } - protected renderTwisties(container: HTMLElement): void { - this.twistiesContainer = append(container, $('.twisties.codicon.codicon-chevron-right')); + protected getTwistyIcon(expanded: boolean): ThemeIcon { + return expanded ? viewPaneContainerExpandedIcon : viewPaneContainerCollapsedIcon; } style(styles: IPaneStyles): void { @@ -338,8 +346,8 @@ export abstract class ViewPane extends Pane implements IView { } } - private getIcon(): string | URI { - return this.viewDescriptorService.getViewDescriptorById(this.id)?.containerIcon || 'codicon-window'; + private getIcon(): ThemeIcon | URI { + return this.viewDescriptorService.getViewDescriptorById(this.id)?.containerIcon || defaultViewIcon; } protected renderHeaderTitle(container: HTMLElement, title: string): void { @@ -357,9 +365,8 @@ export abstract class ViewPane extends Pane implements IView { -webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%; -webkit-mask-size: 16px; `); - } else if (isString(icon)) { - this.iconContainer.classList.add('codicon'); - cssClass = icon; + } else if (ThemeIcon.isThemeIcon(icon)) { + cssClass = ThemeIcon.asClassName(icon); } if (cssClass) { @@ -556,9 +563,7 @@ export abstract class ViewPane extends Pane implements IView { this.bodyContainer.classList.add('welcome'); this.viewWelcomeContainer.innerText = ''; - let buttonIndex = 0; - - for (const { content, preconditions } of contents) { + for (const { content, precondition } of contents) { const lines = content.split('\n'); for (let line of lines) { @@ -581,21 +586,15 @@ export abstract class ViewPane extends Pane implements IView { disposables.add(button); disposables.add(attachButtonStyler(button, this.themeService)); - if (preconditions) { - const precondition = preconditions[buttonIndex]; + if (precondition) { + const updateEnablement = () => button.enabled = this.contextKeyService.contextMatchesRules(precondition); + updateEnablement(); - if (precondition) { - const updateEnablement = () => button.enabled = this.contextKeyService.contextMatchesRules(precondition); - updateEnablement(); - - const keys = new Set(); - precondition.keys().forEach(key => keys.add(key)); - const onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys)); - onDidChangeContext(updateEnablement, null, disposables); - } + const keys = new Set(); + precondition.keys().forEach(key => keys.add(key)); + const onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys)); + onDidChangeContext(updateEnablement, null, disposables); } - - buttonIndex++; } else { const p = append(this.viewWelcomeContainer, $('p')); @@ -1319,7 +1318,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { saveState(): void { this.panes.forEach((view) => view.saveState()); - this.storageService.store(this.visibleViewsStorageId, this.length, StorageScope.WORKSPACE); + this.storageService.store(this.visibleViewsStorageId, this.length, StorageScope.WORKSPACE, StorageTarget.USER); } private onContextMenu(event: StandardMouseEvent, viewDescriptor: IViewDescriptor): void { diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 80544aab3..e7d260b00 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -59,6 +59,8 @@ import { localize } from 'vs/nls'; import { CATEGORIES } from 'vs/workbench/common/actions'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService'; class BrowserMain extends Disposable { @@ -198,9 +200,13 @@ class BrowserMain extends Disposable { serviceCollection.set(IFileService, fileService); await this.registerFileSystemProviders(environmentService, fileService, remoteAgentService, logService, logsPath); + // IURIIdentityService + const uriIdentityService = new UriIdentityService(fileService); + serviceCollection.set(IUriIdentityService, uriIdentityService); + // Long running services (workspace, config, storage) const [configurationService, storageService] = await Promise.all([ - this.createWorkspaceService(payload, environmentService, fileService, remoteAgentService, logService).then(service => { + this.createWorkspaceService(payload, environmentService, fileService, remoteAgentService, uriIdentityService, logService).then(service => { // Workspace serviceCollection.set(IWorkspaceContextService, service); @@ -330,8 +336,8 @@ class BrowserMain extends Disposable { } } - private async createWorkspaceService(payload: IWorkspaceInitializationPayload, environmentService: IWorkbenchEnvironmentService, fileService: FileService, remoteAgentService: IRemoteAgentService, logService: ILogService): Promise { - const workspaceService = new WorkspaceService({ remoteAuthority: this.configuration.remoteAuthority, configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, logService); + private async createWorkspaceService(payload: IWorkspaceInitializationPayload, environmentService: IWorkbenchEnvironmentService, fileService: FileService, remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService): Promise { + const workspaceService = new WorkspaceService({ remoteAuthority: this.configuration.remoteAuthority, configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, uriIdentityService, logService); try { await workspaceService.initialize(payload); diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index c04818dc5..fdc99470b 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -108,6 +108,11 @@ import { isStandalone } from 'vs/base/browser/browser'; ], 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'splitSizing' }, "Controls the sizing of editor groups when splitting them.") }, + 'workbench.editor.splitOnDragAndDrop': { + 'type': 'boolean', + 'default': true, + 'description': nls.localize('splitOnDragAndDrop', "Controls if editor groups can be split from drag and drop operations by dropping an editor or file on the edges of the editor area.") + }, 'workbench.editor.focusRecentEditorAfterClose': { 'type': 'boolean', 'description': nls.localize('focusRecentEditorAfterClose', "Controls whether tabs are closed in most recently used order or from left to right."), @@ -120,13 +125,13 @@ import { isStandalone } from 'vs/base/browser/browser'; }, 'workbench.editor.enablePreview': { 'type': 'boolean', - 'description': nls.localize('enablePreview', "Controls whether opened editors show as preview. Preview editors are reused until they are explicitly set to be kept open (e.g. via double click or editing) and show up with an italic font style."), + 'description': nls.localize('enablePreview', "Controls whether opened editors show as preview. Preview editors do not keep open and are reused until explicitly set to be kept open (e.g. via double click or editing) and show up with an italic font style."), 'default': true }, 'workbench.editor.enablePreviewFromQuickOpen': { 'type': 'boolean', - 'description': nls.localize('enablePreviewFromQuickOpen', "Controls whether editors opened from Quick Open show as preview. Preview editors are reused until they are explicitly set to be kept open (e.g. via double click or editing)."), - 'default': true + 'description': nls.localize('enablePreviewFromQuickOpen', "Controls whether editors opened from Quick Open show as preview. Preview editors do not keep open and are reused until explicitly set to be kept open (e.g. via double click or editing)."), + 'default': false }, 'workbench.editor.closeOnFileDelete': { 'type': 'boolean', diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index dce86a911..2172c0944 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -17,7 +17,7 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr import { IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions'; import { Position, Parts, IWorkbenchLayoutService, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; -import { IStorageService, WillSaveStateReason, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, WillSaveStateReason, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -297,7 +297,7 @@ export class Workbench extends Layout { // local storage and not global storage because it would not make // much sense to synchronize to other machines. if (isNative) { - storageService.store('editorFontInfo', serializedFontInfoRaw, StorageScope.GLOBAL); + storageService.store('editorFontInfo', serializedFontInfoRaw, StorageScope.GLOBAL, StorageTarget.MACHINE); } else { window.localStorage.setItem('vscode.editorFontInfo', serializedFontInfoRaw); } @@ -415,8 +415,7 @@ export class Workbench extends Layout { mark('didStartWorkbench'); // Perf reporting (devtools) - performance.mark('workbench-end'); - performance.measure('perf: workbench create & restore', 'workbench-start', 'workbench-end'); + performance.measure('perf: workbench create & restore', 'didLoadWorkbenchMain', 'didStartWorkbench'); } } } diff --git a/src/vs/workbench/common/component.ts b/src/vs/workbench/common/component.ts index cbe3c7d61..f4454acc5 100644 --- a/src/vs/workbench/common/component.ts +++ b/src/vs/workbench/common/component.ts @@ -5,7 +5,7 @@ import { Memento, MementoObject } from 'vs/workbench/common/memento'; import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; export class Component extends Themable { @@ -35,8 +35,8 @@ export class Component extends Themable { return this.id; } - protected getMemento(scope: StorageScope): MementoObject { - return this.memento.getMemento(scope); + protected getMemento(scope: StorageScope, target: StorageTarget): MementoObject { + return this.memento.getMemento(scope, target); } protected saveState(): void { diff --git a/src/vs/workbench/common/dialogs.ts b/src/vs/workbench/common/dialogs.ts new file mode 100644 index 000000000..5de8eac68 --- /dev/null +++ b/src/vs/workbench/common/dialogs.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event, Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IDialog, IDialogResult } from 'vs/platform/dialogs/common/dialogs'; + +export interface IDialogViewItem { + args: IDialog; + close(result?: IDialogResult): void; +} + +export interface IDialogHandle { + item: IDialogViewItem; + result: Promise; +} + +export interface IDialogsModel { + readonly onDidShowDialog: Event; + + readonly dialogs: IDialogViewItem[]; + + show(dialog: IDialog): IDialogHandle; +} + +export class DialogsModel extends Disposable implements IDialogsModel { + readonly dialogs: IDialogViewItem[] = []; + + private readonly _onDidShowDialog = this._register(new Emitter()); + readonly onDidShowDialog = this._onDidShowDialog.event; + + show(dialog: IDialog): IDialogHandle { + let resolver: (value?: IDialogResult) => void; + + const item: IDialogViewItem = { + args: dialog, + close: (result) => { this.dialogs.splice(0, 1); resolver(result); } + }; + + this.dialogs.push(item); + this._onDidShowDialog.fire(); + + return { + item, + result: new Promise(resolve => { resolver = resolve; }) + }; + } +} diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 30a375204..f104149d8 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -14,13 +14,13 @@ import { IInstantiationService, IConstructorSignature0, ServicesAccessor, Brande import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; import { ITextModel } from 'vs/editor/common/model'; -import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { GroupsOrder, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ICompositeControl, IComposite } from 'vs/workbench/common/composite'; import { ActionRunner, IAction } from 'vs/base/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; import { IPathData } from 'vs/platform/windows/common/windows'; import { coalesce, firstOrDefault } from 'vs/base/common/arrays'; -import { IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService'; +import { ACTIVE_GROUP, IResourceEditorInputType, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IRange } from 'vs/editor/common/core/range'; import { IExtUri } from 'vs/base/common/resources'; @@ -178,7 +178,7 @@ export interface IFileEditorInputFactory { /** * Creates new new editor input capable of showing files. */ - createFileEditorInput(resource: URI, preferredResource: URI | undefined, encoding: string | undefined, mode: string | undefined, instantiationService: IInstantiationService): IFileEditorInput; + createFileEditorInput(resource: URI, preferredResource: URI | undefined, preferredName: string | undefined, preferredDescription: string | undefined, preferredEncoding: string | undefined, preferredMode: string | undefined, instantiationService: IInstantiationService): IFileEditorInput; /** * Check if the provided object is a file editor input. @@ -704,6 +704,24 @@ export interface IFileEditorInput extends IEditorInput, IEncodingSupport, IModeS */ setPreferredResource(preferredResource: URI): void; + /** + * Sets the preferred name to use for this file input. + * + * Note: for certain file schemes the input may decide to ignore this + * name and use our standard naming. Specifically for schemes we own, + * we do not let others override the name. + */ + setPreferredName(name: string): void; + + /** + * Sets the preferred description to use for this file input. + * + * Note: for certain file schemes the input may decide to ignore this + * description and use our standard naming. Specifically for schemes we own, + * we do not let others override the description. + */ + setPreferredDescription(description: string): void; + /** * Sets the preferred encoding to use for this file input. */ @@ -734,7 +752,7 @@ export class SideBySideEditorInput extends EditorInput { constructor( protected readonly name: string | undefined, - private readonly description: string | undefined, + protected readonly description: string | undefined, private readonly _secondary: EditorInput, private readonly _primary: EditorInput ) { @@ -1266,6 +1284,7 @@ interface IEditorPartConfiguration { labelFormat?: 'default' | 'short' | 'medium' | 'long'; restoreViewState?: boolean; splitSizing?: 'split' | 'distribute'; + splitOnDragAndDrop?: boolean; limit?: { enabled?: boolean; value?: number; @@ -1421,13 +1440,15 @@ export const enum CloseDirection { export interface IEditorMemento { saveEditorState(group: IEditorGroup, resource: URI, state: T): void; - saveEditorState(group: IEditorGroup, editor: EditorInput, state: T): void; + saveEditorState(group: IEditorGroup, editor: IEditorInput, state: T): void; loadEditorState(group: IEditorGroup, resource: URI): T | undefined; - loadEditorState(group: IEditorGroup, editor: EditorInput): T | undefined; + loadEditorState(group: IEditorGroup, editor: IEditorInput): T | undefined; clearEditorState(resource: URI, group?: IEditorGroup): void; - clearEditorState(editor: EditorInput, group?: IEditorGroup): void; + clearEditorState(editor: IEditorInput, group?: IEditorGroup): void; + + clearEditorStateOnDispose(resource: URI, editor: IEditorInput): void; moveEditorState(source: URI, target: URI, comparer: IExtUri): void; } @@ -1569,3 +1590,42 @@ export function computeEditorAriaLabel(input: IEditorInput, index: number | unde return ariaLabel; } + + +//#region Editor Group Column + +/** + * A way to address editor groups through a column based system + * where `0` is the first column. Will fallback to `SIDE_GROUP` + * in case the column does not exist yet. + */ +export type EditorGroupColumn = number; + +export function viewColumnToEditorGroup(editorGroupService: IEditorGroupsService, viewColumn?: EditorGroupColumn): GroupIdentifier { + if (typeof viewColumn !== 'number' || viewColumn === ACTIVE_GROUP) { + return ACTIVE_GROUP; // prefer active group when position is undefined or passed in as such + } + + const groups = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE); + + let candidateGroup = groups[viewColumn]; + if (candidateGroup) { + return candidateGroup.id; // found direct match + } + + let firstGroup = groups[0]; + if (groups.length === 1 && firstGroup.count === 0) { + return firstGroup.id; // first editor should always open in first group independent from position provided + } + + return SIDE_GROUP; // open to the side if group not found or we are instructed to +} + +export function editorGroupToViewColumn(editorGroupService: IEditorGroupsService, editorGroup: IEditorGroup | GroupIdentifier): EditorGroupColumn { + let group = (typeof editorGroup === 'number') ? editorGroupService.getGroup(editorGroup) : editorGroup; + group = group ?? editorGroupService.activeGroup; + + return editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE).indexOf(group); +} + +//#endregion diff --git a/src/vs/workbench/common/editor/diffEditorInput.ts b/src/vs/workbench/common/editor/diffEditorInput.ts index 4274f2eb8..38051db4d 100644 --- a/src/vs/workbench/common/editor/diffEditorInput.ts +++ b/src/vs/workbench/common/editor/diffEditorInput.ts @@ -3,11 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { EditorModel, EditorInput, SideBySideEditorInput, TEXT_DIFF_EDITOR_ID, BINARY_DIFF_EDITOR_ID } from 'vs/workbench/common/editor'; +import { EditorModel, EditorInput, SideBySideEditorInput, TEXT_DIFF_EDITOR_ID, BINARY_DIFF_EDITOR_ID, Verbosity } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { DiffEditorModel } from 'vs/workbench/common/editor/diffEditorModel'; import { TextDiffEditorModel } from 'vs/workbench/common/editor/textDiffEditorModel'; import { localize } from 'vs/nls'; +import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; +import { dirname } from 'vs/base/common/resources'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { IFileService } from 'vs/platform/files/common/files'; +import { URI } from 'vs/base/common/uri'; /** * The base editor input for the diff editor. It is made up of two editor inputs, the original version @@ -21,10 +26,12 @@ export class DiffEditorInput extends SideBySideEditorInput { constructor( protected name: string | undefined, - description: string | undefined, + protected description: string | undefined, public readonly originalInput: EditorInput, public readonly modifiedInput: EditorInput, - private readonly forceOpenAsBinary?: boolean + private readonly forceOpenAsBinary: boolean | undefined, + @ILabelService private readonly labelService: ILabelService, + @IFileService private readonly fileService: IFileService ) { super(name, description, originalInput, modifiedInput); } @@ -35,12 +42,51 @@ export class DiffEditorInput extends SideBySideEditorInput { getName(): string { if (!this.name) { + + // Craft a name from original and modified input that includes the + // relative path in case both sides have different parents and we + // compare file resources. + const fileResources = this.asFileResources(); + if (fileResources && dirname(fileResources.original).path !== dirname(fileResources.modified).path) { + return `${this.labelService.getUriLabel(fileResources.original, { relative: true })} ↔ ${this.labelService.getUriLabel(fileResources.modified, { relative: true })}`; + } + return localize('sideBySideLabels', "{0} ↔ {1}", this.originalInput.getName(), this.modifiedInput.getName()); } return this.name; } + getDescription(verbosity: Verbosity = Verbosity.MEDIUM): string | undefined { + if (typeof this.description !== 'string') { + + // Pass the description of the modified side in case both original + // and modified input have the same parent and we compare file resources. + const fileResources = this.asFileResources(); + if (fileResources && dirname(fileResources.original).path === dirname(fileResources.modified).path) { + return this.modifiedInput.getDescription(verbosity); + } + } + + return this.description; + } + + private asFileResources(): { original: URI, modified: URI } | undefined { + if ( + this.originalInput instanceof AbstractTextResourceEditorInput && + this.modifiedInput instanceof AbstractTextResourceEditorInput && + this.fileService.canHandleResource(this.originalInput.preferredResource) && + this.fileService.canHandleResource(this.modifiedInput.preferredResource) + ) { + return { + original: this.originalInput.preferredResource, + modified: this.modifiedInput.preferredResource + }; + } + + return undefined; + } + async resolve(): Promise { // Create Model - we never reuse our cached model if refresh is true because we cannot diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index ee4f35bae..a72d26526 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -55,6 +55,7 @@ export class ResourceEditorInput extends AbstractTextResourceEditorInput impleme setName(name: string): void { if (this.name !== name) { this.name = name; + this._onDidChangeLabel.fire(); } } diff --git a/src/vs/workbench/common/editor/textResourceEditorInput.ts b/src/vs/workbench/common/editor/textResourceEditorInput.ts index e6d7c4d4a..e55b55679 100644 --- a/src/vs/workbench/common/editor/textResourceEditorInput.ts +++ b/src/vs/workbench/common/editor/textResourceEditorInput.ts @@ -56,7 +56,7 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput implem private updateLabel(): void { // Clear any cached labels from before - this._basename = undefined; + this._name = undefined; this._shortDescription = undefined; this._mediumDescription = undefined; this._longDescription = undefined; @@ -76,17 +76,13 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput implem } } + private _name: string | undefined = undefined; getName(): string { - return this.basename; - } - - private _basename: string | undefined; - private get basename(): string { - if (!this._basename) { - this._basename = this.labelService.getUriBasenameLabel(this._preferredResource); + if (typeof this._name !== 'string') { + this._name = this.labelService.getUriBasenameLabel(this._preferredResource); } - return this._basename; + return this._name; } getDescription(verbosity: Verbosity = Verbosity.MEDIUM): string | undefined { @@ -103,49 +99,55 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput implem private _shortDescription: string | undefined = undefined; private get shortDescription(): string { - if (!this._shortDescription) { + if (typeof this._shortDescription !== 'string') { this._shortDescription = this.labelService.getUriBasenameLabel(dirname(this._preferredResource)); } + return this._shortDescription; } private _mediumDescription: string | undefined = undefined; private get mediumDescription(): string { - if (!this._mediumDescription) { + if (typeof this._mediumDescription !== 'string') { this._mediumDescription = this.labelService.getUriLabel(dirname(this._preferredResource), { relative: true }); } + return this._mediumDescription; } private _longDescription: string | undefined = undefined; private get longDescription(): string { - if (!this._longDescription) { + if (typeof this._longDescription !== 'string') { this._longDescription = this.labelService.getUriLabel(dirname(this._preferredResource)); } + return this._longDescription; } private _shortTitle: string | undefined = undefined; private get shortTitle(): string { - if (!this._shortTitle) { + if (typeof this._shortTitle !== 'string') { this._shortTitle = this.getName(); } + return this._shortTitle; } private _mediumTitle: string | undefined = undefined; private get mediumTitle(): string { - if (!this._mediumTitle) { + if (typeof this._mediumTitle !== 'string') { this._mediumTitle = this.labelService.getUriLabel(this._preferredResource, { relative: true }); } + return this._mediumTitle; } private _longTitle: string | undefined = undefined; private get longTitle(): string { - if (!this._longTitle) { + if (typeof this._longTitle !== 'string') { this._longTitle = this.labelService.getUriLabel(this._preferredResource); } + return this._longTitle; } diff --git a/src/vs/workbench/common/memento.ts b/src/vs/workbench/common/memento.ts index 309b0517b..e9854ff82 100644 --- a/src/vs/workbench/common/memento.ts +++ b/src/vs/workbench/common/memento.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { isEmptyObject } from 'vs/base/common/types'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -22,13 +22,13 @@ export class Memento { this.id = Memento.COMMON_PREFIX + id; } - getMemento(scope: StorageScope): MementoObject { + getMemento(scope: StorageScope, target: StorageTarget): MementoObject { // Scope by Workspace if (scope === StorageScope.WORKSPACE) { let workspaceMemento = Memento.workspaceMementos.get(this.id); if (!workspaceMemento) { - workspaceMemento = new ScopedMemento(this.id, scope, this.storageService); + workspaceMemento = new ScopedMemento(this.id, scope, target, this.storageService); Memento.workspaceMementos.set(this.id, workspaceMemento); } @@ -38,7 +38,7 @@ export class Memento { // Scope Global let globalMemento = Memento.globalMementos.get(this.id); if (!globalMemento) { - globalMemento = new ScopedMemento(this.id, scope, this.storageService); + globalMemento = new ScopedMemento(this.id, scope, target, this.storageService); Memento.globalMementos.set(this.id, globalMemento); } @@ -65,7 +65,7 @@ class ScopedMemento { private readonly mementoObj: MementoObject; - constructor(private id: string, private scope: StorageScope, private storageService: IStorageService) { + constructor(private id: string, private scope: StorageScope, private target: StorageTarget, private storageService: IStorageService) { this.mementoObj = this.load(); } @@ -92,7 +92,7 @@ class ScopedMemento { save(): void { if (!isEmptyObject(this.mementoObj)) { - this.storageService.store(this.id, JSON.stringify(this.mementoObj), this.scope); + this.storageService.store(this.id, JSON.stringify(this.mementoObj), this.scope, this.target); } else { this.storageService.remove(this.id, this.scope); } diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index b9fb58ac4..e7df71486 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { INotification, INotificationHandle, INotificationActions, INotificationProgress, NoOpNotification, Severity, NotificationMessage, IPromptChoice, IStatusMessageOptions, NotificationsFilter, INotificationProgressProperties } from 'vs/platform/notification/common/notification'; +import { INotification, INotificationHandle, INotificationActions, INotificationProgress, NoOpNotification, Severity, NotificationMessage, IPromptChoice, IStatusMessageOptions, NotificationsFilter, INotificationProgressProperties, IPromptChoiceWithMenu } from 'vs/platform/notification/common/notification'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -695,6 +695,7 @@ export class ChoiceAction extends Action { readonly onDidRun = this._onDidRun.event; private readonly _keepOpen: boolean; + private readonly _menu: ChoiceAction[] | undefined; constructor(id: string, choice: IPromptChoice) { super(id, choice.label, undefined, true, async () => { @@ -707,6 +708,11 @@ export class ChoiceAction extends Action { }); this._keepOpen = !!choice.keepOpen; + this._menu = !choice.isSecondary && (choice).menu ? (choice).menu.map((c, index) => new ChoiceAction(`${id}.${index}`, c)) : undefined; + } + + get menu(): ChoiceAction[] | undefined { + return this._menu; } get keepOpen(): boolean { diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index f7451eda5..53f29efbd 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { registerColor, editorBackground, contrastBorder, transparent, editorWidgetBackground, textLinkForeground, lighten, darken, focusBorder, activeContrastBorder, editorWidgetForeground, editorErrorForeground, editorWarningForeground, editorInfoForeground, treeIndentGuidesStroke } from 'vs/platform/theme/common/colorRegistry'; +import { registerColor, editorBackground, contrastBorder, transparent, editorWidgetBackground, textLinkForeground, lighten, darken, focusBorder, activeContrastBorder, editorWidgetForeground, editorErrorForeground, editorWarningForeground, editorInfoForeground, treeIndentGuidesStroke, errorForeground } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; @@ -401,6 +401,18 @@ export const STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND = registerColor('statusB hc: Color.black.transparent(0.3), }, nls.localize('statusBarProminentItemHoverBackground', "Status bar prominent items background color when hovering. Prominent items stand out from other status bar entries to indicate importance. Change mode `Toggle Tab Key Moves Focus` from command palette to see an example. The status bar is shown in the bottom of the window.")); +export const STATUS_BAR_ERROR_ITEM_BACKGROUND = registerColor('statusBarItem.errorBackground', { + dark: darken(errorForeground, .4), + light: darken(errorForeground, .4), + hc: null, +}, nls.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, + hc: Color.white, +}, nls.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.")); + // < --- Activity Bar --- > export const ACTIVITY_BAR_BACKGROUND = registerColor('activityBar.background', { diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 24f43b1d7..0744cecf6 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -25,8 +25,13 @@ import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { mixin } from 'vs/base/common/objects'; +import { Codicon } from 'vs/base/common/codicons'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test'; +export const testViewIcon = registerIcon('test-view-icon', Codicon.beaker, localize('testViewIcon', 'View icon of the test view.')); + +export const defaultViewIcon = registerIcon('default-view-icon', Codicon.window, localize('defaultViewIcon', 'Default view icon.')); export namespace Extensions { export const ViewContainersRegistry = 'workbench.registry.view.containers'; @@ -48,7 +53,7 @@ export interface IViewContainerDescriptor { readonly storageId?: string; - readonly icon?: string | URI; + readonly icon?: ThemeIcon | URI; readonly alwaysUseContainerInfo?: boolean; @@ -216,7 +221,7 @@ export interface IViewDescriptor { readonly canMoveView?: boolean; - readonly containerIcon?: string | URI; + readonly containerIcon?: ThemeIcon | URI; readonly containerTitle?: string; @@ -252,7 +257,7 @@ export interface IAddedViewDescriptorState { export interface IViewContainerModel { readonly title: string; - readonly icon: string | URI | undefined; + readonly icon: ThemeIcon | URI | undefined; readonly onDidChangeContainerInfo: Event<{ title?: boolean, icon?: boolean }>; readonly allViewDescriptors: ReadonlyArray; @@ -290,11 +295,7 @@ export interface IViewContentDescriptor { readonly when?: ContextKeyExpression | 'default'; readonly group?: string; readonly order?: number; - - /** - * ordered preconditions for each button in the content - */ - readonly preconditions?: (ContextKeyExpression | undefined)[]; + readonly precondition?: ContextKeyExpression | undefined; } export interface IViewsRegistry { @@ -703,6 +704,27 @@ export class ResolvableTreeItem implements ITreeItem { get hasResolve(): boolean { return this._hasResolve; } + public resetResolve() { + this.resolved = false; + } + public asTreeItem(): ITreeItem { + return { + handle: this.handle, + parentHandle: this.parentHandle, + collapsibleState: this.collapsibleState, + label: this.label, + description: this.description, + icon: this.icon, + iconDark: this.iconDark, + themeIcon: this.themeIcon, + resourceUri: this.resourceUri, + tooltip: this.tooltip, + contextValue: this.contextValue, + command: this.command, + children: this.children, + accessibilityInformation: this.accessibilityInformation + }; + } } export interface ITreeViewDataProvider { diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts index 68c8751e0..dfe8325aa 100644 --- a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts @@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { DefaultEndOfLine } from 'vs/editor/common/model'; -import { hashPath } from 'vs/workbench/services/backup/node/backupFileService'; +import { hashPath } from 'vs/workbench/services/backup/electron-browser/backupFileService'; import { NativeBackupTracker } from 'vs/workbench/contrib/backup/electron-sandbox/backupTracker'; import { workbenchInstantiationService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts index f2e134c7c..efb89fafb 100644 --- a/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts @@ -10,7 +10,7 @@ import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; -import { hashPath } from 'vs/workbench/services/backup/node/backupFileService'; +import { hashPath } from 'vs/workbench/services/backup/electron-browser/backupFileService'; import { NativeBackupTracker } from 'vs/workbench/contrib/backup/electron-sandbox/backupTracker'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -33,7 +33,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { HotExitConfiguration } from 'vs/platform/files/common/files'; import { ShutdownReason, ILifecycleService, BeforeShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IFileDialogService, ConfirmResult, IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IWorkspaceContextService, Workspace } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker'; import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/electron-browser/workbenchTestServices'; @@ -48,6 +48,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { TestWorkingCopy } from 'vs/workbench/test/common/workbenchTestServices'; import { CancellationToken } from 'vs/base/common/cancellation'; import { timeout } from 'vs/base/common/async'; +import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; const userdataDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backuprestorer'); const backupHome = path.join(userdataDir, 'Backups'); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts index 96b5e0e7e..714e45927 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts @@ -4,15 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import { groupBy } from 'vs/base/common/arrays'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { compare } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; import { WorkspaceEditMetadata } from 'vs/editor/common/modes'; import { IProgress } from 'vs/platform/progress/common/progress'; -import { UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo'; +import { UndoRedoGroup, UndoRedoSource } from 'vs/platform/undoRedo/common/undoRedo'; import { ICellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; -import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; export class ResourceNotebookCellEdit extends ResourceEdit { @@ -29,10 +29,11 @@ export class ResourceNotebookCellEdit extends ResourceEdit { export class BulkCellEdits { constructor( - private _undoRedoGroup: UndoRedoGroup, + private readonly _undoRedoGroup: UndoRedoGroup, + undoRedoSource: UndoRedoSource | undefined, private readonly _progress: IProgress, + private readonly _token: CancellationToken, private readonly _edits: ResourceNotebookCellEdit[], - @INotebookService private readonly _notebookService: INotebookService, @INotebookEditorModelResolverService private readonly _notebookModelService: INotebookEditorModelResolverService, ) { } @@ -41,6 +42,9 @@ export class BulkCellEdits { const editsByNotebook = groupBy(this._edits, (a, b) => compare(a.resource.toString(), b.resource.toString())); for (let group of editsByNotebook) { + if (this._token.isCancellationRequested) { + break; + } const [first] = group; const ref = await this._notebookModelService.resolve(first.resource); @@ -52,7 +56,6 @@ export class BulkCellEdits { // apply edits const edits = group.map(entry => entry.cellEdit); - this._notebookService.transformEditsOutputs(ref.object.notebook, edits); ref.object.notebook.applyEdits(ref.object.notebook.versionId, edits, true, undefined, () => undefined, this._undoRedoGroup); ref.dispose(); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts index defc9283a..ffc54667e 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts @@ -16,7 +16,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { BulkTextEdits } from 'vs/workbench/contrib/bulkEdit/browser/bulkTextEdits'; import { BulkFileEdits } from 'vs/workbench/contrib/bulkEdit/browser/bulkFileEdits'; import { BulkCellEdits, ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits'; -import { UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo'; +import { UndoRedoGroup, UndoRedoSource } from 'vs/platform/undoRedo/common/undoRedo'; +import { LinkedList } from 'vs/base/common/linkedList'; +import { CancellationToken } from 'vs/base/common/cancellation'; class BulkEdit { @@ -24,7 +26,10 @@ class BulkEdit { private readonly _label: string | undefined, private readonly _editor: ICodeEditor | undefined, private readonly _progress: IProgress, + private readonly _token: CancellationToken, private readonly _edits: ResourceEdit[], + private readonly _undoRedoGroup: UndoRedoGroup, + private readonly _undoRedoSource: UndoRedoSource | undefined, @IInstantiationService private readonly _instaService: IInstantiationService, @ILogService private readonly _logService: ILogService, ) { @@ -58,20 +63,24 @@ class BulkEdit { } } - this._progress.report({ total: this._edits.length }); - const progress: IProgress = { report: _ => this._progress.report({ increment: 1 }) }; - - const undoRedoGroup = new UndoRedoGroup(); + // Show infinte progress when there is only 1 item since we do not know how long it takes + const increment = this._edits.length > 1 ? 0 : undefined; + this._progress.report({ increment, total: 100 }); + // Increment by percentage points since progress API expects that + const progress: IProgress = { report: _ => this._progress.report({ increment: 100 / this._edits.length }) }; let index = 0; for (let range of ranges) { + if (this._token.isCancellationRequested) { + break; + } const group = this._edits.slice(index, index + range); if (group[0] instanceof ResourceFileEdit) { - await this._performFileEdits(group, undoRedoGroup, progress); + await this._performFileEdits(group, this._undoRedoGroup, this._undoRedoSource, progress); } else if (group[0] instanceof ResourceTextEdit) { - await this._performTextEdits(group, undoRedoGroup, progress); + await this._performTextEdits(group, this._undoRedoGroup, this._undoRedoSource, progress); } else if (group[0] instanceof ResourceNotebookCellEdit) { - await this._performCellEdits(group, undoRedoGroup, progress); + await this._performCellEdits(group, this._undoRedoGroup, this._undoRedoSource, progress); } else { console.log('UNKNOWN EDIT'); } @@ -79,21 +88,21 @@ class BulkEdit { } } - private async _performFileEdits(edits: ResourceFileEdit[], undoRedoGroup: UndoRedoGroup, progress: IProgress) { + private async _performFileEdits(edits: ResourceFileEdit[], undoRedoGroup: UndoRedoGroup, undoRedoSource: UndoRedoSource | undefined, progress: IProgress) { this._logService.debug('_performFileEdits', JSON.stringify(edits)); - const model = this._instaService.createInstance(BulkFileEdits, this._label || localize('workspaceEdit', "Workspace Edit"), undoRedoGroup, progress, edits); + const model = this._instaService.createInstance(BulkFileEdits, this._label || localize('workspaceEdit', "Workspace Edit"), undoRedoGroup, undoRedoSource, progress, this._token, edits); await model.apply(); } - private async _performTextEdits(edits: ResourceTextEdit[], undoRedoGroup: UndoRedoGroup, progress: IProgress): Promise { + private async _performTextEdits(edits: ResourceTextEdit[], undoRedoGroup: UndoRedoGroup, undoRedoSource: UndoRedoSource | undefined, progress: IProgress): Promise { this._logService.debug('_performTextEdits', JSON.stringify(edits)); - const model = this._instaService.createInstance(BulkTextEdits, this._label || localize('workspaceEdit', "Workspace Edit"), this._editor, undoRedoGroup, progress, edits); + const model = this._instaService.createInstance(BulkTextEdits, this._label || localize('workspaceEdit', "Workspace Edit"), this._editor, undoRedoGroup, undoRedoSource, progress, this._token, edits); await model.apply(); } - private async _performCellEdits(edits: ResourceNotebookCellEdit[], undoRedoGroup: UndoRedoGroup, progress: IProgress): Promise { + private async _performCellEdits(edits: ResourceNotebookCellEdit[], undoRedoGroup: UndoRedoGroup, undoRedoSource: UndoRedoSource | undefined, progress: IProgress): Promise { this._logService.debug('_performCellEdits', JSON.stringify(edits)); - const model = this._instaService.createInstance(BulkCellEdits, undoRedoGroup, progress, edits); + const model = this._instaService.createInstance(BulkCellEdits, undoRedoGroup, undoRedoSource, progress, this._token, edits); await model.apply(); } } @@ -102,6 +111,7 @@ export class BulkEditService implements IBulkEditService { declare readonly _serviceBrand: undefined; + private readonly _activeUndoRedoGroups = new LinkedList(); private _previewHandler?: IBulkEditPreviewHandler; constructor( @@ -129,7 +139,7 @@ export class BulkEditService implements IBulkEditService { return { ariaSummary: localize('nothing', "Made no edits") }; } - if (this._previewHandler && (options?.showPreview || edits.some(value => value.metadata?.needsConfirmation))) { + if (this._previewHandler && !options?.suppressPreview && (options?.showPreview || edits.some(value => value.metadata?.needsConfirmation))) { edits = await this._previewHandler(edits, options); } @@ -147,11 +157,33 @@ export class BulkEditService implements IBulkEditService { codeEditor = undefined; } + // undo-redo-group: if a group id is passed then try to find it + // in the list of active edits. otherwise (or when not found) + // create a separate undo-redo-group + let undoRedoGroup: UndoRedoGroup | undefined; + let undoRedoGroupRemove = () => { }; + if (typeof options?.undoRedoGroupId === 'number') { + for (let candidate of this._activeUndoRedoGroups) { + if (candidate.id === options.undoRedoGroupId) { + undoRedoGroup = candidate; + break; + } + } + } + if (!undoRedoGroup) { + undoRedoGroup = new UndoRedoGroup(); + undoRedoGroupRemove = this._activeUndoRedoGroups.push(undoRedoGroup); + } + const bulkEdit = this._instaService.createInstance( BulkEdit, options?.quotableLabel || options?.label, - codeEditor, options?.progress ?? Progress.None, - edits + codeEditor, + options?.progress ?? Progress.None, + options?.token ?? CancellationToken.None, + edits, + undoRedoGroup, + options?.undoRedoSource ); try { @@ -162,6 +194,8 @@ export class BulkEditService implements IBulkEditService { // console.log(err); this._logService.error(err); throw err; + } finally { + undoRedoGroupRemove(); } } } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts index b58430484..f4ac30ca0 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts @@ -5,21 +5,27 @@ import { WorkspaceFileEditOptions } from 'vs/editor/common/modes'; -import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; +import { IFileService, FileSystemProviderCapabilities, IFileContent } from 'vs/platform/files/common/files'; import { IProgress } from 'vs/platform/progress/common/progress'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; -import { IWorkspaceUndoRedoElement, UndoRedoElementType, IUndoRedoService, UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo'; +import { IWorkspaceUndoRedoElement, UndoRedoElementType, IUndoRedoService, UndoRedoGroup, UndoRedoSource } from 'vs/platform/undoRedo/common/undoRedo'; import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { VSBuffer } from 'vs/base/common/buffer'; import { ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService'; import * as resources from 'vs/base/common/resources'; +import { CancellationToken } from 'vs/base/common/cancellation'; + +interface IFileOperationUndoRedoInfo { + undoRedoGroupId?: number; + isUndoing?: boolean; +} interface IFileOperation { uris: URI[]; - perform(): Promise; + perform(token: CancellationToken): Promise; } class Noop implements IFileOperation { @@ -36,6 +42,7 @@ class RenameOperation implements IFileOperation { readonly newUri: URI, readonly oldUri: URI, readonly options: WorkspaceFileEditOptions, + readonly undoRedoInfo: IFileOperationUndoRedoInfo, @IWorkingCopyFileService private readonly _workingCopyFileService: IWorkingCopyFileService, @IFileService private readonly _fileService: IFileService, ) { } @@ -44,14 +51,14 @@ class RenameOperation implements IFileOperation { return [this.newUri, this.oldUri]; } - async perform(): Promise { + async perform(token: CancellationToken): Promise { // rename if (this.options.overwrite === undefined && this.options.ignoreIfExists && await this._fileService.exists(this.newUri)) { return new Noop(); // not overwriting, but ignoring, and the target file exists } - await this._workingCopyFileService.move([{ source: this.oldUri, target: this.newUri }], { overwrite: this.options.overwrite }); - return new RenameOperation(this.oldUri, this.newUri, this.options, this._workingCopyFileService, this._fileService); + await this._workingCopyFileService.move([{ source: this.oldUri, target: this.newUri }], { overwrite: this.options.overwrite, ...this.undoRedoInfo }, token); + return new RenameOperation(this.oldUri, this.newUri, this.options, { isUndoing: true }, this._workingCopyFileService, this._fileService); } toString(): string { @@ -64,11 +71,44 @@ class RenameOperation implements IFileOperation { } } +class CopyOperation implements IFileOperation { + + constructor( + readonly newUri: URI, + readonly oldUri: URI, + readonly options: WorkspaceFileEditOptions, + readonly undoRedoInfo: IFileOperationUndoRedoInfo, + @IWorkingCopyFileService private readonly _workingCopyFileService: IWorkingCopyFileService, + @IFileService private readonly _fileService: IFileService, + @IInstantiationService private readonly _instaService: IInstantiationService + ) { } + + get uris() { + return [this.newUri, this.oldUri]; + } + + async perform(token: CancellationToken): Promise { + // copy + if (this.options.overwrite === undefined && this.options.ignoreIfExists && await this._fileService.exists(this.newUri)) { + return new Noop(); // not overwriting, but ignoring, and the target file exists + } + + const stat = await this._workingCopyFileService.copy([{ source: this.oldUri, target: this.newUri }], { overwrite: this.options.overwrite, ...this.undoRedoInfo }, token); + const folder = this.options.folder || (stat.length === 1 && stat[0].isDirectory); + return this._instaService.createInstance(DeleteOperation, this.newUri, { recursive: true, folder, ...this.options }, { isUndoing: true }, false); + } + + toString(): string { + return `(copy ${this.oldUri} to ${this.newUri})`; + } +} + class CreateOperation implements IFileOperation { constructor( readonly newUri: URI, readonly options: WorkspaceFileEditOptions, + readonly undoRedoInfo: IFileOperationUndoRedoInfo, readonly contents: VSBuffer | undefined, @IFileService private readonly _fileService: IFileService, @IWorkingCopyFileService private readonly _workingCopyFileService: IWorkingCopyFileService, @@ -79,17 +119,22 @@ class CreateOperation implements IFileOperation { return [this.newUri]; } - async perform(): Promise { + async perform(token: CancellationToken): Promise { // create file if (this.options.overwrite === undefined && this.options.ignoreIfExists && await this._fileService.exists(this.newUri)) { return new Noop(); // not overwriting, but ignoring, and the target file exists } - await this._workingCopyFileService.create(this.newUri, this.contents, { overwrite: this.options.overwrite }); - return this._instaService.createInstance(DeleteOperation, this.newUri, this.options, true); + if (this.options.folder) { + await this._workingCopyFileService.createFolder(this.newUri, { ...this.undoRedoInfo }, token); + } else { + await this._workingCopyFileService.create(this.newUri, this.contents, { overwrite: this.options.overwrite, ...this.undoRedoInfo }, token); + } + return this._instaService.createInstance(DeleteOperation, this.newUri, this.options, { isUndoing: true }, !this.options.folder && !this.contents); } toString(): string { - return `(create ${resources.basename(this.newUri)} with ${this.contents?.byteLength || 0} bytes)`; + return this.options.folder ? `create ${resources.basename(this.newUri)} folder` + : `(create ${resources.basename(this.newUri)} with ${this.contents?.byteLength || 0} bytes)`; } } @@ -98,6 +143,7 @@ class DeleteOperation implements IFileOperation { constructor( readonly oldUri: URI, readonly options: WorkspaceFileEditOptions, + readonly undoRedoInfo: IFileOperationUndoRedoInfo, private readonly _undoesCreateOperation: boolean, @IWorkingCopyFileService private readonly _workingCopyFileService: IWorkingCopyFileService, @IFileService private readonly _fileService: IFileService, @@ -110,7 +156,7 @@ class DeleteOperation implements IFileOperation { return [this.oldUri]; } - async perform(): Promise { + async perform(token: CancellationToken): Promise { // delete file if (!await this._fileService.exists(this.oldUri)) { if (!this.options.ignoreIfNotExists) { @@ -119,18 +165,22 @@ class DeleteOperation implements IFileOperation { return new Noop(); } - let contents: VSBuffer | undefined; - if (!this._undoesCreateOperation) { + let fileContent: IFileContent | undefined; + if (!this._undoesCreateOperation && !this.options.folder) { try { - contents = (await this._fileService.readFile(this.oldUri)).value; + fileContent = await this._fileService.readFile(this.oldUri); } catch (err) { this._logService.critical(err); } } - const useTrash = this._fileService.hasCapability(this.oldUri, FileSystemProviderCapabilities.Trash) && this._configurationService.getValue('files.enableTrash'); - await this._workingCopyFileService.delete([this.oldUri], { useTrash, recursive: this.options.recursive }); - return this._instaService.createInstance(CreateOperation, this.oldUri, this.options, contents); + const useTrash = !this.options.skipTrashBin && this._fileService.hasCapability(this.oldUri, FileSystemProviderCapabilities.Trash) && this._configurationService.getValue('files.enableTrash'); + await this._workingCopyFileService.delete([this.oldUri], { useTrash, recursive: this.options.recursive, ...this.undoRedoInfo }, token); + + if (typeof this.options.maxSize === 'number' && fileContent && (fileContent?.size > this.options.maxSize)) { + return new Noop(); + } + return this._instaService.createInstance(CreateOperation, this.oldUri, this.options, { isUndoing: true }, fileContent?.value); } toString(): string { @@ -162,7 +212,7 @@ class FileUndoRedoElement implements IWorkspaceUndoRedoElement { private async _reverse() { for (let i = 0; i < this.operations.length; i++) { const op = this.operations[i]; - const undo = await op.perform(); + const undo = await op.perform(CancellationToken.None); this.operations[i] = undo; } } @@ -177,7 +227,9 @@ export class BulkFileEdits { constructor( private readonly _label: string, private readonly _undoRedoGroup: UndoRedoGroup, + private readonly _undoRedoSource: UndoRedoSource | undefined, private readonly _progress: IProgress, + private readonly _token: CancellationToken, private readonly _edits: ResourceFileEdit[], @IInstantiationService private readonly _instaService: IInstantiationService, @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, @@ -185,27 +237,35 @@ export class BulkFileEdits { async apply(): Promise { const undoOperations: IFileOperation[] = []; + const undoRedoInfo = { undoRedoGroupId: this._undoRedoGroup.id }; for (const edit of this._edits) { - this._progress.report(undefined); + + if (this._token.isCancellationRequested) { + break; + } const options = edit.options || {}; let op: IFileOperation | undefined; - if (edit.newResource && edit.oldResource) { + if (edit.newResource && edit.oldResource && !options.copy) { // rename - op = this._instaService.createInstance(RenameOperation, edit.newResource, edit.oldResource, options); + op = this._instaService.createInstance(RenameOperation, edit.newResource, edit.oldResource, options, undoRedoInfo); + } else if (edit.newResource && edit.oldResource && options.copy) { + op = this._instaService.createInstance(CopyOperation, edit.newResource, edit.oldResource, options, undoRedoInfo); } else if (!edit.newResource && edit.oldResource) { // delete file - op = this._instaService.createInstance(DeleteOperation, edit.oldResource, options, false); + op = this._instaService.createInstance(DeleteOperation, edit.oldResource, options, undoRedoInfo, false); } else if (edit.newResource && !edit.oldResource) { // create file - op = this._instaService.createInstance(CreateOperation, edit.newResource, options, undefined); + op = this._instaService.createInstance(CreateOperation, edit.newResource, options, undoRedoInfo, undefined); } if (op) { - const undoOp = await op.perform(); + const undoOp = await op.perform(this._token); undoOperations.push(undoOp); } + + this._progress.report(undefined); } - this._undoRedoService.pushElement(new FileUndoRedoElement(this._label, undoOperations), this._undoRedoGroup); + this._undoRedoService.pushElement(new FileUndoRedoElement(this._label, undoOperations), this._undoRedoGroup, this._undoRedoSource); } } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts index 533654d98..852da6043 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts @@ -14,11 +14,12 @@ import { EndOfLineSequence, IIdentifiedSingleEditOperation, ITextModel } from 'v import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { IProgress } from 'vs/platform/progress/common/progress'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; -import { IUndoRedoService, UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo'; +import { IUndoRedoService, UndoRedoGroup, UndoRedoSource } from 'vs/platform/undoRedo/common/undoRedo'; import { SingleModelEditStackElement, MultiModelEditStackElement } from 'vs/editor/common/model/editStack'; import { ResourceMap } from 'vs/base/common/map'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; +import { CancellationToken } from 'vs/base/common/cancellation'; type ValidationResult = { canApply: true } | { canApply: false, reason: URI }; @@ -39,6 +40,18 @@ class ModelEditTask implements IDisposable { this._modelReference.dispose(); } + isNoOp() { + if (this._edits.length > 0) { + // contains textual edits + return false; + } + if (this._newEol !== undefined && this._newEol !== this.model.getEndOfLineSequence()) { + // contains an eol change that is a real change + return false; + } + return true; + } + addEdit(resourceEdit: ResourceTextEdit): void { this._expectedModelVersionId = resourceEdit.versionId; const { textEdit } = resourceEdit; @@ -122,7 +135,9 @@ export class BulkTextEdits { private readonly _label: string, private readonly _editor: ICodeEditor | undefined, private readonly _undoRedoGroup: UndoRedoGroup, + private readonly _undoRedoSource: UndoRedoSource | undefined, private readonly _progress: IProgress, + private readonly _token: CancellationToken, edits: ResourceTextEdit[], @IEditorWorkerService private readonly _editorWorker: IEditorWorkerService, @IModelService private readonly _modelService: IModelService, @@ -210,6 +225,9 @@ export class BulkTextEdits { this._validateBeforePrepare(); const tasks = await this._createEditsTasks(); + if (this._token.isCancellationRequested) { + return; + } try { const validation = this._validateTasks(tasks); @@ -219,10 +237,12 @@ export class BulkTextEdits { if (tasks.length === 1) { // This edit touches a single model => keep things simple const task = tasks[0]; - const singleModelEditStackElement = new SingleModelEditStackElement(task.model, task.getBeforeCursorState()); - this._undoRedoService.pushElement(singleModelEditStackElement, this._undoRedoGroup); - task.apply(); - singleModelEditStackElement.close(); + if (!task.isNoOp()) { + const singleModelEditStackElement = new SingleModelEditStackElement(task.model, task.getBeforeCursorState()); + this._undoRedoService.pushElement(singleModelEditStackElement, this._undoRedoGroup, this._undoRedoSource); + task.apply(); + singleModelEditStackElement.close(); + } this._progress.report(undefined); } else { // prepare multi model undo element @@ -230,7 +250,7 @@ export class BulkTextEdits { this._label, tasks.map(t => new SingleModelEditStackElement(t.model, t.getBeforeCursorState())) ); - this._undoRedoService.pushElement(multiModelEditStackElement, this._undoRedoGroup); + this._undoRedoService.pushElement(multiModelEditStackElement, this._undoRedoGroup, this._undoRedoSource); for (const task of tasks) { task.apply(); this._progress.report(undefined); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts index 139ee6ddb..7a757cf09 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts @@ -28,6 +28,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; import { Codicon } from 'vs/base/common/codicons'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; async function getBulkEditPane(viewsService: IViewsService): Promise { const view = await viewsService.openView(BulkEditPane.ID, true); @@ -169,7 +170,7 @@ registerAction2(class ApplyAction extends Action2 { id: 'refactorPreview.apply', title: { value: localize('apply', "Apply Refactoring"), original: 'Apply Refactoring' }, category: { value: localize('cat', "Refactor Preview"), original: 'Refactor Preview' }, - icon: { id: 'codicon/check' }, + icon: Codicon.check, precondition: ContextKeyExpr.and(BulkEditPreviewContribution.ctxEnabled, BulkEditPane.ctxHasCheckedChanges), menu: [{ id: MenuId.BulkEditTitle, @@ -203,7 +204,7 @@ registerAction2(class DiscardAction extends Action2 { id: 'refactorPreview.discard', title: { value: localize('Discard', "Discard Refactoring"), original: 'Discard Refactoring' }, category: { value: localize('cat', "Refactor Preview"), original: 'Refactor Preview' }, - icon: { id: 'codicon/clear-all' }, + icon: Codicon.clearAll, precondition: BulkEditPreviewContribution.ctxEnabled, menu: [{ id: MenuId.BulkEditTitle, @@ -264,7 +265,7 @@ registerAction2(class GroupByFile extends Action2 { id: 'refactorPreview.groupByFile', title: { value: localize('groupByFile', "Group Changes By File"), original: 'Group Changes By File' }, category: { value: localize('cat', "Refactor Preview"), original: 'Refactor Preview' }, - icon: { id: 'codicon/ungroup-by-ref-type' }, + icon: Codicon.ungroupByRefType, precondition: ContextKeyExpr.and(BulkEditPane.ctxHasCategories, BulkEditPane.ctxGroupByFile.negate(), BulkEditPreviewContribution.ctxEnabled), menu: [{ id: MenuId.BulkEditTitle, @@ -291,7 +292,7 @@ registerAction2(class GroupByType extends Action2 { id: 'refactorPreview.groupByType', title: { value: localize('groupByType', "Group Changes By Type"), original: 'Group Changes By Type' }, category: { value: localize('cat', "Refactor Preview"), original: 'Refactor Preview' }, - icon: { id: 'codicon/group-by-ref-type' }, + icon: Codicon.groupByRefType, precondition: ContextKeyExpr.and(BulkEditPane.ctxHasCategories, BulkEditPane.ctxGroupByFile, BulkEditPreviewContribution.ctxEnabled), menu: [{ id: MenuId.BulkEditTitle, @@ -318,7 +319,7 @@ registerAction2(class ToggleGrouping extends Action2 { id: 'refactorPreview.toggleGrouping', title: { value: localize('groupByType', "Group Changes By Type"), original: 'Group Changes By Type' }, category: { value: localize('cat', "Refactor Preview"), original: 'Refactor Preview' }, - icon: { id: 'codicon/list-tree' }, + icon: Codicon.listTree, toggled: BulkEditPane.ctxGroupByFile.negate(), precondition: ContextKeyExpr.and(BulkEditPane.ctxHasCategories, BulkEditPreviewContribution.ctxEnabled), menu: [{ @@ -341,6 +342,8 @@ Registry.as(WorkbenchExtensions.Workbench).regi BulkEditPreviewContribution, LifecyclePhase.Ready ); +const refactorPreviewViewIcon = registerIcon('refactor-preview-view-icon', Codicon.lightbulb, localize('refactorPreviewViewIcon', 'View icon of the refactor preview view.')); + const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: BulkEditPane.ID, name: localize('panel', "Refactor Preview"), @@ -349,7 +352,7 @@ const container = Registry.as(ViewContainerExtensions.V ViewPaneContainer, [BulkEditPane.ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }] ), - icon: Codicon.lightbulb.classNames, + icon: refactorPreviewViewIcon, storageId: BulkEditPane.ID }, ViewContainerLocation.Panel); @@ -358,5 +361,5 @@ Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews name: localize('panel', "Refactor Preview"), when: BulkEditPreviewContribution.ctxEnabled, ctorDescriptor: new SyncDescriptor(BulkEditPane), - containerIcon: Codicon.lightbulb.classNames, + containerIcon: refactorPreviewViewIcon, }], container); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index f36f5f0e9..ab0172eec 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -12,7 +12,7 @@ import { registerThemingParticipant, IColorTheme, ICssStyleCollector, IThemeServ import { diffInserted, diffRemoved } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { BulkEditPreviewProvider, 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'; @@ -26,7 +26,7 @@ import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewl import { ResourceLabels, IResourceLabelsContainer } from 'vs/workbench/browser/labels'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; -import { basename } from 'vs/base/common/resources'; +import { basename, dirname } from 'vs/base/common/resources'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IAction } from 'vs/base/common/actions'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -34,7 +34,7 @@ import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import type { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -128,7 +128,7 @@ export class BulkEditPane extends ViewPane { this._tree = >this._instaService.createInstance( WorkbenchAsyncDataTree, this.id, treeContainer, new BulkEditDelegate(), - [new TextEditElementRenderer(), this._instaService.createInstance(FileElementRenderer, resourceLabels), new CategoryElementRenderer()], + [this._instaService.createInstance(TextEditElementRenderer), this._instaService.createInstance(FileElementRenderer, resourceLabels), this._instaService.createInstance(CategoryElementRenderer)], this._treeDataSource, { accessibilityProvider: this._instaService.createInstance(BulkEditAccessibilityProvider), @@ -293,12 +293,12 @@ export class BulkEditPane extends ViewPane { this._setTreeInput(input); // (3) remember preference - this._storageService.store(BulkEditPane._memGroupByFile, this._treeDataSource.groupByFile, StorageScope.GLOBAL); + this._storageService.store(BulkEditPane._memGroupByFile, this._treeDataSource.groupByFile, StorageScope.GLOBAL, StorageTarget.USER); this._ctxGroupByFile.set(this._treeDataSource.groupByFile); } } - private async _openElementAsEditor(e: IOpenEvent): Promise { + private async _openElementAsEditor(e: IOpenEvent): Promise { type Mutable = { -readonly [P in keyof T]: T[P] }; @@ -356,8 +356,9 @@ export class BulkEditPane extends ViewPane { leftResource, rightResource: previewUri, label, + description: this._labelService.getUriLabel(dirname(leftResource), { relative: true }), options - }); + }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts index 4aecbf746..a1b366c48 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts @@ -22,6 +22,7 @@ import { ResourceMap } from 'vs/base/common/map'; import { localize } from 'vs/nls'; import { extUri } from 'vs/base/common/resources'; import { ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; +import { Codicon } from 'vs/base/common/codicons'; export class CheckedStates { @@ -116,7 +117,7 @@ export class BulkCategory { private static readonly _defaultMetadata = Object.freeze({ label: localize('default', "Other"), - icon: { id: 'codicon/symbol-file' }, + icon: Codicon.symbolFile, needsConfirmation: false }); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts index 4db2acf28..e9dddb2a2 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts @@ -21,7 +21,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import type { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { basename } from 'vs/base/common/resources'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { compare } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; @@ -386,6 +386,8 @@ export class CategoryElementRenderer implements ITreeRenderer, _index: number, template: TextEditElementTemplate): void { diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts index 32c4c2f8b..85c2711cb 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts @@ -18,12 +18,13 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { PeekContext } from 'vs/editor/contrib/peekView/peekView'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { Range } from 'vs/editor/common/core/range'; import { IPosition } from 'vs/editor/common/core/position'; import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; -import { registerIcon, Codicon } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; const _ctxHasCallHierarchyProvider = new RawContextKey('editorHasCallHierarchyProvider', false); const _ctxCallHierarchyVisible = new RawContextKey('callHierarchyVisible', false); @@ -128,7 +129,7 @@ class CallHierarchyController implements IEditorContribution { this._widget.showLoading(); this._sessionDisposables.add(this._widget.onDidClose(() => { this.endCallHierarchy(); - this._storageService.store(CallHierarchyController._StorageDirection, this._widget!.direction, StorageScope.GLOBAL); + this._storageService.store(CallHierarchyController._StorageDirection, this._widget!.direction, StorageScope.GLOBAL, StorageTarget.USER); })); this._sessionDisposables.add({ dispose() { cts.dispose(true); } }); this._sessionDisposables.add(this._widget); @@ -207,7 +208,7 @@ registerAction2(class extends EditorAction2 { super({ id: 'editor.showIncomingCalls', title: { value: localize('title.incoming', "Show Incoming Calls"), original: 'Show Incoming Calls' }, - icon: registerIcon('callhierarchy-incoming', Codicon.callIncoming), + icon: registerIcon('callhierarchy-incoming', Codicon.callIncoming, localize('showIncomingCallsIcons', 'Icon for incoming calls in the call hierarchy view.')), precondition: ContextKeyExpr.and(_ctxCallHierarchyVisible, _ctxCallHierarchyDirection.isEqualTo(CallHierarchyDirection.CallsFrom)), keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -232,7 +233,7 @@ registerAction2(class extends EditorAction2 { super({ id: 'editor.showOutgoingCalls', title: { value: localize('title.outgoing', "Show Outgoing Calls"), original: 'Show Outgoing Calls' }, - icon: registerIcon('callhierarchy-outgoing', Codicon.callOutgoing), + icon: registerIcon('callhierarchy-outgoing', Codicon.callOutgoing, localize('showOutgoingCallsIcon', 'Icon for outgoing calls in the call hierarchy view.')), precondition: ContextKeyExpr.and(_ctxCallHierarchyVisible, _ctxCallHierarchyDirection.isEqualTo(CallHierarchyDirection.CallsTo)), keybinding: { weight: KeybindingWeight.WorkbenchContrib, diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index b6866acaa..6fcd9f8ab 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -28,7 +28,7 @@ import { registerThemingParticipant, themeColorFromId, IThemeService, IColorThem import { IPosition } from 'vs/editor/common/core/position'; import { IAction } from 'vs/base/common/actions'; import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { Color } from 'vs/base/common/color'; import { TreeMouseEventTarget, ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { URI } from 'vs/base/common/uri'; @@ -45,7 +45,7 @@ const enum State { class LayoutInfo { static store(info: LayoutInfo, storageService: IStorageService): void { - storageService.store('callHierarchyPeekLayout', JSON.stringify(info), StorageScope.GLOBAL); + storageService.store('callHierarchyPeekLayout', JSON.stringify(info), StorageScope.GLOBAL, StorageTarget.MACHINE); } static retrieve(storageService: IStorageService): LayoutInfo { @@ -277,7 +277,7 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { this.dispose(); this._editorService.openEditor({ resource: e.element.item.uri, - options: { selection: e.element.item.selectionRange } + options: { selection: e.element.item.selectionRange, pinned: true } }); } })); @@ -289,7 +289,7 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { this.dispose(); this._editorService.openEditor({ resource: element.item.uri, - options: { selection: element.item.selectionRange } + options: { selection: element.item.selectionRange, pinned: true } }); } })); diff --git a/src/vs/workbench/contrib/cli/node/cli.contribution.ts b/src/vs/workbench/contrib/cli/node/cli.contribution.ts index 2000693fd..30972a411 100644 --- a/src/vs/workbench/contrib/cli/node/cli.contribution.ts +++ b/src/vs/workbench/contrib/cli/node/cli.contribution.ts @@ -8,12 +8,8 @@ import * as path from 'vs/base/common/path'; import * as cp from 'child_process'; import * as pfs from 'vs/base/node/pfs'; import * as extpath from 'vs/base/node/extpath'; -import * as platform from 'vs/base/common/platform'; import { promisify } from 'util'; -import { Action } from 'vs/base/common/actions'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import product from 'vs/platform/product/common/product'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -21,6 +17,9 @@ import Severity from 'vs/base/common/severity'; import { ILogService } from 'vs/platform/log/common/log'; import { FileAccess } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IsMacNativeContext } from 'vs/platform/contextkey/common/contextkeys'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; function ignore(code: string, value: T): (err: any) => Promise { return err => err.code === code ? Promise.resolve(value) : Promise.reject(err); @@ -39,45 +38,65 @@ function isAvailable(): Promise { return Promise.resolve(pfs.exists(getSource())); } -class InstallAction extends Action { +const category = nls.localize('shellCommand', "Shell Command"); - static readonly ID = 'workbench.action.installCommandLine'; - static readonly LABEL = nls.localize('install', "Install '{0}' command in PATH", product.applicationName); +class InstallAction extends Action2 { - constructor( - id: string, - label: string, - @INotificationService private readonly notificationService: INotificationService, - @IDialogService private readonly dialogService: IDialogService, - @ILogService private readonly logService: ILogService, - @IProductService private readonly productService: IProductService - ) { - super(id, label); + constructor() { + super({ + id: 'workbench.action.installCommandLine', + title: { + value: nls.localize('install', "Install '{0}' command in PATH", product.applicationName), + original: `Shell Command: Install \'${product.applicationName}\' command in PATH` + }, + category, + f1: true, + precondition: ContextKeyExpr.and(IsMacNativeContext, ContextKeyExpr.equals('remoteName', '')) + }); } - private get target(): string { - return `/usr/local/bin/${this.productService.applicationName}`; - } + run(accessor: ServicesAccessor): Promise { + const productService = accessor.get(IProductService); + const notificationService = accessor.get(INotificationService); + const logService = accessor.get(ILogService); + const dialogService = accessor.get(IDialogService); + const target = `/usr/local/bin/${productService.applicationName}`; - run(): Promise { return isAvailable().then(isAvailable => { if (!isAvailable) { const message = nls.localize('not available', "This command is not available"); - this.notificationService.info(message); + notificationService.info(message); return undefined; } - return this.isInstalled() + return this.isInstalled(target) .then(isInstalled => { if (!isAvailable || isInstalled) { return Promise.resolve(null); } else { - return pfs.unlink(this.target) + return pfs.unlink(target) .then(undefined, ignore('ENOENT', null)) - .then(() => pfs.symlink(getSource(), this.target)) + .then(() => pfs.symlink(getSource(), target)) .then(undefined, err => { if (err.code === 'EACCES' || err.code === 'ENOENT') { - return this.createBinFolderAndSymlinkAsAdmin(); + return new Promise((resolve, reject) => { + const buttons = [nls.localize('ok', "OK"), nls.localize('cancel2', "Cancel")]; + + dialogService.show(Severity.Info, nls.localize('warnEscalation', "Code will now prompt with 'osascript' for Administrator privileges to install the shell command."), buttons, { cancelId: 1 }).then(result => { + switch (result.choice) { + case 0 /* OK */: + const command = 'osascript -e "do shell script \\"mkdir -p /usr/local/bin && ln -sf \'' + getSource() + '\' \'' + target + '\'\\" with administrator privileges"'; + + promisify(cp.exec)(command, {}) + .then(undefined, _ => Promise.reject(new Error(nls.localize('cantCreateBinFolder', "Unable to create '/usr/local/bin'.")))) + .then(() => resolve(), reject); + break; + case 1 /* Cancel */: + reject(new Error(nls.localize('aborted', "Aborted"))); + break; + } + }); + }); } return Promise.reject(err); @@ -85,113 +104,84 @@ class InstallAction extends Action { } }) .then(() => { - this.logService.trace('cli#install', this.target); - this.notificationService.info(nls.localize('successIn', "Shell command '{0}' successfully installed in PATH.", this.productService.applicationName)); + logService.trace('cli#install', target); + notificationService.info(nls.localize('successIn', "Shell command '{0}' successfully installed in PATH.", productService.applicationName)); }); }); } - private isInstalled(): Promise { - return pfs.lstat(this.target) + private isInstalled(target: string): Promise { + return pfs.lstat(target) .then(stat => stat.isSymbolicLink()) - .then(() => extpath.realpath(this.target)) + .then(() => extpath.realpath(target)) .then(link => link === getSource()) .then(undefined, ignore('ENOENT', false)); } - - private createBinFolderAndSymlinkAsAdmin(): Promise { - return new Promise((resolve, reject) => { - const buttons = [nls.localize('ok', "OK"), nls.localize('cancel2', "Cancel")]; - - this.dialogService.show(Severity.Info, nls.localize('warnEscalation', "Code will now prompt with 'osascript' for Administrator privileges to install the shell command."), buttons, { cancelId: 1 }).then(result => { - switch (result.choice) { - case 0 /* OK */: - const command = 'osascript -e "do shell script \\"mkdir -p /usr/local/bin && ln -sf \'' + getSource() + '\' \'' + this.target + '\'\\" with administrator privileges"'; - - promisify(cp.exec)(command, {}) - .then(undefined, _ => Promise.reject(new Error(nls.localize('cantCreateBinFolder', "Unable to create '/usr/local/bin'.")))) - .then(() => resolve(), reject); - break; - case 1 /* Cancel */: - reject(new Error(nls.localize('aborted', "Aborted"))); - break; - } - }); - }); - } } -class UninstallAction extends Action { +class UninstallAction extends Action2 { - static readonly ID = 'workbench.action.uninstallCommandLine'; - static readonly LABEL = nls.localize('uninstall', "Uninstall '{0}' command from PATH", product.applicationName); - - constructor( - id: string, - label: string, - @INotificationService private readonly notificationService: INotificationService, - @ILogService private readonly logService: ILogService, - @IDialogService private readonly dialogService: IDialogService, - @IProductService private readonly productService: IProductService - ) { - super(id, label); + constructor() { + super({ + id: 'workbench.action.uninstallCommandLine', + title: { + value: nls.localize('uninstall', "Uninstall '{0}' command from PATH", product.applicationName), + original: `Shell Command: Uninstall \'${product.applicationName}\' command from PATH` + }, + category, + f1: true, + precondition: ContextKeyExpr.and(IsMacNativeContext, ContextKeyExpr.equals('remoteName', '')) + }); } - private get target(): string { - return `/usr/local/bin/${this.productService.applicationName}`; - } + run(accessor: ServicesAccessor): Promise { + const productService = accessor.get(IProductService); + const notificationService = accessor.get(INotificationService); + const logService = accessor.get(ILogService); + const dialogService = accessor.get(IDialogService); + const target = `/usr/local/bin/${productService.applicationName}`; - run(): Promise { return isAvailable().then(isAvailable => { if (!isAvailable) { const message = nls.localize('not available', "This command is not available"); - this.notificationService.info(message); + notificationService.info(message); return undefined; } const uninstall = () => { - return pfs.unlink(this.target) + return pfs.unlink(target) .then(undefined, ignore('ENOENT', null)); }; return uninstall().then(undefined, err => { if (err.code === 'EACCES') { - return this.deleteSymlinkAsAdmin(); + return new Promise(async (resolve, reject) => { + const buttons = [nls.localize('ok', "OK"), nls.localize('cancel2', "Cancel")]; + + const { choice } = await dialogService.show(Severity.Info, nls.localize('warnEscalationUninstall', "Code will now prompt with 'osascript' for Administrator privileges to uninstall the shell command."), buttons, { cancelId: 1 }); + switch (choice) { + case 0 /* OK */: + const command = 'osascript -e "do shell script \\"rm \'' + target + '\'\\" with administrator privileges"'; + + promisify(cp.exec)(command, {}) + .then(undefined, _ => Promise.reject(new Error(nls.localize('cantUninstall', "Unable to uninstall the shell command '{0}'.", target)))) + .then(() => resolve(), reject); + break; + case 1 /* Cancel */: + reject(new Error(nls.localize('aborted', "Aborted"))); + break; + } + }); } return Promise.reject(err); }).then(() => { - this.logService.trace('cli#uninstall', this.target); - this.notificationService.info(nls.localize('successFrom', "Shell command '{0}' successfully uninstalled from PATH.", this.productService.applicationName)); + logService.trace('cli#uninstall', target); + notificationService.info(nls.localize('successFrom', "Shell command '{0}' successfully uninstalled from PATH.", productService.applicationName)); }); }); } - - private deleteSymlinkAsAdmin(): Promise { - return new Promise(async (resolve, reject) => { - const buttons = [nls.localize('ok', "OK"), nls.localize('cancel2', "Cancel")]; - - const { choice } = await this.dialogService.show(Severity.Info, nls.localize('warnEscalationUninstall', "Code will now prompt with 'osascript' for Administrator privileges to uninstall the shell command."), buttons, { cancelId: 1 }); - switch (choice) { - case 0 /* OK */: - const command = 'osascript -e "do shell script \\"rm \'' + this.target + '\'\\" with administrator privileges"'; - - promisify(cp.exec)(command, {}) - .then(undefined, _ => Promise.reject(new Error(nls.localize('cantUninstall', "Unable to uninstall the shell command '{0}'.", this.target)))) - .then(() => resolve(), reject); - break; - case 1 /* Cancel */: - reject(new Error(nls.localize('aborted', "Aborted"))); - break; - } - }); - } } -if (platform.isMacintosh) { - const category = nls.localize('shellCommand', "Shell Command"); - - const workbenchActionsRegistry = Registry.as(ActionExtensions.WorkbenchActions); - workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(InstallAction), `Shell Command: Install \'${product.applicationName}\' command in PATH`, category); - workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(UninstallAction), `Shell Command: Uninstall \'${product.applicationName}\' command from PATH`, category); -} +registerAction2(InstallAction); +registerAction2(UninstallAction); diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index 1779568eb..87cbcc7d5 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -21,7 +21,7 @@ import { IEditorOptions, EditorOption } from 'vs/editor/common/config/editorOpti import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ToggleTabFocusModeAction } from 'vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode'; -import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -120,7 +120,7 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { if (e.equals(KeyMod.CtrlCmd | KeyCode.KEY_E)) { alert(nls.localize('emergencyConfOn', "Now changing the setting `editor.accessibilitySupport` to 'on'.")); - this._configurationService.updateValue('editor.accessibilitySupport', 'on', ConfigurationTarget.USER); + this._configurationService.updateValue('editor.accessibilitySupport', 'on'); e.preventDefault(); e.stopPropagation(); diff --git a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts index d01c06d5e..c3ede411a 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -50,7 +50,7 @@ class DiffEditorHelperContribution extends Disposable implements IDiffEditorCont [{ label: nls.localize('removeTimeout', "Remove limit"), run: () => { - this._configurationService.updateValue('diffEditor.maxComputationTime', 0, ConfigurationTarget.USER); + this._configurationService.updateValue('diffEditor.maxComputationTime', 0); } }], {} diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts index 9b8cf7a37..cb7b1e97b 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts @@ -12,7 +12,7 @@ import { Delayer } from 'vs/base/common/async'; import { KeyCode } from 'vs/base/common/keyCodes'; import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/findState'; import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; -import { SimpleButton, findCloseIcon, findNextMatchIcon, findPreviousMatchIcon, findReplaceIcon, findReplaceAllIcon } from 'vs/editor/contrib/find/findWidget'; +import { SimpleButton, findNextMatchIcon, findPreviousMatchIcon, findReplaceIcon, findReplaceAllIcon } from 'vs/editor/contrib/find/findWidget'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry'; @@ -21,6 +21,7 @@ import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/b import { ReplaceInput, IReplaceInputStyles } from 'vs/base/browser/ui/findinput/replaceInput'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; +import { widgetClose } from 'vs/platform/theme/common/iconRegistry'; const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find"); @@ -146,7 +147,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { this.prevBtn = this._register(new SimpleButton({ label: NLS_PREVIOUS_MATCH_BTN_LABEL, - className: findPreviousMatchIcon.classNames, + icon: findPreviousMatchIcon, onTrigger: () => { this.find(true); } @@ -154,7 +155,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { this.nextBtn = this._register(new SimpleButton({ label: NLS_NEXT_MATCH_BTN_LABEL, - className: findNextMatchIcon.classNames, + icon: findNextMatchIcon, onTrigger: () => { this.find(false); } @@ -162,7 +163,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { const closeBtn = this._register(new SimpleButton({ label: NLS_CLOSE_BTN_LABEL, - className: findCloseIcon.classNames, + icon: widgetClose, onTrigger: () => { this.hide(); } @@ -220,7 +221,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { this._replaceBtn = this._register(new SimpleButton({ label: NLS_REPLACE_BTN_LABEL, - className: findReplaceIcon.classNames, + icon: findReplaceIcon, onTrigger: () => { this.replaceOne(); } @@ -229,7 +230,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { // Replace all button this._replaceAllBtn = this._register(new SimpleButton({ label: NLS_REPLACE_ALL_BTN_LABEL, - className: findReplaceAllIcon.classNames, + icon: findReplaceAllIcon, onTrigger: () => { this.replaceAll(); } @@ -452,6 +453,6 @@ registerThemingParticipant((theme, collector) => { const widgetShadowColor = theme.getColor(widgetShadow); if (widgetShadowColor) { - collector.addRule(`.monaco-workbench .simple-fr-find-part-wrapper { box-shadow: 0 2px 8px ${widgetShadowColor}; }`); + collector.addRule(`.monaco-workbench .simple-fr-find-part-wrapper { box-shadow: 0 0 8px 2px ${widgetShadowColor}; }`); } }); diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts index e15e3ee78..6226e8fb8 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts @@ -12,12 +12,13 @@ import { Delayer } from 'vs/base/common/async'; import { KeyCode } from 'vs/base/common/keyCodes'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; -import { SimpleButton, findPreviousMatchIcon, findNextMatchIcon, findCloseIcon } from 'vs/editor/contrib/find/findWidget'; +import { SimpleButton, findPreviousMatchIcon, findNextMatchIcon } from 'vs/editor/contrib/find/findWidget'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ContextScopedFindInput } from 'vs/platform/browser/contextScopedHistoryWidget'; +import { widgetClose } from 'vs/platform/theme/common/iconRegistry'; const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find"); @@ -94,7 +95,7 @@ export abstract class SimpleFindWidget extends Widget { this.prevBtn = this._register(new SimpleButton({ label: NLS_PREVIOUS_MATCH_BTN_LABEL, - className: findPreviousMatchIcon.classNames, + icon: findPreviousMatchIcon, onTrigger: () => { this.find(true); } @@ -102,7 +103,7 @@ export abstract class SimpleFindWidget extends Widget { this.nextBtn = this._register(new SimpleButton({ label: NLS_NEXT_MATCH_BTN_LABEL, - className: findNextMatchIcon.classNames, + icon: findNextMatchIcon, onTrigger: () => { this.find(false); } @@ -110,7 +111,7 @@ export abstract class SimpleFindWidget extends Widget { const closeBtn = this._register(new SimpleButton({ label: NLS_CLOSE_BTN_LABEL, - className: findCloseIcon.classNames, + icon: widgetClose, onTrigger: () => { this.hide(); } @@ -285,6 +286,6 @@ registerThemingParticipant((theme, collector) => { const widgetShadowColor = theme.getColor(widgetShadow); if (widgetShadowColor) { - collector.addRule(`.monaco-workbench .simple-find-part { box-shadow: 0 2px 8px ${widgetShadowColor}; }`); + collector.addRule(`.monaco-workbench .simple-find-part { box-shadow: 0 0 8px 2px ${widgetShadowColor}; }`); } }); diff --git a/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts b/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts index 8268057af..f7a7ea98d 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts @@ -11,7 +11,6 @@ import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; /** * Shows a message when opening a large file which has been memory optimized (and features disabled). @@ -24,14 +23,9 @@ export class LargeFileOptimizationsWarner extends Disposable implements IEditorC private readonly _editor: ICodeEditor, @INotificationService private readonly _notificationService: INotificationService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { super(); - // opt-in to syncing - const neverShowAgainId = 'editor.contrib.largeFileOptimizationsWarner'; - storageKeysSyncRegistryService.registerStorageKey({ key: neverShowAgainId, version: 1 }); - this._register(this._editor.onDidChangeModel((e) => { const model = this._editor.getModel(); if (!model) { @@ -61,7 +55,7 @@ export class LargeFileOptimizationsWarner extends Disposable implements IEditorC }); } } - ], { neverShowAgain: { id: neverShowAgainId } }); + ], { neverShowAgain: { id: 'editor.contrib.largeFileOptimizationsWarner' } }); } })); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts index c05ec1cfa..eb4566740 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts @@ -5,7 +5,6 @@ import { localize } from 'vs/nls'; import { IKeyMods, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { IEditor } from 'vs/editor/common/editorCommon'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IRange } from 'vs/editor/common/core/range'; import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoLineQuickAccess'; @@ -17,6 +16,7 @@ import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IQuickAccessTextEditorContext } from 'vs/editor/contrib/quickAccess/editorNavigationQuickAccess'; export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProvider { @@ -41,10 +41,12 @@ export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProv return this.editorService.activeTextEditorControl; } - protected gotoLocation(editor: IEditor, options: { range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean, preserveFocus?: boolean }): void { + protected gotoLocation(context: IQuickAccessTextEditorContext, options: { range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean, preserveFocus?: boolean }): void { // Check for sideBySide use if ((options.keyMods.ctrlCmd || options.forceSideBySide) && this.editorService.activeEditor) { + context.restoreViewState?.(); // since we open to the side, restore view state in this editor + this.editorService.openEditor(this.editorService.activeEditor, { selection: options.range, pinned: options.keyMods.alt || this.configuration.openEditorPinned, @@ -54,7 +56,7 @@ export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProv // Otherwise let parent handle it else { - super.gotoLocation(editor, options); + super.gotoLocation(context, options); } } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts index 5f73a423d..bb7642fe7 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts @@ -26,6 +26,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IQuickAccessTextEditorContext } from 'vs/editor/contrib/quickAccess/editorNavigationQuickAccess'; export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider { @@ -42,11 +43,12 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess //#region DocumentSymbols (text editor required) - protected provideWithTextEditor(editor: IEditor, picker: IQuickPick, token: CancellationToken): IDisposable { + protected provideWithTextEditor(context: IQuickAccessTextEditorContext, picker: IQuickPick, token: CancellationToken): IDisposable { if (this.canPickFromTableOfContents()) { return this.doGetTableOfContentsPicks(picker); } - return super.provideWithTextEditor(editor, picker, token); + + return super.provideWithTextEditor(context, picker, token); } private get configuration() { @@ -62,10 +64,12 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess return this.editorService.activeTextEditorControl; } - protected gotoLocation(editor: IEditor, options: { range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean, preserveFocus?: boolean }): void { + protected gotoLocation(context: IQuickAccessTextEditorContext, options: { range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean, preserveFocus?: boolean }): void { // Check for sideBySide use if ((options.keyMods.ctrlCmd || options.forceSideBySide) && this.editorService.activeEditor) { + context.restoreViewState?.(); // since we open to the side, restore view state in this editor + this.editorService.openEditor(this.editorService.activeEditor, { selection: options.range, pinned: options.keyMods.alt || this.configuration.openEditorPinned, @@ -75,7 +79,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess // Otherwise let parent handle it else { - super.gotoLocation(editor, options); + super.gotoLocation(context, options); } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts index f27703545..76ffc0b60 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts @@ -287,8 +287,7 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { ? setting : Object.keys(setting).filter(x => setting[x]); - const codeActionsOnSave = settingItems - .map(x => new CodeActionKind(x)); + const codeActionsOnSave = this.createCodeActionsOnSave(settingItems); if (!Array.isArray(setting)) { codeActionsOnSave.sort((a, b) => { @@ -319,6 +318,15 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { await this.applyOnSaveActions(textEditorModel, codeActionsOnSave, excludedActions, progress, token); } + private createCodeActionsOnSave(settingItems: readonly string[]): CodeActionKind[] { + const kinds = settingItems.map(x => new CodeActionKind(x)); + + // Remove subsets + return kinds.filter(kind => { + return kinds.every(otherKind => otherKind.equals(kind) || !otherKind.contains(kind)); + }); + } + private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: readonly CodeActionKind[], excludes: readonly CodeActionKind[], progress: IProgress, token: CancellationToken): Promise { const getActionProgress = new class implements IProgress { diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts index ef2018bcd..858bf7be6 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; @@ -42,7 +42,7 @@ export class ToggleColumnSelectionAction extends Action { public async run(): Promise { const oldValue = this._configurationService.getValue('editor.columnSelection'); const codeEditor = this._getCodeEditor(); - await this._configurationService.updateValue('editor.columnSelection', !oldValue, ConfigurationTarget.USER); + await this._configurationService.updateValue('editor.columnSelection', !oldValue); const newValue = this._configurationService.getValue('editor.columnSelection'); if (!codeEditor || codeEditor !== this._getCodeEditor() || oldValue === newValue || !codeEditor.hasModel()) { return; diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts index d40ee948f..fbd79e000 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; import { CATEGORIES, Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; @@ -25,7 +25,7 @@ export class ToggleMinimapAction extends Action { public run(): Promise { const newValue = !this._configurationService.getValue('editor.minimap.enabled'); - return this._configurationService.updateValue('editor.minimap.enabled', newValue, ConfigurationTarget.USER); + return this._configurationService.updateValue('editor.minimap.enabled', newValue); } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts index 2c340ac02..36a63179b 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import * as platform from 'vs/base/common/platform'; import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +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'; @@ -33,7 +33,7 @@ export class ToggleMultiCursorModifierAction extends Action { const editorConf = this.configurationService.getValue<{ multiCursorModifier: 'ctrlCmd' | 'alt' }>('editor'); const newValue: 'ctrlCmd' | 'alt' = (editorConf.multiCursorModifier === 'ctrlCmd' ? 'alt' : 'ctrlCmd'); - return this.configurationService.updateValue(ToggleMultiCursorModifierAction.multiCursorModifierConfigurationKey, newValue, ConfigurationTarget.USER); + return this.configurationService.updateValue(ToggleMultiCursorModifierAction.multiCursorModifierConfigurationKey, newValue); } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts index cc439ce45..d530f1a87 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; import { CATEGORIES, Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; @@ -26,7 +26,7 @@ export class ToggleRenderControlCharacterAction extends Action { public run(): Promise { let newRenderControlCharacters = !this._configurationService.getValue('editor.renderControlCharacters'); - return this._configurationService.updateValue('editor.renderControlCharacters', newRenderControlCharacters, ConfigurationTarget.USER); + return this._configurationService.updateValue('editor.renderControlCharacters', newRenderControlCharacters); } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts index 80a26aee4..5a5174b89 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; import { CATEGORIES, Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; @@ -34,7 +34,7 @@ export class ToggleRenderWhitespaceAction extends Action { newRenderWhitespace = 'none'; } - return this._configurationService.updateValue('editor.renderWhitespace', newRenderWhitespace, ConfigurationTarget.USER); + return this._configurationService.updateValue('editor.renderWhitespace', newRenderWhitespace); } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index 17acd2da4..a0df024be 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts @@ -10,33 +10,25 @@ import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { EditorOption, EditorOptions } from 'vs/editor/common/config/editorOptions'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { INotificationService } from 'vs/platform/notification/common/notification'; import { DefaultSettingsEditorContribution } from 'vs/workbench/contrib/preferences/browser/preferencesEditor'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { Codicon } from 'vs/base/common/codicons'; const transientWordWrapState = 'transientWordWrapState'; const isWordWrapMinifiedKey = 'isWordWrapMinified'; const isDominatedByLongLinesKey = 'isDominatedByLongLines'; -const inDiffEditorKey = 'inDiffEditor'; /** * State written/read by the toggle word wrap action and associated with a particular model. */ interface IWordWrapTransientState { - readonly forceWordWrap: 'on' | 'off' | 'wordWrapColumn' | 'bounded'; - readonly forceWordWrapMinified: boolean; -} - -interface IWordWrapState { - readonly configuredWordWrap: 'on' | 'off' | 'wordWrapColumn' | 'bounded' | undefined; - readonly configuredWordWrapMinified: boolean; - readonly transientState: IWordWrapTransientState | null; + readonly wordWrapOverride: 'on' | 'off'; } /** @@ -49,70 +41,10 @@ export function writeTransientState(model: ITextModel, state: IWordWrapTransient /** * Read (in memory) the word wrap state for a particular model. */ -function readTransientState(model: ITextModel, codeEditorService: ICodeEditorService): IWordWrapTransientState { +function readTransientState(model: ITextModel, codeEditorService: ICodeEditorService): IWordWrapTransientState | null { return codeEditorService.getTransientModelProperty(model, transientWordWrapState); } -function readWordWrapState(model: ITextModel, configurationService: ITextResourceConfigurationService, codeEditorService: ICodeEditorService): IWordWrapState { - const editorConfig = configurationService.getValue(model.uri, 'editor') as { wordWrap: 'on' | 'off' | 'wordWrapColumn' | 'bounded'; wordWrapMinified: boolean }; - let _configuredWordWrap = editorConfig && (typeof editorConfig.wordWrap === 'string' || typeof editorConfig.wordWrap === 'boolean') ? editorConfig.wordWrap : undefined; - - // Compatibility with old true or false values - if (_configuredWordWrap === true) { - _configuredWordWrap = 'on'; - } else if (_configuredWordWrap === false) { - _configuredWordWrap = 'off'; - } - - const _configuredWordWrapMinified = editorConfig && typeof editorConfig.wordWrapMinified === 'boolean' ? editorConfig.wordWrapMinified : undefined; - const _transientState = readTransientState(model, codeEditorService); - return { - configuredWordWrap: _configuredWordWrap, - configuredWordWrapMinified: (typeof _configuredWordWrapMinified === 'boolean' ? _configuredWordWrapMinified : EditorOptions.wordWrapMinified.defaultValue), - transientState: _transientState - }; -} - -function toggleWordWrap(editor: ICodeEditor, state: IWordWrapState): IWordWrapState { - if (state.transientState) { - // toggle off => go to null - return { - configuredWordWrap: state.configuredWordWrap, - configuredWordWrapMinified: state.configuredWordWrapMinified, - transientState: null - }; - } - - let transientState: IWordWrapTransientState; - - const actualWrappingInfo = editor.getOption(EditorOption.wrappingInfo); - if (actualWrappingInfo.isWordWrapMinified) { - // => wrapping due to minified file - transientState = { - forceWordWrap: 'off', - forceWordWrapMinified: false - }; - } else if (state.configuredWordWrap !== 'off') { - // => wrapping is configured to be on (or some variant) - transientState = { - forceWordWrap: 'off', - forceWordWrapMinified: false - }; - } else { - // => wrapping is configured to be off - transientState = { - forceWordWrap: 'on', - forceWordWrapMinified: state.configuredWordWrapMinified - }; - } - - return { - configuredWordWrap: state.configuredWordWrap, - configuredWordWrapMinified: state.configuredWordWrapMinified, - transientState: transientState - }; -} - const TOGGLE_WORD_WRAP_ID = 'editor.action.toggleWordWrap'; class ToggleWordWrapAction extends EditorAction { @@ -138,14 +70,7 @@ class ToggleWordWrapAction extends EditorAction { if (!editor.hasModel()) { return; } - if (editor.getOption(EditorOption.inDiffEditor)) { - // Cannot change wrapping settings inside the diff editor - const notificationService = accessor.get(INotificationService); - notificationService.info(nls.localize('wordWrap.notInDiffEditor', "Cannot toggle word wrap in a diff editor.")); - return; - } - const textResourceConfigurationService = accessor.get(ITextResourceConfigurationService); const codeEditorService = accessor.get(ICodeEditorService); const model = editor.getModel(); @@ -154,12 +79,21 @@ class ToggleWordWrapAction extends EditorAction { } // Read the current state - const currentState = readWordWrapState(model, textResourceConfigurationService, codeEditorService); + const transientState = readTransientState(model, codeEditorService); + // Compute the new state - const newState = toggleWordWrap(editor, currentState); + let newState: IWordWrapTransientState | null; + if (transientState) { + newState = null; + } else { + const actualWrappingInfo = editor.getOption(EditorOption.wrappingInfo); + const wordWrapOverride = (actualWrappingInfo.wrappingColumn === -1 ? 'on' : 'off'); + newState = { wordWrapOverride }; + } + // Write the new state // (this will cause an event and the controller will apply the state) - writeTransientState(model, newState.transientState, codeEditorService); + writeTransientState(model, newState, codeEditorService); } } @@ -168,93 +102,75 @@ class ToggleWordWrapController extends Disposable implements IEditorContribution public static readonly ID = 'editor.contrib.toggleWordWrapController'; constructor( - private readonly editor: ICodeEditor, - @IContextKeyService readonly contextKeyService: IContextKeyService, - @ITextResourceConfigurationService readonly configurationService: ITextResourceConfigurationService, - @ICodeEditorService readonly codeEditorService: ICodeEditorService + private readonly _editor: ICodeEditor, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @ICodeEditorService private readonly _codeEditorService: ICodeEditorService ) { super(); - const options = this.editor.getOptions(); + const options = this._editor.getOptions(); const wrappingInfo = options.get(EditorOption.wrappingInfo); - const isWordWrapMinified = this.contextKeyService.createKey(isWordWrapMinifiedKey, wrappingInfo.isWordWrapMinified); - const isDominatedByLongLines = this.contextKeyService.createKey(isDominatedByLongLinesKey, wrappingInfo.isDominatedByLongLines); - const inDiffEditor = this.contextKeyService.createKey(inDiffEditorKey, options.get(EditorOption.inDiffEditor)); + const isWordWrapMinified = this._contextKeyService.createKey(isWordWrapMinifiedKey, wrappingInfo.isWordWrapMinified); + const isDominatedByLongLines = this._contextKeyService.createKey(isDominatedByLongLinesKey, wrappingInfo.isDominatedByLongLines); let currentlyApplyingEditorConfig = false; - this._register(editor.onDidChangeConfiguration((e) => { - if (!e.hasChanged(EditorOption.wrappingInfo) && !e.hasChanged(EditorOption.inDiffEditor)) { + this._register(_editor.onDidChangeConfiguration((e) => { + if (!e.hasChanged(EditorOption.wrappingInfo)) { return; } - const options = this.editor.getOptions(); + const options = this._editor.getOptions(); const wrappingInfo = options.get(EditorOption.wrappingInfo); isWordWrapMinified.set(wrappingInfo.isWordWrapMinified); isDominatedByLongLines.set(wrappingInfo.isDominatedByLongLines); - inDiffEditor.set(options.get(EditorOption.inDiffEditor)); if (!currentlyApplyingEditorConfig) { // I am not the cause of the word wrap getting changed ensureWordWrapSettings(); } })); - this._register(editor.onDidChangeModel((e) => { + this._register(_editor.onDidChangeModel((e) => { ensureWordWrapSettings(); })); - this._register(codeEditorService.onDidChangeTransientModelProperty(() => { + this._register(_codeEditorService.onDidChangeTransientModelProperty(() => { ensureWordWrapSettings(); })); const ensureWordWrapSettings = () => { - if (this.editor.getContribution(DefaultSettingsEditorContribution.ID)) { + if (this._editor.getContribution(DefaultSettingsEditorContribution.ID)) { // in the settings editor... return; } - if (this.editor.isSimpleWidget) { + if (this._editor.isSimpleWidget) { // in a simple widget... return; } // Ensure correct word wrap settings - const newModel = this.editor.getModel(); + const newModel = this._editor.getModel(); if (!newModel) { return; } - if (this.editor.getOption(EditorOption.inDiffEditor)) { - return; - } - if (!canToggleWordWrap(newModel.uri)) { return; } - // Read current configured values and toggle state - const desiredState = readWordWrapState(newModel, this.configurationService, this.codeEditorService); + const transientState = readTransientState(newModel, this._codeEditorService); // Apply the state try { currentlyApplyingEditorConfig = true; - this._applyWordWrapState(desiredState); + this._applyWordWrapState(transientState); } finally { currentlyApplyingEditorConfig = false; } }; } - private _applyWordWrapState(state: IWordWrapState): void { - if (state.transientState) { - // toggle is on - this.editor.updateOptions({ - wordWrap: state.transientState.forceWordWrap, - wordWrapMinified: state.transientState.forceWordWrapMinified - }); - return; - } - - // toggle is off - this.editor.updateOptions({ - wordWrap: state.configuredWordWrap, - wordWrapMinified: state.configuredWordWrapMinified + private _applyWordWrapState(state: IWordWrapTransientState | null): void { + const wordWrapOverride2 = state ? state.wordWrapOverride : 'inherit'; + this._editor.updateOptions({ + wordWrapOverride2: wordWrapOverride2 }); } } @@ -275,14 +191,11 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_WORD_WRAP_ID, title: nls.localize('unwrapMinified', "Disable wrapping for this file"), - icon: { - id: 'codicon/word-wrap' - } + icon: Codicon.wordWrap }, group: 'navigation', order: 1, when: ContextKeyExpr.and( - ContextKeyExpr.not(inDiffEditorKey), ContextKeyExpr.has(isDominatedByLongLinesKey), ContextKeyExpr.has(isWordWrapMinifiedKey) ) @@ -291,14 +204,12 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_WORD_WRAP_ID, title: nls.localize('wrapMinified', "Enable wrapping for this file"), - icon: { - id: 'codicon/word-wrap' - } + icon: Codicon.wordWrap }, group: 'navigation', order: 1, when: ContextKeyExpr.and( - ContextKeyExpr.not(inDiffEditorKey), + EditorContextKeys.inDiffEditor.negate(), ContextKeyExpr.has(isDominatedByLongLinesKey), ContextKeyExpr.not(isWordWrapMinifiedKey) ) diff --git a/src/vs/workbench/contrib/codeEditor/electron-browser/startDebugTextMate.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/startDebugTextMate.ts index c764c2c42..7718e5056 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-browser/startDebugTextMate.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-browser/startDebugTextMate.ts @@ -70,7 +70,8 @@ class StartDebugTextMate extends Action { }; await this._hostService.openWindow([{ fileUri: URI.file(pathInTemp) }], { forceNewWindow: true }); const textEditorPane = await this._editorService.openEditor({ - resource: model.uri + resource: model.uri, + options: { pinned: true } }); if (!textEditorPane) { return; diff --git a/src/vs/workbench/contrib/codeEditor/electron-sandbox/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/electron-sandbox/codeEditor.contribution.ts index 087637541..d544a687a 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-sandbox/codeEditor.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-sandbox/codeEditor.contribution.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import './displayChangeRemeasureFonts'; import './inputClipboardActions'; import './selectionClipboard'; import './sleepResumeRepaintMinimap'; diff --git a/src/vs/workbench/contrib/codeEditor/electron-sandbox/displayChangeRemeasureFonts.ts b/src/vs/workbench/contrib/codeEditor/electron-sandbox/displayChangeRemeasureFonts.ts new file mode 100644 index 000000000..efcdd8981 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/electron-sandbox/displayChangeRemeasureFonts.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +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 { Disposable } from 'vs/base/common/lifecycle'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IDisplayMainService } from 'vs/platform/display/common/displayMainService'; +import { createChannelSender } from 'vs/base/parts/ipc/common/ipc'; +import { clearAllFontInfos } from 'vs/editor/browser/config/configuration'; + +class DisplayChangeRemeasureFonts extends Disposable implements IWorkbenchContribution { + + constructor( + @IMainProcessService mainProcessService: IMainProcessService + ) { + super(); + const displayMainService = createChannelSender(mainProcessService.getChannel('display')); + displayMainService.onDidDisplayChanged(() => { + clearAllFontInfos(); + }); + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DisplayChangeRemeasureFonts, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 249fd9b10..a059627b0 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -34,6 +34,7 @@ import { CommentFormActions } from 'vs/workbench/contrib/comments/browser/commen import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; +import { Codicon } from 'vs/base/common/codicons'; export class CommentNode extends Disposable { private _domNode: HTMLElement; @@ -156,7 +157,7 @@ export class CommentNode extends Disposable { { actionViewItemProvider: action => this.actionViewItemProvider(action as Action), actionRunner: this.actionRunner, - classNames: ['toolbar-toggle-pickReactions', 'codicon', 'codicon-reactions'], + classNames: ['toolbar-toggle-pickReactions', ...Codicon.reactions.classNamesArray], anchorAlignmentProvider: () => AnchorAlignment.RIGHT } ); diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 9e66d94a6..ffab7e07c 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -6,7 +6,6 @@ import * as dom from 'vs/base/browser/dom'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action, IAction } from 'vs/base/common/actions'; -import * as arrays from 'vs/base/common/arrays'; import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -32,7 +31,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { contrastBorder, editorForeground, focusBorder, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground, transparent } from 'vs/platform/theme/common/colorRegistry'; -import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { CommentFormActions } from 'vs/workbench/contrib/comments/browser/commentFormActions'; import { CommentGlyphWidget } from 'vs/workbench/contrib/comments/browser/commentGlyphWidget'; import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus'; @@ -48,9 +47,14 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { PANEL_BORDER } from 'vs/workbench/common/theme'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; +import { Codicon } from 'vs/base/common/codicons'; + + +const collapseIcon = registerIcon('review-comment-collapse', Codicon.chevronUp, nls.localize('collapseIcon', 'Icon to collapse a review comment.')); export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration'; -const COLLAPSE_ACTION_CLASS = 'expand-review-action codicon-chevron-up'; +const COLLAPSE_ACTION_CLASS = 'expand-review-action ' + ThemeIcon.asClassName(collapseIcon); const COMMENT_SCHEME = 'comment'; @@ -712,10 +716,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget label = this._commentThread.label; if (label === undefined) { - if (this._commentThread.comments && this._commentThread.comments.length) { - const participantsList = this._commentThread.comments.filter(arrays.uniqueFilter(comment => comment.userName)).map(comment => `@${comment.userName}`).join(', '); - label = nls.localize('commentThreadParticipants', "Participants: {0}", participantsList); - } else { + if (!(this._commentThread.comments && this._commentThread.comments.length)) { label = nls.localize('startThread', "Start discussion"); } } @@ -884,13 +885,18 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } private _applyTheme(theme: IColorTheme) { - const borderColor = theme.getColor(peekViewBorder) || Color.transparent; + const borderColor = theme.getColor(peekViewBorder); this.style({ - arrowColor: borderColor, - frameColor: borderColor + arrowColor: borderColor || Color.transparent, + frameColor: borderColor || Color.transparent }); const content: string[] = []; + + if (borderColor) { + content.push(`.monaco-editor .review-widget > .body { border-top: 1px solid ${borderColor} }`); + } + const linkColor = theme.getColor(textLinkForeground); if (linkColor) { content.push(`.monaco-editor .review-widget .body .comment-body a { color: ${linkColor} }`); @@ -945,7 +951,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget const fontInfo = this.editor.getOption(EditorOption.fontInfo); content.push(`.monaco-editor .review-widget .body code { - font-family: ${fontInfo.fontFamily}; + font-family: '${fontInfo.fontFamily}'; font-size: ${fontInfo.fontSize}px; font-weight: ${fontInfo.fontWeight}; }`); diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts index 8d77ef60e..5ed011a2c 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -34,7 +34,6 @@ export class CommentsPanel extends ViewPane { private tree!: CommentsList; private treeContainer!: HTMLElement; private messageBoxContainer!: HTMLElement; - private messageBox!: HTMLElement; private commentsModel!: CommentsModel; private collapseAllAction?: IAction; @@ -85,6 +84,18 @@ export class CommentsPanel extends ViewPane { this.renderComments(); } + public focus(): void { + if (this.tree && this.tree.getHTMLElement() === document.activeElement) { + return; + } + + if (!this.commentsModel.hasCommentThreads() && this.messageBoxContainer) { + this.messageBoxContainer.focus(); + } else if (this.tree) { + this.tree.domFocus(); + } + } + private applyStyles(styleElement: HTMLStyleElement) { const content: string[] = []; @@ -114,8 +125,8 @@ export class CommentsPanel extends ViewPane { private async renderComments(): Promise { this.treeContainer.classList.toggle('hidden', !this.commentsModel.hasCommentThreads()); - await this.tree.setInput(this.commentsModel); this.renderMessage(); + await this.tree.setInput(this.commentsModel); } public getActions(): IAction[] { @@ -138,12 +149,11 @@ export class CommentsPanel extends ViewPane { private createMessageBox(parent: HTMLElement): void { this.messageBoxContainer = dom.append(parent, dom.$('.message-box-container')); - this.messageBox = dom.append(this.messageBoxContainer, dom.$('span')); - this.messageBox.setAttribute('tabindex', '0'); + this.messageBoxContainer.setAttribute('tabIndex', '0'); } private renderMessage(): void { - this.messageBox.textContent = this.commentsModel.getMessage(); + this.messageBoxContainer.textContent = this.commentsModel.getMessage(); this.messageBoxContainer.classList.toggle('hidden', this.commentsModel.hasCommentThreads()); } @@ -231,18 +241,23 @@ export class CommentsPanel extends ViewPane { return true; } - private refresh(): void { + private async refresh(): Promise { if (this.isVisible()) { if (this.collapseAllAction) { this.collapseAllAction.enabled = this.commentsModel.hasCommentThreads(); } this.treeContainer.classList.toggle('hidden', !this.commentsModel.hasCommentThreads()); - this.tree.updateChildren().then(() => { - this.renderMessage(); - }, (e) => { - console.log(e); - }); + this.renderMessage(); + await this.tree.updateChildren(); + + if (this.tree.getSelection().length === 0 && this.commentsModel.hasCommentThreads()) { + const firstComment = this.commentsModel.resourceCommentThreads[0].commentThreads[0]; + if (firstComment) { + this.tree.setFocus([firstComment]); + this.tree.setSelection([firstComment]); + } + } } } diff --git a/src/vs/workbench/contrib/comments/browser/media/panel.css b/src/vs/workbench/contrib/comments/browser/media/panel.css index 1633bd41c..b931b6cdb 100644 --- a/src/vs/workbench/contrib/comments/browser/media/panel.css +++ b/src/vs/workbench/contrib/comments/browser/media/panel.css @@ -48,10 +48,7 @@ .comments-panel .comments-panel-container .message-box-container { line-height: 22px; padding-left: 20px; -} - -.comments-panel .comments-panel-container .message-box-container span:focus { - outline: none; + height: inherit; } .comments-panel .comments-panel-container .tree-container .count-badge-wrapper { @@ -61,4 +58,4 @@ .comments-panel .comments-panel-container .tree-container .comment-container { line-height: 22px; margin-right: 5px; -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/comments/common/commentModel.ts b/src/vs/workbench/contrib/comments/common/commentModel.ts index 0dc8a2390..e56c22677 100644 --- a/src/vs/workbench/contrib/comments/common/commentModel.ts +++ b/src/vs/workbench/contrib/comments/common/commentModel.ts @@ -134,7 +134,7 @@ export class CommentsModel { public getMessage(): string { if (!this.resourceCommentThreads.length) { - return localize('noComments', "There are no comments on this review."); + return localize('noComments', "There are no comments in this workspace yet."); } else { return ''; } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 3bcaaacdb..c57aa6dd3 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { coalesce, distinct } from 'vs/base/common/arrays'; +import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -173,7 +174,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ : undefined, detail: editorDescriptor.providerDisplayName, buttons: resourceExt ? [{ - iconClass: 'codicon-settings-gear', + iconClass: Codicon.settingsGear.classNames, tooltip: nls.localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", resourceExt) }] : undefined })); @@ -447,6 +448,7 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo constructor( @IEditorService private readonly editorService: IEditorService, @ICustomEditorService private readonly customEditorService: ICustomEditorService, + @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); @@ -651,7 +653,7 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo if (modifiedOverride || originalOverride) { return { override: (async () => { - const input = new DiffEditorInput(editor.getName(), editor.getDescription(), originalOverride || editor.originalInput, modifiedOverride || editor.modifiedInput, true); + const input = this.instantiationService.createInstance(DiffEditorInput, editor.getName(), editor.getDescription(), originalOverride || editor.originalInput, modifiedOverride || editor.modifiedInput, true); return this.editorService.openEditor(input, { ...options, override: false }, group); })(), }; diff --git a/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.ts b/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.ts index d61f9874f..98fcdb9b9 100644 --- a/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.ts +++ b/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.ts @@ -8,12 +8,12 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { Memento } from 'vs/workbench/common/memento'; import { CustomEditorDescriptor, CustomEditorInfo, CustomEditorPriority } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { customEditorsExtensionPoint, ICustomEditorsExtensionPoint } from 'vs/workbench/contrib/customEditor/common/extensionPoint'; -import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { DEFAULT_EDITOR_ID } from 'vs/workbench/services/editor/common/editorOpenWith'; +import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; const builtinProviderDisplayName = nls.localize('builtinProviderDisplayName', "Built-in"); @@ -40,7 +40,7 @@ export class ContributedCustomEditors extends Disposable { this._memento = new Memento(ContributedCustomEditors.CUSTOM_EDITORS_STORAGE_ID, storageService); - const mementoObject = this._memento.getMemento(StorageScope.GLOBAL); + const mementoObject = this._memento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); for (const info of (mementoObject[ContributedCustomEditors.CUSTOM_EDITORS_ENTRY_ID] || []) as CustomEditorDescriptor[]) { this.add(new CustomEditorInfo(info)); } @@ -68,7 +68,7 @@ export class ContributedCustomEditors extends Disposable { } } - const mementoObject = this._memento.getMemento(StorageScope.GLOBAL); + const mementoObject = this._memento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); mementoObject[ContributedCustomEditors.CUSTOM_EDITORS_ENTRY_ID] = Array.from(this._editors.values()); this._memento.saveMemento(); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index bd7d28ee0..966dc83e5 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -22,7 +22,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { BreakpointWidget } from 'vs/workbench/contrib/debug/browser/breakpointWidget'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { MarkdownString } from 'vs/base/common/htmlContent'; -import { getBreakpointMessageAndClassName } from 'vs/workbench/contrib/debug/browser/breakpointsView'; +import { getBreakpointMessageAndIcon } from 'vs/workbench/contrib/debug/browser/breakpointsView'; import { generateUuid } from 'vs/base/common/uuid'; import { memoize } from 'vs/base/common/decorators'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -32,9 +32,10 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { BrowserFeatures } from 'vs/base/browser/canIUse'; import { isSafari } from 'vs/base/browser/browser'; -import { registerThemingParticipant, themeColorFromId } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, themeColorFromId, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { ILabelService } from 'vs/platform/label/common/label'; +import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; const $ = dom.$; @@ -46,7 +47,7 @@ interface IBreakpointDecoration { } const breakpointHelperDecoration: IModelDecorationOptions = { - glyphMarginClassName: 'codicon-debug-hint', + glyphMarginClassName: ThemeIcon.asClassName(icons.debugBreakpointHint), stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges }; @@ -72,7 +73,7 @@ export function createBreakpointDecorations(model: ITextModel, breakpoints: Read } function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoint, state: State, breakpointsActivated: boolean, showBreakpointsInOverviewRuler: boolean): IModelDecorationOptions { - const { className, message } = getBreakpointMessageAndClassName(state, breakpointsActivated, breakpoint, undefined); + const { icon, message } = getBreakpointMessageAndIcon(state, breakpointsActivated, breakpoint, undefined); let glyphMarginHoverMessage: MarkdownString | undefined; if (message) { @@ -94,7 +95,7 @@ function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoi const renderInline = breakpoint.column && (breakpoint.column > model.getLineFirstNonWhitespaceColumn(breakpoint.lineNumber)); return { - glyphMarginClassName: `${className}`, + glyphMarginClassName: ThemeIcon.asClassName(icon), glyphMarginHoverMessage, stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, beforeContentClassName: renderInline ? `debug-breakpoint-placeholder` : undefined, @@ -174,7 +175,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi private registerListeners(): void { this.toDispose.push(this.editor.onMouseDown(async (e: IEditorMouseEvent) => { - if (!this.debugService.getConfigurationManager().hasDebuggers()) { + if (!this.debugService.getAdapterManager().hasDebuggers()) { return; } @@ -183,7 +184,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi if (!e.target.position || !model || e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN || data.isAfterLines || !this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) { return; } - const canSetBreakpoints = this.debugService.getConfigurationManager().canSetBreakpointsIn(model); + const canSetBreakpoints = this.debugService.canSetBreakpointsIn(model); const lineNumber = e.target.position.lineNumber; const uri = model.uri; @@ -252,13 +253,13 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi * 2. When users click on line numbers, the breakpoint hint displays immediately, however it doesn't create the breakpoint unless users click on the left gutter. On a touch screen, it's hard to click on that small area. */ this.toDispose.push(this.editor.onMouseMove((e: IEditorMouseEvent) => { - if (!this.debugService.getConfigurationManager().hasDebuggers()) { + if (!this.debugService.getAdapterManager().hasDebuggers()) { return; } let showBreakpointHintAtLineNumber = -1; const model = this.editor.getModel(); - if (model && e.target.position && (e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN || e.target.type === MouseTargetType.GUTTER_LINE_NUMBERS) && this.debugService.getConfigurationManager().canSetBreakpointsIn(model) && + if (model && e.target.position && (e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN || e.target.type === MouseTargetType.GUTTER_LINE_NUMBERS) && this.debugService.canSetBreakpointsIn(model) && this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) { const data = e.target.detail as IMarginData; if (!data.isAfterLines) { @@ -453,9 +454,9 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi // Candidate decoration has a breakpoint attached when a breakpoint is already at that location and we did not yet set a decoration there // In practice this happens for the first breakpoint that was set on a line // We could have also rendered this first decoration as part of desiredBreakpointDecorations however at that moment we have no location information - const cssClass = candidate.breakpoint ? getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), candidate.breakpoint, this.labelService).className : 'codicon-debug-breakpoint-disabled'; + const icon = candidate.breakpoint ? getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), candidate.breakpoint, this.labelService).icon : icons.debugBreakpointDisabled; const contextMenuActions = () => this.getContextMenuActions(candidate.breakpoint ? [candidate.breakpoint] : [], activeCodeEditor.getModel().uri, candidate.range.startLineNumber, candidate.range.startColumn); - const inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, cssClass, candidate.breakpoint, this.debugService, this.contextMenuService, contextMenuActions); + const inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, ThemeIcon.asClassName(icon), candidate.breakpoint, this.debugService, this.contextMenuService, contextMenuActions); return { decorationId, @@ -575,9 +576,8 @@ class InlineBreakpointWidget implements IContentWidget, IDisposable { private create(cssClass: string | null | undefined): void { this.domNode = $('.inline-breakpoint-widget'); - this.domNode.classList.add('codicon'); if (cssClass) { - this.domNode.classList.add(cssClass); + this.domNode.classList.add(...cssClass.split(' ')); } this.toDispose.push(dom.addDisposableListener(this.domNode, dom.EventType.CLICK, async e => { if (this.breakpoint) { @@ -645,15 +645,15 @@ registerThemingParticipant((theme, collector) => { const debugIconBreakpointColor = theme.getColor(debugIconBreakpointForeground); if (debugIconBreakpointColor) { collector.addRule(` - .monaco-workbench .codicon-debug-breakpoint, - .monaco-workbench .codicon-debug-breakpoint-conditional, - .monaco-workbench .codicon-debug-breakpoint-log, - .monaco-workbench .codicon-debug-breakpoint-function, - .monaco-workbench .codicon-debug-breakpoint-data, - .monaco-workbench .codicon-debug-breakpoint-unsupported, - .monaco-workbench .codicon-debug-hint:not([class*='codicon-debug-breakpoint']):not([class*='codicon-debug-stackframe']), - .monaco-workbench .codicon-debug-breakpoint.codicon-debug-stackframe-focused::after, - .monaco-workbench .codicon-debug-breakpoint.codicon-debug-stackframe::after { + .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpoint)}, + .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpointConditional)}, + .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpointLog)}, + .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpointFunction)}, + .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpointData)}, + .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.debugBreakpoint)}${ThemeIcon.asCSSSelector(icons.debugStackframeFocused)}::after, + .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpoint)}${ThemeIcon.asCSSSelector(icons.debugStackframe)}::after { color: ${debugIconBreakpointColor} !important; } `); @@ -680,7 +680,7 @@ registerThemingParticipant((theme, collector) => { const debugIconBreakpointCurrentStackframeForegroundColor = theme.getColor(debugIconBreakpointCurrentStackframeForeground); if (debugIconBreakpointCurrentStackframeForegroundColor) { collector.addRule(` - .monaco-workbench .codicon-debug-stackframe, + .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStackframe)}, .monaco-editor .debug-top-stack-frame-column::before { color: ${debugIconBreakpointCurrentStackframeForegroundColor} !important; } @@ -690,7 +690,7 @@ registerThemingParticipant((theme, collector) => { const debugIconBreakpointStackframeFocusedColor = theme.getColor(debugIconBreakpointStackframeForeground); if (debugIconBreakpointStackframeFocusedColor) { collector.addRule(` - .monaco-workbench .codicon-debug-stackframe-focused { + .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStackframeFocused)} { color: ${debugIconBreakpointStackframeFocusedColor} !important; } `); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 97636b20c..7fa72c43f 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -13,7 +13,7 @@ import { AddFunctionBreakpointAction, ToggleBreakpointsActivatedAction, RemoveAl import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { Constants } from 'vs/base/common/uint'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { IListVirtualDelegate, IListContextMenuEvent, IListRenderer } from 'vs/base/browser/ui/list/list'; @@ -37,6 +37,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Orientation } from 'vs/base/browser/ui/splitview/splitview'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; const $ = dom.$; @@ -93,6 +94,7 @@ export class BreakpointsView extends ViewPane { this.list = >this.instantiationService.createInstance(WorkbenchList, 'Breakpoints', container, delegate, [ this.instantiationService.createInstance(BreakpointsRenderer), new ExceptionBreakpointsRenderer(this.debugService), + new ExceptionBreakpointInputRenderer(this.debugService, this.contextViewService, this.themeService), this.instantiationService.createInstance(FunctionBreakpointsRenderer), this.instantiationService.createInstance(DataBreakpointsRenderer), new FunctionBreakpointInputRenderer(this.debugService, this.contextViewService, this.themeService, this.labelService) @@ -122,7 +124,7 @@ export class BreakpointsView extends ViewPane { const resourceNavigator = this._register(new ListResourceNavigator(this.list, { configurationService: this.configurationService })); this._register(resourceNavigator.onDidOpen(async e => { - if (e.element === null) { + if (!e.element) { return; } @@ -130,14 +132,12 @@ export class BreakpointsView extends ViewPane { return; } - const element = this.list.element(e.element); - - if (element instanceof Breakpoint) { - openBreakpointSource(element, e.sideBySide, e.editorOptions.preserveFocus || false, this.debugService, this.editorService); + if (e.element instanceof Breakpoint) { + openBreakpointSource(e.element, e.sideBySide, e.editorOptions.preserveFocus || false, e.editorOptions.pinned || !e.editorOptions.preserveFocus, this.debugService, this.editorService); } - if (e.browserEvent instanceof MouseEvent && e.browserEvent.detail === 2 && element instanceof FunctionBreakpoint && element !== this.debugService.getViewModel().getSelectedFunctionBreakpoint()) { + if (e.browserEvent instanceof MouseEvent && e.browserEvent.detail === 2 && e.element instanceof FunctionBreakpoint && e.element !== this.debugService.getViewModel().getSelectedBreakpoint()) { // double click - this.debugService.getViewModel().setSelectedFunctionBreakpoint(element); + this.debugService.getViewModel().setSelectedBreakpoint(e.element); this.onBreakpointsChange(); } })); @@ -188,38 +188,48 @@ export class BreakpointsView extends ViewPane { const actions: IAction[] = []; const element = e.element; - const breakpointType = element instanceof Breakpoint && element.logMessage ? nls.localize('Logpoint', "Logpoint") : nls.localize('Breakpoint', "Breakpoint"); - if (element instanceof Breakpoint || element instanceof FunctionBreakpoint) { - actions.push(new Action('workbench.action.debug.openEditorAndEditBreakpoint', nls.localize('editBreakpoint', "Edit {0}...", breakpointType), '', true, async () => { - if (element instanceof Breakpoint) { - const editor = await openBreakpointSource(element, false, false, this.debugService, this.editorService); - if (editor) { - const codeEditor = editor.getControl(); - if (isCodeEditor(codeEditor)) { - codeEditor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(element.lineNumber, element.column); - } - } - } else { - this.debugService.getViewModel().setSelectedFunctionBreakpoint(element); + if (element instanceof ExceptionBreakpoint) { + if (element.supportsCondition) { + actions.push(new Action('workbench.action.debug.editExceptionBreakpointCondition', nls.localize('editCondition', "Edit Condition"), '', true, async () => { + this.debugService.getViewModel().setSelectedBreakpoint(element); this.onBreakpointsChange(); - } - })); + })); + } + } else { + const breakpointType = element instanceof Breakpoint && element.logMessage ? nls.localize('Logpoint', "Logpoint") : nls.localize('Breakpoint', "Breakpoint"); + if (element instanceof Breakpoint || element instanceof FunctionBreakpoint) { + actions.push(new Action('workbench.action.debug.openEditorAndEditBreakpoint', nls.localize('editBreakpoint', "Edit {0}...", breakpointType), '', true, async () => { + if (element instanceof Breakpoint) { + const editor = await openBreakpointSource(element, false, false, true, this.debugService, this.editorService); + if (editor) { + const codeEditor = editor.getControl(); + if (isCodeEditor(codeEditor)) { + codeEditor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(element.lineNumber, element.column); + } + } + } else { + this.debugService.getViewModel().setSelectedBreakpoint(element); + this.onBreakpointsChange(); + } + })); + actions.push(new Separator()); + } + + + actions.push(new RemoveBreakpointAction(RemoveBreakpointAction.ID, nls.localize('removeBreakpoint', "Remove {0}", breakpointType), this.debugService)); + + if (this.debugService.getModel().getBreakpoints().length + this.debugService.getModel().getFunctionBreakpoints().length >= 1) { + actions.push(new RemoveAllBreakpointsAction(RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL, this.debugService, this.keybindingService)); + actions.push(new Separator()); + + actions.push(new EnableAllBreakpointsAction(EnableAllBreakpointsAction.ID, EnableAllBreakpointsAction.LABEL, this.debugService, this.keybindingService)); + actions.push(new DisableAllBreakpointsAction(DisableAllBreakpointsAction.ID, DisableAllBreakpointsAction.LABEL, this.debugService, this.keybindingService)); + } + actions.push(new Separator()); + actions.push(new ReapplyBreakpointsAction(ReapplyBreakpointsAction.ID, ReapplyBreakpointsAction.LABEL, this.debugService, this.keybindingService)); } - actions.push(new RemoveBreakpointAction(RemoveBreakpointAction.ID, nls.localize('removeBreakpoint', "Remove {0}", breakpointType), this.debugService)); - - if (this.debugService.getModel().getBreakpoints().length + this.debugService.getModel().getFunctionBreakpoints().length > 1) { - actions.push(new RemoveAllBreakpointsAction(RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL, this.debugService, this.keybindingService)); - actions.push(new Separator()); - - actions.push(new EnableAllBreakpointsAction(EnableAllBreakpointsAction.ID, EnableAllBreakpointsAction.LABEL, this.debugService, this.keybindingService)); - actions.push(new DisableAllBreakpointsAction(DisableAllBreakpointsAction.ID, DisableAllBreakpointsAction.LABEL, this.debugService, this.keybindingService)); - } - - actions.push(new Separator()); - actions.push(new ReapplyBreakpointsAction(ReapplyBreakpointsAction.ID, ReapplyBreakpointsAction.LABEL, this.debugService, this.keybindingService)); - this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => actions, @@ -285,7 +295,7 @@ class BreakpointsDelegate implements IListVirtualDelegate { return BreakpointsRenderer.ID; } if (element instanceof FunctionBreakpoint) { - const selected = this.debugService.getViewModel().getSelectedFunctionBreakpoint(); + const selected = this.debugService.getViewModel().getSelectedBreakpoint(); if (!element.name || (selected && selected.getId() === element.getId())) { return FunctionBreakpointInputRenderer.ID; } @@ -293,6 +303,10 @@ class BreakpointsDelegate implements IListVirtualDelegate { return FunctionBreakpointsRenderer.ID; } if (element instanceof ExceptionBreakpoint) { + const selected = this.debugService.getViewModel().getSelectedBreakpoint(); + if (selected && selected.getId() === element.getId()) { + return ExceptionBreakpointInputRenderer.ID; + } return ExceptionBreakpointsRenderer.ID; } if (element instanceof DataBreakpoint) { @@ -320,7 +334,11 @@ interface IBreakpointTemplateData extends IBaseBreakpointWithIconTemplateData { filePath: HTMLElement; } -interface IInputTemplateData { +interface IExceptionBreakpointTemplateData extends IBaseBreakpointTemplateData { + condition: HTMLElement; +} + +interface IFunctionBreakpointInputTemplateData { inputBox: InputBox; checkbox: HTMLInputElement; icon: HTMLElement; @@ -329,6 +347,14 @@ interface IInputTemplateData { toDispose: IDisposable[]; } +interface IExceptionBreakpointInputTemplateData { + inputBox: InputBox; + checkbox: HTMLInputElement; + breakpoint: IExceptionBreakpoint; + reactedOnEvent: boolean; + toDispose: IDisposable[]; +} + class BreakpointsRenderer implements IListRenderer { constructor( @@ -379,8 +405,8 @@ class BreakpointsRenderer implements IListRenderer { +class ExceptionBreakpointsRenderer implements IListRenderer { constructor( private debugService: IDebugService @@ -408,8 +434,8 @@ class ExceptionBreakpointsRenderer implements IListRenderer { +class FunctionBreakpointInputRenderer implements IListRenderer { constructor( private debugService: IDebugService, @@ -567,8 +596,8 @@ class FunctionBreakpointInputRenderer implements IListRenderer { if (!template.reactedOnEvent) { template.reactedOnEvent = true; - this.debugService.getViewModel().setSelectedFunctionBreakpoint(undefined); + this.debugService.getViewModel().setSelectedBreakpoint(undefined); if (inputBox.value && (renamed || template.breakpoint.name)) { this.debugService.renameFunctionBreakpoint(template.breakpoint.getId(), renamed ? inputBox.value : template.breakpoint.name); } else { @@ -619,12 +648,12 @@ class FunctionBreakpointInputRenderer implements IListRenderer { + + constructor( + private debugService: IDebugService, + private contextViewService: IContextViewService, + private themeService: IThemeService + ) { + // noop + } + + static readonly ID = 'exceptionbreakpointinput'; + + get templateId() { + return ExceptionBreakpointInputRenderer.ID; + } + + renderTemplate(container: HTMLElement): IExceptionBreakpointInputTemplateData { + const template: IExceptionBreakpointInputTemplateData = Object.create(null); + + const breakpoint = dom.append(container, $('.breakpoint')); + breakpoint.classList.add('exception'); + template.checkbox = createCheckbox(); + + dom.append(breakpoint, template.checkbox); + const inputBoxContainer = dom.append(breakpoint, $('.inputBoxContainer')); + const inputBox = new InputBox(inputBoxContainer, this.contextViewService, { + placeholder: nls.localize('exceptionBreakpointPlaceholder', "Break when expression evaluates to true"), + ariaLabel: nls.localize('exceptionBreakpointAriaLabel', "Type exception breakpoint condition") + }); + const styler = attachInputBoxStyler(inputBox, this.themeService); + const toDispose: IDisposable[] = [inputBox, styler]; + + const wrapUp = (success: boolean) => { + if (!template.reactedOnEvent) { + template.reactedOnEvent = true; + this.debugService.getViewModel().setSelectedBreakpoint(undefined); + let newCondition = template.breakpoint.condition; + if (success) { + newCondition = inputBox.value !== '' ? inputBox.value : undefined; + } + this.debugService.setExceptionBreakpointCondition(template.breakpoint, newCondition); + } + }; + + toDispose.push(dom.addStandardDisposableListener(inputBox.inputElement, 'keydown', (e: IKeyboardEvent) => { + const isEscape = e.equals(KeyCode.Escape); + const isEnter = e.equals(KeyCode.Enter); + if (isEscape || isEnter) { + e.preventDefault(); + e.stopPropagation(); + wrapUp(isEnter); + } + })); + toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'blur', () => { + // Need to react with a timeout on the blur event due to possible concurent splices #56443 + setTimeout(() => { + wrapUp(true); + }); + })); + + template.inputBox = inputBox; + template.toDispose = toDispose; + return template; + } + + renderElement(exceptionBreakpoint: ExceptionBreakpoint, _index: number, data: IExceptionBreakpointInputTemplateData): void { + data.breakpoint = exceptionBreakpoint; + data.reactedOnEvent = false; + data.checkbox.checked = exceptionBreakpoint.enabled; + data.checkbox.disabled = true; + data.inputBox.value = exceptionBreakpoint.condition || ''; + setTimeout(() => { + data.inputBox.focus(); + data.inputBox.select(); + }, 0); + } + + disposeTemplate(templateData: IExceptionBreakpointInputTemplateData): void { dispose(templateData.toDispose); } } @@ -664,14 +775,14 @@ class BreakpointsAccessibilityProvider implements IListAccessibilityProvider { +export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolean, preserveFocus: boolean, pinned: boolean, debugService: IDebugService, editorService: IEditorService): Promise { if (breakpoint.uri.scheme === DEBUG_SCHEME && debugService.state === State.Inactive) { return Promise.resolve(undefined); } @@ -695,17 +806,17 @@ export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolea selection, revealIfOpened: true, selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport, - pinned: !preserveFocus + pinned } }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } -export function getBreakpointMessageAndClassName(state: State, breakpointsActivated: boolean, breakpoint: IBreakpoint | IFunctionBreakpoint | IDataBreakpoint, labelService?: ILabelService): { message?: string, className: string } { +export function getBreakpointMessageAndIcon(state: State, breakpointsActivated: boolean, breakpoint: IBreakpoint | IFunctionBreakpoint | IDataBreakpoint, labelService?: ILabelService): { message?: string, icon: ThemeIcon } { const debugActive = state === State.Running || state === State.Stopped; if (!breakpoint.enabled || !breakpointsActivated) { return { - className: breakpoint instanceof DataBreakpoint ? 'codicon-debug-breakpoint-data-disabled' : breakpoint instanceof FunctionBreakpoint ? 'codicon-debug-breakpoint-function-disabled' : breakpoint.logMessage ? 'codicon-debug-breakpoint-log-disabled' : 'codicon-debug-breakpoint-disabled', + icon: breakpoint instanceof DataBreakpoint ? icons.debugBreakpointDataDisabled : breakpoint instanceof FunctionBreakpoint ? icons.debugBreakpointFunctionDisabled : breakpoint.logMessage ? icons.debugBreakpointLogDisabled : icons.debugBreakpointDisabled, message: breakpoint.logMessage ? nls.localize('disabledLogpoint', "Disabled Logpoint") : nls.localize('disabledBreakpoint', "Disabled Breakpoint"), }; } @@ -715,7 +826,7 @@ export function getBreakpointMessageAndClassName(state: State, breakpointsActiva }; if (debugActive && !breakpoint.verified) { return { - className: breakpoint instanceof DataBreakpoint ? 'codicon-debug-breakpoint-data-unverified' : breakpoint instanceof FunctionBreakpoint ? 'codicon-debug-breakpoint-function-unverified' : breakpoint.logMessage ? 'codicon-debug-breakpoint-log-unverified' : 'codicon-debug-breakpoint-unverified', + icon: breakpoint instanceof DataBreakpoint ? icons.debugBreakpointDataUnverified : breakpoint instanceof FunctionBreakpoint ? icons.debugBreakpointFunctionUnverified : breakpoint.logMessage ? icons.debugBreakpointLogUnverified : icons.debugBreakpointUnverified, message: ('message' in breakpoint && breakpoint.message) ? breakpoint.message : (breakpoint.logMessage ? nls.localize('unverifiedLogpoint', "Unverified Logpoint") : nls.localize('unverifiedBreakopint', "Unverified Breakpoint")), }; } @@ -723,13 +834,13 @@ export function getBreakpointMessageAndClassName(state: State, breakpointsActiva if (breakpoint instanceof FunctionBreakpoint) { if (!breakpoint.supported) { return { - className: 'codicon-debug-breakpoint-function-unverified', + icon: icons.debugBreakpointFunctionUnverified, message: nls.localize('functionBreakpointUnsupported', "Function breakpoints not supported by this debug type"), }; } return { - className: 'codicon-debug-breakpoint-function', + icon: icons.debugBreakpointFunction, message: breakpoint.message || nls.localize('functionBreakpoint', "Function Breakpoint") }; } @@ -737,13 +848,13 @@ export function getBreakpointMessageAndClassName(state: State, breakpointsActiva if (breakpoint instanceof DataBreakpoint) { if (!breakpoint.supported) { return { - className: 'codicon-debug-breakpoint-data-unverified', + icon: icons.debugBreakpointDataUnverified, message: nls.localize('dataBreakpointUnsupported', "Data breakpoints not supported by this debug type"), }; } return { - className: 'codicon-debug-breakpoint-data', + icon: icons.debugBreakpointData, message: breakpoint.message || nls.localize('dataBreakpoint', "Data Breakpoint") }; } @@ -753,7 +864,7 @@ export function getBreakpointMessageAndClassName(state: State, breakpointsActiva if (!breakpoint.supported) { return { - className: 'codicon-debug-breakpoint-unsupported', + icon: icons.debugBreakpointUnsupported, message: nls.localize('breakpointUnsupported', "Breakpoints of this type are not supported by the debugger"), }; } @@ -762,21 +873,21 @@ export function getBreakpointMessageAndClassName(state: State, breakpointsActiva messages.push(nls.localize('logMessage', "Log Message: {0}", breakpoint.logMessage)); } if (breakpoint.condition) { - messages.push(nls.localize('expression', "Expression: {0}", breakpoint.condition)); + messages.push(nls.localize('expression', "Expression condition: {0}", breakpoint.condition)); } if (breakpoint.hitCondition) { messages.push(nls.localize('hitCount', "Hit Count: {0}", breakpoint.hitCondition)); } return { - className: breakpoint.logMessage ? 'codicon-debug-breakpoint-log' : 'codicon-debug-breakpoint-conditional', + icon: breakpoint.logMessage ? icons.debugBreakpointLog : icons.debugBreakpointConditional, message: appendMessage(messages.join('\n')) }; } const message = ('message' in breakpoint && breakpoint.message) ? breakpoint.message : breakpoint instanceof Breakpoint && labelService ? labelService.getUriLabel(breakpoint.uri) : nls.localize('breakpoint', "Breakpoint"); return { - className: 'codicon-debug-breakpoint', + icon: icons.debugBreakpoint, message }; } diff --git a/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts index ec6bdd2a5..9fd5d78b0 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts @@ -5,9 +5,9 @@ import { Constants } from 'vs/base/common/uint'; import { Range, IRange } from 'vs/editor/common/core/range'; -import { TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions } from 'vs/editor/common/model'; +import { TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions, OverviewRulerLane } from 'vs/editor/common/model'; import { IDebugService, IStackFrame } from 'vs/workbench/contrib/debug/common/debug'; -import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, themeColorFromId, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; import { Event } from 'vs/base/common/event'; @@ -16,17 +16,28 @@ import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { distinct } from 'vs/base/common/arrays'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { debugStackframe, debugStackframeFocused } from 'vs/workbench/contrib/debug/browser/debugIcons'; +const topStackFrameColor = registerColor('editor.stackFrameHighlightBackground', { dark: '#ffff0033', light: '#ffff6673', hc: '#ffff0033' }, localize('topStackFrameLineHighlight', 'Background color for the highlight of line at the top stack frame position.')); +const focusedStackFrameColor = registerColor('editor.focusedStackFrameHighlightBackground', { dark: '#7abd7a4d', light: '#cee7ce73', hc: '#7abd7a4d' }, localize('focusedStackFrameLineHighlight', 'Background color for the highlight of line at focused stack frame position.')); const stickiness = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges; // we need a separate decoration for glyph margin, since we do not want it on each line of a multi line statement. const TOP_STACK_FRAME_MARGIN: IModelDecorationOptions = { - glyphMarginClassName: 'codicon-debug-stackframe', - stickiness + glyphMarginClassName: ThemeIcon.asClassName(debugStackframe), + stickiness, + overviewRuler: { + position: OverviewRulerLane.Full, + color: themeColorFromId(topStackFrameColor) + } }; const FOCUSED_STACK_FRAME_MARGIN: IModelDecorationOptions = { - glyphMarginClassName: 'codicon-debug-stackframe-focused', - stickiness + glyphMarginClassName: ThemeIcon.asClassName(debugStackframeFocused), + stickiness, + overviewRuler: { + position: OverviewRulerLane.Full, + color: themeColorFromId(focusedStackFrameColor) + } }; const TOP_STACK_FRAME_DECORATION: IModelDecorationOptions = { isWholeLine: true, @@ -152,6 +163,3 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.monaco-editor .view-overlays .debug-focused-stack-frame-line { background: ${focusedStackFrame}; }`); } }); - -const topStackFrameColor = registerColor('editor.stackFrameHighlightBackground', { dark: '#ffff0033', light: '#ffff6673', hc: '#ffff0033' }, localize('topStackFrameLineHighlight', 'Background color for the highlight of line at the top stack frame position.')); -const focusedStackFrameColor = registerColor('editor.focusedStackFrameHighlightBackground', { dark: '#7abd7a4d', light: '#cee7ce73', hc: '#7abd7a4d' }, localize('focusedStackFrameLineHighlight', 'Background color for the highlight of line at focused stack frame position.')); diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index c7d8774c8..70851479d 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -36,7 +36,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { CollapseAction } from 'vs/workbench/browser/viewlet'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { attachStylerCallback } from 'vs/platform/theme/common/styler'; @@ -46,6 +46,7 @@ import { posix } from 'vs/base/common/path'; import { ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree'; import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; +import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; const $ = dom.$; @@ -206,7 +207,7 @@ export class CallStackView extends ViewPane { getActions(): IAction[] { if (this.stateMessage.hidden) { - return [new CollapseAction(() => this.tree, true, 'explorer-action codicon-collapse-all')]; + return [new CollapseAction(() => this.tree, true, 'explorer-action ' + ThemeIcon.asClassName(icons.debugCollapseAll))]; } return []; @@ -501,7 +502,7 @@ class SessionsRenderer implements ICompressibleTreeRenderer { + const action = new Action('debug.callStack.restartFrame', nls.localize('restartFrame', "Restart Frame"), ThemeIcon.asClassName(icons.debugRestartFrame), true, async () => { try { await stackFrame.restart(); } catch (e) { @@ -918,7 +919,7 @@ class CallStackDataSource implements IAsyncDataSource callStack.length && callStack.length > 1) { + if (!thread.reachedEndOfCallStack && thread.stoppedDetails) { callStack = callStack.concat([new ThreadAndSessionIds(thread.session.getId(), thread.threadId)]); } @@ -994,7 +995,7 @@ class StopAction extends Action { private readonly session: IDebugSession, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${STOP_ID}`, STOP_LABEL, 'debug-action codicon-debug-stop'); + super(`action.${STOP_ID}`, STOP_LABEL, 'debug-action ' + ThemeIcon.asClassName(icons.debugStop)); } public run(): Promise { @@ -1008,7 +1009,7 @@ class DisconnectAction extends Action { private readonly session: IDebugSession, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${DISCONNECT_ID}`, DISCONNECT_LABEL, 'debug-action codicon-debug-disconnect'); + super(`action.${DISCONNECT_ID}`, DISCONNECT_LABEL, 'debug-action ' + ThemeIcon.asClassName(icons.debugDisconnect)); } public run(): Promise { @@ -1022,7 +1023,7 @@ class RestartAction extends Action { private readonly session: IDebugSession, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${RESTART_SESSION_ID}`, RESTART_LABEL, 'debug-action codicon-debug-restart'); + super(`action.${RESTART_SESSION_ID}`, RESTART_LABEL, 'debug-action ' + ThemeIcon.asClassName(icons.debugRestart)); } public run(): Promise { @@ -1036,7 +1037,7 @@ class StepOverAction extends Action { private readonly thread: IThread, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${STEP_OVER_ID}`, STEP_OVER_LABEL, 'debug-action codicon-debug-step-over', thread.stopped); + super(`action.${STEP_OVER_ID}`, STEP_OVER_LABEL, 'debug-action ' + ThemeIcon.asClassName(icons.debugStepOver), thread.stopped); } public run(): Promise { @@ -1050,7 +1051,7 @@ class StepIntoAction extends Action { private readonly thread: IThread, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${STEP_INTO_ID}`, STEP_INTO_LABEL, 'debug-action codicon-debug-step-into', thread.stopped); + super(`action.${STEP_INTO_ID}`, STEP_INTO_LABEL, 'debug-action ' + ThemeIcon.asClassName(icons.debugStepInto), thread.stopped); } public run(): Promise { @@ -1064,7 +1065,7 @@ class StepOutAction extends Action { private readonly thread: IThread, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${STEP_OUT_ID}`, STEP_OUT_LABEL, 'debug-action codicon-debug-step-out', thread.stopped); + super(`action.${STEP_OUT_ID}`, STEP_OUT_LABEL, 'debug-action ' + ThemeIcon.asClassName(icons.debugStepOut), thread.stopped); } public run(): Promise { @@ -1078,7 +1079,7 @@ class PauseAction extends Action { private readonly thread: IThread, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${PAUSE_ID}`, PAUSE_LABEL, 'debug-action codicon-debug-pause', !thread.stopped); + super(`action.${PAUSE_ID}`, PAUSE_LABEL, 'debug-action ' + ThemeIcon.asClassName(icons.debugPause), !thread.stopped); } public run(): Promise { @@ -1092,7 +1093,7 @@ class ContinueAction extends Action { private readonly thread: IThread, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${CONTINUE_ID}`, CONTINUE_LABEL, 'debug-action codicon-debug-continue', thread.stopped); + super(`action.${CONTINUE_ID}`, CONTINUE_LABEL, 'debug-action ' + ThemeIcon.asClassName(icons.debugContinue), thread.stopped); } public run(): Promise { diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index a26cea18b..9438ac11c 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -49,10 +49,10 @@ import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/pl import { StartDebugQuickAccessProvider } from 'vs/workbench/contrib/debug/browser/debugQuickAccess'; import { DebugProgressContribution } from 'vs/workbench/contrib/debug/browser/debugProgress'; import { DebugTitleContribution } from 'vs/workbench/contrib/debug/browser/debugTitle'; -import { Codicon } from 'vs/base/common/codicons'; import { registerColors } from 'vs/workbench/contrib/debug/browser/debugColors'; import { DebugEditorContribution } from 'vs/workbench/contrib/debug/browser/debugEditorContribution'; import { FileAccess } from 'vs/base/common/network'; +import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; const registry = Registry.as(WorkbenchActionRegistryExtensions.WorkbenchActions); const debugCategory = nls.localize('debugCategory', "Debug"); @@ -153,16 +153,16 @@ function registerCommandsAndActions(): void { }); }; - registerDebugToolBarItem(CONTINUE_ID, CONTINUE_LABEL, 10, { id: 'codicon/debug-continue' }, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); - registerDebugToolBarItem(PAUSE_ID, PAUSE_LABEL, 10, { id: 'codicon/debug-pause' }, CONTEXT_DEBUG_STATE.notEqualsTo('stopped'), CONTEXT_DEBUG_STATE.isEqualTo('running')); - registerDebugToolBarItem(STOP_ID, STOP_LABEL, 70, { id: 'codicon/debug-stop' }, CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated()); - registerDebugToolBarItem(DISCONNECT_ID, DISCONNECT_LABEL, 70, { id: 'codicon/debug-disconnect' }, CONTEXT_FOCUSED_SESSION_IS_ATTACH); - registerDebugToolBarItem(STEP_OVER_ID, STEP_OVER_LABEL, 20, { id: 'codicon/debug-step-over' }, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); - registerDebugToolBarItem(STEP_INTO_ID, STEP_INTO_LABEL, 30, { id: 'codicon/debug-step-into' }, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); - registerDebugToolBarItem(STEP_OUT_ID, STEP_OUT_LABEL, 40, { id: 'codicon/debug-step-out' }, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); - registerDebugToolBarItem(RESTART_SESSION_ID, RESTART_LABEL, 60, { id: 'codicon/debug-restart' }); - registerDebugToolBarItem(STEP_BACK_ID, nls.localize('stepBackDebug', "Step Back"), 50, { id: 'codicon/debug-step-back' }, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); - registerDebugToolBarItem(REVERSE_CONTINUE_ID, nls.localize('reverseContinue', "Reverse"), 60, { id: 'codicon/debug-reverse-continue' }, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); + registerDebugToolBarItem(CONTINUE_ID, CONTINUE_LABEL, 10, icons.debugContinue, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); + registerDebugToolBarItem(PAUSE_ID, PAUSE_LABEL, 10, icons.debugPause, CONTEXT_DEBUG_STATE.notEqualsTo('stopped'), CONTEXT_DEBUG_STATE.isEqualTo('running')); + registerDebugToolBarItem(STOP_ID, STOP_LABEL, 70, icons.debugStop, CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated()); + registerDebugToolBarItem(DISCONNECT_ID, DISCONNECT_LABEL, 70, icons.debugDisconnect, CONTEXT_FOCUSED_SESSION_IS_ATTACH); + registerDebugToolBarItem(STEP_OVER_ID, STEP_OVER_LABEL, 20, icons.debugStepOver, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); + registerDebugToolBarItem(STEP_INTO_ID, STEP_INTO_LABEL, 30, icons.debugStepInto, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); + registerDebugToolBarItem(STEP_OUT_ID, STEP_OUT_LABEL, 40, icons.debugStepOut, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); + registerDebugToolBarItem(RESTART_SESSION_ID, RESTART_LABEL, 60, icons.debugRestart); + registerDebugToolBarItem(STEP_BACK_ID, nls.localize('stepBackDebug', "Step Back"), 50, icons.debugStepBack, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); + registerDebugToolBarItem(REVERSE_CONTINUE_ID, nls.localize('reverseContinue', "Reverse"), 60, icons.debugReverseContinue, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); // Debug callstack context menu const registerDebugViewMenuItem = (menuId: MenuId, id: string, title: string, order: number, when?: ContextKeyExpression, precondition?: ContextKeyExpression, group = 'navigation') => { @@ -460,7 +460,7 @@ function registerDebugPanel(): void { const VIEW_CONTAINER: ViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: DEBUG_PANEL_ID, name: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, 'Debug Console'), - icon: Codicon.debugConsole.classNames, + icon: icons.debugConsoleViewIcon, ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [DEBUG_PANEL_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), storageId: DEBUG_PANEL_ID, focusCommand: { id: OpenDebugConsoleAction.ID }, @@ -471,7 +471,7 @@ function registerDebugPanel(): void { Registry.as(ViewExtensions.ViewsRegistry).registerViews([{ id: REPL_VIEW_ID, name: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, 'Debug Console'), - containerIcon: Codicon.debugConsole.classNames, + containerIcon: icons.debugConsoleViewIcon, canToggleVisibility: false, canMoveView: true, when: CONTEXT_DEBUGGERS_AVAILABLE, @@ -481,12 +481,13 @@ function registerDebugPanel(): void { registry.registerWorkbenchAction(SyncActionDescriptor.from(OpenDebugConsoleAction, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Y }), 'View: Debug Console', CATEGORIES.View.value, CONTEXT_DEBUGGERS_AVAILABLE); } + function registerDebugView(): void { const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: nls.localize('run', "Run"), ctorDescriptor: new SyncDescriptor(DebugViewPaneContainer), - icon: Codicon.debugAlt.classNames, + icon: icons.runViewIcon, alwaysUseContainerInfo: true, order: 2 }, ViewContainerLocation.Sidebar); @@ -494,12 +495,12 @@ function registerDebugView(): void { // Register default debug views const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); - viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), containerIcon: Codicon.debugAlt.classNames, ctorDescriptor: new SyncDescriptor(VariablesView), order: 10, weight: 40, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusVariablesView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); - viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), containerIcon: Codicon.debugAlt.classNames, ctorDescriptor: new SyncDescriptor(WatchExpressionsView), order: 20, weight: 10, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); - viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), containerIcon: Codicon.debugAlt.classNames, ctorDescriptor: new SyncDescriptor(CallStackView), order: 30, weight: 30, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); - viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), containerIcon: Codicon.debugAlt.classNames, ctorDescriptor: new SyncDescriptor(BreakpointsView), order: 40, weight: 20, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' }, when: ContextKeyExpr.or(CONTEXT_BREAKPOINTS_EXIST, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer); - viewsRegistry.registerViews([{ id: WelcomeView.ID, name: WelcomeView.LABEL, containerIcon: Codicon.debugAlt.classNames, ctorDescriptor: new SyncDescriptor(WelcomeView), order: 1, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], viewContainer); - viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), containerIcon: Codicon.debugAlt.classNames, ctorDescriptor: new SyncDescriptor(LoadedScriptsView), order: 35, weight: 5, canToggleVisibility: true, canMoveView: true, collapsed: true, when: ContextKeyExpr.and(CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer); + viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), containerIcon: icons.variablesViewIcon, ctorDescriptor: new SyncDescriptor(VariablesView), order: 10, weight: 40, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusVariablesView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); + viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), containerIcon: icons.watchViewIcon, ctorDescriptor: new SyncDescriptor(WatchExpressionsView), order: 20, weight: 10, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); + viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), containerIcon: icons.callStackViewIcon, ctorDescriptor: new SyncDescriptor(CallStackView), order: 30, weight: 30, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); + viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), containerIcon: icons.breakpointsViewIcon, ctorDescriptor: new SyncDescriptor(BreakpointsView), order: 40, weight: 20, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' }, when: ContextKeyExpr.or(CONTEXT_BREAKPOINTS_EXIST, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer); + viewsRegistry.registerViews([{ id: WelcomeView.ID, name: WelcomeView.LABEL, containerIcon: icons.runViewIcon, ctorDescriptor: new SyncDescriptor(WelcomeView), order: 1, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], viewContainer); + viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), containerIcon: icons.loadedScriptsViewIcon, ctorDescriptor: new SyncDescriptor(LoadedScriptsView), order: 35, weight: 5, canToggleVisibility: true, canMoveView: true, collapsed: true, when: ContextKeyExpr.and(CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer); } function registerConfiguration(): void { diff --git a/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts b/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts index 473562714..d3cef4bba 100644 --- a/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts +++ b/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts @@ -7,13 +7,13 @@ import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { RGBA, Color } from 'vs/base/common/color'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ansiColorIdentifiers } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; -import { IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; +import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; /** * @param text The content to stylize. * @returns An {@link HTMLSpanElement} that contains the potentially stylized text. */ -export function handleANSIOutput(text: string, linkDetector: LinkDetector, themeService: IThemeService, debugSession: IDebugSession): HTMLSpanElement { +export function handleANSIOutput(text: string, linkDetector: LinkDetector, themeService: IThemeService, workspaceFolder: IWorkspaceFolder | undefined): HTMLSpanElement { const root: HTMLSpanElement = document.createElement('span'); const textLength: number = text.length; @@ -54,7 +54,7 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector, theme if (sequenceFound) { // Flush buffer with previous styles. - appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, debugSession, customFgColor, customBgColor); + appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, workspaceFolder, customFgColor, customBgColor); buffer = ''; @@ -100,7 +100,7 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector, theme // Flush remaining text buffer if not empty. if (buffer) { - appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, debugSession, customFgColor, customBgColor); + appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, workspaceFolder, customFgColor, customBgColor); } return root; @@ -268,7 +268,7 @@ export function appendStylizedStringToContainer( stringContent: string, cssClasses: string[], linkDetector: LinkDetector, - debugSession: IDebugSession, + workspaceFolder: IWorkspaceFolder | undefined, customTextColor?: RGBA, customBackgroundColor?: RGBA ): void { @@ -276,7 +276,7 @@ export function appendStylizedStringToContainer( return; } - const container = linkDetector.linkify(stringContent, true, debugSession.root); + const container = linkDetector.linkify(stringContent, true, workspaceFolder); container.className = cssClasses.join(' '); if (customTextColor) { diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index f7fcea500..3ab907976 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -12,7 +12,7 @@ import { SelectBox, ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selec import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IDebugService, IDebugSession, IDebugConfiguration, IConfig, ILaunch } from 'vs/workbench/contrib/debug/common/debug'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; import { selectBorder, selectBackground } from 'vs/platform/theme/common/colorRegistry'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; @@ -20,6 +20,7 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ADD_CONFIGURATION_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { debugStart } from 'vs/workbench/contrib/debug/browser/debugIcons'; const $ = dom.$; @@ -68,7 +69,7 @@ export class StartDebugActionViewItem implements IActionViewItem { render(container: HTMLElement): void { this.container = container; container.classList.add('start-debug-action-item'); - this.start = dom.append(container, $('.codicon.codicon-debug-start')); + this.start = dom.append(container, $(ThemeIcon.asCSSSelector(debugStart))); this.start.title = this.action.label; this.start.setAttribute('role', 'button'); this.start.tabIndex = 0; @@ -188,23 +189,35 @@ export class StartDebugActionViewItem implements IActionViewItem { }); }); - if (this.options.length === 0) { - this.options.push({ label: nls.localize('noConfigurations', "No Configurations"), handler: async () => false }); - } else { - this.options.push({ label: StartDebugActionViewItem.SEPARATOR, handler: () => Promise.resolve(false) }); - disabledIdxs.push(this.options.length - 1); - } - - this.providers.forEach(p => { - if (p.type === manager.selectedConfiguration.type) { + // Only take 3 elements from the recent dynamic configurations to not clutter the dropdown + manager.getRecentDynamicConfigurations().slice(0, 3).forEach(({ name, type }) => { + if (type === manager.selectedConfiguration.type && manager.selectedConfiguration.name === name) { this.selected = this.options.length; } + this.options.push({ + label: name, + handler: async () => { + await manager.selectConfiguration(undefined, name, undefined, { type }); + return true; + } + }); + }); + + if (this.options.length === 0) { + this.options.push({ label: nls.localize('noConfigurations', "No Configurations"), handler: async () => false }); + } + + this.options.push({ label: StartDebugActionViewItem.SEPARATOR, handler: () => Promise.resolve(false) }); + disabledIdxs.push(this.options.length - 1); + + this.providers.forEach(p => { this.options.push({ - label: `${p.label}...`, handler: async () => { + label: `${p.label}...`, + handler: async () => { const picked = await p.pick(); if (picked) { - await manager.selectConfiguration(picked.launch, picked.config.name, picked.config, p.type); + await manager.selectConfiguration(picked.launch, picked.config.name, picked.config, { type: p.type }); return true; } return false; @@ -212,11 +225,6 @@ export class StartDebugActionViewItem implements IActionViewItem { }); }); - if (this.providers.length > 0) { - this.options.push({ label: StartDebugActionViewItem.SEPARATOR, handler: () => Promise.resolve(false) }); - disabledIdxs.push(this.options.length - 1); - } - manager.getLaunches().filter(l => !l.hidden).forEach(l => { const label = inWorkspace ? nls.localize("addConfigTo", "Add Config ({0})...", l.name) : nls.localize('addConfiguration', "Add Configuration..."); this.options.push({ diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index 3f3e09c95..3c9472104 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -14,6 +14,8 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { deepClone } from 'vs/base/common/objects'; +import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; export abstract class AbstractDebugAction extends Action { @@ -64,7 +66,7 @@ export class ConfigureAction extends AbstractDebugAction { @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IQuickInputService private readonly quickInputService: IQuickInputService ) { - super(id, label, 'debug-action codicon codicon-gear', debugService, keybindingService); + super(id, label, 'debug-action ' + ThemeIcon.asClassName(icons.debugConfigure), debugService, keybindingService); this._register(debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateClass())); this.updateClass(); } @@ -79,7 +81,7 @@ export class ConfigureAction extends AbstractDebugAction { private updateClass(): void { const configurationManager = this.debugService.getConfigurationManager(); - this.class = configurationManager.selectedConfiguration.name ? 'debug-action codicon codicon-gear' : 'debug-action codicon codicon-gear notification'; + this.class = configurationManager.selectedConfiguration.name ? 'debug-action' + ThemeIcon.asClassName(icons.debugConfigure) : 'debug-action ' + ThemeIcon.asClassName(icons.debugConfigure) + ' notification'; } async run(): Promise { @@ -132,7 +134,8 @@ export class StartAction extends AbstractDebugAction { } async run(): Promise { - let { launch, name, config } = this.debugService.getConfigurationManager().selectedConfiguration; + let { launch, name, getConfig } = this.debugService.getConfigurationManager().selectedConfiguration; + const config = await getConfig(); const clonedConfig = deepClone(config); return this.debugService.startDebugging(launch, clonedConfig || name, { noDebug: this.isNoDebug() }); } @@ -147,10 +150,10 @@ export class StartAction extends AbstractDebugAction { if (debugService.state === State.Initializing) { return false; } - let { name, config } = debugService.getConfigurationManager().selectedConfiguration; - let nameToStart = name || config?.name; + let { name, launch } = debugService.getConfigurationManager().selectedConfiguration; + let nameToStart = name; - if (sessions.some(s => s.configuration.name === nameToStart)) { + if (sessions.some(s => s.configuration.name === nameToStart && s.root === launch?.workspace)) { // There is already a debug session running and we do not have any launch configuration selected return false; } @@ -209,7 +212,7 @@ export class RemoveAllBreakpointsAction extends AbstractDebugAction { static readonly LABEL = nls.localize('removeAllBreakpoints', "Remove All Breakpoints"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action codicon-close-all', debugService, keybindingService); + super(id, label, 'debug-action ' + ThemeIcon.asClassName(icons.breakpointsRemoveAll), debugService, keybindingService); this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); } @@ -267,7 +270,7 @@ export class ToggleBreakpointsActivatedAction extends AbstractDebugAction { static readonly DEACTIVATE_LABEL = nls.localize('deactivateBreakpoints', "Deactivate Breakpoints"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action codicon-activate-breakpoints', debugService, keybindingService); + super(id, label, 'debug-action ' + ThemeIcon.asClassName(icons.breakpointsActivate), debugService, keybindingService); this.updateLabel(this.debugService.getModel().areBreakpointsActivated() ? ToggleBreakpointsActivatedAction.DEACTIVATE_LABEL : ToggleBreakpointsActivatedAction.ACTIVATE_LABEL); this._register(this.debugService.getModel().onDidChangeBreakpoints(() => { @@ -310,7 +313,7 @@ export class AddFunctionBreakpointAction extends AbstractDebugAction { static readonly LABEL = nls.localize('addFunctionBreakpoint', "Add Function Breakpoint"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action codicon-add', debugService, keybindingService); + super(id, label, 'debug-action ' + ThemeIcon.asClassName(icons.watchExpressionsAddFuncBreakpoint), debugService, keybindingService); this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); } @@ -319,7 +322,7 @@ export class AddFunctionBreakpointAction extends AbstractDebugAction { } protected isEnabled(_: State): boolean { - return !this.debugService.getViewModel().getSelectedFunctionBreakpoint() + return !this.debugService.getViewModel().getSelectedBreakpoint() && this.debugService.getModel().getFunctionBreakpoints().every(fbp => !!fbp.name); } } @@ -329,7 +332,7 @@ export class AddWatchExpressionAction extends AbstractDebugAction { static readonly LABEL = nls.localize('addWatchExpression', "Add Expression"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action codicon-add', debugService, keybindingService); + super(id, label, 'debug-action ' + ThemeIcon.asClassName(icons.watchExpressionsAdd), debugService, keybindingService); this._register(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement())); this._register(this.debugService.getViewModel().onDidSelectExpression(() => this.updateEnablement())); } @@ -349,7 +352,7 @@ export class RemoveAllWatchExpressionsAction extends AbstractDebugAction { static readonly LABEL = nls.localize('removeAllWatchExpressions', "Remove All Expressions"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action codicon-close-all', debugService, keybindingService); + super(id, label, 'debug-action ' + ThemeIcon.asClassName(icons.watchExpressionsRemoveAll), debugService, keybindingService); this._register(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement())); } diff --git a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts new file mode 100644 index 000000000..249f1e69d --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts @@ -0,0 +1,268 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IDisposable } from 'vs/base/common/lifecycle'; +import { Event, Emitter } from 'vs/base/common/event'; +import * as strings from 'vs/base/common/strings'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { ITextModel } from 'vs/editor/common/model'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IDebugConfiguration, IConfig, IDebugAdapterDescriptorFactory, IDebugAdapter, IDebugSession, IAdapterDescriptor, IDebugAdapterFactory, CONTEXT_DEBUGGERS_AVAILABLE, IAdapterManager } from 'vs/workbench/contrib/debug/common/debug'; +import { Debugger } from 'vs/workbench/contrib/debug/common/debugger'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { launchSchema, debuggersExtPoint, breakpointsExtPoint } from 'vs/workbench/contrib/debug/common/debugSchemas'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; + +export class AdapterManager implements IAdapterManager { + + private debuggers: Debugger[]; + private adapterDescriptorFactories: IDebugAdapterDescriptorFactory[]; + private debugAdapterFactories = new Map(); + private debuggersAvailable: IContextKey; + private readonly _onDidRegisterDebugger = new Emitter(); + private readonly _onDidDebuggersExtPointRead = new Emitter(); + private breakpointModeIdsSet = new Set(); + + constructor( + @IEditorService private readonly editorService: IEditorService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @ICommandService private readonly commandService: ICommandService, + @IExtensionService private readonly extensionService: IExtensionService, + @IContextKeyService contextKeyService: IContextKeyService + ) { + this.adapterDescriptorFactories = []; + this.debuggers = []; + this.registerListeners(); + this.debuggersAvailable = CONTEXT_DEBUGGERS_AVAILABLE.bindTo(contextKeyService); + } + + private registerListeners(): void { + debuggersExtPoint.setHandler((extensions, delta) => { + delta.added.forEach(added => { + added.value.forEach(rawAdapter => { + if (!rawAdapter.type || (typeof rawAdapter.type !== 'string')) { + added.collector.error(nls.localize('debugNoType', "Debugger 'type' can not be omitted and must be of type 'string'.")); + } + if (rawAdapter.enableBreakpointsFor && rawAdapter.enableBreakpointsFor.languageIds) { + rawAdapter.enableBreakpointsFor.languageIds.forEach(modeId => { + this.breakpointModeIdsSet.add(modeId); + }); + } + + if (rawAdapter.type !== '*') { + const existing = this.getDebugger(rawAdapter.type); + if (existing) { + existing.merge(rawAdapter, added.description); + } else { + this.debuggers.push(this.instantiationService.createInstance(Debugger, this, rawAdapter, added.description)); + } + } + }); + }); + + // take care of all wildcard contributions + extensions.forEach(extension => { + extension.value.forEach(rawAdapter => { + if (rawAdapter.type === '*') { + this.debuggers.forEach(dbg => dbg.merge(rawAdapter, extension.description)); + } + }); + }); + + delta.removed.forEach(removed => { + const removedTypes = removed.value.map(rawAdapter => rawAdapter.type); + this.debuggers = this.debuggers.filter(d => removedTypes.indexOf(d.type) === -1); + }); + + // update the schema to include all attributes, snippets and types from extensions. + this.debuggers.forEach(adapter => { + const items = (launchSchema.properties!['configurations'].items); + const schemaAttributes = adapter.getSchemaAttributes(); + if (schemaAttributes && items.oneOf) { + items.oneOf.push(...schemaAttributes); + } + const configurationSnippets = adapter.configurationSnippets; + if (configurationSnippets && items.defaultSnippets) { + items.defaultSnippets.push(...configurationSnippets); + } + }); + + this._onDidDebuggersExtPointRead.fire(); + }); + + breakpointsExtPoint.setHandler((extensions, delta) => { + delta.removed.forEach(removed => { + removed.value.forEach(breakpoints => this.breakpointModeIdsSet.delete(breakpoints.language)); + }); + delta.added.forEach(added => { + added.value.forEach(breakpoints => this.breakpointModeIdsSet.add(breakpoints.language)); + }); + }); + } + + registerDebugAdapterFactory(debugTypes: string[], debugAdapterLauncher: IDebugAdapterFactory): IDisposable { + debugTypes.forEach(debugType => this.debugAdapterFactories.set(debugType, debugAdapterLauncher)); + this.debuggersAvailable.set(this.debugAdapterFactories.size > 0); + this._onDidRegisterDebugger.fire(); + + return { + dispose: () => { + debugTypes.forEach(debugType => this.debugAdapterFactories.delete(debugType)); + } + }; + } + + hasDebuggers(): boolean { + return this.debugAdapterFactories.size > 0; + } + + createDebugAdapter(session: IDebugSession): IDebugAdapter | undefined { + let factory = this.debugAdapterFactories.get(session.configuration.type); + if (factory) { + return factory.createDebugAdapter(session); + } + return undefined; + } + + substituteVariables(debugType: string, folder: IWorkspaceFolder | undefined, config: IConfig): Promise { + let factory = this.debugAdapterFactories.get(debugType); + if (factory) { + return factory.substituteVariables(folder, config); + } + return Promise.resolve(config); + } + + runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments): Promise { + let factory = this.debugAdapterFactories.get(debugType); + if (factory) { + return factory.runInTerminal(args); + } + return Promise.resolve(void 0); + } + + registerDebugAdapterDescriptorFactory(debugAdapterProvider: IDebugAdapterDescriptorFactory): IDisposable { + this.adapterDescriptorFactories.push(debugAdapterProvider); + return { + dispose: () => { + this.unregisterDebugAdapterDescriptorFactory(debugAdapterProvider); + } + }; + } + + unregisterDebugAdapterDescriptorFactory(debugAdapterProvider: IDebugAdapterDescriptorFactory): void { + const ix = this.adapterDescriptorFactories.indexOf(debugAdapterProvider); + if (ix >= 0) { + this.adapterDescriptorFactories.splice(ix, 1); + } + } + + getDebugAdapterDescriptor(session: IDebugSession): Promise { + const config = session.configuration; + const providers = this.adapterDescriptorFactories.filter(p => p.type === config.type && p.createDebugAdapterDescriptor); + if (providers.length === 1) { + return providers[0].createDebugAdapterDescriptor(session); + } else { + // TODO@AW handle n > 1 case + } + return Promise.resolve(undefined); + } + + getDebuggerLabel(type: string): string | undefined { + const dbgr = this.getDebugger(type); + if (dbgr) { + return dbgr.label; + } + + return undefined; + } + + get onDidRegisterDebugger(): Event { + return this._onDidRegisterDebugger.event; + } + + get onDidDebuggersExtPointRead(): Event { + return this._onDidDebuggersExtPointRead.event; + } + + canSetBreakpointsIn(model: ITextModel): boolean { + const modeId = model.getLanguageIdentifier().language; + if (!modeId || modeId === 'jsonc' || modeId === 'log') { + // do not allow breakpoints in our settings files and output + return false; + } + if (this.configurationService.getValue('debug').allowBreakpointsEverywhere) { + return true; + } + + return this.breakpointModeIdsSet.has(modeId); + } + + getDebugger(type: string): Debugger | undefined { + return this.debuggers.find(dbg => strings.equalsIgnoreCase(dbg.type, type)); + } + + isDebuggerInterestedInLanguage(language: string): boolean { + return !!this.debuggers.find(a => language && a.languages && a.languages.indexOf(language) >= 0); + } + + async guessDebugger(type?: string): Promise { + if (type) { + const adapter = this.getDebugger(type); + return Promise.resolve(adapter); + } + + const activeTextEditorControl = this.editorService.activeTextEditorControl; + let candidates: Debugger[] | undefined; + if (isCodeEditor(activeTextEditorControl)) { + const model = activeTextEditorControl.getModel(); + const language = model ? model.getLanguageIdentifier().language : undefined; + const adapters = this.debuggers.filter(a => language && a.languages && a.languages.indexOf(language) >= 0); + if (adapters.length === 1) { + return adapters[0]; + } + if (adapters.length > 1) { + candidates = adapters; + } + } + + if (!candidates) { + await this.activateDebuggers('onDebugInitialConfigurations'); + candidates = this.debuggers.filter(dbg => dbg.hasInitialConfiguration() || dbg.hasConfigurationProvider()); + } + + candidates.sort((first, second) => first.label.localeCompare(second.label)); + const picks = candidates.map(c => ({ label: c.label, debugger: c })); + return this.quickInputService.pick<{ label: string, debugger: Debugger | undefined }>([...picks, { type: 'separator' }, { label: nls.localize('more', "More..."), debugger: undefined }], { placeHolder: nls.localize('selectDebug', "Select Environment") }) + .then(picked => { + if (picked && picked.debugger) { + return picked.debugger; + } + if (picked) { + this.commandService.executeCommand('debug.installAdditionalDebuggers'); + } + return undefined; + }); + } + + async activateDebuggers(activationEvent: string, debugType?: string): Promise { + const promises: Promise[] = [ + this.extensionService.activateByEvent(activationEvent), + this.extensionService.activateByEvent('onDebug') + ]; + if (debugType) { + promises.push(this.extensionService.activateByEvent(`${activationEvent}:${debugType}`)); + } + await Promise.all(promises); + } +} diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 098a3eebb..47aaedfb9 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -113,9 +113,9 @@ function isSessionContext(obj: any): obj is CallStackContext { export function registerCommands(): void { - // These commands are used in call stack context menu, call stack inline actions, command pallete, debug toolbar, mac native touch bar + // These commands are used in call stack context menu, call stack inline actions, command palette, debug toolbar, mac native touch bar // When the command is exectued in the context of a thread(context menu on a thread, inline call stack action) we pass the thread id - // Otherwise when it is executed "globaly"(using the touch bar, debug toolbar, command pallete) we do not pass any id and just take whatever is the focussed thread + // Otherwise when it is executed "globaly"(using the touch bar, debug toolbar, command palette) we do not pass any id and just take whatever is the focussed thread // Same for stackFrame commands and session commands. CommandsRegistry.registerCommand({ id: COPY_STACK_TRACE_ID, @@ -521,7 +521,7 @@ export function registerCommands(): void { const control = editorService.activeTextEditorControl; if (isCodeEditor(control)) { const position = control.getPosition(); - if (position && control.hasModel() && debugService.getConfigurationManager().canSetBreakpointsIn(control.getModel())) { + if (position && control.hasModel() && debugService.canSetBreakpointsIn(control.getModel())) { const modelUri = control.getModel().uri; const breakpointAlreadySet = debugService.getModel().getBreakpoints({ lineNumber: position.lineNumber, uri: modelUri }) .some(bp => (bp.sessionAgnosticData.column === position.column || (!bp.column && position.column <= 1))); @@ -564,7 +564,7 @@ export function registerCommands(): void { if (list instanceof List) { const focus = list.getFocusedElements(); if (focus.length && focus[0] instanceof Breakpoint) { - return openBreakpointSource(focus[0], true, false, accessor.get(IDebugService), accessor.get(IEditorService)); + return openBreakpointSource(focus[0], true, false, true, accessor.get(IDebugService), accessor.get(IEditorService)); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 041c97e21..1357826cd 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -6,30 +6,25 @@ import * as nls from 'vs/nls'; import { dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; -import * as strings from 'vs/base/common/strings'; import * as objects from 'vs/base/common/objects'; import * as json from 'vs/base/common/json'; import { URI as uri } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { ITextModel } from 'vs/editor/common/model'; import { IEditorPane } from 'vs/workbench/common/editor'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IFileService } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IConfigPresentation, CONTEXT_DEBUGGERS_AVAILABLE } from 'vs/workbench/contrib/debug/common/debug'; -import { Debugger } from 'vs/workbench/contrib/debug/common/debugger'; +import { IDebugConfigurationProvider, ICompound, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, CONTEXT_DEBUG_CONFIGURATION_TYPE, IConfigPresentation } from 'vs/workbench/contrib/debug/common/debug'; import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { Registry } from 'vs/platform/registry/common/platform'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; -import { launchSchema, debuggersExtPoint, breakpointsExtPoint } from 'vs/workbench/contrib/debug/common/debugSchemas'; +import { launchSchema } from 'vs/workbench/contrib/debug/common/debugSchemas'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -37,10 +32,13 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance import { withUndefinedAsNull } from 'vs/base/common/types'; import { sequence } from 'vs/base/common/async'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { flatten } from 'vs/base/common/arrays'; +import { flatten, distinct } from 'vs/base/common/arrays'; import { getVisibleAndSorted } from 'vs/workbench/contrib/debug/common/debugUtils'; import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/api/common/extHostTypes'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { AdapterManager } from 'vs/workbench/contrib/debug/browser/debugAdapterManager'; +import { debugConfigure } from 'vs/workbench/contrib/debug/browser/debugIcons'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); jsonRegistry.registerSchema(launchSchemaId, launchSchema); @@ -49,33 +47,27 @@ const DEBUG_SELECTED_CONFIG_NAME_KEY = 'debug.selectedconfigname'; const DEBUG_SELECTED_ROOT = 'debug.selectedroot'; // Debug type is only stored if a dynamic configuration is used for better restore const DEBUG_SELECTED_TYPE = 'debug.selectedtype'; +const DEBUG_RECENT_DYNAMIC_CONFIGURATIONS = 'debug.recentdynamicconfigurations'; interface IDynamicPickItem { label: string, launch: ILaunch, config: IConfig } export class ConfigurationManager implements IConfigurationManager { - private debuggers: Debugger[]; - private breakpointModeIdsSet = new Set(); private launches!: ILaunch[]; private selectedName: string | undefined; private selectedLaunch: ILaunch | undefined; - private selectedConfig: IConfig | undefined; + private getSelectedConfig: () => Promise = () => Promise.resolve(undefined); private selectedType: string | undefined; private toDispose: IDisposable[]; private readonly _onDidSelectConfigurationName = new Emitter(); private configProviders: IDebugConfigurationProvider[]; - private adapterDescriptorFactories: IDebugAdapterDescriptorFactory[]; - private debugAdapterFactories = new Map(); private debugConfigurationTypeContext: IContextKey; - private debuggersAvailable: IContextKey; - private readonly _onDidRegisterDebugger = new Emitter(); constructor( + private readonly adapterManager: AdapterManager, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService, @IQuickInputService private readonly quickInputService: IQuickInputService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @ICommandService private readonly commandService: ICommandService, @IStorageService private readonly storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, @IHistoryService private readonly historyService: IHistoryService, @@ -83,8 +75,6 @@ export class ConfigurationManager implements IConfigurationManager { @IContextKeyService contextKeyService: IContextKeyService ) { this.configProviders = []; - this.adapterDescriptorFactories = []; - this.debuggers = []; this.toDispose = []; this.initLaunches(); this.registerListeners(); @@ -93,111 +83,13 @@ export class ConfigurationManager implements IConfigurationManager { const previousSelectedLaunch = this.launches.find(l => l.uri.toString() === previousSelectedRoot); const previousSelectedName = this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE); this.debugConfigurationTypeContext = CONTEXT_DEBUG_CONFIGURATION_TYPE.bindTo(contextKeyService); - this.debuggersAvailable = CONTEXT_DEBUGGERS_AVAILABLE.bindTo(contextKeyService); if (previousSelectedLaunch && previousSelectedLaunch.getConfigurationNames().length) { - this.selectConfiguration(previousSelectedLaunch, previousSelectedName, undefined, previousSelectedType); + this.selectConfiguration(previousSelectedLaunch, previousSelectedName, undefined, { type: previousSelectedType }); } else if (this.launches.length > 0) { - this.selectConfiguration(undefined, previousSelectedName, undefined, previousSelectedType); + this.selectConfiguration(undefined, previousSelectedName, undefined, { type: previousSelectedType }); } } - // debuggers - - registerDebugAdapterFactory(debugTypes: string[], debugAdapterLauncher: IDebugAdapterFactory): IDisposable { - debugTypes.forEach(debugType => this.debugAdapterFactories.set(debugType, debugAdapterLauncher)); - this.debuggersAvailable.set(this.debugAdapterFactories.size > 0); - this._onDidRegisterDebugger.fire(); - - return { - dispose: () => { - debugTypes.forEach(debugType => this.debugAdapterFactories.delete(debugType)); - } - }; - } - - hasDebuggers(): boolean { - return this.debugAdapterFactories.size > 0; - } - - createDebugAdapter(session: IDebugSession): IDebugAdapter | undefined { - let factory = this.debugAdapterFactories.get(session.configuration.type); - if (factory) { - return factory.createDebugAdapter(session); - } - return undefined; - } - - substituteVariables(debugType: string, folder: IWorkspaceFolder | undefined, config: IConfig): Promise { - let factory = this.debugAdapterFactories.get(debugType); - if (factory) { - return factory.substituteVariables(folder, config); - } - return Promise.resolve(config); - } - - runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments): Promise { - let factory = this.debugAdapterFactories.get(debugType); - if (factory) { - return factory.runInTerminal(args); - } - return Promise.resolve(void 0); - } - - // debug adapter - - registerDebugAdapterDescriptorFactory(debugAdapterProvider: IDebugAdapterDescriptorFactory): IDisposable { - this.adapterDescriptorFactories.push(debugAdapterProvider); - return { - dispose: () => { - this.unregisterDebugAdapterDescriptorFactory(debugAdapterProvider); - } - }; - } - - unregisterDebugAdapterDescriptorFactory(debugAdapterProvider: IDebugAdapterDescriptorFactory): void { - const ix = this.adapterDescriptorFactories.indexOf(debugAdapterProvider); - if (ix >= 0) { - this.adapterDescriptorFactories.splice(ix, 1); - } - } - - getDebugAdapterDescriptor(session: IDebugSession): Promise { - - const config = session.configuration; - - // first try legacy proposed API: DebugConfigurationProvider.debugAdapterExecutable - const providers0 = this.configProviders.filter(p => p.type === config.type && p.debugAdapterExecutable); - if (providers0.length === 1 && providers0[0].debugAdapterExecutable) { - return providers0[0].debugAdapterExecutable(session.root ? session.root.uri : undefined); - } else { - // TODO@AW handle n > 1 case - } - - // new API - const providers = this.adapterDescriptorFactories.filter(p => p.type === config.type && p.createDebugAdapterDescriptor); - if (providers.length === 1) { - return providers[0].createDebugAdapterDescriptor(session); - } else { - // TODO@AW handle n > 1 case - } - return Promise.resolve(undefined); - } - - getDebuggerLabel(type: string): string | undefined { - const dbgr = this.getDebugger(type); - if (dbgr) { - return dbgr.label; - } - - return undefined; - } - - get onDidRegisterDebugger(): Event { - return this._onDidRegisterDebugger.event; - } - - // debug configurations - registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable { this.configProviders.push(debugConfigurationProvider); return { @@ -298,7 +190,7 @@ export class ConfigurationManager implements IConfigurationManager { return debugDynamicExtensionsTypes.map(type => { return { - label: this.getDebuggerLabel(type)!, + label: this.adapterManager.getDebuggerLabel(type)!, getProvider: async () => { await this.activateDebuggers(onDebugDynamicConfigurationsName, type); return this.configProviders.find(p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Dynamic && p.provideDebugConfigurations); @@ -313,7 +205,6 @@ export class ConfigurationManager implements IConfigurationManager { input.placeholder = nls.localize('selectConfiguration', "Select Launch Configuration"); input.show(); - let chosenDidCancel = false; const chosenPromise = new Promise(resolve => { disposables.add(input.onDidAccept(() => resolve(input.activeItems[0]))); disposables.add(input.onDidTriggerItemButton(async (context) => { @@ -324,7 +215,6 @@ export class ConfigurationManager implements IConfigurationManager { await (launch as Launch).writeConfiguration(config); await this.selectConfiguration(launch, config.name); })); - disposables.add(input.onDidHide(() => { chosenDidCancel = true; resolve(undefined); })); }); const token = new CancellationTokenSource(); @@ -337,7 +227,7 @@ export class ConfigurationManager implements IConfigurationManager { description: launch.name, config, buttons: [{ - iconClass: 'codicon-gear', + iconClass: ThemeIcon.asClassName(debugConfigure), tooltip: nls.localize('editLaunchConfig', "Edit Debug Configuration in launch.json") }], launch @@ -348,16 +238,9 @@ export class ConfigurationManager implements IConfigurationManager { const nestedPicks = await Promise.all(picks); const items = flatten(nestedPicks); - let chosen: IDynamicPickItem | undefined; - - // If there's exactly one item to choose from, pick it automatically - if (items.length === 1 && !chosenDidCancel) { - chosen = items[0]; - } else { - input.items = items; - input.busy = false; - chosen = await chosenPromise; - } + input.items = items; + input.busy = false; + const chosen = await chosenPromise; disposables.dispose(); @@ -387,69 +270,11 @@ export class ConfigurationManager implements IConfigurationManager { return getVisibleAndSorted(all); } + getRecentDynamicConfigurations(): { name: string, type: string }[] { + return JSON.parse(this.storageService.get(DEBUG_RECENT_DYNAMIC_CONFIGURATIONS, StorageScope.WORKSPACE, '[]')); + } + private registerListeners(): void { - debuggersExtPoint.setHandler((extensions, delta) => { - delta.added.forEach(added => { - added.value.forEach(rawAdapter => { - if (!rawAdapter.type || (typeof rawAdapter.type !== 'string')) { - added.collector.error(nls.localize('debugNoType', "Debugger 'type' can not be omitted and must be of type 'string'.")); - } - if (rawAdapter.enableBreakpointsFor) { - rawAdapter.enableBreakpointsFor.languageIds.forEach(modeId => { - this.breakpointModeIdsSet.add(modeId); - }); - } - - if (rawAdapter.type !== '*') { - const existing = this.getDebugger(rawAdapter.type); - if (existing) { - existing.merge(rawAdapter, added.description); - } else { - this.debuggers.push(this.instantiationService.createInstance(Debugger, this, rawAdapter, added.description)); - } - } - }); - }); - - // take care of all wildcard contributions - extensions.forEach(extension => { - extension.value.forEach(rawAdapter => { - if (rawAdapter.type === '*') { - this.debuggers.forEach(dbg => dbg.merge(rawAdapter, extension.description)); - } - }); - }); - - delta.removed.forEach(removed => { - const removedTypes = removed.value.map(rawAdapter => rawAdapter.type); - this.debuggers = this.debuggers.filter(d => removedTypes.indexOf(d.type) === -1); - }); - - // update the schema to include all attributes, snippets and types from extensions. - this.debuggers.forEach(adapter => { - const items = (launchSchema.properties!['configurations'].items); - const schemaAttributes = adapter.getSchemaAttributes(); - if (schemaAttributes && items.oneOf) { - items.oneOf.push(...schemaAttributes); - } - const configurationSnippets = adapter.configurationSnippets; - if (configurationSnippets && items.defaultSnippets) { - items.defaultSnippets.push(...configurationSnippets); - } - }); - - this.setCompoundSchemaValues(); - }); - - breakpointsExtPoint.setHandler((extensions, delta) => { - delta.removed.forEach(removed => { - removed.value.forEach(breakpoints => this.breakpointModeIdsSet.delete(breakpoints.language)); - }); - delta.added.forEach(added => { - added.value.forEach(breakpoints => this.breakpointModeIdsSet.add(breakpoints.language)); - }); - }); - this.toDispose.push(Event.any(this.contextService.onDidChangeWorkspaceFolders, this.contextService.onDidChangeWorkbenchState)(() => { this.initLaunches(); this.selectConfiguration(undefined); @@ -462,14 +287,17 @@ export class ConfigurationManager implements IConfigurationManager { this.setCompoundSchemaValues(); } })); + this.toDispose.push(this.adapterManager.onDidDebuggersExtPointRead(() => { + this.setCompoundSchemaValues(); + })); } private initLaunches(): void { - this.launches = this.contextService.getWorkspace().folders.map(folder => this.instantiationService.createInstance(Launch, this, folder)); + this.launches = this.contextService.getWorkspace().folders.map(folder => this.instantiationService.createInstance(Launch, this, this.adapterManager, folder)); if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { - this.launches.push(this.instantiationService.createInstance(WorkspaceLaunch, this)); + this.launches.push(this.instantiationService.createInstance(WorkspaceLaunch, this, this.adapterManager)); } - this.launches.push(this.instantiationService.createInstance(UserLaunch, this)); + this.launches.push(this.instantiationService.createInstance(UserLaunch, this, this.adapterManager)); if (this.selectedLaunch && this.launches.indexOf(this.selectedLaunch) === -1) { this.selectConfiguration(undefined); @@ -501,11 +329,11 @@ export class ConfigurationManager implements IConfigurationManager { return this.launches.find(l => l.workspace && this.uriIdentityService.extUri.isEqual(l.workspace.uri, workspaceUri)); } - get selectedConfiguration(): { launch: ILaunch | undefined, name: string | undefined, config: IConfig | undefined, type: string | undefined } { + get selectedConfiguration(): { launch: ILaunch | undefined, name: string | undefined, getConfig: () => Promise, type: string | undefined } { return { launch: this.selectedLaunch, name: this.selectedName, - config: this.selectedConfig, + getConfig: this.getSelectedConfig, type: this.selectedType }; } @@ -522,7 +350,7 @@ export class ConfigurationManager implements IConfigurationManager { return undefined; } - async selectConfiguration(launch: ILaunch | undefined, name?: string, config?: IConfig, type?: string): Promise { + async selectConfiguration(launch: ILaunch | undefined, name?: string, config?: IConfig, dynamicConfig?: { type?: string }): Promise { if (typeof launch === 'undefined') { const rootUri = this.historyService.getLastActiveWorkspaceRoot(); launch = this.getLaunch(rootUri); @@ -536,40 +364,56 @@ export class ConfigurationManager implements IConfigurationManager { this.selectedLaunch = launch; if (this.selectedLaunch) { - this.storageService.store(DEBUG_SELECTED_ROOT, this.selectedLaunch.uri.toString(), StorageScope.WORKSPACE); + this.storageService.store(DEBUG_SELECTED_ROOT, this.selectedLaunch.uri.toString(), StorageScope.WORKSPACE, StorageTarget.MACHINE); } else { this.storageService.remove(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE); } const names = launch ? launch.getConfigurationNames() : []; - if ((name && names.indexOf(name) >= 0) || config) { + this.getSelectedConfig = () => Promise.resolve(config); + let type = config?.type; + if (name && names.indexOf(name) >= 0) { this.setSelectedLaunchName(name); - } else if (!this.selectedName || names.indexOf(this.selectedName) === -1) { - // We could not find the previously used name. We should get all dynamic configurations from providers + } else if (dynamicConfig && dynamicConfig.type) { + // We could not find the previously used name and config is not passed. We should get all dynamic configurations from providers // And potentially auto select the previously used dynamic configuration #96293 - const providers = (await this.getDynamicProviders()).filter(p => p.type === type); - const activatedProviders = await Promise.all(providers.map(p => p.getProvider())); - const provider = activatedProviders.find(p => p && p.type === type); - let nameToSet = names.length ? names[0] : undefined; - if (provider && launch && launch.workspace) { - const token = new CancellationTokenSource(); - const dynamicConfigs = await provider.provideDebugConfigurations!(launch.workspace.uri, token.token); - const dynamicConfig = dynamicConfigs.find(c => c.name === name); - if (dynamicConfig) { - config = dynamicConfig; - nameToSet = name; - } - } + type = dynamicConfig.type; + if (!config) { + const providers = (await this.getDynamicProviders()).filter(p => p.type === type); + this.getSelectedConfig = async () => { + const activatedProviders = await Promise.all(providers.map(p => p.getProvider())); + const provider = activatedProviders.find(p => p && p.type === type); + if (provider && launch && launch.workspace) { + const token = new CancellationTokenSource(); + const dynamicConfigs = await provider.provideDebugConfigurations!(launch.workspace.uri, token.token); + const dynamicConfig = dynamicConfigs.find(c => c.name === name); + if (dynamicConfig) { + return dynamicConfig; + } + } + return undefined; + }; + } + this.setSelectedLaunchName(name); + + let recentDynamicProviders = this.getRecentDynamicConfigurations(); + if (name && dynamicConfig.type) { + // We need to store the recently used dynamic configurations to be able to show them in UI #110009 + recentDynamicProviders.unshift({ name, type: dynamicConfig.type }); + recentDynamicProviders = distinct(recentDynamicProviders, t => `${t.name} : ${t.type}`); + this.storageService.store(DEBUG_RECENT_DYNAMIC_CONFIGURATIONS, JSON.stringify(recentDynamicProviders), StorageScope.WORKSPACE, StorageTarget.USER); + } + } else if (!this.selectedName || names.indexOf(this.selectedName) === -1) { + // We could not find the configuration to select, pick the first one, or reset the selection if there is no launch configuration + const nameToSet = names.length ? names[0] : undefined; this.setSelectedLaunchName(nameToSet); } - this.selectedConfig = config; - this.selectedType = type || this.selectedConfig?.type; - this.storageService.store(DEBUG_SELECTED_TYPE, this.selectedType, StorageScope.WORKSPACE); - const configForType = this.selectedConfig || (this.selectedLaunch && this.selectedName ? this.selectedLaunch.getConfiguration(this.selectedName) : undefined); - if (configForType) { - this.debugConfigurationTypeContext.set(configForType.type); + this.selectedType = dynamicConfig?.type || config?.type; + this.storageService.store(DEBUG_SELECTED_TYPE, this.selectedType, StorageScope.WORKSPACE, StorageTarget.MACHINE); + if (type) { + this.debugConfigurationTypeContext.set(type); } else { this.debugConfigurationTypeContext.reset(); } @@ -579,66 +423,6 @@ export class ConfigurationManager implements IConfigurationManager { } } - canSetBreakpointsIn(model: ITextModel): boolean { - const modeId = model.getLanguageIdentifier().language; - if (!modeId || modeId === 'jsonc' || modeId === 'log') { - // do not allow breakpoints in our settings files and output - return false; - } - if (this.configurationService.getValue('debug').allowBreakpointsEverywhere) { - return true; - } - - return this.breakpointModeIdsSet.has(modeId); - } - - getDebugger(type: string): Debugger | undefined { - return this.debuggers.find(dbg => strings.equalsIgnoreCase(dbg.type, type)); - } - - isDebuggerInterestedInLanguage(language: string): boolean { - return !!this.debuggers.find(a => language && a.languages && a.languages.indexOf(language) >= 0); - } - - async guessDebugger(type?: string): Promise { - if (type) { - const adapter = this.getDebugger(type); - return Promise.resolve(adapter); - } - - const activeTextEditorControl = this.editorService.activeTextEditorControl; - let candidates: Debugger[] | undefined; - if (isCodeEditor(activeTextEditorControl)) { - const model = activeTextEditorControl.getModel(); - const language = model ? model.getLanguageIdentifier().language : undefined; - const adapters = this.debuggers.filter(a => language && a.languages && a.languages.indexOf(language) >= 0); - if (adapters.length === 1) { - return adapters[0]; - } - if (adapters.length > 1) { - candidates = adapters; - } - } - - if (!candidates) { - await this.activateDebuggers('onDebugInitialConfigurations'); - candidates = this.debuggers.filter(dbg => dbg.hasInitialConfiguration() || dbg.hasConfigurationProvider()); - } - - candidates.sort((first, second) => first.label.localeCompare(second.label)); - const picks = candidates.map(c => ({ label: c.label, debugger: c })); - return this.quickInputService.pick<{ label: string, debugger: Debugger | undefined }>([...picks, { type: 'separator' }, { label: nls.localize('more', "More..."), debugger: undefined }], { placeHolder: nls.localize('selectDebug', "Select Environment") }) - .then(picked => { - if (picked && picked.debugger) { - return picked.debugger; - } - if (picked) { - this.commandService.executeCommand('debug.installAdditionalDebuggers'); - } - return undefined; - }); - } - async activateDebuggers(activationEvent: string, debugType?: string): Promise { const promises: Promise[] = [ this.extensionService.activateByEvent(activationEvent), @@ -654,7 +438,7 @@ export class ConfigurationManager implements IConfigurationManager { this.selectedName = selectedName; if (this.selectedName) { - this.storageService.store(DEBUG_SELECTED_CONFIG_NAME_KEY, this.selectedName, StorageScope.WORKSPACE); + this.storageService.store(DEBUG_SELECTED_CONFIG_NAME_KEY, this.selectedName, StorageScope.WORKSPACE, StorageTarget.MACHINE); } else { this.storageService.remove(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE); } @@ -668,8 +452,10 @@ export class ConfigurationManager implements IConfigurationManager { abstract class AbstractLaunch { protected abstract getConfig(): IGlobalConfig | undefined; - constructor(protected configurationManager: ConfigurationManager) { - } + constructor( + protected configurationManager: ConfigurationManager, + private readonly adapterManager: AdapterManager + ) { } getCompound(name: string): ICompound | undefined { const config = this.getConfig(); @@ -722,7 +508,7 @@ abstract class AbstractLaunch { async getInitialConfigurationContent(folderUri?: uri, type?: string, token?: CancellationToken): Promise { let content = ''; - const adapter = await this.configurationManager.guessDebugger(type); + const adapter = await this.adapterManager.guessDebugger(type); if (adapter) { const initialConfigs = await this.configurationManager.provideDebugConfigurations(folderUri, adapter.type, token || CancellationToken.None); content = await adapter.getInitialConfigurationContent(initialConfigs); @@ -739,13 +525,14 @@ class Launch extends AbstractLaunch implements ILaunch { constructor( configurationManager: ConfigurationManager, + adapterManager: AdapterManager, public workspace: IWorkspaceFolder, @IFileService private readonly fileService: IFileService, @ITextFileService private readonly textFileService: ITextFileService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService ) { - super(configurationManager); + super(configurationManager, adapterManager); } get uri(): uri { @@ -822,11 +609,12 @@ class Launch extends AbstractLaunch implements ILaunch { class WorkspaceLaunch extends AbstractLaunch implements ILaunch { constructor( configurationManager: ConfigurationManager, + adapterManager: AdapterManager, @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService ) { - super(configurationManager); + super(configurationManager, adapterManager); } get workspace(): undefined { @@ -873,10 +661,11 @@ class UserLaunch extends AbstractLaunch implements ILaunch { constructor( configurationManager: ConfigurationManager, + adapterManager: AdapterManager, @IConfigurationService private readonly configurationService: IConfigurationService, @IPreferencesService private readonly preferencesService: IPreferencesService ) { - super(configurationManager); + super(configurationManager, adapterManager); } get workspace(): undefined { @@ -900,7 +689,7 @@ class UserLaunch extends AbstractLaunch implements ILaunch { } async openConfigFile(preserveFocus: boolean): Promise<{ editor: IEditorPane | null, created: boolean }> { - const editor = await this.preferencesService.openGlobalSettings(true, { preserveFocus }); + const editor = await this.preferencesService.openGlobalSettings(true, { preserveFocus, revealSetting: { key: 'launch' } }); return ({ editor: withUndefinedAsNull(editor), created: false diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index 1d5b88bb0..d8ba1d554 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -9,7 +9,7 @@ import { Range } from 'vs/editor/common/core/range'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ServicesAccessor, registerEditorAction, EditorAction, IActionOptions } from 'vs/editor/browser/editorExtensions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IDebugService, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE, State, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, IBreakpoint, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, REPL_VIEW_ID, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, WATCH_VIEW_ID, CONTEXT_DEBUGGERS_AVAILABLE } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE, State, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, IBreakpoint, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, REPL_VIEW_ID, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, WATCH_VIEW_ID, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_EXCEPTION_WIDGET_VISIBLE } from 'vs/workbench/contrib/debug/common/debug'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { openBreakpointSource } from 'vs/workbench/contrib/debug/browser/breakpointsView'; @@ -20,6 +20,10 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { Action } from 'vs/base/common/actions'; import { getDomNodePagePosition } from 'vs/base/browser/dom'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { Position } from 'vs/editor/common/core/position'; +import { URI } from 'vs/base/common/uri'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { raceTimeout } from 'vs/base/common/async'; export const TOGGLE_BREAKPOINT_ID = 'editor.debug.action.toggleBreakpoint'; class ToggleBreakpointAction extends EditorAction { @@ -41,7 +45,7 @@ class ToggleBreakpointAction extends EditorAction { if (editor.hasModel()) { const debugService = accessor.get(IDebugService); const modelUri = editor.getModel().uri; - const canSet = debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel()); + const canSet = debugService.canSetBreakpointsIn(editor.getModel()); // Does not account for multi line selections, Set to remove multiple cursor on the same line const lineNumbers = [...new Set(editor.getSelections().map(s => s.getPosition().lineNumber))]; @@ -75,7 +79,7 @@ class ConditionalBreakpointAction extends EditorAction { const debugService = accessor.get(IDebugService); const position = editor.getPosition(); - if (position && editor.hasModel() && debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) { + if (position && editor.hasModel() && debugService.canSetBreakpointsIn(editor.getModel())) { editor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(position.lineNumber, undefined, BreakpointWidgetContext.CONDITION); } } @@ -97,7 +101,7 @@ class LogPointAction extends EditorAction { const debugService = accessor.get(IDebugService); const position = editor.getPosition(); - if (position && editor.hasModel() && debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) { + if (position && editor.hasModel() && debugService.canSetBreakpointsIn(editor.getModel())) { editor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(position.lineNumber, position.column, BreakpointWidgetContext.LOG_MESSAGE); } } @@ -123,14 +127,37 @@ export class RunToCursorAction extends EditorAction { async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { const debugService = accessor.get(IDebugService); - const uriIdentityService = accessor.get(IUriIdentityService); const focusedSession = debugService.getViewModel().focusedSession; if (debugService.state !== State.Stopped || !focusedSession) { return; } - let breakpointToRemove: IBreakpoint; - const oneTimeListener = focusedSession.onDidChangeState(() => { + const position = editor.getPosition(); + if (!(editor.hasModel() && position)) { + return; + } + + const uri = editor.getModel().uri; + const bpExists = !!(debugService.getModel().getBreakpoints({ column: position.column, lineNumber: position.lineNumber, uri }).length); + + let breakpointToRemove: IBreakpoint | undefined; + let threadToContinue = debugService.getViewModel().focusedThread; + if (!bpExists) { + const addResult = await this.addBreakpoints(accessor, uri, position); + if (addResult.thread) { + threadToContinue = addResult.thread; + } + + if (addResult.breakpoint) { + breakpointToRemove = addResult.breakpoint; + } + } + + if (!threadToContinue) { + return; + } + + const oneTimeListener = threadToContinue.session.onDidChangeState(() => { const state = focusedSession.state; if (state === State.Stopped || state === State.Inactive) { if (breakpointToRemove) { @@ -140,27 +167,90 @@ export class RunToCursorAction extends EditorAction { } }); - const position = editor.getPosition(); - if (editor.hasModel() && position) { - const uri = editor.getModel().uri; - const bpExists = !!(debugService.getModel().getBreakpoints({ column: position.column, lineNumber: position.lineNumber, uri }).length); - if (!bpExists) { - let column = 0; - const focusedStackFrame = debugService.getViewModel().focusedStackFrame; - if (focusedStackFrame && uriIdentityService.extUri.isEqual(focusedStackFrame.source.uri, uri) && focusedStackFrame.range.startLineNumber === position.lineNumber) { - // If the cursor is on a line different than the one the debugger is currently paused on, then send the breakpoint at column 0 on the line - // otherwise set it at the precise column #102199 - column = position.column; - } + await threadToContinue.continue(); + } - const breakpoints = await debugService.addBreakpoints(uri, [{ lineNumber: position.lineNumber, column }], false); - if (breakpoints && breakpoints.length) { - breakpointToRemove = breakpoints[0]; + private async addBreakpoints(accessor: ServicesAccessor, uri: URI, position: Position) { + const debugService = accessor.get(IDebugService); + const debugModel = debugService.getModel(); + const viewModel = debugService.getViewModel(); + const uriIdentityService = accessor.get(IUriIdentityService); + + let column = 0; + const focusedStackFrame = viewModel.focusedStackFrame; + if (focusedStackFrame && uriIdentityService.extUri.isEqual(focusedStackFrame.source.uri, uri) && focusedStackFrame.range.startLineNumber === position.lineNumber) { + // If the cursor is on a line different than the one the debugger is currently paused on, then send the breakpoint at column 0 on the line + // otherwise set it at the precise column #102199 + column = position.column; + } + + const breakpoints = await debugService.addBreakpoints(uri, [{ lineNumber: position.lineNumber, column }], false); + const breakpoint = breakpoints?.[0]; + if (!breakpoint) { + return { breakpoint: undefined, thread: viewModel.focusedThread }; + } + + // If the breakpoint was not initially verified, wait up to 2s for it to become so. + // Inherently racey if multiple sessions can verify async, but not solvable... + if (!breakpoint.verified) { + let listener: IDisposable; + await raceTimeout(new Promise(resolve => { + listener = debugModel.onDidChangeBreakpoints(() => { + if (breakpoint.verified) { + resolve(); + } + }); + }), 2000); + listener!.dispose(); + } + + // Look at paused threads for sessions that verified this bp. Prefer, in order: + const enum Score { + /** The focused thread */ + Focused, + /** Any other stopped thread of a session that verified the bp */ + Verified, + /** Any thread that verified and paused in the same file */ + VerifiedAndPausedInFile, + /** The focused thread if it verified the breakpoint */ + VerifiedAndFocused, + } + + let bestThread = viewModel.focusedThread; + let bestScore = Score.Focused; + for (const sessionId of breakpoint.sessionsThatVerified) { + const session = debugModel.getSession(sessionId); + if (!session) { + continue; + } + + const threads = session.getAllThreads().filter(t => t.stopped); + if (bestScore < Score.VerifiedAndFocused) { + if (viewModel.focusedThread && threads.includes(viewModel.focusedThread)) { + bestThread = viewModel.focusedThread; + bestScore = Score.VerifiedAndFocused; } } - await debugService.getViewModel().focusedThread!.continue(); + if (bestScore < Score.VerifiedAndPausedInFile) { + const pausedInThisFile = threads.find(t => { + const top = t.getTopStackFrame(); + return top && uriIdentityService.extUri.isEqual(top.source.uri, uri); + }); + + if (pausedInThisFile) { + bestThread = pausedInThisFile; + bestScore = Score.VerifiedAndPausedInFile; + } + } + + if (bestScore < Score.Verified) { + bestThread = threads[0]; + bestScore = Score.VerifiedAndPausedInFile; + } } + + return { thread: bestThread, breakpoint }; } } @@ -336,7 +426,7 @@ class GoToBreakpointAction extends EditorAction { } if (moveBreakpoint) { - return openBreakpointSource(moveBreakpoint, false, true, debugService, editorService); + return openBreakpointSource(moveBreakpoint, false, true, false, debugService, editorService); } } } @@ -364,6 +454,27 @@ class GoToPreviousBreakpointAction extends GoToBreakpointAction { } } +class CloseExceptionWidgetAction extends EditorAction { + + constructor() { + super({ + id: 'editor.debug.action.closeExceptionWidget', + label: nls.localize('closeExceptionWidget', "Close Exception Widget"), + alias: 'Close Exception Widget', + precondition: CONTEXT_EXCEPTION_WIDGET_VISIBLE, + kbOpts: { + primary: KeyCode.Escape, + weight: KeybindingWeight.EditorContrib + } + }); + } + + async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { + const contribution = editor.getContribution(EDITOR_CONTRIBUTION_ID); + contribution.closeExceptionWidget(); + } +} + export function registerEditorActions(): void { registerEditorAction(ToggleBreakpointAction); registerEditorAction(ConditionalBreakpointAction); @@ -375,4 +486,5 @@ export function registerEditorActions(): void { registerEditorAction(ShowDebugHoverAction); registerEditorAction(GoToNextBreakpointAction); registerEditorAction(GoToPreviousBreakpointAction); + registerEditorAction(CloseExceptionWidgetAction); } diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 4e39c57dd..dbd53aa5b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -21,7 +21,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IDebugEditorContribution, IDebugService, State, IStackFrame, IDebugConfiguration, IExpression, IExceptionInfo, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugEditorContribution, IDebugService, State, IStackFrame, IDebugConfiguration, IExpression, IExceptionInfo, IDebugSession, CONTEXT_EXCEPTION_WIDGET_VISIBLE } from 'vs/workbench/contrib/debug/common/debug'; import { ExceptionWidget } from 'vs/workbench/contrib/debug/browser/exceptionWidget'; import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets'; import { Position } from 'vs/editor/common/core/position'; @@ -39,6 +39,7 @@ import { HoverStartMode } from 'vs/editor/contrib/hover/hoverOperation'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { Event } from 'vs/base/common/event'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; const LAUNCH_JSON_REGEX = /\.vscode\/launch\.json$/; const INLINE_VALUE_DECORATION_KEY = 'inlinevaluedecoration'; @@ -173,6 +174,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { private hoverWidget: DebugHoverWidget; private hoverRange: Range | null = null; private mouseDown = false; + private exceptionWidgetVisible: IContextKey; private static readonly MEMOIZER = createMemoizer(); private exceptionWidget: ExceptionWidget | undefined; @@ -189,13 +191,15 @@ export class DebugEditorContribution implements IDebugEditorContribution { @ITelemetryService private readonly telemetryService: ITelemetryService, @IConfigurationService private readonly configurationService: IConfigurationService, @IHostService private readonly hostService: IHostService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IContextKeyService contextKeyService: IContextKeyService ) { this.hoverWidget = this.instantiationService.createInstance(DebugHoverWidget, this.editor); this.toDispose = []; this.registerListeners(); this.updateConfigurationWidgetVisibility(); this.codeEditorService.registerDecorationType(INLINE_VALUE_DECORATION_KEY, {}); + this.exceptionWidgetVisible = CONTEXT_EXCEPTION_WIDGET_VISIBLE.bindTo(contextKeyService); this.toggleExceptionWidget(); } @@ -355,7 +359,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { } private hideHoverWidget(): void { - if (!this.hideHoverScheduler.isScheduled() && this.hoverWidget.isVisible()) { + if (!this.hideHoverScheduler.isScheduled() && this.hoverWidget.willBeVisible()) { this.hideHoverScheduler.schedule(); } this.showHoverScheduler.cancel(); @@ -441,13 +445,20 @@ export class DebugEditorContribution implements IDebugEditorContribution { this.exceptionWidget = this.instantiationService.createInstance(ExceptionWidget, this.editor, exceptionInfo, debugSession); this.exceptionWidget.show({ lineNumber, column }, 0); + this.exceptionWidget.focus(); this.editor.revealLine(lineNumber); + this.exceptionWidgetVisible.set(true); } - private closeExceptionWidget(): void { + closeExceptionWidget(): void { if (this.exceptionWidget) { + const shouldFocusEditor = this.exceptionWidget.hasfocus(); this.exceptionWidget.dispose(); this.exceptionWidget = undefined; + this.exceptionWidgetVisible.set(false); + if (shouldFocusEditor) { + this.editor.focus(); + } } } diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index 4e877402d..d828f5bf4 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -31,7 +31,8 @@ import { coalesce } from 'vs/base/common/arrays'; import { IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView'; import { EvaluatableExpressionProviderRegistry } from 'vs/editor/common/modes'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { isMacintosh } from 'vs/base/common/platform'; const $ = dom.$; @@ -70,6 +71,7 @@ export class DebugHoverWidget implements IContentWidget { allowEditorOverflow = true; private _isVisible: boolean; + private showCancellationSource?: CancellationTokenSource; private domNode!: HTMLElement; private tree!: AsyncDataTree; private showAtPosition: Position | null; @@ -102,6 +104,8 @@ export class DebugHoverWidget implements IContentWidget { this.complexValueTitle = dom.append(this.complexValueContainer, $('.title')); this.treeContainer = dom.append(this.complexValueContainer, $('.debug-hover-tree')); this.treeContainer.setAttribute('role', 'tree'); + const tip = dom.append(this.complexValueContainer, $('.tip')); + tip.textContent = nls.localize('quickTip', 'Hold {0} key to switch to editor language hover', isMacintosh ? 'Option' : 'Alt'); const dataSource = new DebugHoverDataSource(); this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'DebugHover', this.treeContainer, new DebugHoverDelegate(), [this.instantiationService.createInstance(VariablesRenderer)], @@ -161,13 +165,17 @@ export class DebugHoverWidget implements IContentWidget { } isHovered(): boolean { - return this.domNode.matches(':hover'); + return !!this.domNode?.matches(':hover'); } isVisible(): boolean { return this._isVisible; } + willBeVisible(): boolean { + return !!this.showCancellationSource; + } + getId(): string { return DebugHoverWidget.ID; } @@ -177,6 +185,8 @@ export class DebugHoverWidget implements IContentWidget { } async showAt(range: Range, focus: boolean): Promise { + this.showCancellationSource?.cancel(); + const cancellationSource = this.showCancellationSource = new CancellationTokenSource(); const session = this.debugService.getViewModel().focusedSession; if (!session || !this.editor.hasModel()) { @@ -193,7 +203,7 @@ export class DebugHoverWidget implements IContentWidget { const supports = EvaluatableExpressionProviderRegistry.ordered(model); const promises = supports.map(support => { - return Promise.resolve(support.provideEvaluatableExpression(model, pos, CancellationToken.None)).then(expression => { + return Promise.resolve(support.provideEvaluatableExpression(model, pos, cancellationSource.token)).then(expression => { return expression; }, err => { //onUnexpectedExternalError(err); @@ -236,7 +246,7 @@ export class DebugHoverWidget implements IContentWidget { } } - if (!expression || (expression instanceof Expression && !expression.available)) { + if (cancellationSource.token.isCancellationRequested || !expression || (expression instanceof Expression && !expression.available)) { this.hide(); return; } @@ -315,6 +325,11 @@ export class DebugHoverWidget implements IContentWidget { hide(): void { + if (this.showCancellationSource) { + this.showCancellationSource.cancel(); + this.showCancellationSource = undefined; + } + if (!this._isVisible) { return; } diff --git a/src/vs/workbench/contrib/debug/browser/debugIcons.ts b/src/vs/workbench/contrib/debug/browser/debugIcons.ts new file mode 100644 index 000000000..b2203e381 --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/debugIcons.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 { Codicon } from 'vs/base/common/codicons'; +import { localize } from 'vs/nls'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; + +export const debugConsoleViewIcon = registerIcon('debug-console-view-icon', Codicon.debugConsole, localize('debugConsoleViewIcon', 'View icon of the debug console view.')); +export const runViewIcon = registerIcon('run-view-icon', Codicon.debugAlt, localize('runViewIcon', 'View icon of the run view.')); +export const variablesViewIcon = registerIcon('variables-view-icon', Codicon.debugAlt, localize('variablesViewIcon', 'View icon of the variables view.')); +export const watchViewIcon = registerIcon('watch-view-icon', Codicon.debugAlt, localize('watchViewIcon', 'View icon of the watch view.')); +export const callStackViewIcon = registerIcon('callstack-view-icon', Codicon.debugAlt, localize('callStackViewIcon', 'View icon of the call stack view.')); +export const breakpointsViewIcon = registerIcon('breakpoints-view-icon', Codicon.debugAlt, localize('breakpointsViewIcon', 'View icon of the breakpoints view.')); +export const loadedScriptsViewIcon = registerIcon('loaded-scripts-view-icon', Codicon.debugAlt, localize('loadedScriptsViewIcon', 'View icon of the loaded scripts view.')); + +export const debugBreakpoint = registerIcon('debug-breakpoint', Codicon.debugBreakpoint, localize('debugBreakpoint', 'Icon for breakpoints.')); +export const debugBreakpointDisabled = registerIcon('debug-breakpoint-disabled', Codicon.debugBreakpointDisabled, localize('debugBreakpointDisabled', 'Icon for disabled breakpoints.')); +export const debugBreakpointUnverified = registerIcon('debug-breakpoint-unverified', Codicon.debugBreakpointUnverified, localize('debugBreakpointUnverified', 'Icon for unverified breakpoints.')); +export const debugBreakpointHint = registerIcon('debug-hint', Codicon.debugHint, localize('debugBreakpointHint', 'Icon for breakpoint hints shown on hover in editor glyph margin.')); +export const debugBreakpointFunction = registerIcon('debug-breakpoint-function', Codicon.debugBreakpointFunction, localize('debugBreakpointFunction', 'Icon for function breakpoints.')); +export const debugBreakpointFunctionUnverified = registerIcon('debug-breakpoint-function-unverified', Codicon.debugBreakpointFunctionUnverified, localize('debugBreakpointFunctionUnverified', 'Icon for unverified function breakpoints.')); +export const debugBreakpointFunctionDisabled = registerIcon('debug-breakpoint-function-disabled', Codicon.debugBreakpointFunctionDisabled, localize('debugBreakpointFunctionDisabled', 'Icon for disabled function breakpoints.')); + +export const debugBreakpointUnsupported = registerIcon('debug-breakpoint-unsupported', Codicon.debugBreakpointUnsupported, localize('debugBreakpointUnsupported', 'Icon for unsupported breakpoints.')); + +export const debugBreakpointConditionalUnverified = registerIcon('debug-breakpoint-conditional-unverified', Codicon.debugBreakpointConditionalUnverified, localize('debugBreakpointConditionalUnverified', 'Icon for unverified conditional breakpoints.')); +export const debugBreakpointConditional = registerIcon('debug-breakpoint-conditional', Codicon.debugBreakpointConditional, localize('debugBreakpointConditional', 'Icon for conditional breakpoints.')); +export const debugBreakpointConditionalDisabled = registerIcon('debug-breakpoint-conditional-disabled', Codicon.debugBreakpointConditionalDisabled, localize('debugBreakpointConditionalDisabled', 'Icon for disabled conditional breakpoints.')); +export const debugBreakpointDataUnverified = registerIcon('debug-breakpoint-data-unverified', Codicon.debugBreakpointDataUnverified, localize('debugBreakpointDataUnverified', 'Icon for unverified data breakpoints.')); +export const debugBreakpointData = registerIcon('debug-breakpoint-data', Codicon.debugBreakpointData, localize('debugBreakpointData', 'Icon for data breakpoints.')); +export const debugBreakpointDataDisabled = registerIcon('debug-breakpoint-data-disabled', Codicon.debugBreakpointDataDisabled, localize('debugBreakpointDataDisabled', 'Icon for disabled data breakpoints.')); +export const debugBreakpointLogUnverified = registerIcon('debug-breakpoint-log-unverified', Codicon.debugBreakpointLogUnverified, localize('debugBreakpointLogUnverified', 'Icon for unverified log breakpoints.')); +export const debugBreakpointLog = registerIcon('debug-breakpoint-log', Codicon.debugBreakpointLog, localize('debugBreakpointLog', 'Icon for log breakpoints.')); +export const debugBreakpointLogDisabled = registerIcon('debug-breakpoint-log-disabled', Codicon.debugBreakpointLogDisabled, localize('debugBreakpointLogDisabled', 'Icon for disabled log breakpoint.')); + +export const debugStackframe = registerIcon('debug-stackframe', Codicon.debugStackframe, localize('debugStackframe', 'Icon for a stackframe shown in the editor glyph margin.')); +export const debugStackframeFocused = registerIcon('debug-stackframe-focused', Codicon.debugStackframeFocused, localize('debugStackframeFocused', 'Icon for a focused stackframe shown in the editor glyph margin.')); + +export const debugGripper = registerIcon('debug-gripper', Codicon.gripper, localize('debugGripper', 'Icon for the debug bar gripper.')); + +export const debugRestartFrame = registerIcon('debug-restart-frame', Codicon.debugRestartFrame, localize('debugRestartFrame', 'Icon for the debug restart frame action.')); + +export const debugStop = registerIcon('debug-stop', Codicon.debugStop, localize('debugStop', 'Icon for the debug stop action.')); +export const debugDisconnect = registerIcon('debug-disconnect', Codicon.debugDisconnect, localize('debugDisconnect', 'Icon for the debug disconnect action.')); +export const debugRestart = registerIcon('debug-restart', Codicon.debugRestart, localize('debugRestart', 'Icon for the debug restart action.')); +export const debugStepOver = registerIcon('debug-step-over', Codicon.debugStepOver, localize('debugStepOver', 'Icon for the debug step over action.')); +export const debugStepInto = registerIcon('debug-step-into', Codicon.debugStepInto, localize('debugStepInto', 'Icon for the debug step into action.')); +export const debugStepOut = registerIcon('debug-step-out', Codicon.debugStepOut, localize('debugStepOut', 'Icon for the debug step out action.')); +export const debugStepBack = registerIcon('debug-step-back', Codicon.debugStepBack, localize('debugStepBack', 'Icon for the debug step back action.')); +export const debugPause = registerIcon('debug-pause', Codicon.debugPause, localize('debugPause', 'Icon for the debug pause action.')); +export const debugContinue = registerIcon('debug-continue', Codicon.debugContinue, localize('debugContinue', 'Icon for the debug continue action.')); +export const debugReverseContinue = registerIcon('debug-reverse-continue', Codicon.debugReverseContinue, localize('debugReverseContinue', 'Icon for the debug reverse continue action.')); + +export const debugStart = registerIcon('debug-start', Codicon.debugStart, localize('debugStart', 'Icon for the debug start action.')); +export const debugConfigure = registerIcon('debug-configure', Codicon.gear, localize('debugConfigure', 'Icon for the debug configure action.')); +export const debugConsole = registerIcon('debug-console', Codicon.gear, localize('debugConsole', 'Icon for the debug console open action.')); + +export const debugCollapseAll = registerIcon('debug-collapse-all', Codicon.collapseAll, localize('debugCollapseAll', 'Icon for the collapse all action in the debug views.')); +export const callstackViewSession = registerIcon('callstack-view-session', Codicon.bug, localize('callstackViewSession', 'Icon for the session icon in the call stack view.')); +export const debugConsoleClearAll = registerIcon('debug-console-clear-all', Codicon.clearAll, localize('debugConsoleClearAll', 'Icon for the clear all action in the debug console.')); +export const watchExpressionsRemoveAll = registerIcon('watch-expressions-remove-all', Codicon.closeAll, localize('watchExpressionsRemoveAll', 'Icon for the remove all action in the watch view.')); +export const watchExpressionsAdd = registerIcon('watch-expressions-add', Codicon.add, localize('watchExpressionsAdd', 'Icon for the add action in the watch view.')); +export const watchExpressionsAddFuncBreakpoint = registerIcon('watch-expressions-add-function-breakpoint', Codicon.add, localize('watchExpressionsAddFuncBreakpoint', 'Icon for the add function breakpoint action in the watch view.')); + +export const breakpointsRemoveAll = registerIcon('breakpoints-remove-all', Codicon.closeAll, localize('breakpointsRemoveAll', 'Icon for the remove all action in the breakpoints view.')); +export const breakpointsActivate = registerIcon('breakpoints-activate', Codicon.activateBreakpoints, localize('breakpointsActivate', 'Icon for the activate action in the breakpoints view.')); + +export const debugConsoleEvaluationInput = registerIcon('debug-console-evaluation-input', Codicon.arrowSmallRight, localize('debugConsoleEvaluationInput', 'Icon for the debug evaluation input marker.')); +export const debugConsoleEvaluationPrompt = registerIcon('debug-console-evaluation-prompt', Codicon.chevronRight, localize('debugConsoleEvaluationPrompt', 'Icon for the debug evaluation prompt.')); diff --git a/src/vs/workbench/contrib/debug/browser/debugProgress.ts b/src/vs/workbench/contrib/debug/browser/debugProgress.ts index 6a8ef0397..67c975359 100644 --- a/src/vs/workbench/contrib/debug/browser/debugProgress.ts +++ b/src/vs/workbench/contrib/debug/browser/debugProgress.ts @@ -39,7 +39,7 @@ export class DebugProgressContribution implements IWorkbenchContribution { if (viewsService.isViewContainerVisible(VIEWLET_ID)) { progressService.withProgress({ location: VIEWLET_ID }, () => promise); } - const source = debugService.getConfigurationManager().getDebuggerLabel(session.configuration.type); + const source = debugService.getAdapterManager().getDebuggerLabel(session.configuration.type); progressService.withProgress({ location: ProgressLocation.Notification, title: progressStartEvent.body.title, diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts index b8aac7951..2b6f530c4 100644 --- a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts +++ b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts @@ -13,6 +13,8 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { matchesFuzzy } from 'vs/base/common/filters'; import { withNullAsUndefined } from 'vs/base/common/types'; import { ADD_CONFIGURATION_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; +import { debugConfigure } from 'vs/workbench/contrib/debug/browser/debugIcons'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { @@ -55,7 +57,7 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { @@ -64,7 +66,7 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { - await this.debugService.getConfigurationManager().selectConfiguration(config.launch, config.name); + await configManager.selectConfiguration(config.launch, config.name); try { await this.debugService.startDebugging(config.launch); } catch (error) { @@ -86,6 +88,26 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { + const highlights = matchesFuzzy(filter, name, true); + if (highlights) { + picks.push({ + label: name, + highlights: { label: highlights }, + accept: async () => { + await configManager.selectConfiguration(undefined, name, undefined, { type }); + try { + const { launch, getConfig } = configManager.selectedConfiguration; + const config = await getConfig(); + await this.debugService.startDebugging(launch, config); + } catch (error) { + this.notificationService.error(error); + } + } + }); + } + }); + dynamicProviders.forEach(provider => { picks.push({ label: `$(folder) ${provider.label}...`, @@ -93,6 +115,7 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { const pick = await provider.pick(); if (pick) { + await configManager.selectConfiguration(pick.launch, pick.config.name, pick.config, { type: pick.config.type }); this.debugService.startDebugging(pick.launch, pick.config); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 6f2078d03..e05a471f5 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -32,7 +32,7 @@ import { IAction, Action } from 'vs/base/common/actions'; import { deepClone, equals } from 'vs/base/common/objects'; import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IStackFrame, getStateLabel, IDebugSessionOptions, CONTEXT_DEBUG_UX, REPL_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, IGlobalConfig, CALLSTACK_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IStackFrame, getStateLabel, IDebugSessionOptions, CONTEXT_DEBUG_UX, REPL_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, IGlobalConfig, CALLSTACK_VIEW_ID, IAdapterManager, IExceptionBreakpoint } from 'vs/workbench/contrib/debug/common/debug'; import { getExtensionHostDebugSession } from 'vs/workbench/contrib/debug/common/debugUtils'; import { isErrorWithActions } from 'vs/base/common/errorsWithActions'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -49,6 +49,8 @@ import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompou import { ICommandService } from 'vs/platform/commands/common/commands'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { AdapterManager } from 'vs/workbench/contrib/debug/browser/debugAdapterManager'; +import { ITextModel } from 'vs/editor/common/model'; export class DebugService implements IDebugService { declare readonly _serviceBrand: undefined; @@ -63,6 +65,7 @@ export class DebugService implements IDebugService { private telemetry: DebugTelemetry; private taskRunner: DebugTaskRunner; private configurationManager: ConfigurationManager; + private adapterManager: AdapterManager; private toDispose: IDisposable[]; private debugType!: IContextKey; private debugState!: IContextKey; @@ -105,7 +108,8 @@ export class DebugService implements IDebugService { this._onWillNewSession = new Emitter(); this._onDidEndSession = new Emitter(); - this.configurationManager = this.instantiationService.createInstance(ConfigurationManager); + this.adapterManager = this.instantiationService.createInstance(AdapterManager); + this.configurationManager = this.instantiationService.createInstance(ConfigurationManager, this.adapterManager); this.toDispose.push(this.configurationManager); contextKeyService.bufferChangeEvents(() => { @@ -113,7 +117,7 @@ export class DebugService implements IDebugService { this.debugState = CONTEXT_DEBUG_STATE.bindTo(contextKeyService); this.inDebugMode = CONTEXT_IN_DEBUG_MODE.bindTo(contextKeyService); this.debugUx = CONTEXT_DEBUG_UX.bindTo(contextKeyService); - this.debugUx.set((this.configurationManager.hasDebuggers() && !!this.configurationManager.selectedConfiguration.name) ? 'default' : 'simple'); + this.debugUx.set((this.adapterManager.hasDebuggers() && !!this.configurationManager.selectedConfiguration.name) ? 'default' : 'simple'); this.breakpointsExist = CONTEXT_BREAKPOINTS_EXIST.bindTo(contextKeyService); }); @@ -162,8 +166,8 @@ export class DebugService implements IDebugService { this.toDispose.push(this.viewModel.onDidFocusSession(() => { this.onStateChange(); })); - this.toDispose.push(Event.any(this.configurationManager.onDidRegisterDebugger, this.configurationManager.onDidSelectConfiguration)(() => { - this.debugUx.set(!!(this.state !== State.Inactive || (this.configurationManager.selectedConfiguration.name && this.configurationManager.hasDebuggers())) ? 'default' : 'simple'); + this.toDispose.push(Event.any(this.adapterManager.onDidRegisterDebugger, this.configurationManager.onDidSelectConfiguration)(() => { + this.debugUx.set(!!(this.state !== State.Inactive || (this.configurationManager.selectedConfiguration.name && this.adapterManager.hasDebuggers())) ? 'default' : 'simple'); })); this.toDispose.push(this.model.onDidChangeCallStack(() => { const numberOfSessions = this.model.getSessions().filter(s => !s.parentSession).length; @@ -192,6 +196,10 @@ export class DebugService implements IDebugService { return this.configurationManager; } + getAdapterManager(): IAdapterManager { + return this.adapterManager; + } + sourceIsNotAvailable(uri: uri): void { this.model.sourceIsNotAvailable(uri); } @@ -245,7 +253,7 @@ export class DebugService implements IDebugService { this.debugState.set(getStateLabel(state)); this.inDebugMode.set(state !== State.Inactive); // Only show the simple ux if debug is not yet started and if no launch.json exists - this.debugUx.set(((state !== State.Inactive && state !== State.Initializing) || (this.configurationManager.hasDebuggers() && this.configurationManager.selectedConfiguration.name)) ? 'default' : 'simple'); + this.debugUx.set(((state !== State.Inactive && state !== State.Initializing) || (this.adapterManager.hasDebuggers() && this.configurationManager.selectedConfiguration.name)) ? 'default' : 'simple'); }); this.previousState = state; this._onDidChangeState.fire(state); @@ -297,7 +305,7 @@ export class DebugService implements IDebugService { const sessions = this.model.getSessions(); const alreadyRunningMessage = nls.localize('configurationAlreadyRunning', "There is already a debug configuration \"{0}\" running.", configOrName); - if (sessions.some(s => s.configuration.name === configOrName && (!launch || !launch.workspace || !s.root || this.uriIdentityService.extUri.isEqual(s.root.uri, launch.workspace.uri)))) { + if (sessions.some(s => (s.configuration.name === configOrName && s.root === launch.workspace) && (!launch || !launch.workspace || !s.root || this.uriIdentityService.extUri.isEqual(s.root.uri, launch.workspace.uri)))) { throw new Error(alreadyRunningMessage); } if (compound && compound.configurations && sessions.some(p => compound!.configurations.indexOf(p.configuration.name) !== -1)) { @@ -397,7 +405,7 @@ export class DebugService implements IDebugService { const unresolvedConfig = deepClone(config); if (!type) { - const guess = await this.configurationManager.guessDebugger(); + const guess = await this.adapterManager.guessDebugger(); if (guess) { type = guess.type; } @@ -437,7 +445,7 @@ export class DebugService implements IDebugService { } resolvedConfig = cfg; - if (!this.configurationManager.getDebugger(resolvedConfig.type) || (configByProviders.request !== 'attach' && configByProviders.request !== 'launch')) { + if (!this.adapterManager.getDebugger(resolvedConfig.type) || (configByProviders.request !== 'attach' && configByProviders.request !== 'launch')) { let message: string; if (configByProviders.request !== 'attach' && configByProviders.request !== 'launch') { message = configByProviders.request ? nls.localize('debugRequestNotSupported', "Attribute '{0}' has an unsupported value '{1}' in the chosen debug configuration.", 'request', configByProviders.request) @@ -549,7 +557,7 @@ export class DebugService implements IDebugService { } private async launchOrAttachToSession(session: IDebugSession, forceFocus = false): Promise { - const dbgr = this.configurationManager.getDebugger(session.configuration.type); + const dbgr = this.adapterManager.getDebugger(session.configuration.type); try { await session.initialize(dbgr!); await session.launchOrAttach(session.configuration); @@ -754,7 +762,7 @@ export class DebugService implements IDebugService { } private async substituteVariables(launch: ILaunch | undefined, config: IConfig): Promise { - const dbg = this.configurationManager.getDebugger(config.type); + const dbg = this.adapterManager.getDebugger(config.type); if (dbg) { let folder: IWorkspaceFolder | undefined = undefined; if (launch && launch.workspace) { @@ -842,6 +850,10 @@ export class DebugService implements IDebugService { //---- breakpoints + canSetBreakpointsIn(model: ITextModel): boolean { + return this.adapterManager.canSetBreakpointsIn(model); + } + async enableOrDisableBreakpoints(enable: boolean, breakpoint?: IEnablement): Promise { if (breakpoint) { this.model.setEnablement(breakpoint, enable); @@ -906,7 +918,7 @@ export class DebugService implements IDebugService { addFunctionBreakpoint(name?: string, id?: string): void { const newFunctionBreakpoint = this.model.addFunctionBreakpoint(name || '', id); - this.viewModel.setSelectedFunctionBreakpoint(newFunctionBreakpoint); + this.viewModel.setSelectedBreakpoint(newFunctionBreakpoint); } async renameFunctionBreakpoint(id: string, newFunctionName: string): Promise { @@ -934,6 +946,12 @@ export class DebugService implements IDebugService { await this.sendDataBreakpoints(); } + async setExceptionBreakpointCondition(exceptionBreakpoint: IExceptionBreakpoint, condition: string | undefined): Promise { + this.model.setExceptionBreakpointCondition(exceptionBreakpoint, condition); + this.debugStorage.storeBreakpoints(this.model); + await this.sendExceptionBreakpoints(); + } + async sendAllBreakpoints(session?: IDebugSession): Promise { await Promise.all(distinct(this.model.getBreakpoints(), bp => bp.uri.toString()).map(bp => this.sendBreakpoints(bp.uri, false, session))); await this.sendFunctionBreakpoints(session); diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 663ff7109..9b8c75afa 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -394,7 +394,18 @@ export class DebugSession implements IDebugSession { } if (this.raw.readyForBreakpoints) { - await this.raw.setExceptionBreakpoints({ filters: exbpts.map(exb => exb.filter) }); + const args: DebugProtocol.SetExceptionBreakpointsArguments = this.capabilities.supportsExceptionFilterOptions ? { + filters: [], + filterOptions: exbpts.map(exb => { + if (exb.condition) { + return { filterId: exb.filter, condition: exb.condition }; + } + + return { filterId: exb.filter }; + }) + } : { filters: exbpts.map(exb => exb.filter) }; + + await this.raw.setExceptionBreakpoints(args); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 93635f7ff..8f62ac8d9 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -16,9 +16,9 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IDebugConfiguration, IDebugService, State } from 'vs/workbench/contrib/debug/common/debug'; import { FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { registerThemingParticipant, IThemeService, Themable } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, IThemeService, Themable, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { registerColor, contrastBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -29,6 +29,7 @@ import { IMenu, IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from ' import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { FocusSessionAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; const DEBUG_TOOLBAR_POSITION_KEY = 'debug.actionswidgetposition'; const DEBUG_TOOLBAR_Y_KEY = 'debug.actionswidgety'; @@ -64,7 +65,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { this.$el = dom.$('div.debug-toolbar'); this.$el.style.top = `${layoutService.offset?.top ?? 0}px`; - this.dragArea = dom.append(this.$el, dom.$('div.drag-area.codicon.codicon-gripper')); + this.dragArea = dom.append(this.$el, dom.$('div.drag-area' + ThemeIcon.asCSSSelector(icons.debugGripper))); const actionBarContainer = dom.append(this.$el, dom.$('div.action-bar-container')); this.debugToolBarMenu = menuService.createMenu(MenuId.DebugToolBar, contextKeyService); @@ -169,7 +170,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { const left = dom.getComputedStyle(this.$el).left; if (left) { const position = parseFloat(left) / window.innerWidth; - this.storageService.store(DEBUG_TOOLBAR_POSITION_KEY, position, StorageScope.GLOBAL); + this.storageService.store(DEBUG_TOOLBAR_POSITION_KEY, position, StorageScope.GLOBAL, StorageTarget.MACHINE); } } @@ -180,7 +181,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { this.$el.style.backgroundColor = this.getColor(debugToolBarBackground) || ''; const widgetShadowColor = this.getColor(widgetShadow); - this.$el.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : ''; + this.$el.style.boxShadow = widgetShadowColor ? `0 0 8px 2px ${widgetShadowColor}` : ''; const contrastBorderColor = this.getColor(contrastBorder); const borderColor = this.getColor(debugToolBarBorder); @@ -220,7 +221,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { if ((y < titleAreaHeight / 2) || (y > titleAreaHeight + titleAreaHeight / 2)) { const moveToTop = y < titleAreaHeight; this.setYCoordinate(moveToTop ? 0 : titleAreaHeight); - this.storageService.store(DEBUG_TOOLBAR_Y_KEY, moveToTop ? 0 : 2 * titleAreaHeight, StorageScope.GLOBAL); + this.storageService.store(DEBUG_TOOLBAR_Y_KEY, moveToTop ? 0 : 2 * titleAreaHeight, StorageScope.GLOBAL, StorageTarget.MACHINE); } } @@ -351,51 +352,51 @@ registerThemingParticipant((theme, collector) => { const debugIconStartColor = theme.getColor(debugIconStartForeground); if (debugIconStartColor) { - collector.addRule(`.monaco-workbench .codicon-debug-start { color: ${debugIconStartColor} !important; }`); + collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStart)} { color: ${debugIconStartColor} !important; }`); } const debugIconPauseColor = theme.getColor(debugIconPauseForeground); if (debugIconPauseColor) { - collector.addRule(`.monaco-workbench .codicon-debug-pause { color: ${debugIconPauseColor} !important; }`); + collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugPause)} { color: ${debugIconPauseColor} !important; }`); } const debugIconStopColor = theme.getColor(debugIconStopForeground); if (debugIconStopColor) { - collector.addRule(`.monaco-workbench .codicon-debug-stop, .monaco-workbench .debug-view-content .codicon-record { color: ${debugIconStopColor} !important; }`); + collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStop)} { color: ${debugIconStopColor} !important; }`); } const debugIconDisconnectColor = theme.getColor(debugIconDisconnectForeground); if (debugIconDisconnectColor) { - collector.addRule(`.monaco-workbench .codicon-debug-disconnect { color: ${debugIconDisconnectColor} !important; }`); + collector.addRule(`.monaco-workbench .debug-view-content ${ThemeIcon.asCSSSelector(icons.debugDisconnect)}, .monaco-workbench .debug-toolbar ${ThemeIcon.asCSSSelector(icons.debugDisconnect)} { color: ${debugIconDisconnectColor} !important; }`); } const debugIconRestartColor = theme.getColor(debugIconRestartForeground); if (debugIconRestartColor) { - collector.addRule(`.monaco-workbench .codicon-debug-restart, .monaco-workbench .codicon-debug-restart-frame { color: ${debugIconRestartColor} !important; }`); + collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugRestart)}, .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugRestartFrame)} { color: ${debugIconRestartColor} !important; }`); } const debugIconStepOverColor = theme.getColor(debugIconStepOverForeground); if (debugIconStepOverColor) { - collector.addRule(`.monaco-workbench .codicon-debug-step-over { color: ${debugIconStepOverColor} !important; }`); + collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStepOver)} { color: ${debugIconStepOverColor} !important; }`); } const debugIconStepIntoColor = theme.getColor(debugIconStepIntoForeground); if (debugIconStepIntoColor) { - collector.addRule(`.monaco-workbench .codicon-debug-step-into { color: ${debugIconStepIntoColor} !important; }`); + collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStepInto)} { color: ${debugIconStepIntoColor} !important; }`); } const debugIconStepOutColor = theme.getColor(debugIconStepOutForeground); if (debugIconStepOutColor) { - collector.addRule(`.monaco-workbench .codicon-debug-step-out { color: ${debugIconStepOutColor} !important; }`); + collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStepOut)} { color: ${debugIconStepOutColor} !important; }`); } const debugIconContinueColor = theme.getColor(debugIconContinueForeground); if (debugIconContinueColor) { - collector.addRule(`.monaco-workbench .codicon-debug-continue,.monaco-workbench .codicon-debug-reverse-continue { color: ${debugIconContinueColor} !important; }`); + collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugContinue)}, .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugReverseContinue)} { color: ${debugIconContinueColor} !important; }`); } const debugIconStepBackColor = theme.getColor(debugIconStepBackForeground); if (debugIconStepBackColor) { - collector.addRule(`.monaco-workbench .codicon-debug-step-back { color: ${debugIconStepBackColor} !important; }`); + collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStepBack)} { color: ${debugIconStepBackColor} !important; }`); } }); diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index f13479c4e..91187dd3a 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -15,7 +15,7 @@ import { IProgressService } from 'vs/platform/progress/common/progress'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -33,6 +33,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { ShowViewletAction } from 'vs/workbench/browser/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { debugConsole } from 'vs/workbench/contrib/debug/browser/debugIcons'; export class DebugViewPaneContainer extends ViewPaneContainer { @@ -247,7 +248,7 @@ export class OpenDebugConsoleAction extends ToggleViewAction { @IContextKeyService contextKeyService: IContextKeyService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { - super(id, label, REPL_VIEW_ID, viewsService, viewDescriptorService, contextKeyService, layoutService, 'codicon-debug-console'); + super(id, label, REPL_VIEW_ID, viewsService, viewDescriptorService, contextKeyService, layoutService, ThemeIcon.asClassName(debugConsole)); } } diff --git a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts index 93f702fa3..67a12c489 100644 --- a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts @@ -8,14 +8,17 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IExceptionInfo, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; +import { IExceptionInfo, IDebugSession, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID } from 'vs/workbench/contrib/debug/common/debug'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Action } from 'vs/base/common/actions'; +import { widgetClose } from 'vs/platform/theme/common/iconRegistry'; const $ = dom.$; // theming @@ -25,18 +28,19 @@ export const debugExceptionWidgetBackground = registerColor('debugExceptionWidge export class ExceptionWidget extends ZoneWidget { - private _backgroundColor?: Color; + private backgroundColor: Color | undefined; - constructor(editor: ICodeEditor, private exceptionInfo: IExceptionInfo, private debugSession: IDebugSession | undefined, + constructor( + editor: ICodeEditor, + private exceptionInfo: IExceptionInfo, + private debugSession: IDebugSession | undefined, @IThemeService themeService: IThemeService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { - super(editor, { showFrame: true, showArrow: true, frameWidth: 1, className: 'exception-widget-container' }); + super(editor, { showFrame: true, showArrow: true, isAccessible: true, frameWidth: 1, className: 'exception-widget-container' }); - this._backgroundColor = Color.white; - - this._applyTheme(themeService.getColorTheme()); - this._disposables.add(themeService.onDidColorThemeChange(this._applyTheme.bind(this))); + this.applyTheme(themeService.getColorTheme()); + this._disposables.add(themeService.onDidColorThemeChange(this.applyTheme.bind(this))); this.create(); const onDidLayoutChangeScheduler = new RunOnceScheduler(() => this._doLayout(undefined, undefined), 50); @@ -44,8 +48,8 @@ export class ExceptionWidget extends ZoneWidget { this._disposables.add(onDidLayoutChangeScheduler); } - private _applyTheme(theme: IColorTheme): void { - this._backgroundColor = theme.getColor(debugExceptionWidgetBackground); + private applyTheme(theme: IColorTheme): void { + this.backgroundColor = theme.getColor(debugExceptionWidgetBackground); const frameColor = theme.getColor(debugExceptionWidgetBorder); this.style({ arrowColor: frameColor, @@ -55,7 +59,7 @@ export class ExceptionWidget extends ZoneWidget { protected _applyStyles(): void { if (this.container) { - this.container.style.backgroundColor = this._backgroundColor ? this._backgroundColor.toString() : ''; + this.container.style.backgroundColor = this.backgroundColor ? this.backgroundColor.toString() : ''; } super._applyStyles(); } @@ -66,14 +70,27 @@ export class ExceptionWidget extends ZoneWidget { const fontInfo = this.editor.getOption(EditorOption.fontInfo); container.style.fontSize = `${fontInfo.fontSize}px`; container.style.lineHeight = `${fontInfo.lineHeight}px`; + container.tabIndex = 0; + const title = $('.title'); + const label = $('.label'); + dom.append(title, label); + const actions = $('.actions'); + dom.append(title, actions); + label.textContent = this.exceptionInfo.id ? nls.localize('exceptionThrownWithId', 'Exception has occurred: {0}', this.exceptionInfo.id) : nls.localize('exceptionThrown', 'Exception has occurred.'); + let ariaLabel = label.textContent; + + const actionBar = new ActionBar(actions); + actionBar.push(new Action('editor.closeExceptionWidget', nls.localize('close', "Close"), ThemeIcon.asClassName(widgetClose), true, async () => { + const contribution = this.editor.getContribution(EDITOR_CONTRIBUTION_ID); + contribution.closeExceptionWidget(); + }), { label: false, icon: true }); - let title = $('.title'); - title.textContent = this.exceptionInfo.id ? nls.localize('exceptionThrownWithId', 'Exception has occurred: {0}', this.exceptionInfo.id) : nls.localize('exceptionThrown', 'Exception has occurred.'); dom.append(container, title); if (this.exceptionInfo.description) { let description = $('.description'); description.textContent = this.exceptionInfo.description; + ariaLabel += ', ' + this.exceptionInfo.description; dom.append(container, description); } @@ -83,7 +100,9 @@ export class ExceptionWidget extends ZoneWidget { const linkedStackTrace = linkDetector.linkify(this.exceptionInfo.details.stackTrace, true, this.debugSession ? this.debugSession.root : undefined); stackTrace.appendChild(linkedStackTrace); dom.append(container, stackTrace); + ariaLabel += ', ' + this.exceptionInfo.details.stackTrace; } + container.setAttribute('aria-label', ariaLabel); } protected _doLayout(_heightInPixel: number | undefined, _widthInPixel: number | undefined): void { @@ -96,4 +115,13 @@ export class ExceptionWidget extends ZoneWidget { this._relayout(computedLinesNumber); } + + focus(): void { + // Focus into the container for accessibility purposes so the exception and stack trace gets read + this.container?.focus(); + } + + hasfocus(): boolean { + return dom.isAncestor(document.activeElement, this.container); + } } diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index e115fe0be..198da9108 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -16,7 +16,7 @@ import { IDebugSession, IDebugService, CONTEXT_LOADED_SCRIPTS_ITEM_TYPE } from ' import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { tildify } from 'vs/base/common/labels'; +import { normalizeDriveLetter, tildify } from 'vs/base/common/labels'; import { isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { ltrim } from 'vs/base/common/strings'; @@ -350,7 +350,9 @@ class SessionTreeItem extends BaseTreeItem { } else { // on unix try to tildify absolute paths path = normalize(path); - if (!isWindows) { + if (isWindows) { + path = normalizeDriveLetter(path); + } else { path = tildify(path, (await this._pathService.userHome()).fsPath); } } diff --git a/src/vs/workbench/contrib/debug/browser/media/debugHover.css b/src/vs/workbench/contrib/debug/browser/media/debugHover.css index 6c48d940b..8f5ae2c96 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugHover.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugHover.css @@ -19,7 +19,8 @@ max-width: 700px; } -.monaco-editor .debug-hover-widget .complex-value .title { +.monaco-editor .debug-hover-widget .complex-value .title, +.monaco-editor .debug-hover-widget .complex-value .tip { padding-left: 15px; padding-right: 2px; font-size: 11px; @@ -29,9 +30,17 @@ height: 18px; overflow: hidden; white-space: pre; +} + +.monaco-editor .debug-hover-widget .complex-value .title { border-bottom: 1px solid rgba(128, 128, 128, 0.35); } +.monaco-editor .debug-hover-widget .complex-value .tip { + border-top: 1px solid rgba(128, 128, 128, 0.35); + opacity: 0.5; +} + .monaco-editor .debug-hover-widget .debug-hover-tree { line-height: 18px; cursor: pointer; diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index e0b6acb32..47cafc6ae 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -84,6 +84,7 @@ /* Make icons and text the same color as the list foreground on focus selection */ .debug-pane .monaco-list:focus .monaco-list-row.selected .state.label, +.debug-pane .monaco-list:focus .monaco-list-row.selected .load-all, .debug-pane .monaco-list:focus .monaco-list-row.selected.focused .state.label, .debug-pane .monaco-list:focus .monaco-list-row.selected .codicon, .debug-pane .monaco-list:focus .monaco-list-row.selected.focused .codicon { @@ -317,10 +318,10 @@ justify-content: center; } -.debug-pane .debug-breakpoints .breakpoint > .file-path { +.debug-pane .debug-breakpoints .breakpoint > .file-path, +.debug-pane .debug-breakpoints .breakpoint.exception > .condition { opacity: 0.7; - font-size: 0.9em; - margin-left: 0.8em; + margin-left: 0.9em; flex: 1; text-overflow: ellipsis; overflow: hidden; diff --git a/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css b/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css index df149d3b4..f9d7846d9 100644 --- a/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css +++ b/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css @@ -15,9 +15,17 @@ } .monaco-editor .zone-widget .zone-widget-container.exception-widget .title { + display: flex; +} + +.monaco-editor .zone-widget .zone-widget-container.exception-widget .title .label { font-weight: bold; } +.monaco-editor .zone-widget .zone-widget-container.exception-widget .title .actions { + flex: 1; +} + .monaco-editor .zone-widget .zone-widget-container.exception-widget .description, .monaco-editor .zone-widget .zone-widget-container.exception-widget .stack-trace { font-family: var(--monaco-monospace-font); @@ -27,7 +35,7 @@ margin-top: 0.5em; } -.monaco-editor .zone-widget .zone-widget-container.exception-widget a { +.monaco-editor .zone-widget .zone-widget-container.exception-widget .stack-trace a { text-decoration: underline; cursor: pointer; } diff --git a/src/vs/workbench/contrib/debug/browser/media/repl.css b/src/vs/workbench/contrib/debug/browser/media/repl.css index b2a474076..04de3a219 100644 --- a/src/vs/workbench/contrib/debug/browser/media/repl.css +++ b/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -38,6 +38,15 @@ margin-right: 4px; } +.monaco-workbench .repl .repl-tree .output.expression.value-and-source .count-badge-wrapper { + margin-right: 4px; +} + +/* Allow the badge to be a bit shorter so it does not look cut off */ +.monaco-workbench .repl .repl-tree .output.expression.value-and-source .count-badge-wrapper .monaco-count-badge { + min-height: 16px; +} + .monaco-workbench .repl .repl-tree .monaco-tl-contents .arrow { position:absolute; left: 2px; diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index dce391245..a70036599 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -620,10 +620,10 @@ export class RawDebugSession implements IDisposable { } } - let env: IProcessEnvironment = {}; - if (vscodeArgs.env) { + let env: IProcessEnvironment = processEnv; + if (vscodeArgs.env && Object.keys(vscodeArgs.env).length > 0) { // merge environment variables into a copy of the process.env - env = objects.mixin(processEnv, vscodeArgs.env); + env = objects.mixin(objects.deepClone(processEnv), vscodeArgs.env); // and delete some if necessary Object.keys(env).filter(k => env[k] === null).forEach(key => delete env[key]); } diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index cbc9401ec..74467fa80 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -19,8 +19,8 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { memoize } from 'vs/base/common/decorators'; import { dispose, IDisposable, Disposable } from 'vs/base/common/lifecycle'; @@ -59,10 +59,12 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { EDITOR_FONT_DEFAULTS, EditorOption } from 'vs/editor/common/config/editorOptions'; import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor'; import { ReplFilter, ReplFilterState, ReplFilterActionViewItem } from 'vs/workbench/contrib/debug/browser/replFilter'; +import { debugConsoleClearAll, debugConsoleEvaluationPrompt } from 'vs/workbench/contrib/debug/browser/debugIcons'; const $ = dom.$; const HISTORY_STORAGE_KEY = 'debug.repl.history'; +const FILTER_HISTORY_STORAGE_KEY = 'debug.repl.filterHistory'; const DECORATION_KEY = 'replinputdecoration'; const FILTER_ACTION_ID = `workbench.actions.treeView.repl.filter`; @@ -303,7 +305,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { if (this.styleElement) { const debugConsole = this.configurationService.getValue('debug').console; const fontSize = debugConsole.fontSize; - const fontFamily = debugConsole.fontFamily === 'default' ? 'var(--monaco-monospace-font)' : debugConsole.fontFamily; + const fontFamily = debugConsole.fontFamily === 'default' ? 'var(--monaco-monospace-font)' : `'${debugConsole.fontFamily}'`; const lineHeight = debugConsole.lineHeight ? `${debugConsole.lineHeight}px` : '1.4em'; const backgroundColor = this.themeService.getColorTheme().getColor(this.getBackgroundColor()); @@ -462,7 +464,9 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { const session = (this.tree ? this.tree.getInput() : undefined) ?? this.debugService.getViewModel().focusedSession; return this.instantiationService.createInstance(SelectReplActionViewItem, this.selectReplAction, session); } else if (action.id === FILTER_ACTION_ID) { - this.filterActionViewItem = this.instantiationService.createInstance(ReplFilterActionViewItem, action, localize({ key: 'workbench.debug.filter.placeholder', comment: ['Text in the brackets after e.g. is not localizable'] }, "Filter (e.g. text, !exclude)"), this.filterState); + const filterHistory = JSON.parse(this.storageService.get(FILTER_HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')) as string[]; + this.filterActionViewItem = this.instantiationService.createInstance(ReplFilterActionViewItem, action, + localize({ key: 'workbench.debug.filter.placeholder', comment: ['Text in the brackets after e.g. is not localizable'] }, "Filter (e.g. text, !exclude)"), this.filterState, filterHistory); return this.filterActionViewItem; } @@ -592,7 +596,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { private createReplInput(container: HTMLElement): void { this.replInputContainer = dom.append(container, $('.repl-input-wrapper')); - dom.append(this.replInputContainer, $('.repl-input-chevron.codicon.codicon-chevron-right')); + dom.append(this.replInputContainer, $('.repl-input-chevron' + ThemeIcon.asCSSSelector(debugConsoleEvaluationPrompt))); const { scopedContextKeyService, historyNavigationEnablement } = createAndBindHistoryNavigationWidgetScopedContextKeyService(this.contextKeyService, { target: this.replInputContainer, historyNavigator: this }); this.historyNavigationEnablement = historyNavigationEnablement; @@ -708,10 +712,18 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { saveState(): void { const replHistory = this.history.getHistory(); if (replHistory.length) { - this.storageService.store(HISTORY_STORAGE_KEY, JSON.stringify(replHistory), StorageScope.WORKSPACE); + this.storageService.store(HISTORY_STORAGE_KEY, JSON.stringify(replHistory), StorageScope.WORKSPACE, StorageTarget.USER); } else { this.storageService.remove(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE); } + if (this.filterActionViewItem) { + const filterHistory = this.filterActionViewItem.getHistory(); + if (filterHistory.length) { + this.storageService.store(FILTER_HISTORY_STORAGE_KEY, JSON.stringify(filterHistory), StorageScope.WORKSPACE, StorageTarget.USER); + } else { + this.storageService.remove(FILTER_HISTORY_STORAGE_KEY, StorageScope.WORKSPACE); + } + } super.saveState(); } @@ -845,7 +857,7 @@ export class ClearReplAction extends Action { constructor(id: string, label: string, @IViewsService private readonly viewsService: IViewsService ) { - super(id, label, 'debug-action codicon-clear-all'); + super(id, label, 'debug-action ' + ThemeIcon.asClassName(debugConsoleClearAll)); } async run(): Promise { diff --git a/src/vs/workbench/contrib/debug/browser/replFilter.ts b/src/vs/workbench/contrib/debug/browser/replFilter.ts index 143d555a5..3cb5e45d8 100644 --- a/src/vs/workbench/contrib/debug/browser/replFilter.ts +++ b/src/vs/workbench/contrib/debug/browser/replFilter.ts @@ -136,6 +136,7 @@ export class ReplFilterActionViewItem extends BaseActionViewItem { action: IAction, private placeholder: string, private filters: ReplFilterState, + private history: string[], @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService private readonly themeService: IThemeService, @IContextViewService private readonly contextViewService: IContextViewService) { @@ -159,6 +160,10 @@ export class ReplFilterActionViewItem extends BaseActionViewItem { this.filterInputBox.focus(); } + getHistory(): string[] { + return this.filterInputBox.getHistory(); + } + private clearFilterText(): void { this.filterInputBox.value = ''; } @@ -166,7 +171,7 @@ export class ReplFilterActionViewItem extends BaseActionViewItem { private createInput(container: HTMLElement): void { this.filterInputBox = this._register(this.instantiationService.createInstance(ContextScopedHistoryInputBox, container, this.contextViewService, { placeholder: this.placeholder, - history: [] + history: this.history })); this._register(attachInputBoxStyler(this.filterInputBox, this.themeService)); this.filterInputBox.value = this.filters.filterText; diff --git a/src/vs/workbench/contrib/debug/browser/replViewer.ts b/src/vs/workbench/contrib/debug/browser/replViewer.ts index 4fbb33ebc..0d432417f 100644 --- a/src/vs/workbench/contrib/debug/browser/replViewer.ts +++ b/src/vs/workbench/contrib/debug/browser/replViewer.ts @@ -21,8 +21,11 @@ import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IReplElementSource, IDebugService, IExpression, IReplElement, IDebugConfiguration, IDebugSession, IExpressionContainer } from 'vs/workbench/contrib/debug/common/debug'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { localize } from 'vs/nls'; +import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; +import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; +import { debugConsoleEvaluationInput } from 'vs/workbench/contrib/debug/browser/debugIcons'; const $ = dom.$; @@ -40,10 +43,13 @@ interface IReplEvaluationResultTemplateData { interface ISimpleReplElementTemplateData { container: HTMLElement; + count: CountBadge; + countContainer: HTMLElement; value: HTMLElement; source: HTMLElement; getReplElementSource(): IReplElementSource | undefined; toDispose: IDisposable[]; + elementListener: IDisposable; } interface IRawObjectReplTemplateData { @@ -62,7 +68,7 @@ export class ReplEvaluationInputsRenderer implements ITreeRenderer { +export class ReplEvaluationResultsRenderer implements ITreeRenderer { static readonly ID = 'replEvaluationResult'; get templateId(): string { @@ -117,7 +123,7 @@ export class ReplEvaluationResultsRenderer implements ITreeRenderer, index: number, templateData: IReplEvaluationResultTemplateData): void { + renderElement(element: ITreeNode, index: number, templateData: IReplEvaluationResultTemplateData): void { const expression = element.element; renderExpressionValue(expression, templateData.value, { showHover: false, @@ -151,9 +157,12 @@ export class ReplSimpleElementsRenderer implements ITreeRenderer { e.preventDefault(); e.stopPropagation(); @@ -172,11 +181,13 @@ export class ReplSimpleElementsRenderer implements ITreeRenderer, index: number, templateData: ISimpleReplElementTemplateData): void { + this.setElementCount(element, templateData); + templateData.elementListener = element.onDidChangeCount(() => this.setElementCount(element, templateData)); // value dom.clearNode(templateData.value); // Reset classes to clear ansi decorations since templates are reused templateData.value.className = 'value'; - const result = handleANSIOutput(element.value, this.linkDetector, this.themeService, element.session); + const result = handleANSIOutput(element.value, this.linkDetector, this.themeService, element.session.root); templateData.value.appendChild(result); templateData.value.classList.add((element.severity === severity.Warning) ? 'warn' : (element.severity === severity.Error) ? 'error' : (element.severity === severity.Ignore) ? 'ignore' : 'info'); @@ -185,9 +196,22 @@ export class ReplSimpleElementsRenderer implements ITreeRenderer element.sourceData; } + private setElementCount(element: SimpleReplElement, templateData: ISimpleReplElementTemplateData): void { + if (element.count >= 2) { + templateData.count.setCount(element.count); + templateData.countContainer.hidden = false; + } else { + templateData.countContainer.hidden = true; + } + } + disposeTemplate(templateData: ISimpleReplElementTemplateData): void { dispose(templateData.toDispose); } + + disposeElement(_element: ITreeNode, _index: number, templateData: ISimpleReplElementTemplateData): void { + templateData.elementListener.dispose(); + } } export class ReplVariablesRenderer extends AbstractExpressionsRenderer { @@ -296,14 +320,14 @@ export class ReplDelegate extends CachedListVirtualDelegate { if (element instanceof Variable && element.name) { return ReplVariablesRenderer.ID; } - if (element instanceof ReplEvaluationResult) { + if (element instanceof ReplEvaluationResult || (element instanceof Variable && !element.name)) { + // Variable with no name is a top level variable which should be rendered like a repl element #17404 return ReplEvaluationResultsRenderer.ID; } if (element instanceof ReplEvaluationInput) { return ReplEvaluationInputsRenderer.ID; } - if (element instanceof SimpleReplElement || (element instanceof Variable && !element.name)) { - // Variable with no name is a top level variable which should be rendered like a repl element #17404 + if (element instanceof SimpleReplElement) { return ReplSimpleElementsRenderer.ID; } if (element instanceof ReplGroup) { @@ -359,13 +383,13 @@ export class ReplAccessibilityProvider implements IListAccessibilityProvider 1 ? localize('occurred', ", occured {0} times", element.count) : ''); } if (element instanceof RawObjectReplElement) { return localize('replRawObjectAriaLabel', "Debug console variable {0}, value {1}", element.name, element.value); } if (element instanceof ReplGroup) { - return localize('replGroup', "Debug console group {0}, read eval print loop, debug", element.name); + return localize('replGroup', "Debug console group {0}", element.name); } return ''; diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 9a4560c03..7c8acdc41 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -30,12 +30,13 @@ import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/c import { dispose } from 'vs/base/common/lifecycle'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { withUndefinedAsNull } from 'vs/base/common/types'; import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { debugCollapseAll } from 'vs/workbench/contrib/debug/browser/debugIcons'; const $ = dom.$; let forgetScopes = true; @@ -179,7 +180,7 @@ export class VariablesView extends ViewPane { } getActions(): IAction[] { - return [new CollapseAction(() => this.tree, true, 'explorer-action codicon-collapse-all')]; + return [new CollapseAction(() => this.tree, true, 'explorer-action ' + ThemeIcon.asClassName(debugCollapseAll))]; } layoutBody(width: number, height: number): void { @@ -207,8 +208,8 @@ export class VariablesView extends ViewPane { this.variableEvaluateName.set(!!variable.evaluateName); this.breakWhenValueChangesSupported.reset(); if (session && session.capabilities.supportsDataBreakpoints) { - const response = await session.dataBreakpointInfo(variable.name, variable.parent.reference); - const dataBreakpointId = response?.dataId; + dataBreakpointInfoResponse = await session.dataBreakpointInfo(variable.name, variable.parent.reference); + const dataBreakpointId = dataBreakpointInfoResponse?.dataId; this.breakWhenValueChangesSupported.set(!!dataBreakpointId); } diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index ac56c4791..2e18414c9 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -30,8 +30,9 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { dispose } from 'vs/base/common/lifecycle'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { debugCollapseAll } from 'vs/workbench/contrib/debug/browser/debugIcons'; const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; let ignoreViewUpdates = false; @@ -160,7 +161,7 @@ export class WatchExpressionsView extends ViewPane { getActions(): IAction[] { return [ new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService), - new CollapseAction(() => this.tree, true, 'explorer-action codicon-collapse-all'), + new CollapseAction(() => this.tree, true, 'explorer-action ' + ThemeIcon.asClassName(debugCollapseAll)), new RemoveAllWatchExpressionsAction(RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL, this.debugService, this.keybindingService) ]; } diff --git a/src/vs/workbench/contrib/debug/browser/welcomeView.ts b/src/vs/workbench/contrib/debug/browser/welcomeView.ts index 37f5d7975..c88b28e8b 100644 --- a/src/vs/workbench/contrib/debug/browser/welcomeView.ts +++ b/src/vs/workbench/contrib/debug/browser/welcomeView.ts @@ -22,7 +22,7 @@ import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; import { OpenFolderAction, OpenFileAction, OpenFileFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { isMacintosh } from 'vs/base/common/platform'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -65,10 +65,10 @@ export class WelcomeView extends ViewPane { if (isCodeEditor(editorControl)) { const model = editorControl.getModel(); const language = model ? model.getLanguageIdentifier().language : undefined; - if (language && this.debugService.getConfigurationManager().isDebuggerInterestedInLanguage(language)) { + if (language && this.debugService.getAdapterManager().isDebuggerInterestedInLanguage(language)) { this.debugStartLanguageContext.set(language); this.debuggerInterestedContext.set(true); - storageSevice.store(debugStartLanguageKey, language, StorageScope.WORKSPACE); + storageSevice.store(debugStartLanguageKey, language, StorageScope.WORKSPACE, StorageTarget.MACHINE); return; } } @@ -88,7 +88,7 @@ export class WelcomeView extends ViewPane { setContextKey(); })); - this._register(this.debugService.getConfigurationManager().onDidRegisterDebugger(setContextKey)); + this._register(this.debugService.getAdapterManager().onDidRegisterDebugger(setContextKey)); this._register(this.onDidChangeBodyVisibility(visible => { if (visible) { setContextKey(); @@ -117,7 +117,6 @@ let debugKeybindingLabel = ''; viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { content: localize({ key: 'runAndDebugAction', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, "[Run and Debug{0}](command:{1})", debugKeybindingLabel, StartAction.ID), - preconditions: [CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR], when: CONTEXT_DEBUGGERS_AVAILABLE, group: ViewContentGroups.Debug }); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 782bb5af4..8f1715543 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -64,6 +64,7 @@ export const CONTEXT_DEBUG_PROTOCOL_VARIABLE_MENU_CONTEXT = new RawContextKey('debugSetVariableSupported', false); export const CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED = new RawContextKey('breakWhenValueChangesSupported', false); export const CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT = new RawContextKey('variableEvaluateNamePresent', false); +export const CONTEXT_EXCEPTION_WIDGET_VISIBLE = new RawContextKey('exceptionWidgetVisible', false); export const EDITOR_CONTRIBUTION_ID = 'editor.contrib.debug'; export const BREAKPOINT_EDITOR_CONTRIBUTION_ID = 'editor.contrib.breakpoint'; @@ -379,6 +380,7 @@ export interface IBaseBreakpoint extends IEnablement { readonly logMessage?: string; readonly verified: boolean; readonly supported: boolean; + readonly sessionsThatVerified: string[]; getIdFromAdapter(sessionId: string): number | undefined; } @@ -400,6 +402,7 @@ export interface IFunctionBreakpoint extends IBaseBreakpoint { export interface IExceptionBreakpoint extends IEnablement { readonly filter: string; readonly label: string; + readonly condition: string | undefined; } export interface IDataBreakpoint extends IBaseBreakpoint { @@ -434,9 +437,9 @@ export interface IViewModel extends ITreeElement { readonly focusedStackFrame: IStackFrame | undefined; getSelectedExpression(): IExpression | undefined; - getSelectedFunctionBreakpoint(): IFunctionBreakpoint | undefined; + getSelectedBreakpoint(): IFunctionBreakpoint | IExceptionBreakpoint | undefined; setSelectedExpression(expression: IExpression | undefined): void; - setSelectedFunctionBreakpoint(functionBreakpoint: IFunctionBreakpoint | undefined): void; + setSelectedBreakpoint(functionBreakpoint: IFunctionBreakpoint | IExceptionBreakpoint | undefined): void; updateViews(): void; isMultiSessionView(): boolean; @@ -615,8 +618,6 @@ export interface IPlatformSpecificAdapterContribution { export interface IDebuggerContribution extends IPlatformSpecificAdapterContribution { type: string; label?: string; - // debug adapter executable - adapterExecutableCommand?: string; win?: IPlatformSpecificAdapterContribution; winx86?: IPlatformSpecificAdapterContribution; windows?: IPlatformSpecificAdapterContribution; @@ -628,7 +629,7 @@ export interface IDebuggerContribution extends IPlatformSpecificAdapterContribut // supported languages languages?: string[]; - enableBreakpointsFor?: { languageIds: string[] }; + enableBreakpointsFor?: { languageIds?: string[] }; // debug configuration support configurationAttributes?: any; @@ -643,7 +644,6 @@ export interface IDebugConfigurationProvider { resolveDebugConfiguration?(folderUri: uri | undefined, debugConfiguration: IConfig, token: CancellationToken): Promise; resolveDebugConfigurationWithSubstitutedVariables?(folderUri: uri | undefined, debugConfiguration: IConfig, token: CancellationToken): Promise; provideDebugConfigurations?(folderUri: uri | undefined, token: CancellationToken): Promise; - debugAdapterExecutable?(folderUri: uri | undefined): Promise; // TODO@AW legacy } export interface IDebugAdapterDescriptorFactory { @@ -660,57 +660,54 @@ export interface ITerminalLauncher { } export interface IConfigurationManager { - /** - * Returns true if breakpoints can be set for a given editor model. Depends on mode. - */ - canSetBreakpointsIn(model: EditorIModel): boolean; /** * Returns an object containing the selected launch configuration and the selected configuration name. Both these fields can be null (no folder workspace). */ readonly selectedConfiguration: { launch: ILaunch | undefined; - config: IConfig | undefined; + // Potentially activates extensions + getConfig: () => Promise; name: string | undefined; // Type is used when matching dynamic configurations to their corresponding provider type: string | undefined; }; - selectConfiguration(launch: ILaunch | undefined, name?: string, config?: IConfig, type?: string): Promise; + selectConfiguration(launch: ILaunch | undefined, name?: string, config?: IConfig, dynamicConfigOptions?: { type?: string }): Promise; getLaunches(): ReadonlyArray; - - hasDebuggers(): boolean; - getLaunch(workspaceUri: uri | undefined): ILaunch | undefined; - getAllConfigurations(): { launch: ILaunch, name: string, presentation?: IConfigPresentation }[]; + getRecentDynamicConfigurations(): { name: string, type: string }[]; /** * Allows to register on change of selected debug configuration. */ onDidSelectConfiguration: Event; - onDidRegisterDebugger: Event; - - activateDebuggers(activationEvent: string, debugType?: string): Promise; - - isDebuggerInterestedInLanguage(language: string): boolean; hasDebugConfigurationProvider(debugType: string): boolean; getDynamicProviders(): Promise<{ label: string, type: string, pick: () => Promise<{ launch: ILaunch, config: IConfig } | undefined> }[]>; registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable; unregisterDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): void; - registerDebugAdapterDescriptorFactory(debugAdapterDescriptorFactory: IDebugAdapterDescriptorFactory): IDisposable; - unregisterDebugAdapterDescriptorFactory(debugAdapterDescriptorFactory: IDebugAdapterDescriptorFactory): void; - resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: any, token: CancellationToken): Promise; +} + +export interface IAdapterManager { + + onDidRegisterDebugger: Event; + + hasDebuggers(): boolean; getDebugAdapterDescriptor(session: IDebugSession): Promise; getDebuggerLabel(type: string): string | undefined; + isDebuggerInterestedInLanguage(language: string): boolean; + activateDebuggers(activationEvent: string, debugType?: string): Promise; registerDebugAdapterFactory(debugTypes: string[], debugAdapterFactory: IDebugAdapterFactory): IDisposable; createDebugAdapter(session: IDebugSession): IDebugAdapter | undefined; + registerDebugAdapterDescriptorFactory(debugAdapterDescriptorFactory: IDebugAdapterDescriptorFactory): IDisposable; + unregisterDebugAdapterDescriptorFactory(debugAdapterDescriptorFactory: IDebugAdapterDescriptorFactory): void; substituteVariables(debugType: string, folder: IWorkspaceFolder | undefined, config: IConfig): Promise; runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments): Promise; @@ -795,15 +792,25 @@ export interface IDebugService { onDidEndSession: Event; /** - * Gets the current configuration manager. + * Gets the configuration manager. */ getConfigurationManager(): IConfigurationManager; + /** + * Gets the adapter manager. + */ + getAdapterManager(): IAdapterManager; + /** * Sets the focused stack frame and evaluates all expressions against the newly focused stack frame, */ focusStackFrame(focusedStackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession, explicit?: boolean): Promise; + /** + * Returns true if breakpoints can be set for a given editor model. Depends on mode. + */ + canSetBreakpointsIn(model: EditorIModel): boolean; + /** * Adds new breakpoints to the model for the file specified with the uri. Notifies debug adapter of breakpoint changes. */ @@ -860,6 +867,8 @@ export interface IDebugService { */ removeDataBreakpoints(id?: string): Promise; + setExceptionBreakpointCondition(breakpoint: IExceptionBreakpoint, condition: string | undefined): Promise; + /** * Sends all breakpoints to the passed session. * If session is not passed, sends all breakpoints to each session. @@ -932,6 +941,7 @@ export const enum BreakpointWidgetContext { export interface IDebugEditorContribution extends editorCommon.IEditorContribution { showHover(range: Range, focus: boolean): Promise; addLaunchConfiguration(): Promise; + closeExceptionWidget(): void; } export interface IBreakpointEditorContribution extends editorCommon.IEditorContribution { diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 1d203c90b..b98b3b897 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -393,6 +393,7 @@ export class Thread implements IThread { private callStackCancellationTokens: CancellationTokenSource[] = []; public stoppedDetails: IRawStoppedDetails | undefined; public stopped: boolean; + public reachedEndOfCallStack = false; constructor(public session: IDebugSession, public name: string, public threadId: number) { this.callStack = []; @@ -445,6 +446,7 @@ export class Thread implements IThread { if (this.stopped) { const start = this.callStack.length; const callStack = await this.getCallStackImpl(start, levels); + this.reachedEndOfCallStack = callStack.length < levels; if (start < this.callStack.length) { // Set the stack frames for exact position we requested. To make sure no concurrent requests create duplicate stack frames #30660 this.callStack.splice(start, this.callStack.length - start); @@ -613,6 +615,17 @@ export abstract class BaseBreakpoint extends Enablement implements IBaseBreakpoi return this.data ? this.data.verified : true; } + get sessionsThatVerified() { + const sessionIds: string[] = []; + for (const [sessionId, data] of this.sessionData) { + if (data.verified) { + sessionIds.push(sessionId); + } + } + + return sessionIds; + } + abstract get supported(): boolean; getIdFromAdapter(sessionId: string): number | undefined { @@ -847,7 +860,7 @@ export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint { export class ExceptionBreakpoint extends Enablement implements IExceptionBreakpoint { - constructor(public filter: string, public label: string, enabled: boolean) { + constructor(public filter: string, public label: string, enabled: boolean, public supportsCondition: boolean, public condition: string | undefined) { super(enabled, generateUuid()); } @@ -856,6 +869,8 @@ export class ExceptionBreakpoint extends Enablement implements IExceptionBreakpo result.filter = this.filter; result.label = this.label; result.enabled = this.enabled; + result.supportsCondition = this.supportsCondition; + result.condition = this.condition; return result; } @@ -1049,19 +1064,24 @@ export class DebugModel implements IDebugModel { setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void { if (data) { - if (this.exceptionBreakpoints.length === data.length && this.exceptionBreakpoints.every((exbp, i) => exbp.filter === data[i].filter && exbp.label === data[i].label)) { + if (this.exceptionBreakpoints.length === data.length && this.exceptionBreakpoints.every((exbp, i) => exbp.filter === data[i].filter && exbp.label === data[i].label && exbp.supportsCondition === data[i].supportsCondition)) { // No change return; } this.exceptionBreakpoints = data.map(d => { const ebp = this.exceptionBreakpoints.filter(ebp => ebp.filter === d.filter).pop(); - return new ExceptionBreakpoint(d.filter, d.label, ebp ? ebp.enabled : !!d.default); + return new ExceptionBreakpoint(d.filter, d.label, ebp ? ebp.enabled : !!d.default, !!d.supportsCondition, ebp?.condition); }); this._onDidChangeBreakpoints.fire(undefined); } } + setExceptionBreakpointCondition(exceptionBreakpoint: IExceptionBreakpoint, condition: string | undefined): void { + (exceptionBreakpoint as ExceptionBreakpoint).condition = condition; + this._onDidChangeBreakpoints.fire(undefined); + } + areBreakpointsActivated(): boolean { return this.breakpointsActivated; } diff --git a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts index 0f0374d54..f11bab4fb 100644 --- a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts +++ b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts @@ -16,7 +16,7 @@ declare module DebugProtocol { /** Message type. Values: 'request', 'response', 'event', etc. */ - type: string; + type: 'request' | 'response' | 'event' | string; } /** A client or debug adapter initiated request. */ @@ -56,7 +56,7 @@ declare module DebugProtocol { 'cancelled': request was cancelled. etc. */ - message?: string; + message?: 'cancelled' | string; /** Contains request result if success is true and optional error details if success is false. */ body?: any; } @@ -129,7 +129,7 @@ declare module DebugProtocol { For backward compatibility this string is shown in the UI if the 'description' attribute is missing (but it must not be translated). Values: 'step', 'breakpoint', 'exception', 'pause', 'entry', 'goto', 'function breakpoint', 'data breakpoint', 'instruction breakpoint', etc. */ - reason: string; + reason: 'step' | 'breakpoint' | 'exception' | 'pause' | 'entry' | 'goto' | 'function breakpoint' | 'data breakpoint' | 'instruction breakpoint' | string; /** The full reason for the event, e.g. 'Paused on exception'. This string is shown in the UI as is and must be translated. */ description?: string; /** The thread which was stopped. */ @@ -194,7 +194,7 @@ declare module DebugProtocol { /** The reason for the event. Values: 'started', 'exited', etc. */ - reason: string; + reason: 'started' | 'exited' | string; /** The identifier of the thread. */ threadId: number; }; @@ -209,7 +209,7 @@ declare module DebugProtocol { /** The output category. If not specified, 'console' is assumed. Values: 'console', 'stdout', 'stderr', 'telemetry', etc. */ - category?: string; + category?: 'console' | 'stdout' | 'stderr' | 'telemetry' | string; /** The output to report. */ output: string; /** Support for keeping an output log organized by grouping related messages. @@ -221,7 +221,7 @@ declare module DebugProtocol { A non empty 'output' attribute is shown as the unindented end of the group. */ group?: 'start' | 'startCollapsed' | 'end'; - /** If an attribute 'variablesReference' exists and its value is > 0, the output contains objects which can be retrieved by passing 'variablesReference' to the 'variables' request. The value should be less than or equal to 2147483647 (2^31 - 1). */ + /** If an attribute 'variablesReference' exists and its value is > 0, the output contains objects which can be retrieved by passing 'variablesReference' to the 'variables' request. The value should be less than or equal to 2147483647 (2^31-1). */ variablesReference?: number; /** An optional source location where the output was produced. */ source?: Source; @@ -243,7 +243,7 @@ declare module DebugProtocol { /** The reason for the event. Values: 'changed', 'new', 'removed', etc. */ - reason: string; + reason: 'changed' | 'new' | 'removed' | string; /** The 'id' attribute is used to find the target breakpoint and the other attributes are used as the new values. */ breakpoint: Breakpoint; }; @@ -383,7 +383,7 @@ declare module DebugProtocol { export interface InvalidatedEvent extends Event { // event: 'invalidated'; body: { - /** Optional set of logical areas that got invalidated. If this property is missing or empty, a single value 'all' is assumed. */ + /** Optional set of logical areas that got invalidated. This property has a hint characteristic: a client can only be expected to make a 'best effort' in honouring the areas but there are no guarantees. If this property is missing, empty, or if values are not understand the client should assume a single value 'all'. */ areas?: InvalidatedAreas[]; /** If specified, the client only needs to refetch data related to this thread. */ threadId?: number; @@ -408,7 +408,7 @@ declare module DebugProtocol { kind?: 'integrated' | 'external'; /** Optional title of the terminal. */ title?: string; - /** Working directory of the command. */ + /** Working directory for the command. For non-empty, valid paths this typically results in execution of a change directory command. */ cwd: string; /** List of arguments. The first argument is the command to run. */ args: string[]; @@ -419,9 +419,9 @@ declare module DebugProtocol { /** Response to 'runInTerminal' request. */ export interface RunInTerminalResponse extends Response { body: { - /** The process ID. The value should be less than or equal to 2147483647 (2^31 - 1). */ + /** The process ID. The value should be less than or equal to 2147483647 (2^31-1). */ processId?: number; - /** The process ID of the terminal shell. The value should be less than or equal to 2147483647 (2^31 - 1). */ + /** The process ID of the terminal shell. The value should be less than or equal to 2147483647 (2^31-1). */ shellProcessId?: number; }; } @@ -455,7 +455,7 @@ declare module DebugProtocol { /** Determines in what format paths are specified. The default is 'path', which is the native format. Values: 'path', 'uri', etc. */ - pathFormat?: string; + pathFormat?: 'path' | 'uri' | string; /** Client supports the optional type attribute for variables. */ supportsVariableType?: boolean; /** Client supports the paging of variables. */ @@ -712,8 +712,10 @@ declare module DebugProtocol { /** Arguments for 'setExceptionBreakpoints' request. */ export interface SetExceptionBreakpointsArguments { - /** IDs of checked exception options. The set of IDs is returned via the 'exceptionBreakpointFilters' capability. */ + /** Set of exception filters specified by their ID. The set of all possible exception filters is defined by the 'exceptionBreakpointFilters' capability. The 'filter' and 'filterOptions' sets are additive. */ filters: string[]; + /** Set of exception filters and their options. The set of all possible exception filters is defined by the 'exceptionBreakpointFilters' capability. This attribute is only honored by a debug adapter if the capability 'supportsExceptionFilterOptions' is true. The 'filter' and 'filterOptions' sets are additive. */ + filterOptions?: ExceptionFilterOptions[]; /** Configuration options for selected exceptions. The attribute is only honored by a debug adapter if the capability 'supportsExceptionOptions' is true. */ @@ -1009,7 +1011,8 @@ declare module DebugProtocol { } /** StackTrace request; value of command field is 'stackTrace'. - The request returns a stacktrace from the current execution state. + The request returns a stacktrace from the current execution state of a given thread. + A client can request all stack frames by omitting the startFrame and levels arguments. For performance conscious clients stack frames can be retrieved in a piecemeal way with the startFrame and levels arguments. The response of the stackTrace request may contain a totalFrames property that hints at the total number of frames in the stack. If a client needs this total number upfront, it can issue a request for a single (first) frame and depending on the value of totalFrames decide how to proceed. In any case a client should be prepared to receive less frames than requested, which is an indication that the end of the stack has been reached. */ export interface StackTraceRequest extends Request { // command: 'stackTrace'; @@ -1037,7 +1040,7 @@ declare module DebugProtocol { This means that there is no location information available. */ stackFrames: StackFrame[]; - /** The total number of frames available. */ + /** The total number of frames available in the stack. If omitted or if totalFrames is larger than the available frames, a client is expected to request frames until a request returns less frames than requested (which indicates the end of the stack). Returning monotonically increasing totalFrames values for subsequent requests can be used to enforce paging in the client. */ totalFrames?: number; }; } @@ -1125,17 +1128,17 @@ declare module DebugProtocol { /** 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 VariablesRequest. - The value should be less than or equal to 2147483647 (2^31 - 1). + The value should be less than or equal to 2147483647 (2^31-1). */ variablesReference?: number; /** The number of named child variables. The client can use this optional information to present the variables in a paged UI and fetch them in chunks. - The value should be less than or equal to 2147483647 (2^31 - 1). + The value should be less than or equal to 2147483647 (2^31-1). */ namedVariables?: number; /** The number of indexed child variables. The client can use this optional information to present the variables in a paged UI and fetch them in chunks. - The value should be less than or equal to 2147483647 (2^31 - 1). + The value should be less than or equal to 2147483647 (2^31-1). */ indexedVariables?: number; }; @@ -1275,7 +1278,7 @@ declare module DebugProtocol { The attribute is only honored by a debug adapter if the capability 'supportsClipboardContext' is true. etc. */ - context?: string; + context?: 'watch' | 'repl' | 'hover' | 'clipboard' | string; /** Specifies details on how to format the Evaluate result. The attribute is only honored by a debug adapter if the capability 'supportsValueFormattingOptions' is true. */ @@ -1294,17 +1297,17 @@ declare module DebugProtocol { /** Properties of a evaluate result that can be used to determine how to render the result in the UI. */ presentationHint?: VariablePresentationHint; /** If variablesReference is > 0, the evaluate result is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. - The value should be less than or equal to 2147483647 (2^31 - 1). + The value should be less than or equal to 2147483647 (2^31-1). */ variablesReference: number; /** The number of named child variables. The client can use this optional information to present the variables in a paged UI and fetch them in chunks. - The value should be less than or equal to 2147483647 (2^31 - 1). + The value should be less than or equal to 2147483647 (2^31-1). */ namedVariables?: number; /** The number of indexed child variables. The client can use this optional information to present the variables in a paged UI and fetch them in chunks. - The value should be less than or equal to 2147483647 (2^31 - 1). + The value should be less than or equal to 2147483647 (2^31-1). */ indexedVariables?: number; /** Optional memory reference to a location appropriate for this result. @@ -1349,17 +1352,17 @@ declare module DebugProtocol { /** Properties of a value that can be used to determine how to render the result in the UI. */ presentationHint?: VariablePresentationHint; /** If variablesReference is > 0, the value is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. - The value should be less than or equal to 2147483647 (2^31 - 1). + The value should be less than or equal to 2147483647 (2^31-1). */ variablesReference?: number; /** The number of named child variables. The client can use this optional information to present the variables in a paged UI and fetch them in chunks. - The value should be less than or equal to 2147483647 (2^31 - 1). + The value should be less than or equal to 2147483647 (2^31-1). */ namedVariables?: number; /** The number of indexed child variables. The client can use this optional information to present the variables in a paged UI and fetch them in chunks. - The value should be less than or equal to 2147483647 (2^31 - 1). + The value should be less than or equal to 2147483647 (2^31-1). */ indexedVariables?: number; }; @@ -1556,7 +1559,7 @@ declare module DebugProtocol { supportsHitConditionalBreakpoints?: boolean; /** The debug adapter supports a (side effect free) evaluate request for data hovers. */ supportsEvaluateForHovers?: boolean; - /** Available filters or options for the setExceptionBreakpoints request. */ + /** Available exception filter options for the 'setExceptionBreakpoints' request. */ exceptionBreakpointFilters?: ExceptionBreakpointsFilter[]; /** The debug adapter supports stepping back via the 'stepBack' and 'reverseContinue' requests. */ supportsStepBack?: boolean; @@ -1616,16 +1619,20 @@ declare module DebugProtocol { supportsSteppingGranularity?: boolean; /** The debug adapter supports adding breakpoints based on instruction references. */ supportsInstructionBreakpoints?: boolean; + /** The debug adapter supports 'filterOptions' as an argument on the 'setExceptionBreakpoints' request. */ + supportsExceptionFilterOptions?: boolean; } - /** An ExceptionBreakpointsFilter is shown in the UI as an option for configuring how exceptions are dealt with. */ + /** An ExceptionBreakpointsFilter is shown in the UI as an filter option for configuring how exceptions are dealt with. */ export interface ExceptionBreakpointsFilter { - /** The internal ID of the filter. This value is passed to the setExceptionBreakpoints request. */ + /** The internal ID of the filter option. This value is passed to the 'setExceptionBreakpoints' request. */ filter: string; - /** The name of the filter. This will be shown in the UI. */ + /** The name of the filter option. This will be shown in the UI. */ label: string; - /** Initial value of the filter. If not specified a value 'false' is assumed. */ + /** Initial value of the filter option. If not specified a value 'false' is assumed. */ default?: boolean; + /** Controls whether a condition can be specified for this filter option. If false or missing, a condition can not be set. */ + supportsCondition?: boolean; } /** A structured message object. Used to return errors from requests. */ @@ -1730,7 +1737,7 @@ declare module DebugProtocol { path?: string; /** If sourceReference > 0 the contents of the source must be retrieved through the SourceRequest (even if a path is specified). A sourceReference is only valid for a session, so it must not be used to persist a source. - The value should be less than or equal to 2147483647 (2^31 - 1). + The value should be less than or equal to 2147483647 (2^31-1). */ sourceReference?: number; /** An optional hint for how to present the source in the UI. @@ -1788,7 +1795,7 @@ declare module DebugProtocol { 'registers': Scope contains registers. Only a single 'registers' scope should be returned from a 'scopes' request. etc. */ - presentationHint?: string; + presentationHint?: 'arguments' | 'locals' | 'registers' | string; /** The variables of this scope can be retrieved by passing the value of variablesReference to the VariablesRequest. */ variablesReference: number; /** The number of named variables in this scope. @@ -1867,7 +1874,7 @@ declare module DebugProtocol { 'dataBreakpoint': Indicates that a data breakpoint is registered for the object. etc. */ - kind?: string; + kind?: 'property' | 'method' | 'class' | 'data' | 'event' | 'baseClass' | 'innerClass' | 'interface' | 'mostDerivedClass' | 'virtual' | 'dataBreakpoint' | string; /** Set of attributes represented as an array of strings. Before introducing additional values, try to use the listed values. Values: 'static': Indicates that the object is static. @@ -1879,11 +1886,11 @@ declare module DebugProtocol { 'hasSideEffects': Indicates that the evaluation had side effects. etc. */ - attributes?: string[]; + attributes?: ('static' | 'constant' | 'readOnly' | 'rawString' | 'hasObjectId' | 'canHaveObjectId' | 'hasSideEffects' | string)[]; /** Visibility of variable. Before introducing additional values, try to use the listed values. Values: 'public', 'private', 'protected', 'internal', 'final', etc. */ - visibility?: string; + visibility?: 'public' | 'private' | 'protected' | 'internal' | 'final' | string; } /** Properties of a breakpoint location returned from the 'breakpointLocations' request. */ @@ -2108,6 +2115,16 @@ declare module DebugProtocol { includeAll?: boolean; } + /** An ExceptionFilterOptions is used to specify an exception filter together with a condition for the setExceptionsFilter request. */ + export interface ExceptionFilterOptions { + /** ID of an exception filter returned by the 'exceptionBreakpointFilters' capability. */ + filterId: string; + /** An optional expression for conditional exceptions. + The exception will break into the debugger if the result of the condition is true. + */ + condition?: string; + } + /** An ExceptionOptions assigns configuration options to a set of exceptions. */ export interface ExceptionOptions { /** A path that selects a single or multiple exceptions in a tree. If 'path' is missing, the whole tree is selected. @@ -2179,11 +2196,13 @@ declare module DebugProtocol { } /** Logical areas that can be invalidated by the 'invalidated' event. + Values: 'all': All previously fetched data has become invalid and needs to be refetched. 'stacks': Previously fetched stack related data has become invalid and needs to be refetched. 'threads': Previously fetched thread related data has become invalid and needs to be refetched. 'variables': Previously fetched variable data has become invalid and needs to be refetched. + etc. */ - export type InvalidatedAreas = 'all' | 'stacks' | 'threads' | 'variables'; + export type InvalidatedAreas = 'all' | 'stacks' | 'threads' | 'variables' | string; } diff --git a/src/vs/workbench/contrib/debug/common/debugStorage.ts b/src/vs/workbench/contrib/debug/common/debugStorage.ts index 6cabc0ba9..42583a616 100644 --- a/src/vs/workbench/contrib/debug/common/debugStorage.ts +++ b/src/vs/workbench/contrib/debug/common/debugStorage.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage'; +import { StorageScope, IStorageService, StorageTarget } from 'vs/platform/storage/common/storage'; import { ExceptionBreakpoint, Expression, Breakpoint, FunctionBreakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { IEvaluate, IExpression, IDebugModel } from 'vs/workbench/contrib/debug/common/debug'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -49,7 +49,7 @@ export class DebugStorage { let result: ExceptionBreakpoint[] | undefined; try { result = JSON.parse(this.storageService.get(DEBUG_EXCEPTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((exBreakpoint: any) => { - return new ExceptionBreakpoint(exBreakpoint.filter, exBreakpoint.label, exBreakpoint.enabled); + return new ExceptionBreakpoint(exBreakpoint.filter, exBreakpoint.label, exBreakpoint.enabled, exBreakpoint.supportsCondition, exBreakpoint.condition); }); } catch (e) { } @@ -80,7 +80,7 @@ export class DebugStorage { storeWatchExpressions(watchExpressions: (IExpression & IEvaluate)[]): void { if (watchExpressions.length) { - this.storageService.store(DEBUG_WATCH_EXPRESSIONS_KEY, JSON.stringify(watchExpressions.map(we => ({ name: we.name, id: we.getId() }))), StorageScope.WORKSPACE); + this.storageService.store(DEBUG_WATCH_EXPRESSIONS_KEY, JSON.stringify(watchExpressions.map(we => ({ name: we.name, id: we.getId() }))), StorageScope.WORKSPACE, StorageTarget.USER); } else { this.storageService.remove(DEBUG_WATCH_EXPRESSIONS_KEY, StorageScope.WORKSPACE); } @@ -89,28 +89,28 @@ export class DebugStorage { storeBreakpoints(debugModel: IDebugModel): void { const breakpoints = debugModel.getBreakpoints(); if (breakpoints.length) { - this.storageService.store(DEBUG_BREAKPOINTS_KEY, JSON.stringify(breakpoints), StorageScope.WORKSPACE); + this.storageService.store(DEBUG_BREAKPOINTS_KEY, JSON.stringify(breakpoints), StorageScope.WORKSPACE, StorageTarget.USER); } else { this.storageService.remove(DEBUG_BREAKPOINTS_KEY, StorageScope.WORKSPACE); } const functionBreakpoints = debugModel.getFunctionBreakpoints(); if (functionBreakpoints.length) { - this.storageService.store(DEBUG_FUNCTION_BREAKPOINTS_KEY, JSON.stringify(functionBreakpoints), StorageScope.WORKSPACE); + this.storageService.store(DEBUG_FUNCTION_BREAKPOINTS_KEY, JSON.stringify(functionBreakpoints), StorageScope.WORKSPACE, StorageTarget.USER); } else { this.storageService.remove(DEBUG_FUNCTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE); } const dataBreakpoints = debugModel.getDataBreakpoints().filter(dbp => dbp.canPersist); if (dataBreakpoints.length) { - this.storageService.store(DEBUG_DATA_BREAKPOINTS_KEY, JSON.stringify(dataBreakpoints), StorageScope.WORKSPACE); + this.storageService.store(DEBUG_DATA_BREAKPOINTS_KEY, JSON.stringify(dataBreakpoints), StorageScope.WORKSPACE, StorageTarget.USER); } else { this.storageService.remove(DEBUG_DATA_BREAKPOINTS_KEY, StorageScope.WORKSPACE); } const exceptionBreakpoints = debugModel.getExceptionBreakpoints(); if (exceptionBreakpoints.length) { - this.storageService.store(DEBUG_EXCEPTION_BREAKPOINTS_KEY, JSON.stringify(exceptionBreakpoints), StorageScope.WORKSPACE); + this.storageService.store(DEBUG_EXCEPTION_BREAKPOINTS_KEY, JSON.stringify(exceptionBreakpoints), StorageScope.WORKSPACE, StorageTarget.USER); } else { this.storageService.remove(DEBUG_EXCEPTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE); } diff --git a/src/vs/workbench/contrib/debug/common/debugViewModel.ts b/src/vs/workbench/contrib/debug/common/debugViewModel.ts index 7a4b1fbac..e7541cd89 100644 --- a/src/vs/workbench/contrib/debug/common/debugViewModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugViewModel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { CONTEXT_EXPRESSION_SELECTED, IViewModel, IStackFrame, IDebugSession, IThread, IExpression, IFunctionBreakpoint, CONTEXT_BREAKPOINT_SELECTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED } from 'vs/workbench/contrib/debug/common/debug'; +import { CONTEXT_EXPRESSION_SELECTED, IViewModel, IStackFrame, IDebugSession, IThread, IExpression, IFunctionBreakpoint, CONTEXT_BREAKPOINT_SELECTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, IExceptionBreakpoint } from 'vs/workbench/contrib/debug/common/debug'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { isSessionAttach } from 'vs/workbench/contrib/debug/common/debugUtils'; @@ -16,7 +16,7 @@ export class ViewModel implements IViewModel { private _focusedSession: IDebugSession | undefined; private _focusedThread: IThread | undefined; private selectedExpression: IExpression | undefined; - private selectedFunctionBreakpoint: IFunctionBreakpoint | undefined; + private selectedBreakpoint: IFunctionBreakpoint | IExceptionBreakpoint | undefined; private readonly _onDidFocusSession = new Emitter(); private readonly _onDidFocusStackFrame = new Emitter<{ stackFrame: IStackFrame | undefined, explicit: boolean }>(); private readonly _onDidSelectExpression = new Emitter(); @@ -112,8 +112,8 @@ export class ViewModel implements IViewModel { return this._onDidSelectExpression.event; } - getSelectedFunctionBreakpoint(): IFunctionBreakpoint | undefined { - return this.selectedFunctionBreakpoint; + getSelectedBreakpoint(): IFunctionBreakpoint | IExceptionBreakpoint | undefined { + return this.selectedBreakpoint; } updateViews(): void { @@ -124,9 +124,9 @@ export class ViewModel implements IViewModel { return this._onWillUpdateViews.event; } - setSelectedFunctionBreakpoint(functionBreakpoint: IFunctionBreakpoint | undefined): void { - this.selectedFunctionBreakpoint = functionBreakpoint; - this.breakpointSelectedContextKey.set(!!functionBreakpoint); + setSelectedBreakpoint(breakpoint: IFunctionBreakpoint | IExceptionBreakpoint | undefined): void { + this.selectedBreakpoint = breakpoint; + this.breakpointSelectedContextKey.set(!!breakpoint); } isMultiSessionView(): boolean { diff --git a/src/vs/workbench/contrib/debug/common/debugger.ts b/src/vs/workbench/contrib/debug/common/debugger.ts index ae8bc52db..b60cda04e 100644 --- a/src/vs/workbench/contrib/debug/common/debugger.ts +++ b/src/vs/workbench/contrib/debug/common/debugger.ts @@ -8,7 +8,7 @@ import * as objects from 'vs/base/common/objects'; import { isObject } from 'vs/base/common/types'; import { IJSONSchema, IJSONSchemaSnippet } from 'vs/base/common/jsonSchema'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { IConfig, IDebuggerContribution, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager, IDebugAdapter, IDebugger, IDebugSession, IDebugHelperService } from 'vs/workbench/contrib/debug/common/debug'; +import { IConfig, IDebuggerContribution, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IDebugAdapter, IDebugger, IDebugSession, IDebugHelperService, IAdapterManager, IDebugService } from 'vs/workbench/contrib/debug/common/debug'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import * as ConfigurationResolverUtils from 'vs/workbench/services/configurationResolver/common/configurationResolverUtils'; @@ -30,14 +30,15 @@ export class Debugger implements IDebugger { private mainExtensionDescription: IExtensionDescription | undefined; constructor( - private configurationManager: IConfigurationManager, + private adapterManager: IAdapterManager, dbgContribution: IDebuggerContribution, extensionDescription: IExtensionDescription, @IConfigurationService private readonly configurationService: IConfigurationService, @ITextResourcePropertiesService private readonly resourcePropertiesService: ITextResourcePropertiesService, @IConfigurationResolverService private readonly configurationResolverService: IConfigurationResolverService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IDebugHelperService private readonly debugHelperService: IDebugHelperService + @IDebugHelperService private readonly debugHelperService: IDebugHelperService, + @IDebugService private readonly debugService: IDebugService ) { this.debuggerContribution = { type: dbgContribution.type }; this.merge(dbgContribution, extensionDescription); @@ -97,8 +98,8 @@ export class Debugger implements IDebugger { } createDebugAdapter(session: IDebugSession): Promise { - return this.configurationManager.activateDebuggers('onDebugAdapterProtocolTracker', this.type).then(_ => { - const da = this.configurationManager.createDebugAdapter(session); + return this.adapterManager.activateDebuggers('onDebugAdapterProtocolTracker', this.type).then(_ => { + const da = this.adapterManager.createDebugAdapter(session); if (da) { return Promise.resolve(da); } @@ -107,13 +108,13 @@ export class Debugger implements IDebugger { } substituteVariables(folder: IWorkspaceFolder | undefined, config: IConfig): Promise { - return this.configurationManager.substituteVariables(this.type, folder, config).then(config => { + return this.adapterManager.substituteVariables(this.type, folder, config).then(config => { return this.configurationResolverService.resolveWithInteractionReplace(folder, config, 'launch', this.variables, config.__configurationTarget); }); } runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise { - return this.configurationManager.runInTerminal(this.type, args); + return this.adapterManager.runInTerminal(this.type, args); } get label(): string { @@ -141,7 +142,7 @@ export class Debugger implements IDebugger { } hasConfigurationProvider(): boolean { - return this.configurationManager.hasDebugConfigurationProvider(this.type); + return this.debugService.getConfigurationManager().hasDebugConfigurationProvider(this.type); } getInitialConfigurationContent(initialConfigs?: IConfig[]): Promise { diff --git a/src/vs/workbench/contrib/debug/common/replModel.ts b/src/vs/workbench/contrib/debug/common/replModel.ts index 04297b0e9..f10608a01 100644 --- a/src/vs/workbench/contrib/debug/common/replModel.ts +++ b/src/vs/workbench/contrib/debug/common/replModel.ts @@ -11,12 +11,16 @@ import { isString, isUndefinedOrNull, isObject } from 'vs/base/common/types'; import { basenameOrAuthority } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; const MAX_REPL_LENGTH = 10000; let topReplElementCounter = 0; export class SimpleReplElement implements IReplElement { + + private _count = 1; + private _onDidChangeCount = new Emitter(); + constructor( public session: IDebugSession, private id: string, @@ -33,6 +37,19 @@ export class SimpleReplElement implements IReplElement { getId(): string { return this.id; } + + set count(value: number) { + this._count = value; + this._onDidChangeCount.fire(); + } + + get count(): number { + return this._count; + } + + get onDidChangeCount(): Event { + return this._onDidChangeCount.event; + } } export class RawObjectReplElement implements IExpression { @@ -202,13 +219,21 @@ export class ReplModel { if (typeof data === 'string') { const previousElement = this.replElements.length ? this.replElements[this.replElements.length - 1] : undefined; - if (previousElement instanceof SimpleReplElement && previousElement.severity === sev && !previousElement.value.endsWith('\n') && !previousElement.value.endsWith('\r\n')) { - previousElement.value += data; - this._onDidChangeElements.fire(); - } else { - const element = new SimpleReplElement(session, `topReplElement:${topReplElementCounter++}`, data, sev, source); - this.addReplElement(element); + if (previousElement instanceof SimpleReplElement && previousElement.severity === sev) { + if (previousElement.value === data) { + previousElement.count++; + // No need to fire an event, just the count updates and badge will adjust automatically + return; + } + if (!previousElement.value.endsWith('\n') && !previousElement.value.endsWith('\r\n') && previousElement.count === 1) { + previousElement.value += data; + this._onDidChangeElements.fire(); + return; + } } + + const element = new SimpleReplElement(session, `topReplElement:${topReplElementCounter++}`, data, sev, source); + this.addReplElement(element); } else { // TODO@Isidor hack, we should introduce a new type which is an output that can fetch children like an expression (data).severity = sev; diff --git a/src/vs/workbench/contrib/debug/node/debugAdapter.ts b/src/vs/workbench/contrib/debug/node/debugAdapter.ts index dc6d941a0..d8ba9f71e 100644 --- a/src/vs/workbench/contrib/debug/node/debugAdapter.ts +++ b/src/vs/workbench/contrib/debug/node/debugAdapter.ts @@ -199,9 +199,9 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { "Cannot determine executable for debug adapter '{0}'.", this.debugType)); } - let env = objects.mixin({}, process.env); - if (options.env) { - env = objects.mixin(env, options.env); + let env = process.env; + if (options.env && Object.keys(options.env).length > 0) { + env = objects.mixin(objects.deepClone(process.env), options.env); } if (command === 'node') { @@ -263,6 +263,8 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { channel.append(sanitize(data)); } }); + } else { + this.serverProcess.stderr!.resume(); } // finally connect to the DA diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index 0c27cb6d5..45a8fc654 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -9,6 +9,7 @@ import { WindowsExternalTerminalService, MacExternalTerminalService, LinuxExtern import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IExternalTerminalService } from 'vs/workbench/contrib/externalTerminal/common/externalTerminal'; import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; +import { extractDriveLetter } from 'vs/base/common/labels'; let externalTerminalService: IExternalTerminalService | undefined = undefined; @@ -111,7 +112,11 @@ export function prepareCommand(shell: string, args: string[], cwd?: string, env? }; if (cwd) { - command += `cd '${cwd}'; `; + const driveLetter = extractDriveLetter(cwd); + if (driveLetter) { + command += `${driveLetter}:; `; + } + command += `cd ${quote(cwd)}; `; } if (env) { for (let key in env) { @@ -140,6 +145,10 @@ export function prepareCommand(shell: string, args: string[], cwd?: string, env? }; if (cwd) { + const driveLetter = extractDriveLetter(cwd); + if (driveLetter) { + command += `${driveLetter}: && `; + } command += `cd ${quote(cwd)} && `; } if (env) { diff --git a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts index aece705d6..1a2bed974 100644 --- a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { URI as uri } from 'vs/base/common/uri'; import { DebugModel, Breakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; -import { getExpandedBodySize, getBreakpointMessageAndClassName } from 'vs/workbench/contrib/debug/browser/breakpointsView'; +import { getExpandedBodySize, getBreakpointMessageAndIcon } from 'vs/workbench/contrib/debug/browser/breakpointsView'; import { dispose } from 'vs/base/common/lifecycle'; import { Range } from 'vs/editor/common/core/range'; import { IBreakpointData, IBreakpointUpdateData, State } from 'vs/workbench/contrib/debug/common/debug'; @@ -258,40 +258,40 @@ suite('Debug - Breakpoints', () => { ]); const breakpoints = model.getBreakpoints(); - let result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[0]); - assert.equal(result.message, 'Expression: x > 5'); - assert.equal(result.className, 'codicon-debug-breakpoint-conditional'); + let result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[0]); + assert.equal(result.message, 'Expression condition: x > 5'); + assert.equal(result.icon.id, 'debug-breakpoint-conditional'); - result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[1]); + result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[1]); assert.equal(result.message, 'Disabled Breakpoint'); - assert.equal(result.className, 'codicon-debug-breakpoint-disabled'); + assert.equal(result.icon.id, 'debug-breakpoint-disabled'); - result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[2]); + result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[2]); assert.equal(result.message, 'Log Message: hello'); - assert.equal(result.className, 'codicon-debug-breakpoint-log'); + assert.equal(result.icon.id, 'debug-breakpoint-log'); - result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[3]); + result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[3]); assert.equal(result.message, 'Hit Count: 12'); - assert.equal(result.className, 'codicon-debug-breakpoint-conditional'); + assert.equal(result.icon.id, 'debug-breakpoint-conditional'); - result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[4]); + result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[4]); assert.equal(result.message, 'Breakpoint'); - assert.equal(result.className, 'codicon-debug-breakpoint'); + assert.equal(result.icon.id, 'debug-breakpoint'); - result = getBreakpointMessageAndClassName(State.Stopped, false, breakpoints[2]); + result = getBreakpointMessageAndIcon(State.Stopped, false, breakpoints[2]); assert.equal(result.message, 'Disabled Logpoint'); - assert.equal(result.className, 'codicon-debug-breakpoint-log-disabled'); + assert.equal(result.icon.id, 'debug-breakpoint-log-disabled'); model.addDataBreakpoint('label', 'id', true, ['read']); const dataBreakpoints = model.getDataBreakpoints(); - result = getBreakpointMessageAndClassName(State.Stopped, true, dataBreakpoints[0]); + result = getBreakpointMessageAndIcon(State.Stopped, true, dataBreakpoints[0]); assert.equal(result.message, 'Data Breakpoint'); - assert.equal(result.className, 'codicon-debug-breakpoint-data'); + assert.equal(result.icon.id, 'debug-breakpoint-data'); const functionBreakpoint = model.addFunctionBreakpoint('foo', '1'); - result = getBreakpointMessageAndClassName(State.Stopped, true, functionBreakpoint); + result = getBreakpointMessageAndIcon(State.Stopped, true, functionBreakpoint); assert.equal(result.message, 'Function Breakpoint'); - assert.equal(result.className, 'codicon-debug-breakpoint-function'); + assert.equal(result.icon.id, 'debug-breakpoint-function'); const data = new Map(); data.set(breakpoints[0].getId(), { verified: false, line: 10 }); @@ -300,17 +300,17 @@ suite('Debug - Breakpoints', () => { data.set(functionBreakpoint.getId(), { verified: true }); model.setBreakpointSessionData('mocksessionid', { supportsFunctionBreakpoints: false, supportsDataBreakpoints: true, supportsLogPoints: true }, data); - result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[0]); + result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[0]); assert.equal(result.message, 'Unverified Breakpoint'); - assert.equal(result.className, 'codicon-debug-breakpoint-unverified'); + assert.equal(result.icon.id, 'debug-breakpoint-unverified'); - result = getBreakpointMessageAndClassName(State.Stopped, true, functionBreakpoint); + result = getBreakpointMessageAndIcon(State.Stopped, true, functionBreakpoint); assert.equal(result.message, 'Function breakpoints not supported by this debug type'); - assert.equal(result.className, 'codicon-debug-breakpoint-function-unverified'); + assert.equal(result.icon.id, 'debug-breakpoint-function-unverified'); - result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[2]); + result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[2]); assert.equal(result.message, 'Log Message: hello, world'); - assert.equal(result.className, 'codicon-debug-breakpoint-log'); + assert.equal(result.icon.id, 'debug-breakpoint-log'); }); test('decorations', () => { @@ -337,7 +337,7 @@ suite('Debug - Breakpoints', () => { assert.equal(decorations[0].options.beforeContentClassName, undefined); assert.equal(decorations[1].options.beforeContentClassName, `debug-breakpoint-placeholder`); assert.equal(decorations[0].options.overviewRuler?.position, OverviewRulerLane.Left); - const expected = new MarkdownString().appendCodeblock(languageIdentifier.language, 'Expression: x > 5'); + const expected = new MarkdownString().appendCodeblock(languageIdentifier.language, 'Expression condition: x > 5'); assert.deepEqual(decorations[0].options.glyphMarginHoverMessage, expected); decorations = createBreakpointDecorations(textModel, breakpoints, State.Running, true, false); diff --git a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts index ba1d0fa31..ca7c0db50 100644 --- a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts @@ -17,6 +17,8 @@ import { Constants } from 'vs/base/common/uint'; import { getContext, getContextForContributedActions, getSpecificSourceName } from 'vs/workbench/contrib/debug/browser/callStackView'; import { getStackFrameThreadAndSessionToFocus } from 'vs/workbench/contrib/debug/browser/debugService'; import { generateUuid } from 'vs/base/common/uuid'; +import { debugStackframe, debugStackframeFocused } from 'vs/workbench/contrib/debug/browser/debugIcons'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; export function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession { return new DebugSession(generateUuid(), { resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, { @@ -308,7 +310,7 @@ suite('Debug - CallStack', () => { let decorations = createDecorationsForStackFrame(firstStackFrame, firstStackFrame.range, true); assert.equal(decorations.length, 2); assert.deepEqual(decorations[0].range, new Range(1, 2, 1, 1)); - assert.equal(decorations[0].options.glyphMarginClassName, 'codicon-debug-stackframe'); + assert.equal(decorations[0].options.glyphMarginClassName, ThemeIcon.asClassName(debugStackframe)); assert.deepEqual(decorations[1].range, new Range(1, Constants.MAX_SAFE_SMALL_INTEGER, 1, 1)); assert.equal(decorations[1].options.className, 'debug-top-stack-frame-line'); assert.equal(decorations[1].options.isWholeLine, true); @@ -316,7 +318,7 @@ suite('Debug - CallStack', () => { decorations = createDecorationsForStackFrame(secondStackFrame, firstStackFrame.range, true); assert.equal(decorations.length, 2); assert.deepEqual(decorations[0].range, new Range(1, 2, 1, 1)); - assert.equal(decorations[0].options.glyphMarginClassName, 'codicon-debug-stackframe-focused'); + assert.equal(decorations[0].options.glyphMarginClassName, ThemeIcon.asClassName(debugStackframeFocused)); assert.deepEqual(decorations[1].range, new Range(1, Constants.MAX_SAFE_SMALL_INTEGER, 1, 1)); assert.equal(decorations[1].options.className, 'debug-focused-stack-frame-line'); assert.equal(decorations[1].options.isWholeLine, true); @@ -324,7 +326,7 @@ suite('Debug - CallStack', () => { decorations = createDecorationsForStackFrame(firstStackFrame, new Range(1, 5, 1, 6), true); assert.equal(decorations.length, 3); assert.deepEqual(decorations[0].range, new Range(1, 2, 1, 1)); - assert.equal(decorations[0].options.glyphMarginClassName, 'codicon-debug-stackframe'); + assert.equal(decorations[0].options.glyphMarginClassName, ThemeIcon.asClassName(debugStackframe)); assert.deepEqual(decorations[1].range, new Range(1, Constants.MAX_SAFE_SMALL_INTEGER, 1, 1)); assert.equal(decorations[1].options.className, 'debug-top-stack-frame-line'); assert.equal(decorations[1].options.isWholeLine, true); diff --git a/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts b/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts index 4a885f892..96a346ab9 100644 --- a/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts @@ -7,7 +7,7 @@ import { URI as uri } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { Position, IPosition } from 'vs/editor/common/core/position'; -import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IDebugModel, IViewModel, IBreakpoint, LoadedSourceEvent, IThread, IRawModelUpdate, IFunctionBreakpoint, IExceptionBreakpoint, IDebugger, IExceptionInfo, AdapterEndEvent, IReplElement, IExpression, IReplElementSource, IDataBreakpoint, IDebugSessionOptions, IEvaluate } from 'vs/workbench/contrib/debug/common/debug'; +import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IDebugModel, IViewModel, IBreakpoint, LoadedSourceEvent, IThread, IRawModelUpdate, IFunctionBreakpoint, IExceptionBreakpoint, IDebugger, IExceptionInfo, AdapterEndEvent, IReplElement, IExpression, IReplElementSource, IDataBreakpoint, IDebugSessionOptions, IEvaluate, IAdapterManager } from 'vs/workbench/contrib/debug/common/debug'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import Severity from 'vs/base/common/severity'; import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; @@ -17,6 +17,7 @@ import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompou import { CancellationToken } from 'vs/base/common/cancellation'; import { TestFileService } from 'vs/workbench/test/browser/workbenchTestServices'; import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService'; +import { ITextModel } from 'vs/editor/common/model'; const fileService = new TestFileService(); export const mockUriIdentityService = new UriIdentityService(fileService); @@ -49,6 +50,14 @@ export class MockDebugService implements IDebugService { throw new Error('not implemented'); } + getAdapterManager(): IAdapterManager { + throw new Error('Method not implemented.'); + } + + canSetBreakpointsIn(model: ITextModel): boolean { + throw new Error('Method not implemented.'); + } + public focusStackFrame(focusedStackFrame: IStackFrame): Promise { throw new Error('not implemented'); } @@ -77,6 +86,10 @@ export class MockDebugService implements IDebugService { throw new Error('not implemented'); } + setExceptionBreakpointCondition(breakpoint: IExceptionBreakpoint, condition: string): Promise { + throw new Error('Method not implemented.'); + } + public addFunctionBreakpoint(): void { } public moveWatchExpression(id: string, position: number): void { } diff --git a/src/vs/workbench/contrib/debug/test/browser/repl.test.ts b/src/vs/workbench/contrib/debug/test/browser/repl.test.ts index 815ec28a7..288fa962a 100644 --- a/src/vs/workbench/contrib/debug/test/browser/repl.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/repl.test.ts @@ -64,6 +64,41 @@ suite('Debug - REPL', () => { assert.equal(elements[0], '1\n'); assert.equal(elements[1], '23\n45\n'); assert.equal(elements[2], '6'); + + repl.removeReplExpressions(); + repl.appendToRepl(session, 'first line\n', severity.Info); + repl.appendToRepl(session, 'first line\n', severity.Info); + repl.appendToRepl(session, 'first line\n', severity.Info); + repl.appendToRepl(session, 'second line', severity.Info); + repl.appendToRepl(session, 'second line', severity.Info); + repl.appendToRepl(session, 'third line', severity.Info); + elements = repl.getReplElements(); + assert.equal(elements.length, 3); + assert.equal(elements[0], 'first line\n'); + assert.equal(elements[0].count, 3); + assert.equal(elements[1], 'second line'); + assert.equal(elements[1].count, 2); + assert.equal(elements[2], 'third line'); + assert.equal(elements[2].count, 1); + }); + + test('repl output count', () => { + const session = createMockSession(model); + const repl = new ReplModel(); + repl.appendToRepl(session, 'first line\n', severity.Info); + repl.appendToRepl(session, 'first line\n', severity.Info); + repl.appendToRepl(session, 'first line\n', severity.Info); + repl.appendToRepl(session, 'second line', severity.Info); + repl.appendToRepl(session, 'second line', severity.Info); + repl.appendToRepl(session, 'third line', severity.Info); + const elements = repl.getReplElements(); + assert.equal(elements.length, 3); + assert.equal(elements[0], 'first line\n'); + assert.equal(elements[0].count, 3); + assert.equal(elements[1], 'second line'); + assert.equal(elements[1].count, 2); + assert.equal(elements[2], 'third line'); + assert.equal(elements[2].count, 1); }); test('repl merging', () => { @@ -85,7 +120,7 @@ suite('Debug - REPL', () => { assert.equal(grandChild.getReplElements().length, 1); assert.equal(child3.getReplElements().length, 0); - grandChild.appendToRepl('1\n', severity.Info); + grandChild.appendToRepl('2\n', severity.Info); assert.equal(parentChanges, 2); assert.equal(parent.getReplElements().length, 2); assert.equal(child1.getReplElements().length, 0); @@ -93,7 +128,7 @@ suite('Debug - REPL', () => { assert.equal(grandChild.getReplElements().length, 2); assert.equal(child3.getReplElements().length, 0); - child3.appendToRepl('1\n', severity.Info); + child3.appendToRepl('3\n', severity.Info); assert.equal(parentChanges, 2); assert.equal(parent.getReplElements().length, 2); assert.equal(child1.getReplElements().length, 0); @@ -101,7 +136,7 @@ suite('Debug - REPL', () => { assert.equal(grandChild.getReplElements().length, 2); assert.equal(child3.getReplElements().length, 1); - child1.appendToRepl('1\n', severity.Info); + child1.appendToRepl('4\n', severity.Info); assert.equal(parentChanges, 2); assert.equal(parent.getReplElements().length, 2); assert.equal(child1.getReplElements().length, 1); diff --git a/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts b/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts index b56194b26..2a915d524 100644 --- a/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts +++ b/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts @@ -49,8 +49,8 @@ suite('Debug - ANSI Handling', () => { assert.equal(0, root.children.length); - appendStylizedStringToContainer(root, 'content1', ['class1', 'class2'], linkDetector, session); - appendStylizedStringToContainer(root, 'content2', ['class2', 'class3'], linkDetector, session); + appendStylizedStringToContainer(root, 'content1', ['class1', 'class2'], linkDetector, session.root); + appendStylizedStringToContainer(root, 'content2', ['class2', 'class3'], linkDetector, session.root); assert.equal(2, root.children.length); @@ -80,7 +80,7 @@ suite('Debug - ANSI Handling', () => { * @returns An {@link HTMLSpanElement} that contains the stylized text. */ function getSequenceOutput(sequence: string): HTMLSpanElement { - const root: HTMLSpanElement = handleANSIOutput(sequence, linkDetector, themeService, session); + const root: HTMLSpanElement = handleANSIOutput(sequence, linkDetector, themeService, session.root); assert.equal(1, root.children.length); const child: Node = root.lastChild!; if (child instanceof HTMLSpanElement) { @@ -320,7 +320,7 @@ suite('Debug - ANSI Handling', () => { if (elementsExpected === undefined) { elementsExpected = assertions.length; } - const root: HTMLSpanElement = handleANSIOutput(sequence, linkDetector, themeService, session); + const root: HTMLSpanElement = handleANSIOutput(sequence, linkDetector, themeService, session.root); assert.equal(elementsExpected, root.children.length); for (let i = 0; i < elementsExpected; i++) { const child: Node = root.children[i]; diff --git a/src/vs/workbench/contrib/debug/test/node/debugger.test.ts b/src/vs/workbench/contrib/debug/test/node/debugger.test.ts index 6fda912c7..68a34b4b1 100644 --- a/src/vs/workbench/contrib/debug/test/node/debugger.test.ts +++ b/src/vs/workbench/contrib/debug/test/node/debugger.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { join, normalize } from 'vs/base/common/path'; import * as platform from 'vs/base/common/platform'; -import { IDebugAdapterExecutable, IConfigurationManager, IConfig, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugAdapterExecutable, IConfig, IDebugSession, IAdapterManager } from 'vs/workbench/contrib/debug/common/debug'; import { Debugger } from 'vs/workbench/contrib/debug/common/debugger'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { URI } from 'vs/base/common/uri'; @@ -22,7 +22,6 @@ suite('Debug - Debugger', () => { const debuggerContribution = { type: 'mock', label: 'Mock Debug', - enableBreakpointsFor: { 'languageIds': ['markdown'] }, program: './out/mock/mockDebug.js', args: ['arg1', 'arg2'], configurationAttributes: { @@ -123,7 +122,7 @@ suite('Debug - Debugger', () => { }; - const configurationManager = { + const adapterManager = { getDebugAdapterDescriptor(session: IDebugSession, config: IConfig): Promise { return Promise.resolve(undefined); } @@ -133,7 +132,7 @@ suite('Debug - Debugger', () => { const testResourcePropertiesService = new TestTextResourcePropertiesService(configurationService); setup(() => { - _debugger = new Debugger(configurationManager, debuggerContribution, extensionDescriptor0, configurationService, testResourcePropertiesService, undefined!, undefined!, undefined!); + _debugger = new Debugger(adapterManager, debuggerContribution, extensionDescriptor0, configurationService, testResourcePropertiesService, undefined!, undefined!, undefined!, undefined!); }); teardown(() => { diff --git a/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts b/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts deleted file mode 100644 index 1d9314233..000000000 --- a/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.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 * as nls from 'vs/nls'; - -import { registerEditorAction, EditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { MenuId } from 'vs/platform/actions/common/actions'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; - -const EMMET_COMMANDS_PREFIX = '>Emmet: '; - -class ShowEmmetCommandsAction extends EditorAction { - - constructor() { - super({ - id: 'workbench.action.showEmmetCommands', - label: nls.localize('showEmmetCommands', "Show Emmet Commands"), - alias: 'Show Emmet Commands', - precondition: EditorContextKeys.writable, - menuOpts: { - menuId: MenuId.MenubarEditMenu, - group: '5_insert', - title: nls.localize({ key: 'miShowEmmetCommands', comment: ['&& denotes a mnemonic'] }, "E&&mmet..."), - order: 4 - } - }); - } - - public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { - const quickInputService = accessor.get(IQuickInputService); - quickInputService.quickAccess.show(EMMET_COMMANDS_PREFIX); - } -} - -registerEditorAction(ShowEmmetCommandsAction); diff --git a/src/vs/workbench/contrib/emmet/browser/emmet.contribution.ts b/src/vs/workbench/contrib/emmet/browser/emmet.contribution.ts index b603e987b..f29d6b2c0 100644 --- a/src/vs/workbench/contrib/emmet/browser/emmet.contribution.ts +++ b/src/vs/workbench/contrib/emmet/browser/emmet.contribution.ts @@ -3,6 +3,5 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import './actions/showEmmetCommands'; import './actions/expandAbbreviation'; diff --git a/src/vs/workbench/contrib/experiments/common/experimentService.ts b/src/vs/workbench/contrib/experiments/common/experimentService.ts index ee883ca58..a544ac4c6 100644 --- a/src/vs/workbench/contrib/experiments/common/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/common/experimentService.ts @@ -5,7 +5,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Emitter, Event } from 'vs/base/common/event'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -222,7 +222,7 @@ export class ExperimentService extends Disposable implements IExperimentService const storageKey = 'experiments.' + experimentId; const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {}); experimentState.state = ExperimentState.Complete; - this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.GLOBAL); + this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.GLOBAL, StorageTarget.MACHINE); } protected async getExperiments(): Promise { @@ -280,7 +280,7 @@ export class ExperimentService extends Disposable implements IExperimentService }); } if (enabledExperiments.length) { - this.storageService.store('allExperiments', JSON.stringify(enabledExperiments), StorageScope.GLOBAL); + this.storageService.store('allExperiments', JSON.stringify(enabledExperiments), StorageScope.GLOBAL, StorageTarget.MACHINE); } else { this.storageService.remove('allExperiments', StorageScope.GLOBAL); } @@ -350,7 +350,7 @@ export class ExperimentService extends Disposable implements IExperimentService return this.shouldRunExperiment(experiment, processedExperiment).then((state: ExperimentState) => { experimentState.state = processedExperiment.state = state; - this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.GLOBAL); + this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.GLOBAL, StorageTarget.MACHINE); if (state === ExperimentState.Run) { this.fireRunExperiment(processedExperiment); @@ -370,7 +370,7 @@ export class ExperimentService extends Disposable implements IExperimentService // Ensure we dont store duplicates const distinctExperiments = distinct(runExperimentIdsFromStorage); if (runExperimentIdsFromStorage.length !== distinctExperiments.length) { - this.storageService.store('currentOrPreviouslyRunExperiments', JSON.stringify(distinctExperiments), StorageScope.GLOBAL); + this.storageService.store('currentOrPreviouslyRunExperiments', JSON.stringify(distinctExperiments), StorageScope.GLOBAL, StorageTarget.MACHINE); } } @@ -399,7 +399,7 @@ export class ExperimentService extends Disposable implements IExperimentService const key = experimentEventStorageKey(event); const record = getCurrentActivationRecord(safeParse(this.storageService.get(key, StorageScope.GLOBAL), undefined)); record.count[0]++; - this.storageService.store(key, JSON.stringify(record), StorageScope.GLOBAL); + this.storageService.store(key, JSON.stringify(record), StorageScope.GLOBAL, StorageTarget.MACHINE); this._experiments .filter(e => e.state === ExperimentState.Evaluating && e.raw?.condition?.activationEvent?.event === event) @@ -558,12 +558,12 @@ export class ExperimentService extends Disposable implements IExperimentService if (filePathCheck && workspaceCheck) { latestExperimentState.editCount = (latestExperimentState.editCount || 0) + 1; latestExperimentState.lastEditedDate = date; - this.storageService.store(storageKey, JSON.stringify(latestExperimentState), StorageScope.GLOBAL); + this.storageService.store(storageKey, JSON.stringify(latestExperimentState), StorageScope.GLOBAL, StorageTarget.MACHINE); } }); if (typeof latestExperimentState.editCount === 'number' && latestExperimentState.editCount >= fileEdits.minEditCount) { processedExperiment.state = latestExperimentState.state = (typeof condition.userProbability === 'number' && Math.random() < condition.userProbability && this.checkExperimentDependencies(experiment)) ? ExperimentState.Run : ExperimentState.NoRun; - this.storageService.store(storageKey, JSON.stringify(latestExperimentState), StorageScope.GLOBAL); + this.storageService.store(storageKey, JSON.stringify(latestExperimentState), StorageScope.GLOBAL, StorageTarget.MACHINE); if (latestExperimentState.state === ExperimentState.Run && processedExperiment.action && ExperimentActionType[processedExperiment.action.type] === ExperimentActionType.Prompt) { this.fireRunExperiment(processedExperiment); } diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts index b0e18778a..584f71b24 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts @@ -64,7 +64,7 @@ suite('Experimental Prompts', () => { storageData = {}; instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => a === 'experiments.experiment1' ? JSON.stringify(storageData) : c, - store: (a, b, c) => { + store: (a, b, c, d) => { if (a === 'experiments.experiment1') { storageData = JSON.parse(b + ''); } diff --git a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts new file mode 100644 index 000000000..96c54f0a8 --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts @@ -0,0 +1,474 @@ +/*--------------------------------------------------------------------------------------------- + * 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/runtimeExtensionsEditor'; +import * as nls from 'vs/nls'; +import { Action, IAction, Separator } from 'vs/base/common/actions'; +import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IExtensionsWorkbenchService, IExtension } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IExtensionService, IExtensionsStatus, IExtensionHostProfile } from 'vs/workbench/services/extensions/common/extensions'; +import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; +import { WorkbenchList } from 'vs/platform/list/browser/listService'; +import { append, $, reset, Dimension, clearNode } from 'vs/base/browser/dom'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { RunOnceScheduler } from 'vs/base/common/async'; +import { EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { memoize } from 'vs/base/common/decorators'; +import { isNonEmptyArray } from 'vs/base/common/arrays'; +import { Event } from 'vs/base/common/event'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { renderCodicons } from 'vs/base/browser/codicons'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { Schemas } from 'vs/base/common/network'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; +import { domEvent } from 'vs/base/browser/event'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/runtimeExtensionsInput'; + +interface IExtensionProfileInformation { + /** + * segment when the extension was running. + * 2*i = segment start time + * 2*i+1 = segment end time + */ + segments: number[]; + /** + * total time when the extension was running. + * (sum of all segment lengths). + */ + totalTime: number; +} + +export interface IRuntimeExtension { + originalIndex: number; + description: IExtensionDescription; + marketplaceInfo: IExtension; + status: IExtensionsStatus; + profileInfo?: IExtensionProfileInformation; + unresponsiveProfile?: IExtensionHostProfile; +} + +export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { + + public static readonly ID: string = 'workbench.editor.runtimeExtensions'; + + private _list: WorkbenchList | null; + private _elements: IRuntimeExtension[] | null; + private _updateSoon: RunOnceScheduler; + + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IThemeService themeService: IThemeService, + @IContextKeyService contextKeyService: IContextKeyService, + @IExtensionsWorkbenchService private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionService private readonly _extensionService: IExtensionService, + @INotificationService private readonly _notificationService: INotificationService, + @IContextMenuService private readonly _contextMenuService: IContextMenuService, + @IInstantiationService protected readonly _instantiationService: IInstantiationService, + @IStorageService storageService: IStorageService, + @ILabelService private readonly _labelService: ILabelService, + @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, + ) { + super(AbstractRuntimeExtensionsEditor.ID, telemetryService, themeService, storageService); + + this._list = null; + this._elements = null; + this._updateSoon = this._register(new RunOnceScheduler(() => this._updateExtensions(), 200)); + + this._register(this._extensionService.onDidChangeExtensionsStatus(() => this._updateSoon.schedule())); + this._updateExtensions(); + } + + protected async _updateExtensions(): Promise { + this._elements = await this._resolveExtensions(); + if (this._list) { + this._list.splice(0, this._list.length, this._elements); + } + } + + private async _resolveExtensions(): Promise { + // We only deal with extensions with source code! + const extensionsDescriptions = (await this._extensionService.getExtensions()).filter((extension) => { + return Boolean(extension.main) || Boolean(extension.browser); + }); + let marketplaceMap: { [id: string]: IExtension; } = Object.create(null); + const marketPlaceExtensions = await this._extensionsWorkbenchService.queryLocal(); + for (let extension of marketPlaceExtensions) { + marketplaceMap[ExtensionIdentifier.toKey(extension.identifier.id)] = extension; + } + + let statusMap = this._extensionService.getExtensionsStatus(); + + // group profile segments by extension + let segments: { [id: string]: number[]; } = Object.create(null); + + const profileInfo = this._getProfileInfo(); + if (profileInfo) { + let currentStartTime = profileInfo.startTime; + for (let i = 0, len = profileInfo.deltas.length; i < len; i++) { + const id = profileInfo.ids[i]; + const delta = profileInfo.deltas[i]; + + let extensionSegments = segments[ExtensionIdentifier.toKey(id)]; + if (!extensionSegments) { + extensionSegments = []; + segments[ExtensionIdentifier.toKey(id)] = extensionSegments; + } + + extensionSegments.push(currentStartTime); + currentStartTime = currentStartTime + delta; + extensionSegments.push(currentStartTime); + } + } + + let result: IRuntimeExtension[] = []; + for (let i = 0, len = extensionsDescriptions.length; i < len; i++) { + const extensionDescription = extensionsDescriptions[i]; + + let profileInfo: IExtensionProfileInformation | null = null; + if (profileInfo) { + let extensionSegments = segments[ExtensionIdentifier.toKey(extensionDescription.identifier)] || []; + let extensionTotalTime = 0; + for (let j = 0, lenJ = extensionSegments.length / 2; j < lenJ; j++) { + const startTime = extensionSegments[2 * j]; + const endTime = extensionSegments[2 * j + 1]; + extensionTotalTime += (endTime - startTime); + } + profileInfo = { + segments: extensionSegments, + totalTime: extensionTotalTime + }; + } + + result[i] = { + originalIndex: i, + description: extensionDescription, + marketplaceInfo: marketplaceMap[ExtensionIdentifier.toKey(extensionDescription.identifier)], + status: statusMap[extensionDescription.identifier.value], + profileInfo: profileInfo || undefined, + unresponsiveProfile: this._getUnresponsiveProfile(extensionDescription.identifier) + }; + } + + result = result.filter(element => element.status.activationTimes); + + // bubble up extensions that have caused slowness + + const isUnresponsive = (extension: IRuntimeExtension): boolean => + extension.unresponsiveProfile === profileInfo; + + const profileTime = (extension: IRuntimeExtension): number => + extension.profileInfo?.totalTime ?? 0; + + const activationTime = (extension: IRuntimeExtension): number => + (extension.status.activationTimes?.codeLoadingTime ?? 0) + + (extension.status.activationTimes?.activateCallTime ?? 0); + + result = result.sort((a, b) => { + if (isUnresponsive(a) || isUnresponsive(b)) { + return +isUnresponsive(b) - +isUnresponsive(a); + } else if (profileTime(a) || profileTime(b)) { + return profileTime(b) - profileTime(a); + } else if (activationTime(a) || activationTime(b)) { + return activationTime(b) - activationTime(a); + } + return a.originalIndex - b.originalIndex; + }); + + return result; + } + + protected createEditor(parent: HTMLElement): void { + parent.classList.add('runtime-extensions-editor'); + + const TEMPLATE_ID = 'runtimeExtensionElementTemplate'; + + const delegate = new class implements IListVirtualDelegate{ + getHeight(element: IRuntimeExtension): number { + return 62; + } + getTemplateId(element: IRuntimeExtension): string { + return TEMPLATE_ID; + } + }; + + interface IRuntimeExtensionTemplateData { + root: HTMLElement; + element: HTMLElement; + icon: HTMLImageElement; + name: HTMLElement; + version: HTMLElement; + msgContainer: HTMLElement; + actionbar: ActionBar; + activationTime: HTMLElement; + profileTime: HTMLElement; + disposables: IDisposable[]; + elementDisposables: IDisposable[]; + } + + const renderer: IListRenderer = { + templateId: TEMPLATE_ID, + renderTemplate: (root: HTMLElement): IRuntimeExtensionTemplateData => { + const element = append(root, $('.extension')); + const iconContainer = append(element, $('.icon-container')); + const icon = append(iconContainer, $('img.icon')); + + const desc = append(element, $('div.desc')); + const headerContainer = append(desc, $('.header-container')); + const header = append(headerContainer, $('.header')); + const name = append(header, $('div.name')); + const version = append(header, $('span.version')); + + const msgContainer = append(desc, $('div.msg')); + + const actionbar = new ActionBar(desc, { animated: false }); + actionbar.onDidRun(({ error }) => error && this._notificationService.error(error)); + + + const timeContainer = append(element, $('.time')); + const activationTime = append(timeContainer, $('div.activation-time')); + const profileTime = append(timeContainer, $('div.profile-time')); + + const disposables = [actionbar]; + + return { + root, + element, + icon, + name, + version, + actionbar, + activationTime, + profileTime, + msgContainer, + disposables, + elementDisposables: [], + }; + }, + + renderElement: (element: IRuntimeExtension, index: number, data: IRuntimeExtensionTemplateData): void => { + + data.elementDisposables = dispose(data.elementDisposables); + + data.root.classList.toggle('odd', index % 2 === 1); + + const onError = Event.once(domEvent(data.icon, 'error')); + onError(() => data.icon.src = element.marketplaceInfo.iconUrlFallback, null, data.elementDisposables); + data.icon.src = element.marketplaceInfo.iconUrl; + + if (!data.icon.complete) { + data.icon.style.visibility = 'hidden'; + data.icon.onload = () => data.icon.style.visibility = 'inherit'; + } else { + data.icon.style.visibility = 'inherit'; + } + data.name.textContent = element.marketplaceInfo.displayName; + data.version.textContent = element.description.version; + + const activationTimes = element.status.activationTimes!; + let syncTime = activationTimes.codeLoadingTime + activationTimes.activateCallTime; + data.activationTime.textContent = activationTimes.activationReason.startup ? `Startup Activation: ${syncTime}ms` : `Activation: ${syncTime}ms`; + + data.actionbar.clear(); + const slowExtensionAction = this._createSlowExtensionAction(element); + if (slowExtensionAction) { + data.actionbar.push(slowExtensionAction, { icon: true, label: true }); + } + if (isNonEmptyArray(element.status.runtimeErrors)) { + const reportExtensionIssueAction = this._createReportExtensionIssueAction(element); + if (reportExtensionIssueAction) { + data.actionbar.push(reportExtensionIssueAction, { icon: true, label: true }); + } + } + + let title: string; + const activationId = activationTimes.activationReason.extensionId.value; + const activationEvent = activationTimes.activationReason.activationEvent; + if (activationEvent === '*') { + title = nls.localize('starActivation', "Activated by {0} on start-up", activationId); + } else if (/^workspaceContains:/.test(activationEvent)) { + let fileNameOrGlob = activationEvent.substr('workspaceContains:'.length); + if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0) { + title = nls.localize({ + key: 'workspaceContainsGlobActivation', + comment: [ + '{0} will be a glob pattern' + ] + }, "Activated by {1} because a file matching {1} exists in your workspace", fileNameOrGlob, activationId); + } else { + title = nls.localize({ + key: 'workspaceContainsFileActivation', + comment: [ + '{0} will be a file name' + ] + }, "Activated by {1} because file {0} exists in your workspace", fileNameOrGlob, activationId); + } + } else if (/^workspaceContainsTimeout:/.test(activationEvent)) { + const glob = activationEvent.substr('workspaceContainsTimeout:'.length); + title = nls.localize({ + key: 'workspaceContainsTimeout', + comment: [ + '{0} will be a glob pattern' + ] + }, "Activated by {1} because searching for {0} took too long", glob, activationId); + } else if (activationEvent === 'onStartupFinished') { + title = nls.localize({ + key: 'startupFinishedActivation', + comment: [ + 'This refers to an extension. {0} will be an activation event.' + ] + }, "Activated by {0} after start-up finished", activationId); + } else if (/^onLanguage:/.test(activationEvent)) { + let language = activationEvent.substr('onLanguage:'.length); + title = nls.localize('languageActivation', "Activated by {1} because you opened a {0} file", language, activationId); + } else { + title = nls.localize({ + key: 'workspaceGenericActivation', + comment: [ + 'The {0} placeholder will be an activation event, like e.g. \'language:typescript\', \'debug\', etc.' + ] + }, "Activated by {1} on {0}", activationEvent, activationId); + } + data.activationTime.title = title; + + clearNode(data.msgContainer); + + if (this._getUnresponsiveProfile(element.description.identifier)) { + const el = $('span', undefined, ...renderCodicons(` $(alert) Unresponsive`)); + el.title = nls.localize('unresponsive.title', "Extension has caused the extension host to freeze."); + data.msgContainer.appendChild(el); + } + + if (isNonEmptyArray(element.status.runtimeErrors)) { + const el = $('span', undefined, ...renderCodicons(`$(bug) ${nls.localize('errors', "{0} uncaught errors", element.status.runtimeErrors.length)}`)); + data.msgContainer.appendChild(el); + } + + if (element.status.messages && element.status.messages.length > 0) { + const el = $('span', undefined, ...renderCodicons(`$(alert) ${element.status.messages[0].message}`)); + data.msgContainer.appendChild(el); + } + + if (element.description.extensionLocation.scheme === Schemas.vscodeRemote) { + const el = $('span', undefined, ...renderCodicons(`$(remote) ${element.description.extensionLocation.authority}`)); + data.msgContainer.appendChild(el); + + const hostLabel = this._labelService.getHostLabel(Schemas.vscodeRemote, this._environmentService.remoteAuthority); + if (hostLabel) { + reset(el, ...renderCodicons(`$(remote) ${hostLabel}`)); + } + } + + if (element.profileInfo) { + data.profileTime.textContent = `Profile: ${(element.profileInfo.totalTime / 1000).toFixed(2)}ms`; + } else { + data.profileTime.textContent = ''; + } + + }, + + disposeTemplate: (data: IRuntimeExtensionTemplateData): void => { + data.disposables = dispose(data.disposables); + } + }; + + this._list = >this._instantiationService.createInstance(WorkbenchList, + 'RuntimeExtensions', + parent, delegate, [renderer], { + multipleSelectionSupport: false, + setRowLineHeight: false, + horizontalScrolling: false, + overrideStyles: { + listBackground: editorBackground + }, + accessibilityProvider: new class implements IListAccessibilityProvider { + getWidgetAriaLabel(): string { + return nls.localize('runtimeExtensions', "Runtime Extensions"); + } + getAriaLabel(element: IRuntimeExtension): string | null { + return element.description.name; + } + } + }); + + this._list.splice(0, this._list.length, this._elements || undefined); + + this._list.onContextMenu((e) => { + if (!e.element) { + return; + } + + const actions: IAction[] = []; + + const reportExtensionIssueAction = this._createReportExtensionIssueAction(e.element); + if (reportExtensionIssueAction) { + actions.push(reportExtensionIssueAction); + actions.push(new Separator()); + } + + actions.push(new Action('runtimeExtensionsEditor.action.disableWorkspace', nls.localize('disable workspace', "Disable (Workspace)"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo, EnablementState.DisabledWorkspace))); + actions.push(new Action('runtimeExtensionsEditor.action.disable', nls.localize('disable', "Disable"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo, EnablementState.DisabledGlobally))); + actions.push(new Separator()); + + const profileAction = this._createProfileAction(); + if (profileAction) { + actions.push(profileAction); + } + const saveExtensionHostProfileAction = this.saveExtensionHostProfileAction; + if (saveExtensionHostProfileAction) { + actions.push(saveExtensionHostProfileAction); + } + + this._contextMenuService.showContextMenu({ + getAnchor: () => e.anchor, + getActions: () => actions + }); + }); + } + + @memoize + private get saveExtensionHostProfileAction(): IAction | null { + return this._createSaveExtensionHostProfileAction(); + } + + public layout(dimension: Dimension): void { + if (this._list) { + this._list.layout(dimension.height); + } + } + + protected abstract _getProfileInfo(): IExtensionHostProfile | null; + protected abstract _getUnresponsiveProfile(extensionId: ExtensionIdentifier): IExtensionHostProfile | undefined; + protected abstract _createSlowExtensionAction(element: IRuntimeExtension): Action | null; + protected abstract _createReportExtensionIssueAction(element: IRuntimeExtension): Action | null; + protected abstract _createSaveExtensionHostProfileAction(): Action | null; + protected abstract _createProfileAction(): Action | null; +} + +export class ShowRuntimeExtensionsAction extends Action { + static readonly ID = 'workbench.action.showRuntimeExtensions'; + static readonly LABEL = nls.localize('showRuntimeExtensions', "Show Running Extensions"); + + constructor( + id: string, label: string, + @IEditorService private readonly _editorService: IEditorService + ) { + super(id, label); + } + + public async run(e?: any): Promise { + await this._editorService.openEditor(RuntimeExtensionsInput.instance, { revealIfOpened: true, pinned: true }); + } +} diff --git a/src/vs/workbench/contrib/extensions/browser/browserRuntimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/browser/browserRuntimeExtensionsEditor.ts new file mode 100644 index 000000000..a3b1e8090 --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/browserRuntimeExtensionsEditor.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 { Action } from 'vs/base/common/actions'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IExtensionService, IExtensionHostProfile } from 'vs/workbench/services/extensions/common/extensions'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { AbstractRuntimeExtensionsEditor, IRuntimeExtension } from 'vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor'; + +export class RuntimeExtensionsEditor extends AbstractRuntimeExtensionsEditor { + + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IThemeService themeService: IThemeService, + @IContextKeyService contextKeyService: IContextKeyService, + @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionService extensionService: IExtensionService, + @INotificationService notificationService: INotificationService, + @IContextMenuService contextMenuService: IContextMenuService, + @IInstantiationService instantiationService: IInstantiationService, + @IStorageService storageService: IStorageService, + @ILabelService labelService: ILabelService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + ) { + super(telemetryService, themeService, contextKeyService, extensionsWorkbenchService, extensionService, notificationService, contextMenuService, instantiationService, storageService, labelService, environmentService); + } + + protected _getProfileInfo(): IExtensionHostProfile | null { + return null; + } + + protected _getUnresponsiveProfile(extensionId: ExtensionIdentifier): IExtensionHostProfile | undefined { + return undefined; + } + + protected _createSlowExtensionAction(element: IRuntimeExtension): Action | null { + return null; + } + + protected _createReportExtensionIssueAction(element: IRuntimeExtension): Action | null { + return null; + } + + protected _createSaveExtensionHostProfileAction(): Action | null { + return null; + } + + protected _createProfileAction(): Action | null { + return null; + } +} diff --git a/src/vs/workbench/contrib/extensions/browser/dynamicWorkspaceRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/dynamicWorkspaceRecommendations.ts index 87780840a..87e238bf2 100644 --- a/src/vs/workbench/contrib/extensions/browser/dynamicWorkspaceRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/dynamicWorkspaceRecommendations.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IFileService } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -81,7 +81,7 @@ export class DynamicWorkspaceRecommendations extends ExtensionRecommendations { const workspaceTip = workspacesTips.filter(workspaceTip => isNonEmptyArray(workspaceTip.remoteSet) && workspaceTip.remoteSet.indexOf(hashedRemote) > -1)[0]; if (workspaceTip) { this._recommendations = workspaceTip.recommendations.map(id => this.toExtensionRecommendation(id, folder)); - this.storageService.store(dynamicWorkspaceRecommendationsStorageKey, JSON.stringify({ recommendations: workspaceTip.recommendations, timestamp: Date.now() }), StorageScope.WORKSPACE); + this.storageService.store(dynamicWorkspaceRecommendationsStorageKey, JSON.stringify({ recommendations: workspaceTip.recommendations, timestamp: Date.now() }), StorageScope.WORKSPACE, StorageTarget.MACHINE); this.telemetryService.publicLog2<{ count: number, cache: number }, DynamicWorkspaceRecommendationsClassification>('dynamicWorkspaceRecommendations', { count: this._recommendations.length, cache: 0 }); return; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 16dac4771..7a58b96da 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -14,7 +14,7 @@ import { Action, IAction } from 'vs/base/common/actions'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { dispose, toDisposable, Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { domEvent } from 'vs/base/browser/event'; -import { append, $, finalHandler, join, hide, show, addDisposableListener, EventType } from 'vs/base/browser/dom'; +import { append, $, finalHandler, join, hide, show, addDisposableListener, EventType, setParentFlowTo } from 'vs/base/browser/dom'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -27,7 +27,12 @@ import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, import { RatingsWidget, InstallCountWidget, RemoteBadgeWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; import { EditorOptions, IEditorOpenContext } from 'vs/workbench/common/editor'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { UpdateAction, ReloadAction, MaliciousStatusLabelAction, IgnoreExtensionRecommendationAction, UndoIgnoreExtensionRecommendationAction, EnableDropDownAction, DisableDropDownAction, StatusLabelAction, SetFileIconThemeAction, SetColorThemeAction, RemoteInstallAction, ExtensionToolTipAction, SystemDisabledWarningAction, LocalInstallAction, SyncIgnoredIconAction, SetProductIconThemeAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, UninstallAction, ExtensionActionWithDropdownActionViewItem } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { + UpdateAction, ReloadAction, MaliciousStatusLabelAction, EnableDropDownAction, DisableDropDownAction, StatusLabelAction, SetFileIconThemeAction, SetColorThemeAction, + RemoteInstallAction, ExtensionToolTipAction, SystemDisabledWarningAction, LocalInstallAction, ToggleSyncExtensionAction, SetProductIconThemeAction, + ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, UninstallAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, + InstallAnotherVersionAction, ExtensionEditorManageExtensionAction, WebInstallAction +} from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener'; @@ -38,7 +43,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Color } from 'vs/base/common/color'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { ExtensionsTree, ExtensionData, ExtensionsGridView, getExtensions } from 'vs/workbench/contrib/extensions/browser/extensionsViewer'; import { ShowCurrentReleaseNotesActionId } from 'vs/workbench/contrib/update/common/update'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; @@ -251,6 +256,9 @@ export class ExtensionEditor extends EditorPane { const extensionActionBar = this._register(new ActionBar(extensionActions, { animated: false, actionViewItemProvider: (action: IAction) => { + if (action instanceof ExtensionDropDownAction) { + return action.createActionViewItem(); + } if (action instanceof ActionWithDropDownAction) { return new ExtensionActionWithDropdownActionViewItem(action, { icon: true, label: true, menuActionsOrProvider: { getActions: () => action.menuActions }, menuActionClassNames: (action.class || '').split(' ') }, this.contextMenuService); } @@ -276,6 +284,7 @@ export class ExtensionEditor extends EditorPane { const navbar = new NavBar(body); const content = append(body, $('.content')); + content.id = generateUuid(); // An id is needed for the webview parent flow to this.template = { builtin, @@ -323,8 +332,6 @@ export class ExtensionEditor extends EditorPane { } private async updateTemplate(input: ExtensionsInput, template: IExtensionEditorTemplate, preserveFocus: boolean): Promise { - const runningExtensions = await this.extensionService.getExtensions(); - this.activeElement = null; this.editorLoadComplete = false; const extension = input.extension; @@ -405,7 +412,6 @@ export class ExtensionEditor extends EditorPane { const systemDisabledWarningAction = this.instantiationService.createInstance(SystemDisabledWarningAction); const actions = [ reloadAction, - this.instantiationService.createInstance(SyncIgnoredIconAction), this.instantiationService.createInstance(StatusLabelAction), this.instantiationService.createInstance(UpdateAction), this.instantiationService.createInstance(SetColorThemeAction, await this.workbenchThemeService.getColorThemes()), @@ -413,13 +419,19 @@ export class ExtensionEditor extends EditorPane { this.instantiationService.createInstance(SetProductIconThemeAction, await this.workbenchThemeService.getProductIconThemes()), this.instantiationService.createInstance(EnableDropDownAction), - this.instantiationService.createInstance(DisableDropDownAction, runningExtensions), + this.instantiationService.createInstance(DisableDropDownAction), this.instantiationService.createInstance(RemoteInstallAction, false), this.instantiationService.createInstance(LocalInstallAction), + this.instantiationService.createInstance(WebInstallAction), combinedInstallAction, this.instantiationService.createInstance(InstallingLabelAction), - this.instantiationService.createInstance(UninstallAction), + this.instantiationService.createInstance(ActionWithDropDownAction, 'extensions.uninstall', UninstallAction.UninstallLabel, [ + this.instantiationService.createInstance(UninstallAction), + this.instantiationService.createInstance(InstallAnotherVersionAction), + ]), + this.instantiationService.createInstance(ToggleSyncExtensionAction), systemDisabledWarningAction, + this.instantiationService.createInstance(ExtensionEditorManageExtensionAction), this.instantiationService.createInstance(ExtensionToolTipAction, systemDisabledWarningAction, reloadAction), this.instantiationService.createInstance(MaliciousStatusLabelAction, true), ]; @@ -432,7 +444,7 @@ export class ExtensionEditor extends EditorPane { this.transientDisposables.add(disposable); } - this.setSubText(extension, reloadAction, template); + this.setSubText(extension, template); template.content.innerText = ''; // Clear content before setting navbar actions. template.navbar.clear(); @@ -463,56 +475,24 @@ export class ExtensionEditor extends EditorPane { this.editorLoadComplete = true; } - private setSubText(extension: IExtension, reloadAction: ReloadAction, template: IExtensionEditorTemplate): void { + private setSubText(extension: IExtension, template: IExtensionEditorTemplate): void { hide(template.subtextContainer); - const ignoreAction = this.instantiationService.createInstance(IgnoreExtensionRecommendationAction, extension); - const undoIgnoreAction = this.instantiationService.createInstance(UndoIgnoreExtensionRecommendationAction, extension); - ignoreAction.enabled = false; - undoIgnoreAction.enabled = false; - - template.ignoreActionbar.clear(); - template.ignoreActionbar.push([ignoreAction, undoIgnoreAction], { icon: true, label: true }); - this.transientDisposables.add(ignoreAction); - this.transientDisposables.add(undoIgnoreAction); - const updateRecommendationFn = () => { const extRecommendations = this.extensionRecommendationsService.getAllRecommendationsWithReason(); if (extRecommendations[extension.identifier.id.toLowerCase()]) { - ignoreAction.enabled = true; - undoIgnoreAction.enabled = false; template.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText; show(template.subtextContainer); } else if (this.extensionIgnoredRecommendationsService.globalIgnoredRecommendations.indexOf(extension.identifier.id.toLowerCase()) !== -1) { - ignoreAction.enabled = false; - undoIgnoreAction.enabled = true; template.subtext.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension."); show(template.subtextContainer); } else { - ignoreAction.enabled = false; - undoIgnoreAction.enabled = false; template.subtext.textContent = ''; hide(template.subtextContainer); } }; updateRecommendationFn(); this.transientDisposables.add(this.extensionRecommendationsService.onDidChangeRecommendations(() => updateRecommendationFn())); - - this.transientDisposables.add(reloadAction.onDidChange(e => { - if (e.tooltip) { - template.subtext.textContent = reloadAction.tooltip; - show(template.subtextContainer); - ignoreAction.enabled = false; - undoIgnoreAction.enabled = false; - } - if (e.enabled === true) { - show(template.subtextContainer); - } - if (e.enabled === false) { - hide(template.subtextContainer); - } - this.layout(); - })); } clearInput(): void { @@ -558,8 +538,13 @@ export class ExtensionEditor extends EditorPane { template.content.innerText = ''; this.activeElement = null; if (id) { - this.open(id, extension, template) + const cts = new CancellationTokenSource(); + this.contentDisposables.add(toDisposable(() => cts.dispose(true))); + this.open(id, extension, template, cts.token) .then(activeElement => { + if (cts.token.isCancellationRequested) { + return; + } this.activeElement = activeElement; if (focus) { this.focus(); @@ -568,26 +553,31 @@ export class ExtensionEditor extends EditorPane { } } - private open(id: string, extension: IExtension, template: IExtensionEditorTemplate): Promise { + private open(id: string, extension: IExtension, template: IExtensionEditorTemplate, token: CancellationToken): Promise { switch (id) { - case NavbarSection.Readme: return this.openReadme(template); - case NavbarSection.Contributions: return this.openContributions(template); - case NavbarSection.Changelog: return this.openChangelog(template); - case NavbarSection.Dependencies: return this.openDependencies(extension, template); + case NavbarSection.Readme: return this.openReadme(template, token); + case NavbarSection.Contributions: return this.openContributions(template, token); + case NavbarSection.Changelog: return this.openChangelog(template, token); + case NavbarSection.Dependencies: return this.openDependencies(extension, template, token); } return Promise.resolve(null); } - private async openMarkdown(cacheResult: CacheResult, noContentCopy: string, template: IExtensionEditorTemplate): Promise { + private async openMarkdown(cacheResult: CacheResult, noContentCopy: string, template: IExtensionEditorTemplate, token: CancellationToken): Promise { try { const body = await this.renderMarkdown(cacheResult, template); + if (token.isCancellationRequested) { + return Promise.resolve(null); + } const webview = this.contentDisposables.add(this.webviewService.createWebviewOverlay('extensionEditor', { enableFindWidget: true, }, {}, undefined)); - webview.claim(this); + webview.claim(this, this.scopedContextKeyService); + setParentFlowTo(webview.container, template.content); webview.layoutWebviewOverElement(template.content); + webview.html = body; this.contentDisposables.add(webview.onDidFocus(() => this.fireOnDidFocus())); @@ -828,15 +818,19 @@ export class ExtensionEditor extends EditorPane { `; } - private async openReadme(template: IExtensionEditorTemplate): Promise { + private async openReadme(template: IExtensionEditorTemplate, token: CancellationToken): Promise { const manifest = await this.extensionManifest!.get().promise; if (manifest && manifest.extensionPack && manifest.extensionPack.length) { - return this.openExtensionPackReadme(manifest, template); + return this.openExtensionPackReadme(manifest, template, token); } - return this.openMarkdown(this.extensionReadme!.get(), localize('noReadme', "No README available."), template); + return this.openMarkdown(this.extensionReadme!.get(), localize('noReadme', "No README available."), template, token); } - private async openExtensionPackReadme(manifest: IExtensionManifest, template: IExtensionEditorTemplate): Promise { + private async openExtensionPackReadme(manifest: IExtensionManifest, template: IExtensionEditorTemplate, token: CancellationToken): Promise { + if (token.isCancellationRequested) { + return Promise.resolve(null); + } + const extensionPackReadme = append(template.content, $('div', { class: 'extension-pack-readme' })); extensionPackReadme.style.margin = '0 auto'; extensionPackReadme.style.maxWidth = '882px'; @@ -860,21 +854,25 @@ export class ExtensionEditor extends EditorPane { const readmeContent = append(extensionPackReadme, $('div.readme-content')); await Promise.all([ - this.renderExtensionPack(manifest, extensionPackContent), - this.openMarkdown(this.extensionReadme!.get(), localize('noReadme', "No README available."), { ...template, ...{ content: readmeContent } }), + this.renderExtensionPack(manifest, extensionPackContent, token), + this.openMarkdown(this.extensionReadme!.get(), localize('noReadme', "No README available."), { ...template, ...{ content: readmeContent } }, token), ]); return { focus: () => extensionPackContent.focus() }; } - private openChangelog(template: IExtensionEditorTemplate): Promise { - return this.openMarkdown(this.extensionChangelog!.get(), localize('noChangelog', "No Changelog available."), template); + private openChangelog(template: IExtensionEditorTemplate, token: CancellationToken): Promise { + return this.openMarkdown(this.extensionChangelog!.get(), localize('noChangelog', "No Changelog available."), template, token); } - private openContributions(template: IExtensionEditorTemplate): Promise { + private openContributions(template: IExtensionEditorTemplate, token: CancellationToken): Promise { const content = $('div', { class: 'subcontent', tabindex: '0' }); return this.loadContents(() => this.extensionManifest!.get(), template) .then(manifest => { + if (token.isCancellationRequested) { + return null; + } + if (!manifest) { return content; } @@ -900,6 +898,7 @@ export class ExtensionEditor extends EditorPane { this.renderLocalizations(content, manifest, layout), this.renderCustomEditors(content, manifest, layout), this.renderAuthentication(content, manifest, layout), + this.renderActivationEvents(content, manifest, layout), ]; scrollableContent.scanDomNode(); @@ -914,13 +913,21 @@ export class ExtensionEditor extends EditorPane { } return content; }, () => { + if (token.isCancellationRequested) { + return null; + } + append(content, $('p.nocontent')).textContent = localize('noContributions', "No Contributions"); append(template.content, content); return content; }); } - private openDependencies(extension: IExtension, template: IExtensionEditorTemplate): Promise { + private openDependencies(extension: IExtension, template: IExtensionEditorTemplate, token: CancellationToken): Promise { + if (token.isCancellationRequested) { + return Promise.resolve(null); + } + if (arrays.isFalsyOrEmpty(extension.dependencies)) { append(template.content, $('p.nocontent')).textContent = localize('noDependencies', "No Dependencies"); return Promise.resolve(template.content); @@ -949,7 +956,11 @@ export class ExtensionEditor extends EditorPane { return Promise.resolve({ focus() { dependenciesTree.domFocus(); } }); } - private async renderExtensionPack(manifest: IExtensionManifest, parent: HTMLElement): Promise { + private async renderExtensionPack(manifest: IExtensionManifest, parent: HTMLElement, token: CancellationToken): Promise { + if (token.isCancellationRequested) { + return; + } + const content = $('div', { class: 'subcontent' }); const scrollableContent = new DomScrollableElement(content, { useShadows: false }); append(parent, scrollableContent.getDomNode()); @@ -1414,6 +1425,21 @@ export class ExtensionEditor extends EditorPane { return true; } + private renderActivationEvents(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { + const activationEvents = manifest.activationEvents || []; + if (!activationEvents.length) { + return false; + } + + const details = $('details', { open: true, ontoggle: onDetailsToggle }, + $('summary', { tabindex: '0' }, localize('activation events', "Activation Events ({0})", activationEvents.length)), + $('ul', undefined, ...activationEvents.map(activationEvent => $('li', undefined, $('code', undefined, activationEvent)))) + ); + + append(container, details); + return true; + } + private resolveKeybinding(rawKeyBinding: IKeyBinding): ResolvedKeybinding | null { let key: string | undefined; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts index f6aa5db5a..ab902f413 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts @@ -11,14 +11,13 @@ import { isPromiseCanceledError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; -import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionRecommendationNotificationService, RecommendationsNotificationResult, RecommendationSource } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation'; -import { INotificationHandle, INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { INotificationHandle, INotificationService, IPromptChoice, IPromptChoiceWithMenu, Severity } from 'vs/platform/notification/common/notification'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { IUserDataAutoSyncEnablementService, IUserDataSyncResourceEnablementService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { SearchExtensionsAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IExtension, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; @@ -26,14 +25,6 @@ import { ITASExperimentService } from 'vs/workbench/services/experiment/common/e import { EnablementState, IWorkbenchExtensioManagementService, IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IExtensionIgnoredRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; -interface IExtensionsConfiguration { - autoUpdate: boolean; - autoCheckUpdates: boolean; - ignoreRecommendations: boolean; - showRecommendationsOnlyOnDemand: boolean; - closeExtensionDetailsOnViewChange: boolean; -} - type ExtensionRecommendationsNotificationClassification = { userReaction: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; extensionId?: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; @@ -142,18 +133,16 @@ export class ExtensionRecommendationNotificationService implements IExtensionRec @IWorkbenchExtensioManagementService private readonly extensionManagementService: IWorkbenchExtensioManagementService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @IExtensionIgnoredRecommendationsService private readonly extensionIgnoredRecommendationsService: IExtensionIgnoredRecommendationsService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, @IUserDataAutoSyncEnablementService private readonly userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService, @IUserDataSyncResourceEnablementService private readonly userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService, @optional(ITASExperimentService) tasExperimentService: ITASExperimentService, ) { - storageKeysSyncRegistryService.registerStorageKey({ key: ignoreImportantExtensionRecommendationStorageKey, version: 1 }); this.tasExperimentService = tasExperimentService; } hasToIgnoreRecommendationNotifications(): boolean { - const config = this.configurationService.getValue('extensions'); - return config.ignoreRecommendations || config.showRecommendationsOnlyOnDemand; + const config = this.configurationService.getValue<{ ignoreRecommendations: boolean, showRecommendationsOnlyOnDemand?: boolean }>('extensions'); + return config.ignoreRecommendations || !!config.showRecommendationsOnlyOnDemand; } async promptImportantExtensionsInstallNotification(extensionIds: string[], message: string, searchValue: string, source: RecommendationSource): Promise { @@ -207,7 +196,7 @@ export class ExtensionRecommendationNotificationService implements IExtensionRec }); if (result === RecommendationsNotificationResult.Accepted) { - this.storageService.store(donotShowWorkspaceRecommendationsStorageKey, true, StorageScope.WORKSPACE); + this.storageService.store(donotShowWorkspaceRecommendationsStorageKey, true, StorageScope.WORKSPACE, StorageTarget.USER); } } @@ -252,7 +241,7 @@ export class ExtensionRecommendationNotificationService implements IExtensionRec { onDidInstallRecommendedExtensions, onDidShowRecommendedExtensions, onDidCancelRecommendedExtensions, onDidNeverShowRecommendedExtensionsAgain }: RecommendationsNotificationActions): CancelablePromise { return createCancelablePromise(async token => { let accepted = false; - const choices: IPromptChoice[] = []; + const choices: (IPromptChoice | IPromptChoiceWithMenu)[] = []; const installExtensions = async (isMachineScoped?: boolean) => { this.runAction(this.instantiationService.createInstance(SearchExtensionsAction, searchValue)); onDidInstallRecommendedExtensions(extensions); @@ -263,14 +252,12 @@ export class ExtensionRecommendationNotificationService implements IExtensionRec }; choices.push({ label: localize('install', "Install"), - run: () => installExtensions() - }); - if (this.userDataAutoSyncEnablementService.isEnabled() && this.userDataSyncResourceEnablementService.isResourceEnabled(SyncResource.Extensions)) { - choices.push({ + run: () => installExtensions(), + menu: this.userDataAutoSyncEnablementService.isEnabled() && this.userDataSyncResourceEnablementService.isResourceEnabled(SyncResource.Extensions) ? [{ label: localize('install and do no sync', "Install (Do not sync)"), run: () => installExtensions(true) - }); - } + }] : undefined, + }); choices.push(...[{ label: localize('show recommendations', "Show Recommendations"), run: async () => { @@ -423,11 +410,11 @@ export class ExtensionRecommendationNotificationService implements IExtensionRec const importantRecommendationsIgnoreList = [...this.ignoredRecommendations]; if (!importantRecommendationsIgnoreList.includes(id.toLowerCase())) { importantRecommendationsIgnoreList.push(id.toLowerCase()); - this.storageService.store(ignoreImportantExtensionRecommendationStorageKey, JSON.stringify(importantRecommendationsIgnoreList), StorageScope.GLOBAL); + this.storageService.store(ignoreImportantExtensionRecommendationStorageKey, JSON.stringify(importantRecommendationsIgnoreList), StorageScope.GLOBAL, StorageTarget.USER); } } private setIgnoreRecommendationsConfig(configVal: boolean) { - this.configurationService.updateValue('extensions.ignoreRecommendations', configVal, ConfigurationTarget.USER); + this.configurationService.updateValue('extensions.ignoreRecommendations', configVal); } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts index 7df6cf1b9..60fa348e8 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts @@ -7,8 +7,6 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IExtensionManagementService, IExtensionGalleryService, InstallOperation, DidInstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionRecommendationsService, ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ShowRecommendationsOnlyOnDemandKey } from 'vs/workbench/contrib/extensions/common/extensions'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { distinct, shuffle } from 'vs/base/common/arrays'; import { Emitter, Event } from 'vs/base/common/event'; @@ -52,7 +50,6 @@ export class ExtensionRecommendationsService extends Disposable implements IExte @IInstantiationService instantiationService: IInstantiationService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, - @IConfigurationService private readonly configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @@ -92,15 +89,9 @@ export class ExtensionRecommendationsService extends Disposable implements IExte this.fileBasedRecommendations.activate(), this.experimentalRecommendations.activate(), this.keymapRecommendations.activate(), - this.lifecycleService.when(LifecyclePhase.Eventually) - .then(async () => { - if (!this.configurationService.getValue(ShowRecommendationsOnlyOnDemandKey)) { - await this.activateProactiveRecommendations(); - } - }) ]); - this._register(this.extensionRecommendationsManagementService.onDidChangeIgnoredRecommendations(() => this._onDidChangeRecommendations.fire())); + this._register(Event.any(this.workspaceRecommendations.onDidChangeRecommendations, this.configBasedRecommendations.onDidChangeRecommendations, this.extensionRecommendationsManagementService.onDidChangeIgnoredRecommendations)(() => this._onDidChangeRecommendations.fire())); this._register(this.extensionRecommendationsManagementService.onDidChangeGlobalIgnoredRecommendation(({ extensionId, isRecommended }) => { if (!isRecommended) { const reason = this.getAllRecommendationsWithReason()[extensionId]; @@ -111,7 +102,6 @@ export class ExtensionRecommendationsService extends Disposable implements IExte })); await this.promptWorkspaceRecommendations(); - this._register(Event.any(this.workspaceRecommendations.onDidChangeRecommendations, this.configBasedRecommendations.onDidChangeRecommendations)(() => this.promptWorkspaceRecommendations())); } private isEnabled(): boolean { diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index f4b33985a..839c3ffbe 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -6,11 +6,11 @@ import { localize } from 'vs/nls'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; -import { MenuRegistry, MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; +import { MenuRegistry, MenuId, registerAction2, Action2, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExtensionsLabel, ExtensionsLocalizedLabel, ExtensionsChannelId, IExtensionManagementService, IExtensionGalleryService, PreferencesLocalizedLabel } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; +import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -18,7 +18,7 @@ import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer, import { OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction, ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowDisabledExtensionsAction, ShowBuiltInExtensionsAction, UpdateAllAction, - EnableAllAction, EnableAllWorkspaceAction, DisableAllAction, DisableAllWorkspaceAction, CheckForUpdatesAction, ShowLanguageExtensionsAction, EnableAutoUpdateAction, DisableAutoUpdateAction, ConfigureRecommendedExtensionsCommandsContributor, InstallVSIXAction, ReinstallAction, InstallSpecificVersionOfExtensionAction, ClearExtensionsSearchResultsAction + EnableAllAction, EnableAllWorkspaceAction, DisableAllAction, DisableAllWorkspaceAction, CheckForUpdatesAction, ShowLanguageExtensionsAction, EnableAutoUpdateAction, DisableAutoUpdateAction, InstallVSIXAction, ReinstallAction, InstallSpecificVersionOfExtensionAction, ClearExtensionsSearchResultsAction, ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, RefreshExtensionsAction } 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'; @@ -26,7 +26,7 @@ import { StatusUpdater, MaliciousExtensionChecker, ExtensionsViewletViewsContrib import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import * as jsonContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { ExtensionsConfigurationSchema, ExtensionsConfigurationSchemaId } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeymapExtensions } from 'vs/workbench/contrib/extensions/common/extensionsUtils'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -54,7 +54,7 @@ import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; -import { CATEGORIES } from 'vs/workbench/common/actions'; +import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions, CATEGORIES } from 'vs/workbench/common/actions'; 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'; @@ -62,6 +62,10 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { IAction } from 'vs/base/common/actions'; +import { IWorkpsaceExtensionsConfigService } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig'; +import { Schemas } from 'vs/base/common/network'; +import { ShowRuntimeExtensionsAction } from 'vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor'; +import { extensionsViewIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); @@ -100,12 +104,13 @@ Registry.as(EditorExtensions.Editors).registerEditor( new SyncDescriptor(ExtensionsInput) ]); + Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer( { id: VIEWLET_ID, name: localize('extensions', "Extensions"), ctorDescriptor: new SyncDescriptor(ExtensionsViewPaneContainer), - icon: 'codicon-extensions', + icon: extensionsViewIcon, order: 4, rejectAddedViews: true, alwaysUseContainerInfo: true @@ -140,7 +145,7 @@ Registry.as(ConfigurationExtensions.Configuration) }, 'extensions.showRecommendationsOnlyOnDemand': { type: 'boolean', - description: localize('extensionsShowRecommendationsOnlyOnDemand', "When enabled, recommendations will not be fetched or shown unless specifically requested by the user. Some recommendations are fetched from a Microsoft online service."), + deprecationMessage: localize('extensionsShowRecommendationsOnlyOnDemand_Deprecated', "This setting is deprecated. Use extensions.ignoreRecommendations setting to control recommendation notifications. Use Extensions view's visibility actions to hide Recommended view by default."), default: false, tags: ['usesOnlineServices'] }, @@ -234,8 +239,8 @@ CommandsRegistry.registerCommand({ .then(async (extensions) => { for (const extension of extensions) { const requireReload = !(extension.local && extensionService.canAddExtension(toExtensionDescription(extension.local))); - const message = requireReload ? localize('InstallVSIXAction.successReload', "Please reload Visual Studio Code to complete installing the extension {0}.", extension.displayName || extension.name) - : localize('InstallVSIXAction.success', "Completed installing the extension {0}.", extension.displayName || extension.name); + 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() @@ -279,7 +284,7 @@ CommandsRegistry.registerCommand({ } try { - await extensionManagementService.uninstall(extensionToUninstall, true); + await extensionManagementService.uninstall(extensionToUninstall); } catch (e) { onUnexpectedError(e); throw e; @@ -771,6 +776,22 @@ class ExtensionsContributions implements IWorkbenchContribution { } }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: RefreshExtensionsAction.ID, + title: { value: RefreshExtensionsAction.LABEL, original: 'Refresh' }, + category: ExtensionsLocalizedLabel, + menu: { + id: MenuId.CommandPalette, + } + }); + } + run(accessor: ServicesAccessor) { + return runAction(accessor.get(IInstantiationService).createInstance(RefreshExtensionsAction, RefreshExtensionsAction.ID, RefreshExtensionsAction.LABEL)); + } + }); + registerAction2(class extends Action2 { constructor() { super({ @@ -916,7 +937,7 @@ class ExtensionsContributions implements IWorkbenchContribution { menu: { id: MenuId.ExtensionContext, group: '2_configure', - when: CONTEXT_SYNC_ENABLEMENT + when: ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, ContextKeyExpr.has('inExtensionEditor').negate()) }, }); } @@ -929,6 +950,220 @@ class ExtensionsContributions implements IWorkbenchContribution { } } }); + + registerAction2(class extends Action2 { + + constructor() { + super({ + id: 'workbench.extensions.action.ignoreRecommendation', + title: { value: localize('workbench.extensions.action.ignoreRecommendation', "Ignore Recommendation"), original: `Ignore Recommendation` }, + menu: { + id: MenuId.ExtensionContext, + group: '3_recommendations', + when: ContextKeyExpr.has('isExtensionRecommended'), + order: 1 + }, + }); + } + + async run(accessor: ServicesAccessor, id: string): Promise { + accessor.get(IExtensionIgnoredRecommendationsService).toggleGlobalIgnoredRecommendation(id, true); + } + }); + + registerAction2(class extends Action2 { + + constructor() { + super({ + id: 'workbench.extensions.action.undoIgnoredRecommendation', + title: { value: localize('workbench.extensions.action.undoIgnoredRecommendation', "Undo Ignored Recommendation"), original: `Undo Ignored Recommendation` }, + menu: { + id: MenuId.ExtensionContext, + group: '3_recommendations', + when: ContextKeyExpr.has('isUserIgnoredRecommendation'), + order: 1 + }, + }); + } + + async run(accessor: ServicesAccessor, id: string): Promise { + accessor.get(IExtensionIgnoredRecommendationsService).toggleGlobalIgnoredRecommendation(id, false); + } + }); + + registerAction2(class extends Action2 { + + constructor() { + super({ + id: 'workbench.extensions.action.addExtensionToWorkspaceRecommendations', + title: { value: localize('workbench.extensions.action.addExtensionToWorkspaceRecommendations', "Add to Workspace Recommendations"), original: `Add to Workspace Recommendations` }, + menu: { + id: MenuId.ExtensionContext, + group: '3_recommendations', + when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('empty'), ContextKeyExpr.has('isBuiltinExtension').negate(), ContextKeyExpr.has('isExtensionWorkspaceRecommended').negate(), ContextKeyExpr.has('isUserIgnoredRecommendation').negate()), + order: 2 + }, + }); + } + + run(accessor: ServicesAccessor, id: string): Promise { + return accessor.get(IWorkpsaceExtensionsConfigService).toggleRecommendation(id); + } + }); + + registerAction2(class extends Action2 { + + constructor() { + super({ + id: 'workbench.extensions.action.removeExtensionFromWorkspaceRecommendations', + title: { value: localize('workbench.extensions.action.removeExtensionFromWorkspaceRecommendations', "Remove from Workspace Recommendations"), original: `Remove from Workspace Recommendations` }, + menu: { + id: MenuId.ExtensionContext, + group: '3_recommendations', + when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('empty'), ContextKeyExpr.has('isBuiltinExtension').negate(), ContextKeyExpr.has('isExtensionWorkspaceRecommended')), + order: 2 + }, + }); + } + + run(accessor: ServicesAccessor, id: string): Promise { + return accessor.get(IWorkpsaceExtensionsConfigService).toggleRecommendation(id); + } + }); + + registerAction2(class extends Action2 { + + constructor() { + super({ + id: 'workbench.extensions.action.addToWorkspaceRecommendations', + title: { value: localize('workbench.extensions.action.addToWorkspaceRecommendations', "Add Extension to Workspace Recommendations"), original: `Add Extension to Workspace Recommendations` }, + category: localize('extensions', "Extensions"), + menu: { + id: MenuId.CommandPalette, + when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace'), ContextKeyExpr.equals('resourceScheme', Schemas.extension)), + }, + }); + } + + async run(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const workpsaceExtensionsConfigService = accessor.get(IWorkpsaceExtensionsConfigService); + if (!(editorService.activeEditor instanceof ExtensionsInput)) { + return; + } + const extensionId = editorService.activeEditor.extension.identifier.id.toLowerCase(); + const recommendations = await workpsaceExtensionsConfigService.getRecommendations(); + if (recommendations.includes(extensionId)) { + return; + } + await workpsaceExtensionsConfigService.toggleRecommendation(extensionId); + } + }); + + registerAction2(class extends Action2 { + + constructor() { + super({ + id: 'workbench.extensions.action.addToWorkspaceFolderRecommendations', + title: { value: localize('workbench.extensions.action.addToWorkspaceFolderRecommendations', "Add Extension to Workspace Folder Recommendations"), original: `Add Extension to Workspace Folder Recommendations` }, + category: localize('extensions', "Extensions"), + menu: { + id: MenuId.CommandPalette, + when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('folder'), ContextKeyExpr.equals('resourceScheme', Schemas.extension)), + }, + }); + } + + async run(accessor: ServicesAccessor): Promise { + return accessor.get(ICommandService).executeCommand('workbench.extensions.action.addToWorkspaceRecommendations'); + } + }); + + registerAction2(class extends Action2 { + + constructor() { + super({ + id: 'workbench.extensions.action.addToWorkspaceIgnoredRecommendations', + title: { value: localize('workbench.extensions.action.addToWorkspaceIgnoredRecommendations', "Add Extension to Workspace Ignored Recommendations"), original: `Add Extension to Workspace Ignored Recommendations` }, + category: localize('extensions', "Extensions"), + menu: { + id: MenuId.CommandPalette, + when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace'), ContextKeyExpr.equals('resourceScheme', Schemas.extension)), + }, + }); + } + + async run(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const workpsaceExtensionsConfigService = accessor.get(IWorkpsaceExtensionsConfigService); + if (!(editorService.activeEditor instanceof ExtensionsInput)) { + return; + } + const extensionId = editorService.activeEditor.extension.identifier.id.toLowerCase(); + const unwatedRecommendations = await workpsaceExtensionsConfigService.getUnwantedRecommendations(); + if (unwatedRecommendations.includes(extensionId)) { + return; + } + await workpsaceExtensionsConfigService.toggleUnwantedRecommendation(extensionId); + } + }); + + registerAction2(class extends Action2 { + + constructor() { + super({ + id: 'workbench.extensions.action.addToWorkspaceFolderIgnoredRecommendations', + title: { value: localize('workbench.extensions.action.addToWorkspaceFolderIgnoredRecommendations', "Add Extension to Workspace Folder Ignored Recommendations"), original: `Add Extension to Workspace Folder Ignored Recommendations` }, + category: localize('extensions', "Extensions"), + menu: { + id: MenuId.CommandPalette, + when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('folder'), ContextKeyExpr.equals('resourceScheme', Schemas.extension)), + }, + }); + } + + run(accessor: ServicesAccessor): Promise { + return accessor.get(ICommandService).executeCommand('workbench.extensions.action.addToWorkspaceIgnoredRecommendations'); + } + }); + + registerAction2(class extends Action2 { + + constructor() { + super({ + id: ConfigureWorkspaceRecommendedExtensionsAction.ID, + title: { value: ConfigureWorkspaceRecommendedExtensionsAction.LABEL, original: 'Configure Recommended Extensions (Workspace)' }, + category: localize('extensions', "Extensions"), + menu: { + id: MenuId.CommandPalette, + when: WorkbenchStateContext.isEqualTo('workspace'), + }, + }); + } + + run(accessor: ServicesAccessor): Promise { + return accessor.get(IInstantiationService).createInstance(ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceRecommendedExtensionsAction.ID, ConfigureWorkspaceRecommendedExtensionsAction.LABEL).run(); + } + }); + + registerAction2(class extends Action2 { + + constructor() { + super({ + id: ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, + title: { value: ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL, original: 'Configure Recommended Extensions (Workspace Folder)' }, + category: localize('extensions', "Extensions"), + menu: { + id: MenuId.CommandPalette, + when: WorkbenchStateContext.notEqualsTo('empty'), + }, + }); + } + + run(accessor: ServicesAccessor): Promise { + return accessor.get(IInstantiationService).createInstance(ConfigureWorkspaceFolderRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL).run(); + } + }); } } @@ -936,9 +1171,12 @@ const workbenchRegistry = Registry.as(Workbench workbenchRegistry.registerWorkbenchContribution(ExtensionsContributions, LifecyclePhase.Starting); workbenchRegistry.registerWorkbenchContribution(StatusUpdater, LifecyclePhase.Restored); workbenchRegistry.registerWorkbenchContribution(MaliciousExtensionChecker, LifecyclePhase.Eventually); -workbenchRegistry.registerWorkbenchContribution(ConfigureRecommendedExtensionsCommandsContributor, LifecyclePhase.Eventually); workbenchRegistry.registerWorkbenchContribution(KeymapExtensions, LifecyclePhase.Restored); workbenchRegistry.registerWorkbenchContribution(ExtensionsViewletViewsContribution, LifecyclePhase.Starting); workbenchRegistry.registerWorkbenchContribution(ExtensionActivationProgress, LifecyclePhase.Eventually); workbenchRegistry.registerWorkbenchContribution(ExtensionDependencyChecker, LifecyclePhase.Eventually); workbenchRegistry.registerWorkbenchContribution(RemoteExtensionsInstaller, LifecyclePhase.Eventually); + +// Running Extensions +const actionRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ShowRuntimeExtensionsAction), 'Show Running Extensions', CATEGORIES.Developer.value); diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.web.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.web.contribution.ts new file mode 100644 index 000000000..f5d4587b7 --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/extensions.web.contribution.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 { localize } from 'vs/nls'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; +import { RuntimeExtensionsEditor } from 'vs/workbench/contrib/extensions/browser/browserRuntimeExtensionsEditor'; +import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/runtimeExtensionsInput'; + +// Running Extensions +Registry.as(EditorExtensions.Editors).registerEditor( + EditorDescriptor.create(RuntimeExtensionsEditor, RuntimeExtensionsEditor.ID, localize('runtimeExtension', "Running Extensions")), + [new SyncDescriptor(RuntimeExtensionsInput)] +); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index c46dbde91..36dbf7c75 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -11,12 +11,12 @@ import * as DOM from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; import * as json from 'vs/base/common/json'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { dispose, Disposable } from 'vs/base/common/lifecycle'; +import { dispose } from 'vs/base/common/lifecycle'; import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, AutoUpdateConfigurationKey, IExtensionContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; -import { IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension, INSTALL_ERROR_NOT_SUPPORTED, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { IExtensionIgnoredRecommendationsService, IExtensionsConfigContent } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; +import { IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension, INSTALL_ERROR_NOT_SUPPORTED, InstallOptions, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensioManagementService, IWebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -30,22 +30,19 @@ import { IExtensionService, toExtension, toExtensionDescription } from 'vs/workb import { URI } from 'vs/base/common/uri'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, IColorTheme, ICssStyleCollector, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { buttonBackground, buttonForeground, buttonHoverBackground, contrastBorder, registerColor, foreground } from 'vs/platform/theme/common/colorRegistry'; -import { Color } from 'vs/base/common/color'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { ITextEditorSelection } from 'vs/platform/editor/common/editor'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { MenuRegistry, MenuId, IMenuService } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { MenuId, IMenuService } from 'vs/platform/actions/common/actions'; import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { IQuickPickItem, IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -58,51 +55,16 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { IProductService } from 'vs/platform/product/common/productService'; import { IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { Codicon } from 'vs/base/common/codicons'; import { IViewsService } from 'vs/workbench/common/views'; import { IActionViewItemOptions, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { EXTENSIONS_CONFIG } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig'; -import { isPromiseCanceledError } from 'vs/base/common/errors'; +import { EXTENSIONS_CONFIG, IExtensionsConfigContent } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig'; +import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors'; import { IUserDataAutoSyncEnablementService, IUserDataSyncResourceEnablementService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { ActionWithDropdownActionViewItem, IActionWithDropdownActionViewItemOptions } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; - -const promptDownloadManually = (extension: IGalleryExtension | undefined, message: string, error: Error, instantiationService: IInstantiationService): Promise => { - return instantiationService.invokeFunction(accessor => { - if (isPromiseCanceledError(error)) { - return Promise.resolve(); - } - - const productService = accessor.get(IProductService); - const openerService = accessor.get(IOpenerService); - const notificationService = accessor.get(INotificationService); - const dialogService = accessor.get(IDialogService); - const erorrsToShows = [INSTALL_ERROR_INCOMPATIBLE, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_NOT_SUPPORTED]; - if (!extension || erorrsToShows.indexOf(error.name) !== -1 || !productService.extensionsGallery) { - return dialogService.show(Severity.Error, error.message, []); - } else { - const downloadUrl = `${productService.extensionsGallery.serviceUrl}/publishers/${extension.publisher}/vsextensions/${extension.name}/${extension.version}/vspackage`; - notificationService.prompt(Severity.Error, message, [{ - label: localize('download', "Download Manually"), - run: () => openerService.open(URI.parse(downloadUrl)).then(() => { - notificationService.prompt( - Severity.Info, - localize('install vsix', 'Once downloaded, please manually install the downloaded VSIX of \'{0}\'.', extension.identifier.id), - [{ - label: InstallVSIXAction.LABEL, - run: () => { - const action = instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL); - action.run(); - action.dispose(); - } - }] - ); - }) - }]); - return Promise.resolve(); - } - }); -}; +import { ILogService } from 'vs/platform/log/common/log'; +import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; +import { clearSearchResultsIcon, infoIcon, manageExtensionIcon, refreshIcon, syncEnabledIcon, syncIgnoredIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; function getRelativeDateLabel(date: Date): string { const delta = new Date().getTime() - date.getTime(); @@ -138,6 +100,61 @@ function getRelativeDateLabel(date: Date): string { return ''; } +class PromptExtensionInstallFailureAction extends Action { + + constructor( + private readonly extension: IExtension, + private readonly installOperation: InstallOperation, + private readonly error: Error, + @IProductService private readonly productService: IProductService, + @IOpenerService private readonly openerService: IOpenerService, + @INotificationService private readonly notificationService: INotificationService, + @IDialogService private readonly dialogService: IDialogService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @ILogService private readonly logService: ILogService, + ) { + super('extension.promptExtensionInstallFailure'); + } + + async run(): Promise { + if (isPromiseCanceledError(this.error)) { + return; + } + + this.logService.error(this.error); + const operationMessage = this.installOperation === InstallOperation.Update ? localize('update operation', "Error while updating '{0}' extension.", this.extension.displayName || this.extension.identifier.id) + : localize('install operation', "Error while installing '{0}' extension.", this.extension.displayName || this.extension.identifier.id); + + if ([INSTALL_ERROR_INCOMPATIBLE, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_NOT_SUPPORTED].includes(this.error.name)) { + await this.dialogService.show(Severity.Error, `${operationMessage}\n${getErrorMessage(this.error)}`, []); + return; + } + + const promptChoices: IPromptChoice[] = []; + if (this.extension.gallery && this.productService.extensionsGallery) { + promptChoices.push({ + label: localize('download', "Try Downloading Manually..."), + run: () => this.openerService.open(URI.parse(`${this.productService.extensionsGallery!.serviceUrl}/publishers/${this.extension.publisher}/vsextensions/${this.extension.name}/${this.extension.version}/vspackage`)).then(() => { + this.notificationService.prompt( + Severity.Info, + localize('install vsix', 'Once downloaded, please manually install the downloaded VSIX of \'{0}\'.', this.extension.identifier.id), + [{ + label: InstallVSIXAction.LABEL, + run: () => { + const action = this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL); + action.run(); + action.dispose(); + } + }] + ); + }) + }); + } + const checkLogsMessage = localize('check logs', "Please check the [log]({0}) for more details.", `command:${Constants.showWindowLogActionId}`); + this.notificationService.prompt(Severity.Error, `${operationMessage} ${checkLogsMessage}`, promptChoices); + } +} + export abstract class ExtensionAction extends Action implements IExtensionContainer { static readonly EXTENSION_ACTION_CLASS = 'extension-action'; static readonly TEXT_ACTION_CLASS = `${ExtensionAction.EXTENSION_ACTION_CLASS} text`; @@ -149,13 +166,17 @@ export abstract class ExtensionAction extends Action implements IExtensionContai abstract update(): void; } -export abstract class ActionWithDropDownAction extends ExtensionAction { +export class ActionWithDropDownAction extends ExtensionAction { private action: IAction | undefined; private _menuActions: IAction[] = []; get menuActions(): IAction[] { return [...this._menuActions]; } + get extension(): IExtension | null { + return super.extension; + } + set extension(extension: IExtension | null) { this.actions.forEach(a => a.extension = extension); super.extension = extension; @@ -168,6 +189,7 @@ export abstract class ActionWithDropDownAction extends ExtensionAction { super(id, label); this.update(); this._register(Event.any(...actions.map(a => a.onDidChange))(() => this.update(true))); + actions.forEach(a => this._register(a)); } update(donotUpdateActions?: boolean): void { @@ -213,7 +235,6 @@ export abstract class AbstractInstallAction extends ExtensionAction { id: string, label: string, cssClass: string, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @INotificationService private readonly notificationService: INotificationService, @IExtensionService private readonly runtimeExtensionService: IExtensionService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, @ILabelService private readonly labelService: ILabelService, @@ -243,11 +264,10 @@ export abstract class AbstractInstallAction extends ExtensionAction { const extension = await this.install(this.extension); - alert(localize('installExtensionComplete', "Installing extension {0} is completed.", this.extension.displayName)); - - if (extension && extension.local) { + if (extension?.local) { + alert(localize('installExtensionComplete', "Installing extension {0} is completed.", this.extension.displayName)); const runningExtension = await this.getRunningExtension(extension.local); - if (runningExtension) { + if (runningExtension && !(runningExtension.activationEvents && runningExtension.activationEvents.some(activationEent => activationEent.startsWith('onLanguage')))) { let action = await SetColorThemeAction.create(this.workbenchThemeService, this.instantiationService, extension) || await SetFileIconThemeAction.create(this.workbenchThemeService, this.instantiationService, extension) || await SetProductIconThemeAction.create(this.workbenchThemeService, this.instantiationService, extension); @@ -263,17 +283,13 @@ export abstract class AbstractInstallAction extends ExtensionAction { } - private install(extension: IExtension): Promise { - return this.extensionsWorkbenchService.install(extension, this.getInstallOptions()) - .then(null, err => { - if (!extension.gallery) { - return this.notificationService.error(err); - } - - console.error(err); - - return promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.identifier.id), err, this.instantiationService); - }); + private async install(extension: IExtension): Promise { + try { + return await this.extensionsWorkbenchService.install(extension, this.getInstallOptions()); + } catch (error) { + await this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, InstallOperation.Install, error).run(); + return undefined; + } } private async getRunningExtension(extension: ILocalExtension): Promise { @@ -304,18 +320,16 @@ export class InstallAction extends AbstractInstallAction { constructor( @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, @IInstantiationService instantiationService: IInstantiationService, - @INotificationService notificationService: INotificationService, @IExtensionService runtimeExtensionService: IExtensionService, @IWorkbenchThemeService workbenchThemeService: IWorkbenchThemeService, @ILabelService labelService: ILabelService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IProductService private readonly productService: IProductService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @IWorkbenchExtensioManagementService private readonly workbenchExtensioManagementService: IWorkbenchExtensioManagementService, @IUserDataAutoSyncEnablementService protected readonly userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService, @IUserDataSyncResourceEnablementService protected readonly userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService, ) { super(`extensions.installAndSync`, localize('install', "Install"), InstallAction.Class, - extensionsWorkbenchService, instantiationService, notificationService, runtimeExtensionService, workbenchThemeService, labelService); + extensionsWorkbenchService, instantiationService, runtimeExtensionService, workbenchThemeService, labelService); this.updateLabel(); this._register(labelService.onDidChangeFormatters(() => this.updateLabel(), this)); this._register(Event.any(userDataAutoSyncEnablementService.onDidChangeEnablement, @@ -333,20 +347,17 @@ export class InstallAction extends AbstractInstallAction { // When remote connection exists if (this._manifest && this.extensionManagementServerService.remoteExtensionManagementServer) { - // On Desktop and UI Extension - if (this.extensionManagementServerService.localExtensionManagementServer && prefersExecuteOnUI(this._manifest, this.productService, this.configurationService)) { - this.label = isMachineScoped ? localize('install locally and do not sync', "Install Locally (Do not sync)") : localize('install locally', "Install Locally"); + const server = this.workbenchExtensioManagementService.getExtensionManagementServerToInstall(this._manifest); + + if (server === this.extensionManagementServerService.remoteExtensionManagementServer) { + const host = this.extensionManagementServerService.remoteExtensionManagementServer.label; + this.label = isMachineScoped + ? localize({ key: 'install in remote and do not sync', comment: ['This is the name of the action to install an extension in remote server and do not sync it. Placeholder is for the name of remote server.'] }, "Install in {0} (Do not sync)", host) + : localize({ key: 'install in remote', comment: ['This is the name of the action to install an extension in remote server. Placeholder is for the name of remote server.'] }, "Install in {0}", host); return; } - // On Web and Web Extension - if (this.extensionManagementServerService.webExtensionManagementServer && prefersExecuteOnWeb(this._manifest, this.productService, this.configurationService)) { - this.label = isMachineScoped ? localize('install locally and do not sync', "Install Locally (Do not sync)") : localize('install locally', "Install Locally"); - return; - } - - const host = this.extensionManagementServerService.remoteExtensionManagementServer.label; - this.label = isMachineScoped ? localize('install on remote and do not sync', "Install on {0} (Do not sync)", host) : localize('install on remote', "Install on {0}", host); + this.label = isMachineScoped ? localize('install locally and do not sync', "Install Locally (Do not sync)") : localize('install locally', "Install Locally"); return; } } @@ -362,7 +373,6 @@ export class InstallAndSyncAction extends AbstractInstallAction { constructor( @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, @IInstantiationService instantiationService: IInstantiationService, - @INotificationService notificationService: INotificationService, @IExtensionService runtimeExtensionService: IExtensionService, @IWorkbenchThemeService workbenchThemeService: IWorkbenchThemeService, @ILabelService labelService: ILabelService, @@ -371,8 +381,8 @@ export class InstallAndSyncAction extends AbstractInstallAction { @IUserDataSyncResourceEnablementService private readonly userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService, ) { super(`extensions.installAndSync`, localize('install', "Install"), InstallAndSyncAction.Class, - extensionsWorkbenchService, instantiationService, notificationService, runtimeExtensionService, workbenchThemeService, labelService); - this.tooltip = localize('install everywhere tooltip', "Install this extension in all your synced {0} instances", productService.nameLong); + extensionsWorkbenchService, instantiationService, runtimeExtensionService, workbenchThemeService, labelService); + this.tooltip = localize({ key: 'install everywhere tooltip', comment: ['Placeholder is the name of the product. Eg: Visual Studio Code or Visual Studio Code - Insiders'] }, "Install this extension in all your synced {0} instances", productService.nameLong); this._register(Event.any(userDataAutoSyncEnablementService.onDidChangeEnablement, Event.filter(userDataSyncResourceEnablementService.onDidChangeResourceEnablement, e => e[0] === SyncResource.Extensions))(() => this.update())); } @@ -469,7 +479,7 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { } } - private canInstall(): boolean { + protected canInstall(): boolean { // Disable if extension is not installed or not an user extension if ( !this.extension @@ -496,6 +506,11 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { return true; } + // Prefers to run on Web + if (this.server === this.extensionManagementServerService.webExtensionManagementServer && prefersExecuteOnWeb(this.extension.local.manifest, this.productService, this.configurationService)) { + return true; + } + if (this.canInstallAnyWhere) { // Can run on UI if (this.server === this.extensionManagementServerService.localExtensionManagementServer && canExecuteOnUI(this.extension.local.manifest, this.productService, this.configurationService)) { @@ -543,7 +558,9 @@ export class RemoteInstallAction extends InstallInOtherServerAction { } protected getInstallLabel(): string { - return this.extensionManagementServerService.remoteExtensionManagementServer ? localize('Install on Server', "Install in {0}", this.extensionManagementServerService.remoteExtensionManagementServer.label) : InstallInOtherServerAction.INSTALL_LABEL; + return this.extensionManagementServerService.remoteExtensionManagementServer + ? localize({ key: 'install in remote', comment: ['This is the name of the action to install an extension in remote server. Placeholder is for the name of remote server.'] }, "Install in {0}", this.extensionManagementServerService.remoteExtensionManagementServer.label) + : InstallInOtherServerAction.INSTALL_LABEL; } } @@ -565,9 +582,34 @@ export class LocalInstallAction extends InstallInOtherServerAction { } +export class WebInstallAction extends InstallInOtherServerAction { + + constructor( + @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService, + @IProductService productService: IProductService, + @IConfigurationService configurationService: IConfigurationService, + @IWebExtensionsScannerService private readonly webExtensionsScannerService: IWebExtensionsScannerService, + ) { + super(`extensions.webInstall`, extensionManagementServerService.webExtensionManagementServer, false, extensionsWorkbenchService, extensionManagementServerService, productService, configurationService); + } + + protected getInstallLabel(): string { + return localize('install browser', "Install in Browser"); + } + + protected canInstall(): boolean { + if (super.canInstall()) { + return !!this.extension?.gallery && this.webExtensionsScannerService.canAddExtension(this.extension.gallery); + } + return false; + } + +} + export class UninstallAction extends ExtensionAction { - private static readonly UninstallLabel = localize('uninstallAction', "Uninstall"); + static readonly UninstallLabel = localize('uninstallAction', "Uninstall"); private static readonly UninstallingLabel = localize('Uninstalling', "Uninstalling"); private static readonly UninstallClass = `${ExtensionAction.LABEL_ACTION_CLASS} uninstall`; @@ -632,7 +674,6 @@ export class UpdateAction extends ExtensionAction { constructor( @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @INotificationService private readonly notificationService: INotificationService, ) { super(`extensions.update`, '', UpdateAction.DisabledClass, false); this.update(); @@ -669,18 +710,13 @@ export class UpdateAction extends ExtensionAction { return this.install(this.extension); } - private install(extension: IExtension): Promise { - return this.extensionsWorkbenchService.install(extension).then(() => { + private async install(extension: IExtension): Promise { + try { + await this.extensionsWorkbenchService.install(extension); alert(localize('updateExtensionComplete', "Updating extension {0} to version {1} completed.", extension.displayName, extension.latestVersion)); - }, err => { - if (!extension.gallery) { - return this.notificationService.error(err); - } - - console.error(err); - - return promptDownloadManually(extension.gallery, localize('failedToUpdate', "Failed to update \'{0}\'.", extension.identifier.id), err, this.instantiationService); - }); + } catch (err) { + this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, InstallOperation.Update, err).run(); + } } private getUpdateLabel(version?: string): string { @@ -828,44 +864,51 @@ export class DropDownMenuActionViewItem extends ExtensionActionViewItem { } } -export function getContextMenuActions(menuService: IMenuService, contextKeyService: IContextKeyService, instantiationService: IInstantiationService, extension: IExtension | undefined | null): IAction[][] { - const scopedContextKeyService = contextKeyService.createScoped(); - if (extension) { - scopedContextKeyService.createKey('extension', extension.identifier.id); - scopedContextKeyService.createKey('isBuiltinExtension', extension.isBuiltin); - scopedContextKeyService.createKey('extensionHasConfiguration', extension.local && !!extension.local.manifest.contributes && !!extension.local.manifest.contributes.configuration); - if (extension.state === ExtensionState.Installed) { - scopedContextKeyService.createKey('extensionStatus', 'installed'); +export function getContextMenuActions(extension: IExtension | undefined | null, inExtensionEditor: boolean, instantiationService: IInstantiationService): IAction[][] { + return instantiationService.invokeFunction(accessor => { + const scopedContextKeyService = accessor.get(IContextKeyService).createScoped(); + const menuService = accessor.get(IMenuService); + const extensionRecommendationsService = accessor.get(IExtensionRecommendationsService); + const extensionIgnoredRecommendationsService = accessor.get(IExtensionIgnoredRecommendationsService); + if (extension) { + scopedContextKeyService.createKey('extension', extension.identifier.id); + scopedContextKeyService.createKey('isBuiltinExtension', extension.isBuiltin); + scopedContextKeyService.createKey('extensionHasConfiguration', extension.local && !!extension.local.manifest.contributes && !!extension.local.manifest.contributes.configuration); + scopedContextKeyService.createKey('isExtensionRecommended', !!extensionRecommendationsService.getAllRecommendationsWithReason()[extension.identifier.id.toLowerCase()]); + scopedContextKeyService.createKey('isExtensionWorkspaceRecommended', extensionRecommendationsService.getAllRecommendationsWithReason()[extension.identifier.id.toLowerCase()]?.reasonId === ExtensionRecommendationReason.Workspace); + scopedContextKeyService.createKey('isUserIgnoredRecommendation', extensionIgnoredRecommendationsService.globalIgnoredRecommendations.some(e => e === extension.identifier.id.toLowerCase())); + scopedContextKeyService.createKey('inExtensionEditor', inExtensionEditor); + if (extension.state === ExtensionState.Installed) { + scopedContextKeyService.createKey('extensionStatus', 'installed'); + } } - } - const groups: IAction[][] = []; - const menu = menuService.createMenu(MenuId.ExtensionContext, scopedContextKeyService); - menu.getActions({ shouldForwardArgs: true }).forEach(([, actions]) => groups.push(actions.map(action => { - if (action instanceof SubmenuAction) { - return action; - } - return instantiationService.createInstance(MenuItemExtensionAction, action); - }))); - menu.dispose(); - scopedContextKeyService.dispose(); + const groups: IAction[][] = []; + const menu = menuService.createMenu(MenuId.ExtensionContext, scopedContextKeyService); + menu.getActions({ shouldForwardArgs: true }).forEach(([, actions]) => groups.push(actions.map(action => { + if (action instanceof SubmenuAction) { + return action; + } + return instantiationService.createInstance(MenuItemExtensionAction, action); + }))); + menu.dispose(); + scopedContextKeyService.dispose(); - return groups; + return groups; + }); } export class ManageExtensionAction extends ExtensionDropDownAction { static readonly ID = 'extensions.manage'; - private static readonly Class = `${ExtensionAction.ICON_ACTION_CLASS} manage codicon-gear`; + private static readonly Class = `${ExtensionAction.ICON_ACTION_CLASS} manage ` + ThemeIcon.asClassName(manageExtensionIcon); private static readonly HideManageExtensionClass = `${ManageExtensionAction.Class} hide`; constructor( @IInstantiationService instantiationService: IInstantiationService, @IExtensionService private readonly extensionService: IExtensionService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, - @IMenuService private readonly menuService: IMenuService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { super(ManageExtensionAction.ID, '', '', true, true, instantiationService); @@ -902,10 +945,12 @@ export class ManageExtensionAction extends ExtensionDropDownAction { this.instantiationService.createInstance(DisableGloballyAction, runningExtensions), this.instantiationService.createInstance(DisableForWorkspaceAction, runningExtensions) ]); - groups.push([this.instantiationService.createInstance(UninstallAction)]); - groups.push([this.instantiationService.createInstance(InstallAnotherVersionAction)]); + groups.push([ + this.instantiationService.createInstance(UninstallAction), + this.instantiationService.createInstance(InstallAnotherVersionAction) + ]); - getContextMenuActions(this.menuService, this.contextKeyService, this.instantiationService, this.extension).forEach(actions => groups.push(actions)); + getContextMenuActions(this.extension, false, this.instantiationService).forEach(actions => groups.push(actions)); groups.forEach(group => group.forEach(extensionAction => { if (extensionAction instanceof ExtensionAction) { @@ -933,6 +978,30 @@ export class ManageExtensionAction extends ExtensionDropDownAction { } } +export class ExtensionEditorManageExtensionAction extends ExtensionDropDownAction { + + constructor( + @IInstantiationService instantiationService: IInstantiationService + ) { + super('extensionEditor.manageExtension', '', `${ExtensionAction.ICON_ACTION_CLASS} manage ${ThemeIcon.asClassName(manageExtensionIcon)}`, true, true, instantiationService); + this.tooltip = localize('manage', "Manage"); + } + + update(): void { } + + run(): Promise { + const actionGroups: IAction[][] = []; + getContextMenuActions(this.extension, true, this.instantiationService).forEach(actions => actionGroups.push(actions)); + actionGroups.forEach(group => group.forEach(extensionAction => { + if (extensionAction instanceof ExtensionAction) { + extensionAction.extension = this.extension; + } + })); + return super.run({ actionGroups, disposeActionsOnHide: true }); + } + +} + export class MenuItemExtensionAction extends ExtensionAction { constructor( @@ -968,14 +1037,13 @@ export class InstallAnotherVersionAction extends ExtensionAction { @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IQuickInputService private readonly quickInputService: IQuickInputService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @INotificationService private readonly notificationService: INotificationService, ) { - super(InstallAnotherVersionAction.ID, InstallAnotherVersionAction.LABEL); + super(InstallAnotherVersionAction.ID, InstallAnotherVersionAction.LABEL, ExtensionAction.LABEL_ACTION_CLASS); this.update(); } update(): void { - this.enabled = !!this.extension && !this.extension.isBuiltin && !!this.extension.gallery; + this.enabled = !!this.extension && !this.extension.isBuiltin && !!this.extension.gallery && this.extension.state === ExtensionState.Installed; } run(): Promise { @@ -983,22 +1051,20 @@ export class InstallAnotherVersionAction extends ExtensionAction { return Promise.resolve(); } return this.quickInputService.pick(this.getVersionEntries(), { placeHolder: localize('selectVersion', "Select Version to Install"), matchOnDetail: true }) - .then(pick => { + .then(async pick => { if (pick) { if (this.extension!.version === pick.id) { return Promise.resolve(); } - const promise: Promise = pick.latest ? this.extensionsWorkbenchService.install(this.extension!) : this.extensionsWorkbenchService.installVersion(this.extension!, pick.id); - return promise - .then(null, err => { - if (!this.extension!.gallery) { - return this.notificationService.error(err); - } - - console.error(err); - - return promptDownloadManually(this.extension!.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", this.extension!.identifier.id), err, this.instantiationService); - }); + try { + if (pick.latest) { + await this.extensionsWorkbenchService.install(this.extension!); + } else { + await this.extensionsWorkbenchService.installVersion(this.extension!, pick.id); + } + } catch (error) { + this.instantiationService.createInstance(PromptExtensionInstallFailureAction, this.extension!, InstallOperation.Install, error).run(); + } } return null; }); @@ -1020,6 +1086,7 @@ export class EnableForWorkspaceAction extends ExtensionAction { @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService ) { super(EnableForWorkspaceAction.ID, EnableForWorkspaceAction.LABEL, ExtensionAction.LABEL_ACTION_CLASS); + this.tooltip = localize('enableForWorkspaceActionToolTip', "Enable this extension only in this workspace"); this.update(); } @@ -1050,6 +1117,7 @@ export class EnableGloballyAction extends ExtensionAction { @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService ) { super(EnableGloballyAction.ID, EnableGloballyAction.LABEL, ExtensionAction.LABEL_ACTION_CLASS); + this.tooltip = localize('enableGloballyActionToolTip', "Enable this extension"); this.update(); } @@ -1075,18 +1143,24 @@ export class DisableForWorkspaceAction extends ExtensionAction { static readonly ID = 'extensions.disableForWorkspace'; static readonly LABEL = localize('disableForWorkspaceAction', "Disable (Workspace)"); - constructor(readonly runningExtensions: IExtensionDescription[], + constructor(private _runningExtensions: IExtensionDescription[], @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService ) { super(DisableForWorkspaceAction.ID, DisableForWorkspaceAction.LABEL, ExtensionAction.LABEL_ACTION_CLASS); + this.tooltip = localize('disableForWorkspaceActionToolTip', "Disable this extension only in this workspace"); + this.update(); + } + + set runningExtensions(runningExtensions: IExtensionDescription[]) { + this._runningExtensions = runningExtensions; this.update(); } update(): void { this.enabled = false; - if (this.extension && this.extension.local && this.runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier) && this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY)) { + if (this.extension && this.extension.local && this._runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier) && this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY)) { this.enabled = this.extension.state === ExtensionState.Installed && (this.extension.enablementState === EnablementState.EnabledGlobally || this.extension.enablementState === EnablementState.EnabledWorkspace) && this.extensionEnablementService.canChangeWorkspaceEnablement(this.extension.local); @@ -1106,17 +1180,24 @@ export class DisableGloballyAction extends ExtensionAction { static readonly ID = 'extensions.disableGlobally'; static readonly LABEL = localize('disableGloballyAction', "Disable"); - constructor(readonly runningExtensions: IExtensionDescription[], + constructor( + private _runningExtensions: IExtensionDescription[], @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService ) { super(DisableGloballyAction.ID, DisableGloballyAction.LABEL, ExtensionAction.LABEL_ACTION_CLASS); + this.tooltip = localize('disableGloballyActionToolTip', "Disable this extension"); + this.update(); + } + + set runningExtensions(runningExtensions: IExtensionDescription[]) { + this._runningExtensions = runningExtensions; this.update(); } update(): void { this.enabled = false; - if (this.extension && this.extension.local && this.runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))) { + if (this.extension && this.extension.local && this._runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))) { this.enabled = this.extension.state === ExtensionState.Installed && (this.extension.enablementState === EnablementState.EnabledGlobally || this.extension.enablementState === EnablementState.EnabledWorkspace) && this.extensionEnablementService.canChangeEnablement(this.extension.local); @@ -1146,13 +1227,21 @@ export class EnableDropDownAction extends ActionWithDropDownAction { export class DisableDropDownAction extends ActionWithDropDownAction { constructor( - runningExtensions: IExtensionDescription[], + @IExtensionService extensionService: IExtensionService, @IInstantiationService instantiationService: IInstantiationService ) { - super('extensions.disable', localize('disableAction', "Disable"), [ - instantiationService.createInstance(DisableGloballyAction, runningExtensions), - instantiationService.createInstance(DisableForWorkspaceAction, runningExtensions) - ]); + const actions = [ + instantiationService.createInstance(DisableGloballyAction, []), + instantiationService.createInstance(DisableForWorkspaceAction, []) + ]; + super('extensions.disable', localize('disableAction', "Disable"), actions); + + const updateRunningExtensions = async () => { + const runningExtensions = await extensionService.getExtensions(); + actions.forEach(a => a.runningExtensions = runningExtensions); + }; + updateRunningExtensions(); + this._register(extensionService.onDidChangeExtensions(() => updateRunningExtensions())); } } @@ -1265,7 +1354,6 @@ export class UpdateAllAction extends Action { constructor( id: string, label: string, isPrimary: boolean, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @INotificationService private readonly notificationService: INotificationService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(id, label, '', false); @@ -1283,16 +1371,12 @@ export class UpdateAllAction extends Action { return Promise.all(this.extensionsWorkbenchService.outdated.map(e => this.install(e))); } - private install(extension: IExtension): Promise { - return this.extensionsWorkbenchService.install(extension).then(undefined, err => { - if (!extension.gallery) { - return this.notificationService.error(err); - } - - console.error(err); - - return promptDownloadManually(extension.gallery, localize('failedToUpdate', "Failed to update \'{0}\'.", extension.identifier.id), err, this.instantiationService); - }); + private async install(extension: IExtension): Promise { + try { + await this.extensionsWorkbenchService.install(extension); + } catch (err) { + this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, InstallOperation.Update, err).run(); + } } } @@ -1345,11 +1429,12 @@ export class ReloadAction extends ExtensionAction { } const isUninstalled = this.extension.state === ExtensionState.Uninstalled; - const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))[0]; - const isSameExtensionRunning = runningExtension && this.extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension)); + const runningExtension = this._runningExtensions.find(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier)); if (isUninstalled) { - if (isSameExtensionRunning && !this.extensionService.canRemoveExtension(runningExtension)) { + const canRemoveRunningExtension = runningExtension && this.extensionService.canRemoveExtension(runningExtension); + const isSameExtensionRunning = runningExtension && (!this.extension.server || this.extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension))); + if (!canRemoveRunningExtension && isSameExtensionRunning) { this.enabled = true; this.label = localize('reloadRequired', "Reload Required"); this.tooltip = localize('postUninstallTooltip', "Please reload Visual Studio Code to complete the uninstallation of this extension."); @@ -1358,6 +1443,7 @@ export class ReloadAction extends ExtensionAction { return; } if (this.extension.local) { + const isSameExtensionRunning = runningExtension && this.extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension)); const isEnabled = this.extensionEnablementService.isEnabled(this.extension.local); // Extension is running @@ -1693,11 +1779,11 @@ export class ShowInstalledExtensionsAction extends Action { super(id, label, undefined, true); } - run(refresh?: boolean): Promise { + run(): Promise { return this.viewletService.openViewlet(VIEWLET_ID, true) .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) .then(viewlet => { - viewlet.search('@installed ', refresh); + viewlet.search('@installed '); viewlet.focus(); }); } @@ -1736,7 +1822,7 @@ export class ClearExtensionsSearchResultsAction extends Action { label: string, @IViewsService private readonly viewsService: IViewsService ) { - super(id, label, 'codicon-clear-all', true); + super(id, label, ThemeIcon.asClassName(clearSearchResultsIcon), true); } async run(): Promise { @@ -1755,18 +1841,39 @@ export class ClearExtensionsInputAction extends ClearExtensionsSearchResultsActi id: string, label: string, onSearchChange: Event, - value: string, + getValue: () => string, @IViewsService viewsService: IViewsService ) { super(id, label, viewsService); - this.onSearchChange(value); + this.onSearchChange(getValue()); this._register(onSearchChange(this.onSearchChange, this)); } private onSearchChange(value: string): void { this.enabled = !!value; } +} +export class RefreshExtensionsAction extends Action { + + static readonly ID = 'workbench.extensions.action.refreshExtension'; + static readonly LABEL = localize('refreshExtension', "Refresh"); + + constructor( + id: string, + label: string, + @IViewsService private readonly viewsService: IViewsService + ) { + super(id, label, ThemeIcon.asClassName(refreshIcon), true); + } + + async run(): Promise { + const viewPaneContainer = this.viewsService.getActiveViewPaneContainerWithId(VIEWLET_ID); + if (viewPaneContainer) { + const extensionsViewPaneContainer = viewPaneContainer as IExtensionsViewPaneContainer; + return extensionsViewPaneContainer.refresh(); + } + } } export class ShowBuiltInExtensionsAction extends Action { @@ -1899,7 +2006,7 @@ export class ShowRecommendedExtensionsAction extends Action { return this.viewletService.openViewlet(VIEWLET_ID, true) .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) .then(viewlet => { - viewlet.search('@recommended ', true); + viewlet.search('@recommended '); viewlet.focus(); }); } @@ -1956,26 +2063,21 @@ export class InstallRecommendedExtensionAction extends Action { this.extensionId = extensionId; } - run(): Promise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => { - viewlet.search(`@id:${this.extensionId}`); - viewlet.focus(); - return this.extensionWorkbenchService.queryGallery({ names: [this.extensionId], source: 'install-recommendation', pageSize: 1 }, CancellationToken.None) - .then(pager => { - if (pager && pager.firstPage && pager.firstPage.length) { - const extension = pager.firstPage[0]; - return this.extensionWorkbenchService.install(extension) - .then(() => this.extensionWorkbenchService.open(extension)) - .then(() => null, err => { - console.error(err); - return promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.identifier.id), err, this.instantiationService); - }); - } - return null; - }); - }); + async run(): Promise { + const viewlet = await this.viewletService.openViewlet(VIEWLET_ID, true); + const viewPaneContainer = viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer; + viewPaneContainer.search(`@id:${this.extensionId}`); + viewPaneContainer.focus(); + const pager = await this.extensionWorkbenchService.queryGallery({ names: [this.extensionId], source: 'install-recommendation', pageSize: 1 }, CancellationToken.None); + if (pager && pager.firstPage && pager.firstPage.length) { + const extension = pager.firstPage[0]; + await this.extensionWorkbenchService.open(extension); + try { + await this.extensionWorkbenchService.install(extension); + } catch (err) { + this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, InstallOperation.Install, err).run(); + } + } } } @@ -1983,7 +2085,7 @@ export class IgnoreExtensionRecommendationAction extends Action { static readonly ID = 'extensions.ignore'; - private static readonly Class = 'extension-action ignore'; + private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} ignore`; constructor( private readonly extension: IExtension, @@ -2006,7 +2108,7 @@ export class UndoIgnoreExtensionRecommendationAction extends Action { static readonly ID = 'extensions.ignore'; - private static readonly Class = 'extension-action undo-ignore'; + private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} undo-ignore`; constructor( private readonly extension: IExtension, @@ -2122,13 +2224,15 @@ export class ChangeSortAction extends Action { this.query = Query.parse(''); this.enabled = false; + this.checked = false; this._register(onSearchChange(this.onSearchChange, this)); } private onSearchChange(value: string): void { const query = Query.parse(value); this.query = new Query(query.value, this.sortBy || query.sortBy, query.groupBy); - this.enabled = !!value && this.query.isValid() && !this.query.equals(query); + this.enabled = !!value && this.query.isValid(); + this.checked = this.enabled && this.query.equals(query); } run(): Promise { @@ -2141,124 +2245,6 @@ export class ChangeSortAction extends Action { } } -export class ConfigureRecommendedExtensionsCommandsContributor extends Disposable implements IWorkbenchContribution { - - private workspaceContextKey = new RawContextKey('workspaceRecommendations', true); - private workspaceFolderContextKey = new RawContextKey('workspaceFolderRecommendations', true); - private addToWorkspaceRecommendationsContextKey = new RawContextKey('addToWorkspaceRecommendations', false); - private addToWorkspaceFolderRecommendationsContextKey = new RawContextKey('addToWorkspaceFolderRecommendations', false); - - constructor( - @IContextKeyService contextKeyService: IContextKeyService, - @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, - @IEditorService editorService: IEditorService - ) { - super(); - const boundWorkspaceContextKey = this.workspaceContextKey.bindTo(contextKeyService); - boundWorkspaceContextKey.set(workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE); - this._register(workspaceContextService.onDidChangeWorkbenchState(() => boundWorkspaceContextKey.set(workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE))); - - const boundWorkspaceFolderContextKey = this.workspaceFolderContextKey.bindTo(contextKeyService); - boundWorkspaceFolderContextKey.set(workspaceContextService.getWorkspace().folders.length > 0); - this._register(workspaceContextService.onDidChangeWorkspaceFolders(() => boundWorkspaceFolderContextKey.set(workspaceContextService.getWorkspace().folders.length > 0))); - - const boundAddToWorkspaceRecommendationsContextKey = this.addToWorkspaceRecommendationsContextKey.bindTo(contextKeyService); - boundAddToWorkspaceRecommendationsContextKey.set(editorService.activeEditor instanceof ExtensionsInput && workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE); - this._register(editorService.onDidActiveEditorChange(() => boundAddToWorkspaceRecommendationsContextKey.set( - editorService.activeEditor instanceof ExtensionsInput && workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE))); - this._register(workspaceContextService.onDidChangeWorkbenchState(() => boundAddToWorkspaceRecommendationsContextKey.set( - editorService.activeEditor instanceof ExtensionsInput && workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE))); - - const boundAddToWorkspaceFolderRecommendationsContextKey = this.addToWorkspaceFolderRecommendationsContextKey.bindTo(contextKeyService); - boundAddToWorkspaceFolderRecommendationsContextKey.set(editorService.activeEditor instanceof ExtensionsInput); - this._register(editorService.onDidActiveEditorChange(() => boundAddToWorkspaceFolderRecommendationsContextKey.set(editorService.activeEditor instanceof ExtensionsInput))); - - this.registerCommands(); - } - - private registerCommands(): void { - CommandsRegistry.registerCommand(ConfigureWorkspaceRecommendedExtensionsAction.ID, serviceAccessor => { - serviceAccessor.get(IInstantiationService).createInstance(ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceRecommendedExtensionsAction.ID, ConfigureWorkspaceRecommendedExtensionsAction.LABEL).run(); - }); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: ConfigureWorkspaceRecommendedExtensionsAction.ID, - title: { value: ConfigureWorkspaceRecommendedExtensionsAction.LABEL, original: 'Configure Recommended Extensions (Workspace)' }, - category: localize('extensions', "Extensions") - }, - when: this.workspaceContextKey - }); - - CommandsRegistry.registerCommand(ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, serviceAccessor => { - serviceAccessor.get(IInstantiationService).createInstance(ConfigureWorkspaceFolderRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL).run(); - }); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, - title: { value: ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL, original: 'Configure Recommended Extensions (Workspace Folder)' }, - category: localize('extensions', "Extensions") - }, - when: this.workspaceFolderContextKey - }); - - CommandsRegistry.registerCommand(AddToWorkspaceRecommendationsAction.ADD_ID, serviceAccessor => { - serviceAccessor.get(IInstantiationService) - .createInstance(AddToWorkspaceRecommendationsAction, AddToWorkspaceRecommendationsAction.ADD_ID, AddToWorkspaceRecommendationsAction.ADD_LABEL) - .run(AddToWorkspaceRecommendationsAction.ADD); - }); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: AddToWorkspaceRecommendationsAction.ADD_ID, - title: { value: AddToWorkspaceRecommendationsAction.ADD_LABEL, original: 'Add to Recommended Extensions (Workspace)' }, - category: localize('extensions', "Extensions") - }, - when: this.addToWorkspaceRecommendationsContextKey - }); - - CommandsRegistry.registerCommand(AddToWorkspaceFolderRecommendationsAction.ADD_ID, serviceAccessor => { - serviceAccessor.get(IInstantiationService) - .createInstance(AddToWorkspaceFolderRecommendationsAction, AddToWorkspaceFolderRecommendationsAction.ADD_ID, AddToWorkspaceFolderRecommendationsAction.ADD_LABEL) - .run(AddToWorkspaceRecommendationsAction.ADD); - }); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: AddToWorkspaceFolderRecommendationsAction.ADD_ID, - title: { value: AddToWorkspaceFolderRecommendationsAction.ADD_LABEL, original: 'Extensions: Add to Recommended Extensions (Workspace Folder)' }, - category: localize('extensions', "Extensions") - }, - when: this.addToWorkspaceFolderRecommendationsContextKey - }); - - CommandsRegistry.registerCommand(AddToWorkspaceRecommendationsAction.IGNORE_ID, serviceAccessor => { - serviceAccessor.get(IInstantiationService) - .createInstance(AddToWorkspaceRecommendationsAction, AddToWorkspaceRecommendationsAction.IGNORE_ID, AddToWorkspaceRecommendationsAction.IGNORE_LABEL) - .run(AddToWorkspaceRecommendationsAction.IGNORE); - }); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: AddToWorkspaceRecommendationsAction.IGNORE_ID, - title: { value: AddToWorkspaceRecommendationsAction.IGNORE_LABEL, original: 'Extensions: Ignore Recommended Extension (Workspace)' }, - category: localize('extensions', "Extensions") - }, - when: this.addToWorkspaceRecommendationsContextKey - }); - - CommandsRegistry.registerCommand(AddToWorkspaceFolderRecommendationsAction.IGNORE_ID, serviceAccessor => { - serviceAccessor.get(IInstantiationService) - .createInstance(AddToWorkspaceFolderRecommendationsAction, AddToWorkspaceFolderRecommendationsAction.IGNORE_ID, AddToWorkspaceFolderRecommendationsAction.IGNORE_LABEL) - .run(AddToWorkspaceRecommendationsAction.IGNORE); - }); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: AddToWorkspaceFolderRecommendationsAction.IGNORE_ID, - title: { value: AddToWorkspaceFolderRecommendationsAction.IGNORE_LABEL, original: 'Extensions: Ignore Recommended Extension (Workspace Folder)' }, - category: localize('extensions', "Extensions") - }, - when: this.addToWorkspaceFolderRecommendationsContextKey - }); - } -} - export abstract class AbstractConfigureRecommendedExtensionsAction extends Action { constructor( @@ -2300,83 +2286,6 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio })); } - protected addExtensionToWorkspaceConfig(workspaceConfigurationFile: URI, extensionId: string, shouldRecommend: boolean) { - return this.getOrUpdateWorkspaceConfigurationFile(workspaceConfigurationFile) - .then(content => { - const extensionIdLowerCase = extensionId.toLowerCase(); - const workspaceExtensionsConfigContent: IExtensionsConfigContent = (json.parse(content.value.toString()) || {})['extensions'] || {}; - let insertInto = shouldRecommend ? workspaceExtensionsConfigContent.recommendations || [] : workspaceExtensionsConfigContent.unwantedRecommendations || []; - let removeFrom = shouldRecommend ? workspaceExtensionsConfigContent.unwantedRecommendations || [] : workspaceExtensionsConfigContent.recommendations || []; - - if (insertInto.some(e => e.toLowerCase() === extensionIdLowerCase)) { - return Promise.resolve(null); - } - - insertInto.push(extensionId); - removeFrom = removeFrom.filter(x => x.toLowerCase() !== extensionIdLowerCase); - - return this.jsonEditingService.write(workspaceConfigurationFile, - [{ - path: ['extensions'], - value: { - recommendations: shouldRecommend ? insertInto : removeFrom, - unwantedRecommendations: shouldRecommend ? removeFrom : insertInto - } - }], - true); - }); - } - - protected addExtensionToWorkspaceFolderConfig(extensionsFileResource: URI, extensionId: string, shouldRecommend: boolean): Promise { - return this.getOrCreateExtensionsFile(extensionsFileResource) - .then(({ content }) => { - const extensionIdLowerCase = extensionId.toLowerCase(); - const extensionsConfigContent: IExtensionsConfigContent = json.parse(content) || {}; - let insertInto = shouldRecommend ? extensionsConfigContent.recommendations || [] : extensionsConfigContent.unwantedRecommendations || []; - let removeFrom = shouldRecommend ? extensionsConfigContent.unwantedRecommendations || [] : extensionsConfigContent.recommendations || []; - - if (insertInto.some(e => e.toLowerCase() === extensionIdLowerCase)) { - return Promise.resolve(null); - } - - insertInto.push(extensionId); - - let removeFromPromise: Promise = Promise.resolve(); - if (removeFrom.some(e => e.toLowerCase() === extensionIdLowerCase)) { - removeFrom = removeFrom.filter(x => x.toLowerCase() !== extensionIdLowerCase); - removeFromPromise = this.jsonEditingService.write(extensionsFileResource, - [{ - path: shouldRecommend ? ['unwantedRecommendations'] : ['recommendations'], - value: removeFrom - }], - true); - } - - return removeFromPromise.then(() => - this.jsonEditingService.write(extensionsFileResource, - [{ - path: shouldRecommend ? ['recommendations'] : ['unwantedRecommendations'], - value: insertInto - }], - true) - ); - }); - } - - protected getWorkspaceExtensionsConfigContent(extensionsFileResource: URI): Promise { - return Promise.resolve(this.fileService.readFile(extensionsFileResource)) - .then(content => { - return (json.parse(content.value.toString()) || {})['extensions'] || {}; - }, err => ({ recommendations: [], unwantedRecommendations: [] })); - } - - protected getWorkspaceFolderExtensionsConfigContent(extensionsFileResource: URI): Promise { - return Promise.resolve(this.fileService.readFile(extensionsFileResource)) - .then(content => { - return (json.parse(content.value.toString()) || {}); - }, err => ({ recommendations: [], unwantedRecommendations: [] })); - } - private getOrUpdateWorkspaceConfigurationFile(workspaceConfigurationFile: URI): Promise { return Promise.resolve(this.fileService.readFile(workspaceConfigurationFile)) .then(content => { @@ -2495,161 +2404,6 @@ export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends Abstrac } } -export class AddToWorkspaceFolderRecommendationsAction extends AbstractConfigureRecommendedExtensionsAction { - static readonly ADD = true; - static readonly IGNORE = false; - static readonly ADD_ID = 'workbench.extensions.action.addToWorkspaceFolderRecommendations'; - static readonly ADD_LABEL = localize('addToWorkspaceFolderRecommendations', "Add to Recommended Extensions (Workspace Folder)"); - static readonly IGNORE_ID = 'workbench.extensions.action.addToWorkspaceFolderIgnoredRecommendations'; - static readonly IGNORE_LABEL = localize('addToWorkspaceFolderIgnoredRecommendations', "Ignore Recommended Extension (Workspace Folder)"); - - constructor( - id: string, - label: string, - @IFileService fileService: IFileService, - @ITextFileService textFileService: ITextFileService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IEditorService editorService: IEditorService, - @IJSONEditingService jsonEditingService: IJSONEditingService, - @ITextModelService textModelResolverService: ITextModelService, - @ICommandService private readonly commandService: ICommandService, - @INotificationService private readonly notificationService: INotificationService - ) { - super(id, label, contextService, fileService, textFileService, editorService, jsonEditingService, textModelResolverService); - } - - run(shouldRecommend: boolean): Promise { - if (!(this.editorService.activeEditor instanceof ExtensionsInput) || !this.editorService.activeEditor.extension) { - return Promise.resolve(); - } - const folders = this.contextService.getWorkspace().folders; - if (!folders || !folders.length) { - this.notificationService.info(localize('AddToWorkspaceFolderRecommendations.noWorkspace', 'There are no workspace folders open to add recommendations.')); - return Promise.resolve(); - } - - const extensionId = this.editorService.activeEditor.extension.identifier; - const pickFolderPromise = folders.length === 1 - ? Promise.resolve(folders[0]) - : this.commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID); - return Promise.resolve(pickFolderPromise) - .then(workspaceFolder => { - if (!workspaceFolder) { - return Promise.resolve(); - } - const configurationFile = workspaceFolder.toResource(EXTENSIONS_CONFIG); - return this.getWorkspaceFolderExtensionsConfigContent(configurationFile).then(content => { - const extensionIdLowerCase = extensionId.id.toLowerCase(); - if (shouldRecommend) { - if ((content.recommendations || []).some(e => e.toLowerCase() === extensionIdLowerCase)) { - this.notificationService.info(localize('AddToWorkspaceFolderRecommendations.alreadyExists', 'This extension is already present in this workspace folder\'s recommendations.')); - return Promise.resolve(); - } - - return this.addExtensionToWorkspaceFolderConfig(configurationFile, extensionId.id, shouldRecommend).then(() => { - this.notificationService.prompt(Severity.Info, - localize('AddToWorkspaceFolderRecommendations.success', 'The extension was successfully added to this workspace folder\'s recommendations.'), - [{ - label: localize('viewChanges', "View Changes"), - run: () => this.openExtensionsFile(configurationFile) - }]); - }, err => { - this.notificationService.error(localize('AddToWorkspaceFolderRecommendations.failure', 'Failed to write to extensions.json. {0}', err)); - }); - } - else { - if ((content.unwantedRecommendations || []).some(e => e.toLowerCase() === extensionIdLowerCase)) { - this.notificationService.info(localize('AddToWorkspaceFolderIgnoredRecommendations.alreadyExists', 'This extension is already present in this workspace folder\'s unwanted recommendations.')); - return Promise.resolve(); - } - - return this.addExtensionToWorkspaceFolderConfig(configurationFile, extensionId.id, shouldRecommend).then(() => { - this.notificationService.prompt(Severity.Info, - localize('AddToWorkspaceFolderIgnoredRecommendations.success', 'The extension was successfully added to this workspace folder\'s unwanted recommendations.'), - [{ - label: localize('viewChanges', "View Changes"), - run: () => this.openExtensionsFile(configurationFile) - }]); - }, err => { - this.notificationService.error(localize('AddToWorkspaceFolderRecommendations.failure', 'Failed to write to extensions.json. {0}', err)); - }); - } - }); - }); - } -} - -export class AddToWorkspaceRecommendationsAction extends AbstractConfigureRecommendedExtensionsAction { - static readonly ADD = true; - static readonly IGNORE = false; - static readonly ADD_ID = 'workbench.extensions.action.addToWorkspaceRecommendations'; - static readonly ADD_LABEL = localize('addToWorkspaceRecommendations', "Add to Recommended Extensions (Workspace)"); - static readonly IGNORE_ID = 'workbench.extensions.action.addToWorkspaceIgnoredRecommendations'; - static readonly IGNORE_LABEL = localize('addToWorkspaceIgnoredRecommendations', "Ignore Recommended Extension (Workspace)"); - - constructor( - id: string, - label: string, - @IFileService fileService: IFileService, - @ITextFileService textFileService: ITextFileService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IEditorService editorService: IEditorService, - @IJSONEditingService jsonEditingService: IJSONEditingService, - @ITextModelService textModelResolverService: ITextModelService, - @INotificationService private readonly notificationService: INotificationService - ) { - super(id, label, contextService, fileService, textFileService, editorService, jsonEditingService, textModelResolverService); - } - - run(shouldRecommend: boolean): Promise { - const workspaceConfig = this.contextService.getWorkspace().configuration; - - if (!(this.editorService.activeEditor instanceof ExtensionsInput) || !this.editorService.activeEditor.extension || !workspaceConfig) { - return Promise.resolve(); - } - - const extensionId = this.editorService.activeEditor.extension.identifier; - - return this.getWorkspaceExtensionsConfigContent(workspaceConfig).then(content => { - const extensionIdLowerCase = extensionId.id.toLowerCase(); - if (shouldRecommend) { - if ((content.recommendations || []).some(e => e.toLowerCase() === extensionIdLowerCase)) { - this.notificationService.info(localize('AddToWorkspaceRecommendations.alreadyExists', 'This extension is already present in workspace recommendations.')); - return Promise.resolve(); - } - - return this.addExtensionToWorkspaceConfig(workspaceConfig, extensionId.id, shouldRecommend).then(() => { - this.notificationService.prompt(Severity.Info, - localize('AddToWorkspaceRecommendations.success', 'The extension was successfully added to this workspace\'s recommendations.'), - [{ - label: localize('viewChanges', "View Changes"), - run: () => this.openWorkspaceConfigurationFile(workspaceConfig) - }]); - - }, err => { - this.notificationService.error(localize('AddToWorkspaceRecommendations.failure', 'Failed to write. {0}', err)); - }); - } else { - if ((content.unwantedRecommendations || []).some(e => e.toLowerCase() === extensionIdLowerCase)) { - this.notificationService.info(localize('AddToWorkspaceUnwantedRecommendations.alreadyExists', 'This extension is already present in workspace unwanted recommendations.')); - return Promise.resolve(); - } - - return this.addExtensionToWorkspaceConfig(workspaceConfig, extensionId.id, shouldRecommend).then(() => { - this.notificationService.prompt(Severity.Info, - localize('AddToWorkspaceUnwantedRecommendations.success', 'The extension was successfully added to this workspace\'s unwanted recommendations.'), - [{ - label: localize('viewChanges', "View Changes"), - run: () => this.openWorkspaceConfigurationFile(workspaceConfig) - }]); - }, err => { - this.notificationService.error(localize('AddToWorkspaceRecommendations.failure', 'Failed to write. {0}', err)); - }); - } - }); - } -} - export class StatusLabelAction extends Action implements IExtensionContainer { private static readonly ENABLED_CLASS = `${ExtensionAction.TEXT_ACTION_CLASS} extension-status-label`; @@ -2776,30 +2530,43 @@ export class MaliciousStatusLabelAction extends ExtensionAction { } } -export class SyncIgnoredIconAction extends ExtensionAction { +export class ToggleSyncExtensionAction extends ExtensionDropDownAction { - private static readonly ENABLE_CLASS = `${ExtensionAction.ICON_ACTION_CLASS} codicon-sync-ignored`; - private static readonly DISABLE_CLASS = `${SyncIgnoredIconAction.ENABLE_CLASS} hide`; + 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)}`; constructor( @IConfigurationService private readonly configurationService: IConfigurationService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IUserDataAutoSyncEnablementService private readonly userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService, + @IInstantiationService instantiationService: IInstantiationService, ) { - super('extensions.syncignore', '', SyncIgnoredIconAction.DISABLE_CLASS, false); + super('extensions.sync', '', ToggleSyncExtensionAction.SYNC_CLASS, false, true, instantiationService); this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectedKeys.includes('settingsSync.ignoredExtensions'))(() => this.update())); + this._register(userDataAutoSyncEnablementService.onDidChangeEnablement(() => this.update())); this.update(); - this.tooltip = localize('syncingore.label', "This extension is ignored during sync."); } update(): void { - this.class = SyncIgnoredIconAction.DISABLE_CLASS; - if (this.extension && this.extensionsWorkbenchService.isExtensionIgnoredToSync(this.extension)) { - this.class = SyncIgnoredIconAction.ENABLE_CLASS; + this.enabled = !!this.extension && this.userDataAutoSyncEnablementService.isEnabled() && this.extension.state === ExtensionState.Installed; + if (this.extension) { + const isIgnored = this.extensionsWorkbenchService.isExtensionIgnoredToSync(this.extension); + this.class = isIgnored ? ToggleSyncExtensionAction.IGNORED_SYNC_CLASS : ToggleSyncExtensionAction.SYNC_CLASS; + this.tooltip = isIgnored ? localize('ignored', "This extension is ignored during sync") : localize('synced', "This extension is synced"); } } - run(): Promise { - return Promise.resolve(); + async run(): Promise { + return super.run({ + actionGroups: [ + [ + new Action( + 'extensions.syncignore', + this.extensionsWorkbenchService.isExtensionIgnoredToSync(this.extension!) ? localize('sync', "Sync this extension") : localize('do not sync', "Do not sync this extension") + , undefined, true, () => this.extensionsWorkbenchService.toggleExtensionIgnoredToSync(this.extension!)) + ] + ], disposeActionsOnHide: true + }); } } @@ -2883,8 +2650,8 @@ export class ExtensionToolTipAction extends ExtensionAction { export class SystemDisabledWarningAction extends ExtensionAction { private static readonly CLASS = `${ExtensionAction.ICON_ACTION_CLASS} system-disable`; - private static readonly WARNING_CLASS = `${SystemDisabledWarningAction.CLASS} ${Codicon.warning.classNames}`; - private static readonly INFO_CLASS = `${SystemDisabledWarningAction.CLASS} ${Codicon.info.classNames}`; + private static readonly WARNING_CLASS = `${SystemDisabledWarningAction.CLASS} ${ThemeIcon.asClassName(warningIcon)}`; + private static readonly INFO_CLASS = `${SystemDisabledWarningAction.CLASS} ${ThemeIcon.asClassName(infoIcon)}`; updateWhenCounterExtensionChanges: boolean = true; private _runningExtensions: IExtensionDescription[] | null = null; @@ -3105,6 +2872,7 @@ export class InstallVSIXAction extends Action { title: localize('installFromVSIX', "Install from VSIX"), filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }], canSelectFiles: true, + canSelectMany: true, openLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install")) }); @@ -3273,21 +3041,18 @@ interface IExtensionPickItem extends IQuickPickItem { extension?: IExtension; } -export class InstallLocalExtensionsInRemoteAction extends Action { +export abstract class AbstractInstallExtensionsInServerAction extends Action { private extensions: IExtension[] | undefined = undefined; constructor( - @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, - @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + id: string, + @IExtensionsWorkbenchService protected readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IQuickInputService private readonly quickInputService: IQuickInputService, @INotificationService private readonly notificationService: INotificationService, - @IHostService private readonly hostService: IHostService, @IProgressService private readonly progressService: IProgressService, - @IInstantiationService private readonly instantiationService: IInstantiationService ) { - super('workbench.extensions.actions.installLocalExtensionsInRemote'); + super(id); this.update(); this.extensionsWorkbenchService.queryLocal().then(() => this.updateExtensions()); this._register(this.extensionsWorkbenchService.onChange(() => { @@ -3297,13 +3062,6 @@ export class InstallLocalExtensionsInRemoteAction extends Action { })); } - get label(): string { - if (this.extensionManagementServerService.remoteExtensionManagementServer) { - return localize('select and install local extensions', "Install Local Extensions in '{0}'...", this.extensionManagementServerService.remoteExtensionManagementServer.label); - } - return ''; - } - private updateExtensions(): void { this.extensions = this.extensionsWorkbenchService.local; this.update(); @@ -3315,7 +3073,7 @@ export class InstallLocalExtensionsInRemoteAction extends Action { } async run(): Promise { - return this.selectAndInstallLocalExtensions(); + return this.selectAndInstallExtensions(); } private async queryExtensionsToInstall(): Promise { @@ -3323,15 +3081,7 @@ export class InstallLocalExtensionsInRemoteAction extends Action { return this.getExtensionsToInstall(local); } - private getExtensionsToInstall(local: IExtension[]): IExtension[] { - return local.filter(extension => { - const action = this.instantiationService.createInstance(RemoteInstallAction, true); - action.extension = extension; - return action.enabled; - }); - } - - private async selectAndInstallLocalExtensions(): Promise { + private async selectAndInstallExtensions(): Promise { const quickPick = this.quickInputService.createQuickPick(); quickPick.busy = true; const disposable = quickPick.onDidAccept(() => { @@ -3344,7 +3094,7 @@ export class InstallLocalExtensionsInRemoteAction extends Action { const localExtensionsToInstall = await this.queryExtensionsToInstall(); quickPick.busy = false; if (localExtensionsToInstall.length) { - quickPick.title = localize('install local extensions title', "Install Local Extensions in '{0}'", this.extensionManagementServerService.remoteExtensionManagementServer!.label); + quickPick.title = this.getQuickPickTitle(); quickPick.placeholder = localize('select extensions to install', "Select extensions to install"); quickPick.canSelectMany = true; localExtensionsToInstall.sort((e1, e2) => e1.displayName.localeCompare(e2.displayName)); @@ -3359,21 +3109,60 @@ export class InstallLocalExtensionsInRemoteAction extends Action { } } - private onDidAccept(selectedItems: ReadonlyArray): void { + private async onDidAccept(selectedItems: ReadonlyArray): Promise { if (selectedItems.length) { const localExtensionsToInstall = selectedItems.filter(r => !!r.extension).map(r => r.extension!); if (localExtensionsToInstall.length) { - this.progressService.withProgress( + await this.progressService.withProgress( { location: ProgressLocation.Notification, title: localize('installing extensions', "Installing Extensions...") }, - () => this.installLocalExtensions(localExtensionsToInstall)); + () => this.installExtensions(localExtensionsToInstall)); + this.notificationService.info(localize('finished installing', "Successfully installed extensions.")); } } } - private async installLocalExtensions(localExtensionsToInstall: IExtension[]): Promise { + protected abstract getQuickPickTitle(): string; + protected abstract getExtensionsToInstall(local: IExtension[]): IExtension[]; + protected abstract installExtensions(extensions: IExtension[]): Promise; +} + +export class InstallLocalExtensionsInRemoteAction extends AbstractInstallExtensionsInServerAction { + + constructor( + @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, + @IQuickInputService quickInputService: IQuickInputService, + @IProgressService progressService: IProgressService, + @INotificationService notificationService: INotificationService, + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super('workbench.extensions.actions.installLocalExtensionsInRemote', extensionsWorkbenchService, quickInputService, notificationService, progressService); + } + + get label(): string { + if (this.extensionManagementServerService && this.extensionManagementServerService.remoteExtensionManagementServer) { + return localize('select and install local extensions', "Install Local Extensions in '{0}'...", this.extensionManagementServerService.remoteExtensionManagementServer.label); + } + return ''; + } + + protected getQuickPickTitle(): string { + return localize('install local extensions title', "Install Local Extensions in '{0}'", this.extensionManagementServerService.remoteExtensionManagementServer!.label); + } + + protected getExtensionsToInstall(local: IExtension[]): IExtension[] { + return local.filter(extension => { + const action = this.instantiationService.createInstance(RemoteInstallAction, true); + action.extension = extension; + return action.enabled; + }); + } + + protected async installExtensions(localExtensionsToInstall: IExtension[]): Promise { const galleryExtensions: IGalleryExtension[] = []; const vsixs: URI[] = []; await Promise.all(localExtensionsToInstall.map(async extension => { @@ -3390,15 +3179,54 @@ export class InstallLocalExtensionsInRemoteAction extends Action { await Promise.all(galleryExtensions.map(gallery => this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.installFromGallery(gallery))); await Promise.all(vsixs.map(vsix => this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.install(vsix))); + } +} - this.notificationService.notify({ - severity: Severity.Info, - message: localize('finished installing', "Successfully installed extensions in {0}. Please reload the window to enable them.", this.extensionManagementServerService.remoteExtensionManagementServer!.label), - actions: { - primary: [new Action('realod', localize('reload', "Reload Window"), '', true, - () => this.hostService.reload())] +export class InstallRemoteExtensionsInLocalAction extends AbstractInstallExtensionsInServerAction { + + constructor( + id: string, + @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, + @IQuickInputService quickInputService: IQuickInputService, + @IProgressService progressService: IProgressService, + @INotificationService notificationService: INotificationService, + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + ) { + super(id, extensionsWorkbenchService, quickInputService, notificationService, progressService); + } + + get label(): string { + return localize('select and install remote extensions', "Install Remote Extensions Locally..."); + } + + protected getQuickPickTitle(): string { + return localize('install remote extensions', "Install Remote Extensions Locally"); + } + + protected getExtensionsToInstall(local: IExtension[]): IExtension[] { + return local.filter(extension => + extension.type === ExtensionType.User && extension.server !== this.extensionManagementServerService.localExtensionManagementServer + && !this.extensionsWorkbenchService.installed.some(e => e.server === this.extensionManagementServerService.localExtensionManagementServer && areSameExtensions(e.identifier, extension.identifier))); + } + + protected async installExtensions(extensions: IExtension[]): Promise { + const galleryExtensions: IGalleryExtension[] = []; + const vsixs: URI[] = []; + await Promise.all(extensions.map(async extension => { + if (this.extensionGalleryService.isEnabled()) { + const gallery = await this.extensionGalleryService.getCompatibleExtension(extension.identifier, extension.version); + if (gallery) { + galleryExtensions.push(gallery); + return; + } } - }); + const vsix = await this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.zip(extension.local!); + vsixs.push(vsix); + })); + + await Promise.all(galleryExtensions.map(gallery => this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.installFromGallery(gallery))); + await Promise.all(vsixs.map(vsix => this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.install(vsix))); } } @@ -3428,20 +3256,20 @@ CommandsRegistry.registerCommand('workbench.extensions.action.showExtensionsWith }); export const extensionButtonProminentBackground = registerColor('extensionButton.prominentBackground', { - dark: '#327e36', - light: '#327e36', + dark: buttonBackground, + light: buttonBackground, hc: null }, localize('extensionButtonProminentBackground', "Button background color for actions extension that stand out (e.g. install button).")); export const extensionButtonProminentForeground = registerColor('extensionButton.prominentForeground', { - dark: Color.white, - light: Color.white, + dark: buttonForeground, + light: buttonForeground, hc: null }, localize('extensionButtonProminentForeground', "Button foreground color for actions extension that stand out (e.g. install button).")); export const extensionButtonProminentHoverBackground = registerColor('extensionButton.prominentHoverBackground', { - dark: '#28632b', - light: '#28632b', + dark: buttonHoverBackground, + light: buttonHoverBackground, hc: null }, localize('extensionButtonProminentHoverBackground', "Button background hover color for actions extension that stand out (e.g. install button).")); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsIcons.ts b/src/vs/workbench/contrib/extensions/browser/extensionsIcons.ts new file mode 100644 index 000000000..55fea5ddf --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/extensionsIcons.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 { Codicon } from 'vs/base/common/codicons'; +import { localize } from 'vs/nls'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; + +export const extensionsViewIcon = registerIcon('extensions-view-icon', Codicon.extensions, localize('extensionsViewIcon', 'View icon of the extensions view.')); + +export const manageExtensionIcon = registerIcon('extensions-manage', Codicon.gear, localize('manageExtensionIcon', 'Icon for the \'Manage\' action in the extensions view.')); + +export const clearSearchResultsIcon = registerIcon('extensions-clear-search-results', Codicon.clearAll, localize('clearSearchResultsIcon', 'Icon for the \'Clear Search Result\' action in the extensions view.')); +export const refreshIcon = registerIcon('extensions-refresh', Codicon.refresh, localize('refreshIcon', 'Icon for the \'Refresh\' action in the extensions view.')); +export const filterIcon = registerIcon('extensions-filter', Codicon.filter, localize('filterIcon', 'Icon for the \'Filter\' action in the extensions view.')); + +export const installLocalInRemoteIcon = registerIcon('extensions-install-local-in-remote', Codicon.cloudDownload, localize('installLocalInRemoteIcon', 'Icon for the \'Install Local Extension in Remote\' action in the extensions view.')); +export const installWorkspaceRecommendedIcon = registerIcon('extensions-install-workspace-recommended', Codicon.cloudDownload, localize('installWorkspaceRecommendedIcon', 'Icon for the \'Install Workspace Recommended Extensions\' action in the extensions view.')); +export const configureRecommendedIcon = registerIcon('extensions-configure-recommended', Codicon.pencil, localize('configureRecommendedIcon', 'Icon for the \'Configure Recommended Extensions\' action in the extensions view.')); + +export const syncEnabledIcon = registerIcon('extensions-sync-enabled', Codicon.sync, localize('syncEnabledIcon', 'Icon to indicate that an extension is synced.')); +export const syncIgnoredIcon = registerIcon('extensions-sync-ignored', Codicon.syncIgnored, localize('syncIgnoredIcon', 'Icon to indicate that an extension is ignored when syncing.')); +export const remoteIcon = registerIcon('extensions-remote', Codicon.remote, localize('remoteIcon', 'Icon to indicate that an extension is remote in the extensions view and editor.')); +export const installCountIcon = registerIcon('extensions-install-count', Codicon.cloudDownload, localize('installCountIcon', 'Icon shown along with the install count in the extensions view and editor.')); +export const ratingIcon = registerIcon('extensions-rating', Codicon.star, localize('ratingIcon', 'Icon shown along with the rating in the extensions view and editor.')); + +export const starFullIcon = registerIcon('extensions-star-full', Codicon.starFull, localize('starFullIcon', 'Full star icon used for the rating in the extensions editor.')); +export const starHalfIcon = registerIcon('extensions-star-half', Codicon.starHalf, localize('starHalfIcon', 'Half star icon used for the rating in the extensions editor.')); +export const starEmptyIcon = registerIcon('extensions-star-empty', Codicon.starEmpty, localize('starEmptyIcon', 'Empty star icon used for the rating in the extensions editor.')); + +export const warningIcon = registerIcon('extensions-warning-message', Codicon.warning, localize('warningIcon', 'Icon shown with a warning message in the extensions editor.')); +export const infoIcon = registerIcon('extensions-info-message', Codicon.info, localize('infoIcon', 'Icon shown with an info message in the extensions editor.')); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index f1aede07b..563a5534a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -14,9 +14,9 @@ import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging'; import { Event } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; import { IExtension, ExtensionContainers, ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; -import { UpdateAction, ManageExtensionAction, ReloadAction, MaliciousStatusLabelAction, ExtensionActionViewItem, StatusLabelAction, RemoteInstallAction, SystemDisabledWarningAction, ExtensionToolTipAction, LocalInstallAction, SyncIgnoredIconAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, ExtensionActionWithDropdownActionViewItem } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { UpdateAction, ManageExtensionAction, ReloadAction, MaliciousStatusLabelAction, ExtensionActionViewItem, StatusLabelAction, RemoteInstallAction, SystemDisabledWarningAction, ExtensionToolTipAction, LocalInstallAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, WebInstallAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { Label, RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget, TooltipWidget, ExtensionPackCountWidget as ExtensionPackBadgeWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; +import { Label, RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget, TooltipWidget, ExtensionPackCountWidget as ExtensionPackBadgeWidget, SyncIgnoredWidget } 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 { INotificationService } from 'vs/platform/notification/common/notification'; @@ -26,6 +26,8 @@ import { foreground, listActiveSelectionForeground, listActiveSelectionBackgroun import { WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +export const EXTENSION_LIST_ELEMENT_HEIGHT = 62; + export interface IExtensionsViewState { onFocus: Event; onBlur: Event; @@ -47,7 +49,7 @@ export interface ITemplateData { } export class Delegate implements IListVirtualDelegate { - getHeight() { return 62; } + getHeight() { return EXTENSION_LIST_ELEMENT_HEIGHT; } getTemplateId() { return 'extension'; } } @@ -81,6 +83,7 @@ export class Renderer implements IPagedRenderer { const version = append(header, $('span.version')); const installCount = append(header, $('span.install-count')); const ratings = append(header, $('span.ratings')); + const syncIgnore = append(header, $('span.sync-ignored')); const headerRemoteBadgeWidget = this.instantiationService.createInstance(RemoteBadgeWidget, header, false); const description = append(details, $('.description.ellipsis')); const footer = append(details, $('.footer')); @@ -91,8 +94,8 @@ export class Renderer implements IPagedRenderer { if (action instanceof ActionWithDropDownAction) { return new ExtensionActionWithDropdownActionViewItem(action, { icon: true, label: true, menuActionsOrProvider: { getActions: () => action.menuActions }, menuActionClassNames: (action.class || '').split(' ') }, this.contextMenuService); } - if (action.id === ManageExtensionAction.ID) { - return (action).createActionViewItem(); + if (action instanceof ExtensionDropDownAction) { + return action.createActionViewItem(); } return new ExtensionActionViewItem(null, action, actionOptions); } @@ -103,13 +106,13 @@ export class Renderer implements IPagedRenderer { const reloadAction = this.instantiationService.createInstance(ReloadAction); const actions = [ this.instantiationService.createInstance(StatusLabelAction), - this.instantiationService.createInstance(SyncIgnoredIconAction), this.instantiationService.createInstance(UpdateAction), reloadAction, this.instantiationService.createInstance(InstallDropdownAction), this.instantiationService.createInstance(InstallingLabelAction), this.instantiationService.createInstance(RemoteInstallAction, false), this.instantiationService.createInstance(LocalInstallAction), + this.instantiationService.createInstance(WebInstallAction), this.instantiationService.createInstance(MaliciousStatusLabelAction, false), systemDisabledWarningAction, this.instantiationService.createInstance(ManageExtensionAction) @@ -123,6 +126,7 @@ export class Renderer implements IPagedRenderer { headerRemoteBadgeWidget, tooltipWidget, this.instantiationService.createInstance(Label, version, (e: IExtension) => e.version), + this.instantiationService.createInstance(SyncIgnoredWidget, syncIgnore), this.instantiationService.createInstance(InstallCountWidget, installCount, true), this.instantiationService.createInstance(RatingsWidget, ratings, true) ]; @@ -220,10 +224,14 @@ export class Renderer implements IPagedRenderer { }); } }, this, data.extensionDisposables); + } + disposeElement(extension: IExtension, index: number, data: ITemplateData): void { + data.extensionDisposables = dispose(data.extensionDisposables); } disposeTemplate(data: ITemplateData): void { + data.extensionDisposables = dispose(data.extensionDisposables); data.disposables = dispose(data.disposables); } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 9cd1effb5..afe9ebc8a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -9,7 +9,7 @@ import { timeout, Delayer } from 'vs/base/common/async'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { Event as EventOf, Emitter } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; import { IAction, Action, Separator, SubmenuAction } from 'vs/base/common/actions'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -22,20 +22,20 @@ import { ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, CheckForUpdatesAction, DisableAllAction, EnableAllAction, EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction, SearchCategoryAction, RecentlyPublishedExtensionsAction, ShowInstalledExtensionsAction, ShowOutdatedExtensionsAction, ShowDisabledExtensionsAction, - ShowEnabledExtensionsAction, PredefinedExtensionFilterAction + ShowEnabledExtensionsAction, PredefinedExtensionFilterAction, RefreshExtensionsAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; -import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInFeatureExtensionsView, BuiltInThemesExtensionsView, BuiltInProgrammingLanguageExtensionsView, ServerExtensionsView, DefaultRecommendedExtensionsView, OutdatedExtensionsView, InstalledExtensionsView, SearchBuiltInExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; +import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInFeatureExtensionsView, BuiltInThemesExtensionsView, BuiltInProgrammingLanguageExtensionsView, ServerInstalledExtensionsView, DefaultRecommendedExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import Severity from 'vs/base/common/severity'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewDescriptorService, IAddedViewDescriptorRef } from 'vs/workbench/common/views'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -61,6 +61,9 @@ import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; import { ICommandService } from 'vs/platform/commands/common/commands'; +import { isWeb } from 'vs/base/common/platform'; +import { memoize } from 'vs/base/common/decorators'; +import { filterIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; const DefaultViewsContext = new RawContextKey('defaultExtensionViews', true); const SearchMarketplaceExtensionsContext = new RawContextKey('searchMarketplaceExtensions', false); @@ -69,6 +72,7 @@ const SearchOutdatedExtensionsContext = new RawContextKey('searchOutdat const SearchEnabledExtensionsContext = new RawContextKey('searchEnabledExtensions', false); const SearchDisabledExtensionsContext = new RawContextKey('searchDisabledExtensions', false); const HasInstalledExtensionsContext = new RawContextKey('hasInstalledExtensions', true); +const HasInstalledWebExtensionsContext = new RawContextKey('hasInstalledWebExtensions', false); const BuiltInExtensionsContext = new RawContextKey('builtInExtensions', false); const SearchBuiltInExtensionsContext = new RawContextKey('searchBuiltInExtensions', false); const RecommendedExtensionsContext = new RawContextKey('recommendedExtensions', false); @@ -80,7 +84,8 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio constructor( @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @ILabelService private readonly labelService: ILabelService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IContextKeyService private readonly contextKeyService: IContextKeyService ) { this.container = viewDescriptorService.getViewContainerById(VIEWLET_ID)!; this.registerViews(); @@ -107,20 +112,6 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio private createDefaultExtensionsViewDescriptors(): IViewDescriptor[] { const viewDescriptors: IViewDescriptor[] = []; - /* - * Default popular extensions view - * Separate view for popular extensions required as we need to show popular and recommended sections - * in the default view when there is no search text, and user has no installed extensions. - */ - viewDescriptors.push({ - id: 'workbench.views.extensions.popular', - name: localize('popularExtensions', "Popular"), - ctorDescriptor: new SyncDescriptor(ExtensionsListView), - when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.not('hasInstalledExtensions')), - weight: 60, - order: 1, - }); - /* * Default installed extensions views - Shows all user installed extensions. */ @@ -135,27 +126,70 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio servers.push(this.extensionManagementServerService.remoteExtensionManagementServer); } const getViewName = (viewTitle: string, server: IExtensionManagementServer): string => { - if (servers.length) { - const serverLabel = server === this.extensionManagementServerService.webExtensionManagementServer && !this.extensionManagementServerService.localExtensionManagementServer ? localize('local', "Local") : server.label; - return servers.length > 1 ? `${serverLabel} - ${viewTitle}` : viewTitle; + if (servers.length > 1) { + // In Web, use view title as is for remote server, when web extension server is enabled and no web extensions are installed + if (isWeb && server === this.extensionManagementServerService.remoteExtensionManagementServer && + this.extensionManagementServerService.webExtensionManagementServer && !this.contextKeyService.getContextKeyValue('hasInstalledWebExtensions')) { + return viewTitle; + } + return `${server.label} - ${viewTitle}`; } return viewTitle; }; + let installedWebExtensionsContextChangeEvent = Event.None; + if (this.extensionManagementServerService.webExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { + const interestingContextKeys = new Set(); + interestingContextKeys.add('hasInstalledWebExtensions'); + installedWebExtensionsContextChangeEvent = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(interestingContextKeys)); + } + const serverLabelChangeEvent = Event.any(this.labelService.onDidChangeFormatters, installedWebExtensionsContextChangeEvent); for (const server of servers) { const getInstalledViewName = (): string => getViewName(localize('installed', "Installed"), server); - const onDidChangeServerLabel: EventOf = EventOf.map(this.labelService.onDidChangeFormatters, () => undefined); + const onDidChangeTitle = Event.map(serverLabelChangeEvent, () => getInstalledViewName()); + const id = servers.length > 1 ? `workbench.views.extensions.${server.id}.installed` : `workbench.views.extensions.installed`; + const isWebServer = server === this.extensionManagementServerService.webExtensionManagementServer; + if (!isWebServer) { + /* Empty installed extensions view */ + viewDescriptors.push({ + id: `${id}.empty`, + get name() { return getInstalledViewName(); }, + weight: 100, + order: 1, + when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.not('hasInstalledExtensions')), + /* Empty installed extensions view shall have fixed height */ + ctorDescriptor: new SyncDescriptor(ServerInstalledExtensionsView, [{ server, fixedHeight: true, onDidChangeTitle }]), + /* Empty installed extensions views shall not be allowed to hidden */ + canToggleVisibility: false + }); + } + /* Installed extensions view */ viewDescriptors.push({ - id: servers.length > 1 ? `workbench.views.extensions.${server.id}.installed` : `workbench.views.extensions.installed`, + id, get name() { return getInstalledViewName(); }, - ctorDescriptor: new SyncDescriptor(ServerExtensionsView, [server, EventOf.map(onDidChangeServerLabel, () => getInstalledViewName())]), - when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions')), weight: 100, - order: 2, - /* Installed extensions views shall not be hidden when there are more than one server */ + order: 1, + when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), isWebServer ? ContextKeyExpr.has('hasInstalledWebExtensions') : ContextKeyExpr.has('hasInstalledExtensions')), + ctorDescriptor: new SyncDescriptor(ServerInstalledExtensionsView, [{ server, onDidChangeTitle }]), + /* Installed extensions views shall not be allowed to hidden when there are more than one server */ canToggleVisibility: servers.length === 1 }); } + /* + * Default popular extensions view + * Separate view for popular extensions required as we need to show popular and recommended sections + * in the default view when there is no search text, and user has no installed extensions. + */ + viewDescriptors.push({ + id: 'workbench.views.extensions.popular', + name: localize('popularExtensions', "Popular"), + ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]), + when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.not('hasInstalledExtensions')), + weight: 60, + order: 2, + canToggleVisibility: false + }); + /* * Default recommended extensions view * When user has installed extensions, this is shown along with the views for enabled & disabled extensions @@ -164,7 +198,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push({ id: 'extensions.recommendedList', name: localize('recommendedExtensions', "Recommended"), - ctorDescriptor: new SyncDescriptor(DefaultRecommendedExtensionsView), + ctorDescriptor: new SyncDescriptor(DefaultRecommendedExtensionsView, [{}]), when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.not('config.extensions.showRecommendationsOnlyOnDemand')), weight: 40, order: 3, @@ -180,7 +214,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push({ id: 'workbench.views.extensions.enabled', name: localize('enabledExtensions', "Enabled"), - ctorDescriptor: new SyncDescriptor(EnabledExtensionsView), + ctorDescriptor: new SyncDescriptor(EnabledExtensionsView, [{}]), when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions')), hideByDefault: true, weight: 40, @@ -195,7 +229,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push({ id: 'workbench.views.extensions.disabled', name: localize('disabledExtensions', "Disabled"), - ctorDescriptor: new SyncDescriptor(DisabledExtensionsView), + ctorDescriptor: new SyncDescriptor(DisabledExtensionsView, [{}]), when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions')), hideByDefault: true, weight: 10, @@ -217,7 +251,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push({ id: 'workbench.views.extensions.marketplace', name: localize('marketPlace', "Marketplace"), - ctorDescriptor: new SyncDescriptor(ExtensionsListView), + ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]), when: ContextKeyExpr.and(ContextKeyExpr.has('searchMarketplaceExtensions')), }); @@ -227,7 +261,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push({ id: 'workbench.views.extensions.searchInstalled', name: localize('installed', "Installed"), - ctorDescriptor: new SyncDescriptor(InstalledExtensionsView), + ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]), when: ContextKeyExpr.and(ContextKeyExpr.has('searchInstalledExtensions')), }); @@ -237,7 +271,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push({ id: 'workbench.views.extensions.searchEnabled', name: localize('enabled', "Enabled"), - ctorDescriptor: new SyncDescriptor(EnabledExtensionsView), + ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]), when: ContextKeyExpr.and(ContextKeyExpr.has('searchEnabledExtensions')), }); @@ -247,7 +281,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push({ id: 'workbench.views.extensions.searchDisabled', name: localize('disabled', "Disabled"), - ctorDescriptor: new SyncDescriptor(DisabledExtensionsView), + ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]), when: ContextKeyExpr.and(ContextKeyExpr.has('searchDisabledExtensions')), }); @@ -257,7 +291,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push({ id: 'workbench.views.extensions.searchOutdated', name: localize('outdated', "Outdated"), - ctorDescriptor: new SyncDescriptor(OutdatedExtensionsView), + ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]), when: ContextKeyExpr.and(ContextKeyExpr.has('searchOutdatedExtensions')), }); @@ -267,7 +301,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push({ id: 'workbench.views.extensions.searchBuiltin', name: localize('builtin', "Builtin"), - ctorDescriptor: new SyncDescriptor(SearchBuiltInExtensionsView), + ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]), when: ContextKeyExpr.and(ContextKeyExpr.has('searchBuiltInExtensions')), }); @@ -280,7 +314,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push({ id: 'workbench.views.extensions.workspaceRecommendations', name: localize('workspaceRecommendedExtensions', "Workspace Recommendations"), - ctorDescriptor: new SyncDescriptor(WorkspaceRecommendedExtensionsView), + ctorDescriptor: new SyncDescriptor(WorkspaceRecommendedExtensionsView, [{}]), when: ContextKeyExpr.and(ContextKeyExpr.has('recommendedExtensions'), WorkbenchStateContext.notEqualsTo('empty')), order: 1 }); @@ -288,7 +322,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push({ id: 'workbench.views.extensions.otherRecommendations', name: localize('otherRecommendedExtensions', "Other Recommendations"), - ctorDescriptor: new SyncDescriptor(RecommendedExtensionsView), + ctorDescriptor: new SyncDescriptor(RecommendedExtensionsView, [{}]), when: ContextKeyExpr.has('recommendedExtensions'), order: 2 }); @@ -302,21 +336,21 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push({ id: 'workbench.views.extensions.builtinFeatureExtensions', name: localize('builtinFeatureExtensions', "Features"), - ctorDescriptor: new SyncDescriptor(BuiltInFeatureExtensionsView), + ctorDescriptor: new SyncDescriptor(BuiltInFeatureExtensionsView, [{}]), when: ContextKeyExpr.has('builtInExtensions'), }); viewDescriptors.push({ id: 'workbench.views.extensions.builtinThemeExtensions', name: localize('builtInThemesExtensions', "Themes"), - ctorDescriptor: new SyncDescriptor(BuiltInThemesExtensionsView), + ctorDescriptor: new SyncDescriptor(BuiltInThemesExtensionsView, [{}]), when: ContextKeyExpr.has('builtInExtensions'), }); viewDescriptors.push({ id: 'workbench.views.extensions.builtinProgrammingLanguageExtensions', name: localize('builtinProgrammingLanguageExtensions', "Programming Languages"), - ctorDescriptor: new SyncDescriptor(BuiltInProgrammingLanguageExtensionsView), + ctorDescriptor: new SyncDescriptor(BuiltInProgrammingLanguageExtensionsView, [{}]), when: ContextKeyExpr.has('builtInExtensions'), }); @@ -328,7 +362,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IExtensionsViewPaneContainer { private readonly _onSearchChange: Emitter = this._register(new Emitter()); - private readonly onSearchChange: EventOf = this._onSearchChange.event; + private readonly onSearchChange: Event = this._onSearchChange.event; private defaultViewsContextKey: IContextKey; private searchMarketplaceExtensionsContextKey: IContextKey; private searchInstalledExtensionsContextKey: IContextKey; @@ -336,6 +370,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE private searchEnabledExtensionsContextKey: IContextKey; private searchDisabledExtensionsContextKey: IContextKey; private hasInstalledExtensionsContextKey: IContextKey; + private hasInstalledWebExtensionsContextKey: IContextKey; private builtInExtensionsContextKey: IContextKey; private searchBuiltInExtensionsContextKey: IContextKey; private recommendedExtensionsContextKey: IContextKey; @@ -345,6 +380,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE private searchBox: SuggestEnabledInput | undefined; private readonly searchViewletState: MementoObject; private readonly sortActions: ChangeSortAction[]; + private secondaryActions: IAction[] | undefined = undefined; constructor( @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @@ -352,7 +388,8 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE @IProgressService private readonly progressService: IProgressService, @IInstantiationService instantiationService: IInstantiationService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, - @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @INotificationService private readonly notificationService: INotificationService, @IViewletService private readonly viewletService: IViewletService, @@ -377,22 +414,30 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE this.searchEnabledExtensionsContextKey = SearchEnabledExtensionsContext.bindTo(contextKeyService); this.searchDisabledExtensionsContextKey = SearchDisabledExtensionsContext.bindTo(contextKeyService); this.hasInstalledExtensionsContextKey = HasInstalledExtensionsContext.bindTo(contextKeyService); + this.hasInstalledWebExtensionsContextKey = HasInstalledWebExtensionsContext.bindTo(contextKeyService); this.builtInExtensionsContextKey = BuiltInExtensionsContext.bindTo(contextKeyService); this.searchBuiltInExtensionsContextKey = SearchBuiltInExtensionsContext.bindTo(contextKeyService); this.recommendedExtensionsContextKey = RecommendedExtensionsContext.bindTo(contextKeyService); this._register(this.viewletService.onDidViewletOpen(this.onViewletOpen, this)); - this.searchViewletState = this.getMemento(StorageScope.WORKSPACE); - - this.extensionManagementService.getInstalled().then(result => { - this.hasInstalledExtensionsContextKey.set(result.some(r => !r.isBuiltin)); - }); + this.searchViewletState = this.getMemento(StorageScope.WORKSPACE, StorageTarget.USER); this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(AutoUpdateConfigurationKey)) { + this.secondaryActions = undefined; this.updateTitleArea(); } }, this)); + if (extensionManagementServerService.webExtensionManagementServer) { + this._register(extensionsWorkbenchService.onChange(() => { + // show installed web extensions view only when it is not visible + // Do not hide the view automatically when it is visible + if (!this.hasInstalledWebExtensionsContextKey.get()) { + this.updateInstalledWebExtensionsContext(); + } + })); + } + this.sortActions = [ this._register(this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.install', localize('sort by installs', "Install Count"), this.onSearchChange, 'installs')), this._register(this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.rating', localize('sort by rating', "Rating"), this.onSearchChange, 'rating')), @@ -425,6 +470,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE provideResults: (query: string) => Query.suggestions(query) }, placeholder, 'extensions:searchinput', { placeholderText: placeholder, value: searchValue })); + this.updateInstalledExtensionsContexts(); if (this.searchBox.getValue()) { this.triggerSearch(); } @@ -510,71 +556,80 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE return 400; } + @memoize getActions(): IAction[] { - const filterActions: IAction[] = []; - // Local extensions filters - filterActions.push(...[ - this.instantiationService.createInstance(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, localize('builtin filter', "Built-in")), - this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, localize('installed filter', "Installed")), - this.instantiationService.createInstance(ShowEnabledExtensionsAction, ShowEnabledExtensionsAction.ID, localize('enabled filter', "Enabled")), - this.instantiationService.createInstance(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, localize('disabled filter', "Disabled")), - this.instantiationService.createInstance(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, localize('outdated filter', "Outdated")), - ]); + let filterActions: IAction[] = [ + this._register(this.instantiationService.createInstance(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, localize('builtin filter', "Built-in"))), + this._register(this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, localize('installed filter', "Installed"))), + this._register(this.instantiationService.createInstance(ShowEnabledExtensionsAction, ShowEnabledExtensionsAction.ID, localize('enabled filter', "Enabled"))), + this._register(this.instantiationService.createInstance(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, localize('disabled filter', "Disabled"))), + this._register(this.instantiationService.createInstance(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, localize('outdated filter', "Outdated"))), + ]; if (this.extensionGalleryService.isEnabled()) { - const galleryFilterActions = [ - this.instantiationService.createInstance(PredefinedExtensionFilterAction, 'extensions.filter.featured', localize('featured filter', "Featured"), '@featured'), - this.instantiationService.createInstance(PredefinedExtensionFilterAction, 'extensions.filter.popular', localize('most popular filter', "Most Popular"), '@popular'), - this.instantiationService.createInstance(PredefinedExtensionFilterAction, 'extensions.filter.recommended', localize('most popular recommended', "Recommended"), '@recommended'), - this.instantiationService.createInstance(RecentlyPublishedExtensionsAction, RecentlyPublishedExtensionsAction.ID, localize('recently published filter', "Recently Published")), - new Separator(), - new SubmenuAction('workbench.extensions.action.filterExtensionsByCategory', localize('filter by category', "Category"), EXTENSION_CATEGORIES.map(category => this.instantiationService.createInstance(SearchCategoryAction, `extensions.actions.searchByCategory.${category}`, category, category))), - new Separator(), + filterActions = [ + this._register(this.instantiationService.createInstance(PredefinedExtensionFilterAction, 'extensions.filter.featured', localize('featured filter', "Featured"), '@featured')), + this._register(this.instantiationService.createInstance(PredefinedExtensionFilterAction, 'extensions.filter.popular', localize('most popular filter', "Most Popular"), '@popular')), + this._register(this.instantiationService.createInstance(PredefinedExtensionFilterAction, 'extensions.filter.recommended', localize('most popular recommended', "Recommended"), '@recommended')), + this._register(this.instantiationService.createInstance(RecentlyPublishedExtensionsAction, RecentlyPublishedExtensionsAction.ID, localize('recently published filter', "Recently Published"))), + this._register(new Separator()), + this._register(new SubmenuAction('workbench.extensions.action.filterExtensionsByCategory', localize('filter by category', "Category"), EXTENSION_CATEGORIES.map(category => this.instantiationService.createInstance(SearchCategoryAction, `extensions.actions.searchByCategory.${category}`, category, category)))), + this._register(new Separator()), + ...filterActions, + this._register(new Separator()), + this._register(new SubmenuAction('workbench.extensions.action.sortBy', localize('sorty by', "Sort By"), this.sortActions)), ]; - filterActions.splice(0, 0, ...galleryFilterActions); - filterActions.push(...[ - new Separator(), - new SubmenuAction('workbench.extensions.action.sortBy', localize('sorty by', "Sort By"), this.sortActions), - ]); } return [ - new SubmenuAction('workbench.extensions.action.filterExtensions', localize('filterExtensions', "Filter Extensions..."), filterActions, 'codicon-filter'), - this.instantiationService.createInstance(ClearExtensionsInputAction, ClearExtensionsInputAction.ID, ClearExtensionsInputAction.LABEL, this.onSearchChange, this.searchBox ? this.searchBox.getValue() : ''), + this._register(new SubmenuAction('workbench.extensions.action.filterExtensions', localize('filterExtensions', "Filter Extensions..."), filterActions, ThemeIcon.asClassName(filterIcon))), + this._register(this.instantiationService.createInstance(RefreshExtensionsAction, RefreshExtensionsAction.ID, RefreshExtensionsAction.LABEL)), + this._register(this.instantiationService.createInstance(ClearExtensionsInputAction, ClearExtensionsInputAction.ID, ClearExtensionsInputAction.LABEL, this.onSearchChange, () => this.searchBox!.getValue() || '')), ]; } getSecondaryActions(): IAction[] { - const actions: IAction[] = []; + if (!this.secondaryActions) { + this.secondaryActions = []; + this.secondaryActions.push(this.instantiationService.createInstance(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL)); + if (this.configurationService.getValue(AutoUpdateConfigurationKey)) { + this.secondaryActions.push(this.instantiationService.createInstance(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL)); + } else { + this.secondaryActions.push(this.instantiationService.createInstance(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL, false), this.instantiationService.createInstance(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL)); + } - actions.push(this.instantiationService.createInstance(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL)); - if (this.configurationService.getValue(AutoUpdateConfigurationKey)) { - actions.push(this.instantiationService.createInstance(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL)); - } else { - actions.push(this.instantiationService.createInstance(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL, false), this.instantiationService.createInstance(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL)); + this.secondaryActions.push(new Separator()); + this.secondaryActions.push(this.instantiationService.createInstance(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL, false)); + this.secondaryActions.push(this.instantiationService.createInstance(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL, false)); + + this.secondaryActions.push(new Separator()); + this.secondaryActions.push(this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL)); } - - actions.push(new Separator()); - actions.push(this.instantiationService.createInstance(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL, false)); - actions.push(this.instantiationService.createInstance(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL, false)); - - actions.push(new Separator()); - actions.push(this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL)); - - return actions; + return this.secondaryActions; } - search(value: string, refresh: boolean = false): void { - if (this.searchBox) { - if (this.searchBox.getValue() !== value) { - this.searchBox.setValue(value); - } else if (refresh) { - this.doSearch(); - } + search(value: string): void { + if (this.searchBox && this.searchBox.getValue() !== value) { + this.searchBox.setValue(value); } } + async refresh(): Promise { + await this.updateInstalledExtensionsContexts(); + this.doSearch(true); + } + + private async updateInstalledExtensionsContexts(): Promise { + const result = await this.extensionsWorkbenchService.queryLocal(); + this.hasInstalledExtensionsContextKey.set(result.some(r => !r.isBuiltin)); + this.updateInstalledWebExtensionsContext(); + } + + private updateInstalledWebExtensionsContext(): void { + this.hasInstalledWebExtensionsContextKey.set(!!this.extensionManagementServerService.webExtensionManagementServer && this.extensionsWorkbenchService.installed.some(r => r.server === this.extensionManagementServerService.webExtensionManagementServer)); + } + private triggerSearch(): void { this.searchDelayer.trigger(() => this.doSearch(), this.searchBox && this.searchBox.getValue() ? 500 : 0).then(undefined, err => this.onError(err)); } @@ -601,7 +656,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE super.saveState(); } - private doSearch(): Promise { + private doSearch(refresh?: boolean): Promise { const value = this.normalizedQuery(); const isRecommendedExtensionsQuery = ExtensionsListView.isRecommendedExtensionsQuery(value); this.searchInstalledExtensionsContextKey.set(ExtensionsListView.isInstalledExtensionsQuery(value)); @@ -613,9 +668,10 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE this.recommendedExtensionsContextKey.set(isRecommendedExtensionsQuery); this.searchMarketplaceExtensionsContextKey.set(!!value && !ExtensionsListView.isLocalExtensionsQuery(value) && !isRecommendedExtensionsQuery); this.defaultViewsContextKey.set(!value); + this.updateInstalledWebExtensionsContext(); return this.progress(Promise.all(this.panes.map(view => - (view).show(this.normalizedQuery()) + (view).show(this.normalizedQuery(), refresh) .then(model => this.alertSearchResult(model.length, view.id)) ))).then(() => undefined); } @@ -765,7 +821,7 @@ export class MaliciousExtensionChecker implements IWorkbenchContribution { .filter(e => maliciousSet.has(e.identifier.id)); if (maliciousExtensions.length) { - return Promise.all(maliciousExtensions.map(e => this.extensionsManagementService.uninstall(e, true).then(() => { + return Promise.all(maliciousExtensions.map(e => this.extensionsManagementService.uninstall(e).then(() => { this.notificationService.prompt( Severity.Warning, localize('malicious warning', "We have uninstalled '{0}' which was reported to be problematic.", e.identifier.id), diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 8dc0c1069..932a76b68 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { isPromiseCanceledError, getErrorMessage } from 'vs/base/common/errors'; import { PagedModel, IPagedModel, IPager, DelayedPagedModel } from 'vs/base/common/paging'; @@ -16,11 +16,11 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { append, $ } from 'vs/base/browser/dom'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { Delegate, Renderer, IExtensionsViewState } from 'vs/workbench/contrib/extensions/browser/extensionsList'; -import { IExtension, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { Delegate, Renderer, IExtensionsViewState, EXTENSION_LIST_ELEMENT_HEIGHT } from 'vs/workbench/contrib/extensions/browser/extensionsList'; +import { ExtensionState, IExtension, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; import { IExtensionService, toExtension } from 'vs/workbench/services/extensions/common/extensions'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -44,11 +44,12 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; -import { IMenuService } from 'vs/platform/actions/common/actions'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { configureRecommendedIcon, installLocalInRemoteIcon, installWorkspaceRecommendedIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; // Extensions that are automatically classified as Programming Language extensions, but should be Feature extensions const FORCE_FEATURE_EXTENSIONS = ['vscode.git', 'vscode.search-result']; @@ -74,15 +75,22 @@ class ExtensionsViewState extends Disposable implements IExtensionsViewState { } } -export interface ExtensionsListViewOptions extends IViewletViewOptions { +export interface ExtensionsListViewOptions { server?: IExtensionManagementServer; + fixedHeight?: boolean; + onDidChangeTitle?: Event; } class ExtensionListViewWarning extends Error { } +interface IQueryResult { + readonly model: IPagedModel; + readonly onDidChangeModel?: Event>; + readonly disposables: DisposableStore; +} + export class ExtensionsListView extends ViewPane { - protected readonly server: IExtensionManagementServer | undefined; private bodyTemplate: { messageContainer: HTMLElement; messageSeverityIcon: HTMLElement; @@ -92,9 +100,11 @@ export class ExtensionsListView extends ViewPane { private badge: CountBadge | undefined; private list: WorkbenchPagedList | null = null; private queryRequest: { query: string, request: CancelablePromise> } | null = null; + private queryResult: IQueryResult | undefined; constructor( - options: ExtensionsListViewOptions, + protected readonly options: ExtensionsListViewOptions, + viewletViewOptions: IViewletViewOptions, @INotificationService protected notificationService: INotificationService, @IKeybindingService keybindingService: IKeybindingService, @IContextMenuService contextMenuService: IContextMenuService, @@ -112,12 +122,18 @@ export class ExtensionsListView extends ViewPane { @IProductService protected readonly productService: IProductService, @IContextKeyService contextKeyService: IContextKeyService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IMenuService private readonly menuService: IMenuService, @IOpenerService openerService: IOpenerService, @IPreferencesService private readonly preferencesService: IPreferencesService, + @IStorageService private readonly storageService: IStorageService, ) { - super({ ...(options as IViewPaneOptions), showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); - this.server = options.server; + super({ + ...(viewletViewOptions as IViewPaneOptions), + showActionsAlways: true, + maximumBodySize: options.fixedHeight ? storageService.getNumber(viewletViewOptions.id, StorageScope.GLOBAL, 0) : undefined + }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + if (this.options.onDidChangeTitle) { + this._register(this.options.onDidChangeTitle(title => this.updateTitle(title))); + } } protected renderHeader(container: HTMLElement): void { @@ -161,7 +177,7 @@ export class ExtensionsListView extends ViewPane { const resourceNavigator = this._register(new ListResourceNavigator(this.list, { openOnSingleClick: true })); this._register(Event.debounce(Event.filter(resourceNavigator.onDidOpen, e => e.element !== null), (_, event) => event, 75, true)(options => { - this.openExtension(this.list!.model.get(options.element!), { sideByside: options.sideBySide, ...options.editorOptions }); + this.openExtension(options.element!, { sideByside: options.sideBySide, ...options.editorOptions }); })); this.bodyTemplate = { @@ -182,15 +198,20 @@ export class ExtensionsListView extends ViewPane { } } - async show(query: string): Promise> { + async show(query: string, refresh?: boolean): Promise> { if (this.queryRequest) { - if (this.queryRequest.query === query) { + if (!refresh && this.queryRequest.query === query) { return this.queryRequest.request; } this.queryRequest.request.cancel(); this.queryRequest = null; } + if (this.queryResult) { + this.queryResult.disposables.dispose(); + this.queryResult = undefined; + } + const parsedQuery = Query.parse(query); let options: IQueryOptions = { @@ -204,23 +225,25 @@ export class ExtensionsListView extends ViewPane { case 'publishedDate': options.sortBy = SortBy.PublishedDate; break; } - const successCallback = (model: IPagedModel) => { - this.queryRequest = null; - this.setModel(model); - return model; - }; - - - const errorCallback = (e: any) => { - const model = new PagedModel([]); - if (!isPromiseCanceledError(e)) { - this.queryRequest = null; - this.setModel(model, e); + const request = createCancelablePromise(async token => { + try { + this.queryResult = await this.query(parsedQuery, options, token); + const model = this.queryResult.model; + this.setModel(model); + if (this.queryResult.onDidChangeModel) { + this.queryResult.disposables.add(this.queryResult.onDidChangeModel(model => this.updateModel(model))); + } + return model; + } catch (e) { + const model = new PagedModel([]); + if (!isPromiseCanceledError(e)) { + this.setModel(model, e); + } + return this.list ? this.list.model : model; } - return this.list ? this.list.model : model; - }; + }); - const request = createCancelablePromise(token => this.query(parsedQuery, options, token).then(successCallback).catch(errorCallback)); + request.finally(() => this.queryRequest = null); this.queryRequest = { query, request }; return request; } @@ -251,7 +274,7 @@ export class ExtensionsListView extends ViewPane { getActions: () => actions.slice(0, actions.length - 1) }); } else if (e.element) { - const groups = getContextMenuActions(this.menuService, this.contextKeyService.createScoped(), this.instantiationService, e.element); + const groups = getContextMenuActions(e.element, false, this.instantiationService); groups.forEach(group => group.forEach(extensionAction => { if (extensionAction instanceof ExtensionAction) { extensionAction.extension = e.element!; @@ -269,7 +292,7 @@ export class ExtensionsListView extends ViewPane { } } - private async query(query: Query, options: IQueryOptions, token: CancellationToken): Promise> { + private async query(query: Query, options: IQueryOptions, token: CancellationToken): Promise { const idRegex = /@id:(([a-z0-9A-Z][a-z0-9\-A-Z]*)\.([a-z0-9A-Z][a-z0-9\-A-Z]*))/g; const ids: string[] = []; let idMatch; @@ -278,21 +301,26 @@ export class ExtensionsListView extends ViewPane { ids.push(name); } if (ids.length) { - return this.queryByIds(ids, options, token); + const model = await this.queryByIds(ids, options, token); + return { model, disposables: new DisposableStore() }; } - if (ExtensionsListView.isLocalExtensionsQuery(query.value) || /@builtin/.test(query.value)) { + + if (ExtensionsListView.isLocalExtensionsQuery(query.value)) { return this.queryLocal(query, options); } - return this.queryGallery(query, options, token) - .then(null, e => { - console.warn('Error querying extensions gallery', getErrorMessage(e)); - return Promise.reject(new ExtensionListViewWarning(localize('galleryError', "We cannot connect to the Extensions Marketplace at this time, please try again later."))); - }); + + try { + const model = await this.queryGallery(query, options, token); + return { model, disposables: new DisposableStore() }; + } catch (e) { + console.warn('Error querying extensions gallery', getErrorMessage(e)); + return Promise.reject(new ExtensionListViewWarning(localize('galleryError', "We cannot connect to the Extensions Marketplace at this time, please try again later."))); + } } private async queryByIds(ids: string[], options: IQueryOptions, token: CancellationToken): Promise> { const idsSet: Set = ids.reduce((result, id) => { result.add(id.toLowerCase()); return result; }, new Set()); - const result = (await this.extensionsWorkbenchService.queryLocal(this.server)) + const result = (await this.extensionsWorkbenchService.queryLocal(this.options.server)) .filter(e => idsSet.has(e.identifier.id.toLowerCase())); if (result.length) { @@ -303,32 +331,70 @@ export class ExtensionsListView extends ViewPane { .then(pager => this.getPagedModel(pager)); } - private async queryLocal(query: Query, options: IQueryOptions): Promise> { - let value = query.value; - if (/@builtin/i.test(value)) { - return this.queryBuiltinExtensions(query, options); + private async queryLocal(query: Query, options: IQueryOptions): Promise { + const local = await this.extensionsWorkbenchService.queryLocal(this.options.server); + const runningExtensions = await this.extensionService.getExtensions(); + let { extensions, canIncludeInstalledExtensions } = this.filterLocal(local, runningExtensions, query, options); + const disposables = new DisposableStore(); + const onDidChangeModel = disposables.add(new Emitter>()); + + if (canIncludeInstalledExtensions) { + let isDisposed: boolean = false; + disposables.add(toDisposable(() => isDisposed = true)); + disposables.add(Event.debounce(Event.any( + Event.filter(this.extensionsWorkbenchService.onChange, e => e?.state === ExtensionState.Installed), + this.extensionService.onDidChangeExtensions + ), () => undefined)(async () => { + const local = this.options.server ? this.extensionsWorkbenchService.installed.filter(e => e.server === this.options.server) : this.extensionsWorkbenchService.local; + const runningExtensions = await this.extensionService.getExtensions(); + const { extensions: newExtensions } = this.filterLocal(local, runningExtensions, query, options); + if (!isDisposed) { + const mergedExtensions = this.mergeAddedExtensions(extensions, newExtensions); + if (mergedExtensions) { + extensions = mergedExtensions; + onDidChangeModel.fire(new PagedModel(extensions)); + } + } + })); } - if (/@installed/i.test(value)) { - return this.queryInstalledExtensions(query, options); - } - - if (/@outdated/i.test(value)) { - return this.queryOutdatedExtensions(query, options); - } - - if (/@disabled/i.test(value)) { - return this.queryDisabledExtensions(query, options); - } - - if (/@enabled/i.test(value)) { - return this.queryEnabledExtensions(query, options); - } - - return new PagedModel([]); + return { + model: new PagedModel(extensions), + onDidChangeModel: onDidChangeModel.event, + disposables + }; } - private async queryBuiltinExtensions(query: Query, options: IQueryOptions): Promise> { + private filterLocal(local: IExtension[], runningExtensions: IExtensionDescription[], query: Query, options: IQueryOptions): { extensions: IExtension[], canIncludeInstalledExtensions: boolean } { + let value = query.value; + let extensions: IExtension[] = []; + let canIncludeInstalledExtensions = true; + + if (/@builtin/i.test(value)) { + extensions = this.filterBuiltinExtensions(local, query, options); + canIncludeInstalledExtensions = false; + } + + else if (/@installed/i.test(value)) { + extensions = this.filterInstalledExtensions(local, runningExtensions, query, options); + } + + else if (/@outdated/i.test(value)) { + extensions = this.filterOutdatedExtensions(local, query, options); + } + + else if (/@disabled/i.test(value)) { + extensions = this.filterDisabledExtensions(local, runningExtensions, query, options); + } + + else if (/@enabled/i.test(value)) { + extensions = this.filterEnabledExtensions(local, runningExtensions, query, options); + } + + return { extensions, canIncludeInstalledExtensions }; + } + + private filterBuiltinExtensions(local: IExtension[], query: Query, options: IQueryOptions): IExtension[] { let value = query.value; const showThemesOnly = /@builtin:themes/i.test(value); if (showThemesOnly) { @@ -344,9 +410,8 @@ export class ExtensionsListView extends ViewPane { } value = value.replace(/@builtin/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase(); - let result = await this.extensionsWorkbenchService.queryLocal(this.server); - result = result + let result = local .filter(e => e.isBuiltin && (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1)); const isThemeExtension = (e: IExtension): boolean => { @@ -355,7 +420,7 @@ export class ExtensionsListView extends ViewPane { }; if (showThemesOnly) { const themesExtensions = result.filter(isThemeExtension); - return this.getPagedModel(this.sortExtensions(themesExtensions, options)); + return this.sortExtensions(themesExtensions, options); } const isLangaugeBasicExtension = (e: IExtension): boolean => { @@ -364,7 +429,7 @@ export class ExtensionsListView extends ViewPane { }; if (showBasicsOnly) { const basics = result.filter(isLangaugeBasicExtension); - return this.getPagedModel(this.sortExtensions(basics, options)); + return this.sortExtensions(basics, options); } if (showFeaturesOnly) { const others = result.filter(e => { @@ -373,10 +438,10 @@ export class ExtensionsListView extends ViewPane { && !isThemeExtension(e) && !isLangaugeBasicExtension(e); }); - return this.getPagedModel(this.sortExtensions(others, options)); + return this.sortExtensions(others, options); } - return this.getPagedModel(this.sortExtensions(result, options)); + return this.sortExtensions(result, options); } private parseCategories(value: string): { value: string, categories: string[] } { @@ -391,14 +456,12 @@ export class ExtensionsListView extends ViewPane { return { value, categories }; } - private async queryInstalledExtensions(query: Query, options: IQueryOptions): Promise> { + private filterInstalledExtensions(local: IExtension[], runningExtensions: IExtensionDescription[], query: Query, options: IQueryOptions): IExtension[] { let { value, categories } = this.parseCategories(query.value); value = value.replace(/@installed/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase(); - let result = await this.extensionsWorkbenchService.queryLocal(this.server); - - result = result + let result = local .filter(e => !e.isBuiltin && (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1) && (!categories.length || categories.some(category => (e.local && e.local.manifest.categories || []).some(c => c.toLowerCase() === category)))); @@ -406,7 +469,6 @@ export class ExtensionsListView extends ViewPane { if (options.sortBy !== undefined) { result = this.sortExtensions(result, options); } else { - const runningExtensions = await this.extensionService.getExtensions(); const runningExtensionsById = runningExtensions.reduce((result, e) => { result.set(ExtensionIdentifier.toKey(e.identifier.value), e); return result; }, new Map()); result = result.sort((e1, e2) => { const running1 = runningExtensionsById.get(ExtensionIdentifier.toKey(e1.identifier.id)); @@ -433,56 +495,76 @@ export class ExtensionsListView extends ViewPane { return isE1Running ? -1 : 1; }); } - return this.getPagedModel(result); + return result; } - private async queryOutdatedExtensions(query: Query, options: IQueryOptions): Promise> { + private filterOutdatedExtensions(local: IExtension[], query: Query, options: IQueryOptions): IExtension[] { let { value, categories } = this.parseCategories(query.value); value = value.replace(/@outdated/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase(); - const local = await this.extensionsWorkbenchService.queryLocal(this.server); const result = local .sort((e1, e2) => e1.displayName.localeCompare(e2.displayName)) .filter(extension => extension.outdated && (extension.name.toLowerCase().indexOf(value) > -1 || extension.displayName.toLowerCase().indexOf(value) > -1) && (!categories.length || categories.some(category => !!extension.local && extension.local.manifest.categories!.some(c => c.toLowerCase() === category)))); - return this.getPagedModel(this.sortExtensions(result, options)); + return this.sortExtensions(result, options); } - private async queryDisabledExtensions(query: Query, options: IQueryOptions): Promise> { + private filterDisabledExtensions(local: IExtension[], runningExtensions: IExtensionDescription[], query: Query, options: IQueryOptions): IExtension[] { let { value, categories } = this.parseCategories(query.value); value = value.replace(/@disabled/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase(); - const local = await this.extensionsWorkbenchService.queryLocal(this.server); - const runningExtensions = await this.extensionService.getExtensions(); - const result = local .sort((e1, e2) => e1.displayName.localeCompare(e2.displayName)) .filter(e => runningExtensions.every(r => !areSameExtensions({ id: r.identifier.value, uuid: r.uuid }, e.identifier)) && (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1) && (!categories.length || categories.some(category => (e.local && e.local.manifest.categories || []).some(c => c.toLowerCase() === category)))); - return this.getPagedModel(this.sortExtensions(result, options)); + return this.sortExtensions(result, options); } - private async queryEnabledExtensions(query: Query, options: IQueryOptions): Promise> { + private filterEnabledExtensions(local: IExtension[], runningExtensions: IExtensionDescription[], query: Query, options: IQueryOptions): IExtension[] { let { value, categories } = this.parseCategories(query.value); value = value ? value.replace(/@enabled/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase() : ''; - const local = (await this.extensionsWorkbenchService.queryLocal(this.server)).filter(e => !e.isBuiltin); - const runningExtensions = await this.extensionService.getExtensions(); - + local = local.filter(e => !e.isBuiltin); const result = local .sort((e1, e2) => e1.displayName.localeCompare(e2.displayName)) .filter(e => runningExtensions.some(r => areSameExtensions({ id: r.identifier.value, uuid: r.uuid }, e.identifier)) && (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1) && (!categories.length || categories.some(category => (e.local && e.local.manifest.categories || []).some(c => c.toLowerCase() === category)))); - return this.getPagedModel(this.sortExtensions(result, options)); + return this.sortExtensions(result, options); + } + + private mergeAddedExtensions(extensions: IExtension[], newExtensions: IExtension[]): IExtension[] | undefined { + const oldExtensions = [...extensions]; + const findPreviousExtensionIndex = (from: number): number => { + let index = -1; + const previousExtensionInNew = newExtensions[from]; + if (previousExtensionInNew) { + index = oldExtensions.findIndex(e => areSameExtensions(e.identifier, previousExtensionInNew.identifier)); + if (index === -1) { + return findPreviousExtensionIndex(from - 1); + } + } + return index; + }; + + let hasChanged: boolean = false; + for (let index = 0; index < newExtensions.length; index++) { + const extension = newExtensions[index]; + if (extensions.every(r => !areSameExtensions(r.identifier, extension.identifier))) { + hasChanged = true; + extensions.splice(findPreviousExtensionIndex(index - 1) + 1, 0, extension); + } + } + + return hasChanged ? extensions : undefined; } private async queryGallery(query: Query, options: IQueryOptions, token: CancellationToken): Promise> { @@ -675,7 +757,7 @@ export class ExtensionsListView extends ViewPane { private async getOtherRecommendationsModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise> { const value = query.value.replace(/@recommended/g, '').trim().toLowerCase(); - const local = (await this.extensionsWorkbenchService.queryLocal(this.server)) + const local = (await this.extensionsWorkbenchService.queryLocal(this.options.server)) .map(e => e.identifier.id.toLowerCase()); const workspaceRecommendations = (await this.getWorkspaceRecommendations()) .map(extensionId => extensionId.toLowerCase()); @@ -698,7 +780,7 @@ export class ExtensionsListView extends ViewPane { // Get All types of recommendations, trimmed to show a max of 8 at any given time private async getAllRecommendationsModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise> { - const local = (await this.extensionsWorkbenchService.queryLocal(this.server)).map(e => e.identifier.id.toLowerCase()); + const local = (await this.extensionsWorkbenchService.queryLocal(this.options.server)).map(e => e.identifier.id.toLowerCase()); const allRecommendations = distinct( flatten(await Promise.all([ @@ -727,31 +809,51 @@ export class ExtensionsListView extends ViewPane { if (this.list) { this.list.model = new DelayedPagedModel(model); this.list.scrollTop = 0; - const count = this.count(); + this.updateBody(error); + } + } - if (this.bodyTemplate && this.badge) { + private updateBody(error?: any): void { + const count = this.count(); + if (this.bodyTemplate && this.badge) { - this.bodyTemplate.extensionsList.classList.toggle('hidden', count === 0); - this.bodyTemplate.messageContainer.classList.toggle('hidden', count > 0); - this.badge.setCount(count); + this.bodyTemplate.extensionsList.classList.toggle('hidden', count === 0); + this.bodyTemplate.messageContainer.classList.toggle('hidden', count > 0); + this.badge.setCount(count); - if (count === 0 && this.isBodyVisible()) { - if (error) { - if (error instanceof ExtensionListViewWarning) { - this.bodyTemplate.messageSeverityIcon.className = `codicon ${SeverityIcon.className(Severity.Warning)}`; - this.bodyTemplate.messageBox.textContent = getErrorMessage(error); - } else { - this.bodyTemplate.messageSeverityIcon.className = `codicon ${SeverityIcon.className(Severity.Error)}`; - this.bodyTemplate.messageBox.textContent = localize('error', "Error while loading extensions. {0}", getErrorMessage(error)); - } + if (count === 0 && this.isBodyVisible()) { + if (error) { + if (error instanceof ExtensionListViewWarning) { + this.bodyTemplate.messageSeverityIcon.className = SeverityIcon.className(Severity.Warning); + this.bodyTemplate.messageBox.textContent = getErrorMessage(error); } else { - this.bodyTemplate.messageSeverityIcon.className = ''; - this.bodyTemplate.messageBox.textContent = localize('no extensions found', "No extensions found."); + this.bodyTemplate.messageSeverityIcon.className = SeverityIcon.className(Severity.Error); + this.bodyTemplate.messageBox.textContent = localize('error', "Error while loading extensions. {0}", getErrorMessage(error)); } - alert(this.bodyTemplate.messageBox.textContent); + } else { + this.bodyTemplate.messageSeverityIcon.className = ''; + this.bodyTemplate.messageBox.textContent = localize('no extensions found', "No extensions found."); } + alert(this.bodyTemplate.messageBox.textContent); } } + this.updateSize(); + } + + protected updateSize() { + if (this.options.fixedHeight) { + const length = this.list?.model.length || 0; + this.minimumBodySize = Math.min(length, 3) * EXTENSION_LIST_ELEMENT_HEIGHT; + this.maximumBodySize = length * EXTENSION_LIST_ELEMENT_HEIGHT; + this.storageService.store(this.id, this.maximumBodySize, StorageScope.GLOBAL, StorageTarget.MACHINE); + } + } + + private updateModel(model: IPagedModel) { + if (this.list) { + this.list.model = new DelayedPagedModel(model); + this.updateBody(); + } } private openExtension(extension: IExtension, options: { sideByside?: boolean, preserveFocus?: boolean, pinned?: boolean }): void { @@ -799,6 +901,10 @@ export class ExtensionsListView extends ViewPane { this.queryRequest.request.cancel(); this.queryRequest = null; } + if (this.queryResult) { + this.queryResult.disposables.dispose(); + this.queryResult = undefined; + } this.list = null; } @@ -808,7 +914,8 @@ export class ExtensionsListView extends ViewPane { || this.isEnabledExtensionsQuery(query) || this.isDisabledExtensionsQuery(query) || this.isBuiltInExtensionsQuery(query) - || this.isSearchBuiltInExtensionsQuery(query); + || this.isSearchBuiltInExtensionsQuery(query) + || this.isBuiltInGroupExtensionsQuery(query); } static isSearchBuiltInExtensionsQuery(query: string): boolean { @@ -819,6 +926,10 @@ export class ExtensionsListView extends ViewPane { return /^\s*@builtin$/i.test(query.trim()); } + static isBuiltInGroupExtensionsQuery(query: string): boolean { + return /^\s*@builtin:.+$/i.test(query.trim()); + } + static isInstalledExtensionsQuery(query: string): boolean { return /@installed/i.test(query); } @@ -868,39 +979,7 @@ export class ExtensionsListView extends ViewPane { } } -export class ServerExtensionsView extends ExtensionsListView { - - constructor( - server: IExtensionManagementServer, - onDidChangeTitle: Event, - options: ExtensionsListViewOptions, - @INotificationService notificationService: INotificationService, - @IKeybindingService keybindingService: IKeybindingService, - @IContextMenuService contextMenuService: IContextMenuService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IInstantiationService instantiationService: IInstantiationService, - @IExtensionService extensionService: IExtensionService, - @IExtensionRecommendationsService tipsService: IExtensionRecommendationsService, - @ITelemetryService telemetryService: ITelemetryService, - @IConfigurationService configurationService: IConfigurationService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IExperimentService experimentService: IExperimentService, - @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService, - @IWorkbenchExtensioManagementService extensionManagementService: IWorkbenchExtensioManagementService, - @IProductService productService: IProductService, - @IContextKeyService contextKeyService: IContextKeyService, - @IMenuService menuService: IMenuService, - @IOpenerService openerService: IOpenerService, - @IThemeService themeService: IThemeService, - @IPreferencesService preferencesService: IPreferencesService, - ) { - options.server = server; - super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, tipsService, - telemetryService, configurationService, contextService, experimentService, extensionManagementServerService, extensionManagementService, productService, - contextKeyService, viewDescriptorService, menuService, openerService, preferencesService); - this._register(onDidChangeTitle(title => this.updateTitle(title))); - } +export class ServerInstalledExtensionsView extends ExtensionsListView { async show(query: string): Promise> { query = query ? query : '@installed'; @@ -911,9 +990,9 @@ export class ServerExtensionsView extends ExtensionsListView { } getActions(): IAction[] { - if (this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManagementServerService.localExtensionManagementServer === this.server) { + if (this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManagementServerService.localExtensionManagementServer === this.options.server) { const installLocalExtensionsInRemoteAction = this._register(this.instantiationService.createInstance(InstallLocalExtensionsInRemoteAction)); - installLocalExtensionsInRemoteAction.class = 'codicon codicon-cloud-download'; + installLocalExtensionsInRemoteAction.class = ThemeIcon.asClassName(installLocalInRemoteIcon); return [installLocalExtensionsInRemoteAction]; } return []; @@ -936,28 +1015,6 @@ export class DisabledExtensionsView extends ExtensionsListView { } } -export class OutdatedExtensionsView extends ExtensionsListView { - - async show(query: string): Promise> { - query = query || '@outdated'; - return ExtensionsListView.isOutdatedExtensionsQuery(query) ? super.show(query) : this.showEmptyModel(); - } -} - -export class InstalledExtensionsView extends ExtensionsListView { - - async show(query: string): Promise> { - query = query || '@installed'; - return ExtensionsListView.isInstalledExtensionsQuery(query) ? super.show(query) : this.showEmptyModel(); - } -} - -export class SearchBuiltInExtensionsView extends ExtensionsListView { - async show(query: string): Promise> { - return ExtensionsListView.isSearchBuiltInExtensionsQuery(query) ? super.show(query) : this.showEmptyModel(); - } -} - export class BuiltInFeatureExtensionsView extends ExtensionsListView { async show(query: string): Promise> { return (query && query.trim() !== '@builtin') ? this.showEmptyModel() : super.show('@builtin:features'); @@ -1030,11 +1087,11 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView { getActions(): IAction[] { if (!this.installAllAction) { - this.installAllAction = this._register(new Action('workbench.extensions.action.installWorkspaceRecommendedExtensions', localize('installWorkspaceRecommendedExtensions', "Install Workspace Recommended Extensions"), 'codicon codicon-cloud-download', false, () => this.installWorkspaceRecommendations())); + this.installAllAction = this._register(new Action('workbench.extensions.action.installWorkspaceRecommendedExtensions', localize('installWorkspaceRecommendedExtensions', "Install Workspace Recommended Extensions"), ThemeIcon.asClassName(installWorkspaceRecommendedIcon), false, () => this.installWorkspaceRecommendations())); } const configureWorkspaceFolderAction = this._register(this.instantiationService.createInstance(ConfigureWorkspaceFolderRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL)); - configureWorkspaceFolderAction.class = 'codicon codicon-pencil'; + configureWorkspaceFolderAction.class = ThemeIcon.asClassName(configureRecommendedIcon); return [this.installAllAction, configureWorkspaceFolderAction]; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index 8718d0b9b..55607b038 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/extensionsWidgets'; import { Disposable, toDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; -import { IExtension, IExtensionsWorkbenchService, IExtensionContainer } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IExtension, IExtensionsWorkbenchService, IExtensionContainer, ExtensionState } from 'vs/workbench/contrib/extensions/common/extensions'; import { append, $ } from 'vs/base/browser/dom'; import * as platform from 'vs/base/common/platform'; import { localize } from 'vs/nls'; @@ -13,11 +13,14 @@ import { IExtensionManagementServerService } from 'vs/workbench/services/extensi import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { ILabelService } from 'vs/platform/label/common/label'; import { extensionButtonProminentBackground, extensionButtonProminentForeground, ExtensionToolTipAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; -import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { EXTENSION_BADGE_REMOTE_BACKGROUND, EXTENSION_BADGE_REMOTE_FOREGROUND } from 'vs/workbench/common/theme'; import { Emitter, Event } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { installCountIcon, ratingIcon, remoteIcon, starEmptyIcon, starFullIcon, starHalfIcon, syncIgnoredIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; export abstract class ExtensionWidget extends Disposable implements IExtensionContainer { private _extension: IExtension | null = null; @@ -83,7 +86,7 @@ export class InstallCountWidget extends ExtensionWidget { installLabel = installCount.toLocaleString(platform.locale); } - append(this.container, $('span.codicon.codicon-cloud-download')); + append(this.container, $('span' + ThemeIcon.asCSSSelector(installCountIcon))); const count = append(this.container, $('span.count')); count.textContent = installLabel; } @@ -123,18 +126,18 @@ export class RatingsWidget extends ExtensionWidget { const rating = Math.round(this.extension.rating * 2) / 2; if (this.small) { - append(this.container, $('span.codicon.codicon-star-full')); + append(this.container, $('span' + ThemeIcon.asCSSSelector(starFullIcon))); const count = append(this.container, $('span.count')); count.textContent = String(rating); } else { for (let i = 1; i <= 5; i++) { if (rating >= i) { - append(this.container, $('span.codicon.codicon-star-full')); + append(this.container, $('span' + ThemeIcon.asCSSSelector(starFullIcon))); } else if (rating >= i - 0.5) { - append(this.container, $('span.codicon.codicon-star-half')); + append(this.container, $('span' + ThemeIcon.asCSSSelector(starHalfIcon))); } else { - append(this.container, $('span.codicon.codicon-star-empty')); + append(this.container, $('span' + ThemeIcon.asCSSSelector(starEmptyIcon))); } } } @@ -220,7 +223,7 @@ export class RecommendationWidget extends ExtensionWidget { if (extRecommendations[this.extension.identifier.id.toLowerCase()]) { this.element = append(this.parent, $('div.extension-bookmark')); const recommendation = append(this.element, $('.recommendation')); - append(recommendation, $('span.codicon.codicon-star')); + append(recommendation, $('span' + ThemeIcon.asCSSSelector(ratingIcon))); const applyBookmarkStyle = (theme: IColorTheme) => { const bgColor = theme.getColor(extensionButtonProminentBackground); const fgColor = theme.getColor(extensionButtonProminentForeground); @@ -286,7 +289,7 @@ class RemoteBadge extends Disposable { } private render(): void { - append(this.element, $('span.codicon.codicon-remote')); + append(this.element, $('span' + ThemeIcon.asCSSSelector(remoteIcon))); const applyBadgeStyle = () => { if (!this.element) { @@ -340,3 +343,28 @@ export class ExtensionPackCountWidget extends ExtensionWidget { countBadge.setCount(this.extension.extensionPack.length); } } + +export class SyncIgnoredWidget extends ExtensionWidget { + + private element: HTMLElement; + + constructor( + container: HTMLElement, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IUserDataAutoSyncEnablementService private readonly userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService, + ) { + super(); + this.element = append(container, $('span.extension-sync-ignored' + ThemeIcon.asCSSSelector(syncIgnoredIcon))); + this.element.title = localize('syncingore.label', "This extension is ignored during sync."); + this.element.classList.add(...ThemeIcon.asClassNameArray(syncIgnoredIcon)); + this.element.classList.add('hide'); + this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectedKeys.includes('settingsSync.ignoredExtensions'))(() => this.render())); + this._register(userDataAutoSyncEnablementService.onDidChangeEnablement(() => this.update())); + this.render(); + } + + render(): void { + this.element.classList.toggle('hide', !(this.extension && this.extension.state === ExtensionState.Installed && this.userDataAutoSyncEnablementService.isEnabled() && this.extensionsWorkbenchService.isExtensionIgnoredToSync(this.extension))); + } +} diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 5bbd4d88d..3566ca415 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -8,7 +8,7 @@ import * as semver from 'vs/base/common/semver/semver'; import { Event, Emitter } from 'vs/base/common/event'; import { index, distinct } from 'vs/base/common/arrays'; import { ThrottledDelayer } from 'vs/base/common/async'; -import { isPromiseCanceledError } from 'vs/base/common/errors'; +import { canceled, isPromiseCanceledError } from 'vs/base/common/errors'; import { Disposable } from 'vs/base/common/lifecycle'; import { IPager, mapPager, singlePagePager } from 'vs/base/common/paging'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -28,10 +28,10 @@ import { IURLService, IURLHandler, IOpenURLOptions } from 'vs/platform/url/commo import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ILogService } from 'vs/platform/log/common/log'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { INotificationService } from 'vs/platform/notification/common/notification'; +import { INotificationService, 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 } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IFileService } from 'vs/platform/files/common/files'; import { IExtensionManifest, ExtensionType, IExtension as IPlatformExtension } from 'vs/platform/extensions/common/extensions'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -1073,7 +1073,23 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension for (const extension of extensions) { let dependents = this.getDependentsAfterDisablement(extension, allExtensions, this.local); if (dependents.length) { - return Promise.reject(new Error(this.getDependentsErrorMessage(extension, allExtensions, dependents))); + 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(canceled()) + }); + }); } } } @@ -1139,13 +1155,13 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension private getErrorMessageForDisablingAnExtensionWithDependents(extension: IExtension, dependents: IExtension[]): string { if (dependents.length === 1) { - return nls.localize('singleDependentError', "Cannot disable extension '{0}'. Extension '{1}' depends on this.", extension.displayName, dependents[0].displayName); + return nls.localize('singleDependentError', "Cannot disable '{0}' extension alone. '{1}' extension depends on this. Do you want to disable all these extensions?", extension.displayName, dependents[0].displayName); } if (dependents.length === 2) { - return nls.localize('twoDependentsError', "Cannot disable extension '{0}'. Extensions '{1}' and '{2}' depend on this.", + return nls.localize('twoDependentsError', "Cannot disable '{0}' extension alone. '{1}' and '{2}' extensions depend on this. Do you want to disable all these extensions?", extension.displayName, dependents[0].displayName, dependents[1].displayName); } - return nls.localize('multipleDependentsError', "Cannot disable extension '{0}'. Extensions '{1}', '{2}' and others depend on this.", + return nls.localize('multipleDependentsError', "Cannot disable '{0}' extension alone. '{1}', '{2}' and other extensions depend on this. Do you want to disable all these extensions?", extension.displayName, dependents[0].displayName, dependents[1].displayName); } @@ -1253,7 +1269,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension private set ignoredAutoUpdateExtensions(extensionIds: string[]) { this._ignoredAutoUpdateExtensions = distinct(extensionIds.map(id => id.toLowerCase())); - this.storageService.store('extensions.ignoredAutoUpdateExtension', JSON.stringify(this._ignoredAutoUpdateExtensions), StorageScope.GLOBAL); + this.storageService.store('extensions.ignoredAutoUpdateExtension', JSON.stringify(this._ignoredAutoUpdateExtensions), StorageScope.GLOBAL, StorageTarget.MACHINE); } private ignoreAutoUpdate(identifierWithVersion: ExtensionIdentifierWithVersion): void { diff --git a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts index 0de614759..cab0ed9a7 100644 --- a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts @@ -11,7 +11,7 @@ import { ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService import { IExtensionsViewPaneContainer, IExtensionsWorkbenchService, IExtension } from 'vs/workbench/contrib/extensions/common/extensions'; import { CancellationToken } from 'vs/base/common/cancellation'; import { localize } from 'vs/nls'; -import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage'; +import { StorageScope, IStorageService, StorageTarget } from 'vs/platform/storage/common/storage'; import { ImportantExtensionTip, IProductService } from 'vs/platform/product/common/productService'; import { forEach, IStringDictionary } from 'vs/base/common/collections'; import { ITextModel } from 'vs/editor/common/model'; @@ -26,6 +26,8 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { setImmediate } from 'vs/base/common/platform'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IExtensionRecommendationNotificationService, RecommendationsNotificationResult, RecommendationSource } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; +import { distinct } from 'vs/base/common/arrays'; +import { DisposableStore } from 'vs/base/common/lifecycle'; type FileExtensionSuggestionClassification = { userReaction: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; @@ -33,6 +35,7 @@ type FileExtensionSuggestionClassification = { }; const promptedRecommendationsStorageKey = 'fileBasedRecommendations/promptedRecommendations'; +const promptedFileExtensionsStorageKey = 'fileBasedRecommendations/promptedFileExtensions'; const recommendationsStorageKey = 'extensionsAssistant/recommendations'; const searchMarketplace = localize('searchMarketplace', "Search Marketplace"); const milliSecondsInADay = 1000 * 60 * 60 * 24; @@ -148,8 +151,16 @@ export class FileBasedRecommendations extends ExtensionRecommendations { } private onModelAdded(model: ITextModel): void { + const uri = model.uri; + const supportedSchemes = [Schemas.untitled, Schemas.file, Schemas.vscodeRemote]; + if (!uri || !supportedSchemes.includes(uri.scheme)) { + return; + } + this.promptRecommendationsForModel(model); - this._register(model.onDidChangeLanguage(() => this.promptRecommendationsForModel(model))); + const disposables = new DisposableStore(); + disposables.add(model.onDidChangeLanguage(() => this.promptRecommendationsForModel(model))); + disposables.add(model.onWillDispose(() => disposables.dispose())); } /** @@ -158,13 +169,8 @@ export class FileBasedRecommendations extends ExtensionRecommendations { */ private promptRecommendationsForModel(model: ITextModel): void { const uri = model.uri; - const supportedSchemes = [Schemas.untitled, Schemas.file, Schemas.vscodeRemote]; - if (!uri || !supportedSchemes.includes(uri.scheme)) { - return; - } - const language = model.getLanguageIdentifier().language; - const fileExtension = extname(uri); + const fileExtension = extname(uri).toLowerCase(); if (this.processedLanguages.includes(language) && this.processedFileExtensions.includes(fileExtension)) { return; } @@ -269,7 +275,17 @@ export class FileBasedRecommendations extends ExtensionRecommendations { private addToPromptedRecommendations(exeName: string, extensions: string[]) { const promptedRecommendations = this.getPromptedRecommendations(); promptedRecommendations[exeName] = extensions; - this.storageService.store(promptedRecommendationsStorageKey, JSON.stringify(promptedRecommendations), StorageScope.GLOBAL); + this.storageService.store(promptedRecommendationsStorageKey, JSON.stringify(promptedRecommendations), StorageScope.GLOBAL, StorageTarget.USER); + } + + private getPromptedFileExtensions(): string[] { + return JSON.parse(this.storageService.get(promptedFileExtensionsStorageKey, StorageScope.GLOBAL, '[]')); + } + + private addToPromptedFileExtensions(fileExtension: string) { + const promptedFileExtensions = this.getPromptedFileExtensions(); + promptedFileExtensions.push(fileExtension); + this.storageService.store(promptedFileExtensionsStorageKey, JSON.stringify(distinct(promptedFileExtensions)), StorageScope.GLOBAL, StorageTarget.USER); } private async promptRecommendedExtensionForFileExtension(fileExtension: string, installed: IExtension[]): Promise { @@ -278,6 +294,11 @@ export class FileBasedRecommendations extends ExtensionRecommendations { return; } + const promptedFileExtensions = this.getPromptedFileExtensions(); + if (promptedFileExtensions.includes(fileExtension)) { + return; + } + const text = `ext:${fileExtension}`; const pager = await this.extensionsWorkbenchService.queryGallery({ text, pageSize: 100 }, CancellationToken.None); if (pager.firstPage.length === 0) { @@ -295,6 +316,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations { [{ label: searchMarketplace, run: () => { + this.addToPromptedFileExtensions(fileExtension); this.telemetryService.publicLog2<{ userReaction: string, fileExtension: string }, FileExtensionSuggestionClassification>('fileExtensionSuggestion:popup', { userReaction: 'ok', fileExtension }); this.viewletService.openViewlet('workbench.view.extensions', true) .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) @@ -310,8 +332,8 @@ export class FileBasedRecommendations extends ExtensionRecommendations { this.storageService.store( 'extensionsAssistant/fileExtensionsSuggestionIgnore', JSON.stringify(fileExtensionSuggestionIgnoreList), - StorageScope.GLOBAL - ); + StorageScope.GLOBAL, + StorageTarget.USER); this.telemetryService.publicLog2<{ userReaction: string, fileExtension: string }, FileExtensionSuggestionClassification>('fileExtensionSuggestion:popup', { userReaction: 'neverShowAgain', fileExtension }); } }], @@ -356,7 +378,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations { private storeCachedRecommendations(): void { const storedRecommendations: IStringDictionary = {}; this.fileBasedRecommendations.forEach((value, key) => storedRecommendations[key] = value.recommendedTime); - this.storageService.store(recommendationsStorageKey, JSON.stringify(storedRecommendations), StorageScope.GLOBAL); + this.storageService.store(recommendationsStorageKey, JSON.stringify(storedRecommendations), StorageScope.GLOBAL, StorageTarget.MACHINE); } } diff --git a/src/vs/workbench/contrib/extensions/browser/media/extension.css b/src/vs/workbench/contrib/extensions/browser/media/extension.css index 1911ed1d4..0502ce085 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extension.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extension.css @@ -139,6 +139,15 @@ color: currentColor; } +.extension-list-item > .details > .header-container > .header .sync-ignored { + display: flex; + margin-left: 6px; +} + +.extension-list-item > .details > .header-container > .header .sync-ignored > .codicon { + font-size: 100%; +} + .extension-list-item > .details > .description { padding-right: 11px; } diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css index 340a9ad24..bb1107d06 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css @@ -13,7 +13,10 @@ .monaco-action-bar .action-item > .action-label.extension-action.label, .monaco-action-bar .action-dropdown-item > .action-label.extension-action.label { padding: 0 5px; - outline-offset: 2px; +} + +.monaco-action-bar .action-item .action-label.extension-action.label { + outline-offset: 1px; } .monaco-action-bar .action-item .action-label.extension-action.text, @@ -42,6 +45,7 @@ .monaco-action-bar .action-item.disabled .action-label.extension-action.uninstall:not(.uninstalling), .monaco-action-bar .action-item.disabled .action-label.extension-action.update, .monaco-action-bar .action-item.disabled .action-label.extension-action.theme, +.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 .action-label.extension-action.hide, .monaco-action-bar .action-item.disabled .action-label.extension-action.reload, diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index 2acadd17b..7a08b2edb 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -76,7 +76,6 @@ .extension-editor > .header > .details > .title > .identifier { margin-left: 10px; font-size: 14px; - opacity: 0.6; background: rgba(173, 173, 173, 0.31); padding: 0px 4px; border-radius: 4px; @@ -165,6 +164,10 @@ max-width: 300px; } +.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .extension-action.icon { + padding-top: 1px; +} + .extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .extension-action.label { padding: 1px 6px; } @@ -212,8 +215,7 @@ .extension-editor > .header > .details > .subtext-container > .monaco-action-bar .action-label { margin-top: 4px; margin-left: 4px; - padding-top: 0; - padding-bottom: 2px; + padding-bottom: 1px; } .extension-editor > .header.recommended > .details > .recommendation > .monaco-action-bar .actions-container { @@ -397,7 +399,7 @@ word-break: keep-all; } -.extension-editor > .body > .content table code:not(:empty) { +.extension-editor > .body > .content code:not(:empty) { font-family: var(--monaco-monospace-font); font-size: 90%; background-color: rgba(128, 128, 128, 0.17); diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css b/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css index 91cf3cefd..737320d94 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css @@ -3,6 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +.extension-sync-ignored.hide { + display: none; +} + .extension-ratings { display: inline-block; } @@ -11,7 +15,7 @@ font-size: 80%; } -.extension-ratings > .codicon[class*='codicon-star']:not(:first-child) { +.extension-ratings > .codicon[class*='codicon-extensions-rating']:not(:first-child) { margin-left: 3px; } @@ -24,17 +28,17 @@ } /* TODO @misolori make this a color token */ -.extension-ratings .codicon-star-full, -.extension-ratings .codicon-star-half { +.extension-ratings .codicon-extensions-star-full, +.extension-ratings .codicon-extensions-star-half { color: #FF8E00 !important; } .extension-install-count .codicon, -.extension-action.codicon-gear { +.extension-action.codicon-extensions-manage { color: inherit; } -.extension-ratings .codicon-star-empty { +.extension-ratings .codicon-extensions-star-empty { opacity: .4; } diff --git a/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css b/src/vs/workbench/contrib/extensions/browser/media/runtimeExtensionsEditor.css similarity index 91% rename from src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css rename to src/vs/workbench/contrib/extensions/browser/media/runtimeExtensionsEditor.css index 8403039d7..050ddc80b 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/runtimeExtensionsEditor.css @@ -57,11 +57,6 @@ color: currentColor; } -.monaco-workbench.vs .runtime-extensions-editor .extension > .icon-container > .icon, -.monaco-workbench.vs-dark .runtime-extensions-editor .extension > .icon-container > .icon { - opacity: 0.5; -} - .runtime-extensions-editor .extension > .desc > .header-container { display: flex; overflow: hidden; diff --git a/src/vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller.ts b/src/vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller.ts index 3ce9d79e5..3a8caf10a 100644 --- a/src/vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller.ts +++ b/src/vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller.ts @@ -5,13 +5,14 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { MenuRegistry, MenuId, Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { localize } from 'vs/nls'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ILabelService } from 'vs/platform/label/common/label'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { InstallLocalExtensionsInRemoteAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { InstallLocalExtensionsInRemoteAction, InstallRemoteExtensionsInLocalAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; export class RemoteExtensionsInstaller extends Disposable implements IWorkbenchContribution { @@ -38,6 +39,20 @@ export class RemoteExtensionsInstaller extends Disposable implements IWorkbenchC appendMenuItem(); this._register(labelService.onDidChangeFormatters(e => appendMenuItem())); this._register(toDisposable(() => disposable.dispose())); + + this._register(registerAction2(class InstallRemoteExtensionsInLocalAction2 extends Action2 { + constructor() { + super({ + id: 'workbench.extensions.actions.installLocalExtensionsInRemote', + title: { value: localize('install remote in local', "Install Remote Extensions Locally..."), original: 'Install Remote Extensions Locally...' }, + category: localize({ key: 'remote', comment: ['Remote as in remote machine'] }, "Remote"), + f1: true + }); + } + run(accessor: ServicesAccessor): Promise { + return accessor.get(IInstantiationService).createInstance(InstallRemoteExtensionsInLocalAction, 'workbench.extensions.actions.installLocalExtensionsInRemote').run(); + } + })); } } diff --git a/src/vs/workbench/contrib/extensions/browser/workspaceRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/workspaceRecommendations.ts index 231aa8f4b..013e981bf 100644 --- a/src/vs/workbench/contrib/extensions/browser/workspaceRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/workspaceRecommendations.ts @@ -7,12 +7,12 @@ import { EXTENSION_IDENTIFIER_PATTERN, IExtensionGalleryService } from 'vs/platf import { distinct, flatten } from 'vs/base/common/arrays'; import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IExtensionsConfigContent, ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; +import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { ILogService } from 'vs/platform/log/common/log'; import { CancellationToken } from 'vs/base/common/cancellation'; import { localize } from 'vs/nls'; import { Emitter } from 'vs/base/common/event'; -import { IWorkpsaceExtensionsConfigService } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig'; +import { IExtensionsConfigContent, IWorkpsaceExtensionsConfigService } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig'; export class WorkspaceRecommendations extends ExtensionRecommendations { @@ -51,23 +51,28 @@ export class WorkspaceRecommendations extends ExtensionRecommendations { this.notificationService.warn(`The ${invalidRecommendations.length} extension(s) below, in workspace recommendations have issues:\n${message}`); } + this._recommendations = []; this._ignoredRecommendations = []; for (const extensionsConfig of extensionsConfigs) { - for (const unwantedRecommendation of extensionsConfig.unwantedRecommendations) { - if (invalidRecommendations.indexOf(unwantedRecommendation) === -1) { - this._ignoredRecommendations.push(unwantedRecommendation); + if (extensionsConfig.unwantedRecommendations) { + for (const unwantedRecommendation of extensionsConfig.unwantedRecommendations) { + if (invalidRecommendations.indexOf(unwantedRecommendation) === -1) { + this._ignoredRecommendations.push(unwantedRecommendation); + } } } - for (const extensionId of extensionsConfig.recommendations) { - if (invalidRecommendations.indexOf(extensionId) === -1) { - this._recommendations.push({ - extensionId, - reason: { - reasonId: ExtensionRecommendationReason.Workspace, - reasonText: localize('workspaceRecommendation', "This extension is recommended by users of the current workspace.") - } - }); + if (extensionsConfig.recommendations) { + for (const extensionId of extensionsConfig.recommendations) { + if (invalidRecommendations.indexOf(extensionId) === -1) { + this._recommendations.push({ + extensionId, + reason: { + reasonId: ExtensionRecommendationReason.Workspace, + reasonText: localize('workspaceRecommendation', "This extension is recommended by users of the current workspace.") + } + }); + } } } } @@ -114,12 +119,8 @@ export class WorkspaceRecommendations extends ExtensionRecommendations { } private async onDidChangeExtensionsConfigs(): Promise { - const oldWorkspaceRecommended = this._recommendations; await this.fetch(); - // Suggest only if at least one of the newly added recommendations was not suggested before - if (this._recommendations.some(current => oldWorkspaceRecommended.every(old => current.extensionId !== old.extensionId))) { - this._onDidChangeRecommendations.fire(); - } + this._onDidChangeRecommendations.fire(); } } diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index f4586ae31..3cfe2796c 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -18,7 +18,8 @@ import { IViewPaneContainer } from 'vs/workbench/common/views'; export const VIEWLET_ID = 'workbench.view.extensions'; export interface IExtensionsViewPaneContainer extends IViewPaneContainer { - search(text: string, refresh?: boolean): void; + search(text: string): void; + refresh(): Promise; } export const enum ExtensionState { @@ -96,14 +97,12 @@ export interface IExtensionsWorkbenchService { export const ConfigurationKey = 'extensions'; export const AutoUpdateConfigurationKey = 'extensions.autoUpdate'; export const AutoCheckUpdatesConfigurationKey = 'extensions.autoCheckUpdates'; -export const ShowRecommendationsOnlyOnDemandKey = 'extensions.showRecommendationsOnlyOnDemand'; export const CloseExtensionDetailsOnViewChangeKey = 'extensions.closeExtensionDetailsOnViewChange'; export interface IExtensionsConfiguration { autoUpdate: boolean; autoCheckUpdates: boolean; ignoreRecommendations: boolean; - showRecommendationsOnlyOnDemand: boolean; closeExtensionDetailsOnViewChange: boolean; } @@ -131,10 +130,12 @@ export class ExtensionContainers extends Disposable { for (const container of this.containers) { if (extension && container.extension) { if (areSameExtensions(container.extension.identifier, extension.identifier)) { - if (!container.extension.server || !extension.server || container.extension.server === extension.server) { + if (container.extension.server && extension.server && container.extension.server !== extension.server) { + if (container.updateWhenCounterExtensionChanges) { + container.update(); + } + } else { container.extension = extension; - } else if (container.updateWhenCounterExtensionChanges) { - container.update(); } } } else { diff --git a/src/vs/workbench/contrib/extensions/common/extensionsInput.ts b/src/vs/workbench/contrib/extensions/common/extensionsInput.ts index b1ba1de3a..eef812021 100644 --- a/src/vs/workbench/contrib/extensions/common/extensionsInput.ts +++ b/src/vs/workbench/contrib/extensions/common/extensionsInput.ts @@ -9,6 +9,7 @@ import { localize } from 'vs/nls'; import { EditorInput } from 'vs/workbench/common/editor'; import { IExtension } from 'vs/workbench/contrib/extensions/common/extensions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { join } from 'vs/base/common/path'; export class ExtensionsInput extends EditorInput { @@ -17,7 +18,7 @@ export class ExtensionsInput extends EditorInput { get resource() { return URI.from({ scheme: Schemas.extension, - path: this.extension.identifier.id + path: join(this.extension.identifier.id, 'extension') }); } diff --git a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput.ts b/src/vs/workbench/contrib/extensions/common/runtimeExtensionsInput.ts similarity index 100% rename from src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput.ts rename to src/vs/workbench/contrib/extensions/common/runtimeExtensionsInput.ts diff --git a/src/vs/workbench/contrib/extensions/electron-browser/debugExtensionHostAction.ts b/src/vs/workbench/contrib/extensions/electron-browser/debugExtensionHostAction.ts new file mode 100644 index 000000000..bd3ea4886 --- /dev/null +++ b/src/vs/workbench/contrib/extensions/electron-browser/debugExtensionHostAction.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 * as nls from 'vs/nls'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { Action } from 'vs/base/common/actions'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; +import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { randomPort } from 'vs/base/node/ports'; + +export class DebugExtensionHostAction extends Action { + static readonly ID = 'workbench.extensions.action.debugExtensionHost'; + static readonly LABEL = nls.localize('debugExtensionHost', "Start Debugging Extension Host"); + 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 + ) { + super(DebugExtensionHostAction.ID, DebugExtensionHostAction.LABEL, DebugExtensionHostAction.CSS_CLASS); + } + + async run(): Promise { + + const inspectPort = await this._extensionService.getInspectPort(false); + if (!inspectPort) { + const res = await this._dialogService.confirm({ + type: 'info', + 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), + primaryButton: nls.localize('restart3', "&&Restart"), + secondaryButton: nls.localize('cancel', "&&Cancel") + }); + if (res.confirmed) { + await this._nativeHostService.relaunch({ addArgs: [`--inspect-extensions=${randomPort()}`] }); + } + + return; + } + + return this._debugService.startDebugging(undefined, { + type: 'node', + name: nls.localize('debugExtensionHost.launch.name', "Attach Extension Host"), + request: 'attach', + port: inspectPort + }); + } +} diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts index 34cdcea7e..ff347a59d 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts @@ -16,7 +16,7 @@ import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { randomPort } from 'vs/base/node/ports'; import { IProductService } from 'vs/platform/product/common/productService'; -import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput'; +import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/runtimeExtensionsInput'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-browser/extensionHostProfiler'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -56,9 +56,9 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio this._profileSession = null; this._setState(ProfileSessionState.None); - CommandsRegistry.registerCommand('workbench.action.extensionHostProfilder.stop', () => { + CommandsRegistry.registerCommand('workbench.action.extensionHostProfiler.stop', () => { this.stopProfiling(); - this._editorService.openEditor(RuntimeExtensionsInput.instance, { revealIfOpened: true }); + this._editorService.openEditor(RuntimeExtensionsInput.instance, { revealIfOpened: true, pinned: true }); }); } @@ -86,7 +86,7 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio showProgress: true, ariaLabel: nls.localize('profilingExtensionHost', "Profiling Extension Host"), tooltip: nls.localize('selectAndStartDebug', "Click to stop profiling."), - command: 'workbench.action.extensionHostProfilder.stop' + command: 'workbench.action.extensionHostProfiler.stop' }; const timeStarted = Date.now(); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts index f7fb0e3bd..a472654f0 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts @@ -7,17 +7,18 @@ import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions, CATEGORIES } from 'vs/workbench/common/actions'; +import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { RuntimeExtensionsEditor, ShowRuntimeExtensionsAction, IExtensionHostProfileService, DebugExtensionHostAction, StartExtensionHostProfileAction, StopExtensionHostProfileAction, CONTEXT_PROFILE_SESSION_STATE, SaveExtensionHostProfileAction, CONTEXT_EXTENSION_HOST_PROFILE_RECORDED } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor'; +import { RuntimeExtensionsEditor, IExtensionHostProfileService, StartExtensionHostProfileAction, StopExtensionHostProfileAction, CONTEXT_PROFILE_SESSION_STATE, CONTEXT_EXTENSION_HOST_PROFILE_RECORDED, SaveExtensionHostProfileAction } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor'; +import { DebugExtensionHostAction } from 'vs/workbench/contrib/extensions/electron-browser/debugExtensionHostAction'; import { EditorInput, IEditorInputFactory, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, ActiveEditorContext } from 'vs/workbench/common/editor'; import { ExtensionHostProfileService } from 'vs/workbench/contrib/extensions/electron-browser/extensionProfileService'; -import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput'; +import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/runtimeExtensionsInput'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionsAutoProfiler } from 'vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; @@ -26,6 +27,7 @@ import { ExtensionsLabel } from 'vs/platform/extensionManagement/common/extensio import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { ExtensionRecommendationNotificationServiceChannel } from 'vs/platform/extensionRecommendations/electron-sandbox/extensionRecommendationsIpc'; +import { Codicon } from 'vs/base/common/codicons'; // Singletons registerSingleton(IExtensionHostProfileService, ExtensionHostProfileService, true); @@ -34,16 +36,11 @@ const workbenchRegistry = Registry.as(Workbench workbenchRegistry.registerWorkbenchContribution(ExtensionsAutoProfiler, LifecyclePhase.Eventually); // Running Extensions Editor - -const runtimeExtensionsEditorDescriptor = EditorDescriptor.create( - RuntimeExtensionsEditor, - RuntimeExtensionsEditor.ID, - localize('runtimeExtension', "Running Extensions") +Registry.as(EditorExtensions.Editors).registerEditor( + EditorDescriptor.create(RuntimeExtensionsEditor, RuntimeExtensionsEditor.ID, localize('runtimeExtension', "Running Extensions")), + [new SyncDescriptor(RuntimeExtensionsInput)] ); -Registry.as(EditorExtensions.Editors) - .registerEditor(runtimeExtensionsEditorDescriptor, [new SyncDescriptor(RuntimeExtensionsInput)]); - class RuntimeExtensionsInputFactory implements IEditorInputFactory { canSerialize(editorInput: EditorInput): boolean { return true; @@ -62,8 +59,6 @@ Registry.as(EditorInputExtensions.EditorInputFactor // Global actions const actionRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ShowRuntimeExtensionsAction), 'Show Running Extensions', CATEGORIES.Developer.value); - class ExtensionsContributions implements IWorkbenchContribution { constructor( @@ -72,10 +67,8 @@ class ExtensionsContributions implements IWorkbenchContribution { @ISharedProcessService sharedProcessService: ISharedProcessService, ) { sharedProcessService.registerChannel('IExtensionRecommendationNotificationService', new ExtensionRecommendationNotificationServiceChannel(extensionRecommendationNotificationService)); - if (environmentService.extensionsPath) { - const openExtensionsFolderActionDescriptor = SyncActionDescriptor.from(OpenExtensionsFolderAction); - actionRegistry.registerWorkbenchAction(openExtensionsFolderActionDescriptor, 'Extensions: Open Extensions Folder', ExtensionsLabel); - } + const openExtensionsFolderActionDescriptor = SyncActionDescriptor.from(OpenExtensionsFolderAction); + actionRegistry.registerWorkbenchAction(openExtensionsFolderActionDescriptor, 'Extensions: Open Extensions Folder', ExtensionsLabel); } } @@ -109,9 +102,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: DebugExtensionHostAction.ID, title: DebugExtensionHostAction.LABEL, - icon: { - id: 'codicon/debug-start' - } + icon: Codicon.debugStart }, group: 'navigation', when: ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID) @@ -121,9 +112,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: StartExtensionHostProfileAction.ID, title: StartExtensionHostProfileAction.LABEL, - icon: { - id: 'codicon/circle-filled' - } + icon: Codicon.circleFilled }, group: 'navigation', when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID), CONTEXT_PROFILE_SESSION_STATE.notEqualsTo('running')) @@ -133,9 +122,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: StopExtensionHostProfileAction.ID, title: StopExtensionHostProfileAction.LABEL, - icon: { - id: 'codicon/debug-stop' - } + icon: Codicon.debugStop }, group: 'navigation', when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID), CONTEXT_PROFILE_SESSION_STATE.isEqualTo('running')) @@ -145,9 +132,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: SaveExtensionHostProfileAction.ID, title: SaveExtensionHostProfileAction.LABEL, - icon: { - id: 'codicon/save-all' - }, + icon: Codicon.saveAll, precondition: CONTEXT_EXTENSION_HOST_PROFILE_RECORDED }, group: 'navigation', diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts index 226a0bc8e..d8f182cff 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts @@ -16,10 +16,10 @@ import { IExtensionHostProfileService } from 'vs/workbench/contrib/extensions/el import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { localize } from 'vs/nls'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput'; +import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/runtimeExtensionsInput'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { createSlowExtensionAction } from 'vs/workbench/contrib/extensions/electron-sandbox/extensionsSlowActions'; +import { createSlowExtensionAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions'; import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-browser/extensionHostProfiler'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; @@ -187,7 +187,7 @@ export class ExtensionsAutoProfiler extends Disposable implements IWorkbenchCont ), [{ label: localize('show', 'Show Extensions'), - run: () => this._editorService.openEditor(RuntimeExtensionsInput.instance) + run: () => this._editorService.openEditor(RuntimeExtensionsInput.instance, { pinned: true }) }, action ], diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsSlowActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions.ts similarity index 100% rename from src/vs/workbench/contrib/extensions/electron-sandbox/extensionsSlowActions.ts rename to src/vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions.ts diff --git a/src/vs/workbench/contrib/extensions/electron-browser/reportExtensionIssueAction.ts b/src/vs/workbench/contrib/extensions/electron-browser/reportExtensionIssueAction.ts new file mode 100644 index 000000000..faf606a75 --- /dev/null +++ b/src/vs/workbench/contrib/extensions/electron-browser/reportExtensionIssueAction.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. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { Action } from 'vs/base/common/actions'; +import { IExtension } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IExtensionsStatus, IExtensionHostProfile } from 'vs/workbench/services/extensions/common/extensions'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; +import { ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { URI } from 'vs/base/common/uri'; + +export class ReportExtensionIssueAction extends Action { + + private static readonly _id = 'workbench.extensions.action.reportExtensionIssue'; + private static readonly _label = nls.localize('reportExtensionIssue', "Report Issue"); + + private _url: string | undefined; + + constructor( + private extension: { + description: IExtensionDescription; + marketplaceInfo: IExtension; + status?: IExtensionsStatus; + unresponsiveProfile?: IExtensionHostProfile + }, + @IOpenerService private readonly openerService: IOpenerService, + @IClipboardService private readonly clipboardService: IClipboardService, + @IProductService private readonly productService: IProductService, + @INativeHostService private readonly nativeHostService: INativeHostService + ) { + super(ReportExtensionIssueAction._id, ReportExtensionIssueAction._label, 'extension-action report-issue'); + this.enabled = !!extension.description.repository && !!extension.description.repository.url; + } + + async run(): Promise { + if (!this._url) { + this._url = await this._generateNewIssueUrl(this.extension); + } + this.openerService.open(URI.parse(this._url)); + } + + private async _generateNewIssueUrl(extension: { + description: IExtensionDescription; + marketplaceInfo: IExtension; + status?: IExtensionsStatus; + unresponsiveProfile?: IExtensionHostProfile + }): Promise { + let baseUrl = extension.marketplaceInfo && extension.marketplaceInfo.type === ExtensionType.User && extension.description.repository ? extension.description.repository.url : undefined; + if (!!baseUrl) { + baseUrl = `${baseUrl.indexOf('.git') !== -1 ? baseUrl.substr(0, baseUrl.length - 4) : baseUrl}/issues/new/`; + } else { + baseUrl = this.productService.reportIssueUrl!; + } + + let reason = 'Bug'; + let title = 'Extension issue'; + let message = ':warning: We have written the needed data into your clipboard. Please paste! :warning:'; + this.clipboardService.writeText('```json \n' + JSON.stringify(extension.status, null, '\t') + '\n```'); + + const os = await this.nativeHostService.getOSProperties(); + const osVersion = `${os.type} ${os.arch} ${os.release}`; + const queryStringPrefix = baseUrl.indexOf('?') === -1 ? '?' : '&'; + const body = encodeURIComponent( + `- Issue Type: \`${reason}\` +- Extension Name: \`${extension.description.name}\` +- Extension Version: \`${extension.description.version}\` +- OS Version: \`${osVersion}\` +- VSCode version: \`${this.productService.version}\`\n\n${message}` + ); + + return `${baseUrl}${queryStringPrefix}body=${body}&title=${encodeURIComponent(title)}`; + } +} diff --git a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts index 2a81e43f6..8aba9dd18 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -3,50 +3,28 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/runtimeExtensionsEditor'; import * as nls from 'vs/nls'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { Action, IAction, Separator } from 'vs/base/common/actions'; -import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; +import { Action } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IExtensionsWorkbenchService, IExtension } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IExtensionService, IExtensionsStatus, IExtensionHostProfile } from 'vs/workbench/services/extensions/common/extensions'; -import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; -import { WorkbenchList } from 'vs/platform/list/browser/listService'; -import { append, $, reset, Dimension, clearNode } from 'vs/base/browser/dom'; -import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { RunOnceScheduler } from 'vs/base/common/async'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IExtensionService, IExtensionHostProfile } from 'vs/workbench/services/extensions/common/extensions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; -import { memoize } from 'vs/base/common/decorators'; -import { isNonEmptyArray } from 'vs/base/common/arrays'; import { Event } from 'vs/base/common/event'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput'; -import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { randomPort } from 'vs/base/node/ports'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ILabelService } from 'vs/platform/label/common/label'; -import { renderCodicons } from 'vs/base/browser/codicons'; -import { ExtensionIdentifier, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { Schemas } from 'vs/base/common/network'; -import { SlowExtensionAction } from 'vs/workbench/contrib/extensions/electron-sandbox/extensionsSlowActions'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { SlowExtensionAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { URI } from 'vs/base/common/uri'; -import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; -import { domEvent } from 'vs/base/browser/event'; -import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; -import { IFileService } from 'vs/platform/files/common/files'; +import { ReportExtensionIssueAction } from 'vs/workbench/contrib/extensions/electron-browser/reportExtensionIssueAction'; +import { AbstractRuntimeExtensionsEditor, IRuntimeExtension } from 'vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor'; import { VSBuffer } from 'vs/base/common/buffer'; +import { URI } from 'vs/base/common/uri'; +import { IFileService } from 'vs/platform/files/common/files'; +import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; export const IExtensionHostProfileService = createDecorator('extensionHostProfileService'); export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey('profileSessionState', 'none'); @@ -75,64 +53,31 @@ export interface IExtensionHostProfileService { setUnresponsiveProfile(extensionId: ExtensionIdentifier, profile: IExtensionHostProfile): void; } -interface IExtensionProfileInformation { - /** - * segment when the extension was running. - * 2*i = segment start time - * 2*i+1 = segment end time - */ - segments: number[]; - /** - * total time when the extension was running. - * (sum of all segment lengths). - */ - totalTime: number; -} +export class RuntimeExtensionsEditor extends AbstractRuntimeExtensionsEditor { -interface IRuntimeExtension { - originalIndex: number; - description: IExtensionDescription; - marketplaceInfo: IExtension; - status: IExtensionsStatus; - profileInfo?: IExtensionProfileInformation; - unresponsiveProfile?: IExtensionHostProfile; -} - -export class RuntimeExtensionsEditor extends EditorPane { - - public static readonly ID: string = 'workbench.editor.runtimeExtensions'; - - private _list: WorkbenchList | null; private _profileInfo: IExtensionHostProfile | null; - - private _elements: IRuntimeExtension[] | null; - private _extensionsDescriptions: IExtensionDescription[]; - private _updateSoon: RunOnceScheduler; - private _profileSessionState: IContextKey; private _extensionsHostRecorded: IContextKey; + private _profileSessionState: IContextKey; constructor( @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IContextKeyService contextKeyService: IContextKeyService, - @IExtensionsWorkbenchService private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionService private readonly _extensionService: IExtensionService, - @INotificationService private readonly _notificationService: INotificationService, - @IContextMenuService private readonly _contextMenuService: IContextMenuService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService, + @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionService extensionService: IExtensionService, + @INotificationService notificationService: INotificationService, + @IContextMenuService contextMenuService: IContextMenuService, + @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, - @ILabelService private readonly _labelService: ILabelService, - @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, - @IOpenerService private readonly _openerService: IOpenerService, - @IClipboardService private readonly _clipboardService: IClipboardService, - @IProductService private readonly _productService: IProductService, - @INativeHostService private readonly _nativeHostService: INativeHostService + @ILabelService labelService: ILabelService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService, ) { - super(RuntimeExtensionsEditor.ID, telemetryService, themeService, storageService); - - this._list = null; + super(telemetryService, themeService, contextKeyService, extensionsWorkbenchService, extensionService, notificationService, contextMenuService, instantiationService, storageService, labelService, environmentService); this._profileInfo = this._extensionHostProfileService.lastProfile; + this._extensionsHostRecorded = CONTEXT_EXTENSION_HOST_PROFILE_RECORDED.bindTo(contextKeyService); + this._profileSessionState = CONTEXT_PROFILE_SESSION_STATE.bindTo(contextKeyService); + this._register(this._extensionHostProfileService.onDidChangeLastProfile(() => { this._profileInfo = this._extensionHostProfileService.lastProfile; this._extensionsHostRecorded.set(!!this._profileInfo); @@ -142,486 +87,39 @@ export class RuntimeExtensionsEditor extends EditorPane { const state = this._extensionHostProfileService.state; this._profileSessionState.set(ProfileSessionState[state].toLowerCase()); })); - - this._elements = null; - - this._extensionsDescriptions = []; - this._updateExtensions(); - - this._profileSessionState = CONTEXT_PROFILE_SESSION_STATE.bindTo(contextKeyService); - this._extensionsHostRecorded = CONTEXT_EXTENSION_HOST_PROFILE_RECORDED.bindTo(contextKeyService); - - this._updateSoon = this._register(new RunOnceScheduler(() => this._updateExtensions(), 200)); - - this._extensionService.getExtensions().then((extensions) => { - // We only deal with extensions with source code! - this._extensionsDescriptions = extensions.filter((extension) => { - return Boolean(extension.main) || Boolean(extension.browser); - }); - this._updateExtensions(); - }); - this._register(this._extensionService.onDidChangeExtensionsStatus(() => this._updateSoon.schedule())); } - private async _updateExtensions(): Promise { - this._elements = await this._resolveExtensions(); - if (this._list) { - this._list.splice(0, this._list.length, this._elements); - } + protected _getProfileInfo(): IExtensionHostProfile | null { + return this._profileInfo; } - private async _resolveExtensions(): Promise { - let marketplaceMap: { [id: string]: IExtension; } = Object.create(null); - const marketPlaceExtensions = await this._extensionsWorkbenchService.queryLocal(); - for (let extension of marketPlaceExtensions) { - marketplaceMap[ExtensionIdentifier.toKey(extension.identifier.id)] = extension; - } - - let statusMap = this._extensionService.getExtensionsStatus(); - - // group profile segments by extension - let segments: { [id: string]: number[]; } = Object.create(null); - - if (this._profileInfo) { - let currentStartTime = this._profileInfo.startTime; - for (let i = 0, len = this._profileInfo.deltas.length; i < len; i++) { - const id = this._profileInfo.ids[i]; - const delta = this._profileInfo.deltas[i]; - - let extensionSegments = segments[ExtensionIdentifier.toKey(id)]; - if (!extensionSegments) { - extensionSegments = []; - segments[ExtensionIdentifier.toKey(id)] = extensionSegments; - } - - extensionSegments.push(currentStartTime); - currentStartTime = currentStartTime + delta; - extensionSegments.push(currentStartTime); - } - } - - let result: IRuntimeExtension[] = []; - for (let i = 0, len = this._extensionsDescriptions.length; i < len; i++) { - const extensionDescription = this._extensionsDescriptions[i]; - - let profileInfo: IExtensionProfileInformation | null = null; - if (this._profileInfo) { - let extensionSegments = segments[ExtensionIdentifier.toKey(extensionDescription.identifier)] || []; - let extensionTotalTime = 0; - for (let j = 0, lenJ = extensionSegments.length / 2; j < lenJ; j++) { - const startTime = extensionSegments[2 * j]; - const endTime = extensionSegments[2 * j + 1]; - extensionTotalTime += (endTime - startTime); - } - profileInfo = { - segments: extensionSegments, - totalTime: extensionTotalTime - }; - } - - result[i] = { - originalIndex: i, - description: extensionDescription, - marketplaceInfo: marketplaceMap[ExtensionIdentifier.toKey(extensionDescription.identifier)], - status: statusMap[extensionDescription.identifier.value], - profileInfo: profileInfo || undefined, - unresponsiveProfile: this._extensionHostProfileService.getUnresponsiveProfile(extensionDescription.identifier) - }; - } - - result = result.filter(element => element.status.activationTimes); - - // bubble up extensions that have caused slowness - - const isUnresponsive = (extension: IRuntimeExtension): boolean => - extension.unresponsiveProfile === this._profileInfo; - - const profileTime = (extension: IRuntimeExtension): number => - extension.profileInfo?.totalTime ?? 0; - - const activationTime = (extension: IRuntimeExtension): number => - (extension.status.activationTimes?.codeLoadingTime ?? 0) + - (extension.status.activationTimes?.activateCallTime ?? 0); - - result = result.sort((a, b) => { - if (isUnresponsive(a) || isUnresponsive(b)) { - return +isUnresponsive(b) - +isUnresponsive(a); - } else if (profileTime(a) || profileTime(b)) { - return profileTime(b) - profileTime(a); - } else if (activationTime(a) || activationTime(b)) { - return activationTime(b) - activationTime(a); - } - return a.originalIndex - b.originalIndex; - }); - - return result; + protected _getUnresponsiveProfile(extensionId: ExtensionIdentifier): IExtensionHostProfile | undefined { + return this._extensionHostProfileService.getUnresponsiveProfile(extensionId); } - protected createEditor(parent: HTMLElement): void { - parent.classList.add('runtime-extensions-editor'); - - const TEMPLATE_ID = 'runtimeExtensionElementTemplate'; - - const delegate = new class implements IListVirtualDelegate{ - getHeight(element: IRuntimeExtension): number { - return 62; - } - getTemplateId(element: IRuntimeExtension): string { - return TEMPLATE_ID; - } - }; - - interface IRuntimeExtensionTemplateData { - root: HTMLElement; - element: HTMLElement; - icon: HTMLImageElement; - name: HTMLElement; - version: HTMLElement; - msgContainer: HTMLElement; - actionbar: ActionBar; - activationTime: HTMLElement; - profileTime: HTMLElement; - disposables: IDisposable[]; - elementDisposables: IDisposable[]; + protected _createSlowExtensionAction(element: IRuntimeExtension): Action | null { + if (element.unresponsiveProfile) { + return this._instantiationService.createInstance(SlowExtensionAction, element.description, element.unresponsiveProfile); } - - const renderer: IListRenderer = { - templateId: TEMPLATE_ID, - renderTemplate: (root: HTMLElement): IRuntimeExtensionTemplateData => { - const element = append(root, $('.extension')); - const iconContainer = append(element, $('.icon-container')); - const icon = append(iconContainer, $('img.icon')); - - const desc = append(element, $('div.desc')); - const headerContainer = append(desc, $('.header-container')); - const header = append(headerContainer, $('.header')); - const name = append(header, $('div.name')); - const version = append(header, $('span.version')); - - const msgContainer = append(desc, $('div.msg')); - - const actionbar = new ActionBar(desc, { animated: false }); - actionbar.onDidRun(({ error }) => error && this._notificationService.error(error)); - - - const timeContainer = append(element, $('.time')); - const activationTime = append(timeContainer, $('div.activation-time')); - const profileTime = append(timeContainer, $('div.profile-time')); - - const disposables = [actionbar]; - - return { - root, - element, - icon, - name, - version, - actionbar, - activationTime, - profileTime, - msgContainer, - disposables, - elementDisposables: [], - }; - }, - - renderElement: (element: IRuntimeExtension, index: number, data: IRuntimeExtensionTemplateData): void => { - - data.elementDisposables = dispose(data.elementDisposables); - - data.root.classList.toggle('odd', index % 2 === 1); - - const onError = Event.once(domEvent(data.icon, 'error')); - onError(() => data.icon.src = element.marketplaceInfo.iconUrlFallback, null, data.elementDisposables); - data.icon.src = element.marketplaceInfo.iconUrl; - - if (!data.icon.complete) { - data.icon.style.visibility = 'hidden'; - data.icon.onload = () => data.icon.style.visibility = 'inherit'; - } else { - data.icon.style.visibility = 'inherit'; - } - data.name.textContent = element.marketplaceInfo.displayName; - data.version.textContent = element.description.version; - - const activationTimes = element.status.activationTimes!; - let syncTime = activationTimes.codeLoadingTime + activationTimes.activateCallTime; - data.activationTime.textContent = activationTimes.activationReason.startup ? `Startup Activation: ${syncTime}ms` : `Activation: ${syncTime}ms`; - - data.actionbar.clear(); - if (element.unresponsiveProfile) { - data.actionbar.push(this._instantiationService.createInstance(SlowExtensionAction, element.description, element.unresponsiveProfile), { icon: true, label: true }); - } - if (isNonEmptyArray(element.status.runtimeErrors)) { - data.actionbar.push(new ReportExtensionIssueAction(element, this._openerService, this._clipboardService, this._productService, this._nativeHostService), { icon: true, label: true }); - } - - let title: string; - const activationId = activationTimes.activationReason.extensionId.value; - const activationEvent = activationTimes.activationReason.activationEvent; - if (activationEvent === '*') { - title = nls.localize('starActivation', "Activated by {0} on start-up", activationId); - } else if (/^workspaceContains:/.test(activationEvent)) { - let fileNameOrGlob = activationEvent.substr('workspaceContains:'.length); - if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0) { - title = nls.localize({ - key: 'workspaceContainsGlobActivation', - comment: [ - '{0} will be a glob pattern' - ] - }, "Activated by {1} because a file matching {1} exists in your workspace", fileNameOrGlob, activationId); - } else { - title = nls.localize({ - key: 'workspaceContainsFileActivation', - comment: [ - '{0} will be a file name' - ] - }, "Activated by {1} because file {0} exists in your workspace", fileNameOrGlob, activationId); - } - } else if (/^workspaceContainsTimeout:/.test(activationEvent)) { - const glob = activationEvent.substr('workspaceContainsTimeout:'.length); - title = nls.localize({ - key: 'workspaceContainsTimeout', - comment: [ - '{0} will be a glob pattern' - ] - }, "Activated by {1} because searching for {0} took too long", glob, activationId); - } else if (activationEvent === 'onStartupFinished') { - title = nls.localize({ - key: 'startupFinishedActivation', - comment: [ - 'This refers to an extension. {0} will be an activation event.' - ] - }, "Activated by {0} after start-up finished", activationId); - } else if (/^onLanguage:/.test(activationEvent)) { - let language = activationEvent.substr('onLanguage:'.length); - title = nls.localize('languageActivation', "Activated by {1} because you opened a {0} file", language, activationId); - } else { - title = nls.localize({ - key: 'workspaceGenericActivation', - comment: [ - 'The {0} placeholder will be an activation event, like e.g. \'language:typescript\', \'debug\', etc.' - ] - }, "Activated by {1} on {0}", activationEvent, activationId); - } - data.activationTime.title = title; - - clearNode(data.msgContainer); - - if (this._extensionHostProfileService.getUnresponsiveProfile(element.description.identifier)) { - const el = $('span', undefined, ...renderCodicons(` $(alert) Unresponsive`)); - el.title = nls.localize('unresponsive.title', "Extension has caused the extension host to freeze."); - data.msgContainer.appendChild(el); - } - - if (isNonEmptyArray(element.status.runtimeErrors)) { - const el = $('span', undefined, ...renderCodicons(`$(bug) ${nls.localize('errors', "{0} uncaught errors", element.status.runtimeErrors.length)}`)); - data.msgContainer.appendChild(el); - } - - if (element.status.messages && element.status.messages.length > 0) { - const el = $('span', undefined, ...renderCodicons(`$(alert) ${element.status.messages[0].message}`)); - data.msgContainer.appendChild(el); - } - - if (element.description.extensionLocation.scheme !== Schemas.file) { - const el = $('span', undefined, ...renderCodicons(`$(remote) ${element.description.extensionLocation.authority}`)); - data.msgContainer.appendChild(el); - - const hostLabel = this._labelService.getHostLabel(Schemas.vscodeRemote, this._environmentService.remoteAuthority); - if (hostLabel) { - reset(el, ...renderCodicons(`$(remote) ${hostLabel}`)); - } - } - - if (this._profileInfo && element.profileInfo) { - data.profileTime.textContent = `Profile: ${(element.profileInfo.totalTime / 1000).toFixed(2)}ms`; - } else { - data.profileTime.textContent = ''; - } - - }, - - disposeTemplate: (data: IRuntimeExtensionTemplateData): void => { - data.disposables = dispose(data.disposables); - } - }; - - this._list = >this._instantiationService.createInstance(WorkbenchList, - 'RuntimeExtensions', - parent, delegate, [renderer], { - multipleSelectionSupport: false, - setRowLineHeight: false, - horizontalScrolling: false, - overrideStyles: { - listBackground: editorBackground - }, - accessibilityProvider: new RuntimeExtensionsEditorAccessibilityProvider() - }); - - this._list.splice(0, this._list.length, this._elements || undefined); - - this._list.onContextMenu((e) => { - if (!e.element) { - return; - } - - const actions: IAction[] = []; - - actions.push(new ReportExtensionIssueAction(e.element, this._openerService, this._clipboardService, this._productService, this._nativeHostService)); - actions.push(new Separator()); - - actions.push(new Action('runtimeExtensionsEditor.action.disableWorkspace', nls.localize('disable workspace', "Disable (Workspace)"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo, EnablementState.DisabledWorkspace))); - actions.push(new Action('runtimeExtensionsEditor.action.disable', nls.localize('disable', "Disable"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo, EnablementState.DisabledGlobally))); - actions.push(new Separator()); - - const state = this._extensionHostProfileService.state; - if (state === ProfileSessionState.Running) { - actions.push(this._instantiationService.createInstance(StopExtensionHostProfileAction, StopExtensionHostProfileAction.ID, StopExtensionHostProfileAction.LABEL)); - } else { - actions.push(this._instantiationService.createInstance(StartExtensionHostProfileAction, StartExtensionHostProfileAction.ID, StartExtensionHostProfileAction.LABEL)); - } - actions.push(this.saveExtensionHostProfileAction); - - this._contextMenuService.showContextMenu({ - getAnchor: () => e.anchor, - getActions: () => actions - }); - }); + return null; } - @memoize - private get saveExtensionHostProfileAction(): IAction { + protected _createReportExtensionIssueAction(element: IRuntimeExtension): Action | null { + return this._instantiationService.createInstance(ReportExtensionIssueAction, element); + } + + protected _createSaveExtensionHostProfileAction(): Action | null { return this._instantiationService.createInstance(SaveExtensionHostProfileAction, SaveExtensionHostProfileAction.ID, SaveExtensionHostProfileAction.LABEL); } - public layout(dimension: Dimension): void { - if (this._list) { - this._list.layout(dimension.height); - } - } -} - -export class ShowRuntimeExtensionsAction extends Action { - static readonly ID = 'workbench.action.showRuntimeExtensions'; - static readonly LABEL = nls.localize('showRuntimeExtensions', "Show Running Extensions"); - - constructor( - id: string, label: string, - @IEditorService private readonly _editorService: IEditorService - ) { - super(id, label); - } - - public async run(e?: any): Promise { - await this._editorService.openEditor(RuntimeExtensionsInput.instance, { revealIfOpened: true }); - } -} - -export class ReportExtensionIssueAction extends Action { - - private static readonly _id = 'workbench.extensions.action.reportExtensionIssue'; - private static readonly _label = nls.localize('reportExtensionIssue', "Report Issue"); - - private _url: string | undefined; - - constructor( - private extension: { - description: IExtensionDescription; - marketplaceInfo: IExtension; - status?: IExtensionsStatus; - unresponsiveProfile?: IExtensionHostProfile - }, - @IOpenerService private readonly openerService: IOpenerService, - @IClipboardService private readonly clipboardService: IClipboardService, - @IProductService private readonly productService: IProductService, - @INativeHostService private readonly nativeHostService: INativeHostService - ) { - super(ReportExtensionIssueAction._id, ReportExtensionIssueAction._label, 'extension-action report-issue'); - this.enabled = extension.marketplaceInfo - && extension.marketplaceInfo.type === ExtensionType.User - && !!extension.description.repository && !!extension.description.repository.url; - } - - async run(): Promise { - if (!this._url) { - this._url = await this._generateNewIssueUrl(this.extension); - } - this.openerService.open(URI.parse(this._url)); - } - - private async _generateNewIssueUrl(extension: { - description: IExtensionDescription; - marketplaceInfo: IExtension; - status?: IExtensionsStatus; - unresponsiveProfile?: IExtensionHostProfile - }): Promise { - let baseUrl = extension.marketplaceInfo && extension.marketplaceInfo.type === ExtensionType.User && extension.description.repository ? extension.description.repository.url : undefined; - if (!!baseUrl) { - baseUrl = `${baseUrl.indexOf('.git') !== -1 ? baseUrl.substr(0, baseUrl.length - 4) : baseUrl}/issues/new/`; - } else { - baseUrl = this.productService.reportIssueUrl!; - } - - let reason = 'Bug'; - let title = 'Extension issue'; - let message = ':warning: We have written the needed data into your clipboard. Please paste! :warning:'; - this.clipboardService.writeText('```json \n' + JSON.stringify(extension.status, null, '\t') + '\n```'); - - const os = await this.nativeHostService.getOSProperties(); - const osVersion = `${os.type} ${os.arch} ${os.release}`; - const queryStringPrefix = baseUrl.indexOf('?') === -1 ? '?' : '&'; - const body = encodeURIComponent( - `- Issue Type: \`${reason}\` -- Extension Name: \`${extension.description.name}\` -- Extension Version: \`${extension.description.version}\` -- OS Version: \`${osVersion}\` -- VSCode version: \`${this.productService.version}\`\n\n${message}` + protected _createProfileAction(): Action | null { + const state = this._extensionHostProfileService.state; + const profileAction = ( + state === ProfileSessionState.Running + ? this._instantiationService.createInstance(StopExtensionHostProfileAction, StopExtensionHostProfileAction.ID, StopExtensionHostProfileAction.LABEL) + : this._instantiationService.createInstance(StartExtensionHostProfileAction, StartExtensionHostProfileAction.ID, StartExtensionHostProfileAction.LABEL) ); - - return `${baseUrl}${queryStringPrefix}body=${body}&title=${encodeURIComponent(title)}`; - } -} - -export class DebugExtensionHostAction extends Action { - static readonly ID = 'workbench.extensions.action.debugExtensionHost'; - static readonly LABEL = nls.localize('debugExtensionHost', "Start Debugging Extension Host"); - 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 - ) { - super(DebugExtensionHostAction.ID, DebugExtensionHostAction.LABEL, DebugExtensionHostAction.CSS_CLASS); - } - - async run(): Promise { - - const inspectPort = await this._extensionService.getInspectPort(false); - if (!inspectPort) { - const res = await this._dialogService.confirm({ - type: 'info', - 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), - primaryButton: nls.localize('restart3', "&&Restart"), - secondaryButton: nls.localize('cancel', "&&Cancel") - }); - if (res.confirmed) { - await this._nativeHostService.relaunch({ addArgs: [`--inspect-extensions=${randomPort()}`] }); - } - - return; - } - - return this._debugService.startDebugging(undefined, { - type: 'node', - name: nls.localize('debugExtensionHost.launch.name', "Attach Extension Host"), - request: 'attach', - port: inspectPort - }); + return profileAction; } } @@ -707,7 +205,6 @@ export class SaveExtensionHostProfileAction extends Action { // absolute filenames because we don't want to reveal anything // about users. We also append the `.txt` suffix to make it // easier to attach these files to GH issues - let tmp = profiler.rewriteAbsolutePaths({ profile: dataToWrite as any }, 'piiRemoved'); dataToWrite = tmp.profile; @@ -717,13 +214,3 @@ export class SaveExtensionHostProfileAction extends Action { return this._fileService.writeFile(URI.file(savePath), VSBuffer.fromString(JSON.stringify(profileInfo ? profileInfo.data : {}, null, '\t'))); } } - -class RuntimeExtensionsEditorAccessibilityProvider implements IListAccessibilityProvider { - getWidgetAriaLabel(): string { - return nls.localize('runtimeExtensions', "Runtime Extensions"); - } - - getAriaLabel(element: IRuntimeExtension): string | null { - return element.description.name; - } -} diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsActions.ts index 369d6a1bf..99cd8d052 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsActions.ts @@ -27,20 +27,18 @@ export class OpenExtensionsFolderAction extends Action { } async run(): Promise { - if (this.environmentService.extensionsPath) { - const extensionsHome = URI.file(this.environmentService.extensionsPath); - const file = await this.fileService.resolve(extensionsHome); + const extensionsHome = URI.file(this.environmentService.extensionsPath); + const file = await this.fileService.resolve(extensionsHome); - let itemToShow: URI; - if (file.children && file.children.length > 0) { - itemToShow = file.children[0].resource; - } else { - itemToShow = extensionsHome; - } + let itemToShow: URI; + if (file.children && file.children.length > 0) { + itemToShow = file.children[0].resource; + } else { + itemToShow = extensionsHome; + } - if (itemToShow.scheme === Schemas.file) { - return this.nativeHostService.showItemInFolder(itemToShow.fsPath); - } + if (itemToShow.scheme === Schemas.file) { + return this.nativeHostService.showItemInFolder(itemToShow.fsPath); } } } diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts index 3c38a6761..ed8a92a98 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts @@ -42,7 +42,7 @@ import { INotificationService, Severity, IPromptChoice, IPromptOptions } from 'v import { NativeURLService } from 'vs/platform/url/common/urlService'; import { IExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService'; import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { FileService } from 'vs/platform/files/common/fileService'; @@ -55,7 +55,6 @@ import { ExtensionTipsService } from 'vs/platform/extensionManagement/electron-s import { ExtensionRecommendationsService } from 'vs/workbench/contrib/extensions/browser/extensionRecommendationsService'; import { NoOpWorkspaceTagsService } from 'vs/workbench/contrib/tags/browser/workspaceTagsService'; import { IWorkspaceTagsService } from 'vs/workbench/contrib/tags/common/workspaceTags'; -import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkpsaceExtensionsConfigService, WorkspaceExtensionsConfigService } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig'; @@ -222,7 +221,6 @@ suite('ExtensionRecommendationsService Test', () => { instantiationService.stub(IWorkspaceTagsService, new NoOpWorkspaceTagsService()); instantiationService.stub(IStorageService, new TestStorageService()); instantiationService.stub(ILogService, new NullLogService()); - instantiationService.stub(IStorageKeysSyncRegistryService, new StorageKeysSyncRegistryService()); instantiationService.stub(IProductService, >{ extensionTips: { 'ms-dotnettools.csharp': '{**/*.cs,**/project.json,**/global.json,**/*.csproj,**/*.sln,**/appsettings.json}', @@ -273,7 +271,7 @@ suite('ExtensionRecommendationsService Test', () => { instantiationService.stub(INotificationService, new TestNotificationService2()); - testConfigurationService.setUserConfiguration(ConfigurationKey, { ignoreRecommendations: false, showRecommendationsOnlyOnDemand: false }); + testConfigurationService.setUserConfiguration(ConfigurationKey, { ignoreRecommendations: false }); instantiationService.stub(IModelService, { getModels(): any { return []; }, onModelAdded: onModelAddedEvent.event @@ -306,14 +304,14 @@ suite('ExtensionRecommendationsService Test', () => { }, null, '\t')); const myWorkspace = testWorkspace(URI.from({ scheme: 'file', path: folderDir })); + const fileService = new FileService(new NullLogService()); + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); + instantiationService.stub(IFileService, fileService); workspaceService = new TestContextService(myWorkspace); instantiationService.stub(IWorkspaceContextService, workspaceService); instantiationService.stub(IWorkpsaceExtensionsConfigService, instantiationService.createInstance(WorkspaceExtensionsConfigService)); instantiationService.stub(IExtensionIgnoredRecommendationsService, instantiationService.createInstance(ExtensionIgnoredRecommendationsService)); instantiationService.stub(IExtensionRecommendationNotificationService, instantiationService.createInstance(ExtensionRecommendationNotificationService)); - const fileService = new FileService(new NullLogService()); - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - instantiationService.stub(IFileService, fileService); } function testNoPromptForValidRecommendations(recommendations: string[]) { @@ -383,6 +381,16 @@ suite('ExtensionRecommendationsService Test', () => { return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions); }); + test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if ignoreRecommendations is set', () => { + testConfigurationService.setUserConfiguration(ConfigurationKey, { ignoreRecommendations: true }); + return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => { + testObject = instantiationService.createInstance(ExtensionRecommendationsService); + return testObject.activationPromise.then(() => { + assert.ok(!prompted); + }); + }); + }); + test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if showRecommendationsOnlyOnDemand is set', () => { testConfigurationService.setUserConfiguration(ConfigurationKey, { showRecommendationsOnlyOnDemand: true }); return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => { @@ -394,14 +402,14 @@ suite('ExtensionRecommendationsService Test', () => { }); test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if ignoreRecommendations is set for current workspace', () => { - instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE); + instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE); return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions); }); test('ExtensionRecommendationsService: No Recommendations of globally ignored recommendations', () => { - instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE); - instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', '["ms-dotnettools.csharp", "ms-python.python", "ms-vscode.vscode-typescript-tslint-plugin"]', StorageScope.GLOBAL); - instantiationService.get(IStorageService).store('extensionsAssistant/ignored_recommendations', '["ms-dotnettools.csharp", "mockpublisher2.mockextension2"]', StorageScope.GLOBAL); + instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE); + instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', '["ms-dotnettools.csharp", "ms-python.python", "ms-vscode.vscode-typescript-tslint-plugin"]', StorageScope.GLOBAL, StorageTarget.MACHINE); + instantiationService.get(IStorageService).store('extensionsAssistant/ignored_recommendations', '["ms-dotnettools.csharp", "mockpublisher2.mockextension2"]', StorageScope.GLOBAL, StorageTarget.MACHINE); return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => { testObject = instantiationService.createInstance(ExtensionRecommendationsService); @@ -418,8 +426,8 @@ suite('ExtensionRecommendationsService Test', () => { test('ExtensionRecommendationsService: No Recommendations of workspace ignored recommendations', () => { const ignoredRecommendations = ['ms-dotnettools.csharp', 'mockpublisher2.mockextension2']; // ignore a stored recommendation and a workspace recommendation. const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]'; - instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE); - instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL); + instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE); + instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL, StorageTarget.MACHINE); return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions, ignoredRecommendations).then(() => { testObject = instantiationService.createInstance(ExtensionRecommendationsService); @@ -433,15 +441,15 @@ suite('ExtensionRecommendationsService Test', () => { }); }); - test('ExtensionRecommendationsService: Able to retrieve collection of all ignored recommendations', async () => { + test.skip('ExtensionRecommendationsService: Able to retrieve collection of all ignored recommendations', async () => { const storageService = instantiationService.get(IStorageService); const workspaceIgnoredRecommendations = ['ms-dotnettools.csharp']; // ignore a stored recommendation and a workspace recommendation. const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]'; const globallyIgnoredRecommendations = '["mockpublisher2.mockextension2"]'; // ignore a workspace recommendation. - storageService.store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE); - storageService.store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL); - storageService.store('extensionsAssistant/ignored_recommendations', globallyIgnoredRecommendations, StorageScope.GLOBAL); + storageService.store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE); + storageService.store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL, StorageTarget.MACHINE); + storageService.store('extensionsAssistant/ignored_recommendations', globallyIgnoredRecommendations, StorageScope.GLOBAL, StorageTarget.MACHINE); await setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions, workspaceIgnoredRecommendations); testObject = instantiationService.createInstance(ExtensionRecommendationsService); @@ -458,9 +466,9 @@ suite('ExtensionRecommendationsService Test', () => { const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]'; const globallyIgnoredRecommendations = '["mockpublisher2.mockextension2"]'; // ignore a workspace recommendation. - storageService.store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE); - storageService.store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL); - storageService.store('extensionsAssistant/ignored_recommendations', globallyIgnoredRecommendations, StorageScope.GLOBAL); + storageService.store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE); + storageService.store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL, StorageTarget.MACHINE); + storageService.store('extensionsAssistant/ignored_recommendations', globallyIgnoredRecommendations, StorageScope.GLOBAL, StorageTarget.MACHINE); await setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions); const extensionIgnoredRecommendationsService = instantiationService.get(IExtensionIgnoredRecommendationsService); @@ -492,8 +500,8 @@ suite('ExtensionRecommendationsService Test', () => { const changeHandlerTarget = sinon.spy(); const ignoredExtensionId = 'Some.Extension'; - storageService.store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE); - storageService.store('extensionsAssistant/ignored_recommendations', '["ms-vscode.vscode"]', StorageScope.GLOBAL); + storageService.store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE); + storageService.store('extensionsAssistant/ignored_recommendations', '["ms-vscode.vscode"]', StorageScope.GLOBAL, StorageTarget.MACHINE); await setUpFolderWorkspace('myFolder', []); testObject = instantiationService.createInstance(ExtensionRecommendationsService); @@ -508,7 +516,7 @@ suite('ExtensionRecommendationsService Test', () => { test('ExtensionRecommendationsService: Get file based recommendations from storage (old format)', () => { const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python", "ms-vscode.vscode-typescript-tslint-plugin"]'; - instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL); + instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL, StorageTarget.MACHINE); return setUpFolderWorkspace('myFolder', []).then(() => { testObject = instantiationService.createInstance(ExtensionRecommendationsService); @@ -527,7 +535,7 @@ suite('ExtensionRecommendationsService Test', () => { const now = Date.now(); const tenDaysOld = 10 * milliSecondsInADay; const storedRecommendations = `{"ms-dotnettools.csharp": ${now}, "ms-python.python": ${now}, "ms-vscode.vscode-typescript-tslint-plugin": ${now}, "lukehoban.Go": ${tenDaysOld}}`; - instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL); + instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL, StorageTarget.MACHINE); return setUpFolderWorkspace('myFolder', []).then(() => { testObject = instantiationService.createInstance(ExtensionRecommendationsService); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index c928821f6..40cf65cc2 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -38,12 +38,10 @@ import { ExtensionIdentifier, IExtensionContributions, ExtensionType, IExtension import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ILabelService, IFormatterChangeEvent } from 'vs/platform/label/common/label'; -import { ExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService'; import { IProductService } from 'vs/platform/product/common/productService'; import { Schemas } from 'vs/base/common/network'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { ProgressService } from 'vs/workbench/services/progress/browser/progressService'; -import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test'; import { IExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService'; import { ExtensionTipsService } from 'vs/platform/extensionManagement/electron-sandbox/extensionTipsService'; @@ -78,7 +76,6 @@ async function setupTest() { instantiationService.stub(IWorkspaceContextService, new TestContextService()); instantiationService.stub(IConfigurationService, new TestConfigurationService()); instantiationService.stub(IProgressService, ProgressService); - instantiationService.stub(IStorageKeysSyncRegistryService, new StorageKeysSyncRegistryService()); instantiationService.stub(IProductService, {}); instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); @@ -101,14 +98,18 @@ async function setupTest() { instantiationService.stub(IRemoteAgentService, RemoteAgentService); - instantiationService.stub(IExtensionManagementServerService, new class extends ExtensionManagementServerService { - #localExtensionManagementServer: IExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local', id: 'vscode-local' }; - constructor() { - super(instantiationService.get(ISharedProcessService), instantiationService.get(IRemoteAgentService), instantiationService.get(ILabelService), instantiationService); + const localExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local', id: 'vscode-local' }; + instantiationService.stub(IExtensionManagementServerService, >{ + get localExtensionManagementServer(): IExtensionManagementServer { + return localExtensionManagementServer; + }, + getExtensionManagementServer(extension: IExtension): IExtensionManagementServer | null { + if (extension.location.scheme === Schemas.file) { + return localExtensionManagementServer; + } + throw new Error(`Invalid Extension ${extension.location}`); } - get localExtensionManagementServer(): IExtensionManagementServer { return this.#localExtensionManagementServer; } - set localExtensionManagementServer(server: IExtensionManagementServer) { } - }()); + }); instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); instantiationService.stub(ILabelService, { onDidChangeFormatters: new Emitter().event }); @@ -418,7 +419,7 @@ suite('ExtensionsActions', () => { .then(extensions => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('extension-action icon manage codicon-gear', testObject.class); + assert.equal('extension-action icon manage codicon codicon-extensions-manage', testObject.class); assert.equal('', testObject.tooltip); }); }); @@ -433,7 +434,7 @@ suite('ExtensionsActions', () => { .then(page => { testObject.extension = page.firstPage[0]; assert.ok(!testObject.enabled); - assert.equal('extension-action icon manage codicon-gear hide', testObject.class); + assert.equal('extension-action icon manage codicon codicon-extensions-manage hide', testObject.class); assert.equal('', testObject.tooltip); }); }); @@ -450,7 +451,7 @@ suite('ExtensionsActions', () => { installEvent.fire({ identifier: gallery.identifier, gallery }); assert.ok(!testObject.enabled); - assert.equal('extension-action icon manage codicon-gear hide', testObject.class); + assert.equal('extension-action icon manage codicon codicon-extensions-manage hide', testObject.class); assert.equal('', testObject.tooltip); }); }); @@ -468,7 +469,7 @@ suite('ExtensionsActions', () => { didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }); assert.ok(testObject.enabled); - assert.equal('extension-action icon manage codicon-gear', testObject.class); + assert.equal('extension-action icon manage codicon codicon-extensions-manage', testObject.class); assert.equal('', testObject.tooltip); }); }); @@ -483,7 +484,7 @@ suite('ExtensionsActions', () => { .then(extensions => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('extension-action icon manage codicon-gear', testObject.class); + assert.equal('extension-action icon manage codicon codicon-extensions-manage', testObject.class); assert.equal('', testObject.tooltip); }); }); @@ -500,7 +501,7 @@ suite('ExtensionsActions', () => { uninstallEvent.fire(local.identifier); assert.ok(!testObject.enabled); - assert.equal('extension-action icon manage codicon-gear', testObject.class); + assert.equal('extension-action icon manage codicon codicon-extensions-manage', testObject.class); assert.equal('Uninstalling', testObject.tooltip); }); }); @@ -817,25 +818,19 @@ suite('ExtensionsActions', () => { }); }); - test('Test DisableDropDownAction when there is no extension', () => { - const testObject: ExtensionsActions.DisableDropDownAction = instantiationService.createInstance(ExtensionsActions.DisableDropDownAction, []); - - assert.ok(!testObject.enabled); - }); - - test('Test DisableDropDownAction when extension is installed and enabled', () => { + test('Test DisableGloballyAction when extension is installed and enabled', () => { const local = aLocalExtension('a'); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.DisableDropDownAction = instantiationService.createInstance(ExtensionsActions.DisableDropDownAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]); + const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]); testObject.extension = extensions[0]; assert.ok(testObject.enabled); }); }); - test('Test DisableDropDownAction when extension is installed and disabled globally', () => { + test('Test DisableGloballyAction when extension is installed and disabled globally', () => { const local = aLocalExtension('a'); return instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally) .then(() => { @@ -843,47 +838,32 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.DisableDropDownAction = instantiationService.createInstance(ExtensionsActions.DisableDropDownAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]); + const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]); testObject.extension = extensions[0]; assert.ok(!testObject.enabled); }); }); }); - test('Test DisableDropDownAction when extension is installed and disabled for workspace', () => { - const local = aLocalExtension('a'); - return instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledWorkspace) - .then(() => { - instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); - - return instantiationService.get(IExtensionsWorkbenchService).queryLocal() - .then(extensions => { - const testObject: ExtensionsActions.DisableDropDownAction = instantiationService.createInstance(ExtensionsActions.DisableDropDownAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]); - testObject.extension = extensions[0]; - assert.ok(!testObject.enabled); - }); - }); - }); - - test('Test DisableDropDownAction when extension is uninstalled', () => { + test('Test DisableGloballyAction when extension is uninstalled', () => { const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None) .then(page => { - const testObject: ExtensionsActions.DisableDropDownAction = instantiationService.createInstance(ExtensionsActions.DisableDropDownAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]); + const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]); testObject.extension = page.firstPage[0]; assert.ok(!testObject.enabled); }); }); - test('Test DisableDropDownAction when extension is installing', () => { + test('Test DisableGloballyAction when extension is installing', () => { const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None) .then(page => { - const testObject: ExtensionsActions.DisableDropDownAction = instantiationService.createInstance(ExtensionsActions.DisableDropDownAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]); + const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]); testObject.extension = page.firstPage[0]; instantiationService.createInstance(ExtensionContainers, [testObject]); installEvent.fire({ identifier: gallery.identifier, gallery }); @@ -891,13 +871,13 @@ suite('ExtensionsActions', () => { }); }); - test('Test DisableDropDownAction when extension is uninstalling', () => { + test('Test DisableGloballyAction when extension is uninstalling', () => { const local = aLocalExtension('a'); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.DisableDropDownAction = instantiationService.createInstance(ExtensionsActions.DisableDropDownAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]); + const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]); testObject.extension = extensions[0]; instantiationService.createInstance(ExtensionContainers, [testObject]); uninstallEvent.fire(local.identifier); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index bc816ccb7..cde013f45 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -19,7 +19,7 @@ import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/ex import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { IURLService } from 'vs/platform/url/common/url'; -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { IPager } from 'vs/base/common/paging'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; @@ -36,15 +36,14 @@ import { SinonStub } from 'sinon'; import { IExperimentService, ExperimentState, ExperimentActionType, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; -import { ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionType, IExtension, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; -import { ExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService'; -import { ILabelService } from 'vs/platform/label/common/label'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { IMenuService } from 'vs/platform/actions/common/actions'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { Schemas } from 'vs/base/common/network'; suite('ExtensionsListView Tests', () => { @@ -101,14 +100,18 @@ suite('ExtensionsListView Tests', () => { instantiationService.stub(IContextKeyService, new MockContextKeyService()); instantiationService.stub(IMenuService, new TestMenuService()); - instantiationService.stub(IExtensionManagementServerService, new class extends ExtensionManagementServerService { - #localExtensionManagementServer: IExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local', id: 'vscode-local' }; - constructor() { - super(instantiationService.get(ISharedProcessService), instantiationService.get(IRemoteAgentService), instantiationService.get(ILabelService), instantiationService); + const localExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local', id: 'vscode-local' }; + instantiationService.stub(IExtensionManagementServerService, >{ + get localExtensionManagementServer(): IExtensionManagementServer { + return localExtensionManagementServer; + }, + getExtensionManagementServer(extension: IExtension): IExtensionManagementServer | null { + if (extension.location.scheme === Schemas.file) { + return localExtensionManagementServer; + } + throw new Error(`Invalid Extension ${extension.location}`); } - get localExtensionManagementServer(): IExtensionManagementServer { return this.#localExtensionManagementServer; } - set localExtensionManagementServer(server: IExtensionManagementServer) { } - }()); + }); instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -165,7 +168,8 @@ suite('ExtensionsListView Tests', () => { } }); - instantiationService.stub(IExtensionService, { + instantiationService.stub(IExtensionService, >{ + onDidChangeExtensions: Event.None, getExtensions: (): Promise => { return Promise.resolve([ toExtensionDescription(localEnabledTheme), @@ -180,7 +184,7 @@ suite('ExtensionsListView Tests', () => { await (instantiationService.get(IWorkbenchExtensionEnablementService)).setEnablement([localDisabledLanguage], EnablementState.DisabledGlobally); instantiationService.set(IExtensionsWorkbenchService, instantiationService.createInstance(ExtensionsWorkbenchService)); - testableView = instantiationService.createInstance(ExtensionsListView, {}); + testableView = instantiationService.createInstance(ExtensionsListView, {}, {}); }); teardown(() => { @@ -482,7 +486,7 @@ suite('ExtensionsListView Tests', () => { }]); testableView.dispose(); - testableView = instantiationService.createInstance(ExtensionsListView, {}); + testableView = instantiationService.createInstance(ExtensionsListView, {}, {}); return testableView.show('search-me').then(result => { const options: IQueryOptions = queryTarget.args[0][0]; @@ -509,7 +513,7 @@ suite('ExtensionsListView Tests', () => { const queryTarget = instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...realResults)); testableView.dispose(); - testableView = instantiationService.createInstance(ExtensionsListView, {}); + testableView = instantiationService.createInstance(ExtensionsListView, {}, {}); return testableView.show('search-me @sort:installs').then(result => { const options: IQueryOptions = queryTarget.args[0][0]; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index dc1e3e167..f4f5d8ee5 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -39,7 +39,6 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; -import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { IProductService } from 'vs/platform/product/common/productService'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices'; @@ -68,7 +67,6 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(ILogService, NullLogService); instantiationService.stub(IProgressService, ProgressService); - instantiationService.stub(IStorageKeysSyncRegistryService, new StorageKeysSyncRegistryService()); instantiationService.stub(IProductService, {}); instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); @@ -224,8 +222,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { assert.equal('1.1.0', actual.version); assert.equal('1.1.0', actual.latestVersion); assert.equal('localDescription1', actual.description); - assert.equal('file:///localPath1/localIcon1', actual.iconUrl); - assert.equal('file:///localPath1/localIcon1', actual.iconUrlFallback); + assert.ok(actual.iconUrl === 'file:///localPath1/localIcon1' || actual.iconUrl === 'vscode-file://vscode-app/localPath1/localIcon1'); + assert.ok(actual.iconUrlFallback === 'file:///localPath1/localIcon1' || actual.iconUrlFallback === 'vscode-file://vscode-app/localPath1/localIcon1'); assert.equal(null, actual.licenseUrl); assert.equal(ExtensionState.Installed, actual.state); assert.equal(null, actual.installCount); @@ -687,6 +685,11 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionB = aLocalExtension('b'); const extensionC = aLocalExtension('c'); + instantiationService.stub(INotificationService, >{ + prompt(severity, message, choices, options) { + options!.onCancel!(); + } + }); return instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionA], EnablementState.EnabledGlobally) .then(() => instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionB], EnablementState.EnabledGlobally)) .then(() => instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionC], EnablementState.EnabledGlobally)) @@ -697,6 +700,28 @@ suite('ExtensionsWorkbenchServiceTest', () => { }); }); + test('test disable extension disables all dependents when chosen to disable all', async () => { + const extensionA = aLocalExtension('a', { extensionDependencies: ['pub.b'] }); + const extensionB = aLocalExtension('b'); + const extensionC = aLocalExtension('c'); + + instantiationService.stub(INotificationService, >{ + prompt(severity, message, choices, options) { + choices[0].run(); + } + }); + return instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionA], EnablementState.EnabledGlobally) + .then(() => instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionB], EnablementState.EnabledGlobally)) + .then(() => instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionC], EnablementState.EnabledGlobally)) + .then(async () => { + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extensionA, extensionB, extensionC]); + testObject = await aWorkbenchService(); + await testObject.setEnablement(testObject.local[1], EnablementState.DisabledGlobally); + assert.equal(testObject.local[0].enablementState, EnablementState.DisabledGlobally); + assert.equal(testObject.local[1].enablementState, EnablementState.DisabledGlobally); + }); + }); + test('test disable extension when extension is part of a pack', async () => { const extensionA = aLocalExtension('a', { extensionPack: ['pub.b'] }); const extensionB = aLocalExtension('b'); @@ -802,6 +827,11 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionB = aLocalExtension('b', { extensionDependencies: ['pub.a'] }); const extensionC = aLocalExtension('c'); + instantiationService.stub(INotificationService, >{ + prompt(severity, message, choices, options) { + options!.onCancel!(); + } + }); return instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionA], EnablementState.EnabledGlobally) .then(() => instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionB], EnablementState.EnabledGlobally)) .then(() => instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionC], EnablementState.EnabledGlobally)) @@ -837,6 +867,11 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionB = aLocalExtension('b', { extensionDependencies: ['pub.c'] }); const extensionC = aLocalExtension('c', { extensionDependencies: ['pub.a'] }); + instantiationService.stub(INotificationService, >{ + prompt(severity, message, choices, options) { + options!.onCancel!(); + } + }); return instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionA], EnablementState.EnabledGlobally) .then(() => instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionB], EnablementState.EnabledGlobally)) .then(() => instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionC], EnablementState.EnabledGlobally)) diff --git a/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts b/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts index 7b38b373b..148401905 100644 --- a/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts +++ b/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts @@ -12,14 +12,13 @@ import { ITerminalService as IIntegratedTerminalService } from 'vs/workbench/con import { ResourceContextKey } from 'vs/workbench/common/resources'; import { IFileService } from 'vs/platform/files/common/files'; import { IListService } from 'vs/platform/list/browser/listService'; -import { getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; +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 { optional } from 'vs/platform/instantiation/common/instantiation'; -import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { Disposable } from 'vs/base/common/lifecycle'; diff --git a/src/vs/workbench/contrib/externalTerminal/node/TerminalHelper.scpt b/src/vs/workbench/contrib/externalTerminal/node/TerminalHelper.scpt index 145345e002542006dddfcc4871306c35a7679093..a151118b142180cb73e20254576ced1e95fb4c25 100644 GIT binary patch delta 5841 zcmbtYcYIVu*FMh#NVu0tK?sB-WC_JU5E2Cuup^-8Yw%OV3T`$F22yZ$Ary5{!~%-t zVn-AZ6ciOfQ4tlpARzWG_TGCt^PRa9h%dkQ`~G zGun1lBv1ZF+VMC+<9HHDh~%*tgA*}UV+^T;$k~;XaI(fpR-(1cN8uE6tofJ5DU@+k zsC|?<2B(^%J#!3o?iXuJ#A_Opv4*L2btW)Hdhn@)`cTsdoR*E#2y1Y99!?kg=4k85 zv9|jck;ch{MhgF#QN9@!bSo@OpGcKd9!Z@UkHqVzq-I2tu|(~ZRHU>t5=+FRsdzFH zjWooYBB^+yzP2GM@e*ZYthF*O598=WicpHqq_}*>sh+0DY=(@J6r%*yhVuxO%3ma0 ziBnW0;_^Hqqez+>WYQ!PyO6xPhn&gaRV@BOUMiP`clBgZ3DLsx(iqtkFuP7l z9aJ$3RXh)Ix<{h4(Ivx3Qy~E9h~RJ0iVHf8@~}o`H8QIc5FCz;vSJaoxV2#IV5%-B3~Q!x@Jmtt zA@w0IMfFMMaNitGnH7`~tFIwyg9gelhoR9N>Y2l+l{a}pt*C2s@~2{)Im9=I2#*WW zZOoxIqm!u*^@zl$$754dk`AGDtg*g6Dk-gP2&ezfV>y&-FeMLD=tuz|PSd5uXljUH z3)VI>_3{^O&W{DEE>t$0Onu1S?PpD4Gp6QsOG2NAr7^Yw*W@*f{I$9$p zsGn_yn86xnlWGuKcI6zLt8osYQsxsd$sFLB0|d}iDkYlEb%4e&!3^@vAPZzjSWK&PJj*$Otmn!kW(f+z?fJ-@}3O5aol zmz5MAI9t?Qgo`yUqE@Gf;A||#C75R_On;3_NLkQli^P0fsxjZ0nvFT8pKtn!f`YWh zrLIEE5qI|S%szsQts)xpTx|2O7?)vz#$^uCdAJUj;|h(-9ij_yo$2eDzOwJABO(!v zD;x&%unGd{a(&6{MSL8)_PZI|h1!Mj&A2LXUv9zmkjmFiEiOaCe^fEn7 z513w7sHg3ESjlUoU2D2)T8bHlVi2i zIl2m~O_6VkEU_CD7K=^sU1OQExfTy&IaX*acQ)5zoe?#K%{C`Q>yjEPT5R5i+cj=; zHrHc=>Ef9#p~>VFjoY2cby#l-d{baec2~GnOiCykE8Righ>fN*?!cWeo$W!tBY3U! z;I?<#gT6WstAnoH>(Z-a&t0aI#$C?Mje^+SxJTn|XJR8BG5Nm9|2Oz|p?^<{yK8W- z#v14DCOl<2dZwdwS41`Lb-EtGCey(;9Vowo+NPojd9MY13$w>6)5#LA)bga89d);4 zsg$mkOCPW;pt&xRwIar5`bFqI= z*>_3XefkeMenv7CuMaj<_6l}ZmS^>G^Zr4*eS4($BJ`KHxwYI?+I_fR<34A38@8Do zlWjCijy0NXJ8dQJm$n`cXsjn3Bdgo+0v^{?HnwYRH9ss(vZS$*RIP}4 z0>-0wOuDcfggaXa@)^Eq6>RTSp57!I9>)_JkB4aq1(a_TwXdMes5@dZ7;CFWlfn}s z{-lA%lP%)p3sCJUs8v)-yYwfdW+(&ee1^@%W8vu&#npI9;VDt@G@j9Tnv~pF)3QDu z&*C|aXDQ<(nYE&E*vtl7G&Z}P`E)X#S9o4_ZpAi@tsyxnp9--ZFKBFc#LK4w?7)i} zJ3``7Cn{j}n1y(*LcZu+&ZkcNx6gl5SE+`<-14qDFYJzm|MK~-V0KyY!3x{N-b?(a z#!F#+q9SqQWxT?F@b4NglahC;td-zZyr%K0Q&B|4{F~3eiHee(*W6k$b>&}u{x!I( ztW)|G7i1}o!R!1B-q3j61v!95<4uJ(#pqjjTjMPkrQ8M01f1yeEx|#SCAK*nn=VOQmJTlu(Mxw&buw){^;|ML9}P!rwq5QSaUyF&of28wFhUw-wKWS8_SuGorObbI6mhu`3sHD z?F&JXUHJlEYJB1FIFyFrD}1f-mBZsO8pfY{{JG#!JvAv8jjx@}L#fSS{F%?61v~l` z5BoAak~EBt;7>jN^dFTT{H1l`3+u#(;W4IBbPOcKZ=9l0bTohB^CzOGu)X5%t@L-o zb^d|K=U=S)L2o$UdQus_h2ZxsVP69EqB8#2&1vwm21;5Ev%TfAmcV*&t@CU!`^E>vkmR9&f_Wa3jY5eJc5)Sg4KEJsKaTLF0(R$nF z?Dt*8=1Bad@Rum~8?5nnSZrkfa7byB(!HzJ9HGE(D5VwLNhsoSlD=f6U$n5lY)SgMjphK=%0W>;MQShAK`VKUTH<%1B7WZE=XVibb#OWh zs0(lLd5hJV;qx2vSz4}C3)MG!ym=SZl~|Nt9poQW#~ju1zo9w{!1?@~R{1W}5p<;L zq*W(pVFZojXFYy)H`SlB1pZa_bXEmgb#9^hGyF6^1%AeY@wDxp5=m7ctqVV?RhJgY zCwS!Jyb1h-m3rKEn=E)w%1;~iOM6Z(Wl$zRrV9B{enb_iV$~J=7*Vb&q}IHVALb4G z5I^YiM!B1J6tEtan|C*C2rBn0#zR5Xe&u-3o|(BkxVu=ja@minP{o|R>YpHbwK`s@!DnfnuUS7lZ zNF~3U4(GdgHQ&j1(CNHNzVgqdD`^?sO`B*deab88SNa_p@^e;4tmE7HHeSKY`Bq+r z(|9QlU&6QW&Agaz;zfL;$M-h7?DLxcaLM$=VonA5O^u1kwG9);*VHB~`|b{gv@7oD z^IewIS@DKxfpHn#eMD+rLo59NxryfV~E;Et7I{mN}T13hr7T22RjD! z6>x8}cj#^&uMipgt<2-)GD=k^Wpt~I?2iH-FAKgcDipw%{y)D2dA!8#lvlIQw@8BW zakKsYw~xn*W!>IOJif_F$d?C?7lotb6pwGTBe^c;@qKt{Ef4g7CxW(v293ESO5G;4 zqsZ_xsHkePj7qD&(jHjYHS(jN+l)CpFF24d1RrRj6ACt?ehivF64|_(b7{yf3 z@*qmRg7XG-9=dnm5mghy-DNV`6pniL9dR;coLMz`Ox2{Es?j;ws@O8=20X^s^L475 zD&=c^zCmu1z0=uzJ*ifFjq0Ak^4%_5br)vy)x6N>tN-I|(&vSHR3XLJS$cJ&HXQI( zd?jDOm-7Ojga2dyRsYzZY44prU)fwtuCTW_N#*7CMJ=BTY#BT-SCx{F@A)#ml;`t2 zzJxF4i}*sG%NKZjnP6_;svch|qsfgi$?<%@s!*Nz$+MZ*O$vK-3eYl8)YCwA;=0i1*1`FuW4^-w)|w$F3y(H))5=UV{N zs#gZ53E8TbJPT6~YQ^X9**uG9`h3nFGCrT(jKr*F1ZG+U_Rm#4 zDNA+a89bevcp6XT6em5FdwX;2(`DphZ?bD)?9*)QB~xYYB2I;)l*E~ok&AA2FmUMD F{{8jb$eq=$Uuo8G3E=?T-@ZtZ2eo}v!dNV^u-Xv022526@cA6%c_qQ(t`^%#(V0l}Q~s|v4^Jp(aFV<3eC%opjwD9{+}0+dit zXf83`H44cq5nFbq2tzcA299eef}Y4U^RY z7^pF#%Cip2bO_pKBwsX4)ZB;?jTPIYJuc4&Vw*!WdL&jBx;u$0XCn zH*I8J&#ny`XjC{zDli^naf`-S2gz8BH?1+wv{I&Z6(kM+iODz#=2ml|#;wlLvAEr| zRHkJZlM5{-tt=+DI7})qRwGn}NuDx!!SsgdmF*=ObCCtPMA~h*UE?-K&Y75LE-)?3 z`7jq)p%%6imUy^b+8riW;|_=8Ol-#gP-&W*W*Yx|77w*vPH?c^hgl}aH#q{&jpZf7H7Z>e&&CGa>ETYXI}wvKCOWidV+kf> zipFHOeKzKpY~N&4v??qutq7Hk8LBZQd=fSjF%@@dOm!zQAB#;B-y}7$Tpv;-M|PKU zbPnd5#>zCdTrcr3No?MYX&QGsn@jO1?!k18dz{UsSZ1<(lXc4Gu!7=pjpW3X`c!rZt)2VVal}xHM+E++T%NrV(Zdpr(<{{aL}( z%nmi~x49oAAPDwk7FXUUdmbJZ4R=j~_LIVeD6r!;p4ht|PI+&2xFOE8Spn~F& z<>7WGJuA6L+F~rxSWM_6vs>{jmTD|@j%>v9>pq+N5iKQ7Ku|PF4)(kPUR|DTy0`CR#z=* zWi-X)Qe=UYcImGQjq^;bbvE7tzcQR^qNWg!d3a0|JdQOQkCT^gb<-d`fwdY>P{cLz z6iEZI&ctB7#yYn#k*>uC4;y6T#@^VZu`$di^mI9@sctWq&2Nw`J9-2nT^KFF50%@%`147ng7yw zg*mG_SzSLM|Nf(_G|2O=yakk zIDms12OK(`sSE$+^KXJq;h1tUde9l|M4kCp#lQZIPVjJxT3xX}oR!pty7DhR|MHJY z5B|eC^SgEC)o>;yZ5Q=m$>4W1-f`*&Qvo0K`KXw+ zHx-Y65clwqw0H5I#=EC06MxU&@wedbEiT{L?prJQp0xMzfyVo;GR0B?KCHw?8Xul6 zP5cc$=C2ihQ*~AO=S}5fQSk|XrSXXaO?b*j6dyT*O&)(G2Kj4yfFJ!`jC$Zx51)#H z&+xg%XQzr$rEuyCe5vup-_@uqj_{Wd8%NwsB6Y;q`~|+z_}b~pqPF<_w9zRrt z_Nn4e!wLJnnEHu7(Gc3mb(5Nj2S4K%{+I>5pIyE*qx11Af5-yxuP#CD%x{qV`OT?p zM$P$s#qXarL4S8L%_*1PQ~ch)5X}ya1Ak}_@&gO$FOD5~bRquG_`_|K62$K+e%JAr zKeo~z*;DvL_{(3oocP8elSC;vjuRTk9ii({3cus?JEwEvgwvTs_4sYYZ(E%aioXme z!Bjzh=aB}U?mNC9`SzdQI+Jr9=wBE&gU1cs>&BA+P?G4hF4hk zp0^G}$zT`nRJ`jSYl?SLR4&O!nMhQVdbfkO^ERpc&(g*G3~%M9`6(K}Td15S(=1v+ zYiSqlrX#$Wey8JzkWcN}Sin#6Cf>*!cs;M4#ZZH8gDE9{5Tz7QdQLa_04K_CTxfqrXVbZ|m?4W91(b?ZN~&5YpoYP$c6D1f z%ITgzEWA5YM!UkJ^K!b&p9@Ch_wAQIDlWfoT&yP)jORzOk{{ufUUe^;9}bST&Pj=7 zxjjbmLtc#tmRCWnS3@YvEBHahE9@mGTk(VcswHw&jXrwYIlds~x2u-LwM<#$oAvqSB+(WG#g7xDt0&+~XL&*9np00(@(&kF@P z`-=2=fs96%7Rlkvw{vpnKF_lxXg^n;9rkl%6q3Ia_&hs23XSvm0U6oLgwKH;m6nQz z`(BPp2|~rGEd@( ud?!!vd8#<>;FuyK2ghVP7lvb!g(GdEJUbBX438`b6J+E9o)L8G*#G|(aA*Vo diff --git a/src/vs/workbench/contrib/externalTerminal/node/iTermHelper.scpt b/src/vs/workbench/contrib/externalTerminal/node/iTermHelper.scpt index 6ef0afa62045b05821a0012384db2c2840031dca..f4341af8c242f935cad9655d05e2b95606ec4440 100644 GIT binary patch delta 1077 zcmaKr%TpUi5XSpsajeX;fSq6gLVz$fSmq&@t77w#c^DfEwy;6QZ?Onj=v|Q%h)>En zx0I4&{sdQ2#W9uq0XZk9RQZ<5ImhJkrN>I;b;wqA@9gY1zv-Fo{$b|(*{?r$uzd1` z^ZLx01$gN6;0X#Ma53N9?)j;KUJpTW)};+046}ef<_!zdJ5_sCs~lvCe)*$fk^Drg zl}r0o4}F4?B0?Bo&S6f_#=Iz?SFN9i9{Qy%p@InJt;j>D^*x3;gfZwL+}dy&v+pv? zml!h4HuIrS1AHm+4u-^GMx!|}%t*W0$$VV*Ff6Dj^eR4J+GSc!$HYmO4pdKd9tOoJ zLNJC2M7U4iLqu>zSLh7)4EL0@o353?eVwFl*9^3n5sWhBGBqw8F$t72$EkXzsz+?& zp;dLZ&U*?0jC5fHhk@pm;97GrXii=Q2hcc=Q4i;Jdp&;~alYVlJ_BFaZJt~A%-+tZ z+67$na6z!CvvDk9%)?j<9>)znb@A6wib>vnB;Pt-{x zsQOEYdbspfFZ%EyliUFxT4~a{9ZQa?UFNoj%YQ2$@IJS|2Uc|7x-CoI7U@*BbajiP z*+}TEPJ)`%ZcM1=p5Z;M5kGTOuQZd+d%;dj%IR+2l|F8A15=ph9mBiLZ~MZHO$o%L z-LGbkEP2B+rgX5*HJ9uE_~&v>PQ?T{tC*G%-sUZ?N|ZMx#uYAeiHowq8{$h%8uFQZ zDPPGq@+0H&v-~2z%5QKm!UfKAjPZ8Y(vS~=_TR@3kAIkzfZF>#M8)k3bE zO&#Q_A}((>BPQJAa^-CaWXeaWBfnY`F5zw(MZ}&tzP}m z)d>D7N5h3?AQVo|>;0_PtEKYsars|8r2XV_?mwkwIQu_6XE^g`^%`E+k?jb>YuYnm z=MGj*J`EYHUXA8EB$TjzV0BNziGq#>6DbLwm2**Fh9=I6r|;pEykG2)NHUpB>k^lez delta 950 zcmZ8g-%}H36g`KP(p=%mwDQ9x-cS_BOW5fwFr5JLha(Onx zJ82*LCpdP7$NmRC*C%IubNbxJwhyOwrPFr0GxzTI-S6D<-Mjbfi_5=U`ssUz(+58| z&w6_=(r1rPAKV7$@j`p~_^XTB_L)FO#zFxvu#f#d`wsU>e!>9;J_q2;ql4%8u_`Ka z&{d`VDhOIs8F+_-aJ0oxC0ASMbXSUBHGPI)%{V8R)>BVUaWoC5u)Aq^l>nasoE?Hm zp5>5=+VnXDerOpMQNdNgm{tpoDpjbUhB$H`RJPU$mNWRNwbqY zGK+=FqaQ zZB38*Jo^?8d-Oo}wMspRp!XxU8ZnQWjp?4xv44?St!SCbMxd3*El14vfF9#UEDpz} zao1**qV585o-@oHS9j3&LB6=xthG-JyqD-<3PZa!P1yZ_!w61GT*Yr6S^cnJKpoQ=7 zBc9`Dyws<7gJ1C*ey77>&FZSIXhzeTa&;4(AGy1YYRS_);%Rj6m#dpK8*sU~V>P&qZL3J>uBPl?2%TG;IruYh zIXjW6bR)<_esFFbVJ3(07*3?}7#cqY-`>MHyuZ_pk!&`bsl+l%G2|PCItc%d*RP-b E1tVzfEC2ui diff --git a/src/vs/workbench/contrib/feedback/browser/feedback.ts b/src/vs/workbench/contrib/feedback/browser/feedback.ts index 4a013f414..7a6e14ddd 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedback.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedback.ts @@ -277,7 +277,7 @@ export class FeedbackDropdown extends Dropdown { this.sendButton = new Button(buttonsContainer); this.sendButton.enabled = false; this.sendButton.label = nls.localize('tweet', "Tweet"); - dom.prepend(this.sendButton.element, dom.$('span.codicon.codicon-twitter')); + dom.prepend(this.sendButton.element, dom.$('span' + Codicon.twitter.cssSelector)); this.sendButton.element.classList.add('send'); this.sendButton.element.title = nls.localize('tweetFeedback', "Tweet Feedback"); disposables.add(attachButtonStyler(this.sendButton, this.themeService)); @@ -288,7 +288,7 @@ export class FeedbackDropdown extends Dropdown { if (this.feedbackForm) { this.feedbackForm.style.backgroundColor = colors.editorWidgetBackground ? colors.editorWidgetBackground.toString() : ''; this.feedbackForm.style.color = colors.editorWidgetForeground ? colors.editorWidgetForeground.toString() : ''; - this.feedbackForm.style.boxShadow = colors.widgetShadow ? `0 0 8px ${colors.widgetShadow}` : ''; + this.feedbackForm.style.boxShadow = colors.widgetShadow ? `0 0 8px 2px ${colors.widgetShadow}` : ''; } if (this.feedbackDescriptionInput) { this.feedbackDescriptionInput.style.backgroundColor = colors.inputBackground ? colors.inputBackground.toString() : ''; diff --git a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts index 3bbf80a06..5f4f0b991 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts @@ -28,7 +28,7 @@ class TwitterFeedbackService implements IFeedbackDelegate { } submitFeedback(feedback: IFeedback, openerService: IOpenerService): void { - const queryString = `?${feedback.sentiment === 1 ? `hashtags=${this.combineHashTagsAsString()}&` : null}ref_src=twsrc%5Etfw&related=twitterapi%2Ctwitter&text=${encodeURIComponent(feedback.feedback)}&tw_p=tweetbutton&via=${TwitterFeedbackService.VIA_NAME}`; + const queryString = `?${feedback.sentiment === 1 ? `hashtags=${this.combineHashTagsAsString()}&` : ''}ref_src=twsrc%5Etfw&related=twitterapi%2Ctwitter&text=${encodeURIComponent(feedback.feedback)}&tw_p=tweetbutton&via=${TwitterFeedbackService.VIA_NAME}`; const url = TwitterFeedbackService.TWITTER_URL + queryString; openerService.open(URI.parse(url)); diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index ad397db18..ec837dc48 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -9,7 +9,7 @@ import { isFunction, assertIsDefined } from 'vs/base/common/types'; import { isValidBasename } from 'vs/base/common/extpath'; import { basename } from 'vs/base/common/resources'; import { Action } from 'vs/base/common/actions'; -import { VIEWLET_ID, TEXT_FILE_EDITOR_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; +import { VIEWLET_ID, TEXT_FILE_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; import { ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; import { EditorOptions, TextEditorOptions, IEditorInput, IEditorOpenContext } from 'vs/workbench/common/editor'; @@ -30,6 +30,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { EditorActivation, IEditorOptions } from 'vs/platform/editor/common/editor'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { IExplorerService } from 'vs/workbench/contrib/files/browser/files'; /** * An implementation of editor for file system resources. @@ -60,12 +61,16 @@ export class TextFileEditor extends BaseTextEditor { // Move view state for moved files this._register(this.fileService.onDidRunOperation(e => this.onDidRunOperation(e))); + + // Listen to file system provider changes + this._register(this.fileService.onDidChangeFileSystemProviderCapabilities(e => this.onDidFileSystemProviderChange(e.scheme))); + this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(e => this.onDidFileSystemProviderChange(e.scheme))); } private onDidFilesChange(e: FileChangesEvent): void { const deleted = e.getDeleted(); if (deleted?.length) { - this.clearTextEditorViewState(deleted.map(d => d.resource)); + this.clearTextEditorViewState(deleted.map(({ resource }) => resource)); } } @@ -75,6 +80,14 @@ export class TextFileEditor extends BaseTextEditor { } } + private onDidFileSystemProviderChange(scheme: string): void { + const control = this.getControl(); + const input = this.input; + if (control && input?.resource.scheme === scheme) { + control.updateOptions({ readOnly: input.isReadonly() }); + } + } + protected onWillCloseEditorInGroup(editor: IEditorInput): void { // React to editors closing to preserve or clear view state. This needs to happen diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts index 023992aeb..6873c2f15 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts @@ -23,7 +23,7 @@ import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileE import { SAVE_FILE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL } from 'vs/workbench/contrib/files/browser/fileCommands'; import { INotificationService, INotificationHandle, INotificationActions, Severity } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ExecuteCommandAction } from 'vs/platform/actions/common/actions'; import { IProductService } from 'vs/platform/product/common/productService'; import { Event } from 'vs/base/common/event'; @@ -32,7 +32,6 @@ import { isWindows } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { SaveReason } from 'vs/workbench/common/editor'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; export const CONFLICT_RESOLUTION_CONTEXT = 'saveConflictResolutionContext'; export const CONFLICT_RESOLUTION_SCHEME = 'conflictResolution'; @@ -56,13 +55,9 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa @ITextModelService textModelService: ITextModelService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IStorageService private readonly storageService: IStorageService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { super(); - // opt-in to syncing - storageKeysSyncRegistryService.registerStorageKey({ key: LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, version: 1 }); - const provider = this._register(instantiationService.createInstance(TextFileContentProvider)); this._register(textModelService.registerTextModelContentProvider(CONFLICT_RESOLUTION_SCHEME, provider)); @@ -225,7 +220,7 @@ class DoNotShowResolveConflictLearnMoreAction extends Action { } async run(notification: IDisposable): Promise { - this.storageService.store(LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, true, StorageScope.GLOBAL); + this.storageService.store(LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, true, StorageScope.GLOBAL, StorageTarget.USER); // Hide notification notification.dispose(); @@ -239,13 +234,9 @@ class ResolveSaveConflictAction extends Action { @IEditorService private readonly editorService: IEditorService, @INotificationService private readonly notificationService: INotificationService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IProductService private readonly productService: IProductService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService + @IProductService private readonly productService: IProductService ) { super('workbench.files.action.resolveConflict', nls.localize('compareChanges', "Compare")); - - // opt-in to syncing - storageKeysSyncRegistryService.registerStorageKey({ key: LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, version: 1 }); } async run(): Promise { diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/browser/explorerService.ts similarity index 68% rename from src/vs/workbench/contrib/files/common/explorerService.ts rename to src/vs/workbench/contrib/files/browser/explorerService.ts index a5069cb03..a1a537578 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/browser/explorerService.ts @@ -6,32 +6,29 @@ import { Event } from 'vs/base/common/event'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IExplorerService, IFilesConfiguration, SortOrder, IExplorerView } from 'vs/workbench/contrib/files/common/files'; +import { IFilesConfiguration, SortOrder } from 'vs/workbench/contrib/files/common/files'; import { ExplorerItem, ExplorerModel } from 'vs/workbench/contrib/files/common/explorerModel'; import { URI } from 'vs/base/common/uri'; -import { FileOperationEvent, FileOperation, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions } from 'vs/platform/files/common/files'; +import { FileOperationEvent, FileOperation, IFileService, FileChangesEvent, FileChangeType, IResolveFileOptions } from 'vs/platform/files/common/files'; import { dirname } from 'vs/base/common/resources'; -import { memoize } from 'vs/base/common/decorators'; -import { ResourceGlobMatcher } from 'vs/workbench/common/resources'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; -import { IExpression } from 'vs/base/common/glob'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditableData } from 'vs/workbench/common/views'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { IBulkEditService, ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService'; +import { UndoRedoSource } from 'vs/platform/undoRedo/common/undoRedo'; +import { IExplorerView, IExplorerService } from 'vs/workbench/contrib/files/browser/files'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { RunOnceScheduler } from 'vs/base/common/async'; -function getFileEventsExcludes(configurationService: IConfigurationService, root?: URI): IExpression { - const scope = root ? { resource: root } : undefined; - const configuration = scope ? configurationService.getValue(scope) : configurationService.getValue(); - - return configuration?.files?.exclude || Object.create(null); -} +export const UNDO_REDO_SOURCE = new UndoRedoSource(); export class ExplorerService implements IExplorerService { declare readonly _serviceBrand: undefined; - private static readonly EXPLORER_FILE_CHANGES_REACT_DELAY = 500; // delay in ms to react to file changes to give our internal events a chance to react first + private static readonly EXPLORER_FILE_CHANGES_REACT_DELAY = 1000; // delay in ms to react to file changes to give our internal events a chance to react first private readonly disposables = new DisposableStore(); private editable: { stat: ExplorerItem, data: IEditableData } | undefined; @@ -39,22 +36,54 @@ export class ExplorerService implements IExplorerService { private cutItems: ExplorerItem[] | undefined; private view: IExplorerView | undefined; private model: ExplorerModel; + private onFileChangesScheduler: RunOnceScheduler; + private fileChangeEvents: FileChangesEvent[] = []; constructor( @IFileService private fileService: IFileService, - @IInstantiationService private instantiationService: IInstantiationService, @IConfigurationService private configurationService: IConfigurationService, @IWorkspaceContextService private contextService: IWorkspaceContextService, @IClipboardService private clipboardService: IClipboardService, @IEditorService private editorService: IEditorService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IBulkEditService private readonly bulkEditService: IBulkEditService, + @IProgressService private readonly progressService: IProgressService ) { this._sortOrder = this.configurationService.getValue('explorer.sortOrder'); this.model = new ExplorerModel(this.contextService, this.uriIdentityService, this.fileService); this.disposables.add(this.model); this.disposables.add(this.fileService.onDidRunOperation(e => this.onDidRunOperation(e))); - this.disposables.add(this.fileService.onDidFilesChange(e => this.onDidFilesChange(e))); + + this.onFileChangesScheduler = new RunOnceScheduler(async () => { + const events = this.fileChangeEvents; + this.fileChangeEvents = []; + + // Filter to the ones we care + const types = [FileChangeType.ADDED, FileChangeType.DELETED]; + if (this._sortOrder === SortOrder.Modified) { + types.push(FileChangeType.UPDATED); + } + + let shouldRefresh = false; + this.roots.forEach(r => { + if (this.view && !shouldRefresh) { + shouldRefresh = doesFileEventAffect(r, this.view, events, types); + } + }); + + if (shouldRefresh) { + await this.refresh(false); + } + + }, ExplorerService.EXPLORER_FILE_CHANGES_REACT_DELAY); + + this.disposables.add(this.fileService.onDidFilesChange(e => { + this.fileChangeEvents.push(e); + if (!this.onFileChangesScheduler.isScheduled()) { + this.onFileChangesScheduler.schedule(); + } + })); this.disposables.add(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue()))); this.disposables.add(Event.any<{ scheme: string }>(this.fileService.onDidChangeFileSystemProviderRegistrations, this.fileService.onDidChangeFileSystemProviderCapabilities)(async e => { let affected = false; @@ -96,16 +125,27 @@ export class ExplorerService implements IExplorerService { return this.view.getContext(respectMultiSelection); } - // Memoized locals - @memoize private get fileEventsFilter(): ResourceGlobMatcher { - const fileEventsFilter = this.instantiationService.createInstance( - ResourceGlobMatcher, - (root?: URI) => getFileEventsExcludes(this.configurationService, root), - (event: IConfigurationChangeEvent) => event.affectsConfiguration(FILES_EXCLUDE_CONFIG) - ); - this.disposables.add(fileEventsFilter); + async applyBulkEdit(edit: ResourceFileEdit[], options: { undoLabel: string, progressLabel: string }): Promise { + const cancellationTokenSource = new CancellationTokenSource(); + const promise = this.progressService.withProgress({ + location: ProgressLocation.Window, + delay: 500, + title: options.progressLabel, + cancellable: edit.length > 1 // Only allow cancellation when there is more than one edit. Since cancelling will not actually stop the current edit that is in progress. + }, async progress => { + await this.bulkEditService.apply(edit, { + undoRedoSource: UNDO_REDO_SOURCE, + label: options.undoLabel, + progress, + token: cancellationTokenSource.token + }); + }, () => cancellationTokenSource.cancel()); + await this.progressService.withProgress({ location: ProgressLocation.Explorer, delay: 500 }, () => promise); + cancellationTokenSource.dispose(); + } - return fileEventsFilter; + hasViewFocus(): boolean { + return !!this.view && this.view.hasFocus(); } // IExplorerService methods @@ -288,100 +328,6 @@ export class ExplorerService implements IExplorerService { } } - private onDidFilesChange(e: FileChangesEvent): void { - // Check if an explorer refresh is necessary (delayed to give internal events a chance to react first) - // Note: there is no guarantee when the internal events are fired vs real ones. Code has to deal with the fact that one might - // be fired first over the other or not at all. - setTimeout(async () => { - // Filter to the ones we care - const shouldRefresh = () => { - e = this.filterToViewRelevantEvents(e); - // Handle added files/folders - const added = e.getAdded(); - if (added.length) { - - // Check added: Refresh if added file/folder is not part of resolved root and parent is part of it - const ignoredPaths: Set = new Set(); - for (let i = 0; i < added.length; i++) { - const change = added[i]; - - // Find parent - const parent = dirname(change.resource); - - // Continue if parent was already determined as to be ignored - if (ignoredPaths.has(parent.toString())) { - continue; - } - - // Compute if parent is visible and added file not yet part of it - const parentStat = this.model.findClosest(parent); - if (parentStat && parentStat.isDirectoryResolved && !this.model.findClosest(change.resource)) { - return true; - } - - // Keep track of path that can be ignored for faster lookup - if (!parentStat || !parentStat.isDirectoryResolved) { - ignoredPaths.add(parent.toString()); - } - } - } - - // Handle deleted files/folders - const deleted = e.getDeleted(); - if (deleted.length) { - - // Check deleted: Refresh if deleted file/folder part of resolved root - for (let j = 0; j < deleted.length; j++) { - const del = deleted[j]; - const item = this.model.findClosest(del.resource); - if (item && item.parent) { - return true; - } - } - } - - // Handle updated files/folders if we sort by modified - if (this._sortOrder === SortOrder.Modified) { - const updated = e.getUpdated(); - - // Check updated: Refresh if updated file/folder part of resolved root - for (let j = 0; j < updated.length; j++) { - const upd = updated[j]; - const item = this.model.findClosest(upd.resource); - - if (item && item.parent) { - return true; - } - } - } - - return false; - }; - - if (shouldRefresh()) { - await this.refresh(false); - } - }, ExplorerService.EXPLORER_FILE_CHANGES_REACT_DELAY); - } - - private filterToViewRelevantEvents(e: FileChangesEvent): FileChangesEvent { - return e.filter(change => { - if (change.type === FileChangeType.UPDATED && this._sortOrder !== SortOrder.Modified) { - return false; // we only are about updated if we sort by modified time - } - - if (!this.contextService.isInsideWorkspace(change.resource)) { - return false; // exclude changes for resources outside of workspace - } - - if (this.fileEventsFilter.matches(change.resource)) { - return false; // excluded via files.exclude setting - } - - return true; - }); - } - private async onConfigurationUpdated(configuration: IFilesConfiguration, event?: IConfigurationChangeEvent): Promise { const configSortOrder = configuration?.explorer?.sortOrder || 'default'; if (this._sortOrder !== configSortOrder) { @@ -397,3 +343,20 @@ export class ExplorerService implements IExplorerService { this.disposables.dispose(); } } + +function doesFileEventAffect(item: ExplorerItem, view: IExplorerView, events: FileChangesEvent[], types: FileChangeType[]): boolean { + if (events.some(e => e.affects(item.resource, ...types))) { + return true; + } + for (let [_name, child] of item.children) { + if (view.isItemVisible(child)) { + if (child.isDirectory && child.isDirectoryResolved) { + if (doesFileEventAffect(child, view, events, types)) { + return true; + } + } + } + } + + return false; +} diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 54912b106..9745ec52d 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -38,6 +38,9 @@ import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; import { AddRootFolderAction, OpenFolderAction, OpenFileFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { isMacintosh } from 'vs/base/common/platform'; import { Codicon } from 'vs/base/common/codicons'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; + +const explorerViewIcon = registerIcon('explorer-view-icon', Codicon.files, localize('explorerViewIcon', 'View icon of the explorer view.')); export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution { @@ -108,7 +111,7 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor id: OpenEditorsView.ID, name: OpenEditorsView.NAME, ctorDescriptor: new SyncDescriptor(OpenEditorsView), - containerIcon: 'codicon-files', + containerIcon: explorerViewIcon, order: 0, when: OpenEditorsVisibleContext, canToggleVisibility: true, @@ -125,7 +128,7 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor return { id: EmptyView.ID, name: EmptyView.NAME, - containerIcon: Codicon.files.classNames, + containerIcon: explorerViewIcon, ctorDescriptor: new SyncDescriptor(EmptyView), order: 1, canToggleVisibility: true, @@ -139,7 +142,7 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor return { id: VIEW_ID, name: localize('folders', "Folders"), - containerIcon: Codicon.files.classNames, + containerIcon: explorerViewIcon, ctorDescriptor: new SyncDescriptor(ExplorerView), order: 1, canToggleVisibility: false, @@ -267,7 +270,7 @@ export const VIEW_CONTAINER: ViewContainer = viewContainerRegistry.registerViewC name: localize('explore', "Explorer"), ctorDescriptor: new SyncDescriptor(ExplorerViewPaneContainer), storageId: 'workbench.explorer.views.state', - icon: Codicon.files.classNames, + icon: explorerViewIcon, alwaysUseContainerInfo: true, order: 0 }, ViewContainerLocation.Sidebar, true); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index fd399160c..80deb8269 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -15,7 +15,7 @@ import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/c import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { isMacintosh } from 'vs/base/common/platform'; -import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext, ExplorerResourceNotReadonlyContext, ExplorerResourceCut, IExplorerService, ExplorerResourceMoveableToTrash, ExplorerViewletVisibleContext, ExplorerResourceAvailableEditorIdsContext } from 'vs/workbench/contrib/files/common/files'; +import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext, ExplorerResourceNotReadonlyContext, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerViewletVisibleContext, ExplorerResourceAvailableEditorIdsContext } from 'vs/workbench/contrib/files/common/files'; import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL } from 'vs/workbench/browser/actions/workspaceCommands'; import { CLOSE_SAVED_EDITORS_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { AutoSaveAfterShortDelayContext } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; @@ -29,6 +29,8 @@ import { OpenFileFolderAction, OpenFileAction, OpenFolderAction, OpenWorkspaceAc import { ActiveEditorContext } from 'vs/workbench/common/editor'; import { SidebarFocusContext } from 'vs/workbench/common/viewlet'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IExplorerService } from 'vs/workbench/contrib/files/browser/files'; +import { Codicon } from 'vs/base/common/codicons'; // Contribute Global Actions const category = { value: nls.localize('filesCategory', "File"), original: 'File' }; @@ -182,8 +184,8 @@ export function appendEditorTitleContextMenuItem(id: string, title: string, when } // Editor Title Menu for Conflict Resolution -appendSaveConflictEditorTitleAction('workbench.files.action.acceptLocalChanges', nls.localize('acceptLocalChanges', "Use your changes and overwrite file contents"), { id: 'codicon/check' }, -10, acceptLocalChangesCommand); -appendSaveConflictEditorTitleAction('workbench.files.action.revertLocalChanges', nls.localize('revertLocalChanges', "Discard your changes and revert to file contents"), { id: 'codicon/discard' }, -9, revertLocalChangesCommand); +appendSaveConflictEditorTitleAction('workbench.files.action.acceptLocalChanges', nls.localize('acceptLocalChanges', "Use your changes and overwrite file contents"), Codicon.check, -10, acceptLocalChangesCommand); +appendSaveConflictEditorTitleAction('workbench.files.action.revertLocalChanges', nls.localize('revertLocalChanges', "Discard your changes and revert to file contents"), Codicon.discard, -9, revertLocalChangesCommand); function appendSaveConflictEditorTitleAction(id: string, title: string, icon: ThemeIcon, order: number, command: ICommandHandler): void { diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 4f042f28a..46aacd31f 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -12,9 +12,8 @@ import { URI } from 'vs/base/common/uri'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Action } from 'vs/base/common/actions'; import { DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { VIEWLET_ID, IExplorerService, IFilesConfiguration, VIEW_ID } from 'vs/workbench/contrib/files/common/files'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { BinarySize, IFileService, IFileStatWithMetadata, IFileStreamContent } from 'vs/platform/files/common/files'; +import { VIEWLET_ID, IFilesConfiguration, VIEW_ID } from 'vs/workbench/contrib/files/common/files'; +import { ByteSize, IFileService, IFileStatWithMetadata } from 'vs/platform/files/common/files'; import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { IQuickInputService, ItemActivation } from 'vs/platform/quickinput/common/quickInput'; @@ -22,9 +21,9 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ITextModel } from 'vs/editor/common/model'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { REVEAL_IN_EXPLORER_COMMAND_ID, SAVE_ALL_COMMAND_ID, SAVE_ALL_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileCommands'; +import { REVEAL_IN_EXPLORER_COMMAND_ID, SAVE_ALL_COMMAND_ID, SAVE_ALL_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, NEW_UNTITLED_FILE_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileCommands'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; -import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -53,26 +52,21 @@ import { IProgressService, IProgressStep, ProgressLocation } from 'vs/platform/p import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { ILogService } from 'vs/platform/log/common/log'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService'; +import { IExplorerService } from 'vs/workbench/contrib/files/browser/files'; export const NEW_FILE_COMMAND_ID = 'explorer.newFile'; export const NEW_FILE_LABEL = nls.localize('newFile', "New File"); - export const NEW_FOLDER_COMMAND_ID = 'explorer.newFolder'; export const NEW_FOLDER_LABEL = nls.localize('newFolder', "New Folder"); - export const TRIGGER_RENAME_LABEL = nls.localize('rename', "Rename"); - export const MOVE_FILE_TO_TRASH_LABEL = nls.localize('delete', "Delete"); - export const COPY_FILE_LABEL = nls.localize('copyFile', "Copy"); - export const PASTE_FILE_LABEL = nls.localize('pasteFile', "Paste"); - export const FileCopiedContext = new RawContextKey('fileCopied', false); - export const DOWNLOAD_LABEL = nls.localize('download', "Download..."); - const CONFIRM_DELETE_SETTING_KEY = 'explorer.confirmDelete'; +const MAX_UNDO_FILE_SIZE = 5000000; // 5mb function onError(notificationService: INotificationService, error: any): void { if (error.message === 'string') { @@ -123,7 +117,7 @@ export class NewFolderAction extends Action { } } -async function deleteFiles(workingCopyFileService: IWorkingCopyFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise { +async function deleteFiles(explorerService: IExplorerService, workingCopyFileService: IWorkingCopyFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise { let primaryButton: string; if (useTrash) { primaryButton = isWindows ? nls.localize('deleteButtonLabelRecycleBin', "&&Move to Recycle Bin") : nls.localize({ key: 'deleteButtonLabelTrash', comment: ['&& denotes a mnemonic'] }, "&&Move to Trash"); @@ -216,7 +210,7 @@ async function deleteFiles(workingCopyFileService: IWorkingCopyFileService, dial // Check for confirmation checkbox if (confirmation.confirmed && confirmation.checkboxChecked === true) { - await configurationService.updateValue(CONFIRM_DELETE_SETTING_KEY, false, ConfigurationTarget.USER); + await configurationService.updateValue(CONFIRM_DELETE_SETTING_KEY, false); } // Check for confirmation @@ -226,7 +220,12 @@ async function deleteFiles(workingCopyFileService: IWorkingCopyFileService, dial // Call function try { - await workingCopyFileService.delete(distinctElements.map(e => e.resource), { useTrash, recursive: true }); + const resourceFileEdits = distinctElements.map(e => new ResourceFileEdit(e.resource, undefined, { recursive: true, folder: e.isDirectory, skipTrashBin: !useTrash, maxSize: MAX_UNDO_FILE_SIZE })); + const options = { + undoLabel: distinctElements.length > 1 ? nls.localize('deleteBulkEdit', "Delete {0} files", distinctElements.length) : nls.localize('deleteFileBulkEdit', "Delete {0}", distinctElements[0].name), + progressLabel: distinctElements.length > 1 ? nls.localize('deletingBulkEdit', "Deleting {0} files", distinctElements.length) : nls.localize('deletingFileBulkEdit', "Deleting {0}", distinctElements[0].name), + }; + await explorerService.applyBulkEdit(resourceFileEdits, options); } catch (error) { // Handle error to delete file(s) from a modal confirmation dialog @@ -256,7 +255,7 @@ async function deleteFiles(workingCopyFileService: IWorkingCopyFileService, dial skipConfirm = true; - return deleteFiles(workingCopyFileService, dialogService, configurationService, elements, useTrash, skipConfirm); + return deleteFiles(explorerService, workingCopyFileService, dialogService, configurationService, elements, useTrash, skipConfirm); } } } @@ -472,7 +471,7 @@ export class GlobalCompareResourcesAction extends Action { override: this.editorService.openEditor({ leftResource: activeResource, rightResource: resource, - options: { override: false } + options: { override: false, pinned: true } }) }; } @@ -482,7 +481,7 @@ export class GlobalCompareResourcesAction extends Action { return { override: this.editorService.openEditor({ resource: activeResource, - options: { override: false } + options: { override: false, pinned: true } }) }; } @@ -828,7 +827,11 @@ export class CompareWithClipboardAction extends Action { const name = resources.basename(resource); const editorLabel = nls.localize('clipboardComparisonLabel', "Clipboard ↔ {0}", name); - await this.editorService.openEditor({ leftResource: resource.with({ scheme }), rightResource: resource, label: editorLabel }).finally(() => { + await this.editorService.openEditor({ + leftResource: resource.with({ scheme }), + rightResource: resource, label: editorLabel, + options: { pinned: true } + }).finally(() => { dispose(this.registrationDisposal); this.registrationDisposal = undefined; }); @@ -870,13 +873,26 @@ function onErrorWithRetry(notificationService: INotificationService, error: unkn async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boolean): Promise { const explorerService = accessor.get(IExplorerService); const fileService = accessor.get(IFileService); - const textFileService = accessor.get(ITextFileService); const editorService = accessor.get(IEditorService); const viewsService = accessor.get(IViewsService); const notificationService = accessor.get(INotificationService); - const workingCopyFileService = accessor.get(IWorkingCopyFileService); + const commandService = accessor.get(ICommandService); - await viewsService.openView(VIEW_ID, true); + const wasHidden = !viewsService.isViewVisible(VIEW_ID); + const view = await viewsService.openView(VIEW_ID, true); + if (wasHidden) { + // Give explorer some time to resolve itself #111218 + await timeout(500); + } + if (!view) { + // Can happen in empty workspace case (https://github.com/microsoft/vscode/issues/100604) + + if (isFolder) { + throw new Error('Open a folder or workspace first.'); + } + + return commandService.executeCommand(NEW_UNTITLED_FILE_COMMAND_ID); + } const stats = explorerService.getContext(false); const stat = stats.length > 0 ? stats[0] : undefined; @@ -896,12 +912,18 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole const onSuccess = async (value: string): Promise => { try { - const created = isFolder ? await workingCopyFileService.createFolder(resources.joinPath(folder.resource, value)) : await textFileService.create(resources.joinPath(folder.resource, value)); + const resourceToCreate = resources.joinPath(folder.resource, value); + await explorerService.applyBulkEdit([new ResourceFileEdit(undefined, resourceToCreate, { folder: isFolder })], { + undoLabel: nls.localize('createBulkEdit', "Create {0}", value), + progressLabel: nls.localize('creatingBulkEdit', "Creating {0}", value) + }); await refreshIfSeparator(value, explorerService); - isFolder ? - await explorerService.select(created.resource, true) : - await editorService.openEditor({ resource: created.resource, options: { pinned: true } }); + if (isFolder) { + await explorerService.select(resourceToCreate, true); + } else { + await editorService.openEditor({ resource: resourceToCreate, options: { pinned: true } }); + } } catch (error) { onErrorWithRetry(notificationService, error, () => onSuccess(value)); } @@ -935,7 +957,6 @@ CommandsRegistry.registerCommand({ export const renameHandler = async (accessor: ServicesAccessor) => { const explorerService = accessor.get(IExplorerService); - const workingCopyFileService = accessor.get(IWorkingCopyFileService); const notificationService = accessor.get(INotificationService); const stats = explorerService.getContext(false); @@ -952,7 +973,10 @@ export const renameHandler = async (accessor: ServicesAccessor) => { const targetResource = resources.joinPath(parentResource, value); if (stat.resource.toString() !== targetResource.toString()) { try { - await workingCopyFileService.move([{ source: stat.resource, target: targetResource }]); + await explorerService.applyBulkEdit([new ResourceFileEdit(stat.resource, targetResource)], { + undoLabel: nls.localize('renameBulkEdit', "Rename {0} to {1}", stat.name, value), + progressLabel: nls.localize('renamingBulkEdit', "Renaming {0} to {1}", stat.name, value), + }); await refreshIfSeparator(value, explorerService); } catch (e) { notificationService.error(e); @@ -968,7 +992,7 @@ export const moveFileToTrashHandler = async (accessor: ServicesAccessor) => { const explorerService = accessor.get(IExplorerService); const stats = explorerService.getContext(true).filter(s => !s.isRoot); if (stats.length) { - await deleteFiles(accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true); + await deleteFiles(accessor.get(IExplorerService), accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true); } }; @@ -977,7 +1001,7 @@ export const deleteFileHandler = async (accessor: ServicesAccessor) => { const stats = explorerService.getContext(true).filter(s => !s.isRoot); if (stats.length) { - await deleteFiles(accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false); + await deleteFiles(accessor.get(IExplorerService), accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false); } }; @@ -1001,10 +1025,9 @@ export const cutFileHandler = async (accessor: ServicesAccessor) => { }; export const DOWNLOAD_COMMAND_ID = 'explorer.download'; -const downloadFileHandler = (accessor: ServicesAccessor) => { +const downloadFileHandler = async (accessor: ServicesAccessor) => { const logService = accessor.get(ILogService); const fileService = accessor.get(IFileService); - const workingCopyFileService = accessor.get(IWorkingCopyFileService); const fileDialogService = accessor.get(IFileDialogService); const explorerService = accessor.get(IExplorerService); const progressService = accessor.get(IProgressService); @@ -1014,7 +1037,7 @@ const downloadFileHandler = (accessor: ServicesAccessor) => { const cts = new CancellationTokenSource(); - const downloadPromise = progressService.withProgress({ + await progressService.withProgress({ location: ProgressLocation.Window, delay: 800, cancellable: isWeb, @@ -1034,7 +1057,7 @@ const downloadFileHandler = (accessor: ServicesAccessor) => { return; } - const maxBlobDownloadSize = 32 * BinarySize.MB; // avoid to download via blob-trick >32MB to avoid memory pressure + const maxBlobDownloadSize = 32 * ByteSize.MB; // avoid to download via blob-trick >32MB to avoid memory pressure const preferFileSystemAccessWebApis = stat.isDirectory || stat.size > maxBlobDownloadSize; // Folder: use FS APIs to download files and folders if available and preferred @@ -1051,9 +1074,15 @@ const downloadFileHandler = (accessor: ServicesAccessor) => { fileBytesDownloaded: number; } - async function pipeContents(name: string, source: IFileStreamContent, target: WebFileSystemAccess.FileSystemWritableFileStream, operation: IDownloadOperation): Promise { + async function downloadFileBuffered(resource: URI, target: WebFileSystemAccess.FileSystemWritableFileStream, operation: IDownloadOperation): Promise { + const contents = await fileService.readFileStream(resource); + if (cts.token.isCancellationRequested) { + target.close(); + return; + } + return new Promise((resolve, reject) => { - const sourceStream = source.value; + const sourceStream = contents.value; const disposables = new DisposableStore(); disposables.add(toDisposable(() => target.close())); @@ -1069,7 +1098,7 @@ const downloadFileHandler = (accessor: ServicesAccessor) => { sourceStream.on('data', data => { if (!disposed) { target.write(data.buffer); - reportProgress(name, source.size, data.byteLength, operation); + reportProgress(contents.name, contents.size, data.byteLength, operation); } }); @@ -1085,18 +1114,34 @@ const downloadFileHandler = (accessor: ServicesAccessor) => { }); } - async function downloadFile(targetFolder: WebFileSystemAccess.FileSystemDirectoryHandle, name: string, resource: URI, operation: IDownloadOperation): Promise { + async function downloadFileUnbuffered(resource: URI, target: WebFileSystemAccess.FileSystemWritableFileStream, operation: IDownloadOperation): Promise { + const contents = await fileService.readFile(resource); + if (!cts.token.isCancellationRequested) { + target.write(contents.value.buffer); + reportProgress(contents.name, contents.size, contents.value.byteLength, operation); + } + + target.close(); + } + + async function downloadFile(targetFolder: WebFileSystemAccess.FileSystemDirectoryHandle, file: IFileStatWithMetadata, operation: IDownloadOperation): Promise { // Report progress operation.filesDownloaded++; operation.fileBytesDownloaded = 0; // reset for this file - reportProgress(name, 0, 0, operation); + reportProgress(file.name, 0, 0, operation); // Start to download - const targetFile = await targetFolder.getFileHandle(name, { create: true }); + const targetFile = await targetFolder.getFileHandle(file.name, { create: true }); const targetFileWriter = await targetFile.createWritable(); - return pipeContents(name, await fileService.readFileStream(resource), targetFileWriter, operation); + // For large files, write buffered using streams + if (file.size > ByteSize.MB) { + return downloadFileBuffered(file.resource, targetFileWriter, operation); + } + + // For small files prefer to write unbuffered to reduce overhead + return downloadFileUnbuffered(file.resource, targetFileWriter, operation); } async function downloadFolder(folder: IFileStatWithMetadata, targetFolder: WebFileSystemAccess.FileSystemDirectoryHandle, operation: IDownloadOperation): Promise { @@ -1109,7 +1154,7 @@ const downloadFileHandler = (accessor: ServicesAccessor) => { } if (child.isFile) { - await downloadFile(targetFolder, child.name, child.resource, operation); + await downloadFile(targetFolder, child, operation); } else { const childFolder = await targetFolder.getDirectoryHandle(child.name, { create: true }); const resolvedChildFolder = await fileService.resolve(child.resource, { resolveMetadata: true }); @@ -1128,17 +1173,17 @@ const downloadFileHandler = (accessor: ServicesAccessor) => { // Small file let message: string; - if (fileSize < BinarySize.MB) { + if (fileSize < ByteSize.MB) { if (operation.filesTotal === 1) { message = name; } else { - message = nls.localize('downloadProgressSmallMany', "{0} of {1} files ({2}/s)", operation.filesDownloaded, operation.filesTotal, BinarySize.formatSize(bytesDownloadedPerSecond)); + message = nls.localize('downloadProgressSmallMany', "{0} of {1} files ({2}/s)", operation.filesDownloaded, operation.filesTotal, ByteSize.formatSize(bytesDownloadedPerSecond)); } } // Large file else { - message = nls.localize('downloadProgressLarge', "{0} ({1} of {2}, {3}/s)", name, BinarySize.formatSize(operation.fileBytesDownloaded), BinarySize.formatSize(fileSize), BinarySize.formatSize(bytesDownloadedPerSecond)); + message = nls.localize('downloadProgressLarge', "{0} ({1} of {2}, {3}/s)", name, ByteSize.formatSize(operation.fileBytesDownloaded), ByteSize.formatSize(fileSize), ByteSize.formatSize(bytesDownloadedPerSecond)); } // Report progress but limit to update only once per second @@ -1162,7 +1207,7 @@ const downloadFileHandler = (accessor: ServicesAccessor) => { const targetFolder = await parentFolder.getDirectoryHandle(stat.name, { create: true }); await downloadFolder(stat, targetFolder, operation); } else { - await downloadFile(parentFolder, stat.name, stat.resource, operation); + await downloadFile(parentFolder, stat, operation); } operation.progressScheduler.dispose(); @@ -1191,10 +1236,8 @@ const downloadFileHandler = (accessor: ServicesAccessor) => { else { progress.report({ message: explorerItem.name }); - let defaultUri = explorerItem.isDirectory ? fileDialogService.defaultFolderPath(Schemas.file) : fileDialogService.defaultFilePath(Schemas.file); - if (defaultUri) { - defaultUri = resources.joinPath(defaultUri, explorerItem.name); - } + let defaultUri = explorerItem.isDirectory ? await fileDialogService.defaultFolderPath(Schemas.file) : await fileDialogService.defaultFilePath(Schemas.file); + defaultUri = resources.joinPath(defaultUri, explorerItem.name); const destination = await fileDialogService.showSaveDialog({ availableFileSystems: [Schemas.file], @@ -1204,16 +1247,16 @@ const downloadFileHandler = (accessor: ServicesAccessor) => { }); if (destination) { - await workingCopyFileService.copy([{ source: explorerItem.resource, target: destination }], { overwrite: true }); + await explorerService.applyBulkEdit([new ResourceFileEdit(explorerItem.resource, destination, { overwrite: true, copy: true })], { + undoLabel: nls.localize('downloadBulkEdit', "Download {0}", explorerItem.name), + progressLabel: nls.localize('downloadingBulkEdit', "Downloading {0}", explorerItem.name), + }); } else { cts.cancel(); // User canceled a download. In case there were multiple files selected we should cancel the remainder of the prompts #86100 } } })); }, () => cts.dispose(true)); - - // Also indicate progress in the files view - progressService.withProgress({ location: VIEW_ID, delay: 800 }, () => downloadPromise); }; CommandsRegistry.registerCommand({ @@ -1225,7 +1268,6 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { const clipboardService = accessor.get(IClipboardService); const explorerService = accessor.get(IExplorerService); const fileService = accessor.get(IFileService); - const workingCopyFileService = accessor.get(IWorkingCopyFileService); const notificationService = accessor.get(INotificationService); const editorService = accessor.get(IEditorService); const configurationService = accessor.get(IConfigurationService); @@ -1258,21 +1300,31 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { return { source: fileToPaste, target: targetFile }; })); - // Move/Copy File - let stats: IFileStatWithMetadata[] = []; - if (pasteShouldMove) { - stats = await workingCopyFileService.move(sourceTargetPairs); - } else { - stats = await workingCopyFileService.copy(sourceTargetPairs); - } - - if (stats.length >= 1) { - const stat = stats[0]; - if (stat && !stat.isDirectory && stats.length === 1) { - await editorService.openEditor({ resource: stat.resource, options: { pinned: true, preserveFocus: true } }); + if (sourceTargetPairs.length >= 1) { + // Move/Copy File + if (pasteShouldMove) { + const resourceFileEdits = sourceTargetPairs.map(pair => new ResourceFileEdit(pair.source, pair.target)); + const options = { + progressLabel: sourceTargetPairs.length > 1 ? nls.localize('movingBulkEdit', "Moving {0} files", sourceTargetPairs.length) : nls.localize('movingFileBulkEdit', "Moving {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target)), + undoLabel: sourceTargetPairs.length > 1 ? nls.localize('moveBulkEdit', "Move {0} files", sourceTargetPairs.length) : nls.localize('moveFileBulkEdit', "Move {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target)) + }; + await explorerService.applyBulkEdit(resourceFileEdits, options); + } else { + const resourceFileEdits = sourceTargetPairs.map(pair => new ResourceFileEdit(pair.source, pair.target, { copy: true })); + const options = { + progressLabel: sourceTargetPairs.length > 1 ? nls.localize('copyingBulkEdit', "Copying {0} files", sourceTargetPairs.length) : nls.localize('copyingFileBulkEdit', "Copying {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target)), + undoLabel: sourceTargetPairs.length > 1 ? nls.localize('copyBulkEdit', "Copy {0} files", sourceTargetPairs.length) : nls.localize('copyFileBulkEdit', "Copy {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target)) + }; + await explorerService.applyBulkEdit(resourceFileEdits, options); } - if (stat) { - await explorerService.select(stat.resource); + + const pair = sourceTargetPairs[0]; + await explorerService.select(pair.target); + if (sourceTargetPairs.length === 1) { + const item = explorerService.findClosest(pair.target); + if (item && !item.isDirectory) { + await editorService.openEditor({ resource: item.resource, options: { pinned: true, preserveFocus: true } }); + } } } } catch (e) { diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index f8fc0ee1d..8e1badc5f 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -11,7 +11,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { ExplorerFocusCondition, TextFileContentProvider, VIEWLET_ID, IExplorerService, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, FilesExplorerFocusCondition, ExplorerFolderContext } from 'vs/workbench/contrib/files/common/files'; +import { ExplorerFocusCondition, TextFileContentProvider, VIEWLET_ID, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, FilesExplorerFocusCondition, ExplorerFolderContext } from 'vs/workbench/contrib/files/common/files'; import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -23,7 +23,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; import { isWindows } from 'vs/base/common/platform'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { getResourceForCommand, getMultiSelectedResources, getOpenEditorsViewMultiSelection } from 'vs/workbench/contrib/files/browser/files'; +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 { Schemas } from 'vs/base/common/network'; @@ -44,6 +44,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { openEditorWith } from 'vs/workbench/services/editor/common/editorOpenWith'; +import { isPromiseCanceledError } from 'vs/base/common/errors'; // Commands @@ -134,10 +135,20 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const untitledResources = resources.filter(resource => resource.scheme === Schemas.untitled); const fileResources = resources.filter(resource => resource.scheme !== Schemas.untitled); - const resolved = await fileService.resolveAll(fileResources.map(resource => ({ resource }))); - const editors = resolved.filter(r => r.stat && r.success && !r.stat.isDirectory).map(r => ({ - resource: r.stat!.resource - })).concat(...untitledResources.map(untitledResource => ({ resource: untitledResource }))); + const items = await Promise.all(fileResources.map(async resource => { + const item = explorerService.findClosest(resource); + if (item) { + // Explorer already resolved the item, no need to go to the file service #109780 + return item; + } + + return await fileService.resolve(resource); + })); + const files = items.filter(i => !i.isDirectory); + const editors = files.map(f => ({ + resource: f.resource, + options: { pinned: true } + })).concat(...untitledResources.map(untitledResource => ({ resource: untitledResource, options: { pinned: true } }))); await editorService.openEditors(editors, SIDE_GROUP); } @@ -146,7 +157,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib + 10, - when: ContextKeyExpr.and(ExplorerFocusCondition, ExplorerFolderContext.toNegated()), + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerFolderContext.toNegated()), primary: KeyCode.Enter, mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow @@ -157,7 +168,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const resources = explorerService.getContext(true); if (resources.length) { - await editorService.openEditors(resources.map(r => ({ resource: r.resource, options: { preserveFocus: false } }))); + await editorService.openEditors(resources.map(r => ({ resource: r.resource, options: { preserveFocus: false, pinned: true } }))); } } }); @@ -192,7 +203,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const editorLabel = nls.localize('modifiedLabel', "{0} (in file) ↔ {1}", name, name); try { - await TextFileContentProvider.open(uri, COMPARE_WITH_SAVED_SCHEMA, editorLabel, editorService); + await TextFileContentProvider.open(uri, COMPARE_WITH_SAVED_SCHEMA, editorLabel, editorService, { pinned: true }); // Dispose once no more diff editor is opened with the scheme if (registerEditorListener) { providerDisposables.push(editorService.onDidVisibleEditorsChange(() => { @@ -233,7 +244,8 @@ CommandsRegistry.registerCommand({ if (resources.length === 2) { return editorService.openEditor({ leftResource: resources[0], - rightResource: resources[1] + rightResource: resources[1], + options: { pinned: true } }); } @@ -251,7 +263,8 @@ CommandsRegistry.registerCommand({ if (globalResourceToCompare && rightResource) { editorService.openEditor({ leftResource: globalResourceToCompare, - rightResource + rightResource, + options: { pinned: true } }); } } @@ -432,7 +445,9 @@ async function doSaveEditors(accessor: ServicesAccessor, editors: IEditorIdentif try { await editorService.save(editors, options); } catch (error) { - notificationService.error(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))); + if (!isPromiseCanceledError(error)) { + notificationService.error(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))); + } } } diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 160acf7b8..7ea77d336 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -14,12 +14,12 @@ import { IWorkbenchActionRegistry, Extensions as ActionExtensions, CATEGORIES } import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IEditorInputFactory, EditorInput, IFileEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor'; import { AutoSaveConfiguration, HotExitConfiguration, FILES_EXCLUDE_CONFIG, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files'; -import { VIEWLET_ID, SortOrder, FILE_EDITOR_INPUT_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; +import { VIEWLET_ID, SortOrder, FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files'; import { TextFileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/textFileEditorTracker'; import { TextFileSaveErrorHandler } from 'vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { BinaryFileEditor } from 'vs/workbench/contrib/files/browser/editors/binaryFileEditor'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -33,13 +33,16 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { ILabelService } from 'vs/platform/label/common/label'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ExplorerService } from 'vs/workbench/contrib/files/common/explorerService'; +import { ExplorerService, UNDO_REDO_SOURCE } from 'vs/workbench/contrib/files/browser/explorerService'; import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/encoding'; import { Schemas } from 'vs/base/common/network'; import { WorkspaceWatcher } from 'vs/workbench/contrib/files/common/workspaceWatcher'; import { editorConfigurationBaseNode } from 'vs/editor/common/config/commonEditorConfig'; import { DirtyFilesIndicator } from 'vs/workbench/contrib/files/common/dirtyFilesIndicator'; import { isEqual } from 'vs/base/common/resources'; +import { UndoCommand, RedoCommand } from 'vs/editor/browser/editorExtensions'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { IExplorerService } from 'vs/workbench/contrib/files/browser/files'; // Viewlet Action export class OpenExplorerViewletAction extends ShowViewletAction { @@ -102,8 +105,9 @@ Registry.as(EditorExtensions.Editors).registerEditor( // Register default file input factory Registry.as(EditorInputExtensions.EditorInputFactories).registerFileEditorInputFactory({ - createFileEditorInput: (resource, preferredResource, encoding, mode, instantiationService): IFileEditorInput => { - return instantiationService.createInstance(FileEditorInput, resource, preferredResource, encoding, mode); + + createFileEditorInput: (resource, preferredResource, preferredName, preferredDescription, preferredEncoding, preferredMode, instantiationService): IFileEditorInput => { + return instantiationService.createInstance(FileEditorInput, resource, preferredResource, preferredName, preferredDescription, preferredEncoding, preferredMode); }, isFileEditorInput: (obj): obj is IFileEditorInput => { @@ -114,6 +118,8 @@ Registry.as(EditorInputExtensions.EditorInputFactor interface ISerializedFileEditorInput { resourceJSON: UriComponents; preferredResourceJSON?: UriComponents; + name?: string; + description?: string; encoding?: string; modeId?: string; } @@ -132,6 +138,8 @@ class FileEditorInputFactory implements IEditorInputFactory { const serializedFileEditorInput: ISerializedFileEditorInput = { resourceJSON: resource.toJSON(), preferredResourceJSON: isEqual(resource, preferredResource) ? undefined : preferredResource, // only storing preferredResource if it differs from the resource + name: fileEditorInput.getPreferredName(), + description: fileEditorInput.getPreferredDescription(), encoding: fileEditorInput.getEncoding(), modeId: fileEditorInput.getPreferredMode() // only using the preferred user associated mode here if available to not store redundant data }; @@ -144,10 +152,12 @@ class FileEditorInputFactory implements IEditorInputFactory { const serializedFileEditorInput: ISerializedFileEditorInput = JSON.parse(serializedEditorInput); const resource = URI.revive(serializedFileEditorInput.resourceJSON); const preferredResource = URI.revive(serializedFileEditorInput.preferredResourceJSON); + const name = serializedFileEditorInput.name; + const description = serializedFileEditorInput.description; const encoding = serializedFileEditorInput.encoding; const mode = serializedFileEditorInput.modeId; - const fileEditorInput = accessor.get(IEditorService).createEditorInput({ resource, encoding, mode, forceFile: true }) as FileEditorInput; + const fileEditorInput = accessor.get(IEditorService).createEditorInput({ resource, label: name, description, encoding, mode, forceFile: true }) as FileEditorInput; if (preferredResource) { fileEditorInput.setPreferredResource(preferredResource); } @@ -393,6 +403,16 @@ configurationRegistry.registerConfiguration({ 'description': nls.localize({ key: 'openEditorsVisible', comment: ['Open is an adjective'] }, "Number of editors shown in the Open Editors pane. Setting this to 0 hides the Open Editors pane."), 'default': 9 }, + 'explorer.openEditors.sortOrder': { + 'type': 'string', + 'enum': ['editorOrder', 'alphabetical'], + 'description': nls.localize({ key: 'openEditorsSortOrder', comment: ['Open is an adjective'] }, "Controls the sorting order of editors in the Open Editors pane."), + 'enumDescriptions': [ + nls.localize('sortOrder.editorOrder', 'Editors are ordered in the same order editor tabs are shown.'), + nls.localize('sortOrder.alphabetical', 'Editors are ordered in alphabetical order inside each editor group.') + ], + 'default': 'editorOrder' + }, 'explorer.autoReveal': { 'type': ['boolean', 'string'], 'enum': [true, false, 'focusNoScroll'], @@ -406,7 +426,7 @@ configurationRegistry.registerConfiguration({ }, 'explorer.enableDragAndDrop': { 'type': 'boolean', - 'description': nls.localize('enableDragAndDrop', "Controls whether the explorer should allow to move files and folders via drag and drop."), + 'description': nls.localize('enableDragAndDrop', "Controls whether the explorer should allow to move files and folders via drag and drop. This setting only effects drag and drop from inside the explorer."), 'default': true }, 'explorer.confirmDragAndDrop': { @@ -468,3 +488,25 @@ MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { }, order: 1 }); + +UndoCommand.addImplementation(110, (accessor: ServicesAccessor) => { + const undoRedoService = accessor.get(IUndoRedoService); + const explorerService = accessor.get(IExplorerService); + if (explorerService.hasViewFocus()) { + undoRedoService.undo(UNDO_REDO_SOURCE); + return true; + } + + return false; +}); + +RedoCommand.addImplementation(110, (accessor: ServicesAccessor) => { + const undoRedoService = accessor.get(IUndoRedoService); + const explorerService = accessor.get(IExplorerService); + if (explorerService.hasViewFocus()) { + undoRedoService.redo(UNDO_REDO_SOURCE); + return true; + } + + return false; +}); diff --git a/src/vs/workbench/contrib/files/browser/files.ts b/src/vs/workbench/contrib/files/browser/files.ts index ff2019d02..95e137cb3 100644 --- a/src/vs/workbench/contrib/files/browser/files.ts +++ b/src/vs/workbench/contrib/files/browser/files.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { IListService } from 'vs/platform/list/browser/listService'; -import { OpenEditor, IExplorerService } from 'vs/workbench/contrib/files/common/files'; +import { OpenEditor, SortOrder } from 'vs/workbench/contrib/files/common/files'; import { EditorResourceAccessor, SideBySideEditor, IEditorIdentifier } from 'vs/workbench/common/editor'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -13,6 +13,51 @@ import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; 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 { ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService'; + + +export interface IExplorerService { + readonly _serviceBrand: undefined; + readonly roots: ExplorerItem[]; + readonly sortOrder: SortOrder; + + getContext(respectMultiSelection: boolean): ExplorerItem[]; + hasViewFocus(): boolean; + setEditable(stat: ExplorerItem, data: IEditableData | null): Promise; + getEditable(): { stat: ExplorerItem, data: IEditableData } | undefined; + getEditableData(stat: ExplorerItem): IEditableData | undefined; + // If undefined is passed checks if any element is currently being edited. + isEditable(stat: ExplorerItem | undefined): boolean; + findClosest(resource: URI): ExplorerItem | null; + refresh(): Promise; + setToCopy(stats: ExplorerItem[], cut: boolean): Promise; + isCut(stat: ExplorerItem): boolean; + applyBulkEdit(edit: ResourceFileEdit[], options: { undoLabel: string, progressLabel: string }): Promise; + + /** + * Selects and reveal the file element provided by the given resource if its found in the explorer. + * Will try to resolve the path in case the explorer is not yet expanded to the file yet. + */ + select(resource: URI, reveal?: boolean | string): Promise; + + registerView(contextAndRefreshProvider: IExplorerView): void; +} + +export const IExplorerService = createDecorator('explorerService'); + +export interface IExplorerView { + getContext(respectMultiSelection: boolean): ExplorerItem[]; + refresh(recursive: boolean, item?: ExplorerItem): Promise; + selectResource(resource: URI | undefined, reveal?: boolean | string): Promise; + setTreeInput(): Promise; + itemsCopied(tats: ExplorerItem[], cut: boolean, previousCut: ExplorerItem[] | undefined): void; + setEditable(stat: ExplorerItem, isEditing: boolean): Promise; + focusNeighbourIfItemFocused(item: ExplorerItem): void; + isItemVisible(item: ExplorerItem): boolean; + hasFocus(): boolean; +} function getFocus(listService: IListService): unknown | undefined { let list = listService.lastFocusedList; @@ -36,7 +81,7 @@ function getFocus(listService: IListService): unknown | undefined { return undefined; } -// Commands can get exeucted from a command pallete, from a context menu or from some list using a keybinding +// 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)) { diff --git a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css index 7257c3e62..8d645ccaa 100644 --- a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css @@ -76,7 +76,6 @@ } .explorer-viewlet .explorer-item .monaco-icon-name-container.multiple > .label-name > .monaco-highlighted-label { - padding: 1px; border-radius: 3px; } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts b/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts index d3a6797c9..8816edd6e 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts @@ -10,9 +10,9 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IDecorationsProvider, IDecorationData } from 'vs/workbench/services/decorations/browser/decorations'; import { listInvalidItemForeground, listDeemphasizedForeground } from 'vs/platform/theme/common/colorRegistry'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { explorerRootErrorEmitter } from 'vs/workbench/contrib/files/browser/views/explorerViewer'; import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; +import { IExplorerService } from 'vs/workbench/contrib/files/browser/files'; export function provideDecorations(fileStat: ExplorerItem): IDecorationData | undefined { if (fileStat.isRoot && fileStat.isError) { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 3db50a8a9..a9309b467 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -8,9 +8,8 @@ import { URI } from 'vs/base/common/uri'; import * as perf from 'vs/base/common/performance'; import { IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { memoize } from 'vs/base/common/decorators'; -import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, IExplorerService, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, ExplorerResourceAvailableEditorIdsContext } from 'vs/workbench/contrib/files/common/files'; +import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, ExplorerResourceAvailableEditorIdsContext } from 'vs/workbench/contrib/files/common/files'; import { NewFolderAction, NewFileAction, FileCopiedContext, RefreshExplorerView, CollapseExplorerView } from 'vs/workbench/contrib/files/browser/fileActions'; -import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import * as DOM from 'vs/base/browser/dom'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ExplorerDecorationsProvider } from 'vs/workbench/contrib/files/browser/views/explorerDecorationsProvider'; @@ -31,13 +30,13 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { ExplorerDelegate, ExplorerDataSource, FilesRenderer, ICompressedNavigationController, FilesFilter, FileSorter, FileDragAndDrop, ExplorerCompressionDelegate, isCompressedFolderName } from 'vs/workbench/contrib/files/browser/views/explorerViewer'; import { IThemeService, IFileIconTheme } from 'vs/platform/theme/common/themeService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; +import { ITreeContextMenuEvent, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { ResourceLabels } from 'vs/workbench/browser/labels'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; import { FuzzyScore } from 'vs/base/common/filters'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -51,6 +50,8 @@ import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; +import { IExplorerService } from 'vs/workbench/contrib/files/browser/files'; interface IExplorerViewColors extends IColorMapping { listDropBackground?: ColorValue | undefined; @@ -134,6 +135,7 @@ export class ExplorerView extends ViewPane { private styleElement!: HTMLStyleElement; private treeContainer!: HTMLElement; + private container!: HTMLElement; private compressedFocusContext: IContextKey; private compressedFocusFirstContext: IContextKey; private compressedFocusLastContext: IContextKey; @@ -245,6 +247,7 @@ export class ExplorerView extends ViewPane { renderBody(container: HTMLElement): void { super.renderBody(container); + this.container = container; this.treeContainer = DOM.append(container, DOM.$('.explorer-folders-view')); this.styleElement = DOM.createStyleSheet(this.treeContainer); @@ -303,10 +306,18 @@ export class ExplorerView extends ViewPane { } } + hasFocus(): boolean { + return DOM.isAncestor(document.activeElement, this.container); + } + getContext(respectMultiSelection: boolean): ExplorerItem[] { return getContext(this.tree.getFocus(), this.tree.getSelection(), respectMultiSelection, this.renderer); } + isItemVisible(item: ExplorerItem): boolean { + return this.filter.filter(item, TreeVisibility.Visible); + } + async setEditable(stat: ExplorerItem, isEditing: boolean): Promise { if (isEditing) { this.horizontalScrolling = this.tree.options.horizontalScrolling; @@ -337,7 +348,8 @@ export class ExplorerView extends ViewPane { private selectActiveFile(reveal = this.autoReveal): void { if (this.autoReveal) { - const activeFile = this.getActiveFile(); + const activeFile = EditorResourceAccessor.getCanonicalUri(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY }); + if (activeFile) { const focus = this.tree.getFocus(); const selection = this.tree.getSelection(); @@ -458,7 +470,7 @@ export class ExplorerView extends ViewPane { // save view state this._register(this.storageService.onWillSaveState(() => { - this.storageService.store(ExplorerView.TREE_VIEW_STATE_STORAGE_KEY, JSON.stringify(this.tree.getViewState()), StorageScope.WORKSPACE); + this.storageService.store(ExplorerView.TREE_VIEW_STATE_STORAGE_KEY, JSON.stringify(this.tree.getViewState()), StorageScope.WORKSPACE, StorageTarget.MACHINE); })); } @@ -679,18 +691,6 @@ export class ExplorerView extends ViewPane { } } - private getActiveFile(): URI | undefined { - const input = this.editorService.activeEditor; - - // ignore diff editor inputs (helps to get out of diffing when returning to explorer) - if (input instanceof DiffEditorInput) { - return undefined; - } - - // check for files - return input?.resource; - } - public async selectResource(resource: URI | undefined, reveal = this.autoReveal, retry = 0): Promise { // do no retry more than once to prevent inifinite loops in cases of inconsistent model if (retry === 2) { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 9377aea41..9e8330b3d 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -9,17 +9,17 @@ import * as glob from 'vs/base/common/glob'; import { IListVirtualDelegate, ListDragOverEffect } from 'vs/base/browser/ui/list/list'; import { IProgressService, ProgressLocation, IProgressStep, IProgress } from 'vs/platform/progress/common/progress'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IFileService, FileKind, FileOperationError, FileOperationResult, FileSystemProviderCapabilities, BinarySize } from 'vs/platform/files/common/files'; +import { IFileService, FileKind, FileOperationError, FileOperationResult, FileSystemProviderCapabilities, ByteSize } from 'vs/platform/files/common/files'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IDisposable, Disposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IFileLabelOptions, IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; -import { ITreeNode, ITreeFilter, TreeVisibility, TreeFilterResult, IAsyncDataSource, ITreeSorter, ITreeDragAndDrop, ITreeDragOverReaction, TreeDragOverBubble } from 'vs/base/browser/ui/tree/tree'; +import { ITreeNode, ITreeFilter, TreeVisibility, IAsyncDataSource, ITreeSorter, ITreeDragAndDrop, ITreeDragOverReaction, TreeDragOverBubble } from 'vs/base/browser/ui/tree/tree'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { IFilesConfiguration, IExplorerService, VIEW_ID } from 'vs/workbench/contrib/files/common/files'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IFilesConfiguration, VIEW_ID } from 'vs/workbench/contrib/files/common/files'; import { dirname, joinPath, basename, distinctParents } from 'vs/base/common/resources'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { localize } from 'vs/nls'; @@ -37,11 +37,10 @@ import { Schemas } from 'vs/base/common/network'; import { NativeDragAndDropData, ExternalElementsDragAndDropData, ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { isMacintosh, isWeb } from 'vs/base/common/platform'; import { IDialogService, IConfirmation, getFileNamesMessage } from 'vs/platform/dialogs/common/dialogs'; -import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { URI } from 'vs/base/common/uri'; -import { ITask, RunOnceWorker, sequence } from 'vs/base/common/async'; +import { RunOnceWorker } from 'vs/base/common/async'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; import { findValidPasteFileTarget } from 'vs/workbench/contrib/files/browser/fileActions'; @@ -58,6 +57,8 @@ import { IEditableData } from 'vs/workbench/common/views'; import { IEditorInput } from 'vs/workbench/common/editor'; import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService'; +import { IExplorerService } from 'vs/workbench/contrib/files/browser/files'; export class ExplorerDelegate implements IListVirtualDelegate { @@ -532,7 +533,7 @@ interface CachedParsedExpression { */ export class FilesFilter implements ITreeFilter { private hiddenExpressionPerRoot: Map; - private hiddenUris = new Set(); + private uriVisibilityMap = new Map(); private editorsAffectingFilter = new Set(); private _onDidChange = new Emitter(); private toDispose: IDisposable[] = []; @@ -554,13 +555,15 @@ export class FilesFilter implements ITreeFilter { this.toDispose.push(this.editorService.onDidVisibleEditorsChange(() => { const editors = this.editorService.visibleEditors; let shouldFire = false; - this.hiddenUris.forEach(u => { - editors.forEach(e => { - if (e.resource && this.uriIdentityService.extUri.isEqualOrParent(e.resource, u)) { - // A filtered resource suddenly became visible since user opened an editor - shouldFire = true; - } - }); + this.uriVisibilityMap.forEach((visible, uri) => { + if (!visible) { + editors.forEach(e => { + if (e.resource && this.uriIdentityService.extUri.isEqualOrParent(e.resource, uri)) { + // A filtered resource suddenly became visible since user opened an editor + shouldFire = true; + } + }); + } }); this.editorsAffectingFilter.forEach(e => { @@ -571,7 +574,7 @@ export class FilesFilter implements ITreeFilter { }); if (shouldFire) { this.editorsAffectingFilter.clear(); - this.hiddenUris.clear(); + this.uriVisibilityMap.clear(); this._onDidChange.fire(); } })); @@ -600,19 +603,20 @@ export class FilesFilter implements ITreeFilter { if (shouldFire) { this.editorsAffectingFilter.clear(); - this.hiddenUris.clear(); + this.uriVisibilityMap.clear(); this._onDidChange.fire(); } } - filter(stat: ExplorerItem, parentVisibility: TreeVisibility): TreeFilterResult { - const isVisible = this.isVisible(stat, parentVisibility); - if (isVisible) { - this.hiddenUris.delete(stat.resource); - } else { - this.hiddenUris.add(stat.resource); + filter(stat: ExplorerItem, parentVisibility: TreeVisibility): boolean { + const cachedVisibility = this.uriVisibilityMap.get(stat.resource); + if (typeof cachedVisibility === 'boolean') { + return cachedVisibility; } + const isVisible = this.isVisible(stat, parentVisibility); + this.uriVisibilityMap.set(stat.resource, isVisible); + return isVisible; } @@ -806,7 +810,6 @@ export class FileDragAndDrop implements ITreeDragAndDrop { @IFileService private fileService: IFileService, @IConfigurationService private configurationService: IConfigurationService, @IInstantiationService private instantiationService: IInstantiationService, - @IWorkingCopyFileService private workingCopyFileService: IWorkingCopyFileService, @IHostService private hostService: IHostService, @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService, @IProgressService private readonly progressService: IProgressService, @@ -868,7 +871,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // Native DND if (isNative) { - if (!containsDragType(originalEvent, DataTransfers.FILES, CodeDataTransfers.FILES)) { + if (!containsDragType(originalEvent, DataTransfers.FILES, CodeDataTransfers.FILES, DataTransfers.RESOURCES)) { return false; } } @@ -972,14 +975,14 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // The only custom data transfer we set from the explorer is a file transfer // to be able to DND between multiple code file explorers across windows - const fileResources = items.filter(s => !s.isDirectory && s.resource.scheme === Schemas.file).map(r => r.resource.fsPath); + const fileResources = items.filter(s => s.resource.scheme === Schemas.file).map(r => r.resource.fsPath); if (fileResources.length) { originalEvent.dataTransfer.setData(CodeDataTransfers.FILES, JSON.stringify(fileResources)); } } } - drop(data: IDragAndDropData, target: ExplorerItem | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void { + async drop(data: IDragAndDropData, target: ExplorerItem | undefined, targetIndex: number | undefined, originalEvent: DragEvent): Promise { this.compressedDropTargetDisposable.dispose(); // Find compressed target @@ -1010,26 +1013,29 @@ export class FileDragAndDrop implements ITreeDragAndDrop { if (data instanceof NativeDragAndDropData) { const cts = new CancellationTokenSource(); - // Indicate progress globally - const dropPromise = this.progressService.withProgress({ - location: ProgressLocation.Window, - delay: 800, - cancellable: true, - title: isWeb ? localize('uploadingFiles', "Uploading") : localize('copyingFiles', "Copying") - }, async progress => { - try { - if (isWeb) { - await this.handleWebExternalDrop(data, resolvedTarget, originalEvent, progress, cts.token); - } else { - await this.handleExternalDrop(data, resolvedTarget, originalEvent, progress, cts.token); + if (isWeb) { + // Indicate progress globally + const dropPromise = this.progressService.withProgress({ + location: ProgressLocation.Window, + delay: 800, + cancellable: true, + title: localize('uploadingFiles', "Uploading") + }, async progress => { + try { + await this.handleWebExternalDrop(resolvedTarget, originalEvent, progress, cts.token); + } catch (error) { + this.notificationService.warn(error); } + }, () => cts.dispose(true)); + // Also indicate progress in the files view + this.progressService.withProgress({ location: VIEW_ID, delay: 500 }, () => dropPromise); + } else { + try { + await this.handleExternalDrop(resolvedTarget, originalEvent, cts.token); } catch (error) { this.notificationService.warn(error); } - }, () => cts.dispose(true)); - - // Also indicate progress in the files view - this.progressService.withProgress({ location: VIEW_ID, delay: 800 }, () => dropPromise); + } } // In-Explorer DND (Move/Copy file) else { @@ -1037,7 +1043,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { } } - private async handleWebExternalDrop(data: NativeDragAndDropData, target: ExplorerItem, originalEvent: DragEvent, progress: IProgress, token: CancellationToken): Promise { + private async handleWebExternalDrop(target: ExplorerItem, originalEvent: DragEvent, progress: IProgress, token: CancellationToken): Promise { const items = (originalEvent.dataTransfer as unknown as IWebkitDataTransfer).items; // Somehow the items thing is being modified at random, maybe as a security @@ -1071,7 +1077,10 @@ export class FileDragAndDrop implements ITreeDragAndDrop { continue; } - await this.workingCopyFileService.delete([joinPath(target.resource, entry.name)], { recursive: true }); + await this.explorerService.applyBulkEdit([new ResourceFileEdit(joinPath(target.resource, entry.name), undefined, { recursive: true })], { + undoLabel: localize('overwrite', "Overwrite {0}", entry.name), + progressLabel: localize('overwriting', "Overwriting {0}", entry.name), + }); if (token.isCancellationRequested) { break; @@ -1109,17 +1118,17 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // Small file let message: string; - if (fileSize < BinarySize.MB) { + if (fileSize < ByteSize.MB) { if (operation.filesTotal === 1) { message = `${entry.name}`; } else { - message = localize('uploadProgressSmallMany', "{0} of {1} files ({2}/s)", operation.filesUploaded, operation.filesTotal, BinarySize.formatSize(bytesUploadedPerSecond)); + message = localize('uploadProgressSmallMany', "{0} of {1} files ({2}/s)", operation.filesUploaded, operation.filesTotal, ByteSize.formatSize(bytesUploadedPerSecond)); } } // Large file else { - message = localize('uploadProgressLarge', "{0} ({1} of {2}, {3}/s)", entry.name, BinarySize.formatSize(fileBytesUploaded), BinarySize.formatSize(fileSize), BinarySize.formatSize(bytesUploadedPerSecond)); + message = localize('uploadProgressLarge', "{0} ({1} of {2}, {3}/s)", entry.name, ByteSize.formatSize(fileBytesUploaded), ByteSize.formatSize(fileSize), ByteSize.formatSize(bytesUploadedPerSecond)); } // Report progress but limit to update only once per second @@ -1137,12 +1146,13 @@ export class FileDragAndDrop implements ITreeDragAndDrop { return undefined; } - // Chrome/Edge/Firefox support stream method - if (typeof file.stream === 'function') { + // Chrome/Edge/Firefox support stream method, but only use it for + // larger files to reduce the overhead of the streaming approach + if (typeof file.stream === 'function' && file.size > ByteSize.MB) { await this.doUploadWebFileEntryBuffered(resource, file, reportProgress, token); } - // Fallback to unbuffered upload for other browsers + // Fallback to unbuffered upload for other browsers or small files else { await this.doUploadWebFileEntryUnbuffered(resource, file, reportProgress); } @@ -1258,7 +1268,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { }); } - private async handleExternalDrop(data: NativeDragAndDropData, target: ExplorerItem, originalEvent: DragEvent, progress: IProgress, token: CancellationToken): Promise { + private async handleExternalDrop(target: ExplorerItem, originalEvent: DragEvent, token: CancellationToken): Promise { // Check for dropped external files to be folders const droppedResources = extractResources(originalEvent, true); @@ -1271,9 +1281,9 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // Pass focus to window this.hostService.focus(); - // Handle folders by adding to workspace if we are in workspace context + // Handle folders by adding to workspace if we are in workspace context and if dropped on top const folders = result.filter(r => r.success && r.stat && r.stat.isDirectory).map(result => ({ uri: result.stat!.resource })); - if (folders.length > 0) { + if (folders.length > 0 && target.isRoot) { const buttons = [ folders.length > 1 ? localize('copyFolders', "&&Copy Folders") : localize('copyFolder', "&&Copy Folder"), localize('cancel', "Cancel") @@ -1292,7 +1302,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { return this.workspaceEditingService.addFolders(folders); } if (choice === buttons.length - 2) { - return this.addResources(target, droppedResources.map(res => res.resource), progress, token); + return this.addResources(target, droppedResources.map(res => res.resource), token); } return undefined; @@ -1300,11 +1310,11 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // Handle dropped files (only support FileStat as target) else if (target instanceof ExplorerItem) { - return this.addResources(target, droppedResources.map(res => res.resource), progress, token); + return this.addResources(target, droppedResources.map(res => res.resource), token); } } - private async addResources(target: ExplorerItem, resources: URI[], progress: IProgress, token: CancellationToken): Promise { + private async addResources(target: ExplorerItem, resources: URI[], token: CancellationToken): Promise { if (resources && resources.length > 0) { // Resolve target to check for name collisions and ask user @@ -1323,36 +1333,33 @@ export class FileDragAndDrop implements ITreeDragAndDrop { }); } - // Run add in sequence - const addPromisesFactory: ITask>[] = []; - await Promise.all(resources.map(async resource => { + const resourcesFiltered = (await Promise.all(resources.map(async resource => { if (targetNames.has(caseSensitive ? basename(resource) : basename(resource).toLowerCase())) { const confirmationResult = await this.dialogService.confirm(getFileOverwriteConfirm(basename(resource))); if (!confirmationResult.confirmed) { - return; + return undefined; } } + return resource; + }))).filter(r => r instanceof URI) as URI[]; + const resourceFileEdits = resourcesFiltered.map(resource => { + const sourceFileName = basename(resource); + const targetFile = joinPath(target.resource, sourceFileName); + return new ResourceFileEdit(resource, targetFile, { overwrite: true, copy: true }); + }); - addPromisesFactory.push(async () => { - if (token.isCancellationRequested) { - return; - } + await this.explorerService.applyBulkEdit(resourceFileEdits, { + undoLabel: resourcesFiltered.length === 1 ? localize('copyFile', "Copy {0}", basename(resourcesFiltered[0])) : localize('copynFile', "Copy {0} resources", resourcesFiltered.length), + progressLabel: resourcesFiltered.length === 1 ? localize('copyingFile', "Copying {0}", basename(resourcesFiltered[0])) : localize('copyingnFile', "Copying {0} resources", resourcesFiltered.length) + }); - const sourceFile = resource; - const sourceFileName = basename(sourceFile); - const targetFile = joinPath(target.resource, sourceFileName); - - progress.report({ message: sourceFileName }); - - const stat = (await this.workingCopyFileService.copy([{ source: sourceFile, target: targetFile }], { overwrite: true }))[0]; - // if we only add one file, just open it directly - if (resources.length === 1 && !stat.isDirectory) { - this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } }); - } - }); - })); - - await sequence(addPromisesFactory); + // if we only add one file, just open it directly + if (resourceFileEdits.length === 1) { + const item = this.explorerService.findClosest(resourceFileEdits[0].newResource!); + if (item && !item.isDirectory) { + this.editorService.openEditor({ resource: item.resource, options: { pinned: true } }); + } + } } } @@ -1386,7 +1393,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // Check for confirmation checkbox if (confirmation.checkboxChecked === true) { - await this.configurationService.updateValue(FileDragAndDrop.CONFIRM_DND_SETTING_KEY, false, ConfigurationTarget.USER); + await this.configurationService.updateValue(FileDragAndDrop.CONFIRM_DND_SETTING_KEY, false); } } @@ -1436,9 +1443,17 @@ export class FileDragAndDrop implements ITreeDragAndDrop { private async doHandleExplorerDropOnCopy(sources: ExplorerItem[], target: ExplorerItem): Promise { // Reuse duplicate action when user copies const incrementalNaming = this.configurationService.getValue().explorer.incrementalNaming; - const sourceTargetPairs = sources.map(({ resource, isDirectory }) => ({ source: resource, target: findValidPasteFileTarget(this.explorerService, target, { resource, isDirectory, allowOverwrite: false }, incrementalNaming) })); - const stats = await this.workingCopyFileService.copy(sourceTargetPairs); - const editors = stats.filter(stat => !stat.isDirectory).map(({ resource }) => ({ resource, options: { pinned: true } })); + const resourceFileEdits = sources.map(({ resource, isDirectory }) => (new ResourceFileEdit(resource, findValidPasteFileTarget(this.explorerService, target, { resource, isDirectory, allowOverwrite: false }, incrementalNaming), { copy: true }))); + const labelSufix = getFileOrFolderLabelSufix(sources); + await this.explorerService.applyBulkEdit(resourceFileEdits, { + undoLabel: localize('copy', "Copy {0}", labelSufix), + progressLabel: localize('copying', "Copying {0}", labelSufix), + }); + + const editors = resourceFileEdits.filter(edit => { + const item = edit.newResource ? this.explorerService.findClosest(edit.newResource) : undefined; + return item && !item.isDirectory; + }).map(edit => ({ resource: edit.newResource, options: { pinned: true } })); await this.editorService.openEditors(editors); } @@ -1446,18 +1461,23 @@ export class FileDragAndDrop implements ITreeDragAndDrop { private async doHandleExplorerDropOnMove(sources: ExplorerItem[], target: ExplorerItem): Promise { // Do not allow moving readonly items - const sourceTargetPairs = sources.filter(source => !source.isReadonly).map(source => ({ source: source.resource, target: joinPath(target.resource, source.name) })); + const resourceFileEdits = sources.filter(source => !source.isReadonly).map(source => new ResourceFileEdit(source.resource, joinPath(target.resource, source.name))); + const labelSufix = getFileOrFolderLabelSufix(sources); + const options = { + undoLabel: localize('move', "Move {0}", labelSufix), + progressLabel: localize('moving', "Moving {0}", labelSufix) + }; try { - await this.workingCopyFileService.move(sourceTargetPairs); + await this.explorerService.applyBulkEdit(resourceFileEdits, options); } catch (error) { // Conflict if ((error).fileOperationResult === FileOperationResult.FILE_MOVE_CONFLICT) { const overwrites: URI[] = []; - for (const { target } of sourceTargetPairs) { - if (await this.fileService.exists(target)) { - overwrites.push(target); + for (const edit of resourceFileEdits) { + if (edit.newResource && await this.fileService.exists(edit.newResource)) { + overwrites.push(edit.newResource); } } @@ -1466,7 +1486,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { const { confirmed } = await this.dialogService.confirm(confirm); if (confirmed) { try { - await this.workingCopyFileService.move(sourceTargetPairs, { overwrite: true }); + await this.explorerService.applyBulkEdit(resourceFileEdits.map(re => new ResourceFileEdit(re.oldResource, re.newResource, { overwrite: true })), options); } catch (error) { this.notificationService.error(error); } @@ -1550,3 +1570,18 @@ export class ExplorerCompressionDelegate implements ITreeCompressionDelegate i.isDirectory)) { + return `${items.length} folders`; + } + if (items.every(i => !i.isDirectory)) { + return `${items.length} files`; + } + + return `${items.length} files and folders`; +} diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 5c1e9e59b..3535108c3 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -48,6 +48,7 @@ import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { Orientation } from 'vs/base/browser/ui/splitview/splitview'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { compareFileNamesDefault } from 'vs/base/common/comparers'; const $ = dom.$; @@ -64,6 +65,8 @@ export class OpenEditorsView extends ViewPane { private listLabels: ResourceLabels | undefined; private contributedContextMenu!: IMenu; private needsRefresh = false; + private elements: (OpenEditor | IEditorGroup)[] = []; + private sortOrder: 'editorOrder' | 'alphabetical'; private resourceContext!: ResourceContextKey; private groupFocusedContext!: IContextKey; private dirtyEditorFocusedContext!: IContextKey; @@ -91,13 +94,14 @@ export class OpenEditorsView extends ViewPane { this.structuralRefreshDelay = 0; this.listRefreshScheduler = new RunOnceScheduler(() => { const previousLength = this.list.length; - this.list.splice(0, this.list.length, this.elements); + this.list.splice(0, this.list.length, this.getElements()); this.focusActiveEditor(); if (previousLength !== this.list.length) { this.updateSize(); } this.needsRefresh = false; }, this.structuralRefreshDelay); + this.sortOrder = configurationService.getValue('explorer.openEditors.sortOrder'); this.registerUpdateEvents(); @@ -132,7 +136,7 @@ export class OpenEditorsView extends ViewPane { const index = this.getIndex(group, e.editor); switch (e.kind) { case GroupChangeKind.GROUP_INDEX: { - if (this.showGroups) { + if (index >= 0) { this.list.splice(index, 1, [group]); } break; @@ -149,19 +153,10 @@ export class OpenEditorsView extends ViewPane { this.list.splice(index, 1, [new OpenEditor(e.editor!, group)]); break; } - case GroupChangeKind.EDITOR_OPEN: { - this.list.splice(index, 0, [new OpenEditor(e.editor!, group)]); - setTimeout(() => this.updateSize(), this.structuralRefreshDelay); - break; - } - case GroupChangeKind.EDITOR_CLOSE: { - const previousIndex = this.getIndex(group, undefined) + (e.editorIndex || 0) + (this.showGroups ? 1 : 0); - this.list.splice(previousIndex, 1); - this.updateSize(); - break; - } + case GroupChangeKind.EDITOR_OPEN: + case GroupChangeKind.EDITOR_CLOSE: case GroupChangeKind.EDITOR_MOVE: { - this.listRefreshScheduler.schedule(); + updateWholeList(); break; } } @@ -272,20 +267,16 @@ export class OpenEditorsView extends ViewPane { })); const resourceNavigator = this._register(new ListResourceNavigator(this.list, { configurationService: this.configurationService })); this._register(resourceNavigator.onDidOpen(e => { - if (typeof e.element !== 'number') { + if (!e.element) { return; - } - - const element = this.list.element(e.element); - - if (element instanceof OpenEditor) { + } else if (e.element instanceof OpenEditor) { if (e.browserEvent instanceof MouseEvent && e.browserEvent.button === 1) { return; // middle click already handled above: closes the editor } - this.openEditor(element, { preserveFocus: e.editorOptions.preserveFocus, pinned: e.editorOptions.pinned, sideBySide: e.sideBySide }); + this.openEditor(e.element, { preserveFocus: e.editorOptions.preserveFocus, pinned: e.editorOptions.pinned, sideBySide: e.sideBySide }); } else { - this.editorGroupService.activateGroup(element); + this.editorGroupService.activateGroup(e.element); } })); @@ -331,33 +322,28 @@ export class OpenEditorsView extends ViewPane { return this.editorGroupService.groups.length > 1; } - private get elements(): Array { - const result: Array = []; + private getElements(): Array { + this.elements = []; this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE).forEach(g => { if (this.showGroups) { - result.push(g); + this.elements.push(g); } - result.push(...g.editors.map(ei => new OpenEditor(ei, g))); + let editors = g.editors.map(ei => new OpenEditor(ei, g)); + if (this.sortOrder === 'alphabetical') { + editors = editors.sort((first, second) => compareFileNamesDefault(first.editor.getName(), second.editor.getName())); + } + this.elements.push(...editors); }); - return result; + return this.elements; } private getIndex(group: IEditorGroup, editor: IEditorInput | undefined | null): number { - let index = editor ? group.getIndexOfEditor(editor) : 0; - if (!this.showGroups) { - return index; + if (!editor) { + return this.elements.findIndex(e => !(e instanceof OpenEditor) && e.id === group.id); } - for (let g of this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)) { - if (g.id === group.id) { - return index + (!!editor ? 1 : 0); - } else { - index += g.count + 1; - } - } - - return -1; + return this.elements.findIndex(e => e instanceof OpenEditor && e.editor === editor && e.group.id === group.id); } private openEditor(element: OpenEditor, options: { preserveFocus?: boolean; pinned?: boolean; sideBySide?: boolean; }): void { @@ -384,7 +370,7 @@ export class OpenEditorsView extends ViewPane { this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => actions, - getActionsContext: () => element instanceof OpenEditor ? { groupId: element.groupId, editorIndex: element.editorIndex } : { groupId: element.id }, + getActionsContext: () => element instanceof OpenEditor ? { groupId: element.groupId, editorIndex: element.group.getIndexOfEditor(element.editor) } : { groupId: element.id }, onHide: () => dispose(actionsDisposable) }); } @@ -393,9 +379,13 @@ export class OpenEditorsView extends ViewPane { if (this.list.length && this.editorGroupService.activeGroup) { const index = this.getIndex(this.editorGroupService.activeGroup, this.editorGroupService.activeGroup.activeEditor); if (index >= 0) { - this.list.setFocus([index]); - this.list.setSelection([index]); - this.list.reveal(index); + try { + this.list.setFocus([index]); + this.list.setSelection([index]); + this.list.reveal(index); + } catch (e) { + // noop list updated in the meantime + } return; } } @@ -408,9 +398,9 @@ export class OpenEditorsView extends ViewPane { if (event.affectsConfiguration('explorer.openEditors')) { this.updateSize(); } - - // Trigger a 'repaint' when decoration settings change - if (event.affectsConfiguration('explorer.decorations')) { + // Trigger a 'repaint' when decoration settings change or the sort order changed + if (event.affectsConfiguration('explorer.decorations') || event.affectsConfiguration('explorer.openEditors.sortOrder')) { + this.sortOrder = this.configurationService.getValue('explorer.openEditors.sortOrder'); this.listRefreshScheduler.schedule(); } } @@ -500,7 +490,7 @@ class OpenEditorActionRunner extends ActionRunner { return; } - return super.run(action, { groupId: this.editor.groupId, editorIndex: this.editor.editorIndex }); + return super.run(action, { groupId: this.editor.groupId, editorIndex: this.editor.group.getIndexOfEditor(this.editor.editor) }); } } @@ -687,7 +677,7 @@ class OpenEditorsDragAndDrop implements IListDragAndDrop; - getEditable(): { stat: ExplorerItem, data: IEditableData } | undefined; - getEditableData(stat: ExplorerItem): IEditableData | undefined; - // If undefined is passed checks if any element is currently being edited. - isEditable(stat: ExplorerItem | undefined): boolean; - findClosest(resource: URI): ExplorerItem | null; - refresh(): Promise; - setToCopy(stats: ExplorerItem[], cut: boolean): Promise; - isCut(stat: ExplorerItem): boolean; - - /** - * Selects and reveal the file element provided by the given resource if its found in the explorer. - * Will try to resolve the path in case the explorer is not yet expanded to the file yet. - */ - select(resource: URI, reveal?: boolean | string): Promise; - - registerView(contextAndRefreshProvider: IExplorerView): void; -} - -export interface IExplorerView { - getContext(respectMultiSelection: boolean): ExplorerItem[]; - refresh(recursive: boolean, item?: ExplorerItem): Promise; - selectResource(resource: URI | undefined, reveal?: boolean | string): Promise; - setTreeInput(): Promise; - itemsCopied(tats: ExplorerItem[], cut: boolean, previousCut: ExplorerItem[] | undefined): void; - setEditable(stat: ExplorerItem, isEditing: boolean): Promise; - focusNeighbourIfItemFocused(item: ExplorerItem): void; -} - -export const IExplorerService = createDecorator('explorerService'); - /** * Context Keys to use with keybindings for the Explorer and Open Editors view */ @@ -116,6 +76,7 @@ export interface IFilesConfiguration extends PlatformIFilesConfiguration, IWorkb explorer: { openEditors: { visible: number; + sortOrder: 'editorOrder' | 'alphabetical'; }; autoReveal: boolean | 'focusNoScroll'; enableDragAndDrop: boolean; @@ -230,18 +191,17 @@ export class TextFileContentProvider extends Disposable implements ITextModelCon export class OpenEditor implements IEditorIdentifier { + private id: number; + private static COUNTER = 0; + constructor(private _editor: IEditorInput, private _group: IEditorGroup) { - // noop + this.id = OpenEditor.COUNTER++; } get editor() { return this._editor; } - get editorIndex() { - return this._group.getIndexOfEditor(this.editor); - } - get group() { return this._group; } @@ -251,7 +211,7 @@ export class OpenEditor implements IEditorIdentifier { } getId(): string { - return `openeditor:${this.groupId}:${this.editorIndex}:${this.editor.getName()}:${this.editor.getDescription()}`; + return `openeditor:${this.groupId}:${this.id}`; } isPreview(): boolean { diff --git a/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts b/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts index 086d9c9dd..72211dd04 100644 --- a/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts @@ -14,18 +14,19 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co 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 } from 'vs/workbench/contrib/files/browser/files'; +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'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { appendToCommandPalette, appendEditorTitleContextMenuItem } from 'vs/workbench/contrib/files/browser/fileActions.contribution'; -import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { SideBySideEditor, EditorResourceAccessor } from 'vs/workbench/common/editor'; +import { ContextKeyOrExpr } from 'vs/platform/contextkey/common/contextkey'; const REVEAL_IN_OS_COMMAND_ID = 'revealFileInOS'; const REVEAL_IN_OS_LABEL = isWindows ? nls.localize('revealInWindows', "Reveal in File Explorer") : isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder"); +const REVEAL_IN_OS_WHEN_CONTEXT = ContextKeyOrExpr.create([ResourceContextKey.Scheme.isEqualTo(Schemas.file), ResourceContextKey.Scheme.isEqualTo(Schemas.userData)]); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: REVEAL_IN_OS_COMMAND_ID, @@ -57,19 +58,19 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, ResourceContextKey.Scheme.isEqualTo(Schemas.file)); +appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, REVEAL_IN_OS_WHEN_CONTEXT); // Menu registration - open editors const revealInOsCommand = { id: REVEAL_IN_OS_COMMAND_ID, - title: isWindows ? nls.localize('revealInWindows', "Reveal in File Explorer") : isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder") + title: REVEAL_IN_OS_LABEL }; MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { group: 'navigation', order: 20, command: revealInOsCommand, - when: ResourceContextKey.IsFileSystemResource + when: REVEAL_IN_OS_WHEN_CONTEXT }); // Menu registration - explorer @@ -78,10 +79,10 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { group: 'navigation', order: 20, command: revealInOsCommand, - when: ResourceContextKey.Scheme.isEqualTo(Schemas.file) + when: REVEAL_IN_OS_WHEN_CONTEXT }); // Command Palette const category = { value: nls.localize('filesCategory', "File"), original: 'File' }; -appendToCommandPalette(REVEAL_IN_OS_COMMAND_ID, { value: REVEAL_IN_OS_LABEL, original: isWindows ? 'Reveal in File Explorer' : isMacintosh ? 'Reveal in Finder' : 'Open Containing Folder' }, category, ResourceContextKey.Scheme.isEqualTo(Schemas.file)); +appendToCommandPalette(REVEAL_IN_OS_COMMAND_ID, { value: REVEAL_IN_OS_LABEL, original: isWindows ? 'Reveal in File Explorer' : isMacintosh ? 'Reveal in Finder' : 'Open Containing Folder' }, category, REVEAL_IN_OS_WHEN_CONTEXT); diff --git a/src/vs/workbench/contrib/files/electron-sandbox/textFileEditor.ts b/src/vs/workbench/contrib/files/electron-sandbox/textFileEditor.ts index c6655f263..d310e9872 100644 --- a/src/vs/workbench/contrib/files/electron-sandbox/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/electron-sandbox/textFileEditor.ts @@ -21,9 +21,9 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { IExplorerService } from 'vs/workbench/contrib/files/browser/files'; /** * An implementation of editor for file system resources. diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts index 8e0e190bf..0df2ed5d5 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -24,12 +24,16 @@ suite('Files - FileEditorInput', () => { let instantiationService: IInstantiationService; let accessor: TestServiceAccessor; + function createFileInput(resource: URI, preferredResource?: URI, preferredMode?: string, preferredName?: string, preferredDescription?: string): FileEditorInput { + return instantiationService.createInstance(FileEditorInput, resource, preferredResource, preferredName, preferredDescription, undefined, preferredMode); + } + setup(() => { instantiationService = workbenchInstantiationService({ editorService: () => { return new class extends TestEditorService { createEditorInput(input: IResourceEditorInput) { - return instantiationService.createInstance(FileEditorInput, input.resource, undefined, undefined, undefined); + return createFileInput(input.resource); } }; } @@ -39,9 +43,9 @@ suite('Files - FileEditorInput', () => { }); test('Basics', async function () { - let input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined, undefined, undefined); - const otherInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, 'foo/bar/otherfile.js'), undefined, undefined, undefined); - const otherInputSame = instantiationService.createInstance(FileEditorInput, toResource.call(this, 'foo/bar/file.js'), undefined, undefined, undefined); + let input = createFileInput(toResource.call(this, '/foo/bar/file.js')); + const otherInput = createFileInput(toResource.call(this, 'foo/bar/otherfile.js')); + const otherInputSame = createFileInput(toResource.call(this, 'foo/bar/file.js')); assert(input.matches(input)); assert(input.matches(otherInputSame)); @@ -56,10 +60,10 @@ suite('Files - FileEditorInput', () => { assert.strictEqual(toResource.call(this, '/foo/bar/file.js').fsPath, input.resource.fsPath); assert(input.resource instanceof URI); - input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar.html'), undefined, undefined, undefined); + input = createFileInput(toResource.call(this, '/foo/bar.html')); - const inputToResolve: FileEditorInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined, undefined, undefined); - const sameOtherInput: FileEditorInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined, undefined, undefined); + const inputToResolve: FileEditorInput = createFileInput(toResource.call(this, '/foo/bar/file.js')); + const sameOtherInput: FileEditorInput = createFileInput(toResource.call(this, '/foo/bar/file.js')); let resolved = await inputToResolve.resolve(); assert.ok(inputToResolve.isResolved()); @@ -97,11 +101,11 @@ suite('Files - FileEditorInput', () => { const resource = toResource.call(this, '/foo/bar/updatefile.js'); const preferredResource = toResource.call(this, '/foo/bar/UPDATEFILE.js'); - const inputWithoutPreferredResource = instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined); + const inputWithoutPreferredResource = createFileInput(resource); assert.equal(inputWithoutPreferredResource.resource.toString(), resource.toString()); assert.equal(inputWithoutPreferredResource.preferredResource.toString(), resource.toString()); - const inputWithPreferredResource = instantiationService.createInstance(FileEditorInput, resource, preferredResource, undefined, undefined); + const inputWithPreferredResource = createFileInput(resource, preferredResource); assert.equal(inputWithPreferredResource.resource.toString(), resource.toString()); assert.equal(inputWithPreferredResource.preferredResource.toString(), preferredResource.toString()); @@ -130,7 +134,7 @@ suite('Files - FileEditorInput', () => { id: mode, }); - const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined, undefined, mode); + const input = createFileInput(toResource.call(this, '/foo/bar/file.js'), undefined, mode); assert.equal(input.getPreferredMode(), mode); const model = await input.resolve() as TextFileEditorModel; @@ -140,7 +144,7 @@ suite('Files - FileEditorInput', () => { assert.equal(input.getPreferredMode(), 'text'); assert.equal(model.textEditorModel!.getModeId(), PLAINTEXT_MODE_ID); - const input2 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined, undefined, undefined); + const input2 = createFileInput(toResource.call(this, '/foo/bar/file.js')); input2.setPreferredMode(mode); const model2 = await input2.resolve() as TextFileEditorModel; @@ -148,10 +152,10 @@ suite('Files - FileEditorInput', () => { }); test('matches', function () { - const input1 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined, undefined); - const input2 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined, undefined); - const input3 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/other.js'), undefined, undefined, undefined); - const input2Upper = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/UPDATEFILE.js'), undefined, undefined, undefined); + const input1 = createFileInput(toResource.call(this, '/foo/bar/updatefile.js')); + const input2 = createFileInput(toResource.call(this, '/foo/bar/updatefile.js')); + const input3 = createFileInput(toResource.call(this, '/foo/bar/other.js')); + const input2Upper = createFileInput(toResource.call(this, '/foo/bar/UPDATEFILE.js')); assert.strictEqual(input1.matches(null), false); assert.strictEqual(input1.matches(input1), true); @@ -162,7 +166,7 @@ suite('Files - FileEditorInput', () => { }); test('getEncoding/setEncoding', async function () { - const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined, undefined); + const input = createFileInput(toResource.call(this, '/foo/bar/updatefile.js')); input.setEncoding('utf16', EncodingMode.Encode); assert.equal(input.getEncoding(), 'utf16'); @@ -173,7 +177,7 @@ suite('Files - FileEditorInput', () => { }); test('save', async function () { - const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined, undefined); + const input = createFileInput(toResource.call(this, '/foo/bar/updatefile.js')); const resolved = await input.resolve() as TextFileEditorModel; resolved.textEditorModel!.setValue('changed'); @@ -185,7 +189,7 @@ suite('Files - FileEditorInput', () => { }); test('revert', async function () { - const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined, undefined); + const input = createFileInput(toResource.call(this, '/foo/bar/updatefile.js')); const resolved = await input.resolve() as TextFileEditorModel; resolved.textEditorModel!.setValue('changed'); @@ -201,7 +205,7 @@ suite('Files - FileEditorInput', () => { }); test('resolve handles binary files', async function () { - const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined, undefined); + const input = createFileInput(toResource.call(this, '/foo/bar/updatefile.js')); accessor.textFileService.setResolveTextContentErrorOnce(new TextFileOperationError('error', TextFileOperationResult.FILE_IS_BINARY)); @@ -211,7 +215,7 @@ suite('Files - FileEditorInput', () => { }); test('resolve handles too large files', async function () { - const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined, undefined); + const input = createFileInput(toResource.call(this, '/foo/bar/updatefile.js')); accessor.textFileService.setResolveTextContentErrorOnce(new FileOperationError('error', FileOperationResult.FILE_TOO_LARGE)); @@ -221,7 +225,7 @@ suite('Files - FileEditorInput', () => { }); test('attaches to model when created and reports dirty', async function () { - const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined, undefined); + const input = createFileInput(toResource.call(this, '/foo/bar/updatefile.js')); let listenerCount = 0; const listener = input.onDidChangeDirty(() => { @@ -241,7 +245,7 @@ suite('Files - FileEditorInput', () => { }); test('force open text/binary', async function () { - const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined, undefined); + const input = createFileInput(toResource.call(this, '/foo/bar/updatefile.js')); input.setForceOpenAsBinary(); let resolved = await input.resolve(); @@ -258,7 +262,7 @@ suite('Files - FileEditorInput', () => { test('file editor input factory', async function () { instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); - const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined, undefined); + const input = createFileInput(toResource.call(this, '/foo/bar/updatefile.js')); const factory = Registry.as(EditorExtensions.EditorInputFactories).getEditorInputFactory(input.getTypeId()); if (!factory) { @@ -276,7 +280,7 @@ suite('Files - FileEditorInput', () => { assert.equal(input.matches(inputDeserialized), true); const preferredResource = toResource.call(this, '/foo/bar/UPDATEfile.js'); - const inputWithPreferredResource = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), preferredResource, undefined, undefined); + const inputWithPreferredResource = createFileInput(toResource.call(this, '/foo/bar/updatefile.js'), preferredResource); const inputWithPreferredResourceSerialized = factory.serialize(inputWithPreferredResource); if (!inputWithPreferredResourceSerialized) { @@ -287,4 +291,49 @@ suite('Files - FileEditorInput', () => { assert.equal(inputWithPreferredResource.resource.toString(), inputWithPreferredResourceDeserialized.resource.toString()); assert.equal(inputWithPreferredResource.preferredResource.toString(), inputWithPreferredResourceDeserialized.preferredResource.toString()); }); + + test('preferred name/description', async function () { + + // Works with custom file input + const customFileInput = createFileInput(toResource.call(this, '/foo/bar/updatefile.js').with({ scheme: 'test-custom' }), undefined, undefined, 'My Name', 'My Description'); + + let didChangeLabelCounter = 0; + customFileInput.onDidChangeLabel(() => { + didChangeLabelCounter++; + }); + + assert.equal(customFileInput.getName(), 'My Name'); + assert.equal(customFileInput.getDescription(), 'My Description'); + + customFileInput.setPreferredName('My Name 2'); + customFileInput.setPreferredDescription('My Description 2'); + + assert.equal(customFileInput.getName(), 'My Name 2'); + assert.equal(customFileInput.getDescription(), 'My Description 2'); + + assert.equal(didChangeLabelCounter, 2); + + customFileInput.dispose(); + + // Disallowed with local file input + const fileInput = createFileInput(toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined, 'My Name', 'My Description'); + + didChangeLabelCounter = 0; + fileInput.onDidChangeLabel(() => { + didChangeLabelCounter++; + }); + + assert.notEqual(fileInput.getName(), 'My Name'); + assert.notEqual(fileInput.getDescription(), 'My Description'); + + fileInput.setPreferredName('My Name 2'); + fileInput.setPreferredDescription('My Description 2'); + + assert.notEqual(fileInput.getName(), 'My Name 2'); + assert.notEqual(fileInput.getDescription(), 'My Description 2'); + + assert.equal(didChangeLabelCounter, 0); + + fileInput.dispose(); + }); }); diff --git a/src/vs/workbench/contrib/format/browser/formatActionsNone.ts b/src/vs/workbench/contrib/format/browser/formatActionsNone.ts index 9355b4e6d..1efff633b 100644 --- a/src/vs/workbench/contrib/format/browser/formatActionsNone.ts +++ b/src/vs/workbench/contrib/format/browser/formatActionsNone.ts @@ -32,7 +32,7 @@ registerEditorAction(class FormatDocumentMultipleAction extends EditorAction { alias: 'Format Document', precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasDocumentFormattingProvider.toNegated()), kbOpts: { - kbExpr: ContextKeyExpr.and(EditorContextKeys.editorTextFocus, EditorContextKeys.hasDocumentFormattingProvider.toNegated()), + kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_F, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_I }, weight: KeybindingWeight.EditorContrib, @@ -55,6 +55,8 @@ registerEditorAction(class FormatDocumentMultipleAction extends EditorAction { return commandService.executeCommand('editor.action.formatDocument.multiple'); } else if (formatterCount === 1) { return commandService.executeCommand('editor.action.formatDocument'); + } else if (model.isTooLargeForSyncing()) { + notificationService.prompt(Severity.Info, nls.localize('too.large', "This file cannot be formatted because it is too large"), []); } else { const langName = model.getLanguageIdentifier().language; const message = nls.localize('no.provider', "There is no formatter for '{0}' files installed.", langName); diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts b/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts index 88b20371e..acd99472a 100644 --- a/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts +++ b/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts @@ -10,7 +10,7 @@ import { SyncActionDescriptor, ICommandAction, MenuRegistry, MenuId } from 'vs/p import { IWorkbenchActionRegistry, Extensions, CATEGORIES } from 'vs/workbench/common/actions'; import { ReportPerformanceIssueUsingReporterAction, OpenProcessExplorer } from 'vs/workbench/contrib/issue/electron-sandbox/issueActions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-sandbox/issue'; +import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; import { WorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-sandbox/issueService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IssueReporterData } from 'vs/platform/issue/common/issue'; diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issueActions.ts b/src/vs/workbench/contrib/issue/electron-sandbox/issueActions.ts index 5285dbdba..44a396575 100644 --- a/src/vs/workbench/contrib/issue/electron-sandbox/issueActions.ts +++ b/src/vs/workbench/contrib/issue/electron-sandbox/issueActions.ts @@ -6,7 +6,7 @@ import { Action } from 'vs/base/common/actions'; import * as nls from 'vs/nls'; import { IssueType } from 'vs/platform/issue/common/issue'; -import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-sandbox/issue'; +import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; export class OpenProcessExplorer extends Action { static readonly ID = 'workbench.action.openProcessExplorer'; diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issueService.ts b/src/vs/workbench/contrib/issue/electron-sandbox/issueService.ts index ba04db21e..f7beaf0f5 100644 --- a/src/vs/workbench/contrib/issue/electron-sandbox/issueService.ts +++ b/src/vs/workbench/contrib/issue/electron-sandbox/issueService.ts @@ -11,11 +11,12 @@ import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { getZoomLevel } from 'vs/base/browser/browser'; -import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-sandbox/issue'; +import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { IProductService } from 'vs/platform/product/common/productService'; +import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService'; export class WorkbenchIssueService implements IWorkbenchIssueService { declare readonly _serviceBrand: undefined; @@ -26,12 +27,13 @@ export class WorkbenchIssueService implements IWorkbenchIssueService { @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @INativeWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService, - @IProductService private readonly productService: IProductService + @IProductService private readonly productService: IProductService, + @ITASExperimentService private readonly experimentService: ITASExperimentService ) { } async openReporter(dataOverrides: Partial = {}): Promise { const extensions = await this.extensionManagementService.getInstalled(); - const enabledExtensions = extensions.filter(extension => this.extensionEnablementService.isEnabled(extension)); + const enabledExtensions = extensions.filter(extension => this.extensionEnablementService.isEnabled(extension) || (dataOverrides.extensionId && extension.identifier.id === dataOverrides.extensionId)); const extensionData = enabledExtensions.map((extension): IssueReporterExtensionData => { const { manifest } = extension; const manifestKeys = manifest.contributes ? Object.keys(manifest.contributes) : []; @@ -49,11 +51,13 @@ export class WorkbenchIssueService implements IWorkbenchIssueService { isBuiltin, }; }); + const experiments = await this.experimentService.getCurrentExperiments(); const theme = this.themeService.getColorTheme(); const issueReporterData: IssueReporterData = Object.assign({ styles: getIssueReporterStyles(theme), zoomLevel: getZoomLevel(), enabledExtensions: extensionData, + experiments: experiments?.join('\n') }, dataOverrides); return this.issueService.openReporter(issueReporterData); } diff --git a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts index 446ad446f..f8b4c1582 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts @@ -19,13 +19,12 @@ import Severity from 'vs/base/common/severity'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { VIEWLET_ID as EXTENSIONS_VIEWLET_ID, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions'; import { minimumTranslatedStrings } from 'vs/workbench/contrib/localizations/browser/minimalTranslations'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; // Register action to configure locale and related settings const registry = Registry.as(Extensions.WorkbenchActions); @@ -44,12 +43,9 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @IViewletService private readonly viewletService: IViewletService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { super(); - storageKeysSyncRegistryService.registerStorageKey({ key: LANGUAGEPACK_SUGGESTION_IGNORE_STORAGE_KEY, version: 1 }); - storageKeysSyncRegistryService.registerStorageKey({ key: 'langugage.update.donotask', version: 1 }); this.checkAndInstall(); this._register(this.extensionManagementService.onDidInstallExtension(e => this.onDidInstallExtension(e))); } @@ -175,7 +171,8 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo this.storageService.store( LANGUAGEPACK_SUGGESTION_IGNORE_STORAGE_KEY, JSON.stringify(languagePackSuggestionIgnoreList), - StorageScope.GLOBAL + StorageScope.GLOBAL, + StorageTarget.USER ); logUserReaction('neverShowAgain'); } diff --git a/src/vs/workbench/contrib/logs/common/logConstants.ts b/src/vs/workbench/contrib/logs/common/logConstants.ts index ca22b9947..9ba3e7aa0 100644 --- a/src/vs/workbench/contrib/logs/common/logConstants.ts +++ b/src/vs/workbench/contrib/logs/common/logConstants.ts @@ -9,3 +9,5 @@ export const rendererLogChannelId = 'rendererLog'; export const extHostLogChannelId = 'extHostLog'; export const telemetryLogChannelId = 'telemetryLog'; export const userDataSyncLogChannelId = 'userDataSyncLog'; + +export const showWindowLogActionId = 'workbench.action.showWindowLog'; diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index 5d04d1b97..713e81985 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { join } from 'vs/base/common/path'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions, CATEGORIES } from 'vs/workbench/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { Action2, registerAction2, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { SetLogLevelAction, OpenWindowSessionLogFileAction } from 'vs/workbench/contrib/logs/common/logsActions'; import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; @@ -20,8 +20,9 @@ import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { dirname } from 'vs/base/common/resources'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { LogsDataCleaner } from 'vs/workbench/contrib/logs/common/logsDataCleaner'; +import { IOutputService } from 'vs/workbench/contrib/output/common/output'; const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(SetLogLevelAction), 'Developer: Set Log Level...', CATEGORIES.Developer.value); @@ -54,6 +55,21 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { }; registerTelemetryChannel(this.logService.getLevel()); this.logService.onDidChangeLogLevel(registerTelemetryChannel); + + registerAction2(class ShowWindowLogAction extends Action2 { + constructor() { + super({ + id: Constants.showWindowLogActionId, + title: { value: nls.localize('show window log', "Show Window Log"), original: 'Show Window Log' }, + category: CATEGORIES.Developer, + f1: true + }); + } + async run(servicesAccessor: ServicesAccessor): Promise { + const outputService = servicesAccessor.get(IOutputService); + outputService.showChannel(Constants.rendererLogChannelId); + } + }); } private registerWebContributions(): void { diff --git a/src/vs/workbench/contrib/logs/common/logsActions.ts b/src/vs/workbench/contrib/logs/common/logsActions.ts index 625a8d783..f19eb8a8f 100644 --- a/src/vs/workbench/contrib/logs/common/logsActions.ts +++ b/src/vs/workbench/contrib/logs/common/logsActions.ts @@ -94,7 +94,7 @@ export class OpenWindowSessionLogFileAction extends Action { placeHolder: nls.localize('log placeholder', "Select Log file") }); if (logFileResult) { - return this.editorService.openEditor({ resource: URI.parse(logFileResult.id!) }).then(() => undefined); + return this.editorService.openEditor({ resource: URI.parse(logFileResult.id!), options: { pinned: true } }).then(() => undefined); } } } diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index e04673226..208fea76b 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -18,8 +18,7 @@ import { ShowProblemsPanelAction } from 'vs/workbench/contrib/markers/browser/ma import Constants from 'vs/workbench/contrib/markers/browser/constants'; import Messages from 'vs/workbench/contrib/markers/browser/messages'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IMarkersWorkbenchService, MarkersWorkbenchService, ActivityUpdater } from 'vs/workbench/contrib/markers/browser/markers'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ActivityUpdater } from 'vs/workbench/contrib/markers/browser/markers'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -33,8 +32,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import type { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ToggleViewAction } from 'vs/workbench/browser/actions/layoutActions'; import { Codicon } from 'vs/base/common/codicons'; - -registerSingleton(IMarkersWorkbenchService, MarkersWorkbenchService, false); +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; KeybindingsRegistry.registerCommandAndKeybindingRule({ id: Constants.MARKER_OPEN_ACTION_ID, @@ -124,11 +122,13 @@ class ToggleMarkersPanelAction extends ToggleViewAction { } } +const markersViewIcon = registerIcon('markers-view-icon', Codicon.warning, localize('markersViewIcon', 'View icon of the markers view.')); + // markers view container const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: Constants.MARKERS_CONTAINER_ID, name: Messages.MARKERS_PANEL_TITLE_PROBLEMS, - icon: Codicon.warning.classNames, + icon: markersViewIcon, hideIfEmpty: true, order: 0, ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [Constants.MARKERS_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), @@ -142,7 +142,7 @@ const VIEW_CONTAINER: ViewContainer = Registry.as(ViewC Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ id: Constants.MARKERS_VIEW_ID, - containerIcon: Codicon.warning.classNames, + containerIcon: markersViewIcon, name: Messages.MARKERS_PANEL_TITLE_PROBLEMS, canToggleVisibility: false, canMoveView: true, diff --git a/src/vs/workbench/contrib/markers/browser/markers.ts b/src/vs/workbench/contrib/markers/browser/markers.ts index 383d52fca..196235465 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.ts @@ -3,55 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { MarkersModel, compareMarkersByUri } from './markersModel'; import { Disposable, MutableDisposable, IDisposable } from 'vs/base/common/lifecycle'; -import { IMarkerService, MarkerSeverity, IMarker } from 'vs/platform/markers/common/markers'; +import { IMarkerService } from 'vs/platform/markers/common/markers'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { localize } from 'vs/nls'; import Constants from './constants'; -import { URI } from 'vs/base/common/uri'; -import { groupBy } from 'vs/base/common/arrays'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { Event } from 'vs/base/common/event'; -import { ResourceMap } from 'vs/base/common/map'; - -export const IMarkersWorkbenchService = createDecorator('markersWorkbenchService'); - -export interface IMarkersWorkbenchService { - readonly _serviceBrand: undefined; - readonly markersModel: MarkersModel; -} - -export class MarkersWorkbenchService extends Disposable implements IMarkersWorkbenchService { - declare readonly _serviceBrand: undefined; - - readonly markersModel: MarkersModel; - - constructor( - @IMarkerService private readonly markerService: IMarkerService, - @IInstantiationService instantiationService: IInstantiationService, - ) { - super(); - this.markersModel = this._register(instantiationService.createInstance(MarkersModel)); - - this.markersModel.setResourceMarkers(groupBy(this.readMarkers(), compareMarkersByUri).map(group => [group[0].resource, group])); - this._register(Event.debounce>(markerService.onMarkerChanged, (resourcesMap, resources) => { - resourcesMap = resourcesMap ? resourcesMap : new ResourceMap(); - resources.forEach(resource => resourcesMap!.set(resource, resource)); - return resourcesMap; - }, 0)(resourcesMap => this.onMarkerChanged([...resourcesMap.values()]))); - } - - private onMarkerChanged(resources: URI[]): void { - this.markersModel.setResourceMarkers(resources.map(resource => [resource, this.readMarkers(resource)])); - } - - private readMarkers(resource?: URI): IMarker[] { - return this.markerService.read({ resource, severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info }); - } - -} export class ActivityUpdater extends Disposable implements IWorkbenchContribution { diff --git a/src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts b/src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts index 393094a99..d6c633ebe 100644 --- a/src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts @@ -4,10 +4,41 @@ *--------------------------------------------------------------------------------------------*/ import { IFilter, matchesFuzzy, matchesFuzzy2 } from 'vs/base/common/filters'; -import { IExpression, splitGlobAware, getEmptyExpression } from 'vs/base/common/glob'; +import { IExpression, splitGlobAware, getEmptyExpression, ParsedExpression, parse } from 'vs/base/common/glob'; import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; -import { ResourceGlobMatcher } from 'vs/base/common/resources'; +import { relativePath } from 'vs/base/common/resources'; +import { TernarySearchTree } from 'vs/base/common/map'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; + +export class ResourceGlobMatcher { + + private readonly globalExpression: ParsedExpression; + private readonly expressionsByRoot: TernarySearchTree; + + constructor( + globalExpression: IExpression, + rootExpressions: { root: URI, expression: IExpression }[], + uriIdentityService: IUriIdentityService + ) { + this.globalExpression = parse(globalExpression); + this.expressionsByRoot = TernarySearchTree.forUris<{ root: URI, expression: ParsedExpression }>(uri => uriIdentityService.extUri.ignorePathCasing(uri)); + for (const expression of rootExpressions) { + this.expressionsByRoot.set(expression.root, { root: expression.root, expression: parse(expression.expression) }); + } + } + + matches(resource: URI): boolean { + const rootExpression = this.expressionsByRoot.findSubstr(resource); + if (rootExpression) { + const path = relativePath(rootExpression.root, resource); + if (path && !!rootExpression.expression(path)) { + return true; + } + } + return !!this.globalExpression(resource.path); + } +} export class FilterOptions { @@ -21,7 +52,16 @@ export class FilterOptions { readonly excludesMatcher: ResourceGlobMatcher; readonly includesMatcher: ResourceGlobMatcher; - constructor(readonly filter: string = '', filesExclude: { root: URI, expression: IExpression }[] | IExpression = [], showWarnings: boolean = false, showErrors: boolean = false, showInfos: boolean = false) { + static EMPTY(uriIdentityService: IUriIdentityService) { return new FilterOptions('', [], false, false, false, uriIdentityService); } + + constructor( + readonly filter: string, + filesExclude: { root: URI, expression: IExpression }[] | IExpression, + showWarnings: boolean, + showErrors: boolean, + showInfos: boolean, + uriIdentityService: IUriIdentityService + ) { filter = filter.trim(); this.showWarnings = showWarnings; this.showErrors = showErrors; @@ -43,8 +83,8 @@ export class FilterOptions { } } - this.excludesMatcher = new ResourceGlobMatcher(excludesExpression, filesExcludeByRoot); - this.includesMatcher = new ResourceGlobMatcher(includeExpression, []); + this.excludesMatcher = new ResourceGlobMatcher(excludesExpression, filesExcludeByRoot, uriIdentityService); + this.includesMatcher = new ResourceGlobMatcher(includeExpression, [], uriIdentityService); this.textFilter = this.textFilter.trim(); } diff --git a/src/vs/workbench/contrib/markers/browser/markersModel.ts b/src/vs/workbench/contrib/markers/browser/markersModel.ts index f8016d3d6..a12e1658e 100644 --- a/src/vs/workbench/contrib/markers/browser/markersModel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersModel.ts @@ -12,6 +12,7 @@ import { ResourceMap } from 'vs/base/common/map'; import { Emitter, Event } from 'vs/base/common/event'; import { Hasher } from 'vs/base/common/hash'; import { withUndefinedAsNull } from 'vs/base/common/types'; +import { splitLines } from 'vs/base/common/strings'; export function compareMarkersByUri(a: IMarker, b: IMarker) { @@ -95,7 +96,7 @@ export class Marker { private _lines: string[] | undefined; get lines(): string[] { if (!this._lines) { - this._lines = this.marker.message.split(/\r\n|\r|\n/g); + this._lines = splitLines(this.marker.message); } return this._lines; } @@ -150,6 +151,16 @@ export class MarkersModel { this.resourcesByUri = new Map(); } + reset(): void { + const removed = new Set(); + for (const resourceMarker of this.resourcesByUri.values()) { + removed.add(resourceMarker); + } + this.resourcesByUri.clear(); + this._total = 0; + this._onDidChange.fire({ removed, added: new Set(), updated: new Set() }); + } + private _total: number = 0; get total(): number { return this._total; diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 67f945f0b..6a344c8a0 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -14,7 +14,7 @@ import { ResourceMarkers, Marker, RelatedInformation } from 'vs/workbench/contri import Messages from 'vs/workbench/contrib/markers/browser/messages'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { QuickFixAction, QuickFixActionViewItem } from 'vs/workbench/contrib/markers/browser/markersViewActions'; @@ -53,6 +53,8 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Progress } from 'vs/platform/progress/common/progress'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { Codicon } from 'vs/base/common/codicons'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; export type TreeElement = ResourceMarkers | Marker | RelatedInformation; @@ -255,9 +257,10 @@ export class MarkerRenderer implements ITreeRenderer 1; action.tooltip = multiline ? localize('single line', "Show message in single line") : localize('multi line', "Show message in multiple lines"); - action.class = multiline ? expandedClass : collapsedClass; + action.class = ThemeIcon.asClassName(multiline ? expandedIcon : collapsedIcon); action.run = () => { if (viewModel) { viewModel.multiline = !viewModel.multiline; } return Promise.resolve(); }; this.multilineActionbar.push([action], { icon: true, label: false }); } diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index 2163578c6..8836c8371 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -11,7 +11,7 @@ import { IAction, IActionViewItem, Action, Separator } from 'vs/base/common/acti import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; -import { Marker, ResourceMarkers, RelatedInformation, MarkerChangesEvent } from 'vs/workbench/contrib/markers/browser/markersModel'; +import { Marker, ResourceMarkers, RelatedInformation, MarkerChangesEvent, MarkersModel, compareMarkersByUri } from 'vs/workbench/contrib/markers/browser/markersModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { MarkersFilterActionViewItem, MarkersFilters, IMarkersFiltersChangeEvent, IMarkerFilterController } from 'vs/workbench/contrib/markers/browser/markersViewActions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -19,8 +19,7 @@ import Messages from 'vs/workbench/contrib/markers/browser/messages'; import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations'; import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IMarkersWorkbenchService } from 'vs/workbench/contrib/markers/browser/markers'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService, ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Iterable } from 'vs/base/common/iterator'; @@ -39,7 +38,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { domEvent } from 'vs/base/browser/event'; import { ResourceLabels } from 'vs/workbench/browser/labels'; -import { IMarker } from 'vs/platform/markers/common/markers'; +import { IMarker, IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { withUndefinedAsNull } from 'vs/base/common/types'; import { MementoObject, Memento } from 'vs/workbench/common/memento'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; @@ -51,6 +50,11 @@ import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { Codicon } from 'vs/base/common/codicons'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { groupBy } from 'vs/base/common/arrays'; +import { ResourceMap } from 'vs/base/common/map'; +import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterable> { return Iterable.map(resourceMarkers.markers, m => { @@ -67,7 +71,9 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { private currentActiveResource: URI | null = null; private readonly rangeHighlightDecorations: RangeHighlightDecorations; + private readonly markersModel: MarkersModel; private readonly filter: Filter; + private readonly onVisibleDisposables = this._register(new DisposableStore()); private tree: MarkersTree | undefined; private filterActionBar: ActionBar | undefined; @@ -102,11 +108,12 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { @IEditorService private readonly editorService: IEditorService, @IConfigurationService configurationService: IConfigurationService, @ITelemetryService telemetryService: ITelemetryService, - @IMarkersWorkbenchService private readonly markersWorkbenchService: IMarkersWorkbenchService, + @IMarkerService private readonly markerService: IMarkerService, @IContextKeyService contextKeyService: IContextKeyService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IContextMenuService contextMenuService: IContextMenuService, @IMenuService private readonly menuService: IMenuService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @IKeybindingService keybindingService: IKeybindingService, @IStorageService storageService: IStorageService, @IOpenerService openerService: IOpenerService, @@ -114,17 +121,15 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.smallLayoutContextKey = Constants.MarkersViewSmallLayoutContextKey.bindTo(this.contextKeyService); - this.panelState = new Memento(Constants.MARKERS_VIEW_STORAGE_ID, storageService).getMemento(StorageScope.WORKSPACE); + this.panelState = new Memento(Constants.MARKERS_VIEW_STORAGE_ID, storageService).getMemento(StorageScope.WORKSPACE, StorageTarget.USER); + this.markersModel = this._register(instantiationService.createInstance(MarkersModel)); this.markersViewModel = this._register(instantiationService.createInstance(MarkersViewModel, this.panelState['multiline'])); - for (const resourceMarker of this.markersWorkbenchService.markersModel.resourceMarkers) { - resourceMarker.markers.forEach(marker => this.markersViewModel.add(marker)); - } - this._register(this.markersViewModel.onDidChange(marker => this.onDidChangeViewState(marker))); + this._register(this.onDidChangeVisibility(visible => this.onDidChangeMarkersViewVisibility(visible))); this.setCurrentActiveEditor(); - this.filter = new Filter(new FilterOptions()); + this.filter = new Filter(FilterOptions.EMPTY(uriIdentityService)); this.rangeHighlightDecorations = this._register(this.instantiationService.createInstance(RangeHighlightDecorations)); // actions @@ -152,18 +157,9 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { this.createArialLabelElement(container); this.createMessageBox(container); this.createTree(container); - this.createListeners(); this.updateFilter(); - this._register(this.onDidChangeVisibility(visible => { - if (visible) { - this.refreshPanel(); - } else { - this.rangeHighlightDecorations.removeHighlightRange(); - } - })); - this.filterActionBar!.push(new Action(`workbench.actions.treeView.${this.id}.filter`)); this.renderContent(); } @@ -225,7 +221,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { group: 'navigation', order: Number.MAX_SAFE_INTEGER, }, - icon: { id: 'codicon/collapse-all' } + icon: Codicon.collapseAll }); } async run(): Promise { @@ -332,11 +328,15 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } private setTreeSelection(): void { - if (this.tree && this.tree.getSelection().length === 0) { - const firstMarker = this.markersWorkbenchService.markersModel.resourceMarkers[0]?.markers[0]; - if (firstMarker) { - this.tree.setFocus([firstMarker]); - this.tree.setSelection([firstMarker]); + if (this.tree && this.tree.isVisible() && this.tree.getSelection().length === 0) { + const firstVisibleElement = this.tree.firstVisibleElement; + const marker = firstVisibleElement ? + firstVisibleElement instanceof ResourceMarkers ? firstVisibleElement.markers[0] : + firstVisibleElement instanceof Marker ? firstVisibleElement : undefined + : undefined; + if (marker) { + this.tree.setFocus([marker]); + this.tree.setSelection([marker]); } } } @@ -352,20 +352,20 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { let resourceMarkers: ResourceMarkers[] = []; if (this.filters.activeFile) { if (this.currentActiveResource) { - const activeResourceMarkers = this.markersWorkbenchService.markersModel.getResourceMarkers(this.currentActiveResource); + const activeResourceMarkers = this.markersModel.getResourceMarkers(this.currentActiveResource); if (activeResourceMarkers) { resourceMarkers = [activeResourceMarkers]; } } } else { - resourceMarkers = this.markersWorkbenchService.markersModel.resourceMarkers; + resourceMarkers = this.markersModel.resourceMarkers; } this.tree.setChildren(null, Iterable.map(resourceMarkers, m => ({ element: m, children: createResourceMarkersIterator(m) }))); } private updateFilter() { this.cachedFilterStats = undefined; - this.filter.options = new FilterOptions(this.filters.filterText, this.getFilesExcludeExpressions(), this.filters.showWarnings, this.filters.showErrors, this.filters.showInfos); + this.filter.options = new FilterOptions(this.filters.filterText, this.getFilesExcludeExpressions(), this.filters.showWarnings, this.filters.showErrors, this.filters.showInfos, this.uriIdentityService); if (this.tree) { this.tree.refilter(); } @@ -497,6 +497,8 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } } })); + + this._register(this.tree.onDidChangeSelection(() => this.onSelected())); } private collapseAll(): void { @@ -509,18 +511,45 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } } - private createListeners(): void { - this._register(Event.any(this.markersWorkbenchService.markersModel.onDidChange, this.editorService.onDidActiveEditorChange)(changes => { + private onDidChangeMarkersViewVisibility(visible: boolean): void { + this.onVisibleDisposables.clear(); + if (visible) { + for (const disposable of this.reInitialize()) { + this.onVisibleDisposables.add(disposable); + } + this.refreshPanel(); + } + } + + private reInitialize(): IDisposable[] { + const disposables = []; + + // Markers Model + const readMarkers = (resource?: URI) => this.markerService.read({ resource, severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info }); + this.markersModel.setResourceMarkers(groupBy(readMarkers(), compareMarkersByUri).map(group => [group[0].resource, group])); + disposables.push(Event.debounce>(this.markerService.onMarkerChanged, (resourcesMap, resources) => { + resourcesMap = resourcesMap || new ResourceMap(); + resources.forEach(resource => resourcesMap!.set(resource, resource)); + return resourcesMap; + }, 64)(resourcesMap => { + this.markersModel.setResourceMarkers([...resourcesMap.values()].map(resource => [resource, readMarkers(resource)])); + })); + disposables.push(Event.any(this.markersModel.onDidChange, this.editorService.onDidActiveEditorChange)(changes => { if (changes) { this.onDidChangeModel(changes); } else { this.onActiveEditorChanged(); } })); - if (this.tree) { - this._register(this.tree.onDidChangeSelection(() => this.onSelected())); - } - this._register(this.filters.onDidChange((event: IMarkersFiltersChangeEvent) => { + disposables.push(toDisposable(() => this.markersModel.reset())); + + // Markers View Model + this.markersModel.resourceMarkers.forEach(resourceMarker => resourceMarker.markers.forEach(marker => this.markersViewModel.add(marker))); + disposables.push(this.markersViewModel.onDidChange(marker => this.onDidChangeViewState(marker))); + disposables.push(toDisposable(() => this.markersModel.resourceMarkers.forEach(resourceMarker => this.markersViewModel.remove(resourceMarker.resource)))); + + // Markers Filters + disposables.push(this.filters.onDidChange((event: IMarkersFiltersChangeEvent) => { this.reportFilteringUsed(); if (event.activeFile) { this.refreshPanel(); @@ -528,14 +557,19 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { this.updateFilter(); } })); + disposables.push(toDisposable(() => { this.cachedFilterStats = undefined; })); + + disposables.push(toDisposable(() => this.rangeHighlightDecorations.removeHighlightRange())); + + return disposables; } - private onDidChangeModel(change: MarkerChangesEvent) { + private onDidChangeModel(change: MarkerChangesEvent): void { const resourceMarkers = [...change.added, ...change.removed, ...change.updated]; const resources: URI[] = []; for (const { resource } of resourceMarkers) { this.markersViewModel.remove(resource); - const resourceMarkers = this.markersWorkbenchService.markersModel.getResourceMarkers(resource); + const resourceMarkers = this.markersModel.getResourceMarkers(resource); if (resourceMarkers) { for (const marker of resourceMarkers.markers) { this.markersViewModel.add(marker); @@ -574,7 +608,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { private setCurrentActiveEditor(): void { const activeEditor = this.editorService.activeEditor; - this.currentActiveResource = activeEditor ? withUndefinedAsNull(activeEditor.resource) : null; + this.currentActiveResource = activeEditor ? withUndefinedAsNull(EditorResourceAccessor.getOriginalUri(activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY })) : null; } private onSelected(): void { @@ -631,7 +665,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } private renderFilterMessageForActiveFile(container: HTMLElement): void { - if (this.currentActiveResource && this.markersWorkbenchService.markersModel.getResourceMarkers(this.currentActiveResource)) { + if (this.currentActiveResource && this.markersModel.getResourceMarkers(this.currentActiveResource)) { this.renderFilteredByFilterMessage(container); } else { this.renderNoProblemsMessageForActiveFile(container); @@ -716,7 +750,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } private getResourceForCurrentActiveResource(): ResourceMarkers | null { - return this.currentActiveResource ? this.markersWorkbenchService.markersModel.getResourceMarkers(this.currentActiveResource) : null; + return this.currentActiveResource ? this.markersModel.getResourceMarkers(this.currentActiveResource) : null; } private hasSelectedMarkerFor(resource: ResourceMarkers): boolean { @@ -844,7 +878,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } } - return { total: this.markersWorkbenchService.markersModel.total, filtered }; + return { total: this.markersModel.total, filtered }; } private getTelemetryData({ source, code }: IMarker): any { @@ -917,6 +951,10 @@ class MarkersTree extends WorkbenchObjectTree { this.container.classList.toggle('hidden', hide); } + isVisible(): boolean { + return !this.container.classList.contains('hidden'); + } + } registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { diff --git a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts index 55fbbbb14..973e9e697 100644 --- a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts @@ -12,7 +12,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import Messages from 'vs/workbench/contrib/markers/browser/messages'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; -import { IThemeService, registerThemingParticipant, ICssStyleCollector, IColorTheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, ICssStyleCollector, IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; import { toDisposable, Disposable } from 'vs/base/common/lifecycle'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -28,6 +28,7 @@ import { IViewsService } from 'vs/workbench/common/views'; import { Codicon } from 'vs/base/common/codicons'; import { BaseActionViewItem, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; export class ShowProblemsPanelAction extends Action { @@ -256,6 +257,9 @@ class FiltersDropdownMenuActionViewItem extends DropdownMenuActionViewItem { } + +const filterIcon = registerIcon('markers-view-filter', Codicon.filter, localize('filterIcon', 'Icon for the filter configuration in the markers view.')); + export class MarkersFilterActionViewItem extends BaseActionViewItem { private delayedFilterUpdate: Delayer; @@ -279,7 +283,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { this._register(toDisposable(() => this.delayedFilterUpdate.cancel())); this._register(filterController.onDidFocusFilter(() => this.focus())); this._register(filterController.onDidClearFilterText(() => this.clearFilterText())); - this.filtersAction = new Action('markersFiltersAction', Messages.MARKERS_PANEL_ACTION_TOOLTIP_MORE_FILTERS, 'markers-filters codicon-filter'); + this.filtersAction = new Action('markersFiltersAction', Messages.MARKERS_PANEL_ACTION_TOOLTIP_MORE_FILTERS, 'markers-filters ' + ThemeIcon.asClassName(filterIcon)); this.filtersAction.checked = this.hasFiltersChanged(); this._register(filterController.filters.onDidChange(e => this.onDidFiltersChange(e))); } diff --git a/src/vs/workbench/contrib/notebook/browser/constants.ts b/src/vs/workbench/contrib/notebook/browser/constants.ts index 245f72a87..3232b0474 100644 --- a/src/vs/workbench/contrib/notebook/browser/constants.ts +++ b/src/vs/workbench/contrib/notebook/browser/constants.ts @@ -22,7 +22,7 @@ export const CELL_TOP_MARGIN = 6; export const CELL_BOTTOM_MARGIN = 6; // Top and bottom padding inside the monaco editor in a cell, which are included in `cell.editorHeight` -export const EDITOR_TOP_PADDING = 12; +// export const EDITOR_TOP_PADDING = 12; export const EDITOR_BOTTOM_PADDING = 4; export const EDITOR_BOTTOM_PADDING_WITHOUT_STATUSBAR = 12; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index 337cc9eb5..acf32d1a9 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -6,7 +6,7 @@ import * as glob from 'vs/base/common/glob'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import * as platform from 'vs/base/common/platform'; -import { URI } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -21,12 +21,13 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { CATEGORIES } from 'vs/workbench/common/actions'; -import { BaseCellRenderTemplate, CellEditState, CellFocusMode, EXPAND_CELL_CONTENT_COMMAND_ID, ICellViewModel, INotebookEditor, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { BaseCellRenderTemplate, CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, EXPAND_CELL_CONTENT_COMMAND_ID, IActiveNotebookEditor, ICellViewModel, INotebookEditor, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { CellEditType, CellKind, ICellEditOperation, ICellRange, isDocumentExcludePattern, NotebookCellMetadata, NotebookCellRunState, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellKind, ICellEditOperation, ICellRange, isDocumentExcludePattern, NotebookCellMetadata, NotebookCellRunState, NOTEBOOK_EDITOR_CURSOR_BEGIN_END, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; // Notebook Commands const EXECUTE_NOTEBOOK_COMMAND_ID = 'notebook.execute'; @@ -64,7 +65,6 @@ const SPLIT_CELL_COMMAND_ID = 'notebook.cell.split'; const JOIN_CELL_ABOVE_COMMAND_ID = 'notebook.cell.joinAbove'; const JOIN_CELL_BELOW_COMMAND_ID = 'notebook.cell.joinBelow'; -const EXECUTE_CELL_COMMAND_ID = 'notebook.cell.execute'; const CANCEL_CELL_COMMAND_ID = 'notebook.cell.cancelExecution'; const EXECUTE_CELL_SELECT_BELOW = 'notebook.cell.executeAndSelectBelow'; const EXECUTE_CELL_INSERT_BELOW = 'notebook.cell.executeAndInsertBelow'; @@ -103,7 +103,7 @@ const enum CellOverflowToolbarGroups { export interface INotebookActionContext { readonly cellTemplate?: BaseCellRenderTemplate; readonly cell?: ICellViewModel; - readonly notebookEditor: INotebookEditor; + readonly notebookEditor: IActiveNotebookEditor; readonly ui?: boolean; } @@ -162,6 +162,10 @@ abstract class NotebookAction extends Action2 { return; } + if (!editor.hasModel()) { + return; + } + const activeCell = editor.getActiveCell(); return { cell: activeCell, @@ -233,7 +237,7 @@ registerAction2(class extends NotebookCellAction { } ] }, - icon: { id: 'codicon/play' }, + icon: icons.executeIcon }); } @@ -267,7 +271,7 @@ registerAction2(class extends NotebookCellAction { super({ id: CANCEL_CELL_COMMAND_ID, title: localize('notebookActions.cancel', "Stop Cell Execution"), - icon: { id: 'codicon/primitive-square' }, + icon: icons.stopIcon, description: { description: localize('notebookActions.execute', "Execute Cell"), args: [ @@ -326,7 +330,7 @@ export class ExecuteCellAction extends MenuItemAction { { id: EXECUTE_CELL_COMMAND_ID, title: localize('notebookActions.executeCell', "Execute Cell"), - icon: { id: 'codicon/play' } + icon: icons.executeIcon }, undefined, { shouldForwardArgs: true }, @@ -344,7 +348,7 @@ export class CancelCellAction extends MenuItemAction { { id: CANCEL_CELL_COMMAND_ID, title: localize('notebookActions.CancelCell', "Cancel Execution"), - icon: { id: 'codicon/primitive-square' } + icon: icons.stopIcon }, undefined, { shouldForwardArgs: true }, @@ -362,7 +366,7 @@ export class DeleteCellAction extends MenuItemAction { { id: DELETE_CELL_COMMAND_ID, title: localize('notebookActions.deleteCell', "Delete Cell"), - icon: { id: 'codicon/trash' } + icon: icons.deleteCellIcon, }, undefined, { shouldForwardArgs: true }, @@ -385,7 +389,7 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { - const idx = context.notebookEditor.viewModel?.getCellIndex(context.cell); + const idx = context.notebookEditor.viewModel.getCellIndex(context.cell); if (typeof idx !== 'number') { return; } @@ -393,7 +397,7 @@ registerAction2(class extends NotebookCellAction { const executionP = runCell(accessor, context); // Try to select below, fall back on inserting - const nextCell = context.notebookEditor.viewModel?.viewCells[idx + 1]; + const nextCell = context.notebookEditor.viewModel.viewCells[idx + 1]; if (nextCell) { context.notebookEditor.focusNotebookCell(nextCell, 'container'); } else { @@ -471,7 +475,7 @@ registerAction2(class extends NotebookAction { }); function renderAllMarkdownCells(context: INotebookActionContext): void { - context.notebookEditor.viewModel!.viewCells.forEach(cell => { + context.notebookEditor.viewModel.viewCells.forEach(cell => { if (cell.cellKind === CellKind.Markdown) { cell.editState = CellEditState.Preview; } @@ -508,7 +512,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: EXECUTE_NOTEBOOK_COMMAND_ID, title: localize('notebookActions.menu.executeNotebook', "Execute Notebook (Run all cells)"), - icon: { id: 'codicon/run-all' } + icon: icons.executeAllIcon, }, order: -1, group: 'navigation', @@ -519,7 +523,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: CANCEL_NOTEBOOK_COMMAND_ID, title: localize('notebookActions.menu.cancelNotebook', "Stop Notebook Execution"), - icon: { id: 'codicon/primitive-square' } + icon: icons.stopIcon, }, order: -1, group: 'navigation', @@ -836,7 +840,7 @@ registerAction2(class extends NotebookCellAction { order: CellToolbarOrder.EditCell, group: CELL_TITLE_CELL_GROUP_ID }, - icon: { id: 'codicon/pencil' } + icon: icons.editIcon, }); } @@ -860,13 +864,15 @@ registerAction2(class extends NotebookCellAction { order: CellToolbarOrder.SaveCell, group: CELL_TITLE_CELL_GROUP_ID }, - icon: { id: 'codicon/check' }, + icon: icons.stopEditIcon, keybinding: { when: ContextKeyExpr.and( NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext, EditorContextKeys.hoverVisible.toNegated(), - EditorContextKeys.hasNonEmptySelection.toNegated()), + EditorContextKeys.hasNonEmptySelection.toNegated(), + EditorContextKeys.hasMultipleSelections.toNegated() + ), primary: KeyCode.Escape, weight: EDITOR_WIDGET_ACTION_WEIGHT - 5 }, @@ -900,19 +906,19 @@ registerAction2(class extends NotebookCellAction { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), weight: KeybindingWeight.WorkbenchContrib }, - icon: { id: 'codicon/trash' }, + icon: icons.deleteCellIcon }); } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const index = context.notebookEditor.viewModel!.getCellIndex(context.cell); + const index = context.notebookEditor.viewModel.getCellIndex(context.cell); const result = await context.notebookEditor.deleteNotebookCell(context.cell); if (result) { // deletion succeeds, move focus to the next cell - const nextCellIdx = index < context.notebookEditor.viewModel!.length ? index : context.notebookEditor.viewModel!.length - 1; + const nextCellIdx = index < context.notebookEditor.viewModel.length ? index : context.notebookEditor.viewModel.length - 1; if (nextCellIdx >= 0) { - context.notebookEditor.focusNotebookCell(context.notebookEditor.viewModel!.viewCells[nextCellIdx], 'container'); + context.notebookEditor.focusNotebookCell(context.notebookEditor.viewModel.viewCells[nextCellIdx], 'container'); } } } @@ -944,7 +950,7 @@ registerAction2(class extends NotebookCellAction { { id: MOVE_CELL_UP_COMMAND_ID, title: localize('notebookActions.moveCellUp', "Move Cell Up"), - icon: { id: 'codicon/arrow-up' }, + icon: icons.moveUpIcon, keybinding: { primary: KeyMod.Alt | KeyCode.UpArrow, when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()), @@ -964,7 +970,7 @@ registerAction2(class extends NotebookCellAction { { id: MOVE_CELL_DOWN_COMMAND_ID, title: localize('notebookActions.moveCellDown', "Move Cell Down"), - icon: { id: 'codicon/arrow-down' }, + icon: icons.moveDownIcon, keybinding: { primary: KeyMod.Alt | KeyCode.DownArrow, when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()), @@ -1139,7 +1145,7 @@ registerAction2(class extends NotebookCellAction { return; } - const currCellIndex = viewModel.getCellIndex(context!.cell); + const currCellIndex = viewModel.getCellIndex(context.cell); let topPastedCell: CellViewModel | undefined = undefined; pasteCells.items.reverse().map(cell => { @@ -1237,12 +1243,12 @@ registerAction2(class extends NotebookCellAction { const editor = context.notebookEditor; const activeCell = context.cell; - const idx = editor.viewModel?.getCellIndex(activeCell); + const idx = editor.viewModel.getCellIndex(activeCell); if (typeof idx !== 'number') { return; } - const newCell = editor.viewModel?.viewCells[idx + 1]; + const newCell = editor.viewModel.viewCells[idx + 1]; if (!newCell) { return; @@ -1276,7 +1282,7 @@ registerAction2(class extends NotebookCellAction { const editor = context.notebookEditor; const activeCell = context.cell; - const idx = editor.viewModel?.getCellIndex(activeCell); + const idx = editor.viewModel.getCellIndex(activeCell); if (typeof idx !== 'number') { return; } @@ -1286,7 +1292,7 @@ registerAction2(class extends NotebookCellAction { return; } - const newCell = editor.viewModel?.viewCells[idx - 1]; + const newCell = editor.viewModel.viewCells[idx - 1]; if (!newCell) { return; @@ -1407,7 +1413,7 @@ registerAction2(class extends NotebookCellAction { primary: KeyMod.Alt | KeyCode.Delete, weight: KeybindingWeight.WorkbenchContrib }, - icon: { id: 'codicon/clear-all' }, + icon: icons.clearIcon }); } @@ -1427,7 +1433,7 @@ registerAction2(class extends NotebookCellAction { editor.viewModel.notebookDocument.applyEdits(editor.viewModel.notebookDocument.versionId, [{ editType: CellEditType.Output, index, outputs: [] }], true, undefined, () => undefined, undefined); if (context.cell.metadata && context.cell.metadata?.runState !== NotebookCellRunState.Running) { - context.notebookEditor.viewModel!.notebookDocument.applyEdits(context.notebookEditor.viewModel!.notebookDocument.versionId, [{ + context.notebookEditor.viewModel.notebookDocument.applyEdits(context.notebookEditor.viewModel.notebookDocument.versionId, [{ editType: CellEditType.Metadata, index, metadata: { ...context.cell.metadata, runState: NotebookCellRunState.Idle, @@ -1466,7 +1472,7 @@ export class ChangeCellLanguageAction extends NotebookCellAction { const modelService = accessor.get(IModelService); const quickInputService = accessor.get(IQuickInputService); - const providerLanguages = [...context.notebookEditor.viewModel!.notebookDocument.resolvedLanguages, 'markdown']; + const providerLanguages = [...context.notebookEditor.viewModel.notebookDocument.resolvedLanguages, 'markdown']; providerLanguages.forEach(languageId => { let description: string; if (context.cell.cellKind === CellKind.Markdown ? (languageId === 'markdown') : (languageId === context.cell.language)) { @@ -1515,9 +1521,9 @@ export class ChangeCellLanguageAction extends NotebookCellAction { } else if (selection.languageId !== 'markdown' && context.cell?.cellKind === CellKind.Markdown) { await changeCellToKind(CellKind.Code, { cell: context.cell, notebookEditor: context.notebookEditor }, selection.languageId); } else { - const index = context.notebookEditor.viewModel!.notebookDocument.cells.indexOf(context.cell.model); - context.notebookEditor.viewModel!.notebookDocument.applyEdits( - context.notebookEditor.viewModel!.notebookDocument.versionId, + const index = context.notebookEditor.viewModel.notebookDocument.cells.indexOf(context.cell.model); + context.notebookEditor.viewModel.notebookDocument.applyEdits( + context.notebookEditor.viewModel.notebookDocument.versionId, [{ editType: CellEditType.CellLanguage, index, language: selection.languageId }], true, undefined, () => undefined, undefined ); @@ -1557,7 +1563,7 @@ registerAction2(class extends NotebookAction { group: 'navigation', order: 0 }, - icon: { id: 'codicon/clear-all' }, + icon: icons.clearIcon }); } @@ -1589,7 +1595,7 @@ registerAction2(class extends NotebookAction { } }).filter(edit => !!edit) as ICellEditOperation[]; if (clearExecutionMetadataEdits.length) { - context.notebookEditor.viewModel!.notebookDocument.applyEdits(context.notebookEditor.viewModel!.notebookDocument.versionId, clearExecutionMetadataEdits, true, undefined, () => undefined, undefined); + context.notebookEditor.viewModel.notebookDocument.applyEdits(context.notebookEditor.viewModel.notebookDocument.versionId, clearExecutionMetadataEdits, true, undefined, () => undefined, undefined); } } }); @@ -1609,7 +1615,7 @@ registerAction2(class extends NotebookCellAction { title: localize('notebookActions.splitCell', "Split Cell"), menu: { id: MenuId.NotebookCellTitle, - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED), + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_EDITOR_CURSOR_BEGIN_END.toNegated()), order: CellToolbarOrder.SplitCell, group: CELL_TITLE_CELL_GROUP_ID, // alt: { @@ -1617,9 +1623,9 @@ registerAction2(class extends NotebookCellAction { // title: localize('notebookActions.joinCellBelow', "Join with Next Cell") // } }, - icon: { id: 'codicon/split-vertical' }, + icon: icons.splitCellIcon, keybinding: { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED), + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_EDITOR_CURSOR_BEGIN_END.toNegated()), primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_BACKSLASH), weight: KeybindingWeight.WorkbenchContrib }, @@ -1713,7 +1719,7 @@ registerAction2(class extends NotebookCellAction { abstract class ChangeNotebookCellMetadataAction extends NotebookCellAction { async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { const cell = context.cell; - const textModel = context.notebookEditor.viewModel?.notebookDocument; + const textModel = context.notebookEditor.viewModel.notebookDocument; if (!textModel) { return; } @@ -1840,6 +1846,10 @@ registerAction2(class extends Action2 { return; } + if (!editor.hasModel()) { + return; + } + const activeCell = editor.getActiveCell(); return { cell: activeCell, @@ -1851,7 +1861,7 @@ registerAction2(class extends Action2 { const activeEditorContext = this.getActiveEditorContext(accessor); if (activeEditorContext) { - const viewModel = activeEditorContext.notebookEditor.viewModel!; + const viewModel = activeEditorContext.notebookEditor.viewModel; console.log('--- notebook ---'); console.log(viewModel.layoutInfo); console.log('--- cells ---'); @@ -1865,6 +1875,19 @@ registerAction2(class extends Action2 { } }); +// Revisit once we have a story for trusted workspace +CommandsRegistry.registerCommand('notebook.trust', (accessor, args) => { + const uri = URI.revive(args as UriComponents); + const notebookService = accessor.get(INotebookService); + + + const document = notebookService.listNotebookDocuments().find(document => document.uri.toString() === uri.toString()); + + if (document) { + document.applyEdits(document.versionId, [{ editType: CellEditType.DocumentMetadata, metadata: { ...document.metadata, ...{ trusted: true } } }], true, undefined, () => undefined, undefined, false); + } +}); + CommandsRegistry.registerCommand('_resolveNotebookContentProvider', (accessor, args): { viewType: string; displayName: string; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts index 71f749486..2c2c46b89 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts @@ -43,6 +43,7 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote private _currentMatchDecorations: ICellModelDecorations[] = []; private _showTimeout: number | null = null; private _hideTimeout: number | null = null; + private _previousFocusElement?: HTMLElement; constructor( private readonly _notebookEditor: INotebookEditor, @@ -65,6 +66,10 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote this._register(this._state.onFindReplaceStateChange(() => { this.onInputChanged(); })); + + this._register(DOM.addDisposableListener(this.getDomNode(), DOM.EventType.FOCUS, e => { + this._previousFocusElement = e.relatedTarget instanceof HTMLElement ? e.relatedTarget : undefined; + }, true)); } private _onFindInputKeyDown(e: IKeyboardEvent): void { @@ -114,7 +119,7 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote if (!this._findMatchesStarts) { this.set(this._findMatches, true); } else { - const totalVal = this._findMatchesStarts!.getTotalValue(); + const totalVal = this._findMatchesStarts.getTotalValue(); const nextVal = (this._currentMatch + (previous ? -1 : 1) + totalVal) % totalVal; this._currentMatch = nextVal; } @@ -167,6 +172,7 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote } protected onFocusTrackerBlur() { + this._previousFocusElement = undefined; this._findWidgetFocused.reset(); } @@ -324,6 +330,11 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote } else { // no op } + + if (this._previousFocusElement && this._previousFocusElement.offsetParent) { + this._previousFocusElement.focus(); + this._previousFocusElement = undefined; + } } clear() { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts b/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts index 0e4f30a00..0235b4431 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts @@ -116,7 +116,7 @@ export class FoldingController extends Disposable implements INotebookEditorCont const target = e.event.target as HTMLElement; - if (target.classList.contains('codicon-chevron-down') || target.classList.contains('codicon-chevron-right')) { + if (target.classList.contains('codicon-notebook-collapsed') || target.classList.contains('codicon-notebook-expanded')) { const parent = target.parentElement as HTMLElement; if (!parent.classList.contains('notebook-folding-indicator')) { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel.ts index 3675729c2..777b97b7d 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel.ts @@ -48,7 +48,11 @@ export class FoldingModel extends Disposable { })); this._viewModelStore.add(this._viewModel.onDidChangeSelection(() => { - const selectionHandles = this._viewModel!.selectionHandles; + if (!this._viewModel) { + return; + } + + const selectionHandles = this._viewModel.selectionHandles; const indexes = selectionHandles.map(handle => this._viewModel!.getCellIndex(this._viewModel!.getCellByHandle(handle)!) ); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/scm/scm.ts b/src/vs/workbench/contrib/notebook/browser/contrib/scm/scm.ts deleted file mode 100644 index 670a805cf..000000000 --- a/src/vs/workbench/contrib/notebook/browser/contrib/scm/scm.ts +++ /dev/null @@ -1,163 +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 { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { INotebookEditorContribution, INotebookEditor } from '../../notebookBrowser'; -import { registerNotebookContribution } from '../../notebookEditorExtensions'; -import { ISCMService } from 'vs/workbench/contrib/scm/common/scm'; -import { createProviderComparer } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator'; -import { first, ThrottledDelayer } from 'vs/base/common/async'; -import { INotebookService } from '../../../common/notebookService'; -import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { FileService } from 'vs/platform/files/common/fileService'; -import { IFileService } from 'vs/platform/files/common/files'; -import { URI } from 'vs/base/common/uri'; - -export class SCMController extends Disposable implements INotebookEditorContribution { - static id: string = 'workbench.notebook.findController'; - private _lastDecorationId: string[] = []; - private _localDisposable = new DisposableStore(); - private _originalDocument: NotebookTextModel | undefined = undefined; - private _originalResourceDisposableStore = new DisposableStore(); - private _diffDelayer = new ThrottledDelayer(200); - - private _lastVersion = -1; - - - constructor( - private readonly _notebookEditor: INotebookEditor, - @IFileService private readonly _fileService: FileService, - @ISCMService private readonly _scmService: ISCMService, - @INotebookService private readonly _notebookService: INotebookService - - ) { - super(); - - if (!this._notebookEditor.isEmbedded) { - this._register(this._notebookEditor.onDidChangeModel(() => { - this._localDisposable.clear(); - this._originalResourceDisposableStore.clear(); - this._diffDelayer.cancel(); - this.update(); - - if (this._notebookEditor.textModel) { - this._localDisposable.add(this._notebookEditor.textModel.onDidChangeContent((e) => { - this.update(); - })); - } - })); - - this._register(this._notebookEditor.onWillDispose(() => { - this._localDisposable.clear(); - this._originalResourceDisposableStore.clear(); - })); - - this.update(); - } - } - - private async _resolveNotebookDocument(uri: URI, viewType: string) { - const providers = this._scmService.repositories.map(r => r.provider); - const rootedProviders = providers.filter(p => !!p.rootUri); - - rootedProviders.sort(createProviderComparer(uri)); - - const result = await first(rootedProviders.map(p => () => p.getOriginalResource(uri))); - - if (!result) { - this._originalDocument = undefined; - this._originalResourceDisposableStore.clear(); - return; - } - - if (result.toString() === this._originalDocument?.uri.toString()) { - // original document not changed - return; - } - - this._originalResourceDisposableStore.add(this._fileService.watch(result)); - this._originalResourceDisposableStore.add(this._fileService.onDidFilesChange(e => { - if (e.contains(result)) { - this._originalDocument = undefined; - this._originalResourceDisposableStore.clear(); - this.update(); - } - })); - - const originalDocument = await this._notebookService.resolveNotebook(viewType, result, false); - this._originalResourceDisposableStore.add({ - dispose: () => { - this._originalDocument?.dispose(); - this._originalDocument = undefined; - } - }); - - this._originalDocument = originalDocument; - } - - async update() { - if (!this._diffDelayer) { - return; - } - - await this._diffDelayer - .trigger(async () => { - const modifiedDocument = this._notebookEditor.textModel; - if (!modifiedDocument) { - return; - } - - if (this._lastVersion >= modifiedDocument.versionId) { - return; - } - - this._lastVersion = modifiedDocument.versionId; - await this._resolveNotebookDocument(modifiedDocument.uri, modifiedDocument.viewType); - - if (!this._originalDocument) { - this._clear(); - return; - } - - // const diff = new LcsDiff(new CellSequence(this._originalDocument), new CellSequence(modifiedDocument)); - // const diffResult = diff.ComputeDiff(false); - - // const decorations: INotebookDeltaDecoration[] = []; - // diffResult.changes.forEach(change => { - // if (change.originalLength === 0) { - // // doesn't exist in original - // for (let i = 0; i < change.modifiedLength; i++) { - // decorations.push({ - // handle: modifiedDocument.cells[change.modifiedStart + i].handle, - // options: { gutterClassName: 'nb-gutter-cell-inserted' } - // }); - // } - // } else { - // if (change.modifiedLength === 0) { - // // diff.deleteCount - // // removed from original - // } else { - // // modification - // for (let i = 0; i < change.modifiedLength; i++) { - // decorations.push({ - // handle: modifiedDocument.cells[change.modifiedStart + i].handle, - // options: { gutterClassName: 'nb-gutter-cell-changed' } - // }); - // } - // } - // } - // }); - - - // this._lastDecorationId = this._notebookEditor.deltaCellDecorations(this._lastDecorationId, decorations); - }); - } - - private _clear() { - this._lastDecorationId = this._notebookEditor.deltaCellDecorations(this._lastDecorationId, []); - } -} - -registerNotebookContribution(SCMController.id, SCMController); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts b/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts index 7d7abed7f..2f32225cb 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts @@ -21,6 +21,8 @@ import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/l import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; import { NotebookKernelProviderAssociation, NotebookKernelProviderAssociations, notebookKernelProviderAssociationsSettingId } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { configureKernelIcon, selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; registerAction2(class extends Action2 { @@ -30,14 +32,13 @@ registerAction2(class extends Action2 { category: NOTEBOOK_ACTIONS_CATEGORY, title: { value: nls.localize('notebookActions.selectKernel', "Select Notebook Kernel"), original: 'Select Notebook Kernel' }, precondition: NOTEBOOK_IS_ACTIVE_EDITOR, - icon: { id: 'codicon/server-environment' }, + icon: selectKernelIcon, f1: true }); } async run(accessor: ServicesAccessor, context?: INotebookActionContext): Promise { const editorService = accessor.get(IEditorService); - const notebookService = accessor.get(INotebookService); const quickInputService = accessor.get(IQuickInputService); const configurationService = accessor.get(IConfigurationService); @@ -48,8 +49,14 @@ registerAction2(class extends Action2 { const editor = editorService.activeEditorPane?.getControl() as INotebookEditor; const activeKernel = editor.activeKernel; + const picker = quickInputService.createQuickPick<(IQuickPickItem & { run(): void; kernelProviderId?: string })>(); + picker.placeholder = nls.localize('notebook.runCell.selectKernel', "Select a notebook kernel to run this notebook"); + picker.matchOnDetail = true; + picker.show(); + picker.busy = true; + const tokenSource = new CancellationTokenSource(); - const availableKernels2 = await notebookService.getContributedNotebookKernels2(editor.viewModel!.viewType, editor.viewModel!.uri, tokenSource.token); + const availableKernels2 = await editor.beginComputeContributedKernels(); const picks: QuickPickInput[] = [...availableKernels2].map((a) => { return { id: a.id, @@ -68,17 +75,15 @@ registerAction2(class extends Action2 { a.resolve(editor.uri!, editor.getId(), tokenSource.token); }, buttons: [{ - iconClass: 'codicon-settings-gear', + iconClass: ThemeIcon.asClassName(configureKernelIcon), tooltip: nls.localize('notebook.promptKernel.setDefaultTooltip', "Set as default kernel provider for '{0}'", editor.viewModel!.viewType) }] }; }); - const picker = quickInputService.createQuickPick<(IQuickPickItem & { run(): void; kernelProviderId?: string })>(); picker.items = picks; + picker.busy = false; picker.activeItems = picks.filter(pick => (pick as IQuickPickItem).picked) as (IQuickPickItem & { run(): void; kernelProviderId?: string; })[]; - picker.placeholder = nls.localize('pickAction', "Select Action"); - picker.matchOnDetail = true; const pickedItem = await new Promise<(IQuickPickItem & { run(): void; kernelProviderId?: string; }) | undefined>(resolve => { picker.onDidAccept(() => { @@ -113,7 +118,6 @@ registerAction2(class extends Action2 { } }); - picker.show(); }); tokenSource.dispose(); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/cellComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/cellComponents.ts index a53d35365..f526080b2 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/cellComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/cellComponents.ts @@ -9,10 +9,9 @@ import { IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/edit import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { CellDiffViewModel, PropertyFoldingState } from 'vs/workbench/contrib/notebook/browser/diff/celllDiffViewModel'; import { CellDiffRenderTemplate, CellDiffViewModelLayoutChangeEvent, DIFF_CELL_MARGIN, INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/common'; -import { EDITOR_BOTTOM_PADDING, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; +import { EDITOR_BOTTOM_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; -import { renderCodicons } from 'vs/base/browser/codicons'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { format } from 'vs/base/common/jsonFormatter'; @@ -22,12 +21,16 @@ import { hash } from 'vs/base/common/hash'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IMenu, IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; -import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/commonViewComponents'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IAction } from 'vs/base/common/actions'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellActionView'; +import { getEditorTopPadding } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { collapsedIcon, expandedIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { renderCodicons } from 'vs/base/browser/codicons'; const fixedEditorOptions: IEditorOptions = { padding: { @@ -62,7 +65,8 @@ const fixedDiffEditorOptions: IDiffEditorOptions = { glyphMargin: true, enableSplitViewResizing: false, renderIndicators: false, - readOnly: false + readOnly: false, + isInEmbeddedEditor: true }; @@ -123,11 +127,13 @@ class PropertyHeader extends Disposable { return undefined; } }); + this._register(this._toolbar); this._toolbar.context = { cell: this.cell }; this._menu = this.menuService.createMenu(this.accessor.menuId, this.contextKeyService); + this._register(this._menu); if (metadataChanged) { const actions: IAction[] = []; @@ -142,7 +148,7 @@ class PropertyHeader extends Disposable { const target = e.event.target as HTMLElement; - if (target.classList.contains('codicon-chevron-down') || target.classList.contains('codicon-chevron-right')) { + if (target.classList.contains('codicon-notebook-collapsed') || target.classList.contains('codicon-notebook-expanded')) { const parent = target.parentElement as HTMLElement; if (!parent) { @@ -194,9 +200,9 @@ class PropertyHeader extends Disposable { private _updateFoldingIcon() { if (this.accessor.getFoldingState(this.cell) === PropertyFoldingState.Collapsed) { - DOM.reset(this._foldingIndicator, ...renderCodicons('$(chevron-right)')); + DOM.reset(this._foldingIndicator, ...renderCodicons(ThemeIcon.asCodiconLabel(collapsedIcon))); } else { - DOM.reset(this._foldingIndicator, ...renderCodicons('$(chevron-down)')); + DOM.reset(this._foldingIndicator, ...renderCodicons(ThemeIcon.asCodiconLabel(expandedIcon))); } } } @@ -228,6 +234,7 @@ abstract class AbstractCellRenderer extends Disposable { outputHeight: number; bodyMargin: number; }; + protected _isDisposed: boolean; constructor( readonly notebookEditor: INotebookTextDiffEditor, @@ -247,6 +254,7 @@ abstract class AbstractCellRenderer extends Disposable { ) { super(); // init + this._isDisposed = false; this._layoutInfo = { editorHeight: 0, editorMargin: 0, @@ -528,6 +536,7 @@ abstract class AbstractCellRenderer extends Disposable { originalEditable: false, ignoreTrimWhitespace: false }); + this._register(this._metadataEditor); this._metadataEditorContainer?.classList.add('diff'); @@ -583,6 +592,7 @@ abstract class AbstractCellRenderer extends Disposable { overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode(), readOnly: false }, {}); + this._register(this._metadataEditor); const mode = this.modeService.create('jsonc'); const originalMetadataSource = this._getFormatedMetadataJSON( @@ -632,6 +642,7 @@ abstract class AbstractCellRenderer extends Disposable { readOnly: true, ignoreTrimWhitespace: false }); + this._register(this._outputEditor); this._outputEditorContainer?.classList.add('diff'); @@ -671,10 +682,11 @@ abstract class AbstractCellRenderer extends Disposable { }, overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode() }, {}); + this._register(this._outputEditor); const mode = this.modeService.create('json'); const originaloutputSource = this._getFormatedOutputJSON( - this.notebookEditor.textModel!.transientOptions + this.notebookEditor.textModel!.transientOptions.transientOutputs ? [] : this.cell.type === 'insert' ? this.cell.modified!.outputs || [] @@ -706,6 +718,11 @@ abstract class AbstractCellRenderer extends Disposable { ); } + dispose() { + this._isDisposed = true; + super.dispose(); + } + abstract initData(): void; abstract styleContainer(container: HTMLElement): void; abstract buildSourceEditor(sourceContainer: HTMLElement): void; @@ -744,7 +761,7 @@ export class DeletedCell extends AbstractCellRenderer { const originalCell = this.cell.original!; const lineCount = originalCell.textBuffer.getLineCount(); const lineHeight = this.notebookEditor.getLayoutInfo().fontInfo.lineHeight || 17; - const editorHeight = lineCount * lineHeight + EDITOR_TOP_PADDING + EDITOR_BOTTOM_PADDING; + const editorHeight = lineCount * lineHeight + getEditorTopPadding() + EDITOR_BOTTOM_PADDING; const editorContainer = DOM.append(sourceContainer, DOM.$('.editor-container')); @@ -756,6 +773,8 @@ export class DeletedCell extends AbstractCellRenderer { }, overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode() }, {}); + this._register(this._editor); + this._layoutInfo.editorHeight = editorHeight; this._register(this._editor.onDidContentSizeChange((e) => { @@ -766,6 +785,10 @@ export class DeletedCell extends AbstractCellRenderer { })); originalCell.resolveTextModelRef().then(ref => { + if (this._isDisposed) { + return; + } + this._register(ref); const textModel = ref.object.textEditorModel; @@ -838,7 +861,7 @@ export class InsertCell extends AbstractCellRenderer { const modifiedCell = this.cell.modified!; const lineCount = modifiedCell.textBuffer.getLineCount(); const lineHeight = this.notebookEditor.getLayoutInfo().fontInfo.lineHeight || 17; - const editorHeight = lineCount * lineHeight + EDITOR_TOP_PADDING + EDITOR_BOTTOM_PADDING; + const editorHeight = lineCount * lineHeight + getEditorTopPadding() + EDITOR_BOTTOM_PADDING; const editorContainer = DOM.append(sourceContainer, DOM.$('.editor-container')); this._editor = this.instantiationService.createInstance(CodeEditorWidget, editorContainer, { @@ -850,6 +873,7 @@ export class InsertCell extends AbstractCellRenderer { overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode(), readOnly: false }, {}); + this._register(this._editor); this._layoutInfo.editorHeight = editorHeight; @@ -861,6 +885,10 @@ export class InsertCell extends AbstractCellRenderer { })); modifiedCell.resolveTextModelRef().then(ref => { + if (this._isDisposed) { + return; + } + this._register(ref); const textModel = ref.object.textEditorModel; @@ -937,7 +965,7 @@ export class ModifiedCell extends AbstractCellRenderer { const modifiedCell = this.cell.modified!; const lineCount = modifiedCell.textBuffer.getLineCount(); const lineHeight = this.notebookEditor.getLayoutInfo().fontInfo.lineHeight || 17; - const editorHeight = lineCount * lineHeight + EDITOR_TOP_PADDING + EDITOR_BOTTOM_PADDING; + const editorHeight = lineCount * lineHeight + getEditorTopPadding() + EDITOR_BOTTOM_PADDING; this._editorContainer = DOM.append(sourceContainer, DOM.$('.editor-container')); this._editor = this.instantiationService.createInstance(DiffEditorWidget, this._editorContainer, { @@ -946,6 +974,7 @@ export class ModifiedCell extends AbstractCellRenderer { originalEditable: false, ignoreTrimWhitespace: false }); + this._register(this._editor); this._editorContainer.classList.add('diff'); this._editor.layout({ @@ -982,6 +1011,7 @@ export class ModifiedCell extends AbstractCellRenderer { }; this._menu = this.menuService.createMenu(MenuId.NotebookDiffCellInputTitle, this.contextKeyService); + this._register(this._menu); const actions: IAction[] = []; createAndFillInActionBarActions(this._menu, { shouldForwardArgs: true }, actions); this._toolbar.setActions(actions); @@ -1007,6 +1037,11 @@ export class ModifiedCell extends AbstractCellRenderer { const originalRef = await originalCell.resolveTextModelRef(); const modifiedRef = await modifiedCell.resolveTextModelRef(); + + if (this._isDisposed) { + return; + } + const textModel = originalRef.object.textEditorModel; const modifiedTextModel = modifiedRef.object.textEditorModel; this._register(originalRef); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts index f91e70ba6..cb8709d16 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts @@ -7,11 +7,11 @@ import { IBulkEditService, ResourceTextEdit } from 'vs/editor/browser/services/b import { localize } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor'; -import { ActiveEditorContext } from 'vs/workbench/common/editor'; +import { ActiveEditorContext, viewColumnToEditorGroup } from 'vs/workbench/common/editor'; import { CellDiffViewModel } from 'vs/workbench/contrib/notebook/browser/diff/celllDiffViewModel'; import { NotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor'; import { NotebookDiffEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookDiffEditorInput'; +import { openAsTextIcon, revertIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -21,7 +21,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'notebook.diff.switchToText', - icon: { id: 'codicon/file-code' }, + icon: openAsTextIcon, title: { value: localize('notebook.diff.switchToText', "Open Text Diff Editor"), original: 'Open Text Diff Editor' }, precondition: ActiveEditorContext.isEqualTo(NotebookTextDiffEditor.ID), menu: [{ @@ -57,7 +57,7 @@ registerAction2(class extends Action2 { { id: 'notebook.diff.cell.revertMetadata', title: localize('notebook.diff.cell.revertMetadata', "Revert Metadata"), - icon: { id: 'codicon/discard' }, + icon: revertIcon, f1: false, menu: { id: MenuId.NotebookDiffCellMetadataTitle @@ -87,7 +87,7 @@ registerAction2(class extends Action2 { { id: 'notebook.diff.cell.revertOutputs', title: localize('notebook.diff.cell.revertOutputs', "Revert Outputs"), - icon: { id: 'codicon/discard' }, + icon: revertIcon, f1: false, menu: { id: MenuId.NotebookDiffCellOutputsTitle @@ -117,7 +117,7 @@ registerAction2(class extends Action2 { { id: 'notebook.diff.cell.revertInput', title: localize('notebook.diff.cell.revertInput', "Revert Input"), - icon: { id: 'codicon/discard' }, + icon: revertIcon, f1: false, menu: { id: MenuId.NotebookDiffCellInputTitle diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts index 6c6c52dc9..13f6973c0 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts @@ -60,6 +60,8 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD return this._model?.modified.notebook; } + private _revealFirst: boolean; + constructor( @IInstantiationService readonly instantiationService: IInstantiationService, @IThemeService readonly themeService: IThemeService, @@ -74,6 +76,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD super(NotebookTextDiffEditor.ID, telemetryService, themeService, storageService); const editorOptions = this.configurationService.getValue('editor'); this._fontInfo = BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel()); + this._revealFirst = true; this._register(this._modifiedResourceDisposableStore); } @@ -152,13 +155,15 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD return; } + this._revealFirst = true; + this._modifiedResourceDisposableStore.add(this._fileService.watch(this._model.modified.resource)); this._modifiedResourceDisposableStore.add(this._fileService.onDidFilesChange(async e => { if (this._model === null) { return; } - if (e.contains(this._model!.modified.resource)) { + if (e.contains(this._model.modified.resource)) { if (this._model.modified.isDirty()) { return; } @@ -174,7 +179,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } } - if (e.contains(this._model!.original.resource)) { + if (e.contains(this._model.original.resource)) { if (this._model.original.isDirty()) { return; } @@ -223,6 +228,8 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD let originalCellIndex = 0; let modifiedCellIndex = 0; + let firstChangeIndex = -1; + for (let i = 0; i < cellChanges.length; i++) { const change = cellChanges[i]; // common cells @@ -238,6 +245,10 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD this._eventDispatcher! )); } else { + if (firstChangeIndex === -1) { + firstChangeIndex = cellDiffViewModels.length; + } + cellDiffViewModels.push(new CellDiffViewModel( originalCell, modifiedCell, @@ -247,7 +258,12 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } } - cellDiffViewModels.push(...this._computeModifiedLCS(change, originalModel, modifiedModel)); + const modifiedLCS = this._computeModifiedLCS(change, originalModel, modifiedModel); + if (modifiedLCS.length && firstChangeIndex === -1) { + firstChangeIndex = cellDiffViewModels.length; + } + + cellDiffViewModels.push(...modifiedLCS); originalCellIndex = change.originalStart + change.originalLength; modifiedCellIndex = change.modifiedStart + change.modifiedLength; } @@ -262,6 +278,12 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } this._list.splice(0, this._list.length, cellDiffViewModels); + + if (this._revealFirst && firstChangeIndex !== -1) { + this._revealFirst = false; + this._list.setFocus([firstChangeIndex]); + this._list.reveal(firstChangeIndex, 0.3); + } } private _computeModifiedLCS(change: IDiffChange, originalModel: NotebookTextModel, modifiedModel: NotebookTextModel) { @@ -306,7 +328,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD layoutNotebookCell(cell: CellDiffViewModel, height: number) { const relayout = (cell: CellDiffViewModel, height: number) => { - const viewIndex = this._list!.indexOf(cell); + const viewIndex = this._list.indexOf(cell); this._list?.updateElementHeight(viewIndex, height); }; diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index 526e80d5b..c2b2830c4 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -211,6 +211,15 @@ max-width: 100%; } +.monaco-workbench .notebookOverlay .output-show-more-container { + position: absolute; +} + +.monaco-workbench .notebookOverlay .output-show-more-container p { + padding: 8px 8px 0 8px; + margin: 0px; +} + .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .menu { position: absolute; left: 0; @@ -258,8 +267,8 @@ /* top and bottom borders on cells */ .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-top:before, .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-bottom:before, -.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused:before, -.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused:after { +.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused .cell-inner-container:before, +.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused .cell-inner-container:after { content: ""; position: absolute; width: 100%; @@ -268,35 +277,29 @@ /* top border */ .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-top:before, -.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused:before { +.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused .cell-inner-container:before { border-top: 1px solid transparent; } -.monaco-workbench.hc-black .notebookOverlay .monaco-list .monaco-list-row.focused.cell-editor-focus .cell-focus-indicator-top:before, -.monaco-workbench.hc-black .notebookOverlay .monaco-list .markdown-cell-row.focused.cell-editor-focus:before { - border-top-style: dashed; -} - /* bottom border */ .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-bottom:before, -.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused:after { +.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused .cell-inner-container:after { border-bottom: 1px solid transparent; } -.monaco-workbench.hc-black .notebookOverlay .monaco-list .monaco-list-row.focused.cell-editor-focus .cell-focus-indicator-bottom:before, -.monaco-workbench.hc-black .notebookOverlay .monaco-list .markdown-cell-row.focused.cell-editor-focus:after { - border-bottom-style: dashed; -} - .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-top:before, -.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused:before { +.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused .cell-inner-container:before { top: 0; } .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-bottom:before, -.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused:after { +.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused .cell-inner-container:after { bottom: 0px; } +.monaco-workbench.hc-black .notebookOverlay .monaco-list-row .cell-editor-focus .cell-editor-part:before { + outline-style: dashed; +} + .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .menu.mouseover, .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .menu:hover { cursor: pointer; @@ -433,9 +436,7 @@ } .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .run-button-container { position: relative; - height: 16px; flex-shrink: 0; - top: 9px; z-index: 27; /* Above the drag handle */ } @@ -445,8 +446,8 @@ } .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .run-button-container .monaco-toolbar .codicon { - margin: 0; - padding-right: 4px; + margin: 0 4px 0 0; + padding: 6px; } .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .run-button-container .monaco-toolbar .actions-container { @@ -479,7 +480,7 @@ } .monaco-workbench .notebookOverlay .cell .monaco-progress-container { - top: -5px; + top: -3px; position: absolute; left: 0; @@ -491,7 +492,8 @@ height: 2px; } -.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused .cell-has-toolbar-actions .cell-title-toolbar, +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list:focus-within > .monaco-scrollable-element > .monaco-list-rows:not(:hover) > .monaco-list-row.focused .cell-has-toolbar-actions .cell-title-toolbar, +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover .cell-has-toolbar-actions .cell-title-toolbar, .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-has-toolbar-actions.cell-output-hover .cell-title-toolbar, .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-has-toolbar-actions:hover .cell-title-toolbar { visibility: visible; @@ -533,6 +535,11 @@ cursor: grab; } +.monaco-workbench .notebookOverlay.notebook-editor-editable > .cell-list-container > .monaco-list > .monaco-scrollable-element > .scrollbar.visible { + z-index: 25; + cursor: default; +} + .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator .codicon:hover { cursor: pointer; } @@ -795,6 +802,7 @@ .monaco-workbench .notebookOverlay > .cell-list-container .notebook-folding-indicator .codicon { visibility: visible; height: 16px; + padding: 4px; } /** Theming */ @@ -847,3 +855,11 @@ position: absolute; display: flex; } + +.output-show-more { + padding: 8px 0; +} + +.cell-contributed-items.cell-contributed-items-left { + margin-left: 4px; +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 7cd908830..b3e9c30f7 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -241,7 +241,7 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri }); }, open: (editor, options, group) => { - return this.onEditorOpening2(editor, options, group); + return this.onEditorOpening(editor, options, group); } })); @@ -285,7 +285,7 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri return this.notebookService.getContributedNotebookProviders(resource); } - private onEditorOpening2(originalInput: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): IOpenEditorOverride | undefined { + private onEditorOpening(originalInput: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): IOpenEditorOverride | undefined { let id = typeof options?.override === 'string' ? options.override : undefined; if (id === undefined && originalInput.resource?.scheme === Schemas.untitled) { @@ -304,6 +304,10 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri return undefined; } + if (originalInput instanceof NotebookDiffEditorInput) { + return undefined; + } + let notebookUri: URI = originalInput.resource; let cellOptions: IResourceEditorInput | undefined; @@ -319,7 +323,12 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri } if (id === undefined) { - const existingEditors = group.editors.filter(editor => editor.resource && isEqual(editor.resource, notebookUri) && !(editor instanceof NotebookEditorInput)); + const existingEditors = group.editors.filter(editor => + editor.resource + && isEqual(editor.resource, notebookUri) + && !(editor instanceof NotebookEditorInput) + && !(editor instanceof NotebookDiffEditorInput) + ); if (existingEditors.length) { return undefined; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 0306ba95e..1633a9f7b 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -8,7 +8,7 @@ import { IListContextMenuEvent, IListEvent, IListMouseEvent } from 'vs/base/brow import { IListOptions, IListStyles } from 'vs/base/browser/ui/list/listWidget'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; -import { Event } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ScrollEvent } from 'vs/base/common/scrollable'; import { URI } from 'vs/base/common/uri'; @@ -60,6 +60,7 @@ export const NOTEBOOK_CELL_OUTPUT_COLLAPSED = new RawContextKey('notebo // Shared commands export const EXPAND_CELL_CONTENT_COMMAND_ID = 'notebook.cell.expandCellContent'; +export const EXECUTE_CELL_COMMAND_ID = 'notebook.cell.execute'; // Kernels @@ -91,6 +92,8 @@ export interface CodeCellLayoutInfo { readonly totalHeight: number; readonly outputContainerOffset: number; readonly outputTotalHeight: number; + readonly outputShowMoreContainerHeight: number; + readonly outputShowMoreContainerOffset: number; readonly indicatorHeight: number; readonly bottomToolbarOffset: number; readonly layoutState: CodeCellLayoutState; @@ -99,6 +102,7 @@ export interface CodeCellLayoutInfo { export interface CodeCellLayoutChangeEvent { editorHeight?: boolean; outputHeight?: boolean; + outputShowMoreContainerHeight?: number; totalHeight?: boolean; outerWidth?: number; font?: BareFontInfo; @@ -203,6 +207,11 @@ export interface INotebookEditorCreationOptions { readonly contributions?: INotebookEditorContributionDescription[]; } +export interface IActiveNotebookEditor extends INotebookEditor { + viewModel: NotebookViewModel; + uri: URI; +} + export interface INotebookEditor extends IEditor { isEmbedded: boolean; @@ -212,6 +221,7 @@ export interface INotebookEditor extends IEditor { * Notebook view model attached to the current editor */ viewModel: NotebookViewModel | undefined; + hasModel(): this is IActiveNotebookEditor; /** * An event emitted when the model of this editor has changed. @@ -256,11 +266,17 @@ export interface INotebookEditor extends IEditor { * Layout info for the notebook editor */ getLayoutInfo(): NotebookLayoutInfo; + /** * Fetch the output renderers for notebook outputs. */ getOutputRenderer(): OutputRenderer; + /** + * Fetch the contributed kernels for this notebook + */ + beginComputeContributedKernels(): Promise; + /** * Insert a new cell around `cell` */ @@ -493,6 +509,7 @@ export interface INotebookCellList { revealElementInView(element: ICellViewModel): void; revealElementInCenterIfOutsideViewport(element: ICellViewModel): void; revealElementInCenter(element: ICellViewModel): void; + revealElementInCenterIfOutsideViewportAsync(element: ICellViewModel): Promise; revealElementLineInViewAsync(element: ICellViewModel, line: number): Promise; revealElementLineInCenterAsync(element: ICellViewModel, line: number): Promise; revealElementLineInCenterIfOutsideViewportAsync(element: ICellViewModel, line: number): Promise; @@ -552,6 +569,7 @@ export interface CodeCellRenderTemplate extends BaseCellRenderTemplate { runButtonContainer: HTMLElement; executionOrderLabel: HTMLElement; outputContainer: HTMLElement; + outputShowMoreContainer: HTMLElement; focusSinkElement: HTMLElement; editor: ICodeEditor; progressBar: ProgressBar; @@ -708,3 +726,17 @@ export function getActiveNotebookEditor(editorService: IEditorService): INoteboo const activeEditorPane = editorService.activeEditorPane as unknown as { isNotebookEditor?: boolean } | undefined; return activeEditorPane?.isNotebookEditor ? (editorService.activeEditorPane?.getControl() as INotebookEditor) : undefined; } + +let EDITOR_TOP_PADDING = 12; +const editorTopPaddingChangeEmitter = new Emitter(); + +export const EditorTopPaddingChangeEvent = editorTopPaddingChangeEmitter.event; + +export function updateEditorTopPadding(top: number) { + EDITOR_TOP_PADDING = top; + editorTopPaddingChangeEmitter.fire(); +} + +export function getEditorTopPadding() { + return EDITOR_TOP_PADDING; +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index 55391ea4f..3e8cd0308 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -14,7 +14,6 @@ import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; import { IReference } from 'vs/base/common/lifecycle'; import { INotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { IPathService } from 'vs/workbench/services/path/common/pathService'; interface NotebookEditorInputOptions { startDirty?: boolean; @@ -39,7 +38,6 @@ export class NotebookEditorInput extends EditorInput { @INotebookEditorModelResolverService private readonly _notebookModelResolverService: INotebookEditorModelResolverService, @IFilesConfigurationService private readonly _filesConfigurationService: IFilesConfigurationService, @IFileDialogService private readonly _fileDialogService: IFileDialogService, - @IPathService private readonly _pathService: IPathService, @IInstantiationService private readonly _instantiationService: IInstantiationService ) { super(); @@ -145,7 +143,7 @@ ${patterns} } async suggestName(suggestedFilename: string) { - return joinPath(this._fileDialogService.defaultFilePath() || (await this._pathService.userHome()), suggestedFilename); + return joinPath(await this._fileDialogService.defaultFilePath(), suggestedFilename); } // called when users rename a notebook document diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 0aa5ef97e..b58b545fe 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -9,7 +9,7 @@ import * as strings from 'vs/base/common/strings'; import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; import { IAction, Separator } from 'vs/base/common/actions'; -import { SequencerByKey } from 'vs/base/common/async'; +import { CancelablePromise, createCancelablePromise, SequencerByKey } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Color, RGBA } from 'vs/base/common/color'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -33,16 +33,16 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { contrastBorder, diffInserted, diffRemoved, editorBackground, errorForeground, focusBorder, foreground, listFocusBackground, listInactiveSelectionBackground, registerColor, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground, textPreformatForeground, transparent } from 'vs/platform/theme/common/colorRegistry'; -import { IColorTheme, IThemeService, registerThemingParticipant, ThemeColor } from 'vs/platform/theme/common/themeService'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { contrastBorder, diffInserted, diffRemoved, editorBackground, errorForeground, focusBorder, foreground, listInactiveSelectionBackground, registerColor, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground, textPreformatForeground, transparent } from 'vs/platform/theme/common/colorRegistry'; +import { IColorTheme, IThemeService, registerThemingParticipant, ThemeColor, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { EditorMemento } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorMemento } from 'vs/workbench/common/editor'; import { Memento, MementoObject } from 'vs/workbench/common/memento'; import { PANEL_BORDER } from 'vs/workbench/common/theme'; import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugToolBar'; import { BOTTOM_CELL_TOOLBAR_GAP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CELL_MARGIN, CELL_RUN_GUTTER, CELL_TOP_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT, SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellEditState, CellFocusMode, ICellViewModel, INotebookCellList, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorMouseEvent, NotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CellFocusMode, IActiveNotebookEditor, ICellViewModel, INotebookCellList, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorMouseEvent, NotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { NotebookKernelProviderAssociation, NotebookKernelProviderAssociations, notebookKernelProviderAssociationsSettingId } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; @@ -50,7 +50,7 @@ import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/outpu import { BackLayerWebView } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView'; import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys'; import { CodeCellRenderer, ListTopCellToolbar, MarkdownCellRenderer, NotebookCellListDelegate } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; -import { CellDragAndDropController } from 'vs/workbench/contrib/notebook/browser/view/renderers/dnd'; +import { CellDragAndDropController } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellDnd'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { NotebookEventDispatcher, NotebookLayoutChangedEvent } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; import { CellViewModel, IModelDecorationsChangeAccessor, INotebookEditorViewState, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; @@ -61,6 +61,7 @@ import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookS import { editorGutterModifiedBackground } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { configureKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; const $ = DOM.$; @@ -76,7 +77,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private _webviewResolved: boolean = false; private _webviewResolvePromise: Promise | null = null; private _webviewTransparentCover: HTMLElement | null = null; - private _list: INotebookCellList | undefined; + private _list!: INotebookCellList; private _dndController: CellDragAndDropController | null = null; private _listTopCellToolbar: ListTopCellToolbar | null = null; private _renderedEditors: Map = new Map(); @@ -108,9 +109,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private readonly _insetModifyQueueByOutputId = new SequencerByKey(); set scrollTop(top: number) { - if (this._list) { - this._list.scrollTop = top; - } + this._list.scrollTop = top; } private _cellContextKeyManager: CellContextKeyManager | null = null; @@ -153,6 +152,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private readonly _onDidChangeAvailableKernels = this._register(new Emitter()); readonly onDidChangeAvailableKernels: Event = this._onDidChangeAvailableKernels.event; + private _contributedKernelsComputePromise: CancelablePromise | null = null; + get activeKernel() { return this._activeKernel; } @@ -162,6 +163,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return; } + if (!this.viewModel) { + return; + } + if (this._activeKernel === kernel) { return; } @@ -169,8 +174,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._activeKernel = kernel; this._activeKernelResolvePromise = undefined; - const memento = this._activeKernelMemento.getMemento(StorageScope.GLOBAL); - memento[this.viewModel!.viewType] = this._activeKernel?.id; + const memento = this._activeKernelMemento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); + memento[this.viewModel.viewType] = this._activeKernel?.id; this._activeKernelMemento.saveMemento(); this._onDidChangeKernel.fire(); } @@ -197,7 +202,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return; } - const [focused] = this._list!.getFocusedElements(); + const [focused] = this._list.getFocusedElements(); return this._renderedEditors.get(focused); } @@ -224,7 +229,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor onDidChangeVisibleRanges: Event = this._onDidChangeVisibleRanges.event; get visibleRanges() { - return this._list?.visibleRanges || []; + return this._list.visibleRanges || []; } readonly isEmbedded: boolean; @@ -273,6 +278,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor }); this.notebookService.addNotebookEditor(this); + this._createEditor(); } /** @@ -286,7 +292,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return this.viewModel?.selectionHandles || []; } - hasModel() { + hasModel(): this is IActiveNotebookEditor { return !!this._notebookViewModel; } @@ -305,7 +311,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } protected getMemento(scope: StorageScope): MementoObject { - return this._memento.getMemento(scope); + return this._memento.getMemento(scope, StorageTarget.MACHINE); } public get isNotebookEditor() { @@ -335,7 +341,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor // a descendent of the notebook editor root. const focused = DOM.isAncestor(document.activeElement, this._overlayContainer); this._editorFocus?.set(focused); - this._notebookViewModel?.setFocus(focused); + this.viewModel?.setFocus(focused); } hasFocus() { @@ -380,7 +386,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return false; } - createEditor(): void { + private _createEditor(): void { const id = generateUuid(); this._overlayContainer.id = `notebook-${id}`; this._overlayContainer.className = 'notebookOverlay'; @@ -440,7 +446,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor 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); + const getScopedContextKeyService = (container?: HTMLElement) => this._list.contextKeyService.createScoped(container); const renderers = [ this.instantiationService.createInstance(CodeCellRenderer, this, this._renderedEditors, this._dndController, getScopedContextKeyService), this.instantiationService.createInstance(MarkdownCellRenderer, this, this._dndController, this._renderedEditors, getScopedContextKeyService), @@ -465,7 +471,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor enableKeyboardNavigation: true, additionalScrollHeight: 0, transformOptimization: false, //(isMacintosh && isNative) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native', - styleController: (_suffix: string) => { return this._list!; }, + styleController: (_suffix: string) => { return this._list; }, overrideStyles: { listBackground: editorBackground, listActiveSelectionBackground: editorBackground, @@ -583,7 +589,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor // Will fire onDidChangeFocus, resetting the state to Container applyFocusChange(); - const newFocusedCell = this._list!.getFocusedElements()[0]; + const newFocusedCell = this._list.getFocusedElements()[0]; if (newFocusedCell.cellKind === CellKind.Code || newFocusedCell.editState === CellEditState.Editing) { this.focusNotebookCell(newFocusedCell, 'editor'); } else { @@ -621,9 +627,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor if (this._webiewFocused) { this._webview?.focusWebview(); } else { - const focus = this._list?.getFocus()[0]; - if (typeof focus === 'number') { - const element = this._notebookViewModel!.viewCells[focus]; + const focus = this._list.getFocus()[0]; + if (typeof focus === 'number' && this.viewModel) { + const element = this.viewModel.viewCells[focus]; if (element.focusMode === CellFocusMode.Editor) { element.editState = CellEditState.Editing; @@ -633,7 +639,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } } - this._list?.domFocus(); + this._list.domFocus(); } this._onDidFocusEditorWidget.fire(); @@ -644,7 +650,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } async setModel(textModel: NotebookTextModel, viewState: INotebookEditorViewState | undefined): Promise { - if (this._notebookViewModel === undefined || !this._notebookViewModel.equal(textModel)) { + if (this.viewModel === undefined || !this.viewModel.equal(textModel)) { this._detachModel(); await this._attachModel(textModel, viewState); } else { @@ -657,7 +663,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._currentKernelTokenSource = new CancellationTokenSource(); this._localStore.add(this._currentKernelTokenSource); // we don't await for it, otherwise it will slow down the file opening - this._setKernels(textModel, this._currentKernelTokenSource); + this._setKernels(this._currentKernelTokenSource); this._localStore.add(this.notebookService.onDidChangeKernels(async (e) => { if (e && e.toString() !== this.textModel?.uri.toString()) { @@ -666,11 +672,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } this._currentKernelTokenSource?.cancel(); this._currentKernelTokenSource = new CancellationTokenSource(); - await this._setKernels(textModel, this._currentKernelTokenSource); + await this._setKernels(this._currentKernelTokenSource); })); - this._localStore.add(this._list!.onDidChangeFocus(() => { - const focused = this._list!.getFocusedElements()[0]; + this._localStore.add(this._list.onDidChangeFocus(() => { + const focused = this._list.getFocusedElements()[0]; if (focused) { if (!this._cellContextKeyManager) { this._cellContextKeyManager = this._localStore.add(new CellContextKeyManager(this.scopedContextKeyService, this, textModel, focused as CellViewModel)); @@ -683,12 +689,12 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor async setOptions(options: NotebookEditorOptions | undefined) { // reveal cell if editor options tell to do so - if (options?.cellOptions) { + if (options?.cellOptions && this.viewModel) { const cellOptions = options.cellOptions; - const cell = this._notebookViewModel!.viewCells.find(cell => cell.uri.toString() === cellOptions.resource.toString()); + const cell = this.viewModel.viewCells.find(cell => cell.uri.toString() === cellOptions.resource.toString()); if (cell) { this.selectElement(cell); - this.revealInCenterIfOutsideViewport(cell); + await this.revealInCenterIfOutsideViewportAsync(cell); const editor = this._renderedEditors.get(cell)!; if (editor) { if (cellOptions.options?.selection) { @@ -714,21 +720,40 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private _detachModel() { this._localStore.clear(); - this._list?.detachViewModel(); + this._list.detachViewModel(); this.viewModel?.dispose(); // avoid event - this._notebookViewModel = undefined; + this.viewModel = undefined; // this.webview?.clearInsets(); // this.webview?.clearPreloadsCache(); this._webview?.dispose(); this._webview?.element.remove(); this._webview = null; - this._list?.clear(); + this._list.clear(); } - private async _setKernels(textModel: NotebookTextModel, tokenSource: CancellationTokenSource) { - const provider = this.notebookService.getContributedNotebookProvider(textModel.viewType) || this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0]; - const availableKernels2 = await this.notebookService.getContributedNotebookKernels2(textModel.viewType, textModel.uri, tokenSource.token); + async beginComputeContributedKernels() { + if (this._contributedKernelsComputePromise) { + return this._contributedKernelsComputePromise; + } + + this._contributedKernelsComputePromise = createCancelablePromise(token => { + return this.notebookService.getContributedNotebookKernels(this.viewModel!.viewType, this.viewModel!.uri, token); + }); + + const result = await this._contributedKernelsComputePromise; + this._contributedKernelsComputePromise = null; + + return result; + } + + private async _setKernels(tokenSource: CancellationTokenSource) { + if (!this.viewModel) { + return; + } + + const provider = this.notebookService.getContributedNotebookProvider(this.viewModel.viewType) || this.notebookService.getContributedNotebookProviders(this.viewModel.uri)[0]; + const availableKernels = await this.beginComputeContributedKernels(); if (tokenSource.token.isCancellationRequested) { return; @@ -738,7 +763,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return; } - if ((availableKernels2.length) > 1) { + if ((availableKernels.length) > 1) { this._notebookHasMultipleKernels!.set(true); this.multipleKernelsAvailable = true; } else { @@ -746,30 +771,24 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this.multipleKernelsAvailable = false; } - const activeKernelStillExist = [...availableKernels2].find(kernel => kernel.id === this.activeKernel?.id && this.activeKernel?.id !== undefined); + const activeKernelStillExist = [...availableKernels].find(kernel => kernel.id === this.activeKernel?.id && this.activeKernel?.id !== undefined); if (activeKernelStillExist) { // the kernel still exist, we don't want to modify the selection otherwise user's temporary preference is lost return; } - if (availableKernels2.length) { - return this._setKernelsFromProviders(provider, availableKernels2, tokenSource); + if (availableKernels.length) { + return this._setKernelsFromProviders(provider, availableKernels, tokenSource); } - // the provider doesn't have a builtin kernel, choose a kernel - // this.activeKernel = availableKernels[0]; - // if (this.activeKernel) { - // await this._loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel); - // } - tokenSource.dispose(); } private async _setKernelsFromProviders(provider: NotebookProviderInfo, kernels: INotebookKernelInfo2[], tokenSource: CancellationTokenSource) { const rawAssociations = this.configurationService.getValue(notebookKernelProviderAssociationsSettingId) || []; const userSetKernelProvider = rawAssociations.filter(e => e.viewType === this.viewModel?.viewType)[0]?.kernelProvider; - const memento = this._activeKernelMemento.getMemento(StorageScope.GLOBAL); + const memento = this._activeKernelMemento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); if (userSetKernelProvider) { const filteredKernels = kernels.filter(kernel => kernel.extension.value === userSetKernelProvider); @@ -860,9 +879,13 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } private _updateForMetadata(): void { - const notebookMetadata = this.viewModel!.metadata; + if (!this.viewModel) { + return; + } + + const notebookMetadata = this.viewModel.metadata; this._editorEditable?.set(!!notebookMetadata?.editable); - this._editorRunnable?.set(!!notebookMetadata?.runnable); + this._editorRunnable?.set(this.viewModel.runnable); this._overflowContainer.classList.toggle('notebook-editor-editable', !!notebookMetadata?.editable); this.getDomNode().classList.toggle('notebook-editor-editable', !!notebookMetadata?.editable); @@ -881,12 +904,20 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor if (!this._webview) { this._webview = this.instantiationService.createInstance(BackLayerWebView, this, this.getId(), this.textModel!.uri); // attach the webview container to the DOM tree first - this._list?.rowsContainer.insertAdjacentElement('afterbegin', this._webview.element); + this._list.rowsContainer.insertAdjacentElement('afterbegin', this._webview.element); } this._webviewResolvePromise = new Promise(async resolve => { - await this._webview!.createWebview(); - this._webview!.webview!.onDidBlur(() => { + if (!this._webview) { + throw new Error('Notebook output webview object is not created successfully.'); + } + + await this._webview.createWebview(); + if (!this._webview.webview) { + throw new Error('Notebook output webview elemented is not created successfully.'); + } + + this._webview.webview.onDidBlur(() => { this._outputFocus?.set(false); this.updateEditorFocus(); @@ -894,7 +925,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._webiewFocused = false; } }); - this._webview!.webview!.onDidFocus(() => { + this._webview.webview.onDidFocus(() => { this._outputFocus?.set(true); this.updateEditorFocus(); this._onDidFocusEmitter.fire(); @@ -904,7 +935,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } }); - this._localStore.add(this._webview!.onMessage(({ message, forRenderer }) => { + this._localStore.add(this._webview.onMessage(({ message, forRenderer }) => { if (this.viewModel) { this.notebookService.onDidReceiveMessage(this.viewModel.viewType, this.getId(), forRenderer, message); } @@ -912,7 +943,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._webviewResolved = true; - resolve(this._webview!); + resolve(this._webview); }); return this._webviewResolvePromise; @@ -921,7 +952,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private async _createWebview(id: string, resource: URI): Promise { this._webview = this.instantiationService.createInstance(BackLayerWebView, this, id, resource); // attach the webview container to the DOM tree first - this._list?.rowsContainer.insertAdjacentElement('afterbegin', this._webview.element); + this._list.rowsContainer.insertAdjacentElement('afterbegin', this._webview.element); } private async _attachModel(textModel: NotebookTextModel, viewState: INotebookEditorViewState | undefined) { @@ -959,7 +990,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._onDidChangeSelection.fire(); })); - this._localStore.add(this._list!.onWillScroll(e => { + this._localStore.add(this._list.onWillScroll(e => { this._onWillScroll.fire(e); if (!this._webviewResolved) { return; @@ -969,14 +1000,14 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._webviewTransparentCover!.style.top = `${e.scrollTop}px`; })); - this._localStore.add(this._list!.onDidChangeContentHeight(() => { + this._localStore.add(this._list.onDidChangeContentHeight(() => { DOM.scheduleAtNextAnimationFrame(() => { if (this._isDisposed) { return; } - const scrollTop = this._list?.scrollTop || 0; - const scrollHeight = this._list?.scrollHeight || 0; + const scrollTop = this._list.scrollTop; + const scrollHeight = this._list.scrollHeight; if (!this._webviewResolved) { return; @@ -989,7 +1020,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor const removedItems: IProcessedOutput[] = []; this._webview?.insetMapping.forEach((value, key) => { const cell = value.cell; - const viewIndex = this._list?.getViewIndex(cell); + const viewIndex = this._list.getViewIndex(cell); if (viewIndex === undefined) { return; @@ -1000,7 +1031,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor removedItems.push(key); } - const cellTop = this._list?.getAbsoluteTopOfElement(cell) || 0; + const cellTop = this._list.getAbsoluteTopOfElement(cell); if (this._webview!.shouldUpdateInset(cell, key, cellTop)) { updateItems.push({ cell: cell, @@ -1019,18 +1050,18 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor }); })); - this._list!.attachViewModel(this.viewModel); - this._localStore.add(this._list!.onDidRemoveOutput(output => { + this._list.attachViewModel(this.viewModel); + this._localStore.add(this._list.onDidRemoveOutput(output => { this.removeInset(output); })); - this._localStore.add(this._list!.onDidHideOutput(output => { + this._localStore.add(this._list.onDidHideOutput(output => { this.hideInset(output); })); if (this._dimension) { - this._list?.layout(this._dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP, this._dimension.width); + this._list.layout(this._dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP, this._dimension.width); } else { - this._list!.layout(); + this._list.layout(); } this._dndController?.clearGlobalDragState(); @@ -1041,23 +1072,23 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor restoreListViewState(viewState: INotebookEditorViewState | undefined): void { if (viewState?.scrollPosition !== undefined) { - this._list!.scrollTop = viewState!.scrollPosition.top; - this._list!.scrollLeft = viewState!.scrollPosition.left; + this._list.scrollTop = viewState!.scrollPosition.top; + this._list.scrollLeft = viewState!.scrollPosition.left; } else { - this._list!.scrollTop = 0; - this._list!.scrollLeft = 0; + this._list.scrollTop = 0; + this._list.scrollLeft = 0; } const focusIdx = typeof viewState?.focus === 'number' ? viewState.focus : 0; - if (focusIdx < this._list!.length) { - this._list!.setFocus([focusIdx]); - this._list!.setSelection([focusIdx]); - } else if (this._list!.length > 0) { - this._list!.setFocus([0]); + if (focusIdx < this._list.length) { + this._list.setFocus([focusIdx]); + this._list.setSelection([focusIdx]); + } else if (this._list.length > 0) { + this._list.setFocus([0]); } if (viewState?.editorFocused) { - const cell = this._notebookViewModel?.viewCells[focusIdx]; + const cell = this.viewModel?.viewCells[focusIdx]; if (cell) { cell.focusMode = CellFocusMode.Editor; } @@ -1065,7 +1096,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } getEditorViewState(): INotebookEditorViewState { - const state = this._notebookViewModel?.getEditorViewState(); + const state = this.viewModel?.getEditorViewState(); if (!state) { return { editingCells: {}, @@ -1088,10 +1119,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor state.cellTotalHeights = cellHeights; const focus = this._list.getFocus()[0]; - if (typeof focus === 'number') { - const element = this._notebookViewModel!.viewCells[focus]; + if (typeof focus === 'number' && this.viewModel) { + const element = this.viewModel.viewCells[focus]; if (element) { - const itemDOM = this._list?.domElementOfElement(element); + const itemDOM = this._list.domElementOfElement(element); const editorFocused = !!(document.activeElement && itemDOM && itemDOM.contains(document.activeElement)); state.editorFocused = editorFocused; @@ -1143,8 +1174,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._dimension = new DOM.Dimension(dimension.width, dimension.height); DOM.size(this._body, dimension.width, dimension.height); - this._list?.updateOptions({ additionalScrollHeight: this._scrollBeyondLastLine ? dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP : 0 }); - this._list?.layout(dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP, dimension.width); + this._list.updateOptions({ additionalScrollHeight: this._scrollBeyondLastLine ? dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP : 0 }); + this._list.layout(dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP, dimension.width); this._overlayContainer.style.visibility = 'visible'; this._overlayContainer.style.display = 'block'; @@ -1177,56 +1208,60 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor //#region Editor Features selectElement(cell: ICellViewModel) { - this._list?.selectElement(cell); + this._list.selectElement(cell); // this.viewModel!.selectionHandles = [cell.handle]; } revealInView(cell: ICellViewModel) { - this._list?.revealElementInView(cell); + this._list.revealElementInView(cell); } revealInCenterIfOutsideViewport(cell: ICellViewModel) { - this._list?.revealElementInCenterIfOutsideViewport(cell); + this._list.revealElementInCenterIfOutsideViewport(cell); + } + + async revealInCenterIfOutsideViewportAsync(cell: ICellViewModel) { + return this._list.revealElementInCenterIfOutsideViewportAsync(cell); } revealInCenter(cell: ICellViewModel) { - this._list?.revealElementInCenter(cell); + this._list.revealElementInCenter(cell); } async revealLineInViewAsync(cell: ICellViewModel, line: number): Promise { - return this._list?.revealElementLineInViewAsync(cell, line); + return this._list.revealElementLineInViewAsync(cell, line); } async revealLineInCenterAsync(cell: ICellViewModel, line: number): Promise { - return this._list?.revealElementLineInCenterAsync(cell, line); + return this._list.revealElementLineInCenterAsync(cell, line); } async revealLineInCenterIfOutsideViewportAsync(cell: ICellViewModel, line: number): Promise { - return this._list?.revealElementLineInCenterIfOutsideViewportAsync(cell, line); + return this._list.revealElementLineInCenterIfOutsideViewportAsync(cell, line); } async revealRangeInViewAsync(cell: ICellViewModel, range: Range): Promise { - return this._list?.revealElementRangeInViewAsync(cell, range); + return this._list.revealElementRangeInViewAsync(cell, range); } async revealRangeInCenterAsync(cell: ICellViewModel, range: Range): Promise { - return this._list?.revealElementRangeInCenterAsync(cell, range); + return this._list.revealElementRangeInCenterAsync(cell, range); } async revealRangeInCenterIfOutsideViewportAsync(cell: ICellViewModel, range: Range): Promise { - return this._list?.revealElementRangeInCenterIfOutsideViewportAsync(cell, range); + return this._list.revealElementRangeInCenterIfOutsideViewportAsync(cell, range); } setCellSelection(cell: ICellViewModel, range: Range): void { - this._list?.setCellSelection(cell, range); + this._list.setCellSelection(cell, range); } changeModelDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null { - return this._notebookViewModel?.changeModelDecorations(callback) || null; + return this.viewModel?.changeModelDecorations(callback) || null; } setHiddenAreas(_ranges: ICellRange[]): boolean { - return this._list!.setHiddenAreas(_ranges, true); + return this._list.setHiddenAreas(_ranges, true); } private _editorStyleSheets = new Map(); @@ -1301,7 +1336,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor //#region Cell operations async layoutNotebookCell(cell: ICellViewModel, height: number): Promise { - const viewIndex = this._list!.getViewIndex(cell); + const viewIndex = this._list.getViewIndex(cell); if (viewIndex === undefined) { // the cell is hidden return; @@ -1312,7 +1347,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return; } - this._list?.updateElementHeight2(cell, height); + this._list.updateElementHeight2(cell, height); }; if (this.pendingLayouts.has(cell)) { @@ -1339,42 +1374,90 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return new Promise(resolve => { r = resolve; }); } + private _nearestCodeCellIndex(index: number /* exclusive */, direction: 'above' | 'below') { + if (!this.viewModel) { + return -1; + } + + const nearest = this.viewModel.viewCells.slice(0, index).reverse().findIndex(cell => cell.cellKind === CellKind.Code); + if (nearest > -1) { + return index - nearest - 1; + } else { + const nearestCellTheOtherDirection = this.viewModel.viewCells.slice(index + 1).findIndex(cell => cell.cellKind === CellKind.Code); + if (nearestCellTheOtherDirection > -1) { + return index + nearestCellTheOtherDirection; + } + return -1; + } + } + insertNotebookCell(cell: ICellViewModel | undefined, type: CellKind, direction: 'above' | 'below' = 'above', initialText: string = '', ui: boolean = false): CellViewModel | null { - if (!this._notebookViewModel!.metadata.editable) { + if (!this.viewModel) { return null; } - const index = cell ? this._notebookViewModel!.getCellIndex(cell) : 0; - const nextIndex = ui ? this._notebookViewModel!.getNextVisibleCellIndex(index) : index + 1; - const newLanguages = this._notebookViewModel!.resolvedLanguages; - const language = (cell?.cellKind === CellKind.Code && type === CellKind.Code) - ? cell.language - : ((type === CellKind.Code && newLanguages && newLanguages.length) ? newLanguages[0] : 'markdown'); + if (!this.viewModel.metadata.editable) { + return null; + } + + const index = cell ? this.viewModel.getCellIndex(cell) : 0; + const nextIndex = ui ? this.viewModel.getNextVisibleCellIndex(index) : index + 1; + let language; + if (type === CellKind.Code) { + if (cell?.cellKind === CellKind.Code) { + language = cell.language; + } else if (cell?.cellKind === CellKind.Markdown) { + const nearestCodeCellIndex = this._nearestCodeCellIndex(index, direction); + if (nearestCodeCellIndex > -1) { + language = this.viewModel.viewCells[nearestCodeCellIndex].language; + } else { + language = this.viewModel.resolvedLanguages[0] || 'plaintext'; + } + } else { + if (cell === undefined && direction === 'above') { + // insert cell at the very top + language = this.viewModel.viewCells.find(cell => cell.cellKind === CellKind.Code)?.language || this.viewModel.resolvedLanguages[0] || 'plaintext'; + } else { + language = this.viewModel.resolvedLanguages[0] || 'plaintext'; + } + } + } else { + language = 'markdown'; + } + const insertIndex = cell ? (direction === 'above' ? index : nextIndex) : index; - const focused = this._list?.getFocusedElements(); - const newCell = this._notebookViewModel!.createCell(insertIndex, initialText, language, type, undefined, [], true, undefined, focused); + const focused = this._list.getFocusedElements(); + const newCell = this.viewModel.createCell(insertIndex, initialText, language, type, undefined, [], true, undefined, focused); return newCell as CellViewModel; } async splitNotebookCell(cell: ICellViewModel): Promise { - if (!this._notebookViewModel!.metadata.editable) { + if (!this.viewModel) { return null; } - const index = this._notebookViewModel!.getCellIndex(cell); + if (!this.viewModel.metadata.editable) { + return null; + } - return this._notebookViewModel!.splitNotebookCell(index); + const index = this.viewModel.getCellIndex(cell); + + return this.viewModel.splitNotebookCell(index); } async joinNotebookCells(cell: ICellViewModel, direction: 'above' | 'below', constraint?: CellKind): Promise { - if (!this._notebookViewModel!.metadata.editable) { + if (!this.viewModel) { return null; } - const index = this._notebookViewModel!.getCellIndex(cell); - const ret = await this._notebookViewModel!.joinNotebookCells(index, direction, constraint); + if (!this.viewModel.metadata.editable) { + return null; + } + + const index = this.viewModel.getCellIndex(cell); + const ret = await this.viewModel.joinNotebookCells(index, direction, constraint); if (ret) { ret.deletedCells.forEach(cell => { @@ -1390,7 +1473,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } async deleteNotebookCell(cell: ICellViewModel): Promise { - if (!this._notebookViewModel!.metadata.editable) { + if (!this.viewModel) { + return false; + } + + if (!this.viewModel.metadata.editable) { return false; } @@ -1398,18 +1485,22 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this.pendingLayouts.get(cell)!.dispose(); } - const index = this._notebookViewModel!.getCellIndex(cell); - this._notebookViewModel!.deleteCell(index, true); + const index = this.viewModel.getCellIndex(cell); + this.viewModel.deleteCell(index, true); return true; } async moveCellDown(cell: ICellViewModel): Promise { - if (!this._notebookViewModel!.metadata.editable) { + if (!this.viewModel) { return null; } - const index = this._notebookViewModel!.getCellIndex(cell); - if (index === this._notebookViewModel!.length - 1) { + if (!this.viewModel.metadata.editable) { + return null; + } + + const index = this.viewModel.getCellIndex(cell); + if (index === this.viewModel.length - 1) { return null; } @@ -1418,11 +1509,15 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } async moveCellUp(cell: ICellViewModel): Promise { - if (!this._notebookViewModel!.metadata.editable) { + if (!this.viewModel) { return null; } - const index = this._notebookViewModel!.getCellIndex(cell); + if (!this.viewModel.metadata.editable) { + return null; + } + + const index = this.viewModel.getCellIndex(cell); if (index === 0) { return null; } @@ -1432,7 +1527,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } async moveCell(cell: ICellViewModel, relativeToCell: ICellViewModel, direction: 'above' | 'below'): Promise { - if (!this._notebookViewModel!.metadata.editable) { + if (!this.viewModel) { + return null; + } + + if (!this.viewModel.metadata.editable) { return null; } @@ -1440,15 +1539,19 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return null; } - const originalIdx = this._notebookViewModel!.getCellIndex(cell); - const relativeToIndex = this._notebookViewModel!.getCellIndex(relativeToCell); + const originalIdx = this.viewModel.getCellIndex(cell); + const relativeToIndex = this.viewModel.getCellIndex(relativeToCell); const newIdx = direction === 'above' ? relativeToIndex : relativeToIndex + 1; return this._moveCellToIndex(originalIdx, 1, newIdx); } async moveCellsToIdx(index: number, length: number, toIdx: number): Promise { - if (!this._notebookViewModel!.metadata.editable) { + if (!this.viewModel) { + return null; + } + + if (!this.viewModel.metadata.editable) { return null; } @@ -1461,6 +1564,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor * @example to move the cell from index 0 down one spot, call with (0, 2) */ private async _moveCellToIndex(index: number, length: number, desiredIndex: number): Promise { + if (!this.viewModel) { + return null; + } + if (index < desiredIndex) { // The cell is moving "down", it will free up one index spot and consume a new one desiredIndex -= length; @@ -1470,7 +1577,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return null; } - if (!this._notebookViewModel!.moveCellToIdx(index, length, desiredIndex, true)) { + if (!this.viewModel.moveCellToIdx(index, length, desiredIndex, true)) { throw new Error('Notebook Editor move cell, index out of range'); } @@ -1478,10 +1585,16 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor DOM.scheduleAtNextAnimationFrame(() => { if (this._isDisposed) { r(null); + return; } - const viewCell = this._notebookViewModel!.viewCells[desiredIndex]; - this._list?.revealElementInView(viewCell); + if (!this.viewModel) { + r(null); + return; + } + + const viewCell = this.viewModel.viewCells[desiredIndex]; + this._list.revealElementInView(viewCell); r(viewCell); }); @@ -1489,7 +1602,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } editNotebookCell(cell: CellViewModel): void { - if (!cell.getEvaluatedMetadata(this._notebookViewModel!.metadata).editable) { + if (!this.viewModel) { + return; + } + + if (!cell.getEvaluatedMetadata(this.viewModel.metadata).editable) { return; } @@ -1499,7 +1616,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } getActiveCell() { - const elements = this._list?.getFocusedElements(); + const elements = this._list.getFocusedElements(); if (elements && elements.length) { return elements[0]; @@ -1519,9 +1636,15 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor // pick active kernel + const picker = this.quickInputService.createQuickPick<(IQuickPickItem & { run(): void; kernelProviderId?: string })>(); + picker.placeholder = nls.localize('notebook.runCell.selectKernel', "Select a notebook kernel to run this notebook"); + picker.matchOnDetail = true; + picker.busy = true; + picker.show(); + const tokenSource = new CancellationTokenSource(); - const availableKernels2 = await this.notebookService.getContributedNotebookKernels2(this.viewModel!.viewType, this.viewModel!.uri, tokenSource.token); - const picks: QuickPickInput[] = availableKernels2.map((a) => { + const availableKernels = await this.beginComputeContributedKernels(); + const picks: QuickPickInput[] = availableKernels.map((a) => { return { id: a.id, label: a.label, @@ -1537,16 +1660,14 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._activeKernelResolvePromise = this.activeKernel.resolve(this.viewModel!.uri, this.getId(), tokenSource.token); }, buttons: [{ - iconClass: 'codicon-settings-gear', + iconClass: ThemeIcon.asClassName(configureKernelIcon), tooltip: nls.localize('notebook.promptKernel.setDefaultTooltip', "Set as default kernel provider for '{0}'", this.viewModel!.viewType) }] }; }); - const picker = this.quickInputService.createQuickPick<(IQuickPickItem & { run(): void; kernelProviderId?: string })>(); picker.items = picks; - picker.placeholder = nls.localize('notebook.runCell.selectKernel', "Select a notebook kernel to run this notebook"); - picker.matchOnDetail = true; + picker.busy = false; const pickedItem = await new Promise<(IQuickPickItem & { run(): void; kernelProviderId?: string; }) | undefined>(resolve => { picker.onDidAccept(() => { @@ -1581,7 +1702,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } }); - picker.show(); }); tokenSource.dispose(); @@ -1594,29 +1714,41 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } async cancelNotebookExecution(): Promise { - if (this._notebookViewModel?.metadata.runState !== NotebookRunState.Running) { + if (!this.viewModel) { + return; + } + + if (this.viewModel.metadata.runState !== NotebookRunState.Running) { return; } await this._ensureActiveKernel(); - await this._activeKernel?.cancelNotebookCell!(this._notebookViewModel!.uri, undefined); + await this._activeKernel?.cancelNotebookCell!(this.viewModel.uri, undefined); } async executeNotebook(): Promise { - if (!this._notebookViewModel!.metadata.runnable) { + if (!this.viewModel) { + return; + } + + if (!this.viewModel.runnable) { return; } await this._ensureActiveKernel(); - await this._activeKernel?.executeNotebookCell!(this._notebookViewModel!.uri, undefined); + await this._activeKernel?.executeNotebookCell!(this.viewModel.uri, undefined); } async cancelNotebookCellExecution(cell: ICellViewModel): Promise { + if (!this.viewModel) { + return; + } + if (cell.cellKind !== CellKind.Code) { return; } - const metadata = cell.getEvaluatedMetadata(this._notebookViewModel!.metadata); + const metadata = cell.getEvaluatedMetadata(this.viewModel.metadata); if (!metadata.runnable) { return; } @@ -1626,21 +1758,25 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } await this._ensureActiveKernel(); - await this._activeKernel?.cancelNotebookCell!(this._notebookViewModel!.uri, cell.handle); + await this._activeKernel?.cancelNotebookCell!(this.viewModel.uri, cell.handle); } async executeNotebookCell(cell: ICellViewModel): Promise { + if (!this.viewModel) { + return; + } + if (cell.cellKind === CellKind.Markdown) { this.focusNotebookCell(cell, 'container'); return; } - if (!cell.getEvaluatedMetadata(this._notebookViewModel!.metadata).runnable) { + if (!cell.getEvaluatedMetadata(this.viewModel.metadata).runnable) { return; } await this._ensureActiveKernel(); - await this._activeKernel?.executeNotebookCell!(this._notebookViewModel!.uri, cell.handle); + await this._activeKernel?.executeNotebookCell!(this.viewModel.uri, cell.handle); } focusNotebookCell(cell: ICellViewModel, focusItem: 'editor' | 'container' | 'output') { @@ -1650,14 +1786,14 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor if (focusItem === 'editor') { this.selectElement(cell); - this._list?.focusView(); + this._list.focusView(); cell.editState = CellEditState.Editing; cell.focusMode = CellFocusMode.Editor; this.revealInCenterIfOutsideViewport(cell); } else if (focusItem === 'output') { this.selectElement(cell); - this._list?.focusView(); + this._list.focusView(); if (!this._webview) { return; @@ -1668,7 +1804,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor cell.focusMode = CellFocusMode.Container; this.revealInCenterIfOutsideViewport(cell); } else { - const itemDOM = this._list?.domElementOfElement(cell); + const itemDOM = this._list.domElementOfElement(cell); if (document.activeElement && itemDOM && itemDOM.contains(document.activeElement)) { (document.activeElement as HTMLElement).blur(); } @@ -1678,7 +1814,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this.selectElement(cell); this.revealInCenterIfOutsideViewport(cell); - this._list?.focusView(); + this._list.focusView(); } } @@ -1687,7 +1823,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor //#region MISC deltaCellDecorations(oldDecorations: string[], newDecorations: INotebookDeltaDecoration[]): string[] { - return this._notebookViewModel?.deltaCellDecorations(oldDecorations, newDecorations) || []; + return this.viewModel?.deltaCellDecorations(oldDecorations, newDecorations) || []; } deltaCellOutputContainerClassNames(cellId: string, added: string[], removed: string[]) { @@ -1707,7 +1843,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } triggerScroll(event: IMouseWheelEvent) { - this._list?.triggerScrollFromMouseWheelEvent(event); + this._list.triggerScrollFromMouseWheelEvent(event); } async createInset(cell: CodeCellViewModel, output: IInsetRenderOutput, offset: number): Promise { @@ -1719,11 +1855,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor await this._resolveWebview(); if (!this._webview!.insetMapping.has(output.source)) { - const cellTop = this._list?.getAbsoluteTopOfElement(cell) || 0; + const cellTop = this._list.getAbsoluteTopOfElement(cell); await this._webview!.createInset(cell, output, cellTop, offset); } else { - const cellTop = this._list?.getAbsoluteTopOfElement(cell) || 0; - const scrollTop = this._list?.scrollTop || 0; + const cellTop = this._list.getAbsoluteTopOfElement(cell); + const scrollTop = this._list.scrollTop; this._webview!.updateViewScrollTop(-scrollTop, true, [{ cell, output: output.source, cellTop }]); } @@ -1809,7 +1945,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } this._localStore.clear(); - this._list?.dispose(); + this._list.dispose(); this._listTopCellToolbar?.dispose(); this._overlayContainer.remove(); @@ -1828,7 +1964,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } export const notebookCellBorder = registerColor('notebook.cellBorderColor', { - dark: transparent(PANEL_BORDER, .4), + dark: transparent(listInactiveSelectionBackground, 1), light: transparent(listInactiveSelectionBackground, 1), hc: PANEL_BORDER }, nls.localize('notebook.cellBorderColor', "The border color for notebook cells.")); @@ -1859,7 +1995,7 @@ export const cellStatusIconRunning = registerColor('notebookStatusRunningIcon.fo export const notebookOutputContainerColor = registerColor('notebook.outputContainerBackgroundColor', { dark: notebookCellBorder, - light: transparent(listFocusBackground, .4), + light: notebookCellBorder, hc: null }, nls.localize('notebook.outputContainerBackgroundColor', "The Color of the notebook output container background.")); @@ -1871,8 +2007,8 @@ export const CELL_TOOLBAR_SEPERATOR = registerColor('notebook.cellToolbarSeparat }, nls.localize('notebook.cellToolbarSeparator', "The color of the separator in the cell bottom toolbar")); export const focusedCellBackground = registerColor('notebook.focusedCellBackground', { - dark: transparent(PANEL_BORDER, .4), - light: transparent(listFocusBackground, .4), + dark: null, + light: null, hc: null }, nls.localize('focusedCellBackground', "The background color of a cell when the cell is focused.")); @@ -1882,9 +2018,15 @@ export const cellHoverBackground = registerColor('notebook.cellHoverBackground', hc: null }, nls.localize('notebook.cellHoverBackground', "The background color of a cell when the cell is hovered.")); +export const selectedCellBorder = registerColor('notebook.selectedCellBorder', { + dark: notebookCellBorder, + light: notebookCellBorder, + hc: contrastBorder +}, nls.localize('notebook.selectedCellBorder', "The color of the cell's top and bottom border when the cell is selected but not focusd.")); + export const focusedCellBorder = registerColor('notebook.focusedCellBorder', { - dark: Color.white.transparent(0.12), - light: Color.black.transparent(0.12), + dark: focusBorder, + light: focusBorder, hc: focusBorder }, nls.localize('notebook.focusedCellBorder', "The color of the cell's top and bottom border when the cell is focused.")); @@ -1935,12 +2077,17 @@ registerThemingParticipant((theme, collector) => { const link = theme.getColor(textLinkForeground); if (link) { collector.addRule(`.notebookOverlay .output a, - .notebookOverlay .cell.markdown a { color: ${link};} `); + .notebookOverlay .cell.markdown a, + .notebookOverlay .output-show-more-container a + { color: ${link};} `); + } const activeLink = theme.getColor(textLinkActiveForeground); if (activeLink) { collector.addRule(`.notebookOverlay .output a:hover, - .notebookOverlay .cell .output a:active { color: ${activeLink}; }`); + .notebookOverlay .cell .output a:active, + .notebookOverlay .output-show-more-container a:active + { color: ${activeLink}; }`); } const shortcut = theme.getColor(textPreformatForeground); if (shortcut) { @@ -1964,6 +2111,7 @@ registerThemingParticipant((theme, collector) => { if (containerBackground) { collector.addRule(`.notebookOverlay .output { background-color: ${containerBackground}; }`); collector.addRule(`.notebookOverlay .output-element { background-color: ${containerBackground}; }`); + collector.addRule(`.notebookOverlay .output-show-more-container { background-color: ${containerBackground}; }`); } const editorBackgroundColor = theme.getColor(editorBackground); @@ -2005,13 +2153,23 @@ registerThemingParticipant((theme, collector) => { } const focusedCellBorderColor = theme.getColor(focusedCellBorder); - collector.addRule(`.monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused .cell-focus-indicator-top:before, + collector.addRule(` + .monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused .cell-focus-indicator-top:before, .monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused .cell-focus-indicator-bottom:before, - .monaco-workbench .notebookOverlay .monaco-list:focus-within .markdown-cell-row.focused:before, - .monaco-workbench .notebookOverlay .monaco-list:focus-within .markdown-cell-row.focused:after { + .monaco-workbench .notebookOverlay .monaco-list:focus-within .markdown-cell-row.focused .cell-inner-container:not(.cell-editor-focus):before, + .monaco-workbench .notebookOverlay .monaco-list:focus-within .markdown-cell-row.focused .cell-inner-container:not(.cell-editor-focus):after { border-color: ${focusedCellBorderColor} !important; }`); + const selectedCellBorderColor = theme.getColor(selectedCellBorder); + collector.addRule(` + .monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused .cell-editor-focus .cell-focus-indicator-top:before, + .monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused .cell-editor-focus .cell-focus-indicator-bottom:before, + .monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused .cell-inner-container.cell-editor-focus:before, + .monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused .cell-inner-container.cell-editor-focus:after { + border-color: ${selectedCellBorderColor} !important; + }`); + const cellSymbolHighlightColor = theme.getColor(cellSymbolHighlight); if (cellSymbolHighlightColor) { collector.addRule(`.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row.nb-symbolHighlight .cell-focus-indicator, @@ -2033,12 +2191,12 @@ registerThemingParticipant((theme, collector) => { const cellStatusSuccessIcon = theme.getColor(cellStatusIconSuccess); if (cellStatusSuccessIcon) { - collector.addRule(`.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-run-status .codicon-check { color: ${cellStatusSuccessIcon} }`); + collector.addRule(`.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-run-status .codicon-notebook-state-success { color: ${cellStatusSuccessIcon} }`); } const cellStatusErrorIcon = theme.getColor(cellStatusIconError); if (cellStatusErrorIcon) { - collector.addRule(`.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-run-status .codicon-error { color: ${cellStatusErrorIcon} }`); + collector.addRule(`.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-run-status .codicon-notebook-state-error { color: ${cellStatusErrorIcon} }`); } const cellStatusRunningIcon = theme.getColor(cellStatusIconRunning); @@ -2123,6 +2281,9 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.notebookOverlay .output { margin: 0px ${CELL_MARGIN}px 0px ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`); collector.addRule(`.notebookOverlay .output { width: calc(100% - ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER + (CELL_MARGIN * 2)}px); }`); + collector.addRule(`.notebookOverlay .output-show-more-container { margin: 0px ${CELL_MARGIN}px 0px ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`); + collector.addRule(`.notebookOverlay .output-show-more-container { width: calc(100% - ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER + (CELL_MARGIN * 2)}px); }`); + collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row div.cell.markdown { padding-left: ${CELL_RUN_GUTTER}px; }`); collector.addRule(`.notebookOverlay .cell .run-button-container { width: 20px; margin: 0px ${Math.floor(CELL_RUN_GUTTER - 20) / 2}px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-top { height: ${CELL_TOP_MARGIN}px; }`); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidgetService.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidgetService.ts index 783b82070..f9650ad09 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidgetService.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidgetService.ts @@ -127,7 +127,6 @@ class NotebookEditorWidgetService implements INotebookEditorWidgetService { // NEW widget const instantiationService = accessor.get(IInstantiationService); const widget = instantiationService.createInstance(NotebookEditorWidget, { isEmbedded: false }); - widget.createEditor(); const token = this._tokenPool++; value = { widget, token }; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookIcons.ts b/src/vs/workbench/contrib/notebook/browser/notebookIcons.ts new file mode 100644 index 000000000..6780f1fa9 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebookIcons.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { localize } from 'vs/nls'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; + +export const configureKernelIcon = registerIcon('notebook-kernel-configure', Codicon.settingsGear, localize('configureKernel', 'Configure icon in kernel configuation widget in notebook editors.')); +export const selectKernelIcon = registerIcon('notebook-kernel-select', Codicon.serverEnvironment, localize('selectKernelIcon', 'Configure icon to select a kernel in notebook editors.')); + +export const executeIcon = registerIcon('notebook-execute', Codicon.play, localize('executeIcon', 'Icon to execute in notebook editors.')); +export const stopIcon = registerIcon('notebook-stop', Codicon.primitiveSquare, localize('stopIcon', 'Icon to stop an execution in notebook editors.')); +export const deleteCellIcon = registerIcon('notebook-delete-cell', Codicon.trash, localize('deleteCellIcon', 'Icon to delete a cell in notebook editors.')); +export const executeAllIcon = registerIcon('notebook-execute-all', Codicon.runAll, localize('executeAllIcon', 'Icon to execute all cells in notebook editors.')); +export const editIcon = registerIcon('notebook-edit', Codicon.pencil, localize('editIcon', 'Icon to edit a cell in notebook editors.')); +export const stopEditIcon = registerIcon('notebook-stop-edit', Codicon.check, localize('stopEditIcon', 'Icon to stop editing a cell in notebook editors.')); +export const moveUpIcon = registerIcon('notebook-move-up', Codicon.arrowUp, localize('moveUpIcon', 'Icon to move a cell up in notebook editors.')); +export const moveDownIcon = registerIcon('notebook-move-down', Codicon.arrowDown, localize('moveDownIcon', 'Icon to move a cell down in notebook editors.')); +export const clearIcon = registerIcon('notebook-clear', Codicon.clearAll, localize('clearIcon', 'Icon to clear cell outputs in notebook editors.')); +export const splitCellIcon = registerIcon('notebook-split-cell', Codicon.splitVertical, localize('splitCellIcon', 'Icon to split a cell in notebook editors.')); +export const unfoldIcon = registerIcon('notebook-unfold', Codicon.unfold, localize('unfoldIcon', 'Icon to unfold a cell in notebook editors.')); + +export const successStateIcon = registerIcon('notebook-state-success', Codicon.check, localize('successStateIcon', 'Icon to indicate a success state in notebook editors.')); +export const errorStateIcon = registerIcon('notebook-state-error', Codicon.error, localize('errorStateIcon', 'Icon to indicate a error state in notebook editors.')); + +export const collapsedIcon = registerIcon('notebook-collapsed', Codicon.chevronRight, localize('collapsedIcon', 'Icon to annotated a collapsed section in notebook editors.')); +export const expandedIcon = registerIcon('notebook-expanded', Codicon.chevronDown, localize('expandedIcon', 'Icon to annotated a 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 mimetypeIcon = registerIcon('notebook-mimetype', Codicon.code, localize('mimetypeIcon', 'Icon for a mime type in notebook editors.')); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 36869d4c8..ec00de561 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { getZoomLevel } from 'vs/base/browser/browser'; import { flatten } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; @@ -13,22 +14,25 @@ import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import * as UUID from 'vs/base/common/uuid'; import { RedoCommand, UndoCommand } from 'vs/editor/browser/editorExtensions'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { CopyAction, CutAction, PasteAction } from 'vs/editor/contrib/clipboard/clipboard'; import * as nls from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; import { Memento } from 'vs/workbench/common/memento'; import { INotebookEditorContribution, notebookProviderExtensionPoint, notebookRendererExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint'; -import { getActiveNotebookEditor, INotebookEditor, NotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, getActiveNotebookEditor, INotebookEditor, NotebookEditorOptions, updateEditorTopPadding } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookKernelProviderAssociationRegistry, NotebookViewTypesExtensionRegistry, updateNotebookKernelProvideAssociationSchema } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; 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, BUILTIN_RENDERER_ID, CellEditType, CellKind, CellOutputKind, DisplayOrderKey, ICellEditOperation, IDisplayOutput, INotebookDecorationRenderOptions, INotebookKernelInfo2, INotebookKernelProvider, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, NotebookCellOutputsSplice, notebookDocumentFilterMatch, NotebookEditorPriority, NOTEBOOK_DISPLAY_ORDER, sortMimeTypes } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellKind, CellOutputKind, DisplayOrderKey, IDisplayOutput, INotebookDecorationRenderOptions, INotebookKernelInfo2, INotebookKernelProvider, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, ITransformedDisplayOutputDto, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, notebookDocumentFilterMatch, NotebookEditorPriority, NOTEBOOK_DISPLAY_ORDER, RENDERER_NOT_AVAILABLE, sortMimeTypes } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; import { NotebookEditorDescriptor, NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; @@ -88,7 +92,7 @@ export class NotebookProviderInfoStore extends Disposable { super(); this._memento = new Memento(NotebookProviderInfoStore.CUSTOM_EDITORS_STORAGE_ID, storageService); - const mementoObject = this._memento.getMemento(StorageScope.GLOBAL); + const mementoObject = this._memento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); for (const info of (mementoObject[NotebookProviderInfoStore.CUSTOM_EDITORS_ENTRY_ID] || []) as NotebookEditorDescriptor[]) { this.add(new NotebookProviderInfo(info)); } @@ -129,7 +133,7 @@ export class NotebookProviderInfoStore extends Disposable { } } - const mementoObject = this._memento.getMemento(StorageScope.GLOBAL); + const mementoObject = this._memento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); mementoObject[NotebookProviderInfoStore.CUSTOM_EDITORS_ENTRY_ID] = Array.from(this._contributedEditors.values()); this._memento.saveMemento(); @@ -179,7 +183,7 @@ export class NotebookProviderInfoStore extends Disposable { } this._contributedEditors.set(info.id, info); - const mementoObject = this._memento.getMemento(StorageScope.GLOBAL); + const mementoObject = this._memento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); mementoObject[NotebookProviderInfoStore.CUSTOM_EDITORS_ENTRY_ID] = Array.from(this._contributedEditors.values()); this._memento.saveMemento(); } @@ -276,7 +280,9 @@ export class NotebookService extends Disposable implements INotebookService, ICu @IConfigurationService private readonly _configurationService: IConfigurationService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, @IStorageService private readonly _storageService: IStorageService, - @IInstantiationService private readonly _instantiationService: IInstantiationService + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); @@ -336,6 +342,41 @@ export class NotebookService extends Disposable implements INotebookService, ICu updateOrder(); })); + let decorationTriggeredAdjustment = false; + let decorationCheckSet = new Set(); + this._register(this._codeEditorService.onDecorationTypeRegistered(e => { + if (decorationTriggeredAdjustment) { + return; + } + + if (decorationCheckSet.has(e)) { + return; + } + + const options = this._codeEditorService.resolveDecorationOptions(e, true); + if (options.afterContentClassName || options.beforeContentClassName) { + const cssRules = this._codeEditorService.resolveDecorationCSSRules(e); + if (cssRules !== null) { + for (let i = 0; i < cssRules.length; i++) { + // The following ways to index into the list are equivalent + if ( + ((cssRules[i] as CSSStyleRule).selectorText.endsWith('::after') || (cssRules[i] as CSSStyleRule).selectorText.endsWith('::after')) + && (cssRules[i] as CSSStyleRule).cssText.indexOf('top:') > -1 + ) { + // there is a `::before` or `::after` text decoration whose position is above or below current line + // we at least make sure that the editor top padding is at least one line + const editorOptions = this.configurationService.getValue('editor'); + updateEditorTopPadding(BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel()).lineHeight + 2); + decorationTriggeredAdjustment = true; + break; + } + } + } + } + + decorationCheckSet.add(e); + })); + const getContext = () => { const editor = getActiveNotebookEditor(this._editorService); const activeCell = editor?.getActiveCell(); @@ -352,7 +393,13 @@ export class NotebookService extends Disposable implements INotebookService, ICu if (editor?.viewModel) { return editor.viewModel.undo().then(cellResources => { if (cellResources?.length) { - editor?.setOptions(new NotebookEditorOptions({ cellOptions: { resource: cellResources[0] } })); + editor?.viewModel?.viewCells.forEach(cell => { + if (cell.cellKind === CellKind.Markdown && cellResources.find(resource => resource.fragment === cell.model.uri.fragment)) { + cell.editState = CellEditState.Editing; + } + }); + + editor?.setOptions(new NotebookEditorOptions({ cellOptions: { resource: cellResources[0] }, preserveFocus: true })); } }); } @@ -365,7 +412,13 @@ export class NotebookService extends Disposable implements INotebookService, ICu if (editor?.viewModel) { return editor.viewModel.redo().then(cellResources => { if (cellResources?.length) { - editor?.setOptions(new NotebookEditorOptions({ cellOptions: { resource: cellResources[0] } })); + editor?.viewModel?.viewCells.forEach(cell => { + if (cell.cellKind === CellKind.Markdown && cellResources.find(resource => resource.fragment === cell.model.uri.fragment)) { + cell.editState = CellEditState.Editing; + } + }); + + editor?.setOptions(new NotebookEditorOptions({ cellOptions: { resource: cellResources[0] }, preserveFocus: true })); } }); } @@ -630,7 +683,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu }); } - async getContributedNotebookKernels2(viewType: string, resource: URI, token: CancellationToken): Promise { + async getContributedNotebookKernels(viewType: string, resource: URI, token: CancellationToken): Promise { const filteredProvider = this.notebookKernelProviderInfoStore.get(viewType, resource); const result = new Array(filteredProvider.length); @@ -706,8 +759,6 @@ export class NotebookService extends Disposable implements INotebookService, ICu this._models.set(uri, modelData); this._onDidAddNotebookDocument.fire(notebookModel); - // after the document is added to the store and sent to ext host, we transform the ouputs - await this.transformTextModelOutputs(notebookModel); return modelData.model; } @@ -720,68 +771,12 @@ export class NotebookService extends Disposable implements INotebookService, ICu return Iterable.map(this._models.values(), data => data.model); } - private async transformTextModelOutputs(textModel: NotebookTextModel) { - for (let i = 0; i < textModel.cells.length; i++) { - const cell = textModel.cells[i]; - - cell.outputs.forEach((output) => { - if (output.outputKind === CellOutputKind.Rich) { - // TODO@rebornix no string[] casting - const ret = this._transformMimeTypes(output, output.outputId, textModel.metadata.displayOrder as string[] || []); - const orderedMimeTypes = ret.orderedMimeTypes!; - const pickedMimeTypeIndex = ret.pickedMimeTypeIndex!; - output.pickedMimeTypeIndex = pickedMimeTypeIndex; - output.orderedMimeTypes = orderedMimeTypes; - } - }); - } + getMimeTypeInfo(textModel: NotebookTextModel, output: ITransformedDisplayOutputDto): readonly IOrderedMimeType[] { + // TODO@rebornix no string[] casting + return this._getOrderedMimeTypes(textModel, output, textModel.metadata.displayOrder as string[] ?? []); } - transformEditsOutputs(textModel: NotebookTextModel, edits: ICellEditOperation[]) { - edits.forEach((edit) => { - if (edit.editType === CellEditType.Replace) { - edit.cells.forEach((cell) => { - const outputs = cell.outputs; - outputs.map((output) => { - if (output.outputKind === CellOutputKind.Rich) { - const ret = this._transformMimeTypes(output, output.outputId, textModel.metadata.displayOrder as string[] || []); - const orderedMimeTypes = ret.orderedMimeTypes!; - const pickedMimeTypeIndex = ret.pickedMimeTypeIndex!; - output.pickedMimeTypeIndex = pickedMimeTypeIndex; - output.orderedMimeTypes = orderedMimeTypes; - } - }); - }); - } else if (edit.editType === CellEditType.Output) { - edit.outputs.map((output) => { - if (output.outputKind === CellOutputKind.Rich) { - const ret = this._transformMimeTypes(output, output.outputId, textModel.metadata.displayOrder as string[] || []); - const orderedMimeTypes = ret.orderedMimeTypes!; - const pickedMimeTypeIndex = ret.pickedMimeTypeIndex!; - output.pickedMimeTypeIndex = pickedMimeTypeIndex; - output.orderedMimeTypes = orderedMimeTypes; - } - }); - } - }); - } - - transformSpliceOutputs(textModel: NotebookTextModel, splices: NotebookCellOutputsSplice[]) { - splices.forEach((splice) => { - const outputs = splice[2]; - outputs.map((output) => { - if (output.outputKind === CellOutputKind.Rich) { - const ret = this._transformMimeTypes(output, output.outputId, textModel.metadata.displayOrder as string[] || []); - const orderedMimeTypes = ret.orderedMimeTypes!; - const pickedMimeTypeIndex = ret.pickedMimeTypeIndex!; - output.pickedMimeTypeIndex = pickedMimeTypeIndex; - output.orderedMimeTypes = orderedMimeTypes; - } - }); - }); - } - - private _transformMimeTypes(output: IDisplayOutput, outputId: string, documentDisplayOrder: string[]): ITransformedDisplayOutputDto { + private _getOrderedMimeTypes(textModel: NotebookTextModel, output: IDisplayOutput, documentDisplayOrder: string[]): IOrderedMimeType[] { const mimeTypes = Object.keys(output.data); const coreDisplayOrder = this._displayOrder; const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], documentDisplayOrder, coreDisplayOrder?.defaultOrder || []); @@ -797,36 +792,42 @@ export class NotebookService extends Disposable implements INotebookService, ICu orderMimeTypes.push({ mimeType: mimeType, rendererId: handler.id, + isTrusted: textModel.metadata.trusted }); for (let i = 1; i < handlers.length; i++) { orderMimeTypes.push({ mimeType: mimeType, - rendererId: handlers[i].id + rendererId: handlers[i].id, + isTrusted: textModel.metadata.trusted }); } if (mimeTypeSupportedByCore(mimeType)) { orderMimeTypes.push({ mimeType: mimeType, - rendererId: BUILTIN_RENDERER_ID + rendererId: BUILTIN_RENDERER_ID, + isTrusted: mimeTypeIsAlwaysSecure(mimeType) || textModel.metadata.trusted }); } } else { - orderMimeTypes.push({ - mimeType: mimeType, - rendererId: BUILTIN_RENDERER_ID - }); + if (mimeTypeSupportedByCore(mimeType)) { + orderMimeTypes.push({ + mimeType: mimeType, + rendererId: BUILTIN_RENDERER_ID, + isTrusted: mimeTypeIsAlwaysSecure(mimeType) || textModel.metadata.trusted + }); + } else { + orderMimeTypes.push({ + mimeType: mimeType, + rendererId: RENDERER_NOT_AVAILABLE, + isTrusted: textModel.metadata.trusted + }); + } } }); - return { - outputKind: output.outputKind, - outputId, - data: output.data, - orderedMimeTypes: orderMimeTypes, - pickedMimeTypeIndex: 0 - }; + return orderMimeTypes; } private _findBestMatchedRenderer(mimeType: string): readonly NotebookOutputRendererInfo[] { @@ -992,11 +993,11 @@ export class NotebookService extends Disposable implements INotebookService, ICu if (modelData) { // delete editors and documents const willRemovedEditors: INotebookEditor[] = []; - this._notebookEditors.forEach(editor => { - if (editor.textModel === modelData!.model) { + for (const editor of this._notebookEditors.values()) { + if (editor.textModel === modelData.model) { willRemovedEditors.push(editor); } - }); + } modelData.model.dispose(); modelData.dispose(); diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index e51a8c00d..d6d8ed7c0 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -21,7 +21,7 @@ import { IListService, IWorkbenchListOptions, WorkbenchList } from 'vs/platform/ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { CellRevealPosition, CellRevealType, CursorAtBoundary, getVisibleCells, ICellViewModel, INotebookCellList, reduceCellRanges, CellEditState, CellFocusMode, BaseCellRenderTemplate, NOTEBOOK_CELL_LIST_FOCUSED, cellRangesEqual } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { diff, IProcessedOutput, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, CellKind, ICellRange } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { diff, IProcessedOutput, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, CellKind, ICellRange, NOTEBOOK_EDITOR_CURSOR_BEGIN_END } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { clamp } from 'vs/base/common/numbers'; import { SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants'; @@ -115,6 +115,9 @@ export class NotebookCellList extends WorkbenchList implements ID const notebookEditorCursorAtBoundaryContext = NOTEBOOK_EDITOR_CURSOR_BOUNDARY.bindTo(contextKeyService); notebookEditorCursorAtBoundaryContext.set('none'); + const notebookEditorCursorAtBeginEndContext = NOTEBOOK_EDITOR_CURSOR_BEGIN_END.bindTo(contextKeyService); + notebookEditorCursorAtBeginEndContext.set(false); + let cursorSelectionListener: IDisposable | null = null; let textEditorAttachListener: IDisposable | null = null; @@ -133,6 +136,13 @@ export class NotebookCellList extends WorkbenchList implements ID notebookEditorCursorAtBoundaryContext.set('none'); break; } + + if (element.cursorAtBeginEnd()) { + notebookEditorCursorAtBeginEndContext.set(true); + } else { + notebookEditorCursorAtBeginEndContext.set(false); + } + return; }; @@ -595,6 +605,14 @@ export class NotebookCellList extends WorkbenchList implements ID } } + async revealElementInCenterIfOutsideViewportAsync(cell: ICellViewModel): Promise { + const index = this._getViewIndexUpperBound(cell); + + if (index >= 0) { + return this._revealInCenterIfOutsideViewportAsync(index); + } + } + async revealElementLineInViewAsync(cell: ICellViewModel, line: number): Promise { const index = this._getViewIndexUpperBound(cell); @@ -855,6 +873,17 @@ export class NotebookCellList extends WorkbenchList implements ID } } + private async _revealInCenterIfOutsideViewportAsync(viewIndex: number): Promise { + this._revealInternal(viewIndex, true, CellRevealPosition.Center); + const element = this.view.element(viewIndex); + + if (!element.editorAttached) { + return getEditorAttachedPromise(element); + } + + return; + } + private async _revealLineInCenterIfOutsideViewportAsync(viewIndex: number, line: number): Promise { return this._revealRangeInCenterIfOutsideViewportInternalAsync(viewIndex, new Range(line, 1, line, 1), CellRevealType.Line); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts index 7840f0231..b2cec3995 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts @@ -16,8 +16,10 @@ import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { URI } from 'vs/base/common/uri'; import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { handleANSIOutput } from 'vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform'; import { dirname } from 'vs/base/common/resources'; +import { truncatedArrayOfString } from 'vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; class RichRenderer implements IOutputTransformContribution { private _richMimeTypeRenderers = new Map IRenderOutput>(); @@ -27,7 +29,9 @@ class RichRenderer implements IOutputTransformContribution { @IInstantiationService private readonly instantiationService: IInstantiationService, @IModelService private readonly modelService: IModelService, @IModeService private readonly modeService: IModeService, - @IThemeService private readonly themeService: IThemeService + @IThemeService private readonly themeService: IThemeService, + @IOpenerService readonly openerService: IOpenerService, + @ITextFileService readonly textFileService: ITextFileService, ) { this._richMimeTypeRenderers.set('application/json', this.renderJSON.bind(this)); this._richMimeTypeRenderers.set('application/javascript', this.renderJavaScript.bind(this)); @@ -204,9 +208,8 @@ class RichRenderer implements IOutputTransformContribution { renderPlainText(output: ITransformedDisplayOutputDto, notebookUri: URI, container: HTMLElement): IRenderOutput { const data = output.data['text/plain']; - const str = (isArray(data) ? data.join('') : data) as string; const contentNode = DOM.$('.output-plaintext'); - contentNode.appendChild(handleANSIOutput(str, this.themeService)); + truncatedArrayOfString(contentNode, isArray(data) ? data : [data], this.openerService, this.textFileService, this.themeService, true); container.appendChild(contentNode); return { type: RenderOutputType.None, hasDynamicHeight: false }; diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/streamTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/streamTransform.ts index 65f1826f7..b05a25d3b 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/streamTransform.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/streamTransform.ts @@ -7,16 +7,23 @@ import * as DOM from 'vs/base/browser/dom'; import { IRenderOutput, CellOutputKind, IStreamOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookRegistry } from 'vs/workbench/contrib/notebook/browser/notebookRegistry'; import { INotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { truncatedArrayOfString } from 'vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; class StreamRenderer implements IOutputTransformContribution { constructor( - editor: INotebookEditor + editor: INotebookEditor, + @IOpenerService readonly openerService: IOpenerService, + @ITextFileService readonly textFileService: ITextFileService, + @IThemeService readonly themeService: IThemeService ) { } render(output: IStreamOutput, container: HTMLElement): IRenderOutput { const contentNode = DOM.$('.output-stream'); - contentNode.innerText = output.text; + truncatedArrayOfString(contentNode, [output.text], this.openerService, this.textFileService, this.themeService, false); container.appendChild(contentNode); return { type: RenderOutputType.None, hasDynamicHeight: false }; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper.ts new file mode 100644 index 000000000..8ef36f850 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper.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. + *--------------------------------------------------------------------------------------------*/ + +import * as DOM from 'vs/base/browser/dom'; +import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DefaultEndOfLine, EndOfLinePreference, ITextBuffer } from 'vs/editor/common/model'; +import { Range } from 'vs/editor/common/core/range'; +import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { handleANSIOutput } from 'vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; + +const SIZE_LIMIT = 65535; +const LINES_LIMIT = 500; + +function generateViewMoreElement(outputs: string[], openerService: IOpenerService, textFileService: ITextFileService) { + const md: IMarkdownString = { + value: '[show more (open the raw output data in a text editor) ...](command:workbench.action.openLargeOutput)', + isTrusted: true, + supportThemeIcons: true + }; + + const element = renderMarkdown(md, { + actionHandler: { + callback: (content) => { + if (content === 'command:workbench.action.openLargeOutput') { + return textFileService.untitled.resolve({ + associatedResource: undefined, + mode: 'plaintext', + initialValue: outputs.join('') + }).then(model => { + const resource = model.resource; + openerService.open(resource); + }); + } + + return; + }, + disposeables: new DisposableStore() + } + }); + + element.classList.add('output-show-more'); + return element; +} + +export function truncatedArrayOfString(container: HTMLElement, outputs: string[], openerService: IOpenerService, textFileService: ITextFileService, themeService: IThemeService, renderANSI: boolean) { + const fullLen = outputs.reduce((p, c) => { + return p + c.length; + }, 0); + + let buffer: ITextBuffer | undefined = undefined; + + if (fullLen > SIZE_LIMIT) { + // it's too large and we should find min(maxSizeLimit, maxLineLimit) + const bufferBuilder = new PieceTreeTextBufferBuilder(); + outputs.forEach(output => bufferBuilder.acceptChunk(output)); + const factory = bufferBuilder.finish(); + buffer = factory.create(DefaultEndOfLine.LF); + const sizeBufferLimitPosition = buffer.getPositionAt(SIZE_LIMIT); + if (sizeBufferLimitPosition.lineNumber < LINES_LIMIT) { + const truncatedText = buffer.getValueInRange(new Range(1, 1, sizeBufferLimitPosition.lineNumber, sizeBufferLimitPosition.column), EndOfLinePreference.TextDefined); + if (renderANSI) { + container.appendChild(handleANSIOutput(truncatedText, themeService)); + } else { + const pre = DOM.$('div'); + pre.innerText = truncatedText; + container.appendChild(pre); + } + + // view more ... + container.appendChild(generateViewMoreElement(outputs, openerService, textFileService)); + return; + } + } + + if (!buffer) { + const bufferBuilder = new PieceTreeTextBufferBuilder(); + outputs.forEach(output => bufferBuilder.acceptChunk(output)); + const factory = bufferBuilder.finish(); + buffer = factory.create(DefaultEndOfLine.LF); + } + + if (buffer.getLineCount() < LINES_LIMIT) { + const lineCount = buffer.getLineCount(); + const fullRange = new Range(1, 1, lineCount, buffer.getLineLastNonWhitespaceColumn(lineCount)); + + container.innerText = buffer.getValueInRange(fullRange, EndOfLinePreference.TextDefined); + return; + } + + if (renderANSI) { + container.appendChild(handleANSIOutput(buffer.getValueInRange(new Range(1, 1, LINES_LIMIT - 5, buffer.getLineLastNonWhitespaceColumn(LINES_LIMIT - 5)), EndOfLinePreference.TextDefined), themeService)); + } else { + const pre = DOM.$('div'); + pre.innerText = buffer.getValueInRange(new Range(1, 1, LINES_LIMIT - 5, buffer.getLineLastNonWhitespaceColumn(LINES_LIMIT - 5)), EndOfLinePreference.TextDefined); + container.appendChild(pre); + } + + + // view more ... + container.appendChild(generateViewMoreElement(outputs, openerService, textFileService)); + + const lineCount = buffer.getLineCount(); + if (renderANSI) { + container.appendChild(handleANSIOutput(buffer.getValueInRange(new Range(lineCount - 5, 1, lineCount, buffer.getLineLastNonWhitespaceColumn(lineCount)), EndOfLinePreference.TextDefined), themeService)); + } else { + const post = DOM.$('div'); + post.innerText = buffer.getValueInRange(new Range(lineCount - 5, 1, lineCount, buffer.getLineLastNonWhitespaceColumn(lineCount)), EndOfLinePreference.TextDefined); + container.appendChild(post); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 7e99796e6..31fc6590e 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -6,7 +6,6 @@ import * as DOM from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import * as path from 'vs/base/common/path'; import { isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import * as UUID from 'vs/base/common/uuid'; @@ -373,7 +372,7 @@ export class BackLayerWebView extends Disposable { }()); `; const htmlContent = this.generateContent(CELL_OUTPUT_PADDING, coreDependencies, baseUrl.toString()); - this.initialize(htmlContent); + this._initialize(htmlContent); resolveFunc!(); } else { const loaderUri = FileAccess.asBrowserUri('vs/loader.js', require); @@ -397,7 +396,7 @@ var requirejs = (function() { `; const htmlContent = this.generateContent(CELL_OUTPUT_PADDING, coreDependencies, baseUrl.toString()); - this.initialize(htmlContent); + this._initialize(htmlContent); resolveFunc!(); }); } @@ -405,7 +404,7 @@ var requirejs = (function() { await this._initalized; } - async initialize(content: string) { + private async _initialize(content: string) { if (!document.body.contains(this.element)) { throw new Error('Element is already detached from the DOM tree'); } @@ -555,9 +554,8 @@ var requirejs = (function() { } private _createInset(webviewService: IWebviewService, content: string) { - const rootPathStr = path.dirname(FileAccess.asFileUri('', require).fsPath); + const rootPath = isWeb ? FileAccess.asBrowserUri('', require) : FileAccess.asFileUri('', require); - const rootPath = isWeb ? FileAccess.asBrowserUri(rootPathStr, require) : FileAccess.asFileUri(rootPathStr, require); const workspaceFolders = this.contextService.getWorkspace().folders.map(x => x.uri); this.localResourceRootsCache = [...this.notebookService.getNotebookProviderResourceRoots(), ...workspaceFolders, rootPath]; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellActionView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellActionView.ts index 2e698f1ed..3ded6bb42 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellActionView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellActionView.ts @@ -3,10 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Action, IAction, Separator } from 'vs/base/common/actions'; -import { IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { renderCodicons } from 'vs/base/browser/codicons'; +import * as DOM from 'vs/base/browser/dom'; import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { Action, IAction, Separator } from 'vs/base/common/actions'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { INotificationService } from 'vs/platform/notification/common/notification'; export class VerticalSeparator extends Action { static readonly ID = 'vs.actions.verticalSeparator'; @@ -74,3 +79,19 @@ function asDisposable(groups: ReadonlyArray<[string, ReadonlyArray(); + + readonly resizeListener = new DisposableStore(); + domNode!: HTMLElement; + renderResult?: IRenderOutput; + + constructor( + private notebookEditor: INotebookEditor, + private notebookService: INotebookService, + private quickInputService: IQuickInputService, + private viewCell: CodeCellViewModel, + private outputContainer: HTMLElement, + readonly output: IProcessedOutput + ) { + super(); + } + + render(index: number, beforeElement?: HTMLElement) { + if (this.viewCell.metadata.outputCollapsed) { + return; + } + + const outputItemDiv = document.createElement('div'); + let result: IRenderOutput | undefined = undefined; + + if (this.output.outputKind === CellOutputKind.Rich) { + const transformedDisplayOutput = this.output as ITransformedDisplayOutputDto; + + const mimeTypes = this.notebookService.getMimeTypeInfo(this.notebookEditor.textModel!, this.output); + + // there is at least one mimetype which is safe and can be rendered by the core + const pick = this.pickedMimeTypes.get(this.output) ?? Math.max(mimeTypes.findIndex(mimeType => mimeType.rendererId !== RENDERER_NOT_AVAILABLE && mimeType.isTrusted), 0); + + if (mimeTypes.length > 1) { + outputItemDiv.style.position = 'relative'; + const mimeTypePicker = DOM.$('.multi-mimetype-output'); + mimeTypePicker.classList.add(...ThemeIcon.asClassNameArray(mimetypeIcon)); + mimeTypePicker.tabIndex = 0; + mimeTypePicker.title = nls.localize('mimeTypePicker', "Choose a different output mimetype, available mimetypes: {0}", mimeTypes.map(mimeType => mimeType.mimeType).join(', ')); + outputItemDiv.appendChild(mimeTypePicker); + this.resizeListener.add(DOM.addStandardDisposableListener(mimeTypePicker, 'mousedown', async e => { + if (e.leftButton) { + e.preventDefault(); + e.stopPropagation(); + await this.pickActiveMimeTypeRenderer(transformedDisplayOutput); + } + })); + + this.resizeListener.add((DOM.addDisposableListener(mimeTypePicker, DOM.EventType.KEY_DOWN, async e => { + const event = new StandardKeyboardEvent(e); + if ((event.equals(KeyCode.Enter) || event.equals(KeyCode.Space))) { + e.preventDefault(); + e.stopPropagation(); + await this.pickActiveMimeTypeRenderer(transformedDisplayOutput); + } + }))); + + } + const pickedMimeTypeRenderer = mimeTypes[pick]; + + const innerContainer = DOM.$('.output-inner-container'); + DOM.append(outputItemDiv, innerContainer); + + + if (pickedMimeTypeRenderer.rendererId !== BUILTIN_RENDERER_ID) { + const renderer = this.notebookService.getRendererInfo(pickedMimeTypeRenderer.rendererId); + result = renderer + ? { type: RenderOutputType.Extension, renderer, source: this.output, mimeType: pickedMimeTypeRenderer.mimeType } + : this.notebookEditor.getOutputRenderer().render(this.output, innerContainer, pickedMimeTypeRenderer.mimeType, this.getNotebookUri(),); + } else { + result = this.notebookEditor.getOutputRenderer().render(this.output, innerContainer, pickedMimeTypeRenderer.mimeType, this.getNotebookUri(),); + } + + this.pickedMimeTypes.set(this.output, pick); + } else { + // for text and error, there is no mimetype + const innerContainer = DOM.$('.output-inner-container'); + DOM.append(outputItemDiv, innerContainer); + + result = this.notebookEditor.getOutputRenderer().render(this.output, innerContainer, undefined, this.getNotebookUri(),); + } + + this.domNode = outputItemDiv; + this.renderResult = result; + + if (!result) { + this.viewCell.updateOutputHeight(index, 0); + return; + } + + if (beforeElement) { + this.outputContainer.insertBefore(outputItemDiv, beforeElement); + } else { + this.outputContainer.appendChild(outputItemDiv); + } + + if (result.type !== RenderOutputType.None) { + this.viewCell.selfSizeMonitoring = true; + this.notebookEditor.createInset(this.viewCell, result as any, this.viewCell.getOutputOffset(index)); + } else { + outputItemDiv.classList.add('foreground', 'output-element'); + outputItemDiv.style.position = 'absolute'; + } + + if (outputHasDynamicHeight(result)) { + this.viewCell.selfSizeMonitoring = true; + + const clientHeight = outputItemDiv.clientHeight; + const dimension = { + width: this.viewCell.layoutInfo.editorWidth, + height: clientHeight + }; + const elementSizeObserver = getResizesObserver(outputItemDiv, dimension, () => { + if (this.outputContainer && document.body.contains(this.outputContainer!)) { + const height = Math.ceil(elementSizeObserver.getHeight()); + + if (clientHeight === height) { + return; + } + + const currIndex = this.viewCell.outputs.indexOf(this.output); + if (currIndex < 0) { + return; + } + + this.viewCell.updateOutputHeight(currIndex, height); + this.relayoutCell(); + } + }); + elementSizeObserver.startObserving(); + this.resizeListener.add(elementSizeObserver); + this.viewCell.updateOutputHeight(index, clientHeight); + } else if (result.type === RenderOutputType.None) { // no-op if it's a webview + const clientHeight = Math.ceil(outputItemDiv.clientHeight); + this.viewCell.updateOutputHeight(index, clientHeight); + + const top = this.viewCell.getOutputOffsetInContainer(index); + outputItemDiv.style.top = `${top}px`; + } + } + + async pickActiveMimeTypeRenderer(output: ITransformedDisplayOutputDto) { + + const mimeTypes = this.notebookService.getMimeTypeInfo(this.notebookEditor.textModel!, output); + const currIndex = this.pickedMimeTypes.get(output); + + // const currIndex = output.pickedMimeTypeIndex; + const items = mimeTypes.filter(mimeType => mimeType.isTrusted).map((mimeType, index): IMimeTypeRenderer => ({ + label: mimeType.mimeType, + id: mimeType.mimeType, + index: index, + picked: index === currIndex, + detail: this.generateRendererInfo(mimeType.rendererId), + description: index === currIndex ? nls.localize('curruentActiveMimeType', "Currently Active") : undefined + })); + + const picker = this.quickInputService.createQuickPick(); + picker.items = items; + picker.activeItems = items.filter(item => !!item.picked); + picker.placeholder = items.length !== mimeTypes.length + ? nls.localize('promptChooseMimeTypeInSecure.placeHolder', "Select mimetype to render for current output. Rich mimetypes are available only when the notebook is trusted") + : nls.localize('promptChooseMimeType.placeHolder', "Select mimetype to render for current output"); + + const pick = await new Promise(resolve => { + picker.onDidAccept(() => { + resolve(picker.selectedItems.length === 1 ? (picker.selectedItems[0] as IMimeTypeRenderer).index : undefined); + picker.dispose(); + }); + picker.show(); + }); + + if (pick === undefined) { + return; + } + + if (pick !== currIndex) { + // user chooses another mimetype + const index = this.viewCell.outputs.indexOf(output); + const nextElement = this.domNode.nextElementSibling; + this.resizeListener.clear(); + const element = this.domNode; + if (element) { + element.parentElement?.removeChild(element); + this.notebookEditor.removeInset(output); + } + + this.pickedMimeTypes.set(output, pick); + this.render(index, nextElement as HTMLElement); + this.relayoutCell(); + } + } + + private getNotebookUri(): URI | undefined { + return CellUri.parse(this.viewCell.uri)?.notebook; + } + + generateRendererInfo(renderId: string | undefined): string { + if (renderId === undefined || renderId === BUILTIN_RENDERER_ID) { + return nls.localize('builtinRenderInfo', "built-in"); + } + + const renderInfo = this.notebookService.getRendererInfo(renderId); + + if (renderInfo) { + const displayName = renderInfo.displayName !== '' ? renderInfo.displayName : renderInfo.id; + return `${displayName} (${renderInfo.extensionId.value})`; + } + + return nls.localize('builtinRenderInfo', "built-in"); + } + + relayoutCell() { + this.notebookEditor.layoutNotebookCell(this.viewCell, this.viewCell.layoutInfo.totalHeight); + } +} + +export class OutputContainer extends Disposable { + private outputEntries = new Map(); + + constructor( + private notebookEditor: INotebookEditor, + private viewCell: CodeCellViewModel, + private templateData: CodeCellRenderTemplate, + @INotebookService private notebookService: INotebookService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IOpenerService readonly openerService: IOpenerService, + @ITextFileService readonly textFileService: ITextFileService, + ) { + super(); + + this._register(viewCell.onDidChangeOutputs(splices => { + this._updateOutputs(splices); + })); + + this._register(viewCell.onDidChangeLayout(() => { + this.outputEntries.forEach((value, key) => { + const index = viewCell.outputs.indexOf(key); + if (index >= 0) { + const top = this.viewCell.getOutputOffsetInContainer(index); + value.domNode.style.top = `${top}px`; + } + }); + })); + } + + render(editorHeight: number) { + if (this.viewCell.outputs.length > 0) { + let layoutCache = false; + if (this.viewCell.layoutInfo.totalHeight !== 0 && this.viewCell.layoutInfo.editorHeight > editorHeight) { + layoutCache = true; + this._relayoutCell(); + } + + this.templateData.outputContainer.style.display = 'block'; + // there are outputs, we need to calcualte their sizes and trigger relayout + // @TODO@rebornix, if there is no resizable output, we should not check their height individually, which hurts the performance + const outputsToRender = this._calcuateOutputsToRender(); + for (let index = 0; index < outputsToRender.length; index++) { + const currOutput = this.viewCell.outputs[index]; + + // always add to the end + this._renderOutput(currOutput, index, undefined); + } + + this.viewCell.editorHeight = editorHeight; + if (this.viewCell.outputs.length > OUTPUT_COUNT_LIMIT) { + this.templateData.outputShowMoreContainer.style.display = 'block'; + this.viewCell.updateOutputShowMoreContainerHeight(46); + } + + if (layoutCache) { + this._relayoutCellDebounced(); + } else { + this._relayoutCell(); + } + } else { + // noop + this.viewCell.editorHeight = editorHeight; + this._relayoutCell(); + this.templateData.outputContainer.style.display = 'none'; + } + + this.templateData.outputShowMoreContainer.innerText = ''; + this.templateData.outputShowMoreContainer.appendChild(this._generateShowMoreElement()); + // this.templateData.outputShowMoreContainer.style.top = `${this.viewCell.layoutInfo.outputShowMoreContainerOffset}px`; + + if (this.viewCell.outputs.length < OUTPUT_COUNT_LIMIT) { + this.templateData.outputShowMoreContainer.style.display = 'none'; + this.viewCell.updateOutputShowMoreContainerHeight(0); + } + } + + viewUpdateShowOutputs(): void { + for (let index = 0; index < this.viewCell.outputs.length; index++) { + const currOutput = this.viewCell.outputs[index]; + + const renderedOutput = this.outputEntries.get(currOutput); + if (renderedOutput && renderedOutput.renderResult) { + if (renderedOutput.renderResult.type !== RenderOutputType.None) { + this.notebookEditor.createInset(this.viewCell, renderedOutput.renderResult as IInsetRenderOutput, this.viewCell.getOutputOffset(index)); + } else { + // Anything else, just update the height + this.viewCell.updateOutputHeight(index, renderedOutput.domNode.clientHeight); + } + } else { + // Wasn't previously rendered, render it now + this._renderOutput(currOutput, index); + } + } + + this._relayoutCell(); + } + + viewUpdateHideOuputs(): void { + for (const e of this.outputEntries.keys()) { + this.notebookEditor.hideInset(e); + } + } + + onCellWidthChange(): void { + this.viewCell.outputs.forEach((o, i) => { + const renderedOutput = this.outputEntries.get(o); + if (renderedOutput && renderedOutput.renderResult && renderedOutput.renderResult.type === RenderOutputType.None && !renderedOutput.renderResult.hasDynamicHeight) { + this.viewCell.updateOutputHeight(i, renderedOutput.domNode.clientHeight); + } + }); + } + + private _calcuateOutputsToRender() { + const outputs = this.viewCell.outputs.slice(0, Math.min(OUTPUT_COUNT_LIMIT, this.viewCell.outputs.length)); + if (!this.notebookEditor.viewModel!.metadata.trusted) { + // not trusted + const secureOutput = outputs.filter(output => { + switch (output.outputKind) { + case CellOutputKind.Text: + return true; + case CellOutputKind.Error: + return true; + case CellOutputKind.Rich: + { + const mimeTypes = []; + for (const property in output.data) { + mimeTypes.push(property); + } + + if (mimeTypes.indexOf('text/plain') >= 0 + || mimeTypes.indexOf('text/markdown') >= 0 + || mimeTypes.indexOf('application/json') >= 0 + || mimeTypes.includes('image/png')) { + return true; + } + + return false; + } + default: + return false; + } + }); + + return secureOutput; + } + + return outputs; + } + + private _updateOutputs(splices: NotebookCellOutputsSplice[]) { + if (!splices.length) { + return; + } + + const previousOutputHeight = this.viewCell.layoutInfo.outputTotalHeight; + + if (this.viewCell.outputs.length) { + this.templateData.outputContainer.style.display = 'block'; + } else { + this.templateData.outputContainer.style.display = 'none'; + } + + const reversedSplices = splices.reverse(); + + reversedSplices.forEach(splice => { + this.viewCell.spliceOutputHeights(splice[0], splice[1], splice[2].map(_ => 0)); + }); + + const removedKeys: IProcessedOutput[] = []; + + this.outputEntries.forEach((value, key) => { + if (this.viewCell.outputs.indexOf(key) < 0) { + // already removed + removedKeys.push(key); + // remove element from DOM + this.templateData?.outputContainer?.removeChild(value.domNode); + this.notebookEditor.removeInset(key); + } + }); + + removedKeys.forEach(key => { + this.outputEntries.get(key)?.dispose(); + this.outputEntries.delete(key); + }); + + let prevElement: HTMLElement | undefined = undefined; + const outputsToRender = this._calcuateOutputsToRender(); + + outputsToRender.reverse().forEach(output => { + if (this.outputEntries.has(output)) { + // already exist + prevElement = this.outputEntries.get(output)!.domNode; + return; + } + + // newly added element + const currIndex = this.viewCell.outputs.indexOf(output); + this._renderOutput(output, currIndex, prevElement); + prevElement = this.outputEntries.get(output)?.domNode; + }); + + if (this.viewCell.outputs.length > OUTPUT_COUNT_LIMIT) { + this.templateData.outputShowMoreContainer.style.display = 'block'; + this.viewCell.updateOutputShowMoreContainerHeight(46); + } else { + this.templateData.outputShowMoreContainer.style.display = 'none'; + } + + const editorHeight = this.templateData.editor.getContentHeight(); + this.viewCell.editorHeight = editorHeight; + + if (previousOutputHeight === 0 || this.viewCell.outputs.length === 0) { + // first execution or removing all outputs + this._relayoutCell(); + } else { + this._relayoutCellDebounced(); + } + } + + private _renderOutput(currOutput: IProcessedOutput, index: number, beforeElement?: HTMLElement) { + if (!this.outputEntries.has(currOutput)) { + this.outputEntries.set(currOutput, new OutputElement(this.notebookEditor, this.notebookService, this.quickInputService, this.viewCell, this.templateData.outputContainer, currOutput)); + } + + this.outputEntries.get(currOutput)!.render(index, beforeElement); + } + + private _generateShowMoreElement(): any { + const md: IMarkdownString = { + value: `There are more than ${OUTPUT_COUNT_LIMIT} outputs, [show more (open the raw output data in a text editor) ...](command:workbench.action.openLargeOutput)`, + isTrusted: true, + supportThemeIcons: true + }; + + const element = renderMarkdown(md, { + actionHandler: { + callback: (content) => { + if (content === 'command:workbench.action.openLargeOutput') { + const content = JSON.stringify(this.viewCell.outputs.map(output => { + switch (output.outputKind) { + case CellOutputKind.Text: + return { + outputKind: 'text', + text: output.text + }; + case CellOutputKind.Error: + return { + outputKind: 'error', + ename: output.ename, + evalue: output.evalue, + traceback: output.traceback + }; + case CellOutputKind.Rich: + return { + data: output.data, + metadata: output.metadata + }; + } + })); + const edits = format(content, undefined, {}); + const metadataSource = applyEdits(content, edits); + + return this.textFileService.untitled.resolve({ + associatedResource: undefined, + mode: 'json', + initialValue: metadataSource + }).then(model => { + const resource = model.resource; + this.openerService.open(resource); + }); + } + + return; + }, + disposeables: new DisposableStore() + } + }); + + element.classList.add('output-show-more'); + return element; + } + + private _relayoutCell() { + if (this._timer !== null) { + clearTimeout(this._timer); + } + + this.notebookEditor.layoutNotebookCell(this.viewCell, this.viewCell.layoutInfo.totalHeight); + } + + private _timer: number | null = null; + + private _relayoutCellDebounced() { + if (this._timer !== null) { + clearTimeout(this._timer); + } + + this._timer = setTimeout(() => { + this.notebookEditor.layoutNotebookCell(this.viewCell, this.viewCell.layoutInfo.totalHeight); + this._timer = null; + }, 200) as unknown as number | null; + } + + dispose() { + this.outputEntries.forEach((value) => { + value.dispose(); + }); + + super.dispose(); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 25156780e..2fccd56c7 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -35,21 +35,22 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { BOTTOM_CELL_TOOLBAR_GAP, CELL_BOTTOM_MARGIN, CELL_TOP_MARGIN, EDITOR_BOTTOM_PADDING, EDITOR_BOTTOM_PADDING_WITHOUT_STATUSBAR, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; +import { BOTTOM_CELL_TOOLBAR_GAP, CELL_BOTTOM_MARGIN, CELL_TOP_MARGIN, EDITOR_BOTTOM_PADDING, EDITOR_BOTTOM_PADDING_WITHOUT_STATUSBAR, EDITOR_TOOLBAR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants'; import { CancelCellAction, DeleteCellAction, ExecuteCellAction, INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; -import { BaseCellRenderTemplate, CellEditState, CodeCellRenderTemplate, EXPAND_CELL_CONTENT_COMMAND_ID, ICellViewModel, INotebookEditor, isCodeCellRenderTemplate, MarkdownCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { BaseCellRenderTemplate, CellEditState, CodeCellRenderTemplate, EditorTopPaddingChangeEvent, EXPAND_CELL_CONTENT_COMMAND_ID, getEditorTopPadding, ICellViewModel, INotebookEditor, isCodeCellRenderTemplate, MarkdownCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys'; import { CellMenus } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellMenus'; import { CellEditorStatusBar } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; import { CodeCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/codeCell'; -import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/commonViewComponents'; -import { CellDragAndDropController, DRAGGING_CLASS } from 'vs/workbench/contrib/notebook/browser/view/renderers/dnd'; +import { CellDragAndDropController, DRAGGING_CLASS } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellDnd'; import { StatefulMarkdownCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/markdownCell'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { CellEditType, CellKind, NotebookCellMetadata, NotebookCellRunState, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { createAndFillInActionBarActionsWithVerticalSeparators, VerticalSeparator, VerticalSeparatorViewItem } from './cellActionView'; +import { CellEditType, CellKind, NotebookCellMetadata, NotebookCellRunState, NotebookCellsChangeType, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CodiconActionViewItem, createAndFillInActionBarActionsWithVerticalSeparators, VerticalSeparator, VerticalSeparatorViewItem } from './cellActionView'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { errorStateIcon, successStateIcon, unfoldIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; const $ = DOM.$; @@ -118,10 +119,16 @@ export class CellEditorOptions { } }); + EditorTopPaddingChangeEvent(() => { + this._value = computeEditorOptions(); + this._onDidChange.fire(this.value); + + }); + const computeEditorOptions = () => { const showCellStatusBar = configurationService.getValue(ShowCellStatusBarKey); const editorPadding = { - top: EDITOR_TOP_PADDING, + top: getEditorTopPadding(), bottom: showCellStatusBar ? EDITOR_BOTTOM_PADDING : EDITOR_BOTTOM_PADDING_WITHOUT_STATUSBAR }; @@ -352,8 +359,8 @@ abstract class AbstractCellRenderer { } protected setupCollapsedPart(container: HTMLElement): { collapsedPart: HTMLElement, expandButton: HTMLElement } { - const collapsedPart = DOM.append(container, $('.cell.cell-collapsed-part', undefined, ...renderCodicons('$(unfold)'))); - const expandButton = collapsedPart.querySelector('.codicon') as HTMLElement; + const collapsedPart = DOM.append(container, $('.cell.cell-collapsed-part', undefined, $('span.expandButton' + ThemeIcon.asCSSSelector(unfoldIcon)))); + const expandButton = collapsedPart.querySelector('.expandButton') as HTMLElement; const keybinding = this.keybindingService.lookupKeybinding(EXPAND_CELL_CONTENT_COMMAND_ID); let title = localize('cellExpandButtonLabel', "Expand"); if (keybinding) { @@ -415,6 +422,9 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR const betweenCellToolbar = disposables.add(this.createBetweenCellToolbar(bottomCellContainer, disposables, contextKeyService)); const statusBar = disposables.add(this.instantiationService.createInstance(CellEditorStatusBar, editorPart)); + DOM.hide(statusBar.durationContainer); + DOM.hide(statusBar.cellRunStatusContainer); + const titleMenu = disposables.add(this.cellMenus.getCellTitleMenu(contextKeyService)); const templateData: MarkdownCellRenderTemplate = { @@ -446,7 +456,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR } private getDragImage(templateData: MarkdownCellRenderTemplate): HTMLElement { - if (templateData.currentRenderedCell!.editState === CellEditState.Editing) { + if (templateData.currentRenderedCell?.editState === CellEditState.Editing) { return this.getEditDragImage(templateData); } else { return this.getMarkdownDragImage(templateData); @@ -472,6 +482,10 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR } renderElement(element: MarkdownCellViewModel, index: number, templateData: MarkdownCellRenderTemplate, height: number | undefined): void { + if (!this.notebookEditor.hasModel()) { + throw new Error('The notebook editor is not attached with view model yet.'); + } + const removedClassNames: string[] = []; templateData.rootContainer.classList.forEach(className => { if (/^nb\-.*$/.test(className)) { @@ -489,7 +503,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR templateData.currentRenderedCell = element; templateData.currentEditor = undefined; - templateData.editorPart!.style.display = 'none'; + templateData.editorPart.style.display = 'none'; templateData.cellContainer.innerText = ''; if (height === undefined) { @@ -514,7 +528,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR } })); - elementDisposables.add(new CellContextKeyManager(templateData.contextKeyService, this.notebookEditor, this.notebookEditor.viewModel?.notebookDocument!, element)); + elementDisposables.add(new CellContextKeyManager(templateData.contextKeyService, this.notebookEditor, this.notebookEditor.viewModel.notebookDocument!, element)); // render toolbar first this.setupCellToolbarActions(templateData, elementDisposables); @@ -715,7 +729,6 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende }, {}); disposables.add(this.editorOptions.onDidChange(newValue => editor.updateOptions(newValue))); - const { collapsedPart, expandButton } = this.setupCollapsedPart(container); const progressBar = new ProgressBar(editorPart); @@ -727,6 +740,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende const cellRunState = new RunStateRenderer(statusBar.cellRunStatusContainer, runToolbar, this.instantiationService); const outputContainer = DOM.append(container, $('.output')); + const outputShowMoreContainer = DOM.append(container, $('.output-show-more-container')); const focusIndicatorRight = DOM.append(container, DOM.$('.cell-focus-indicator.cell-focus-indicator-side.cell-focus-indicator-right')); @@ -761,6 +775,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende runButtonContainer, executionOrderLabel, outputContainer, + outputShowMoreContainer, editor, disposables, elementDisposables: new DisposableStore(), @@ -793,7 +808,11 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende } private updateForMetadata(element: CodeCellViewModel, templateData: CodeCellRenderTemplate): void { - const metadata = element.getEvaluatedMetadata(this.notebookEditor.viewModel!.notebookDocument.metadata); + if (!this.notebookEditor.hasModel()) { + return; + } + + const metadata = element.getEvaluatedMetadata(this.notebookEditor.viewModel.notebookDocument.metadata); templateData.container.classList.toggle('runnable', !!metadata.runnable); this.updateExecutionOrder(metadata, templateData); templateData.statusBar.cellStatusMessageContainer.textContent = metadata?.statusMessage || ''; @@ -843,10 +862,15 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende templateData.focusIndicatorRight.style.height = `${element.layoutInfo.indicatorHeight}px`; templateData.focusIndicatorBottom.style.top = `${element.layoutInfo.totalHeight - BOTTOM_CELL_TOOLBAR_GAP - CELL_BOTTOM_MARGIN}px`; templateData.outputContainer.style.top = `${element.layoutInfo.outputContainerOffset}px`; + templateData.outputShowMoreContainer.style.top = `${element.layoutInfo.outputShowMoreContainerOffset}px`; templateData.dragHandle.style.height = `${element.layoutInfo.totalHeight - BOTTOM_CELL_TOOLBAR_GAP}px`; } renderElement(element: CodeCellViewModel, index: number, templateData: CodeCellRenderTemplate, height: number | undefined): void { + if (!this.notebookEditor.hasModel()) { + throw new Error('The notebook editor is not attached with view model yet.'); + } + const removedClassNames: string[] = []; templateData.rootContainer.classList.forEach(className => { if (/^nb\-.*$/.test(className)) { @@ -893,7 +917,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende elementDisposables.add(this.instantiationService.createInstance(CodeCell, this.notebookEditor, element, templateData)); this.renderedEditors.set(element, templateData.editor); - elementDisposables.add(new CellContextKeyManager(templateData.contextKeyService, this.notebookEditor, this.notebookEditor.viewModel?.notebookDocument!, element)); + elementDisposables.add(new CellContextKeyManager(templateData.contextKeyService, this.notebookEditor, this.notebookEditor.viewModel.notebookDocument!, element)); this.updateForLayout(element, templateData); elementDisposables.add(element.onDidChangeLayout(() => { @@ -912,6 +936,11 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende this.updateForHover(element, templateData); } })); + elementDisposables.add(this.notebookEditor.viewModel.notebookDocument.onDidChangeContent(e => { + if (e.rawEvents.find(event => event.kind === NotebookCellsChangeType.ChangeDocumentMetadata)) { + this.updateForMetadata(element, templateData); + } + })); this.updateForOutputs(element, templateData); elementDisposables.add(element.onDidChangeOutputs(_e => this.updateForOutputs(element, templateData))); @@ -999,11 +1028,13 @@ export class RunStateRenderer { private pendingNewState: NotebookCellRunState | undefined; constructor(private readonly element: HTMLElement, private readonly runToolbar: ToolBar, private readonly instantiationService: IInstantiationService) { + DOM.hide(element); } clear() { if (this.spinnerTimer) { clearTimeout(this.spinnerTimer); + this.spinnerTimer = undefined; } } @@ -1020,9 +1051,9 @@ export class RunStateRenderer { } if (runState === NotebookCellRunState.Success) { - DOM.reset(this.element, ...renderCodicons('$(check)')); + DOM.reset(this.element, ...renderCodicons(ThemeIcon.asCodiconLabel(successStateIcon))); } else if (runState === NotebookCellRunState.Error) { - DOM.reset(this.element, ...renderCodicons('$(error)')); + DOM.reset(this.element, ...renderCodicons(ThemeIcon.asCodiconLabel(errorStateIcon))); } else if (runState === NotebookCellRunState.Running) { DOM.reset(this.element, ...renderCodicons('$(sync~spin)')); @@ -1036,6 +1067,12 @@ export class RunStateRenderer { } else { this.element.innerText = ''; } + + if (runState === NotebookCellRunState.Idle) { + DOM.hide(this.element); + } else { + this.element.style.display = 'flex'; + } } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets.ts index 41bb82bb6..6be1604ee 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets.ts @@ -9,9 +9,13 @@ import { CodiconLabel } from 'vs/base/browser/ui/codicons/codiconLabel'; import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; import { stripCodicons } from 'vs/base/common/codicons'; import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { isWindows } from 'vs/base/common/platform'; import { extUri } from 'vs/base/common/resources'; +import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver'; +import { IDimension } from 'vs/editor/common/editorCommon'; import { IModeService } from 'vs/editor/common/services/modeService'; import { localize } from 'vs/nls'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -20,11 +24,25 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ChangeCellLanguageAction, INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; import { ICellViewModel, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { BaseCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel'; import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; import { CellKind, CellStatusbarAlignment, INotebookCellStatusBarEntry } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + const $ = DOM.$; +export interface IClickTarget { + type: ClickTargetType; + event: MouseEvent; +} + +export const enum ClickTargetType { + Container = 0, + CellStatus = 1, + ContributedTextItem = 2, + ContributedCommandItem = 3 +} + export class CellEditorStatusBar extends Disposable { readonly cellStatusMessageContainer: HTMLElement; readonly cellRunStatusContainer: HTMLElement; @@ -37,6 +55,8 @@ export class CellEditorStatusBar extends Disposable { private readonly itemsDisposable: DisposableStore; private currentContext: INotebookCellActionContext | undefined; + protected readonly _onDidClick: Emitter = this._register(new Emitter()); + readonly onDidClick: Event = this._onDidClick.event; constructor( container: HTMLElement, @@ -60,6 +80,38 @@ export class CellEditorStatusBar extends Disposable { this.updateStatusBarItems(); } })); + + this._register(DOM.addDisposableListener(this.statusBarContainer, DOM.EventType.CLICK, e => { + if (e.target === leftItemsContainer || e.target === rightItemsContainer || e.target === this.statusBarContainer) { + // hit on empty space + this._onDidClick.fire({ + type: ClickTargetType.Container, + event: e + }); + } else if (e.target && ( + this.cellStatusMessageContainer.contains(e.target as Node) + || this.cellRunStatusContainer.contains(e.target as Node) + || this.durationContainer.contains(e.target as Node) + )) { + this._onDidClick.fire({ + type: ClickTargetType.CellStatus, + event: e + }); + } else { + if ((e.target as HTMLElement).classList.contains('cell-status-item-has-command')) { + this._onDidClick.fire({ + type: ClickTargetType.ContributedCommandItem, + event: e + }); + } else { + // text + this._onDidClick.fire({ + type: ClickTargetType.ContributedTextItem, + event: e + }); + } + } + })); } update(context: INotebookCellActionContext) { @@ -110,6 +162,10 @@ class CellStatusBarItem extends Disposable { super(); new CodiconLabel(this.container).text = this._itemModel.text; + if (this._itemModel.opacity) { + this.container.style.opacity = this._itemModel.opacity; + } + let ariaLabel: string; let role: string | undefined; if (this._itemModel.accessibilityInformation) { @@ -181,11 +237,12 @@ export class CellLanguageStatusBarItem extends Disposable { super(); this.labelElement = DOM.append(container, $('.cell-language-picker.cell-status-item')); this.labelElement.tabIndex = 0; + this.labelElement.classList.add('cell-status-item-has-command'); this._register(DOM.addDisposableListener(this.labelElement, DOM.EventType.CLICK, () => { this.run(); })); - this._register(DOM.addDisposableListener(this.labelElement, DOM.EventType.KEY_UP, e => { + this._register(DOM.addDisposableListener(this.labelElement, DOM.EventType.KEY_DOWN, e => { const event = new StandardKeyboardEvent(e); if (event.equals(KeyCode.Space) || event.equals(KeyCode.Enter)) { this.run(); @@ -196,7 +253,10 @@ export class CellLanguageStatusBarItem extends Disposable { private run() { this.instantiationService.invokeFunction(accessor => { - new ChangeCellLanguageAction().run(accessor, { notebookEditor: this.editor!, cell: this.cell! }); + if (!this.editor || !this.editor.hasModel() || !this.cell) { + return; + } + new ChangeCellLanguageAction().run(accessor, { notebookEditor: this.editor, cell: this.cell }); }); } @@ -215,3 +275,87 @@ export class CellLanguageStatusBarItem extends Disposable { this.labelElement.title = localize('notebook.cell.status.language', "Select Cell Language Mode"); } } + +declare const ResizeObserver: any; + +export interface IResizeObserver { + startObserving: () => void; + stopObserving: () => void; + getWidth(): number; + getHeight(): number; + dispose(): void; +} + +export class BrowserResizeObserver extends Disposable implements IResizeObserver { + private readonly referenceDomElement: HTMLElement | null; + + private readonly observer: any; + private width: number; + private height: number; + + constructor(referenceDomElement: HTMLElement | null, dimension: IDimension | undefined, changeCallback: () => void) { + super(); + + this.referenceDomElement = referenceDomElement; + this.width = -1; + this.height = -1; + + this.observer = new ResizeObserver((entries: any) => { + for (const entry of entries) { + if (entry.target === referenceDomElement && entry.contentRect) { + if (this.width !== entry.contentRect.width || this.height !== entry.contentRect.height) { + this.width = entry.contentRect.width; + this.height = entry.contentRect.height; + DOM.scheduleAtNextAnimationFrame(() => { + changeCallback(); + }); + } + } + } + }); + } + + getWidth(): number { + return this.width; + } + + getHeight(): number { + return this.height; + } + + startObserving(): void { + this.observer.observe(this.referenceDomElement!); + } + + stopObserving(): void { + this.observer.unobserve(this.referenceDomElement!); + } + + dispose(): void { + this.observer.disconnect(); + super.dispose(); + } +} + +export function getResizesObserver(referenceDomElement: HTMLElement | null, dimension: IDimension | undefined, changeCallback: () => void): IResizeObserver { + if (ResizeObserver) { + return new BrowserResizeObserver(referenceDomElement, dimension, changeCallback); + } else { + return new ElementSizeObserver(referenceDomElement, dimension, changeCallback); + } +} + +export function getExecuteCellPlaceholder(viewCell: BaseCellViewModel) { + return { + alignment: CellStatusbarAlignment.LEFT, + priority: -1, + cellResource: viewCell.uri, + command: undefined, + // text: `${keybinding?.getLabel() || 'Ctrl + Enter'} to run`, + // tooltip: `${keybinding?.getLabel() || 'Ctrl + Enter'} to run`, + text: isWindows ? 'Ctrl + Alt + Enter' : 'Ctrl + Enter to run', + tooltip: isWindows ? 'Ctrl + Alt + Enter' : 'Ctrl + Enter to run', + visible: true, + opacity: '0.7' + }; +} diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts index 98638963f..56869eb83 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts @@ -4,239 +4,41 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from 'vs/base/browser/dom'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { raceCancellation } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IDimension } from 'vs/editor/common/editorCommon'; import { IModeService } from 'vs/editor/common/services/modeService'; -import * as nls from 'vs/nls'; -import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { EDITOR_BOTTOM_PADDING, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellFocusMode, CodeCellRenderTemplate, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { EDITOR_BOTTOM_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CellFocusMode, CodeCellRenderTemplate, getEditorTopPadding, IActiveNotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; -import { BUILTIN_RENDERER_ID, CellOutputKind, CellUri, IInsetRenderOutput, IProcessedOutput, IRenderOutput, ITransformedDisplayOutputDto, outputHasDynamicHeight, RenderOutputType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ClickTargetType, getExecuteCellPlaceholder } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; +import { OutputContainer } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellOutput'; +import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; +import { NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -interface IMimeTypeRenderer extends IQuickPickItem { - index: number; -} - -class OutputElement extends Disposable { - readonly resizeListener = new DisposableStore(); - domNode!: HTMLElement; - renderResult?: IRenderOutput; - - constructor( - private notebookEditor: INotebookEditor, - private notebookService: INotebookService, - private quickInputService: IQuickInputService, - private viewCell: CodeCellViewModel, - private outputContainer: HTMLElement, - readonly output: IProcessedOutput - ) { - super(); - } - - render(index: number, beforeElement?: HTMLElement) { - if (this.viewCell.metadata.outputCollapsed) { - return; - } - - const outputItemDiv = document.createElement('div'); - let result: IRenderOutput | undefined = undefined; - - if (this.output.outputKind === CellOutputKind.Rich) { - const transformedDisplayOutput = this.output as ITransformedDisplayOutputDto; - - if (transformedDisplayOutput.orderedMimeTypes!.length > 1) { - outputItemDiv.style.position = 'relative'; - const mimeTypePicker = DOM.$('.multi-mimetype-output'); - mimeTypePicker.classList.add('codicon', 'codicon-code'); - mimeTypePicker.tabIndex = 0; - mimeTypePicker.title = nls.localize('mimeTypePicker', "Choose a different output mimetype, available mimetypes: {0}", transformedDisplayOutput.orderedMimeTypes!.map(mimeType => mimeType.mimeType).join(', ')); - outputItemDiv.appendChild(mimeTypePicker); - this.resizeListener.add(DOM.addStandardDisposableListener(mimeTypePicker, 'mousedown', async e => { - if (e.leftButton) { - e.preventDefault(); - e.stopPropagation(); - await this.pickActiveMimeTypeRenderer(transformedDisplayOutput); - } - })); - - this.resizeListener.add((DOM.addDisposableListener(mimeTypePicker, DOM.EventType.KEY_DOWN, async e => { - const event = new StandardKeyboardEvent(e); - if ((event.equals(KeyCode.Enter) || event.equals(KeyCode.Space))) { - e.preventDefault(); - e.stopPropagation(); - await this.pickActiveMimeTypeRenderer(transformedDisplayOutput); - } - }))); - - } - const pickedMimeTypeRenderer = this.output.orderedMimeTypes![this.output.pickedMimeTypeIndex!]; - - const innerContainer = DOM.$('.output-inner-container'); - DOM.append(outputItemDiv, innerContainer); - - - if (pickedMimeTypeRenderer.rendererId !== BUILTIN_RENDERER_ID) { - const renderer = this.notebookService.getRendererInfo(pickedMimeTypeRenderer.rendererId); - result = renderer - ? { type: RenderOutputType.Extension, renderer, source: this.output, mimeType: pickedMimeTypeRenderer.mimeType } - : this.notebookEditor.getOutputRenderer().render(this.output, innerContainer, pickedMimeTypeRenderer.mimeType, this.getNotebookUri(),); - } else { - result = this.notebookEditor.getOutputRenderer().render(this.output, innerContainer, pickedMimeTypeRenderer.mimeType, this.getNotebookUri(),); - } - } else { - // for text and error, there is no mimetype - const innerContainer = DOM.$('.output-inner-container'); - DOM.append(outputItemDiv, innerContainer); - - result = this.notebookEditor.getOutputRenderer().render(this.output, innerContainer, undefined, this.getNotebookUri(),); - } - - this.domNode = outputItemDiv; - this.renderResult = result; - - if (!result) { - this.viewCell.updateOutputHeight(index, 0); - return; - } - - if (beforeElement) { - this.outputContainer.insertBefore(outputItemDiv, beforeElement); - } else { - this.outputContainer.appendChild(outputItemDiv); - } - - if (result.type !== RenderOutputType.None) { - this.viewCell.selfSizeMonitoring = true; - this.notebookEditor.createInset(this.viewCell, result as any, this.viewCell.getOutputOffset(index)); - } else { - outputItemDiv.classList.add('foreground', 'output-element'); - outputItemDiv.style.position = 'absolute'; - } - - if (outputHasDynamicHeight(result)) { - this.viewCell.selfSizeMonitoring = true; - - const clientHeight = outputItemDiv.clientHeight; - const dimension = { - width: this.viewCell.layoutInfo.editorWidth, - height: clientHeight - }; - const elementSizeObserver = getResizesObserver(outputItemDiv, dimension, () => { - if (this.outputContainer && document.body.contains(this.outputContainer!)) { - const height = Math.ceil(elementSizeObserver.getHeight()); - - if (clientHeight === height) { - return; - } - - const currIndex = this.viewCell.outputs.indexOf(this.output); - if (currIndex < 0) { - return; - } - - this.viewCell.updateOutputHeight(currIndex, height); - this.relayoutCell(); - } - }); - elementSizeObserver.startObserving(); - this.resizeListener.add(elementSizeObserver); - this.viewCell.updateOutputHeight(index, clientHeight); - } else if (result.type === RenderOutputType.None) { // no-op if it's a webview - const clientHeight = Math.ceil(outputItemDiv.clientHeight); - this.viewCell.updateOutputHeight(index, clientHeight); - - const top = this.viewCell.getOutputOffsetInContainer(index); - outputItemDiv.style.top = `${top}px`; - } - } - - async pickActiveMimeTypeRenderer(output: ITransformedDisplayOutputDto) { - const currIndex = output.pickedMimeTypeIndex; - const items = output.orderedMimeTypes!.map((mimeType, index): IMimeTypeRenderer => ({ - label: mimeType.mimeType, - id: mimeType.mimeType, - index: index, - picked: index === currIndex, - detail: this.generateRendererInfo(mimeType.rendererId), - description: index === currIndex ? nls.localize('curruentActiveMimeType', "Currently Active") : undefined - })); - - const picker = this.quickInputService.createQuickPick(); - picker.items = items; - picker.activeItems = items.filter(item => !!item.picked); - picker.placeholder = nls.localize('promptChooseMimeType.placeHolder', "Select output mimetype to render for current output"); - - const pick = await new Promise(resolve => { - picker.onDidAccept(() => { - resolve(picker.selectedItems.length === 1 ? (picker.selectedItems[0] as IMimeTypeRenderer).index : undefined); - picker.dispose(); - }); - picker.show(); - }); - - if (pick === undefined) { - return; - } - - if (pick !== currIndex) { - // user chooses another mimetype - const index = this.viewCell.outputs.indexOf(output); - const nextElement = this.domNode.nextElementSibling; - this.resizeListener.clear(); - const element = this.domNode; - if (element) { - element.parentElement?.removeChild(element); - this.notebookEditor.removeInset(output); - } - - output.pickedMimeTypeIndex = pick; - this.render(index, nextElement as HTMLElement); - this.relayoutCell(); - } - } - - private getNotebookUri(): URI | undefined { - return CellUri.parse(this.viewCell.uri)?.notebook; - } - - generateRendererInfo(renderId: string | undefined): string { - if (renderId === undefined || renderId === BUILTIN_RENDERER_ID) { - return nls.localize('builtinRenderInfo', "built-in"); - } - - const renderInfo = this.notebookService.getRendererInfo(renderId); - - if (renderInfo) { - const displayName = renderInfo.displayName !== '' ? renderInfo.displayName : renderInfo.id; - return `${displayName} (${renderInfo.extensionId.value})`; - } - - return nls.localize('builtinRenderInfo', "built-in"); - } - - relayoutCell() { - this.notebookEditor.layoutNotebookCell(this.viewCell, this.viewCell.layoutInfo.totalHeight); - } -} export class CodeCell extends Disposable { - private outputEntries = new Map(); + private _outputContainerRenderer: OutputContainer; + private _activeCellRunPlaceholder: IDisposable | null = null; + private _untrustedStatusItem: IDisposable | null = null; constructor( - private notebookEditor: INotebookEditor, + private notebookEditor: IActiveNotebookEditor, private viewCell: CodeCellViewModel, private templateData: CodeCellRenderTemplate, - @INotebookService private notebookService: INotebookService, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @IModeService private readonly _modeService: IModeService + @INotebookService notebookService: INotebookService, + @IQuickInputService quickInputService: IQuickInputService, + @INotebookCellStatusBarService readonly notebookCellStatusBarService: INotebookCellStatusBarService, + @IOpenerService readonly openerService: IOpenerService, + @ITextFileService readonly textFileService: ITextFileService, + @IModeService private readonly _modeService: IModeService, + // @IKeybindingService private readonly _keybindingService: IKeybindingService, + ) { super(); @@ -244,7 +46,7 @@ export class CodeCell extends Disposable { const lineNum = this.viewCell.lineCount; const lineHeight = this.viewCell.layoutInfo.fontInfo?.lineHeight || 17; const editorHeight = this.viewCell.layoutInfo.editorHeight === 0 - ? lineNum * lineHeight + EDITOR_TOP_PADDING + EDITOR_BOTTOM_PADDING + ? lineNum * lineHeight + getEditorTopPadding() + EDITOR_BOTTOM_PADDING : this.viewCell.layoutInfo.editorHeight; this.layoutEditor( @@ -292,10 +94,10 @@ export class CodeCell extends Disposable { })); updateForFocusMode(); - templateData.editor?.updateOptions({ readOnly: !(viewCell.getEvaluatedMetadata(notebookEditor.viewModel!.metadata).editable) }); + templateData.editor?.updateOptions({ readOnly: !(viewCell.getEvaluatedMetadata(notebookEditor.viewModel.metadata).editable) }); this._register(viewCell.onDidChangeState((e) => { if (e.metadataChanged) { - templateData.editor?.updateOptions({ readOnly: !(viewCell.getEvaluatedMetadata(notebookEditor.viewModel!.metadata).editable) }); + templateData.editor?.updateOptions({ readOnly: !(viewCell.getEvaluatedMetadata(notebookEditor.viewModel.metadata).editable) }); // TODO@rob this isn't nice this.viewCell.layoutChange({}); @@ -313,14 +115,14 @@ export class CodeCell extends Disposable { this._register(viewCell.onDidChangeLayout((e) => { if (e.outerWidth !== undefined) { - const layoutInfo = templateData.editor!.getLayoutInfo(); + const layoutInfo = templateData.editor.getLayoutInfo(); if (layoutInfo.width !== viewCell.layoutInfo.editorWidth) { this.onCellWidthChange(); } } })); - this._register(templateData.editor!.onDidContentSizeChange((e) => { + this._register(templateData.editor.onDidContentSizeChange((e) => { if (e.contentHeightChanged) { if (this.viewCell.layoutInfo.editorHeight !== e.contentHeight) { this.onCellHeightChange(e.contentHeight); @@ -328,92 +130,20 @@ export class CodeCell extends Disposable { } })); - this._register(templateData.editor!.onDidChangeCursorSelection((e) => { + this._register(templateData.editor.onDidChangeCursorSelection((e) => { if (e.source === 'restoreState') { // do not reveal the cell into view if this selection change was caused by restoring editors... return; } - const primarySelection = templateData.editor!.getSelection(); + const primarySelection = templateData.editor.getSelection(); if (primarySelection) { - this.notebookEditor.revealLineInViewAsync(viewCell, primarySelection!.positionLineNumber); + this.notebookEditor.revealLineInViewAsync(viewCell, primarySelection.positionLineNumber); } })); - this._register(viewCell.onDidChangeOutputs((splices) => { - if (!splices.length) { - return; - } - - const previousOutputHeight = this.viewCell.layoutInfo.outputTotalHeight; - - if (this.viewCell.outputs.length) { - this.templateData.outputContainer!.style.display = 'block'; - } else { - this.templateData.outputContainer!.style.display = 'none'; - } - - const reversedSplices = splices.reverse(); - - reversedSplices.forEach(splice => { - viewCell.spliceOutputHeights(splice[0], splice[1], splice[2].map(_ => 0)); - }); - - const removedKeys: IProcessedOutput[] = []; - - this.outputEntries.forEach((value, key) => { - if (viewCell.outputs.indexOf(key) < 0) { - // already removed - removedKeys.push(key); - // remove element from DOM - this.templateData?.outputContainer?.removeChild(value.domNode); - this.notebookEditor.removeInset(key); - } - }); - - removedKeys.forEach(key => { - this.outputEntries.get(key)?.dispose(); - this.outputEntries.delete(key); - }); - - let prevElement: HTMLElement | undefined = undefined; - - [...this.viewCell.outputs].reverse().forEach(output => { - if (this.outputEntries.has(output)) { - // already exist - prevElement = this.outputEntries.get(output)!.domNode; - return; - } - - // newly added element - const currIndex = this.viewCell.outputs.indexOf(output); - this.renderOutput(output, currIndex, prevElement); - prevElement = this.outputEntries.get(output)?.domNode; - }); - - const editorHeight = templateData.editor!.getContentHeight(); - viewCell.editorHeight = editorHeight; - - if (previousOutputHeight === 0 || this.viewCell.outputs.length === 0) { - // first execution or removing all outputs - this.relayoutCell(); - } else { - this.relayoutCellDebounced(); - } - })); - - this._register(viewCell.onDidChangeLayout(() => { - this.outputEntries.forEach((value, key) => { - const index = viewCell.outputs.indexOf(key); - if (index >= 0) { - const top = this.viewCell.getOutputOffsetInContainer(index); - value.domNode.style.top = `${top}px`; - } - }); - - })); - + // Apply decorations this._register(viewCell.onCellDecorationsChanged((e) => { e.added.forEach(options => { if (options.className) { @@ -435,7 +165,6 @@ export class CodeCell extends Disposable { } }); })); - // apply decorations viewCell.getCellDecorations().forEach(options => { if (options.className) { @@ -447,7 +176,18 @@ export class CodeCell extends Disposable { } }); - this._register(templateData.editor!.onMouseDown(e => { + // Mouse click handlers + this._register(templateData.statusBar.onDidClick(e => { + if (e.type !== ClickTargetType.ContributedCommandItem) { + const target = templateData.editor.getTargetAtClientPoint(e.event.clientX, e.event.clientY - viewCell.getEditorStatusbarHeight()); + if (target?.position) { + templateData.editor.setPosition(target.position); + templateData.editor.focus(); + } + } + })); + + this._register(templateData.editor.onMouseDown(e => { // prevent default on right mouse click, otherwise it will trigger unexpected focus changes // the catch is, it means we don't allow customization of right button mouse down handlers other than the built in ones. if (e.event.rightButton) { @@ -455,49 +195,88 @@ export class CodeCell extends Disposable { } })); - const updateFocusMode = () => viewCell.focusMode = templateData.editor!.hasWidgetFocus() ? CellFocusMode.Editor : CellFocusMode.Container; - this._register(templateData.editor!.onDidFocusEditorWidget(() => { + // Focus Mode + const updateFocusMode = () => viewCell.focusMode = templateData.editor.hasWidgetFocus() ? CellFocusMode.Editor : CellFocusMode.Container; + this._register(templateData.editor.onDidFocusEditorWidget(() => { updateFocusMode(); })); - - this._register(templateData.editor!.onDidBlurEditorWidget(() => { - updateFocusMode(); + this._register(templateData.editor.onDidBlurEditorWidget(() => { + // this is for a special case: + // users click the status bar empty space, which we will then focus the editor + // so we don't want to update the focus state too eagerly + if (document.activeElement?.contains(this.templateData.container)) { + setTimeout(() => { + updateFocusMode(); + }, 300); + } else { + updateFocusMode(); + } })); - updateFocusMode(); - if (viewCell.outputs.length > 0) { - let layoutCache = false; - if (this.viewCell.layoutInfo.totalHeight !== 0 && this.viewCell.layoutInfo.editorHeight > editorHeight) { - layoutCache = true; - this.relayoutCell(); - } - - this.templateData.outputContainer!.style.display = 'block'; - // there are outputs, we need to calcualte their sizes and trigger relayout - // @TODO@rebornix, if there is no resizable output, we should not check their height individually, which hurts the performance - for (let index = 0; index < this.viewCell.outputs.length; index++) { - const currOutput = this.viewCell.outputs[index]; - - // always add to the end - this.renderOutput(currOutput, index, undefined); - } - - viewCell.editorHeight = editorHeight; - if (layoutCache) { - this.relayoutCellDebounced(); - } else { - this.relayoutCell(); - } - } else { - // noop - viewCell.editorHeight = editorHeight; - this.relayoutCell(); - this.templateData.outputContainer!.style.display = 'none'; - } - + // Render Outputs + this._outputContainerRenderer = new OutputContainer(notebookEditor, viewCell, templateData, notebookService, quickInputService, openerService, textFileService); + this._outputContainerRenderer.render(editorHeight); // Need to do this after the intial renderOutput updateForCollapseState(); + + const updatePlaceholder = () => { + if (this.notebookEditor.viewModel + && this.notebookEditor.getActiveCell() === this.viewCell + && viewCell.getEvaluatedMetadata(this.notebookEditor.viewModel.metadata).runnable + && viewCell.metadata.runState === undefined + && viewCell.metadata.lastRunDuration === undefined + ) { + // active cell and no run status + if (this._activeCellRunPlaceholder === null) { + // const keybinding = this._keybindingService.lookupKeybinding(EXECUTE_CELL_COMMAND_ID); + this._activeCellRunPlaceholder = this.notebookCellStatusBarService.addEntry(getExecuteCellPlaceholder(this.viewCell)); + } + + return; + } + + this._activeCellRunPlaceholder?.dispose(); + this._activeCellRunPlaceholder = null; + }; + + this._register(this.notebookEditor.onDidChangeActiveCell(() => { + updatePlaceholder(); + })); + + this._register(this.viewCell.model.onDidChangeMetadata(() => { + updatePlaceholder(); + })); + + // const updateUntrustedStatus = () => { + // if (this.notebookEditor.viewModel + // && this.notebookEditor.viewModel.metadata.trusted) { + // this._untrustedStatusItem?.dispose(); + // this._untrustedStatusItem = null; + // } else { + // if (this._untrustedStatusItem === null) { + // this._untrustedStatusItem = this.notebookCellStatusBarService.addEntry({ + // alignment: CellStatusbarAlignment.LEFT, + // priority: -1, + // cellResource: viewCell.uri, + // command: TRUST_NOTEBOOK_COMMAND_ID, + // text: 'Untrusted', + // tooltip: 'Untrusted notebook', + // visible: true, + // }); + // } + // } + // }; + + this._register(this.notebookEditor.viewModel.notebookDocument.onDidChangeContent(e => { + if (e.rawEvents.find(event => event.kind === NotebookCellsChangeType.ChangeDocumentMetadata)) { + updatePlaceholder(); + // updateUntrustedStatus(); + } + })); + + updatePlaceholder(); + // updateUntrustedStatus(); } private viewUpdate(): void { @@ -512,52 +291,24 @@ export class CodeCell extends Disposable { } } - private viewUpdateShowOutputs(): void { - for (let index = 0; index < this.viewCell.outputs.length; index++) { - const currOutput = this.viewCell.outputs[index]; - - const renderedOutput = this.outputEntries.get(currOutput); - if (renderedOutput && renderedOutput.renderResult) { - if (renderedOutput.renderResult.type !== RenderOutputType.None) { - this.notebookEditor.createInset(this.viewCell, renderedOutput.renderResult as IInsetRenderOutput, this.viewCell.getOutputOffset(index)); - } else { - // Anything else, just update the height - this.viewCell.updateOutputHeight(index, renderedOutput.domNode.clientHeight); - } - } else { - // Wasn't previously rendered, render it now - this.renderOutput(currOutput, index); - } - } - - this.relayoutCell(); - } - private viewUpdateInputCollapsed(): void { DOM.hide(this.templateData.cellContainer); DOM.hide(this.templateData.runButtonContainer); DOM.show(this.templateData.collapsedPart); DOM.show(this.templateData.outputContainer); this.templateData.container.classList.toggle('collapsed', true); - - this.viewUpdateShowOutputs(); + this._outputContainerRenderer.viewUpdateShowOutputs(); this.relayoutCell(); } - private viewUpdateHideOuputs(): void { - for (const e of this.outputEntries.keys()) { - this.notebookEditor.hideInset(e); - } - } - private viewUpdateOutputCollapsed(): void { DOM.show(this.templateData.cellContainer); DOM.show(this.templateData.runButtonContainer); DOM.show(this.templateData.collapsedPart); DOM.hide(this.templateData.outputContainer); - this.viewUpdateHideOuputs(); + this._outputContainerRenderer.viewUpdateHideOuputs(); this.templateData.container.classList.toggle('collapsed', false); this.templateData.container.classList.toggle('output-collapsed', true); @@ -572,11 +323,7 @@ export class CodeCell extends Disposable { DOM.hide(this.templateData.outputContainer); this.templateData.container.classList.toggle('collapsed', true); this.templateData.container.classList.toggle('output-collapsed', true); - - for (const e of this.outputEntries.keys()) { - this.notebookEditor.hideInset(e); - } - + this._outputContainerRenderer.viewUpdateHideOuputs(); this.relayoutCell(); } @@ -587,9 +334,7 @@ export class CodeCell extends Disposable { DOM.show(this.templateData.outputContainer); this.templateData.container.classList.toggle('collapsed', false); this.templateData.container.classList.toggle('output-collapsed', false); - - this.viewUpdateShowOutputs(); - + this._outputContainerRenderer.viewUpdateShowOutputs(); this.relayoutCell(); } @@ -599,7 +344,7 @@ export class CodeCell extends Disposable { } private onCellWidthChange(): void { - const realContentHeight = this.templateData.editor!.getContentHeight(); + const realContentHeight = this.templateData.editor.getContentHeight(); this.viewCell.editorHeight = realContentHeight; this.relayoutCell(); @@ -611,16 +356,11 @@ export class CodeCell extends Disposable { ); // for contents for which we don't observe for dynamic height, update them manually - this.viewCell.outputs.forEach((o, i) => { - const renderedOutput = this.outputEntries.get(o); - if (renderedOutput && renderedOutput.renderResult && renderedOutput.renderResult.type === RenderOutputType.None && !renderedOutput.renderResult.hasDynamicHeight) { - this.viewCell.updateOutputHeight(i, renderedOutput.domNode.clientHeight); - } - }); + this._outputContainerRenderer.onCellWidthChange(); } private onCellHeightChange(newHeight: number): void { - const viewLayout = this.templateData.editor!.getLayoutInfo(); + const viewLayout = this.templateData.editor.getLayoutInfo(); this.viewCell.editorHeight = newHeight; this.relayoutCell(); this.layoutEditor( @@ -631,14 +371,6 @@ export class CodeCell extends Disposable { ); } - private renderOutput(currOutput: IProcessedOutput, index: number, beforeElement?: HTMLElement) { - if (!this.outputEntries.has(currOutput)) { - this.outputEntries.set(currOutput, new OutputElement(this.notebookEditor, this.notebookService, this.quickInputService, this.viewCell, this.templateData.outputContainer, currOutput)); - } - - this.outputEntries.get(currOutput)!.render(index, beforeElement); - } - relayoutCell() { if (this._timer !== null) { clearTimeout(this._timer); @@ -662,13 +394,11 @@ export class CodeCell extends Disposable { dispose() { this.viewCell.detachTextEditor(); - this.outputEntries.forEach((value) => { - value.dispose(); - }); - - this.templateData.focusIndicatorLeft!.style.height = 'initial'; + this._outputContainerRenderer.dispose(); + this._activeCellRunPlaceholder?.dispose(); + this._untrustedStatusItem?.dispose(); + this.templateData.focusIndicatorLeft.style.height = 'initial'; super.dispose(); } } - diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/commonViewComponents.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/commonViewComponents.ts deleted file mode 100644 index 4b5002ebc..000000000 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/commonViewComponents.ts +++ /dev/null @@ -1,26 +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 { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { MenuItemAction } from 'vs/platform/actions/common/actions'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { renderCodicons } from 'vs/base/browser/codicons'; - -export class CodiconActionViewItem extends MenuEntryActionViewItem { - constructor( - readonly _action: MenuItemAction, - keybindingService: IKeybindingService, - notificationService: INotificationService, - ) { - super(_action, keybindingService, notificationService); - } - updateLabel(): void { - if (this.options.label && this.label) { - DOM.reset(this.label, ...renderCodicons(this._commandAction.label ?? '')); - } - } -} diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts index f75ca70ee..da2e04bc6 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts @@ -6,20 +6,24 @@ import * as DOM from 'vs/base/browser/dom'; import { raceCancellation } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { renderCodicons } from 'vs/base/browser/codicons'; -import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { EDITOR_BOTTOM_PADDING, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellEditState, CellFocusMode, INotebookEditor, MarkdownCellRenderTemplate, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { EDITOR_BOTTOM_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CellEditState, CellFocusMode, MarkdownCellRenderTemplate, ICellViewModel, getEditorTopPadding, IActiveNotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellFoldingState } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel'; import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver'; +import { getExecuteCellPlaceholder, getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; +import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; +import { NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { collapsedIcon, expandedIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { renderCodicons } from 'vs/base/browser/codicons'; export class StatefulMarkdownCell extends Disposable { @@ -29,14 +33,16 @@ export class StatefulMarkdownCell extends Disposable { private localDisposables = new DisposableStore(); private foldingState: CellFoldingState; + private _activeCellRunPlaceholder: IDisposable | null = null; constructor( - private readonly notebookEditor: INotebookEditor, + private readonly notebookEditor: IActiveNotebookEditor, private readonly viewCell: MarkdownCellViewModel, private readonly templateData: MarkdownCellRenderTemplate, private editorOptions: IEditorOptions, private readonly renderedEditors: Map, @IContextKeyService private readonly contextKeyService: IContextKeyService, + @INotebookCellStatusBarService readonly notebookCellStatusBarService: INotebookCellStatusBarService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(); @@ -129,6 +135,40 @@ export class StatefulMarkdownCell extends Disposable { }); this.viewUpdate(); + + const updatePlaceholder = () => { + if ( + this.notebookEditor.getActiveCell() === this.viewCell + && !!this.notebookEditor.viewModel.metadata.trusted + ) { + // active cell and no run status + if (this._activeCellRunPlaceholder === null) { + // const keybinding = this._keybindingService.lookupKeybinding(EXECUTE_CELL_COMMAND_ID); + this._activeCellRunPlaceholder = this.notebookCellStatusBarService.addEntry(getExecuteCellPlaceholder(this.viewCell)); + } + + return; + } + + this._activeCellRunPlaceholder?.dispose(); + this._activeCellRunPlaceholder = null; + }; + + this._register(this.notebookEditor.onDidChangeActiveCell(() => { + updatePlaceholder(); + })); + + this._register(this.viewCell.model.onDidChangeMetadata(() => { + updatePlaceholder(); + })); + + this._register(this.notebookEditor.viewModel.notebookDocument.onDidChangeContent(e => { + if (e.rawEvents.find(event => event.kind === NotebookCellsChangeType.ChangeDocumentMetadata)) { + updatePlaceholder(); + } + })); + + updatePlaceholder(); } private viewUpdate(): void { @@ -159,13 +199,13 @@ export class StatefulMarkdownCell extends Disposable { this.templateData.container.classList.toggle('collapsed', false); if (this.editor) { - editorHeight = this.editor!.getContentHeight(); + editorHeight = this.editor.getContentHeight(); // not first time, we don't need to create editor or bind listeners this.viewCell.attachTextEditor(this.editor); this.focusEditorIfNeeded(); - this.bindEditorListeners(); + this.bindEditorListeners(this.editor); this.editor.layout({ width: this.viewCell.layoutInfo.editorWidth, @@ -175,7 +215,7 @@ export class StatefulMarkdownCell extends Disposable { const width = this.viewCell.layoutInfo.editorWidth; const lineNum = this.viewCell.lineCount; const lineHeight = this.viewCell.layoutInfo.fontInfo?.lineHeight || 17; - editorHeight = Math.max(lineNum, 1) * lineHeight + EDITOR_TOP_PADDING + EDITOR_BOTTOM_PADDING; + editorHeight = Math.max(lineNum, 1) * lineHeight + getEditorTopPadding() + EDITOR_BOTTOM_PADDING; this.templateData.editorContainer.innerText = ''; @@ -221,7 +261,7 @@ export class StatefulMarkdownCell extends Disposable { this.focusEditorIfNeeded(); } - this.bindEditorListeners(); + this.bindEditorListeners(this.editor!); this.viewCell.editorHeight = editorHeight; }); @@ -255,7 +295,7 @@ export class StatefulMarkdownCell extends Disposable { this.relayoutCell(); } else { // first time, readonly mode - this.localDisposables.add(markdownRenderer.onDidRenderCodeBlock(() => { + this.localDisposables.add(markdownRenderer.onDidRenderAsync(() => { this.viewCell.renderedMarkdownHeight = this.templateData.container.clientHeight; this.relayoutCell(); })); @@ -315,10 +355,10 @@ export class StatefulMarkdownCell extends Disposable { this.templateData.foldingIndicator.innerText = ''; break; case CellFoldingState.Collapsed: - DOM.reset(this.templateData.foldingIndicator, ...renderCodicons('$(chevron-right)')); + DOM.reset(this.templateData.foldingIndicator, ...renderCodicons(ThemeIcon.asCodiconLabel(collapsedIcon))); break; case CellFoldingState.Expanded: - DOM.reset(this.templateData.foldingIndicator, ...renderCodicons('$(chevron-down)')); + DOM.reset(this.templateData.foldingIndicator, ...renderCodicons(ThemeIcon.asCodiconLabel(expandedIcon))); break; default: @@ -326,13 +366,13 @@ export class StatefulMarkdownCell extends Disposable { } } - private bindEditorListeners() { - this.localDisposables.add(this.editor!.onDidContentSizeChange(e => { - const viewLayout = this.editor!.getLayoutInfo(); + private bindEditorListeners(editor: CodeEditorWidget) { + this.localDisposables.add(editor.onDidContentSizeChange(e => { + const viewLayout = editor.getLayoutInfo(); if (e.contentHeightChanged) { this.viewCell.editorHeight = e.contentHeight; - this.editor!.layout( + editor.layout( { width: viewLayout.width, height: e.contentHeight @@ -341,26 +381,35 @@ export class StatefulMarkdownCell extends Disposable { } })); - this.localDisposables.add(this.editor!.onDidChangeCursorSelection((e) => { + this.localDisposables.add(editor.onDidChangeCursorSelection((e) => { if (e.source === 'restoreState') { // do not reveal the cell into view if this selection change was caused by restoring editors... return; } - const primarySelection = this.editor!.getSelection(); + const primarySelection = editor.getSelection(); if (primarySelection) { - this.notebookEditor.revealLineInViewAsync(this.viewCell, primarySelection!.positionLineNumber); + this.notebookEditor.revealLineInViewAsync(this.viewCell, primarySelection.positionLineNumber); } })); - const updateFocusMode = () => this.viewCell.focusMode = this.editor!.hasWidgetFocus() ? CellFocusMode.Editor : CellFocusMode.Container; - this.localDisposables.add(this.editor!.onDidFocusEditorWidget(() => { + const updateFocusMode = () => this.viewCell.focusMode = editor.hasWidgetFocus() ? CellFocusMode.Editor : CellFocusMode.Container; + this.localDisposables.add(editor.onDidFocusEditorWidget(() => { updateFocusMode(); })); - this.localDisposables.add(this.editor!.onDidBlurEditorWidget(() => { - updateFocusMode(); + this.localDisposables.add(editor.onDidBlurEditorWidget(() => { + // this is for a special case: + // users click the status bar empty space, which we will then focus the editor + // so we don't want to update the focus state too eagerly + if (document.activeElement?.contains(this.templateData.container)) { + setTimeout(() => { + updateFocusMode(); + }, 300); + } else { + updateFocusMode(); + } })); updateFocusMode(); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver.ts deleted file mode 100644 index d9be462c0..000000000 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver.ts +++ /dev/null @@ -1,78 +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 { Disposable } from 'vs/base/common/lifecycle'; -import { IDimension } from 'vs/editor/common/editorCommon'; -import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver'; - -declare const ResizeObserver: any; - -export interface IResizeObserver { - startObserving: () => void; - stopObserving: () => void; - getWidth(): number; - getHeight(): number; - dispose(): void; -} - -export class BrowserResizeObserver extends Disposable implements IResizeObserver { - private readonly referenceDomElement: HTMLElement | null; - - private readonly observer: any; - private width: number; - private height: number; - - constructor(referenceDomElement: HTMLElement | null, dimension: IDimension | undefined, changeCallback: () => void) { - super(); - - this.referenceDomElement = referenceDomElement; - this.width = -1; - this.height = -1; - - this.observer = new ResizeObserver((entries: any) => { - for (const entry of entries) { - if (entry.target === referenceDomElement && entry.contentRect) { - if (this.width !== entry.contentRect.width || this.height !== entry.contentRect.height) { - this.width = entry.contentRect.width; - this.height = entry.contentRect.height; - DOM.scheduleAtNextAnimationFrame(() => { - changeCallback(); - }); - } - } - } - }); - } - - getWidth(): number { - return this.width; - } - - getHeight(): number { - return this.height; - } - - startObserving(): void { - this.observer.observe(this.referenceDomElement!); - } - - stopObserving(): void { - this.observer.unobserve(this.referenceDomElement!); - } - - dispose(): void { - this.observer.disconnect(); - super.dispose(); - } -} - -export function getResizesObserver(referenceDomElement: HTMLElement | null, dimension: IDimension | undefined, changeCallback: () => void): IResizeObserver { - if (ResizeObserver) { - return new BrowserResizeObserver(referenceDomElement, dimension, changeCallback); - } else { - return new ElementSizeObserver(referenceDomElement, dimension, changeCallback); - } -} diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 4036c5128..936792e8a 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -101,16 +101,30 @@ function webviewPreloads() { } }; - const runScript = async (url: string, originalUri: string, globals: { [name: string]: unknown } = {}): Promise => { - const res = await fetch(url); - const text = await res.text(); - if (!res.ok) { - throw new Error(`Unexpected ${res.status} requesting ${originalUri}: ${text || res.statusText}`); + const runScript = async (url: string, originalUri: string, globals: { [name: string]: unknown } = {}): Promise<() => (PreloadResult)> => { + let text: string; + try { + const res = await fetch(url); + text = await res.text(); + if (!res.ok) { + throw new Error(`Unexpected ${res.status} requesting ${originalUri}: ${text || res.statusText}`); + } + + globals.scriptUrl = url; + } catch (e) { + return () => ({ state: PreloadState.Error, error: e.message }); } const args = Object.entries(globals); - new Function(...args.map(([k]) => k), text)(...args.map(([, v]) => v)); - return undefined; + return () => { + try { + new Function(...args.map(([k]) => k), text)(...args.map(([, v]) => v)); + return { state: PreloadState.Ok }; + } catch (e) { + console.error(e); + return { state: PreloadState.Error, error: e.message }; + } + }; }; const outputObservers = new Map(); @@ -123,14 +137,27 @@ function webviewPreloads() { } if (entry.target.id === id && entry.contentRect) { - vscode.postMessage({ - __vscode_notebook_message: true, - type: 'dimension', - id: id, - data: { - height: entry.contentRect.height + __outputNodePadding__ * 2 - } - }); + if (entry.contentRect.height !== 0) { + entry.target.style.padding = `${__outputNodePadding__}px`; + vscode.postMessage({ + __vscode_notebook_message: true, + type: 'dimension', + id: id, + data: { + height: entry.contentRect.height + __outputNodePadding__ * 2 + } + }); + } else { + entry.target.style.padding = `0px`; + vscode.postMessage({ + __vscode_notebook_message: true, + type: 'dimension', + id: id, + data: { + height: entry.contentRect.height + } + }); + } } } }); @@ -324,11 +351,18 @@ function webviewPreloads() { }; }; + const enum PreloadState { + Ok, + Error + } + + type PreloadResult = { state: PreloadState.Ok } | { state: PreloadState.Error, error: string }; + /** * Map of preload resource URIs to promises that resolve one the resource * loads or errors. */ - const preloadPromises = new Map>(); + const preloadPromises = new Map>(); const queuedOuputActions = new Map>(); /** @@ -361,7 +395,7 @@ function webviewPreloads() { switch (event.data.type) { case 'html': enqueueOutputAction(event.data, async data => { - const preloadErrs = await Promise.all(data.requiredPreloads.map(p => preloadPromises.get(p.uri))); + const preloadResults = await Promise.all(data.requiredPreloads.map(p => preloadPromises.get(p.uri))); if (!queuedOuputActions.has(data.outputId)) { // output was cleared while loading return; } @@ -389,7 +423,8 @@ function webviewPreloads() { outputNode.style.top = data.top + 'px'; outputNode.style.left = data.left + 'px'; outputNode.style.width = 'calc(100% - ' + data.left + 'px)'; - outputNode.style.minHeight = '32px'; + // outputNode.style.minHeight = '32px'; + outputNode.style.padding = '0px'; outputNode.id = outputId; addMouseoverListeners(outputNode, outputId); @@ -398,13 +433,13 @@ function webviewPreloads() { outputNode.innerHTML = content.htmlContent; cellOutputContainer.appendChild(outputNode); domEval(outputNode); - } else if (preloadErrs.some(e => !!e)) { + } else if (preloadResults.some(e => e?.state === PreloadState.Error)) { outputNode.innerText = `Error loading preloads:`; const errList = document.createElement('ul'); - for (const err of preloadErrs) { - if (err) { + for (const result of preloadResults) { + if (result?.state === PreloadState.Error) { const item = document.createElement('li'); - item.innerText = err; + item.innerText = result.error; errList.appendChild(item); } } @@ -498,11 +533,20 @@ function webviewPreloads() { case 'preload': const resources = event.data.resources; const globals = event.data.type === 'preload' ? { acquireVsCodeApi } : {}; + let queue: Promise = Promise.resolve({ state: PreloadState.Ok }); for (const { uri, originalUri } of resources) { - preloadPromises.set(uri, runScript(uri, originalUri, globals).catch(err => { - console.error(err); - return err.message || String(err); + // create the promise so that the scripts download in parallel, but + // only invoke them in series within the queue + const promise = runScript(uri, originalUri, globals); + queue = queue.then(() => promise.then(fn => { + const result = fn(); + if (result.state === PreloadState.Error) { + console.error(result.error); + } + + return result; })); + preloadPromises.set(uri, queue); } break; case 'focus-output': diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts index 6d42d6707..3d4b3b4e5 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -12,8 +12,8 @@ import { IPosition } from 'vs/editor/common/core/position'; import * as editorCommon from 'vs/editor/common/editorCommon'; import * as model from 'vs/editor/common/model'; import { SearchParams } from 'vs/editor/common/model/textModelSearch'; -import { CELL_STATUSBAR_HEIGHT, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellEditState, CellFocusMode, CursorAtBoundary, CellViewModelStateChangeEvent, IEditableCellViewModel, INotebookCellDecorationOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CELL_STATUSBAR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CellEditState, CellFocusMode, CursorAtBoundary, CellViewModelStateChangeEvent, IEditableCellViewModel, INotebookCellDecorationOptions, getEditorTopPadding } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind, NotebookCellMetadata, NotebookDocumentMetadata, INotebookSearchOptions, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -131,7 +131,7 @@ export abstract class BaseCellViewModel extends Disposable { })); } - protected getEditorStatusbarHeight() { + getEditorStatusbarHeight() { const showCellStatusBar = this._configurationService.getValue(ShowCellStatusBarKey); return showCellStatusBar ? CELL_STATUSBAR_HEIGHT : 0; } @@ -341,7 +341,7 @@ export abstract class BaseCellViewModel extends Disposable { return 0; } - return this._textEditor.getTopForLineNumber(line) + EDITOR_TOP_PADDING; + return this._textEditor.getTopForLineNumber(line) + getEditorTopPadding(); } getPositionScrollTopOffset(line: number, column: number): number { @@ -349,7 +349,35 @@ export abstract class BaseCellViewModel extends Disposable { return 0; } - return this._textEditor.getTopForPosition(line, column) + EDITOR_TOP_PADDING; + return this._textEditor.getTopForPosition(line, column) + getEditorTopPadding(); + } + + cursorAtBeginEnd(): boolean { + if (!this._textEditor) { + return false; + } + + if (!this.textModel) { + return false; + } + + // only validate primary cursor + const selection = this._textEditor.getSelection(); + + // only validate empty cursor + if (!selection || !selection.isEmpty()) { + return false; + } + + if (selection.startLineNumber === 1 && selection.startColumn === 1) { + return true; + } + + if (selection.startLineNumber === this._textModel?.getLineCount() && selection.startColumn === this._textModel?.getLineMaxColumn(selection.startLineNumber)) { + return true; + } + + return false; } cursorAtBoundary(): CursorAtBoundary { @@ -425,8 +453,8 @@ export abstract class BaseCellViewModel extends Disposable { const editable = this.metadata?.editable ?? documentMetadata.cellEditable; - const runnable = this.metadata?.runnable ?? - documentMetadata.cellRunnable; + const runnable = (this.metadata?.runnable ?? + documentMetadata.cellRunnable) && !!documentMetadata.trusted; const hasExecutionOrder = this.metadata?.hasExecutionOrder ?? documentMetadata.cellHasExecutionOrder; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts index 9e57b51a6..bbc8ee237 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -9,8 +9,8 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import * as model from 'vs/editor/common/model'; import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { BOTTOM_CELL_TOOLBAR_GAP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CELL_MARGIN, CELL_RUN_GUTTER, CELL_TOP_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT, EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellEditState, CellFindMatch, CodeCellLayoutChangeEvent, CodeCellLayoutInfo, CodeCellLayoutState, ICellViewModel, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { BOTTOM_CELL_TOOLBAR_GAP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CELL_MARGIN, CELL_RUN_GUTTER, CELL_TOP_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT, EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CellEditState, CellFindMatch, CodeCellLayoutChangeEvent, CodeCellLayoutInfo, CodeCellLayoutState, getEditorTopPadding, ICellViewModel, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { CellKind, INotebookSearchOptions, NotebookCellOutputsSplice } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -86,9 +86,11 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod this._layoutInfo = { fontInfo: initialNotebookLayoutInfo?.fontInfo || null, editorHeight: 0, - editorWidth: initialNotebookLayoutInfo ? this.computeEditorWidth(initialNotebookLayoutInfo!.width) : 0, + editorWidth: initialNotebookLayoutInfo ? this.computeEditorWidth(initialNotebookLayoutInfo.width) : 0, outputContainerOffset: 0, outputTotalHeight: 0, + outputShowMoreContainerHeight: 0, + outputShowMoreContainerOffset: 0, totalHeight: 0, indicatorHeight: 0, bottomToolbarOffset: 0, @@ -103,6 +105,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod layoutChange(state: CodeCellLayoutChangeEvent) { // recompute this._ensureOutputsTop(); + const outputShowMoreContainerHeight = state.outputShowMoreContainerHeight ? state.outputShowMoreContainerHeight : this._layoutInfo.outputShowMoreContainerHeight; let outputTotalHeight = this.metadata?.outputCollapsed ? COLLAPSED_INDICATOR_HEIGHT : this._outputsTop!.getTotalValue(); if (!this.metadata?.inputCollapsed) { @@ -117,17 +120,18 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod } else if (state.editorHeight || this._layoutInfo.layoutState === CodeCellLayoutState.Measured) { // Editor has been measured editorHeight = this._editorHeight; - totalHeight = this.computeTotalHeight(this._editorHeight, outputTotalHeight); + totalHeight = this.computeTotalHeight(this._editorHeight, outputTotalHeight, outputShowMoreContainerHeight); newState = CodeCellLayoutState.Measured; } else { editorHeight = this.estimateEditorHeight(state.font?.lineHeight); - totalHeight = this.computeTotalHeight(editorHeight, outputTotalHeight); + totalHeight = this.computeTotalHeight(editorHeight, outputTotalHeight, outputShowMoreContainerHeight); newState = CodeCellLayoutState.Estimated; } const statusbarHeight = this.getEditorStatusbarHeight(); - const indicatorHeight = editorHeight + statusbarHeight + outputTotalHeight; + const indicatorHeight = editorHeight + statusbarHeight + outputTotalHeight + outputShowMoreContainerHeight; const outputContainerOffset = EDITOR_TOOLBAR_HEIGHT + CELL_TOP_MARGIN + editorHeight + statusbarHeight; + const outputShowMoreContainerOffset = totalHeight - BOTTOM_CELL_TOOLBAR_GAP - BOTTOM_CELL_TOOLBAR_HEIGHT / 2 - outputShowMoreContainerHeight; const bottomToolbarOffset = totalHeight - BOTTOM_CELL_TOOLBAR_GAP - BOTTOM_CELL_TOOLBAR_HEIGHT / 2; const editorWidth = state.outerWidth !== undefined ? this.computeEditorWidth(state.outerWidth) : this._layoutInfo?.editorWidth; @@ -137,6 +141,8 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod editorWidth, outputContainerOffset, outputTotalHeight, + outputShowMoreContainerHeight, + outputShowMoreContainerOffset, totalHeight, indicatorHeight, bottomToolbarOffset, @@ -144,9 +150,10 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod }; } else { outputTotalHeight = this.metadata?.inputCollapsed && this.metadata.outputCollapsed ? 0 : outputTotalHeight; - const indicatorHeight = COLLAPSED_INDICATOR_HEIGHT + outputTotalHeight; + const indicatorHeight = COLLAPSED_INDICATOR_HEIGHT + outputTotalHeight + outputShowMoreContainerHeight; const outputContainerOffset = CELL_TOP_MARGIN + COLLAPSED_INDICATOR_HEIGHT; - const totalHeight = CELL_TOP_MARGIN + COLLAPSED_INDICATOR_HEIGHT + CELL_BOTTOM_MARGIN + BOTTOM_CELL_TOOLBAR_GAP + outputTotalHeight; + const totalHeight = CELL_TOP_MARGIN + COLLAPSED_INDICATOR_HEIGHT + CELL_BOTTOM_MARGIN + BOTTOM_CELL_TOOLBAR_GAP + outputTotalHeight + outputShowMoreContainerHeight; + const outputShowMoreContainerOffset = totalHeight - BOTTOM_CELL_TOOLBAR_GAP - BOTTOM_CELL_TOOLBAR_HEIGHT / 2 - outputShowMoreContainerHeight; const bottomToolbarOffset = totalHeight - BOTTOM_CELL_TOOLBAR_GAP - BOTTOM_CELL_TOOLBAR_HEIGHT / 2; const editorWidth = state.outerWidth !== undefined ? this.computeEditorWidth(state.outerWidth) : this._layoutInfo?.editorWidth; @@ -156,6 +163,8 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod editorWidth, outputContainerOffset, outputTotalHeight, + outputShowMoreContainerHeight, + outputShowMoreContainerOffset, totalHeight, indicatorHeight, bottomToolbarOffset, @@ -183,6 +192,8 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod editorWidth: this._layoutInfo.editorWidth, outputContainerOffset: this._layoutInfo.outputContainerOffset, outputTotalHeight: this._layoutInfo.outputTotalHeight, + outputShowMoreContainerHeight: this._layoutInfo.outputShowMoreContainerHeight, + outputShowMoreContainerOffset: this._layoutInfo.outputShowMoreContainerOffset, totalHeight: totalHeight, indicatorHeight: this._layoutInfo.indicatorHeight, bottomToolbarOffset: this._layoutInfo.bottomToolbarOffset, @@ -203,18 +214,18 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod getHeight(lineHeight: number) { if (this._layoutInfo.layoutState === CodeCellLayoutState.Uninitialized) { const editorHeight = this.estimateEditorHeight(lineHeight); - return this.computeTotalHeight(editorHeight, 0); + return this.computeTotalHeight(editorHeight, 0, 0); } else { return this._layoutInfo.totalHeight; } } private estimateEditorHeight(lineHeight: number | undefined = 20): number { - return this.lineCount * lineHeight + EDITOR_TOP_PADDING + EDITOR_BOTTOM_PADDING; + return this.lineCount * lineHeight + getEditorTopPadding() + EDITOR_BOTTOM_PADDING; } - private computeTotalHeight(editorHeight: number, outputsTotalHeight: number): number { - return EDITOR_TOOLBAR_HEIGHT + CELL_TOP_MARGIN + editorHeight + this.getEditorStatusbarHeight() + outputsTotalHeight + BOTTOM_CELL_TOOLBAR_GAP + CELL_BOTTOM_MARGIN; + private computeTotalHeight(editorHeight: number, outputsTotalHeight: number, outputShowMoreContainerHeight: number): number { + return EDITOR_TOOLBAR_HEIGHT + CELL_TOP_MARGIN + editorHeight + this.getEditorStatusbarHeight() + outputsTotalHeight + outputShowMoreContainerHeight + BOTTOM_CELL_TOOLBAR_GAP + CELL_BOTTOM_MARGIN; } /** @@ -240,6 +251,10 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod this.editState = CellEditState.Preview; } + updateOutputShowMoreContainerHeight(height: number) { + this.layoutChange({ outputShowMoreContainerHeight: height }); + } + updateOutputHeight(index: number, height: number) { if (index >= this._outputCollection.length) { throw new Error('Output index out of range!'); diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index 4ea7ba52c..d1843f21d 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -180,6 +180,10 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD return this._notebook.metadata; } + get runnable() { + return !!this._notebook.metadata?.runnable && !!this._notebook.metadata?.trusted; + } + private readonly _onDidChangeViewCells = this._register(new Emitter()); get onDidChangeViewCells(): Event { return this._onDidChangeViewCells.event; } @@ -348,7 +352,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD }); })); - this._viewCells = this._notebook!.cells.map(cell => { + this._viewCells = this._notebook.cells.map(cell => { return createCellViewModel(this._instantiationService, this, cell); }); diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts index d46143ecb..709f0a4f7 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -12,6 +12,7 @@ import { Range } from 'vs/editor/common/core/range'; import { Disposable } from 'vs/base/common/lifecycle'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { hash } from 'vs/base/common/hash'; +import { createTextBuffer } from 'vs/editor/common/model/textModel'; export class NotebookCellTextModel extends Disposable implements ICell { private _onDidChangeOutputs = new Emitter(); @@ -171,6 +172,9 @@ export class NotebookCellTextModel extends Disposable implements ICell { } dispose() { + // Manually release reference to previous text buffer to avoid large leaks + // in case someone leaks a CellTextModel reference + this._textBuffer = createTextBuffer('', model.DefaultEndOfLine.LF); super.dispose(); } } diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index 36c08e95a..f932f185e 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -337,14 +337,14 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel this._replaceCells(edit.index, edit.count, edit.cells, synchronous, computeUndoRedo); break; case CellEditType.Output: - //TODO@joh,@rebornix no event, no undo stop (?) + //TODO@jrieken,@rebornix no event, no undo stop (?) this._assertIndex(edit.index); const cell = this._cells[edit.index]; this._spliceNotebookCellOutputs2(cell.handle, edit.outputs, computeUndoRedo); break; case CellEditType.OutputsSplice: { - //TODO@joh,@rebornix no event, no undo stop (?) + //TODO@jrieken,@rebornix no event, no undo stop (?) this._assertIndex(edit.index); const cell = this._cells[edit.index]; this._spliceNotebookCellOutputs(cell.handle, edit.splices, computeUndoRedo); @@ -483,6 +483,17 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel } } + private _isDocumentMetadataChangeTransient(a: NotebookDocumentMetadata, b: NotebookDocumentMetadata) { + const keys = new Set([...Object.keys(a || {}), ...Object.keys(b || {})]); + for (let key of keys) { + if (key !== 'trusted') { + return true; + } + } + + return false; + } + private _updateNotebookMetadata(metadata: NotebookDocumentMetadata, computeUndoRedo: boolean) { const oldMetadata = this.metadata; this.metadata = metadata; @@ -504,7 +515,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel }(), undefined, undefined); } - this._eventEmitter.emit({ kind: NotebookCellsChangeType.ChangeDocumentMetadata, metadata: this.metadata, transient: false }, true); + this._eventEmitter.emit({ kind: NotebookCellsChangeType.ChangeDocumentMetadata, metadata: this.metadata, transient: this._isDocumentMetadataChangeTransient(oldMetadata, metadata) }, true); } private _insertNewCell(index: number, cells: NotebookCellTextModel[], synchronous: boolean, endSelections?: number[]): void { diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 4b3bbab23..941f4a479 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -58,6 +58,7 @@ export const ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER = [ ]; export const BUILTIN_RENDERER_ID = '_builtin'; +export const RENDERER_NOT_AVAILABLE = '_notAvailable'; export enum NotebookRunState { Running = 1, @@ -72,7 +73,8 @@ export const notebookDocumentMetadataDefaults: Required output.outputKind === CellOutputKind.Rich ? ({ ...output, outputId: id }) : output; @@ -548,6 +539,19 @@ export namespace CellUri { } } +export function mimeTypeIsAlwaysSecure(mimeType: string) { + if ([ + 'application/json', + 'text/markdown', + 'image/png', + 'text/plain' + ].indexOf(mimeType) > -1) { + return true; + } + + return false; +} + export function mimeTypeSupportedByCore(mimeType: string) { if ([ 'application/json', @@ -681,6 +685,7 @@ export interface ICellEditorViewState { } export const NOTEBOOK_EDITOR_CURSOR_BOUNDARY = new RawContextKey<'none' | 'top' | 'bottom' | 'both'>('notebookEditorCursorAtBoundary', 'none'); +export const NOTEBOOK_EDITOR_CURSOR_BEGIN_END = new RawContextKey('notebookEditorCursorAtEditorBeginEnd', false); export interface INotebookEditorModel extends IEditorModel { @@ -743,7 +748,6 @@ export interface IEditor extends editorCommon.ICompositeCodeEditor { textModel?: NotebookTextModel; getId(): string; hasFocus(): boolean; - hasModel(): boolean; } export enum NotebookEditorPriority { @@ -865,6 +869,7 @@ export interface INotebookCellStatusBarEntry { readonly command: string | Command | undefined; readonly accessibilityInformation?: IAccessibilityInformation; readonly visible: boolean; + readonly opacity?: string; } export const DisplayOrderKey = 'notebook.displayOrder'; @@ -882,3 +887,4 @@ export interface INotebookDecorationRenderOptions { borderColor?: string | ThemeColor; top?: editorCommon.IContentDecorationRenderOptions; } + diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index fd79380d7..1ad9b60a8 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -17,6 +17,8 @@ import { Schemas } from 'vs/base/common/network'; import { IFileStatWithMetadata, IFileService } from 'vs/platform/files/common/files'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { ILabelService } from 'vs/platform/label/common/label'; +import { ILogService } from 'vs/platform/log/common/log'; +import { TaskSequentializer } from 'vs/base/common/async'; export interface INotebookLoadOptions { @@ -40,6 +42,7 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM private readonly _name: string; private readonly _workingCopyResource: URI; + private readonly saveSequentializer = new TaskSequentializer(); private _dirty = false; @@ -51,6 +54,7 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM @IBackupFileService private readonly _backupFileService: IBackupFileService, @IFileService private readonly _fileService: IFileService, @INotificationService private readonly _notificationService: INotificationService, + @ILogService private readonly _logService: ILogService, @ILabelService labelService: ILabelService, ) { super(); @@ -197,8 +201,14 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM } private async _assertStat() { + this._logService.debug('[notebook editor model] start assert stat'); const stats = await this._resolveStats(this.resource); if (this._lastResolvedFileStat && stats && stats.mtime > this._lastResolvedFileStat.mtime) { + this._logService.debug(`[notebook editor model] noteboook file on disk is newer: +LastResolvedStat: ${this._lastResolvedFileStat ? JSON.stringify(this._lastResolvedFileStat) : undefined}. +Current stat: ${JSON.stringify(stats)} +`); + this._lastResolvedFileStat = stats; return new Promise<'overwrite' | 'revert' | 'none'>(resolve => { const handle = this._notificationService.prompt( Severity.Info, @@ -221,31 +231,57 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM resolve('none'); }); }); + } else if (!this._lastResolvedFileStat && stats) { + // finally get a stats + this._lastResolvedFileStat = stats; } return 'overwrite'; } async save(): Promise { - const result = await this._assertStat(); - if (result === 'none') { - return false; + let versionId = this._notebook.versionId; + this._logService.debug(`[notebook editor model] save(${versionId}) - enter with versionId ${versionId}`, this.resource.toString(true)); + + if (this.saveSequentializer.hasPending(versionId)) { + this._logService.debug(`[notebook editor model] save(${versionId}) - exit - found a pending save for versionId ${versionId}`, this.resource.toString(true)); + return this.saveSequentializer.pending.then(() => { + return true; + }); } - if (result === 'revert') { - await this.revert(); + if (this.saveSequentializer.hasPending()) { + return this.saveSequentializer.setNext(async () => { + await this.save(); + }).then(() => { + return true; + }); + } + + return this.saveSequentializer.setPending(versionId, (async () => { + const result = await this._assertStat(); + if (result === 'none') { + return; + } + + if (result === 'revert') { + await this.revert(); + return; + } + + const tokenSource = new CancellationTokenSource(); + await this._notebookService.save(this.notebook.viewType, this.notebook.uri, tokenSource.token); + this._logService.debug(`[notebook editor model] save(${versionId}) - document saved saved, start updating file stats`, this.resource.toString(true)); + const newStats = await this._resolveStats(this.resource); + this._lastResolvedFileStat = newStats; + this.setDirty(false); + })()).then(() => { return true; - } - - const tokenSource = new CancellationTokenSource(); - await this._notebookService.save(this.notebook.viewType, this.notebook.uri, tokenSource.token); - const newStats = await this._resolveStats(this.resource); - this._lastResolvedFileStat = newStats; - this.setDirty(false); - return true; + }); } async saveAs(targetResource: URI): Promise { + this._logService.debug(`[notebook editor model] saveAs - enter`, this.resource.toString(true)); const result = await this._assertStat(); if (result === 'none') { @@ -259,6 +295,7 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM const tokenSource = new CancellationTokenSource(); await this._notebookService.saveAs(this.notebook.viewType, this.notebook.uri, targetResource, tokenSource.token); + this._logService.debug(`[notebook editor model] saveAs - document saved, start updating file stats`, this.resource.toString(true)); const newStats = await this._resolveStats(this.resource); this._lastResolvedFileStat = newStats; this.setDirty(false); @@ -271,7 +308,9 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM } try { + this._logService.debug(`[notebook editor model] _resolveStats`, this.resource.toString(true)); const newStats = await this._fileService.resolve(this.resource, { resolveMetadata: true }); + this._logService.debug(`[notebook editor model] _resolveStats - latest file stats: ${JSON.stringify(newStats)}`, this.resource.toString(true)); return newStats; } catch (e) { return undefined; diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index 51a80bc3c..06f770817 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -10,7 +10,7 @@ import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.pr import { Event } from 'vs/base/common/event'; import { INotebookTextModel, INotebookRendererInfo, - IEditor, ICellEditOperation, NotebookCellOutputsSplice, INotebookKernelProvider, INotebookKernelInfo2, TransientMetadata, NotebookDataDto, TransientOptions, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter + IEditor, INotebookKernelProvider, INotebookKernelInfo2, TransientMetadata, NotebookDataDto, TransientOptions, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter, IOrderedMimeType, ITransformedDisplayOutputDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -49,10 +49,10 @@ export interface INotebookService { onDidChangeNotebookActiveKernel: Event<{ uri: URI, providerHandle: number | undefined, kernelId: string | undefined }>; registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): IDisposable; - transformEditsOutputs(textModel: NotebookTextModel, edits: ICellEditOperation[]): void; - transformSpliceOutputs(textModel: NotebookTextModel, splices: NotebookCellOutputsSplice[]): void; + getMimeTypeInfo(textModel: NotebookTextModel, output: ITransformedDisplayOutputDto): readonly IOrderedMimeType[]; + registerNotebookKernelProvider(provider: INotebookKernelProvider): IDisposable; - getContributedNotebookKernels2(viewType: string, resource: URI, token: CancellationToken): Promise; + getContributedNotebookKernels(viewType: string, resource: URI, token: CancellationToken): Promise; getContributedNotebookOutputRenderers(id: string): NotebookOutputRendererInfo | undefined; getRendererInfo(id: string): INotebookRendererInfo | undefined; diff --git a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts index b8d970e6e..1ec883fe1 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts @@ -156,7 +156,7 @@ suite('NotebookViewModel', () => { ['var e = 5;', 'javascript', CellKind.Code, [], { editable: false, runnable: false }], ], (editor, viewModel) => { - viewModel.notebookDocument.metadata = { editable: true, runnable: true, cellRunnable: true, cellEditable: true, cellHasExecutionOrder: true }; + viewModel.notebookDocument.metadata = { editable: true, runnable: true, cellRunnable: true, cellEditable: true, cellHasExecutionOrder: true, trusted: true }; const defaults = { hasExecutionOrder: true }; @@ -190,7 +190,7 @@ suite('NotebookViewModel', () => { ...defaults }); - viewModel.notebookDocument.metadata = { editable: true, runnable: true, cellRunnable: false, cellEditable: true, cellHasExecutionOrder: true }; + viewModel.notebookDocument.metadata = { editable: true, runnable: true, cellRunnable: false, cellEditable: true, cellHasExecutionOrder: true, trusted: true }; assert.deepEqual(viewModel.viewCells[0].getEvaluatedMetadata(viewModel.metadata), { editable: true, @@ -222,7 +222,7 @@ suite('NotebookViewModel', () => { ...defaults }); - viewModel.notebookDocument.metadata = { editable: true, runnable: true, cellRunnable: false, cellEditable: false, cellHasExecutionOrder: true }; + viewModel.notebookDocument.metadata = { editable: true, runnable: true, cellRunnable: false, cellEditable: false, cellHasExecutionOrder: true, trusted: true }; assert.deepEqual(viewModel.viewCells[0].getEvaluatedMetadata(viewModel.metadata), { editable: false, diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index 1679604a8..855ffe9fc 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -66,6 +66,9 @@ export class TestNotebookEditor implements INotebookEditor { constructor( ) { } + async beginComputeContributedKernels(): Promise { + return []; + } setEditorDecorations(key: string, range: ICellRange): void { // throw new Error('Method not implemented.'); } diff --git a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts index 3dc1cf7fb..01b5f2a9e 100644 --- a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts +++ b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts @@ -11,13 +11,17 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { OutlineConfigKeys, OutlineViewId } from 'vs/editor/contrib/documentSymbols/outline'; import { VIEW_CONTAINER } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { Codicon } from 'vs/base/common/codicons'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; export const PANEL_ID = 'panel.view.outline'; +const outlineViewIcon = registerIcon('outline-view-icon', Codicon.symbolClass, localize('outlineViewIcon', 'View icon of the outline view.')); + const _outlineDesc = { id: OutlineViewId, name: localize('name', "Outline"), - containerIcon: 'codicon-symbol-class', + containerIcon: outlineViewIcon, ctorDescriptor: new SyncDescriptor(OutlinePane), canToggleVisibility: true, canMoveView: true, diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts index 3f981c8e4..ab48b16b7 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -29,7 +29,7 @@ import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor' import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { WorkbenchDataTree } from 'vs/platform/list/browser/listService'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; @@ -48,6 +48,7 @@ import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { Codicon } from 'vs/base/common/codicons'; class RequestState { @@ -211,7 +212,7 @@ class OutlineViewState { followCursor: this.followCursor, sortBy: this.sortBy, filterOnType: this.filterOnType, - }), StorageScope.WORKSPACE); + }), StorageScope.WORKSPACE, StorageTarget.USER); } restore(storageService: IStorageService): void { @@ -399,7 +400,7 @@ export class OutlinePane extends ViewPane { getActions(): IAction[] { return [ - new CollapseAction(() => this._tree, true, 'explorer-action codicon-collapse-all') + new CollapseAction(() => this._tree, true, 'explorer-action ' + Codicon.collapseAll.classNames) ]; } @@ -425,7 +426,7 @@ export class OutlinePane extends ViewPane { private _onDidChangeUserState(e: { followCursor?: boolean, sortBy?: boolean, filterOnType?: boolean }) { this._outlineViewState.persist(this._storageService); if (e.followCursor) { - // todo@joh update immediately + // todo@jrieken update immediately } if (e.sortBy) { this._treeComparator.type = this._outlineViewState.sortBy; diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index 7b0283691..0b6157459 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -33,6 +33,7 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la import { ContextKeyEqualsExpr, ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ToggleViewAction } from 'vs/workbench/browser/actions/layoutActions'; import { Codicon } from 'vs/base/common/codicons'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { CATEGORIES } from 'vs/workbench/common/actions'; // Register Service @@ -60,10 +61,13 @@ const toggleOutputActionKeybindings = { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_H) // On Ubuntu Ctrl+Shift+U is taken by some global OS command } }; + +const outputViewIcon = registerIcon('output-view-icon', Codicon.output, nls.localize('outputViewIcon', 'View icon of the output view.')); + const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: OUTPUT_VIEW_ID, name: nls.localize('output', "Output"), - icon: Codicon.output.classNames, + icon: outputViewIcon, order: 1, ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [OUTPUT_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), storageId: OUTPUT_VIEW_ID, @@ -74,7 +78,7 @@ const VIEW_CONTAINER: ViewContainer = Registry.as(ViewC Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ id: OUTPUT_VIEW_ID, name: nls.localize('output', "Output"), - containerIcon: Codicon.output.classNames, + containerIcon: outputViewIcon, canMoveView: true, canToggleVisibility: false, ctorDescriptor: new SyncDescriptor(OutputViewPane), @@ -139,7 +143,7 @@ registerAction2(class extends Action2 { id: MenuId.EditorContext, when: CONTEXT_IN_OUTPUT }], - icon: { id: 'codicon/clear-all' } + icon: Codicon.clearAll }); } async run(accessor: ServicesAccessor): Promise { @@ -163,10 +167,10 @@ registerAction2(class extends Action2 { group: 'navigation', order: 3, }, - icon: { id: 'codicon/unlock' }, + icon: Codicon.unlock, toggled: { condition: CONTEXT_OUTPUT_SCROLL_LOCK, - icon: { id: 'codicon/lock' }, + icon: Codicon.lock, tooltip: { value: nls.localize('outputScrollOn', "Turn Auto Scrolling On"), original: 'Turn Auto Scrolling On' } } }); @@ -190,7 +194,7 @@ registerAction2(class extends Action2 { id: MenuId.CommandPalette, when: CONTEXT_ACTIVE_LOG_OUTPUT, }], - icon: { id: 'codicon/go-to-file' }, + icon: Codicon.goToFile, precondition: CONTEXT_ACTIVE_LOG_OUTPUT }); } @@ -200,7 +204,7 @@ registerAction2(class extends Action2 { const instantiationService = accessor.get(IInstantiationService); const logFileOutputChannelDescriptor = this.getLogFileOutputChannelDescriptor(outputService); if (logFileOutputChannelDescriptor) { - await editorService.openEditor(instantiationService.createInstance(LogViewerInput, logFileOutputChannelDescriptor)); + await editorService.openEditor(instantiationService.createInstance(LogViewerInput, logFileOutputChannelDescriptor), { pinned: true }); } } private getLogFileOutputChannelDescriptor(outputService: IOutputService): IFileOutputChannelDescriptor | null { @@ -298,7 +302,7 @@ registerAction2(class extends Action2 { const entry = await quickInputService.pick(entries, { placeHolder: nls.localize('selectlogFile', "Select Log file") }); if (entry) { assertIsDefined(entry.channel.file); - await editorService.openEditor(instantiationService.createInstance(LogViewerInput, (entry.channel as IFileOutputChannelDescriptor))); + await editorService.openEditor(instantiationService.createInstance(LogViewerInput, (entry.channel as IFileOutputChannelDescriptor)), { pinned: true }); } } }); diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts index f1f111052..062b00c08 100644 --- a/src/vs/workbench/contrib/output/browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -7,7 +7,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +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_SCHEME, LOG_MIME, OUTPUT_MIME } from 'vs/workbench/contrib/output/common/output'; import { IOutputChannelDescriptor, Extensions, IOutputChannelRegistry } from 'vs/workbench/services/output/common/output'; @@ -16,7 +16,7 @@ import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/s import { ITextModel } from 'vs/editor/common/model'; import { ILogService } from 'vs/platform/log/common/log'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { IOutputChannelModel, IOutputChannelModelService } from 'vs/workbench/services/output/common/outputChannelModel'; +import { IOutputChannelModel, IOutputChannelModelService } from 'vs/workbench/contrib/output/common/outputChannelModel'; import { IViewsService } from 'vs/workbench/common/views'; import { OutputViewPane } from 'vs/workbench/contrib/output/browser/outputView'; @@ -177,7 +177,7 @@ export class OutputService extends Disposable implements IOutputService, ITextMo this.activeChannel = channel; if (this.activeChannel) { - this.storageService.store(OUTPUT_ACTIVE_CHANNEL_KEY, this.activeChannel.id, StorageScope.WORKSPACE); + this.storageService.store(OUTPUT_ACTIVE_CHANNEL_KEY, this.activeChannel.id, StorageScope.WORKSPACE, StorageTarget.USER); } else { this.storageService.remove(OUTPUT_ACTIVE_CHANNEL_KEY, StorageScope.WORKSPACE); } diff --git a/src/vs/workbench/contrib/output/browser/outputView.ts b/src/vs/workbench/contrib/output/browser/outputView.ts index 7e86eb96f..a01c54ae7 100644 --- a/src/vs/workbench/contrib/output/browser/outputView.ts +++ b/src/vs/workbench/contrib/output/browser/outputView.ts @@ -11,7 +11,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { EditorInput, EditorOptions, IEditorOpenContext } from 'vs/workbench/common/editor'; import { AbstractTextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; @@ -164,11 +163,6 @@ export class OutputViewPane extends ViewPane { export class OutputEditor extends AbstractTextResourceEditor { - // Override the instantiation service to use to be the scoped one - private scopedInstantiationService: IInstantiationService; - protected get instantiationService(): IInstantiationService { return this.scopedInstantiationService; } - protected set instantiationService(instantiationService: IInstantiationService) { } - constructor( @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, @@ -177,15 +171,10 @@ export class OutputEditor extends AbstractTextResourceEditor { @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @IThemeService themeService: IThemeService, @IOutputService private readonly outputService: IOutputService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService ) { super(OUTPUT_VIEW_ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, editorService); - - // Initially, the scoped instantiation service is the global - // one until the editor is created later on - this.scopedInstantiationService = instantiationService; } getId(): string { @@ -258,13 +247,12 @@ export class OutputEditor extends AbstractTextResourceEditor { parent.setAttribute('role', 'document'); - // First create the scoped instantiation service and only then construct the editor using the scoped service - const scopedContextKeyService = this._register(this.contextKeyService.createScoped(parent)); - this.scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); - super.createEditor(parent); - CONTEXT_IN_OUTPUT.bindTo(scopedContextKeyService).set(true); + const scopedContextKeyService = this.scopedContextKeyService; + if (scopedContextKeyService) { + CONTEXT_IN_OUTPUT.bindTo(scopedContextKeyService).set(true); + } } } diff --git a/src/vs/workbench/services/output/common/outputChannelModel.ts b/src/vs/workbench/contrib/output/common/outputChannelModel.ts similarity index 99% rename from src/vs/workbench/services/output/common/outputChannelModel.ts rename to src/vs/workbench/contrib/output/common/outputChannelModel.ts index 3fa844819..364c4a8b1 100644 --- a/src/vs/workbench/services/output/common/outputChannelModel.ts +++ b/src/vs/workbench/contrib/output/common/outputChannelModel.ts @@ -37,7 +37,7 @@ export interface IOutputChannelModelService { } -export abstract class AsbtractOutputChannelModelService { +export abstract class AbstractOutputChannelModelService { constructor( @IInstantiationService protected readonly instantiationService: IInstantiationService diff --git a/src/vs/workbench/services/output/common/outputChannelModelService.ts b/src/vs/workbench/contrib/output/common/outputChannelModelService.ts similarity index 75% rename from src/vs/workbench/services/output/common/outputChannelModelService.ts rename to src/vs/workbench/contrib/output/common/outputChannelModelService.ts index 4d9c36ad0..794183199 100644 --- a/src/vs/workbench/services/output/common/outputChannelModelService.ts +++ b/src/vs/workbench/contrib/output/common/outputChannelModelService.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IOutputChannelModelService, AsbtractOutputChannelModelService } from 'vs/workbench/services/output/common/outputChannelModel'; +import { IOutputChannelModelService, AbstractOutputChannelModelService } from 'vs/workbench/contrib/output/common/outputChannelModel'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -export class OutputChannelModelService extends AsbtractOutputChannelModelService implements IOutputChannelModelService { +export class OutputChannelModelService extends AbstractOutputChannelModelService implements IOutputChannelModelService { declare readonly _serviceBrand: undefined; } diff --git a/src/vs/workbench/contrib/output/common/outputLinkComputer.ts b/src/vs/workbench/contrib/output/common/outputLinkComputer.ts index e532dbc50..d9297827b 100644 --- a/src/vs/workbench/contrib/output/common/outputLinkComputer.ts +++ b/src/vs/workbench/contrib/output/common/outputLinkComputer.ts @@ -56,7 +56,7 @@ export class OutputLinkComputer { } const links: ILink[] = []; - const lines = model.getValue().split(/\r\n|\r|\n/); + const lines = strings.splitLines(model.getValue()); // For each workspace root patterns for (const [folderUri, folderPatterns] of this.patterns) { diff --git a/src/vs/workbench/services/output/electron-browser/outputChannelModelService.ts b/src/vs/workbench/contrib/output/electron-browser/outputChannelModelService.ts similarity index 97% rename from src/vs/workbench/services/output/electron-browser/outputChannelModelService.ts rename to src/vs/workbench/contrib/output/electron-browser/outputChannelModelService.ts index fdc63aae9..6613887f7 100644 --- a/src/vs/workbench/services/output/electron-browser/outputChannelModelService.ts +++ b/src/vs/workbench/contrib/output/electron-browser/outputChannelModelService.ts @@ -14,7 +14,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { Disposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; -import { IOutputChannelModel, AbstractFileOutputChannelModel, IOutputChannelModelService, AsbtractOutputChannelModelService, BufferredOutputChannel } from 'vs/workbench/services/output/common/outputChannelModel'; +import { IOutputChannelModel, AbstractFileOutputChannelModel, IOutputChannelModelService, AbstractOutputChannelModelService, BufferredOutputChannel } from 'vs/workbench/contrib/output/common/outputChannelModel'; import { OutputAppender } from 'vs/workbench/services/output/node/outputAppender'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { toLocalISOString } from 'vs/base/common/date'; @@ -197,7 +197,7 @@ class DelegatedOutputChannelModel extends Disposable implements IOutputChannelMo } -export class OutputChannelModelService extends AsbtractOutputChannelModelService implements IOutputChannelModelService { +export class OutputChannelModelService extends AbstractOutputChannelModelService implements IOutputChannelModelService { declare readonly _serviceBrand: undefined; diff --git a/src/vs/workbench/contrib/performance/browser/performance.contribution.ts b/src/vs/workbench/contrib/performance/browser/performance.contribution.ts index a1f68bc0f..2daa52259 100644 --- a/src/vs/workbench/contrib/performance/browser/performance.contribution.ts +++ b/src/vs/workbench/contrib/performance/browser/performance.contribution.ts @@ -51,6 +51,6 @@ registerAction2(class extends Action2 { run(accessor: ServicesAccessor) { const editorService = accessor.get(IEditorService); const instaService = accessor.get(IInstantiationService); - return editorService.openEditor(instaService.createInstance(PerfviewInput)); + return editorService.openEditor(instaService.createInstance(PerfviewInput), { pinned: true }); } }); diff --git a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts index 4d0b7970c..60f7caa47 100644 --- a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts @@ -18,14 +18,15 @@ import * as perf from 'vs/base/common/performance'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { writeTransientState } from 'vs/workbench/contrib/codeEditor/browser/toggleWordWrap'; -import { mergeSort } from 'vs/base/common/arrays'; +import { LoaderStats } from 'vs/base/common/amd'; import { IProductService } from 'vs/platform/product/common/productService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IFileService } from 'vs/platform/files/common/files'; +import { ByteSize, IFileService } from 'vs/platform/files/common/files'; import { ILabelService } from 'vs/platform/label/common/label'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { isWeb } from 'vs/base/common/platform'; export class PerfviewContrib { @@ -107,7 +108,7 @@ class PerfModelContentProvider implements ITextModelContentProvider { this._modelDisposables.push(langId); this._modelDisposables.push(this._extensionService.onDidChangeExtensionsStatus(this._updateModel, this)); - writeTransientState(this._model, { forceWordWrap: 'off', forceWordWrapMinified: false }, this._editorService); + writeTransientState(this._model, { wordWrapOverride: 'off' }, this._editorService); } this._updateModel(); return Promise.resolve(this._model); @@ -150,10 +151,10 @@ class PerfModelContentProvider implements ITextModelContentProvider { md.li(`CPUs: ${metrics.cpus.model}(${metrics.cpus.count} x ${metrics.cpus.speed})`); } if (typeof metrics.totalmem === 'number' && typeof metrics.freemem === 'number') { - md.li(`Memory(System): ${(metrics.totalmem / (1024 * 1024 * 1024)).toFixed(2)} GB(${(metrics.freemem / (1024 * 1024 * 1024)).toFixed(2)}GB free)`); + md.li(`Memory(System): ${(metrics.totalmem / (ByteSize.GB)).toFixed(2)} GB(${(metrics.freemem / (ByteSize.GB)).toFixed(2)}GB free)`); } if (metrics.meminfo) { - md.li(`Memory(Process): ${(metrics.meminfo.workingSetSize / 1024).toFixed(2)} MB working set(${(metrics.meminfo.privateBytes / 1024).toFixed(2)}MB private, ${(metrics.meminfo.sharedBytes / 1024).toFixed(2)}MB shared)`); + md.li(`Memory(Process): ${(metrics.meminfo.workingSetSize / ByteSize.KB).toFixed(2)} MB working set(${(metrics.meminfo.privateBytes / ByteSize.KB).toFixed(2)}MB private, ${(metrics.meminfo.sharedBytes / ByteSize.KB).toFixed(2)}MB shared)`); } md.li(`VM(likelyhood): ${metrics.isVMLikelyhood}%`); md.li(`Initial Startup: ${metrics.initialStartup}`); @@ -171,8 +172,13 @@ class PerfModelContentProvider implements ITextModelContentProvider { 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(['wait for shell environment', metrics.timers.ellapsedWaitForShellEnv, '[renderer]', undefined]); table.push(['require & init workspace storage', metrics.timers.ellapsedWorkspaceStorageInit, '[renderer]', undefined]); table.push(['init workspace service', metrics.timers.ellapsedWorkspaceServiceInit, '[renderer]', undefined]); + if (isWeb) { + table.push(['init settings and global state from settings sync service', metrics.timers.ellapsedRequiredUserDataInit, '[renderer]', undefined]); + table.push(['init keybindings, snippets & extensions from settings sync service', metrics.timers.ellapsedOtherUserDataInit, '[renderer]', undefined]); + } table.push(['register extensions & spawn extension host', metrics.timers.ellapsedExtensions, '[renderer]', undefined]); table.push(['restore viewlet', metrics.timers.ellapsedViewletRestore, '[renderer]', metrics.viewletId]); table.push(['restore panel', metrics.timers.ellapsedPanelRestore, '[renderer]', metrics.panelId]); @@ -279,100 +285,6 @@ class PerfModelContentProvider implements ITextModelContentProvider { } } -abstract class LoaderStats { - abstract get amdLoad(): (string | number)[][]; - abstract get amdInvoke(): (string | number)[][]; - abstract get nodeRequire(): (string | number)[][]; - abstract get nodeEval(): (string | number)[][]; - abstract get nodeRequireTotal(): number; - - - static get(): LoaderStats { - - - const amdLoadScript = new Map(); - const amdInvokeFactory = new Map(); - const nodeRequire = new Map(); - const nodeEval = new Map(); - - function mark(map: Map, stat: LoaderEvent) { - if (map.has(stat.detail)) { - // console.warn('BAD events, DOUBLE start', stat); - // map.delete(stat.detail); - return; - } - map.set(stat.detail, -stat.timestamp); - } - - function diff(map: Map, stat: LoaderEvent) { - let duration = map.get(stat.detail); - if (!duration) { - // console.warn('BAD events, end WITHOUT start', stat); - // map.delete(stat.detail); - return; - } - if (duration >= 0) { - // console.warn('BAD events, DOUBLE end', stat); - // map.delete(stat.detail); - return; - } - map.set(stat.detail, duration + stat.timestamp); - } - - const stats = mergeSort(require.getStats().slice(0), (a, b) => a.timestamp - b.timestamp); - - for (const stat of stats) { - switch (stat.type) { - case LoaderEventType.BeginLoadingScript: - mark(amdLoadScript, stat); - break; - case LoaderEventType.EndLoadingScriptOK: - case LoaderEventType.EndLoadingScriptError: - diff(amdLoadScript, stat); - break; - - case LoaderEventType.BeginInvokeFactory: - mark(amdInvokeFactory, stat); - break; - case LoaderEventType.EndInvokeFactory: - diff(amdInvokeFactory, stat); - break; - - case LoaderEventType.NodeBeginNativeRequire: - mark(nodeRequire, stat); - break; - case LoaderEventType.NodeEndNativeRequire: - diff(nodeRequire, stat); - break; - - case LoaderEventType.NodeBeginEvaluatingScript: - mark(nodeEval, stat); - break; - case LoaderEventType.NodeEndEvaluatingScript: - diff(nodeEval, stat); - break; - } - } - - let nodeRequireTotal = 0; - nodeRequire.forEach(value => nodeRequireTotal += value); - - function to2dArray(map: Map): (string | number)[][] { - let res: (string | number)[][] = []; - map.forEach((value, index) => res.push([index, value])); - return res; - } - - return { - amdLoad: to2dArray(amdLoadScript), - amdInvoke: to2dArray(amdInvokeFactory), - nodeRequire: to2dArray(nodeRequire), - nodeEval: to2dArray(nodeEval), - nodeRequireTotal - }; - } -} - class MarkdownBuilder { value: string = ''; @@ -393,34 +305,6 @@ class MarkdownBuilder { } table(header: string[], rows: Array>) { - let lengths: number[] = []; - header.forEach((cell, ci) => { - lengths[ci] = cell.length; - }); - rows.forEach(row => { - row.forEach((cell, ci) => { - if (typeof cell === 'undefined') { - cell = row[ci] = '-'; - } - const len = cell.toString().length; - lengths[ci] = Math.max(len, lengths[ci]); - }); - }); - - // header - header.forEach((cell, ci) => { this.value += `| ${cell + ' '.repeat(lengths[ci] - cell.toString().length)} `; }); - this.value += '|\n'; - header.forEach((_cell, ci) => { this.value += `| ${'-'.repeat(lengths[ci])} `; }); - this.value += '|\n'; - - // cells - rows.forEach(row => { - row.forEach((cell, ci) => { - if (typeof cell !== 'undefined') { - this.value += `| ${cell + ' '.repeat(lengths[ci] - cell.toString().length)} `; - } - }); - this.value += '|\n'; - }); + this.value += LoaderStats.toMarkdownTable(header, rows); } } diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts index cfb822450..0e640c752 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts @@ -24,6 +24,8 @@ import { editorWidgetBackground, editorWidgetForeground, widgetShadow } from 'vs import { ScrollType } from 'vs/editor/common/editorCommon'; import { SearchWidget, SearchOptions } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { timeout } from 'vs/base/common/async'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; export interface KeybindingsSearchOptions extends SearchOptions { recordEnter?: boolean; @@ -50,13 +52,14 @@ export class KeybindingsSearchWidget extends SearchWidget { private _onBlur = this._register(new Emitter()); readonly onBlur: Event = this._onBlur.event; - constructor(parent: HTMLElement, options: SearchOptions, + constructor(parent: HTMLElement, options: KeybindingsSearchOptions, @IContextViewService contextViewService: IContextViewService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IInstantiationService instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService + @IThemeService themeService: IThemeService, + @IContextKeyService contextKeyService: IContextKeyService ) { - super(parent, options, contextViewService, instantiationService, themeService); + super(parent, options, contextViewService, instantiationService, themeService, contextKeyService); this._register(attachInputBoxStyler(this.inputBox, themeService)); this._register(toDisposable(() => this.stopRecordingKeys())); this._firstPart = null; @@ -198,7 +201,7 @@ export class DefineKeybindingWidget extends Widget { } })); - this._keybindingInputWidget = this._register(this.instantiationService.createInstance(KeybindingsSearchWidget, this._domNode.domNode, { ariaLabel: message })); + this._keybindingInputWidget = this._register(this.instantiationService.createInstance(KeybindingsSearchWidget, this._domNode.domNode, { ariaLabel: message, history: [] })); this._keybindingInputWidget.startRecordingKeys(); this._register(this._keybindingInputWidget.onKeybinding(keybinding => this.onKeybinding(keybinding))); this._register(this._keybindingInputWidget.onEnter(() => this.hide())); @@ -219,7 +222,7 @@ export class DefineKeybindingWidget extends Widget { define(): Promise { this._keybindingInputWidget.clear(); - return new Promise((c) => { + return new Promise(async (c) => { if (!this._isVisible) { this._isVisible = true; this._domNode.setDisplay('block'); @@ -229,6 +232,11 @@ export class DefineKeybindingWidget extends Widget { this._keybindingInputWidget.setInputValue(''); dom.clearNode(this._outputNode); dom.clearNode(this._showExistingKeybindingsNode); + + // Input is not getting focus without timeout in safari + // https://github.com/microsoft/vscode/issues/108817 + await timeout(0); + this._keybindingInputWidget.focus(); } const disposable = this._onHide.event(() => { diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index 08273a277..7b7c3dd59 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -21,12 +21,12 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { KeybindingsEditorModel, IKeybindingItemEntry, IListEntry, KEYBINDING_ENTRY_TEMPLATE_ID } from 'vs/workbench/services/preferences/common/keybindingsEditorModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService, IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; -import { DefineKeybindingWidget, KeybindingsSearchWidget, KeybindingsSearchOptions } from 'vs/workbench/contrib/preferences/browser/keybindingWidgets'; -import { IKeybindingsEditorPane, CONTEXT_KEYBINDING_FOCUS, CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR } from 'vs/workbench/contrib/preferences/common/preferences'; +import { DefineKeybindingWidget, KeybindingsSearchWidget } from 'vs/workbench/contrib/preferences/browser/keybindingWidgets'; +import { CONTEXT_KEYBINDING_FOCUS, CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_ADD } from 'vs/workbench/contrib/preferences/common/preferences'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListEvent } from 'vs/base/browser/ui/list/list'; -import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, ResolvedKeybinding } from 'vs/base/common/keyCodes'; @@ -37,16 +37,17 @@ import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { KeybindingsEditorInput } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { attachStylerCallback, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; -import { IStorageService } from 'vs/platform/storage/common/storage'; +import { attachStylerCallback, attachInputBoxStyler, attachCheckboxStyler } from 'vs/platform/theme/common/styler'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { Emitter, Event } from 'vs/base/common/event'; import { MenuRegistry, MenuId, isIMenuItem } from 'vs/platform/actions/common/actions'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; -import { preferencesEditIcon } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; import { Color, RGBA } from 'vs/base/common/color'; import { WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; -import { ThemableCheckboxActionViewItem } from 'vs/platform/theme/browser/checkbox'; +import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { IKeybindingsEditorPane } from 'vs/workbench/services/preferences/common/preferences'; +import { keybindingsRecordKeysIcon, keybindingsSortIcon, keybindingsAddIcon, preferencesClearInputIcon, keybindingsEditIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons'; const $ = DOM.$; @@ -58,6 +59,20 @@ interface ColumnItem { const oddRowBackgroundColor = new Color(new RGBA(130, 130, 130, 0.04)); +class ThemableCheckboxActionViewItem extends CheckboxActionViewItem { + + constructor(context: any, action: IAction, options: IBaseActionViewItemOptions | undefined, private readonly themeService: IThemeService) { + super(context, action, options); + } + + render(container: HTMLElement): void { + super.render(container); + if (this.checkbox) { + this.disposables.add(attachCheckboxStyler(this.checkbox, this.themeService)); + } + } +} + export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorPane { static readonly ID: string = 'workbench.editor.keybindings'; @@ -73,6 +88,7 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP private headerContainer!: HTMLElement; private actionsContainer!: HTMLElement; private searchWidget!: KeybindingsSearchWidget; + private searchHistoryDelayer: Delayer; private overlayContainer!: HTMLElement; private defineKeybindingWidget!: DefineKeybindingWidget; @@ -86,7 +102,6 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP private dimension: DOM.Dimension | null = null; private delayedFiltering: Delayer; private latestEmptyFilters: string[] = []; - private delayedFilterLogging: Delayer; private keybindingsEditorContextKey: IContextKey; private keybindingFocusContextKey: IContextKey; private searchFocusContextKey: IContextKey; @@ -116,16 +131,16 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP this.keybindingsEditorContextKey = CONTEXT_KEYBINDINGS_EDITOR.bindTo(this.contextKeyService); this.searchFocusContextKey = CONTEXT_KEYBINDINGS_SEARCH_FOCUS.bindTo(this.contextKeyService); this.keybindingFocusContextKey = CONTEXT_KEYBINDING_FOCUS.bindTo(this.contextKeyService); - this.delayedFilterLogging = new Delayer(1000); + this.searchHistoryDelayer = new Delayer(500); const recordKeysActionKeybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS); const recordKeysActionLabel = localize('recordKeysLabel', "Record Keys"); - this.recordKeysAction = new Action(KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, recordKeysActionKeybinding ? localize('recordKeysLabelWithKeybinding', "{0} ({1})", recordKeysActionLabel, recordKeysActionKeybinding.getLabel()) : recordKeysActionLabel, 'codicon-record-keys'); + this.recordKeysAction = new Action(KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, recordKeysActionKeybinding ? localize('recordKeysLabelWithKeybinding', "{0} ({1})", recordKeysActionLabel, recordKeysActionKeybinding.getLabel()) : recordKeysActionLabel, ThemeIcon.asClassName(keybindingsRecordKeysIcon)); this.recordKeysAction.checked = false; const sortByPrecedenceActionKeybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE); const sortByPrecedenceActionLabel = localize('sortByPrecedeneLabel', "Sort by Precedence"); - this.sortByPrecedenceAction = new Action('keybindings.editor.sortByPrecedence', sortByPrecedenceActionKeybinding ? localize('sortByPrecedeneLabelWithKeybinding', "{0} ({1})", sortByPrecedenceActionLabel, sortByPrecedenceActionKeybinding.getLabel()) : sortByPrecedenceActionLabel, 'codicon-sort-precedence'); + this.sortByPrecedenceAction = new Action('keybindings.editor.sortByPrecedence', sortByPrecedenceActionKeybinding ? localize('sortByPrecedeneLabelWithKeybinding', "{0} ({1})", sortByPrecedenceActionLabel, sortByPrecedenceActionKeybinding.getLabel()) : sortByPrecedenceActionLabel, ThemeIcon.asClassName(keybindingsSortIcon)); this.sortByPrecedenceAction.checked = false; } @@ -190,13 +205,13 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP return focusedElement && focusedElement.templateId === KEYBINDING_ENTRY_TEMPLATE_ID ? focusedElement : null; } - defineKeybinding(keybindingEntry: IKeybindingItemEntry): Promise { + defineKeybinding(keybindingEntry: IKeybindingItemEntry, add: boolean): Promise { this.selectEntry(keybindingEntry); this.showOverlayContainer(); return this.defineKeybindingWidget.define().then(key => { if (key) { this.reportKeybindingAction(KEYBINDINGS_EDITOR_COMMAND_DEFINE, keybindingEntry.keybindingItem.command, key); - return this.updateKeybinding(keybindingEntry, key, keybindingEntry.keybindingItem.when); + return this.updateKeybinding(keybindingEntry, key, keybindingEntry.keybindingItem.when, add); } return null; }).then(() => { @@ -217,17 +232,18 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP } } - updateKeybinding(keybindingEntry: IKeybindingItemEntry, key: string, when: string | undefined): Promise { + async updateKeybinding(keybindingEntry: IKeybindingItemEntry, key: string, when: string | undefined, add?: boolean): Promise { const currentKey = keybindingEntry.keybindingItem.keybinding ? keybindingEntry.keybindingItem.keybinding.getUserSettingsLabel() : ''; if (currentKey !== key || keybindingEntry.keybindingItem.when !== when) { - return this.keybindingEditingService.editKeybinding(keybindingEntry.keybindingItem.keybindingItem, key, when || undefined) - .then(() => { - if (!keybindingEntry.keybindingItem.keybinding) { // reveal only if keybinding was added to unassinged. Because the entry will be placed in different position after rendering - this.unAssignedKeybindingItemToRevealAndFocus = keybindingEntry; - } - }); + if (add) { + await this.keybindingEditingService.addKeybinding(keybindingEntry.keybindingItem.keybindingItem, key, when || undefined); + } else { + await this.keybindingEditingService.editKeybinding(keybindingEntry.keybindingItem.keybindingItem, key, when || undefined); + } + if (!keybindingEntry.keybindingItem.keybinding) { // reveal only if keybinding was added to unassinged. Because the entry will be placed in different position after rendering + this.unAssignedKeybindingItemToRevealAndFocus = keybindingEntry; + } } - return Promise.resolve(); } removeKeybinding(keybindingEntry: IKeybindingItemEntry): Promise { @@ -286,6 +302,7 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP search(filter: string): void { this.focusSearch(); this.searchWidget.setValue(filter); + this.selectEntry(0); } clearSearchResults(): void { @@ -328,16 +345,17 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP const fullTextSearchPlaceholder = localize('SearchKeybindings.FullTextSearchPlaceholder', "Type to search in keybindings"); const keybindingsSearchPlaceholder = localize('SearchKeybindings.KeybindingsSearchPlaceholder', "Recording Keys. Press Escape to exit"); - const clearInputAction = new Action(KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, localize('clearInput', "Clear Keybindings Search Input"), 'codicon-clear-all', false, () => { this.search(''); return Promise.resolve(null); }); + const clearInputAction = new Action(KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, localize('clearInput', "Clear Keybindings Search Input"), ThemeIcon.asClassName(preferencesClearInputIcon), false, () => { this.clearSearchResults(); return Promise.resolve(null); }); const searchContainer = DOM.append(this.headerContainer, $('.search-container')); - this.searchWidget = this._register(this.instantiationService.createInstance(KeybindingsSearchWidget, searchContainer, { + this.searchWidget = this._register(this.instantiationService.createInstance(KeybindingsSearchWidget, searchContainer, { ariaLabel: fullTextSearchPlaceholder, placeholder: fullTextSearchPlaceholder, focusKey: this.searchFocusContextKey, ariaLabelledBy: 'keybindings-editor-aria-label-element', recordEnter: true, - quoteRecordedKeys: true + quoteRecordedKeys: true, + history: this.getMemento(StorageScope.GLOBAL, StorageTarget.USER)['searchHistory'] || [], })); this._register(this.searchWidget.onDidChange(searchValue => { clearInputAction.enabled = !!searchValue; @@ -483,7 +501,7 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP this._register(this.keybindingsList.onMouseDblClick(() => { const activeKeybindingEntry = this.activeKeybindingEntry; if (activeKeybindingEntry) { - this.defineKeybinding(activeKeybindingEntry); + this.defineKeybinding(activeKeybindingEntry, false); } })); this._register(this.keybindingsList.onKeyDown(e => { @@ -491,7 +509,7 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP if (event.keyCode === KeyCode.Enter) { const keybindingEntry = this.activeKeybindingEntry; if (keybindingEntry) { - this.defineKeybinding(keybindingEntry); + this.defineKeybinding(keybindingEntry, false); } e.stopPropagation(); } @@ -529,7 +547,12 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP private filterKeybindings(): void { this.renderKeybindingsEntries(this.searchWidget.hasFocus()); - this.delayedFilterLogging.trigger(() => this.reportFilteringUsed(this.searchWidget.getValue())); + this.searchHistoryDelayer.trigger(() => { + this.searchWidget.inputBox.addToHistory(); + this.getMemento(StorageScope.GLOBAL, StorageTarget.USER)['searchHistory'] = this.searchWidget.inputBox.getHistory(); + this.saveState(); + this.reportFilteringUsed(this.searchWidget.getValue()); + }); } private renderKeybindingsEntries(reset: boolean, preserveFocus?: boolean): void { @@ -657,19 +680,24 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP } if (e.element.templateId === KEYBINDING_ENTRY_TEMPLATE_ID) { - this.selectEntry(e.element); + const keybindingItemEntry = e.element; + this.selectEntry(keybindingItemEntry); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => [ - this.createCopyAction(e.element), - this.createCopyCommandAction(e.element), + this.createCopyAction(keybindingItemEntry), + this.createCopyCommandAction(keybindingItemEntry), new Separator(), - this.createDefineAction(e.element), - this.createRemoveAction(e.element), - this.createResetAction(e.element), - this.createDefineWhenExpressionAction(e.element), + ...(keybindingItemEntry.keybindingItem.keybinding + ? [this.createDefineKeybindingAction(keybindingItemEntry), this.createAddKeybindingAction(keybindingItemEntry)] + : [this.createDefineKeybindingAction(keybindingItemEntry)]), new Separator(), - this.createShowConflictsAction(e.element)] + this.createRemoveAction(keybindingItemEntry), + this.createResetAction(keybindingItemEntry), + new Separator(), + this.createDefineWhenExpressionAction(keybindingItemEntry), + new Separator(), + this.createShowConflictsAction(keybindingItemEntry)] }); } } @@ -685,12 +713,21 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP } } - private createDefineAction(keybindingItemEntry: IKeybindingItemEntry): IAction { + private createDefineKeybindingAction(keybindingItemEntry: IKeybindingItemEntry): IAction { return { - label: keybindingItemEntry.keybindingItem.keybinding ? localize('changeLabel', "Change Keybinding") : localize('addLabel', "Add Keybinding"), + label: keybindingItemEntry.keybindingItem.keybinding ? localize('changeLabel', "Change Keybinding...") : localize('addLabel', "Add Keybinding..."), enabled: true, id: KEYBINDINGS_EDITOR_COMMAND_DEFINE, - run: () => this.defineKeybinding(keybindingItemEntry) + run: () => this.defineKeybinding(keybindingItemEntry, false) + }; + } + + private createAddKeybindingAction(keybindingItemEntry: IKeybindingItemEntry): IAction { + return { + label: localize('addLabel', "Add Keybinding..."), + enabled: true, + id: KEYBINDINGS_EDITOR_COMMAND_ADD, + run: () => this.defineKeybinding(keybindingItemEntry, true) }; } @@ -897,22 +934,22 @@ class ActionsColumn extends Column { private createEditAction(keybindingItemEntry: IKeybindingItemEntry): IAction { const keybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_DEFINE); return { - class: preferencesEditIcon.classNames, + class: ThemeIcon.asClassName(keybindingsEditIcon), enabled: true, id: 'editKeybinding', tooltip: keybinding ? localize('editKeybindingLabelWithKey', "Change Keybinding {0}", `(${keybinding.getLabel()})`) : localize('editKeybindingLabel', "Change Keybinding"), - run: () => this.keybindingsEditor.defineKeybinding(keybindingItemEntry) + run: () => this.keybindingsEditor.defineKeybinding(keybindingItemEntry, false) }; } private createAddAction(keybindingItemEntry: IKeybindingItemEntry): IAction { const keybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_DEFINE); return { - class: 'codicon-add', + class: ThemeIcon.asClassName(keybindingsAddIcon), enabled: true, id: 'addKeybinding', tooltip: keybinding ? localize('addKeybindingLabelWithKey', "Add Keybinding {0}", `(${keybinding.getLabel()})`) : localize('addKeybindingLabel', "Add Keybinding"), - run: () => this.keybindingsEditor.defineKeybinding(keybindingItemEntry) + run: () => this.keybindingsEditor.defineKeybinding(keybindingItemEntry, false) }; } diff --git a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts index cdf16c245..a7e4bad5e 100644 --- a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts +++ b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/common/statusbar'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { IKeymapService, areKeyboardLayoutsEqual, parseKeyboardLayoutDescription, getKeyboardLayoutId, IKeyboardLayoutInfo } from 'vs/workbench/services/keybinding/common/keymapInfo'; +import { parseKeyboardLayoutDescription, areKeyboardLayoutsEqual, getKeyboardLayoutId, IKeyboardLayoutService, IKeyboardLayoutInfo } from 'vs/platform/keyboardLayout/common/keyboardLayout'; 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'; @@ -27,12 +27,12 @@ export class KeyboardLayoutPickerContribution extends Disposable implements IWor private readonly pickerElement = this._register(new MutableDisposable()); constructor( - @IKeymapService private readonly keymapService: IKeymapService, + @IKeyboardLayoutService private readonly keyboardLayoutService: IKeyboardLayoutService, @IStatusbarService private readonly statusbarService: IStatusbarService, ) { super(); - let layout = this.keymapService.getCurrentKeyboardLayout(); + let layout = this.keyboardLayoutService.getCurrentKeyboardLayout(); if (layout) { let layoutInfo = parseKeyboardLayoutDescription(layout); const text = nls.localize('keyboardLayout', "Layout: {0}", layoutInfo.label); @@ -49,8 +49,8 @@ export class KeyboardLayoutPickerContribution extends Disposable implements IWor ); } - this._register(keymapService.onDidChangeKeyboardMapper(() => { - let layout = this.keymapService.getCurrentKeyboardLayout(); + this._register(keyboardLayoutService.onDidChangeKeyboardLayout(() => { + let layout = this.keyboardLayoutService.getCurrentKeyboardLayout(); let layoutInfo = parseKeyboardLayoutDescription(layout); if (this.pickerElement.value) { @@ -101,7 +101,7 @@ export class KeyboardLayoutPickerAction extends Action { actionLabel: string, @IFileService private readonly fileService: IFileService, @IQuickInputService private readonly quickInputService: IQuickInputService, - @IKeymapService private readonly keymapService: IKeymapService, + @IKeyboardLayoutService private readonly keyboardLayoutService: IKeyboardLayoutService, @IConfigurationService private readonly configurationService: IConfigurationService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IEditorService private readonly editorService: IEditorService @@ -110,8 +110,8 @@ export class KeyboardLayoutPickerAction extends Action { } async run(): Promise { - let layouts = this.keymapService.getAllKeyboardLayouts(); - let currentLayout = this.keymapService.getCurrentKeyboardLayout(); + let layouts = this.keyboardLayoutService.getAllKeyboardLayouts(); + let currentLayout = this.keyboardLayoutService.getCurrentKeyboardLayout(); let layoutConfig = this.configurationService.getValue('keyboard.layout'); let isAutoDetect = layoutConfig === 'autodetect'; @@ -169,7 +169,8 @@ export class KeyboardLayoutPickerAction extends Action { } return this.editorService.openEditor({ resource: stat.resource, - mode: 'jsonc' + mode: 'jsonc', + options: { pinned: true } }); }, (error) => { throw new Error(nls.localize('fail.createSettings', "Unable to create '{0}' ({1}).", file.toString(), error)); diff --git a/src/vs/workbench/contrib/preferences/browser/media/preferences.css b/src/vs/workbench/contrib/preferences/browser/media/preferences.css index 2476aeea7..89e322320 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/preferences.css +++ b/src/vs/workbench/contrib/preferences/browser/media/preferences.css @@ -194,28 +194,6 @@ justify-content: center; } -.monaco-editor .settings-group-title-widget .title-container.collapsed .codicon::before { - transform: rotate(-90deg); -} - -.monaco-editor .codicon-edit { - transform: rotate(-90deg); - width:16px; - height: 16px; - cursor: pointer; -} - -.monaco-editor .codicon-edit.hidden { - display: none; - visibility: hidden; -} - .monaco-editor .dim-configuration { color: #b1b1b1; } - -.monaco-editor .floating-click-widget { - padding: 10px; - border-radius: 5px; - cursor: pointer; -} diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index 58ce2825e..f9f59178f 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -124,6 +124,7 @@ .settings-editor > .settings-body { position: relative; + margin-top: 14px; } .settings-editor > .settings-body > .no-results-message { @@ -247,7 +248,6 @@ } .settings-editor > .settings-body .settings-tree-container { - margin-top: 14px; border-spacing: 0; border-collapse: separate; position: relative; @@ -344,13 +344,11 @@ } .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-title .setting-item-overrides { - opacity: 0.9; font-style: italic; margin-right: 4px; } .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-title .setting-item-ignored { - opacity: 0.9; font-style: italic; } diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index 8c59d7911..511b2c236 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -33,13 +33,14 @@ import { KeybindingsEditor } from 'vs/workbench/contrib/preferences/browser/keyb import { ConfigureLanguageBasedSettingsAction } from 'vs/workbench/contrib/preferences/browser/preferencesActions'; import { PreferencesEditor } from 'vs/workbench/contrib/preferences/browser/preferencesEditor'; import { SettingsEditor2, SettingsFocusContext } from 'vs/workbench/contrib/preferences/browser/settingsEditor2'; -import { CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, CONTEXT_KEYBINDING_FOCUS, CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_SEARCH, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, MODIFIED_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; +import { CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, CONTEXT_KEYBINDING_FOCUS, CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, KEYBINDINGS_EDITOR_COMMAND_ADD, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_SEARCH, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, MODIFIED_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; import { PreferencesContribution } from 'vs/workbench/contrib/preferences/common/preferencesContribution'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { DefaultPreferencesEditorInput, KeybindingsEditorInput, PreferencesEditorInput, SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; +import { preferencesOpenSettingsIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons'; const SETTINGS_EDITOR_COMMAND_SEARCH = 'settings.action.search'; @@ -95,7 +96,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( // Register Preferences Editor Input Factory class PreferencesEditorInputFactory extends AbstractSideBySideEditorInputFactory { - protected createEditorInput(name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput { + protected createEditorInput(instantiationService: IInstantiationService, name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput { return new PreferencesEditorInput(name, description, secondaryInput, primaryInput); } } @@ -277,7 +278,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon super({ id: '_workbench.openUserSettingsEditor', title: OPEN_SETTINGS2_ACTION_TITLE, - icon: { id: 'codicon/go-to-file' }, + icon: preferencesOpenSettingsIcon, menu: [{ id: MenuId.EditorTitle, when: ResourceContextKey.Resource.isEqualTo(that.environmentService.settingsResource.toString()), @@ -295,7 +296,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon super({ id: SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON, title: { value: nls.localize('openSettingsJson', "Open Settings (JSON)"), original: 'Open Settings (JSON)' }, - icon: { id: 'codicon/go-to-file' }, + icon: preferencesOpenSettingsIcon, menu: [{ id: MenuId.EditorTitle, when: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR.toNegated()), @@ -815,7 +816,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon id: 'workbench.action.openGlobalKeybindings', title: { value: nls.localize('openGlobalKeybindings', "Open Keyboard Shortcuts"), original: 'Open Keyboard Shortcuts' }, category, - icon: { id: 'codicon/go-to-file' }, + icon: preferencesOpenSettingsIcon, keybinding: { when: null, weight: KeybindingWeight.WorkbenchContrib, @@ -832,8 +833,9 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon ] }); } - run(accessor: ServicesAccessor) { - return accessor.get(IPreferencesService).openGlobalKeybindingSettings(false); + run(accessor: ServicesAccessor, args: string | undefined) { + const query = typeof args === 'string' ? args : undefined; + return accessor.get(IPreferencesService).openGlobalKeybindingSettings(false, { query }); } }); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { @@ -871,7 +873,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon id: 'workbench.action.openGlobalKeybindingsFile', title: { value: nls.localize('openGlobalKeybindingsFile', "Open Keyboard Shortcuts (JSON)"), original: 'Open Keyboard Shortcuts (JSON)' }, category, - icon: { id: 'codicon/go-to-file' }, + icon: preferencesOpenSettingsIcon, menu: [ { id: MenuId.CommandPalette }, { @@ -960,7 +962,20 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon handler: (accessor, args: any) => { const editorPane = accessor.get(IEditorService).activeEditorPane; if (editorPane instanceof KeybindingsEditor) { - editorPane.defineKeybinding(editorPane.activeKeybindingEntry!); + editorPane.defineKeybinding(editorPane.activeKeybindingEntry!, false); + } + } + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: KEYBINDINGS_EDITOR_COMMAND_ADD, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_A), + handler: (accessor, args: any) => { + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.defineKeybinding(editorPane.activeKeybindingEntry!, true); } } }); @@ -1091,7 +1106,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon id: KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, weight: KeybindingWeight.WorkbenchContrib, when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS), - primary: KeyCode.DownArrow, + primary: KeyMod.CtrlCmd | KeyCode.DownArrow, handler: (accessor, args: any) => { const editorPane = accessor.get(IEditorService).activeEditorPane; if (editorPane instanceof KeybindingsEditor) { @@ -1109,7 +1124,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon command: { id: commandId, title: OPEN_SETTINGS2_ACTION_TITLE, - icon: { id: 'codicon/go-to-file' } + icon: preferencesOpenSettingsIcon }, when: ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(this.preferencesService.workspaceSettingsResource!.toString()), WorkbenchStateContext.isEqualTo('workspace')), group: 'navigation', @@ -1134,7 +1149,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon command: { id: commandId, title: OPEN_SETTINGS2_ACTION_TITLE, - icon: { id: 'codicon/go-to-file' } + icon: preferencesOpenSettingsIcon }, when: ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(this.preferencesService.getFolderSettingsResource(folder.uri)!.toString())), group: 'navigation', diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts index 8b095ecb5..e6955647c 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts @@ -55,7 +55,7 @@ export class ConfigureLanguageBasedSettingsAction extends Action { if (pick) { const modeId = this.modeService.getModeIdForLanguageName(pick.label.toLowerCase()); if (typeof modeId === 'string') { - return this.preferencesService.openGlobalSettings(true, { editSetting: `[${modeId}]` }); + return this.preferencesService.openGlobalSettings(true, { revealSetting: { key: `[${modeId}]`, edit: true } }); } } return undefined; diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index 8c39361fd..8f4719506 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -111,7 +111,8 @@ export class PreferencesEditor extends EditorPane { placeholder: nls.localize('SearchSettingsWidget.Placeholder', "Search Settings"), focusKey: this.searchFocusContextKey, showResultCount: true, - ariaLive: 'assertive' + ariaLive: 'assertive', + history: [], })); this._register(this.searchWidget.onDidChange(value => this.onInputChanged())); this._register(this.searchWidget.onFocus(() => this.lastFocusedWidget = this.searchWidget)); diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesIcons.ts b/src/vs/workbench/contrib/preferences/browser/preferencesIcons.ts new file mode 100644 index 000000000..ec9d0a958 --- /dev/null +++ b/src/vs/workbench/contrib/preferences/browser/preferencesIcons.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { localize } from 'vs/nls'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; + +export const settingsGroupExpandedIcon = registerIcon('settings-group-expanded', Codicon.chevronDown, localize('settingsGroupExpandedIcon', 'Icon for an expanded section in the split JSON settings editor.')); +export const settingsGroupCollapsedIcon = registerIcon('settings-group-collapsed', Codicon.chevronRight, localize('settingsGroupCollapsedIcon', 'Icon for an collapsed section in the split JSON settings editor.')); +export const settingsScopeDropDownIcon = registerIcon('settings-folder-dropdown', Codicon.triangleDown, localize('settingsScopeDropDownIcon', 'Icon for the folder dropdown button in the split JSON settings editor.')); +export const settingsMoreActionIcon = registerIcon('settings-more-action', Codicon.gear, localize('settingsMoreActionIcon', 'Icon for the \'more actions\' action in the settings UI.')); + +export const keybindingsRecordKeysIcon = registerIcon('keybindings-record-keys', Codicon.recordKeys, localize('keybindingsRecordKeysIcon', 'Icon for the \'record keys\' action in the keybinding UI.')); +export const keybindingsSortIcon = registerIcon('keybindings-sort', Codicon.sortPrecedence, localize('keybindingsSortIcon', 'Icon for the \'sort by precedence\' toggle in the keybinding UI.')); + +export const keybindingsEditIcon = registerIcon('keybindings-edit', Codicon.edit, localize('keybindingsEditIcon', 'Icon for the edit action in the keybinding UI.')); +export const keybindingsAddIcon = registerIcon('keybindings-add', Codicon.add, localize('keybindingsAddIcon', 'Icon for the add action in the keybinding UI.')); + +export const settingsEditIcon = registerIcon('settings-edit', Codicon.edit, localize('settingsEditIcon', 'Icon for the edit action in the settings UI.')); +export const settingsAddIcon = registerIcon('settings-add', Codicon.add, localize('settingsAddIcon', 'Icon for the add action in the settings UI.')); + +export const settingsRemoveIcon = registerIcon('settings-remove', Codicon.close, localize('settingsRemoveIcon', 'Icon for the remove action in the settings UI.')); +export const settingsDiscardIcon = registerIcon('settings-discard', Codicon.discard, localize('preferencesDiscardIcon', 'Icon for the discard action in the settings UI.')); + +export const preferencesClearInputIcon = registerIcon('preferences-clear-input', Codicon.clearAll, localize('preferencesClearInput', 'Icon for clear input in the settings and keybinding UI.')); +export const preferencesOpenSettingsIcon = registerIcon('preferences-open-settings', Codicon.goToFile, localize('preferencesOpenSettings', 'Icon for open settings commands.')); diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index b13338f99..49511397c 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -25,12 +25,15 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations'; -import { DefaultSettingsHeaderWidget, EditPreferenceWidget, SettingsGroupTitleWidget, SettingsHeaderWidget, preferencesEditIcon } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; +import { DefaultSettingsHeaderWidget, EditPreferenceWidget, SettingsGroupTitleWidget, SettingsHeaderWidget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; import { IFilterResult, IPreferencesEditorModel, IPreferencesService, ISetting, ISettingsEditorModel, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences'; import { DefaultSettingsEditorModel, SettingsEditorModel, WorkspaceConfigurationEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; import { IMarkerService, IMarkerData, MarkerSeverity, MarkerTag } from 'vs/platform/markers/common/markers'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { FindDecorations } from 'vs/editor/contrib/find/findDecorations'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { settingsEditIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons'; export interface IPreferencesRenderer extends IDisposable { readonly preferencesModel: IPreferencesEditorModel; @@ -569,15 +572,10 @@ export class FilteredMatchesRenderer extends Disposable implements HiddenAreasPr private createDecoration(range: IRange): IModelDeltaDecoration { return { range, - options: FilteredMatchesRenderer._FIND_MATCH + options: FindDecorations._FIND_MATCH_DECORATION }; } - private static readonly _FIND_MATCH = ModelDecorationOptions.register({ - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - className: 'findMatch' - }); - private computeHiddenRanges(filteredGroups: ISettingsGroup[] | undefined, allSettingsGroups: ISettingsGroup[]): IRange[] { // Hide the contents of hidden groups const notMatchesRanges: IRange[] = []; @@ -614,15 +612,10 @@ export class HighlightMatchesRenderer extends Disposable { this.decorationIds = this.editor.deltaDecorations(this.decorationIds, matches.map(match => this.createDecoration(match))); } - private static readonly _FIND_MATCH = ModelDecorationOptions.register({ - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - className: 'findMatch' - }); - private createDecoration(range: IRange): IModelDeltaDecoration { return { range, - options: HighlightMatchesRenderer._FIND_MATCH + options: FindDecorations._FIND_MATCH_DECORATION }; } @@ -746,7 +739,7 @@ class EditSettingRenderer extends Disposable { const decorations = this.editor.getLineDecorations(line); if (decorations) { for (const { options } of decorations) { - if (options.glyphMarginClassName && options.glyphMarginClassName.indexOf(preferencesEditIcon.classNames) === -1) { + if (options.glyphMarginClassName && options.glyphMarginClassName.indexOf(ThemeIcon.asClassName(settingsEditIcon)) === -1) { return false; } } diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index a736bea30..67b174bdd 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -6,7 +6,7 @@ import * as DOM from 'vs/base/browser/dom'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IInputOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; +import { HistoryInputBox, IHistoryInputOptions } from 'vs/base/browser/ui/inputbox/inputBox'; import { Widget } from 'vs/base/browser/ui/widget'; import { Action, IAction } from 'vs/base/common/actions'; import { Emitter, Event } from 'vs/base/common/event'; @@ -21,22 +21,23 @@ import { Position } from 'vs/editor/common/core/position'; import { IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { Schemas } from 'vs/base/common/network'; import { activeContrastBorder, badgeBackground, badgeForeground, contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; -import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND } from 'vs/workbench/common/theme'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ISettingsGroup, IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { isEqual } from 'vs/base/common/resources'; -import { registerIcon, Codicon } from 'vs/base/common/codicons'; import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { settingsEditIcon, settingsGroupCollapsedIcon, settingsGroupExpandedIcon, settingsScopeDropDownIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons'; +import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget'; export class SettingsHeaderWidget extends Widget implements IViewZone { @@ -171,13 +172,23 @@ export class SettingsGroupTitleWidget extends Widget implements IViewZone { this._register(focusTracker.onDidFocus(() => this.toggleFocus(true))); this._register(focusTracker.onDidBlur(() => this.toggleFocus(false))); - this.icon = DOM.append(this.titleContainer, DOM.$('.codicon.codicon-chevron-down')); + this.icon = DOM.append(this.titleContainer, DOM.$('')); this.title = DOM.append(this.titleContainer, DOM.$('.title')); this.title.textContent = this.settingsGroup.title + ` (${this.settingsGroup.sections.reduce((count, section) => count + section.settings.length, 0)})`; + this.updateTwisty(false); this.layout(); } + private getTwistyIcon(isCollapsed: boolean): ThemeIcon { + return isCollapsed ? settingsGroupCollapsedIcon : settingsGroupExpandedIcon; + } + + private updateTwisty(collapse: boolean) { + this.icon.classList.remove(...ThemeIcon.asClassNameArray(this.getTwistyIcon(!collapse))); + this.icon.classList.add(...ThemeIcon.asClassNameArray(this.getTwistyIcon(collapse))); + } + render() { if (!this.settingsGroup.range) { // #61352 @@ -193,6 +204,7 @@ export class SettingsGroupTitleWidget extends Widget implements IViewZone { toggleCollapse(collapse: boolean) { this.titleContainer.classList.toggle('collapsed', collapse); + this.updateTwisty(collapse); } toggleFocus(focus: boolean): void { @@ -257,6 +269,7 @@ export class SettingsGroupTitleWidget extends Widget implements IViewZone { private collapse(collapse: boolean) { if (collapse !== this.isCollapsed()) { this.titleContainer.classList.toggle('collapsed', collapse); + this.updateTwisty(collapse); this._onToggled.fire(collapse); } } @@ -345,7 +358,7 @@ export class FolderSettingsActionViewItem extends BaseActionViewItem { this.container = container; this.labelElement = DOM.$('.action-title'); this.detailsElement = DOM.$('.action-details'); - this.dropDownElement = DOM.$('.dropdown-icon.codicon.codicon-triangle-down.hide'); + this.dropDownElement = DOM.$('.dropdown-icon.hide' + ThemeIcon.asCSSSelector(settingsScopeDropDownIcon)); this.anchorElement = DOM.$('a.action-label.folder-settings', { role: 'button', 'aria-haspopup': 'true', @@ -604,7 +617,7 @@ export class SettingsTargetsWidget extends Widget { } } -export interface SearchOptions extends IInputOptions { +export interface SearchOptions extends IHistoryInputOptions { focusKey?: IContextKey; showResultCount?: boolean; ariaLive?: string; @@ -617,7 +630,7 @@ export class SearchWidget extends Widget { private countElement!: HTMLElement; private searchContainer!: HTMLElement; - inputBox!: InputBox; + inputBox!: HistoryInputBox; private controlsDiv!: HTMLElement; private readonly _onDidChange: Emitter = this._register(new Emitter()); @@ -629,7 +642,8 @@ export class SearchWidget extends Widget { constructor(parent: HTMLElement, protected options: SearchOptions, @IContextViewService private readonly contextViewService: IContextViewService, @IInstantiationService protected instantiationService: IInstantiationService, - @IThemeService private readonly themeService: IThemeService + @IThemeService private readonly themeService: IThemeService, + @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(); this.create(parent); @@ -678,8 +692,8 @@ export class SearchWidget extends Widget { this._register(this.inputBox.onDidChange(value => this._onDidChange.fire(value))); } - protected createInputBox(parent: HTMLElement): InputBox { - const box = this._register(new InputBox(parent, this.contextViewService, this.options)); + protected createInputBox(parent: HTMLElement): HistoryInputBox { + const box = this._register(new ContextScopedHistoryInputBox(parent, this.contextViewService, this.options, this.contextKeyService)); this._register(attachInputBoxStyler(box, this.themeService)); return box; @@ -746,8 +760,6 @@ export class SearchWidget extends Widget { } } -export const preferencesEditIcon = registerIcon('preferences-edit', Codicon.edit, localize('preferencesEditIcon', 'Icon for the edit action in preferences.')); - export class EditPreferenceWidget extends Disposable { private _line: number = -1; @@ -785,7 +797,7 @@ export class EditPreferenceWidget extends Disposable { this._line = line; newDecoration.push({ options: { - glyphMarginClassName: preferencesEditIcon.classNames, + glyphMarginClassName: ThemeIcon.asClassName(settingsEditIcon), glyphMarginHoverMessage: new MarkdownString().appendText(hoverMessage), stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, }, diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 941cd7b5b..7abb3fca7 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -31,12 +31,11 @@ import { IEditorModel } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { badgeBackground, badgeForeground, contrastBorder, editorForeground } from 'vs/platform/theme/common/colorRegistry'; import { attachButtonStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; +import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IUserDataAutoSyncEnablementService, IUserDataSyncService, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorMemento, IEditorOpenContext, IEditorPane } from 'vs/workbench/common/editor'; @@ -47,12 +46,13 @@ import { AbstractSettingRenderer, ISettingLinkClickEvent, ISettingOverrideClickE import { ISettingsEditorViewState, parseQuery, SearchResultIdx, SearchResultModel, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; import { settingsTextInputBorder } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; import { createTOCIterator, TOCTree, TOCTreeModel } from 'vs/workbench/contrib/preferences/browser/tocTree'; -import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, EXTENSION_SETTING_TAG, IPreferencesSearchService, ISearchProvider, MODIFIED_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS } from 'vs/workbench/contrib/preferences/common/preferences'; +import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, ID_SETTING_TAG, IPreferencesSearchService, ISearchProvider, MODIFIED_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS } from 'vs/workbench/contrib/preferences/common/preferences'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IPreferencesService, ISearchResult, ISettingsEditorModel, ISettingsEditorOptions, SettingsEditorOptions, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; import { IUserDataSyncWorkbenchService } from 'vs/workbench/services/userDataSync/common/userDataSync'; +import { preferencesClearInputIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons'; export const enum SettingsFocusContext { Search, @@ -91,7 +91,7 @@ export class SettingsEditor2 extends EditorPane { private static CONFIG_SCHEMA_UPDATE_DELAYER = 500; private static readonly SUGGESTIONS: string[] = [ - `@${MODIFIED_SETTING_TAG}`, '@tag:usesOnlineServices', '@tag:sync', `@${EXTENSION_SETTING_TAG}` + `@${MODIFIED_SETTING_TAG}`, '@tag:usesOnlineServices', '@tag:sync', `@${ID_SETTING_TAG}`, `@${EXTENSION_SETTING_TAG}`, `@${FEATURE_SETTING_TAG}scm`, `@${FEATURE_SETTING_TAG}explorer`, `@${FEATURE_SETTING_TAG}search`, `@${FEATURE_SETTING_TAG}debug`, `@${FEATURE_SETTING_TAG}extensions`, `@${FEATURE_SETTING_TAG}terminal`, `@${FEATURE_SETTING_TAG}task`, `@${FEATURE_SETTING_TAG}problems`, `@${FEATURE_SETTING_TAG}output`, `@${FEATURE_SETTING_TAG}comments`, `@${FEATURE_SETTING_TAG}remote`, `@${FEATURE_SETTING_TAG}timeline` ]; private static shouldSettingUpdateFast(type: SettingValueType | SettingValueType[]): boolean { @@ -173,7 +173,6 @@ export class SettingsEditor2 extends EditorPane { @IStorageService private readonly storageService: IStorageService, @INotificationService private readonly notificationService: INotificationService, @IEditorGroupsService protected editorGroupService: IEditorGroupsService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, @IUserDataSyncWorkbenchService private readonly userDataSyncWorkbenchService: IUserDataSyncWorkbenchService, @IUserDataAutoSyncEnablementService private readonly userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService ) { @@ -202,8 +201,6 @@ export class SettingsEditor2 extends EditorPane { this.onConfigUpdate(e.affectedKeys); } })); - - storageKeysSyncRegistryService.registerStorageKey({ key: SETTINGS_AUTOSAVE_NOTIFIED_KEY, version: 1 }); } get minimumWidth(): number { return 375; } @@ -439,7 +436,7 @@ export class SettingsEditor2 extends EditorPane { const searchContainer = DOM.append(this.headerContainer, $('.search-container')); - const clearInputAction = new Action(SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, localize('clearInput', "Clear Settings Search Input"), 'codicon-clear-all', false, () => { this.clearSearchResults(); return Promise.resolve(null); }); + const clearInputAction = new Action(SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, localize('clearInput', "Clear Settings Search Input"), ThemeIcon.asClassName(preferencesClearInputIcon), false, () => { this.clearSearchResults(); return Promise.resolve(null); }); this.searchWidget = this._register(this.instantiationService.createInstance(SuggestEnabledInput, `${SettingsEditor2.ID}.searchbox`, searchContainer, { triggerCharacters: ['@'], @@ -664,7 +661,7 @@ export class SettingsEditor2 extends EditorPane { this.settingRenderers = this.instantiationService.createInstance(SettingTreeRenderers); this._register(this.settingRenderers.onDidChangeSetting(e => this.onDidChangeSetting(e.key, e.value, e.type))); this._register(this.settingRenderers.onDidOpenSettings(settingKey => { - this.openSettingsFile({ editSetting: settingKey }); + this.openSettingsFile({ revealSetting: { key: settingKey, edit: true } }); })); this._register(this.settingRenderers.onDidClickSettingLink(settingName => this.onDidClickSetting(settingName))); this._register(this.settingRenderers.onDidFocusSetting(element => { @@ -737,8 +734,8 @@ export class SettingsEditor2 extends EditorPane { private notifyNoSaveNeeded() { if (!this.storageService.getBoolean(SETTINGS_AUTOSAVE_NOTIFIED_KEY, StorageScope.GLOBAL, false)) { - this.storageService.store(SETTINGS_AUTOSAVE_NOTIFIED_KEY, true, StorageScope.GLOBAL); - this.notificationService.info(localize('settingsNoSaveNeeded', "Your changes are automatically saved as you edit.")); + this.storageService.store(SETTINGS_AUTOSAVE_NOTIFIED_KEY, true, StorageScope.GLOBAL, StorageTarget.USER); + this.notificationService.info(localize('settingsNoSaveNeeded', "Changes to settings are saved automatically.")); } } @@ -968,7 +965,7 @@ export class SettingsEditor2 extends EditorPane { const groups = this.defaultSettingsEditorModel.settingsGroups.slice(1); // Without commonlyUsed const dividedGroups = collections.groupBy(groups, g => g.extensionInfo ? 'extension' : 'core'); - const settingsResult = resolveSettingsTree(tocData, dividedGroups.core); + const settingsResult = resolveSettingsTree(tocData, dividedGroups.core, this.logService); const resolvedSettingsRoot = settingsResult.tree; // Warn for settings not included in layout @@ -982,7 +979,7 @@ export class SettingsEditor2 extends EditorPane { this.hasWarnedMissingSettings = true; } - const commonlyUsed = resolveSettingsTree(commonlyUsedData, dividedGroups.core); + const commonlyUsed = resolveSettingsTree(commonlyUsedData, dividedGroups.core, this.logService); resolvedSettingsRoot.children!.unshift(commonlyUsed.tree); resolvedSettingsRoot.children!.push(resolveExtensionsSettings(dividedGroups.extension || [])); @@ -1138,18 +1135,22 @@ export class SettingsEditor2 extends EditorPane { private triggerSearch(query: string): Promise { this.viewState.tagFilters = new Set(); this.viewState.extensionFilters = new Set(); + this.viewState.featureFilters = new Set(); + this.viewState.idFilters = new Set(); if (query) { const parsedQuery = parseQuery(query); query = parsedQuery.query; parsedQuery.tags.forEach(tag => this.viewState.tagFilters!.add(tag)); parsedQuery.extensionFilters.forEach(extensionId => this.viewState.extensionFilters!.add(extensionId)); + parsedQuery.featureFilters!.forEach(feature => this.viewState.featureFilters!.add(feature)); + parsedQuery.idFilters!.forEach(id => this.viewState.idFilters!.add(id)); } if (query && query !== '@') { query = this.parseSettingFromJSON(query) || query; return this.triggerFilterPreferences(query); } else { - if ((this.viewState.tagFilters && this.viewState.tagFilters.size) || (this.viewState.extensionFilters && this.viewState.extensionFilters.size)) { + if (this.viewState.tagFilters.size || this.viewState.extensionFilters.size || this.viewState.featureFilters.size || this.viewState.idFilters.size) { this.searchResultModel = this.createFilterModel(); } else { this.searchResultModel = null; @@ -1306,9 +1307,11 @@ export class SettingsEditor2 extends EditorPane { this.tocTreeModel.update(); } - this.tocTree.setFocus([]); - this.viewState.filterToCategory = undefined; - this.tocTree.expandAll(); + if (type === SearchResultIdx.Local) { + this.tocTree.setFocus([]); + this.viewState.filterToCategory = undefined; + this.tocTree.expandAll(); + } this.refreshTOCTree(); this.renderTree(undefined, true); @@ -1382,12 +1385,12 @@ export class SettingsEditor2 extends EditorPane { } private layoutTrees(dimension: DOM.Dimension): void { - const listHeight = dimension.height - (76 + 11 /* header height + padding*/); + const listHeight = dimension.height - (72 + 11 /* header height + editor padding */); const settingsTreeHeight = listHeight - 14; this.settingsTreeContainer.style.height = `${settingsTreeHeight}px`; this.settingsTree.layout(settingsTreeHeight, dimension.width); - const tocTreeHeight = listHeight - 17; + const tocTreeHeight = settingsTreeHeight - 1; this.tocTreeContainer.style.height = `${tocTreeHeight}px`; this.tocTree.layout(tocTreeHeight); } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index b2eb1cb29..86b50e989 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -4,23 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { ISetting } from 'vs/workbench/services/preferences/common/preferences'; - -export interface ITOCEntry { +export interface ITOCEntry { id: string; label: string; - - children?: ITOCEntry[]; - settings?: Array; + children?: ITOCEntry[]; + settings?: Array; } -export const commonlyUsedData: ITOCEntry = { +export const commonlyUsedData: ITOCEntry = { id: 'commonlyUsed', label: localize('commonlyUsed', "Commonly Used"), - settings: ['files.autoSave', 'editor.fontSize', 'editor.fontFamily', 'editor.tabSize', 'editor.renderWhitespace', 'editor.cursorStyle', 'editor.multiCursorModifier', 'editor.insertSpaces', 'editor.wordWrap', 'files.exclude', 'files.associations'] + settings: ['files.autoSave', 'editor.fontSize', 'editor.fontFamily', 'editor.tabSize', 'editor.renderWhitespace', 'editor.cursorStyle', 'editor.multiCursorModifier', 'editor.insertSpaces', 'editor.wordWrap', 'files.exclude', 'files.associations', 'workbench.editor.enablePreview'] }; -export const tocData: ITOCEntry = { +export const tocData: ITOCEntry = { id: 'root', label: 'root', children: [ diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 2618f9f41..3b26e64a1 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -38,7 +38,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { editorBackground, errorForeground, focusBorder, foreground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry'; import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler, attachStyler } from 'vs/platform/theme/common/styler'; -import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; import { ITOCEntry } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; import { ISettingsEditorViewState, settingKeyToDisplayFormat, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeNewExtensionsElement, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; @@ -55,6 +55,8 @@ import { IList } from 'vs/base/browser/ui/tree/indexTreeModel'; import { IListService, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { ILogService } from 'vs/platform/log/common/log'; +import { settingsMoreActionIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons'; const $ = DOM.$; @@ -257,15 +259,15 @@ function getListDisplayValue(element: SettingsTreeSettingElement): IListDataItem }); } -export function resolveSettingsTree(tocData: ITOCEntry, coreSettingsGroups: ISettingsGroup[]): { tree: ITOCEntry, leftoverSettings: Set } { +export function resolveSettingsTree(tocData: ITOCEntry, coreSettingsGroups: ISettingsGroup[], logService: ILogService): { tree: ITOCEntry, leftoverSettings: Set } { const allSettings = getFlatSettings(coreSettingsGroups); return { - tree: _resolveSettingsTree(tocData, allSettings), + tree: _resolveSettingsTree(tocData, allSettings, logService), leftoverSettings: allSettings }; } -export function resolveExtensionsSettings(groups: ISettingsGroup[]): ITOCEntry { +export function resolveExtensionsSettings(groups: ISettingsGroup[]): ITOCEntry { const settingsGroupToEntry = (group: ISettingsGroup) => { const flatSettings = arrays.flatten( group.sections.map(section => section.settings)); @@ -288,17 +290,17 @@ export function resolveExtensionsSettings(groups: ISettingsGroup[]): ITOCEntry { }; } -function _resolveSettingsTree(tocData: ITOCEntry, allSettings: Set): ITOCEntry { - let children: ITOCEntry[] | undefined; +function _resolveSettingsTree(tocData: ITOCEntry, allSettings: Set, logService: ILogService): ITOCEntry { + let children: ITOCEntry[] | undefined; if (tocData.children) { children = tocData.children - .map(child => _resolveSettingsTree(child, allSettings)) + .map(child => _resolveSettingsTree(child, allSettings, logService)) .filter(child => (child.children && child.children.length) || (child.settings && child.settings.length)); } let settings: ISetting[] | undefined; if (tocData.settings) { - settings = arrays.flatten(tocData.settings.map(pattern => getMatchingSettings(allSettings, pattern))); + settings = arrays.flatten(tocData.settings.map(pattern => getMatchingSettings(allSettings, pattern, logService))); } if (!children && !settings) { @@ -313,7 +315,7 @@ function _resolveSettingsTree(tocData: ITOCEntry, allSettings: Set): I }; } -function getMatchingSettings(allSettings: Set, pattern: string): ISetting[] { +function getMatchingSettings(allSettings: Set, pattern: string, logService: ILogService): ISetting[] { const result: ISetting[] = []; allSettings.forEach(s => { @@ -323,17 +325,20 @@ function getMatchingSettings(allSettings: Set, pattern: string): ISett } }); + if (!result.length) { + logService.warn(`Settings pattern "${pattern}" doesn't match any settings`); + } return result.sort((a, b) => a.key.localeCompare(b.key)); } const settingPatternCache = new Map(); -function createSettingMatchRegExp(pattern: string): RegExp { +export function createSettingMatchRegExp(pattern: string): RegExp { pattern = escapeRegExpCharacters(pattern) .replace(/\\\*/g, '.*'); - return new RegExp(`^${pattern}`, 'i'); + return new RegExp(`^${pattern}$`, 'i'); } function settingMatches(s: ISetting, pattern: string): boolean { @@ -631,22 +636,12 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const toolbar = new ToolBar(container, this._contextMenuService, { toggleMenuTitle, - renderDropdownAsChildElement: true + renderDropdownAsChildElement: true, + moreIcon: ThemeIcon.asCSSIcon(settingsMoreActionIcon) // change icon from ellipsis to gear }); return toolbar; } - private fixToolbarIcon(toolbar: ToolBar): void { - const button = toolbar.getElement().querySelector('.codicon-toolbar-more'); - if (button) { - (button).tabIndex = 0; - - // change icon from ellipsis to gear - (button).classList.add('codicon-gear'); - (button).classList.remove('codicon-toolbar-more'); - } - } - protected renderSettingElement(node: ITreeNode, index: number, template: ISettingItemTemplate | ISettingBoolItemTemplate): void { const element = node.element; template.context = element; @@ -654,7 +649,6 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const actions = this.disposableActionFactory(element.setting); actions.forEach(a => template.elementDisposables?.add(a)); template.toolbar.setActions([], [...this.settingActions, ...actions]); - this.fixToolbarIcon(template.toolbar); const setting = element.setting; @@ -1332,7 +1326,8 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre const description = (enumDescriptions && enumDescriptions[index] && (enumDescriptionsAreMarkdown ? fixSettingLinks(enumDescriptions[index], false) : enumDescriptions[index])); return { text: enumItemLabels && enumItemLabels[index] ? enumItemLabels[index] : data, - description: enumItemLabels && enumItemLabels[index] ? `[${data}] ${description}` : description, + detail: enumItemLabels && enumItemLabels[index] ? data : '', + description, descriptionIsMarkdown: enumDescriptionsAreMarkdown, descriptionMarkdownActionHandler: { callback: (content) => { @@ -1897,8 +1892,11 @@ export class SettingsTree extends WorkbenchObjectTree { // applying an opacity to the link color. const fgWithOpacity = new Color(new RGBA(foregroundColor.rgba.r, foregroundColor.rgba.g, foregroundColor.rgba.b, 0.9)); collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description { color: ${fgWithOpacity}; }`); - collector.addRule(`.settings-editor > .settings-body .settings-toc-container .monaco-list-row:not(.selected) { color: ${fgWithOpacity}; }`); + + // Hack for subpixel antialiasing + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-title .setting-item-overrides, + .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-title .setting-item-ignored { color: ${fgWithOpacity}; }`); } const errorColor = theme.getColor(errorForeground); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index 12a988e55..e2f231a83 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as arrays from 'vs/base/common/arrays'; -import { isFalsyOrWhitespace } from 'vs/base/common/strings'; +import { escapeRegExpCharacters, isFalsyOrWhitespace } from 'vs/base/common/strings'; import { isArray, withUndefinedAsNull, isUndefinedOrNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { ConfigurationTarget, IConfigurationService, IConfigurationValue } from 'vs/platform/configuration/common/configuration'; import { SettingsTarget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; -import { ITOCEntry, knownAcronyms, knownTermMappings } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; +import { ITOCEntry, knownAcronyms, knownTermMappings, tocData } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; import { MODIFIED_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; import { IExtensionSetting, ISearchResult, ISetting, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -25,6 +25,8 @@ export interface ISettingsEditorViewState { settingsTarget: SettingsTarget; tagFilters?: Set; extensionFilters?: Set; + featureFilters?: Set; + idFilters?: Set; filterToCategory?: SettingsTreeGroupElement; } @@ -288,12 +290,49 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { return Array.from(extensionFilters).some(extensionId => extensionId.toLowerCase() === this.setting.extensionInfo!.id.toLowerCase()); } + + matchesAnyFeature(featureFilters?: Set): boolean { + if (!featureFilters || !featureFilters.size) { + return true; + } + + const features = tocData.children!.find(child => child.id === 'features'); + + return Array.from(featureFilters).some(filter => { + if (features && features.children) { + const feature = features.children.find(feature => 'features/' + filter === feature.id); + if (feature) { + const patterns = feature.settings?.map(setting => createSettingMatchRegExp(setting)); + return patterns && !this.setting.extensionInfo && patterns.some(pattern => pattern.test(this.setting.key.toLowerCase())); + } else { + return false; + } + } else { + return false; + } + }); + } + + matchesAnyId(idFilters?: Set): boolean { + if (!idFilters || !idFilters.size) { + return true; + } + return idFilters.has(this.setting.key); + } +} + + +function createSettingMatchRegExp(pattern: string): RegExp { + pattern = escapeRegExpCharacters(pattern) + .replace(/\\\*/g, '.*'); + + return new RegExp(`^${pattern}$`, 'i'); } export class SettingsTreeModel { protected _root!: SettingsTreeGroupElement; private _treeElementsBySettingName = new Map(); - private _tocRoot!: ITOCEntry; + private _tocRoot!: ITOCEntry; constructor( protected _viewState: ISettingsEditorViewState, @@ -349,14 +388,14 @@ export class SettingsTreeModel { }); } - private createSettingsTreeGroupElement(tocEntry: ITOCEntry, parent?: SettingsTreeGroupElement): SettingsTreeGroupElement { + private createSettingsTreeGroupElement(tocEntry: ITOCEntry, parent?: SettingsTreeGroupElement): SettingsTreeGroupElement { const depth = parent ? this.getDepth(parent) + 1 : 0; const element = new SettingsTreeGroupElement(tocEntry.id, undefined, tocEntry.label, depth, false); const children: SettingsTreeGroupChild[] = []; if (tocEntry.settings) { - const settingChildren = tocEntry.settings.map(s => this.createSettingsTreeSettingElement(s, element)) + const settingChildren = tocEntry.settings.map(s => this.createSettingsTreeSettingElement(s, element)) .filter(el => el.setting.deprecationMessage ? el.isConfigured : true); children.push(...settingChildren); } @@ -608,8 +647,9 @@ export class SearchResultModel extends SettingsTreeModel { // Save time, filter children in the search model instead of relying on the tree filter, which still requires heights to be calculated. const isRemote = !!this.environmentService.remoteAuthority; + this.root.children = this.root.children - .filter(child => child instanceof SettingsTreeSettingElement && child.matchesAllTags(this._viewState.tagFilters) && child.matchesScope(this._viewState.settingsTarget, isRemote) && child.matchesAnyExtension(this._viewState.extensionFilters)); + .filter(child => child instanceof SettingsTreeSettingElement && child.matchesAllTags(this._viewState.tagFilters) && child.matchesScope(this._viewState.settingsTarget, isRemote) && child.matchesAnyExtension(this._viewState.extensionFilters) && child.matchesAnyId(this._viewState.idFilters) && (this.containsValidFeature() ? child.matchesAnyFeature(this._viewState.featureFilters) : true)); if (this.newExtensionSearchResults && this.newExtensionSearchResults.filterMatches.length) { const resultExtensionIds = this.newExtensionSearchResults.filterMatches @@ -623,6 +663,22 @@ export class SearchResultModel extends SettingsTreeModel { } } + private containsValidFeature(): boolean { + if (!this._viewState.featureFilters || !this._viewState.featureFilters.size || !tocData.children) { + return false; + } + + const features = tocData.children.find(child => child.id === 'features'); + + if (features && features.children) { + return Array.from(this._viewState.featureFilters).some(filter => { + return features.children?.find(feature => 'features/' + filter === feature.id); + }); + } else { + return false; + } + } + private getFlatSettings(): ISetting[] { const flatSettings: ISetting[] = []; arrays.coalesce(this.getUniqueResults()) @@ -639,13 +695,19 @@ export interface IParsedQuery { tags: string[]; query: string; extensionFilters: string[]; + idFilters: string[]; + featureFilters: string[]; } const tagRegex = /(^|\s)@tag:("([^"]*)"|[^"]\S*)/g; const extensionRegex = /(^|\s)@ext:("([^"]*)"|[^"]\S*)?/g; +const featureRegex = /(^|\s)@feature:("([^"]*)"|[^"]\S*)?/g; +const idRegex = /(^|\s)@id:("([^"]*)"|[^"]\S*)?/g; export function parseQuery(query: string): IParsedQuery { const tags: string[] = []; const extensions: string[] = []; + const features: string[] = []; + const ids: string[] = []; query = query.replace(tagRegex, (_, __, quotedTag, tag) => { tags.push(tag || quotedTag); return ''; @@ -664,11 +726,29 @@ export function parseQuery(query: string): IParsedQuery { return ''; }); + query = query.replace(featureRegex, (_, __, quotedFeature, feature) => { + const featureQuery: string = feature || quotedFeature; + if (featureQuery) { + features.push(...featureQuery.split(',').map(s => s.trim()).filter(s => !isFalsyOrWhitespace(s))); + } + return ''; + }); + + query = query.replace(idRegex, (_, __, quotedId, id) => { + const idRegex: string = id || quotedId; + if (idRegex) { + ids.push(...idRegex.split(',').map(s => s.trim()).filter(s => !isFalsyOrWhitespace(s))); + } + return ''; + }); + query = query.trim(); return { tags, extensionFilters: extensions, + featureFilters: features, + idFilters: ids, query }; } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts index 944f05ef4..9994ccc1b 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts @@ -23,8 +23,8 @@ import { localize } from 'vs/nls'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { editorWidgetBorder, focusBorder, foreground, inputBackground, inputBorder, inputForeground, listActiveSelectionBackground, listActiveSelectionForeground, listFocusBackground, listHoverBackground, listHoverForeground, listInactiveSelectionBackground, listInactiveSelectionForeground, registerColor, selectBackground, selectBorder, selectForeground, simpleCheckboxBackground, simpleCheckboxBorder, simpleCheckboxForeground, textLinkActiveForeground, textLinkForeground, textPreformatForeground, transparent } from 'vs/platform/theme/common/colorRegistry'; import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; -import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { preferencesEditIcon } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; +import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { settingsDiscardIcon, settingsEditIcon, settingsRemoveIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons'; const $ = DOM.$; export const settingsHeaderForeground = registerColor('settings.headerForeground', { light: '#444444', dark: '#e7e7e7', hc: '#ffffff' }, localize('headerForeground', "The foreground color for a section header or active title.")); @@ -496,14 +496,14 @@ export class ListSettingWidget extends AbstractListSettingWidget protected getActionsForItem(item: IListDataItem, idx: number): IAction[] { return [ { - class: preferencesEditIcon.classNames, + class: ThemeIcon.asClassName(settingsEditIcon), enabled: true, id: 'workbench.action.editListItem', tooltip: this.getLocalizedStrings().editActionTooltip, run: () => this.editSetting(idx) }, { - class: 'codicon-close', + class: ThemeIcon.asClassName(settingsRemoveIcon), enabled: true, id: 'workbench.action.removeListItem', tooltip: this.getLocalizedStrings().deleteActionTooltip, @@ -740,7 +740,7 @@ export class ObjectSettingWidget extends AbstractListSettingWidget { styleController: id => new DefaultStyleController(DOM.createStyleSheet(container), id), accessibilityProvider: instantiationService.createInstance(SettingsAccessibilityProvider), collapseByDefault: true, - horizontalScrolling: false + horizontalScrolling: false, + hideTwistiesOfChildlessElements: true }; super( diff --git a/src/vs/workbench/contrib/preferences/common/preferences.ts b/src/vs/workbench/contrib/preferences/common/preferences.ts index 62f57e04a..928106092 100644 --- a/src/vs/workbench/contrib/preferences/common/preferences.ts +++ b/src/vs/workbench/contrib/preferences/common/preferences.ts @@ -6,10 +6,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ISettingsEditorModel, ISearchResult } from 'vs/workbench/services/preferences/common/preferences'; -import { IEditorPane } from 'vs/workbench/common/editor'; -import { IKeybindingItemEntry } from 'vs/workbench/services/preferences/common/keybindingsEditorModel'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { Event } from 'vs/base/common/event'; export interface IWorkbenchSettingsConfiguration { workbench: { @@ -43,29 +40,6 @@ export interface ISearchProvider { searchModel(preferencesModel: ISettingsEditorModel, token?: CancellationToken): Promise; } -export interface IKeybindingsEditorPane extends IEditorPane { - - readonly activeKeybindingEntry: IKeybindingItemEntry | null; - readonly onDefineWhenExpression: Event; - readonly onLayout: Event; - - search(filter: string): void; - focusSearch(): void; - clearSearchResults(): void; - focusKeybindings(): void; - recordSearchKeys(): void; - toggleSortByPrecedence(): void; - selectKeybinding(keybindingEntry: IKeybindingItemEntry): void; - defineKeybinding(keybindingEntry: IKeybindingItemEntry): Promise; - defineWhenExpression(keybindingEntry: IKeybindingItemEntry): void; - updateKeybinding(keybindingEntry: IKeybindingItemEntry, key: string, when: string | undefined): Promise; - removeKeybinding(keybindingEntry: IKeybindingItemEntry): Promise; - resetKeybinding(keybindingEntry: IKeybindingItemEntry): Promise; - copyKeybinding(keybindingEntry: IKeybindingItemEntry): Promise; - copyKeybindingCommand(keybindingEntry: IKeybindingItemEntry): Promise; - showSimilarKeybindings(keybindingEntry: IKeybindingItemEntry): void; -} - export const SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS = 'settings.action.clearSearchResults'; export const SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU = 'settings.action.showContextMenu'; @@ -83,6 +57,7 @@ export const KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS = 'keybindings.edit export const KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS = 'keybindings.editor.recordSearchKeys'; export const KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE = 'keybindings.editor.toggleSortByPrecedence'; export const KEYBINDINGS_EDITOR_COMMAND_DEFINE = 'keybindings.editor.defineKeybinding'; +export const KEYBINDINGS_EDITOR_COMMAND_ADD = 'keybindings.editor.addKeybinding'; export const KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN = 'keybindings.editor.defineWhenExpression'; export const KEYBINDINGS_EDITOR_COMMAND_REMOVE = 'keybindings.editor.removeKeybinding'; export const KEYBINDINGS_EDITOR_COMMAND_RESET = 'keybindings.editor.resetKeybinding'; @@ -95,5 +70,6 @@ export const KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS = 'keybindings.editor.show export const MODIFIED_SETTING_TAG = 'modified'; export const EXTENSION_SETTING_TAG = 'ext:'; - +export const FEATURE_SETTING_TAG = 'feature:'; +export const ID_SETTING_TAG = 'id:'; export const KEYBOARD_LAYOUT_OPEN_PICKER = 'workbench.action.openKeyboardLayoutPicker'; diff --git a/src/vs/workbench/contrib/preferences/test/browser/settingsTreeModels.test.ts b/src/vs/workbench/contrib/preferences/test/browser/settingsTreeModels.test.ts index 8113f3d37..b2ae0a9e8 100644 --- a/src/vs/workbench/contrib/preferences/test/browser/settingsTreeModels.test.ts +++ b/src/vs/workbench/contrib/preferences/test/browser/settingsTreeModels.test.ts @@ -147,7 +147,9 @@ suite('SettingsTree', () => { { tags: [], extensionFilters: [], - query: '' + query: '', + featureFilters: [], + idFilters: [] }); testParseQuery( @@ -155,7 +157,9 @@ suite('SettingsTree', () => { { tags: ['modified'], extensionFilters: [], - query: '' + query: '', + featureFilters: [], + idFilters: [] }); testParseQuery( @@ -163,7 +167,9 @@ suite('SettingsTree', () => { { tags: ['foo'], extensionFilters: [], - query: '' + query: '', + featureFilters: [], + idFilters: [] }); testParseQuery( @@ -171,7 +177,9 @@ suite('SettingsTree', () => { { tags: ['modified'], extensionFilters: [], - query: 'foo' + query: 'foo', + featureFilters: [], + idFilters: [] }); testParseQuery( @@ -179,7 +187,9 @@ suite('SettingsTree', () => { { tags: ['foo', 'modified'], extensionFilters: [], - query: '' + query: '', + featureFilters: [], + idFilters: [] }); testParseQuery( @@ -187,7 +197,9 @@ suite('SettingsTree', () => { { tags: ['foo', 'modified'], extensionFilters: [], - query: 'my query' + query: 'my query', + featureFilters: [], + idFilters: [] }); testParseQuery( @@ -195,7 +207,9 @@ suite('SettingsTree', () => { { tags: ['modified'], extensionFilters: [], - query: 'test query' + query: 'test query', + featureFilters: [], + idFilters: [] }); testParseQuery( @@ -203,7 +217,9 @@ suite('SettingsTree', () => { { tags: ['modified'], extensionFilters: [], - query: 'test' + query: 'test', + featureFilters: [], + idFilters: [] }); testParseQuery( @@ -211,7 +227,9 @@ suite('SettingsTree', () => { { tags: [], extensionFilters: [], - query: 'query has @ for some reason' + query: 'query has @ for some reason', + featureFilters: [], + idFilters: [] }); testParseQuery( @@ -219,7 +237,9 @@ suite('SettingsTree', () => { { tags: [], extensionFilters: ['github.vscode-pull-request-github'], - query: '' + query: '', + featureFilters: [], + idFilters: [] }); testParseQuery( @@ -227,7 +247,47 @@ suite('SettingsTree', () => { { tags: [], extensionFilters: ['github.vscode-pull-request-github', 'vscode.git'], - query: '' + query: '', + featureFilters: [], + idFilters: [] + }); + testParseQuery( + '@feature:scm', + { + tags: [], + extensionFilters: [], + featureFilters: ['scm'], + query: '', + idFilters: [] + }); + + testParseQuery( + '@feature:scm,terminal', + { + tags: [], + extensionFilters: [], + featureFilters: ['scm', 'terminal'], + query: '', + idFilters: [] + }); + testParseQuery( + '@id:files.autoSave', + { + tags: [], + extensionFilters: [], + featureFilters: [], + query: '', + idFilters: ['files.autoSave'] + }); + + testParseQuery( + '@id:files.autoSave,terminal.integrated.commandsToSkipShell', + { + tags: [], + extensionFilters: [], + featureFilters: [], + query: '', + idFilters: ['files.autoSave', 'terminal.integrated.commandsToSkipShell'] }); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts index d24b9139c..d742a1d28 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @@ -22,12 +22,14 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { DefaultQuickAccessFilterValue } from 'vs/platform/quickinput/common/quickAccess'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchQuickAccessConfiguration } from 'vs/workbench/browser/quickaccess'; -import { stripCodicons } from 'vs/base/common/codicons'; +import { Codicon, stripCodicons } from 'vs/base/common/codicons'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider { @@ -60,7 +62,8 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce @ITelemetryService telemetryService: ITelemetryService, @INotificationService notificationService: INotificationService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, + @IPreferencesService private readonly preferencesService: IPreferencesService, ) { super({ showAlias: !Language.isDefaultVariant(), @@ -91,7 +94,17 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce return [ ...this.getCodeEditorCommandPicks(), ...this.getGlobalCommandPicks(disposables) - ]; + ].map(c => ({ + ...c, + buttons: [{ + iconClass: Codicon.gear.classNames, + tooltip: localize('configure keybinding', "Configure Keybinding"), + }], + trigger: (): TriggerAction => { + this.preferencesService.openGlobalKeybindingSettings(false, { query: `@command:${c.commandId}` }); + return TriggerAction.CLOSE_PICKER; + }, + })); } private getGlobalCommandPicks(disposables: DisposableStore): ICommandQuickPick[] { @@ -100,7 +113,7 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce const globalCommandsMenu = this.menuService.createMenu(MenuId.CommandPalette, scopedContextKeyService); const globalCommandsMenuActions = globalCommandsMenu.getActions() .reduce((r, [, actions]) => [...r, ...actions], >[]) - .filter(action => action instanceof MenuItemAction) as MenuItemAction[]; + .filter(action => action instanceof MenuItemAction && action.enabled) as MenuItemAction[]; for (const action of globalCommandsMenuActions) { diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index 386eb37bb..c5a10561f 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -36,7 +36,7 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo private updateMode: string | undefined; private debugConsoleWordWrap: boolean | undefined; private accessibilitySupport: 'on' | 'off' | 'auto' | undefined; - private enableExperimentalProxyLoginDialog: boolean | undefined; + private enableExperimentalProxyLoginDialog = true; // default constructor( @IHostService private readonly hostService: IHostService, diff --git a/src/vs/workbench/contrib/remote/browser/media/tunnelView.css b/src/vs/workbench/contrib/remote/browser/media/tunnelView.css index 659c87549..55776ca99 100644 --- a/src/vs/workbench/contrib/remote/browser/media/tunnelView.css +++ b/src/vs/workbench/contrib/remote/browser/media/tunnelView.css @@ -3,6 +3,30 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.customview-tree .tunnel-view-label { +.pane-body:not(.wide) .ports-view .monaco-icon-label, +.pane-body:not(.wide) .ports-view .monaco-icon-label { flex: 1; } + +.pane-body:not(.wide) .ports-view .monaco-list .monaco-list-row:hover:not(.highlighted) .monaco-icon-label, +.pane-body:not(.wide) .ports-view .monaco-list .monaco-list-row.focused .monaco-icon-label { + flex: 1; +} + +.pane-body.wide .ports-view .monaco-list .monaco-list-row .actionBarContainer, +.ports-view .monaco-list .monaco-list-row .actionBarContainer { + flex: 1 0 auto; +} + +.pane-body:not(.wide) .ports-view .monaco-list .monaco-list-row .actionBarContainer { + flex: 0 0 auto; +} + +.ports-view.actions-right .monaco-list .monaco-list-row .actionBarContainer, +.pane-body:not(.wide) .ports-view .monaco-list .monaco-list-row .actionBarContainer { + text-align: right; +} + +.pane-body.wide .ports-view .monaco-action-bar { + margin-left: 10px; +} diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index c6d98b601..29ab926a3 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -13,7 +13,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IStorageService } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { FilterViewPaneContainer } from 'vs/workbench/browser/parts/views/viewsViewlet'; @@ -54,7 +54,8 @@ import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/e import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { RemoteStatusIndicator } from 'vs/workbench/contrib/remote/browser/remoteIndicator'; import { inQuickPickContextKeyValue } from 'vs/workbench/browser/quickaccess'; -import { Codicon, registerIcon } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; export interface HelpInformation { extensionDescription: IExtensionDescription; @@ -135,12 +136,12 @@ class HelpTreeRenderer implements ITreeRenderer { - hasChildren(element: any) { +class HelpDataSource implements IAsyncDataSource { + hasChildren(element: HelpModel) { return element instanceof HelpModel; } - getChildren(element: any) { + getChildren(element: HelpModel) { if (element instanceof HelpModel && element.items) { return element.items; } @@ -149,14 +150,15 @@ class HelpDataSource implements IAsyncDataSource { } } -const getStartedIcon = registerIcon('remote-explorer-get-started', Codicon.star); -const documentationIcon = registerIcon('remote-explorer-documentation', Codicon.book); -const feedbackIcon = registerIcon('remote-explorer-feedback', Codicon.twitter); -const reviewIssuesIcon = registerIcon('remote-explorer-review-issues', Codicon.issues); -const reportIssuesIcon = registerIcon('remote-explorer-report-issues', Codicon.comment); +const getStartedIcon = registerIcon('remote-explorer-get-started', Codicon.star, nls.localize('getStartedIcon', 'Getting started icon in the remote explorer view.')); +const documentationIcon = registerIcon('remote-explorer-documentation', Codicon.book, nls.localize('documentationIcon', 'Documentation icon in the remote explorer view.')); +const feedbackIcon = registerIcon('remote-explorer-feedback', Codicon.twitter, nls.localize('feedbackIcon', 'Feedback icon in the remote explorer view.')); +const reviewIssuesIcon = registerIcon('remote-explorer-review-issues', Codicon.issues, nls.localize('reviewIssuesIcon', 'Review issue icon in the remote explorer view.')); +const reportIssuesIcon = registerIcon('remote-explorer-report-issues', Codicon.comment, nls.localize('reportIssuesIcon', 'Report issue icon in the remote explorer view.')); +const remoteExplorerViewIcon = registerIcon('remote-explorer-view-icon', Codicon.remoteExplorer, nls.localize('remoteExplorerViewIcon', 'View icon of the remote explorer view.')); interface IHelpItem { - icon: Codicon, + icon: ThemeIcon, iconClasses: string[]; label: string; handleClick(): Promise; @@ -297,14 +299,14 @@ class HelpItemValue { abstract class HelpItemBase implements IHelpItem { public iconClasses: string[] = []; constructor( - public icon: Codicon, + public icon: ThemeIcon, public label: string, public values: HelpItemValue[], private quickInputService: IQuickInputService, private environmentService: IWorkbenchEnvironmentService, private remoteExplorerService: IRemoteExplorerService ) { - this.iconClasses.push(...icon.classNamesArray); + this.iconClasses.push(...ThemeIcon.asClassNameArray(icon)); this.iconClasses.push('remote-help-tree-node-item-icon'); } @@ -351,7 +353,7 @@ abstract class HelpItemBase implements IHelpItem { class HelpItem extends HelpItemBase { constructor( - icon: Codicon, + icon: ThemeIcon, label: string, values: HelpItemValue[], quickInputService: IQuickInputService, @@ -369,7 +371,7 @@ class HelpItem extends HelpItemBase { class IssueReporterItem extends HelpItemBase { constructor( - icon: Codicon, + icon: ThemeIcon, label: string, values: HelpItemValue[], quickInputService: IQuickInputService, @@ -388,7 +390,7 @@ class IssueReporterItem extends HelpItemBase { class HelpPanel extends ViewPane { static readonly ID = '~remote.helpPanel'; static readonly TITLE = nls.localize('remote.help', "Help and feedback"); - private tree!: WorkbenchAsyncDataTree; + private tree!: WorkbenchAsyncDataTree; constructor( protected viewModel: IViewModel, @@ -418,7 +420,7 @@ class HelpPanel extends ViewPane { treeContainer.classList.add('remote-help-content'); container.appendChild(treeContainer); - this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, + this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'RemoteHelp', treeContainer, new HelpTreeVirtualDelegate(), @@ -439,7 +441,7 @@ class HelpPanel extends ViewPane { this.tree.setInput(model); this._register(Event.debounce(this.tree.onDidOpen, (last, event) => event, 75, true)(e => { - e.element.handleClick(); + e.element?.handleClick(); })); } @@ -572,7 +574,7 @@ Registry.as(Extensions.ViewContainersRegistry).register matches = /^details(@(\d+))?$/.exec(group); if (matches) { - return -500; + return -500 + Number(matches[2]); } matches = /^help(@(\d+))?$/.exec(group); @@ -583,7 +585,7 @@ Registry.as(Extensions.ViewContainersRegistry).register return; } }, - icon: 'codicon-remote-explorer', + icon: remoteExplorerViewIcon, order: 4 }, ViewContainerLocation.Sidebar); diff --git a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts index 4eb693366..aaabe66b0 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts @@ -5,9 +5,9 @@ import * as nls from 'vs/nls'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { Extensions, IViewDescriptorService, IViewsRegistry, IViewsService } from 'vs/workbench/common/views'; -import { IRemoteExplorerService, MakeAddress, mapHasTunnelLocalhostOrAllInterfaces, TUNNEL_VIEW_ID } from 'vs/workbench/services/remote/common/remoteExplorerService'; -import { forwardedPortsViewEnabled, ForwardPortAction, OpenPortInBrowserAction, TunnelPanelDescriptor, TunnelViewModel } from 'vs/workbench/contrib/remote/browser/tunnelView'; +import { Extensions, IViewContainersRegistry, IViewDescriptorService, IViewsRegistry, IViewsService, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IRemoteExplorerService, makeAddress, mapHasAddressLocalhostOrAllInterfaces, TUNNEL_VIEW_ID } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { PORT_AUTO_FORWARD_SETTING, forwardedPortsViewEnabled, ForwardPortAction, OpenPortInBrowserAction, TunnelPanel, TunnelPanelDescriptor, TunnelViewModel } from 'vs/workbench/contrib/remote/browser/tunnelView'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -15,25 +15,40 @@ import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarA import { UrlFinder } from 'vs/workbench/contrib/remote/browser/urlFinder'; import Severity from 'vs/base/common/severity'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { INotificationService, IPromptChoice } from 'vs/platform/notification/common/notification'; +import { INotificationHandle, INotificationService, IPromptChoice } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { OperatingSystem } from 'vs/base/common/platform'; +import { RemoteTunnel } from 'vs/platform/remote/common/tunnel'; +import { Codicon } from 'vs/base/common/codicons'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; +import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService'; +import { optional } from 'vs/platform/instantiation/common/instantiation'; export const VIEWLET_ID = 'workbench.view.remote'; export class ForwardedPortsView extends Disposable implements IWorkbenchContribution { private contextKeyListener?: IDisposable; + private _activityBadge?: IDisposable; private entryAccessor: IStatusbarEntryAccessor | undefined; + private readonly tasExperimentService: ITASExperimentService | undefined; constructor( @IContextKeyService private readonly contextKeyService: IContextKeyService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService, @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, - @IStatusbarService private readonly statusbarService: IStatusbarService + @IActivityService private readonly activityService: IActivityService, + @IStatusbarService private readonly statusbarService: IStatusbarService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @optional(ITASExperimentService) tasExperimentService: ITASExperimentService, ) { super(); + this.tasExperimentService = tasExperimentService; this._register(Registry.as(Extensions.ViewsRegistry).registerViewWelcomeContent(TUNNEL_VIEW_ID, { content: `Forwarded ports allow you to access your running services locally.\n[Forward a Port](command:${ForwardPortAction.INLINE_ID})`, })); @@ -41,17 +56,41 @@ export class ForwardedPortsView extends Disposable implements IWorkbenchContribu this.enableForwardedPortsView(); } - private enableForwardedPortsView() { + private async usePanelTreatment(): Promise { + if (this.tasExperimentService) { + return !!(await this.tasExperimentService.getTreatment('portspanel')); + } + return false; + } + + private async getViewContainer(): Promise { + if (await this.usePanelTreatment()) { + return Registry.as(Extensions.ViewContainersRegistry).registerViewContainer({ + id: TunnelPanel.ID, + name: nls.localize('ports', "Ports"), + icon: Codicon.plug, + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [TunnelPanel.ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + storageId: TunnelPanel.ID, + hideIfEmpty: true, + order: 5 + }, ViewContainerLocation.Panel); + } else { + return this.viewDescriptorService.getViewContainerById(VIEWLET_ID); + } + } + + private async enableForwardedPortsView() { if (this.contextKeyListener) { this.contextKeyListener.dispose(); this.contextKeyListener = undefined; } const viewEnabled: boolean = !!forwardedPortsViewEnabled.getValue(this.contextKeyService); + if (this.environmentService.remoteAuthority && viewEnabled) { - const tunnelPanelDescriptor = new TunnelPanelDescriptor(new TunnelViewModel(this.remoteExplorerService), this.environmentService); + const viewContainer = await this.getViewContainer(); + const tunnelPanelDescriptor = new TunnelPanelDescriptor(new TunnelViewModel(this.remoteExplorerService, this.configurationService), this.environmentService); const viewsRegistry = Registry.as(Extensions.ViewsRegistry); - const viewContainer = this.viewDescriptorService.getViewContainerById(VIEWLET_ID); if (viewContainer) { viewsRegistry.registerViews([tunnelPanelDescriptor!], viewContainer); } @@ -68,18 +107,38 @@ export class ForwardedPortsView extends Disposable implements IWorkbenchContribu const disposable = Registry.as(Extensions.ViewsRegistry).onViewsRegistered(e => { if (e.find(view => view.views.find(viewDescriptor => viewDescriptor.id === TUNNEL_VIEW_ID))) { this._register(this.remoteExplorerService.tunnelModel.onForwardPort(() => { + this.updateActivityBadge(); this.updateStatusBar(); })); this._register(this.remoteExplorerService.tunnelModel.onClosePort(() => { + this.updateActivityBadge(); this.updateStatusBar(); })); + this.updateActivityBadge(); this.updateStatusBar(); disposable.dispose(); } }); } + private async updateActivityBadge() { + if (this._activityBadge) { + this._activityBadge.dispose(); + } + if (!(await this.usePanelTreatment())) { + return; + } + if (this.remoteExplorerService.tunnelModel.forwarded.size > 0) { + const viewContainer = this.viewDescriptorService.getViewContainerByViewId(TUNNEL_VIEW_ID); + if (viewContainer) { + this._activityBadge = this.activityService.showViewContainerActivity(viewContainer.id, { + badge: new NumberBadge(this.remoteExplorerService.tunnelModel.forwarded.size, n => n === 1 ? nls.localize('1forwardedPort', "1 forwarded port") : nls.localize('nForwardedPorts', "{0} forwarded ports", n)) + }); + } + } + } + private updateStatusBar() { if (!this.entryAccessor) { this._register(this.entryAccessor = this.statusbarService.addEntry(this.entry, 'status.forwardedPorts', nls.localize('status.forwardedPorts', "Forwarded Ports"), StatusbarAlignment.LEFT, 40)); @@ -92,18 +151,13 @@ export class ForwardedPortsView extends Disposable implements IWorkbenchContribu let text: string; let tooltip: string; const count = this.remoteExplorerService.tunnelModel.forwarded.size + this.remoteExplorerService.tunnelModel.detected.size; + text = `${count}`; if (count === 0) { - text = nls.localize('remote.forwardedPorts.statusbarTextNone', "No Ports Available"); - tooltip = text; + tooltip = nls.localize('remote.forwardedPorts.statusbarTextNone', "No Ports Forwarded"); } else { - if (count === 1) { - text = nls.localize('remote.forwardedPorts.statusbarTextSingle', "1 Port Available"); - } else { - text = nls.localize('remote.forwardedPorts.statusbarTextMultiple', "{0} Ports Available", count); - } const allTunnels = Array.from(this.remoteExplorerService.tunnelModel.forwarded.values()); allTunnels.push(...Array.from(this.remoteExplorerService.tunnelModel.detected.values())); - tooltip = nls.localize('remote.forwardedPorts.statusbarTooltip', "Available Ports: {0}", + tooltip = nls.localize('remote.forwardedPorts.statusbarTooltip', "Forwarded Ports: {0}", allTunnels.map(forwarded => forwarded.remotePort).join(', ')); } return { @@ -117,40 +171,134 @@ export class ForwardedPortsView extends Disposable implements IWorkbenchContribu export class AutomaticPortForwarding extends Disposable implements IWorkbenchContribution { - private contextServiceListener?: IDisposable; - private urlFinder?: UrlFinder; - private static AUTO_FORWARD_SETTING = 'remote.autoForwardPorts'; constructor( - @ITerminalService private readonly terminalService: ITerminalService, - @INotificationService private readonly notificationService: INotificationService, - @IOpenerService private readonly openerService: IOpenerService, - @IViewsService private readonly viewsService: IViewsService, - @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IDebugService private readonly debugService: IDebugService + @ITerminalService readonly terminalService: ITerminalService, + @INotificationService readonly notificationService: INotificationService, + @IOpenerService readonly openerService: IOpenerService, + @IViewsService readonly viewsService: IViewsService, + @IRemoteExplorerService readonly remoteExplorerService: IRemoteExplorerService, + @IWorkbenchEnvironmentService readonly environmentService: IWorkbenchEnvironmentService, + @IContextKeyService readonly contextKeyService: IContextKeyService, + @IConfigurationService readonly configurationService: IConfigurationService, + @IDebugService readonly debugService: IDebugService, + @IRemoteAgentService readonly remoteAgentService: IRemoteAgentService ) { super(); + if (!this.environmentService.remoteAuthority) { + return; + } + + remoteAgentService.getEnvironment().then(environment => { + if (environment?.os === OperatingSystem.Windows) { + this._register(new WindowsAutomaticPortForwarding(terminalService, notificationService, openerService, + remoteExplorerService, contextKeyService, configurationService, debugService)); + } else if (environment?.os === OperatingSystem.Linux) { + this._register(new LinuxAutomaticPortForwarding(configurationService, remoteExplorerService, notificationService, openerService, contextKeyService)); + } + }); + } +} + +class ForwardedPortNotifier extends Disposable { + private lastNotifyTime: Date; + private static COOL_DOWN = 5000; // milliseconds + private lastNotification: INotificationHandle | undefined; + + constructor(private readonly notificationService: INotificationService, + private readonly remoteExplorerService: IRemoteExplorerService, + private readonly openerService: IOpenerService) { + super(); + this.lastNotifyTime = new Date(); + this.lastNotifyTime.setFullYear(this.lastNotifyTime.getFullYear() - 1); + } + + public async notify(tunnels: RemoteTunnel[]) { + const tunnel = await this.portNumberHeuristicDelay(tunnels); + if (tunnel) { + if (Date.now() - this.lastNotifyTime.getTime() > ForwardedPortNotifier.COOL_DOWN) { + this.showNotification(tunnel); + } + } + } + + private newerTunnel: RemoteTunnel | undefined; + private async portNumberHeuristicDelay(tunnels: RemoteTunnel[]): Promise { + if (tunnels.length === 0) { + return; + } + tunnels = tunnels.sort((a, b) => a.tunnelRemotePort - b.tunnelRemotePort); + const firstTunnel = tunnels.shift()!; + // Heuristic. + if (firstTunnel.tunnelRemotePort % 1000 === 0) { + this.newerTunnel = firstTunnel; + return firstTunnel; + // 9229 is the node inspect port + } else if (firstTunnel.tunnelRemotePort < 10000 && firstTunnel.tunnelRemotePort !== 9229) { + this.newerTunnel = firstTunnel; + return firstTunnel; + } + + this.newerTunnel = undefined; + return new Promise(resolve => { + setTimeout(() => { + if (this.newerTunnel) { + resolve(undefined); + } else { + resolve(firstTunnel); + } + }, 3000); + }); + } + + private showNotification(tunnel: RemoteTunnel) { + const address = makeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort); + const message = nls.localize('remote.tunnelsView.automaticForward', "Your service running on port {0} is available. [See all forwarded ports](command:{1}.focus)", + tunnel.tunnelRemotePort, TunnelPanel.ID); + const browserChoice: IPromptChoice = { + label: OpenPortInBrowserAction.LABEL, + run: () => OpenPortInBrowserAction.run(this.remoteExplorerService.tunnelModel, this.openerService, address) + }; + this.lastNotification = this.notificationService.prompt(Severity.Info, message, [browserChoice], { neverShowAgain: { id: 'remote.tunnelsView.autoForwardNeverShow', isSecondary: true } }); + this.lastNotifyTime = new Date(); + this.lastNotification.onDidClose(() => { + this.lastNotification = undefined; + }); + } +} + +class WindowsAutomaticPortForwarding extends Disposable { + private contextServiceListener?: IDisposable; + private urlFinder?: UrlFinder; + private notifier: ForwardedPortNotifier; + + constructor( + private readonly terminalService: ITerminalService, + readonly notificationService: INotificationService, + readonly openerService: IOpenerService, + private readonly remoteExplorerService: IRemoteExplorerService, + private readonly contextKeyService: IContextKeyService, + private readonly configurationService: IConfigurationService, + private readonly debugService: IDebugService + ) { + super(); + this.notifier = new ForwardedPortNotifier(notificationService, remoteExplorerService, openerService); this._register(configurationService.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration(AutomaticPortForwarding.AUTO_FORWARD_SETTING)) { + if (e.affectsConfiguration(PORT_AUTO_FORWARD_SETTING)) { this.tryStartStopUrlFinder(); } })); - if (this.environmentService.remoteAuthority) { - this.contextServiceListener = this._register(this.contextKeyService.onDidChangeContext(e => { - if (e.affectsSome(new Set(forwardedPortsViewEnabled.keys()))) { - this.tryStartStopUrlFinder(); - } - })); - this.tryStartStopUrlFinder(); - } + this.contextServiceListener = this._register(this.contextKeyService.onDidChangeContext(e => { + if (e.affectsSome(new Set(forwardedPortsViewEnabled.keys()))) { + this.tryStartStopUrlFinder(); + } + })); + this.tryStartStopUrlFinder(); } private tryStartStopUrlFinder() { - if (this.configurationService.getValue(AutomaticPortForwarding.AUTO_FORWARD_SETTING)) { + if (this.configurationService.getValue(PORT_AUTO_FORWARD_SETTING)) { this.startUrlFinder(); } else { this.stopUrlFinder(); @@ -166,31 +314,12 @@ export class AutomaticPortForwarding extends Disposable implements IWorkbenchCon } this.urlFinder = this._register(new UrlFinder(this.terminalService, this.debugService)); this._register(this.urlFinder.onDidMatchLocalUrl(async (localUrl) => { - if (mapHasTunnelLocalhostOrAllInterfaces(this.remoteExplorerService.tunnelModel.forwarded, localUrl.host, localUrl.port)) { + if (mapHasAddressLocalhostOrAllInterfaces(this.remoteExplorerService.tunnelModel.forwarded, localUrl.host, localUrl.port)) { return; } const forwarded = await this.remoteExplorerService.forward(localUrl); if (forwarded) { - const address = MakeAddress(forwarded.tunnelRemoteHost, forwarded.tunnelRemotePort); - const message = nls.localize('remote.tunnelsView.automaticForward', "Your service running on port {0} is available.", - forwarded.tunnelRemotePort); - const browserChoice: IPromptChoice = { - label: OpenPortInBrowserAction.LABEL, - run: () => OpenPortInBrowserAction.run(this.remoteExplorerService.tunnelModel, this.openerService, address) - }; - const showChoice: IPromptChoice = { - label: nls.localize('remote.tunnelsView.showView', "Show Forwarded Ports"), - run: () => { - const remoteAuthority = this.environmentService.remoteAuthority; - const explorerType: string[] | undefined = remoteAuthority ? [remoteAuthority.split('+')[0]] : undefined; - if (explorerType) { - this.remoteExplorerService.targetType = explorerType; - } - this.viewsService.openViewContainer(VIEWLET_ID); - }, - isSecondary: true - }; - this.notificationService.prompt(Severity.Info, message, [browserChoice, showChoice], { neverShowAgain: { id: 'remote.tunnelsView.autoForwardNeverShow', isSecondary: true } }); + this.notifier.notify([forwarded]); } })); } @@ -202,3 +331,114 @@ export class AutomaticPortForwarding extends Disposable implements IWorkbenchCon } } } + +class LinuxAutomaticPortForwarding extends Disposable { + private candidateListener: IDisposable | undefined; + private autoForwarded: Set = new Set(); + private notifier: ForwardedPortNotifier; + private initialCandidates: Set = new Set(); + private contextServiceListener: IDisposable | undefined; + + constructor( + private readonly configurationService: IConfigurationService, + readonly remoteExplorerService: IRemoteExplorerService, + readonly notificationService: INotificationService, + readonly openerService: IOpenerService, + readonly contextKeyService: IContextKeyService + ) { + super(); + this.notifier = new ForwardedPortNotifier(notificationService, remoteExplorerService, openerService); + this._register(configurationService.onDidChangeConfiguration(async (e) => { + if (e.affectsConfiguration(PORT_AUTO_FORWARD_SETTING)) { + await this.startStopCandidateListener(); + } + })); + + this.contextServiceListener = this._register(this.contextKeyService.onDidChangeContext(async (e) => { + if (e.affectsSome(new Set(forwardedPortsViewEnabled.keys()))) { + await this.startStopCandidateListener(); + } + })); + + this.startStopCandidateListener(); + } + + private async startStopCandidateListener() { + if (this.configurationService.getValue(PORT_AUTO_FORWARD_SETTING)) { + await this.startCandidateListener(); + } else { + this.stopCandidateListener(); + } + } + + private stopCandidateListener() { + if (this.candidateListener) { + this.candidateListener.dispose(); + this.candidateListener = undefined; + } + } + + private async startCandidateListener() { + if (this.candidateListener || !forwardedPortsViewEnabled.getValue(this.contextKeyService)) { + return; + } + if (this.contextServiceListener) { + this.contextServiceListener.dispose(); + } + + if (!this.remoteExplorerService.tunnelModel.environmentTunnelsSet) { + await new Promise(resolve => this.remoteExplorerService.tunnelModel.onEnvironmentTunnelsSet(() => resolve())); + } + + // Capture list of starting candidates so we don't auto forward them later. + this.setInitialCandidates(); + + this.candidateListener = this._register(this.remoteExplorerService.tunnelModel.onCandidatesChanged(this.handleCandidateUpdate, this)); + } + + private setInitialCandidates() { + this.remoteExplorerService.tunnelModel.candidates.forEach(async (value) => { + this.initialCandidates.add(makeAddress(value.host, value.port)); + }); + } + + private async forwardCandidates(): Promise { + const allTunnels = (await Promise.all(this.remoteExplorerService.tunnelModel.candidates.map(async (value) => { + const address = makeAddress(value.host, value.port); + if (this.initialCandidates.has(address)) { + return undefined; + } + if (mapHasAddressLocalhostOrAllInterfaces(this.remoteExplorerService.tunnelModel.detected, value.host, value.port)) { + return undefined; + } + if (mapHasAddressLocalhostOrAllInterfaces(this.remoteExplorerService.tunnelModel.forwarded, value.host, value.port)) { + return undefined; + } + const forwarded = await this.remoteExplorerService.forward(value); + if (forwarded) { + this.autoForwarded.add(address); + } + return forwarded; + }))).filter(tunnel => !!tunnel); + if (allTunnels.length === 0) { + return undefined; + } + return allTunnels; + } + + private async handleCandidateUpdate(removed: Map) { + removed.forEach((value, key) => { + if (this.autoForwarded.has(key)) { + this.remoteExplorerService.close(value); + this.autoForwarded.delete(key); + } else if (this.initialCandidates.has(key)) { + this.initialCandidates.delete(key); + } + }); + + const tunnels = await this.forwardCandidates(); + if (tunnels) { + this.notifier.notify(tunnels); + } + } +} diff --git a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts index 83e5499b9..f3ee1a1f6 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts @@ -23,21 +23,24 @@ import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remot import { IHostService } from 'vs/workbench/services/host/browser/host'; import { isWeb } from 'vs/base/common/platform'; import { once } from 'vs/base/common/functional'; +import { truncate } from 'vs/base/common/strings'; export class RemoteStatusIndicator extends Disposable implements IWorkbenchContribution { - private static REMOTE_ACTIONS_COMMAND_ID = 'workbench.action.remote.showMenu'; - private static CLOSE_REMOTE_COMMAND_ID = 'workbench.action.remote.close'; - private static SHOW_CLOSE_REMOTE_COMMAND_ID = !isWeb; // web does not have a "Close Remote" command + private static readonly REMOTE_ACTIONS_COMMAND_ID = 'workbench.action.remote.showMenu'; + private static readonly CLOSE_REMOTE_COMMAND_ID = 'workbench.action.remote.close'; + private static readonly SHOW_CLOSE_REMOTE_COMMAND_ID = !isWeb; // web does not have a "Close Remote" command + + private static readonly REMOTE_STATUS_LABEL_MAX_LENGTH = 40; private remoteStatusEntry: IStatusbarEntryAccessor | undefined; - private remoteMenu = this._register(this.menuService.createMenu(MenuId.StatusBarWindowIndicatorMenu, this.contextKeyService)); + private readonly remoteMenu = this._register(this.menuService.createMenu(MenuId.StatusBarWindowIndicatorMenu, this.contextKeyService)); private hasRemoteActions = false; - private remoteAuthority = this.environmentService.remoteAuthority; + private readonly remoteAuthority = this.environmentService.remoteAuthority; private connectionState: 'initializing' | 'connected' | 'disconnected' | undefined = undefined; - private connectionStateContextKey = new RawContextKey<'' | 'initializing' | 'disconnected' | 'connected'>('remoteConnectionState', '').bindTo(this.contextKeyService); + private readonly connectionStateContextKey = new RawContextKey<'' | 'initializing' | 'disconnected' | 'connected'>('remoteConnectionState', '').bindTo(this.contextKeyService); constructor( @IStatusbarService private readonly statusbarService: IStatusbarService, @@ -189,7 +192,7 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr // Remote indicator: show if provided via options const remoteIndicator = this.environmentService.options?.windowIndicator; if (remoteIndicator) { - this.renderRemoteStatusIndicator(remoteIndicator.label, remoteIndicator.tooltip, remoteIndicator.command); + this.renderRemoteStatusIndicator(truncate(remoteIndicator.label, RemoteStatusIndicator.REMOTE_STATUS_LABEL_MAX_LENGTH), remoteIndicator.tooltip, remoteIndicator.command); } // Remote Authority: show connection state @@ -200,10 +203,10 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr this.renderRemoteStatusIndicator(nls.localize('host.open', "Opening Remote..."), nls.localize('host.open', "Opening Remote..."), undefined, true /* progress */); break; case 'disconnected': - this.renderRemoteStatusIndicator(`$(alert) ${nls.localize('disconnectedFrom', "Disconnected from {0}", hostLabel)}`, nls.localize('host.tooltipDisconnected', "Disconnected from {0}", hostLabel)); + this.renderRemoteStatusIndicator(`$(alert) ${nls.localize('disconnectedFrom', "Disconnected from {0}", truncate(hostLabel, RemoteStatusIndicator.REMOTE_STATUS_LABEL_MAX_LENGTH))}`, nls.localize('host.tooltipDisconnected', "Disconnected from {0}", hostLabel)); break; default: - this.renderRemoteStatusIndicator(`$(remote) ${hostLabel}`, nls.localize('host.tooltip', "Editing on {0}", hostLabel)); + this.renderRemoteStatusIndicator(`$(remote) ${truncate(hostLabel, RemoteStatusIndicator.REMOTE_STATUS_LABEL_MAX_LENGTH)}`, nls.localize('host.tooltip', "Editing on {0}", hostLabel)); } } diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 235dce546..7ed9fa790 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -26,7 +26,7 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { ActionRunner, IAction } from 'vs/base/common/actions'; import { IMenuService, MenuId, IMenu, MenuRegistry, MenuItemAction, ILocalizedString, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { createAndFillInContextMenuActions, createAndFillInActionBarActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IRemoteExplorerService, TunnelModel, MakeAddress, TunnelType, ITunnelItem, Tunnel, mapHasTunnelLocalhostOrAllInterfaces, TUNNEL_VIEW_ID } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { IRemoteExplorerService, TunnelModel, makeAddress, TunnelType, ITunnelItem, Tunnel, mapHasAddressLocalhostOrAllInterfaces, TUNNEL_VIEW_ID, parseAddress } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; @@ -42,8 +42,10 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { Codicon } from 'vs/base/common/codicons'; export const forwardedPortsViewEnabled = new RawContextKey('forwardedPortsViewEnabled', false); +export const PORT_AUTO_FORWARD_SETTING = 'remote.autoForwardPorts'; class TunnelTreeVirtualDelegate implements IListVirtualDelegate { getHeight(element: ITunnelItem): number { @@ -61,7 +63,7 @@ export interface ITunnelViewModel { readonly detected: TunnelItem[]; readonly candidates: TunnelItem[]; readonly input: TunnelItem; - groups(): Promise; + groupsAndForwarded(): Promise; } export class TunnelViewModel extends Disposable implements ITunnelViewModel { @@ -72,7 +74,8 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { private _candidates: Map = new Map(); constructor( - @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService) { + @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService, + @IConfigurationService private readonly configurationService: IConfigurationService) { super(); this.model = remoteExplorerService.tunnelModel; this._register(this.model.onForwardPort(() => this._onForwardedPortsChanged.fire())); @@ -84,22 +87,19 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { tunnelType: TunnelType.Add, remoteHost: 'localhost', remotePort: 0, - description: '' + description: '', + iconClasses: undefined }; } - async groups(): Promise { - const groups: ITunnelGroup[] = []; + async groupsAndForwarded(): Promise { + const groups: (ITunnelGroup | TunnelItem)[] = []; this._candidates = new Map(); (await this.model.candidates).forEach(candidate => { - this._candidates.set(MakeAddress(candidate.host, candidate.port), candidate); + this._candidates.set(makeAddress(candidate.host, candidate.port), candidate); }); if ((this.model.forwarded.size > 0) || this.remoteExplorerService.getEditableData(undefined)) { - groups.push({ - label: nls.localize('remote.tunnelsView.forwarded', "Forwarded"), - tunnelType: TunnelType.Forwarded, - items: this.forwarded - }); + groups.push(...this.forwarded); } if (this.model.detected.size > 0) { groups.push({ @@ -108,13 +108,15 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { items: this.detected }); } - const candidates = await this.candidates; - if (candidates.length > 0) { - groups.push({ - label: nls.localize('remote.tunnelsView.candidates', "Not Forwarded"), - tunnelType: TunnelType.Candidate, - items: candidates - }); + if (!this.configurationService.getValue(PORT_AUTO_FORWARD_SETTING)) { + const candidates = await this.candidates; + if (candidates.length > 0) { + groups.push({ + label: nls.localize('remote.tunnelsView.candidates', "Not Forwarded"), + tunnelType: TunnelType.Candidate, + items: candidates + }); + } } if (groups.length === 0) { groups.push(this._input); @@ -123,7 +125,7 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { } private addProcessInfoFromCandidate(tunnelItem: ITunnelItem) { - const key = MakeAddress(tunnelItem.remoteHost, tunnelItem.remotePort); + const key = makeAddress(tunnelItem.remoteHost, tunnelItem.remotePort); if (this._candidates.has(key)) { tunnelItem.description = this._candidates.get(key)!.detail; } @@ -158,8 +160,8 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { get candidates(): TunnelItem[] { const candidates: TunnelItem[] = []; this._candidates.forEach(value => { - if (!mapHasTunnelLocalhostOrAllInterfaces(this.model.forwarded, value.host, value.port) && - !mapHasTunnelLocalhostOrAllInterfaces(this.model.detected, value.host, value.port)) { + if (!mapHasAddressLocalhostOrAllInterfaces(this.model.forwarded, value.host, value.port) && + !mapHasAddressLocalhostOrAllInterfaces(this.model.detected, value.host, value.port)) { candidates.push(new TunnelItem(TunnelType.Candidate, value.host, value.port, undefined, undefined, false, undefined, value.detail)); } }); @@ -179,6 +181,7 @@ interface ITunnelTemplateData { elementDisposable: IDisposable; container: HTMLElement; iconLabel: IconLabel; + icon: HTMLElement; actionBar: ActionBar; } @@ -211,6 +214,7 @@ class TunnelTreeRenderer extends Disposable implements ITreeRendererelement).items) { @@ -387,7 +405,7 @@ interface ITunnelGroup { class TunnelItem implements ITunnelItem { static createFromTunnel(tunnel: Tunnel, type: TunnelType = TunnelType.Forwarded, closeable?: boolean) { - return new TunnelItem(type, tunnel.remoteHost, tunnel.remotePort, tunnel.localAddress, tunnel.localPort, closeable === undefined ? tunnel.closeable : closeable, tunnel.name, tunnel.description); + return new TunnelItem(type, tunnel.remoteHost, tunnel.remotePort, tunnel.localAddress, tunnel.localPort, closeable === undefined ? tunnel.closeable : closeable, tunnel.name, tunnel.description ?? tunnel.runningProcess, tunnel.source); } constructor( @@ -399,6 +417,7 @@ class TunnelItem implements ITunnelItem { public closeable?: boolean, public name?: string, private _description?: string, + private source?: string ) { } get label(): string { if (this.name) { @@ -414,13 +433,23 @@ class TunnelItem implements ITunnelItem { if (address.length < 16) { return address; } - let url: URL | undefined; + let displayAddress: string = address; try { - url = new URL(address); + if (!address.startsWith('http')) { + address = `http://${address}`; + } + const url = new URL(address); + if (url && url.host) { + const lastDotIndex = url.host.lastIndexOf('.'); + const secondLastDotIndex = lastDotIndex !== -1 ? url.host.substring(0, lastDotIndex).lastIndexOf('.') : -1; + if (secondLastDotIndex !== -1) { + displayAddress = `${url.protocol}//...${url.host.substring(secondLastDotIndex + 1)}`; + } + } } catch (e) { // Address isn't a valid url and can't be compacted. } - return url && url.host.length > 0 ? url.host : address; + return displayAddress; } set description(description: string | undefined) { @@ -428,13 +457,30 @@ class TunnelItem implements ITunnelItem { } get description(): string | undefined { - if (this._description) { - return this._description; - } else if (this.name) { - return nls.localize('remote.tunnelsView.forwardedPortDescription0', "{0} \u2192 {1}", this.remotePort, this.localAddress); + const description: string[] = []; + + if (this.name && this.localAddress) { + description.push(nls.localize('remote.tunnelsView.forwardedPortDescription0', "{0} \u2192 {1}", this.remotePort, TunnelItem.compactLongAddress(this.localAddress))); } + + if (this._description) { + description.push(this._description); + } + + if (this.source) { + description.push(this.source); + } + + if (description.length > 0) { + return description.join(' \u2022 '); + } + return undefined; } + + get iconClasses(): string | undefined { + return this.tunnelType === TunnelType.Detected || this.tunnelType === TunnelType.Forwarded ? Codicon.plug.classNames : undefined; + } } export const TunnelTypeContextKey = new RawContextKey('tunnelType', TunnelType.Add); @@ -507,6 +553,7 @@ export class TunnelPanel extends ViewPane { const panelContainer = dom.append(container, dom.$('.tree-explorer-viewlet-tree-view')); const treeContainer = dom.append(panelContainer, dom.$('.customview-tree')); + treeContainer.classList.add('ports-view'); treeContainer.classList.add('file-icon-themable-tree', 'show-file-icons'); const renderer = new TunnelTreeRenderer(TunnelPanel.ID, this.menuService, this.contextKeyService, this.instantiationService, this.contextViewService, this.themeService, this.remoteExplorerService); @@ -682,9 +729,13 @@ export class TunnelPanelDescriptor implements IViewDescriptor { readonly canToggleVisibility = true; readonly hideByDefault = false; readonly workspace = true; + // group is not actually used for views that are not extension contributed. Use order instead. readonly group = 'details@0'; + // -500 comes from the remote explorer viewOrderDelegate + readonly order = -500; readonly remoteAuthority?: string | string[]; readonly canMoveView = true; + readonly containerIcon = Codicon.plug; constructor(viewModel: ITunnelViewModel, environmentService: IWorkbenchEnvironmentService) { this.ctorDescriptor = new SyncDescriptor(TunnelPanel, [viewModel]); @@ -740,16 +791,8 @@ export namespace ForwardPortAction { export const TREEITEM_LABEL = nls.localize('remote.tunnel.forwardItem', "Forward Port"); const forwardPrompt = nls.localize('remote.tunnel.forwardPrompt', "Port number or address (eg. 3000 or 10.10.10.10:2000)."); - function parseInput(value: string): { host: string, port: number } | undefined { - const matches = value.match(/^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\:|localhost:)?([0-9]+)$/); - if (!matches) { - return undefined; - } - return { host: matches[1]?.substring(0, matches[1].length - 1) || 'localhost', port: Number(matches[2]) }; - } - function validateInput(value: string): string | null { - const parsed = parseInput(value); + const parsed = parseAddress(value); if (!parsed) { return invalidPortString; } else if (parsed.port >= maxPortNumber) { @@ -774,7 +817,7 @@ export namespace ForwardPortAction { remoteExplorerService.setEditable(undefined, { onFinish: async (value, success) => { let parsed: { host: string, port: number } | undefined; - if (success && (parsed = parseInput(value))) { + if (success && (parsed = parseAddress(value))) { remoteExplorerService.forward({ host: parsed.host, port: parsed.port }).then(tunnel => error(notificationService, tunnel, parsed!.host, parsed!.port)); } remoteExplorerService.setEditable(undefined, null); @@ -798,7 +841,7 @@ export namespace ForwardPortAction { validateInput: (value) => Promise.resolve(validateInput(value)) }); let parsed: { host: string, port: number } | undefined; - if (value && (parsed = parseInput(value))) { + if (value && (parsed = parseAddress(value))) { remoteExplorerService.forward({ host: parsed.host, port: parsed.port }).then(tunnel => error(notificationService, tunnel, parsed!.host, parsed!.port)); } }; @@ -864,10 +907,15 @@ export namespace OpenPortInBrowserAction { export function handler(): ICommandHandler { return async (accessor, arg) => { + let key: string | undefined; if (arg instanceof TunnelItem) { + key = makeAddress(arg.remoteHost, arg.remotePort); + } else if (arg.tunnelRemoteHost && arg.tunnelRemotePort) { + key = makeAddress(arg.tunnelRemoteHost, arg.tunnelRemotePort); + } + if (key) { const model = accessor.get(IRemoteExplorerService).tunnelModel; const openerService = accessor.get(IOpenerService); - const key = MakeAddress(arg.remoteHost, arg.remotePort); return run(model, openerService, key); } }; @@ -877,12 +925,56 @@ export namespace OpenPortInBrowserAction { const tunnel = model.forwarded.get(key) || model.detected.get(key); let address: string | undefined; if (tunnel && tunnel.localAddress && (address = model.address(tunnel.remoteHost, tunnel.remotePort))) { - return openerService.open(URI.parse('http://' + address)); + if (!address.startsWith('http')) { + address = `http://${address}`; + } + return openerService.open(URI.parse(address)); } return Promise.resolve(); } } +namespace OpenPortInBrowserCommandPaletteAction { + export const ID = 'remote.tunnel.openCommandPalette'; + export const LABEL = nls.localize('remote.tunnel.openCommandPalette', "Open Port in Browser"); + + interface QuickPickTunnel extends IQuickPickItem { + tunnel?: TunnelItem; + } + + export function handler(): ICommandHandler { + return async (accessor, arg) => { + const model = accessor.get(IRemoteExplorerService).tunnelModel; + const quickPickService = accessor.get(IQuickInputService); + const openerService = accessor.get(IOpenerService); + const commandService = accessor.get(ICommandService); + const options: QuickPickTunnel[] = [...model.forwarded, ...model.detected].map(value => { + const tunnelItem = TunnelItem.createFromTunnel(value[1]); + return { + label: tunnelItem.label, + description: tunnelItem.description, + tunnel: tunnelItem + }; + }); + if (options.length === 0) { + options.push({ + label: nls.localize('remote.tunnel.openCommandPaletteNone', "No ports currently forwarded. Open the Ports view to get started.") + }); + } else { + options.push({ + label: nls.localize('remote.tunnel.openCommandPaletteView', "Open the Ports view...") + }); + } + const picked = await quickPickService.pick(options, { placeHolder: nls.localize('remote.tunnel.openCommandPalettePick', "Choose the port to open") }); + if (picked && picked.tunnel) { + return OpenPortInBrowserAction.run(model, openerService, makeAddress(picked.tunnel.remoteHost, picked.tunnel.remotePort)); + } else if (picked) { + return commandService.executeCommand(`${TUNNEL_VIEW_ID}.focus`); + } + }; + } +} + namespace CopyAddressAction { export const INLINE_ID = 'remote.tunnel.copyAddressInline'; export const COMMANDPALETTE_ID = 'remote.tunnel.copyAddressCommandPalette'; @@ -923,18 +1015,6 @@ namespace CopyAddressAction { } } -namespace RefreshTunnelViewAction { - export const ID = 'remote.tunnel.refresh'; - export const LABEL = nls.localize('remote.tunnel.refreshView', "Refresh"); - - export function handler(): ICommandHandler { - return (accessor, arg) => { - const remoteExplorerService = accessor.get(IRemoteExplorerService); - return remoteExplorerService.refresh(); - }; - } -} - namespace ChangeLocalPortAction { export const ID = 'remote.tunnel.changeLocalPort'; export const LABEL = nls.localize('remote.tunnel.changeLocalPort', "Change Local Port"); @@ -962,7 +1042,7 @@ namespace ChangeLocalPortAction { const numberValue = Number(value); const newForward = await remoteExplorerService.forward({ host: context.remoteHost, port: context.remotePort }, numberValue, context.name); if (newForward && newForward.tunnelLocalPort !== numberValue) { - notificationService.warn(nls.localize('remote.tunnel.changeLocalPortNumber', "The local port {0} is not available. Port number {1} has been used instead", value, newForward.tunnelLocalPort)); + notificationService.warn(nls.localize('remote.tunnel.changeLocalPortNumber', "The local port {0} is not available. Port number {1} has been used instead", value, newForward.tunnelLocalPort ?? newForward.localAddress)); } } }, @@ -1001,6 +1081,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ CommandsRegistry.registerCommand(ClosePortAction.COMMANDPALETTE_ID, ClosePortAction.commandPaletteHandler()); CommandsRegistry.registerCommand(OpenPortInBrowserAction.ID, OpenPortInBrowserAction.handler()); +CommandsRegistry.registerCommand(OpenPortInBrowserCommandPaletteAction.ID, OpenPortInBrowserCommandPaletteAction.handler()); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: CopyAddressAction.INLINE_ID, weight: KeybindingWeight.WorkbenchContrib + tunnelViewCommandsWeightBonus, @@ -1009,7 +1090,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: CopyAddressAction.inlineHandler() }); CommandsRegistry.registerCommand(CopyAddressAction.COMMANDPALETTE_ID, CopyAddressAction.commandPaletteHandler()); -CommandsRegistry.registerCommand(RefreshTunnelViewAction.ID, RefreshTunnelViewAction.handler()); CommandsRegistry.registerCommand(ChangeLocalPortAction.ID, ChangeLocalPortAction.handler()); MenuRegistry.appendMenuItem(MenuId.CommandPalette, ({ @@ -1033,22 +1113,20 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, ({ }, when: forwardedPortsViewEnabled })); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, ({ + command: { + id: OpenPortInBrowserCommandPaletteAction.ID, + title: OpenPortInBrowserCommandPaletteAction.LABEL + }, + when: forwardedPortsViewEnabled +})); MenuRegistry.appendMenuItem(MenuId.TunnelTitle, ({ group: 'navigation', order: 0, command: { id: ForwardPortAction.INLINE_ID, title: ForwardPortAction.LABEL, - icon: { id: 'codicon/plus' } - } -})); -MenuRegistry.appendMenuItem(MenuId.TunnelTitle, ({ - group: 'navigation', - order: 1, - command: { - id: RefreshTunnelViewAction.ID, - title: RefreshTunnelViewAction.LABEL, - icon: { id: 'codicon/refresh' } + icon: Codicon.plus } })); MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ @@ -1111,7 +1189,7 @@ MenuRegistry.appendMenuItem(MenuId.TunnelInline, ({ command: { id: OpenPortInBrowserAction.ID, title: OpenPortInBrowserAction.LABEL, - icon: { id: 'codicon/globe' } + icon: Codicon.globe }, when: ContextKeyExpr.or(TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded), TunnelTypeContextKey.isEqualTo(TunnelType.Detected)) })); @@ -1120,7 +1198,7 @@ MenuRegistry.appendMenuItem(MenuId.TunnelInline, ({ command: { id: ForwardPortAction.INLINE_ID, title: ForwardPortAction.TREEITEM_LABEL, - icon: { id: 'codicon/plus' } + icon: Codicon.plus }, when: TunnelTypeContextKey.isEqualTo(TunnelType.Candidate) })); @@ -1129,7 +1207,7 @@ MenuRegistry.appendMenuItem(MenuId.TunnelInline, ({ command: { id: ClosePortAction.INLINE_ID, title: ClosePortAction.LABEL, - icon: { id: 'codicon/x' } + icon: Codicon.x }, when: TunnelCloseableContextKey })); diff --git a/src/vs/workbench/contrib/remote/browser/urlFinder.ts b/src/vs/workbench/contrib/remote/browser/urlFinder.ts index bc74bb759..e9c21167a 100644 --- a/src/vs/workbench/contrib/remote/browser/urlFinder.ts +++ b/src/vs/workbench/contrib/remote/browser/urlFinder.ts @@ -18,6 +18,12 @@ export class UrlFinder extends Disposable { * http://0.0.0.0:4000 - Elixir Phoenix */ private static readonly localUrlRegex = /\b\w{2,20}:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|:\d{2,5})[\w\-\.\~:\/\?\#[\]\@!\$&\(\)\*\+\,\;\=]*/gim; + /** + * https://github.com/microsoft/vscode-remote-release/issues/3949 + */ + private static readonly localPythonServerRegex = /HTTP\son\s(127\.0\.0\.1|0\.0\.0\.0)\sport\s(\d+)/; + + private static readonly excludeTerminals = ['Dev Containers']; private _onDidMatchLocalUrl: Emitter<{ host: string, port: number }> = new Emitter(); public readonly onDidMatchLocalUrl = this._onDidMatchLocalUrl.event; @@ -27,14 +33,10 @@ export class UrlFinder extends Disposable { super(); // Terminal terminalService.terminalInstances.forEach(instance => { - this.listeners.set(instance, instance.onData(data => { - this.processData(data); - })); + this.registerTerminalInstance(instance); }); this._register(terminalService.onInstanceCreated(instance => { - this.listeners.set(instance, instance.onData(data => { - this.processData(data); - })); + this.registerTerminalInstance(instance); })); this._register(terminalService.onInstanceDisposed(instance => { this.listeners.get(instance)?.dispose(); @@ -57,6 +59,14 @@ export class UrlFinder extends Disposable { })); } + private registerTerminalInstance(instance: ITerminalInstance) { + if (!UrlFinder.excludeTerminals.includes(instance.title)) { + this.listeners.set(instance, instance.onData(data => { + this.processData(data); + })); + } + } + private replPositions: Map = new Map(); private processNewReplElements(session: IDebugSession) { const oldReplPosition = this.replPositions.get(session.getId()); @@ -90,25 +100,33 @@ export class UrlFinder extends Disposable { // strip ANSI terminal codes data = data.replace(UrlFinder.terminalCodesRegex, ''); const urlMatches = data.match(UrlFinder.localUrlRegex) || []; - urlMatches.forEach((match) => { - // check if valid url - const serverUrl = new URL(match); - if (serverUrl) { - // check if the port is a valid integer value - const port = parseFloat(serverUrl.port!); - if (!isNaN(port) && Number.isInteger(port) && port > 0 && port <= 65535) { - // normalize the host name - let host = serverUrl.hostname; - if (host !== '0.0.0.0' && host !== '127.0.0.1') { - host = 'localhost'; + if (urlMatches && urlMatches.length > 0) { + urlMatches.forEach((match) => { + // check if valid url + const serverUrl = new URL(match); + if (serverUrl) { + // check if the port is a valid integer value + const port = parseFloat(serverUrl.port!); + if (!isNaN(port) && Number.isInteger(port) && port > 0 && port <= 65535) { + // normalize the host name + let host = serverUrl.hostname; + if (host !== '0.0.0.0' && host !== '127.0.0.1') { + host = 'localhost'; + } + // Exclude node inspect, except when using default port + if (port !== 9229 && data.startsWith('Debugger listening on')) { + return; + } + this._onDidMatchLocalUrl.fire({ port, host }); } - // Exclude node inspect, except when using default port - if (port !== 9229 && data.startsWith('Debugger listening on')) { - return; - } - this._onDidMatchLocalUrl.fire({ port, host }); } + }); + } else { + // Try special python case + const pythonMatch = data.match(UrlFinder.localPythonServerRegex); + if (pythonMatch && pythonMatch.length === 3) { + this._onDidMatchLocalUrl.fire({ host: pythonMatch[1], port: Number(pythonMatch[2]) }); } - }); + } } } diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 1728f76ea..93911f192 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -131,7 +131,7 @@ Registry.as(ConfigurationExtensions.Configuration) }, 'remote.autoForwardPorts': { type: 'boolean', - markdownDescription: localize('remote.autoForwardPorts', "When enabled, URLs with ports (ex. `http://127.0.0.1:3000`) that are printed to your terminals are automatically forwarded."), + markdownDescription: localize('remote.autoForwardPorts', "When enabled, new running processes are detected and ports that they listen on are automatically forwarded."), default: true } } diff --git a/src/vs/workbench/contrib/remote/common/tunnelFactory.ts b/src/vs/workbench/contrib/remote/common/tunnelFactory.ts index ab8c80e67..6da6a2745 100644 --- a/src/vs/workbench/contrib/remote/common/tunnelFactory.ts +++ b/src/vs/workbench/contrib/remote/common/tunnelFactory.ts @@ -3,31 +3,39 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITunnelService, TunnelOptions, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; +import { ITunnelService, TunnelOptions, RemoteTunnel, TunnelCreationOptions } from 'vs/platform/remote/common/tunnel'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { URI } from 'vs/base/common/uri'; +import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; export class TunnelFactoryContribution extends Disposable implements IWorkbenchContribution { constructor( @ITunnelService tunnelService: ITunnelService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IOpenerService openerService: IOpenerService, + @IRemoteExplorerService remoteExplorerService: IRemoteExplorerService ) { super(); const tunnelFactory = environmentService.options?.tunnelProvider?.tunnelFactory; if (tunnelFactory) { this._register(tunnelService.setTunnelProvider({ - forwardPort: (tunnelOptions: TunnelOptions): Promise | undefined => { - const tunnelPromise = tunnelFactory(tunnelOptions); + forwardPort: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise | undefined => { + const tunnelPromise = tunnelFactory(tunnelOptions, tunnelCreationOptions); if (!tunnelPromise) { return undefined; } return new Promise(resolve => { - tunnelPromise.then(tunnel => { + tunnelPromise.then(async (tunnel) => { + const localAddress = tunnel.localAddress.startsWith('http') ? tunnel.localAddress : `http://${tunnel.localAddress}`; const remoteTunnel: RemoteTunnel = { tunnelRemotePort: tunnel.remoteAddress.port, tunnelRemoteHost: tunnel.remoteAddress.host, - localAddress: tunnel.localAddress, + // The tunnel factory may give us an inaccessible local address. + // To make sure this doesn't happen, resolve the uri immediately. + localAddress: (await openerService.resolveExternalUri(URI.parse(localAddress))).resolved.toString(), dispose: tunnel.dispose }; resolve(remoteTunnel); @@ -35,6 +43,7 @@ export class TunnelFactoryContribution extends Disposable implements IWorkbenchC }); } })); + remoteExplorerService.setTunnelInformation(undefined); } } } diff --git a/src/vs/workbench/contrib/sash/browser/sash.contribution.ts b/src/vs/workbench/contrib/sash/browser/sash.contribution.ts index c4b13519c..c63a2d4be 100644 --- a/src/vs/workbench/contrib/sash/browser/sash.contribution.ts +++ b/src/vs/workbench/contrib/sash/browser/sash.contribution.ts @@ -11,6 +11,8 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { SashSizeController, minSize, maxSize } from 'vs/workbench/contrib/sash/browser/sash'; import { isIPad } from 'vs/base/browser/browser'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { sashHoverBorder } from 'vs/platform/theme/common/colorRegistry'; // Sash size contribution Registry.as(WorkbenchExtensions.Workbench) @@ -30,3 +32,13 @@ Registry.as(ConfigurationExtensions.Configuration) }, } }); + +registerThemingParticipant((theme, collector) => { + const sashHoverBorderColor = theme.getColor(sashHoverBorder); + collector.addRule(` + .monaco-sash:hover, + .monaco-sash.active { + background: ${sashHoverBorderColor} + } + `); +}); diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index b826a548c..ffd41e575 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -19,7 +19,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { URI } from 'vs/base/common/uri'; import { ISCMService, ISCMRepository, ISCMProvider } from 'vs/workbench/contrib/scm/common/scm'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { registerThemingParticipant, IColorTheme, ICssStyleCollector, themeColorFromId, IThemeService } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, IColorTheme, ICssStyleCollector, themeColorFromId, IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; import { Color, RGBA } from 'vs/base/common/color'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; @@ -48,6 +48,7 @@ import { ISplice } from 'vs/base/common/sequence'; import { createStyleSheet } from 'vs/base/browser/dom'; import { ITextFileEditorModel, IResolvedTextFileEditorModel, ITextFileService, isTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { EncodingMode } from 'vs/workbench/common/editor'; +import { gotoNextLocation, gotoPreviousLocation } from 'vs/platform/theme/common/iconRegistry'; class DiffActionRunner extends ActionRunner { @@ -246,8 +247,8 @@ class DirtyDiffWidget extends PeekViewWidget { protected _fillHead(container: HTMLElement): void { super._fillHead(container); - const previous = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowPreviousChangeAction(), 'codicon-arrow-up'); - const next = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowNextChangeAction(), 'codicon-arrow-down'); + const previous = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowPreviousChangeAction(), ThemeIcon.asClassName(gotoPreviousLocation)); + const next = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowNextChangeAction(), ThemeIcon.asClassName(gotoNextLocation)); this._disposables.add(previous); this._disposables.add(next); @@ -563,20 +564,57 @@ export class DirtyDiffController extends Disposable implements IEditorContributi private session: IDisposable = Disposable.None; private mouseDownInfo: { lineNumber: number } | null = null; private enabled = false; + private gutterActionDisposables = new DisposableStore(); + private stylesheet: HTMLStyleElement; constructor( private editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService private readonly configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); this.enabled = !contextKeyService.getContextKeyValue('isInDiffEditor'); + this.stylesheet = createStyleSheet(); + this._register(toDisposable(() => this.stylesheet.remove())); if (this.enabled) { this.isDirtyDiffVisible = isDirtyDiffVisible.bindTo(contextKeyService); - this._register(editor.onMouseDown(e => this.onEditorMouseDown(e))); - this._register(editor.onMouseUp(e => this.onEditorMouseUp(e))); this._register(editor.onDidChangeModel(() => this.close())); + + const onDidChangeGutterAction = Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.diffDecorationsGutterAction')); + this._register(onDidChangeGutterAction(this.onDidChangeGutterAction, this)); + this.onDidChangeGutterAction(); + } + } + + private onDidChangeGutterAction(): void { + const gutterAction = this.configurationService.getValue<'diff' | 'none'>('scm.diffDecorationsGutterAction'); + + this.gutterActionDisposables.dispose(); + this.gutterActionDisposables = new DisposableStore(); + + if (gutterAction === 'diff') { + this.gutterActionDisposables.add(this.editor.onMouseDown(e => this.onEditorMouseDown(e))); + this.gutterActionDisposables.add(this.editor.onMouseUp(e => this.onEditorMouseUp(e))); + this.stylesheet.textContent = ` + .monaco-editor .dirty-diff-glyph { + cursor: pointer; + } + + .monaco-editor .margin-view-overlays .dirty-diff-glyph:hover::before { + width: 9px; + left: -6px; + } + + .monaco-editor .margin-view-overlays .dirty-diff-deleted:hover::after { + bottom: 0; + border-top-width: 0; + border-bottom-width: 0; + } + `; + } else { + this.stylesheet.textContent = ``; } } @@ -798,6 +836,11 @@ export class DirtyDiffController extends Disposable implements IEditorContributi return model.changes; } + + dispose(): void { + this.gutterActionDisposables.dispose(); + super.dispose(); + } } export const editorGutterModifiedBackground = registerColor('editorGutter.modifiedBackground', { diff --git a/src/vs/workbench/contrib/scm/browser/media/dirtydiffDecorator.css b/src/vs/workbench/contrib/scm/browser/media/dirtydiffDecorator.css index 472114eed..dd0921f4d 100644 --- a/src/vs/workbench/contrib/scm/browser/media/dirtydiffDecorator.css +++ b/src/vs/workbench/contrib/scm/browser/media/dirtydiffDecorator.css @@ -5,7 +5,6 @@ .monaco-editor .dirty-diff-glyph { margin-left: 5px; - cursor: pointer; z-index: 5; } @@ -39,20 +38,6 @@ transition: height 80ms linear; } -.monaco-editor .margin-view-overlays .dirty-diff-glyph:hover::before { - position: absolute; - content: ''; - height: 100%; - width: 9px; - left: -6px; -} - -.monaco-editor .margin-view-overlays .dirty-diff-deleted:hover::after { - bottom: 0; - border-top-width: 0; - border-bottom-width: 0; -} - /* Hide glyph decorations when inside the inline diff editor */ .monaco-editor.modified-in-monaco-diff-editor .margin-view-overlays > div > .dirty-diff-glyph { display: none; diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index db8fb95f8..98a0f62f8 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -23,6 +23,7 @@ import { SCMViewPaneContainer } from 'vs/workbench/contrib/scm/browser/scmViewPa import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { Codicon } from 'vs/base/common/codicons'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { SCMViewPane } from 'vs/workbench/contrib/scm/browser/scmViewPane'; import { SCMViewService } from 'vs/workbench/contrib/scm/browser/scmViewService'; import { SCMRepositoriesViewPane } from 'vs/workbench/contrib/scm/browser/scmRepositoriesViewPane'; @@ -38,12 +39,14 @@ ModesRegistry.registerLanguage({ Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(DirtyDiffWorkbenchController, LifecyclePhase.Restored); +const sourceControlViewIcon = registerIcon('source-control-view-icon', Codicon.sourceControl, localize('sourceControlViewIcon', 'View icon of the source control view.')); + const viewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: localize('source control', "Source Control"), ctorDescriptor: new SyncDescriptor(SCMViewPaneContainer), storageId: 'workbench.scm.views.state', - icon: Codicon.sourceControl.classNames, + icon: sourceControlViewIcon, alwaysUseContainerInfo: true, order: 2, hideIfEmpty: true @@ -65,7 +68,7 @@ viewsRegistry.registerViews([{ canMoveView: true, weight: 80, order: -999, - containerIcon: Codicon.sourceControl.classNames + containerIcon: sourceControlViewIcon }], viewContainer); viewsRegistry.registerViews([{ @@ -80,7 +83,7 @@ viewsRegistry.registerViews([{ order: -1000, when: ContextKeyExpr.and(ContextKeyExpr.has('scm.providerCount'), ContextKeyExpr.notEquals('scm.providerCount', 0)), // readonly when = ContextKeyExpr.or(ContextKeyExpr.equals('config.scm.alwaysShowProviders', true), ContextKeyExpr.and(ContextKeyExpr.notEquals('scm.providerCount', 0), ContextKeyExpr.notEquals('scm.providerCount', 1))); - containerIcon: Codicon.sourceControl.classNames + containerIcon: sourceControlViewIcon }], viewContainer); Registry.as(WorkbenchExtensions.Workbench) @@ -138,6 +141,16 @@ Registry.as(ConfigurationExtensions.Configuration).regis description: localize('scm.diffDecorationsGutterVisibility', "Controls the visibility of the Source Control diff decorator in the gutter."), default: 'always' }, + 'scm.diffDecorationsGutterAction': { + type: 'string', + enum: ['diff', 'none'], + enumDescriptions: [ + localize('scm.diffDecorationsGutterAction.diff', "Show the inline diff peek view on click."), + localize('scm.diffDecorationsGutterAction.none', "Do nothing.") + ], + description: localize('scm.diffDecorationsGutterAction', "Controls the behavior of Source Control diff gutter decorations."), + default: 'diff' + }, 'scm.alwaysShowActions': { type: 'boolean', description: localize('alwaysShowActions', "Controls whether inline actions are always visible in the Source Control view."), diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts index 75d93fa6e..ffcb36be9 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/scm'; -import { basename } from 'vs/base/common/resources'; import { IDisposable, Disposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; import { append, $ } from 'vs/base/browser/dom'; import { ISCMRepository, ISCMViewService } from 'vs/workbench/contrib/scm/common/scm'; @@ -20,6 +19,8 @@ import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; import { FuzzyScore } from 'vs/base/common/filters'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IListRenderer } from 'vs/base/browser/ui/list/list'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { basename } from 'vs/base/common/resources'; interface RepositoryTemplate { readonly label: HTMLElement; @@ -42,7 +43,8 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer | ISCMResource; @@ -655,26 +656,30 @@ export class SCMTreeKeyboardNavigationLabelProvider implements ICompressibleKeyb } } +function getSCMResourceId(element: TreeElement): string { + if (ResourceTree.isResourceNode(element)) { + const group = element.context; + return `folder:${group.provider.id}/${group.id}/$FOLDER/${element.uri.toString()}`; + } else if (isSCMRepository(element)) { + const provider = element.provider; + return `repo:${provider.id}`; + } else if (isSCMInput(element)) { + const provider = element.repository.provider; + return `input:${provider.id}`; + } else if (isSCMResource(element)) { + const group = element.resourceGroup; + const provider = group.provider; + return `resource:${provider.id}/${group.id}/${element.sourceUri.toString()}`; + } else { + const provider = element.provider; + return `group:${provider.id}/${element.id}`; + } +} + class SCMResourceIdentityProvider implements IIdentityProvider { getId(element: TreeElement): string { - if (ResourceTree.isResourceNode(element)) { - const group = element.context; - return `folder:${group.provider.id}/${group.id}/$FOLDER/${element.uri.toString()}`; - } else if (isSCMRepository(element)) { - const provider = element.provider; - return `repo:${provider.id}`; - } else if (isSCMInput(element)) { - const provider = element.repository.provider; - return `input:${provider.id}`; - } else if (isSCMResource(element)) { - const group = element.resourceGroup; - const provider = group.provider; - return `resource:${provider.id}/${group.id}/${element.sourceUri.toString()}`; - } else { - const provider = element.provider; - return `group:${provider.id}/${element.id}`; - } + return getSCMResourceId(element); } } @@ -728,15 +733,24 @@ interface IRepositoryItem { dispose(): void; } +interface ITreeViewState { + readonly collapsed: string[]; +} + function isRepositoryItem(item: IRepositoryItem | IGroupItem): item is IRepositoryItem { return Array.isArray((item as IRepositoryItem).groupItems); } -function asTreeElement(node: IResourceNode, forceIncompressible: boolean): ICompressedTreeElement { +function asTreeElement(node: IResourceNode, forceIncompressible: boolean, viewState?: ITreeViewState): ICompressedTreeElement { + const element = (node.childrenCount === 0 && node.element) ? node.element : node; + const collapsed = viewState ? viewState.collapsed.indexOf(getSCMResourceId(element)) > -1 : false; + return { - element: (node.childrenCount === 0 && node.element) ? node.element : node, - children: Iterable.map(node.children, node => asTreeElement(node, false)), - incompressible: !!node.element || forceIncompressible + element, + children: Iterable.map(node.children, node => asTreeElement(node, false, viewState)), + incompressible: !!node.element || forceIncompressible, + collapsed, + collapsible: node.childrenCount > 0 }; } @@ -788,12 +802,21 @@ class ViewModel { } } + private _treeViewStateIsStale = false; + get treeViewState(): ITreeViewState | undefined { + if (this.visible && this._treeViewStateIsStale) { + this.updateViewState(); + this._treeViewStateIsStale = false; + } + + return this._treeViewState; + } + private items = new Map(); private visibilityDisposables = new DisposableStore(); private scrollTop: number | undefined; private alwaysShowRepositories = false; private firstVisible = true; - private repositoryCollapseStates: Map | undefined; private viewSubMenuAction: SCMViewSubMenuAction | undefined; private disposables = new DisposableStore(); @@ -802,6 +825,7 @@ class ViewModel { private inputRenderer: InputRenderer, private _mode: ViewModelMode, private _sortKey: ViewModelSortKey, + private _treeViewState: ITreeViewState | undefined, @IInstantiationService protected instantiationService: IInstantiationService, @IEditorService protected editorService: IEditorService, @IConfigurationService protected configurationService: IConfigurationService, @@ -815,6 +839,8 @@ class ViewModel { configurationService.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables); this.onDidChangeConfiguration(); + + this.disposables.add(this.tree.onDidChangeCollapseState(() => this._treeViewStateIsStale = true)); } private onDidChangeConfiguration(e?: IConfigurationChangeEvent): void { @@ -862,7 +888,7 @@ class ViewModel { } private createGroupItem(group: ISCMResourceGroup): IGroupItem { - const tree = new ResourceTree(group, group.provider.rootUri || URI.file('/')); + const tree = new ResourceTree(group, group.provider.rootUri || URI.file('/'), this.uriIdentityService.extUri); const resources: ISCMResource[] = [...group.elements]; const disposable = combinedDisposable( group.onDidChange(() => this.tree.refilter()), @@ -907,7 +933,6 @@ class ViewModel { this.visibilityDisposables = new DisposableStore(); this.scmViewService.onDidChangeVisibleRepositories(this._onDidChangeVisibleRepositories, this, this.visibilityDisposables); this._onDidChangeVisibleRepositories({ added: this.scmViewService.visibleRepositories, removed: Iterable.empty() }); - this.repositoryCollapseStates = undefined; if (typeof this.scrollTop === 'number') { this.tree.scrollTop = this.scrollTop; @@ -917,13 +942,7 @@ class ViewModel { this.editorService.onDidActiveEditorChange(this.onDidActiveEditorChange, this, this.visibilityDisposables); this.onDidActiveEditorChange(); } else { - if (this.items.size > 1) { - this.repositoryCollapseStates = new Map(); - - for (const [, item] of this.items) { - this.repositoryCollapseStates.set(item.element, this.tree.isCollapsed(item.element)); - } - } + this.updateViewState(); this.visibilityDisposables.dispose(); this._onDidChangeVisibleRepositories({ added: Iterable.empty(), removed: [...this.items.keys()] }); @@ -939,12 +958,12 @@ class ViewModel { if (!this.alwaysShowRepositories && (this.items.size === 1 && (!item || isRepositoryItem(item)))) { const item = Iterable.first(this.items.values())!; - this.tree.setChildren(null, this.render(item).children); + this.tree.setChildren(null, this.render(item, this._treeViewState).children); } else if (item) { - this.tree.setChildren(item.element, this.render(item).children); + this.tree.setChildren(item.element, this.render(item, this._treeViewState).children); } else { const items = coalesce(this.scmViewService.visibleRepositories.map(r => this.items.get(r))); - this.tree.setChildren(null, items.map(item => this.render(item))); + this.tree.setChildren(null, items.map(item => this.render(item, this._treeViewState))); } if (focusedInput) { @@ -958,7 +977,7 @@ class ViewModel { this._onDidChangeRepositoryCollapseState.fire(); } - private render(item: IRepositoryItem | IGroupItem): ICompressedTreeElement { + private render(item: IRepositoryItem | IGroupItem, treeViewState?: ITreeViewState): ICompressedTreeElement { if (isRepositoryItem(item)) { const children: ICompressedTreeElement[] = []; const hasSomeChanges = item.groupItems.some(item => item.element.elements.length > 0); @@ -968,20 +987,40 @@ class ViewModel { children.push({ element: item.element.input, incompressible: true, collapsible: false }); } - children.push(...item.groupItems.map(i => this.render(i))); + children.push(...item.groupItems.map(i => this.render(i, treeViewState))); } - const collapsed = this.repositoryCollapseStates?.get(item.element); + const collapsed = treeViewState ? treeViewState.collapsed.indexOf(getSCMResourceId(item.element)) > -1 : false; + return { element: item.element, children, incompressible: true, collapsed, collapsible: true }; } else { const children = this.mode === ViewModelMode.List ? Iterable.map(item.resources, element => ({ element, incompressible: true })) - : Iterable.map(item.tree.root.children, node => asTreeElement(node, true)); + : Iterable.map(item.tree.root.children, node => asTreeElement(node, true, treeViewState)); - return { element: item.element, children, incompressible: true, collapsible: true }; + const collapsed = treeViewState ? treeViewState.collapsed.indexOf(getSCMResourceId(item.element)) > -1 : false; + + return { element: item.element, children, incompressible: true, collapsed, collapsible: true }; } } + private updateViewState(): void { + const collapsed: string[] = []; + const visit = (node: ITreeNode) => { + if (node.element && node.collapsible && node.collapsed) { + collapsed.push(getSCMResourceId(node.element)); + } + + for (const child of node.children) { + visit(child); + } + }; + + visit(this.tree.getNode()); + + this._treeViewState = { collapsed }; + } + private onDidActiveEditorChange(): void { if (!this.configurationService.getValue('scm.autoReveal')) { return; @@ -1000,13 +1039,17 @@ class ViewModel { } for (const repository of this.scmViewService.visibleRepositories) { - const item = this.items.get(repository)!; + const item = this.items.get(repository); + + if (!item) { + continue; + } // go backwards from last group for (let j = item.groupItems.length - 1; j >= 0; j--) { const groupItem = item.groupItems[j]; const resource = this.mode === ViewModelMode.Tree - ? groupItem.tree.getNode(uri)?.element // TODO@Joao URI identity? + ? groupItem.tree.getNode(uri)?.element : groupItem.resources.find(r => this.uriIdentityService.extUri.isEqual(r.sourceUri, uri)); if (resource) { @@ -1020,12 +1063,14 @@ class ViewModel { } focus() { - for (const repository of this.scmViewService.visibleRepositories) { - const widget = this.inputRenderer.getRenderedInputWidget(repository.input); + if (this.tree.getFocus().length === 0) { + for (const repository of this.scmViewService.visibleRepositories) { + const widget = this.inputRenderer.getRenderedInputWidget(repository.input); - if (widget) { - widget.focus(); - return; + if (widget) { + widget.focus(); + return; + } } } @@ -1184,7 +1229,7 @@ export class ToggleViewModeAction extends Action { } private onDidChangeMode(mode: ViewModelMode): void { - const iconClass = mode === ViewModelMode.List ? 'codicon-list-tree' : 'codicon-list-flat'; + const iconClass = ThemeIcon.asClassName(mode === ViewModelMode.List ? Codicon.listTree : Codicon.listFlat); this.class = `scm-action toggle-view-mode ${iconClass}`; this.checked = this.viewModel.mode === this.mode; } @@ -1444,7 +1489,8 @@ class SCMInputWidget extends Disposable { padding: { top: 3, bottom: 3 }, quickSuggestions: false, scrollbar: { alwaysConsumeMouseWheel: false }, - overflowWidgetsDomNode + overflowWidgetsDomNode, + renderWhitespace: 'none' }; const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { @@ -1612,7 +1658,7 @@ class SCMCollapseAction extends Action { this.enabled = isAnyProviderCollapsible; this.allCollapsed = isAnyProviderCollapsible && this.viewModel.areAllProvidersCollapsed(); this.label = this.allCollapsed ? localize('expand all', "Expand All Repositories") : localize('collapse all', "Collapse All Repositories"); - this.class = this.allCollapsed ? Codicon.expandAll.classNames : Codicon.collapseAll.classNames; + this.class = ThemeIcon.asClassName(this.allCollapsed ? Codicon.expandAll : Codicon.collapseAll); } } @@ -1689,7 +1735,7 @@ export class SCMViewPane extends ViewPane { const actionRunner = new RepositoryPaneActionRunner(() => this.getSelectedResources()); this._register(actionRunner); - this._register(actionRunner.onDidBeforeRun(() => this.tree.domFocus())); + this._register(actionRunner.onBeforeRun(() => this.tree.domFocus())); const renderers: ICompressibleTreeRenderer[] = [ this.instantiationService.createInstance(RepositoryRenderer, actionViewItemProvider), @@ -1731,13 +1777,22 @@ export class SCMViewPane extends ViewPane { append(this.listContainer, overflowWidgetsDomNode); let viewMode = this.configurationService.getValue<'tree' | 'list'>('scm.defaultViewMode') === 'list' ? ViewModelMode.List : ViewModelMode.Tree; - const storageMode = this.storageService.get(`scm.viewMode`, StorageScope.WORKSPACE) as ViewModelMode; + const storageMode = this.storageService.get(`scm.viewMode`, StorageScope.WORKSPACE) as ViewModelMode; if (typeof storageMode === 'string') { viewMode = storageMode; } - this.viewModel = this.instantiationService.createInstance(ViewModel, this.tree, this.inputRenderer, viewMode, ViewModelSortKey.Path); + let viewState: ITreeViewState | undefined; + + const storageViewState = this.storageService.get(`scm.viewState`, StorageScope.WORKSPACE); + if (storageViewState) { + try { + viewState = JSON.parse(storageViewState); + } catch {/* noop */ } + } + + this.viewModel = this.instantiationService.createInstance(ViewModel, this.tree, this.inputRenderer, viewMode, ViewModelSortKey.Path, viewState); this._register(this.viewModel); this.listContainer.classList.add('file-icon-themable-tree'); @@ -1754,6 +1809,12 @@ export class SCMViewPane extends ViewPane { this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.alwaysShowRepositories'))(this.updateActions, this)); this.updateActions(); + + this._register(this.storageService.onWillSaveState(e => { + if (e.reason === WillSaveStateReason.SHUTDOWN) { + this.storageService.store(`scm.viewState`, JSON.stringify(this.viewModel.treeViewState), StorageScope.WORKSPACE, StorageTarget.MACHINE); + } + })); } private updateIndentStyles(theme: IFileIconTheme): void { @@ -1765,7 +1826,7 @@ export class SCMViewPane extends ViewPane { private onDidChangeMode(): void { this.updateIndentStyles(this.themeService.getFileIconTheme()); - this.storageService.store(`scm.viewMode`, this.viewModel.mode, StorageScope.WORKSPACE); + this.storageService.store(`scm.viewMode`, this.viewModel.mode, StorageScope.WORKSPACE, StorageTarget.USER); } layoutBody(height: number | undefined = this.layoutCache.height, width: number | undefined = this.layoutCache.width): void { @@ -1839,7 +1900,7 @@ export class SCMViewPane extends ViewPane { return this.viewModel.getViewActionsContext(); } - private async open(e: IOpenEvent): Promise { + private async open(e: IOpenEvent): Promise { if (!e.element) { return; } else if (isSCMRepository(e.element)) { @@ -1878,13 +1939,17 @@ export class SCMViewPane extends ViewPane { } // ISCMResource - await e.element.open(!!e.editorOptions.preserveFocus); + if (e.element.command?.id === API_OPEN_EDITOR_COMMAND_ID || e.element.command?.id === API_OPEN_DIFF_EDITOR_COMMAND_ID) { + await this.commandService.executeCommand(e.element.command.id, ...(e.element.command.arguments || []), e); + } else { + await e.element.open(!!e.editorOptions.preserveFocus); - if (e.editorOptions.pinned) { - const activeEditorPane = this.editorService.activeEditorPane; + if (e.editorOptions.pinned) { + const activeEditorPane = this.editorService.activeEditorPane; - if (activeEditorPane) { - activeEditorPane.group.pinEditor(activeEditorPane.input); + if (activeEditorPane) { + activeEditorPane.group.pinEditor(activeEditorPane.input); + } } } @@ -1937,7 +2002,7 @@ export class SCMViewPane extends ViewPane { } const actionRunner = new RepositoryPaneActionRunner(() => this.getSelectedResources()); - actionRunner.onDidBeforeRun(() => this.tree.domFocus()); + actionRunner.onBeforeRun(() => this.tree.domFocus()); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, diff --git a/src/vs/workbench/contrib/scm/browser/scmViewService.ts b/src/vs/workbench/contrib/scm/browser/scmViewService.ts index 2704f6a7b..78ac23a15 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewService.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewService.ts @@ -5,10 +5,21 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; -import { ISCMViewService, ISCMRepository, ISCMService, ISCMViewVisibleRepositoryChangeEvent, ISCMMenus } from 'vs/workbench/contrib/scm/common/scm'; +import { ISCMViewService, ISCMRepository, ISCMService, ISCMViewVisibleRepositoryChangeEvent, ISCMMenus, ISCMProvider } from 'vs/workbench/contrib/scm/common/scm'; import { Iterable } from 'vs/base/common/iterator'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { SCMMenus } from 'vs/workbench/contrib/scm/browser/menus'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { debounce } from 'vs/base/common/decorators'; + +function getProviderStorageKey(provider: ISCMProvider): string { + return `${provider.contextValue}:${provider.label}${provider.rootUri ? `:${provider.rootUri.toString()}` : ''}`; +} + +export interface ISCMViewServiceState { + readonly all: string[]; + readonly visible: number[]; +} export class SCMViewService implements ISCMViewService { @@ -16,6 +27,9 @@ export class SCMViewService implements ISCMViewService { readonly menus: ISCMMenus; + private didFinishLoading: boolean = false; + private provisionalVisibleRepository: ISCMRepository | undefined; + private previousState: ISCMViewServiceState | undefined; private disposables = new DisposableStore(); private _visibleRepositoriesSet = new Set(); @@ -84,7 +98,8 @@ export class SCMViewService implements ISCMViewService { constructor( @ISCMService private readonly scmService: ISCMService, - @IInstantiationService instantiationService: IInstantiationService + @IInstantiationService instantiationService: IInstantiationService, + @IStorageService private readonly storageService: IStorageService ) { this.menus = instantiationService.createInstance(SCMMenus); @@ -94,13 +109,63 @@ export class SCMViewService implements ISCMViewService { for (const repository of scmService.repositories) { this.onDidAddRepository(repository); } + + try { + this.previousState = JSON.parse(storageService.get('scm:view:visibleRepositories', StorageScope.WORKSPACE, '')); + this.eventuallyFinishLoading(); + } catch { + // noop + } + + storageService.onWillSaveState(this.onWillSaveState, this, this.disposables); } private onDidAddRepository(repository: ISCMRepository): void { + if (!this.didFinishLoading) { + this.eventuallyFinishLoading(); + } + + let removed: Iterable = Iterable.empty(); + + if (this.previousState) { + const index = this.previousState.all.indexOf(getProviderStorageKey(repository.provider)); + + if (index === -1) { // saw a repo we did not expect + const added: ISCMRepository[] = []; + for (const repo of this.scmService.repositories) { // all should be visible + if (!this._visibleRepositoriesSet.has(repo)) { + added.push(repository); + } + } + + this._visibleRepositories = [...this.scmService.repositories]; + this._visibleRepositoriesSet = new Set(this.scmService.repositories); + this._onDidChangeRepositories.fire({ added, removed: Iterable.empty() }); + this.finishLoading(); + return; + } + + const visible = this.previousState.visible.indexOf(index) > -1; + + if (!visible) { + if (this._visibleRepositories.length === 0) { // should make it visible, until other repos come along + this.provisionalVisibleRepository = repository; + } else { + return; + } + } else { + if (this.provisionalVisibleRepository) { + this._visibleRepositories = []; + this._visibleRepositoriesSet = new Set(); + removed = [this.provisionalVisibleRepository]; + this.provisionalVisibleRepository = undefined; + } + } + } + this._visibleRepositories.push(repository); this._visibleRepositoriesSet.add(repository); - - this._onDidChangeRepositories.fire({ added: [repository], removed: Iterable.empty() }); + this._onDidChangeRepositories.fire({ added: [repository], removed }); if (!this._focusedRepository) { this.focus(repository); @@ -108,6 +173,10 @@ export class SCMViewService implements ISCMViewService { } private onDidRemoveRepository(repository: ISCMRepository): void { + if (!this.didFinishLoading) { + this.eventuallyFinishLoading(); + } + const index = this._visibleRepositories.indexOf(repository); if (index > -1) { @@ -166,6 +235,32 @@ export class SCMViewService implements ISCMViewService { this._onDidFocusRepository.fire(repository); } + private onWillSaveState(): void { + if (!this.didFinishLoading) { // don't remember state, if the workbench didn't really finish loading + return; + } + + const all = this.scmService.repositories.map(r => getProviderStorageKey(r.provider)); + const visible = this.visibleRepositories.map(r => all.indexOf(getProviderStorageKey(r.provider))); + const raw = JSON.stringify({ all, visible }); + + this.storageService.store('scm:view:visibleRepositories', raw, StorageScope.WORKSPACE, StorageTarget.MACHINE); + } + + @debounce(2000) + private eventuallyFinishLoading(): void { + this.finishLoading(); + } + + private finishLoading(): void { + if (this.didFinishLoading) { + return; + } + + this.didFinishLoading = true; + this.previousState = undefined; + } + dispose(): void { this.disposables.dispose(); this._onDidChangeRepositories.dispose(); diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index 7ce0990af..407c50843 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -34,7 +34,8 @@ export interface ISCMResource { readonly resourceGroup: ISCMResourceGroup; readonly sourceUri: URI; readonly decorations: ISCMResourceDecorations; - readonly contextValue?: string; + readonly contextValue: string | undefined; + readonly command: Command | undefined; open(preserveFocus: boolean): Promise; } diff --git a/src/vs/workbench/contrib/scm/common/scmService.ts b/src/vs/workbench/contrib/scm/common/scmService.ts index 43da9ea76..48af74fb9 100644 --- a/src/vs/workbench/contrib/scm/common/scmService.ts +++ b/src/vs/workbench/contrib/scm/common/scmService.ts @@ -8,7 +8,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { ISCMService, ISCMProvider, ISCMInput, ISCMRepository, IInputValidator, ISCMInputChangeEvent, SCMInputChangeReason } from './scm'; import { ILogService } from 'vs/platform/log/common/log'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { HistoryNavigator2 } from 'vs/base/common/history'; class SCMInput implements ISCMInput { @@ -97,7 +97,7 @@ class SCMInput implements ISCMInput { } if (this.repository.provider.rootUri) { - this.storageService.store(historyKey, JSON.stringify([...this.historyNavigator]), StorageScope.GLOBAL); + this.storageService.store(historyKey, JSON.stringify([...this.historyNavigator]), StorageScope.GLOBAL, StorageTarget.USER); } }); } diff --git a/src/vs/workbench/contrib/search/browser/replaceService.ts b/src/vs/workbench/contrib/search/browser/replaceService.ts index 83a6db8d7..c85764d07 100644 --- a/src/vs/workbench/contrib/search/browser/replaceService.ts +++ b/src/vs/workbench/contrib/search/browser/replaceService.ts @@ -24,6 +24,8 @@ import { IBulkEditService, ResourceTextEdit } from 'vs/editor/browser/services/b import { Range } from 'vs/editor/common/core/range'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { mergeSort } from 'vs/base/common/arrays'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { dirname } from 'vs/base/common/resources'; const REPLACE_PREVIEW = 'replacePreview'; @@ -93,7 +95,8 @@ export class ReplaceService implements IReplaceService { @ITextFileService private readonly textFileService: ITextFileService, @IEditorService private readonly editorService: IEditorService, @ITextModelService private readonly textModelResolverService: ITextModelService, - @IBulkEditService private readonly bulkEditorService: IBulkEditService + @IBulkEditService private readonly bulkEditorService: IBulkEditService, + @ILabelService private readonly labelService: ILabelService ) { } replace(match: Match): Promise; @@ -113,6 +116,7 @@ export class ReplaceService implements IReplaceService { leftResource: fileMatch.resource, rightResource: toReplaceResource(fileMatch.resource), label: nls.localize('fileReplaceChanges', "{0} ↔ {1} (Replace Preview)", fileMatch.name(), fileMatch.name()), + description: this.labelService.getUriLabel(dirname(fileMatch.resource), { relative: true }), options: { preserveFocus, pinned, diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 041daae25..b0ccc8847 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -28,8 +28,8 @@ import { defaultQuickAccessContextKeyValue } from 'vs/workbench/browser/quickacc import { CATEGORIES, Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { Extensions as ViewExtensions, IViewsRegistry, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; -import { getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; -import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition, IExplorerService, VIEWLET_ID as VIEWLET_ID_FILES } from 'vs/workbench/contrib/files/common/files'; +import { getMultiSelectedResources, IExplorerService } from 'vs/workbench/contrib/files/browser/files'; +import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition, VIEWLET_ID as VIEWLET_ID_FILES } from 'vs/workbench/contrib/files/common/files'; import { registerContributions as replaceContributions } from 'vs/workbench/contrib/search/browser/replaceContributions'; import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, togglePreserveCaseCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, ExpandAllAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; @@ -509,11 +509,11 @@ const viewContainer = Registry.as(ViewExtensions.ViewCo name: nls.localize('name', "Search"), ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), hideIfEmpty: true, - icon: searchViewIcon.classNames, + icon: searchViewIcon, order: 1 }, ViewContainerLocation.Sidebar); -const viewDescriptor = { id: VIEW_ID, containerIcon: 'codicon-search', name: nls.localize('search', "Search"), ctorDescriptor: new SyncDescriptor(SearchView), canToggleVisibility: false, canMoveView: true }; +const viewDescriptor = { id: VIEW_ID, containerIcon: searchViewIcon, name: nls.localize('search', "Search"), ctorDescriptor: new SyncDescriptor(SearchView), canToggleVisibility: false, canMoveView: true }; // Register search default location to sidebar Registry.as(ViewExtensions.ViewsRegistry).registerViews([viewDescriptor], viewContainer); @@ -824,7 +824,7 @@ configurationRegistry.registerConfiguration({ nls.localize('search.actionsPositionAuto', "Position the actionbar to the right when the search view is narrow, and immediately after the content when the search view is wide."), nls.localize('search.actionsPositionRight', "Always position the actionbar to the right."), ], - default: 'auto', + default: 'right', description: nls.localize('search.actionsPosition', "Controls the positioning of the actionbar on rows in the search view.") }, 'search.searchOnType': { @@ -861,7 +861,7 @@ configurationRegistry.registerConfiguration({ 'search.searchEditor.reusePriorSearchConfiguration': { type: 'boolean', default: false, - markdownDescription: nls.localize({ key: 'search.searchEditor.reusePriorSearchConfiguration', comment: ['"Search Editor" is a type that editor that can display search results. "includes, excludes, and flags" just refers to settings that affect search. For example, the "search.exclude" setting.'] }, "When enabled, new Search Editors will reuse the includes, excludes, and flags of the previously opened Search Editor") + markdownDescription: nls.localize({ key: 'search.searchEditor.reusePriorSearchConfiguration', comment: ['"Search Editor" is a type of editor that can display search results. "includes, excludes, and flags" refers to the "files to include" and "files to exclude" input boxes, and the flags that control whether a query is case-sensitive or a regex.'] }, "When enabled, new Search Editors will reuse the includes, excludes, and flags of the previously opened Search Editor") }, 'search.searchEditor.defaultNumberOfContextLines': { type: ['number', 'null'], diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index d2f108408..4e136901b 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -29,6 +29,7 @@ import { SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/sea import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; import { searchRefreshIcon, searchCollapseAllIcon, searchExpandAllIcon, searchClearIcon, searchReplaceAllIcon, searchReplaceIcon, searchRemoveIcon, searchStopIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; export function isSearchViewFocused(viewsService: IViewsService): boolean { const searchView = getSearchView(viewsService); @@ -153,7 +154,7 @@ export abstract class FindOrReplaceInFilesAction extends Action { const searchAndReplaceWidget = openedView.searchAndReplaceWidget; searchAndReplaceWidget.toggleReplace(this.expandSearchReplaceWidget); - const updatedText = openedView.updateTextFromSelection({ allowUnselectedWord: !this.expandSearchReplaceWidget }); + const updatedText = openedView.updateTextFromFindWidgetOrSelection({ allowUnselectedWord: !this.expandSearchReplaceWidget }); openedView.searchAndReplaceWidget.focus(undefined, updatedText, updatedText); } }); @@ -182,7 +183,7 @@ export const FindInFilesCommand: ICommandHandler = (accessor, args: IFindInFiles if (typeof args.query === 'string') { openedView.setSearchParameters(args); } else { - updatedText = openedView.updateTextFromSelection({ allowUnselectedWord: typeof args.replace !== 'string' }); + updatedText = openedView.updateTextFromFindWidgetOrSelection({ allowUnselectedWord: typeof args.replace !== 'string' }); } openedView.searchAndReplaceWidget.focus(undefined, updatedText, updatedText); } @@ -279,7 +280,7 @@ export class RefreshAction extends Action { constructor(id: string, label: string, @IViewsService private readonly viewsService: IViewsService ) { - super(id, label, 'search-action ' + searchRefreshIcon.classNames); + super(id, label, 'search-action ' + ThemeIcon.asClassName(searchRefreshIcon)); } get enabled(): boolean { @@ -309,7 +310,7 @@ export class CollapseDeepestExpandedLevelAction extends Action { constructor(id: string, label: string, @IViewsService private readonly viewsService: IViewsService ) { - super(id, label, 'search-action ' + searchCollapseAllIcon.classNames); + super(id, label, 'search-action ' + ThemeIcon.asClassName(searchCollapseAllIcon)); this.update(); } @@ -365,7 +366,7 @@ export class ExpandAllAction extends Action { constructor(id: string, label: string, @IViewsService private readonly viewsService: IViewsService ) { - super(id, label, 'search-action ' + searchExpandAllIcon.classNames); + super(id, label, 'search-action ' + ThemeIcon.asClassName(searchExpandAllIcon)); this.update(); } @@ -449,7 +450,7 @@ export class ClearSearchResultsAction extends Action { constructor(id: string, label: string, @IViewsService private readonly viewsService: IViewsService ) { - super(id, label, 'search-action ' + searchClearIcon.classNames); + super(id, label, 'search-action ' + ThemeIcon.asClassName(searchClearIcon)); this.update(); } @@ -475,7 +476,7 @@ export class CancelSearchAction extends Action { constructor(id: string, label: string, @IViewsService private readonly viewsService: IViewsService ) { - super(id, label, 'search-action ' + searchStopIcon.classNames); + super(id, label, 'search-action ' + ThemeIcon.asClassName(searchStopIcon)); this.update(); } @@ -610,7 +611,7 @@ export class RemoveAction extends AbstractSearchAndReplaceAction { private viewer: WorkbenchObjectTree, private element: RenderableMatch ) { - super('remove', RemoveAction.LABEL, searchRemoveIcon.classNames); + super('remove', RemoveAction.LABEL, ThemeIcon.asClassName(searchRemoveIcon)); } run(): Promise { @@ -650,7 +651,7 @@ export class ReplaceAllAction extends AbstractSearchAndReplaceAction { private fileMatch: FileMatch, @IKeybindingService keyBindingService: IKeybindingService ) { - super(Constants.ReplaceAllInFileActionId, appendKeyBindingLabel(ReplaceAllAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceAllInFileActionId), keyBindingService), searchReplaceAllIcon.classNames); + super(Constants.ReplaceAllInFileActionId, appendKeyBindingLabel(ReplaceAllAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceAllInFileActionId), keyBindingService), ThemeIcon.asClassName(searchReplaceAllIcon)); } run(): Promise { @@ -674,7 +675,7 @@ export class ReplaceAllInFolderAction extends AbstractSearchAndReplaceAction { constructor(private viewer: WorkbenchObjectTree, private folderMatch: FolderMatch, @IKeybindingService keyBindingService: IKeybindingService ) { - super(Constants.ReplaceAllInFolderActionId, appendKeyBindingLabel(ReplaceAllInFolderAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceAllInFolderActionId), keyBindingService), searchReplaceAllIcon.classNames); + super(Constants.ReplaceAllInFolderActionId, appendKeyBindingLabel(ReplaceAllInFolderAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceAllInFolderActionId), keyBindingService), ThemeIcon.asClassName(searchReplaceAllIcon)); } run(): Promise { @@ -701,7 +702,7 @@ export class ReplaceAction extends AbstractSearchAndReplaceAction { @IConfigurationService private readonly configurationService: IConfigurationService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService ) { - super(Constants.ReplaceActionId, appendKeyBindingLabel(ReplaceAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceActionId), keyBindingService), searchReplaceIcon.classNames); + super(Constants.ReplaceActionId, appendKeyBindingLabel(ReplaceAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceActionId), keyBindingService), ThemeIcon.asClassName(searchReplaceIcon)); } async run(): Promise { diff --git a/src/vs/workbench/contrib/search/browser/searchIcons.ts b/src/vs/workbench/contrib/search/browser/searchIcons.ts index 2138fbaa3..8c6d633bd 100644 --- a/src/vs/workbench/contrib/search/browser/searchIcons.ts +++ b/src/vs/workbench/contrib/search/browser/searchIcons.ts @@ -3,25 +3,25 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Codicon, registerIcon } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; +import { localize } from 'vs/nls'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; -export const searchDetailsIcon = registerIcon('search-details', Codicon.ellipsis); +export const searchDetailsIcon = registerIcon('search-details', Codicon.ellipsis, localize('searchDetailsIcon', 'Icon to make search details visible.')); -export const searchShowContextIcon = registerIcon('search-show-context', Codicon.listSelection); -export const searchHideReplaceIcon = registerIcon('search-hide-replace', Codicon.chevronRight); -export const searchShowReplaceIcon = registerIcon('search-show-replace', Codicon.chevronDown); -export const searchReplaceAllIcon = registerIcon('search-replace-all', Codicon.replaceAll); -export const searchReplaceIcon = registerIcon('search-replace', Codicon.replace); -export const searchRemoveIcon = registerIcon('search-remove', Codicon.close); +export const searchShowContextIcon = registerIcon('search-show-context', Codicon.listSelection, localize('searchShowContextIcon', 'Icon for toggle the context in the search editor.')); +export const searchHideReplaceIcon = registerIcon('search-hide-replace', Codicon.chevronRight, localize('searchHideReplaceIcon', 'Icon to collapse the replace section in the search view.')); +export const searchShowReplaceIcon = registerIcon('search-show-replace', Codicon.chevronDown, localize('searchShowReplaceIcon', 'Icon to expand the replace section in the search view.')); +export const searchReplaceAllIcon = registerIcon('search-replace-all', Codicon.replaceAll, localize('searchReplaceAllIcon', 'Icon for replace all in the search view.')); +export const searchReplaceIcon = registerIcon('search-replace', Codicon.replace, localize('searchReplaceIcon', 'Icon for replace in the search view.')); +export const searchRemoveIcon = registerIcon('search-remove', Codicon.close, localize('searchRemoveIcon', 'Icon to remove a search result.')); -export const searchRefreshIcon = registerIcon('search-refresh', Codicon.refresh); -export const searchCollapseAllIcon = registerIcon('search-collapse-results', Codicon.collapseAll); -export const searchExpandAllIcon = registerIcon('search-expand-results', Codicon.expandAll); -export const searchClearIcon = registerIcon('search-clear-results', Codicon.clearAll); -export const searchStopIcon = Codicon.searchStop; +export const searchRefreshIcon = registerIcon('search-refresh', Codicon.refresh, localize('searchRefreshIcon', 'Icon for refresh in the search view.')); +export const searchCollapseAllIcon = registerIcon('search-collapse-results', Codicon.collapseAll, localize('searchCollapseAllIcon', 'Icon for collapse results in the search view.')); +export const searchExpandAllIcon = registerIcon('search-expand-results', Codicon.expandAll, localize('searchExpandAllIcon', 'Icon for expand results in the search view.')); +export const searchClearIcon = registerIcon('search-clear-results', Codicon.clearAll, localize('searchClearIcon', 'Icon for clear results in the search view.')); +export const searchStopIcon = registerIcon('search-stop', Codicon.searchStop, localize('searchStopIcon', 'Icon for stop in the search view.')); -export const searchViewIcon = Codicon.search; - -export const searchNewEditorIcon = registerIcon('search-new-editor', Codicon.newFile); -export const searchGotoFileIcon = registerIcon('search-goto-file', Codicon.goToFile); +export const searchViewIcon = registerIcon('search-view-icon', Codicon.search, localize('searchViewIcon', 'View icon of the search view.')); +export const searchNewEditorIcon = registerIcon('search-new-editor', Codicon.newFile, localize('searchNewEditorIcon', 'Icon for the action to open a new search editor.')); diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 71a2cae44..5a42e7b88 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -22,6 +22,7 @@ import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/searchview'; import { ICodeEditor, isCodeEditor, isDiffEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { CommonFindController } from 'vs/editor/contrib/find/findController'; import * as nls from 'vs/nls'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; @@ -37,7 +38,7 @@ import { IProgressService, IProgressStep, IProgress } from 'vs/platform/progress import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, SearchSortOrder, SearchCompletionExitCode } from 'vs/workbench/services/search/common/search'; import { ISearchHistoryService, ISearchHistoryValues } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, listActiveSelectionForeground, foreground } from 'vs/platform/theme/common/colorRegistry'; -import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { OpenFileFolderAction, OpenFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { ResourceLabels } from 'vs/workbench/browser/labels'; @@ -59,7 +60,7 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Memento, MementoObject } from 'vs/workbench/common/memento'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { MultiCursorSelectionController } from 'vs/editor/contrib/multicursor/multicursor'; import { Selection } from 'vs/editor/common/core/selection'; @@ -219,7 +220,7 @@ export class SearchView extends ViewPane { this.viewModel = this._register(this.searchWorkbenchService.searchModel); this.queryBuilder = this.instantiationService.createInstance(QueryBuilder); this.memento = new Memento(this.id, storageService); - this.viewletState = this.memento.getMemento(StorageScope.WORKSPACE); + this.viewletState = this.memento.getMemento(StorageScope.WORKSPACE, StorageTarget.USER); this._register(this.fileService.onDidFilesChange(e => this.onFilesChanged(e))); this._register(this.textFileService.untitled.onDidDispose(model => this.onUntitledDidDispose(model.resource))); @@ -282,7 +283,7 @@ export class SearchView extends ViewPane { // Toggle query details button this.toggleQueryDetailsButton = dom.append(this.queryDetails, - $('.more' + searchDetailsIcon.cssSelector, { tabindex: 0, role: 'button', title: nls.localize('moreSearch', "Toggle Search Details") })); + $('.more' + ThemeIcon.asCSSSelector(searchDetailsIcon), { tabindex: 0, role: 'button', title: nls.localize('moreSearch', "Toggle Search Details") })); this._register(dom.addDisposableListener(this.toggleQueryDetailsButton, dom.EventType.CLICK, e => { dom.EventHelper.stop(e); @@ -888,28 +889,63 @@ export class SearchView extends ViewPane { } } - updateTextFromSelection({ allowUnselectedWord = true, allowSearchOnType = true }): boolean { - let updatedText = false; - const seedSearchStringFromSelection = this.configurationService.getValue('editor').find!.seedSearchStringFromSelection; - if (seedSearchStringFromSelection) { - let selectedText = this.getSearchTextFromEditor(allowUnselectedWord); - if (selectedText) { - if (this.searchWidget.searchInput.getRegex()) { - selectedText = strings.escapeRegExpCharacters(selectedText); - } - - if (allowSearchOnType && !this.viewModel.searchResult.isDirty) { - this.searchWidget.setValue(selectedText); - } else { - this.pauseSearching = true; - this.searchWidget.setValue(selectedText); - this.pauseSearching = false; - } - updatedText = true; + updateTextFromFindWidgetOrSelection({ allowUnselectedWord = true, allowSearchOnType = true }): boolean { + const activeEditor = this.editorService.activeTextEditorControl; + if (isCodeEditor(activeEditor)) { + const controller = CommonFindController.get(activeEditor as ICodeEditor); + if (controller.isFindInputFocused()) { + return this.updateTextFromFindWidget(controller, { allowSearchOnType }); } } - return updatedText; + return this.updateTextFromSelection({ allowUnselectedWord, allowSearchOnType }); + } + + private updateTextFromFindWidget(controller: CommonFindController, { allowSearchOnType = true }): boolean { + if (!this.searchConfig.seedWithNearestWord && (window.getSelection()?.toString() ?? '') === '') { + return false; + } + + const searchString = controller.getState().searchString; + if (searchString === '') { + return false; + } + + this.searchWidget.searchInput.setCaseSensitive(controller.getState().matchCase); + this.searchWidget.searchInput.setWholeWords(controller.getState().wholeWord); + this.searchWidget.searchInput.setRegex(controller.getState().isRegex); + this.updateText(searchString, allowSearchOnType); + + return true; + } + + private updateTextFromSelection({ allowUnselectedWord = true, allowSearchOnType = true }): boolean { + const seedSearchStringFromSelection = this.configurationService.getValue('editor').find!.seedSearchStringFromSelection; + if (!seedSearchStringFromSelection) { + return false; + } + + let selectedText = this.getSearchTextFromEditor(allowUnselectedWord); + if (selectedText === null) { + return false; + } + + if (this.searchWidget.searchInput.getRegex()) { + selectedText = strings.escapeRegExpCharacters(selectedText); + } + + this.updateText(selectedText, allowSearchOnType); + return true; + } + + private updateText(text: string, allowSearchOnType: boolean = true) { + if (allowSearchOnType && !this.viewModel.searchResult.isDirty) { + this.searchWidget.setValue(text); + } else { + this.pauseSearching = true; + this.searchWidget.setValue(text); + this.pauseSearching = false; + } } focusNextInputBox(): void { diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index c566e5d38..a7dbe426c 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -25,7 +25,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; import { attachFindReplaceInputBoxStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/browser/contextScopedHistoryWidget'; import { appendKeyBindingLabel, isSearchViewFocused, getSearchView } from 'vs/workbench/contrib/search/browser/searchActions'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; @@ -34,6 +34,7 @@ import { isMacintosh } from 'vs/base/common/platform'; import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; import { IViewsService } from 'vs/workbench/common/views'; import { searchReplaceAllIcon, searchHideReplaceIcon, searchShowContextIcon, searchShowReplaceIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; +import { ToggleSearchEditorContextLinesCommandId } from 'vs/workbench/contrib/searchEditor/browser/constants'; /** Specified in searchview.css */ export const SingleLineInputHeight = 24; @@ -56,7 +57,7 @@ class ReplaceAllAction extends Action { static readonly ID: string = 'search.action.replaceAll'; constructor(private _searchWidget: SearchWidget) { - super(ReplaceAllAction.ID, '', searchReplaceAllIcon.classNames, false); + super(ReplaceAllAction.ID, '', ThemeIcon.asClassName(searchReplaceAllIcon), false); } set searchWidget(searchWidget: SearchWidget) { @@ -293,9 +294,9 @@ export class SearchWidget extends Widget { }; this.toggleReplaceButton = this._register(new Button(parent, opts)); this.toggleReplaceButton.element.setAttribute('aria-expanded', 'false'); - this.toggleReplaceButton.element.classList.add(...searchHideReplaceIcon.classNamesArray); - this.toggleReplaceButton.icon = 'toggle-replace-button'; - // TODO@joh need to dispose this listener eventually + this.toggleReplaceButton.element.classList.add('toggle-replace-button'); + this.toggleReplaceButton.icon = ThemeIcon.asCSSIcon(searchHideReplaceIcon); + // TODO@joao need to dispose this listener eventually this.toggleReplaceButton.onDidClick(() => this.onToggleReplaceButton()); this.toggleReplaceButton.element.title = nls.localize('search.replace.toggle.button.title', "Toggle Replace"); } @@ -351,7 +352,11 @@ export class SearchWidget extends Widget { this._register(this.searchInputFocusTracker.onDidBlur(() => this.searchInputBoxFocused.set(false))); - this.showContextCheckbox = new Checkbox({ isChecked: false, title: nls.localize('showContext', "Show Context"), icon: searchShowContextIcon }); + this.showContextCheckbox = new Checkbox({ + isChecked: false, + title: appendKeyBindingLabel(nls.localize('showContext', "Toggle Context Lines"), this.keyBindingService.lookupKeybinding(ToggleSearchEditorContextLinesCommandId), this.keyBindingService), + icon: ThemeIcon.asCSSIcon(searchShowContextIcon) + }); this._register(this.showContextCheckbox.onChange(() => this.onContextLinesChanged())); if (options.showContextToggle) { @@ -431,11 +436,11 @@ export class SearchWidget extends Widget { private onToggleReplaceButton(): void { this.replaceContainer.classList.toggle('disabled'); if (this.isReplaceShown()) { - this.toggleReplaceButton.element.classList.remove(...searchHideReplaceIcon.classNamesArray); - this.toggleReplaceButton.element.classList.add(...searchShowReplaceIcon.classNamesArray); + this.toggleReplaceButton.element.classList.remove(...ThemeIcon.asClassNameArray(searchHideReplaceIcon)); + this.toggleReplaceButton.element.classList.add(...ThemeIcon.asClassNameArray(searchShowReplaceIcon)); } else { - this.toggleReplaceButton.element.classList.remove(...searchShowReplaceIcon.classNamesArray); - this.toggleReplaceButton.element.classList.add(...searchHideReplaceIcon.classNamesArray); + this.toggleReplaceButton.element.classList.remove(...ThemeIcon.asClassNameArray(searchShowReplaceIcon)); + this.toggleReplaceButton.element.classList.add(...ThemeIcon.asClassNameArray(searchHideReplaceIcon)); } this.toggleReplaceButton.element.setAttribute('aria-expanded', this.isReplaceShown() ? 'true' : 'false'); this.updateReplaceActiveState(); diff --git a/src/vs/workbench/contrib/search/common/searchHistoryService.ts b/src/vs/workbench/contrib/search/common/searchHistoryService.ts index bb5a9209e..018799b96 100644 --- a/src/vs/workbench/contrib/search/common/searchHistoryService.ts +++ b/src/vs/workbench/contrib/search/common/searchHistoryService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { isEmptyObject } from 'vs/base/common/types'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -61,7 +61,7 @@ export class SearchHistoryService implements ISearchHistoryService { if (isEmptyObject(history)) { this.storageService.remove(SearchHistoryService.SEARCH_HISTORY_KEY, StorageScope.WORKSPACE); } else { - this.storageService.store(SearchHistoryService.SEARCH_HISTORY_KEY, JSON.stringify(history), StorageScope.WORKSPACE); + this.storageService.store(SearchHistoryService.SEARCH_HISTORY_KEY, JSON.stringify(history), StorageScope.WORKSPACE, StorageTarget.USER); } } } diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index 0c6e0d194..3c7044d06 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -31,6 +31,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { compareFileNames, compareFileExtensions, comparePaths } from 'vs/base/common/comparers'; import { IFileService, IFileStatWithMetadata } from 'vs/platform/files/common/files'; import { Schemas } from 'vs/base/common/network'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; export class Match { @@ -698,7 +699,7 @@ export class SearchResult extends Disposable { private _folderMatches: FolderMatchWithResource[] = []; private _otherFilesMatch: FolderMatch | null = null; - private _folderMatchesMap: TernarySearchTree = TernarySearchTree.forUris(); + private _folderMatchesMap: TernarySearchTree = TernarySearchTree.forUris(key => this.uriIdentityService.extUri.ignorePathCasing(key)); private _showHighlights: boolean = false; private _query: ITextQuery | null = null; @@ -713,6 +714,7 @@ export class SearchResult extends Disposable { @ITelemetryService private readonly telemetryService: ITelemetryService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IModelService private readonly modelService: IModelService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, ) { super(); this._rangeHighlightDecorations = this.instantiationService.createInstance(RangeHighlightDecorations); @@ -743,7 +745,7 @@ export class SearchResult extends Disposable { .then(() => this._isDirty = false); this._rangeHighlightDecorations.removeHighlightRange(); - this._folderMatchesMap = TernarySearchTree.forUris(); + this._folderMatchesMap = TernarySearchTree.forUris(key => this.uriIdentityService.extUri.ignorePathCasing(key)); if (!query) { return; @@ -965,7 +967,7 @@ export class SearchResult extends Disposable { private disposeMatches(): void { this.folderMatches().forEach(folderMatch => folderMatch.dispose()); this._folderMatches = []; - this._folderMatchesMap = TernarySearchTree.forUris(); + this._folderMatchesMap = TernarySearchTree.forUris(key => this.uriIdentityService.extUri.ignorePathCasing(key)); this._rangeHighlightDecorations.removeHighlightRange(); } diff --git a/src/vs/workbench/contrib/search/test/browser/queryBuilder.test.ts b/src/vs/workbench/contrib/search/test/browser/queryBuilder.test.ts index 0a86fa7f4..9bd262f90 100644 --- a/src/vs/workbench/contrib/search/test/browser/queryBuilder.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/queryBuilder.test.ts @@ -10,13 +10,14 @@ import { URI as uri } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { IWorkspaceContextService, toWorkspaceFolder, toWorkspaceFolders, Workspace } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, toWorkspaceFolder, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; import { ISearchPathsInfo, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IFileQuery, IFolderQuery, IPatternInfo, ITextQuery, QueryType } from 'vs/workbench/services/search/common/search'; import { TestPathService, TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; const DEFAULT_EDITOR_CONFIG = {}; const DEFAULT_USER_CONFIG = { useRipgrep: true, useIgnoreFiles: true, useGlobalIgnoreFiles: true }; diff --git a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts index 363eab383..9dc2993ce 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts @@ -17,6 +17,10 @@ import { isWindows } from 'vs/base/common/platform'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; +import { FileService } from 'vs/platform/files/common/fileService'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService'; suite('Search - Viewlet', () => { let instantiation: TestInstantiationService; @@ -25,6 +29,7 @@ suite('Search - Viewlet', () => { instantiation = new TestInstantiationService(); instantiation.stub(IModelService, stubModelService(instantiation)); instantiation.set(IWorkspaceContextService, new TestContextService(TestWorkspace)); + instantiation.stub(IUriIdentityService, new UriIdentityService(new FileService(new NullLogService()))); }); test('Data Source', function () { diff --git a/src/vs/workbench/contrib/search/test/common/searchModel.test.ts b/src/vs/workbench/contrib/search/test/common/searchModel.test.ts index 545d7de99..0a99a6a7a 100644 --- a/src/vs/workbench/contrib/search/test/common/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/common/searchModel.test.ts @@ -21,6 +21,10 @@ import { SearchModel } from 'vs/workbench/contrib/search/common/searchModel'; import * as process from 'vs/base/common/process'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; +import { FileService } from 'vs/platform/files/common/fileService'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService'; const nullEvent = new class { id: number = -1; @@ -72,6 +76,7 @@ suite('SearchModel', () => { instantiationService.stub(IModelService, stubModelService(instantiationService)); instantiationService.stub(ISearchService, {}); instantiationService.stub(ISearchService, 'textSearch', Promise.resolve({ results: [] })); + instantiationService.stub(IUriIdentityService, new UriIdentityService(new FileService(new NullLogService()))); const config = new TestConfigurationService(); config.setUserConfiguration('search', { searchOnType: true }); diff --git a/src/vs/workbench/contrib/search/test/common/searchResult.test.ts b/src/vs/workbench/contrib/search/test/common/searchResult.test.ts index f46f158ac..e147ce757 100644 --- a/src/vs/workbench/contrib/search/test/common/searchResult.test.ts +++ b/src/vs/workbench/contrib/search/test/common/searchResult.test.ts @@ -18,6 +18,10 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService'; +import { FileService } from 'vs/platform/files/common/fileService'; +import { NullLogService } from 'vs/platform/log/common/log'; const lineOneRange = new OneLineRange(1, 0, 1); @@ -29,6 +33,7 @@ suite('SearchResult', () => { instantiationService = new TestInstantiationService(); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(IModelService, stubModelService(instantiationService)); + instantiationService.stub(IUriIdentityService, new UriIdentityService(new FileService(new NullLogService()))); instantiationService.stubPromise(IReplaceService, {}); instantiationService.stubPromise(IReplaceService, 'replace', null); }); diff --git a/src/vs/workbench/contrib/search/test/electron-browser/queryBuilder.test.ts b/src/vs/workbench/contrib/search/test/electron-browser/queryBuilder.test.ts index 92b450d22..e6a021cc7 100644 --- a/src/vs/workbench/contrib/search/test/electron-browser/queryBuilder.test.ts +++ b/src/vs/workbench/contrib/search/test/electron-browser/queryBuilder.test.ts @@ -6,12 +6,13 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { IWorkspaceContextService, toWorkspaceFolder, Workspace } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ISearchPathsInfo, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; import { TestEnvironmentService, TestNativePathService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { assertEqualSearchPathResults, getUri, patternsToIExpression, globalGlob, fixPath } from 'vs/workbench/contrib/search/test/browser/queryBuilder.test'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; +import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; const DEFAULT_EDITOR_CONFIG = {}; const DEFAULT_USER_CONFIG = { useRipgrep: true, useIgnoreFiles: true, useGlobalIgnoreFiles: true }; diff --git a/src/vs/workbench/contrib/searchEditor/browser/constants.ts b/src/vs/workbench/contrib/searchEditor/browser/constants.ts index b37c41a7a..e4d4b412b 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/constants.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/constants.ts @@ -15,3 +15,4 @@ export const SearchEditorID = 'workbench.editor.searchEditor'; export const OpenNewEditorCommandId = 'search.action.openNewEditor'; export const OpenEditorCommandId = 'search.action.openEditor'; +export const ToggleSearchEditorContextLinesCommandId = 'toggleSearchEditorContextLines'; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index 08518d3e3..cc6faa97d 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -15,7 +15,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -41,7 +41,6 @@ const FocusQueryEditorWidgetCommandId = 'search.action.focusQueryEditorWidget'; const ToggleSearchEditorCaseSensitiveCommandId = 'toggleSearchEditorCaseSensitive'; const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord'; const ToggleSearchEditorRegexCommandId = 'toggleSearchEditorRegex'; -const ToggleSearchEditorContextLinesCommandId = 'toggleSearchEditorContextLines'; const IncreaseSearchEditorContextLinesCommandId = 'increaseSearchEditorContextLines'; const DecreaseSearchEditorContextLinesCommandId = 'decreaseSearchEditorContextLines'; @@ -161,60 +160,6 @@ Registry.as(EditorInputExtensions.EditorInputFactor //#endregion //#region Commands -KeybindingsRegistry.registerCommandAndKeybindingRule(Object.assign({ - id: ToggleSearchEditorCaseSensitiveCommandId, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, SearchConstants.SearchInputBoxFocusedKey), - handler: toggleSearchEditorCaseSensitiveCommand -}, ToggleCaseSensitiveKeybinding)); - -KeybindingsRegistry.registerCommandAndKeybindingRule(Object.assign({ - id: ToggleSearchEditorWholeWordCommandId, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, SearchConstants.SearchInputBoxFocusedKey), - handler: toggleSearchEditorWholeWordCommand -}, ToggleWholeWordKeybinding)); - -KeybindingsRegistry.registerCommandAndKeybindingRule(Object.assign({ - id: ToggleSearchEditorRegexCommandId, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, SearchConstants.SearchInputBoxFocusedKey), - handler: toggleSearchEditorRegexCommand -}, ToggleRegexKeybinding)); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: ToggleSearchEditorContextLinesCommandId, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor), - handler: toggleSearchEditorContextLinesCommand, - primary: KeyMod.Alt | KeyCode.KEY_L, - mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_L } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: IncreaseSearchEditorContextLinesCommandId, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor), - handler: (accessor: ServicesAccessor) => modifySearchEditorContextLinesCommand(accessor, true), - primary: KeyMod.Alt | KeyCode.US_EQUAL -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: DecreaseSearchEditorContextLinesCommandId, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor), - handler: (accessor: ServicesAccessor) => modifySearchEditorContextLinesCommand(accessor, false), - primary: KeyMod.Alt | KeyCode.US_MINUS -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: SelectAllSearchEditorMatchesCommandId, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor), - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_L, - handler: selectAllSearchEditorMatchesCommand -}); - CommandsRegistry.registerCommand( CleanSearchEditorStateCommandId, (accessor: ServicesAccessor) => { @@ -289,7 +234,6 @@ registerAction2(class extends Action2 { title: { value: localize('searchEditor.deleteResultBlock', "Delete File Results"), original: 'Delete File Results' }, keybinding: { weight: KeybindingWeight.EditorContrib, - when: SearchEditorConstants.InSearchEditor, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Backspace, }, precondition: SearchEditorConstants.InSearchEditor, @@ -416,13 +360,10 @@ registerAction2(class extends Action2 { id: FocusQueryEditorWidgetCommandId, title: { value: localize('search.action.focusQueryEditorWidget', "Focus Search Editor Input"), original: 'Focus Search Editor Input' }, category, - menu: { - id: MenuId.CommandPalette, - when: ActiveEditorContext.isEqualTo(SearchEditorConstants.SearchEditorID) - }, + f1: true, + precondition: SearchEditorConstants.InSearchEditor, keybinding: { primary: KeyCode.Escape, - when: SearchEditorConstants.InSearchEditor, weight: KeybindingWeight.EditorContrib } }); @@ -435,4 +376,134 @@ registerAction2(class extends Action2 { } } }); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: ToggleSearchEditorCaseSensitiveCommandId, + title: { value: localize('searchEditor.action.toggleSearchEditorCaseSensitive', "Toggle Match Case"), original: 'Toggle Match Case' }, + category, + f1: true, + precondition: SearchEditorConstants.InSearchEditor, + keybinding: Object.assign({ + weight: KeybindingWeight.WorkbenchContrib, + when: SearchConstants.SearchInputBoxFocusedKey, + }, ToggleCaseSensitiveKeybinding) + }); + } + run(accessor: ServicesAccessor) { + toggleSearchEditorCaseSensitiveCommand(accessor); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: ToggleSearchEditorWholeWordCommandId, + title: { value: localize('searchEditor.action.toggleSearchEditorWholeWord', "Toggle Match Whole Word"), original: 'Toggle Match Whole Word' }, + category, + f1: true, + precondition: SearchEditorConstants.InSearchEditor, + keybinding: Object.assign({ + weight: KeybindingWeight.WorkbenchContrib, + when: SearchConstants.SearchInputBoxFocusedKey, + }, ToggleWholeWordKeybinding) + }); + } + run(accessor: ServicesAccessor) { + toggleSearchEditorWholeWordCommand(accessor); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: ToggleSearchEditorRegexCommandId, + title: { value: localize('searchEditor.action.toggleSearchEditorRegex', "Toggle Use Regular Expression"), original: 'Toggle Use Regular Expression"' }, + category, + f1: true, + precondition: SearchEditorConstants.InSearchEditor, + keybinding: Object.assign({ + weight: KeybindingWeight.WorkbenchContrib, + when: SearchConstants.SearchInputBoxFocusedKey, + }, ToggleRegexKeybinding) + }); + } + run(accessor: ServicesAccessor) { + toggleSearchEditorRegexCommand(accessor); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: SearchEditorConstants.ToggleSearchEditorContextLinesCommandId, + title: { value: localize('searchEditor.action.toggleSearchEditorContextLines', "Toggle Context Lines"), original: 'Toggle Context Lines"' }, + category, + f1: true, + precondition: SearchEditorConstants.InSearchEditor, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.Alt | KeyCode.KEY_L, + mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_L } + } + }); + } + run(accessor: ServicesAccessor) { + toggleSearchEditorContextLinesCommand(accessor); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: IncreaseSearchEditorContextLinesCommandId, + title: { original: 'Increase Context Lines', value: localize('searchEditor.action.increaseSearchEditorContextLines', "Increase Context Lines") }, + category, + f1: true, + precondition: SearchEditorConstants.InSearchEditor, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.Alt | KeyCode.US_EQUAL + } + }); + } + run(accessor: ServicesAccessor) { modifySearchEditorContextLinesCommand(accessor, true); } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: DecreaseSearchEditorContextLinesCommandId, + title: { original: 'Decrease Context Lines', value: localize('searchEditor.action.decreaseSearchEditorContextLines', "Decrease Context Lines") }, + category, + f1: true, + precondition: SearchEditorConstants.InSearchEditor, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.Alt | KeyCode.US_MINUS + } + }); + } + run(accessor: ServicesAccessor) { modifySearchEditorContextLinesCommand(accessor, false); } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: SelectAllSearchEditorMatchesCommandId, + title: { original: 'Select All Matches', value: localize('searchEditor.action.selectAllSearchEditorMatches', "Select All Matches") }, + category, + f1: true, + precondition: SearchEditorConstants.InSearchEditor, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_L, + } + }); + } + run(accessor: ServicesAccessor) { + selectAllSearchEditorMatchesCommand(accessor); + } +}); //#endregion diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index 7b7ddcc6e..caf6d8012 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -34,7 +34,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { inputBorder, registerColor, searchEditorFindMatch, searchEditorFindMatchBorder } from 'vs/platform/theme/common/colorRegistry'; import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; import { EditorOptions, IEditorOpenContext } from 'vs/workbench/common/editor'; @@ -139,7 +139,7 @@ export class SearchEditor extends BaseTextEditor { this.includesExcludesContainer = DOM.append(this.queryEditorContainer, DOM.$('.includes-excludes')); // // Toggle query details button - this.toggleQueryDetailsButton = DOM.append(this.includesExcludesContainer, DOM.$('.expand' + searchDetailsIcon.cssSelector, { tabindex: 0, role: 'button', title: localize('moreSearch', "Toggle Search Details") })); + this.toggleQueryDetailsButton = DOM.append(this.includesExcludesContainer, DOM.$('.expand' + ThemeIcon.asCSSSelector(searchDetailsIcon), { tabindex: 0, role: 'button', title: localize('moreSearch', "Toggle Search Details") })); this._register(DOM.addDisposableListener(this.toggleQueryDetailsButton, DOM.EventType.CLICK, e => { DOM.EventHelper.stop(e); this.toggleIncludesExcludes(); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index 0c124a1f0..f1d1426f4 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -28,6 +28,7 @@ import { OpenNewEditorCommandId } from 'vs/workbench/contrib/searchEditor/browse import { OpenSearchEditorArgs } from 'vs/workbench/contrib/searchEditor/browser/searchEditor.contribution'; import { EditorsOrder } from 'vs/workbench/common/editor'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; export const toggleSearchEditorCaseSensitiveCommand = (accessor: ServicesAccessor) => { const editorService = accessor.get(IEditorService); @@ -85,7 +86,7 @@ export class OpenSearchEditorAction extends Action { constructor(id: string, label: string, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { - super(id, label, searchNewEditorIcon.classNames); + super(id, label, ThemeIcon.asClassName(searchNewEditorIcon)); } update() { diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts index a75dbd61e..c5a551a47 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts @@ -16,7 +16,7 @@ import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { EditorInput, GroupIdentifier, IEditorInput, IMoveResult, IRevertOptions, ISaveOptions, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, EditorResourceAccessor } from 'vs/workbench/common/editor'; import { Memento } from 'vs/workbench/common/memento'; @@ -65,7 +65,7 @@ export class SearchEditorInput extends EditorInput { public get config(): Readonly { return this._config; } public set config(value: Readonly) { this._config = value; - this.memento.getMemento(StorageScope.WORKSPACE).searchConfig = value; + this.memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE).searchConfig = value; this._onDidChangeLabel.fire(); } @@ -276,7 +276,7 @@ export class SearchEditorInput extends EditorInput { const searchFileName = (query.replace(/[^\w \-_]+/g, '_') || 'Search') + SEARCH_EDITOR_EXT; - return joinPath(this.fileDialogService.defaultFilePath(this.pathService.defaultUriScheme) || (await this.pathService.userHome()), searchFileName); + return joinPath(await this.fileDialogService.defaultFilePath(this.pathService.defaultUriScheme), searchFileName); } } @@ -298,7 +298,7 @@ export const getOrMakeSearchEditorInput = ( const reuseOldSettings = searchEditorSettings.reusePriorSearchConfiguration; const defaultNumberOfContextLines = searchEditorSettings.defaultNumberOfContextLines; - const priorConfig: SearchConfiguration = reuseOldSettings ? new Memento(SearchEditorInput.ID, storageService).getMemento(StorageScope.WORKSPACE).searchConfig : {}; + const priorConfig: SearchConfiguration = reuseOldSettings ? new Memento(SearchEditorInput.ID, storageService).getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE).searchConfig : {}; const defaultConfig = defaultSearchConfig(); const config = { ...defaultConfig, ...priorConfig, ...existingData.config }; diff --git a/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts b/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts index fd8440e40..48b60f977 100644 --- a/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts @@ -15,10 +15,9 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { IQuickPickItem, IQuickInputService, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { Codicon } from 'vs/base/common/codicons'; +import { Event } from 'vs/base/common/event'; -interface ISnippetPick extends IQuickPickItem { - snippet: Snippet; -} class Args { @@ -91,7 +90,7 @@ class InsertSnippetAction extends EditorAction { const clipboardService = accessor.get(IClipboardService); const quickInputService = accessor.get(IQuickInputService); - const snippet = await new Promise(async (resolve, reject) => { + const snippet = await new Promise(async (resolve) => { const { lineNumber, column } = editor.getPosition(); let { snippet, name, langId } = Args.fromUser(arg); @@ -129,44 +128,13 @@ class InsertSnippetAction extends EditorAction { if (name) { // take selected snippet - (await snippetService.getSnippets(languageId)).every(snippet => { - if (snippet.name !== name) { - return true; - } - resolve(snippet); - return false; - }); + const snippet = (await snippetService.getSnippets(languageId)).find(snippet => snippet.name === name); + resolve(snippet); + } else { // let user pick a snippet - const snippets = (await snippetService.getSnippets(languageId)).sort(Snippet.compare); - const picks: QuickPickInput[] = []; - let prevSnippet: Snippet | undefined; - for (const snippet of snippets) { - const pick: ISnippetPick = { - label: snippet.prefix, - detail: snippet.description, - snippet - }; - if (!prevSnippet || prevSnippet.snippetSource !== snippet.snippetSource) { - let label = ''; - switch (snippet.snippetSource) { - case SnippetSource.User: - label = nls.localize('sep.userSnippet', "User Snippets"); - break; - case SnippetSource.Extension: - label = nls.localize('sep.extSnippet', "Extension Snippets"); - break; - case SnippetSource.Workspace: - label = nls.localize('sep.workspaceSnippet', "Workspace Snippets"); - break; - } - picks.push({ type: 'separator', label }); - - } - picks.push(pick); - prevSnippet = snippet; - } - return quickInputService.pick(picks, { matchOnDetail: true }).then(pick => resolve(pick && pick.snippet), reject); + const snippet = await this._pickSnippet(snippetService, quickInputService, languageId); + resolve(snippet); } }); @@ -179,6 +147,80 @@ class InsertSnippetAction extends EditorAction { } SnippetController2.get(editor).insert(snippet.codeSnippet, { clipboardText }); } + + private async _pickSnippet(snippetService: ISnippetsService, quickInputService: IQuickInputService, languageId: LanguageId): Promise { + + interface ISnippetPick extends IQuickPickItem { + snippet: Snippet; + } + + const snippets = (await snippetService.getSnippets(languageId, { includeDisabledSnippets: true, includeNoPrefixSnippets: true })).sort(Snippet.compare); + + const makeSnippetPicks = () => { + const result: QuickPickInput[] = []; + let prevSnippet: Snippet | undefined; + for (const snippet of snippets) { + const pick: ISnippetPick = { + label: snippet.prefix || snippet.name, + detail: snippet.description, + snippet + }; + if (!prevSnippet || prevSnippet.snippetSource !== snippet.snippetSource) { + let label = ''; + switch (snippet.snippetSource) { + case SnippetSource.User: + label = nls.localize('sep.userSnippet', "User Snippets"); + break; + case SnippetSource.Extension: + label = nls.localize('sep.extSnippet', "Extension Snippets"); + break; + case SnippetSource.Workspace: + label = nls.localize('sep.workspaceSnippet', "Workspace Snippets"); + break; + } + result.push({ type: 'separator', label }); + } + + if (snippet.snippetSource === SnippetSource.Extension) { + const isEnabled = snippetService.isEnabled(snippet); + if (isEnabled) { + pick.buttons = [{ + iconClass: Codicon.eyeClosed.classNames, + tooltip: nls.localize('disableSnippet', 'Hide from IntelliSense') + }]; + } else { + pick.description = nls.localize('isDisabled', "(hidden from IntelliSense)"); + pick.buttons = [{ + iconClass: Codicon.eye.classNames, + tooltip: nls.localize('enable.snippet', 'Show in IntelliSense') + }]; + } + } + + result.push(pick); + prevSnippet = snippet; + } + return result; + }; + + const picker = quickInputService.createQuickPick(); + picker.placeholder = nls.localize('pick.placeholder', "Select a snippet"); + picker.matchOnDescription = true; + picker.ignoreFocusOut = false; + picker.onDidTriggerItemButton(ctx => { + const isEnabled = snippetService.isEnabled(ctx.item.snippet); + snippetService.updateEnablement(ctx.item.snippet, !isEnabled); + picker.items = makeSnippetPicks(); + }); + picker.items = makeSnippetPicks(); + picker.show(); + + // 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(); + return result; + } } registerEditorAction(InsertSnippetAction); diff --git a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts index c7adf5c4f..7ccba80fa 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts @@ -13,15 +13,24 @@ import { SnippetFile, Snippet } from 'vs/workbench/contrib/snippets/browser/snip export const ISnippetsService = createDecorator('snippetService'); +export interface ISnippetGetOptions { + includeDisabledSnippets?: boolean; + includeNoPrefixSnippets?: boolean; +} + export interface ISnippetsService { readonly _serviceBrand: undefined; getSnippetFiles(): Promise>; - getSnippets(languageId: LanguageId): Promise; + isEnabled(snippet: Snippet): boolean; - getSnippetsSync(languageId: LanguageId): Snippet[]; + updateEnablement(snippet: Snippet, enabled: boolean): void; + + getSnippets(languageId: LanguageId, opt?: ISnippetGetOptions): Promise; + + getSnippetsSync(languageId: LanguageId, opt?: ISnippetGetOptions): Snippet[]; } const languageScopeSchemaId = 'vscode://schemas/snippets'; @@ -56,7 +65,7 @@ const languageScopeSchema: IJSONSchema = { description: nls.localize('snippetSchema.json', 'User snippet configuration'), additionalProperties: { type: 'object', - required: ['prefix', 'body'], + required: ['body'], properties: snippetSchemaProperties, additionalProperties: false } @@ -76,7 +85,7 @@ const globalSchema: IJSONSchema = { description: nls.localize('snippetSchema.json', 'User snippet configuration'), additionalProperties: { type: 'object', - required: ['prefix', 'body'], + required: ['body'], properties: { ...snippetSchemaProperties, scope: { diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts index a9e0f87a0..98d24e31a 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts @@ -15,6 +15,9 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IdleValue } from 'vs/base/common/async'; import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; +import { relativePath } from 'vs/base/common/resources'; +import { isObject } from 'vs/base/common/types'; +import { Iterable } from 'vs/base/common/iterator'; class SnippetBodyInsights { @@ -86,9 +89,9 @@ export class Snippet { readonly body: string, readonly source: string, readonly snippetSource: SnippetSource, + readonly snippetIdentifier?: string ) { - // - this.prefixLow = prefix ? prefix.toLowerCase() : prefix; + this.prefixLow = prefix.toLowerCase(); this._bodyInsights = new IdleValue(() => new SnippetBodyInsights(this.body)); } @@ -121,14 +124,14 @@ export class Snippet { interface JsonSerializedSnippet { - body: string; + body: string | string[]; scope: string; - prefix: string | string[]; + prefix: string | string[] | undefined; description: string; } function isJsonSerializedSnippet(thing: any): thing is JsonSerializedSnippet { - return Boolean((thing).body) && Boolean((thing).prefix); + return isObject(thing) && Boolean((thing).body); } interface JsonSerializedSnippets { @@ -242,18 +245,21 @@ export class SnippetFile { let { prefix, body, description } = snippet; + if (!prefix) { + prefix = ''; + } + if (Array.isArray(body)) { body = body.join('\n'); } + if (typeof body !== 'string') { + return; + } if (Array.isArray(description)) { description = description.join('\n'); } - if ((typeof prefix !== 'string' && !Array.isArray(prefix)) || typeof body !== 'string') { - return; - } - let scopes: string[]; if (this.defaultScopes) { scopes = this.defaultScopes; @@ -280,17 +286,17 @@ export class SnippetFile { } } - let prefixes = Array.isArray(prefix) ? prefix : [prefix]; - prefixes.forEach(p => { + for (const _prefix of Array.isArray(prefix) ? prefix : Iterable.single(prefix)) { bucket.push(new Snippet( scopes, name, - p, + _prefix, description, body, source, - this.source + this.source, + this._extension && `${relativePath(this._extension.extensionLocation, this.location)}/${name}` )); - }); + } } } diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index 3976dd126..a1bace7ed 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -19,12 +19,16 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkspace, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; +import { ISnippetGetOptions, ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; import { Snippet, SnippetFile, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { languagesExtPoint } from 'vs/workbench/services/mode/common/workbenchModeService'; import { SnippetCompletionProvider } from './snippetCompletionProvider'; import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; +import { ResourceMap } from 'vs/base/common/map'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { isStringArray } from 'vs/base/common/types'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; namespace snippetExt { @@ -123,13 +127,52 @@ function watch(service: IFileService, resource: URI, callback: () => any): IDisp ); } +class SnippetEnablement { + + private static _key = 'snippets.ignoredSnippets'; + + private readonly _ignored: Set; + + constructor( + @IStorageService private readonly _storageService: IStorageService, + ) { + + const raw = _storageService.get(SnippetEnablement._key, StorageScope.GLOBAL, ''); + let data: string[] | undefined; + try { + data = JSON.parse(raw); + } catch { } + + this._ignored = isStringArray(data) ? new Set(data) : new Set(); + } + + isIgnored(id: string): boolean { + return this._ignored.has(id); + } + + updateIgnored(id: string, value: boolean): void { + let changed = false; + if (this._ignored.has(id) && !value) { + this._ignored.delete(id); + changed = true; + } else if (!this._ignored.has(id) && value) { + this._ignored.add(id); + changed = true; + } + if (changed) { + this._storageService.store(SnippetEnablement._key, JSON.stringify(Array.from(this._ignored)), StorageScope.GLOBAL, StorageTarget.USER); + } + } +} + class SnippetsService implements ISnippetsService { - readonly _serviceBrand: undefined; + declare readonly _serviceBrand: undefined; private readonly _disposables = new DisposableStore(); private readonly _pendingWork: Promise[] = []; - private readonly _files = new Map(); + private readonly _files = new ResourceMap(); + private readonly _enablement: SnippetEnablement; constructor( @IEnvironmentService private readonly _environmentService: IEnvironmentService, @@ -139,6 +182,7 @@ class SnippetsService implements ISnippetsService { @IFileService private readonly _fileService: IFileService, @IExtensionResourceLoaderService private readonly _extensionResourceLoaderService: IExtensionResourceLoaderService, @ILifecycleService lifecycleService: ILifecycleService, + @IInstantiationService instantiationService: IInstantiationService, ) { this._pendingWork.push(Promise.resolve(lifecycleService.when(LifecyclePhase.Restored).then(() => { this._initExtensionSnippets(); @@ -147,12 +191,24 @@ class SnippetsService implements ISnippetsService { }))); setSnippetSuggestSupport(new SnippetCompletionProvider(this._modeService, this)); + + this._enablement = instantiationService.createInstance(SnippetEnablement); } dispose(): void { this._disposables.dispose(); } + isEnabled(snippet: Snippet): boolean { + return !snippet.snippetIdentifier || !this._enablement.isIgnored(snippet.snippetIdentifier); + } + + updateEnablement(snippet: Snippet, enabled: boolean): void { + if (snippet.snippetIdentifier) { + this._enablement.updateIgnored(snippet.snippetIdentifier, !enabled); + } + } + private _joinSnippets(): Promise { const promises = this._pendingWork.slice(0); this._pendingWork.length = 0; @@ -164,26 +220,27 @@ class SnippetsService implements ISnippetsService { return this._files.values(); } - getSnippets(languageId: LanguageId): Promise { - return this._joinSnippets().then(() => { - const result: Snippet[] = []; - const promises: Promise[] = []; + async getSnippets(languageId: LanguageId, opts?: ISnippetGetOptions): Promise { + await this._joinSnippets(); - const languageIdentifier = this._modeService.getLanguageIdentifier(languageId); - if (languageIdentifier) { - const langName = languageIdentifier.language; - for (const file of this._files.values()) { - promises.push(file.load() - .then(file => file.select(langName, result)) - .catch(err => this._logService.error(err, file.location.toString())) - ); - } + const result: Snippet[] = []; + const promises: Promise[] = []; + + const languageIdentifier = this._modeService.getLanguageIdentifier(languageId); + if (languageIdentifier) { + const langName = languageIdentifier.language; + for (const file of this._files.values()) { + promises.push(file.load() + .then(file => file.select(langName, result)) + .catch(err => this._logService.error(err, file.location.toString())) + ); } - return Promise.all(promises).then(() => result); - }); + } + await Promise.all(promises); + return this._filterSnippets(result, opts); } - getSnippetsSync(languageId: LanguageId): Snippet[] { + getSnippetsSync(languageId: LanguageId, opts?: ISnippetGetOptions): Snippet[] { const result: Snippet[] = []; const languageIdentifier = this._modeService.getLanguageIdentifier(languageId); if (languageIdentifier) { @@ -191,11 +248,18 @@ class SnippetsService implements ISnippetsService { for (const file of this._files.values()) { // kick off loading (which is a noop in case it's already loaded) // and optimistically collect snippets - file.load().catch(err => { /*ignore*/ }); + file.load().catch(_err => { /*ignore*/ }); file.select(langName, result); } } - return result; + return this._filterSnippets(result, opts); + } + + private _filterSnippets(snippets: Snippet[], opts?: ISnippetGetOptions): Snippet[] { + return snippets.filter(snippet => { + return (snippet.prefix || opts?.includeNoPrefixSnippets) // prefix or no-prefix wanted + && (this.isEnabled(snippet) || opts?.includeDisabledSnippets); // enabled or disabled wanted + }); } // --- loading, watching @@ -203,7 +267,7 @@ class SnippetsService implements ISnippetsService { private _initExtensionSnippets(): void { snippetExt.point.setHandler(extensions => { - for (let [key, value] of this._files) { + for (const [key, value] of this._files) { if (value.source === SnippetSource.Extension) { this._files.delete(key); } @@ -216,8 +280,7 @@ class SnippetsService implements ISnippetsService { continue; } - const resource = validContribution.location.toString(); - const file = this._files.get(resource); + const file = this._files.get(validContribution.location); if (file) { if (file.defaultScopes) { file.defaultScopes.push(validContribution.language); @@ -226,7 +289,7 @@ class SnippetsService implements ISnippetsService { } } else { const file = new SnippetFile(SnippetSource.Extension, validContribution.location, validContribution.language ? [validContribution.language] : undefined, extension.description, this._fileService, this._extensionResourceLoaderService); - this._files.set(file.location.toString(), file); + this._files.set(file.location, file); if (this._environmentService.isExtensionDevelopment) { file.load().then(file => { @@ -267,28 +330,28 @@ class SnippetsService implements ISnippetsService { updateWorkspaceSnippets(); } - private _initWorkspaceFolderSnippets(workspace: IWorkspace, bucket: DisposableStore): Promise { - let promises = workspace.folders.map(folder => { + private async _initWorkspaceFolderSnippets(workspace: IWorkspace, bucket: DisposableStore): Promise { + const promises = workspace.folders.map(async folder => { const snippetFolder = folder.toResource('.vscode'); - return this._fileService.exists(snippetFolder).then(value => { - if (value) { - this._initFolderSnippets(SnippetSource.Workspace, snippetFolder, bucket); - } else { - // watch - bucket.add(this._fileService.onDidFilesChange(e => { - if (e.contains(snippetFolder, FileChangeType.ADDED)) { - this._initFolderSnippets(SnippetSource.Workspace, snippetFolder, bucket); - } - })); - } - }); + const value = await this._fileService.exists(snippetFolder); + if (value) { + this._initFolderSnippets(SnippetSource.Workspace, snippetFolder, bucket); + } else { + // watch + bucket.add(this._fileService.onDidFilesChange(e => { + if (e.contains(snippetFolder, FileChangeType.ADDED)) { + this._initFolderSnippets(SnippetSource.Workspace, snippetFolder, bucket); + } + })); + } }); - return Promise.all(promises); + await Promise.all(promises); } - private _initUserSnippets(): Promise { + private async _initUserSnippets(): Promise { const userSnippetsFolder = this._environmentService.snippetsHome; - return this._fileService.createFolder(userSnippetsFolder).then(() => this._initFolderSnippets(SnippetSource.User, userSnippetsFolder, this._disposables)); + await this._fileService.createFolder(userSnippetsFolder); + return await this._initFolderSnippets(SnippetSource.User, userSnippetsFolder, this._disposables); } private _initFolderSnippets(source: SnippetSource, folder: URI, bucket: DisposableStore): Promise { @@ -315,15 +378,14 @@ class SnippetsService implements ISnippetsService { private _addSnippetFile(uri: URI, source: SnippetSource): IDisposable { const ext = resources.extname(uri); - const key = uri.toString(); if (source === SnippetSource.User && ext === '.json') { const langName = resources.basename(uri).replace(/\.json/, ''); - this._files.set(key, new SnippetFile(source, uri, [langName], undefined, this._fileService, this._extensionResourceLoaderService)); + this._files.set(uri, new SnippetFile(source, uri, [langName], undefined, this._fileService, this._extensionResourceLoaderService)); } else if (ext === '.code-snippets') { - this._files.set(key, new SnippetFile(source, uri, undefined, undefined, this._fileService, this._extensionResourceLoaderService)); + this._files.set(uri, new SnippetFile(source, uri, undefined, undefined, this._fileService, this._extensionResourceLoaderService)); } return { - dispose: () => this._files.delete(key) + dispose: () => this._files.delete(uri) }; } } diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts index cb3b8cbc6..10419ba6c 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts @@ -16,8 +16,7 @@ import { CompletionContext, CompletionTriggerKind } from 'vs/editor/common/modes class SimpleSnippetService implements ISnippetsService { declare readonly _serviceBrand: undefined; - constructor(readonly snippets: Snippet[]) { - } + constructor(readonly snippets: Snippet[]) { } getSnippets() { return Promise.resolve(this.getSnippetsSync()); } @@ -27,6 +26,12 @@ class SimpleSnippetService implements ISnippetsService { getSnippetFiles(): any { throw new Error(); } + isEnabled(): boolean { + throw new Error(); + } + updateEnablement(): void { + throw new Error(); + } } suite('SnippetsService', function () { diff --git a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts index dc2cdaeaf..76c12fa6a 100644 --- a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts +++ b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts @@ -102,7 +102,7 @@ class PartsSplash { layoutInfo, baseTheme }), - { encoding: 'utf8', overwriteEncoding: true } + { encoding: 'utf8' } ); if (baseTheme !== this._lastBaseTheme || colorInfo.editorBackground !== this._lastBackground) { diff --git a/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts b/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts index 273f4cc82..d7452466e 100644 --- a/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts +++ b/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts @@ -9,8 +9,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; 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 } from 'vs/platform/storage/common/storage'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ISurveyData, 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'; @@ -26,7 +25,6 @@ class LanguageSurvey extends Disposable { constructor( data: ISurveyData, storageService: IStorageService, - storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, notificationService: INotificationService, telemetryService: ITelemetryService, modelService: IModelService, @@ -43,14 +41,6 @@ class LanguageSurvey extends Disposable { const EDITED_LANGUAGE_COUNT_KEY = `${data.surveyId}.editedCount`; const EDITED_LANGUAGE_DATE_KEY = `${data.surveyId}.editedDate`; - // opt-in to syncing - storageKeysSyncRegistryService.registerStorageKey({ key: SESSION_COUNT_KEY, version: 1 }); - storageKeysSyncRegistryService.registerStorageKey({ key: LAST_SESSION_DATE_KEY, version: 1 }); - storageKeysSyncRegistryService.registerStorageKey({ key: SKIP_VERSION_KEY, version: 1 }); - storageKeysSyncRegistryService.registerStorageKey({ key: IS_CANDIDATE_KEY, version: 1 }); - storageKeysSyncRegistryService.registerStorageKey({ key: EDITED_LANGUAGE_COUNT_KEY, version: 1 }); - storageKeysSyncRegistryService.registerStorageKey({ key: EDITED_LANGUAGE_DATE_KEY, version: 1 }); - const skipVersion = storageService.get(SKIP_VERSION_KEY, StorageScope.GLOBAL, ''); if (skipVersion) { return; @@ -65,8 +55,8 @@ class LanguageSurvey extends Disposable { models.forEach(m => { if (m.getMode() === data.languageId && date !== storageService.get(EDITED_LANGUAGE_DATE_KEY, StorageScope.GLOBAL)) { const editedCount = storageService.getNumber(EDITED_LANGUAGE_COUNT_KEY, StorageScope.GLOBAL, 0) + 1; - storageService.store(EDITED_LANGUAGE_COUNT_KEY, editedCount, StorageScope.GLOBAL); - storageService.store(EDITED_LANGUAGE_DATE_KEY, date, StorageScope.GLOBAL); + storageService.store(EDITED_LANGUAGE_COUNT_KEY, editedCount, StorageScope.GLOBAL, StorageTarget.USER); + storageService.store(EDITED_LANGUAGE_DATE_KEY, date, StorageScope.GLOBAL, StorageTarget.USER); } }); }, 250)); @@ -80,8 +70,8 @@ class LanguageSurvey extends Disposable { } const sessionCount = storageService.getNumber(SESSION_COUNT_KEY, StorageScope.GLOBAL, 0) + 1; - storageService.store(LAST_SESSION_DATE_KEY, date, StorageScope.GLOBAL); - storageService.store(SESSION_COUNT_KEY, sessionCount, StorageScope.GLOBAL); + storageService.store(LAST_SESSION_DATE_KEY, date, StorageScope.GLOBAL, StorageTarget.USER); + storageService.store(SESSION_COUNT_KEY, sessionCount, StorageScope.GLOBAL, StorageTarget.USER); if (sessionCount < 9) { return; @@ -94,10 +84,10 @@ class LanguageSurvey extends Disposable { const isCandidate = storageService.getBoolean(IS_CANDIDATE_KEY, StorageScope.GLOBAL, false) || Math.random() < data.userProbability; - storageService.store(IS_CANDIDATE_KEY, isCandidate, StorageScope.GLOBAL); + storageService.store(IS_CANDIDATE_KEY, isCandidate, StorageScope.GLOBAL, StorageTarget.USER); if (!isCandidate) { - storageService.store(SKIP_VERSION_KEY, productService.version, StorageScope.GLOBAL); + storageService.store(SKIP_VERSION_KEY, productService.version, StorageScope.GLOBAL, StorageTarget.USER); return; } @@ -113,23 +103,23 @@ class LanguageSurvey extends Disposable { telemetryService.publicLog(`${data.surveyId}.survey/takeShortSurvey`); telemetryService.getTelemetryInfo().then(info => { openerService.open(URI.parse(`${data.surveyUrl}?o=${encodeURIComponent(platform)}&v=${encodeURIComponent(productService.version)}&m=${encodeURIComponent(info.machineId)}`)); - storageService.store(IS_CANDIDATE_KEY, false, StorageScope.GLOBAL); - storageService.store(SKIP_VERSION_KEY, productService.version, StorageScope.GLOBAL); + storageService.store(IS_CANDIDATE_KEY, false, StorageScope.GLOBAL, StorageTarget.USER); + storageService.store(SKIP_VERSION_KEY, productService.version, StorageScope.GLOBAL, StorageTarget.USER); }); } }, { label: nls.localize('remindLater', "Remind Me later"), run: () => { telemetryService.publicLog(`${data.surveyId}.survey/remindMeLater`); - storageService.store(SESSION_COUNT_KEY, sessionCount - 3, StorageScope.GLOBAL); + storageService.store(SESSION_COUNT_KEY, sessionCount - 3, StorageScope.GLOBAL, StorageTarget.USER); } }, { label: nls.localize('neverAgain', "Don't Show Again"), isSecondary: true, run: () => { telemetryService.publicLog(`${data.surveyId}.survey/dontShowAgain`); - storageService.store(IS_CANDIDATE_KEY, false, StorageScope.GLOBAL); - storageService.store(SKIP_VERSION_KEY, productService.version, StorageScope.GLOBAL); + storageService.store(IS_CANDIDATE_KEY, false, StorageScope.GLOBAL, StorageTarget.USER); + storageService.store(SKIP_VERSION_KEY, productService.version, StorageScope.GLOBAL, StorageTarget.USER); } }], { sticky: true } @@ -141,7 +131,6 @@ class LanguageSurveysContribution implements IWorkbenchContribution { constructor( @IStorageService storageService: IStorageService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, @INotificationService notificationService: INotificationService, @ITelemetryService telemetryService: ITelemetryService, @IModelService modelService: IModelService, @@ -155,7 +144,7 @@ class LanguageSurveysContribution implements IWorkbenchContribution { productService.surveys .filter(surveyData => surveyData.surveyId && surveyData.editCount && surveyData.languageId && surveyData.surveyUrl && surveyData.userProbability) - .map(surveyData => new LanguageSurvey(surveyData, storageService, storageKeysSyncRegistryService, notificationService, telemetryService, modelService, textFileService, openerService, productService)); + .map(surveyData => new LanguageSurvey(surveyData, storageService, notificationService, telemetryService, modelService, textFileService, openerService, productService)); } } diff --git a/src/vs/workbench/contrib/surveys/browser/nps.contribution.ts b/src/vs/workbench/contrib/surveys/browser/nps.contribution.ts index 7f8e32d28..243eb7500 100644 --- a/src/vs/workbench/contrib/surveys/browser/nps.contribution.ts +++ b/src/vs/workbench/contrib/surveys/browser/nps.contribution.ts @@ -8,8 +8,7 @@ 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 } from 'vs/platform/storage/common/storage'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; +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'; @@ -27,7 +26,6 @@ class NPSContribution implements IWorkbenchContribution { constructor( @IStorageService storageService: IStorageService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, @INotificationService notificationService: INotificationService, @ITelemetryService telemetryService: ITelemetryService, @IOpenerService openerService: IOpenerService, @@ -37,12 +35,6 @@ class NPSContribution implements IWorkbenchContribution { return; } - // opt-in to syncing - storageKeysSyncRegistryService.registerStorageKey({ key: SESSION_COUNT_KEY, version: 1 }); - storageKeysSyncRegistryService.registerStorageKey({ key: LAST_SESSION_DATE_KEY, version: 1 }); - storageKeysSyncRegistryService.registerStorageKey({ key: SKIP_VERSION_KEY, version: 1 }); - storageKeysSyncRegistryService.registerStorageKey({ key: IS_CANDIDATE_KEY, version: 1 }); - const skipVersion = storageService.get(SKIP_VERSION_KEY, StorageScope.GLOBAL, ''); if (skipVersion) { return; @@ -56,8 +48,8 @@ class NPSContribution implements IWorkbenchContribution { } const sessionCount = (storageService.getNumber(SESSION_COUNT_KEY, StorageScope.GLOBAL, 0) || 0) + 1; - storageService.store(LAST_SESSION_DATE_KEY, date, StorageScope.GLOBAL); - storageService.store(SESSION_COUNT_KEY, sessionCount, StorageScope.GLOBAL); + storageService.store(LAST_SESSION_DATE_KEY, date, StorageScope.GLOBAL, StorageTarget.USER); + storageService.store(SESSION_COUNT_KEY, sessionCount, StorageScope.GLOBAL, StorageTarget.USER); if (sessionCount < 9) { return; @@ -66,10 +58,10 @@ class NPSContribution implements IWorkbenchContribution { const isCandidate = storageService.getBoolean(IS_CANDIDATE_KEY, StorageScope.GLOBAL, false) || Math.random() < PROBABILITY; - storageService.store(IS_CANDIDATE_KEY, isCandidate, StorageScope.GLOBAL); + storageService.store(IS_CANDIDATE_KEY, isCandidate, StorageScope.GLOBAL, StorageTarget.USER); if (!isCandidate) { - storageService.store(SKIP_VERSION_KEY, productService.version, StorageScope.GLOBAL); + storageService.store(SKIP_VERSION_KEY, productService.version, StorageScope.GLOBAL, StorageTarget.USER); return; } @@ -81,18 +73,18 @@ class NPSContribution implements IWorkbenchContribution { run: () => { telemetryService.getTelemetryInfo().then(info => { openerService.open(URI.parse(`${productService.npsSurveyUrl}?o=${encodeURIComponent(platform)}&v=${encodeURIComponent(productService.version)}&m=${encodeURIComponent(info.machineId)}`)); - storageService.store(IS_CANDIDATE_KEY, false, StorageScope.GLOBAL); - storageService.store(SKIP_VERSION_KEY, productService.version, StorageScope.GLOBAL); + storageService.store(IS_CANDIDATE_KEY, false, StorageScope.GLOBAL, StorageTarget.USER); + storageService.store(SKIP_VERSION_KEY, productService.version, StorageScope.GLOBAL, StorageTarget.USER); }); } }, { label: nls.localize('remindLater', "Remind Me later"), - run: () => storageService.store(SESSION_COUNT_KEY, sessionCount - 3, StorageScope.GLOBAL) + run: () => storageService.store(SESSION_COUNT_KEY, sessionCount - 3, StorageScope.GLOBAL, StorageTarget.USER) }, { label: nls.localize('neverAgain', "Don't Show Again"), run: () => { - storageService.store(IS_CANDIDATE_KEY, false, StorageScope.GLOBAL); - storageService.store(SKIP_VERSION_KEY, productService.version, StorageScope.GLOBAL); + storageService.store(IS_CANDIDATE_KEY, false, StorageScope.GLOBAL, StorageTarget.USER); + storageService.store(SKIP_VERSION_KEY, productService.version, StorageScope.GLOBAL, StorageTarget.USER); } }], { sticky: true } diff --git a/src/vs/workbench/contrib/tags/browser/workspaceTagsService.ts b/src/vs/workbench/contrib/tags/browser/workspaceTagsService.ts index 2aecc6350..0fbc6faa5 100644 --- a/src/vs/workbench/contrib/tags/browser/workspaceTagsService.ts +++ b/src/vs/workbench/contrib/tags/browser/workspaceTagsService.ts @@ -16,7 +16,7 @@ export class NoOpWorkspaceTagsService implements IWorkspaceTagsService { return Promise.resolve({}); } - getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): string | undefined { + async getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): Promise { return undefined; } diff --git a/src/vs/workbench/contrib/tags/common/workspaceTags.ts b/src/vs/workbench/contrib/tags/common/workspaceTags.ts index d1683b977..487eb6caf 100644 --- a/src/vs/workbench/contrib/tags/common/workspaceTags.ts +++ b/src/vs/workbench/contrib/tags/common/workspaceTags.ts @@ -20,7 +20,7 @@ export interface IWorkspaceTagsService { * Returns an id for the workspace, different from the id returned by the context service. A hash based * on the folder uri or workspace configuration, not time-based, and undefined for empty workspaces. */ - getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): string | undefined; + getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): Promise; getHashedRemotesFromUri(workspaceUri: URI, stripEndingDotGit?: boolean): Promise; } diff --git a/src/vs/workbench/contrib/tags/electron-browser/workspaceTags.ts b/src/vs/workbench/contrib/tags/electron-browser/workspaceTags.ts index df781b92f..726575e82 100644 --- a/src/vs/workbench/contrib/tags/electron-browser/workspaceTags.ts +++ b/src/vs/workbench/contrib/tags/electron-browser/workspaceTags.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as crypto from 'crypto'; +import { sha1Hex } from 'vs/base/browser/hash'; import { onUnexpectedError } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; import { IFileService, IFileStat } from 'vs/platform/files/common/files'; @@ -20,10 +20,8 @@ import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsSer import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { IProductService } from 'vs/platform/product/common/productService'; -export function getHashedRemotesFromConfig(text: string, stripEndingDotGit: boolean = false): string[] { - return getRemotes(text, stripEndingDotGit).map(r => { - return crypto.createHash('sha1').update(r).digest('hex'); - }); +export async function getHashedRemotesFromConfig(text: string, stripEndingDotGit: boolean = false): Promise { + return Promise.all(getRemotes(text, stripEndingDotGit).map(remote => sha1Hex(remote))); } export class WorkspaceTags implements IWorkbenchContribution { @@ -76,7 +74,7 @@ export class WorkspaceTags implements IWorkbenchContribution { private async getWorkspaceInformation(): Promise { const workspace = this.contextService.getWorkspace(); const state = this.contextService.getWorkbenchState(); - const telemetryId = this.workspaceTagsService.getTelemetryWorkspaceId(workspace, state); + const telemetryId = await this.workspaceTagsService.getTelemetryWorkspaceId(workspace, state); return this.telemetryService.getTelemetryInfo().then(info => { return { id: workspace.id, diff --git a/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts b/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts index 35b943f87..bf203f81c 100644 --- a/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts +++ b/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as crypto from 'crypto'; +import { sha1Hex } from 'vs/base/browser/hash'; import { IFileService, IResolveFileResult, IFileStat } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -14,6 +14,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkspaceTagsService, Tags } from 'vs/workbench/contrib/tags/common/workspaceTags'; import { getHashedRemotesFromConfig } from 'vs/workbench/contrib/tags/electron-browser/workspaceTags'; import { IProductService } from 'vs/platform/product/common/productService'; +import { splitLines } from 'vs/base/common/strings'; const MetaModulesToLookFor = [ // Azure packages @@ -137,9 +138,9 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { return this._tags; } - getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): string | undefined { - function createHash(uri: URI): string { - return crypto.createHash('sha1').update(uri.scheme === Schemas.file ? uri.fsPath : uri.toString()).digest('hex'); + async getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): Promise { + function createHash(uri: URI): Promise { + return sha1Hex(uri.scheme === Schemas.file ? uri.fsPath : uri.toString()); } let workspaceId: string | undefined; @@ -148,11 +149,11 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { workspaceId = undefined; break; case WorkbenchState.FOLDER: - workspaceId = createHash(workspace.folders[0].uri); + workspaceId = await createHash(workspace.folders[0].uri); break; case WorkbenchState.WORKSPACE: if (workspace.configuration) { - workspaceId = createHash(workspace.configuration); + workspaceId = await createHash(workspace.configuration); } } @@ -291,13 +292,13 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "workspace.py.playwright" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } } */ - private resolveWorkspaceTags(): Promise { + private async resolveWorkspaceTags(): Promise { const tags: Tags = Object.create(null); const state = this.contextService.getWorkbenchState(); const workspace = this.contextService.getWorkspace(); - tags['workspace.id'] = this.getTelemetryWorkspaceId(workspace, state); + tags['workspace.id'] = await this.getTelemetryWorkspaceId(workspace, state); const { filesToOpenOrCreate, filesToDiff } = this.environmentService.configuration; tags['workbench.filesToOpenOrCreate'] = filesToOpenOrCreate && filesToOpenOrCreate.length || 0; @@ -412,7 +413,7 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { } const requirementsTxtPromises = getFilePromises('requirements.txt', this.fileService, this.textFileService, content => { - const dependencies: string[] = content.value.split(/\r\n|\r|\n/); + const dependencies: string[] = splitLines(content.value); for (let dependency of dependencies) { // Dependencies in requirements.txt can have 3 formats: `foo==3.1, foo>=3.1, foo` const format1 = dependency.split('=='); @@ -423,7 +424,7 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { }); const pipfilePromises = getFilePromises('pipfile', this.fileService, this.textFileService, content => { - let dependencies: string[] = content.value.split(/\r\n|\r|\n/); + let dependencies: string[] = splitLines(content.value); // We're only interested in the '[packages]' section of the Pipfile dependencies = dependencies.slice(dependencies.indexOf('[packages]') + 1); diff --git a/src/vs/workbench/contrib/tags/test/electron-browser/workspaceTags.test.ts b/src/vs/workbench/contrib/tags/test/electron-browser/workspaceTags.test.ts index ed3c428da..faad40a9e 100644 --- a/src/vs/workbench/contrib/tags/test/electron-browser/workspaceTags.test.ts +++ b/src/vs/workbench/contrib/tags/test/electron-browser/workspaceTags.test.ts @@ -13,35 +13,35 @@ function hash(value: string): string { suite('Telemetry - WorkspaceTags', () => { - test('Single remote hashed', function () { - assert.deepStrictEqual(getHashedRemotesFromConfig(remote('https://username:password@github3.com/username/repository.git')), [hash('github3.com/username/repository.git')]); - assert.deepStrictEqual(getHashedRemotesFromConfig(remote('ssh://user@git.server.org/project.git')), [hash('git.server.org/project.git')]); - assert.deepStrictEqual(getHashedRemotesFromConfig(remote('user@git.server.org:project.git')), [hash('git.server.org/project.git')]); - assert.deepStrictEqual(getHashedRemotesFromConfig(remote('/opt/git/project.git')), []); + test('Single remote hashed', async function () { + assert.deepStrictEqual(await getHashedRemotesFromConfig(remote('https://username:password@github3.com/username/repository.git')), [hash('github3.com/username/repository.git')]); + assert.deepStrictEqual(await getHashedRemotesFromConfig(remote('ssh://user@git.server.org/project.git')), [hash('git.server.org/project.git')]); + assert.deepStrictEqual(await getHashedRemotesFromConfig(remote('user@git.server.org:project.git')), [hash('git.server.org/project.git')]); + assert.deepStrictEqual(await getHashedRemotesFromConfig(remote('/opt/git/project.git')), []); // Strip .git - assert.deepStrictEqual(getHashedRemotesFromConfig(remote('https://username:password@github3.com/username/repository.git'), true), [hash('github3.com/username/repository')]); - assert.deepStrictEqual(getHashedRemotesFromConfig(remote('ssh://user@git.server.org/project.git'), true), [hash('git.server.org/project')]); - assert.deepStrictEqual(getHashedRemotesFromConfig(remote('user@git.server.org:project.git'), true), [hash('git.server.org/project')]); - assert.deepStrictEqual(getHashedRemotesFromConfig(remote('/opt/git/project.git'), true), []); + assert.deepStrictEqual(await getHashedRemotesFromConfig(remote('https://username:password@github3.com/username/repository.git'), true), [hash('github3.com/username/repository')]); + assert.deepStrictEqual(await getHashedRemotesFromConfig(remote('ssh://user@git.server.org/project.git'), true), [hash('git.server.org/project')]); + assert.deepStrictEqual(await getHashedRemotesFromConfig(remote('user@git.server.org:project.git'), true), [hash('git.server.org/project')]); + assert.deepStrictEqual(await getHashedRemotesFromConfig(remote('/opt/git/project.git'), true), []); // Compare Striped .git with no .git - assert.deepStrictEqual(getHashedRemotesFromConfig(remote('https://username:password@github3.com/username/repository.git'), true), getHashedRemotesFromConfig(remote('https://username:password@github3.com/username/repository'))); - assert.deepStrictEqual(getHashedRemotesFromConfig(remote('ssh://user@git.server.org/project.git'), true), getHashedRemotesFromConfig(remote('ssh://user@git.server.org/project'))); - assert.deepStrictEqual(getHashedRemotesFromConfig(remote('user@git.server.org:project.git'), true), [hash('git.server.org/project')]); - assert.deepStrictEqual(getHashedRemotesFromConfig(remote('/opt/git/project.git'), true), getHashedRemotesFromConfig(remote('/opt/git/project'))); + assert.deepStrictEqual(await getHashedRemotesFromConfig(remote('https://username:password@github3.com/username/repository.git'), true), await getHashedRemotesFromConfig(remote('https://username:password@github3.com/username/repository'))); + assert.deepStrictEqual(await getHashedRemotesFromConfig(remote('ssh://user@git.server.org/project.git'), true), await getHashedRemotesFromConfig(remote('ssh://user@git.server.org/project'))); + assert.deepStrictEqual(await getHashedRemotesFromConfig(remote('user@git.server.org:project.git'), true), [hash('git.server.org/project')]); + assert.deepStrictEqual(await getHashedRemotesFromConfig(remote('/opt/git/project.git'), true), await getHashedRemotesFromConfig(remote('/opt/git/project'))); }); - test('Multiple remotes hashed', function () { + test('Multiple remotes hashed', async function () { const config = ['https://github.com/microsoft/vscode.git', 'https://git.example.com/gitproject.git'].map(remote).join(' '); - assert.deepStrictEqual(getHashedRemotesFromConfig(config), [hash('github.com/microsoft/vscode.git'), hash('git.example.com/gitproject.git')]); + assert.deepStrictEqual(await getHashedRemotesFromConfig(config), [hash('github.com/microsoft/vscode.git'), hash('git.example.com/gitproject.git')]); // Strip .git - assert.deepStrictEqual(getHashedRemotesFromConfig(config, true), [hash('github.com/microsoft/vscode'), hash('git.example.com/gitproject')]); + assert.deepStrictEqual(await getHashedRemotesFromConfig(config, true), [hash('github.com/microsoft/vscode'), hash('git.example.com/gitproject')]); // Compare Striped .git with no .git const noDotGitConfig = ['https://github.com/microsoft/vscode', 'https://git.example.com/gitproject'].map(remote).join(' '); - assert.deepStrictEqual(getHashedRemotesFromConfig(config, true), getHashedRemotesFromConfig(noDotGitConfig)); + assert.deepStrictEqual(await getHashedRemotesFromConfig(config, true), await getHashedRemotesFromConfig(noDotGitConfig)); }); function remote(url: string): string { diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 54b59c57c..64f74982d 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -19,8 +19,6 @@ import { ValidationStatus, ValidationState } from 'vs/base/common/parsers'; import * as UUID from 'vs/base/common/uuid'; import * as Platform from 'vs/base/common/platform'; import { LRUCache, Touch } from 'vs/base/common/map'; - -import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IMarkerService } from 'vs/platform/markers/common/markers'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; @@ -28,13 +26,13 @@ import { IFileService, IFileStat } from 'vs/platform/files/common/files'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { ProblemMatcherRegistry, NamedProblemMatcher } from 'vs/workbench/contrib/tasks/common/problemMatcher'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IProgressService, IProgressOptions, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IDialogService, IConfirmationResult } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -42,7 +40,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; -import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder, IWorkspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IOutputService, IOutputChannel } from 'vs/workbench/contrib/output/common/output'; @@ -78,9 +76,10 @@ import { ITextEditorSelection, TextEditorSelectionRevealType } from 'vs/platform import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IViewsService, IViewDescriptorService } from 'vs/workbench/common/views'; -import { isWorkspaceFolder, TaskQuickPickEntry, QUICKOPEN_DETAIL_CONFIG, TaskQuickPick, QUICKOPEN_SKIP_CONFIG } from 'vs/workbench/contrib/tasks/browser/taskQuickPick'; +import { isWorkspaceFolder, TaskQuickPickEntry, QUICKOPEN_DETAIL_CONFIG, TaskQuickPick, QUICKOPEN_SKIP_CONFIG, configureTaskIcon } from 'vs/workbench/contrib/tasks/browser/taskQuickPick'; import { ILogService } from 'vs/platform/log/common/log'; import { once } from 'vs/base/common/functional'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; const QUICKOPEN_HISTORY_LIMIT_CONFIG = 'task.quickOpen.history'; const PROBLEM_MATCHER_NEVER_CONFIG = 'task.problemMatchers.neverPrompt'; @@ -238,7 +237,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer @IWorkspaceContextService protected readonly contextService: IWorkspaceContextService, @ITelemetryService protected readonly telemetryService: ITelemetryService, @ITextFileService private readonly textFileService: ITextFileService, - @ILifecycleService lifecycleService: ILifecycleService, @IModelService protected readonly modelService: IModelService, @IExtensionService private readonly extensionService: IExtensionService, @IQuickInputService private readonly quickInputService: IQuickInputService, @@ -248,7 +246,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer @IProgressService private readonly progressService: IProgressService, @IOpenerService private readonly openerService: IOpenerService, @IHostService private readonly _hostService: IHostService, - @IDialogService private readonly dialogService: IDialogService, + @IDialogService protected readonly dialogService: IDialogService, @INotificationService private readonly notificationService: INotificationService, @IContextKeyService protected readonly contextKeyService: IContextKeyService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @@ -308,7 +306,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this.updateWorkspaceTasks(TaskRunSource.ConfigurationChange); })); this._taskRunningState = TASK_RUNNING_STATE.bindTo(contextKeyService); - this._register(lifecycleService.onBeforeShutdown(event => event.veto(this.beforeShutdown()))); this._onDidStateChange = this._register(new Emitter()); this.registerCommands(); this.configurationResolverService.contributeVariable('defaultBuildTask', async (): Promise => { @@ -518,7 +515,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } - private disposeTaskSystemListeners(): void { + protected disposeTaskSystemListeners(): void { if (this._taskSystemListener) { this._taskSystemListener.dispose(); } @@ -580,7 +577,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (!values) { return undefined; } - return values.find(task => task.matches(key, compareId)); + values = values.filter(task => task.matches(key, compareId)).sort(task => task._source.kind === TaskSourceKind.Extension ? 1 : -1); + return values.length > 0 ? values[0] : undefined; }); } @@ -801,6 +799,13 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return tasks; } + public removeRecentlyUsedTask(taskRecentlyUsedKey: string) { + if (this.getRecentlyUsedTasks().has(taskRecentlyUsedKey)) { + this.getRecentlyUsedTasks().delete(taskRecentlyUsedKey); + this.saveRecentlyUsedTasks(); + } + } + private setTaskLRUCacheLimit() { const quickOpenHistoryLimit = this.configurationService.getValue(QUICKOPEN_HISTORY_LIMIT_CONFIG); if (this._recentlyUsedTasks) { @@ -845,7 +850,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer for (const key of keys) { keyValues.push([key, this._recentlyUsedTasks.get(key, Touch.None)!]); } - this.storageService.store(AbstractTaskService.RecentlyUsedTasks_KeyV2, JSON.stringify(keyValues), StorageScope.WORKSPACE); + this.storageService.store(AbstractTaskService.RecentlyUsedTasks_KeyV2, JSON.stringify(keyValues), StorageScope.WORKSPACE, StorageTarget.USER); } private openDocumentation(): void { @@ -981,7 +986,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer newValue = Object.create(null); } newValue[type] = true; - return this.configurationService.updateValue(PROBLEM_MATCHER_NEVER_CONFIG, newValue, ConfigurationTarget.USER); + return this.configurationService.updateValue(PROBLEM_MATCHER_NEVER_CONFIG, newValue); } private attachProblemMatcher(task: ContributedTask | CustomTask): Promise { @@ -1593,7 +1598,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this.modelService, this.configurationResolverService, this.telemetryService, this.contextService, this.environmentService, AbstractTaskService.OutputChannelId, this.fileService, this.terminalInstanceService, - this.pathService, this.viewDescriptorService, this.logService, + this.pathService, this.viewDescriptorService, this.logService, this.configurationService, (workspaceFolder: IWorkspaceFolder | undefined) => { if (workspaceFolder) { return this.getTaskSystemInfo(workspaceFolder.uri.scheme); @@ -1699,7 +1704,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return; } - if (!contributed) { + if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { result.add(key, ...folderTasks.set.tasks); } else { let configurations = folderTasks.configurations; @@ -1864,31 +1869,35 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer protected abstract updateWorkspaceTasks(runSource: TaskRunSource | void): void; protected computeWorkspaceTasks(runSource: TaskRunSource = TaskRunSource.User): Promise> { - if (this.workspaceFolders.length === 0) { - return Promise.resolve(new Map()); - } else { - let promises: Promise[] = []; - for (let folder of this.workspaceFolders) { - promises.push(this.computeWorkspaceFolderTasks(folder, runSource).then((value) => value, () => undefined)); + let promises: Promise[] = []; + for (let folder of this.workspaceFolders) { + promises.push(this.computeWorkspaceFolderTasks(folder, runSource).then((value) => value, () => undefined)); + } + return Promise.all(promises).then(async (values) => { + let result = new Map(); + for (let value of values) { + if (value) { + result.set(value.workspaceFolder.uri.toString(), value); + } } - return Promise.all(promises).then(async (values) => { - let result = new Map(); - for (let value of values) { - if (value) { - result.set(value.workspaceFolder.uri.toString(), value); - } - } - const userTasks = await this.computeUserTasks(this.workspaceFolders[0], runSource).then((value) => value, () => undefined); - if (userTasks) { - result.set(USER_TASKS_GROUP_KEY, userTasks); - } - const workspaceFileTasks = await this.computeWorkspaceFileTasks(this.workspaceFolders[0], runSource).then((value) => value, () => undefined); + let folder = this.workspaceFolders.length > 0 ? this.workspaceFolders[0] : undefined; + if (!folder) { + const userhome = await this.pathService.userHome(); + folder = new WorkspaceFolder({ uri: userhome, name: resources.basename(userhome), index: 0 }); + } + const userTasks = await this.computeUserTasks(folder, runSource).then((value) => value, () => undefined); + if (userTasks) { + result.set(USER_TASKS_GROUP_KEY, userTasks); + } + + if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY) { + const workspaceFileTasks = await this.computeWorkspaceFileTasks(folder, runSource).then((value) => value, () => undefined); if (workspaceFileTasks && this._workspace && this._workspace.configuration) { result.set(this._workspace.configuration.toString(), workspaceFileTasks); } - return result; - }); - } + } + return result; + }); } private get jsonTasksSupported(): boolean { @@ -2129,64 +2138,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }; } - public beforeShutdown(): boolean | Promise { - if (!this._taskSystem) { - return false; - } - if (!this._taskSystem.isActiveSync()) { - return false; - } - // The terminal service kills all terminal on shutdown. So there - // is nothing we can do to prevent this here. - if (this._taskSystem instanceof TerminalTaskSystem) { - return false; - } - - let terminatePromise: Promise; - if (this._taskSystem.canAutoTerminate()) { - terminatePromise = Promise.resolve({ confirmed: true }); - } else { - terminatePromise = this.dialogService.confirm({ - message: nls.localize('TaskSystem.runningTask', 'There is a task running. Do you want to terminate it?'), - primaryButton: nls.localize({ key: 'TaskSystem.terminateTask', comment: ['&& denotes a mnemonic'] }, "&&Terminate Task"), - type: 'question' - }); - } - - return terminatePromise.then(res => { - if (res.confirmed) { - return this._taskSystem!.terminateAll().then((responses) => { - let success = true; - let code: number | undefined = undefined; - for (let response of responses) { - success = success && response.success; - // We only have a code in the old output runner which only has one task - // So we can use the first code. - if (code === undefined && response.code !== undefined) { - code = response.code; - } - } - if (success) { - this._taskSystem = undefined; - this.disposeTaskSystemListeners(); - return false; // no veto - } else if (code && code === TerminateResponseCode.ProcessNotFound) { - return this.dialogService.confirm({ - message: nls.localize('TaskSystem.noProcess', 'The launched task doesn\'t exist anymore. If the task spawned background processes exiting VS Code might result in orphaned processes. To avoid this start the last background process with a wait flag.'), - primaryButton: nls.localize({ key: 'TaskSystem.exitAnyways', comment: ['&& denotes a mnemonic'] }, "&&Exit Anyways"), - type: 'info' - }).then(res => !res.confirmed); - } - return true; // veto - }, (err) => { - return true; // veto - }); - } - - return true; // veto - }); - } - private handleError(err: any): void { let showOutput = true; if (err instanceof TaskError) { @@ -2222,17 +2173,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } private canRunCommand(): boolean { - if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { - this.notificationService.prompt( - Severity.Info, - nls.localize('TaskService.noWorkspace', "Tasks are only available on a workspace folder."), - [{ - label: nls.localize('TaskService.learnMore', "Learn More"), - run: () => this.openerService.open(URI.parse('https://code.visualstudio.com/docs/editor/tasks')) - }] - ); - return false; - } return true; } @@ -2262,7 +2202,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } for (let task of tasks) { let entry: TaskQuickPickEntry = TaskQuickPickEntry(task); - entry.buttons = [{ iconClass: 'codicon-gear', tooltip: nls.localize('configureTask', "Configure Task") }]; + entry.buttons = [{ iconClass: ThemeIcon.asClassName(configureTaskIcon), tooltip: nls.localize('configureTask', "Configure Task") }]; if (selectedEntry && (task === selectedEntry.task)) { entries.unshift(selectedEntry); } else { @@ -2446,7 +2386,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer label: nls.localize('TaskService.notAgain', "Don't Show Again"), isSecondary: true, run: () => { - this.storageService.store(AbstractTaskService.IgnoreTask010DonotShowAgain_key, true, StorageScope.WORKSPACE); + this.storageService.store(AbstractTaskService.IgnoreTask010DonotShowAgain_key, true, StorageScope.WORKSPACE, StorageTarget.USER); this._showIgnoreMessage = false; } }] @@ -2541,7 +2481,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } this.showQuickPick(tasks ? tasks : taskResult!.tasks, placeholder, { - label: nls.localize('TaskService.noEntryToRunSlow', 'No task to run found. Configure Tasks...'), + label: nls.localize('TaskService.noEntryToRunSlow', '$(plus) Configure a Task'), task: null }, true). @@ -2551,7 +2491,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } else { this.showTwoLevelQuickPick(placeholder, { - label: nls.localize('TaskService.noEntryToRun', 'No configured tasks. Configure Tasks...'), + label: nls.localize('TaskService.noEntryToRun', '$(plus) Configure a Task'), task: null }). then(pickThen); @@ -2926,8 +2866,13 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } if (this.isTaskEntry(selection)) { this.configureTask(selection.task); - } else { + } else if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY) { this.openTaskFile(selection.folder.toResource('.vscode/tasks.json'), TaskSourceKind.Workspace); + } else { + const resource = this.getResourceForKind(TaskSourceKind.User); + if (resource) { + this.openTaskFile(resource, TaskSourceKind.User); + } } } @@ -2969,46 +2914,22 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return taskPromise.then((taskMap) => { let entries: QuickPickInput[] = []; let needsCreateOrOpen: boolean = true; - if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY) { - let tasks = taskMap.all(); - if (tasks.length > 0) { - tasks = tasks.sort((a, b) => a._label.localeCompare(b._label)); - for (let task of tasks) { - entries.push({ label: task._label, task, description: this.getTaskDescription(task), detail: this.showDetail() ? task.configurationProperties.detail : undefined }); - if (!ContributedTask.is(task)) { - needsCreateOrOpen = false; - } + let tasks = taskMap.all(); + if (tasks.length > 0) { + tasks = tasks.sort((a, b) => a._label.localeCompare(b._label)); + for (let task of tasks) { + entries.push({ label: task._label, task, description: this.getTaskDescription(task), detail: this.showDetail() ? task.configurationProperties.detail : undefined }); + if (!ContributedTask.is(task)) { + needsCreateOrOpen = false; } } - if (needsCreateOrOpen) { - let label = stats[0] !== undefined ? openLabel : createLabel; - if (entries.length) { - entries.push({ type: 'separator' }); - } - entries.push({ label, folder: this.contextService.getWorkspace().folders[0] }); - } - } else { - let folders = this.contextService.getWorkspace().folders; - let index = 0; - for (let folder of folders) { - let tasks = taskMap.get(folder); - if (tasks.length > 0) { - tasks = tasks.slice().sort((a, b) => a._label.localeCompare(b._label)); - for (let i = 0; i < tasks.length; i++) { - let entry: TaskQuickPickEntryType = { label: tasks[i]._label, task: tasks[i], description: this.getTaskDescription(tasks[i]) }; - if (i === 0) { - entries.push({ type: 'separator', label: folder.name }); - } - entries.push(entry); - } - } else { - let label = stats[index] !== undefined ? openLabel : createLabel; - let entry: TaskQuickPickEntryType = { label, folder: folder }; - entries.push({ type: 'separator', label: folder.name }); - entries.push(entry); - } - index++; + } + if (needsCreateOrOpen) { + let label = stats[0] !== undefined ? openLabel : createLabel; + if (entries.length) { + entries.push({ type: 'separator' }); } + entries.push({ label, folder: this.contextService.getWorkspace().folders[0] }); } if ((entries.length === 1) && !needsCreateOrOpen) { tokenSource.cancel(); diff --git a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts index 4b20d3ac1..40899ecaf 100644 --- a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts +++ b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts @@ -9,7 +9,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { ITaskService, WorkspaceFolderTaskResult } from 'vs/workbench/contrib/tasks/common/taskService'; import { forEach } from 'vs/base/common/collections'; import { RunOnOptions, Task, TaskRunSource, TASKS_CATEGORY } from 'vs/workbench/contrib/tasks/common/tasks'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { Action2 } from 'vs/platform/actions/common/actions'; @@ -110,14 +110,14 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut label: nls.localize('allow', "Allow and run"), run: () => { resolve(true); - storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, true, StorageScope.WORKSPACE); + storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, true, StorageScope.WORKSPACE, StorageTarget.MACHINE); } }, { label: nls.localize('disallow', "Disallow"), run: () => { resolve(false); - storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, false, StorageScope.WORKSPACE); + storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, false, StorageScope.WORKSPACE, StorageTarget.MACHINE); } }, { @@ -156,6 +156,6 @@ export class ManageAutomaticTaskRunning extends Action2 { return; } - storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, value === allowItem, StorageScope.WORKSPACE); + storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, value === allowItem, StorageScope.WORKSPACE, StorageTarget.MACHINE); } } diff --git a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts index cd19578ce..a423d7665 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts @@ -9,12 +9,15 @@ import { Task, ContributedTask, CustomTask, ConfiguringTask, TaskSorter, KeyedTa import { IWorkspace, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import * as Types from 'vs/base/common/types'; import { ITaskService, WorkspaceFolderTaskResult } from 'vs/workbench/contrib/tasks/common/taskService'; -import { IQuickPickItem, QuickPickInput, IQuickPick } from 'vs/base/parts/quickinput/common/quickInput'; +import { IQuickPickItem, QuickPickInput, IQuickPick, IQuickInputButton } from 'vs/base/parts/quickinput/common/quickInput'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { Disposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { Codicon } from 'vs/base/common/codicons'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; export const QUICKOPEN_DETAIL_CONFIG = 'task.quickOpen.detail'; export const QUICKOPEN_SKIP_CONFIG = 'task.quickOpen.skip'; @@ -32,6 +35,9 @@ export interface TaskTwoLevelQuickPickEntry extends IQuickPickItem { const SHOW_ALL: string = nls.localize('taskQuickPick.showAll', "Show All Tasks..."); +export const configureTaskIcon = registerIcon('tasks-list-configure', Codicon.gear, nls.localize('configureTaskIcon', 'Configration icon in the tasks selection list.')); +const removeTaskIcon = registerIcon('tasks-remove', Codicon.close, nls.localize('removeTaskIcon', 'Icon for remove in the tasks selection list.')); + export class TaskQuickPick extends Disposable { private sorter: TaskSorter; private topLevelEntries: QuickPickInput[] | undefined; @@ -63,16 +69,17 @@ export class TaskQuickPick extends Disposable { return ''; } - private createTaskEntry(task: Task | ConfiguringTask): TaskTwoLevelQuickPickEntry { + private createTaskEntry(task: Task | ConfiguringTask, extraButtons: IQuickInputButton[] = []): TaskTwoLevelQuickPickEntry { const entry: TaskTwoLevelQuickPickEntry = { label: this.guessTaskLabel(task), description: this.taskService.getTaskDescription(task), task, detail: this.showDetail() ? task.configurationProperties.detail : undefined }; - entry.buttons = [{ iconClass: 'codicon-gear', tooltip: nls.localize('configureTask', "Configure Task") }]; + entry.buttons = [{ iconClass: ThemeIcon.asClassName(configureTaskIcon), tooltip: nls.localize('configureTask', "Configure Task") }, ...extraButtons]; return entry; } - private createEntriesForGroup(entries: QuickPickInput[], tasks: (Task | ConfiguringTask)[], groupLabel: string) { + private createEntriesForGroup(entries: QuickPickInput[], tasks: (Task | ConfiguringTask)[], + groupLabel: string, extraButtons: IQuickInputButton[] = []) { entries.push({ type: 'separator', label: groupLabel }); tasks.forEach(task => { - entries.push(this.createTaskEntry(task)); + entries.push(this.createTaskEntry(task, extraButtons)); }); } @@ -143,7 +150,11 @@ export class TaskQuickPick extends Disposable { let dedupedConfiguredTasks: (Task | ConfiguringTask)[] = dedupeAndPrune.configuredTasks; recentTasks = dedupeAndPrune.recentTasks; if (recentTasks.length > 0) { - this.createEntriesForGroup(this.topLevelEntries, recentTasks, nls.localize('recentlyUsed', 'recently used')); + const removeRecentButton: IQuickInputButton = { + iconClass: ThemeIcon.asClassName(removeTaskIcon), + tooltip: nls.localize('removeRecent', 'Remove Recently Used Task') + }; + this.createEntriesForGroup(this.topLevelEntries, recentTasks, nls.localize('recentlyUsed', 'recently used'), [removeRecentButton]); } if (configuredTasks.length > 0) { if (dedupedConfiguredTasks.length > 0) { @@ -171,6 +182,18 @@ export class TaskQuickPick extends Disposable { picker.onDidTriggerItemButton(async (context) => { let task = context.item.task; + if (task && !Types.isString(task) && context.button.iconClass === ThemeIcon.asClassName(removeTaskIcon)) { + const key = task.getRecentlyUsedKey(); + if (key) { + this.taskService.removeRecentlyUsedTask(key); + const indexToRemove = picker.items.indexOf(context.item); + if (indexToRemove >= 0) { + picker.items = [...picker.items.slice(0, indexToRemove), ...picker.items.slice(indexToRemove + 1)]; + } + } + return; + } + this.quickInputService.cancel(); if (ContributedTask.is(task)) { this.taskService.customize(task, undefined, true); @@ -226,10 +249,10 @@ export class TaskQuickPick extends Disposable { private async doPickerSecondLevel(picker: IQuickPick, type: string) { picker.busy = true; - picker.value = ''; if (type === SHOW_ALL) { picker.items = (await this.taskService.tasks()).sort((a, b) => this.sorter.compare(a, b)).map(task => this.createTaskEntry(task)); } else { + picker.value = ''; picker.items = await this.getEntriesForProvider(type); } picker.busy = false; diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index f96226900..b011e99c7 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -47,6 +47,7 @@ import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { env as processEnv, cwd as processCwd } from 'vs/base/common/process'; import { IViewsService, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; import { ILogService } from 'vs/platform/log/common/log'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; interface TerminalData { terminal: ITerminalInstance; @@ -204,6 +205,7 @@ export class TerminalTaskSystem implements ITaskSystem { private pathService: IPathService, private viewDescriptorService: IViewDescriptorService, private logService: ILogService, + private configurationService: IConfigurationService, taskSystemInfoResolver: TaskSystemInfoResolver, ) { @@ -247,7 +249,7 @@ export class TerminalTaskSystem implements ITaskSystem { } try { - const executeResult = { kind: TaskExecuteKind.Started, task, started: {}, promise: this.executeTask(task, resolver, trigger) }; + const executeResult = { kind: TaskExecuteKind.Started, task, started: {}, promise: this.executeTask(task, resolver, trigger, new Set()) }; executeResult.promise.then(summary => { this.lastTask = this.currentTask; }); @@ -435,7 +437,21 @@ export class TerminalTaskSystem implements ITaskSystem { return Promise.all(promises); } - private async executeTask(task: Task, resolver: ITaskResolver, trigger: string, alreadyResolved?: Map): Promise { + + private showDependencyCycleMessage(task: Task) { + this.log(nls.localize('dependencyCycle', + 'There is a dependency cycle. See task "{0}".', + task._label + )); + this.showOutput(); + } + + private async executeTask(task: Task, resolver: ITaskResolver, trigger: string, encounteredDependencies: Set, alreadyResolved?: Map): Promise { + if (encounteredDependencies.has(task.getCommonTaskId())) { + this.showDependencyCycleMessage(task); + return {}; + } + alreadyResolved = alreadyResolved ?? new Map(); let promises: Promise[] = []; if (task.configurationProperties.dependsOn) { @@ -446,7 +462,8 @@ export class TerminalTaskSystem implements ITaskSystem { let promise = this.activeTasks[key] ? this.activeTasks[key].promise : undefined; if (!promise) { this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.DependsOnStarted, task)); - promise = this.executeDependencyTask(dependencyTask, resolver, trigger, alreadyResolved); + encounteredDependencies.add(task.getCommonTaskId()); + promise = this.executeDependencyTask(dependencyTask, resolver, trigger, encounteredDependencies, alreadyResolved); } promises.push(promise); if (task.configurationProperties.dependsOrder === DependsOrder.sequence) { @@ -472,6 +489,7 @@ export class TerminalTaskSystem implements ITaskSystem { if ((ContributedTask.is(task) || CustomTask.is(task)) && (task.command)) { return Promise.all(promises).then((summaries): Promise | ITaskSummary => { + encounteredDependencies.delete(task.getCommonTaskId()); for (let summary of summaries) { if (summary.exitCode !== 0) { this.removeInstances(task); @@ -486,6 +504,7 @@ export class TerminalTaskSystem implements ITaskSystem { }); } else { return Promise.all(promises).then((summaries): ITaskSummary => { + encounteredDependencies.delete(task.getCommonTaskId()); for (let summary of summaries) { if (summary.exitCode !== 0) { return { exitCode: summary.exitCode }; @@ -496,11 +515,11 @@ export class TerminalTaskSystem implements ITaskSystem { } } - private async executeDependencyTask(task: Task, resolver: ITaskResolver, trigger: string, alreadyResolved?: Map): Promise { + private async executeDependencyTask(task: Task, resolver: ITaskResolver, trigger: string, encounteredDependencies: Set, alreadyResolved?: Map): Promise { // If the task is a background task with a watching problem matcher, we don't wait for the whole task to finish, // just for the problem matcher to go inactive. if (!task.configurationProperties.isBackground) { - return this.executeTask(task, resolver, trigger, alreadyResolved); + return this.executeTask(task, resolver, trigger, encounteredDependencies, alreadyResolved); } const inactivePromise = new Promise(resolve => { @@ -511,7 +530,7 @@ export class TerminalTaskSystem implements ITaskSystem { } }); }); - return Promise.race([inactivePromise, this.executeTask(task, resolver, trigger, alreadyResolved)]); + return Promise.race([inactivePromise, this.executeTask(task, resolver, trigger, encounteredDependencies, alreadyResolved)]); } private async resolveAndFindExecutable(systemInfo: TaskSystemInfo | undefined, workspaceFolder: IWorkspaceFolder | undefined, task: CustomTask | ContributedTask, cwd: string | undefined, envPath: string | undefined): Promise { @@ -856,7 +875,6 @@ export class TerminalTaskSystem implements ITaskSystem { }); promise = new Promise((resolve, reject) => { const onExit = terminal!.onExit((exitCode) => { - onData.dispose(); onExit.dispose(); let key = task.getMapKey(); this.removeFromActiveTasks(task); @@ -887,8 +905,12 @@ export class TerminalTaskSystem implements ITaskSystem { // There is nothing else to do here. } } - startStopProblemMatcher.done(); - startStopProblemMatcher.dispose(); + // Hack to work around #92868 until terminal is fixed. + setTimeout(() => { + onData.dispose(); + startStopProblemMatcher.done(); + startStopProblemMatcher.dispose(); + }, 100); if (!processStartedSignaled && terminal) { this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal.processId!)); processStartedSignaled = true; @@ -1019,9 +1041,17 @@ export class TerminalTaskSystem implements ITaskSystem { if (!shellSpecified) { // Under Mac remove -l to not start it as a login shell. if (platform === Platform.Platform.Mac) { - let index = shellArgs.indexOf('-l'); - if (index !== -1) { - shellArgs.splice(index, 1); + // Background on -l on osx https://github.com/microsoft/vscode/issues/107563 + const osxShellArgs = this.configurationService.inspect('terminal.integrated.shellArgs.osx'); + if ((osxShellArgs.user === undefined) && (osxShellArgs.userLocal === undefined) && (osxShellArgs.userLocalValue === undefined) + && (osxShellArgs.userRemote === undefined) && (osxShellArgs.userRemoteValue === undefined) + && (osxShellArgs.userValue === undefined) && (osxShellArgs.workspace === undefined) + && (osxShellArgs.workspaceFolder === undefined) && (osxShellArgs.workspaceFolderValue === undefined) + && (osxShellArgs.workspaceValue === undefined)) { + let index = shellArgs.indexOf('-l'); + if (index !== -1) { + shellArgs.splice(index, 1); + } } } toAdd.push('-c'); diff --git a/src/vs/workbench/contrib/tasks/common/problemCollectors.ts b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts index 46574e022..5e6c6f746 100644 --- a/src/vs/workbench/contrib/tasks/common/problemCollectors.ts +++ b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts @@ -14,6 +14,7 @@ import { ILineMatcher, createLineMatcher, ProblemMatcher, ProblemMatch, ApplyToK import { IMarkerService, IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { generateUuid } from 'vs/base/common/uuid'; import { IFileService } from 'vs/platform/files/common/files'; +import { isWindows } from 'vs/base/common/platform'; export const enum ProblemCollectorEventKind { BackgroundProcessingBegins = 'backgroundProcessingBegins', @@ -282,7 +283,7 @@ export abstract class AbstractProblemCollector implements IDisposable { let existingMarker; if (!markersPerResource.has(key)) { markersPerResource.set(key, marker); - } else if (((existingMarker = markersPerResource.get(key)) !== undefined) && existingMarker.message.length < marker.message.length) { + } else if (((existingMarker = markersPerResource.get(key)) !== undefined) && (existingMarker.message.length < marker.message.length) && isWindows) { // Most likely https://github.com/microsoft/vscode/issues/77475 // Heuristic dictates that when the key is the same and message is smaller, we have hit this limitation. markersPerResource.set(key, marker); diff --git a/src/vs/workbench/contrib/tasks/common/taskService.ts b/src/vs/workbench/contrib/tasks/common/taskService.ts index 19e45d105..8b38d041a 100644 --- a/src/vs/workbench/contrib/tasks/common/taskService.ts +++ b/src/vs/workbench/contrib/tasks/common/taskService.ts @@ -77,6 +77,7 @@ export interface ITaskService { taskTypes(): string[]; getWorkspaceTasks(runSource?: TaskRunSource): Promise>; readRecentTasks(): Promise<(Task | ConfiguringTask)[]>; + removeRecentlyUsedTask(taskRecentlyUsedKey: string): void; /** * @param alias The task's name, label or defined identifier. */ diff --git a/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts b/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts index 70145f1b9..64fe0389f 100644 --- a/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import * as Objects from 'vs/base/common/objects'; import * as semver from 'vs/base/common/semver/semver'; import { IStringDictionary } from 'vs/base/common/collections'; -import { WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { WorkbenchState, IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ITaskSystem } from 'vs/workbench/contrib/tasks/common/taskSystem'; import { ExecutionEngine, TaskRunSource } from 'vs/workbench/contrib/tasks/common/tasks'; import * as TaskConfig from '../common/taskConfiguration'; @@ -15,6 +16,36 @@ import { ProcessRunnerDetector } from 'vs/workbench/contrib/tasks/node/processRu import { AbstractTaskService } from 'vs/workbench/contrib/tasks/browser/abstractTaskService'; import { TaskFilter, ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { TerminalTaskSystem } from 'vs/workbench/contrib/tasks/browser/terminalTaskSystem'; +import { IConfirmationResult, IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { TerminateResponseCode } from 'vs/base/common/processes'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +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 { IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IMarkerService } from 'vs/platform/markers/common/markers'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IProgressService } from 'vs/platform/progress/common/progress'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IViewsService, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IOutputService } from 'vs/workbench/contrib/output/common/output'; +import { ITerminalService, ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { IPathService } from 'vs/workbench/services/path/common/pathService'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; interface WorkspaceFolderConfigurationResult { workspaceFolder: IWorkspaceFolder; @@ -24,6 +55,69 @@ interface WorkspaceFolderConfigurationResult { export class TaskService extends AbstractTaskService { private _configHasErrors: boolean = false; + constructor(@IConfigurationService configurationService: IConfigurationService, + @IMarkerService markerService: IMarkerService, + @IOutputService outputService: IOutputService, + @IPanelService panelService: IPanelService, + @IViewsService viewsService: IViewsService, + @ICommandService commandService: ICommandService, + @IEditorService editorService: IEditorService, + @IFileService fileService: IFileService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @ITelemetryService telemetryService: ITelemetryService, + @ITextFileService textFileService: ITextFileService, + @ILifecycleService lifecycleService: ILifecycleService, + @IModelService modelService: IModelService, + @IExtensionService extensionService: IExtensionService, + @IQuickInputService quickInputService: IQuickInputService, + @IConfigurationResolverService configurationResolverService: IConfigurationResolverService, + @ITerminalService terminalService: ITerminalService, + @IStorageService storageService: IStorageService, + @IProgressService progressService: IProgressService, + @IOpenerService openerService: IOpenerService, + @IHostService _hostService: IHostService, + @IDialogService dialogService: IDialogService, + @INotificationService notificationService: INotificationService, + @IContextKeyService contextKeyService: IContextKeyService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @ITerminalInstanceService terminalInstanceService: ITerminalInstanceService, + @IPathService pathService: IPathService, + @ITextModelService textModelResolverService: ITextModelService, + @IPreferencesService preferencesService: IPreferencesService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @ILogService logService: ILogService) { + super(configurationService, + markerService, + outputService, + panelService, + viewsService, + commandService, + editorService, + fileService, + contextService, + telemetryService, + textFileService, + modelService, + extensionService, + quickInputService, + configurationResolverService, + terminalService, + storageService, + progressService, + openerService, + _hostService, + dialogService, + notificationService, + contextKeyService, + environmentService, + terminalInstanceService, + pathService, + textModelResolverService, + preferencesService, + viewDescriptorService, + logService); + this._register(lifecycleService.onBeforeShutdown(event => event.veto(this.beforeShutdown()))); + } protected getTaskSystem(): ITaskSystem { if (this._taskSystem) { @@ -133,6 +227,64 @@ export class TaskService extends AbstractTaskService { } return result; } + + public beforeShutdown(): boolean | Promise { + if (!this._taskSystem) { + return false; + } + if (!this._taskSystem.isActiveSync()) { + return false; + } + // The terminal service kills all terminal on shutdown. So there + // is nothing we can do to prevent this here. + if (this._taskSystem instanceof TerminalTaskSystem) { + return false; + } + + let terminatePromise: Promise; + if (this._taskSystem.canAutoTerminate()) { + terminatePromise = Promise.resolve({ confirmed: true }); + } else { + terminatePromise = this.dialogService.confirm({ + message: nls.localize('TaskSystem.runningTask', 'There is a task running. Do you want to terminate it?'), + primaryButton: nls.localize({ key: 'TaskSystem.terminateTask', comment: ['&& denotes a mnemonic'] }, "&&Terminate Task"), + type: 'question' + }); + } + + return terminatePromise.then(res => { + if (res.confirmed) { + return this._taskSystem!.terminateAll().then((responses) => { + let success = true; + let code: number | undefined = undefined; + for (let response of responses) { + success = success && response.success; + // We only have a code in the old output runner which only has one task + // So we can use the first code. + if (code === undefined && response.code !== undefined) { + code = response.code; + } + } + if (success) { + this._taskSystem = undefined; + this.disposeTaskSystemListeners(); + return false; // no veto + } else if (code && code === TerminateResponseCode.ProcessNotFound) { + return this.dialogService.confirm({ + message: nls.localize('TaskSystem.noProcess', 'The launched task doesn\'t exist anymore. If the task spawned background processes exiting VS Code might result in orphaned processes. To avoid this start the last background process with a wait flag.'), + primaryButton: nls.localize({ key: 'TaskSystem.exitAnyways', comment: ['&& denotes a mnemonic'] }, "&&Exit Anyways"), + type: 'info' + }).then(res => !res.confirmed); + } + return true; // veto + }, (err) => { + return true; // veto + }); + } + + return true; // veto + }); + } } registerSingleton(ITaskService, TaskService, true); diff --git a/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts b/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts index 778410948..9e1ecd640 100644 --- a/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts +++ b/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts @@ -10,12 +10,13 @@ import * as UUID from 'vs/base/common/uuid'; import * as Platform from 'vs/base/common/platform'; import { ValidationStatus } from 'vs/base/common/parsers'; import { ProblemMatcher, FileLocationKind, ProblemPattern, ApplyToKind } from 'vs/workbench/contrib/tasks/common/problemMatcher'; -import { WorkspaceFolder, Workspace, IWorkspace } from 'vs/platform/workspace/common/workspace'; +import { WorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; import * as Tasks from 'vs/workbench/contrib/tasks/common/tasks'; import { parse, ParseResult, IProblemReporter, ExternalTaskRunnerConfiguration, CustomTask, TaskConfigSource } from 'vs/workbench/contrib/tasks/common/taskConfiguration'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { IContext } from 'vs/platform/contextkey/common/contextkey'; +import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; const workspaceFolder: WorkspaceFolder = new WorkspaceFolder({ uri: URI.file('/workspace/folderOne'), diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index 004f9217c..511c82963 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -31,7 +31,7 @@ type TelemetryData = { ext: string; path: number; reason?: number; - whitelistedjson?: string; + allowlistedjson?: string; }; type FileTelemetryDataFragment = { @@ -39,13 +39,13 @@ type FileTelemetryDataFragment = { ext: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; path: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; reason?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - whitelistedjson?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + allowlistedjson?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; }; export class TelemetryContribution extends Disposable implements IWorkbenchContribution { - private static WHITELIST_JSON = ['package.json', 'package-lock.json', 'tsconfig.json', 'jsconfig.json', 'bower.json', '.eslintrc.json', 'tslint.json', 'composer.json']; - private static WHITELIST_WORKSPACE_JSON = ['settings.json', 'extensions.json', 'tasks.json', 'launch.json']; + private static ALLOWLIST_JSON = ['package.json', 'package-lock.json', 'tsconfig.json', 'jsconfig.json', 'bower.json', '.eslintrc.json', 'tslint.json', 'composer.json']; + private static ALLOWLIST_WORKSPACE_JSON = ['settings.json', 'extensions.json', 'tasks.json', 'launch.json']; constructor( @ITelemetryService private readonly telemetryService: ITelemetryService, @@ -184,7 +184,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr for (const folder of folders) { if (isEqualOrParent(resource, folder.toResource('.vscode'))) { const filename = basename(resource); - if (TelemetryContribution.WHITELIST_WORKSPACE_JSON.indexOf(filename) > -1) { + if (TelemetryContribution.ALLOWLIST_WORKSPACE_JSON.indexOf(filename) > -1) { return `.vscode/${filename}`; } } @@ -202,11 +202,11 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr ext, path: hash(path), reason, - whitelistedjson: undefined as string | undefined + allowlistedjson: undefined as string | undefined }; - if (ext === '.json' && TelemetryContribution.WHITELIST_JSON.indexOf(fileName) > -1) { - telemetryData['whitelistedjson'] = fileName; + if (ext === '.json' && TelemetryContribution.ALLOWLIST_JSON.indexOf(fileName) > -1) { + telemetryData['allowlistedjson'] = fileName; } return telemetryData; diff --git a/src/vs/workbench/contrib/terminal/browser/environmentVariableInfo.ts b/src/vs/workbench/contrib/terminal/browser/environmentVariableInfo.ts index 09750a14e..d91501663 100644 --- a/src/vs/workbench/contrib/terminal/browser/environmentVariableInfo.ts +++ b/src/vs/workbench/contrib/terminal/browser/environmentVariableInfo.ts @@ -7,6 +7,8 @@ import { IEnvironmentVariableInfo, IMergedEnvironmentVariableCollection, IMerged import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { localize } from 'vs/nls'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { Codicon } from 'vs/base/common/codicons'; export class EnvironmentVariableInfoStale implements IEnvironmentVariableInfo { readonly requiresAction = true; @@ -53,8 +55,8 @@ export class EnvironmentVariableInfoStale implements IEnvironmentVariableInfo { return info; } - getIcon(): string { - return 'warning'; + getIcon(): ThemeIcon { + return Codicon.warning; } getActions(): { label: string, iconClass?: string, run: () => void, commandId: string }[] { @@ -83,8 +85,8 @@ export class EnvironmentVariableInfoChangesActive implements IEnvironmentVariabl return message + '\n\n```\n' + changes.join('\n') + '\n```'; } - getIcon(): string { - return 'info'; + getIcon(): ThemeIcon { + return Codicon.info; } } diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider.ts index 5c0e8243f..fb420ef52 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider.ts @@ -49,6 +49,8 @@ export const unixLineAndColumnMatchIndex = 11; // Each line and column clause have 6 groups (ie no. of expressions in round brackets) export const lineAndColumnClauseGroupCount = 6; +const MAX_LENGTH = 2000; + export class TerminalValidatedLocalLinkProvider extends TerminalBaseLinkProvider { constructor( private readonly _xterm: Terminal, @@ -85,6 +87,9 @@ export class TerminalValidatedLocalLinkProvider extends TerminalBaseLinkProvider } const text = getXtermLineContent(this._xterm.buffer.active, startLine, endLine, this._xterm.cols); + if (text.length > MAX_LENGTH) { + return []; + } // clone regex to do a global search on text const rex = new RegExp(this._localLinkRegex, 'g'); diff --git a/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css b/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css index da4ecfc43..5732fb11c 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css +++ b/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css @@ -11,6 +11,10 @@ transition: background-color 800ms linear; } +.monaco-workbench .pane-body.integrated-terminal .xterm-viewport { + scrollbar-width: thin; +} + .monaco-workbench .pane-body.integrated-terminal .xterm-viewport::-webkit-scrollbar { width: 10px; } diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 8c394244f..43de91e60 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -196,7 +196,3 @@ padding: 0 22px 0 6px; } -/* HACK: Can remove when fixed upstream https://github.com/xtermjs/xterm.js/issues/3058 */ -.xterm-helper-textarea { - border: 0px; -} diff --git a/src/vs/workbench/contrib/terminal/browser/media/xterm.css b/src/vs/workbench/contrib/terminal/browser/media/xterm.css index 712baa284..1ac20b3df 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/xterm.css +++ b/src/vs/workbench/contrib/terminal/browser/media/xterm.css @@ -62,10 +62,10 @@ } .xterm .xterm-helper-textarea { - /* - * HACK: to fix IE's blinking cursor - * Move textarea out of the screen to the far left, so that the cursor is not visible. - */ + padding: 0; + border: 0; + margin: 0; + /* Move textarea out of the screen to the far left, so that the cursor is not visible */ position: absolute; opacity: 0; left: -9999em; diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 34e4327a1..61d5525a7 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -36,6 +36,7 @@ import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/pl import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminalQuickAccess'; import { terminalConfiguration } from 'vs/workbench/contrib/terminal/common/terminalConfiguration'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; +import { terminalViewIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons'; // Register services registerSingleton(ITerminalService, TerminalService, true); @@ -63,7 +64,7 @@ configurationRegistry.registerConfiguration(terminalConfiguration); const VIEW_CONTAINER = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: TERMINAL_VIEW_ID, name: nls.localize('terminal', "Terminal"), - icon: 'codicon-terminal', + icon: terminalViewIcon, ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [TERMINAL_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), storageId: TERMINAL_VIEW_ID, focusCommand: { id: TERMINAL_COMMAND_ID.FOCUS }, @@ -74,7 +75,7 @@ Registry.as(panel.Extensions.Panels).setDefaultPanelId(TERM Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ id: TERMINAL_VIEW_ID, name: nls.localize('terminal', "Terminal"), - containerIcon: 'codicon-terminal', + containerIcon: terminalViewIcon, canToggleVisibility: false, canMoveView: true, ctorDescriptor: new SyncDescriptor(TerminalViewPane) @@ -150,16 +151,17 @@ if (BrowserFeatures.clipboard.readText) { win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_V] }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_V } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Paste into Active Terminal', category, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED); - // An extra Windows-only ctrl+v keybinding is used for pwsh that sends ctrl+v directly to the - // shell, this gets handled by PSReadLine which properly handles multi-line pastes. This is - // disabled in accessibility mode as PowerShell does not run PSReadLine when it detects a screen - // reader. - if (platform.isWindows) { - registerSendSequenceKeybinding(String.fromCharCode('V'.charCodeAt(0) - CTRL_LETTER_OFFSET), { // ctrl+v - when: ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, ContextKeyExpr.equals(KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE_KEY, WindowsShellType.PowerShell), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), - primary: KeyMod.CtrlCmd | KeyCode.KEY_V - }); - } +} + +// An extra Windows-only ctrl+v keybinding is used for pwsh that sends ctrl+v directly to the +// shell, this gets handled by PSReadLine which properly handles multi-line pastes. This is +// disabled in accessibility mode as PowerShell does not run PSReadLine when it detects a screen +// reader. This works even when clipboard.readText is not supported. +if (platform.isWindows) { + registerSendSequenceKeybinding(String.fromCharCode('V'.charCodeAt(0) - CTRL_LETTER_OFFSET), { // ctrl+v + when: ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, ContextKeyExpr.equals(KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE_KEY, WindowsShellType.PowerShell), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), + primary: KeyMod.CtrlCmd | KeyCode.KEY_V + }); } // Delete word left: ctrl+w diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index bb382e0f9..1f58d65ea 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -59,7 +59,6 @@ export interface ITerminalTab { onDisposed: Event; onInstancesChanged: Event; - setWillFocus(focus: boolean): void; focusPreviousPane(): void; focusNextPane(): void; resizePane(direction: Direction): void; @@ -71,6 +70,11 @@ export interface ITerminalTab { split(shellLaunchConfig: IShellLaunchConfig): ITerminalInstance; } +export const enum TerminalConnectionState { + Connecting, + Connected +} + export interface ITerminalService { readonly _serviceBrand: undefined; @@ -79,6 +83,7 @@ export interface ITerminalService { terminalInstances: ITerminalInstance[]; terminalTabs: ITerminalTab[]; isProcessSupportRegistered: boolean; + readonly connectionState: TerminalConnectionState; initializeTerminals(): Promise; onActiveTabChanged: Event; @@ -95,6 +100,7 @@ export interface ITerminalService { onActiveInstanceChanged: Event; onRequestAvailableShells: Event; onDidRegisterProcessSupport: Event; + onDidChangeConnectionState: Event; /** * Creates a terminal. @@ -416,6 +422,11 @@ export interface ITerminalInstance { */ notifyFindWidgetFocusChanged(isFocused: boolean): void; + /** + * Notifies the terminal to refresh its focus state based on the active document elemnet in DOM + */ + refreshFocusState(): void; + /** * Focuses the terminal instance if it's able to (xterm.js instance exists). * diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 953b22493..63264f9ab 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -9,7 +9,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { TERMINAL_VIEW_ID, ITerminalConfigHelper, TitleEventSource, TERMINAL_COMMAND_ID, KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED, TERMINAL_ACTION_CATEGORY, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FIND_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, IRemoteTerminalAttachTarget } from 'vs/workbench/contrib/terminal/common/terminal'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IQuickInputService, IPickOptions, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; @@ -24,7 +24,7 @@ import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { isWindows } from 'vs/base/common/platform'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { ITerminalInstance, ITerminalService, Direction, IRemoteTerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalInstance, ITerminalService, Direction, IRemoteTerminalService, TerminalConnectionState } from 'vs/workbench/contrib/terminal/browser/terminal'; import { Action2, registerAction2, ILocalizedString } from 'vs/platform/actions/common/actions'; import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminalQuickAccess'; import { ToggleViewAction } from 'vs/workbench/browser/actions/layoutActions'; @@ -41,6 +41,9 @@ import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewIte import { FindInFilesCommand, IFindInFilesArgs } from 'vs/workbench/contrib/search/browser/searchActions'; import { ILabelService } from 'vs/platform/label/common/label'; import { RemoteNameContext } from 'vs/workbench/browser/contextkeys'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { killTerminalIcon, newTerminalIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons'; +import { Codicon } from 'vs/base/common/codicons'; async function getCwdForSplit(configHelper: ITerminalConfigHelper, instance: ITerminalInstance, folders?: IWorkspaceFolder[], commandService?: ICommandService): Promise { switch (configHelper.config.splitCwd) { @@ -95,7 +98,7 @@ export class KillTerminalAction extends Action { id: string, label: string, @ITerminalService private readonly _terminalService: ITerminalService ) { - super(id, label, 'terminal-action codicon-trash'); + super(id, label, 'terminal-action ' + ThemeIcon.asClassName(killTerminalIcon)); } async run() { @@ -175,7 +178,7 @@ export class CreateNewTerminalAction extends Action { @ICommandService private readonly _commandService: ICommandService, @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService ) { - super(id, label, 'terminal-action codicon-add'); + super(id, label, 'terminal-action ' + ThemeIcon.asClassName(newTerminalIcon)); } async run(event?: any) { @@ -216,8 +219,8 @@ export class SplitTerminalAction extends Action { public static readonly ID = TERMINAL_COMMAND_ID.SPLIT; public static readonly LABEL = localize('workbench.action.terminal.split', "Split Terminal"); public static readonly SHORT_LABEL = localize('workbench.action.terminal.split.short', "Split"); - public static readonly HORIZONTAL_CLASS = 'terminal-action codicon-split-horizontal'; - public static readonly VERTICAL_CLASS = 'terminal-action codicon-split-vertical'; + public static readonly HORIZONTAL_CLASS = 'terminal-action ' + Codicon.splitHorizontal.classNames; + public static readonly VERTICAL_CLASS = 'terminal-action ' + Codicon.splitVertical.classNames; constructor( id: string, label: string, @@ -295,6 +298,23 @@ export class SelectDefaultShellWindowsTerminalAction extends Action { } } +export class ConfigureTerminalSettingsAction extends Action { + + public static readonly ID = TERMINAL_COMMAND_ID.CONFIGURE_TERMINAL_SETTINGS; + public static readonly LABEL = localize('workbench.action.terminal.openSettings', "Configure Terminal Settings"); + + constructor( + id: string, label: string, + @IPreferencesService private readonly _preferencesService: IPreferencesService + ) { + super(id, label); + } + + async run() { + this._preferencesService.openSettings(false, '@feature:terminal'); + } +} + const terminalIndexRe = /^([0-9]+): /; export class SwitchTerminalAction extends Action { @@ -307,6 +327,7 @@ export class SwitchTerminalAction extends Action { @ITerminalService private readonly _terminalService: ITerminalService, @ITerminalContributionService private readonly _contributions: ITerminalContributionService, @ICommandService private readonly _commands: ICommandService, + @IPreferencesService private readonly preferencesService: IPreferencesService ) { super(id, label, 'terminal-action switch-terminal'); } @@ -323,7 +344,11 @@ export class SwitchTerminalAction extends Action { this._terminalService.refreshActiveTab(); return this._terminalService.selectDefaultShell(); } - + if (item === ConfigureTerminalSettingsAction.LABEL) { + const settingsAction = new ConfigureTerminalSettingsAction(ConfigureTerminalSettingsAction.ID, ConfigureTerminalSettingsAction.LABEL, this.preferencesService); + settingsAction.run(); + this._terminalService.refreshActiveTab(); + } const indexMatches = terminalIndexRe.exec(item); if (indexMatches) { this._terminalService.setActiveTabByIndex(Number(indexMatches[1]) - 1); @@ -357,6 +382,7 @@ export class SwitchTerminalActionViewItem extends SelectActionViewItem { this._register(_terminalService.onActiveTabChanged(this._updateItems, this)); this._register(_terminalService.onInstanceTitleChanged(this._updateItems, this)); this._register(_terminalService.onTabDisposed(this._updateItems, this)); + this._register(_terminalService.onDidChangeConnectionState(this._updateItems, this)); this._register(attachSelectBoxStyler(this.selectBox, this._themeService)); } @@ -374,7 +400,10 @@ export class SwitchTerminalActionViewItem extends SelectActionViewItem { } function getTerminalSelectOpenItems(terminalService: ITerminalService, contributions: ITerminalContributionService): ISelectOptionItem[] { - const items = terminalService.getTabLabels().map(label => { text: label }); + const items = terminalService.connectionState === TerminalConnectionState.Connected ? + terminalService.getTabLabels().map(label => { text: label }) : + [{ text: localize('terminalConnectingLabel', "Starting...") }]; + items.push({ text: SwitchTerminalActionViewItem.SEPARATOR, isDisabled: true }); for (const contributed of contributions.terminalTypes) { @@ -382,6 +411,7 @@ function getTerminalSelectOpenItems(terminalService: ITerminalService, contribut } items.push({ text: SelectDefaultShellWindowsTerminalAction.LABEL }); + items.push({ text: ConfigureTerminalSettingsAction.LABEL }); return items; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts index 6dc113471..105d4cbfc 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import * as platform from 'vs/base/common/platform'; import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITerminalConfiguration, ITerminalFont, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, TERMINAL_CONFIG_SECTION, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, MINIMUM_LETTER_SPACING, LinuxDistro, IShellLaunchConfig, MINIMUM_FONT_WEIGHT, MAXIMUM_FONT_WEIGHT, DEFAULT_FONT_WEIGHT, DEFAULT_BOLD_FONT_WEIGHT, FontWeight } from 'vs/workbench/contrib/terminal/common/terminal'; import Severity from 'vs/base/common/severity'; import { INotificationService, NeverShowAgainScope } from 'vs/platform/notification/common/notification'; @@ -20,7 +20,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { InstallRecommendedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IProductService } from 'vs/platform/product/common/productService'; import { XTermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; const MINIMUM_FONT_SIZE = 6; const MAXIMUM_FONT_SIZE = 25; @@ -51,7 +50,6 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { @ITelemetryService private readonly telemetryService: ITelemetryService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IProductService private readonly productService: IProductService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { this._updateConfig(); this._configurationService.onDidChangeConfiguration(e => { @@ -59,9 +57,6 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { this._updateConfig(); } }); - - // opt-in to syncing - storageKeysSyncRegistryService.registerStorageKey({ key: 'terminalConfigHelper/launchRecommendationsIgnore', version: 1 }); } public setLinuxDistro(linuxDistro: LinuxDistro) { @@ -211,7 +206,7 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { public setWorkspaceShellAllowed(isAllowed: boolean): void { this._onWorkspacePermissionsChanged.fire(isAllowed); - this._storageService.store(IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, isAllowed, StorageScope.WORKSPACE); + this._storageService.store(IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, isAllowed, StorageScope.WORKSPACE, StorageTarget.MACHINE); } public isWorkspaceShellAllowed(defaultValue: boolean | undefined = undefined): boolean | undefined { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalIcons.ts b/src/vs/workbench/contrib/terminal/browser/terminalIcons.ts new file mode 100644 index 000000000..88d2b3131 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/browser/terminalIcons.ts @@ -0,0 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { localize } from 'vs/nls'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; + +export const terminalViewIcon = registerIcon('terminal-view-icon', Codicon.terminal, localize('terminalViewIcon', 'View icon of the terminal view.')); + +export const renameTerminalIcon = registerIcon('terminal-rename', Codicon.gear, localize('renameTerminalIcon', 'Icon for rename in the terminal quick menu.')); +export const killTerminalIcon = registerIcon('terminal-kill', Codicon.trash, localize('killTerminalIcon', 'Icon for killing a terminal instance.')); +export const newTerminalIcon = registerIcon('terminal-new', Codicon.add, localize('newTerminalIcon', 'Icon for creating a new terminal instance.')); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 675990117..08dfc59ed 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -20,12 +20,12 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry'; import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; -import { IShellLaunchConfig, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_VIEW_ID, IWindowsShellHelper, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, DEFAULT_COMMANDS_TO_SKIP_SHELL, ITerminalLaunchError, IProcessDataEvent, ITerminalDimensionsOverride } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IShellLaunchConfig, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_VIEW_ID, IWindowsShellHelper, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, DEFAULT_COMMANDS_TO_SKIP_SHELL, ITerminalLaunchError, IProcessDataEvent, ITerminalDimensionsOverride, SHOW_TERMINAL_CONFIG_PROMPT } from 'vs/workbench/contrib/terminal/common/terminal'; import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_FOREGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalLinkManager } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager'; @@ -45,6 +45,8 @@ import { EnvironmentVariableInfoWidget } from 'vs/workbench/contrib/terminal/bro import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { TerminalLaunchHelpAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TypeAheadAddon } from 'vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon'; +import { BrowserFeatures } from 'vs/base/browser/canIUse'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; // How long in milliseconds should an average frame take to render for a notification to appear // which suggests the fallback DOM-based renderer @@ -186,6 +188,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @INotificationService private readonly _notificationService: INotificationService, + @IPreferencesService private readonly _preferencesService: IPreferencesService, @IViewsService private readonly _viewsService: IViewsService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IClipboardService private readonly _clipboardService: IClipboardService, @@ -541,9 +544,29 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return false; } - // Skip processing by xterm.js of keyboard events that resolve to commands described - // within commandsToSkipShell - if (resolveResult && this._skipTerminalCommands.some(k => k === resolveResult.commandId)) { + // for keyboard events that resolve to commands described + // within commandsToSkipShell, either alert or skip processing by xterm.js + if (resolveResult && this._skipTerminalCommands.some(k => k === resolveResult.commandId) && !this._configHelper.config.sendKeybindingsToShell) { + // don't alert when terminal is opened or closed + if (this._storageService.getBoolean(SHOW_TERMINAL_CONFIG_PROMPT, StorageScope.GLOBAL, true) && + resolveResult.commandId !== 'workbench.action.terminal.toggleTerminal' && + resolveResult.commandId !== 'workbench.action.terminal.new' && + resolveResult.commandId !== 'workbench.action.togglePanel' && + resolveResult.commandId !== 'workbench.action.terminal.focus') { + this._notificationService.prompt( + Severity.Info, + nls.localize('configure terminal settings', "Some keybindings are dispatched to the workbench by default."), + [ + { + label: nls.localize('configureTerminalSettings', "Configure Terminal Settings"), + run: () => { + this._preferencesService.openSettings(false, '@id:terminal.integrated.commandsToSkipShell,terminal.integrated.sendKeybindingsToShell,terminal.integrated.allowChords'); + } + } as IPromptChoice + ] + ); + this._storageService.store(SHOW_TERMINAL_CONFIG_PROMPT, false, StorageScope.GLOBAL, StorageTarget.USER); + } event.preventDefault(); return false; } @@ -564,6 +587,12 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return false; } + // Fallback to force ctrl+v to paste on browsers that do not support + // navigator.clipboard.readText + if (!BrowserFeatures.clipboard.readText && event.key === 'v' && event.ctrlKey) { + return false; + } + return true; }); this._register(dom.addDisposableListener(xterm.element, 'mousedown', () => { @@ -656,7 +685,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { { label: nls.localize('dontShowAgain', "Don't Show Again"), isSecondary: true, - run: () => this._storageService.store(NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, true, StorageScope.GLOBAL) + run: () => this._storageService.store(NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, true, StorageScope.GLOBAL, StorageTarget.MACHINE) } as IPromptChoice ]; this._notificationService.prompt( @@ -728,6 +757,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._terminalFocusContextKey.set(terminalFocused); } + public refreshFocusState() { + this.notifyFindWidgetFocusChanged(false); + } + public dispose(immediate?: boolean): void { this._logService.trace(`terminalInstance#dispose (id: ${this.id})`); @@ -1404,7 +1437,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // maximize on Windows/Linux would fire an event saying that the terminal was not // visible. if (this._xterm.getOption('rendererType') === 'canvas') { - this._xtermCore._renderService._onIntersectionChange({ intersectionRatio: 1 }); + this._xtermCore._renderService?._onIntersectionChange({ intersectionRatio: 1 }); // HACK: Force a refresh of the screen to ensure links are refresh corrected. // This can probably be removed when the above hack is fixed in Chromium. this._xterm.refresh(0, this._xterm.rows - 1); @@ -1594,13 +1627,17 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = .monaco-workbench .pane-body.integrated-terminal .find-focused .xterm .xterm-viewport, .monaco-workbench .pane-body.integrated-terminal .xterm.focus .xterm-viewport, .monaco-workbench .pane-body.integrated-terminal .xterm:focus .xterm-viewport, - .monaco-workbench .pane-body.integrated-terminal .xterm:hover .xterm-viewport { background-color: ${scrollbarSliderBackgroundColor} !important; }` - ); + .monaco-workbench .pane-body.integrated-terminal .xterm:hover .xterm-viewport { background-color: ${scrollbarSliderBackgroundColor} !important; } + .monaco-workbench .pane-body.integrated-terminal .xterm-viewport { scrollbar-color: ${scrollbarSliderBackgroundColor} transparent; } + `); } const scrollbarSliderHoverBackgroundColor = theme.getColor(scrollbarSliderHoverBackground); if (scrollbarSliderHoverBackgroundColor) { - collector.addRule(`.monaco-workbench .pane-body.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover { background-color: ${scrollbarSliderHoverBackgroundColor}; }`); + collector.addRule(` + .monaco-workbench .pane-body.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover { background-color: ${scrollbarSliderHoverBackgroundColor}; } + .monaco-workbench .pane-body.integrated-terminal .xterm-viewport:hover { scrollbar-color: ${scrollbarSliderHoverBackgroundColor} transparent; } + `); } const scrollbarSliderActiveBackgroundColor = theme.getColor(scrollbarSliderActiveBackground); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts b/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts index 6c4af6350..7a283761a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts @@ -10,6 +10,8 @@ import { matchesFuzzy } from 'vs/base/common/filters'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { killTerminalIcon, renameTerminalIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons'; export class TerminalQuickAccessProvider extends PickerQuickAccessProvider { @@ -39,11 +41,11 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider | undefined; + private _connectionState: TerminalConnectionState; public get configHelper(): ITerminalConfigHelper { return this._configHelper; } @@ -97,6 +99,9 @@ export class TerminalService implements ITerminalService { public get onRequestAvailableShells(): Event { return this._onRequestAvailableShells.event; } private readonly _onDidRegisterProcessSupport = new Emitter(); public get onDidRegisterProcessSupport(): Event { return this._onDidRegisterProcessSupport.event; } + private readonly _onDidChangeConnectionState = new Emitter(); + public get onDidChangeConnectionState(): Event { return this._onDidChangeConnectionState.event; } + public get connectionState(): TerminalConnectionState { return this._connectionState; } constructor( @IContextKeyService private _contextKeyService: IContextKeyService, @@ -134,6 +139,42 @@ export class TerminalService implements ITerminalService { this._handleInstanceContextKeys(); this._processSupportContextKey = KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED.bindTo(this._contextKeyService); this._processSupportContextKey.set(!isWeb || this._remoteAgentService.getConnection() !== null); + + const enableTerminalReconnection = this.configHelper.config.enablePersistentSessions; + const serverSpawn = this.configHelper.config.serverSpawn; + if (!!this._environmentService.remoteAuthority && enableTerminalReconnection && serverSpawn) { + this._remoteTerminalsInitialized = this._reconnectToRemoteTerminals(); + this._connectionState = TerminalConnectionState.Connecting; + } else { + this._connectionState = TerminalConnectionState.Connected; + } + } + + private async _reconnectToRemoteTerminals(): Promise { + const remoteTerms = await this._remoteTerminalService.listTerminals(true); + const workspace = this._workspaceContextService.getWorkspace(); + const unattachedWorkspaceRemoteTerms = remoteTerms + .filter(term => term.workspaceId === workspace.id) + .filter(term => !this.isAttachedToTerminal(term)); + + /* __GDPR__ + "terminalReconnection" : { + "count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + } + */ + const data = { + count: unattachedWorkspaceRemoteTerms.length + }; + this._telemetryService.publicLog('terminalReconnection', data); + if (unattachedWorkspaceRemoteTerms.length > 0) { + // Reattach to all remote terminals + for (let term of unattachedWorkspaceRemoteTerms) { + this.createTerminal({ remoteAttach: term }); + } + } + + this._connectionState = TerminalConnectionState.Connected; + this._onDidChangeConnectionState.fire(); } public setNativeWindowsDelegate(delegate: ITerminalNativeWindowsDelegate): void { @@ -226,7 +267,7 @@ export class TerminalService implements ITerminalService { } public getTabLabels(): string[] { - return this._terminalTabs.map((tab, index) => `${index + 1}: ${tab.title ? tab.title : ''}`); + return this._terminalTabs.filter(tab => tab.terminalInstances.length > 0).map((tab, index) => `${index + 1}: ${tab.title ? tab.title : ''}`); } public getFindState(): FindReplaceState { @@ -349,45 +390,13 @@ export class TerminalService implements ITerminalService { } public async initializeTerminals(): Promise { - const enableTerminalReconnection = this.configHelper.config.enablePersistentSessions; - const serverSpawn = this.configHelper.config.serverSpawn; - if (!!this._environmentService.remoteAuthority && enableTerminalReconnection && serverSpawn) { - let emptyTab: TerminalTab | undefined; + if (this._remoteTerminalsInitialized) { + await this._remoteTerminalsInitialized; + if (!this.terminalTabs.length) { - emptyTab = this._instantiationService.createInstance(TerminalTab, this._terminalContainer, undefined); - this._terminalTabs.push(emptyTab); - this._onInstanceTitleChanged.fire(undefined); + this.createTerminal(undefined); } - - const remoteTerms = await this._remoteTerminalService.listTerminals(true); - const workspace = this._workspaceContextService.getWorkspace(); - const unattachedWorkspaceRemoteTerms = remoteTerms - .filter(term => term.workspaceId === workspace.id) - .filter(term => !this.isAttachedToTerminal(term)); - - /* __GDPR__ - "terminalReconnection" : { - "count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - } - */ - const data = { - count: unattachedWorkspaceRemoteTerms.length - }; - this._telemetryService.publicLog('terminalReconnection', data); - if (unattachedWorkspaceRemoteTerms.length > 0) { - // Reattach to all remote terminals - this.createTerminal({ remoteAttach: unattachedWorkspaceRemoteTerms[0] }, emptyTab); - for (let term of unattachedWorkspaceRemoteTerms.slice(1)) { - this.createTerminal({ remoteAttach: term }); - } - } else if (this.terminalTabs.length === 1 && this.terminalTabs[0] === emptyTab) { - // No terminals to reconnect to, and the only terminal tab is our placeholder - this.createTerminal(undefined, emptyTab); - } else if (emptyTab) { - // There is another tab, need to remove the empty tab - this._removeTab(emptyTab); - } - } else if (this.terminalTabs.length === 0) { + } else if (!this._environmentService.remoteAuthority && this.terminalTabs.length === 0) { // Local, just create a terminal this.createTerminal(); } @@ -664,7 +673,7 @@ export class TerminalService implements ITerminalService { return instance; } - public createTerminal(shell: IShellLaunchConfig = {}, terminalTab?: TerminalTab): ITerminalInstance { + public createTerminal(shell: IShellLaunchConfig = {}): ITerminalInstance { if (!this.isProcessSupportRegistered) { throw new Error('Could not create terminal when process support is not registered'); } @@ -675,14 +684,11 @@ export class TerminalService implements ITerminalService { return instance; } - if (terminalTab) { - terminalTab.addInstance(shell); - } else { - terminalTab = this._instantiationService.createInstance(TerminalTab, this._terminalContainer, shell); - this._terminalTabs.push(terminalTab); - } + const terminalTab = this._instantiationService.createInstance(TerminalTab, this._terminalContainer, shell); + this._terminalTabs.push(terminalTab); const instance = terminalTab.terminalInstances[0]; + terminalTab.addDisposable(terminalTab.onDisposed(this._onTabDisposed.fire, this._onTabDisposed)); terminalTab.addDisposable(terminalTab.onInstancesChanged(this._onInstancesChanged.fire, this._onInstancesChanged)); this._initInstanceListeners(instance); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTab.ts b/src/vs/workbench/contrib/terminal/browser/terminalTab.ts index ac96823f3..6e8e1f58d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTab.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTab.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { IShellLaunchConfig, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; -import * as nls from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { SplitView, Orientation, IView, Sizing } from 'vs/base/browser/ui/splitview/splitview'; @@ -219,7 +218,6 @@ export class TerminalTab extends Disposable implements ITerminalTab { private _activeInstanceIndex: number; private _isVisible: boolean = false; - private _willFocus: boolean = false; public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; } @@ -249,16 +247,6 @@ export class TerminalTab extends Disposable implements ITerminalTab { } } - /** - * Focus the current active instance, or if there isn't one yet, the first instance added - */ - setWillFocus(focus: boolean): void { - this._willFocus = focus; - if (focus && this.activeInstance) { - this.activeInstance.focusWhenReady(); - } - } - public addInstance(shellLaunchConfigOrInstance: IShellLaunchConfig | ITerminalInstance): void { let instance: ITerminalInstance; if ('id' in shellLaunchConfigOrInstance) { @@ -274,10 +262,6 @@ export class TerminalTab extends Disposable implements ITerminalTab { } this._onInstancesChanged.fire(); - - if (this.terminalInstances.length === 1 && this._willFocus) { - instance.focusWhenReady(); - } } public dispose(): void { @@ -387,10 +371,6 @@ export class TerminalTab extends Disposable implements ITerminalTab { } public get title(): string { - if (!this.terminalInstances.length) { - return nls.localize('terminal.integrated.starting', "Starting..."); - } - let title = this.terminalInstances[0].title; for (let i = 1; i < this.terminalInstances.length; i++) { if (this.terminalInstances[i].title) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon.ts b/src/vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon.ts index ca60145b4..5a9b02d8e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon.ts @@ -3,15 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { disposableTimeout } from 'vs/base/common/async'; import { Color } from 'vs/base/common/color'; import { debounce } from 'vs/base/common/decorators'; import { Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { escapeRegExpCharacters } from 'vs/base/common/strings'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { XTermAttributes, XTermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private'; -import { IBeforeProcessDataEvent, ITerminalConfiguration, ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; -import type { IBuffer, IBufferCell, ITerminalAddon, Terminal } from 'xterm'; +import { DEFAULT_LOCAL_ECHO_EXCLUDE, IBeforeProcessDataEvent, ITerminalConfiguration, ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; +import type { IBuffer, IBufferCell, IDisposable, ITerminalAddon, Terminal } from 'xterm'; const ESC = '\x1b'; const CSI = `${ESC}[`; @@ -21,7 +23,6 @@ const DELETE_CHAR = `${CSI}X`; const DELETE_REST_OF_LINE = `${CSI}K`; const CSI_STYLE_RE = /^\x1b\[[0-9;]*m/; const CSI_MOVE_RE = /^\x1b\[([0-9]*)(;[35])?O?([DC])/; -const PASSWORD_INPUT_RE = /(?:\W|^)(?:pat|token|password|passphrase|passwd)(\W.*:|:)/i; const NOT_WORD_RE = /[^a-z0-9]/i; const statsBufferSize = 24; @@ -171,6 +172,12 @@ export interface IPrediction { */ readonly affectsStyle?: boolean; + /** + * If set to false, the prediction will not be cleared if no input is + * received from the server. + */ + readonly clearAfterTimeout?: boolean; + /** * Returns a sequence to apply the prediction. * @param buffer to write to @@ -292,6 +299,8 @@ class StringReader { * after it. */ class HardBoundary implements IPrediction { + public readonly clearAfterTimeout = false; + public apply() { return ''; } @@ -314,12 +323,10 @@ class HardBoundary implements IPrediction { * through its `matches` request. */ class TentativeBoundary implements IPrediction { - private expected?: Cursor; - constructor(private readonly inner: IPrediction) { } + constructor(public readonly inner: IPrediction) { } public apply(buffer: IBuffer, cursor: Cursor) { - this.expected = cursor.clone(); - this.inner.apply(buffer, this.expected); + this.inner.apply(buffer, cursor); return ''; } @@ -328,10 +335,6 @@ class TentativeBoundary implements IPrediction { } public rollForwards(cursor: Cursor, withInput: string) { - if (this.expected) { - cursor.moveTo(this.expected); - } - return withInput; } @@ -340,13 +343,16 @@ class TentativeBoundary implements IPrediction { } } +export const isTenativeCharacterPrediction = (p: unknown): p is (TentativeBoundary & { inner: CharacterPrediction }) => + p instanceof TentativeBoundary && p.inner instanceof CharacterPrediction; + /** * Prediction for a single alphanumeric character. */ class CharacterPrediction implements IPrediction { public readonly affectsStyle = true; - protected appliedAt?: { + public appliedAt?: { pos: ICoordinate; oldAttributes: string; oldChar: string; @@ -407,7 +413,7 @@ class BackspacePrediction implements IPrediction { pos: ICoordinate; oldAttributes: string; oldChar: string; - eol: boolean; + isLastChar: boolean; }; constructor(private readonly terminal: Terminal) { } @@ -415,13 +421,13 @@ class BackspacePrediction implements IPrediction { public apply(_: IBuffer, cursor: Cursor) { // at eol if everything to the right is whitespace (zsh will emit a "clear line" code in this case) // todo: can be optimized if `getTrimmedLength` is exposed from xterm - const eol = !cursor.getLine()?.translateToString(undefined, cursor.x).trim(); + const isLastChar = !cursor.getLine()?.translateToString(undefined, cursor.x).trim(); const pos = cursor.coordinate; const move = cursor.shift(-1); const cell = cursor.getCell(); this.appliedAt = cell - ? { eol, pos, oldAttributes: attributesToSeq(cell), oldChar: cell.getChars() } - : { eol, pos, oldAttributes: '', oldChar: '' }; + ? { isLastChar, pos, oldAttributes: attributesToSeq(cell), oldChar: cell.getChars() } + : { isLastChar, pos, oldAttributes: '', oldChar: '' }; return move + DELETE_CHAR; } @@ -444,7 +450,7 @@ class BackspacePrediction implements IPrediction { } public matches(input: StringReader) { - if (this.appliedAt?.eol) { + if (this.appliedAt?.isLastChar) { const r1 = input.eatGradually(`\b${CSI}K`); if (r1 !== MatchResult.Failure) { return r1; @@ -637,6 +643,20 @@ export class PredictionStats extends Disposable { }; } + /** + * Gets the maximum observed latency. + */ + public get maxLatency() { + let max = -Infinity; + for (const [latency, correct] of this.stats) { + if (correct) { + max = Math.max(latency, max); + } + } + + return max; + } + constructor(timeline: PredictionTimeline) { super(); this._register(timeline.onPredictionAdded(p => this.addedAtTime.set(p, Date.now()))); @@ -690,10 +710,18 @@ export class PredictionTimeline { private readonly succeededEmitter = new Emitter(); public readonly onPredictionSucceeded = this.succeededEmitter.event; + private get currentGenerationPredictions() { + return this.expected.filter(({ gen }) => gen === this.expected[0].gen).map(({ p }) => p); + } + public get isShowingPredictions() { return this.showPredictions; } + public get length() { + return this.expected.length; + } + constructor(public readonly terminal: Terminal, private readonly style: TypeAheadStyle) { } public setShowPredictions(show: boolean) { @@ -709,7 +737,7 @@ export class PredictionTimeline { return; } - const toApply = this.expected.filter(({ gen }) => gen === this.expected[0].gen).map(({ p }) => p); + const toApply = this.currentGenerationPredictions; if (show) { this.cursor = undefined; this.style.expectIncomingStyle(toApply.reduce((count, p) => p.affectsStyle ? count + 1 : count, 0)); @@ -719,6 +747,19 @@ export class PredictionTimeline { } } + /** + * Undoes any predictions written and resets expectations. + */ + public undoAllPredictions() { + const buffer = this.getActiveBuffer(); + if (this.showPredictions && buffer) { + this.terminal.write(this.currentGenerationPredictions.reverse() + .map(p => p.rollback(this.getCursor(buffer))).join('')); + } + + this.expected = []; + } + /** * Should be called when input is incoming to the temrinal. */ @@ -754,7 +795,7 @@ export class PredictionTimeline { ReadLoop: while (this.expected.length && reader.remaining > 0) { emitPredictionOmitted(); - const prediction = this.expected[0].p; + const { p: prediction, gen } = this.expected[0]; const cursor = this.getCursor(buffer); let beforeTestReaderIndex = reader.index; switch (prediction.matches(reader)) { @@ -762,7 +803,13 @@ export class PredictionTimeline { // if the input character matches what the next prediction expected, undo // the prediction and write the real character out. const eaten = input.slice(beforeTestReaderIndex, reader.index); - output += prediction.rollForwards?.(cursor, eaten); + if (gen === startingGen) { + output += prediction.rollForwards?.(cursor, eaten); + } else { + prediction.apply(buffer, this.getCursor(buffer)); // move cursor for additional apply + output += eaten; + } + this.succeededEmitter.fire(prediction); this.expected.shift(); break; @@ -860,21 +907,30 @@ export class PredictionTimeline { * pty output/ */ public addBoundary(): void; - public addBoundary(buffer: IBuffer, prediction: IPrediction): void; + public addBoundary(buffer: IBuffer, prediction: IPrediction): boolean; public addBoundary(buffer?: IBuffer, prediction?: IPrediction) { + let applied = false; if (buffer && prediction) { - this.addPrediction(buffer, prediction); + applied = this.addPrediction(buffer, prediction); } this.currentGen++; + return applied; } /** * Peeks the last prediction written. */ - public peekEnd() { + public peekEnd(): IPrediction | undefined { return this.expected[this.expected.length - 1]?.p; } + /** + * Peeks the first pending prediction. + */ + public peekStart(): IPrediction | undefined { + return this.expected[0]?.p; + } + public getCursor(buffer: IBuffer) { if (!this.cursor) { if (this.showPredictions) { @@ -895,29 +951,37 @@ export class PredictionTimeline { return buffer.type === 'normal' ? buffer : undefined; } } + +/** + * Gets the escape sequence args to restore state/appearence in the cell. + */ +const attributesToArgs = (cell: XTermAttributes) => { + if (cell.isAttributeDefault()) { return [0]; } + + const args = []; + if (cell.isBold()) { args.push(1); } + if (cell.isDim()) { args.push(2); } + if (cell.isItalic()) { args.push(3); } + if (cell.isUnderline()) { args.push(4); } + if (cell.isBlink()) { args.push(5); } + if (cell.isInverse()) { args.push(7); } + if (cell.isInvisible()) { args.push(8); } + + if (cell.isFgRGB()) { args.push(38, 2, cell.getFgColor() >>> 24, (cell.getFgColor() >>> 16) & 0xFF, cell.getFgColor() & 0xFF); } + if (cell.isFgPalette()) { args.push(38, 5, cell.getFgColor()); } + if (cell.isFgDefault()) { args.push(39); } + + if (cell.isBgRGB()) { args.push(48, 2, cell.getBgColor() >>> 24, (cell.getBgColor() >>> 16) & 0xFF, cell.getBgColor() & 0xFF); } + if (cell.isBgPalette()) { args.push(48, 5, cell.getBgColor()); } + if (cell.isBgDefault()) { args.push(49); } + + return args; +}; + /** * Gets the escape sequence to restore state/appearence in the cell. */ -const attributesToSeq = (cell: XTermAttributes) => cell.isAttributeDefault() - ? `${CSI}0m` - : [ - cell.isBold() && `${CSI}1m`, - cell.isDim() && `${CSI}2m`, - cell.isItalic() && `${CSI}3m`, - cell.isUnderline() && `${CSI}4m`, - cell.isBlink() && `${CSI}5m`, - cell.isInverse() && `${CSI}7m`, - cell.isInvisible() && `${CSI}8m`, - - cell.isFgRGB() && `${CSI}38;2;${cell.getFgColor() >>> 24};${(cell.getFgColor() >>> 16) & 0xFF};${cell.getFgColor() & 0xFF}m`, - cell.isFgPalette() && `${CSI}38;5;${cell.getFgColor()}m`, - cell.isFgDefault() && `${CSI}39m`, - - cell.isBgRGB() && `${CSI}48;2;${cell.getBgColor() >>> 24};${(cell.getBgColor() >>> 16) & 0xFF};${cell.getBgColor() & 0xFF}m`, - cell.isBgPalette() && `${CSI}48;5;${cell.getBgColor()}m`, - cell.isBgDefault() && `${CSI}49m`, - ].filter(seq => !!seq).join(''); - +const attributesToSeq = (cell: XTermAttributes) => `${CSI}${attributesToArgs(cell).join(';')}m`; const arrayHasPrefixAt = (a: ReadonlyArray, ai: number, b: ReadonlyArray) => { if (a.length - ai > b.length) { @@ -968,7 +1032,7 @@ const getColorWidth = (params: (number | number[])[], pos: number) => { return advance; }; -class TypeAheadStyle { +class TypeAheadStyle implements IDisposable { private static compileArgs(args: ReadonlyArray) { return `${CSI}${args.join(';')}m`; } @@ -984,8 +1048,9 @@ class TypeAheadStyle { public apply!: string; public undo!: string; + private csiHandler?: IDisposable; - constructor(value: ITerminalConfiguration['localEchoStyle']) { + constructor(value: ITerminalConfiguration['localEchoStyle'], private readonly terminal: Terminal) { this.onUpdate(value); } @@ -998,9 +1063,38 @@ class TypeAheadStyle { } /** - * Should be called when an attribut eupdate happens in the terminal. + * Starts tracking for CSI changes in the terminal. */ - public onDidWriteSGR(args: (number | number[])[]) { + public startTracking() { + this.expectedIncomingStyles = 0; + this.onDidWriteSGR(attributesToArgs(core(this.terminal)._inputHandler._curAttrData)); + this.csiHandler = this.terminal.parser.registerCsiHandler({ final: 'm' }, args => { + this.onDidWriteSGR(args); + return false; + }); + } + + /** + * Stops tracking terminal CSI changes. + */ + @debounce(2000) + public debounceStopTracking() { + this.stopTracking(); + } + + /** + * @inheritdoc + */ + public dispose() { + this.stopTracking(); + } + + private stopTracking() { + this.csiHandler?.dispose(); + this.csiHandler = undefined; + } + + private onDidWriteSGR(args: (number | number[])[]) { const originalUndo = this.undoArgs; for (let i = 0; i < args.length;) { const px = args[i]; @@ -1089,41 +1183,70 @@ class TypeAheadStyle { } } +const compileExcludeRegexp = (programs = DEFAULT_LOCAL_ECHO_EXCLUDE) => + new RegExp(`\\b(${programs.map(escapeRegExpCharacters).join('|')})\\b`, 'i'); + +export const enum CharPredictState { + /** No characters typed on this line yet */ + Unknown, + /** Has a pending character prediction */ + HasPendingChar, + /** Character validated on this line */ + Validated, +} + export class TypeAheadAddon extends Disposable implements ITerminalAddon { - private typeaheadStyle = new TypeAheadStyle(this.config.config.localEchoStyle); + private typeaheadStyle?: TypeAheadStyle; private typeaheadThreshold = this.config.config.localEchoLatencyThreshold; - protected lastRow?: { y: number; startingX: number }; - private timeline?: PredictionTimeline; + private excludeProgramRe = compileExcludeRegexp(this.config.config.localEchoExcludePrograms); + protected lastRow?: { y: number; startingX: number; charState: CharPredictState }; + protected timeline?: PredictionTimeline; + private terminalTitle = ''; public stats?: PredictionStats; + /** + * Debounce that clears predictions after a timeout if the PTY doesn't apply them. + */ + private clearPredictionDebounce?: IDisposable; + constructor( private readonly processManager: ITerminalProcessManager, private readonly config: TerminalConfigHelper, @ITelemetryService private readonly telemetryService: ITelemetryService, ) { super(); + this._register(toDisposable(() => this.clearPredictionDebounce?.dispose())); } public activate(terminal: Terminal): void { + const style = this.typeaheadStyle = this._register(new TypeAheadStyle(this.config.config.localEchoStyle, terminal)); const timeline = this.timeline = new PredictionTimeline(terminal, this.typeaheadStyle); const stats = this.stats = this._register(new PredictionStats(this.timeline)); timeline.setShowPredictions(this.typeaheadThreshold === 0); - this._register(terminal.parser.registerCsiHandler({ final: 'm' }, args => { - this.typeaheadStyle.onDidWriteSGR(args); - return false; - })); this._register(terminal.onData(e => this.onUserData(e))); + this._register(terminal.onTitleChange(title => { + this.terminalTitle = title; + this.reevaluatePredictorState(stats, timeline); + })); this._register(terminal.onResize(() => { timeline.setShowPredictions(false); timeline.clearCursor(); this.reevaluatePredictorState(stats, timeline); })); this._register(this.config.onConfigChanged(() => { - this.typeaheadStyle.onUpdate(this.config.config.localEchoStyle); + style.onUpdate(this.config.config.localEchoStyle); this.typeaheadThreshold = this.config.config.localEchoLatencyThreshold; + this.excludeProgramRe = compileExcludeRegexp(this.config.config.localEchoExcludePrograms); this.reevaluatePredictorState(stats, timeline); })); + this._register(this.timeline.onPredictionSucceeded(p => { + if (this.lastRow?.charState === CharPredictState.HasPendingChar && isTenativeCharacterPrediction(p) && p.inner.appliedAt) { + if (p.inner.appliedAt.pos.y + p.inner.appliedAt.pos.baseY === this.lastRow.y) { + this.lastRow.charState = CharPredictState.Validated; + } + } + })); this._register(this.processManager.onBeforeProcessData(e => this.onBeforeProcessData(e))); let nextStatsSend: any; @@ -1135,10 +1258,36 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon { }, statsSendTelemetryEvery); } + if (timeline.length === 0) { + style.debounceStopTracking(); + } + this.reevaluatePredictorState(stats, timeline); })); } + private deferClearingPredictions() { + if (!this.stats || !this.timeline) { + return; + } + + this.clearPredictionDebounce?.dispose(); + if (this.timeline.length === 0 || this.timeline.peekStart()?.clearAfterTimeout === false) { + this.clearPredictionDebounce = undefined; + return; + } + + this.clearPredictionDebounce = disposableTimeout( + () => { + this.timeline?.undoAllPredictions(); + if (this.lastRow?.charState === CharPredictState.HasPendingChar) { + this.lastRow.charState = CharPredictState.Unknown; + } + }, + Math.max(500, this.stats.maxLatency * 3 / 2), + ); + } + /** * Note on debounce: * @@ -1147,8 +1296,14 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon { * terminal cursor is not updated, causes issues. */ @debounce(100) - private reevaluatePredictorState(stats: PredictionStats, timeline: PredictionTimeline) { - if (this.typeaheadThreshold < 0) { + protected reevaluatePredictorState(stats: PredictionStats, timeline: PredictionTimeline) { + this.reevaluatePredictorStateNow(stats, timeline); + } + + protected reevaluatePredictorStateNow(stats: PredictionStats, timeline: PredictionTimeline) { + if (this.excludeProgramRe.test(this.terminalTitle)) { + timeline.setShowPredictions(false); + } else if (this.typeaheadThreshold < 0) { timeline.setShowPredictions(false); } else if (this.typeaheadThreshold === 0) { timeline.setShowPredictions(true); @@ -1201,7 +1356,7 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon { // the user gave input, and mark all additions before that as tentative. const actualY = buffer.baseY + buffer.cursorY; if (actualY !== this.lastRow?.y) { - this.lastRow = { y: actualY, startingX: buffer.cursorX }; + this.lastRow = { y: actualY, startingX: buffer.cursorX, charState: CharPredictState.Unknown }; } else { this.lastRow.startingX = Math.min(this.lastRow.startingX, buffer.cursorX); } @@ -1232,7 +1387,16 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon { if (reader.eatCharCode(32, 126)) { // alphanum const char = data[reader.index - 1]; - if (this.timeline.addPrediction(buffer, new CharacterPrediction(this.typeaheadStyle, char)) && this.timeline.getCursor(buffer).x === terminal.cols) { + const prediction = new CharacterPrediction(this.typeaheadStyle!, char); + let applied: boolean; + if (this.lastRow.charState === CharPredictState.Unknown) { + applied = this.timeline.addBoundary(buffer, new TentativeBoundary(prediction)); + this.lastRow.charState = CharPredictState.HasPendingChar; + } else { + applied = this.timeline.addPrediction(buffer, prediction); + } + + if (applied && this.timeline.getCursor(buffer).x >= terminal.cols) { this.timeline.addBoundary(buffer, new TentativeBoundary(new LinewrapPrediction())); } continue; @@ -1269,6 +1433,11 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon { this.timeline.addBoundary(buffer, new HardBoundary()); break; } + + if (this.timeline.length === 1) { + this.deferClearingPredictions(); + this.typeaheadStyle!.startTracking(); + } } private onBeforeProcessData(event: IBeforeProcessDataEvent): void { @@ -1280,12 +1449,6 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon { event.data = this.timeline.beforeServerInput(event.data); // console.log('emitted data:', JSON.stringify(event.data)); - // If there's something that looks like a password prompt, omit giving - // input. This is approximate since there's no TTY "password here" code, - // but should be enough to cover common cases like sudo - if (PASSWORD_INPUT_RE.test(event.data)) { - const terminal = this.timeline.terminal; - this.timeline.addBoundary(terminal.buffer.active, new HardBoundary()); - } + this.deferClearingPredictions(); } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index 74fc5f854..2d2f0a1b2 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -19,7 +19,7 @@ import { URI } from 'vs/base/common/uri'; import { TERMINAL_BACKGROUND_COLOR, TERMINAL_BORDER_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { DataTransfers } from 'vs/base/browser/dnd'; import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; -import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalService, TerminalConnectionState } from 'vs/workbench/contrib/terminal/browser/terminal'; import { BrowserFeatures } from 'vs/base/browser/canIUse'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -67,10 +67,6 @@ export class TerminalViewPane extends ViewPane { } this._onDidChangeViewWelcomeState.fire(); }); - - this._register(this.onDidBlur(() => { - this._terminalService.getActiveTab()?.setWillFocus(false); - })); } protected renderBody(container: HTMLElement): void { @@ -133,6 +129,9 @@ export class TerminalViewPane extends ViewPane { } } else { this._terminalService.getActiveTab()?.setVisible(false); + this._terminalService.terminalInstances.forEach(instance => { + instance.notifyFindWidgetFocusChanged(false); + }); } })); @@ -217,7 +216,25 @@ export class TerminalViewPane extends ViewPane { } public focus() { - this._terminalService.getActiveTab()?.setWillFocus(true); + if (this._terminalService.connectionState === TerminalConnectionState.Connecting) { + // If the terminal is waiting to reconnect to remote terminals, then there is no TerminalInstance yet that can + // be focused. So wait for connection to finish, then focus. + const activeElement = document.activeElement; + this._register(this._terminalService.onDidChangeConnectionState(() => { + // Only focus the terminal if the activeElement has not changed since focus() was called + // TODO hack + if (document.activeElement === activeElement) { + this._focus(); + } + })); + + return; + } + this._focus(); + } + + private _focus() { + this._terminalService.getActiveInstance()?.focusWhenReady(); } public focusFindWidget() { diff --git a/src/vs/workbench/contrib/terminal/browser/widgets/environmentVariableInfoWidget.ts b/src/vs/workbench/contrib/terminal/browser/widgets/environmentVariableInfoWidget.ts index 3bd498445..913e4de79 100644 --- a/src/vs/workbench/contrib/terminal/browser/widgets/environmentVariableInfoWidget.ts +++ b/src/vs/workbench/contrib/terminal/browser/widgets/environmentVariableInfoWidget.ts @@ -12,6 +12,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import * as dom from 'vs/base/browser/dom'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IHoverService, IHoverOptions } from 'vs/workbench/services/hover/browser/hover'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; export class EnvironmentVariableInfoWidget extends Widget implements ITerminalWidget { readonly id = 'env-var-info'; @@ -34,7 +35,7 @@ export class EnvironmentVariableInfoWidget extends Widget implements ITerminalWi attach(container: HTMLElement): void { this._container = container; this._domNode = document.createElement('div'); - this._domNode.classList.add('terminal-env-var-info', 'codicon', `codicon-${this._info.getIcon()}`); + this._domNode.classList.add('terminal-env-var-info', ...ThemeIcon.asClassNameArray(this._info.getIcon())); if (this.requiresAction) { this._domNode.classList.add('requires-action'); } diff --git a/src/vs/workbench/contrib/terminal/common/environmentVariable.ts b/src/vs/workbench/contrib/terminal/common/environmentVariable.ts index bb32eafc8..196f18495 100644 --- a/src/vs/workbench/contrib/terminal/common/environmentVariable.ts +++ b/src/vs/workbench/contrib/terminal/common/environmentVariable.ts @@ -6,6 +6,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; import { IProcessEnvironment } from 'vs/base/common/platform'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; export const IEnvironmentVariableService = createDecorator('environmentVariableService'); @@ -98,6 +99,6 @@ export type ISerializableEnvironmentVariableCollection = [string, IEnvironmentVa export interface IEnvironmentVariableInfo { readonly requiresAction: boolean; getInfo(): string; - getIcon(): string; + getIcon(): ThemeIcon; getActions?(): { label: string, iconClass?: string, run: () => void, commandId: string }[]; } diff --git a/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts b/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts index 8373296bf..e1d984407 100644 --- a/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts +++ b/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts @@ -6,7 +6,7 @@ import { IEnvironmentVariableService, IMergedEnvironmentVariableCollection, ISerializableEnvironmentVariableCollection, IEnvironmentVariableCollectionWithPersistence } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { Event, Emitter } from 'vs/base/common/event'; import { debounce, throttle } from 'vs/base/common/decorators'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection'; import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared'; @@ -85,7 +85,7 @@ export class EnvironmentVariableService implements IEnvironmentVariableService { } }); const stringifiedJson = JSON.stringify(collectionsJson); - this._storageService.store(ENVIRONMENT_VARIABLE_COLLECTIONS_KEY, stringifiedJson, StorageScope.WORKSPACE); + this._storageService.store(ENVIRONMENT_VARIABLE_COLLECTIONS_KEY, stringifiedJson, StorageScope.WORKSPACE, StorageTarget.MACHINE); } @debounce(1000) diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 3b32d9e9f..ef7ff2e50 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -50,7 +50,7 @@ export const KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED = new RawContextKey; localEchoStyle: 'bold' | 'dim' | 'italic' | 'underlined' | 'inverted' | string; serverSpawn: boolean; enablePersistentSessions: boolean; } +export const DEFAULT_LOCAL_ECHO_EXCLUDE: ReadonlyArray = ['vim', 'vi', 'nano', 'tmux']; + export interface ITerminalConfigHelper { config: ITerminalConfiguration; @@ -486,6 +490,7 @@ export const enum TERMINAL_COMMAND_ID { TOGGLE = 'workbench.action.terminal.toggleTerminal', KILL = 'workbench.action.terminal.kill', QUICK_KILL = 'workbench.action.terminal.quickKill', + CONFIGURE_TERMINAL_SETTINGS = 'workbench.action.terminal.openSettings', COPY_SELECTION = 'workbench.action.terminal.copySelection', SELECT_ALL = 'workbench.action.terminal.selectAll', DELETE_WORD_LEFT = 'workbench.action.terminal.deleteWordLeft', diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index eba3dd720..ed21243bf 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -6,7 +6,7 @@ import { IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; import { localize } from 'vs/nls'; import { EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; -import { DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, DEFAULT_COMMANDS_TO_SKIP_SHELL, SUGGESTIONS_FONT_WEIGHT, MINIMUM_FONT_WEIGHT, MAXIMUM_FONT_WEIGHT } from 'vs/workbench/contrib/terminal/common/terminal'; +import { DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, DEFAULT_COMMANDS_TO_SKIP_SHELL, SUGGESTIONS_FONT_WEIGHT, MINIMUM_FONT_WEIGHT, MAXIMUM_FONT_WEIGHT, DEFAULT_LOCAL_ECHO_EXCLUDE } from 'vs/workbench/contrib/terminal/common/terminal'; import { isMacintosh, isWindows, Platform } from 'vs/base/common/platform'; export const terminalConfiguration: IConfigurationNode = { @@ -15,6 +15,11 @@ export const terminalConfiguration: IConfigurationNode = { title: localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"), type: 'object', properties: { + 'terminal.integrated.sendKeybindingsToShell': { + markdownDescription: localize('terminal.integrated.sendKeybindingsToShell', "Dispatches most keybindings to the terminal instead of the workbench, overriding `#terminal.integrated.commandsToSkipShell#`, which can be used alternatively for fine tuning."), + type: 'boolean', + default: false + }, 'terminal.integrated.automationShell.linux': { markdownDescription: localize({ key: 'terminal.integrated.automationShell.linux', @@ -211,7 +216,7 @@ export const terminalConfiguration: IConfigurationNode = { localize('terminal.integrated.rendererType.auto', "Let VS Code guess which renderer to use."), localize('terminal.integrated.rendererType.canvas', "Use the standard GPU/canvas-based renderer."), localize('terminal.integrated.rendererType.dom', "Use the fallback DOM-based renderer."), - localize('terminal.integrated.rendererType.experimentalWebgl', "Use the experimental webgl-based renderer. Note that this has some [known issues](https://github.com/xtermjs/xterm.js/issues?q=is%3Aopen+is%3Aissue+label%3Aarea%2Faddon%2Fwebgl) and this will only be enabled for new terminals (not hot swappable like the other renderers).") + localize('terminal.integrated.rendererType.experimentalWebgl', "Use the experimental webgl-based renderer. Note that this has some [known issues](https://github.com/xtermjs/xterm.js/issues?q=is%3Aopen+is%3Aissue+label%3Aarea%2Faddon%2Fwebgl).") ], default: 'auto', description: localize('terminal.integrated.rendererType', "Controls how the terminal is rendered.") @@ -358,6 +363,15 @@ export const terminalConfiguration: IConfigurationNode = { minimum: -1, default: 30, }, + 'terminal.integrated.localEchoExcludePrograms': { + description: localize('terminal.integrated.localEchoExcludePrograms', "Experimental: local echo will be disabled when any of these program names are found in the terminal title."), + type: 'array', + items: { + type: 'string', + uniqueItems: true + }, + default: DEFAULT_LOCAL_ECHO_EXCLUDE, + }, 'terminal.integrated.localEchoStyle': { description: localize('terminal.integrated.localEchoStyle', "Experimental: terminal style of locally echoed text; either a font style or an RGB color."), default: 'dim', diff --git a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts index 069f76a47..92dcbb35b 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts @@ -93,7 +93,8 @@ export function shouldSetLangEnvVariable(env: platform.IProcessEnvironment, dete return true; } if (detectLocale === 'auto') { - return !env['LANG'] || (env['LANG'].search(/\.UTF\-8$/) === -1 && env['LANG'].search(/\.utf8$/) === -1); + const lang = env['LANG']; + return !lang || (lang.search(/\.UTF\-8$/) === -1 && lang.search(/\.utf8$/) === -1 && lang.search(/\.euc.+/) === -1); } return false; // 'off' } diff --git a/src/vs/workbench/contrib/terminal/node/terminal.ts b/src/vs/workbench/contrib/terminal/node/terminal.ts index 8dc3b6c15..4a006c386 100644 --- a/src/vs/workbench/contrib/terminal/node/terminal.ts +++ b/src/vs/workbench/contrib/terminal/node/terminal.ts @@ -152,7 +152,7 @@ async function validateShellPaths(label: string, potentialPaths: string[]): Prom } try { const result = await stat(normalize(current)); - if (result.isFile || result.isSymbolicLink) { + if (result.isFile() || result.isSymbolicLink()) { return { label, path: current diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalConfigHelper.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalConfigHelper.test.ts index eed4382af..d8263b558 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalConfigHelper.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalConfigHelper.test.ts @@ -8,7 +8,6 @@ import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/term import { EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal'; -import { StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; suite('Workbench - TerminalConfigHelper', () => { let fixture: HTMLElement; @@ -30,7 +29,7 @@ suite('Workbench - TerminalConfigHelper', () => { const configurationService = new TestConfigurationService(); configurationService.setUserConfiguration('editor', { fontFamily: 'foo' }); configurationService.setUserConfiguration('terminal', { integrated: { fontFamily: null } }); - const configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); + const configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!); configHelper.setLinuxDistro(LinuxDistro.Fedora); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontFamily, '\'DejaVu Sans Mono\', monospace', 'Fedora should have its font overridden when terminal.integrated.fontFamily not set'); @@ -40,7 +39,7 @@ suite('Workbench - TerminalConfigHelper', () => { const configurationService = new TestConfigurationService(); configurationService.setUserConfiguration('editor', { fontFamily: 'foo' }); configurationService.setUserConfiguration('terminal', { integrated: { fontFamily: null } }); - const configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); + const configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!); configHelper.setLinuxDistro(LinuxDistro.Ubuntu); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontFamily, '\'Ubuntu Mono\', monospace', 'Ubuntu should have its font overridden when terminal.integrated.fontFamily not set'); @@ -50,7 +49,7 @@ suite('Workbench - TerminalConfigHelper', () => { const configurationService = new TestConfigurationService(); configurationService.setUserConfiguration('editor', { fontFamily: 'foo' }); configurationService.setUserConfiguration('terminal', { integrated: { fontFamily: null } }); - const configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); + const configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontFamily, 'foo', 'editor.fontFamily should be the fallback when terminal.integrated.fontFamily not set'); }); @@ -68,7 +67,7 @@ suite('Workbench - TerminalConfigHelper', () => { fontSize: 10 } }); - let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); + let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontSize, 10, 'terminal.integrated.fontSize should be selected over editor.fontSize'); @@ -81,12 +80,12 @@ suite('Workbench - TerminalConfigHelper', () => { fontSize: 0 } }); - configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); + configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!); configHelper.setLinuxDistro(LinuxDistro.Ubuntu); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontSize, 8, 'The minimum terminal font size (with adjustment) should be used when terminal.integrated.fontSize less than it'); - configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); + configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontSize, 6, 'The minimum terminal font size should be used when terminal.integrated.fontSize less than it'); @@ -99,7 +98,7 @@ suite('Workbench - TerminalConfigHelper', () => { fontSize: 1500 } }); - configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); + configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontSize, 25, 'The maximum terminal font size should be used when terminal.integrated.fontSize more than it'); @@ -112,12 +111,12 @@ suite('Workbench - TerminalConfigHelper', () => { fontSize: null } }); - configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); + configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!); configHelper.setLinuxDistro(LinuxDistro.Ubuntu); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontSize, EDITOR_FONT_DEFAULTS.fontSize + 2, 'The default editor font size (with adjustment) should be used when terminal.integrated.fontSize is not set'); - configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); + configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontSize, EDITOR_FONT_DEFAULTS.fontSize, 'The default editor font size should be used when terminal.integrated.fontSize is not set'); }); @@ -135,7 +134,7 @@ suite('Workbench - TerminalConfigHelper', () => { lineHeight: 2 } }); - let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); + let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().lineHeight, 2, 'terminal.integrated.lineHeight should be selected over editor.lineHeight'); @@ -149,7 +148,7 @@ suite('Workbench - TerminalConfigHelper', () => { lineHeight: 0 } }); - configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); + configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().lineHeight, 1, 'editor.lineHeight should be 1 when terminal.integrated.lineHeight not set'); }); @@ -162,7 +161,7 @@ suite('Workbench - TerminalConfigHelper', () => { } }); - let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); + let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!); configHelper.panelContainer = fixture; assert.equal(configHelper.configFontIsMonospace(), true, 'monospace is monospaced'); }); @@ -174,7 +173,7 @@ suite('Workbench - TerminalConfigHelper', () => { fontFamily: 'sans-serif' } }); - let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); + let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!); configHelper.panelContainer = fixture; assert.equal(configHelper.configFontIsMonospace(), false, 'sans-serif is not monospaced'); }); @@ -186,7 +185,7 @@ suite('Workbench - TerminalConfigHelper', () => { fontFamily: 'serif' } }); - let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); + let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!); configHelper.panelContainer = fixture; assert.equal(configHelper.configFontIsMonospace(), false, 'serif is not monospaced'); }); @@ -202,7 +201,7 @@ suite('Workbench - TerminalConfigHelper', () => { } }); - let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); + let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!); configHelper.panelContainer = fixture; assert.equal(configHelper.configFontIsMonospace(), true, 'monospace is monospaced'); }); @@ -218,7 +217,7 @@ suite('Workbench - TerminalConfigHelper', () => { } }); - let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); + let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!); configHelper.panelContainer = fixture; assert.equal(configHelper.configFontIsMonospace(), false, 'sans-serif is not monospaced'); }); @@ -234,7 +233,7 @@ suite('Workbench - TerminalConfigHelper', () => { } }); - let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); + let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!); configHelper.panelContainer = fixture; assert.equal(configHelper.configFontIsMonospace(), false, 'serif is not monospaced'); }); diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalTypeahead.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalTypeahead.test.ts index f50415e5e..0814572f0 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalTypeahead.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalTypeahead.test.ts @@ -7,8 +7,8 @@ import * as assert from 'assert'; import { Terminal } from 'xterm'; import { SinonStub, stub, useFakeTimers } from 'sinon'; import { Emitter } from 'vs/base/common/event'; -import { IPrediction, PredictionStats, TypeAheadAddon } from 'vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon'; -import { IBeforeProcessDataEvent, ITerminalConfiguration, ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; +import { CharPredictState, IPrediction, PredictionStats, TypeAheadAddon } from 'vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon'; +import { DEFAULT_LOCAL_ECHO_EXCLUDE, IBeforeProcessDataEvent, ITerminalConfiguration, ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -92,7 +92,8 @@ suite('Workbench - Terminal Typeahead', () => { setup(() => { config = upcastPartial({ localEchoStyle: 'italic', - localEchoLatencyThreshold: 0 + localEchoLatencyThreshold: 0, + localEchoExcludePrograms: DEFAULT_LOCAL_ECHO_EXCLUDE, }); publicLog = stub(); addon = new TestTypeAheadAddon( @@ -100,6 +101,7 @@ suite('Workbench - Terminal Typeahead', () => { upcastPartial({ config, onConfigChanged: onConfigChanged.event }), upcastPartial({ publicLog }) ); + addon.unlockMakingPredictions(); }); teardown(() => { @@ -149,8 +151,7 @@ suite('Workbench - Terminal Typeahead', () => { `${CSI}?25l`, // hide cursor `${CSI}2;7H`, // move cursor cursor `${CSI}X`, // delete character - `${CSI}1m`, // reset style - `${CSI}38;5;1m`, // reset style + `${CSI}1;38;5;1m`, // reset style 'q', // new character `${CSI}?25h`, // show cursor ].join('')); @@ -244,29 +245,56 @@ suite('Workbench - Terminal Typeahead', () => { t.expectWritten(`${CSI}2;6H${CSI}X`); }); - test('avoids predicting password input', () => { + test('waits for first valid prediction on a line', () => { + const t = createMockTerminal({ lines: ['hello|'] }); + addon.lockMakingPredictions(); + addon.activate(t.terminal); + + t.onData('o'); + t.expectWritten(''); + expectProcessed('o', 'o'); + + t.onData('o'); + t.expectWritten(`${CSI}3mo${CSI}23m`); + }); + + test('disables on title change', () => { const t = createMockTerminal({ lines: ['hello|'] }); addon.activate(t.terminal); - const tcases = ['Your password:', 'Password here:', 'PAT:', 'Access token:']; - for (const tcase of tcases) { - expectProcessed(tcase, tcase); + addon.reevaluateNow(); + assert.strictEqual(addon.isShowing, true, 'expected to show initially'); - t.onData('mellon\r\n'); - t.expectWritten(''); - expectProcessed('\r\n', '\r\n'); + t.onTitleChange.fire('foo - VIM.exe'); + addon.reevaluateNow(); + assert.strictEqual(addon.isShowing, false, 'expected to hide when vim is open'); - t.onData('o'); // back to normal mode - t.expectWritten(`${CSI}3mo${CSI}23m`); - onBeforeProcessData.fire({ data: 'o' }); - } + t.onTitleChange.fire('foo - git.exe'); + addon.reevaluateNow(); + assert.strictEqual(addon.isShowing, true, 'expected to show again after vim closed'); }); }); }); class TestTypeAheadAddon extends TypeAheadAddon { + public unlockMakingPredictions() { + this.lastRow = { y: 1, startingX: 100, charState: CharPredictState.Validated }; + } + + public lockMakingPredictions() { + this.lastRow = undefined; + } + public unlockLeftNavigating() { - this.lastRow = { y: 1, startingX: 1 }; + this.lastRow = { y: 1, startingX: 1, charState: CharPredictState.Validated }; + } + + public reevaluateNow() { + this.reevaluatePredictorStateNow(this.stats!, this.timeline!); + } + + public get isShowing() { + return !!this.timeline?.isShowingPredictions; } } @@ -293,6 +321,7 @@ function createMockTerminal({ lines, cursorAttrs }: { }) { const written: string[] = []; const cursor = { y: 1, x: 1 }; + const onTitleChange = new Emitter(); const onData = new Emitter(); const csiEmitter = new Emitter(); @@ -316,11 +345,13 @@ function createMockTerminal({ lines, cursorAttrs }: { clearWritten: () => written.splice(0, written.length), onData: (s: string) => onData.fire(s), csiEmitter, + onTitleChange, terminal: { cols: 80, rows: 5, onResize: new Emitter().event, onData: onData.event, + onTitleChange: onTitleChange.event, parser: { registerCsiHandler(_: unknown, callback: () => void) { csiEmitter.event(callback); diff --git a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts new file mode 100644 index 000000000..4e541bc83 --- /dev/null +++ b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ITestService } from 'vs/workbench/contrib/testing/common/testService'; +import { TestService } from 'vs/workbench/contrib/testing/common/testServiceImpl'; + +registerSingleton(ITestService, TestService); diff --git a/src/vs/workbench/contrib/testing/common/testCollection.ts b/src/vs/workbench/contrib/testing/common/testCollection.ts new file mode 100644 index 000000000..ab76dab73 --- /dev/null +++ b/src/vs/workbench/contrib/testing/common/testCollection.ts @@ -0,0 +1,229 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { URI } from 'vs/base/common/uri'; +import { Location as ModeLocation } from 'vs/editor/common/modes'; +import { ExtHostTestingResource } from 'vs/workbench/api/common/extHost.protocol'; +import { TestMessageSeverity, TestRunState } from 'vs/workbench/api/common/extHostTypes'; + +/** + * Request to them main thread to run a set of tests. + */ +export interface RunTestsRequest { + tests: { testId: string; providerId: string }[]; + debug: boolean; +} + +/** + * Request from the main thread to run tests for a single provider. + */ +export interface RunTestForProviderRequest { + providerId: string; + ids: string[]; + debug: boolean; +} + +/** + * Response to a {@link RunTestsRequest} + */ +export interface RunTestsResult { + // todo +} + +export const EMPTY_TEST_RESULT: RunTestsResult = {}; + +export const collectTestResults = (results: ReadonlyArray) => { + return results[0] || {}; // todo +}; + +export interface ITestMessage { + message: string | IMarkdownString; + severity: TestMessageSeverity | undefined; + expectedOutput: string | undefined; + actualOutput: string | undefined; + location: ModeLocation | undefined; +} + +export interface ITestState { + runState: TestRunState; + duration: number | undefined; + messages: ITestMessage[]; +} + +/** + * The TestItem from .d.ts, as a plain object without children. + */ +export interface ITestItem { + label: string; + children?: never; + location: ModeLocation | undefined; + description: string | undefined; + runnable: boolean | undefined; + debuggable: boolean | undefined; + state: ITestState; +} + +/** + * TestItem-like shape, butm with an ID and children as strings. + */ +export interface InternalTestItem { + id: string; + providerId: string; + parent: string | null; + item: ITestItem; +} + +export const enum TestDiffOpType { + Add, + Update, + Remove, +} + +export type TestsDiffOp = + | [op: TestDiffOpType.Add, item: InternalTestItem] + | [op: TestDiffOpType.Update, item: InternalTestItem] + | [op: TestDiffOpType.Remove, itemId: string]; + +/** + * Utility function to get a unique string for a subscription to a resource, + * useful to keep maps of document or workspace folder subscription info. + */ +export const getTestSubscriptionKey = (resource: ExtHostTestingResource, uri: URI) => `${resource}:${uri.toString()}`; + +/** + * Request from the ext host or main thread to indicate that tests have + * changed. It's assumed that any item upserted *must* have its children + * previously also upserted, or upserted as part of the same operation. + * Children that no longer exist in an upserted item will be removed. + */ +export type TestsDiff = TestsDiffOp[]; + +/** + * @private + */ +export interface IncrementalTestCollectionItem extends InternalTestItem { + children: Set; +} + +/** + * The IncrementalChangeCollector is used in the IncrementalTestCollection + * and called with diff changes as they're applied. This is used in the + * ext host to create a cohesive change event from a diff. + */ +export class IncrementalChangeCollector { + /** + * A node was added. + */ + public add(node: T): void { } + + /** + * A node in the collection was updated. + */ + public update(node: T): void { } + + /** + * A node was removed. + */ + public remove(node: T): void { } + + /** + * Called when the diff has been applied. + */ + public complete(): void { } +} + +/** + * Maintains tests in this extension host sent from the main thread. + */ +export abstract class AbstractIncrementalTestCollection { + /** + * Map of item IDs to test item objects. + */ + protected readonly items = new Map(); + + /** + * ID of test root items. + */ + protected readonly roots = new Set(); + + /** + * Applies the diff to the collection. + */ + public apply(diff: TestsDiff) { + const changes = this.createChangeCollector(); + + for (const op of diff) { + switch (op[0]) { + case TestDiffOpType.Add: { + const item = op[1]; + if (!item.parent) { + this.roots.add(item.id); + const created = this.createItem(item); + this.items.set(item.id, created); + changes.add(created); + } else if (this.items.has(item.parent)) { + const parent = this.items.get(item.parent)!; + parent.children.add(item.id); + const created = this.createItem(item, parent); + this.items.set(item.id, created); + changes.add(created); + } + break; + } + + case TestDiffOpType.Update: { + const item = op[1]; + const existing = this.items.get(item.id); + if (existing) { + Object.assign(existing.item, item.item); + changes.update(existing); + } + break; + } + + case TestDiffOpType.Remove: { + const toRemove = this.items.get(op[1]); + if (!toRemove) { + break; + } + + if (toRemove.parent) { + const parent = this.items.get(toRemove.parent)!; + parent.children.delete(toRemove.id); + } else { + this.roots.delete(toRemove.id); + } + + const queue: Iterable[] = [[op[1]]]; + while (queue.length) { + for (const itemId of queue.pop()!) { + const existing = this.items.get(itemId); + if (existing) { + queue.push(existing.children); + this.items.delete(itemId); + changes.remove(existing); + } + } + } + } + } + } + + changes.complete(); + } + + /** + * Called before a diff is applied to create a new change collector. + */ + protected createChangeCollector() { + return new IncrementalChangeCollector(); + } + + /** + * Creates a new item for the collection from the internal test item. + */ + protected abstract createItem(internal: InternalTestItem, parent?: T): T; +} diff --git a/src/vs/workbench/contrib/testing/common/testService.ts b/src/vs/workbench/contrib/testing/common/testService.ts new file mode 100644 index 000000000..f4d84d583 --- /dev/null +++ b/src/vs/workbench/contrib/testing/common/testService.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 { Event } from 'vs/base/common/event'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ExtHostTestingResource } from 'vs/workbench/api/common/extHost.protocol'; +import { RunTestForProviderRequest, RunTestsRequest, RunTestsResult, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; + +export const ITestService = createDecorator('testService'); + +export interface MainTestController { + runTests(request: RunTestForProviderRequest): Promise; +} + +export type TestDiffListener = (diff: TestsDiff) => void; + +export interface ITestService { + readonly _serviceBrand: undefined; + readonly onShouldSubscribe: Event<{ resource: ExtHostTestingResource, uri: URI }>; + readonly onShouldUnsubscribe: Event<{ resource: ExtHostTestingResource, uri: URI }>; + registerTestController(id: string, controller: MainTestController): void; + unregisterTestController(id: string): void; + runTests(req: RunTestsRequest): Promise; + publishDiff(resource: ExtHostTestingResource, uri: URI, diff: TestsDiff): void; + subscribeToDiffs(resource: ExtHostTestingResource, uri: URI, acceptDiff: TestDiffListener): IDisposable; +} diff --git a/src/vs/workbench/contrib/testing/common/testServiceImpl.ts b/src/vs/workbench/contrib/testing/common/testServiceImpl.ts new file mode 100644 index 000000000..845534dd8 --- /dev/null +++ b/src/vs/workbench/contrib/testing/common/testServiceImpl.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 { groupBy } from 'vs/base/common/arrays'; +import { Emitter } from 'vs/base/common/event'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { isDefined } from 'vs/base/common/types'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { ExtHostTestingResource } from 'vs/workbench/api/common/extHost.protocol'; +import { AbstractIncrementalTestCollection, collectTestResults, getTestSubscriptionKey, IncrementalTestCollectionItem, InternalTestItem, RunTestsRequest, RunTestsResult, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; +import { ITestService, MainTestController, TestDiffListener } from 'vs/workbench/contrib/testing/common/testService'; + +export class TestService extends Disposable implements ITestService { + declare readonly _serviceBrand: undefined; + private testControllers = new Map(); + private readonly testSubscriptions = new Map; + listeners: number; + }>(); + private readonly subscribeEmitter = new Emitter<{ resource: ExtHostTestingResource, uri: URI }>(); + private readonly unsubscribeEmitter = new Emitter<{ resource: ExtHostTestingResource, uri: URI }>(); + + /** + * Fired when extension hosts should pull events from their test factories. + */ + public readonly onShouldSubscribe = this.subscribeEmitter.event; + + /** + * Fired when extension hosts should stop pulling events from their test factories. + */ + public readonly onShouldUnsubscribe = this.unsubscribeEmitter.event; + + /** + * @inheritdoc + */ + async runTests(req: RunTestsRequest): Promise { + const tests = groupBy(req.tests, (a, b) => a.providerId === b.providerId ? 0 : 1); + const requests = tests.map(group => { + const providerId = group[0].providerId; + const controller = this.testControllers.get(providerId); + return controller?.runTests({ providerId, debug: req.debug, ids: group.map(t => t.testId) }); + }).filter(isDefined); + + return collectTestResults(await Promise.all(requests)); + } + + /** + * @inheritdoc + */ + public subscribeToDiffs(resource: ExtHostTestingResource, uri: URI, acceptDiff: TestDiffListener) { + const subscriptionKey = getTestSubscriptionKey(resource, uri); + let subscription = this.testSubscriptions.get(subscriptionKey); + if (!subscription) { + subscription = { collection: new MainThreadTestCollection(), listeners: 0, onDiff: new Emitter() }; + this.subscribeEmitter.fire({ resource, uri }); + this.testSubscriptions.set(subscriptionKey, subscription); + } + + subscription.listeners++; + + const revive = subscription.collection.getReviverDiff(); + if (revive.length) { + acceptDiff(revive); + } + + const listener = subscription.onDiff.event(acceptDiff); + return toDisposable(() => { + listener.dispose(); + + if (!--subscription!.listeners) { + this.unsubscribeEmitter.fire({ resource, uri }); + this.testSubscriptions.delete(subscriptionKey); + } + }); + } + + /** + * @inheritdoc + */ + public publishDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff) { + const sub = this.testSubscriptions.get(getTestSubscriptionKey(resource, URI.revive(uri))); + if (sub) { + sub.collection.apply(diff); + sub.onDiff.fire(diff); + } + } + + /** + * @inheritdoc + */ + public registerTestController(id: string, controller: MainTestController): void { + this.testControllers.set(id, controller); + } + + /** + * @inheritdoc + */ + public unregisterTestController(id: string): void { + this.testControllers.delete(id); + } +} + +class MainThreadTestCollection extends AbstractIncrementalTestCollection { + /** + * Gets a diff that adds all items currently in the tree to a new collection, + * allowing it to fully hydrate. + */ + public getReviverDiff() { + const ops: TestsDiff = []; + const queue = [this.roots]; + while (queue.length) { + for (const child of queue.pop()!) { + const item = this.items.get(child)!; + ops.push([TestDiffOpType.Add, { id: item.id, providerId: item.providerId, item: item.item, parent: item.parent }]); + queue.push(item.children); + } + } + + return ops; + } + + protected createItem(internal: InternalTestItem): IncrementalTestCollectionItem { + return { ...internal, children: new Set() }; + } +} diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index 769f87085..ca1165af6 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -22,6 +22,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { IQuickInputService, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { DEFAULT_PRODUCT_ICON_THEME_ID } from 'vs/workbench/services/themes/browser/productIconThemeData'; +import { IGettingStartedService } from 'vs/workbench/services/gettingStarted/common/gettingStartedService'; export class SelectColorThemeAction extends Action { @@ -34,6 +35,7 @@ export class SelectColorThemeAction extends Action { @IQuickInputService private readonly quickInputService: IQuickInputService, @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + @IGettingStartedService private readonly gettingStartedService: IGettingStartedService, @IViewletService private readonly viewletService: IViewletService ) { super(id, label); @@ -84,6 +86,7 @@ export class SelectColorThemeAction extends Action { openExtensionViewlet(this.viewletService, `category:themes ${quickpick.value}`); } else { selectTheme(theme, true); + this.gettingStartedService.progressByEvent('themeSelected'); } isCompleted = true; quickpick.hide(); @@ -302,7 +305,7 @@ class GenerateColorThemeAction extends Action { let theme = this.themeService.getColorTheme(); let colors = Registry.as(ColorRegistryExtensions.ColorContribution).getColors(); let colorIds = colors.map(c => c.id).sort(); - let resultingColors: { [key: string]: string } = {}; + let resultingColors: { [key: string]: string | null } = {}; let inherited: string[] = []; for (let colorId of colorIds) { const color = theme.getColor(colorId, false); @@ -312,12 +315,18 @@ class GenerateColorThemeAction extends Action { inherited.push(colorId); } } + const nullDefaults = []; for (let id of inherited) { const color = theme.getColor(id); if (color) { resultingColors['__' + id] = Color.Format.CSS.formatHexA(color, true); + } else { + nullDefaults.push(id); } } + for (let id of nullDefaults) { + resultingColors['__' + id] = null; + } let contents = JSON.stringify({ '$schema': colorThemeSchemaId, type: theme.type, @@ -326,7 +335,7 @@ class GenerateColorThemeAction extends Action { }, null, '\t'); contents = contents.replace(/\"__/g, '//"'); - return this.editorService.openEditor({ contents, mode: 'jsonc' }); + return this.editorService.openEditor({ contents, mode: 'jsonc', options: { pinned: true } }); } } diff --git a/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts index 0eb622bbe..d97e3c087 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts @@ -18,6 +18,7 @@ import { Color } from 'vs/base/common/color'; import { IFileService } from 'vs/platform/files/common/files'; import { basename } from 'vs/base/common/resources'; import { Schemas } from 'vs/base/common/network'; +import { splitLines } from 'vs/base/common/strings'; interface IToken { c: string; @@ -220,7 +221,7 @@ class Snapper { if (!grammar) { return []; } - let lines = content.split(/\r\n|\r|\n/); + let lines = splitLines(content); let result = this._tokenize(grammar, lines); return this._getThemesResult(grammar, lines).then((themesResult) => { diff --git a/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts b/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts index b7ca9dbb2..212f827bf 100644 --- a/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts +++ b/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts @@ -18,11 +18,17 @@ import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ExplorerFolderContext } from 'vs/workbench/contrib/files/common/files'; import { ResourceContextKey } from 'vs/workbench/common/resources'; +import { Codicon } from 'vs/base/common/codicons'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; + + +const timelineViewIcon = registerIcon('timeline-view-icon', Codicon.history, localize('timelineViewIcon', 'View icon of the timeline view.')); +const timelineOpenIcon = registerIcon('timeline-open', Codicon.history, localize('timelineOpenIcon', 'Icon for the open timeline action.')); export class TimelinePaneDescriptor implements IViewDescriptor { readonly id = TimelinePaneId; readonly name = TimelinePane.TITLE; - readonly containerIcon = 'codicon-history'; + readonly containerIcon = timelineViewIcon; readonly ctorDescriptor = new SyncDescriptor(TimelinePane); readonly order = 2; readonly weight = 30; @@ -87,7 +93,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, ({ command: { id: OpenTimelineAction.ID, title: OpenTimelineAction.LABEL, - icon: { id: 'codicon/history' } + icon: timelineOpenIcon }, when: ContextKeyExpr.and(ExplorerFolderContext.toNegated(), ResourceContextKey.HasResource) })); diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 78ae88936..64f30027e 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -42,6 +42,8 @@ import { MenuItemAction, IMenuService, MenuId, registerAction2, Action2, MenuReg import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { ColorScheme } from 'vs/platform/theme/common/theme'; +import { Codicon } from 'vs/base/common/codicons'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; const ItemHeight = 22; @@ -208,7 +210,7 @@ class LoadMoreCommand { return this.loading ? localize('timeline.loadingMore', "Loading...") : localize('timeline.loadMore', "Load more"); } - get themeIcon(): { id: string; } | undefined { + get themeIcon(): ThemeIcon | undefined { return undefined; //this.loading ? { id: 'sync~spin' } : undefined; } } @@ -1150,6 +1152,11 @@ class TimelineTreeRenderer implements ITreeRenderer('updateState', StateType.Idle); @@ -117,6 +115,30 @@ export class ShowCurrentReleaseNotesAction extends AbstractShowReleaseNotesActio } } +interface IVersion { + major: number; + minor: number; + patch: number; +} + +function parseVersion(version: string): IVersion | undefined { + const match = /([0-9]+)\.([0-9]+)\.([0-9]+)/.exec(version); + + if (!match) { + return undefined; + } + + return { + major: parseInt(match[1]), + minor: parseInt(match[2]), + patch: parseInt(match[3]) + }; +} + +function isMajorMinorUpdate(before: IVersion, after: IVersion): boolean { + return before.major < after.major || before.minor < after.minor; +} + export class ProductContribution implements IWorkbenchContribution { private static readonly KEY = 'releaseNotes/lastVersion'; @@ -136,12 +158,13 @@ export class ProductContribution implements IWorkbenchContribution { return; } - const lastVersion = storageService.get(ProductContribution.KEY, StorageScope.GLOBAL, ''); + const lastVersion = parseVersion(storageService.get(ProductContribution.KEY, StorageScope.GLOBAL, '')); + const currentVersion = parseVersion(productService.version); const shouldShowReleaseNotes = configurationService.getValue('update.showReleaseNotes'); - - // was there an update? if so, open release notes const releaseNotesUrl = productService.releaseNotesUrl; - if (shouldShowReleaseNotes && !environmentService.skipReleaseNotes && releaseNotesUrl && lastVersion && productService.version !== lastVersion) { + + // was there a major/minor update? if so, open release notes + if (shouldShowReleaseNotes && !environmentService.skipReleaseNotes && releaseNotesUrl && lastVersion && currentVersion && isMajorMinorUpdate(lastVersion, currentVersion)) { showReleaseNotes(instantiationService, productService.version) .then(undefined, () => { notificationService.prompt( @@ -160,11 +183,11 @@ export class ProductContribution implements IWorkbenchContribution { } // should we show the new license? - if (productService.licenseUrl && lastVersion && semver.satisfies(lastVersion, '<1.0.0') && semver.satisfies(productService.version, '>=1.0.0')) { + if (productService.licenseUrl && lastVersion && lastVersion.major < 1 && currentVersion && currentVersion.major >= 1) { notificationService.info(nls.localize('licenseChanged', "Our license terms have changed, please click [here]({0}) to go through them.", productService.licenseUrl)); } - storageService.store(ProductContribution.KEY, productService.version, StorageScope.GLOBAL); + storageService.store(ProductContribution.KEY, productService.version, StorageScope.GLOBAL, StorageTarget.MACHINE); }); } } @@ -185,15 +208,12 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu @IContextKeyService private readonly contextKeyService: IContextKeyService, @IProductService private readonly productService: IProductService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService + @IOpenerService private readonly openerService: IOpenerService ) { super(); this.state = updateService.state; this.updateStateContextKey = CONTEXT_UPDATE_STATE.bindTo(this.contextKeyService); - // opt-in to syncing - storageKeysSyncRegistryService.registerStorageKey({ key: 'neverShowAgain:update/win32-fast-updates', version: 1 }); - this._register(updateService.onStateChange(this.onUpdateStateChange, this)); this.onUpdateStateChange(this.updateService.state); @@ -268,6 +288,10 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu } 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/, 'See [this link](https://github.com/microsoft/vscode/issues/7426#issuecomment-425093469) for more information'); this.notificationService.notify({ @@ -398,8 +422,8 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu // if version != stored version, save version and date if (currentVersion !== lastKnownVersion) { - this.storageService.store('update/lastKnownVersion', currentVersion!, StorageScope.GLOBAL); - this.storageService.store('update/updateNotificationTime', currentMillis, StorageScope.GLOBAL); + this.storageService.store('update/lastKnownVersion', currentVersion!, StorageScope.GLOBAL, StorageTarget.MACHINE); + this.storageService.store('update/updateNotificationTime', currentMillis, StorageScope.GLOBAL, StorageTarget.MACHINE); } const updateNotificationMillis = this.storageService.getNumber('update/updateNotificationTime', StorageScope.GLOBAL, currentMillis); @@ -472,7 +496,35 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Updating) }); - CommandsRegistry.registerCommand('update.restart', () => this.updateService.quitAndInstall()); + CommandsRegistry.registerCommand('update.restart', async () => { + if (isNative && isMacintosh && typeof require !== 'undefined' && typeof require.__$__nodeRequire === 'function') { + const os = require.__$__nodeRequire('os') as { release(): string; }; + const release = os.release(); + + if (parseInt(release) >= 20) { // Big Sur + const answer = await this.dialogService.show( + Severity.Warning, + nls.localize('good luck', "'Restart to Update' is not working properly on macOS Big Sur. Click 'Quit to Update' to quit {0} and update it. Then, relaunch it from Finder.", this.productService.nameShort), + [ + nls.localize('quit', "Quit to Update"), + nls.localize('learn more', "Learn More"), + nls.localize('cancel', "Cancel") + ], + { cancelId: 2 } + ); + + if (answer.choice === 2) { + return; + } else if (answer.choice === 1) { + await this.openerService.open(URI.parse('https://github.com/microsoft/vscode/issues/109728')); + return; + } + } + } + + this.updateService.quitAndInstall(); + }); + MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '6_update', command: { diff --git a/src/vs/workbench/contrib/url/browser/trustedDomains.ts b/src/vs/workbench/contrib/url/browser/trustedDomains.ts index 81ec10aea..145afdf03 100644 --- a/src/vs/workbench/contrib/url/browser/trustedDomains.ts +++ b/src/vs/workbench/contrib/url/browser/trustedDomains.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IProductService } from 'vs/platform/product/common/productService'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; @@ -31,7 +31,7 @@ export const manageTrustedDomainSettingsCommand = { }, handler: async (accessor: ServicesAccessor) => { const editorService = accessor.get(IEditorService); - editorService.openEditor({ resource: TRUSTED_DOMAINS_URI, mode: 'jsonc' }); + editorService.openEditor({ resource: TRUSTED_DOMAINS_URI, mode: 'jsonc', options: { pinned: true } }); return; } }; @@ -117,7 +117,8 @@ export async function configureOpenerTrustedDomainsHandler( case 'manage': await editorService.openEditor({ resource: TRUSTED_DOMAINS_URI, - mode: 'jsonc' + mode: 'jsonc', + options: { pinned: true } }); notificationService.prompt(Severity.Info, localize('configuringURL', "Configuring trust for: {0}", resource.toString()), [{ label: 'Copy', run: () => clipboardService.writeText(resource.toString()) }]); @@ -129,7 +130,8 @@ export async function configureOpenerTrustedDomainsHandler( storageService.store( TRUSTED_DOMAINS_STORAGE_KEY, JSON.stringify([...trustedDomains, itemToTrust]), - StorageScope.GLOBAL + StorageScope.GLOBAL, + StorageTarget.USER ); return [...trustedDomains, itemToTrust]; diff --git a/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts b/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts index a04a706c8..988007b32 100644 --- a/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts +++ b/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts @@ -8,11 +8,10 @@ import { parse } from 'vs/base/common/json'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, FileWriteOptions, IFileService, IStat, IWatchOptions, IFileSystemProviderWithFileReadWriteCapability } from 'vs/platform/files/common/files'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { VSBuffer } from 'vs/base/common/buffer'; import { readTrustedDomains, TRUSTED_DOMAINS_CONTENT_STORAGE_KEY, TRUSTED_DOMAINS_STORAGE_KEY } from 'vs/workbench/contrib/url/browser/trustedDomains'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { assertIsDefined } from 'vs/base/common/types'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -97,12 +96,8 @@ export class TrustedDomainsFileSystemProvider implements IFileSystemProviderWith @IFileService private readonly fileService: IFileService, @IStorageService private readonly storageService: IStorageService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IStorageKeysSyncRegistryService private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, ) { this.fileService.registerProvider(TRUSTED_DOMAINS_SCHEMA, this); - - this.storageKeysSyncRegistryService.registerStorageKey({ key: TRUSTED_DOMAINS_STORAGE_KEY, version: 1 }); - this.storageKeysSyncRegistryService.registerStorageKey({ key: TRUSTED_DOMAINS_CONTENT_STORAGE_KEY, version: 1 }); } stat(resource: URI): Promise { @@ -134,11 +129,12 @@ export class TrustedDomainsFileSystemProvider implements IFileSystemProviderWith const trustedDomainsContent = VSBuffer.wrap(content).toString(); const trustedDomains = parse(trustedDomainsContent); - this.storageService.store(TRUSTED_DOMAINS_CONTENT_STORAGE_KEY, trustedDomainsContent, StorageScope.GLOBAL); + this.storageService.store(TRUSTED_DOMAINS_CONTENT_STORAGE_KEY, trustedDomainsContent, StorageScope.GLOBAL, StorageTarget.USER); this.storageService.store( TRUSTED_DOMAINS_STORAGE_KEY, JSON.stringify(trustedDomains) || '', - StorageScope.GLOBAL + StorageScope.GLOBAL, + StorageTarget.USER ); } catch (err) { } diff --git a/src/vs/workbench/contrib/url/browser/trustedDomainsValidator.ts b/src/vs/workbench/contrib/url/browser/trustedDomainsValidator.ts index 2c8428a63..c8f144bda 100644 --- a/src/vs/workbench/contrib/url/browser/trustedDomainsValidator.ts +++ b/src/vs/workbench/contrib/url/browser/trustedDomainsValidator.ts @@ -283,13 +283,15 @@ const doURLMatch = ( options.push(doURLMatch(memo, url, trustedURL, urlOffset, trustedURLOffset + 2)); } - if (trustedURL[trustedURLOffset] + trustedURL[trustedURLOffset + 1] === '.*' && url[urlOffset] === '.') { - // IP mode. Consume one segment of numbers or nothing. - let endBlockIndex = urlOffset + 1; - do { endBlockIndex++; } while (/[0-9]/.test(url[endBlockIndex])); - if (['.', ':', '/', undefined].includes(url[endBlockIndex])) { - options.push(doURLMatch(memo, url, trustedURL, endBlockIndex, trustedURLOffset + 2)); + if (trustedURL[trustedURLOffset] === '*') { + // Any match. Either consume one thing and don't advance base or consume nothing and do. + if (urlOffset + 1 === url.length) { + // If we're at the end of the input url consume one from both. + options.push(doURLMatch(memo, url, trustedURL, urlOffset + 1, trustedURLOffset + 1)); + } else { + options.push(doURLMatch(memo, url, trustedURL, urlOffset + 1, trustedURLOffset)); } + options.push(doURLMatch(memo, url, trustedURL, urlOffset, trustedURLOffset + 1)); } if (trustedURL[trustedURLOffset] + trustedURL[trustedURLOffset + 1] === ':*') { diff --git a/src/vs/workbench/contrib/url/browser/url.contribution.ts b/src/vs/workbench/contrib/url/browser/url.contribution.ts index 949c99803..5d3a01480 100644 --- a/src/vs/workbench/contrib/url/browser/url.contribution.ts +++ b/src/vs/workbench/contrib/url/browser/url.contribution.ts @@ -37,7 +37,7 @@ class OpenUrlAction extends Action2 { return quickInputService.input({ prompt: localize('urlToOpen', "URL to open") }).then(input => { if (input) { const uri = URI.parse(input); - urlService.open(uri, { trusted: true }); + urlService.open(uri, { originalUrl: input }); } }); } diff --git a/src/vs/workbench/contrib/url/test/browser/trustedDomains.test.ts b/src/vs/workbench/contrib/url/test/browser/trustedDomains.test.ts index 6f8ec1ad0..77144c981 100644 --- a/src/vs/workbench/contrib/url/test/browser/trustedDomains.test.ts +++ b/src/vs/workbench/contrib/url/test/browser/trustedDomains.test.ts @@ -116,6 +116,14 @@ suite('Link protection domain matching', () => { linkNotAllowedByRules('http://192.168.1.7:3000/', ['http://192.168.*.6:*']); }); + test('scheme match', () => { + linkAllowedByRules('http://192.168.1.7/', ['http://*']); + linkAllowedByRules('http://twitter.com', ['http://*']); + linkAllowedByRules('http://twitter.com/hello', ['http://*']); + linkNotAllowedByRules('https://192.168.1.7/', ['http://*']); + linkNotAllowedByRules('https://twitter.com/', ['http://*']); + }); + test('case normalization', () => { // https://github.com/microsoft/vscode/issues/99294 linkAllowedByRules('https://github.com/microsoft/vscode/issues/new', ['https://github.com/microsoft']); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index f41dbb1c2..8a1aa13b2 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { Action } from 'vs/base/common/actions'; -import { toErrorMessage } from 'vs/base/common/errorMessage'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, dispose, MutableDisposable, toDisposable, IDisposable } from 'vs/base/common/lifecycle'; @@ -45,15 +44,14 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; import { fromNow } from 'vs/base/common/date'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; import { Registry } from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { Codicon } from 'vs/base/common/codicons'; import { ViewContainerLocation, IViewContainersRegistry, Extensions, ViewContainer } from 'vs/workbench/common/views'; import { UserDataSyncViewPaneContainer, UserDataSyncDataViews } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncViews'; -import { IUserDataSyncWorkbenchService, getSyncAreaLabel, AccountStatus, CONTEXT_SYNC_STATE, CONTEXT_SYNC_ENABLEMENT, CONTEXT_ACCOUNT_STATE, CONFIGURE_SYNC_COMMAND_ID, SHOW_SYNC_LOG_COMMAND_ID, SYNC_VIEW_CONTAINER_ID, SYNC_TITLE } from 'vs/workbench/services/userDataSync/common/userDataSync'; +import { IUserDataSyncWorkbenchService, getSyncAreaLabel, AccountStatus, CONTEXT_SYNC_STATE, CONTEXT_SYNC_ENABLEMENT, CONTEXT_ACCOUNT_STATE, CONFIGURE_SYNC_COMMAND_ID, SHOW_SYNC_LOG_COMMAND_ID, SYNC_VIEW_CONTAINER_ID, SYNC_TITLE, SYNC_VIEW_ICON } from 'vs/workbench/services/userDataSync/common/userDataSync'; const CONTEXT_CONFLICTS_SOURCES = new RawContextKey('conflictsSources', ''); @@ -259,7 +257,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo await this.userDataSyncService.accept(syncResource, conflict.remoteResource, undefined, this.userDataAutoSyncEnablementService.isEnabled()); } } catch (e) { - this.notificationService.error(e); + this.notificationService.error(localize('accept failed', "Error while accepting changes. Please check [logs]({0}) for more details.", `command:${SHOW_SYNC_LOG_COMMAND_ID}`)); } } @@ -269,7 +267,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo await this.userDataSyncService.accept(syncResource, conflict.localResource, undefined, this.userDataAutoSyncEnablementService.isEnabled()); } } catch (e) { - this.notificationService.error(e); + this.notificationService.error(localize('accept failed', "Error while accepting changes. Please check [logs]({0}) for more details.", `command:${SHOW_SYNC_LOG_COMMAND_ID}`)); } } @@ -447,7 +445,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo await this.selectSettingsSyncService(this.userDataSyncStoreManagementService.userDataSyncStore); } await this.userDataSyncWorkbenchService.turnOn(); - this.storageService.store('sync.donotAskPreviewConfirmation', true, StorageScope.GLOBAL); + this.storageService.store('sync.donotAskPreviewConfirmation', true, StorageScope.GLOBAL, StorageTarget.MACHINE); } catch (e) { if (isPromiseCanceledError(e)) { return; @@ -482,9 +480,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } }); return; + case UserDataSyncErrorCode.Unauthorized: + this.notificationService.error(localize('auth failed', "Error while turning on Settings Sync: Authentication failed.")); + return; } } - this.notificationService.error(localize('turn on failed', "Error while starting Settings Sync: {0}", toErrorMessage(e))); + this.notificationService.error(localize('turn on failed', "Error while turning on Settings Sync. Please check [logs]({0}) for more details.", `command:${SHOW_SYNC_LOG_COMMAND_ID}`)); } } @@ -658,6 +659,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo leftResource: conflict.remoteResource, rightResource: conflict.previewResource, label: localize('sideBySideLabels', "{0} ↔ {1}", leftResourceName, rightResourceName), + description: localize('sideBySideDescription', "Settings Sync"), options: { preserveFocus: false, pinned: true, @@ -1047,7 +1049,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo await that.turnOff(); } catch (e) { if (!isPromiseCanceledError(e)) { - that.notificationService.error(localize('turn off failed', "Error while turning off sync: {0}", toErrorMessage(e))); + that.notificationService.error(localize('turn off failed', "Error while turning off Settings Sync. Please check [logs]({0}) for more details.", `command:${SHOW_SYNC_LOG_COMMAND_ID}`)); } } } @@ -1121,7 +1123,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo UserDataSyncViewPaneContainer, [SYNC_VIEW_CONTAINER_ID] ), - icon: Codicon.sync.classNames, + icon: SYNC_VIEW_ICON, hideIfEmpty: true, }, ViewContainerLocation.Sidebar); } @@ -1251,7 +1253,7 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio this.notificationService.warn(localize('update conflicts', "Could not resolve conflicts as there is new local version available. Please try again.")); } } else { - this.notificationService.error(e); + this.notificationService.error(localize('accept failed', "Error while accepting changes. Please check [logs]({0}) for more details.", `command:${SHOW_SYNC_LOG_COMMAND_ID}`)); } } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts index bf0675cdb..3610b1ca5 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts @@ -302,7 +302,11 @@ export class UserDataSyncMergesViewPane extends TreeViewPane { if (previewResource.mergeState === MergeState.Accepted) { if (previewResource.localChange !== Change.Deleted && previewResource.remoteChange !== Change.Deleted) { // Do not open deleted preview - await this.editorService.openEditor({ resource: previewResource.accepted, label: localize('preview', "{0} (Preview)", basename(previewResource.accepted)) }); + await this.editorService.openEditor({ + resource: previewResource.accepted, + label: localize('preview', "{0} (Preview)", basename(previewResource.accepted)), + options: { pinned: true } + }); } } else { const leftResource = previewResource.remote; @@ -314,9 +318,11 @@ export class UserDataSyncMergesViewPane extends TreeViewPane { leftResource, rightResource, label: localize('sideBySideLabels', "{0} ↔ {1}", leftResourceName, rightResourceName), + description: localize('sideBySideDescription', "Settings Sync"), options: { preserveFocus: true, revealIfVisible: true, + pinned: true }, }); } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts index 1bf03ec3a..d26d9dd1a 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts @@ -7,7 +7,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IViewsRegistry, Extensions, ITreeViewDescriptor, ITreeViewDataProvider, ITreeItem, TreeItemCollapsibleState, TreeViewItemHandleArg, ViewContainer, IViewDescriptorService } from 'vs/workbench/common/views'; import { localize } from 'vs/nls'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { TreeViewPane } from 'vs/workbench/browser/parts/views/treeView'; +import { TreeView, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ALL_SYNC_RESOURCES, SyncResource, IUserDataSyncService, ISyncResourceHandle as IResourceHandle, SyncStatus, IUserDataSyncResourceEnablementService, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; @@ -34,7 +34,6 @@ import { IUserDataSyncWorkbenchService, CONTEXT_SYNC_STATE, getSyncAreaLabel, CO import { IUserDataSyncMachinesService, IUserDataSyncMachine } from 'vs/platform/userDataSync/common/userDataSyncMachines'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { TreeView } from 'vs/workbench/contrib/views/browser/treeView'; import { flatten } from 'vs/base/common/arrays'; import { UserDataSyncMergesViewPane } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView'; import { basename } from 'vs/base/common/resources'; @@ -229,7 +228,7 @@ export class UserDataSyncDataViews extends Disposable { async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise { const { resource } = <{ resource: string }>JSON.parse(handle.$treeItemHandle); const editorService = accessor.get(IEditorService); - await editorService.openEditor({ resource: URI.parse(resource) }); + await editorService.openEditor({ resource: URI.parse(resource), options: { pinned: true } }); } }); @@ -238,7 +237,7 @@ export class UserDataSyncDataViews extends Disposable { super({ id: `workbench.actions.sync.replaceCurrent`, title: localize('workbench.actions.sync.replaceCurrent', "Restore"), - icon: { id: 'codicon/discard' }, + icon: Codicon.discard, menu: { id: MenuId.ViewItemContext, when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', viewId), ContextKeyExpr.regex('viewItem', /sync-resource-.*/i)), @@ -279,9 +278,11 @@ export class UserDataSyncDataViews extends Disposable { leftResource, rightResource, label: localize('sideBySideLabels', "{0} ↔ {1}", leftResourceName, rightResourceName), + description: localize('sideBySideDescription', "Settings Sync"), options: { preserveFocus: true, revealIfVisible: true, + pinned: true }, }); } diff --git a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts index bad65d90f..098549a7b 100644 --- a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts @@ -17,7 +17,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { Action } from 'vs/base/common/actions'; -import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-sandbox/issue'; +import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; import { Disposable } from 'vs/base/common/lifecycle'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { CONTEXT_SYNC_STATE, SHOW_SYNC_LOG_COMMAND_ID, SYNC_TITLE } from 'vs/workbench/services/userDataSync/common/userDataSync'; diff --git a/src/vs/workbench/contrib/views/browser/treeView.ts b/src/vs/workbench/contrib/views/browser/treeView.ts deleted file mode 100644 index b035d4cac..000000000 --- a/src/vs/workbench/contrib/views/browser/treeView.ts +++ /dev/null @@ -1,1059 +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 'vs/css!./media/views'; -import { Event, Emitter } from 'vs/base/common/event'; -import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IAction, ActionRunner, IActionViewItemProvider } from 'vs/base/common/actions'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IMenuService, MenuId, MenuItemAction, registerAction2, Action2, SubmenuItemAction } from 'vs/platform/actions/common/actions'; -import { MenuEntryActionViewItem, createAndFillInContextMenuActions, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IContextKeyService, ContextKeyExpr, ContextKeyEqualsExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ITreeView, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeItemLabel, IViewDescriptorService, ViewContainer, ViewContainerLocation, ResolvableTreeItem } from 'vs/workbench/common/views'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IProgressService } from 'vs/platform/progress/common/progress'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import * as DOM from 'vs/base/browser/dom'; -import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; -import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { URI } from 'vs/base/common/uri'; -import { dirname, basename } from 'vs/base/common/resources'; -import { FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon, IThemeService } from 'vs/platform/theme/common/themeService'; -import { FileKind } from 'vs/platform/files/common/files'; -import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; -import { localize } from 'vs/nls'; -import { timeout } from 'vs/base/common/async'; -import { textLinkForeground, textCodeBlockBackground, focusBorder, listFilterMatchHighlight, listFilterMatchHighlightBorder } from 'vs/platform/theme/common/colorRegistry'; -import { isString } from 'vs/base/common/types'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list'; -import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; -import { FuzzyScore, createMatches } from 'vs/base/common/filters'; -import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults'; -import { isFalsyOrWhitespace } from 'vs/base/common/strings'; -import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; -import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { ColorScheme } from 'vs/platform/theme/common/theme'; -import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; - -class Root implements ITreeItem { - label = { label: 'root' }; - handle = '0'; - parentHandle: string | undefined = undefined; - collapsibleState = TreeItemCollapsibleState.Expanded; - children: ITreeItem[] | undefined = undefined; -} - -const noDataProviderMessage = localize('no-dataprovider', "There is no data provider registered that can provide view data."); - -class Tree extends WorkbenchAsyncDataTree { } - -export class TreeView extends Disposable implements ITreeView { - - private isVisible: boolean = false; - private _hasIconForParentNode = false; - private _hasIconForLeafNode = false; - - private readonly collapseAllContextKey: RawContextKey; - private readonly collapseAllContext: IContextKey; - private readonly collapseAllToggleContextKey: RawContextKey; - private readonly collapseAllToggleContext: IContextKey; - private readonly refreshContextKey: RawContextKey; - private readonly refreshContext: IContextKey; - - private focused: boolean = false; - private domNode!: HTMLElement; - private treeContainer!: HTMLElement; - private _messageValue: string | undefined; - private _canSelectMany: boolean = false; - private messageElement!: HTMLDivElement; - private tree: Tree | undefined; - private treeLabels: ResourceLabels | undefined; - - private root: ITreeItem; - private elementsToRefresh: ITreeItem[] = []; - - private readonly _onDidExpandItem: Emitter = this._register(new Emitter()); - readonly onDidExpandItem: Event = this._onDidExpandItem.event; - - private readonly _onDidCollapseItem: Emitter = this._register(new Emitter()); - readonly onDidCollapseItem: Event = this._onDidCollapseItem.event; - - private _onDidChangeSelection: Emitter = this._register(new Emitter()); - readonly onDidChangeSelection: Event = this._onDidChangeSelection.event; - - private readonly _onDidChangeVisibility: Emitter = this._register(new Emitter()); - readonly onDidChangeVisibility: Event = this._onDidChangeVisibility.event; - - private readonly _onDidChangeActions: Emitter = this._register(new Emitter()); - readonly onDidChangeActions: Event = this._onDidChangeActions.event; - - private readonly _onDidChangeWelcomeState: Emitter = this._register(new Emitter()); - readonly onDidChangeWelcomeState: Event = this._onDidChangeWelcomeState.event; - - private readonly _onDidChangeTitle: Emitter = this._register(new Emitter()); - readonly onDidChangeTitle: Event = this._onDidChangeTitle.event; - - private readonly _onDidChangeDescription: Emitter = this._register(new Emitter()); - readonly onDidChangeDescription: Event = this._onDidChangeDescription.event; - - private readonly _onDidCompleteRefresh: Emitter = this._register(new Emitter()); - - constructor( - readonly id: string, - private _title: string, - @IThemeService private readonly themeService: IThemeService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @ICommandService private readonly commandService: ICommandService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IProgressService protected readonly progressService: IProgressService, - @IContextMenuService private readonly contextMenuService: IContextMenuService, - @IKeybindingService private readonly keybindingService: IKeybindingService, - @INotificationService private readonly notificationService: INotificationService, - @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, - @IHoverService private readonly hoverService: IHoverService, - @IContextKeyService contextKeyService: IContextKeyService - ) { - super(); - this.root = new Root(); - this.collapseAllContextKey = new RawContextKey(`treeView.${this.id}.enableCollapseAll`, false); - this.collapseAllContext = this.collapseAllContextKey.bindTo(contextKeyService); - this.collapseAllToggleContextKey = new RawContextKey(`treeView.${this.id}.toggleCollapseAll`, false); - this.collapseAllToggleContext = this.collapseAllToggleContextKey.bindTo(contextKeyService); - this.refreshContextKey = new RawContextKey(`treeView.${this.id}.enableRefresh`, false); - this.refreshContext = this.refreshContextKey.bindTo(contextKeyService); - - this._register(this.themeService.onDidFileIconThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); - this._register(this.themeService.onDidColorThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); - this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('explorer.decorations')) { - this.doRefresh([this.root]); /** soft refresh **/ - } - })); - this._register(this.viewDescriptorService.onDidChangeLocation(({ views, from, to }) => { - if (views.some(v => v.id === this.id)) { - this.tree?.updateOptions({ overrideStyles: { listBackground: this.viewLocation === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND } }); - } - })); - this.registerActions(); - - this.create(); - } - - get viewContainer(): ViewContainer { - return this.viewDescriptorService.getViewContainerByViewId(this.id)!; - } - - get viewLocation(): ViewContainerLocation { - return this.viewDescriptorService.getViewLocationById(this.id)!; - } - - private _dataProvider: ITreeViewDataProvider | undefined; - get dataProvider(): ITreeViewDataProvider | undefined { - return this._dataProvider; - } - - set dataProvider(dataProvider: ITreeViewDataProvider | undefined) { - if (this.tree === undefined) { - this.createTree(); - } - - if (dataProvider) { - const self = this; - this._dataProvider = new class implements ITreeViewDataProvider { - private _isEmpty: boolean = true; - private _onDidChangeEmpty: Emitter = new Emitter(); - public onDidChangeEmpty: Event = this._onDidChangeEmpty.event; - - get isTreeEmpty(): boolean { - return this._isEmpty; - } - - async getChildren(node?: ITreeItem): Promise { - let children: ITreeItem[]; - if (node && node.children) { - children = node.children; - } else { - node = node ?? self.root; - children = await (node instanceof Root ? dataProvider.getChildren() : dataProvider.getChildren(node)); - node.children = children; - } - if (node instanceof Root) { - const oldEmpty = this._isEmpty; - this._isEmpty = children.length === 0; - if (oldEmpty !== this._isEmpty) { - this._onDidChangeEmpty.fire(); - } - } - return children; - } - }; - if (this._dataProvider.onDidChangeEmpty) { - this._register(this._dataProvider.onDidChangeEmpty(() => this._onDidChangeWelcomeState.fire())); - } - this.updateMessage(); - this.refresh(); - } else { - this._dataProvider = undefined; - this.updateMessage(); - } - - this._onDidChangeWelcomeState.fire(); - } - - private _message: string | undefined; - get message(): string | undefined { - return this._message; - } - - set message(message: string | undefined) { - this._message = message; - this.updateMessage(); - this._onDidChangeWelcomeState.fire(); - } - - get title(): string { - return this._title; - } - - set title(name: string) { - this._title = name; - this._onDidChangeTitle.fire(this._title); - } - - private _description: string | undefined; - get description(): string | undefined { - return this._description; - } - - set description(description: string | undefined) { - this._description = description; - this._onDidChangeDescription.fire(this._description); - } - - get canSelectMany(): boolean { - return this._canSelectMany; - } - - set canSelectMany(canSelectMany: boolean) { - this._canSelectMany = canSelectMany; - } - - get hasIconForParentNode(): boolean { - return this._hasIconForParentNode; - } - - get hasIconForLeafNode(): boolean { - return this._hasIconForLeafNode; - } - - get visible(): boolean { - return this.isVisible; - } - - get showCollapseAllAction(): boolean { - return !!this.collapseAllContext.get(); - } - - set showCollapseAllAction(showCollapseAllAction: boolean) { - this.collapseAllContext.set(showCollapseAllAction); - } - - get showRefreshAction(): boolean { - return !!this.refreshContext.get(); - } - - set showRefreshAction(showRefreshAction: boolean) { - this.refreshContext.set(showRefreshAction); - } - - private registerActions() { - const that = this; - this._register(registerAction2(class extends Action2 { - constructor() { - super({ - id: `workbench.actions.treeView.${that.id}.refresh`, - title: localize('refresh', "Refresh"), - menu: { - id: MenuId.ViewTitle, - when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', that.id), that.refreshContextKey), - group: 'navigation', - order: Number.MAX_SAFE_INTEGER - 1, - }, - icon: { id: 'codicon/refresh' } - }); - } - async run(): Promise { - return that.refresh(); - } - })); - this._register(registerAction2(class extends Action2 { - constructor() { - super({ - id: `workbench.actions.treeView.${that.id}.collapseAll`, - title: localize('collapseAll', "Collapse All"), - menu: { - id: MenuId.ViewTitle, - when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', that.id), that.collapseAllContextKey), - group: 'navigation', - order: Number.MAX_SAFE_INTEGER, - }, - precondition: that.collapseAllToggleContextKey, - icon: { id: 'codicon/collapse-all' } - }); - } - async run(): Promise { - if (that.tree) { - return new CollapseAllAction(that.tree, true).run(); - } - } - })); - } - - setVisibility(isVisible: boolean): void { - isVisible = !!isVisible; - if (this.isVisible === isVisible) { - return; - } - - this.isVisible = isVisible; - - if (this.tree) { - if (this.isVisible) { - DOM.show(this.tree.getHTMLElement()); - } else { - DOM.hide(this.tree.getHTMLElement()); // make sure the tree goes out of the tabindex world by hiding it - } - - if (this.isVisible && this.elementsToRefresh.length) { - this.doRefresh(this.elementsToRefresh); - this.elementsToRefresh = []; - } - } - - this._onDidChangeVisibility.fire(this.isVisible); - } - - focus(reveal: boolean = true): void { - if (this.tree && this.root.children && this.root.children.length > 0) { - // Make sure the current selected element is revealed - const selectedElement = this.tree.getSelection()[0]; - if (selectedElement && reveal) { - this.tree.reveal(selectedElement, 0.5); - } - - // Pass Focus to Viewer - this.tree.domFocus(); - } else if (this.tree) { - this.tree.domFocus(); - } else { - this.domNode.focus(); - } - } - - show(container: HTMLElement): void { - DOM.append(container, this.domNode); - } - - private create() { - this.domNode = DOM.$('.tree-explorer-viewlet-tree-view'); - this.messageElement = DOM.append(this.domNode, DOM.$('.message')); - this.treeContainer = DOM.append(this.domNode, DOM.$('.customview-tree')); - this.treeContainer.classList.add('file-icon-themable-tree', 'show-file-icons'); - const focusTracker = this._register(DOM.trackFocus(this.domNode)); - this._register(focusTracker.onDidFocus(() => this.focused = true)); - this._register(focusTracker.onDidBlur(() => this.focused = false)); - } - - private createTree() { - const actionViewItemProvider = (action: IAction) => { - if (action instanceof MenuItemAction) { - return this.instantiationService.createInstance(MenuEntryActionViewItem, action); - } else if (action instanceof SubmenuItemAction) { - return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action); - } - - return undefined; - }; - const treeMenus = this._register(this.instantiationService.createInstance(TreeMenus, this.id)); - this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this)); - const dataSource = this.instantiationService.createInstance(TreeDataSource, this, (task: Promise) => this.progressService.withProgress({ location: this.id }, () => task)); - const aligner = new Aligner(this.themeService); - const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner); - const widgetAriaLabel = this._title; - - this.tree = this._register(this.instantiationService.createInstance(Tree, this.id, this.treeContainer, new TreeViewDelegate(), [renderer], - dataSource, { - identityProvider: new TreeViewIdentityProvider(), - accessibilityProvider: { - getAriaLabel(element: ITreeItem): string { - if (element.accessibilityInformation) { - return element.accessibilityInformation.label; - } - - return isString(element.tooltip) ? element.tooltip : element.label ? element.label.label : ''; - }, - getRole(element: ITreeItem): string | undefined { - return element.accessibilityInformation?.role ?? 'treeitem'; - }, - getWidgetAriaLabel(): string { - return widgetAriaLabel; - } - }, - keyboardNavigationLabelProvider: { - getKeyboardNavigationLabel: (item: ITreeItem) => { - return item.label ? item.label.label : (item.resourceUri ? basename(URI.revive(item.resourceUri)) : undefined); - } - }, - expandOnlyOnTwistieClick: (e: ITreeItem) => !!e.command, - collapseByDefault: (e: ITreeItem): boolean => { - return e.collapsibleState !== TreeItemCollapsibleState.Expanded; - }, - multipleSelectionSupport: this.canSelectMany, - overrideStyles: { - listBackground: this.viewLocation === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND - } - }) as WorkbenchAsyncDataTree); - aligner.tree = this.tree; - const actionRunner = new MultipleSelectionActionRunner(this.notificationService, () => this.tree!.getSelection()); - renderer.actionRunner = actionRunner; - - this.tree.contextKeyService.createKey(this.id, true); - this._register(this.tree.onContextMenu(e => this.onContextMenu(treeMenus, e, actionRunner))); - this._register(this.tree.onDidChangeSelection(e => this._onDidChangeSelection.fire(e.elements))); - this._register(this.tree.onDidChangeCollapseState(e => { - if (!e.node.element) { - return; - } - - const element: ITreeItem = Array.isArray(e.node.element.element) ? e.node.element.element[0] : e.node.element.element; - if (e.node.collapsed) { - this._onDidCollapseItem.fire(element); - } else { - this._onDidExpandItem.fire(element); - } - })); - this.tree.setInput(this.root).then(() => this.updateContentAreas()); - - this._register(this.tree.onDidOpen(e => { - if (!e.browserEvent) { - return; - } - const selection = this.tree!.getSelection(); - if ((selection.length === 1) && selection[0].command) { - this.commandService.executeCommand(selection[0].command.id, ...(selection[0].command.arguments || [])); - } - })); - - } - - private onContextMenu(treeMenus: TreeMenus, treeEvent: ITreeContextMenuEvent, actionRunner: MultipleSelectionActionRunner): void { - this.hoverService.hideHover(); - const node: ITreeItem | null = treeEvent.element; - if (node === null) { - return; - } - const event: UIEvent = treeEvent.browserEvent; - - event.preventDefault(); - event.stopPropagation(); - - this.tree!.setFocus([node]); - const actions = treeMenus.getResourceContextActions(node); - if (!actions.length) { - return; - } - this.contextMenuService.showContextMenu({ - getAnchor: () => treeEvent.anchor, - - getActions: () => actions, - - getActionViewItem: (action) => { - const keybinding = this.keybindingService.lookupKeybinding(action.id); - if (keybinding) { - return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel() }); - } - return undefined; - }, - - onHide: (wasCancelled?: boolean) => { - if (wasCancelled) { - this.tree!.domFocus(); - } - }, - - getActionsContext: () => ({ $treeViewId: this.id, $treeItemHandle: node.handle }), - - actionRunner - }); - } - - protected updateMessage(): void { - if (this._message) { - this.showMessage(this._message); - } else if (!this.dataProvider) { - this.showMessage(noDataProviderMessage); - } else { - this.hideMessage(); - } - this.updateContentAreas(); - } - - private showMessage(message: string): void { - this.messageElement.classList.remove('hide'); - this.resetMessageElement(); - this._messageValue = message; - if (!isFalsyOrWhitespace(this._message)) { - this.messageElement.textContent = this._messageValue; - } - this.layout(this._height, this._width); - } - - private hideMessage(): void { - this.resetMessageElement(); - this.messageElement.classList.add('hide'); - this.layout(this._height, this._width); - } - - private resetMessageElement(): void { - DOM.clearNode(this.messageElement); - } - - private _height: number = 0; - private _width: number = 0; - layout(height: number, width: number) { - if (height && width) { - this._height = height; - this._width = width; - const treeHeight = height - DOM.getTotalHeight(this.messageElement); - this.treeContainer.style.height = treeHeight + 'px'; - if (this.tree) { - this.tree.layout(treeHeight, width); - } - } - } - - getOptimalWidth(): number { - if (this.tree) { - const parentNode = this.tree.getHTMLElement(); - const childNodes = ([] as HTMLElement[]).slice.call(parentNode.querySelectorAll('.outline-item-label > a')); - return DOM.getLargestChildWidth(parentNode, childNodes); - } - return 0; - } - - async refresh(elements?: ITreeItem[]): Promise { - if (this.dataProvider && this.tree) { - if (this.refreshing) { - await Event.toPromise(this._onDidCompleteRefresh.event); - } - if (!elements) { - elements = [this.root]; - // remove all waiting elements to refresh if root is asked to refresh - this.elementsToRefresh = []; - } - for (const element of elements) { - element.children = undefined; // reset children - } - if (this.isVisible) { - return this.doRefresh(elements); - } else { - if (this.elementsToRefresh.length) { - const seen: Set = new Set(); - this.elementsToRefresh.forEach(element => seen.add(element.handle)); - for (const element of elements) { - if (!seen.has(element.handle)) { - this.elementsToRefresh.push(element); - } - } - } else { - this.elementsToRefresh.push(...elements); - } - } - } - return undefined; - } - - async expand(itemOrItems: ITreeItem | ITreeItem[]): Promise { - const tree = this.tree; - if (tree) { - itemOrItems = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems]; - await Promise.all(itemOrItems.map(element => { - return tree.expand(element, false); - })); - } - } - - setSelection(items: ITreeItem[]): void { - if (this.tree) { - this.tree.setSelection(items); - } - } - - setFocus(item: ITreeItem): void { - if (this.tree) { - this.focus(); - this.tree.setFocus([item]); - } - } - - async reveal(item: ITreeItem): Promise { - if (this.tree) { - return this.tree.reveal(item); - } - } - - private refreshing: boolean = false; - private async doRefresh(elements: ITreeItem[]): Promise { - const tree = this.tree; - if (tree && this.visible) { - this.refreshing = true; - await Promise.all(elements.map(element => tree.updateChildren(element, true, true))); - this.refreshing = false; - this._onDidCompleteRefresh.fire(); - this.updateContentAreas(); - if (this.focused) { - this.focus(false); - } - this.updateCollapseAllToggle(); - } - } - - private updateCollapseAllToggle() { - if (this.showCollapseAllAction) { - this.collapseAllToggleContext.set(!!this.root.children && (this.root.children.length > 0) && - this.root.children.some(value => value.collapsibleState !== TreeItemCollapsibleState.None)); - } - } - - private updateContentAreas(): void { - const isTreeEmpty = !this.root.children || this.root.children.length === 0; - // Hide tree container only when there is a message and tree is empty and not refreshing - if (this._messageValue && isTreeEmpty && !this.refreshing) { - this.treeContainer.classList.add('hide'); - this.domNode.setAttribute('tabindex', '0'); - } else { - this.treeContainer.classList.remove('hide'); - this.domNode.removeAttribute('tabindex'); - } - } -} - -class TreeViewIdentityProvider implements IIdentityProvider { - getId(element: ITreeItem): { toString(): string; } { - return element.handle; - } -} - -class TreeViewDelegate implements IListVirtualDelegate { - - getHeight(element: ITreeItem): number { - return TreeRenderer.ITEM_HEIGHT; - } - - getTemplateId(element: ITreeItem): string { - return TreeRenderer.TREE_TEMPLATE_ID; - } -} - -class TreeDataSource implements IAsyncDataSource { - - constructor( - private treeView: ITreeView, - private withProgress: (task: Promise) => Promise - ) { - } - - hasChildren(element: ITreeItem): boolean { - return !!this.treeView.dataProvider && (element.collapsibleState !== TreeItemCollapsibleState.None); - } - - async getChildren(element: ITreeItem): Promise { - if (this.treeView.dataProvider) { - return this.withProgress(this.treeView.dataProvider.getChildren(element)); - } - return []; - } -} - -// todo@joh,sandy make this proper and contributable from extensions -registerThemingParticipant((theme, collector) => { - - const matchBackgroundColor = theme.getColor(listFilterMatchHighlight); - if (matchBackgroundColor) { - collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; background-color: ${matchBackgroundColor}; }`); - collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; background-color: ${matchBackgroundColor}; }`); - } - const matchBorderColor = theme.getColor(listFilterMatchHighlightBorder); - if (matchBorderColor) { - collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${matchBorderColor}; box-sizing: border-box; }`); - collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${matchBorderColor}; box-sizing: border-box; }`); - } - const link = theme.getColor(textLinkForeground); - if (link) { - collector.addRule(`.tree-explorer-viewlet-tree-view > .message a { color: ${link}; }`); - } - const focusBorderColor = theme.getColor(focusBorder); - if (focusBorderColor) { - collector.addRule(`.tree-explorer-viewlet-tree-view > .message a:focus { outline: 1px solid ${focusBorderColor}; outline-offset: -1px; }`); - } - const codeBackground = theme.getColor(textCodeBlockBackground); - if (codeBackground) { - collector.addRule(`.tree-explorer-viewlet-tree-view > .message code { background-color: ${codeBackground}; }`); - } -}); - -interface ITreeExplorerTemplateData { - elementDisposable: IDisposable; - container: HTMLElement; - resourceLabel: IResourceLabel; - icon: HTMLElement; - actionBar: ActionBar; -} - -class TreeRenderer extends Disposable implements ITreeRenderer { - static readonly ITEM_HEIGHT = 22; - static readonly TREE_TEMPLATE_ID = 'treeExplorer'; - - private _actionRunner: MultipleSelectionActionRunner | undefined; - private _hoverDelegate: IHoverDelegate; - - constructor( - private treeViewId: string, - private menus: TreeMenus, - private labels: ResourceLabels, - private actionViewItemProvider: IActionViewItemProvider, - private aligner: Aligner, - @IThemeService private readonly themeService: IThemeService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @ILabelService private readonly labelService: ILabelService, - @IHoverService private readonly hoverService: IHoverService - ) { - super(); - this._hoverDelegate = { - showHover: (options: IHoverDelegateOptions): IDisposable | undefined => { - return this.hoverService.showHover(options); - } - }; - } - - get templateId(): string { - return TreeRenderer.TREE_TEMPLATE_ID; - } - - set actionRunner(actionRunner: MultipleSelectionActionRunner) { - this._actionRunner = actionRunner; - } - - renderTemplate(container: HTMLElement): ITreeExplorerTemplateData { - container.classList.add('custom-view-tree-node-item'); - - const icon = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon')); - - const resourceLabel = this.labels.create(container, { supportHighlights: true, hoverDelegate: this._hoverDelegate }); - const actionsContainer = DOM.append(resourceLabel.element, DOM.$('.actions')); - const actionBar = new ActionBar(actionsContainer, { - actionViewItemProvider: this.actionViewItemProvider - }); - - return { resourceLabel, icon, actionBar, container, elementDisposable: Disposable.None }; - } - - private getHover(label: string | undefined, resource: URI | null, node: ITreeItem): string | Promise | undefined { - if (!(node instanceof ResolvableTreeItem) || !node.hasResolve) { - if (resource) { - return undefined; - } else if (!node.tooltip) { - return label; - } else if (!isString(node.tooltip)) { - return Promise.resolve(node.tooltip); - } else { - return node.tooltip; - } - } - - return new Promise(async (resolve) => { - await node.resolve(); - resolve(node.tooltip); - }); - } - - renderElement(element: ITreeNode, index: number, templateData: ITreeExplorerTemplateData): void { - templateData.elementDisposable.dispose(); - const node = element.element; - const resource = node.resourceUri ? URI.revive(node.resourceUri) : null; - const treeItemLabel: ITreeItemLabel | undefined = node.label ? node.label : (resource ? { label: basename(resource) } : undefined); - const description = isString(node.description) ? node.description : resource && node.description === true ? this.labelService.getUriLabel(dirname(resource), { relative: true }) : undefined; - const label = treeItemLabel ? treeItemLabel.label : undefined; - const matches = (treeItemLabel && treeItemLabel.highlights && label) ? treeItemLabel.highlights.map(([start, end]) => { - if (start < 0) { - start = label.length + start; - } - if (end < 0) { - end = label.length + end; - } - if ((start >= label.length) || (end > label.length)) { - return ({ start: 0, end: 0 }); - } - if (start > end) { - const swap = start; - start = end; - end = swap; - } - return ({ start, end }); - }) : undefined; - const icon = this.themeService.getColorTheme().type === ColorScheme.LIGHT ? node.icon : node.iconDark; - const iconUrl = icon ? URI.revive(icon) : null; - const title = this.getHover(label, resource, node); - - // reset - templateData.actionBar.clear(); - templateData.icon.style.color = ''; - - if (resource || this.isFileKindThemeIcon(node.themeIcon)) { - const fileDecorations = this.configurationService.getValue<{ colors: boolean, badges: boolean }>('explorer.decorations'); - const labelResource = resource ? resource : URI.parse('missing:_icon_resource'); - templateData.resourceLabel.setResource({ name: label, description, resource: labelResource }, { - fileKind: this.getFileKind(node), - title, - hideIcon: !!iconUrl, - fileDecorations, - extraClasses: ['custom-view-tree-node-item-resourceLabel'], - matches: matches ? matches : createMatches(element.filterData), - strikethrough: treeItemLabel?.strikethrough - }); - } else { - templateData.resourceLabel.setResource({ name: label, description }, { - title, - hideIcon: true, - extraClasses: ['custom-view-tree-node-item-resourceLabel'], - matches: matches ? matches : createMatches(element.filterData), - strikethrough: treeItemLabel?.strikethrough - }); - } - - if (iconUrl) { - templateData.icon.className = 'custom-view-tree-node-item-icon'; - templateData.icon.style.backgroundImage = DOM.asCSSUrl(iconUrl); - } else { - let iconClass: string | undefined; - if (node.themeIcon && !this.isFileKindThemeIcon(node.themeIcon)) { - iconClass = ThemeIcon.asClassName(node.themeIcon); - if (node.themeIcon.color) { - templateData.icon.style.color = this.themeService.getColorTheme().getColor(node.themeIcon.color.id)?.toString() ?? ''; - } - } - templateData.icon.className = iconClass ? `custom-view-tree-node-item-icon ${iconClass}` : ''; - templateData.icon.style.backgroundImage = ''; - } - - templateData.actionBar.context = { $treeViewId: this.treeViewId, $treeItemHandle: node.handle }; - templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false }); - if (this._actionRunner) { - templateData.actionBar.actionRunner = this._actionRunner; - } - this.setAlignment(templateData.container, node); - const disposableStore = new DisposableStore(); - templateData.elementDisposable = disposableStore; - disposableStore.add(this.themeService.onDidFileIconThemeChange(() => this.setAlignment(templateData.container, node))); - } - - private setAlignment(container: HTMLElement, treeItem: ITreeItem) { - container.parentElement!.classList.toggle('align-icon-with-twisty', this.aligner.alignIconWithTwisty(treeItem)); - } - - private isFileKindThemeIcon(icon: ThemeIcon | undefined): boolean { - if (icon) { - return icon.id === FileThemeIcon.id || icon.id === FolderThemeIcon.id; - } else { - return false; - } - } - - private getFileKind(node: ITreeItem): FileKind { - if (node.themeIcon) { - switch (node.themeIcon.id) { - case FileThemeIcon.id: - return FileKind.FILE; - case FolderThemeIcon.id: - return FileKind.FOLDER; - } - } - return node.collapsibleState === TreeItemCollapsibleState.Collapsed || node.collapsibleState === TreeItemCollapsibleState.Expanded ? FileKind.FOLDER : FileKind.FILE; - } - - disposeElement(resource: ITreeNode, index: number, templateData: ITreeExplorerTemplateData): void { - templateData.elementDisposable.dispose(); - } - - disposeTemplate(templateData: ITreeExplorerTemplateData): void { - templateData.resourceLabel.dispose(); - templateData.actionBar.dispose(); - templateData.elementDisposable.dispose(); - } -} - -class Aligner extends Disposable { - private _tree: WorkbenchAsyncDataTree | undefined; - - constructor(private themeService: IThemeService) { - super(); - } - - set tree(tree: WorkbenchAsyncDataTree) { - this._tree = tree; - } - - public alignIconWithTwisty(treeItem: ITreeItem): boolean { - if (treeItem.collapsibleState !== TreeItemCollapsibleState.None) { - return false; - } - if (!this.hasIcon(treeItem)) { - return false; - } - - if (this._tree) { - const parent: ITreeItem = this._tree.getParentElement(treeItem) || this._tree.getInput(); - if (this.hasIcon(parent)) { - return !!parent.children && parent.children.some(c => c.collapsibleState !== TreeItemCollapsibleState.None && !this.hasIcon(c)); - } - return !!parent.children && parent.children.every(c => c.collapsibleState === TreeItemCollapsibleState.None || !this.hasIcon(c)); - } else { - return false; - } - } - - private hasIcon(node: ITreeItem): boolean { - const icon = this.themeService.getColorTheme().type === ColorScheme.LIGHT ? node.icon : node.iconDark; - if (icon) { - return true; - } - if (node.resourceUri || node.themeIcon) { - const fileIconTheme = this.themeService.getFileIconTheme(); - const isFolder = node.themeIcon ? node.themeIcon.id === FolderThemeIcon.id : node.collapsibleState !== TreeItemCollapsibleState.None; - if (isFolder) { - return fileIconTheme.hasFileIcons && fileIconTheme.hasFolderIcons; - } - return fileIconTheme.hasFileIcons; - } - return false; - } -} - -class MultipleSelectionActionRunner extends ActionRunner { - - constructor(notificationService: INotificationService, private getSelectedResources: (() => ITreeItem[])) { - super(); - this._register(this.onDidRun(e => { - if (e.error) { - notificationService.error(localize('command-error', 'Error running command {1}: {0}. This is likely caused by the extension that contributes {1}.', e.error.message, e.action.id)); - } - })); - } - - runAction(action: IAction, context: TreeViewItemHandleArg): Promise { - const selection = this.getSelectedResources(); - let selectionHandleArgs: TreeViewItemHandleArg[] | undefined = undefined; - let actionInSelected: boolean = false; - if (selection.length > 1) { - selectionHandleArgs = selection.map(selected => { - if (selected.handle === context.$treeItemHandle) { - actionInSelected = true; - } - return { $treeViewId: context.$treeViewId, $treeItemHandle: selected.handle }; - }); - } - - if (!actionInSelected) { - selectionHandleArgs = undefined; - } - - return action.run(...[context, selectionHandleArgs]); - } -} - -class TreeMenus extends Disposable implements IDisposable { - - constructor( - private id: string, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IMenuService private readonly menuService: IMenuService - ) { - super(); - } - - getResourceActions(element: ITreeItem): IAction[] { - return this.getActions(MenuId.ViewItemContext, { key: 'viewItem', value: element.contextValue }).primary; - } - - getResourceContextActions(element: ITreeItem): IAction[] { - return this.getActions(MenuId.ViewItemContext, { key: 'viewItem', value: element.contextValue }).secondary; - } - - private getActions(menuId: MenuId, context: { key: string, value?: string }): { primary: IAction[]; secondary: IAction[]; } { - const contextKeyService = this.contextKeyService.createScoped(); - contextKeyService.createKey('view', this.id); - contextKeyService.createKey(context.key, context.value); - - const menu = this.menuService.createMenu(menuId, contextKeyService); - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, g => /^inline/.test(g)); - - menu.dispose(); - contextKeyService.dispose(); - - return result; - } -} - -export class CustomTreeView extends TreeView { - - private activated: boolean = false; - - constructor( - id: string, - title: string, - @IThemeService themeService: IThemeService, - @IInstantiationService instantiationService: IInstantiationService, - @ICommandService commandService: ICommandService, - @IConfigurationService configurationService: IConfigurationService, - @IProgressService progressService: IProgressService, - @IContextMenuService contextMenuService: IContextMenuService, - @IKeybindingService keybindingService: IKeybindingService, - @INotificationService notificationService: INotificationService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IContextKeyService contextKeyService: IContextKeyService, - @IHoverService hoverService: IHoverService, - @IExtensionService private readonly extensionService: IExtensionService, - ) { - super(id, title, themeService, instantiationService, commandService, configurationService, progressService, contextMenuService, keybindingService, notificationService, viewDescriptorService, hoverService, contextKeyService); - } - - setVisibility(isVisible: boolean): void { - super.setVisibility(isVisible); - if (this.visible) { - this.activate(); - } - } - - private activate() { - if (!this.activated) { - this.progressService.withProgress({ location: this.id }, () => this.extensionService.activateByEvent(`onView:${this.id}`)) - .then(() => timeout(2000)) - .then(() => { - this.updateMessage(); - }); - this.activated = true; - } - } -} diff --git a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts index 316861241..142650d37 100644 --- a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts +++ b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts @@ -37,7 +37,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOv private _owner: any = undefined; private readonly _scopedContextKeyService = this._register(new MutableDisposable()); - private _findWidgetVisible: IContextKey; + private _findWidgetVisible: IContextKey | undefined; public constructor( public readonly id: string, @@ -46,15 +46,13 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOv extension: WebviewExtensionDescription | undefined, @ILayoutService private readonly _layoutService: ILayoutService, @IWebviewService private readonly _webviewService: IWebviewService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService + @IContextKeyService private readonly _baseContextKeyService: IContextKeyService ) { super(); this._extension = extension; this._options = initialOptions; this._contentOptions = initialContentOptions; - - this._findWidgetVisible = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE.bindTo(_contextKeyService); } public get isFocused() { @@ -83,15 +81,32 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOv return container; } - public claim(owner: any) { + public claim(owner: any, scopedContextKeyService: IContextKeyService | undefined) { + const oldOwner = this._owner; + this._owner = owner; this.show(); + + if (oldOwner !== owner) { + const contextKeyService = (scopedContextKeyService || this._baseContextKeyService); + + // Explicitly clear before creating the new context. + // Otherwise we create the new context while the old one is still around + this._scopedContextKeyService.clear(); + this._scopedContextKeyService.value = contextKeyService.createScoped(this.container); + + this._findWidgetVisible?.reset(); + this._findWidgetVisible = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE.bindTo(contextKeyService); + } } public release(owner: any) { if (this._owner !== owner) { return; } + + this._scopedContextKeyService.clear(); + this._owner = undefined; this.container.style.visibility = 'hidden'; if (!this._options.retainContextWhenHidden) { @@ -130,8 +145,6 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOv } webview.mountTo(this.container); - this._scopedContextKeyService.value = this._contextKeyService.createScoped(this.container); - this._findWidgetVisible = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE.bindTo(this._scopedContextKeyService.value); // Forward events from inner webview to outer listeners this._webviewEvents.clear(); @@ -156,6 +169,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOv this._pendingMessages.forEach(msg => webview.postMessage(msg)); this._pendingMessages.clear(); } + this.container.style.visibility = 'visible'; } @@ -240,12 +254,12 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOv showFind() { if (this._webview.value) { this._webview.value.showFind(); - this._findWidgetVisible.set(true); + this._findWidgetVisible?.set(true); } } hideFind() { - this._findWidgetVisible.reset(); + this._findWidgetVisible?.reset(); this._webview.value?.hideFind(); } diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index 0f0773fce..726eff39c 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -58,6 +58,10 @@ }; const defaultCssRules = ` + html { + scrollbar-color: var(--vscode-scrollbarSlider-background) var(--vscode-editor-background); + } + body { background-color: transparent; color: var(--vscode-editor-foreground); @@ -439,10 +443,18 @@ // propagate focus host.onMessage('focus', () => { - const target = getActiveFrame(); - if (target) { - target.contentWindow.focus(); + const activeFrame = getActiveFrame(); + if (!activeFrame || !activeFrame.contentWindow) { + return; } + + if (document.activeElement === activeFrame) { + // We are already focused on the iframe (or one of its children) so no need + // to refocus. + return; + } + + activeFrame.contentWindow.focus(); }); // update iframe-contents @@ -573,6 +585,8 @@ newFrame.contentWindow.focus(); } + + contentWindow.addEventListener('scroll', handleInnerScroll); contentWindow.addEventListener('wheel', handleWheel); diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index 0da00297f..945817c5d 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { Dimension } from 'vs/base/browser/dom'; +import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import * as modes from 'vs/editor/common/modes'; -import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; /** * Set when the find widget in a webview is visible. @@ -145,7 +145,7 @@ export interface WebviewOverlay extends Webview { readonly container: HTMLElement; options: WebviewOptions; - claim(owner: any): void; + claim(owner: any, scopedContextKeyService: IContextKeyService | undefined): void; release(owner: any): void; getInnerWebview(): Webview | undefined; diff --git a/src/vs/workbench/contrib/webview/browser/webviewWindowDragMonitor.ts b/src/vs/workbench/contrib/webview/browser/webviewWindowDragMonitor.ts new file mode 100644 index 000000000..3bbc9e857 --- /dev/null +++ b/src/vs/workbench/contrib/webview/browser/webviewWindowDragMonitor.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 * as DOM from 'vs/base/browser/dom'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; + +/** + * Allows webviews to monitor when an element in the VS Code editor is being dragged/dropped. + * + * This is required since webview end up eating the drag event. VS Code needs to see this + * event so it can handle editor element drag drop. + */ +export class WebviewWindowDragMonitor extends Disposable { + constructor(getWebview: () => Webview | undefined) { + super(); + + this._register(DOM.addDisposableListener(window, DOM.EventType.DRAG_START, () => { + getWebview()?.windowDidDragStart(); + })); + + const onDragEnd = () => { + getWebview()?.windowDidDragEnd(); + }; + + this._register(DOM.addDisposableListener(window, DOM.EventType.DRAG_END, onDragEnd)); + this._register(DOM.addDisposableListener(window, DOM.EventType.MOUSE_MOVE, currentEvent => { + if (currentEvent.buttons === 0) { + onDragEnd(); + } + })); + } +} diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index 6ac6a86d7..d11fa2273 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -8,96 +8,35 @@ import { addDisposableListener } from 'vs/base/browser/dom'; import { ThrottledDelayer } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { once } from 'vs/base/common/functional'; -import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable } from 'vs/base/common/lifecycle'; import { FileAccess, Schemas } from 'vs/base/common/network'; -import { isMacintosh } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; -import { createChannelSender } from 'vs/base/parts/ipc/common/ipc'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; import { ILogService } from 'vs/platform/log/common/log'; +import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { webviewPartitionId } from 'vs/platform/webview/common/resourceLoader'; -import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService'; import { BaseWebview, WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/baseWebviewElement'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing'; import { Webview, WebviewContentOptions, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { WebviewFindDelegate, WebviewFindWidget } from 'vs/workbench/contrib/webview/browser/webviewFindWidget'; -import { WebviewResourceRequestManager, rewriteVsCodeResourceUrls } from 'vs/workbench/contrib/webview/electron-sandbox/resourceLoading'; - -class WebviewKeyboardHandler { - - private readonly _webviews = new Set(); - private readonly _isUsingNativeTitleBars: boolean; - - private readonly webviewMainService: IWebviewManagerService; - - constructor( - configurationService: IConfigurationService, - mainProcessService: IMainProcessService, - ) { - this._isUsingNativeTitleBars = configurationService.getValue('window.titleBarStyle') === 'native'; - - this.webviewMainService = createChannelSender(mainProcessService.getChannel('webview')); - } - - public add(webview: WebviewTag): IDisposable { - this._webviews.add(webview); - - const disposables = new DisposableStore(); - - if (this.shouldToggleMenuShortcutsEnablement) { - this.setIgnoreMenuShortcutsForWebview(webview, true); - } - - disposables.add(addDisposableListener(webview, 'ipc-message', (event) => { - switch (event.channel) { - case 'did-focus': - this.setIgnoreMenuShortcuts(true); - break; - - case 'did-blur': - this.setIgnoreMenuShortcuts(false); - return; - } - })); - - return toDisposable(() => { - disposables.dispose(); - this._webviews.delete(webview); - }); - } - - private get shouldToggleMenuShortcutsEnablement() { - return isMacintosh || this._isUsingNativeTitleBars; - } - - private setIgnoreMenuShortcuts(value: boolean) { - for (const webview of this._webviews) { - this.setIgnoreMenuShortcutsForWebview(webview, value); - } - } - - private setIgnoreMenuShortcutsForWebview(webview: WebviewTag, value: boolean) { - if (this.shouldToggleMenuShortcutsEnablement) { - this.webviewMainService.setIgnoreMenuShortcuts(webview.getWebContentsId(), value); - } - } -} +import { WebviewIgnoreMenuShortcutsManager } from 'vs/workbench/contrib/webview/electron-browser/webviewIgnoreMenuShortcutsManager'; +import { rewriteVsCodeResourceUrls, WebviewResourceRequestManager } from 'vs/workbench/contrib/webview/electron-sandbox/resourceLoading'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export class ElectronWebviewBasedWebview extends BaseWebview implements Webview, WebviewFindDelegate { - private static _webviewKeyboardHandler: WebviewKeyboardHandler | undefined; + private static _webviewKeyboardHandler: WebviewIgnoreMenuShortcutsManager | undefined; private static getWebviewKeyboardHandler( configService: IConfigurationService, mainProcessService: IMainProcessService, ) { if (!this._webviewKeyboardHandler) { - this._webviewKeyboardHandler = new WebviewKeyboardHandler(configService, mainProcessService); + this._webviewKeyboardHandler = new WebviewIgnoreMenuShortcutsManager(configService, mainProcessService); } return this._webviewKeyboardHandler; } @@ -124,6 +63,7 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme @IConfigurationService configurationService: IConfigurationService, @IMainProcessService mainProcessService: IMainProcessService, @INotificationService noficationService: INotificationService, + @INativeHostService nativeHostService: INativeHostService, ) { super(id, options, contentOptions, extension, _webviewThemeDataProvider, noficationService, _myLogService, telemetryService, environmentService); diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewIgnoreMenuShortcutsManager.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewIgnoreMenuShortcutsManager.ts new file mode 100644 index 000000000..5b5e4dd75 --- /dev/null +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewIgnoreMenuShortcutsManager.ts @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { WebviewTag } from 'electron'; +import { addDisposableListener } from 'vs/base/browser/dom'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { isMacintosh } from 'vs/base/common/platform'; +import { createChannelSender } from 'vs/base/parts/ipc/common/ipc'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService'; +import { WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/baseWebviewElement'; + +export class WebviewIgnoreMenuShortcutsManager { + + private readonly _webviews = new Set(); + private readonly _isUsingNativeTitleBars: boolean; + + private readonly webviewMainService: IWebviewManagerService; + + constructor( + configurationService: IConfigurationService, + mainProcessService: IMainProcessService, + ) { + this._isUsingNativeTitleBars = configurationService.getValue('window.titleBarStyle') === 'native'; + + this.webviewMainService = createChannelSender(mainProcessService.getChannel('webview')); + } + + public add(webview: WebviewTag): IDisposable { + this._webviews.add(webview); + + const disposables = new DisposableStore(); + + if (this.shouldToggleMenuShortcutsEnablement) { + this.setIgnoreMenuShortcutsForWebview(webview, true); + } + + disposables.add(addDisposableListener(webview, 'ipc-message', (event) => { + switch (event.channel) { + case WebviewMessageChannels.didFocus: + this.setIgnoreMenuShortcuts(true); + break; + + case WebviewMessageChannels.didBlur: + this.setIgnoreMenuShortcuts(false); + return; + } + })); + + return toDisposable(() => { + disposables.dispose(); + this._webviews.delete(webview); + }); + } + + private get shouldToggleMenuShortcutsEnablement() { + return isMacintosh || this._isUsingNativeTitleBars; + } + + private setIgnoreMenuShortcuts(value: boolean) { + for (const webview of this._webviews) { + this.setIgnoreMenuShortcutsForWebview(webview, value); + } + } + + private setIgnoreMenuShortcutsForWebview(webview: WebviewTag, value: boolean) { + if (this.shouldToggleMenuShortcutsEnablement) { + this.webviewMainService.setIgnoreMenuShortcuts({ webContentsId: webview.getWebContentsId() }, value); + } + } +} diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/iframeWebviewElement.ts b/src/vs/workbench/contrib/webview/electron-sandbox/iframeWebviewElement.ts index 3b9c5e75a..8c010272b 100644 --- a/src/vs/workbench/contrib/webview/electron-sandbox/iframeWebviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-sandbox/iframeWebviewElement.ts @@ -6,18 +6,23 @@ import { ThrottledDelayer } from 'vs/base/common/async'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; import { ILogService } from 'vs/platform/log/common/log'; +import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { IRequestService } from 'vs/platform/request/common/request'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/baseWebviewElement'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing'; import { WebviewContentOptions, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement'; import { rewriteVsCodeResourceUrls, WebviewResourceRequestManager } from 'vs/workbench/contrib/webview/electron-sandbox/resourceLoading'; +import { WindowIgnoreMenuShortcutsManager } from 'vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; /** @@ -31,6 +36,8 @@ export class ElectronIframeWebview extends IFrameWebview { private readonly _focusDelayer = this._register(new ThrottledDelayer(10)); private _elementFocusImpl!: (options?: FocusOptions | undefined) => void; + private readonly _webviewKeyboardHandler: WindowIgnoreMenuShortcutsManager; + constructor( id: string, options: WebviewOptions, @@ -45,17 +52,30 @@ export class ElectronIframeWebview extends IFrameWebview { @IRemoteAuthorityResolverService _remoteAuthorityResolverService: IRemoteAuthorityResolverService, @ILogService logService: ILogService, @IInstantiationService instantiationService: IInstantiationService, + @IConfigurationService configurationService: IConfigurationService, + @IMainProcessService mainProcessService: IMainProcessService, @INotificationService noficationService: INotificationService, + @INativeHostService nativeHostService: INativeHostService, ) { super(id, options, contentOptions, extension, webviewThemeDataProvider, noficationService, tunnelService, fileService, requestService, telemetryService, environmentService, _remoteAuthorityResolverService, logService); this._resourceRequestManager = this._register(instantiationService.createInstance(WebviewResourceRequestManager, id, extension, this.content.options)); + + this._webviewKeyboardHandler = new WindowIgnoreMenuShortcutsManager(configurationService, mainProcessService, nativeHostService); + + this._register(this.on(WebviewMessageChannels.didFocus, () => { + this._webviewKeyboardHandler.didFocus(); + })); + + this._register(this.on(WebviewMessageChannels.didBlur, () => { + this._webviewKeyboardHandler.didBlur(); + })); } protected createElement(options: WebviewOptions, contentOptions: WebviewContentOptions) { const element = super.createElement(options, contentOptions); - this._elementFocusImpl = element.focus.bind(element); + this._elementFocusImpl = () => element.contentWindow?.focus(); element.focus = () => { this.doFocus(); }; diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager.ts b/src/vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager.ts new file mode 100644 index 000000000..00da271c3 --- /dev/null +++ b/src/vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager.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 { isMacintosh } from 'vs/base/common/platform'; +import { createChannelSender } from 'vs/base/parts/ipc/common/ipc'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; +import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService'; + +export class WindowIgnoreMenuShortcutsManager { + + private readonly _isUsingNativeTitleBars: boolean; + + private readonly webviewMainService: IWebviewManagerService; + + constructor( + configurationService: IConfigurationService, + mainProcessService: IMainProcessService, + private readonly nativeHostService: INativeHostService + ) { + this._isUsingNativeTitleBars = configurationService.getValue('window.titleBarStyle') === 'native'; + + this.webviewMainService = createChannelSender(mainProcessService.getChannel('webview')); + } + + public didFocus(): void { + this.setIgnoreMenuShortcuts(true); + } + + public didBlur(): void { + this.setIgnoreMenuShortcuts(false); + } + + private get shouldToggleMenuShortcutsEnablement() { + return isMacintosh || this._isUsingNativeTitleBars; + } + + protected setIgnoreMenuShortcuts(value: boolean) { + if (this.shouldToggleMenuShortcutsEnablement) { + this.webviewMainService.setIgnoreMenuShortcuts({ windowId: this.nativeHostService.windowId }, value); + } + } +} diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts index 1ec3edc79..1eebb76e4 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts @@ -8,18 +8,21 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; +import { generateUuid } from 'vs/base/common/uuid'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; 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 { IEditorDropService } from 'vs/workbench/services/editor/browser/editorDropService'; import { EditorInput, EditorOptions, IEditorOpenContext } from 'vs/workbench/common/editor'; import { WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewWindowDragMonitor } from 'vs/workbench/contrib/webview/browser/webviewWindowDragMonitor'; +import { WebviewInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInput'; +import { IEditorDropService } from 'vs/workbench/services/editor/browser/editorDropService'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; -import { WebviewInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInput'; export class WebviewEditor extends EditorPane { @@ -35,6 +38,8 @@ export class WebviewEditor extends EditorPane { private readonly _onDidFocusWebview = this._register(new Emitter()); public get onDidFocus(): Event { return this._onDidFocusWebview.event; } + private readonly _scopedContextKeyService = this._register(new MutableDisposable()); + constructor( @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @@ -43,6 +48,7 @@ export class WebviewEditor extends EditorPane { @IWorkbenchLayoutService private readonly _workbenchLayoutService: IWorkbenchLayoutService, @IEditorDropService private readonly _editorDropService: IEditorDropService, @IHostService private readonly _hostService: IHostService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, ) { super(WebviewEditor.ID, telemetryService, themeService, storageService); } @@ -51,10 +57,17 @@ export class WebviewEditor extends EditorPane { return this.input instanceof WebviewInput ? this.input.webview : undefined; } + get scopedContextKeyService(): IContextKeyService | undefined { + return this._scopedContextKeyService.value; + } + protected createEditor(parent: HTMLElement): void { const element = document.createElement('div'); this._element = element; + this._element.id = `webview-editor-element-${generateUuid()}`; parent.appendChild(element); + + this._scopedContextKeyService.value = this._contextKeyService.createScoped(element); } public dispose(): void { @@ -139,10 +152,11 @@ export class WebviewEditor extends EditorPane { } private claimWebview(input: WebviewInput): void { - input.webview.claim(this); + input.webview.claim(this, this.scopedContextKeyService); if (this._element) { this._element.setAttribute('aria-flowto', input.webview.container.id); + DOM.setParentFlowTo(input.webview.container, this._element); } this._webviewVisibleDisposables.clear(); @@ -152,19 +166,7 @@ export class WebviewEditor extends EditorPane { containsGroup: (group) => this.group?.id === group.group.id })); - this._webviewVisibleDisposables.add(DOM.addDisposableListener(window, DOM.EventType.DRAG_START, () => { - this.webview?.windowDidDragStart(); - })); - - const onDragEnd = () => { - this.webview?.windowDidDragEnd(); - }; - this._webviewVisibleDisposables.add(DOM.addDisposableListener(window, DOM.EventType.DRAG_END, onDragEnd)); - this._webviewVisibleDisposables.add(DOM.addDisposableListener(window, DOM.EventType.MOUSE_MOVE, currentEvent => { - if (currentEvent.buttons === 0) { - onDragEnd(); - } - })); + this._webviewVisibleDisposables.add(new WebviewWindowDragMonitor(() => this.webview)); this.synchronizeWebviewContainerDimensions(input.webview); this._webviewVisibleDisposables.add(this.trackFocus(input.webview)); diff --git a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts index 41e915c4e..d9beb099f 100644 --- a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts +++ b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Dimension } from 'vs/base/browser/dom'; +import * as DOM from 'vs/base/browser/dom'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -16,7 +16,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IProgressService } from 'vs/platform/progress/common/progress'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; @@ -24,6 +24,7 @@ import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewl import { Memento, MementoObject } from 'vs/workbench/common/memento'; import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; import { IWebviewService, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewWindowDragMonitor } from 'vs/workbench/contrib/webview/browser/webviewWindowDragMonitor'; import { IWebviewViewService, WebviewView } from 'vs/workbench/contrib/webviewView/browser/webviewViewService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -70,7 +71,7 @@ export class WebviewViewPane extends ViewPane { this.defaultTitle = this.title; this.memento = new Memento(`webviewView.${this.id}`, storageService); - this.viewState = this.memento.getMemento(StorageScope.WORKSPACE); + this.viewState = this.memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); this._register(this.onDidChangeBodyVisibility(() => this.updateTreeVisibility())); @@ -139,14 +140,14 @@ export class WebviewViewPane extends ViewPane { } if (this._container) { - this._webview.value.layoutWebviewOverElement(this._container, new Dimension(width, height)); + this._webview.value.layoutWebviewOverElement(this._container, new DOM.Dimension(width, height)); } } private updateTreeVisibility() { if (this.isBodyVisible()) { this.activate(); - this._webview.value?.claim(this); + this._webview.value?.claim(this, undefined); } else { this._webview.value?.release(this); } @@ -172,6 +173,9 @@ export class WebviewViewPane extends ViewPane { this._webviewDisposables.add(webview.onDidUpdateState(() => { this.viewState[storageKeys.webviewState] = webview.state; })); + + this._webviewDisposables.add(new WebviewWindowDragMonitor(() => this._webview.value)); + const source = this._webviewDisposables.add(new CancellationTokenSource()); this.withProgress(async () => { diff --git a/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts b/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts index 25049bac5..ff54b32e0 100644 --- a/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts +++ b/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts @@ -36,9 +36,11 @@ export class ViewsWelcomeContribution extends Disposable implements IWorkbenchCo for (const welcome of contribution.value) { const id = ViewIdentifierMap[welcome.view] ?? welcome.view; const { group, order } = parseGroupAndOrder(welcome, contribution); + const precondition = ContextKeyExpr.deserialize(welcome.enablement); const disposable = viewsRegistry.registerViewWelcomeContent(id, { content: welcome.contents, when: ContextKeyExpr.deserialize(welcome.when), + precondition, group, order }); diff --git a/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts b/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts index ebaae9f7c..6e19480fc 100644 --- a/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts +++ b/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts @@ -11,6 +11,7 @@ export enum ViewsWelcomeExtensionPointFields { contents = 'contents', when = 'when', group = 'group', + enablement = 'enablement', } export interface ViewWelcome { @@ -18,6 +19,7 @@ export interface ViewWelcome { readonly [ViewsWelcomeExtensionPointFields.contents]: string; readonly [ViewsWelcomeExtensionPointFields.when]: string; readonly [ViewsWelcomeExtensionPointFields.group]: string; + readonly [ViewsWelcomeExtensionPointFields.enablement]: string; } export type ViewsWelcomeExtensionPoint = ViewWelcome[]; @@ -64,6 +66,10 @@ const viewsWelcomeExtensionPointSchema = Object.freeze(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(GettingStartedInput.ID, GettingStartedInputFactory); + +if (product.quality !== 'stable') { + Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ + ...workbenchConfigurationNodeBase, + properties: { + 'workbench.experimental.gettingStarted': { + type: 'boolean', + description: localize('gettingStartedDescription', "Enables an experimental Getting Started page, accesible via the Help menu."), + default: false, + } + } + }); +} diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css new file mode 100644 index 000000000..7c199b89d --- /dev/null +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css @@ -0,0 +1,248 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.file-icons-enabled .show-file-icons .vs_code_editor_getting_started\.md-name-file-icon.md-ext-file-icon.ext-file-icon.markdown-lang-file-icon.file-icon::before { + content: ' '; + background-image: url('../../../../browser/media/code-icon.svg'); +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide { + width: 100%; + height: 100%; + position: absolute; + transition: left 0.25s, opacity 0.25s; + left: 0; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories { + height: calc(100% - 50px); + display: flex; + flex-direction: column; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .gap { + flex: 150px 0 1 +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .header { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .category-title { + margin-bottom: 4px; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .category-description-container { + width: 100% +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .category-progress { + margin-top: 12px; + font-size: 12px; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories progress { + font-size: 12pt; + width: 100%; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories #getting-started-categories-container { + display: flex; + flex-wrap: wrap; + justify-content: center; + max-width: 900px; + margin: 32px auto; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide .getting-started-category { + width: 330px; + min-height: 80px; + margin: 12px; + text-align: left; + display: flex; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .getting-started-category { + padding-right: 46px; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide .getting-started-category .codicon { + margin-right: 10px; + font-size: 32px; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-category { + width: 330px; + display: flex; + padding: 40px 0 20px; + margin-left: 12px; + min-height: auto; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-category .codicon { + margin-left:0; + font-size: 22pt; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail #getting-started-detail-columns { + display: flex; + height: 100%; + justify-content: center; + padding: 44px; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task { + display: flex; + width: 100%; + overflow: hidden; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) .task-description, +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) .actions, +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) button, +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) a { + display: none; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task.expanded { + width: 100%; + height: unset; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .task-title { + font-size: 14pt; +} + + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .task-description { + margin-top: 4px; + font-size: 13px; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .actions { + margin-top: 12px; + display: flex; + align-items: center; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .task-next { + margin-left: auto; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .codicon.hidden { + display: none; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .codicon { + margin-right: 8px; + font-size: 20px; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task-action { + padding: 6px 12px; + font-size: 11pt; + min-width: 100px; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail #getting-started-detail-left { + min-width: 330px; + width: 40%; + max-width: 400px; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail #getting-started-detail-right { + width: 66%; + text-align: center; + padding-left: 44px; + margin-bottom: 50px; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button { + border: none; + color: inherit; + text-align: left; + padding: 16px; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button:not(:first-child) { + margin-top: 12px; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button:hover { + cursor: pointer; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button:focus { + outline-style: solid; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .prev-button { + position: absolute; + left: 0; + font-size: 12pt; + margin: 10px; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .prev-button:hover { + cursor: pointer; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .prev-button .codicon { + position: relative; + top: 2px; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide .skip { + display: block; + width: 150px; + margin: 0 auto; + text-align: center; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide h1 { + font-size: 32px; + font-weight: normal; + border-bottom: none; + margin: 0; + padding: 0; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide h2 { + font-weight: normal; + margin: 0 0 4px 0; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide h3 { + font-size: 18px; + font-weight: bold; + margin: 0; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide .subtitle { + font-size: 16px; + margin: 0; + padding: 0; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .footer { + position: absolute; + text-align: center; + bottom: 0; + width: calc(100% - 40px); + margin-bottom: 20px; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.next { + left: 100%; + opacity: 0; +} + +.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.prev { + left: -100%; + opacity: 0; +} diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts new file mode 100644 index 000000000..637330e06 --- /dev/null +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts @@ -0,0 +1,430 @@ +/*--------------------------------------------------------------------------------------------- + * 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!./gettingStarted'; +import 'vs/workbench/contrib/welcome/gettingStarted/browser/vs_code_editor_getting_started'; +import { localize } from 'vs/nls'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { WalkThroughInput } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput'; +import { FileAccess, Schemas } from 'vs/base/common/network'; +import { IEditorInputFactory } from 'vs/workbench/common/editor'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IEditorOptions } from 'vs/platform/editor/common/editor'; +import { assertIsDefined } from 'vs/base/common/types'; +import { $, addDisposableListener } from 'vs/base/browser/dom'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IGettingStartedCategoryWithProgress, IGettingStartedService } from 'vs/workbench/services/gettingStarted/common/gettingStartedService'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { buttonBackground as welcomeButtonBackground, buttonHoverBackground as welcomeButtonHoverBackground, welcomePageBackground } from 'vs/workbench/contrib/welcome/page/browser/welcomePageColors'; +import { activeContrastBorder, buttonBackground, buttonForeground, buttonHoverBackground, contrastBorder, descriptionForeground, focusBorder, foreground, textLinkActiveForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; +import { getExtraColor } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; + +export const gettingStartedInputTypeId = 'workbench.editors.gettingStartedInput'; +const telemetryFrom = 'gettingStartedPage'; + +export class GettingStartedInput extends WalkThroughInput { + static readonly ID = gettingStartedInputTypeId; + + selectedCategory: string | undefined; + selectedTask: string | undefined; +} + +export class GettingStartedPage extends Disposable { + readonly editorInput: GettingStartedInput; + private inProgressScroll = Promise.resolve(); + + private dispatchListeners = new DisposableStore(); + + private gettingStartedCategories: IGettingStartedCategoryWithProgress[]; + private currentCategory: IGettingStartedCategoryWithProgress | undefined; + + constructor( + initialState: { selectedCategory?: string, selectedTask?: string }, + @IEditorService private readonly editorService: IEditorService, + @ICommandService private readonly commandService: ICommandService, + @IProductService private readonly productService: IProductService, + @IKeybindingService private readonly keybindingService: IKeybindingService, + @IGettingStartedService private readonly gettingStartedService: IGettingStartedService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super(); + + const resource = FileAccess.asBrowserUri('./vs_code_editor_getting_started.md', require) + .with({ + scheme: Schemas.walkThrough, + query: JSON.stringify({ moduleId: 'vs/workbench/contrib/welcome/gettingStarted/browser/vs_code_editor_getting_started' }) + }); + + + this.editorInput = this.instantiationService.createInstance(GettingStartedInput, { + typeId: gettingStartedInputTypeId, + name: localize('editorGettingStarted.title', "Getting Started"), + resource, + telemetryFrom, + onReady: (container: HTMLElement) => this.onReady(container) + }); + + this.editorInput.selectedCategory = initialState.selectedCategory; + this.editorInput.selectedTask = initialState.selectedTask; + + this.gettingStartedCategories = this.gettingStartedService.getCategories(); + this._register(this.dispatchListeners); + this._register(this.gettingStartedService.onDidAddTask(task => console.log('added new task', task, 'that isnt being rendered yet'))); + this._register(this.gettingStartedService.onDidAddCategory(category => console.log('added new category', category, 'that isnt being rendered yet'))); + this._register(this.gettingStartedService.onDidProgressTask(task => { + const category = this.gettingStartedCategories.find(category => category.id === task.category); + if (!category) { throw Error('Could not find category with ID: ' + task.category); } + if (category.content.type !== 'items') { throw Error('internaal error: progressing task in a non-items category'); } + const ourTask = category.content.items.find(_task => _task.id === task.id); + if (!ourTask) { + throw Error('Could not find task with ID: ' + task.id); + } + ourTask.done = task.done; + if (category.id === this.currentCategory?.id) { + const badgeelement = assertIsDefined(document.getElementById('done-task-' + task.id)); + if (task.done) { + badgeelement.classList.remove('codicon-star-empty'); + badgeelement.classList.add('codicon-star-full'); + } + else { + badgeelement.classList.add('codicon-star-empty'); + badgeelement.classList.remove('codicon-star-full'); + } + } + this.updateCategoryProgress(); + })); + } + + public openEditor(options: IEditorOptions = { pinned: true }) { + return this.editorService.openEditor(this.editorInput, options); + } + + private registerDispatchListeners(container: HTMLElement) { + this.dispatchListeners.clear(); + + container.querySelectorAll('[x-dispatch]').forEach(element => { + const [command, argument] = (element.getAttribute('x-dispatch') ?? '').split(':'); + if (command) { + this.dispatchListeners.add(addDisposableListener(element, 'click', (e) => { + + type GettingStartedActionClassification = { + command: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; + argument: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; + }; + type GettingStartedActionEvent = { + command: string; + argument: string | undefined; + }; + this.telemetryService.publicLog2('gettingStarted.ActionExecuted', { command, argument }); + + switch (command) { + case 'scrollPrev': { + this.scrollPrev(container); + break; + } + case 'skip': { + this.commandService.executeCommand('workbench.action.closeActiveEditor'); + break; + } + case 'selectCategory': { + const selectedCategory = this.gettingStartedCategories.find(category => category.id === argument); + if (!selectedCategory) { throw Error('Could not find category with ID ' + argument); } + if (selectedCategory.content.type === 'command') { + this.commandService.executeCommand(selectedCategory.content.command); + } else { + this.scrollToCategory(container, argument); + } + break; + } + case 'selectTask': { + this.selectTask(argument); + e.stopPropagation(); + break; + } + case 'runTaskAction': { + if (!this.currentCategory || this.currentCategory.content.type !== 'items') { + throw Error('cannot run task action for category of non items type' + this.currentCategory?.id); + } + const taskToRun = assertIsDefined(this.currentCategory?.content.items.find(task => task.id === argument)); + const commandToRun = assertIsDefined(taskToRun.button?.command); + this.commandService.executeCommand(commandToRun); + break; + } + default: { + console.error('Dispatch to', command, argument, 'not defined'); + break; + } + } + })); + } + }); + } + + private selectTask(id: string | undefined) { + const mediaElement = assertIsDefined(document.getElementById('getting-started-media')); + if (id) { + const taskElement = assertIsDefined(document.getElementById('getting-started-task-' + id)); + if (!this.currentCategory || this.currentCategory.content.type !== 'items') { + throw Error('cannot expand task for category of non items type' + this.currentCategory?.id); + } + this.editorInput.selectedTask = id; + const taskToExpand = assertIsDefined(this.currentCategory.content.items.find(task => task.id === id)); + + mediaElement.setAttribute('src', taskToExpand.media.path.toString()); + mediaElement.setAttribute('alt', taskToExpand.media.altText); + taskElement.parentElement?.querySelectorAll('.expanded').forEach(node => node.classList.remove('expanded')); + taskElement.classList.add('expanded'); + } else { + mediaElement.setAttribute('src', ''); + mediaElement.setAttribute('alt', ''); + } + } + + private onReady(container: HTMLElement) { + const categoryElements = this.gettingStartedCategories.map( + category => { + const categoryDescriptionElement = + category.content.type === 'items' ? + $('.category-description-container', {}, + $('h3.category-title', {}, category.title), + $('.category-description.description', {}, category.description), + $('.category-progress', { 'x-data-category-id': category.id, }, $('.message'), $('progress'))) : + $('.category-description-container', {}, + $('h3.category-title', {}, category.title), + $('.category-description.description', {}, category.description)); + + return $('button.getting-started-category', + { 'x-dispatch': 'selectCategory:' + category.id }, + $('.codicon.codicon-' + category.codicon, {}), categoryDescriptionElement); + }); + + const rightColumn = assertIsDefined(container.querySelector('#getting-started-detail-right')); + rightColumn.appendChild($('img#getting-started-media')); + + const categoriesContainer = assertIsDefined(document.getElementById('getting-started-categories-container')); + categoryElements.forEach(element => { + categoriesContainer.appendChild(element); + }); + + this.updateCategoryProgress(); + + assertIsDefined(document.getElementById('product-name')).textContent = this.productService.nameLong; + this.registerDispatchListeners(container); + + const categoriesSlide = assertIsDefined(document.getElementById('gettingStartedSlideCategory')); + const tasksSlide = assertIsDefined(document.getElementById('gettingStartedSlideDetails')); + + if (this.editorInput.selectedCategory) { + this.currentCategory = this.gettingStartedCategories.find(category => category.id === this.editorInput.selectedCategory); + if (!this.currentCategory) { + throw Error('Could not restore to category ' + this.editorInput.selectedCategory + ' as it was not found'); + } + this.buildCategorySlide(container, this.editorInput.selectedCategory, this.editorInput.selectedTask); + categoriesSlide.classList.add('prev'); + } else { + tasksSlide.classList.add('next'); + } + } + + private updateCategoryProgress() { + document.querySelectorAll('.category-progress').forEach(element => { + const categoryID = element.getAttribute('x-data-category-id'); + const category = this.gettingStartedCategories.find(category => category.id === categoryID); + if (!category) { throw Error('Could not find c=ategory with ID ' + categoryID); } + if (category.content.type !== 'items') { throw Error('Category with ID ' + categoryID + ' is not of items type'); } + const numDone = category.content.items.filter(task => task.done).length; + const numTotal = category.content.items.length; + + const message = assertIsDefined(element.firstChild); + const bar = assertIsDefined(element.lastChild) as HTMLProgressElement; + bar.value = numDone; + bar.max = numTotal; + if (numTotal === numDone) { + message.textContent = `All items complete!`; + } + else { + message.textContent = `${numDone} of ${numTotal} items complete`; + } + }); + } + + private async scrollToCategory(container: HTMLElement, categoryID: string) { + this.inProgressScroll = this.inProgressScroll.then(async () => { + this.clearDetialView(); + this.editorInput.selectedCategory = categoryID; + this.currentCategory = this.gettingStartedCategories.find(category => category.id === categoryID); + const slides = [...container.querySelectorAll('.gettingStartedSlide').values()]; + const currentSlide = slides.findIndex(element => !element.classList.contains('prev') && !element.classList.contains('next')); + if (currentSlide < slides.length - 1) { + this.buildCategorySlide(container, categoryID); + slides[currentSlide].classList.add('prev'); + slides[currentSlide + 1].classList.remove('next'); + } + }); + } + + private buildCategorySlide(container: HTMLElement, categoryID: string, selectedItem?: string) { + const category = this.gettingStartedCategories.find(category => category.id === categoryID); + if (!category) { throw Error('could not find category with ID ' + categoryID); } + if (category.content.type !== 'items') { throw Error('category with ID ' + categoryID + ' is not of items type'); } + + const detailTitle = assertIsDefined(document.getElementById('getting-started-detail-title')); + detailTitle.appendChild( + $('.getting-started-category', + {}, + $('.codicon.codicon-' + category.codicon, {}), + $('.category-description-container', {}, + $('h2.category-title', {}, category.title), + $('.category-description.description', {}, category.description)))); + + const categoryElements = category.content.items.map( + (task, i, arr) => $('button.getting-started-task', + { 'x-dispatch': 'selectTask:' + task.id, id: 'getting-started-task-' + task.id }, + $('.codicon' + (task.done ? '.codicon-pass-filled' : '.codicon-circle-large-outline'), { id: 'done-task-' + task.id }), + $('.task-description-container', {}, + $('h3.task-title', {}, task.title), + $('.task-description.description', {}, task.description), + $('.actions', {}, + ...( + task.button + ? [$('button.emphasis.getting-started-task-action', { 'x-dispatch': 'runTaskAction:' + task.id }, + task.button.title + this.getKeybindingLabel(task.button.command) + )] + : []), + ...( + arr[i + 1] + ? [ + $('a.task-next', + { 'x-dispatch': 'selectTask:' + arr[i + 1].id }, localize('next', "Next")), + ] : [] + )) + ))); + + const detailContainer = assertIsDefined(document.getElementById('getting-started-detail-container')); + categoryElements.forEach(element => detailContainer.appendChild(element)); + + const toExpand = category.content.items.find(item => !item.done) ?? category.content.items[0]; + this.selectTask(selectedItem ?? toExpand.id); + this.registerDispatchListeners(container); + } + + private clearDetialView() { + const detailContainer = assertIsDefined(document.getElementById('getting-started-detail-container')); + while (detailContainer.firstChild) { detailContainer.removeChild(detailContainer.firstChild); } + const detailTitle = assertIsDefined(document.getElementById('getting-started-detail-title')); + while (detailTitle.firstChild) { detailTitle.removeChild(detailTitle.firstChild); } + } + + private getKeybindingLabel(command: string) { + const binding = this.keybindingService.lookupKeybinding(command); + if (!binding) { return ''; } + else { return ` (${binding.getLabel()})`; } + } + + private async scrollPrev(container: HTMLElement) { + this.inProgressScroll = this.inProgressScroll.then(async () => { + this.currentCategory = undefined; + this.editorInput.selectedCategory = undefined; + this.editorInput.selectedTask = undefined; + this.selectTask(undefined); + const slides = [...container.querySelectorAll('.gettingStartedSlide').values()]; + const currentSlide = slides.findIndex(element => + !element.classList.contains('prev') && !element.classList.contains('next')); + if (currentSlide > 0) { + slides[currentSlide].classList.add('next'); + assertIsDefined(slides[currentSlide - 1]).classList.remove('prev'); + } + }); + } +} + +export class GettingStartedInputFactory implements IEditorInputFactory { + public canSerialize(editorInput: GettingStartedInput): boolean { + return true; + } + + public serialize(editorInput: GettingStartedInput): string { + return JSON.stringify({ selectedCategory: editorInput.selectedCategory, selectedTask: editorInput.selectedTask }); + } + + public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): GettingStartedInput { + try { + const { selectedCategory, selectedTask } = JSON.parse(serializedEditorInput); + return instantiationService.createInstance(GettingStartedPage, { selectedCategory, selectedTask }).editorInput; + } catch { } + return instantiationService.createInstance(GettingStartedPage, {}).editorInput; + } +} + +registerThemingParticipant((theme, collector) => { + const backgroundColor = theme.getColor(welcomePageBackground); + if (backgroundColor) { + collector.addRule(`.monaco-workbench .part.editor > .content .welcomePageContainer { background-color: ${backgroundColor}; }`); + } + const foregroundColor = theme.getColor(foreground); + if (foregroundColor) { + collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer { color: ${foregroundColor}; }`); + } + const descriptionColor = theme.getColor(descriptionForeground); + if (descriptionColor) { + collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .description { color: ${descriptionColor}; }`); + } + const buttonColor = getExtraColor(theme, welcomeButtonBackground, { dark: 'rgba(0, 0, 0, .2)', extra_dark: 'rgba(200, 235, 255, .042)', light: 'rgba(0,0,0,.04)', hc: 'black' }); + if (buttonColor) { + collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button { background: ${buttonColor}; }`); + } + + const buttonHoverColor = getExtraColor(theme, welcomeButtonHoverBackground, { dark: 'rgba(200, 235, 255, .072)', extra_dark: 'rgba(200, 235, 255, .072)', light: 'rgba(0,0,0,.10)', hc: null }); + if (buttonHoverColor) { + collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button:hover { background: ${buttonHoverColor}; }`); + } + if (buttonColor && buttonHoverColor) { + collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button.expanded:hover { background: ${buttonColor}; }`); + } + + const emphasisButtonForeground = theme.getColor(buttonForeground); + if (emphasisButtonForeground) { + collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button.emphasis { color: ${emphasisButtonForeground}; }`); + } + + const emphasisButtonBackground = theme.getColor(buttonBackground); + if (emphasisButtonBackground) { + collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button.emphasis { background: ${emphasisButtonBackground}; }`); + collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .getting-started-category .codicon { color: ${emphasisButtonBackground} }`); + collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .codicon { color: ${emphasisButtonBackground} } `); + } + + const emphasisButtonHoverBackground = theme.getColor(buttonHoverBackground); + if (emphasisButtonHoverBackground) { + collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button.emphasis:hover { background: ${emphasisButtonHoverBackground}; }`); + } + + const link = theme.getColor(textLinkForeground); + if (link) { + collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer a { color: ${link}; }`); + } + const activeLink = theme.getColor(textLinkActiveForeground); + if (activeLink) { + collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer a:hover, + .monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer a:active { color: ${activeLink}; }`); + } + const focusColor = theme.getColor(focusBorder); + if (focusColor) { + collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer a:focus { outline-color: ${focusColor}; }`); + } + const border = theme.getColor(contrastBorder); + if (border) { + collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button { border-color: ${border}; border: 1px solid; }`); + } + const activeBorder = theme.getColor(activeContrastBorder); + if (activeBorder) { + collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button:hover { outline-color: ${activeBorder}; }`); + } +}); diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/vs_code_editor_getting_started.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/vs_code_editor_getting_started.ts new file mode 100644 index 000000000..dd6254527 --- /dev/null +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/vs_code_editor_getting_started.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 { escape } from 'vs/base/common/strings'; +import { localize } from 'vs/nls'; + +export default () => ` +
+
+
+
+
+

${escape(localize('gettingStarted.vscode', "Visual Studio Code"))}

+

${escape(localize({ key: 'gettingStarted.editingRedefined', comment: ['Shown as subtitle on the Welcome page.'] }, "Code editing. Redefined"))}

+
+
+
+
+
+ Back +
+
+
+
+
+
+
+
+
+ +
+
+`.replace(/\|/g, '`'); diff --git a/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts b/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts index 6af6a4b7f..a405271ac 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts @@ -19,9 +19,8 @@ export default () => `

${escape(localize('welcomePage.start', "Start"))}

diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts index 396871dc3..1a59cbb17 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts @@ -13,6 +13,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; +import product from 'vs/platform/product/common/product'; Registry.as(ConfigurationExtensions.Configuration) .registerConfiguration({ @@ -21,13 +22,21 @@ Registry.as(ConfigurationExtensions.Configuration) 'workbench.startupEditor': { 'scope': ConfigurationScope.APPLICATION, // Make sure repositories cannot trigger opening a README for tracking. 'type': 'string', - 'enum': ['none', 'welcomePage', 'readme', 'newUntitledFile', 'welcomePageInEmptyWorkbench'], - 'enumDescriptions': [ + 'enum': [ + ...['none', 'welcomePage', 'readme', 'newUntitledFile', 'welcomePageInEmptyWorkbench'], + ...(product.quality !== 'stable' + ? ['gettingStarted'] + : []) + ], + 'enumDescriptions': [...[ localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.none' }, "Start without an editor."), localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePage' }, "Open the Welcome page (default)."), localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.readme' }, "Open the README when opening a folder that contains one, fallback to 'welcomePage' otherwise."), localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.newUntitledFile' }, "Open a new untitled file (only applies when opening an empty workspace)."), - localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePageInEmptyWorkbench' }, "Open the Welcome page when opening an empty workbench."), + localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePageInEmptyWorkbench' }, "Open the Welcome page when opening an empty workbench."),], + ...(product.quality !== 'stable' + ? [localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.gettingStarted' }, "Open the Getting Started page (experimental).")] + : []) ], 'default': 'welcomePage', 'description': localize('workbench.startupEditor', "Controls which editor is shown at startup, if none are restored from the previous session.") diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index 4a61a79fe..37a7512d4 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -15,7 +15,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { onUnexpectedError, isPromiseCanceledError } from 'vs/base/common/errors'; import { IWindowOpenable } from 'vs/platform/windows/common/windows'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { localize } from 'vs/nls'; import { Action, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -29,7 +29,7 @@ import { ILifecycleService, StartupKind } from 'vs/workbench/services/lifecycle/ import { Disposable } from 'vs/base/common/lifecycle'; import { splitName } from 'vs/base/common/labels'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { registerColor, focusBorder, textLinkForeground, textLinkActiveForeground, foreground, descriptionForeground, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; +import { focusBorder, textLinkForeground, textLinkActiveForeground, foreground, descriptionForeground, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { getExtraColor } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils'; import { IExtensionsViewPaneContainer, IExtensionsWorkbenchService, VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { IEditorInputFactory, EditorInput } from 'vs/workbench/common/editor'; @@ -46,6 +46,8 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { gettingStartedInputTypeId, GettingStartedPage } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted'; +import { buttonBackground, buttonHoverBackground, welcomePageBackground } from 'vs/workbench/contrib/welcome/page/browser/welcomePageColors'; const configurationKey = 'workbench.startupEditor'; const oldConfigurationKey = 'workbench.welcome.enabled'; @@ -69,7 +71,8 @@ export class WelcomePageContribution implements IWorkbenchContribution { backupFileService.hasBackups().then(hasBackups => { // Open the welcome even if we opened a set of default editors if ((!editorService.activeEditor || layoutService.openedDefaultEditors) && !hasBackups) { - const openWithReadme = configurationService.getValue(configurationKey) === 'readme'; + const startupEditorSetting = configurationService.getValue(configurationKey) as string; + const openWithReadme = startupEditorSetting === 'readme'; if (openWithReadme) { return Promise.all(contextService.getWorkspace().folders.map(folder => { const folderUri = folder.uri; @@ -101,18 +104,23 @@ export class WelcomePageContribution implements IWorkbenchContribution { return undefined; }); } else { + const startupEditorTypeID = startupEditorSetting === 'gettingStarted' ? gettingStartedInputTypeId : welcomeInputTypeId; + const launchEditor = startupEditorSetting === 'gettingStarted' + ? instantiationService.createInstance(GettingStartedPage, {}) + : instantiationService.createInstance(WelcomePage); + let options: IEditorOptions; let editor = editorService.activeEditor; if (editor) { // Ensure that the welcome editor won't get opened more than once - if (editor.getTypeId() === welcomeInputTypeId || editorService.editors.some(e => e.getTypeId() === welcomeInputTypeId)) { + if (editor.getTypeId() === startupEditorTypeID || editorService.editors.some(e => e.getTypeId() === startupEditorTypeID)) { return undefined; } options = { pinned: false, index: 0 }; } else { options = { pinned: false }; } - return instantiationService.createInstance(WelcomePage).openEditor(options); + return launchEditor.openEditor(options); } } return undefined; @@ -129,7 +137,7 @@ function isWelcomePageEnabled(configurationService: IConfigurationService, conte return welcomeEnabled.value; } } - return startupEditor.value === 'welcomePage' || startupEditor.value === 'readme' || startupEditor.value === 'welcomePageInEmptyWorkbench' && contextService.getWorkbenchState() === WorkbenchState.EMPTY; + return startupEditor.value === 'welcomePage' || startupEditor.value === 'gettingStarted' || startupEditor.value === 'readme' || startupEditor.value === 'welcomePageInEmptyWorkbench' && contextService.getWorkbenchState() === WorkbenchState.EMPTY; } export class WelcomePageAction extends Action { @@ -323,7 +331,7 @@ class WelcomePage extends Disposable { showOnStartup.setAttribute('checked', 'checked'); } showOnStartup.addEventListener('click', e => { - this.configurationService.updateValue(configurationKey, showOnStartup.checked ? 'welcomePage' : 'newUntitledFile', ConfigurationTarget.USER); + this.configurationService.updateValue(configurationKey, showOnStartup.checked ? 'welcomePage' : 'newUntitledFile'); }); const prodName = container.querySelector('.welcomePage .title .caption') as HTMLElement; @@ -637,10 +645,6 @@ export class WelcomeInputFactory implements IEditorInputFactory { // theming -export const buttonBackground = registerColor('welcomePage.buttonBackground', { dark: null, light: null, hc: null }, localize('welcomePage.buttonBackground', 'Background color for the buttons on the Welcome page.')); -export const buttonHoverBackground = registerColor('welcomePage.buttonHoverBackground', { dark: null, light: null, hc: null }, localize('welcomePage.buttonHoverBackground', 'Hover background color for the buttons on the Welcome page.')); -export const welcomePageBackground = registerColor('welcomePage.background', { light: null, dark: null, hc: null }, localize('welcomePage.background', 'Background color for the Welcome page.')); - registerThemingParticipant((theme, collector) => { const backgroundColor = theme.getColor(welcomePageBackground); if (backgroundColor) { diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePageColors.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePageColors.ts new file mode 100644 index 000000000..e87483cf4 --- /dev/null +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePageColors.ts @@ -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. + *--------------------------------------------------------------------------------------------*/ + +import { registerColor } from 'vs/platform/theme/common/colorRegistry'; +import { localize } from 'vs/nls'; + +// Seprate from main module to break dependency cycles between welcomePage and gettingStarted. +export const buttonBackground = registerColor('welcomePage.buttonBackground', { dark: null, light: null, hc: null }, localize('welcomePage.buttonBackground', 'Background color for the buttons on the Welcome page.')); +export const buttonHoverBackground = registerColor('welcomePage.buttonHoverBackground', { dark: null, light: null, hc: null }, localize('welcomePage.buttonHoverBackground', 'Hover background color for the buttons on the Welcome page.')); +export const welcomePageBackground = registerColor('welcomePage.background', { light: null, dark: null, hc: null }, localize('welcomePage.background', 'Background color for the Welcome page.')); diff --git a/src/vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.ts b/src/vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.ts index 6b2fa23f3..3cf7db6db 100644 --- a/src/vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.ts +++ b/src/vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; @@ -19,7 +19,6 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; export abstract class AbstractTelemetryOptOut implements IWorkbenchContribution { @@ -28,7 +27,6 @@ export abstract class AbstractTelemetryOptOut implements IWorkbenchContribution constructor( @IStorageService private readonly storageService: IStorageService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, @IOpenerService private readonly openerService: IOpenerService, @INotificationService private readonly notificationService: INotificationService, @IHostService private readonly hostService: IHostService, @@ -40,7 +38,6 @@ export abstract class AbstractTelemetryOptOut implements IWorkbenchContribution @IEnvironmentService private readonly environmentService: IEnvironmentService, @IJSONEditingService private readonly jsonEditingService: IJSONEditingService ) { - storageKeysSyncRegistryService.registerStorageKey({ key: AbstractTelemetryOptOut.TELEMETRY_OPT_OUT_SHOWN, version: 1 }); } protected async handleTelemetryOptOut(): Promise { @@ -53,7 +50,7 @@ export abstract class AbstractTelemetryOptOut implements IWorkbenchContribution return; // return early if meanwhile another window opened (we only show the opt-out once) } - this.storageService.store(AbstractTelemetryOptOut.TELEMETRY_OPT_OUT_SHOWN, true, StorageScope.GLOBAL); + this.storageService.store(AbstractTelemetryOptOut.TELEMETRY_OPT_OUT_SHOWN, true, StorageScope.GLOBAL, StorageTarget.USER); this.privacyUrl = this.productService.privacyStatementUrl || this.productService.telemetryOptOutUrl; @@ -165,7 +162,6 @@ export class BrowserTelemetryOptOut extends AbstractTelemetryOptOut { constructor( @IStorageService storageService: IStorageService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, @IOpenerService openerService: IOpenerService, @INotificationService notificationService: INotificationService, @IHostService hostService: IHostService, @@ -177,7 +173,7 @@ export class BrowserTelemetryOptOut extends AbstractTelemetryOptOut { @IEnvironmentService environmentService: IEnvironmentService, @IJSONEditingService jsonEditingService: IJSONEditingService ) { - super(storageService, storageKeysSyncRegistryService, openerService, notificationService, hostService, telemetryService, experimentService, configurationService, galleryService, productService, environmentService, jsonEditingService); + super(storageService, openerService, notificationService, hostService, telemetryService, experimentService, configurationService, galleryService, productService, environmentService, jsonEditingService); this.handleTelemetryOptOut(); } diff --git a/src/vs/workbench/contrib/welcome/telemetryOptOut/electron-sandbox/telemetryOptOut.ts b/src/vs/workbench/contrib/welcome/telemetryOptOut/electron-sandbox/telemetryOptOut.ts index 60c4fcfa3..81b28a108 100644 --- a/src/vs/workbench/contrib/welcome/telemetryOptOut/electron-sandbox/telemetryOptOut.ts +++ b/src/vs/workbench/contrib/welcome/telemetryOptOut/electron-sandbox/telemetryOptOut.ts @@ -16,13 +16,11 @@ import { AbstractTelemetryOptOut } from 'vs/workbench/contrib/welcome/telemetryO import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; export class NativeTelemetryOptOut extends AbstractTelemetryOptOut { constructor( @IStorageService storageService: IStorageService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, @IOpenerService openerService: IOpenerService, @INotificationService notificationService: INotificationService, @IHostService hostService: IHostService, @@ -35,7 +33,7 @@ export class NativeTelemetryOptOut extends AbstractTelemetryOptOut { @IJSONEditingService jsonEditingService: IJSONEditingService, @INativeHostService private readonly nativeHostService: INativeHostService ) { - super(storageService, storageKeysSyncRegistryService, openerService, notificationService, hostService, telemetryService, experimentService, configurationService, galleryService, productService, environmentService, jsonEditingService); + super(storageService, openerService, notificationService, hostService, telemetryService, experimentService, configurationService, galleryService, productService, environmentService, jsonEditingService); this.handleTelemetryOptOut(); } diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts index 04a605f1c..7db8ceec1 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts @@ -40,7 +40,7 @@ export class EditorWalkThroughAction extends Action { public run(): Promise { const input = this.instantiationService.createInstance(WalkThroughInput, inputOptions); - return this.editorService.openEditor(input, { pinned: true }) + return this.editorService.openEditor(input, { pinned: true, override: false }) .then(() => void (0)); } } diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.css b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.css index e2a46b379..0bdf1be5c 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.css +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.css @@ -7,6 +7,7 @@ box-sizing: border-box; padding: 10px 20px; line-height: 22px; + height: inherit; user-select: initial; -webkit-user-select: initial; } diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index f98b00507..5f159a168 100644 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -45,12 +45,16 @@ import { ConfigurationCache } from 'vs/workbench/services/configuration/electron import { SignService } from 'vs/platform/sign/node/signService'; import { ISignService } from 'vs/platform/sign/common/sign'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; -import { basename } from 'vs/base/common/resources'; +import { basename } from 'vs/base/common/path'; import { IProductService } from 'vs/platform/product/common/productService'; import product from 'vs/platform/product/common/product'; import { NativeLogService } from 'vs/workbench/services/log/electron-browser/logService'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { NativeHostService } from 'vs/platform/native/electron-sandbox/nativeHostService'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService'; +import { KeyboardLayoutService } from 'vs/workbench/services/keybinding/electron-sandbox/nativeKeyboardLayout'; +import { IKeyboardLayoutService } from 'vs/platform/keyboardLayout/common/keyboardLayout'; class DesktopMain extends Disposable { @@ -232,8 +236,11 @@ class DesktopMain extends Disposable { fileService.registerProvider(Schemas.file, diskFileSystemProvider); // User Data Provider - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(this.environmentService.appSettingsHome, this.configuration.backupPath ? URI.file(this.configuration.backupPath) : undefined, diskFileSystemProvider, this.environmentService, logService)); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.userData, logService)); + // Uri Identity + const uriIdentityService = new UriIdentityService(fileService); + serviceCollection.set(IUriIdentityService, uriIdentityService); // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // @@ -257,7 +264,7 @@ class DesktopMain extends Disposable { const payload = await this.resolveWorkspaceInitializationPayload(); const services = await Promise.all([ - this.createWorkspaceService(payload, fileService, remoteAgentService, logService).then(service => { + this.createWorkspaceService(payload, fileService, remoteAgentService, uriIdentityService, logService).then(service => { // Workspace serviceCollection.set(IWorkspaceContextService, service); @@ -273,6 +280,14 @@ class DesktopMain extends Disposable { // Storage serviceCollection.set(IStorageService, service); + return service; + }), + + this.createKeyboardLayoutService(logService, mainProcessService).then(service => { + + // KeyboardLayout + serviceCollection.set(IKeyboardLayoutService, service); + return service; }) ]); @@ -310,8 +325,8 @@ class DesktopMain extends Disposable { // Fallback to empty workspace if we have no payload yet. if (!workspaceInitializationPayload) { let id: string; - if (this.environmentService.backupWorkspaceHome) { - id = basename(this.environmentService.backupWorkspaceHome); // we know the backupPath must be a unique path so we leverage its name as workspace ID + if (this.configuration.backupPath) { + id = basename(this.configuration.backupPath); // we know the backupPath must be a unique path so we leverage its name as workspace ID } else if (this.environmentService.isExtensionDevelopment) { id = 'ext-dev'; // extension development window never stores backups and is a singleton } else { @@ -334,10 +349,12 @@ class DesktopMain extends Disposable { } catch (error) { onUnexpectedError(error); } + return; } private async createHash(resource: URI): Promise { + // Return early the folder is not local if (resource.scheme !== Schemas.file) { return createHash('md5').update(resource.toString()).digest('hex'); @@ -362,8 +379,8 @@ class DesktopMain extends Disposable { return createHash('md5').update(resource.fsPath).update(ctime ? String(ctime) : '').digest('hex'); } - private async createWorkspaceService(payload: IWorkspaceInitializationPayload, fileService: FileService, remoteAgentService: IRemoteAgentService, logService: ILogService): Promise { - const workspaceService = new WorkspaceService({ remoteAuthority: this.environmentService.remoteAuthority, configurationCache: new ConfigurationCache(this.environmentService) }, this.environmentService, fileService, remoteAgentService, logService); + private async createWorkspaceService(payload: IWorkspaceInitializationPayload, fileService: FileService, remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService): Promise { + const workspaceService = new WorkspaceService({ remoteAuthority: this.environmentService.remoteAuthority, configurationCache: new ConfigurationCache(this.environmentService) }, this.environmentService, fileService, remoteAgentService, uriIdentityService, logService); try { await workspaceService.initialize(payload); @@ -393,6 +410,20 @@ class DesktopMain extends Disposable { } } + private async createKeyboardLayoutService(logService: ILogService, mainProcessService: IMainProcessService): Promise { + const keyboardLayoutService = new KeyboardLayoutService(mainProcessService); + + try { + await keyboardLayoutService.initialize(); + + return keyboardLayoutService; + } catch (error) { + onUnexpectedError(error); + logService.error(error); + + return keyboardLayoutService; + } + } } export function main(configuration: INativeWorkbenchConfiguration): Promise { diff --git a/src/vs/workbench/electron-sandbox/actions/developerActions.ts b/src/vs/workbench/electron-sandbox/actions/developerActions.ts index 1753e42e6..8daa9ffde 100644 --- a/src/vs/workbench/electron-sandbox/actions/developerActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/developerActions.ts @@ -42,6 +42,9 @@ export class ConfigureRuntimeArgumentsAction extends Action { } async run(): Promise { - await this.editorService.openEditor({ resource: this.environmentService.argvResource }); + await this.editorService.openEditor({ + resource: this.environmentService.argvResource, + options: { pinned: true } + }); } } diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 8e5d43e83..63bc327fa 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -211,16 +211,17 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; }, 'window.restoreWindows': { 'type': 'string', - 'enum': ['all', 'folders', 'one', 'none'], + 'enum': ['preserve', 'all', 'folders', 'one', 'none'], 'enumDescriptions': [ - nls.localize('window.reopenFolders.all', "Reopen all windows."), - nls.localize('window.reopenFolders.folders', "Reopen all folders. Empty workspaces will not be restored."), - nls.localize('window.reopenFolders.one', "Reopen the last active window."), - nls.localize('window.reopenFolders.none', "Never reopen a window. Always start with an empty one.") + nls.localize('window.reopenFolders.preserve', "Always reopen all windows. If a folder or workspace is opened (e.g. from the command line) it opens as a new window unless it was opened before. If files are opened they will open in one of the restored windows."), + nls.localize('window.reopenFolders.all', "Reopen all windows unless a folder, workspace or file is opened (e.g. from the command line)."), + nls.localize('window.reopenFolders.folders', "Reopen all windows that had folders or workspaces opened unless a folder, workspace or file is opened (e.g. from the command line)."), + nls.localize('window.reopenFolders.one', "Reopen the last active window unless a folder, workspace or file is opened (e.g. from the command line)."), + nls.localize('window.reopenFolders.none', "Never reopen a window. Unless a folder or workspace is opened (e.g. from the command line), an empty window will appear.") ], 'default': 'all', 'scope': ConfigurationScope.APPLICATION, - 'description': nls.localize('restoreWindows', "Controls how windows are being reopened after a restart.") + 'description': nls.localize('restoreWindows', "Controls how windows are being reopened after starting for the first time. This setting has no effect when the application is already running.") }, 'window.restoreFullscreen': { 'type': 'boolean', @@ -295,7 +296,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; }, 'window.enableExperimentalProxyLoginDialog': { 'type': 'boolean', - 'default': false, + 'default': true, 'scope': ConfigurationScope.APPLICATION, 'description': nls.localize('window.enableExperimentalProxyLoginDialog', "Enables a new login dialog for proxy authentication. Requires a restart to take effect."), } diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index 891df1710..eddd5ab64 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -34,6 +34,8 @@ import { NativeHostService } from 'vs/platform/native/electron-sandbox/nativeHos import { SimpleConfigurationService, simpleFileSystemProvider, SimpleLogService, SimpleRemoteAgentService, SimpleSignService, SimpleStorageService, SimpleNativeWorkbenchEnvironmentService, SimpleWorkspaceService } from 'vs/workbench/electron-sandbox/sandbox.simpleservices'; import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-sandbox/remoteAuthorityResolverService'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService'; class DesktopMain extends Disposable { @@ -205,7 +207,11 @@ class DesktopMain extends Disposable { fileService.registerProvider(Schemas.file, simpleFileSystemProvider); // User Data Provider - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(URI.file('user-home'), undefined, simpleFileSystemProvider, this.environmentService, logService)); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(Schemas.file, simpleFileSystemProvider, Schemas.userData, logService)); + + // Uri Identity + const uriIdentityService = new UriIdentityService(fileService); + serviceCollection.set(IUriIdentityService, uriIdentityService); const connection = remoteAgentService.getConnection(); if (connection) { diff --git a/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts b/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts new file mode 100644 index 000000000..5ed67ad69 --- /dev/null +++ b/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.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 { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IDialogHandler, IDialogResult, IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { ILogService } from 'vs/platform/log/common/log'; +import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IDialogsModel, IDialogViewItem } from 'vs/workbench/common/dialogs'; +import { BrowserDialogHandler } from 'vs/workbench/browser/parts/dialogs/dialogHandler'; +import { NativeDialogHandler } from 'vs/workbench/electron-sandbox/parts/dialogs/dialogHandler'; +import { DialogService } from 'vs/workbench/services/dialogs/common/dialogService'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; + +export class DialogHandlerContribution extends Disposable implements IWorkbenchContribution { + private nativeImpl: IDialogHandler; + private browserImpl: IDialogHandler; + + private model: IDialogsModel; + private currentDialog: IDialogViewItem | undefined; + + constructor( + @IConfigurationService private configurationService: IConfigurationService, + @IDialogService private dialogService: IDialogService, + @ILogService logService: ILogService, + @ILayoutService layoutService: ILayoutService, + @IThemeService themeService: IThemeService, + @IKeybindingService keybindingService: IKeybindingService, + @IProductService productService: IProductService, + @IClipboardService clipboardService: IClipboardService, + @INativeHostService nativeHostService: INativeHostService + ) { + super(); + + this.browserImpl = new BrowserDialogHandler(logService, layoutService, themeService, keybindingService, productService, clipboardService); + this.nativeImpl = new NativeDialogHandler(logService, nativeHostService, productService, clipboardService); + + this.model = (this.dialogService as DialogService).model; + + this._register(this.model.onDidShowDialog(() => { + if (!this.currentDialog) { + this.processDialogs(); + } + })); + + this.processDialogs(); + } + + private async processDialogs(): Promise { + while (this.model.dialogs.length) { + this.currentDialog = this.model.dialogs[0]; + + let result: IDialogResult | undefined = undefined; + + // Confirm + if (this.currentDialog.args.confirmArgs) { + const args = this.currentDialog.args.confirmArgs; + result = this.useCustomDialog ? await this.browserImpl.confirm(args.confirmation) : await this.nativeImpl.confirm(args.confirmation); + } + + // Input (custom only) + else if (this.currentDialog.args.inputArgs) { + const args = this.currentDialog.args.inputArgs; + result = await this.browserImpl.input(args.severity, args.message, args.buttons, args.inputs, args.options); + } + + // Message + else if (this.currentDialog.args.showArgs) { + const args = this.currentDialog.args.showArgs; + result = this.useCustomDialog ? + await this.browserImpl.show(args.severity, args.message, args.buttons, args.options) : + await this.nativeImpl.show(args.severity, args.message, args.buttons, args.options); + } + + // About + else { + await this.nativeImpl.about(); + } + + this.currentDialog.close(result); + this.currentDialog = undefined; + } + } + + private get useCustomDialog(): boolean { + return this.configurationService.getValue('window.dialogStyle') === 'custom'; + } +} + +const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchRegistry.registerWorkbenchContribution(DialogHandlerContribution, LifecyclePhase.Starting); diff --git a/src/vs/workbench/services/dialogs/electron-sandbox/dialogService.ts b/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts similarity index 74% rename from src/vs/workbench/services/dialogs/electron-sandbox/dialogService.ts rename to src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts index c6e5fb62d..f56a97383 100644 --- a/src/vs/workbench/services/dialogs/electron-sandbox/dialogService.ts +++ b/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts @@ -4,22 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import Severity from 'vs/base/common/severity'; -import { isLinux, isWindows } from 'vs/base/common/platform'; -import { mnemonicButtonLabel } from 'vs/base/common/labels'; -import { IDialogService, IConfirmation, IConfirmationResult, IDialogOptions, IShowResult, IInputResult, IInput } from 'vs/platform/dialogs/common/dialogs'; -import { DialogService as HTMLDialogService } from 'vs/workbench/services/dialogs/browser/dialogService'; -import { ILogService } from 'vs/platform/log/common/log'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; -import { MessageBoxOptions } from 'vs/base/parts/sandbox/common/electronTypes'; import { fromNow } from 'vs/base/common/date'; +import { mnemonicButtonLabel } from 'vs/base/common/labels'; +import { isLinux, isWindows } from 'vs/base/common/platform'; +import Severity from 'vs/base/common/severity'; +import { MessageBoxOptions } from 'vs/base/parts/sandbox/common/electronTypes'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IConfirmation, IConfirmationResult, IDialogHandler, IDialogOptions, IShowResult } from 'vs/platform/dialogs/common/dialogs'; +import { ILogService } from 'vs/platform/log/common/log'; +import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; +import { IProductService } from 'vs/platform/product/common/productService'; import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; interface IMassagedMessageBoxOptions { @@ -37,59 +31,7 @@ interface IMassagedMessageBoxOptions { buttonIndexMap: number[]; } -export class DialogService implements IDialogService { - - declare readonly _serviceBrand: undefined; - - private nativeImpl: IDialogService; - private customImpl: IDialogService; - - constructor( - @IConfigurationService private configurationService: IConfigurationService, - @ILogService logService: ILogService, - @ILayoutService layoutService: ILayoutService, - @IThemeService themeService: IThemeService, - @IKeybindingService keybindingService: IKeybindingService, - @IProductService productService: IProductService, - @IClipboardService clipboardService: IClipboardService, - @INativeHostService nativeHostService: INativeHostService - ) { - this.customImpl = new HTMLDialogService(logService, layoutService, themeService, keybindingService, productService, clipboardService); - this.nativeImpl = new NativeDialogService(logService, nativeHostService, productService, clipboardService); - } - - private get useCustomDialog(): boolean { - return this.configurationService.getValue('window.dialogStyle') === 'custom'; - } - - confirm(confirmation: IConfirmation): Promise { - if (this.useCustomDialog) { - return this.customImpl.confirm(confirmation); - } - - return this.nativeImpl.confirm(confirmation); - } - - show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise { - if (this.useCustomDialog) { - return this.customImpl.show(severity, message, buttons, options); - } - - return this.nativeImpl.show(severity, message, buttons, options); - } - - input(severity: Severity, message: string, buttons: string[], inputs: IInput[], options?: IDialogOptions): Promise { - return this.customImpl.input(severity, message, buttons, inputs, options); - } - - about(): Promise { - return this.nativeImpl.about(); - } -} - -class NativeDialogService implements IDialogService { - - declare readonly _serviceBrand: undefined; +export class NativeDialogHandler implements IDialogHandler { constructor( @ILogService private readonly logService: ILogService, @@ -265,4 +207,3 @@ class NativeDialogService implements IDialogService { } } -registerSingleton(IDialogService, DialogService, true); diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts index 7002cb288..ea147a06b 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts @@ -9,7 +9,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { ILabelService } from 'vs/platform/label/common/label'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { isMacintosh, isWindows, isLinux } from 'vs/base/common/platform'; import { IMenuService } from 'vs/platform/actions/common/actions'; @@ -32,11 +32,23 @@ export class TitlebarPart extends BrowserTitleBarPart { private dragRegion: HTMLElement | undefined; private resizer: HTMLElement | undefined; + private getMacTitlebarSize() { + const osVersion = this.environmentService.os.release; + if (parseFloat(osVersion) >= 20) { // Big Sur increases title bar height + return 28; + } + + return 22; + } + + get minimumHeight(): number { return isMacintosh ? this.getMacTitlebarSize() / getZoomFactor() : super.minimumHeight; } + get maximumHeight(): number { return this.minimumHeight; } + constructor( @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService protected readonly configurationService: IConfigurationService, @IEditorService editorService: IEditorService, - @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, + @INativeWorkbenchEnvironmentService protected readonly environmentService: INativeWorkbenchEnvironmentService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @@ -206,7 +218,7 @@ export class TitlebarPart extends BrowserTitleBarPart { updateLayout(dimension: DOM.Dimension): void { this.lastLayoutDimensions = dimension; - if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { + if (getTitleBarStyle(this.configurationService) === 'custom') { // Only prevent zooming behavior on macOS or when the menubar is not visible if (isMacintosh || this.currentMenubarVisibility === 'hidden') { this.title.style.zoom = `${1 / getZoomFactor()}`; diff --git a/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts b/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts index ed4f26407..ada7a23fa 100644 --- a/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts +++ b/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts @@ -25,14 +25,8 @@ import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backu import { ITextSnapshot } from 'vs/editor/common/model'; import { IExtensionService, NullExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ClassifiedEvent, GDPRClassification, StrictPropertyChecker } from 'vs/platform/telemetry/common/gdprTypings'; -import { IKeyboardLayoutInfo, IKeymapService, ILinuxKeyboardLayoutInfo, ILinuxKeyboardMapping, IMacKeyboardLayoutInfo, IMacKeyboardMapping, IWindowsKeyboardLayoutInfo, IWindowsKeyboardMapping } from 'vs/workbench/services/keybinding/common/keymapInfo'; -import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; -import { DispatchConfig } from 'vs/workbench/services/keybinding/common/dispatchConfig'; -import { IKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper'; -import { ChordKeybinding, ResolvedKeybinding, SimpleKeybinding } from 'vs/base/common/keyCodes'; -import { ScanCodeBinding } from 'vs/base/common/scanCode'; -import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; -import { isWindows, OS } from 'vs/base/common/platform'; +import { IKeyboardLayoutService } from 'vs/platform/keyboardLayout/common/keyboardLayout'; +import { isWindows } from 'vs/base/common/platform'; import { IWebviewService, WebviewContentOptions, WebviewElement, WebviewExtensionDescription, WebviewIcons, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { AbstractTextFileService } from 'vs/workbench/services/textfile/browser/textFileService'; @@ -50,7 +44,7 @@ import { CustomTask, ContributedTask, InMemoryTask, TaskRunSource, ConfiguringTa import { TaskSystemInfo } from 'vs/workbench/contrib/tasks/common/taskSystem'; import { IExtensionTipsService, IConfigBasedExtensionTip, IExecutableBasedExtensionTip, IWorkspaceTips } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkspaceTagsService, Tags } from 'vs/workbench/contrib/tags/common/workspaceTags'; -import { AsbtractOutputChannelModelService, IOutputChannelModelService } from 'vs/workbench/services/output/common/outputChannelModel'; +import { AbstractOutputChannelModelService, IOutputChannelModelService } from 'vs/workbench/contrib/output/common/outputChannelModel'; import { joinPath } from 'vs/base/common/resources'; import { VSBuffer } from 'vs/base/common/buffer'; import { IIntegrityService, IntegrityTestResult } from 'vs/workbench/services/integrity/common/integrity'; @@ -59,7 +53,9 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { IExtensionHostDebugParams } from 'vs/platform/environment/common/environment'; import type { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; import { Schemas } from 'vs/base/common/network'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; +import { BrowserKeyboardLayoutService } from 'vs/workbench/services/keybinding/browser/keyboardLayoutService'; +import { TerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminalInstanceService'; +import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; //#region Environment @@ -87,12 +83,10 @@ export class SimpleNativeWorkbenchEnvironmentService implements INativeWorkbench get tmpDir(): URI { return joinPath(this.userRoamingDataHome, 'tmp'); } get logsPath(): string { return joinPath(this.userRoamingDataHome, 'logs').path; } - get backupWorkspaceHome(): URI { return joinPath(this.userRoamingDataHome, 'Backups', 'workspace'); } - updateBackupPath(newPath: string | undefined): void { } - sessionId = this.configuration.sessionId; machineId = this.configuration.machineId; remoteAuthority = this.configuration.remoteAuthority; + os = { release: 'unknown' }; options?: IWorkbenchConstructionOptions | undefined; logExtensionHostCommunication?: boolean | undefined; @@ -127,7 +121,7 @@ export class SimpleNativeWorkbenchEnvironmentService implements INativeWorkbench sharedIPCHandle: string = undefined!; - extensionsPath?: string | undefined; + extensionsPath: string = undefined!; extensionsDownloadPath: string = undefined!; builtinExtensionsPath: string = undefined!; @@ -433,6 +427,7 @@ export class SimpleRemoteAgentService implements IRemoteAgentService { async getRawEnvironment(): Promise { return null; } async scanExtensions(skipExtensions?: ExtensionIdentifier[]): Promise { return []; } async scanSingleExtension(extensionLocation: URI, isBuiltin: boolean): Promise { return null; } + async whenExtensionsReady(): Promise { } } //#endregion @@ -499,37 +494,11 @@ registerSingleton(ITelemetryService, SimpleTelemetryService); //#endregion -//#region Keymap Service +//#region Keymap Service (borrowed from browser for now to enable keyboard access) -class SimpleKeyboardMapper implements IKeyboardMapper { - dumpDebugInfo(): string { return ''; } - resolveKeybinding(keybinding: ChordKeybinding): ResolvedKeybinding[] { return []; } - resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding { - let keybinding = new SimpleKeybinding( - keyboardEvent.ctrlKey, - keyboardEvent.shiftKey, - keyboardEvent.altKey, - keyboardEvent.metaKey, - keyboardEvent.keyCode - ).toChord(); - return new USLayoutResolvedKeybinding(keybinding, OS); - } - resolveUserBinding(firstPart: (SimpleKeybinding | ScanCodeBinding)[]): ResolvedKeybinding[] { return []; } -} +class SimpleKeyboardLayoutService extends BrowserKeyboardLayoutService { } -class SimpleKeymapService implements IKeymapService { - - declare readonly _serviceBrand: undefined; - - onDidChangeKeyboardMapper = Event.None; - getKeyboardMapper(dispatchConfig: DispatchConfig): IKeyboardMapper { return new SimpleKeyboardMapper(); } - getCurrentKeyboardLayout(): (IWindowsKeyboardLayoutInfo & { isUserKeyboardLayout?: boolean | undefined; isUSStandard?: true | undefined; }) | (ILinuxKeyboardLayoutInfo & { isUserKeyboardLayout?: boolean | undefined; isUSStandard?: true | undefined; }) | (IMacKeyboardLayoutInfo & { isUserKeyboardLayout?: boolean | undefined; isUSStandard?: true | undefined; }) | null { return null; } - getAllKeyboardLayouts(): IKeyboardLayoutInfo[] { return []; } - getRawKeyboardMapping(): IWindowsKeyboardMapping | ILinuxKeyboardMapping | IMacKeyboardMapping | null { return null; } - validateCurrentKeyboardMapping(keyboardEvent: IKeyboardEvent): void { } -} - -registerSingleton(IKeymapService, SimpleKeymapService); +registerSingleton(IKeyboardLayoutService, SimpleKeyboardLayoutService); //#endregion @@ -683,7 +652,7 @@ registerSingleton(IUserDataAutoSyncService, SimpleUserDataAutoSyncAccountService //#region User Data Sync Store Management -class SimpleIUserDataSyncStoreManagementService implements IUserDataSyncStoreManagementService { +class SimpleUserDataSyncStoreManagementService implements IUserDataSyncStoreManagementService { declare readonly _serviceBrand: undefined; @@ -696,36 +665,10 @@ class SimpleIUserDataSyncStoreManagementService implements IUserDataSyncStoreMan async getPreviousUserDataSyncStore(): Promise { return undefined; } } -registerSingleton(IUserDataSyncStoreManagementService, SimpleIUserDataSyncStoreManagementService); +registerSingleton(IUserDataSyncStoreManagementService, SimpleUserDataSyncStoreManagementService); //#endregion -//#region IStorageKeysSyncRegistryService - -class SimpleIStorageKeysSyncRegistryService implements IStorageKeysSyncRegistryService { - - declare readonly _serviceBrand: undefined; - - onDidChangeStorageKeys = Event.None; - - storageKeys = []; - - registerStorageKey(): void { } - - onDidChangeExtensionStorageKeys = Event.None; - - extensionsStorageKeys = []; - - getExtensioStorageKeys() { return undefined; } - - registerExtensionStorageKeys(): void { } -} - -registerSingleton(IStorageKeysSyncRegistryService, SimpleIStorageKeysSyncRegistryService); - -//#endregion - - //#region Task class SimpleTaskService implements ITaskService { @@ -754,6 +697,7 @@ class SimpleTaskService implements ITaskService { tryResolveTask(configuringTask: ConfiguringTask): Promise { throw new Error('Method not implemented.'); } getTasksForGroup(group: string): Promise { throw new Error('Method not implemented.'); } getRecentlyUsedTasks(): LinkedMap { throw new Error('Method not implemented.'); } + removeRecentlyUsedTask(taskRecentlyUsedKey: string): void { throw new Error('Method not implemented.'); } migrateRecentTasks(tasks: Task[]): Promise { throw new Error('Method not implemented.'); } createSorter(): TaskSorter { throw new Error('Method not implemented.'); } getTaskDescription(task: CustomTask | ContributedTask | InMemoryTask | ConfiguringTask): string | undefined { throw new Error('Method not implemented.'); } @@ -798,7 +742,7 @@ class SimpleWorkspaceTagsService implements IWorkspaceTagsService { declare readonly _serviceBrand: undefined; async getTags(): Promise { return Object.create(null); } - getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): string | undefined { return undefined; } + async getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): Promise { return undefined; } async getHashedRemotesFromUri(workspaceUri: URI, stripEndingDotGit?: boolean): Promise { return []; } } @@ -809,7 +753,7 @@ registerSingleton(IWorkspaceTagsService, SimpleWorkspaceTagsService); //#region Output Channel -class SimpleOutputChannelModelService extends AsbtractOutputChannelModelService { +class SimpleOutputChannelModelService extends AbstractOutputChannelModelService { declare readonly _serviceBrand: undefined; } @@ -832,3 +776,9 @@ class SimpleIntegrityService implements IIntegrityService { registerSingleton(IIntegrityService, SimpleIntegrityService); //#endregion + +//#region Terminal Instance + +class SimpleTerminalInstanceService extends TerminalInstanceService { } + +registerSingleton(ITerminalInstanceService, SimpleTerminalInstanceService); diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 6f294f3c5..37afcaaf5 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -32,7 +32,7 @@ import { IWorkspaceFolderCreationData, IWorkspacesService } from 'vs/platform/wo import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity'; import { isWindows, isMacintosh } from 'vs/base/common/platform'; import { IProductService } from 'vs/platform/product/common/productService'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { INotificationService, IPromptChoice, NeverShowAgainScope, Severity } from 'vs/platform/notification/common/notification'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; @@ -43,7 +43,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { MenubarControl } from '../browser/parts/titlebar/menubarControl'; import { ILabelService } from 'vs/platform/label/common/label'; import { IUpdateService } from 'vs/platform/update/common/update'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IPreferencesService } from '../services/preferences/common/preferences'; import { IMenubarData, IMenubarMenu, IMenubarKeybinding, IMenubarMenuItemSubmenu, IMenubarMenuItemAction, MenubarMenuItem } from 'vs/platform/menubar/common/menubar'; import { IMenubarService } from 'vs/platform/menubar/electron-sandbox/menubar'; @@ -59,7 +59,6 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { Event } from 'vs/base/common/event'; -import { clearAllFontInfos } from 'vs/editor/browser/config/configuration'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IAddressProvider, IAddress } from 'vs/platform/remote/common/remoteAgentConnection'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -185,14 +184,29 @@ export class NativeWindow extends Disposable { ipcRenderer.on('vscode:addFolders', (event: unknown, request: IAddFoldersRequest) => this.onAddFoldersRequest(request)); // Message support - ipcRenderer.on('vscode:showInfoMessage', (event: unknown, message: string) => { - this.notificationService.info(message); - }); + ipcRenderer.on('vscode:showInfoMessage', (event: unknown, message: string) => this.notificationService.info(message)); - // Display change events - ipcRenderer.on('vscode:displayChanged', () => { - clearAllFontInfos(); - }); + // Shell Environment Issue Notifications + const choices: IPromptChoice[] = [{ + label: nls.localize('learnMode', "Learn More"), + run: () => this.openerService.open('https://go.microsoft.com/fwlink/?linkid=2149667') + }]; + + ipcRenderer.on('vscode:showShellEnvSlowWarning', () => this.notificationService.prompt( + Severity.Warning, + nls.localize('shellEnvSlowWarning', "Resolving your shell environment is taking very long. Please review your shell configuration."), + choices, + { + sticky: true, + neverShowAgain: { id: 'ignoreShellEnvSlowWarning', scope: NeverShowAgainScope.GLOBAL } + } + )); + + ipcRenderer.on('vscode:showShellEnvTimeoutError', () => this.notificationService.prompt( + Severity.Error, + nls.localize('shellEnvTimeoutError', "Unable to resolve your shell environment in a reasonable time. Please review your shell configuration."), + choices + )); // Fullscreen Events ipcRenderer.on('vscode:enterFullScreen', async () => { @@ -237,7 +251,7 @@ export class NativeWindow extends Disposable { // Update state based on checkbox if (result.checkboxChecked) { - this.storageService.store(NativeWindow.REMEMBER_PROXY_CREDENTIALS_KEY, true, StorageScope.GLOBAL); + this.storageService.store(NativeWindow.REMEMBER_PROXY_CREDENTIALS_KEY, true, StorageScope.GLOBAL, StorageTarget.MACHINE); } else { this.storageService.remove(NativeWindow.REMEMBER_PROXY_CREDENTIALS_KEY, StorageScope.GLOBAL); } @@ -286,7 +300,7 @@ export class NativeWindow extends Disposable { } // Maximize/Restore on doubleclick (for macOS custom title) - if (isMacintosh && getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { + if (isMacintosh && getTitleBarStyle(this.configurationService) === 'custom') { const titlePart = assertIsDefined(this.layoutService.getContainer(Parts.TITLEBAR_PART)); this._register(DOM.addDisposableListener(titlePart, DOM.EventType.DBLCLICK, e => { @@ -402,7 +416,7 @@ export class NativeWindow extends Disposable { this.customTitleContextMenuDisposable.clear(); // Provide new menu if a file is opened and we are on a custom title - if (!filePath || getTitleBarStyle(this.configurationService, this.environmentService) !== 'custom') { + if (!filePath || getTitleBarStyle(this.configurationService) !== 'custom') { return; } @@ -434,7 +448,7 @@ export class NativeWindow extends Disposable { private create(): void { // Native menu controller - if (isMacintosh || getTitleBarStyle(this.configurationService, this.environmentService) === 'native') { + if (isMacintosh || getTitleBarStyle(this.configurationService) === 'native') { this._register(this.instantiationService.createInstance(NativeMenubarControl)); } diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 74fa84fa6..f28d3149c 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -14,7 +14,7 @@ import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IProductService } from 'vs/platform/product/common/productService'; import { isString } from 'vs/base/common/types'; @@ -375,11 +375,11 @@ export class AuthenticationService extends Disposable implements IAuthentication const allowList = readAllowedExtensions(storageService, providerId, session.account.label); if (!allowList.find(allowed => allowed.id === extensionId)) { allowList.push({ id: extensionId, name: extensionName }); - storageService.store(`${providerId}-${session.account.label}`, JSON.stringify(allowList), StorageScope.GLOBAL); + storageService.store(`${providerId}-${session.account.label}`, JSON.stringify(allowList), StorageScope.GLOBAL, StorageTarget.USER); } // And also set it as the preferred account for the extension - storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.GLOBAL); + storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.GLOBAL, StorageTarget.MACHINE); } }); diff --git a/src/vs/workbench/services/backup/browser/backupFileService.ts b/src/vs/workbench/services/backup/browser/backupFileService.ts new file mode 100644 index 000000000..afd98b6c7 --- /dev/null +++ b/src/vs/workbench/services/backup/browser/backupFileService.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 { IFileService } from 'vs/platform/files/common/files'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { ILogService } from 'vs/platform/log/common/log'; +import { BackupFileService } from 'vs/workbench/services/backup/common/backupFileService'; +import { hash } from 'vs/base/common/hash'; +import { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { joinPath } from 'vs/base/common/resources'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; + +export class BrowserBackupFileService extends BackupFileService { + + declare readonly _serviceBrand: undefined; + + constructor( + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IFileService fileService: IFileService, + @ILogService logService: ILogService + ) { + super(joinPath(environmentService.userRoamingDataHome, 'Backups', contextService.getWorkspace().id), fileService, logService); + } + + protected hashPath(resource: URI): string { + const str = resource.scheme === Schemas.file || resource.scheme === Schemas.untitled ? resource.fsPath : resource.toString(); + + return hash(str).toString(16); + } +} + +registerSingleton(IBackupFileService, BrowserBackupFileService); diff --git a/src/vs/workbench/services/backup/common/backupFileService.ts b/src/vs/workbench/services/backup/common/backupFileService.ts index 5049d8f6d..16546d87a 100644 --- a/src/vs/workbench/services/backup/common/backupFileService.ts +++ b/src/vs/workbench/services/backup/common/backupFileService.ts @@ -6,7 +6,6 @@ import { join } from 'vs/base/common/path'; import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { hash } from 'vs/base/common/hash'; import { coalesce } from 'vs/base/common/arrays'; import { equals, deepClone } from 'vs/base/common/objects'; import { ResourceQueue } from 'vs/base/common/async'; @@ -15,8 +14,6 @@ import { IFileService, FileOperationError, FileOperationResult } from 'vs/platfo import { ITextSnapshot } from 'vs/editor/common/model'; import { createTextBufferFactoryFromStream, createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; import { ResourceMap } from 'vs/base/common/map'; -import { Schemas } from 'vs/base/common/network'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { VSBuffer } from 'vs/base/common/buffer'; import { TextSnapshotReadable, stringToSnapshot } from 'vs/workbench/services/textfile/common/textfiles'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -108,42 +105,36 @@ export class BackupFilesModel implements IBackupFilesModel { } } -export class BackupFileService implements IBackupFileService { +export abstract class BackupFileService implements IBackupFileService { declare readonly _serviceBrand: undefined; private impl: BackupFileServiceImpl | InMemoryBackupFileService; constructor( - @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService, + backupWorkspaceHome: URI | undefined, @IFileService protected fileService: IFileService, @ILogService private readonly logService: ILogService ) { - this.impl = this.initialize(); + this.impl = this.initialize(backupWorkspaceHome); } - protected hashPath(resource: URI): string { - const str = resource.scheme === Schemas.file || resource.scheme === Schemas.untitled ? resource.fsPath : resource.toString(); + protected abstract hashPath(resource: URI): string; - return hash(str).toString(16); - } - - private initialize(): BackupFileServiceImpl | InMemoryBackupFileService { - const backupWorkspaceResource = this.environmentService.backupWorkspaceHome; - if (backupWorkspaceResource) { - return new BackupFileServiceImpl(backupWorkspaceResource, this.hashPath, this.fileService, this.logService); + private initialize(backupWorkspaceHome: URI | undefined): BackupFileServiceImpl | InMemoryBackupFileService { + if (backupWorkspaceHome) { + return new BackupFileServiceImpl(backupWorkspaceHome, this.hashPath, this.fileService, this.logService); } return new InMemoryBackupFileService(this.hashPath); } - reinitialize(): void { + reinitialize(backupWorkspaceHome: URI | undefined): void { // Re-init implementation (unless we are running in-memory) if (this.impl instanceof BackupFileServiceImpl) { - const backupWorkspaceResource = this.environmentService.backupWorkspaceHome; - if (backupWorkspaceResource) { - this.impl.initialize(backupWorkspaceResource); + if (backupWorkspaceHome) { + this.impl.initialize(backupWorkspaceHome); } else { this.impl = new InMemoryBackupFileService(this.hashPath); } diff --git a/src/vs/workbench/services/backup/node/backupFileService.ts b/src/vs/workbench/services/backup/electron-browser/backupFileService.ts similarity index 53% rename from src/vs/workbench/services/backup/node/backupFileService.ts rename to src/vs/workbench/services/backup/electron-browser/backupFileService.ts index 74beddef4..efbbeec9e 100644 --- a/src/vs/workbench/services/backup/node/backupFileService.ts +++ b/src/vs/workbench/services/backup/electron-browser/backupFileService.ts @@ -3,14 +3,25 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BackupFileService as CommonBackupFileService } from 'vs/workbench/services/backup/common/backupFileService'; +import { BackupFileService } from 'vs/workbench/services/backup/common/backupFileService'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import * as crypto from 'crypto'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; -export class BackupFileService extends CommonBackupFileService { +export class NativeBackupFileService extends BackupFileService { + + constructor( + @INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, + @IFileService fileService: IFileService, + @ILogService logService: ILogService + ) { + super(environmentService.configuration.backupPath ? URI.file(environmentService.configuration.backupPath).with({ scheme: environmentService.userRoamingDataHome.scheme }) : undefined, fileService, logService); + } protected hashPath(resource: URI): string { return hashPath(resource); @@ -26,4 +37,4 @@ export function hashPath(resource: URI): string { return crypto.createHash('md5').update(str).digest('hex'); } -registerSingleton(IBackupFileService, BackupFileService); +registerSingleton(IBackupFileService, NativeBackupFileService); diff --git a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts index 0e00c5a49..1835f2775 100644 --- a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts +++ b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts @@ -23,7 +23,7 @@ import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemPro import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { IFileService } from 'vs/platform/files/common/files'; -import { hashPath, BackupFileService } from 'vs/workbench/services/backup/node/backupFileService'; +import { hashPath, NativeBackupFileService } from 'vs/workbench/services/backup/electron-browser/backupFileService'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; import { VSBuffer } from 'vs/base/common/buffer'; import { TestWorkbenchConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices'; @@ -53,7 +53,7 @@ class TestWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService } } -export class NodeTestBackupFileService extends BackupFileService { +export class NodeTestBackupFileService extends NativeBackupFileService { readonly fileService: IFileService; @@ -67,7 +67,7 @@ export class NodeTestBackupFileService extends BackupFileService { const fileService = new FileService(logService); const diskFileSystemProvider = new DiskFileSystemProvider(logService); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, URI.file(workspaceBackupPath), diskFileSystemProvider, environmentService, logService)); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.userData, logService)); super(environmentService, fileService, logService); diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 717a91dd1..973ef52ff 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -7,7 +7,7 @@ import { URI } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import { Event, Emitter } from 'vs/base/common/event'; import * as errors from 'vs/base/common/errors'; -import { Disposable, IDisposable, dispose, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, dispose, toDisposable, MutableDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; import { FileChangeType, FileChangesEvent, IFileService, whenProviderRegistered, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { ConfigurationModel, ConfigurationModelParser, UserSettings } from 'vs/platform/configuration/common/configurationModels'; @@ -92,6 +92,7 @@ class FileServiceBasedConfiguration extends Disposable { ) { super(); this.allResources = [...this.settingsResources, ...this.standAloneConfigurationResources.map(([, resource]) => resource)]; + this._register(combinedDisposable(...this.allResources.map(resource => this.fileService.watch(resources.dirname(resource))))); this._folderSettingsModelParser = new ConfigurationModelParser(name, this.scopes); this._standAloneConfigurations = []; this._cache = new ConfigurationModel(); diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 2e23db80a..7a1b2cd45 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -32,6 +32,7 @@ import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; class Workspace extends BaseWorkspace { initialized: boolean = false; @@ -55,6 +56,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic private readonly logService: ILogService; private readonly fileService: IFileService; + private readonly uriIdentityService: IUriIdentityService; protected readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); public readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; @@ -79,6 +81,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic environmentService: IWorkbenchEnvironmentService, fileService: IFileService, remoteAgentService: IRemoteAgentService, + uriIdentityService: IUriIdentityService, logService: ILogService, ) { super(); @@ -94,6 +97,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic this.defaultConfiguration = new DefaultConfigurationModel(); this.configurationCache = configurationCache; this.fileService = fileService; + this.uriIdentityService = uriIdentityService; this.logService = logService; this._configuration = new Configuration(this.defaultConfiguration, new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), this.workspace); this.cachedFolderConfigs = new ResourceMap(); @@ -285,13 +289,38 @@ export class WorkspaceService extends Disposable implements IConfigurationServic }); } - reloadConfiguration(folder?: IWorkspaceFolder, key?: string): Promise { - if (folder) { - return this.reloadWorkspaceFolderConfiguration(folder, key); + async reloadConfiguration(target?: ConfigurationTarget | IWorkspaceFolder): Promise { + if (target === undefined) { + const { local, remote } = await this.reloadUserConfiguration(); + await this.reloadWorkspaceConfiguration(); + await this.loadConfiguration(local, remote); + return; + } + + if (IWorkspaceFolder.isIWorkspaceFolder(target)) { + await this.reloadWorkspaceFolderConfiguration(target); + return; + } + + switch (target) { + case ConfigurationTarget.USER: + const { local, remote } = await this.reloadUserConfiguration(); + await this.loadConfiguration(local, remote); + return; + + case ConfigurationTarget.USER_LOCAL: + await this.reloadLocalUserConfiguration(); + return; + + case ConfigurationTarget.USER_REMOTE: + await this.reloadRemoteUserConfiguration(); + return; + + case ConfigurationTarget.WORKSPACE: + case ConfigurationTarget.WORKSPACE_FOLDER: + await this.reloadWorkspaceConfiguration(); + return; } - return this.reloadUserConfiguration() - .then(({ local, remote }) => this.reloadWorkspaceConfiguration() - .then(() => this.loadConfiguration(local, remote))); } inspect(key: string, overrides?: IConfigurationOverrides): IConfigurationValue { @@ -346,20 +375,20 @@ export class WorkspaceService extends Disposable implements IConfigurationServic const workspaceConfigPath = workspaceIdentifier.configPath; const workspaceFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), workspaceConfigPath); const workspaceId = workspaceIdentifier.id; - const workspace = new Workspace(workspaceId, workspaceFolders, workspaceConfigPath); + const workspace = new Workspace(workspaceId, workspaceFolders, workspaceConfigPath, uri => this.uriIdentityService.extUri.ignorePathCasing(uri)); workspace.initialized = this.workspaceConfiguration.initialized; return workspace; }); } private createSingleFolderWorkspace(singleFolder: ISingleFolderWorkspaceInitializationPayload): Promise { - const workspace = new Workspace(singleFolder.id, [toWorkspaceFolder(singleFolder.folder)]); + const workspace = new Workspace(singleFolder.id, [toWorkspaceFolder(singleFolder.folder)], null, uri => this.uriIdentityService.extUri.ignorePathCasing(uri)); workspace.initialized = true; return Promise.resolve(workspace); } private createEmptyWorkspace(emptyWorkspace: IEmptyWorkspaceInitializationPayload): Promise { - const workspace = new Workspace(emptyWorkspace.id); + const workspace = new Workspace(emptyWorkspace.id, [], null, uri => this.uriIdentityService.extUri.ignorePathCasing(uri)); workspace.initialized = true; return Promise.resolve(workspace); } @@ -465,10 +494,10 @@ export class WorkspaceService extends Disposable implements IConfigurationServic return new ConfigurationModel(); } - private reloadWorkspaceConfiguration(key?: string): Promise { + private reloadWorkspaceConfiguration(): Promise { const workbenchState = this.getWorkbenchState(); if (workbenchState === WorkbenchState.FOLDER) { - return this.onWorkspaceFolderConfigurationChanged(this.workspace.folders[0], key); + return this.onWorkspaceFolderConfigurationChanged(this.workspace.folders[0]); } if (workbenchState === WorkbenchState.WORKSPACE) { return this.workspaceConfiguration.reload().then(() => this.onWorkspaceConfigurationChanged()); @@ -476,8 +505,8 @@ export class WorkspaceService extends Disposable implements IConfigurationServic return Promise.resolve(undefined); } - private reloadWorkspaceFolderConfiguration(folder: IWorkspaceFolder, key?: string): Promise { - return this.onWorkspaceFolderConfigurationChanged(folder, key); + private reloadWorkspaceFolderConfiguration(folder: IWorkspaceFolder): Promise { + return this.onWorkspaceFolderConfigurationChanged(folder); } private loadConfiguration(userConfigurationModel: ConfigurationModel, remoteUserConfigurationModel: ConfigurationModel): Promise { @@ -592,7 +621,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic } } - private onWorkspaceFolderConfigurationChanged(folder: IWorkspaceFolder, key?: string): Promise { + private onWorkspaceFolderConfigurationChanged(folder: IWorkspaceFolder): Promise { return this.loadFolderConfigurations([folder]) .then(([folderConfiguration]) => { const previous = { data: this._configuration.toData(), workspace: this.workspace }; @@ -700,7 +729,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic case EditableConfigurationTarget.WORKSPACE_FOLDER: const workspaceFolder = overrides && overrides.resource ? this.workspace.getFolder(overrides.resource) : null; if (workspaceFolder) { - return this.reloadWorkspaceFolderConfiguration(workspaceFolder, key); + return this.reloadWorkspaceFolderConfiguration(workspaceFolder); } } return Promise.resolve(); diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index ee38ee006..71256640c 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -305,7 +305,7 @@ export class ConfigurationEditingService { } private openFile(resource: URI): void { - this.editorService.openEditor({ resource }); + this.editorService.openEditor({ resource, options: { pinned: true } }); } private reject(code: ConfigurationEditingErrorCode, target: EditableConfigurationTarget, operation: IConfigurationEditOperation): Promise { diff --git a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts index c0399d236..ef4976ce0 100644 --- a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts +++ b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts @@ -8,8 +8,9 @@ import { StandaloneConfigurationModelParser, Configuration } from 'vs/workbench/ import { ConfigurationModelParser, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { ResourceMap } from 'vs/base/common/map'; -import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { URI } from 'vs/base/common/uri'; +import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; suite('FolderSettingsModelParser', () => { diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts index f9f3adb53..f33ff6088 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts @@ -41,6 +41,8 @@ import { ConfigurationCache } from 'vs/workbench/services/configuration/electron import { KeybindingsEditingService, IKeybindingEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; +import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService'; +import { DisposableStore } from 'vs/base/common/lifecycle'; class TestWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService { @@ -61,6 +63,8 @@ suite('ConfigurationEditingService', () => { let globalTasksFile: string; let workspaceSettingsDir; + const disposables = new DisposableStore(); + suiteSetup(() => { const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); configurationRegistry.registerConfiguration({ @@ -83,9 +87,9 @@ suite('ConfigurationEditingService', () => { }); }); - setup(() => { - return setUpWorkspace() - .then(() => setUpServices()); + setup(async () => { + await setUpWorkspace(); + await setUpServices(); }); async function setUpWorkspace(): Promise { @@ -99,50 +103,36 @@ suite('ConfigurationEditingService', () => { return await mkdirp(workspaceSettingsDir, 493); } - function setUpServices(noWorkspace: boolean = false): Promise { - // Clear services if they are already created - clearServices(); - + async function setUpServices(noWorkspace: boolean = false): Promise { instantiationService = workbenchInstantiationService(); const environmentService = new TestWorkbenchEnvironmentService(URI.file(workspaceDir)); instantiationService.stub(IEnvironmentService, environmentService); const remoteAgentService = instantiationService.createInstance(RemoteAgentService); - const fileService = new FileService(new NullLogService()); - const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + const fileService = disposables.add(new FileService(new NullLogService())); + const diskFileSystemProvider = disposables.add(new DiskFileSystemProvider(new NullLogService())); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService())); + fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.userData, new NullLogService()))); instantiationService.stub(IFileService, fileService); instantiationService.stub(IRemoteAgentService, remoteAgentService); - const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService, new NullLogService()); + const workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); instantiationService.stub(IWorkspaceContextService, workspaceService); - return workspaceService.initialize(noWorkspace ? { id: '' } : { folder: URI.file(workspaceDir), id: createHash('md5').update(URI.file(workspaceDir).toString()).digest('hex') }).then(() => { - instantiationService.stub(IConfigurationService, workspaceService); - instantiationService.stub(IKeybindingEditingService, instantiationService.createInstance(KeybindingsEditingService)); - instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); - instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); - instantiationService.stub(ICommandService, CommandService); - testObject = instantiationService.createInstance(ConfigurationEditingService); - }); + await workspaceService.initialize(noWorkspace ? { id: '' } : { folder: URI.file(workspaceDir), id: createHash('md5').update(URI.file(workspaceDir).toString()).digest('hex') }); + instantiationService.stub(IConfigurationService, workspaceService); + instantiationService.stub(IKeybindingEditingService, instantiationService.createInstance(KeybindingsEditingService)); + instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); + instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); + instantiationService.stub(ICommandService, CommandService); + testObject = instantiationService.createInstance(ConfigurationEditingService); } teardown(() => { - clearServices(); + disposables.clear(); if (workspaceDir) { return rimraf(workspaceDir, RimRafMode.MOVE); } return undefined; }); - function clearServices(): void { - if (instantiationService) { - const configuraitonService = instantiationService.get(IConfigurationService); - if (configuraitonService) { - configuraitonService.dispose(); - } - instantiationService = null!; - } - } - test('errors cases - invalid key', () => { return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'unknown.key', value: 'value' }) .then(() => assert.fail('Should fail with ERROR_UNKNOWN_KEY'), diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index 57a0b0591..a7f455cd5 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -54,6 +54,7 @@ import product from 'vs/platform/product/common/product'; import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { Event } from 'vs/base/common/event'; +import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService'; class TestWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService { @@ -105,6 +106,7 @@ function setUpWorkspace(folders: string[]): Promise<{ parentDir: string, configP suite('WorkspaceContextService - Folder', () => { let workspaceName = `testWorkspace${uuid.generateUuid()}`, parentResource: string, workspaceResource: string, workspaceContextService: IWorkspaceContextService; + const disposables = new DisposableStore(); setup(() => { return setUpFolderWorkspace(workspaceName) @@ -112,19 +114,17 @@ suite('WorkspaceContextService - Folder', () => { parentResource = parentDir; workspaceResource = folderDir; const environmentService = new TestWorkbenchEnvironmentService(URI.file(parentDir)); - const fileService = new FileService(new NullLogService()); - const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + const fileService = disposables.add(new FileService(new NullLogService())); + const diskFileSystemProvider = disposables.add(new DiskFileSystemProvider(new NullLogService())); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, new DiskFileSystemProvider(new NullLogService()), environmentService, new NullLogService())); - workspaceContextService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, new RemoteAgentService(environmentService, { _serviceBrand: undefined, ...product }, new RemoteAuthorityResolverService(), new SignService(undefined), new NullLogService()), new NullLogService()); + fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService()), Schemas.userData, new NullLogService()))); + workspaceContextService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, new RemoteAgentService(environmentService, { _serviceBrand: undefined, ...product }, new RemoteAuthorityResolverService(), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService())); return (workspaceContextService).initialize(convertToWorkspacePayload(URI.file(folderDir))); }); }); teardown(() => { - if (workspaceContextService) { - (workspaceContextService).dispose(); - } + disposables.clear(); if (parentResource) { return pfs.rimraf(parentResource, pfs.RimRafMode.MOVE); } @@ -167,6 +167,7 @@ suite('WorkspaceContextService - Folder', () => { suite('WorkspaceContextService - Workspace', () => { let parentResource: string, testObject: WorkspaceService, instantiationService: TestInstantiationService; + const disposables = new DisposableStore(); setup(() => { return setUpWorkspace(['a', 'b']) @@ -178,11 +179,11 @@ suite('WorkspaceContextService - Workspace', () => { const environmentService = new TestWorkbenchEnvironmentService(URI.file(parentDir)); const remoteAgentService = instantiationService.createInstance(RemoteAgentService); instantiationService.stub(IRemoteAgentService, remoteAgentService); - const fileService = new FileService(new NullLogService()); - const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + const fileService = disposables.add(new FileService(new NullLogService())); + const diskFileSystemProvider = disposables.add(new DiskFileSystemProvider(new NullLogService())); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService())); - const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService, new NullLogService()); + fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.userData, new NullLogService()))); + const workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); instantiationService.stub(IWorkspaceContextService, workspaceService); instantiationService.stub(IConfigurationService, workspaceService); @@ -196,9 +197,7 @@ suite('WorkspaceContextService - Workspace', () => { }); teardown(() => { - if (testObject) { - (testObject).dispose(); - } + disposables.clear(); if (parentResource) { return pfs.rimraf(parentResource, pfs.RimRafMode.MOVE); } @@ -227,6 +226,7 @@ suite('WorkspaceContextService - Workspace', () => { suite('WorkspaceContextService - Workspace Editing', () => { let parentResource: string, testObject: WorkspaceService, instantiationService: TestInstantiationService; + const disposables = new DisposableStore(); setup(() => { return setUpWorkspace(['a', 'b']) @@ -238,11 +238,11 @@ suite('WorkspaceContextService - Workspace Editing', () => { const environmentService = new TestWorkbenchEnvironmentService(URI.file(parentDir)); const remoteAgentService = instantiationService.createInstance(RemoteAgentService); instantiationService.stub(IRemoteAgentService, remoteAgentService); - const fileService = new FileService(new NullLogService()); - const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + const fileService = disposables.add(new FileService(new NullLogService())); + const diskFileSystemProvider = disposables.add(new DiskFileSystemProvider(new NullLogService())); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService())); - const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService, new NullLogService()); + fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.userData, new NullLogService()))); + const workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); instantiationService.stub(IWorkspaceContextService, workspaceService); instantiationService.stub(IConfigurationService, workspaceService); @@ -260,9 +260,7 @@ suite('WorkspaceContextService - Workspace Editing', () => { }); teardown(() => { - if (testObject) { - (testObject).dispose(); - } + disposables.clear(); if (parentResource) { return pfs.rimraf(parentResource, pfs.RimRafMode.MOVE); } @@ -467,6 +465,7 @@ suite('WorkspaceService - Initialization', () => { let parentResource: string, workspaceConfigPath: URI, testObject: WorkspaceService, globalSettingsFile: string; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + const disposables = new DisposableStore(); suiteSetup(() => { configurationRegistry.registerConfiguration({ @@ -499,11 +498,11 @@ suite('WorkspaceService - Initialization', () => { const environmentService = new TestWorkbenchEnvironmentService(URI.file(parentDir)); const remoteAgentService = instantiationService.createInstance(RemoteAgentService); instantiationService.stub(IRemoteAgentService, remoteAgentService); - const fileService = new FileService(new NullLogService()); - const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + const fileService = disposables.add(new FileService(new NullLogService())); + const diskFileSystemProvider = disposables.add(new DiskFileSystemProvider(new NullLogService())); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService())); - const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService, new NullLogService()); + fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.userData, new NullLogService()))); + const workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); instantiationService.stub(IWorkspaceContextService, workspaceService); instantiationService.stub(IConfigurationService, workspaceService); instantiationService.stub(IEnvironmentService, environmentService); @@ -519,9 +518,7 @@ suite('WorkspaceService - Initialization', () => { }); teardown(() => { - if (testObject) { - (testObject).dispose(); - } + disposables.clear(); if (parentResource) { return pfs.rimraf(parentResource, pfs.RimRafMode.MOVE); } @@ -727,7 +724,7 @@ suite('WorkspaceConfigurationService - Folder', () => { let workspaceName = `testWorkspace${uuid.generateUuid()}`, parentResource: string, workspaceDir: string, testObject: IConfigurationService, globalSettingsFile: string, globalTasksFile: string, workspaceService: WorkspaceService; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); let fileService: IFileService; - let disposableStore: DisposableStore = new DisposableStore(); + const disposables: DisposableStore = new DisposableStore(); suiteSetup(() => { configurationRegistry.registerConfiguration({ @@ -776,17 +773,17 @@ suite('WorkspaceConfigurationService - Folder', () => { const environmentService = new TestWorkbenchEnvironmentService(URI.file(parentDir)); const remoteAgentService = instantiationService.createInstance(RemoteAgentService); instantiationService.stub(IRemoteAgentService, remoteAgentService); - fileService = new FileService(new NullLogService()); - const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + fileService = disposables.add(new FileService(new NullLogService())); + const diskFileSystemProvider = disposables.add(new DiskFileSystemProvider(new NullLogService())); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService())); - workspaceService = disposableStore.add(new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService, new NullLogService())); + fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.userData, new NullLogService()))); + workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); instantiationService.stub(IWorkspaceContextService, workspaceService); instantiationService.stub(IConfigurationService, workspaceService); instantiationService.stub(IEnvironmentService, environmentService); // Watch workspace configuration directory - disposableStore.add(fileService.watch(joinPath(URI.file(workspaceDir), '.vscode'))); + disposables.add(fileService.watch(joinPath(URI.file(workspaceDir), '.vscode'))); return workspaceService.initialize(convertToWorkspacePayload(URI.file(folderDir))).then(() => { instantiationService.stub(IFileService, fileService); @@ -800,7 +797,7 @@ suite('WorkspaceConfigurationService - Folder', () => { }); teardown(() => { - disposableStore.clear(); + disposables.clear(); if (parentResource) { return pfs.rimraf(parentResource, pfs.RimRafMode.MOVE); } @@ -1232,6 +1229,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { let parentResource: string, workspaceContextService: IWorkspaceContextService, jsonEditingServce: IJSONEditingService, testObject: IConfigurationService, globalSettingsFile: string; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + const disposables = new DisposableStore(); suiteSetup(() => { configurationRegistry.registerConfiguration({ @@ -1282,11 +1280,11 @@ suite('WorkspaceConfigurationService-Multiroot', () => { const environmentService = new TestWorkbenchEnvironmentService(URI.file(parentDir)); const remoteAgentService = instantiationService.createInstance(RemoteAgentService); instantiationService.stub(IRemoteAgentService, remoteAgentService); - const fileService = new FileService(new NullLogService()); - const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + const fileService = disposables.add(new FileService(new NullLogService())); + const diskFileSystemProvider = disposables.add(new DiskFileSystemProvider(new NullLogService())); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService())); - const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService, new NullLogService()); + fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.userData, new NullLogService()))); + const workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); instantiationService.stub(IWorkspaceContextService, workspaceService); instantiationService.stub(IConfigurationService, workspaceService); @@ -1308,9 +1306,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { }); teardown(() => { - if (testObject) { - (testObject).dispose(); - } + disposables.clear(); if (parentResource) { return pfs.rimraf(parentResource, pfs.RimRafMode.MOVE); } @@ -1841,7 +1837,8 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { let workspaceName = `testWorkspace${uuid.generateUuid()}`, parentResource: string, workspaceDir: string, testObject: WorkspaceService, globalSettingsFile: string, remoteSettingsFile: string, remoteSettingsResource: URI, instantiationService: TestInstantiationService, resolveRemoteEnvironment: () => void; const remoteAuthority = 'configuraiton-tests'; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); - const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + const disposables = new DisposableStore(); + let diskFileSystemProvider: DiskFileSystemProvider; suiteSetup(() => { configurationRegistry.registerConfiguration({ @@ -1886,11 +1883,12 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { const environmentService = new TestWorkbenchEnvironmentService(URI.file(parentDir)); const remoteEnvironmentPromise = new Promise>(c => resolveRemoteEnvironment = () => c({ settingsPath: remoteSettingsResource })); const remoteAgentService = instantiationService.stub(IRemoteAgentService, >{ getEnvironment: () => remoteEnvironmentPromise }); - const fileService = new FileService(new NullLogService()); + const fileService = disposables.add(new FileService(new NullLogService())); + diskFileSystemProvider = disposables.add(new DiskFileSystemProvider(new NullLogService())); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService())); + fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.userData, new NullLogService()))); const configurationCache: IConfigurationCache = { read: () => Promise.resolve(''), write: () => Promise.resolve(), remove: () => Promise.resolve(), needsCaching: () => false }; - testObject = new WorkspaceService({ configurationCache, remoteAuthority }, environmentService, fileService, remoteAgentService, new NullLogService()); + testObject = disposables.add(new WorkspaceService({ configurationCache, remoteAuthority }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); instantiationService.stub(IWorkspaceContextService, testObject); instantiationService.stub(IConfigurationService, testObject); instantiationService.stub(IEnvironmentService, environmentService); @@ -1919,9 +1917,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { } teardown(() => { - if (testObject) { - (testObject).dispose(); - } + disposables.clear(); if (parentResource) { return pfs.rimraf(parentResource, pfs.RimRafMode.MOVE); } @@ -2096,7 +2092,7 @@ suite('ConfigurationService - Configuration Defaults', () => { const remoteAgentService = (workbenchInstantiationService()).createInstance(RemoteAgentService); const environmentService = new BrowserWorkbenchEnvironmentService({ logsPath: URI.file(''), workspaceId: '', configurationDefaults }, TestProductService); const fileService = new FileService(new NullLogService()); - return disposableStore.add(new WorkspaceService({ configurationCache: new BrowserConfigurationCache() }, environmentService, fileService, remoteAgentService, new NullLogService())); + return disposableStore.add(new WorkspaceService({ configurationCache: new BrowserConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); } }); diff --git a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts index a40d034ec..5f22a895f 100644 --- a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts @@ -44,7 +44,7 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR getWorkspaceFolderCount: (): number => { return workspaceContextService.getWorkspace().folders.length; }, - getConfigurationValue: (folderUri: uri, suffix: string): string | undefined => { + getConfigurationValue: (folderUri: uri | undefined, suffix: string): string | undefined => { return configurationService.getValue(suffix, folderUri ? { resource: folderUri } : {}); }, getExecPath: (): string | undefined => { @@ -60,6 +60,20 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR } return this.labelService.getUriLabel(fileResource, { noPrefix: true }); }, + getWorkspaceFolderPathForFile: (): string | undefined => { + const fileResource = EditorResourceAccessor.getOriginalUri(editorService.activeEditor, { + supportSideBySide: SideBySideEditor.PRIMARY, + filterByScheme: [Schemas.file, Schemas.userData, Schemas.vscodeRemote] + }); + if (!fileResource) { + return undefined; + } + const wsFolder = workspaceContextService.getWorkspaceFolder(fileResource); + if (!wsFolder) { + return undefined; + } + return this.labelService.getUriLabel(wsFolder.uri, { noPrefix: true }); + }, getSelectedText: (): string | undefined => { const activeTextEditorControl = editorService.activeTextEditorControl; if (isCodeEditor(activeTextEditorControl)) { diff --git a/src/vs/workbench/services/configurationResolver/common/variableResolver.ts b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts index 4d632ea03..70459256a 100644 --- a/src/vs/workbench/services/configurationResolver/common/variableResolver.ts +++ b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts @@ -19,15 +19,17 @@ import { ILabelService } from 'vs/platform/label/common/label'; export interface IVariableResolveContext { getFolderUri(folderName: string): uri | undefined; getWorkspaceFolderCount(): number; - getConfigurationValue(folderUri: uri, section: string): string | undefined; + getConfigurationValue(folderUri: uri | undefined, section: string): string | undefined; getExecPath(): string | undefined; getFilePath(): string | undefined; + getWorkspaceFolderPathForFile?(): string | undefined; getSelectedText(): string | undefined; getLineNumber(): string | undefined; } export class AbstractVariableResolverService implements IConfigurationResolverService { + static readonly VARIABLE_LHS = '${'; static readonly VARIABLE_REGEXP = /\$\{(.*?)\}/g; declare readonly _serviceBrand: undefined; @@ -38,7 +40,7 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe protected _contributedVariables: Map Promise> = new Map(); - constructor(_context: IVariableResolveContext, _labelService?: ILabelService, _envVariables?: IProcessEnvironment, private _ignoreEditorVariables = false) { + constructor(_context: IVariableResolveContext, _labelService?: ILabelService, _envVariables?: IProcessEnvironment) { this._context = _context; this._labelService = _labelService; if (_envVariables) { @@ -130,6 +132,10 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe // loop through all variables occurrences in 'value' const replaced = value.replace(AbstractVariableResolverService.VARIABLE_REGEXP, (match: string, variable: string) => { + // disallow attempted nesting, see #77289 + if (variable.includes(AbstractVariableResolverService.VARIABLE_LHS)) { + return match; + } let resolvedValue = this.evaluateSingleVariable(match, variable, folderUri, commandValueMapping); @@ -164,18 +170,31 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe if (filePath) { return filePath; } - throw new Error(localize('canNotResolveFile', "'{0}' can not be resolved. Please open an editor.", match)); + throw new Error(localize('canNotResolveFile', "Variable {0} can not be resolved. Please open an editor.", match)); + }; + + // common error handling for all variables that require an open editor + const getFolderPathForFile = (): string => { + + const filePath = getFilePath(); // throws error if no editor open + if (this._context.getWorkspaceFolderPathForFile) { + const folderPath = this._context.getWorkspaceFolderPathForFile(); + if (folderPath) { + return folderPath; + } + } + throw new Error(localize('canNotResolveFolderForFile', "Variable {0}: can not find workspace folder of '{1}'.", match, paths.basename(filePath))); }; // common error handling for all variables that require an open folder and accept a folder name argument - const getFolderUri = (withArg = true): uri => { + const getFolderUri = (): uri => { - if (withArg && argument) { + if (argument) { const folder = this._context.getFolderUri(argument); if (folder) { return folder; } - throw new Error(localize('canNotFindFolder', "'{0}' can not be resolved. No such folder '{1}'.", match, argument)); + throw new Error(localize('canNotFindFolder', "Variable {0} can not be resolved. No such folder '{1}'.", match, argument)); } if (folderUri) { @@ -183,9 +202,9 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe } if (this._context.getWorkspaceFolderCount() > 1) { - throw new Error(localize('canNotResolveWorkspaceFolderMultiRoot', "'{0}' can not be resolved in a multi folder workspace. Scope this variable using ':' and a workspace folder name.", match)); + throw new Error(localize('canNotResolveWorkspaceFolderMultiRoot', "Variable {0} can not be resolved in a multi folder workspace. Scope this variable using ':' and a workspace folder name.", match)); } - throw new Error(localize('canNotResolveWorkspaceFolder', "'{0}' can not be resolved. Please open a folder.", match)); + throw new Error(localize('canNotResolveWorkspaceFolder', "Variable {0} can not be resolved. Please open a folder.", match)); }; @@ -202,20 +221,20 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe // For `env` we should do the same as a normal shell does - evaluates undefined envs to an empty string #46436 return ''; } - throw new Error(localize('missingEnvVarName', "'{0}' can not be resolved because no environment variable name is given.", match)); + throw new Error(localize('missingEnvVarName', "Variable {0} can not be resolved because no environment variable name is given.", match)); case 'config': if (argument) { - const config = this._context.getConfigurationValue(getFolderUri(false), argument); + const config = this._context.getConfigurationValue(folderUri, argument); if (types.isUndefinedOrNull(config)) { - throw new Error(localize('configNotFound', "'{0}' can not be resolved because setting '{1}' not found.", match, argument)); + throw new Error(localize('configNotFound', "Variable {0} can not be resolved because setting '{1}' not found.", match, argument)); } if (types.isObject(config)) { - throw new Error(localize('configNoString', "'{0}' can not be resolved because '{1}' is a structured value.", match, argument)); + throw new Error(localize('configNoString', "Variable {0} can not be resolved because '{1}' is a structured value.", match, argument)); } return config; } - throw new Error(localize('missingConfigName', "'{0}' can not be resolved because no settings name is given.", match)); + throw new Error(localize('missingConfigName', "Variable {0} can not be resolved because no settings name is given.", match)); case 'command': return this.resolveFromMap(match, argument, commandValueMapping, 'command'); @@ -238,44 +257,32 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe return paths.basename(this.fsPath(getFolderUri())); case 'lineNumber': - if (this._ignoreEditorVariables) { - return match; - } const lineNumber = this._context.getLineNumber(); if (lineNumber) { return lineNumber; } - throw new Error(localize('canNotResolveLineNumber', "'{0}' can not be resolved. Make sure to have a line selected in the active editor.", match)); + throw new Error(localize('canNotResolveLineNumber', "Variable {0} can not be resolved. Make sure to have a line selected in the active editor.", match)); case 'selectedText': - if (this._ignoreEditorVariables) { - return match; - } const selectedText = this._context.getSelectedText(); if (selectedText) { return selectedText; } - throw new Error(localize('canNotResolveSelectedText', "'{0}' can not be resolved. Make sure to have some text selected in the active editor.", match)); + throw new Error(localize('canNotResolveSelectedText', "Variable {0} can not be resolved. Make sure to have some text selected in the active editor.", match)); case 'file': - if (this._ignoreEditorVariables) { - return match; - } return getFilePath(); + case 'fileWorkspaceFolder': + return getFolderPathForFile(); + case 'relativeFile': - if (this._ignoreEditorVariables) { - return match; - } if (folderUri || argument) { return paths.relative(this.fsPath(getFolderUri()), getFilePath()); } return getFilePath(); case 'relativeFileDirname': - if (this._ignoreEditorVariables) { - return match; - } const dirname = paths.dirname(getFilePath()); if (folderUri || argument) { const relative = paths.relative(this.fsPath(getFolderUri()), dirname); @@ -284,30 +291,21 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe return dirname; case 'fileDirname': - if (this._ignoreEditorVariables) { - return match; - } return paths.dirname(getFilePath()); case 'fileExtname': - if (this._ignoreEditorVariables) { - return match; - } return paths.extname(getFilePath()); case 'fileBasename': - if (this._ignoreEditorVariables) { - return match; - } return paths.basename(getFilePath()); case 'fileBasenameNoExtension': - if (this._ignoreEditorVariables) { - return match; - } const basename = paths.basename(getFilePath()); return (basename.slice(0, basename.length - paths.extname(basename).length)); + case 'fileDirnameBasename': + return paths.basename(paths.dirname(getFilePath())); + case 'execPath': const ep = this._context.getExecPath(); if (ep) { @@ -315,6 +313,9 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe } return match; + case 'pathSeparator': + return paths.sep; + default: try { return this.resolveFromMap(match, variable, commandValueMapping, undefined); @@ -332,7 +333,7 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe if (typeof v === 'string') { return v; } - throw new Error(localize('noValueForCommand', "'{0}' can not be resolved because the command has no value.", match)); + throw new Error(localize('noValueForCommand', "Variable {0} can not be resolved because the command has no value.", match)); } return match; } diff --git a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts index 3e3de85fb..e1a333b06 100644 --- a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts @@ -175,6 +175,10 @@ suite('Configuration Resolver Service', () => { } }); + test('disallows nested keys (#77289)', () => { + assert.strictEqual(configurationResolverService!.resolve(workspace, '${env:key1} ${env:key1${env:key2}}'), 'Value for key1 ${env:key1${env:key2}}'); + }); + // test('substitute keys and values in object', () => { // const myObject = { // '${workspaceRootFolderName}': '${lineNumber}', @@ -211,6 +215,17 @@ suite('Configuration Resolver Service', () => { assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} xyz'), 'abc foo xyz'); }); + test('substitute configuration variable with undefined workspace folder', () => { + let configurationService: IConfigurationService = new TestConfigurationService({ + editor: { + fontFamily: 'foo' + } + }); + + let service = new TestConfigurationResolverService({ getExecPath: () => undefined }, environmentService.userEnv, new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); + assert.strictEqual(service.resolve(undefined, 'abc ${config:editor.fontFamily} xyz'), 'abc foo xyz'); + }); + test('substitute many configuration variables', () => { let configurationService: IConfigurationService; configurationService = new TestConfigurationService({ diff --git a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts index a2005fe69..09a3e187b 100644 --- a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts @@ -44,7 +44,7 @@ export class ContextMenuService extends Disposable implements IContextMenuServic super(); // Custom context menu: Linux/Windows if custom title is enabled - if (!isMacintosh && getTitleBarStyle(configurationService, environmentService) === 'custom') { + if (!isMacintosh && getTitleBarStyle(configurationService) === 'custom') { this.impl = new HTMLContextMenuService(telemetryService, notificationService, contextViewService, keybindingService, themeService); } diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts index fda885169..7cfea5300 100644 --- a/src/vs/workbench/services/decorations/browser/decorationsService.ts +++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts @@ -18,6 +18,7 @@ import { isPromiseCanceledError } from 'vs/base/common/errors'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { hash } from 'vs/base/common/hash'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; class DecorationRule { @@ -106,9 +107,7 @@ class DecorationStyles { private readonly _decorationRules = new Map(); private readonly _dispoables = new DisposableStore(); - constructor( - private _themeService: IThemeService, - ) { + constructor(private readonly _themeService: IThemeService) { this._themeService.onDidColorThemeChange(this._onThemeChange, this, this._dispoables); } @@ -149,7 +148,7 @@ class DecorationStyles { badgeClassName, tooltip, dispose: () => { - if (rule && rule.release()) { + if (rule?.release()) { this._decorationRules.delete(key); rule.removeCSSRules(this._styleElement); rule = undefined; @@ -168,13 +167,13 @@ class DecorationStyles { class FileDecorationChangeEvent implements IResourceDecorationChangeEvent { - private readonly _data = TernarySearchTree.forUris(); + private readonly _data = TernarySearchTree.forUris(_uri => true); // events ignore all path casings affectsResource(uri: URI): boolean { - return this._data.get(uri) || this._data.findSuperstr(uri) !== undefined; + return this._data.get(uri) ?? this._data.findSuperstr(uri) !== undefined; } - static debouncer(last: FileDecorationChangeEvent | undefined, current: URI | URI[]) { + static debouncer(last: FileDecorationChangeEvent | undefined, current: URI | URI[]): FileDecorationChangeEvent { if (!last) { last = new FileDecorationChangeEvent(); } @@ -201,14 +200,18 @@ class DecorationDataRequest { class DecorationProviderWrapper { - readonly data = TernarySearchTree.forUris(); + readonly data: TernarySearchTree; private readonly _dispoable: IDisposable; constructor( readonly provider: IDecorationsProvider, + uriIdentityService: IUriIdentityService, private readonly _uriEmitter: Emitter, private readonly _flushEmitter: Emitter ) { + + this.data = TernarySearchTree.forUris(uri => uriIdentityService.extUri.ignorePathCasing(uri)); + this._dispoable = this.provider.onDidChange(uris => { if (!uris) { // flush event -> drop all data, can affect everything @@ -233,7 +236,7 @@ class DecorationProviderWrapper { } knowsAbout(uri: URI): boolean { - return Boolean(this.data.get(uri)) || Boolean(this.data.findSuperstr(uri)); + return this.data.has(uri) || Boolean(this.data.findSuperstr(uri)); } getOrRetrieve(uri: URI, includeChildren: boolean, callback: (data: IDecorationData, isChild: boolean) => void): void { @@ -254,9 +257,9 @@ class DecorationProviderWrapper { // (resolved) children const iter = this.data.findSuperstr(uri); if (iter) { - for (let item = iter.next(); !item.done; item = iter.next()) { - if (item.value && !(item.value instanceof DecorationDataRequest)) { - callback(item.value, true); + for (const [, value] of iter) { + if (value && !(value instanceof DecorationDataRequest)) { + callback(value, true); } } } @@ -326,6 +329,7 @@ export class DecorationsService implements IDecorationsService { constructor( @IThemeService themeService: IThemeService, + @IUriIdentityService private readonly _uriIdentityService: IUriIdentityService, ) { this._decorationStyles = new DecorationStyles(themeService); } @@ -340,6 +344,7 @@ export class DecorationsService implements IDecorationsService { const wrapper = new DecorationProviderWrapper( provider, + this._uriIdentityService, this._onDidChangeDecorationsDelayed, this._onDidChangeDecorations ); diff --git a/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts b/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts index 9cbdbb2a6..5a4b76eb6 100644 --- a/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts +++ b/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts @@ -8,8 +8,11 @@ import { DecorationsService } from 'vs/workbench/services/decorations/browser/de import { IDecorationsProvider, IDecorationData } from 'vs/workbench/services/decorations/browser/decorations'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; +import * as resources from 'vs/base/common/resources'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { mock } from 'vs/base/test/common/mock'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; suite('DecorationsService', function () { @@ -19,7 +22,12 @@ suite('DecorationsService', function () { if (service) { service.dispose(); } - service = new DecorationsService(new TestThemeService()); + service = new DecorationsService( + new TestThemeService(), + new class extends mock() { + extUri = resources.extUri; + } + ); }); test('Async provider, async/evented result', function () { diff --git a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts index 3d6973abe..222e877c6 100644 --- a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts @@ -24,6 +24,7 @@ import { trim } from 'vs/base/common/strings'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ILabelService } from 'vs/platform/label/common/label'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; +import { Schemas } from 'vs/base/common/network'; export abstract class AbstractFileDialogService implements IFileDialogService { @@ -45,7 +46,7 @@ export abstract class AbstractFileDialogService implements IFileDialogService { @IPathService private readonly pathService: IPathService ) { } - defaultFilePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { + async defaultFilePath(schemeFilter = this.getSchemeFilterForWindow()): Promise { // Check for last active file first... let candidate = this.historyService.getLastActiveFile(schemeFilter); @@ -57,10 +58,14 @@ export abstract class AbstractFileDialogService implements IFileDialogService { candidate = candidate && resources.dirname(candidate); } - return candidate || undefined; + if (!candidate) { + candidate = await this.pathService.userHome({ preferLocal: schemeFilter === Schemas.file }); + } + + return candidate; } - defaultFolderPath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { + async defaultFolderPath(schemeFilter = this.getSchemeFilterForWindow()): Promise { // Check for last active file root first... let candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); @@ -70,10 +75,14 @@ export abstract class AbstractFileDialogService implements IFileDialogService { candidate = this.historyService.getLastActiveFile(schemeFilter); } - return candidate && resources.dirname(candidate) || undefined; + if (!candidate) { + return this.pathService.userHome({ preferLocal: schemeFilter === Schemas.file }); + } else { + return resources.dirname(candidate); + } } - defaultWorkspacePath(schemeFilter = this.getSchemeFilterForWindow(), filename?: string): URI | undefined { + async defaultWorkspacePath(schemeFilter = this.getSchemeFilterForWindow(), filename?: string): Promise { let defaultWorkspacePath: URI | undefined; // Check for current workspace config file first... if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { @@ -85,7 +94,7 @@ export abstract class AbstractFileDialogService implements IFileDialogService { // ...then fallback to default file path if (!defaultWorkspacePath) { - defaultWorkspacePath = this.defaultFilePath(schemeFilter); + defaultWorkspacePath = await this.defaultFilePath(schemeFilter); } if (defaultWorkspacePath && filename) { @@ -155,7 +164,7 @@ export abstract class AbstractFileDialogService implements IFileDialogService { if (stat.isDirectory || options.forceNewWindow || preferNewWindow) { return this.hostService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow }); } else { - return this.openerService.open(uri, { fromUserGesture: true }); + return this.openerService.open(uri, { fromUserGesture: true, editorOptions: { pinned: true } }); } } } @@ -172,7 +181,7 @@ export abstract class AbstractFileDialogService implements IFileDialogService { if (options.forceNewWindow || preferNewWindow) { return this.hostService.openWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow }); } else { - return this.openerService.open(uri, { fromUserGesture: true }); + return this.openerService.open(uri, { fromUserGesture: true, editorOptions: { pinned: true } }); } } } diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts index a25f8e001..a6eb25f5a 100644 --- a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -15,7 +15,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil const schema = this.getFileSystemSchema(options); if (!options.defaultUri) { - options.defaultUri = this.defaultFilePath(schema); + options.defaultUri = await this.defaultFilePath(schema); } return this.pickFileFolderAndOpenSimplified(schema, options, false); @@ -25,7 +25,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil const schema = this.getFileSystemSchema(options); if (!options.defaultUri) { - options.defaultUri = this.defaultFilePath(schema); + options.defaultUri = await this.defaultFilePath(schema); } return this.pickFileAndOpenSimplified(schema, options, false); @@ -35,7 +35,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil const schema = this.getFileSystemSchema(options); if (!options.defaultUri) { - options.defaultUri = this.defaultFolderPath(schema); + options.defaultUri = await this.defaultFolderPath(schema); } return this.pickFolderAndOpenSimplified(schema, options); @@ -45,7 +45,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil const schema = this.getFileSystemSchema(options); if (!options.defaultUri) { - options.defaultUri = this.defaultWorkspacePath(schema); + options.defaultUri = await this.defaultWorkspacePath(schema); } return this.pickWorkspaceAndOpenSimplified(schema, options); diff --git a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index 67885964d..e0eca9ebe 100644 --- a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -212,11 +212,16 @@ export class SimpleFileDialog { path = path.replace(/\\/g, '/'); } const uri: URI = this.scheme === Schemas.file ? URI.file(path) : URI.from({ scheme: this.scheme, path }); - return resources.toLocalResource(uri, uri.scheme === Schemas.file ? undefined : this.remoteAuthority, this.pathService.defaultUriScheme); + return resources.toLocalResource(uri, + // If the default scheme is file, then we don't care about the remote authority + uri.scheme === Schemas.file ? undefined : this.remoteAuthority, + // If there is a remote authority, then we should use the system's default URI as the local scheme. + // If there is *no* remote authority, then we should use the default scheme for this dialog as that is already local. + this.remoteAuthority ? this.pathService.defaultUriScheme : uri.scheme); } private getScheme(available: readonly string[] | undefined, defaultUri: URI | undefined): string { - if (available) { + if (available && available.length > 0) { if (defaultUri && (available.indexOf(defaultUri.scheme) >= 0)) { return defaultUri.scheme; } @@ -919,7 +924,8 @@ export class SimpleFileDialog { const ext = resources.extname(file); for (let i = 0; i < this.options.filters.length; i++) { for (let j = 0; j < this.options.filters[i].extensions.length; j++) { - if (ext === ('.' + this.options.filters[i].extensions[j])) { + const testExt = this.options.filters[i].extensions[j]; + if ((testExt === '*') || (ext === ('.' + testExt))) { return true; } } diff --git a/src/vs/workbench/services/dialogs/common/dialogService.ts b/src/vs/workbench/services/dialogs/common/dialogService.ts new file mode 100644 index 000000000..df9400859 --- /dev/null +++ b/src/vs/workbench/services/dialogs/common/dialogService.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 Severity from 'vs/base/common/severity'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IConfirmation, IConfirmationResult, IDialogOptions, IDialogService, IInput, IInputResult, IShowResult } from 'vs/platform/dialogs/common/dialogs'; +import { DialogsModel, IDialogsModel } from 'vs/workbench/common/dialogs'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +export class DialogService extends Disposable implements IDialogService { + _serviceBrand: undefined; + + readonly model: IDialogsModel = this._register(new DialogsModel()); + + async confirm(confirmation: IConfirmation): Promise { + const handle = this.model.show({ confirmArgs: { confirmation } }); + return await handle.result as IConfirmationResult; + } + + async show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise { + const handle = this.model.show({ showArgs: { severity, message, buttons, options } }); + return await handle.result as IShowResult; + } + + async input(severity: Severity, message: string, buttons: string[], inputs: IInput[], options?: IDialogOptions): Promise { + const handle = this.model.show({ inputArgs: { severity, message, buttons, inputs, options } }); + return await handle.result as IInputResult; + } + + async about(): Promise { + const handle = this.model.show({}); + await handle.result; + } +} + +registerSingleton(IDialogService, DialogService, true); diff --git a/src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts b/src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts index a6287ee77..3398150a5 100644 --- a/src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts @@ -68,7 +68,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil const schema = this.getFileSystemSchema(options); if (!options.defaultUri) { - options.defaultUri = this.defaultFilePath(schema); + options.defaultUri = await this.defaultFilePath(schema); } const shouldUseSimplified = this.shouldUseSimplified(schema); @@ -82,7 +82,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil const schema = this.getFileSystemSchema(options); if (!options.defaultUri) { - options.defaultUri = this.defaultFilePath(schema); + options.defaultUri = await this.defaultFilePath(schema); } const shouldUseSimplified = this.shouldUseSimplified(schema); @@ -96,7 +96,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil const schema = this.getFileSystemSchema(options); if (!options.defaultUri) { - options.defaultUri = this.defaultFolderPath(schema); + options.defaultUri = await this.defaultFolderPath(schema); } if (this.shouldUseSimplified(schema).useSimplified) { @@ -109,7 +109,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil const schema = this.getFileSystemSchema(options); if (!options.defaultUri) { - options.defaultUri = this.defaultWorkspacePath(schema); + options.defaultUri = await this.defaultWorkspacePath(schema); } if (this.shouldUseSimplified(schema).useSimplified) { diff --git a/src/vs/workbench/services/editor/browser/codeEditorService.ts b/src/vs/workbench/services/editor/browser/codeEditorService.ts index d0474b57d..89e432817 100644 --- a/src/vs/workbench/services/editor/browser/codeEditorService.ts +++ b/src/vs/workbench/services/editor/browser/codeEditorService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ICodeEditor, isCodeEditor, isDiffEditor, isCompositeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, isCodeEditor, isDiffEditor, isCompositeEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { CodeEditorServiceImpl } from 'vs/editor/browser/services/codeEditorServiceImpl'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; @@ -69,12 +69,34 @@ export class CodeEditorService extends CodeEditorServiceImpl { } private async doOpenCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { + + // Special case: we want to detect the request to open an editor that + // is different from the current one to decide wether the current editor + // should be pinned or not. This ensures that the source of a navigation + // is not being replaced by the target. An example is "Goto definition" + // that otherwise would replace the editor everytime the user navigates. + if ( + source && // we need to know the origin of the navigation + !input.options?.pinned && // we only need to look at preview editors that open + !sideBySide && // we only need to care if editor opens in same group + !isEqual(source.getModel()?.uri, input.resource) // we only need to do this if the editor is about to change + ) { + for (const visiblePane of this.editorService.visibleEditorPanes) { + if (getCodeEditor(visiblePane.getControl()) === source) { + visiblePane.group.pinEditor(); + break; + } + } + } + + // Open as editor const control = await this.editorService.openEditor(input, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); if (control) { const widget = control.getControl(); if (isCodeEditor(widget)) { return widget; } + if (isCompositeEditor(widget) && isCodeEditor(widget.activeCodeEditor)) { return widget.activeCodeEditor; } diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index a50d37b30..458e5e411 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -24,7 +24,6 @@ import { Disposable, IDisposable, dispose, toDisposable, DisposableStore } from import { coalesce, distinct, insert } from 'vs/base/common/arrays'; import { isCodeEditor, isDiffEditor, ICodeEditor, IDiffEditor, isCompositeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorGroupView, IEditorOpeningEvent, EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; -import { ILabelService } from 'vs/platform/label/common/label'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { withNullAsUndefined } from 'vs/base/common/types'; import { EditorsObserver } from 'vs/workbench/browser/parts/editor/editorsObserver'; @@ -73,7 +72,6 @@ export class EditorService extends Disposable implements EditorServiceImpl { @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @ILabelService private readonly labelService: ILabelService, @IFileService private readonly fileService: IFileService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @@ -817,11 +815,12 @@ export class EditorService extends Disposable implements EditorServiceImpl { const leftInput = this.createEditorInput({ resource: resourceDiffInput.leftResource, forceFile: resourceDiffInput.forceFile }); const rightInput = this.createEditorInput({ resource: resourceDiffInput.rightResource, forceFile: resourceDiffInput.forceFile }); - return new DiffEditorInput( - resourceDiffInput.label || this.toSideBySideLabel(leftInput, rightInput), + return this.instantiationService.createInstance(DiffEditorInput, + resourceDiffInput.label, resourceDiffInput.description, leftInput, - rightInput + rightInput, + undefined ); } @@ -881,7 +880,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // File if (resourceEditorInput.forceFile || this.fileService.canHandleResource(canonicalResource)) { - return this.fileEditorInputFactory.createFileEditorInput(canonicalResource, preferredResource, resourceEditorInput.encoding, resourceEditorInput.mode, this.instantiationService); + return this.fileEditorInputFactory.createFileEditorInput(canonicalResource, preferredResource, resourceEditorInput.label, resourceEditorInput.description, resourceEditorInput.encoding, resourceEditorInput.mode, this.instantiationService); } // Resource @@ -897,6 +896,14 @@ export class EditorService extends Disposable implements EditorServiceImpl { else if (!(cachedInput instanceof ResourceEditorInput)) { cachedInput.setPreferredResource(preferredResource); + if (resourceEditorInput.label) { + cachedInput.setPreferredName(resourceEditorInput.label); + } + + if (resourceEditorInput.description) { + cachedInput.setPreferredDescription(resourceEditorInput.description); + } + if (resourceEditorInput.encoding) { cachedInput.setPreferredEncoding(resourceEditorInput.encoding); } @@ -968,19 +975,6 @@ export class EditorService extends Disposable implements EditorServiceImpl { return input; } - private toSideBySideLabel(leftInput: EditorInput, rightInput: EditorInput): string | undefined { - - // If both editors are file inputs, we produce an optimized label - // by adding the relative path of both inputs to the label. This - // makes it easier to understand a file-based comparison. - if (this.fileEditorInputFactory.isFileEditorInput(leftInput) && this.fileEditorInputFactory.isFileEditorInput(rightInput)) { - return `${this.labelService.getUriLabel(leftInput.preferredResource, { relative: true })} ↔ ${this.labelService.getUriLabel(rightInput.preferredResource, { relative: true })}`; - } - - // Signal back that the label should be computed from within the editor - return undefined; - } - //#endregion //#region save/revert diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index 851734c7d..c54ec57ee 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -21,16 +21,6 @@ export const enum GroupDirection { RIGHT } -export function preferredSideBySideGroupDirection(configurationService: IConfigurationService): GroupDirection.DOWN | GroupDirection.RIGHT { - const openSideBySideDirection = configurationService.getValue<'right' | 'down'>('workbench.editor.openSideBySideDirection'); - - if (openSideBySideDirection === 'down') { - return GroupDirection.DOWN; - } - - return GroupDirection.RIGHT; -} - export const enum GroupOrientation { HORIZONTAL, VERTICAL @@ -595,3 +585,18 @@ export interface IEditorGroup { */ focus(): void; } + + +//#region Editor Group Helpers + +export function preferredSideBySideGroupDirection(configurationService: IConfigurationService): GroupDirection.DOWN | GroupDirection.RIGHT { + const openSideBySideDirection = configurationService.getValue<'right' | 'down'>('workbench.editor.openSideBySideDirection'); + + if (openSideBySideDirection === 'down') { + return GroupDirection.DOWN; + } + + return GroupDirection.RIGHT; +} + +//#endregion diff --git a/src/vs/workbench/services/editor/common/editorOpenWith.ts b/src/vs/workbench/services/editor/common/editorOpenWith.ts index f0e0503c3..425fa2aba 100644 --- a/src/vs/workbench/services/editor/common/editorOpenWith.ts +++ b/src/vs/workbench/services/editor/common/editorOpenWith.ts @@ -16,6 +16,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { URI } from 'vs/base/common/uri'; import { extname, basename, isEqual } from 'vs/base/common/resources'; +import { Codicon } from 'vs/base/common/codicons'; /** * Id of the default editor for open with. @@ -71,7 +72,7 @@ export async function openEditorWith( description: entry.active ? nls.localize('promptOpenWith.currentlyActive', 'Currently Active') : undefined, detail: entry.detail, buttons: resourceExt ? [{ - iconClass: 'codicon-settings-gear', + iconClass: Codicon.gear.classNames, tooltip: nls.localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", resourceExt) }] : undefined }; diff --git a/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts b/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts index bd08a0b36..5f927c557 100644 --- a/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts @@ -298,7 +298,7 @@ suite('EditorsObserver', function () { assert.equal(observer.hasEditor(input2.resource), true); assert.equal(observer.hasEditor(input3.resource), true); - storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); + storage.emitWillSaveState(WillSaveStateReason.SHUTDOWN); const restoredObserver = new EditorsObserver(part, storage); await part.whenRestored; @@ -350,7 +350,7 @@ suite('EditorsObserver', function () { assert.equal(observer.hasEditor(input2.resource), true); assert.equal(observer.hasEditor(input3.resource), true); - storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); + storage.emitWillSaveState(WillSaveStateReason.SHUTDOWN); const restoredObserver = new EditorsObserver(part, storage); await part.whenRestored; @@ -390,7 +390,7 @@ suite('EditorsObserver', function () { assert.equal(currentEditorsMRU[0].editor, input1); assert.equal(observer.hasEditor(input1.resource), true); - storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); + storage.emitWillSaveState(WillSaveStateReason.SHUTDOWN); const restoredObserver = new EditorsObserver(part, storage); await part.whenRestored; diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index a8d43045e..8f70949cd 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -15,6 +15,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { memoize } from 'vs/base/common/decorators'; import { onUnexpectedError } from 'vs/base/common/errors'; import { parseLineAndColumnAware } from 'vs/base/common/extpath'; +import { LogLevelToString } from 'vs/platform/log/common/log'; class BrowserWorkbenchConfiguration implements IWindowConfiguration { @@ -114,7 +115,8 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment @memoize get logsPath(): string { return this.options.logsPath.path; } - get logLevel(): string | undefined { return this.payload?.get('logLevel'); } + @memoize + get logLevel(): string | undefined { return this.payload?.get('logLevel') || (this.options.logLevel !== undefined ? LogLevelToString(this.options.logLevel) : undefined); } @memoize get logFile(): URI { return joinPath(this.options.logsPath, 'window.log'); } @@ -157,9 +159,6 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment @memoize get keyboardLayoutResource(): URI { return joinPath(this.userRoamingDataHome, 'keyboardLayout.json'); } - @memoize - get backupWorkspaceHome(): URI { return joinPath(this.userRoamingDataHome, 'Backups', this.options.workspaceId); } - @memoize get untitledWorkspacesHome(): URI { return joinPath(this.userRoamingDataHome, 'Workspaces'); } diff --git a/src/vs/workbench/services/environment/common/environmentService.ts b/src/vs/workbench/services/environment/common/environmentService.ts index 54164056e..61725bf15 100644 --- a/src/vs/workbench/services/environment/common/environmentService.ts +++ b/src/vs/workbench/services/environment/common/environmentService.ts @@ -34,7 +34,6 @@ export interface IWorkbenchEnvironmentService extends IEnvironmentService { readonly sessionId: string; readonly logFile: URI; - readonly backupWorkspaceHome?: URI; readonly extHostLogsPath: URI; readonly logExtensionHostCommunication?: boolean; diff --git a/src/vs/workbench/services/environment/electron-browser/environmentService.ts b/src/vs/workbench/services/environment/electron-browser/environmentService.ts index 576f2cc55..6746e75b0 100644 --- a/src/vs/workbench/services/environment/electron-browser/environmentService.ts +++ b/src/vs/workbench/services/environment/electron-browser/environmentService.ts @@ -10,6 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { join } from 'vs/base/common/path'; import { IProductService } from 'vs/platform/product/common/productService'; +import { IOSConfiguration } from 'vs/platform/windows/common/windows'; export class NativeWorkbenchEnvironmentService extends NativeEnvironmentService implements INativeWorkbenchEnvironmentService { @@ -30,20 +31,6 @@ export class NativeWorkbenchEnvironmentService extends NativeEnvironmentService @memoize get userRoamingDataHome(): URI { return this.appSettingsHome.with({ scheme: Schemas.userData }); } - // Do NOT! memoize as `backupPath` can change in configuration - // via the `updateBackupPath` method below - get backupWorkspaceHome(): URI | undefined { - if (this.configuration.backupPath) { - return URI.file(this.configuration.backupPath).with({ scheme: this.userRoamingDataHome.scheme }); - } - - return undefined; - } - - updateBackupPath(newBackupPath: string | undefined): void { - this.configuration.backupPath = newBackupPath; - } - @memoize get logFile(): URI { return URI.file(join(this.logsPath, `renderer${this.configuration.windowId}.log`)); } @@ -82,6 +69,10 @@ export class NativeWorkbenchEnvironmentService extends NativeEnvironmentService return undefined; } + get os(): IOSConfiguration { + return this.configuration.os; + } + constructor( readonly configuration: INativeWorkbenchConfiguration, private readonly productService: IProductService diff --git a/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts b/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts index e534169ba..4f1ef89cf 100644 --- a/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts +++ b/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkbenchConfiguration, IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { INativeWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { INativeWindowConfiguration, IOSConfiguration } from 'vs/platform/windows/common/windows'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -27,8 +27,7 @@ export interface INativeWorkbenchEnvironmentService extends IWorkbenchEnvironmen readonly log?: string; - // TODO@ben this is a bit ugly - updateBackupPath(newPath: string | undefined): void; + readonly os: IOSConfiguration; /** * @deprecated this property will go away eventually as it diff --git a/src/vs/workbench/services/experiment/common/experimentService.ts b/src/vs/workbench/services/experiment/common/experimentService.ts index dc84569d6..0fa7fda89 100644 --- a/src/vs/workbench/services/experiment/common/experimentService.ts +++ b/src/vs/workbench/services/experiment/common/experimentService.ts @@ -6,18 +6,19 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import * as platform from 'vs/base/common/platform'; import type { IKeyValueStorage, IExperimentationTelemetry, IExperimentationFilterProvider, ExperimentationService as TASClient } from 'tas-client-umd'; import { MementoObject, Memento } from 'vs/workbench/common/memento'; -import { IProductService } from 'vs/platform/product/common/productService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryData } from 'vs/base/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import product from 'vs/platform/product/common/product'; export const ITASExperimentService = createDecorator('TASExperimentService'); export interface ITASExperimentService { readonly _serviceBrand: undefined; getTreatment(name: string): Promise; + getCurrentExperiments(): Promise; } const storageKey = 'VSCode.ABExp.FeatureData'; @@ -37,11 +38,20 @@ class MementoKeyValueStorage implements IKeyValueStorage { } class ExperimentServiceTelemetry implements IExperimentationTelemetry { + private _lastAssignmentContext: string | undefined; constructor(private telemetryService: ITelemetryService) { } + get assignmentContext(): string[] | undefined { + return this._lastAssignmentContext?.split(';'); + } + // __GDPR__COMMON__ "VSCode.ABExp.Features" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } // __GDPR__COMMON__ "abexp.assignmentcontext" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } setSharedProperty(name: string, value: string): void { + if (name === product.tasConfig?.assignmentContextTelemetryPropertyName) { + this._lastAssignmentContext = value; + } + this.telemetryService.setExperimentProperty(name, value); } @@ -165,6 +175,7 @@ enum TargetPopulation { export class ExperimentService implements ITASExperimentService { _serviceBrand: undefined; private tasClient: Promise | undefined; + private telemetry: ExperimentServiceTelemetry | undefined; private static MEMENTO_ID = 'experiment.service.memento'; private get experimentsEnabled(): boolean { @@ -172,13 +183,12 @@ export class ExperimentService implements ITASExperimentService { } constructor( - @IProductService private productService: IProductService, @ITelemetryService private telemetryService: ITelemetryService, @IStorageService private storageService: IStorageService, @IConfigurationService private configurationService: IConfigurationService, ) { - if (this.productService.tasConfig && this.experimentsEnabled && this.telemetryService.isOptedIn) { + if (product.tasConfig && this.experimentsEnabled && this.telemetryService.isOptedIn) { this.tasClient = this.setupTASClient(); } } @@ -195,26 +205,40 @@ export class ExperimentService implements ITASExperimentService { return (await this.tasClient).getTreatmentVariable('vscode', name); } + async getCurrentExperiments(): Promise { + if (!this.tasClient) { + return undefined; + } + + if (!this.experimentsEnabled) { + return undefined; + } + + await this.tasClient; + + return this.telemetry?.assignmentContext; + } + private async setupTASClient(): Promise { const telemetryInfo = await this.telemetryService.getTelemetryInfo(); - const targetPopulation = telemetryInfo.msftInternal ? TargetPopulation.Internal : (this.productService.quality === 'stable' ? TargetPopulation.Public : TargetPopulation.Insiders); + const targetPopulation = telemetryInfo.msftInternal ? TargetPopulation.Internal : (product.quality === 'stable' ? TargetPopulation.Public : TargetPopulation.Insiders); const machineId = telemetryInfo.machineId; const filterProvider = new ExperimentServiceFilterProvider( - this.productService.version, - this.productService.nameLong, + product.version, + product.nameLong, machineId, targetPopulation ); const memento = new Memento(ExperimentService.MEMENTO_ID, this.storageService); - const keyValueStorage = new MementoKeyValueStorage(memento.getMemento(StorageScope.GLOBAL)); + const keyValueStorage = new MementoKeyValueStorage(memento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE)); - const telemetry = new ExperimentServiceTelemetry(this.telemetryService); + this.telemetry = new ExperimentServiceTelemetry(this.telemetryService); - const tasConfig = this.productService.tasConfig!; + const tasConfig = product.tasConfig!; const tasClient = new (await import('tas-client-umd')).ExperimentationService({ filterProviders: [filterProvider], - telemetry: telemetry, + telemetry: this.telemetry, storageKey: storageKey, keyValueStorage: keyValueStorage, featuresTelemetryPropertyName: tasConfig.featuresTelemetryPropertyName, diff --git a/src/vs/workbench/services/extensionManagement/browser/extensionBisect.ts b/src/vs/workbench/services/extensionManagement/browser/extensionBisect.ts new file mode 100644 index 000000000..7a93a5290 --- /dev/null +++ b/src/vs/workbench/services/extensionManagement/browser/extensionBisect.ts @@ -0,0 +1,337 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IExtensionManagementService, IGlobalExtensionEnablementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { ExtensionType, IExtension } from 'vs/platform/extensions/common/extensions'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; +import { timeout } from 'vs/base/common/async'; + +// --- bisect service + +export const IExtensionBisectService = createDecorator('IExtensionBisectService'); + +export interface IExtensionBisectService { + + readonly _serviceBrand: undefined; + + isDisabledByBisect(extension: IExtension): boolean; + isActive: boolean; + disabledCount: number; + start(extensions: ILocalExtension[]): Promise; + next(seeingBad: boolean): Promise<{ id: string, bad: boolean } | undefined>; + reset(): Promise; +} + +class BisectState { + + static fromJSON(raw: string | undefined): BisectState | undefined { + if (!raw) { + return undefined; + } + try { + interface Raw extends BisectState { } + const data: Raw = JSON.parse(raw); + return new BisectState(data.extensions, data.low, data.high, data.mid); + } catch { + return undefined; + } + } + + constructor( + readonly extensions: string[], + readonly low: number, + readonly high: number, + readonly mid: number = ((low + high) / 2) | 0 + ) { } +} + +class ExtensionBisectService implements IExtensionBisectService { + + declare readonly _serviceBrand: undefined; + + private static readonly _storageKey = 'extensionBisectState'; + + private readonly _state: BisectState | undefined; + private readonly _disabled = new Map(); + + constructor( + @ILogService logService: ILogService, + @IStorageService private readonly _storageService: IStorageService, + ) { + const raw = _storageService.get(ExtensionBisectService._storageKey, StorageScope.GLOBAL); + this._state = BisectState.fromJSON(raw); + + if (this._state) { + const { mid, high } = this._state; + for (let i = 0; i < this._state.extensions.length; i++) { + const isDisabled = i >= mid && i < high; + this._disabled.set(this._state.extensions[i], isDisabled); + } + logService.warn('extension BISECT active', [...this._disabled]); + } + } + + get isActive() { + return !!this._state; + } + + get disabledCount() { + return this._state ? this._state.high - this._state.mid : -1; + } + + isDisabledByBisect(extension: IExtension): boolean { + if (!this._state) { + return false; + } + const disabled = this._disabled.get(extension.identifier.id); + return disabled ?? false; + } + + async start(extensions: ILocalExtension[]): Promise { + if (this._state) { + throw new Error('invalid state'); + } + const extensionIds = extensions.map(ext => ext.identifier.id); + const newState = new BisectState(extensionIds, 0, extensionIds.length, 0); + this._storageService.store(ExtensionBisectService._storageKey, JSON.stringify(newState), StorageScope.GLOBAL, StorageTarget.MACHINE); + await this._storageService.flush(); + } + + async next(seeingBad: boolean): Promise<{ id: string; bad: boolean; } | undefined> { + if (!this._state) { + throw new Error('invalid state'); + } + // check if bad when all extensions are disabled + if (seeingBad && this._state.mid === 0 && this._state.high === this._state.extensions.length) { + return { bad: true, id: '' }; + } + // check if there is only one left + if (this._state.low === this._state.high - 1) { + await this.reset(); + return { id: this._state.extensions[this._state.low], bad: seeingBad }; + } + // the second half is disabled so if there is still bad it must be + // in the first half + const nextState = new BisectState( + this._state.extensions, + seeingBad ? this._state.low : this._state.mid, + seeingBad ? this._state.mid : this._state.high, + ); + this._storageService.store(ExtensionBisectService._storageKey, JSON.stringify(nextState), StorageScope.GLOBAL, StorageTarget.MACHINE); + await this._storageService.flush(); + return undefined; + } + + async reset(): Promise { + this._storageService.remove(ExtensionBisectService._storageKey, StorageScope.GLOBAL); + await this._storageService.flush(); + } +} + +registerSingleton(IExtensionBisectService, ExtensionBisectService, true); + +// --- bisect UI + +class ExtensionBisectUi { + + static ctxIsBisectActive = new RawContextKey('isExtensionBisectActive', false); + + constructor( + @IContextKeyService contextKeyService: IContextKeyService, + @IExtensionBisectService private readonly _extensionBisectService: IExtensionBisectService, + @INotificationService private readonly _notificationService: INotificationService, + @ICommandService private readonly _commandService: ICommandService, + ) { + if (_extensionBisectService.isActive) { + ExtensionBisectUi.ctxIsBisectActive.bindTo(contextKeyService).set(true); + this._showBisectPrompt(); + } + } + + private _showBisectPrompt(): void { + + const goodPrompt: IPromptChoice = { + label: 'Good now', + run: () => this._commandService.executeCommand('extension.bisect.next', false) + }; + const badPrompt: IPromptChoice = { + label: 'This is bad', + run: () => this._commandService.executeCommand('extension.bisect.next', true) + }; + const stop: IPromptChoice = { + label: 'Stop Bisect', + run: () => this._commandService.executeCommand('extension.bisect.stop') + }; + + this._notificationService.prompt( + Severity.Info, + localize('bisect', "Extension Bisect is active and has disabled {0} extensions. Check if you can still reproduce the problem and proceed by selecting from these options.", this._extensionBisectService.disabledCount), + [goodPrompt, badPrompt, stop], + { sticky: true } + ); + } +} + +Registry.as(Extensions.Workbench).registerWorkbenchContribution( + ExtensionBisectUi, + LifecyclePhase.Restored +); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'extension.bisect.start', + title: localize('title.start', "Start Extension Bisect"), + category: localize('help', "Help"), + f1: true, + precondition: ExtensionBisectUi.ctxIsBisectActive.negate() + }); + } + + async run(accessor: ServicesAccessor): Promise { + const dialogService = accessor.get(IDialogService); + const hostService = accessor.get(IHostService); + const extensionManagement = accessor.get(IExtensionManagementService); + const extensionEnablementService = accessor.get(IGlobalExtensionEnablementService); + const extensionsBisect = accessor.get(IExtensionBisectService); + + const disabled = new Set(extensionEnablementService.getDisabledExtensions().map(id => id.id)); + const extensions = (await extensionManagement.getInstalled(ExtensionType.User)).filter(ext => !disabled.has(ext.identifier.id)); + + const res = await dialogService.confirm({ + message: localize('msg.start', "Extension Bisect"), + detail: localize('detail.start', "Extension Bisect will use binary search to find an extension that causes a problem. During the process the window reloads repeatedly (~{0} times). Each time you must confirm if you are still seeing problems.", 2 + Math.log2(extensions.length) | 0), + primaryButton: localize('msg2', "Start Extension Bisect") + }); + + if (res.confirmed) { + await extensionsBisect.start(extensions); + hostService.reload(); + } + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'extension.bisect.next', + title: localize('title.isBad', "Continue Extension Bisect"), + category: localize('help', "Help"), + f1: true, + precondition: ExtensionBisectUi.ctxIsBisectActive + }); + } + + async run(accessor: ServicesAccessor, seeingBad: boolean | undefined): Promise { + const dialogService = accessor.get(IDialogService); + const hostService = accessor.get(IHostService); + const bisectService = accessor.get(IExtensionBisectService); + const productService = accessor.get(IProductService); + const extensionEnablementService = accessor.get(IGlobalExtensionEnablementService); + const issueService = accessor.get(IWorkbenchIssueService); + + if (!bisectService.isActive) { + return; + } + if (seeingBad === undefined) { + const goodBadStopCancel = await this._checkForBad(dialogService, bisectService); + if (goodBadStopCancel === null) { + return; + } + seeingBad = goodBadStopCancel; + } + if (seeingBad === undefined) { + await bisectService.reset(); + hostService.reload(); + return; + } + const done = await bisectService.next(seeingBad); + if (!done) { + hostService.reload(); + return; + } + + if (done.bad) { + // DONE but nothing found + await dialogService.show(Severity.Info, localize('done.msg', "Extension Bisect"), [], { + detail: localize('done.detail2', "Extension Bisect is done but no extension has been identified. This might be a problem with {0}.", productService.nameShort) + }); + + } else { + // DONE and identified extension + const res = await dialogService.show(Severity.Info, localize('done.msg', "Extension Bisect"), + [localize('report', "Report Issue & Continue"), localize('done', "Continue")], + // [], + { + detail: localize('done.detail', "Extension Bisect is done and has identified {0} as the extension causing the problem.", done.id), + checkbox: { label: localize('done.disbale', "Keep this extension disabled"), checked: true }, + cancelId: 1 + } + ); + if (res.checkboxChecked) { + await extensionEnablementService.disableExtension({ id: done.id }, undefined); + } + if (res.choice === 0) { + issueService.openReporter({ extensionId: done.id }); + await timeout(750); // workaround for https://github.com/microsoft/vscode/issues/111871 + } + } + await bisectService.reset(); + hostService.reload(); + } + + private async _checkForBad(dialogService: IDialogService, bisectService: IExtensionBisectService): Promise { + const options = { + cancelId: 3, + detail: localize('bisect', "Extension Bisect is active and has disabled {0} extensions. Check if you can still reproduce the problem and proceed by selecting from these options.", bisectService.disabledCount), + }; + const res = await dialogService.show( + Severity.Info, + localize('msg.next', "Extension Bisect"), + [localize('next.good', "Good now"), localize('next.bad', "This is bad"), localize('next.stop', "Stop Bisect"), localize('next.cancel', "Cancel")], + options + ); + switch (res.choice) { + case 0: return false; //good now + case 1: return true; //bad + case 2: return undefined; //stop + } + return null; //cancel + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'extension.bisect.stop', + title: localize('title.stop', "Stop Extension Bisect"), + category: localize('help', "Help"), + f1: true, + precondition: ExtensionBisectUi.ctxIsBisectActive + }); + } + + async run(accessor: ServicesAccessor): Promise { + const extensionsBisect = accessor.get(IExtensionBisectService); + const hostService = accessor.get(IHostService); + await extensionsBisect.reset(); + hostService.reload(); + } +}); diff --git a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts index 50d4d812b..94d47c9cd 100644 --- a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts @@ -24,6 +24,7 @@ import { IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/com import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IExtensionBisectService } from 'vs/workbench/services/extensionManagement/browser/extensionBisect'; const SOURCE = 'IWorkbenchExtensionEnablementService'; @@ -50,6 +51,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench @ILifecycleService private readonly lifecycleService: ILifecycleService, @INotificationService private readonly notificationService: INotificationService, @IHostService hostService: IHostService, + @IExtensionBisectService private readonly extensionBisectService: IExtensionBisectService, ) { super(); this.storageManger = this._register(new StorageManager(storageService)); @@ -59,9 +61,9 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench // delay notification for extensions disabled until workbench restored if (this.allUserExtensionsDisabled) { this.lifecycleService.when(LifecyclePhase.Restored).then(() => { - this.notificationService.prompt(Severity.Info, localize('extensionsDisabled', "All installed extensions are temporarily disabled. Reload the window to return to the previous state."), [{ - label: localize('Reload', "Reload"), - run: () => hostService.reload() + this.notificationService.prompt(Severity.Info, localize('extensionsDisabled', "All installed extensions are temporarily disabled."), [{ + label: localize('Reload', "Reload and Enable Extensions"), + run: () => hostService.reload({ disableExtensions: false }) }]); }); } @@ -76,6 +78,9 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench } getEnablementState(extension: IExtension): EnablementState { + if (this.extensionBisectService.isDisabledByBisect(extension)) { + return EnablementState.DisabledByEnvironemt; + } if (this._isDisabledInEnv(extension)) { return EnablementState.DisabledByEnvironemt; } @@ -210,14 +215,16 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench } } if (extensionKind === 'web') { - const enableLocalWebWorker = this.configurationService.getValue(webWorkerExtHostConfig); - if (enableLocalWebWorker) { - // Web extensions are enabled on all configurations - return false; - } - if (this.extensionManagementServerService.localExtensionManagementServer === null) { - // Web extensions run only in the web - return false; + if (this.extensionManagementServerService.webExtensionManagementServer) { + if (server === this.extensionManagementServerService.webExtensionManagementServer) { + return false; + } + } else if (server === this.extensionManagementServerService.localExtensionManagementServer) { + const enableLocalWebWorker = this.configurationService.getValue(webWorkerExtHostConfig); + if (enableLocalWebWorker) { + // Web extensions are enabled on all configurations + return false; + } } } } diff --git a/src/vs/workbench/services/extensionManagement/browser/extensionUrlTrustService.ts b/src/vs/workbench/services/extensionManagement/browser/extensionUrlTrustService.ts new file mode 100644 index 000000000..7ade5f59a --- /dev/null +++ b/src/vs/workbench/services/extensionManagement/browser/extensionUrlTrustService.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +class ExtensionUrlTrustService implements IExtensionUrlTrustService { + + declare readonly _serviceBrand: undefined; + + async isExtensionUrlTrusted(): Promise { + return false; + } +} + +registerSingleton(IExtensionUrlTrustService, ExtensionUrlTrustService); diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts index e51b22179..380898ba5 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts @@ -5,7 +5,7 @@ import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IExtension, IScannedExtension, ExtensionType, ITranslatedScannedExtension } from 'vs/platform/extensions/common/extensions'; +import { IExtension, IScannedExtension, ExtensionType, ITranslatedScannedExtension, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { IExtensionManagementService, IGalleryExtension, IExtensionIdentifier, ILocalExtension, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; @@ -29,6 +29,7 @@ export interface IWorkbenchExtensioManagementService extends IExtensionManagemen readonly _serviceBrand: undefined; installExtensions(extensions: IGalleryExtension[], installOptions?: InstallOptions): Promise; updateFromGallery(gallery: IGalleryExtension, extension: ILocalExtension): Promise; + getExtensionManagementServerToInstall(manifest: IExtensionManifest): IExtensionManagementServer | null } export const enum EnablementState { @@ -96,7 +97,7 @@ export interface IWebExtensionsScannerService { scanExtensions(type?: ExtensionType): Promise; scanAndTranslateExtensions(type?: ExtensionType): Promise; scanAndTranslateSingleExtension(extensionLocation: URI, extensionType: ExtensionType): Promise; - canAddExtension(galleryExtension: IGalleryExtension): Promise; + canAddExtension(galleryExtension: IGalleryExtension): boolean; addExtension(galleryExtension: IGalleryExtension): Promise; removeExtension(identifier: IExtensionIdentifier, version?: string): Promise; } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts index 410c10e04..bd730af53 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts @@ -43,12 +43,13 @@ export class ExtensionManagementServerService implements IExtensionManagementSer extensionManagementService, get label() { return labelService.getHostLabel(Schemas.vscodeRemote, remoteAgentConnection!.remoteAuthority) || localize('remote', "Remote"); } }; - } else if (isWeb) { + } + if (isWeb) { const extensionManagementService = instantiationService.createInstance(WebExtensionManagementService); this.webExtensionManagementServer = { id: 'web', extensionManagementService, - label: localize('web', "Web") + label: localize('browser', "Browser") }; } } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index de7e301d3..b1781e08b 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -5,7 +5,7 @@ import { Event, EventMultiplexer } from 'vs/base/common/event'; import { - ILocalExtension, IGalleryExtension, InstallExtensionEvent, DidInstallExtensionEvent, IExtensionIdentifier, DidUninstallExtensionEvent, IReportedExtension, IGalleryMetadata, IExtensionGalleryService, INSTALL_ERROR_NOT_SUPPORTED, InstallOptions + ILocalExtension, IGalleryExtension, InstallExtensionEvent, DidInstallExtensionEvent, IExtensionIdentifier, DidUninstallExtensionEvent, IReportedExtension, IGalleryMetadata, IExtensionGalleryService, INSTALL_ERROR_NOT_SUPPORTED, InstallOptions, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionManagementServer, IExtensionManagementServerService, IWorkbenchExtensioManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionType, isLanguagePackExtension, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; @@ -15,7 +15,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { CancellationToken } from 'vs/base/common/cancellation'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { localize } from 'vs/nls'; -import { prefersExecuteOnUI, canExecuteOnWorkspace, prefersExecuteOnWorkspace, canExecuteOnUI, prefersExecuteOnWeb, canExecuteOnWeb } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { prefersExecuteOnUI, getExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { IProductService } from 'vs/platform/product/common/productService'; import { Schemas } from 'vs/base/common/network'; import { IDownloadService } from 'vs/platform/download/common/download'; @@ -23,7 +23,6 @@ import { flatten } from 'vs/base/common/arrays'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; import { canceled } from 'vs/base/common/errors'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IUserDataAutoSyncEnablementService, IUserDataSyncResourceEnablementService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; export class ExtensionManagementService extends Disposable implements IWorkbenchExtensioManagementService { @@ -45,7 +44,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench @IDownloadService protected readonly downloadService: IDownloadService, @IUserDataAutoSyncEnablementService private readonly userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService, @IUserDataSyncResourceEnablementService private readonly userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IDialogService private readonly dialogService: IDialogService, ) { super(); if (this.extensionManagementServerService.localExtensionManagementServer) { @@ -69,7 +68,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench return flatten(result); } - async uninstall(extension: ILocalExtension): Promise { + async uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise { const server = this.getServer(extension); if (!server) { return Promise.reject(`Invalid location ${extension.location.toString()}`); @@ -78,7 +77,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench if (isLanguagePackExtension(extension.manifest)) { return this.uninstallEverywhere(extension); } - return this.uninstallInServer(extension, server); + return this.uninstallInServer(extension, server, options); } return server.extensionManagementService.uninstall(extension); } @@ -102,7 +101,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench return promise; } - private async uninstallInServer(extension: ILocalExtension, server: IExtensionManagementServer, force?: boolean): Promise { + private async uninstallInServer(extension: ILocalExtension, server: IExtensionManagementServer, options?: UninstallOptions): Promise { if (server === this.extensionManagementServerService.localExtensionManagementServer) { const installedExtensions = await this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getInstalled(ExtensionType.User); const dependentNonUIExtensions = installedExtensions.filter(i => !prefersExecuteOnUI(i.manifest, this.productService, this.configurationService) @@ -111,7 +110,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench return Promise.reject(new Error(this.getDependentsErrorMessage(extension, dependentNonUIExtensions))); } } - return server.extensionManagementService.uninstall(extension, force); + return server.extensionManagementService.uninstall(extension, options); } private getDependentsErrorMessage(extension: ILocalExtension, dependents: ILocalExtension[]): string { @@ -283,75 +282,56 @@ export class ExtensionManagementService extends Disposable implements IWorkbench return Promise.reject(error); } - private getExtensionManagementServerToInstall(manifest: IExtensionManifest): IExtensionManagementServer | undefined { + getExtensionManagementServerToInstall(manifest: IExtensionManifest): IExtensionManagementServer | null { // Only local server if (this.servers.length === 1 && this.extensionManagementServerService.localExtensionManagementServer) { return this.extensionManagementServerService.localExtensionManagementServer; } - // 1. Install on preferred location - - // Install UI preferred extension on local server - if (prefersExecuteOnUI(manifest, this.productService, this.configurationService) && this.extensionManagementServerService.localExtensionManagementServer) { - return this.extensionManagementServerService.localExtensionManagementServer; - } - // Install Workspace preferred extension on remote server - if (prefersExecuteOnWorkspace(manifest, this.productService, this.configurationService) && this.extensionManagementServerService.remoteExtensionManagementServer) { - return this.extensionManagementServerService.remoteExtensionManagementServer; - } - // Install Web preferred extension on web server - if (prefersExecuteOnWeb(manifest, this.productService, this.configurationService) && this.extensionManagementServerService.webExtensionManagementServer) { - return this.extensionManagementServerService.webExtensionManagementServer; + const extensionKind = getExtensionKind(manifest, this.productService, this.configurationService); + for (const kind of extensionKind) { + if (kind === 'ui' && this.extensionManagementServerService.localExtensionManagementServer) { + return this.extensionManagementServerService.localExtensionManagementServer; + } + if (kind === 'workspace' && this.extensionManagementServerService.remoteExtensionManagementServer) { + return this.extensionManagementServerService.remoteExtensionManagementServer; + } + if (kind === 'web' && this.extensionManagementServerService.webExtensionManagementServer) { + return this.extensionManagementServerService.webExtensionManagementServer; + } } - // 2. Install on supported location - - // Install UI supported extension on local server - if (canExecuteOnUI(manifest, this.productService, this.configurationService) && this.extensionManagementServerService.localExtensionManagementServer) { - return this.extensionManagementServerService.localExtensionManagementServer; - } - // Install Workspace supported extension on remote server - if (canExecuteOnWorkspace(manifest, this.productService, this.configurationService) && this.extensionManagementServerService.remoteExtensionManagementServer) { - return this.extensionManagementServerService.remoteExtensionManagementServer; - } - // Install Web supported extension on web server - if (canExecuteOnWeb(manifest, this.productService, this.configurationService) && this.extensionManagementServerService.webExtensionManagementServer) { - return this.extensionManagementServerService.webExtensionManagementServer; - } - - return undefined; + // Local server can accept any extension. So return local server if not compatible server found. + return this.extensionManagementServerService.localExtensionManagementServer; } private async hasToFlagExtensionsMachineScoped(extensions: IGalleryExtension[]): Promise { if (!this.userDataAutoSyncEnablementService.isEnabled() || !this.userDataSyncResourceEnablementService.isResourceEnabled(SyncResource.Extensions)) { return false; } - return this.instantiationService.invokeFunction(async accessor => { - const dialogService = accessor.get(IDialogService); - const result = await dialogService.show( - Severity.Info, - extensions.length === 1 ? localize('install extension', "Install Extension") : localize('install extensions', "Install Extensions"), - [ - localize('install', "Install"), - localize('install and do no sync', "Install (Do not sync)"), - localize('cancel', "Cancel"), - ], - { - cancelId: 2, - detail: extensions.length === 1 - ? localize('install single extension', "Would you like to install and synchronize '{0}' extension across your devices?", extensions[0].displayName) - : localize('install multiple extensions', "Would you like to install and synchronize extensions across your devices?") - } - ); - switch (result.choice) { - case 0: - return false; - case 1: - return true; + const result = await this.dialogService.show( + Severity.Info, + extensions.length === 1 ? localize('install extension', "Install Extension") : localize('install extensions', "Install Extensions"), + [ + localize('install', "Install"), + localize('install and do no sync', "Install (Do not sync)"), + localize('cancel', "Cancel"), + ], + { + cancelId: 2, + detail: extensions.length === 1 + ? localize('install single extension', "Would you like to install and synchronize '{0}' extension across your devices?", extensions[0].displayName) + : localize('install multiple extensions', "Would you like to install and synchronize extensions across your devices?") } - throw canceled(); - }); + ); + switch (result.choice) { + case 0: + return false; + case 1: + return true; + } + throw canceled(); } getExtensionsReport(): Promise { diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts index 1bf8114d3..f0cc1bc86 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts @@ -25,6 +25,7 @@ import { Event } from 'vs/base/common/event'; import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls'; import { localize } from 'vs/nls'; import * as semver from 'vs/base/common/semver/semver'; +import { isArray } from 'vs/base/common/types'; interface IUserExtension { identifier: IExtensionIdentifier; @@ -125,25 +126,31 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten private async readDefaultUserWebExtensions(): Promise { const result: IStaticExtension[] = []; - const defaultUserWebExtensions = this.configurationService.getValue<{ location: string }[]>('_extensions.defaultUserWebExtensions') || []; - for (const webExtension of defaultUserWebExtensions) { - const extensionLocation = URI.parse(webExtension.location); - const manifestLocation = joinPath(extensionLocation, 'package.json'); - const context = await this.requestService.request({ type: 'GET', url: manifestLocation.toString(true) }, CancellationToken.None); - if (!isSuccess(context)) { - this.logService.warn('Skipped default user web extension as there is an error while fetching manifest', manifestLocation); - continue; + const defaultUserWebExtensions = this.configurationService.getValue<{ location: string }[]>('_extensions.defaultUserWebExtensions'); + if (isArray(defaultUserWebExtensions)) { + for (const webExtension of defaultUserWebExtensions) { + try { + const extensionLocation = URI.parse(webExtension.location); + const manifestLocation = joinPath(extensionLocation, 'package.json'); + const context = await this.requestService.request({ type: 'GET', url: manifestLocation.toString(true) }, CancellationToken.None); + if (!isSuccess(context)) { + this.logService.warn('Skipped default user web extension as there is an error while fetching manifest', manifestLocation); + continue; + } + const content = await asText(context); + if (!content) { + this.logService.warn('Skipped default user web extension as there is manifest is not found', manifestLocation); + continue; + } + const packageJSON = JSON.parse(content); + result.push({ + packageJSON, + extensionLocation, + }); + } catch (error) { + this.logService.warn('Skipped default user web extension as there is an error while fetching manifest', webExtension); + } } - const content = await asText(context); - if (!content) { - this.logService.warn('Skipped default user web extension as there is manifest is not found', manifestLocation); - continue; - } - const packageJSON = JSON.parse(content); - result.push({ - packageJSON, - extensionLocation, - }); } return result; } @@ -231,12 +238,12 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten }; } - async canAddExtension(galleryExtension: IGalleryExtension): Promise { + canAddExtension(galleryExtension: IGalleryExtension): boolean { return !!galleryExtension.properties.webExtension && !!galleryExtension.webResource; } async addExtension(galleryExtension: IGalleryExtension): Promise { - if (!(await this.canAddExtension(galleryExtension))) { + if (!this.canAddExtension(galleryExtension)) { const error = new Error(localize('cannot be installed', "Cannot install '{0}' because this extension is not a web extension.", galleryExtension.displayName || galleryExtension.name)); error.name = INSTALL_ERROR_NOT_SUPPORTED; throw error; diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementService.ts index a58a136ac..e8a05aff2 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementService.ts @@ -15,8 +15,8 @@ import { IDownloadService } from 'vs/platform/download/common/download'; import { IProductService } from 'vs/platform/product/common/productService'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { joinPath } from 'vs/base/common/resources'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IUserDataAutoSyncEnablementService, IUserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; export class ExtensionManagementService extends BaseExtensionManagementService { @@ -29,9 +29,9 @@ export class ExtensionManagementService extends BaseExtensionManagementService { @IDownloadService downloadService: IDownloadService, @IUserDataAutoSyncEnablementService userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService, @IUserDataSyncResourceEnablementService userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService, - @IInstantiationService instantiationService: IInstantiationService, + @IDialogService dialogService: IDialogService, ) { - super(extensionManagementServerService, extensionGalleryService, configurationService, productService, downloadService, userDataAutoSyncEnablementService, userDataSyncResourceEnablementService, instantiationService); + super(extensionManagementServerService, extensionGalleryService, configurationService, productService, downloadService, userDataAutoSyncEnablementService, userDataSyncResourceEnablementService, dialogService); } protected async installVSIX(vsix: URI, server: IExtensionManagementServer): Promise { diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionUrlTrustService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionUrlTrustService.ts new file mode 100644 index 000000000..3cabb4951 --- /dev/null +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionUrlTrustService.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createChannelSender } from 'vs/base/parts/ipc/common/ipc'; +import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; + +class ExtensionUrlTrustService { + + declare readonly _serviceBrand: undefined; + + constructor(@IMainProcessService mainProcessService: IMainProcessService) { + return createChannelSender(mainProcessService.getChannel('extensionUrlTrust')); + } +} + +registerSingleton(IExtensionUrlTrustService, ExtensionUrlTrustService); diff --git a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts index fe48d4f14..7259d0698 100644 --- a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts @@ -28,6 +28,8 @@ import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecy import { INotificationService } from 'vs/platform/notification/common/notification'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { mock } from 'vs/base/test/common/mock'; +import { IExtensionBisectService } from 'vs/workbench/services/extensionManagement/browser/extensionBisect'; function createStorageService(instantiationService: TestInstantiationService): IStorageService { let service = instantiationService.get(IStorageService); @@ -61,7 +63,8 @@ export class TestExtensionEnablementService extends ExtensionEnablementService { instantiationService.get(IUserDataSyncAccountService) || instantiationService.stub(IUserDataSyncAccountService, UserDataSyncAccountService), instantiationService.get(ILifecycleService) || instantiationService.stub(ILifecycleService, new TestLifecycleService()), instantiationService.get(INotificationService) || instantiationService.stub(INotificationService, new TestNotificationService()), - instantiationService.get(IHostService) + instantiationService.get(IHostService), + new class extends mock() { isDisabledByBisect() { return false; } } ); } @@ -542,36 +545,48 @@ suite('ExtensionEnablementService Test', () => { assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), true); }); - test('test web extension on local server is disabled by kind', async () => { + test('test web extension on local server is disabled by kind when web worker is not enabled', async () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`) }); + (instantiationService.get(IConfigurationService)).setUserConfiguration('extensions', { webWorker: false }); testObject = new TestExtensionEnablementService(instantiationService); - assert.ok(!testObject.isEnabled(localWorkspaceExtension)); + assert.equal(testObject.isEnabled(localWorkspaceExtension), false); assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); }); - test('test web extension on remote server is not disabled by kind when there is no local server', async () => { - instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(null, anExtensionManagementServer('vscode-remote', instantiationService), anExtensionManagementServer('web', instantiationService))); - const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + test('test web extension on local server is not disabled by kind when web worker is enabled', async () => { + instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`) }); + (instantiationService.get(IConfigurationService)).setUserConfiguration('extensions', { webWorker: true }); testObject = new TestExtensionEnablementService(instantiationService); - assert.ok(testObject.isEnabled(localWorkspaceExtension)); + assert.equal(testObject.isEnabled(localWorkspaceExtension), true); assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); }); - test('test web extension with no server is not disabled by kind when there is no local server', async () => { - instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(null, anExtensionManagementServer('vscode-remote', instantiationService), anExtensionManagementServer('web', instantiationService))); - const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.https }) }); + test('test web extension on remote server is disabled by kind when web worker is not enabled', async () => { + instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(anExtensionManagementServer('vscode-local', instantiationService), anExtensionManagementServer('vscode-remote', instantiationService), anExtensionManagementServer('web', instantiationService))); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`).with({ scheme: 'vscode-remote' }) }); + (instantiationService.get(IConfigurationService)).setUserConfiguration('extensions', { webWorker: false }); testObject = new TestExtensionEnablementService(instantiationService); - assert.ok(testObject.isEnabled(localWorkspaceExtension)); - assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); + assert.equal(testObject.isEnabled(localWorkspaceExtension), false); + assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); }); - test('test web extension with no server is not disabled by kind when there is no local and remote server', async () => { - instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(null, null, anExtensionManagementServer('web', instantiationService))); - const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.https }) }); + test('test web extension on remote server is disabled by kind when web worker is enabled', async () => { + instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(anExtensionManagementServer('vscode-local', instantiationService), anExtensionManagementServer('vscode-remote', instantiationService), anExtensionManagementServer('web', instantiationService))); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`).with({ scheme: 'vscode-remote' }) }); + (instantiationService.get(IConfigurationService)).setUserConfiguration('extensions', { webWorker: true }); testObject = new TestExtensionEnablementService(instantiationService); - assert.ok(testObject.isEnabled(localWorkspaceExtension)); - assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); + assert.equal(testObject.isEnabled(localWorkspaceExtension), false); + assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); + }); + + test('test web extension on web server is not disabled by kind', async () => { + instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(anExtensionManagementServer('vscode-local', instantiationService), anExtensionManagementServer('vscode-remote', instantiationService), anExtensionManagementServer('web', instantiationService))); + const webExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`).with({ scheme: 'web' }) }); + testObject = new TestExtensionEnablementService(instantiationService); + assert.equal(testObject.isEnabled(webExtension), true); + assert.deepEqual(testObject.getEnablementState(webExtension), EnablementState.EnabledGlobally); }); }); @@ -595,7 +610,7 @@ function anExtensionManagementServerService(localExtensionManagementServer: IExt _serviceBrand: undefined, localExtensionManagementServer, remoteExtensionManagementServer, - webExtensionManagementServer: null, + webExtensionManagementServer, getExtensionManagementServer: (extension: IExtension) => { if (extension.location.scheme === Schemas.file) { return localExtensionManagementServer; diff --git a/src/vs/workbench/services/extensionRecommendations/common/extensionIgnoredRecommendationsService.ts b/src/vs/workbench/services/extensionRecommendations/common/extensionIgnoredRecommendationsService.ts index ce9782b3a..d2a6f0012 100644 --- a/src/vs/workbench/services/extensionRecommendations/common/extensionIgnoredRecommendationsService.ts +++ b/src/vs/workbench/services/extensionRecommendations/common/extensionIgnoredRecommendationsService.ts @@ -7,8 +7,7 @@ import { distinct } from 'vs/base/common/arrays'; import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope } from 'vs/platform/storage/common/storage'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; +import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IExtensionIgnoredRecommendationsService, IgnoredRecommendationChangeNotification } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { IWorkpsaceExtensionsConfigService } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig'; @@ -35,12 +34,10 @@ export class ExtensionIgnoredRecommendationsService extends Disposable implement constructor( @IWorkpsaceExtensionsConfigService private readonly workpsaceExtensionsConfigService: IWorkpsaceExtensionsConfigService, @IStorageService private readonly storageService: IStorageService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, ) { super(); - storageKeysSyncRegistryService.registerStorageKey({ key: ignoredRecommendationsStorageKey, version: 1 }); this._globalIgnoredRecommendations = this.getCachedIgnoredRecommendations(); - this._register(this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); + this._register(this.storageService.onDidChangeValue(e => this.onDidStorageChange(e))); this.initIgnoredWorkspaceRecommendations(); } @@ -72,7 +69,7 @@ export class ExtensionIgnoredRecommendationsService extends Disposable implement return ignoredRecommendations.map(e => e.toLowerCase()); } - private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { + private onDidStorageChange(e: IStorageValueChangeEvent): void { if (e.key === ignoredRecommendationsStorageKey && e.scope === StorageScope.GLOBAL && this.ignoredRecommendationsValue !== this.getStoredIgnoredRecommendationsValue() /* This checks if current window changed the value or not */) { this._ignoredRecommendationsValue = undefined; @@ -106,7 +103,7 @@ export class ExtensionIgnoredRecommendationsService extends Disposable implement } private setStoredIgnoredRecommendationsValue(value: string): void { - this.storageService.store(ignoredRecommendationsStorageKey, value, StorageScope.GLOBAL); + this.storageService.store(ignoredRecommendationsStorageKey, value, StorageScope.GLOBAL, StorageTarget.USER); } } diff --git a/src/vs/workbench/services/extensionRecommendations/common/extensionRecommendations.ts b/src/vs/workbench/services/extensionRecommendations/common/extensionRecommendations.ts index 21fb2dcf2..0d76094d0 100644 --- a/src/vs/workbench/services/extensionRecommendations/common/extensionRecommendations.ts +++ b/src/vs/workbench/services/extensionRecommendations/common/extensionRecommendations.ts @@ -7,11 +7,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IStringDictionary } from 'vs/base/common/collections'; import { Event } from 'vs/base/common/event'; -export interface IExtensionsConfigContent { - recommendations: string[]; - unwantedRecommendations: string[]; -} - export type DynamicRecommendation = 'dynamic'; export type ConfigRecommendation = 'config'; export type ExecutableRecommendation = 'executable'; diff --git a/src/vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig.ts b/src/vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig.ts index dfcdaf501..dc16b3575 100644 --- a/src/vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig.ts +++ b/src/vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig.ts @@ -3,20 +3,28 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { coalesce, distinct, flatten } from 'vs/base/common/arrays'; +import { distinct, flatten } from 'vs/base/common/arrays'; import { Emitter, Event } from 'vs/base/common/event'; import { parse } from 'vs/base/common/json'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IFileService } from 'vs/platform/files/common/files'; +import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; +import { FileKind, IFileService } from 'vs/platform/files/common/files'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { localize } from 'vs/nls'; +import { URI } from 'vs/base/common/uri'; +import { IJSONEditingService, IJSONValue } from 'vs/workbench/services/configuration/common/jsonEditing'; +import { ResourceMap } from 'vs/base/common/map'; export const EXTENSIONS_CONFIG = '.vscode/extensions.json'; export interface IExtensionsConfigContent { - recommendations: string[]; - unwantedRecommendations: string[]; + recommendations?: string[]; + unwantedRecommendations?: string[]; } export const IWorkpsaceExtensionsConfigService = createDecorator('IWorkpsaceExtensionsConfigService'); @@ -26,8 +34,11 @@ export interface IWorkpsaceExtensionsConfigService { onDidChangeExtensionsConfigs: Event; getExtensionsConfigs(): Promise; + getRecommendations(): Promise; getUnwantedRecommendations(): Promise; + toggleRecommendation(extensionId: string): Promise; + toggleUnwantedRecommendation(extensionId: string): Promise; } export class WorkspaceExtensionsConfigService extends Disposable implements IWorkpsaceExtensionsConfigService { @@ -40,53 +51,217 @@ export class WorkspaceExtensionsConfigService extends Disposable implements IWor constructor( @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IFileService private readonly fileService: IFileService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IModelService private readonly modelService: IModelService, + @IModeService private readonly modeService: IModeService, + @IJSONEditingService private readonly jsonEditingService: IJSONEditingService, ) { super(); - this._register(this.workspaceContextService.onDidChangeWorkspaceFolders(e => this._onDidChangeExtensionsConfigs.fire())); + this._register(workspaceContextService.onDidChangeWorkspaceFolders(e => this._onDidChangeExtensionsConfigs.fire())); + this._register(fileService.onDidFilesChange(e => { + const workspace = workspaceContextService.getWorkspace(); + if ((workspace.configuration && e.affects(workspace.configuration)) + || workspace.folders.some(folder => e.affects(folder.toResource(EXTENSIONS_CONFIG))) + ) { + this._onDidChangeExtensionsConfigs.fire(); + } + })); } async getExtensionsConfigs(): Promise { const workspace = this.workspaceContextService.getWorkspace(); - const result = await Promise.all([ - this.resolveWorkspaceExtensionConfig(workspace), - ...workspace.folders.map(workspaceFolder => this.resolveWorkspaceFolderExtensionConfig(workspaceFolder)) - ]); - return coalesce(result); + const result: IExtensionsConfigContent[] = []; + const workspaceExtensionsConfigContent = workspace.configuration ? await this.resolveWorkspaceExtensionConfig(workspace.configuration) : undefined; + if (workspaceExtensionsConfigContent) { + result.push(workspaceExtensionsConfigContent); + } + result.push(...await Promise.all(workspace.folders.map(workspaceFolder => this.resolveWorkspaceFolderExtensionConfig(workspaceFolder)))); + return result; + } + + async getRecommendations(): Promise { + const configs = await this.getExtensionsConfigs(); + return distinct(flatten(configs.map(c => c.recommendations ? c.recommendations.map(c => c.toLowerCase()) : []))); } async getUnwantedRecommendations(): Promise { const configs = await this.getExtensionsConfigs(); - return distinct(flatten(configs.map(c => c.unwantedRecommendations.map(c => c.toLowerCase())))); + return distinct(flatten(configs.map(c => c.unwantedRecommendations ? c.unwantedRecommendations.map(c => c.toLowerCase()) : []))); } - private async resolveWorkspaceExtensionConfig(workspace: IWorkspace): Promise { - try { - if (workspace.configuration) { - const content = await this.fileService.readFile(workspace.configuration); - const extensionsConfigContent = parse(content.value.toString())['extensions']; - return this.parseExtensionConfig(extensionsConfigContent); + async toggleRecommendation(extensionId: string): Promise { + const workspace = this.workspaceContextService.getWorkspace(); + const workspaceExtensionsConfigContent = workspace.configuration ? await this.resolveWorkspaceExtensionConfig(workspace.configuration) : undefined; + const workspaceFolderExtensionsConfigContents = new ResourceMap(); + await Promise.all(workspace.folders.map(async workspaceFolder => { + const extensionsConfigContent = await this.resolveWorkspaceFolderExtensionConfig(workspaceFolder); + workspaceFolderExtensionsConfigContents.set(workspaceFolder.uri, extensionsConfigContent); + })); + + const isWorkspaceRecommended = workspaceExtensionsConfigContent && workspaceExtensionsConfigContent.recommendations?.some(r => r === extensionId); + const recommendedWorksapceFolders = workspace.folders.filter(workspaceFolder => workspaceFolderExtensionsConfigContents.get(workspaceFolder.uri)?.recommendations?.some(r => r === extensionId)); + const isRecommended = isWorkspaceRecommended || recommendedWorksapceFolders.length > 0; + + const workspaceOrFolders = isRecommended + ? await this.pickWorkspaceOrFolders(recommendedWorksapceFolders, isWorkspaceRecommended ? workspace : undefined, localize('select for remove', "Remove extension recommendation from")) + : await this.pickWorkspaceOrFolders(workspace.folders, workspace.configuration ? workspace : undefined, localize('select for add', "Add extension recommendation to")); + + for (const workspaceOrWorkspaceFolder of workspaceOrFolders) { + if (IWorkspace.isIWorkspace(workspaceOrWorkspaceFolder)) { + await this.addOrRemoveWorkspaceRecommendation(extensionId, workspaceOrWorkspaceFolder, workspaceExtensionsConfigContent, !isRecommended); + } else { + await this.addOrRemoveWorkspaceFolderRecommendation(extensionId, workspaceOrWorkspaceFolder, workspaceFolderExtensionsConfigContents.get(workspaceOrWorkspaceFolder.uri)!, !isRecommended); } - } catch (e) { /* Ignore */ } - return null; + } } - private async resolveWorkspaceFolderExtensionConfig(workspaceFolder: IWorkspaceFolder): Promise { + async toggleUnwantedRecommendation(extensionId: string): Promise { + const workspace = this.workspaceContextService.getWorkspace(); + const workspaceExtensionsConfigContent = workspace.configuration ? await this.resolveWorkspaceExtensionConfig(workspace.configuration) : undefined; + const workspaceFolderExtensionsConfigContents = new ResourceMap(); + await Promise.all(workspace.folders.map(async workspaceFolder => { + const extensionsConfigContent = await this.resolveWorkspaceFolderExtensionConfig(workspaceFolder); + workspaceFolderExtensionsConfigContents.set(workspaceFolder.uri, extensionsConfigContent); + })); + + const isWorkspaceUnwanted = workspaceExtensionsConfigContent && workspaceExtensionsConfigContent.unwantedRecommendations?.some(r => r === extensionId); + const unWantedWorksapceFolders = workspace.folders.filter(workspaceFolder => workspaceFolderExtensionsConfigContents.get(workspaceFolder.uri)?.unwantedRecommendations?.some(r => r === extensionId)); + const isUnwanted = isWorkspaceUnwanted || unWantedWorksapceFolders.length > 0; + + const workspaceOrFolders = isUnwanted + ? await this.pickWorkspaceOrFolders(unWantedWorksapceFolders, isWorkspaceUnwanted ? workspace : undefined, localize('select for remove', "Remove extension recommendation from")) + : await this.pickWorkspaceOrFolders(workspace.folders, workspace.configuration ? workspace : undefined, localize('select for add', "Add extension recommendation to")); + + for (const workspaceOrWorkspaceFolder of workspaceOrFolders) { + if (IWorkspace.isIWorkspace(workspaceOrWorkspaceFolder)) { + await this.addOrRemoveWorkspaceUnwantedRecommendation(extensionId, workspaceOrWorkspaceFolder, workspaceExtensionsConfigContent, !isUnwanted); + } else { + await this.addOrRemoveWorkspaceFolderUnwantedRecommendation(extensionId, workspaceOrWorkspaceFolder, workspaceFolderExtensionsConfigContents.get(workspaceOrWorkspaceFolder.uri)!, !isUnwanted); + } + } + } + + private async addOrRemoveWorkspaceFolderRecommendation(extensionId: string, workspaceFolder: IWorkspaceFolder, extensionsConfigContent: IExtensionsConfigContent, add: boolean): Promise { + const values: IJSONValue[] = []; + if (add) { + values.push({ path: ['recommendations'], value: [...extensionsConfigContent.recommendations || [], extensionId] }); + if (extensionsConfigContent.unwantedRecommendations && extensionsConfigContent.unwantedRecommendations.some(e => e === extensionId)) { + values.push({ path: ['unwantedRecommendations'], value: extensionsConfigContent.unwantedRecommendations.filter(e => e !== extensionId) }); + } + } else if (extensionsConfigContent.recommendations) { + values.push({ path: ['recommendations'], value: extensionsConfigContent.recommendations.filter(e => e !== extensionId) }); + } + + if (values.length) { + return this.jsonEditingService.write(workspaceFolder.toResource(EXTENSIONS_CONFIG), values, true); + } + } + + private async addOrRemoveWorkspaceRecommendation(extensionId: string, workspace: IWorkspace, extensionsConfigContent: IExtensionsConfigContent | undefined, add: boolean): Promise { + const values: IJSONValue[] = []; + if (extensionsConfigContent) { + if (add) { + values.push({ path: ['recommendations'], value: [...extensionsConfigContent.recommendations || [], extensionId] }); + if (extensionsConfigContent.unwantedRecommendations && extensionsConfigContent.unwantedRecommendations.some(e => e === extensionId)) { + values.push({ path: ['unwantedRecommendations'], value: extensionsConfigContent.unwantedRecommendations.filter(e => e !== extensionId) }); + } + } else if (extensionsConfigContent.recommendations) { + values.push({ path: ['recommendations'], value: extensionsConfigContent.recommendations.filter(e => e !== extensionId) }); + } + } else if (add) { + values.push({ path: ['extensions'], value: { recommendations: [extensionId] } }); + } + + if (values.length) { + return this.jsonEditingService.write(workspace.configuration!, values, true); + } + } + + private async addOrRemoveWorkspaceFolderUnwantedRecommendation(extensionId: string, workspaceFolder: IWorkspaceFolder, extensionsConfigContent: IExtensionsConfigContent, add: boolean): Promise { + const values: IJSONValue[] = []; + if (add) { + values.push({ path: ['unwantedRecommendations'], value: [...extensionsConfigContent.unwantedRecommendations || [], extensionId] }); + if (extensionsConfigContent.recommendations && extensionsConfigContent.recommendations.some(e => e === extensionId)) { + values.push({ path: ['recommendations'], value: extensionsConfigContent.recommendations.filter(e => e !== extensionId) }); + } + } else if (extensionsConfigContent.unwantedRecommendations) { + values.push({ path: ['unwantedRecommendations'], value: extensionsConfigContent.unwantedRecommendations.filter(e => e !== extensionId) }); + } + if (values.length) { + return this.jsonEditingService.write(workspaceFolder.toResource(EXTENSIONS_CONFIG), values, true); + } + } + + private async addOrRemoveWorkspaceUnwantedRecommendation(extensionId: string, workspace: IWorkspace, extensionsConfigContent: IExtensionsConfigContent | undefined, add: boolean): Promise { + const values: IJSONValue[] = []; + if (extensionsConfigContent) { + if (add) { + values.push({ path: ['unwantedRecommendations'], value: [...extensionsConfigContent.unwantedRecommendations || [], extensionId] }); + if (extensionsConfigContent.recommendations && extensionsConfigContent.recommendations.some(e => e === extensionId)) { + values.push({ path: ['recommendations'], value: extensionsConfigContent.recommendations.filter(e => e !== extensionId) }); + } + } else if (extensionsConfigContent.unwantedRecommendations) { + values.push({ path: ['unwantedRecommendations'], value: extensionsConfigContent.unwantedRecommendations.filter(e => e !== extensionId) }); + } + } else if (add) { + values.push({ path: ['extensions'], value: { unwantedRecommendations: [extensionId] } }); + } + + if (values.length) { + return this.jsonEditingService.write(workspace.configuration!, values, true); + } + } + + private async pickWorkspaceOrFolders(workspaceFolders: IWorkspaceFolder[], workspace: IWorkspace | undefined, placeHolder: string): Promise<(IWorkspace | IWorkspaceFolder)[]> { + const workspaceOrFolders = workspace ? [...workspaceFolders, workspace] : [...workspaceFolders]; + if (workspaceOrFolders.length === 1) { + return workspaceOrFolders; + } + + const folderPicks: (IQuickPickItem & { workspaceOrFolder: IWorkspace | IWorkspaceFolder } | IQuickPickSeparator)[] = workspaceFolders.map(workspaceFolder => { + return { + label: workspaceFolder.name, + description: localize('workspace folder', "Workspace Folder"), + workspaceOrFolder: workspaceFolder, + iconClasses: getIconClasses(this.modelService, this.modeService, workspaceFolder.uri, FileKind.ROOT_FOLDER) + }; + }); + + if (workspace) { + folderPicks.push({ type: 'separator' }); + folderPicks.push({ + label: localize('workspace', "Workspace"), + workspaceOrFolder: workspace, + }); + } + + const result = await this.quickInputService.pick(folderPicks, { placeHolder, canPickMany: true }) || []; + return result.map(r => r.workspaceOrFolder!); + } + + private async resolveWorkspaceExtensionConfig(workspaceConfigurationResource: URI): Promise { + try { + const content = await this.fileService.readFile(workspaceConfigurationResource); + const extensionsConfigContent = parse(content.value.toString())['extensions']; + return extensionsConfigContent ? this.parseExtensionConfig(extensionsConfigContent) : undefined; + } catch (e) { /* Ignore */ } + return undefined; + } + + private async resolveWorkspaceFolderExtensionConfig(workspaceFolder: IWorkspaceFolder): Promise { try { const content = await this.fileService.readFile(workspaceFolder.toResource(EXTENSIONS_CONFIG)); const extensionsConfigContent = parse(content.value.toString()); return this.parseExtensionConfig(extensionsConfigContent); } catch (e) { /* ignore */ } - return null; + return {}; } - private parseExtensionConfig(extensionsConfigContent: IExtensionsConfigContent | undefined): IExtensionsConfigContent | null { - if (extensionsConfigContent) { - return { - recommendations: distinct((extensionsConfigContent.recommendations || []).map(e => e.toLowerCase())), - unwantedRecommendations: distinct((extensionsConfigContent.unwantedRecommendations || []).map(e => e.toLowerCase())) - }; - } - return null; + private parseExtensionConfig(extensionsConfigContent: IExtensionsConfigContent): IExtensionsConfigContent { + return { + recommendations: distinct((extensionsConfigContent.recommendations || []).map(e => e.toLowerCase())), + unwantedRecommendations: distinct((extensionsConfigContent.unwantedRecommendations || []).map(e => e.toLowerCase())) + }; } } diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index 1dff19bf1..c6eb78294 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -54,7 +54,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten new ExtensionRunningLocationClassifier( productService, configurationService, - (extensionKinds, isInstalledLocally, isInstalledRemotely) => this._pickRunningLocation(extensionKinds, isInstalledLocally, isInstalledRemotely) + (extensionKinds, isInstalledLocally, isInstalledRemotely) => ExtensionService.pickRunningLocation(extensionKinds, isInstalledLocally, isInstalledRemotely) ), instantiationService, notificationService, @@ -137,11 +137,12 @@ export class ExtensionService extends AbstractExtensionService implements IExten }; } - private _pickRunningLocation(extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean): ExtensionRunningLocation { + public static pickRunningLocation(extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean): ExtensionRunningLocation { + let canRunRemotely = false; for (const extensionKind of extensionKinds) { if (extensionKind === 'ui' && isInstalledRemotely) { - // ui extensions run remotely if possible - return ExtensionRunningLocation.Remote; + // ui extensions run remotely if possible (but only as a last resort) + canRunRemotely = true; } if (extensionKind === 'workspace' && isInstalledRemotely) { // workspace extensions run remotely if possible @@ -152,7 +153,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten return ExtensionRunningLocation.LocalWebWorker; } } - return ExtensionRunningLocation.None; + return (canRunRemotely ? ExtensionRunningLocation.Remote : ExtensionRunningLocation.None); } protected _createExtensionHosts(_isInitialStart: boolean): IExtensionHost[] { diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index 207a43ec7..129ab322e 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -13,7 +13,7 @@ import { IWorkbenchExtensionEnablementService, EnablementState } from 'vs/workbe import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IURLHandler, IURLService, IOpenURLOptions } from 'vs/platform/url/common/url'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -26,24 +26,25 @@ import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/act import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; +import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust'; const FIVE_MINUTES = 5 * 60 * 1000; const THIRTY_SECONDS = 30 * 1000; const URL_TO_HANDLE = 'extensionUrlHandler.urlToHandle'; -const CONFIRMED_EXTENSIONS_CONFIGURATION_KEY = 'extensions.confirmedUriHandlerExtensionIds'; -const CONFIRMED_EXTENSIONS_STORAGE_KEY = 'extensionUrlHandler.confirmedExtensions'; +const USER_TRUSTED_EXTENSIONS_CONFIGURATION_KEY = 'extensions.confirmedUriHandlerExtensionIds'; +const USER_TRUSTED_EXTENSIONS_STORAGE_KEY = 'extensionUrlHandler.confirmedExtensions'; function isExtensionId(value: string): boolean { return /^[a-z0-9][a-z0-9\-]*\.[a-z0-9][a-z0-9\-]*$/i.test(value); } -class ConfirmedExtensionIdStorage { +class UserTrustedExtensionIdStorage { get extensions(): string[] { - const confirmedExtensionIdsJson = this.storageService.get(CONFIRMED_EXTENSIONS_STORAGE_KEY, StorageScope.GLOBAL, '[]'); + const userTrustedExtensionIdsJson = this.storageService.get(USER_TRUSTED_EXTENSIONS_STORAGE_KEY, StorageScope.GLOBAL, '[]'); try { - return JSON.parse(confirmedExtensionIdsJson); + return JSON.parse(userTrustedExtensionIdsJson); } catch { return []; } @@ -60,7 +61,7 @@ class ConfirmedExtensionIdStorage { } set(ids: string[]): void { - this.storageService.store(CONFIRMED_EXTENSIONS_STORAGE_KEY, JSON.stringify(ids), StorageScope.GLOBAL); + this.storageService.store(USER_TRUSTED_EXTENSIONS_STORAGE_KEY, JSON.stringify(ids), StorageScope.GLOBAL, StorageTarget.MACHINE); } } @@ -87,7 +88,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { private extensionHandlers = new Map(); private uriBuffer = new Map(); - private storage: ConfirmedExtensionIdStorage; + private userTrustedExtensionsStorage: UserTrustedExtensionIdStorage; private disposable: IDisposable; constructor( @@ -101,9 +102,10 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @IStorageService private readonly storageService: IStorageService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IProgressService private readonly progressService: IProgressService + @IProgressService private readonly progressService: IProgressService, + @IExtensionUrlTrustService private readonly extensionUrlTrustService: IExtensionUrlTrustService ) { - this.storage = new ConfirmedExtensionIdStorage(storageService); + this.userTrustedExtensionsStorage = new UserTrustedExtensionIdStorage(storageService); const interval = setInterval(() => this.garbageCollect(), THIRTY_SECONDS); const urlToHandleValue = this.storageService.get(URL_TO_HANDLE, StorageScope.WORKSPACE); @@ -118,7 +120,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { ); const cache = ExtensionUrlBootstrapHandler.cache; - setTimeout(() => cache.forEach(uri => this.handleURL(uri))); + setTimeout(() => cache.forEach(([uri, option]) => this.handleURL(uri, option))); } async handleURL(uri: URI, options?: IOpenURLOptions): Promise { @@ -135,14 +137,11 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { return true; } - let showConfirm: boolean; - if (options && options.trusted) { - showConfirm = false; - } else { - showConfirm = !this.isConfirmed(ExtensionIdentifier.toKey(extensionId)); - } + const trusted = options?.trusted + || (options?.originalUrl ? await this.extensionUrlTrustService.isExtensionUrlTrusted(extensionId, options.originalUrl) : false) + || this.didUserTrustExtension(ExtensionIdentifier.toKey(extensionId)); - if (showConfirm) { + if (!trusted) { let uriString = uri.toString(false); if (uriString.length > 40) { @@ -164,7 +163,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { } if (result.checkboxChecked) { - this.storage.add(ExtensionIdentifier.toKey(extensionId)); + this.userTrustedExtensionsStorage.add(ExtensionIdentifier.toKey(extensionId)); } } @@ -293,7 +292,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { } private async reloadAndHandle(url: URI): Promise { - this.storageService.store(URL_TO_HANDLE, JSON.stringify(url.toJSON()), StorageScope.WORKSPACE); + this.storageService.store(URL_TO_HANDLE, JSON.stringify(url.toJSON()), StorageScope.WORKSPACE, StorageTarget.MACHINE); await this.hostService.reload(); } @@ -313,22 +312,22 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { this.uriBuffer = uriBuffer; } - private isConfirmed(id: string): boolean { - if (this.storage.has(id)) { + private didUserTrustExtension(id: string): boolean { + if (this.userTrustedExtensionsStorage.has(id)) { return true; } - return this.getConfirmedExtensionIdsFromConfiguration().indexOf(id) > -1; + return this.getConfirmedTrustedExtensionIdsFromConfiguration().indexOf(id) > -1; } - private getConfirmedExtensionIdsFromConfiguration(): Array { - const confirmedExtensionIds = this.configurationService.getValue>(CONFIRMED_EXTENSIONS_CONFIGURATION_KEY); + private getConfirmedTrustedExtensionIdsFromConfiguration(): Array { + const trustedExtensionIds = this.configurationService.getValue>(USER_TRUSTED_EXTENSIONS_CONFIGURATION_KEY); - if (!Array.isArray(confirmedExtensionIds)) { + if (!Array.isArray(trustedExtensionIds)) { return []; } - return confirmedExtensionIds; + return trustedExtensionIds; } dispose(): void { @@ -346,10 +345,10 @@ registerSingleton(IExtensionUrlHandler, ExtensionUrlHandler); */ class ExtensionUrlBootstrapHandler implements IWorkbenchContribution, IURLHandler { - private static _cache: URI[] = []; + private static _cache: [URI, IOpenURLOptions | undefined][] = []; private static disposable: IDisposable; - static get cache(): URI[] { + static get cache(): [URI, IOpenURLOptions | undefined][] { ExtensionUrlBootstrapHandler.disposable.dispose(); const result = ExtensionUrlBootstrapHandler._cache; @@ -361,12 +360,12 @@ class ExtensionUrlBootstrapHandler implements IWorkbenchContribution, IURLHandle ExtensionUrlBootstrapHandler.disposable = urlService.registerHandler(this); } - async handleURL(uri: URI): Promise { + async handleURL(uri: URI, options?: IOpenURLOptions): Promise { if (!isExtensionId(uri.authority)) { return false; } - ExtensionUrlBootstrapHandler._cache.push(uri); + ExtensionUrlBootstrapHandler._cache.push([uri, options]); return true; } } @@ -391,7 +390,7 @@ class ManageAuthorizedExtensionURIsAction extends Action2 { async run(accessor: ServicesAccessor): Promise { const storageService = accessor.get(IStorageService); const quickInputService = accessor.get(IQuickInputService); - const storage = new ConfirmedExtensionIdStorage(storageService); + const storage = new UserTrustedExtensionIdStorage(storageService); const items = storage.extensions.map(label => ({ label, picked: true } as IQuickPickItem)); if (items.length === 0) { diff --git a/src/vs/workbench/services/extensions/common/extensionHostMain.ts b/src/vs/workbench/services/extensions/common/extensionHostMain.ts index e6b9d6fcb..b91ba18ad 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostMain.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostMain.ts @@ -60,7 +60,6 @@ export class ExtensionHostMain { const instaService: IInstantiationService = new InstantiationService(services, true); - // todo@joh // ugly self - inject const terminalService = instaService.invokeFunction(accessor => accessor.get(IExtHostTerminalService)); this._disposables.add(terminalService); @@ -71,7 +70,6 @@ export class ExtensionHostMain { logService.info('extension host started'); logService.trace('initData', initData); - // todo@joh // ugly self - inject // must call initialize *after* creating the extension service // because `initialize` itself creates instances that depend on it diff --git a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts index a68c61147..f974db68e 100644 --- a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts +++ b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts @@ -273,18 +273,14 @@ export class CachedExtensionScanner { finalBuiltinExtensions = ExtensionScanner.mergeBuiltinExtensions(builtinExtensions, extraBuiltinExtensions); } - const userExtensions = ( - !environmentService.extensionsPath - ? Promise.resolve([]) - : this._scanExtensionsWithCache( - hostService, - notificationService, - environmentService, - USER_MANIFEST_CACHE_FILE, - new ExtensionScannerInput(version, commit, locale, devMode, environmentService.extensionsPath, false, false, translations), - log - ) - ); + const userExtensions = (this._scanExtensionsWithCache( + hostService, + notificationService, + environmentService, + USER_MANIFEST_CACHE_FILE, + new ExtensionScannerInput(version, commit, locale, devMode, environmentService.extensionsPath, false, false, translations), + log + )); // Always load developed extensions while extensions development let developedExtensions: Promise = Promise.resolve([]); diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index 87fee781f..3050bc6e2 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -468,9 +468,12 @@ let _caCertificates: ReturnType | Promise; async function getCaCertificates(extHostLogService: ILogService) { if (!_caCertificates) { _caCertificates = readCaCertificates() - .then(res => res && res.certs.length ? res : undefined) + .then(res => { + extHostLogService.debug('ProxyResolver#getCaCertificates count', res && res.certs.length); + return res && res.certs.length ? res : undefined; + }) .catch(err => { - extHostLogService.error('ProxyResolver#getCertificates', toErrorMessage(err)); + extHostLogService.error('ProxyResolver#getCaCertificates error', toErrorMessage(err)); return undefined; }); } diff --git a/src/vs/workbench/services/extensions/test/electron-browser/extensionService.test.ts b/src/vs/workbench/services/extensions/test/electron-browser/extensionService.test.ts new file mode 100644 index 000000000..77a803dfa --- /dev/null +++ b/src/vs/workbench/services/extensions/test/electron-browser/extensionService.test.ts @@ -0,0 +1,88 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { ExtensionService as BrowserExtensionService } from 'vs/workbench/services/extensions/browser/extensionService'; +import { ExtensionRunningLocation } from 'vs/workbench/services/extensions/common/abstractExtensionService'; + +suite('BrowserExtensionService', () => { + test('pickRunningLocation', () => { + assert.deepEqual(BrowserExtensionService.pickRunningLocation([], false, false), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation([], false, true), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation([], true, false), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation([], true, true), ExtensionRunningLocation.None); + + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui'], false, false), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui'], false, true), ExtensionRunningLocation.Remote); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui'], true, false), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui'], true, true), ExtensionRunningLocation.Remote); + + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace'], false, false), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace'], false, true), ExtensionRunningLocation.Remote); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace'], true, false), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace'], true, true), ExtensionRunningLocation.Remote); + + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web'], false, false), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web'], false, true), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web'], true, false), ExtensionRunningLocation.LocalWebWorker); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web'], true, true), ExtensionRunningLocation.LocalWebWorker); + + + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], false, false), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], false, true), ExtensionRunningLocation.Remote); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], true, false), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], true, true), ExtensionRunningLocation.Remote); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], false, false), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], false, true), ExtensionRunningLocation.Remote); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], true, false), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], true, true), ExtensionRunningLocation.Remote); + + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], false, false), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], false, true), ExtensionRunningLocation.Remote); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], true, false), ExtensionRunningLocation.LocalWebWorker); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], true, true), ExtensionRunningLocation.LocalWebWorker); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], false, false), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], false, true), ExtensionRunningLocation.Remote); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], true, false), ExtensionRunningLocation.LocalWebWorker); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], true, true), ExtensionRunningLocation.Remote); + + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], false, false), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], false, true), ExtensionRunningLocation.Remote); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], true, false), ExtensionRunningLocation.LocalWebWorker); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], true, true), ExtensionRunningLocation.LocalWebWorker); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], false, false), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], false, true), ExtensionRunningLocation.Remote); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], true, false), ExtensionRunningLocation.LocalWebWorker); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], true, true), ExtensionRunningLocation.LocalWebWorker); + + + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], false, false), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], false, true), ExtensionRunningLocation.Remote); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], true, false), ExtensionRunningLocation.LocalWebWorker); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], true, true), ExtensionRunningLocation.LocalWebWorker); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], false, false), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], false, true), ExtensionRunningLocation.Remote); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], true, false), ExtensionRunningLocation.LocalWebWorker); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], true, true), ExtensionRunningLocation.Remote); + + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], false, false), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], false, true), ExtensionRunningLocation.Remote); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], true, false), ExtensionRunningLocation.LocalWebWorker); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], true, true), ExtensionRunningLocation.LocalWebWorker); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], false, false), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], false, true), ExtensionRunningLocation.Remote); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], true, false), ExtensionRunningLocation.LocalWebWorker); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], true, true), ExtensionRunningLocation.LocalWebWorker); + + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], false, false), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], false, true), ExtensionRunningLocation.Remote); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], true, false), ExtensionRunningLocation.LocalWebWorker); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], true, true), ExtensionRunningLocation.Remote); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], false, false), ExtensionRunningLocation.None); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], false, true), ExtensionRunningLocation.Remote); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], true, false), ExtensionRunningLocation.LocalWebWorker); + assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], true, true), ExtensionRunningLocation.Remote); + }); +}); diff --git a/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts b/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts index b39a5cbb9..f5c555225 100644 --- a/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts +++ b/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts @@ -8,14 +8,20 @@ let MonacoEnvironment = (self).MonacoEnvironment; let monacoBaseUrl = MonacoEnvironment && MonacoEnvironment.baseUrl ? MonacoEnvironment.baseUrl : '../../../../../'; + const trustedTypesPolicy = self.trustedTypes?.createPolicy('amdLoader', { createScriptURL: value => value }); + if (typeof (self).define !== 'function' || !(self).define.amd) { - importScripts(monacoBaseUrl + 'vs/loader.js'); + let loaderSrc: string | TrustedScriptURL = monacoBaseUrl + 'vs/loader.js'; + if (trustedTypesPolicy) { + loaderSrc = trustedTypesPolicy.createScriptURL(loaderSrc); + } + importScripts(loaderSrc as string); } require.config({ baseUrl: monacoBaseUrl, catchError: true, - createTrustedScriptURL: (value: string) => value + trustedTypesPolicy }); require(['vs/workbench/services/extensions/worker/extensionHostWorker'], () => { }, err => console.error(err)); diff --git a/src/vs/workbench/services/extensions/worker/httpWebWorkerExtensionHostIframe.html b/src/vs/workbench/services/extensions/worker/httpWebWorkerExtensionHostIframe.html index a99d3df29..392b4b4ae 100644 --- a/src/vs/workbench/services/extensions/worker/httpWebWorkerExtensionHostIframe.html +++ b/src/vs/workbench/services/extensions/worker/httpWebWorkerExtensionHostIframe.html @@ -1,7 +1,7 @@ - + `; diff --git a/extensions/emmet/src/test/toggleComment.test.ts b/extensions/emmet/src/test/toggleComment.test.ts index 4793c3ed8..42a867f9c 100644 --- a/extensions/emmet/src/test/toggleComment.test.ts +++ b/extensions/emmet/src/test/toggleComment.test.ts @@ -26,7 +26,7 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => {
  • Bye
    • - +
    • Another Node
    @@ -47,24 +47,24 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => { const expectedContents = `
      -
    • - - +
    • + +
    - + -->
    `; @@ -89,24 +89,24 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => { const expectedContents = `
      -
    • - +
    • +
    • Bye
    - + -->
    `; @@ -130,19 +130,19 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => { const expectedContents = `
      - +
    • Bye
      - +
    • Another Node
    --> + -->
    `; return withRandomFileEditor(contents, 'html', (editor, doc) => { @@ -252,16 +252,16 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => { const templateContents = ` `; const expectedContents = ` `; @@ -298,13 +298,13 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => { test('toggle comment with multiple cursors, but no selection (CSS)', () => { const expectedContents = ` .one { - /*margin: 10px;*/ + /* margin: 10px; */ padding: 10px; } - /*.two { + /* .two { height: 42px; display: none; - }*/ + } */ .three { width: 42px; }`; @@ -327,13 +327,13 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => { test('toggle comment with multiple cursors and whole node selected (CSS)', () => { const expectedContents = ` .one { - /*margin: 10px;*/ - /*padding: 10px;*/ + /* margin: 10px; */ + /* padding: 10px; */ } - /*.two { + /* .two { height: 42px; display: none; - }*/ + } */ .three { width: 42px; }`; @@ -359,16 +359,16 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => { test('toggle comment when multiple nodes of same parent are completely under single selection (CSS)', () => { const expectedContents = ` .one { -/* margin: 10px; - padding: 10px;*/ +/* margin: 10px; + padding: 10px; */ } - /*.two { + /* .two { height: 42px; display: none; } .three { width: 42px; - }*/`; + } */`; return withRandomFileEditor(contents, 'css', (editor, doc) => { editor.selections = [ new Selection(2, 0, 3, 16), // 2 properties completely under a single selection along with whitespace @@ -389,10 +389,10 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => { const expectedContents = ` .one { margin: 10px; - /*padding: 10px; + /* padding: 10px; } .two { - height: 42px;*/ + height: 42px; */ display: none; } .three { @@ -417,10 +417,10 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => { const expectedContents = ` .one { margin: 10px; - /*padding: 10px; + /* padding: 10px; } .two { - height: 42px;*/ + height: 42px; */ display: none; } .three { @@ -445,10 +445,10 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => { const expectedContents = ` .one { margin: 10px; - /*padding: 10px; + /* padding: 10px; } .two { - height: 42px;*/ + height: 42px; */ display: none; } .three { @@ -473,10 +473,10 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => { const expectedContents = ` .one { margin: 10px; - /*padding: 10px; + /* padding: 10px; } .two { - height: 42px;*/ + height: 42px; */ display: none; } .three { @@ -500,16 +500,16 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => { test('toggle comment when multiple nodes of same parent are partially under single selection (CSS)', () => { const expectedContents = ` .one { - /*margin: 10px; - padding: 10px;*/ + /* margin: 10px; + padding: 10px; */ } - /*.two { + /* .two { height: 42px; display: none; } .three { width: 42px; -*/ }`; + */ }`; return withRandomFileEditor(contents, 'css', (editor, doc) => { editor.selections = [ new Selection(2, 7, 3, 10), // 2 properties partially under a single selection @@ -549,14 +549,14 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => { test('toggle comment with multiple cursors selecting nested nodes (SCSS)', () => { const expectedContents = ` .one { - /*height: 42px;*/ + /* height: 42px; */ - /*.two { + /* .two { width: 42px; - }*/ + } */ .three { - /*padding: 10px;*/ + /* padding: 10px; */ } }`; return withRandomFileEditor(contents, 'css', (editor, doc) => { @@ -578,7 +578,7 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => { }); test('toggle comment with multiple cursors selecting several nested nodes (SCSS)', () => { const expectedContents = ` - /*.one { + /* .one { height: 42px; .two { @@ -588,7 +588,7 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => { .three { padding: 10px; } - }*/`; + } */`; return withRandomFileEditor(contents, 'css', (editor, doc) => { editor.selections = [ new Selection(1, 3, 1, 3), // cursor in the outside rule. And several cursors inside: @@ -611,14 +611,14 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => { test('toggle comment with multiple cursors, but no selection (SCSS)', () => { const expectedContents = ` .one { - /*height: 42px;*/ + /* height: 42px; */ - /*.two { + /* .two { width: 42px; - }*/ + } */ .three { - /*padding: 10px;*/ + /* padding: 10px; */ } }`; return withRandomFileEditor(contents, 'css', (editor, doc) => { @@ -641,14 +641,14 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => { test('toggle comment with multiple cursors and whole node selected (CSS)', () => { const expectedContents = ` .one { - /*height: 42px;*/ + /* height: 42px; */ - /*.two { + /* .two { width: 42px; - }*/ + } */ .three { - /*padding: 10px;*/ + /* padding: 10px; */ } }`; return withRandomFileEditor(contents, 'css', (editor, doc) => { @@ -673,11 +673,11 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => { test('toggle comment when multiple nodes are completely under single selection (CSS)', () => { const expectedContents = ` .one { - /*height: 42px; + /* height: 42px; .two { width: 42px; - }*/ + } */ .three { padding: 10px; @@ -701,11 +701,11 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => { test('toggle comment when multiple nodes are partially under single selection (CSS)', () => { const expectedContents = ` .one { - /*height: 42px; + /* height: 42px; .two { width: 42px; - */ } + */ } .three { padding: 10px; @@ -726,4 +726,29 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => { }); }); -}); \ No newline at end of file + test('toggle comment doesn\'t fail when start and end nodes differ HTML', () => { + const contents = ` +
    +

    Hello

    +
    + `; + const expectedContents = ` + + `; + return withRandomFileEditor(contents, 'html', (editor, doc) => { + editor.selections = [ + new Selection(1, 2, 2, 9), //
    to

    inclusive + ]; + + return toggleComment().then(() => { + assert.equal(doc.getText(), expectedContents); + return toggleComment().then(() => { + assert.equal(doc.getText(), contents); + return Promise.resolve(); + }); + }); + }); + }); +}); diff --git a/extensions/emmet/src/test/updateImageSize.test.ts b/extensions/emmet/src/test/updateImageSize.test.ts index 63452786a..b43b3e6ed 100644 --- a/extensions/emmet/src/test/updateImageSize.test.ts +++ b/extensions/emmet/src/test/updateImageSize.test.ts @@ -3,148 +3,147 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// import 'mocha'; -// import * as assert from 'assert'; -// import { Selection } from 'vscode'; -// import { withRandomFileEditor, closeAllEditors } from './testUtils'; -// import { updateImageSize } from '../updateImageSize'; +import 'mocha'; +import * as assert from 'assert'; +import { Selection } from 'vscode'; +import { withRandomFileEditor, closeAllEditors } from './testUtils'; +import { updateImageSize } from '../updateImageSize'; -// suite('Tests for Emmet actions on html tags', () => { -// teardown(closeAllEditors); +suite('Tests for Emmet actions on html tags', () => { + teardown(closeAllEditors); - // test('update image css with multiple cursors in css file', () => { - // const cssContents = ` - // .one { - // margin: 10px; - // padding: 10px; - // background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png); - // } - // .two { - // background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png); - // height: 42px; - // } - // .three { - // background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png); - // width: 42px; - // } - // `; - // const expectedContents = ` - // .one { - // margin: 10px; - // padding: 10px; - // background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png); - // width: 32px; - // height: 32px; - // } - // .two { - // background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png); - // width: 32px; - // height: 32px; - // } - // .three { - // background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png); - // height: 32px; - // width: 32px; - // } - // `; - // return withRandomFileEditor(cssContents, 'css', (editor, doc) => { - // editor.selections = [ - // new Selection(4, 50, 4, 50), - // new Selection(7, 50, 7, 50), - // new Selection(11, 50, 11, 50) - // ]; + test('update image css with multiple cursors in css file', () => { + const cssContents = ` + .one { + margin: 10px; + padding: 10px; + background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png); + } + .two { + background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png); + height: 42px; + } + .three { + background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png); + width: 42px; + } + `; + const expectedContents = ` + .one { + margin: 10px; + padding: 10px; + background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png); + width: 1024px; + height: 1024px; + } + .two { + background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png); + width: 1024px; + height: 1024px; + } + .three { + background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png); + height: 1024px; + width: 1024px; + } + `; + return withRandomFileEditor(cssContents, 'css', (editor, doc) => { + editor.selections = [ + new Selection(4, 50, 4, 50), + new Selection(7, 50, 7, 50), + new Selection(11, 50, 11, 50) + ]; - // return updateImageSize()!.then(() => { - // assert.equal(doc.getText(), expectedContents); - // return Promise.resolve(); - // }); - // }); - // }); + return updateImageSize()!.then(() => { + assert.equal(doc.getText(), expectedContents); + return Promise.resolve(); + }); + }); + }); - // test('update image size in css in html file with multiple cursors', () => { - // const htmlWithCssContents = ` - // - // - // - // `; - // const expectedContents = ` - // - // - // - // `; - // return withRandomFileEditor(htmlWithCssContents, 'html', (editor, doc) => { - // editor.selections = [ - // new Selection(6, 50, 6, 50), - // new Selection(9, 50, 9, 50), - // new Selection(13, 50, 13, 50) - // ]; + test('update image size in css in html file with multiple cursors', () => { + const htmlWithCssContents = ` + + + + `; + const expectedContents = ` + + + + `; + return withRandomFileEditor(htmlWithCssContents, 'html', (editor, doc) => { + editor.selections = [ + new Selection(6, 50, 6, 50), + new Selection(9, 50, 9, 50), + new Selection(13, 50, 13, 50) + ]; - // return updateImageSize()!.then(() => { - // assert.equal(doc.getText(), expectedContents); - // return Promise.resolve(); - // }); - // }); - // }); + return updateImageSize()!.then(() => { + assert.equal(doc.getText(), expectedContents); + return Promise.resolve(); + }); + }); + }); - // test('update image size in img tag in html file with multiple cursors', () => { - // const htmlwithimgtag = ` - // - // - // - // - // - // `; - // const expectedContents = ` - // - // - // - // - // - // `; - // return withRandomFileEditor(htmlwithimgtag, 'html', (editor, doc) => { - // editor.selections = [ - // new Selection(2, 50, 2, 50), - // new Selection(3, 50, 3, 50), - // new Selection(4, 50, 4, 50) - // ]; + test('update image size in img tag in html file with multiple cursors', () => { + const htmlwithimgtag = ` + + + + + + `; + const expectedContents = ` + + + + + + `; + return withRandomFileEditor(htmlwithimgtag, 'html', (editor, doc) => { + editor.selections = [ + new Selection(2, 50, 2, 50), + new Selection(3, 50, 3, 50), + new Selection(4, 50, 4, 50) + ]; - // return updateImageSize()!.then(() => { - // assert.equal(doc.getText(), expectedContents); - // return Promise.resolve(); - // }); - // }); - // }); - -// }); + return updateImageSize()!.then(() => { + assert.equal(doc.getText(), expectedContents); + return Promise.resolve(); + }); + }); + }); +}); diff --git a/extensions/emmet/src/test/wrapWithAbbreviation.test.ts b/extensions/emmet/src/test/wrapWithAbbreviation.test.ts index d5a4a2bce..c89e6942d 100644 --- a/extensions/emmet/src/test/wrapWithAbbreviation.test.ts +++ b/extensions/emmet/src/test/wrapWithAbbreviation.test.ts @@ -9,13 +9,20 @@ import { Selection, workspace, ConfigurationTarget } from 'vscode'; import { withRandomFileEditor, closeAllEditors } from './testUtils'; import { wrapWithAbbreviation, wrapIndividualLinesWithAbbreviation } from '../abbreviationActions'; -const htmlContentsForWrapTests = ` +const htmlContentsForBlockWrapTests = `

    `; +const htmlContentsForInlineWrapTests = ` + +`; + const wrapBlockElementExpected = ` `; +// technically a bug, but also a feature (requested behaviour) +// https://github.com/microsoft/vscode/issues/78015 const wrapInlineElementExpectedFormatFalse = ` `; @@ -73,51 +90,51 @@ suite('Tests for Wrap with Abbreviations', () => { const oldValueForSyntaxProfiles = workspace.getConfiguration('emmet').inspect('syntaxProfile'); test('Wrap with block element using multi cursor', () => { - return testWrapWithAbbreviation(multiCursors, 'div', wrapBlockElementExpected); + return testWrapWithAbbreviation(multiCursors, 'div', wrapBlockElementExpected, htmlContentsForBlockWrapTests); }); test('Wrap with inline element using multi cursor', () => { - return testWrapWithAbbreviation(multiCursors, 'span', wrapInlineElementExpected); + return testWrapWithAbbreviation(multiCursors, 'span', wrapInlineElementExpected, htmlContentsForInlineWrapTests); }); test('Wrap with snippet using multi cursor', () => { - return testWrapWithAbbreviation(multiCursors, 'a', wrapSnippetExpected); + return testWrapWithAbbreviation(multiCursors, 'a', wrapSnippetExpected, htmlContentsForBlockWrapTests); }); test('Wrap with multi line abbreviation using multi cursor', () => { - return testWrapWithAbbreviation(multiCursors, 'ul>li', wrapMultiLineAbbrExpected); + return testWrapWithAbbreviation(multiCursors, 'ul>li', wrapMultiLineAbbrExpected, htmlContentsForBlockWrapTests); }); test('Wrap with block element using multi cursor selection', () => { - return testWrapWithAbbreviation(multiCursorsWithSelection, 'div', wrapBlockElementExpected); + return testWrapWithAbbreviation(multiCursorsWithSelection, 'div', wrapBlockElementExpected, htmlContentsForBlockWrapTests); }); test('Wrap with inline element using multi cursor selection', () => { - return testWrapWithAbbreviation(multiCursorsWithSelection, 'span', wrapInlineElementExpected); + return testWrapWithAbbreviation(multiCursorsWithSelection, 'span', wrapInlineElementExpected, htmlContentsForInlineWrapTests); }); test('Wrap with snippet using multi cursor selection', () => { - return testWrapWithAbbreviation(multiCursorsWithSelection, 'a', wrapSnippetExpected); + return testWrapWithAbbreviation(multiCursorsWithSelection, 'a', wrapSnippetExpected, htmlContentsForBlockWrapTests); }); test('Wrap with multi line abbreviation using multi cursor selection', () => { - return testWrapWithAbbreviation(multiCursorsWithSelection, 'ul>li', wrapMultiLineAbbrExpected); + return testWrapWithAbbreviation(multiCursorsWithSelection, 'ul>li', wrapMultiLineAbbrExpected, htmlContentsForBlockWrapTests); }); test('Wrap with block element using multi cursor full line selection', () => { - return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'div', wrapBlockElementExpected); + return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'div', wrapBlockElementExpected, htmlContentsForBlockWrapTests); }); test('Wrap with inline element using multi cursor full line selection', () => { - return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'span', wrapInlineElementExpected); + return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'span', wrapInlineElementExpected, htmlContentsForInlineWrapTests); }); test('Wrap with snippet using multi cursor full line selection', () => { - return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'a', wrapSnippetExpected); + return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'a', wrapSnippetExpected, htmlContentsForBlockWrapTests); }); test('Wrap with multi line abbreviation using multi cursor full line selection', () => { - return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'ul>li', wrapMultiLineAbbrExpected); + return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'ul>li', wrapMultiLineAbbrExpected, htmlContentsForBlockWrapTests); }); test('Wrap with abbreviation and comment filter', () => { @@ -128,15 +145,31 @@ suite('Tests for Wrap with Abbreviations', () => { `; const expectedContents = ` `; return testWrapWithAbbreviation([new Selection(2, 0, 2, 0)], 'li.hello|c', expectedContents, contents); }); + test('Wrap with abbreviation link', () => { + const contents = ` + + `; + const expectedContents = ` + +
    + +
    +
    + `; + return testWrapWithAbbreviation([new Selection(1, 1, 1, 1)], 'a[href="https://example.com"]>div', expectedContents, contents); + }); + test('Wrap with abbreviation entire node when cursor is on opening tag', () => { const contents = ` ', 'adiv classname="">', fuzzyScore); + }); + test('Suggestion is not highlighted #85826', function () { assertMatches('SemanticTokens', 'SemanticTokensEdits', '^S^e^m^a^n^t^i^c^T^o^k^e^n^sEdits', fuzzyScore); assertMatches('SemanticTokens', 'SemanticTokensEdits', '^S^e^m^a^n^t^i^c^T^o^k^e^n^sEdits', fuzzyScoreGracefulAggressive); }); + + test('IntelliSense completion not correctly highlighting text in front of cursor #115250', function () { + assertMatches('lo', 'log', '^l^og', fuzzyScore); + assertMatches('.lo', 'log', '^l^og', anyScore); + assertMatches('.', 'log', 'log', anyScore); + }); }); diff --git a/src/vs/base/test/common/fuzzyScorer.test.ts b/src/vs/base/test/common/fuzzyScorer.test.ts index 5aac1fb98..59c52ce00 100644 --- a/src/vs/base/test/common/fuzzyScorer.test.ts +++ b/src/vs/base/test/common/fuzzyScorer.test.ts @@ -120,30 +120,30 @@ suite('Fuzzy Scorer', () => { // Assert scoring order let sortedScores = scores.concat().sort((a, b) => b[0] - a[0]); - assert.deepEqual(scores, sortedScores); + assert.deepStrictEqual(scores, sortedScores); // Assert scoring positions // let positions = scores[0][1]; - // assert.equal(positions.length, 'HelLo-World'.length); + // assert.strictEqual(positions.length, 'HelLo-World'.length); // positions = scores[2][1]; - // assert.equal(positions.length, 'HW'.length); - // assert.equal(positions[0], 0); - // assert.equal(positions[1], 6); + // assert.strictEqual(positions.length, 'HW'.length); + // assert.strictEqual(positions[0], 0); + // assert.strictEqual(positions[1], 6); }); test('score (non fuzzy)', function () { const target = 'HeLlo-World'; assert.ok(_doScore(target, 'HelLo-World', false)[0] > 0); - assert.equal(_doScore(target, 'HelLo-World', false)[1].length, 'HelLo-World'.length); + assert.strictEqual(_doScore(target, 'HelLo-World', false)[1].length, 'HelLo-World'.length); assert.ok(_doScore(target, 'hello-world', false)[0] > 0); - assert.equal(_doScore(target, 'HW', false)[0], 0); + assert.strictEqual(_doScore(target, 'HW', false)[0], 0); assert.ok(_doScore(target, 'h', false)[0] > 0); assert.ok(_doScore(target, 'ello', false)[0] > 0); assert.ok(_doScore(target, 'ld', false)[0] > 0); - assert.equal(_doScore(target, 'eo', false)[0], 0); + assert.strictEqual(_doScore(target, 'eo', false)[0], 0); }); test('scoreItem - matches are proper', function () { @@ -158,52 +158,52 @@ suite('Fuzzy Scorer', () => { // Path Identity const identityRes = scoreItem(resource, ResourceAccessor.getItemPath(resource), true, ResourceAccessor); assert.ok(identityRes.score); - assert.equal(identityRes.descriptionMatch!.length, 1); - assert.equal(identityRes.labelMatch!.length, 1); - assert.equal(identityRes.descriptionMatch![0].start, 0); - assert.equal(identityRes.descriptionMatch![0].end, ResourceAccessor.getItemDescription(resource).length); - assert.equal(identityRes.labelMatch![0].start, 0); - assert.equal(identityRes.labelMatch![0].end, ResourceAccessor.getItemLabel(resource).length); + assert.strictEqual(identityRes.descriptionMatch!.length, 1); + assert.strictEqual(identityRes.labelMatch!.length, 1); + assert.strictEqual(identityRes.descriptionMatch![0].start, 0); + assert.strictEqual(identityRes.descriptionMatch![0].end, ResourceAccessor.getItemDescription(resource).length); + assert.strictEqual(identityRes.labelMatch![0].start, 0); + assert.strictEqual(identityRes.labelMatch![0].end, ResourceAccessor.getItemLabel(resource).length); // Basename Prefix const basenamePrefixRes = scoreItem(resource, 'som', true, ResourceAccessor); assert.ok(basenamePrefixRes.score); assert.ok(!basenamePrefixRes.descriptionMatch); - assert.equal(basenamePrefixRes.labelMatch!.length, 1); - assert.equal(basenamePrefixRes.labelMatch![0].start, 0); - assert.equal(basenamePrefixRes.labelMatch![0].end, 'som'.length); + assert.strictEqual(basenamePrefixRes.labelMatch!.length, 1); + assert.strictEqual(basenamePrefixRes.labelMatch![0].start, 0); + assert.strictEqual(basenamePrefixRes.labelMatch![0].end, 'som'.length); // Basename Camelcase const basenameCamelcaseRes = scoreItem(resource, 'sF', true, ResourceAccessor); assert.ok(basenameCamelcaseRes.score); assert.ok(!basenameCamelcaseRes.descriptionMatch); - assert.equal(basenameCamelcaseRes.labelMatch!.length, 2); - assert.equal(basenameCamelcaseRes.labelMatch![0].start, 0); - assert.equal(basenameCamelcaseRes.labelMatch![0].end, 1); - assert.equal(basenameCamelcaseRes.labelMatch![1].start, 4); - assert.equal(basenameCamelcaseRes.labelMatch![1].end, 5); + assert.strictEqual(basenameCamelcaseRes.labelMatch!.length, 2); + assert.strictEqual(basenameCamelcaseRes.labelMatch![0].start, 0); + assert.strictEqual(basenameCamelcaseRes.labelMatch![0].end, 1); + assert.strictEqual(basenameCamelcaseRes.labelMatch![1].start, 4); + assert.strictEqual(basenameCamelcaseRes.labelMatch![1].end, 5); // Basename Match const basenameRes = scoreItem(resource, 'of', true, ResourceAccessor); assert.ok(basenameRes.score); assert.ok(!basenameRes.descriptionMatch); - assert.equal(basenameRes.labelMatch!.length, 2); - assert.equal(basenameRes.labelMatch![0].start, 1); - assert.equal(basenameRes.labelMatch![0].end, 2); - assert.equal(basenameRes.labelMatch![1].start, 4); - assert.equal(basenameRes.labelMatch![1].end, 5); + assert.strictEqual(basenameRes.labelMatch!.length, 2); + assert.strictEqual(basenameRes.labelMatch![0].start, 1); + assert.strictEqual(basenameRes.labelMatch![0].end, 2); + assert.strictEqual(basenameRes.labelMatch![1].start, 4); + assert.strictEqual(basenameRes.labelMatch![1].end, 5); // Path Match const pathRes = scoreItem(resource, 'xyz123', true, ResourceAccessor); assert.ok(pathRes.score); assert.ok(pathRes.descriptionMatch); assert.ok(pathRes.labelMatch); - assert.equal(pathRes.labelMatch!.length, 1); - assert.equal(pathRes.labelMatch![0].start, 8); - assert.equal(pathRes.labelMatch![0].end, 11); - assert.equal(pathRes.descriptionMatch!.length, 1); - assert.equal(pathRes.descriptionMatch![0].start, 1); - assert.equal(pathRes.descriptionMatch![0].end, 4); + assert.strictEqual(pathRes.labelMatch!.length, 1); + assert.strictEqual(pathRes.labelMatch![0].start, 8); + assert.strictEqual(pathRes.labelMatch![0].end, 11); + assert.strictEqual(pathRes.descriptionMatch!.length, 1); + assert.strictEqual(pathRes.descriptionMatch![0].start, 1); + assert.strictEqual(pathRes.descriptionMatch![0].end, 4); // No Match const noRes = scoreItem(resource, '987', true, ResourceAccessor); @@ -223,51 +223,51 @@ suite('Fuzzy Scorer', () => { let res1 = scoreItem(resource, 'xyz some', true, ResourceAccessor); assert.ok(res1.score); - assert.equal(res1.labelMatch?.length, 1); - assert.equal(res1.labelMatch![0].start, 0); - assert.equal(res1.labelMatch![0].end, 4); - assert.equal(res1.descriptionMatch?.length, 1); - assert.equal(res1.descriptionMatch![0].start, 1); - assert.equal(res1.descriptionMatch![0].end, 4); + assert.strictEqual(res1.labelMatch?.length, 1); + assert.strictEqual(res1.labelMatch![0].start, 0); + assert.strictEqual(res1.labelMatch![0].end, 4); + assert.strictEqual(res1.descriptionMatch?.length, 1); + assert.strictEqual(res1.descriptionMatch![0].start, 1); + assert.strictEqual(res1.descriptionMatch![0].end, 4); let res2 = scoreItem(resource, 'some xyz', true, ResourceAccessor); assert.ok(res2.score); - assert.equal(res1.score, res2.score); - assert.equal(res2.labelMatch?.length, 1); - assert.equal(res2.labelMatch![0].start, 0); - assert.equal(res2.labelMatch![0].end, 4); - assert.equal(res2.descriptionMatch?.length, 1); - assert.equal(res2.descriptionMatch![0].start, 1); - assert.equal(res2.descriptionMatch![0].end, 4); + assert.strictEqual(res1.score, res2.score); + assert.strictEqual(res2.labelMatch?.length, 1); + assert.strictEqual(res2.labelMatch![0].start, 0); + assert.strictEqual(res2.labelMatch![0].end, 4); + assert.strictEqual(res2.descriptionMatch?.length, 1); + assert.strictEqual(res2.descriptionMatch![0].start, 1); + assert.strictEqual(res2.descriptionMatch![0].end, 4); let res3 = scoreItem(resource, 'some xyz file file123', true, ResourceAccessor); assert.ok(res3.score); assert.ok(res3.score > res2.score); - assert.equal(res3.labelMatch?.length, 1); - assert.equal(res3.labelMatch![0].start, 0); - assert.equal(res3.labelMatch![0].end, 11); - assert.equal(res3.descriptionMatch?.length, 1); - assert.equal(res3.descriptionMatch![0].start, 1); - assert.equal(res3.descriptionMatch![0].end, 4); + assert.strictEqual(res3.labelMatch?.length, 1); + assert.strictEqual(res3.labelMatch![0].start, 0); + assert.strictEqual(res3.labelMatch![0].end, 11); + assert.strictEqual(res3.descriptionMatch?.length, 1); + assert.strictEqual(res3.descriptionMatch![0].start, 1); + assert.strictEqual(res3.descriptionMatch![0].end, 4); let res4 = scoreItem(resource, 'path z y', true, ResourceAccessor); assert.ok(res4.score); assert.ok(res4.score < res2.score); - assert.equal(res4.labelMatch?.length, 0); - assert.equal(res4.descriptionMatch?.length, 2); - assert.equal(res4.descriptionMatch![0].start, 2); - assert.equal(res4.descriptionMatch![0].end, 4); - assert.equal(res4.descriptionMatch![1].start, 10); - assert.equal(res4.descriptionMatch![1].end, 14); + assert.strictEqual(res4.labelMatch?.length, 0); + assert.strictEqual(res4.descriptionMatch?.length, 2); + assert.strictEqual(res4.descriptionMatch![0].start, 2); + assert.strictEqual(res4.descriptionMatch![0].end, 4); + assert.strictEqual(res4.descriptionMatch![1].start, 10); + assert.strictEqual(res4.descriptionMatch![1].end, 14); }); test('scoreItem - invalid input', function () { let res = scoreItem(null, null!, true, ResourceAccessor); - assert.equal(res.score, 0); + assert.strictEqual(res.score, 0); res = scoreItem(null, 'null', true, ResourceAccessor); - assert.equal(res.score, 0); + assert.strictEqual(res.score, 0); }); test('scoreItem - optimize for file paths', function () { @@ -280,12 +280,12 @@ suite('Fuzzy Scorer', () => { assert.ok(pathRes.score); assert.ok(pathRes.descriptionMatch); assert.ok(pathRes.labelMatch); - assert.equal(pathRes.labelMatch!.length, 1); - assert.equal(pathRes.labelMatch![0].start, 0); - assert.equal(pathRes.labelMatch![0].end, 7); - assert.equal(pathRes.descriptionMatch!.length, 1); - assert.equal(pathRes.descriptionMatch![0].start, 23); - assert.equal(pathRes.descriptionMatch![0].end, 26); + assert.strictEqual(pathRes.labelMatch!.length, 1); + assert.strictEqual(pathRes.labelMatch![0].start, 0); + assert.strictEqual(pathRes.labelMatch![0].end, 7); + assert.strictEqual(pathRes.descriptionMatch!.length, 1); + assert.strictEqual(pathRes.descriptionMatch![0].start, 23); + assert.strictEqual(pathRes.descriptionMatch![0].end, 26); }); test('scoreItem - avoid match scattering (bug #36119)', function () { @@ -295,9 +295,9 @@ suite('Fuzzy Scorer', () => { assert.ok(pathRes.score); assert.ok(pathRes.descriptionMatch); assert.ok(pathRes.labelMatch); - assert.equal(pathRes.labelMatch!.length, 1); - assert.equal(pathRes.labelMatch![0].start, 0); - assert.equal(pathRes.labelMatch![0].end, 9); + assert.strictEqual(pathRes.labelMatch!.length, 1); + assert.strictEqual(pathRes.labelMatch![0].start, 0); + assert.strictEqual(pathRes.labelMatch![0].end, 9); }); test('scoreItem - prefers more compact matches', function () { @@ -309,11 +309,11 @@ suite('Fuzzy Scorer', () => { assert.ok(res.score); assert.ok(res.descriptionMatch); assert.ok(!res.labelMatch!.length); - assert.equal(res.descriptionMatch!.length, 2); - assert.equal(res.descriptionMatch![0].start, 11); - assert.equal(res.descriptionMatch![0].end, 12); - assert.equal(res.descriptionMatch![1].start, 13); - assert.equal(res.descriptionMatch![1].end, 14); + assert.strictEqual(res.descriptionMatch!.length, 2); + assert.strictEqual(res.descriptionMatch![0].start, 11); + assert.strictEqual(res.descriptionMatch![0].end, 12); + assert.strictEqual(res.descriptionMatch![1].start, 13); + assert.strictEqual(res.descriptionMatch![1].end, 14); }); test('scoreItem - proper target offset', function () { @@ -328,9 +328,9 @@ suite('Fuzzy Scorer', () => { const res = scoreItem(resource, 'de', true, ResourceAccessor); - assert.equal(res.labelMatch!.length, 1); - assert.equal(res.labelMatch![0].start, 1); - assert.equal(res.labelMatch![0].end, 3); + assert.strictEqual(res.labelMatch!.length, 1); + assert.strictEqual(res.labelMatch![0].start, 1); + assert.strictEqual(res.labelMatch![0].end, 3); }); test('scoreItem - proper target offset #3', function () { @@ -338,19 +338,19 @@ suite('Fuzzy Scorer', () => { const res = scoreItem(resource, 'debug', true, ResourceAccessor); - assert.equal(res.descriptionMatch!.length, 3); - assert.equal(res.descriptionMatch![0].start, 9); - assert.equal(res.descriptionMatch![0].end, 10); - assert.equal(res.descriptionMatch![1].start, 36); - assert.equal(res.descriptionMatch![1].end, 37); - assert.equal(res.descriptionMatch![2].start, 40); - assert.equal(res.descriptionMatch![2].end, 41); + assert.strictEqual(res.descriptionMatch!.length, 3); + assert.strictEqual(res.descriptionMatch![0].start, 9); + assert.strictEqual(res.descriptionMatch![0].end, 10); + assert.strictEqual(res.descriptionMatch![1].start, 36); + assert.strictEqual(res.descriptionMatch![1].end, 37); + assert.strictEqual(res.descriptionMatch![2].start, 40); + assert.strictEqual(res.descriptionMatch![2].end, 41); - assert.equal(res.labelMatch!.length, 2); - assert.equal(res.labelMatch![0].start, 9); - assert.equal(res.labelMatch![0].end, 10); - assert.equal(res.labelMatch![1].start, 20); - assert.equal(res.labelMatch![1].end, 21); + assert.strictEqual(res.labelMatch!.length, 2); + assert.strictEqual(res.labelMatch![0].start, 9); + assert.strictEqual(res.labelMatch![0].end, 10); + assert.strictEqual(res.labelMatch![1].start, 20); + assert.strictEqual(res.labelMatch![1].end, 21); }); test('scoreItem - no match unless query contained in sequence', function () { @@ -394,27 +394,27 @@ suite('Fuzzy Scorer', () => { let query = ResourceAccessor.getItemPath(resourceA); let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); + assert.strictEqual(res[2], resourceC); res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); + assert.strictEqual(res[2], resourceC); // Full resource B path query = ResourceAccessor.getItemPath(resourceB); res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); + assert.strictEqual(res[2], resourceC); res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); + assert.strictEqual(res[2], resourceC); }); test('compareFilesByScore - basename prefix', function () { @@ -426,27 +426,27 @@ suite('Fuzzy Scorer', () => { let query = ResourceAccessor.getItemLabel(resourceA); let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); + assert.strictEqual(res[2], resourceC); res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); + assert.strictEqual(res[2], resourceC); // Full resource B basename query = ResourceAccessor.getItemLabel(resourceB); res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); + assert.strictEqual(res[2], resourceC); res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); + assert.strictEqual(res[2], resourceC); }); test('compareFilesByScore - basename camelcase', function () { @@ -458,27 +458,27 @@ suite('Fuzzy Scorer', () => { let query = 'fA'; let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); + assert.strictEqual(res[2], resourceC); res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); + assert.strictEqual(res[2], resourceC); // resource B camelcase query = 'fB'; res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); + assert.strictEqual(res[2], resourceC); res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); + assert.strictEqual(res[2], resourceC); }); test('compareFilesByScore - basename scores', function () { @@ -490,27 +490,27 @@ suite('Fuzzy Scorer', () => { let query = 'fileA'; let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); + assert.strictEqual(res[2], resourceC); res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); + assert.strictEqual(res[2], resourceC); // Resource B part of basename query = 'fileB'; res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); + assert.strictEqual(res[2], resourceC); res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); + assert.strictEqual(res[2], resourceC); }); test('compareFilesByScore - path scores', function () { @@ -522,27 +522,27 @@ suite('Fuzzy Scorer', () => { let query = 'pathfileA'; let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); + assert.strictEqual(res[2], resourceC); res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); + assert.strictEqual(res[2], resourceC); // Resource B part of path query = 'pathfileB'; res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); + assert.strictEqual(res[2], resourceC); res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); + assert.strictEqual(res[2], resourceC); }); test('compareFilesByScore - prefer shorter basenames', function () { @@ -554,14 +554,14 @@ suite('Fuzzy Scorer', () => { let query = 'somepath'; let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); + assert.strictEqual(res[2], resourceC); res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); + assert.strictEqual(res[2], resourceC); }); test('compareFilesByScore - prefer shorter basenames (match on basename)', function () { @@ -573,14 +573,14 @@ suite('Fuzzy Scorer', () => { let query = 'file'; let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceC); - assert.equal(res[2], resourceB); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceC); + assert.strictEqual(res[2], resourceB); res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceC); - assert.equal(res[2], resourceB); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceC); + assert.strictEqual(res[2], resourceB); }); test('compareFilesByScore - prefer shorter paths', function () { @@ -592,14 +592,14 @@ suite('Fuzzy Scorer', () => { let query = 'somepath'; let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); + assert.strictEqual(res[2], resourceC); res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); + assert.strictEqual(res[2], resourceC); }); test('compareFilesByScore - prefer shorter paths (bug #17443)', function () { @@ -610,9 +610,9 @@ suite('Fuzzy Scorer', () => { let query = 'co/te'; let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); - assert.equal(res[2], resourceC); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); + assert.strictEqual(res[2], resourceC); }); test('compareFilesByScore - prefer matches in label over description if scores are otherwise equal', function () { @@ -622,8 +622,8 @@ suite('Fuzzy Scorer', () => { let query = 'partsquick'; let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); }); test('compareFilesByScore - prefer camel case matches', function () { @@ -632,12 +632,12 @@ suite('Fuzzy Scorer', () => { for (const query of ['npe', 'NPE']) { let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); } }); @@ -648,12 +648,12 @@ suite('Fuzzy Scorer', () => { let query = 'AH'; let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); }); test('compareFilesByScore - prefer more compact matches (label)', function () { @@ -663,12 +663,12 @@ suite('Fuzzy Scorer', () => { let query = 'xp'; let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); }); test('compareFilesByScore - prefer more compact matches (path)', function () { @@ -678,12 +678,12 @@ suite('Fuzzy Scorer', () => { let query = 'xp'; let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); }); test('compareFilesByScore - prefer more compact matches (label and path)', function () { @@ -693,12 +693,12 @@ suite('Fuzzy Scorer', () => { let query = 'exfile'; let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); }); test('compareFilesByScore - avoid match scattering (bug #34210)', function () { @@ -710,18 +710,18 @@ suite('Fuzzy Scorer', () => { let query = isWindows ? 'modu1\\index.js' : 'modu1/index.js'; let res = [resourceA, resourceB, resourceC, resourceD].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceC); + assert.strictEqual(res[0], resourceC); res = [resourceC, resourceB, resourceA, resourceD].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceC); + assert.strictEqual(res[0], resourceC); query = isWindows ? 'un1\\index.js' : 'un1/index.js'; res = [resourceA, resourceB, resourceC, resourceD].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); res = [resourceC, resourceB, resourceA, resourceD].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); }); test('compareFilesByScore - avoid match scattering (bug #21019 1.)', function () { @@ -732,10 +732,10 @@ suite('Fuzzy Scorer', () => { let query = 'StatVideoindex'; let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceC); + assert.strictEqual(res[0], resourceC); res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceC); + assert.strictEqual(res[0], resourceC); }); test('compareFilesByScore - avoid match scattering (bug #21019 2.)', function () { @@ -745,10 +745,10 @@ suite('Fuzzy Scorer', () => { let query = 'reproreduxts'; let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); }); test('compareFilesByScore - avoid match scattering (bug #26649)', function () { @@ -759,10 +759,10 @@ suite('Fuzzy Scorer', () => { let query = 'bookpageIndex'; let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceC); + assert.strictEqual(res[0], resourceC); res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceC); + assert.strictEqual(res[0], resourceC); }); test('compareFilesByScore - avoid match scattering (bug #33247)', function () { @@ -772,10 +772,10 @@ suite('Fuzzy Scorer', () => { let query = isWindows ? 'ui\\icons' : 'ui/icons'; let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); }); test('compareFilesByScore - avoid match scattering (bug #33247 comment)', function () { @@ -785,10 +785,10 @@ suite('Fuzzy Scorer', () => { let query = isWindows ? 'ui\\input\\index' : 'ui/input/index'; let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); }); test('compareFilesByScore - avoid match scattering (bug #36166)', function () { @@ -798,10 +798,10 @@ suite('Fuzzy Scorer', () => { let query = 'djancosig'; let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); }); test('compareFilesByScore - avoid match scattering (bug #32918)', function () { @@ -812,14 +812,14 @@ suite('Fuzzy Scorer', () => { let query = 'protectedconfig.php'; let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceC); - assert.equal(res[2], resourceB); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceC); + assert.strictEqual(res[2], resourceB); res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceC); - assert.equal(res[2], resourceB); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceC); + assert.strictEqual(res[2], resourceB); }); test('compareFilesByScore - avoid match scattering (bug #14879)', function () { @@ -829,10 +829,10 @@ suite('Fuzzy Scorer', () => { let query = 'gradientmain'; let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); }); test('compareFilesByScore - avoid match scattering (bug #14727 1)', function () { @@ -842,10 +842,10 @@ suite('Fuzzy Scorer', () => { let query = 'abc'; let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); }); test('compareFilesByScore - avoid match scattering (bug #14727 2)', function () { @@ -855,10 +855,10 @@ suite('Fuzzy Scorer', () => { let query = 'xyz'; let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); }); test('compareFilesByScore - avoid match scattering (bug #18381)', function () { @@ -868,10 +868,10 @@ suite('Fuzzy Scorer', () => { let query = 'async'; let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); }); test('compareFilesByScore - avoid match scattering (bug #35572)', function () { @@ -881,10 +881,10 @@ suite('Fuzzy Scorer', () => { let query = 'partisettings'; let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); }); test('compareFilesByScore - avoid match scattering (bug #36810)', function () { @@ -894,10 +894,10 @@ suite('Fuzzy Scorer', () => { let query = 'tipsindex.cshtml'; let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); }); test('compareFilesByScore - prefer shorter hit (bug #20546)', function () { @@ -907,10 +907,10 @@ suite('Fuzzy Scorer', () => { let query = 'listview'; let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); }); test('compareFilesByScore - avoid match scattering (bug #12095)', function () { @@ -921,10 +921,10 @@ suite('Fuzzy Scorer', () => { let query = 'filesexplorerview.ts'; let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); res = [resourceA, resourceC, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); }); test('compareFilesByScore - prefer case match (bug #96122)', function () { @@ -934,10 +934,10 @@ suite('Fuzzy Scorer', () => { let query = 'Lists.php'; let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); + assert.strictEqual(res[0], resourceB); }); test('compareFilesByScore - prefer shorter match (bug #103052) - foo bar', function () { @@ -946,12 +946,12 @@ suite('Fuzzy Scorer', () => { for (const query of ['foo bar', 'foobar']) { let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); } }); @@ -961,12 +961,12 @@ suite('Fuzzy Scorer', () => { for (const query of ['payment model', 'paymentmodel']) { let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); } }); @@ -976,12 +976,12 @@ suite('Fuzzy Scorer', () => { for (const query of ['color js', 'colorjs']) { let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); } }); @@ -992,22 +992,22 @@ suite('Fuzzy Scorer', () => { let query = 'Color'; let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); query = 'color'; res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); }); test('compareFilesByScore - prefer prefix (bug #103052)', function () { @@ -1017,12 +1017,12 @@ suite('Fuzzy Scorer', () => { let query = 'smoke main.ts'; let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); + assert.strictEqual(res[0], resourceA); + assert.strictEqual(res[1], resourceB); }); test('compareFilesByScore - boost better prefix match if multiple queries are used', function () { @@ -1031,12 +1031,12 @@ suite('Fuzzy Scorer', () => { for (const query of ['workbench.ts browser', 'browser workbench.ts', 'browser workbench', 'workbench browser']) { let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); } }); @@ -1046,12 +1046,12 @@ suite('Fuzzy Scorer', () => { for (const query of ['window browser', 'window.ts browser']) { let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); } }); @@ -1061,73 +1061,73 @@ suite('Fuzzy Scorer', () => { for (const query of ['m life, life m']) { let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); - assert.equal(res[0], resourceB); - assert.equal(res[1], resourceA); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); } }); test('prepareQuery', () => { - assert.equal(scorer.prepareQuery(' f*a ').normalized, 'fa'); - assert.equal(scorer.prepareQuery('model Tester.ts').original, 'model Tester.ts'); - assert.equal(scorer.prepareQuery('model Tester.ts').originalLowercase, 'model Tester.ts'.toLowerCase()); - assert.equal(scorer.prepareQuery('model Tester.ts').normalized, 'modelTester.ts'); - assert.equal(scorer.prepareQuery('Model Tester.ts').normalizedLowercase, 'modeltester.ts'); - assert.equal(scorer.prepareQuery('ModelTester.ts').containsPathSeparator, false); - assert.equal(scorer.prepareQuery('Model' + sep + 'Tester.ts').containsPathSeparator, true); + assert.strictEqual(scorer.prepareQuery(' f*a ').normalized, 'fa'); + assert.strictEqual(scorer.prepareQuery('model Tester.ts').original, 'model Tester.ts'); + assert.strictEqual(scorer.prepareQuery('model Tester.ts').originalLowercase, 'model Tester.ts'.toLowerCase()); + assert.strictEqual(scorer.prepareQuery('model Tester.ts').normalized, 'modelTester.ts'); + assert.strictEqual(scorer.prepareQuery('Model Tester.ts').normalizedLowercase, 'modeltester.ts'); + assert.strictEqual(scorer.prepareQuery('ModelTester.ts').containsPathSeparator, false); + assert.strictEqual(scorer.prepareQuery('Model' + sep + 'Tester.ts').containsPathSeparator, true); // with spaces let query = scorer.prepareQuery('He*llo World'); - assert.equal(query.original, 'He*llo World'); - assert.equal(query.normalized, 'HelloWorld'); - assert.equal(query.normalizedLowercase, 'HelloWorld'.toLowerCase()); - assert.equal(query.values?.length, 2); - assert.equal(query.values?.[0].original, 'He*llo'); - assert.equal(query.values?.[0].normalized, 'Hello'); - assert.equal(query.values?.[0].normalizedLowercase, 'Hello'.toLowerCase()); - assert.equal(query.values?.[1].original, 'World'); - assert.equal(query.values?.[1].normalized, 'World'); - assert.equal(query.values?.[1].normalizedLowercase, 'World'.toLowerCase()); + assert.strictEqual(query.original, 'He*llo World'); + assert.strictEqual(query.normalized, 'HelloWorld'); + assert.strictEqual(query.normalizedLowercase, 'HelloWorld'.toLowerCase()); + assert.strictEqual(query.values?.length, 2); + assert.strictEqual(query.values?.[0].original, 'He*llo'); + assert.strictEqual(query.values?.[0].normalized, 'Hello'); + assert.strictEqual(query.values?.[0].normalizedLowercase, 'Hello'.toLowerCase()); + assert.strictEqual(query.values?.[1].original, 'World'); + assert.strictEqual(query.values?.[1].normalized, 'World'); + assert.strictEqual(query.values?.[1].normalizedLowercase, 'World'.toLowerCase()); let restoredQuery = scorer.pieceToQuery(query.values!); - assert.equal(restoredQuery.original, query.original); - assert.equal(restoredQuery.values?.length, query.values?.length); - assert.equal(restoredQuery.containsPathSeparator, query.containsPathSeparator); + assert.strictEqual(restoredQuery.original, query.original); + assert.strictEqual(restoredQuery.values?.length, query.values?.length); + assert.strictEqual(restoredQuery.containsPathSeparator, query.containsPathSeparator); // with spaces that are empty query = scorer.prepareQuery(' Hello World '); - assert.equal(query.original, ' Hello World '); - assert.equal(query.originalLowercase, ' Hello World '.toLowerCase()); - assert.equal(query.normalized, 'HelloWorld'); - assert.equal(query.normalizedLowercase, 'HelloWorld'.toLowerCase()); - assert.equal(query.values?.length, 2); - assert.equal(query.values?.[0].original, 'Hello'); - assert.equal(query.values?.[0].originalLowercase, 'Hello'.toLowerCase()); - assert.equal(query.values?.[0].normalized, 'Hello'); - assert.equal(query.values?.[0].normalizedLowercase, 'Hello'.toLowerCase()); - assert.equal(query.values?.[1].original, 'World'); - assert.equal(query.values?.[1].originalLowercase, 'World'.toLowerCase()); - assert.equal(query.values?.[1].normalized, 'World'); - assert.equal(query.values?.[1].normalizedLowercase, 'World'.toLowerCase()); + assert.strictEqual(query.original, ' Hello World '); + assert.strictEqual(query.originalLowercase, ' Hello World '.toLowerCase()); + assert.strictEqual(query.normalized, 'HelloWorld'); + assert.strictEqual(query.normalizedLowercase, 'HelloWorld'.toLowerCase()); + assert.strictEqual(query.values?.length, 2); + assert.strictEqual(query.values?.[0].original, 'Hello'); + assert.strictEqual(query.values?.[0].originalLowercase, 'Hello'.toLowerCase()); + assert.strictEqual(query.values?.[0].normalized, 'Hello'); + assert.strictEqual(query.values?.[0].normalizedLowercase, 'Hello'.toLowerCase()); + assert.strictEqual(query.values?.[1].original, 'World'); + assert.strictEqual(query.values?.[1].originalLowercase, 'World'.toLowerCase()); + assert.strictEqual(query.values?.[1].normalized, 'World'); + assert.strictEqual(query.values?.[1].normalizedLowercase, 'World'.toLowerCase()); // Path related if (isWindows) { - assert.equal(scorer.prepareQuery('C:\\some\\path').pathNormalized, 'C:\\some\\path'); - assert.equal(scorer.prepareQuery('C:\\some\\path').normalized, 'C:\\some\\path'); - assert.equal(scorer.prepareQuery('C:\\some\\path').containsPathSeparator, true); - assert.equal(scorer.prepareQuery('C:/some/path').pathNormalized, 'C:\\some\\path'); - assert.equal(scorer.prepareQuery('C:/some/path').normalized, 'C:\\some\\path'); - assert.equal(scorer.prepareQuery('C:/some/path').containsPathSeparator, true); + assert.strictEqual(scorer.prepareQuery('C:\\some\\path').pathNormalized, 'C:\\some\\path'); + assert.strictEqual(scorer.prepareQuery('C:\\some\\path').normalized, 'C:\\some\\path'); + assert.strictEqual(scorer.prepareQuery('C:\\some\\path').containsPathSeparator, true); + assert.strictEqual(scorer.prepareQuery('C:/some/path').pathNormalized, 'C:\\some\\path'); + assert.strictEqual(scorer.prepareQuery('C:/some/path').normalized, 'C:\\some\\path'); + assert.strictEqual(scorer.prepareQuery('C:/some/path').containsPathSeparator, true); } else { - assert.equal(scorer.prepareQuery('/some/path').pathNormalized, '/some/path'); - assert.equal(scorer.prepareQuery('/some/path').normalized, '/some/path'); - assert.equal(scorer.prepareQuery('/some/path').containsPathSeparator, true); - assert.equal(scorer.prepareQuery('\\some\\path').pathNormalized, '/some/path'); - assert.equal(scorer.prepareQuery('\\some\\path').normalized, '/some/path'); - assert.equal(scorer.prepareQuery('\\some\\path').containsPathSeparator, true); + assert.strictEqual(scorer.prepareQuery('/some/path').pathNormalized, '/some/path'); + assert.strictEqual(scorer.prepareQuery('/some/path').normalized, '/some/path'); + assert.strictEqual(scorer.prepareQuery('/some/path').containsPathSeparator, true); + assert.strictEqual(scorer.prepareQuery('\\some\\path').pathNormalized, '/some/path'); + assert.strictEqual(scorer.prepareQuery('\\some\\path').normalized, '/some/path'); + assert.strictEqual(scorer.prepareQuery('\\some\\path').containsPathSeparator, true); } }); @@ -1138,18 +1138,18 @@ suite('Fuzzy Scorer', () => { let [score, matches] = _doScore2(offset === 0 ? target : `123${target}`, 'HeLlo-World', offset); assert.ok(score); - assert.equal(matches.length, 1); - assert.equal(matches[0].start, 0 + offset); - assert.equal(matches[0].end, target.length + offset); + assert.strictEqual(matches.length, 1); + assert.strictEqual(matches[0].start, 0 + offset); + assert.strictEqual(matches[0].end, target.length + offset); [score, matches] = _doScore2(offset === 0 ? target : `123${target}`, 'HW', offset); assert.ok(score); - assert.equal(matches.length, 2); - assert.equal(matches[0].start, 0 + offset); - assert.equal(matches[0].end, 1 + offset); - assert.equal(matches[1].start, 6 + offset); - assert.equal(matches[1].end, 7 + offset); + assert.strictEqual(matches.length, 2); + assert.strictEqual(matches[0].start, 0 + offset); + assert.strictEqual(matches[0].end, 1 + offset); + assert.strictEqual(matches[1].start, 6 + offset); + assert.strictEqual(matches[1].end, 7 + offset); } }); @@ -1169,8 +1169,8 @@ suite('Fuzzy Scorer', () => { const firstAndSecondSingleMatch = firstAndSecondSingleMatches[i]; if (multiMatch && firstAndSecondSingleMatch) { - assert.equal(multiMatch.start, firstAndSecondSingleMatch.start); - assert.equal(multiMatch.end, firstAndSecondSingleMatch.end); + assert.strictEqual(multiMatch.start, firstAndSecondSingleMatch.start); + assert.strictEqual(multiMatch.end, firstAndSecondSingleMatch.end); } else { assert.fail(); } @@ -1178,8 +1178,8 @@ suite('Fuzzy Scorer', () => { } function assertNoScore() { - assert.equal(multiScore, undefined); - assert.equal(multiMatches.length, 0); + assert.strictEqual(multiScore, undefined); + assert.strictEqual(multiMatches.length, 0); } assertScore(); diff --git a/src/vs/base/test/node/glob.test.ts b/src/vs/base/test/common/glob.test.ts similarity index 98% rename from src/vs/base/test/node/glob.test.ts rename to src/vs/base/test/common/glob.test.ts index 2c0288e38..25b838853 100644 --- a/src/vs/base/test/node/glob.test.ts +++ b/src/vs/base/test/common/glob.test.ts @@ -63,10 +63,12 @@ suite('Glob', () => { function assertGlobMatch(pattern: string | glob.IRelativePattern, input: string) { assert(glob.match(pattern, input), `${pattern} should match ${input}`); + assert(glob.match(pattern, nativeSep(input)), `${pattern} should match ${nativeSep(input)}`); } function assertNoGlobMatch(pattern: string | glob.IRelativePattern, input: string) { assert(!glob.match(pattern, input), `${pattern} should not match ${input}`); + assert(!glob.match(pattern, nativeSep(input)), `${pattern} should not match ${nativeSep(input)}`); } test('simple', () => { @@ -538,9 +540,13 @@ suite('Glob', () => { }); test('full path', function () { - let p = 'testing/this/foo.txt'; + assertGlobMatch('testing/this/foo.txt', 'testing/this/foo.txt'); + // assertGlobMatch('testing/this/foo.txt', 'testing\\this\\foo.txt'); + }); - assert(glob.match(p, nativeSep('testing/this/foo.txt'))); + test('ending path', function () { + assertGlobMatch('**/testing/this/foo.txt', 'some/path/testing/this/foo.txt'); + // assertGlobMatch('**/testing/this/foo.txt', 'some\\path\\testing\\this\\foo.txt'); }); test('prefix agnostic', function () { diff --git a/src/vs/base/test/common/iconLabels.test.ts b/src/vs/base/test/common/iconLabels.test.ts new file mode 100644 index 000000000..998c68971 --- /dev/null +++ b/src/vs/base/test/common/iconLabels.test.ts @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IMatch } from 'vs/base/common/filters'; +import { matchesFuzzyIconAware, parseLabelWithIcons, IParsedLabelWithIcons, stripIcons } from 'vs/base/common/iconLabels'; + +export interface IIconFilter { + // Returns null if word doesn't match. + (query: string, target: IParsedLabelWithIcons): IMatch[] | null; +} + +function filterOk(filter: IIconFilter, word: string, target: IParsedLabelWithIcons, highlights?: { start: number; end: number; }[]) { + let r = filter(word, target); + assert(r); + if (highlights) { + assert.deepEqual(r, highlights); + } +} + +suite('Icon Labels', () => { + test('matchesFuzzyIconAware', () => { + + // Camel Case + + filterOk(matchesFuzzyIconAware, 'ccr', parseLabelWithIcons('$(codicon)CamelCaseRocks$(codicon)'), [ + { start: 10, end: 11 }, + { start: 15, end: 16 }, + { start: 19, end: 20 } + ]); + + filterOk(matchesFuzzyIconAware, 'ccr', parseLabelWithIcons('$(codicon) CamelCaseRocks $(codicon)'), [ + { start: 11, end: 12 }, + { start: 16, end: 17 }, + { start: 20, end: 21 } + ]); + + filterOk(matchesFuzzyIconAware, 'iut', parseLabelWithIcons('$(codicon) Indent $(octico) Using $(octic) Tpaces'), [ + { start: 11, end: 12 }, + { start: 28, end: 29 }, + { start: 43, end: 44 }, + ]); + + // Prefix + + filterOk(matchesFuzzyIconAware, 'using', parseLabelWithIcons('$(codicon) Indent Using Spaces'), [ + { start: 18, end: 23 }, + ]); + + // Broken Codicon + + filterOk(matchesFuzzyIconAware, 'codicon', parseLabelWithIcons('This $(codicon Indent Using Spaces'), [ + { start: 7, end: 14 }, + ]); + + filterOk(matchesFuzzyIconAware, 'indent', parseLabelWithIcons('This $codicon Indent Using Spaces'), [ + { start: 14, end: 20 }, + ]); + + // Testing #59343 + filterOk(matchesFuzzyIconAware, 'unt', parseLabelWithIcons('$(primitive-dot) $(file-text) Untitled-1'), [ + { start: 30, end: 33 }, + ]); + }); + + test('stripIcons', () => { + assert.equal(stripIcons('Hello World'), 'Hello World'); + assert.equal(stripIcons('$(Hello World'), '$(Hello World'); + assert.equal(stripIcons('$(Hello) World'), ' World'); + assert.equal(stripIcons('$(Hello) W$(oi)rld'), ' Wrld'); + }); +}); diff --git a/src/vs/base/test/common/keyCodes.test.ts b/src/vs/base/test/common/keyCodes.test.ts index f8b2b55a4..f5c5bcdcc 100644 --- a/src/vs/base/test/common/keyCodes.test.ts +++ b/src/vs/base/test/common/keyCodes.test.ts @@ -10,7 +10,7 @@ import { OperatingSystem } from 'vs/base/common/platform'; suite('keyCodes', () => { function testBinaryEncoding(expected: Keybinding | null, k: number, OS: OperatingSystem): void { - assert.deepEqual(createKeybinding(k, OS), expected); + assert.deepStrictEqual(createKeybinding(k, OS), expected); } test('MAC binary encoding', () => { diff --git a/src/vs/base/test/common/labels.test.ts b/src/vs/base/test/common/labels.test.ts index a3e54c162..d7f0a401f 100644 --- a/src/vs/base/test/common/labels.test.ts +++ b/src/vs/base/test/common/labels.test.ts @@ -8,106 +8,98 @@ import * as labels from 'vs/base/common/labels'; import * as platform from 'vs/base/common/platform'; suite('Labels', () => { - test('shorten - windows', () => { - if (!platform.isWindows) { - assert.ok(true); - return; - } + (!platform.isWindows ? test.skip : test)('shorten - windows', () => { // nothing to shorten - assert.deepEqual(labels.shorten(['a']), ['a']); - assert.deepEqual(labels.shorten(['a', 'b']), ['a', 'b']); - assert.deepEqual(labels.shorten(['a', 'b', 'c']), ['a', 'b', 'c']); + assert.deepStrictEqual(labels.shorten(['a']), ['a']); + assert.deepStrictEqual(labels.shorten(['a', 'b']), ['a', 'b']); + assert.deepStrictEqual(labels.shorten(['a', 'b', 'c']), ['a', 'b', 'c']); // completely different paths - assert.deepEqual(labels.shorten(['a\\b', 'c\\d', 'e\\f']), ['…\\b', '…\\d', '…\\f']); + assert.deepStrictEqual(labels.shorten(['a\\b', 'c\\d', 'e\\f']), ['…\\b', '…\\d', '…\\f']); // same beginning - assert.deepEqual(labels.shorten(['a', 'a\\b']), ['a', '…\\b']); - assert.deepEqual(labels.shorten(['a\\b', 'a\\b\\c']), ['…\\b', '…\\c']); - assert.deepEqual(labels.shorten(['a', 'a\\b', 'a\\b\\c']), ['a', '…\\b', '…\\c']); - assert.deepEqual(labels.shorten(['x:\\a\\b', 'x:\\a\\c']), ['x:\\…\\b', 'x:\\…\\c']); - assert.deepEqual(labels.shorten(['\\\\a\\b', '\\\\a\\c']), ['\\\\a\\b', '\\\\a\\c']); + assert.deepStrictEqual(labels.shorten(['a', 'a\\b']), ['a', '…\\b']); + assert.deepStrictEqual(labels.shorten(['a\\b', 'a\\b\\c']), ['…\\b', '…\\c']); + assert.deepStrictEqual(labels.shorten(['a', 'a\\b', 'a\\b\\c']), ['a', '…\\b', '…\\c']); + assert.deepStrictEqual(labels.shorten(['x:\\a\\b', 'x:\\a\\c']), ['x:\\…\\b', 'x:\\…\\c']); + assert.deepStrictEqual(labels.shorten(['\\\\a\\b', '\\\\a\\c']), ['\\\\a\\b', '\\\\a\\c']); // same ending - assert.deepEqual(labels.shorten(['a', 'b\\a']), ['a', 'b\\…']); - assert.deepEqual(labels.shorten(['a\\b\\c', 'd\\b\\c']), ['a\\…', 'd\\…']); - assert.deepEqual(labels.shorten(['a\\b\\c\\d', 'f\\b\\c\\d']), ['a\\…', 'f\\…']); - assert.deepEqual(labels.shorten(['d\\e\\a\\b\\c', 'd\\b\\c']), ['…\\a\\…', 'd\\b\\…']); - assert.deepEqual(labels.shorten(['a\\b\\c\\d', 'a\\f\\b\\c\\d']), ['a\\b\\…', '…\\f\\…']); - assert.deepEqual(labels.shorten(['a\\b\\a', 'b\\b\\a']), ['a\\b\\…', 'b\\b\\…']); - assert.deepEqual(labels.shorten(['d\\f\\a\\b\\c', 'h\\d\\b\\c']), ['…\\a\\…', 'h\\…']); - assert.deepEqual(labels.shorten(['a\\b\\c', 'x:\\0\\a\\b\\c']), ['a\\b\\c', 'x:\\0\\…']); - assert.deepEqual(labels.shorten(['x:\\a\\b\\c', 'x:\\0\\a\\b\\c']), ['x:\\a\\…', 'x:\\0\\…']); - assert.deepEqual(labels.shorten(['x:\\a\\b', 'y:\\a\\b']), ['x:\\…', 'y:\\…']); - assert.deepEqual(labels.shorten(['x:\\a', 'x:\\c']), ['x:\\a', 'x:\\c']); - assert.deepEqual(labels.shorten(['x:\\a\\b', 'y:\\x\\a\\b']), ['x:\\…', 'y:\\…']); - assert.deepEqual(labels.shorten(['\\\\x\\b', '\\\\y\\b']), ['\\\\x\\…', '\\\\y\\…']); - assert.deepEqual(labels.shorten(['\\\\x\\a', '\\\\x\\b']), ['\\\\x\\a', '\\\\x\\b']); + assert.deepStrictEqual(labels.shorten(['a', 'b\\a']), ['a', 'b\\…']); + assert.deepStrictEqual(labels.shorten(['a\\b\\c', 'd\\b\\c']), ['a\\…', 'd\\…']); + assert.deepStrictEqual(labels.shorten(['a\\b\\c\\d', 'f\\b\\c\\d']), ['a\\…', 'f\\…']); + assert.deepStrictEqual(labels.shorten(['d\\e\\a\\b\\c', 'd\\b\\c']), ['…\\a\\…', 'd\\b\\…']); + assert.deepStrictEqual(labels.shorten(['a\\b\\c\\d', 'a\\f\\b\\c\\d']), ['a\\b\\…', '…\\f\\…']); + assert.deepStrictEqual(labels.shorten(['a\\b\\a', 'b\\b\\a']), ['a\\b\\…', 'b\\b\\…']); + assert.deepStrictEqual(labels.shorten(['d\\f\\a\\b\\c', 'h\\d\\b\\c']), ['…\\a\\…', 'h\\…']); + assert.deepStrictEqual(labels.shorten(['a\\b\\c', 'x:\\0\\a\\b\\c']), ['a\\b\\c', 'x:\\0\\…']); + assert.deepStrictEqual(labels.shorten(['x:\\a\\b\\c', 'x:\\0\\a\\b\\c']), ['x:\\a\\…', 'x:\\0\\…']); + assert.deepStrictEqual(labels.shorten(['x:\\a\\b', 'y:\\a\\b']), ['x:\\…', 'y:\\…']); + assert.deepStrictEqual(labels.shorten(['x:\\a', 'x:\\c']), ['x:\\a', 'x:\\c']); + assert.deepStrictEqual(labels.shorten(['x:\\a\\b', 'y:\\x\\a\\b']), ['x:\\…', 'y:\\…']); + assert.deepStrictEqual(labels.shorten(['\\\\x\\b', '\\\\y\\b']), ['\\\\x\\…', '\\\\y\\…']); + assert.deepStrictEqual(labels.shorten(['\\\\x\\a', '\\\\x\\b']), ['\\\\x\\a', '\\\\x\\b']); // same name ending - assert.deepEqual(labels.shorten(['a\\b', 'a\\c', 'a\\e-b']), ['…\\b', '…\\c', '…\\e-b']); + assert.deepStrictEqual(labels.shorten(['a\\b', 'a\\c', 'a\\e-b']), ['…\\b', '…\\c', '…\\e-b']); // same in the middle - assert.deepEqual(labels.shorten(['a\\b\\c', 'd\\b\\e']), ['…\\c', '…\\e']); + assert.deepStrictEqual(labels.shorten(['a\\b\\c', 'd\\b\\e']), ['…\\c', '…\\e']); // case-sensetive - assert.deepEqual(labels.shorten(['a\\b\\c', 'd\\b\\C']), ['…\\c', '…\\C']); + assert.deepStrictEqual(labels.shorten(['a\\b\\c', 'd\\b\\C']), ['…\\c', '…\\C']); // empty or null - assert.deepEqual(labels.shorten(['', null!]), ['.\\', null]); + assert.deepStrictEqual(labels.shorten(['', null!]), ['.\\', null]); - assert.deepEqual(labels.shorten(['a', 'a\\b', 'a\\b\\c', 'd\\b\\c', 'd\\b']), ['a', 'a\\b', 'a\\b\\c', 'd\\b\\c', 'd\\b']); - assert.deepEqual(labels.shorten(['a', 'a\\b', 'b']), ['a', 'a\\b', 'b']); - assert.deepEqual(labels.shorten(['', 'a', 'b', 'b\\c', 'a\\c']), ['.\\', 'a', 'b', 'b\\c', 'a\\c']); - assert.deepEqual(labels.shorten(['src\\vs\\workbench\\parts\\execution\\electron-browser', 'src\\vs\\workbench\\parts\\execution\\electron-browser\\something', 'src\\vs\\workbench\\parts\\terminal\\electron-browser']), ['…\\execution\\electron-browser', '…\\something', '…\\terminal\\…']); + assert.deepStrictEqual(labels.shorten(['a', 'a\\b', 'a\\b\\c', 'd\\b\\c', 'd\\b']), ['a', 'a\\b', 'a\\b\\c', 'd\\b\\c', 'd\\b']); + assert.deepStrictEqual(labels.shorten(['a', 'a\\b', 'b']), ['a', 'a\\b', 'b']); + assert.deepStrictEqual(labels.shorten(['', 'a', 'b', 'b\\c', 'a\\c']), ['.\\', 'a', 'b', 'b\\c', 'a\\c']); + assert.deepStrictEqual(labels.shorten(['src\\vs\\workbench\\parts\\execution\\electron-browser', 'src\\vs\\workbench\\parts\\execution\\electron-browser\\something', 'src\\vs\\workbench\\parts\\terminal\\electron-browser']), ['…\\execution\\electron-browser', '…\\something', '…\\terminal\\…']); }); - test('shorten - not windows', () => { - if (platform.isWindows) { - assert.ok(true); - return; - } + (platform.isWindows ? test.skip : test)('shorten - not windows', () => { // nothing to shorten - assert.deepEqual(labels.shorten(['a']), ['a']); - assert.deepEqual(labels.shorten(['a', 'b']), ['a', 'b']); - assert.deepEqual(labels.shorten(['a', 'b', 'c']), ['a', 'b', 'c']); + assert.deepStrictEqual(labels.shorten(['a']), ['a']); + assert.deepStrictEqual(labels.shorten(['a', 'b']), ['a', 'b']); + assert.deepStrictEqual(labels.shorten(['a', 'b', 'c']), ['a', 'b', 'c']); // completely different paths - assert.deepEqual(labels.shorten(['a/b', 'c/d', 'e/f']), ['…/b', '…/d', '…/f']); + assert.deepStrictEqual(labels.shorten(['a/b', 'c/d', 'e/f']), ['…/b', '…/d', '…/f']); // same beginning - assert.deepEqual(labels.shorten(['a', 'a/b']), ['a', '…/b']); - assert.deepEqual(labels.shorten(['a/b', 'a/b/c']), ['…/b', '…/c']); - assert.deepEqual(labels.shorten(['a', 'a/b', 'a/b/c']), ['a', '…/b', '…/c']); - assert.deepEqual(labels.shorten(['/a/b', '/a/c']), ['/a/b', '/a/c']); + assert.deepStrictEqual(labels.shorten(['a', 'a/b']), ['a', '…/b']); + assert.deepStrictEqual(labels.shorten(['a/b', 'a/b/c']), ['…/b', '…/c']); + assert.deepStrictEqual(labels.shorten(['a', 'a/b', 'a/b/c']), ['a', '…/b', '…/c']); + assert.deepStrictEqual(labels.shorten(['/a/b', '/a/c']), ['/a/b', '/a/c']); // same ending - assert.deepEqual(labels.shorten(['a', 'b/a']), ['a', 'b/…']); - assert.deepEqual(labels.shorten(['a/b/c', 'd/b/c']), ['a/…', 'd/…']); - assert.deepEqual(labels.shorten(['a/b/c/d', 'f/b/c/d']), ['a/…', 'f/…']); - assert.deepEqual(labels.shorten(['d/e/a/b/c', 'd/b/c']), ['…/a/…', 'd/b/…']); - assert.deepEqual(labels.shorten(['a/b/c/d', 'a/f/b/c/d']), ['a/b/…', '…/f/…']); - assert.deepEqual(labels.shorten(['a/b/a', 'b/b/a']), ['a/b/…', 'b/b/…']); - assert.deepEqual(labels.shorten(['d/f/a/b/c', 'h/d/b/c']), ['…/a/…', 'h/…']); - assert.deepEqual(labels.shorten(['/x/b', '/y/b']), ['/x/…', '/y/…']); + assert.deepStrictEqual(labels.shorten(['a', 'b/a']), ['a', 'b/…']); + assert.deepStrictEqual(labels.shorten(['a/b/c', 'd/b/c']), ['a/…', 'd/…']); + assert.deepStrictEqual(labels.shorten(['a/b/c/d', 'f/b/c/d']), ['a/…', 'f/…']); + assert.deepStrictEqual(labels.shorten(['d/e/a/b/c', 'd/b/c']), ['…/a/…', 'd/b/…']); + assert.deepStrictEqual(labels.shorten(['a/b/c/d', 'a/f/b/c/d']), ['a/b/…', '…/f/…']); + assert.deepStrictEqual(labels.shorten(['a/b/a', 'b/b/a']), ['a/b/…', 'b/b/…']); + assert.deepStrictEqual(labels.shorten(['d/f/a/b/c', 'h/d/b/c']), ['…/a/…', 'h/…']); + assert.deepStrictEqual(labels.shorten(['/x/b', '/y/b']), ['/x/…', '/y/…']); // same name ending - assert.deepEqual(labels.shorten(['a/b', 'a/c', 'a/e-b']), ['…/b', '…/c', '…/e-b']); + assert.deepStrictEqual(labels.shorten(['a/b', 'a/c', 'a/e-b']), ['…/b', '…/c', '…/e-b']); // same in the middle - assert.deepEqual(labels.shorten(['a/b/c', 'd/b/e']), ['…/c', '…/e']); + assert.deepStrictEqual(labels.shorten(['a/b/c', 'd/b/e']), ['…/c', '…/e']); // case-sensitive - assert.deepEqual(labels.shorten(['a/b/c', 'd/b/C']), ['…/c', '…/C']); + assert.deepStrictEqual(labels.shorten(['a/b/c', 'd/b/C']), ['…/c', '…/C']); // empty or null - assert.deepEqual(labels.shorten(['', null!]), ['./', null]); + assert.deepStrictEqual(labels.shorten(['', null!]), ['./', null]); - assert.deepEqual(labels.shorten(['a', 'a/b', 'a/b/c', 'd/b/c', 'd/b']), ['a', 'a/b', 'a/b/c', 'd/b/c', 'd/b']); - assert.deepEqual(labels.shorten(['a', 'a/b', 'b']), ['a', 'a/b', 'b']); - assert.deepEqual(labels.shorten(['', 'a', 'b', 'b/c', 'a/c']), ['./', 'a', 'b', 'b/c', 'a/c']); + assert.deepStrictEqual(labels.shorten(['a', 'a/b', 'a/b/c', 'd/b/c', 'd/b']), ['a', 'a/b', 'a/b/c', 'd/b/c', 'd/b']); + assert.deepStrictEqual(labels.shorten(['a', 'a/b', 'b']), ['a', 'a/b', 'b']); + assert.deepStrictEqual(labels.shorten(['', 'a', 'b', 'b/c', 'a/c']), ['./', 'a', 'b', 'b/c', 'a/c']); }); test('template', () => { @@ -142,41 +134,32 @@ suite('Labels', () => { assert.strictEqual(labels.template(t, { dirty: '* ', activeEditorShort: 'somefile.txt', rootName: 'monaco', appName: 'Visual Studio Code', separator: { label: ' - ' } }), '* somefile.txt - monaco - Visual Studio Code'); }); - test('getBaseLabel - unix', () => { - if (platform.isWindows) { - assert.ok(true); - return; - } - - assert.equal(labels.getBaseLabel('/some/folder/file.txt'), 'file.txt'); - assert.equal(labels.getBaseLabel('/some/folder'), 'folder'); - assert.equal(labels.getBaseLabel('/'), '/'); + (platform.isWindows ? test.skip : test)('getBaseLabel - unix', () => { + assert.strictEqual(labels.getBaseLabel('/some/folder/file.txt'), 'file.txt'); + assert.strictEqual(labels.getBaseLabel('/some/folder'), 'folder'); + assert.strictEqual(labels.getBaseLabel('/'), '/'); }); - test('getBaseLabel - windows', () => { - if (!platform.isWindows) { - assert.ok(true); - return; - } - - assert.equal(labels.getBaseLabel('c:'), 'C:'); - assert.equal(labels.getBaseLabel('c:\\'), 'C:'); - assert.equal(labels.getBaseLabel('c:\\some\\folder\\file.txt'), 'file.txt'); - assert.equal(labels.getBaseLabel('c:\\some\\folder'), 'folder'); + (!platform.isWindows ? test.skip : test)('getBaseLabel - windows', () => { + assert.strictEqual(labels.getBaseLabel('c:'), 'C:'); + assert.strictEqual(labels.getBaseLabel('c:\\'), 'C:'); + assert.strictEqual(labels.getBaseLabel('c:\\some\\folder\\file.txt'), 'file.txt'); + assert.strictEqual(labels.getBaseLabel('c:\\some\\folder'), 'folder'); + assert.strictEqual(labels.getBaseLabel('c:\\some\\f:older'), 'f:older'); // https://github.com/microsoft/vscode-remote-release/issues/4227 }); test('mnemonicButtonLabel', () => { - assert.equal(labels.mnemonicButtonLabel('Hello World'), 'Hello World'); - assert.equal(labels.mnemonicButtonLabel(''), ''); + assert.strictEqual(labels.mnemonicButtonLabel('Hello World'), 'Hello World'); + assert.strictEqual(labels.mnemonicButtonLabel(''), ''); if (platform.isWindows) { - assert.equal(labels.mnemonicButtonLabel('Hello & World'), 'Hello && World'); - assert.equal(labels.mnemonicButtonLabel('Do &¬ Save & Continue'), 'Do ¬ Save && Continue'); + assert.strictEqual(labels.mnemonicButtonLabel('Hello & World'), 'Hello && World'); + assert.strictEqual(labels.mnemonicButtonLabel('Do &¬ Save & Continue'), 'Do ¬ Save && Continue'); } else if (platform.isMacintosh) { - assert.equal(labels.mnemonicButtonLabel('Hello & World'), 'Hello & World'); - assert.equal(labels.mnemonicButtonLabel('Do &¬ Save & Continue'), 'Do not Save & Continue'); + assert.strictEqual(labels.mnemonicButtonLabel('Hello & World'), 'Hello & World'); + assert.strictEqual(labels.mnemonicButtonLabel('Do &¬ Save & Continue'), 'Do not Save & Continue'); } else { - assert.equal(labels.mnemonicButtonLabel('Hello & World'), 'Hello & World'); - assert.equal(labels.mnemonicButtonLabel('Do &¬ Save & Continue'), 'Do _not Save & Continue'); + assert.strictEqual(labels.mnemonicButtonLabel('Hello & World'), 'Hello & World'); + assert.strictEqual(labels.mnemonicButtonLabel('Do &¬ Save & Continue'), 'Do _not Save & Continue'); } }); -}); \ No newline at end of file +}); diff --git a/src/vs/base/test/common/linkedList.test.ts b/src/vs/base/test/common/linkedList.test.ts index dbb057a21..d4eb08320 100644 --- a/src/vs/base/test/common/linkedList.test.ts +++ b/src/vs/base/test/common/linkedList.test.ts @@ -11,19 +11,19 @@ suite('LinkedList', function () { function assertElements(list: LinkedList, ...elements: E[]) { // check size - assert.equal(list.size, elements.length); + assert.strictEqual(list.size, elements.length); // assert toArray - assert.deepEqual(Array.from(list), elements); + assert.deepStrictEqual(Array.from(list), elements); // assert Symbol.iterator (1) - assert.deepEqual([...list], elements); + assert.deepStrictEqual([...list], elements); // assert Symbol.iterator (2) for (const item of list) { - assert.equal(item, elements.shift()); + assert.strictEqual(item, elements.shift()); } - assert.equal(elements.length, 0); + assert.strictEqual(elements.length, 0); } test('Push/Iter', () => { @@ -123,14 +123,14 @@ suite('LinkedList', function () { assertElements(list, 'a', 'b'); let a = list.shift(); - assert.equal(a, 'a'); + assert.strictEqual(a, 'a'); assertElements(list, 'b'); list.unshift('a'); assertElements(list, 'a', 'b'); let b = list.pop(); - assert.equal(b, 'b'); + assert.strictEqual(b, 'b'); assertElements(list, 'a'); }); diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts index 5781de52a..8988e8e68 100644 --- a/src/vs/base/test/common/map.test.ts +++ b/src/vs/base/test/common/map.test.ts @@ -16,8 +16,8 @@ suite('Map', () => { map.set('bk', 'bv'); assert.deepStrictEqual([...map.keys()], ['ak', 'bk']); assert.deepStrictEqual([...map.values()], ['av', 'bv']); - assert.equal(map.first, 'av'); - assert.equal(map.last, 'bv'); + assert.strictEqual(map.first, 'av'); + assert.strictEqual(map.last, 'bv'); }); test('LinkedMap - Touch Old one', () => { @@ -77,7 +77,7 @@ suite('Map', () => { test('LinkedMap - basics', function () { const map = new LinkedMap(); - assert.equal(map.size, 0); + assert.strictEqual(map.size, 0); map.set('1', 1); map.set('2', '2'); @@ -89,23 +89,23 @@ suite('Map', () => { const date = Date.now(); map.set('5', date); - assert.equal(map.size, 5); - assert.equal(map.get('1'), 1); - assert.equal(map.get('2'), '2'); - assert.equal(map.get('3'), true); - assert.equal(map.get('4'), obj); - assert.equal(map.get('5'), date); + assert.strictEqual(map.size, 5); + assert.strictEqual(map.get('1'), 1); + assert.strictEqual(map.get('2'), '2'); + assert.strictEqual(map.get('3'), true); + assert.strictEqual(map.get('4'), obj); + assert.strictEqual(map.get('5'), date); assert.ok(!map.get('6')); map.delete('6'); - assert.equal(map.size, 5); - assert.equal(map.delete('1'), true); - assert.equal(map.delete('2'), true); - assert.equal(map.delete('3'), true); - assert.equal(map.delete('4'), true); - assert.equal(map.delete('5'), true); + assert.strictEqual(map.size, 5); + assert.strictEqual(map.delete('1'), true); + assert.strictEqual(map.delete('2'), true); + assert.strictEqual(map.delete('3'), true); + assert.strictEqual(map.delete('4'), true); + assert.strictEqual(map.delete('5'), true); - assert.equal(map.size, 0); + assert.strictEqual(map.size, 0); assert.ok(!map.get('5')); assert.ok(!map.get('4')); assert.ok(!map.get('3')); @@ -117,13 +117,13 @@ suite('Map', () => { map.set('3', true); assert.ok(map.has('1')); - assert.equal(map.get('1'), 1); - assert.equal(map.get('2'), '2'); - assert.equal(map.get('3'), true); + assert.strictEqual(map.get('1'), 1); + assert.strictEqual(map.get('2'), '2'); + assert.strictEqual(map.get('3'), true); map.clear(); - assert.equal(map.size, 0); + assert.strictEqual(map.size, 0); assert.ok(!map.get('1')); assert.ok(!map.get('2')); assert.ok(!map.get('3')); @@ -231,7 +231,7 @@ suite('Map', () => { for (let i = 11; i <= 20; i++) { cache.set(i, i); } - assert.deepEqual(cache.size, 15); + assert.deepStrictEqual(cache.size, 15); let values: number[] = []; for (let i = 6; i <= 20; i++) { values.push(cache.get(i)!); @@ -269,14 +269,14 @@ suite('Map', () => { let i = 0; map.forEach((value, key) => { if (i === 0) { - assert.equal(key, 'ak'); - assert.equal(value, 'av'); + assert.strictEqual(key, 'ak'); + assert.strictEqual(value, 'av'); } else if (i === 1) { - assert.equal(key, 'bk'); - assert.equal(value, 'bv'); + assert.strictEqual(key, 'bk'); + assert.strictEqual(value, 'bv'); } else if (i === 2) { - assert.equal(key, 'ck'); - assert.equal(value, 'cv'); + assert.strictEqual(key, 'ck'); + assert.strictEqual(value, 'cv'); } i++; }); @@ -285,44 +285,44 @@ suite('Map', () => { test('LinkedMap - delete Head and Tail', function () { const map = new LinkedMap(); - assert.equal(map.size, 0); + assert.strictEqual(map.size, 0); map.set('1', 1); - assert.equal(map.size, 1); + assert.strictEqual(map.size, 1); map.delete('1'); - assert.equal(map.get('1'), undefined); - assert.equal(map.size, 0); - assert.equal([...map.keys()].length, 0); + assert.strictEqual(map.get('1'), undefined); + assert.strictEqual(map.size, 0); + assert.strictEqual([...map.keys()].length, 0); }); test('LinkedMap - delete Head', function () { const map = new LinkedMap(); - assert.equal(map.size, 0); + assert.strictEqual(map.size, 0); map.set('1', 1); map.set('2', 2); - assert.equal(map.size, 2); + assert.strictEqual(map.size, 2); map.delete('1'); - assert.equal(map.get('2'), 2); - assert.equal(map.size, 1); - assert.equal([...map.keys()].length, 1); - assert.equal([...map.keys()][0], 2); + assert.strictEqual(map.get('2'), 2); + assert.strictEqual(map.size, 1); + assert.strictEqual([...map.keys()].length, 1); + assert.strictEqual([...map.keys()][0], '2'); }); test('LinkedMap - delete Tail', function () { const map = new LinkedMap(); - assert.equal(map.size, 0); + assert.strictEqual(map.size, 0); map.set('1', 1); map.set('2', 2); - assert.equal(map.size, 2); + assert.strictEqual(map.size, 2); map.delete('2'); - assert.equal(map.get('1'), 1); - assert.equal(map.size, 1); - assert.equal([...map.keys()].length, 1); - assert.equal([...map.keys()][0], 1); + assert.strictEqual(map.get('1'), 1); + assert.strictEqual(map.size, 1); + assert.strictEqual([...map.keys()].length, 1); + assert.strictEqual([...map.keys()][0], '1'); }); @@ -330,100 +330,100 @@ suite('Map', () => { const iter = new PathIterator(); iter.reset('file:///usr/bin/file.txt'); - assert.equal(iter.value(), 'file:'); - assert.equal(iter.hasNext(), true); - assert.equal(iter.cmp('file:'), 0); + assert.strictEqual(iter.value(), 'file:'); + assert.strictEqual(iter.hasNext(), true); + assert.strictEqual(iter.cmp('file:'), 0); assert.ok(iter.cmp('a') < 0); assert.ok(iter.cmp('aile:') < 0); assert.ok(iter.cmp('z') > 0); assert.ok(iter.cmp('zile:') > 0); iter.next(); - assert.equal(iter.value(), 'usr'); - assert.equal(iter.hasNext(), true); + assert.strictEqual(iter.value(), 'usr'); + assert.strictEqual(iter.hasNext(), true); iter.next(); - assert.equal(iter.value(), 'bin'); - assert.equal(iter.hasNext(), true); + assert.strictEqual(iter.value(), 'bin'); + assert.strictEqual(iter.hasNext(), true); iter.next(); - assert.equal(iter.value(), 'file.txt'); - assert.equal(iter.hasNext(), false); + assert.strictEqual(iter.value(), 'file.txt'); + assert.strictEqual(iter.hasNext(), false); iter.next(); - assert.equal(iter.value(), ''); - assert.equal(iter.hasNext(), false); + assert.strictEqual(iter.value(), ''); + assert.strictEqual(iter.hasNext(), false); iter.next(); - assert.equal(iter.value(), ''); - assert.equal(iter.hasNext(), false); + assert.strictEqual(iter.value(), ''); + assert.strictEqual(iter.hasNext(), false); // iter.reset('/foo/bar/'); - assert.equal(iter.value(), 'foo'); - assert.equal(iter.hasNext(), true); + assert.strictEqual(iter.value(), 'foo'); + assert.strictEqual(iter.hasNext(), true); iter.next(); - assert.equal(iter.value(), 'bar'); - assert.equal(iter.hasNext(), false); + assert.strictEqual(iter.value(), 'bar'); + assert.strictEqual(iter.hasNext(), false); }); test('URIIterator', function () { const iter = new UriIterator(() => false); iter.reset(URI.parse('file:///usr/bin/file.txt')); - assert.equal(iter.value(), 'file'); - // assert.equal(iter.cmp('FILE'), 0); - assert.equal(iter.cmp('file'), 0); - assert.equal(iter.hasNext(), true); + assert.strictEqual(iter.value(), 'file'); + // assert.strictEqual(iter.cmp('FILE'), 0); + assert.strictEqual(iter.cmp('file'), 0); + assert.strictEqual(iter.hasNext(), true); iter.next(); - assert.equal(iter.value(), 'usr'); - assert.equal(iter.hasNext(), true); + assert.strictEqual(iter.value(), 'usr'); + assert.strictEqual(iter.hasNext(), true); iter.next(); - assert.equal(iter.value(), 'bin'); - assert.equal(iter.hasNext(), true); + assert.strictEqual(iter.value(), 'bin'); + assert.strictEqual(iter.hasNext(), true); iter.next(); - assert.equal(iter.value(), 'file.txt'); - assert.equal(iter.hasNext(), false); + assert.strictEqual(iter.value(), 'file.txt'); + assert.strictEqual(iter.hasNext(), false); iter.reset(URI.parse('file://share/usr/bin/file.txt?foo')); // scheme - assert.equal(iter.value(), 'file'); - // assert.equal(iter.cmp('FILE'), 0); - assert.equal(iter.cmp('file'), 0); - assert.equal(iter.hasNext(), true); + assert.strictEqual(iter.value(), 'file'); + // assert.strictEqual(iter.cmp('FILE'), 0); + assert.strictEqual(iter.cmp('file'), 0); + assert.strictEqual(iter.hasNext(), true); iter.next(); // authority - assert.equal(iter.value(), 'share'); - assert.equal(iter.cmp('SHARe'), 0); - assert.equal(iter.hasNext(), true); + assert.strictEqual(iter.value(), 'share'); + assert.strictEqual(iter.cmp('SHARe'), 0); + assert.strictEqual(iter.hasNext(), true); iter.next(); // path - assert.equal(iter.value(), 'usr'); - assert.equal(iter.hasNext(), true); + assert.strictEqual(iter.value(), 'usr'); + assert.strictEqual(iter.hasNext(), true); iter.next(); // path - assert.equal(iter.value(), 'bin'); - assert.equal(iter.hasNext(), true); + assert.strictEqual(iter.value(), 'bin'); + assert.strictEqual(iter.hasNext(), true); iter.next(); // path - assert.equal(iter.value(), 'file.txt'); - assert.equal(iter.hasNext(), true); + assert.strictEqual(iter.value(), 'file.txt'); + assert.strictEqual(iter.hasNext(), true); iter.next(); // query - assert.equal(iter.value(), 'foo'); - assert.equal(iter.cmp('z') > 0, true); - assert.equal(iter.cmp('a') < 0, true); - assert.equal(iter.hasNext(), false); + assert.strictEqual(iter.value(), 'foo'); + assert.strictEqual(iter.cmp('z') > 0, true); + assert.strictEqual(iter.cmp('a') < 0, true); + assert.strictEqual(iter.hasNext(), false); }); function assertTernarySearchTree(trie: TernarySearchTree, ...elements: [string, E][]) { @@ -432,24 +432,24 @@ suite('Map', () => { map.set(key, value); } map.forEach((value, key) => { - assert.equal(trie.get(key), value); + assert.strictEqual(trie.get(key), value); }); // forEach let forEachCount = 0; trie.forEach((element, key) => { - assert.equal(element, map.get(key)); + assert.strictEqual(element, map.get(key)); forEachCount++; }); - assert.equal(map.size, forEachCount); + assert.strictEqual(map.size, forEachCount); // iterator let iterCount = 0; for (let [key, value] of trie) { - assert.equal(value, map.get(key)); + assert.strictEqual(value, map.get(key)); iterCount++; } - assert.equal(map.size, iterCount); + assert.strictEqual(map.size, iterCount); } test('TernarySearchTree - set', function () { @@ -493,13 +493,13 @@ suite('Map', () => { trie.set('foobar', 2); trie.set('foobaz', 3); - assert.equal(trie.findSubstr('f'), undefined); - assert.equal(trie.findSubstr('z'), undefined); - assert.equal(trie.findSubstr('foo'), 1); - assert.equal(trie.findSubstr('fooö'), 1); - assert.equal(trie.findSubstr('fooba'), 1); - assert.equal(trie.findSubstr('foobarr'), 2); - assert.equal(trie.findSubstr('foobazrr'), 3); + assert.strictEqual(trie.findSubstr('f'), undefined); + assert.strictEqual(trie.findSubstr('z'), undefined); + assert.strictEqual(trie.findSubstr('foo'), 1); + assert.strictEqual(trie.findSubstr('fooö'), 1); + assert.strictEqual(trie.findSubstr('fooba'), 1); + assert.strictEqual(trie.findSubstr('foobarr'), 2); + assert.strictEqual(trie.findSubstr('foobazrr'), 3); }); test('TernarySearchTree - basics', function () { @@ -509,27 +509,27 @@ suite('Map', () => { trie.set('bar', 2); trie.set('foobar', 3); - assert.equal(trie.get('foo'), 1); - assert.equal(trie.get('bar'), 2); - assert.equal(trie.get('foobar'), 3); - assert.equal(trie.get('foobaz'), undefined); - assert.equal(trie.get('foobarr'), undefined); + assert.strictEqual(trie.get('foo'), 1); + assert.strictEqual(trie.get('bar'), 2); + assert.strictEqual(trie.get('foobar'), 3); + assert.strictEqual(trie.get('foobaz'), undefined); + assert.strictEqual(trie.get('foobarr'), undefined); - assert.equal(trie.findSubstr('fo'), undefined); - assert.equal(trie.findSubstr('foo'), 1); - assert.equal(trie.findSubstr('foooo'), 1); + assert.strictEqual(trie.findSubstr('fo'), undefined); + assert.strictEqual(trie.findSubstr('foo'), 1); + assert.strictEqual(trie.findSubstr('foooo'), 1); trie.delete('foobar'); trie.delete('bar'); - assert.equal(trie.get('foobar'), undefined); - assert.equal(trie.get('bar'), undefined); + assert.strictEqual(trie.get('foobar'), undefined); + assert.strictEqual(trie.get('bar'), undefined); trie.set('foobar', 17); trie.set('barr', 18); - assert.equal(trie.get('foobar'), 17); - assert.equal(trie.get('barr'), 18); - assert.equal(trie.get('bar'), undefined); + assert.strictEqual(trie.get('foobar'), 17); + assert.strictEqual(trie.get('barr'), 18); + assert.strictEqual(trie.get('bar'), undefined); }); test('TernarySearchTree - delete & cleanup', function () { @@ -576,20 +576,20 @@ suite('Map', () => { trie.set('/user/foo', 2); trie.set('/user/foo/flip/flop', 3); - assert.equal(trie.get('/user/foo/bar'), 1); - assert.equal(trie.get('/user/foo'), 2); - assert.equal(trie.get('/user//foo'), 2); - assert.equal(trie.get('/user\\foo'), 2); - assert.equal(trie.get('/user/foo/flip/flop'), 3); + assert.strictEqual(trie.get('/user/foo/bar'), 1); + assert.strictEqual(trie.get('/user/foo'), 2); + assert.strictEqual(trie.get('/user//foo'), 2); + assert.strictEqual(trie.get('/user\\foo'), 2); + assert.strictEqual(trie.get('/user/foo/flip/flop'), 3); - assert.equal(trie.findSubstr('/user/bar'), undefined); - assert.equal(trie.findSubstr('/user/foo'), 2); - assert.equal(trie.findSubstr('\\user\\foo'), 2); - assert.equal(trie.findSubstr('/user//foo'), 2); - assert.equal(trie.findSubstr('/user/foo/ba'), 2); - assert.equal(trie.findSubstr('/user/foo/far/boo'), 2); - assert.equal(trie.findSubstr('/user/foo/bar'), 1); - assert.equal(trie.findSubstr('/user/foo/bar/far/boo'), 1); + assert.strictEqual(trie.findSubstr('/user/bar'), undefined); + assert.strictEqual(trie.findSubstr('/user/foo'), 2); + assert.strictEqual(trie.findSubstr('\\user\\foo'), 2); + assert.strictEqual(trie.findSubstr('/user//foo'), 2); + assert.strictEqual(trie.findSubstr('/user/foo/ba'), 2); + assert.strictEqual(trie.findSubstr('/user/foo/far/boo'), 2); + assert.strictEqual(trie.findSubstr('/user/foo/bar'), 1); + assert.strictEqual(trie.findSubstr('/user/foo/bar/far/boo'), 1); }); test('TernarySearchTree (PathSegments) - lookup', function () { @@ -599,11 +599,11 @@ suite('Map', () => { map.set('/user/foo', 2); map.set('/user/foo/flip/flop', 3); - assert.equal(map.get('/foo'), undefined); - assert.equal(map.get('/user'), undefined); - assert.equal(map.get('/user/foo'), 2); - assert.equal(map.get('/user/foo/bar'), 1); - assert.equal(map.get('/user/foo/bar/boo'), undefined); + assert.strictEqual(map.get('/foo'), undefined); + assert.strictEqual(map.get('/user'), undefined); + assert.strictEqual(map.get('/user/foo'), 2); + assert.strictEqual(map.get('/user/foo/bar'), 1); + assert.strictEqual(map.get('/user/foo/bar/boo'), undefined); }); test('TernarySearchTree (PathSegments) - superstr', function () { @@ -618,31 +618,31 @@ suite('Map', () => { let iter = map.findSuperstr('/user'); item = iter!.next(); - assert.equal(item.value[1], 2); - assert.equal(item.done, false); + assert.strictEqual(item.value[1], 2); + assert.strictEqual(item.done, false); item = iter!.next(); - assert.equal(item.value[1], 1); - assert.equal(item.done, false); + assert.strictEqual(item.value[1], 1); + assert.strictEqual(item.done, false); item = iter!.next(); - assert.equal(item.value[1], 3); - assert.equal(item.done, false); + assert.strictEqual(item.value[1], 3); + assert.strictEqual(item.done, false); item = iter!.next(); - assert.equal(item.value, undefined); - assert.equal(item.done, true); + assert.strictEqual(item.value, undefined); + assert.strictEqual(item.done, true); iter = map.findSuperstr('/usr'); item = iter!.next(); - assert.equal(item.value[1], 4); - assert.equal(item.done, false); + assert.strictEqual(item.value[1], 4); + assert.strictEqual(item.done, false); item = iter!.next(); - assert.equal(item.value, undefined); - assert.equal(item.done, true); + assert.strictEqual(item.value, undefined); + assert.strictEqual(item.done, true); - assert.equal(map.findSuperstr('/not'), undefined); - assert.equal(map.findSuperstr('/us'), undefined); - assert.equal(map.findSuperstr('/usrr'), undefined); - assert.equal(map.findSuperstr('/userr'), undefined); + assert.strictEqual(map.findSuperstr('/not'), undefined); + assert.strictEqual(map.findSuperstr('/us'), undefined); + assert.strictEqual(map.findSuperstr('/usrr'), undefined); + assert.strictEqual(map.findSuperstr('/userr'), undefined); }); @@ -688,16 +688,16 @@ suite('Map', () => { trie.set(URI.file('/user/foo'), 2); trie.set(URI.file('/user/foo/flip/flop'), 3); - assert.equal(trie.get(URI.file('/user/foo/bar')), 1); - assert.equal(trie.get(URI.file('/user/foo')), 2); - assert.equal(trie.get(URI.file('/user/foo/flip/flop')), 3); + assert.strictEqual(trie.get(URI.file('/user/foo/bar')), 1); + assert.strictEqual(trie.get(URI.file('/user/foo')), 2); + assert.strictEqual(trie.get(URI.file('/user/foo/flip/flop')), 3); - assert.equal(trie.findSubstr(URI.file('/user/bar')), undefined); - assert.equal(trie.findSubstr(URI.file('/user/foo')), 2); - assert.equal(trie.findSubstr(URI.file('/user/foo/ba')), 2); - assert.equal(trie.findSubstr(URI.file('/user/foo/far/boo')), 2); - assert.equal(trie.findSubstr(URI.file('/user/foo/bar')), 1); - assert.equal(trie.findSubstr(URI.file('/user/foo/bar/far/boo')), 1); + assert.strictEqual(trie.findSubstr(URI.file('/user/bar')), undefined); + assert.strictEqual(trie.findSubstr(URI.file('/user/foo')), 2); + assert.strictEqual(trie.findSubstr(URI.file('/user/foo/ba')), 2); + assert.strictEqual(trie.findSubstr(URI.file('/user/foo/far/boo')), 2); + assert.strictEqual(trie.findSubstr(URI.file('/user/foo/bar')), 1); + assert.strictEqual(trie.findSubstr(URI.file('/user/foo/bar/far/boo')), 1); }); test('TernarySearchTree (URI) - lookup', function () { @@ -708,23 +708,23 @@ suite('Map', () => { map.set(URI.parse('http://foo.bar/user/foo?QUERY'), 3); map.set(URI.parse('http://foo.bar/user/foo/flip/flop'), 3); - assert.equal(map.get(URI.parse('http://foo.bar/foo')), undefined); - assert.equal(map.get(URI.parse('http://foo.bar/user')), undefined); - assert.equal(map.get(URI.parse('http://foo.bar/user/foo/bar')), 1); - assert.equal(map.get(URI.parse('http://foo.bar/user/foo?query')), 2); - assert.equal(map.get(URI.parse('http://foo.bar/user/foo?Query')), undefined); - assert.equal(map.get(URI.parse('http://foo.bar/user/foo?QUERY')), 3); - assert.equal(map.get(URI.parse('http://foo.bar/user/foo/bar/boo')), undefined); + assert.strictEqual(map.get(URI.parse('http://foo.bar/foo')), undefined); + assert.strictEqual(map.get(URI.parse('http://foo.bar/user')), undefined); + assert.strictEqual(map.get(URI.parse('http://foo.bar/user/foo/bar')), 1); + assert.strictEqual(map.get(URI.parse('http://foo.bar/user/foo?query')), 2); + assert.strictEqual(map.get(URI.parse('http://foo.bar/user/foo?Query')), undefined); + assert.strictEqual(map.get(URI.parse('http://foo.bar/user/foo?QUERY')), 3); + assert.strictEqual(map.get(URI.parse('http://foo.bar/user/foo/bar/boo')), undefined); }); test('TernarySearchTree (URI) - lookup, casing', function () { const map = new TernarySearchTree(new UriIterator(uri => /^https?$/.test(uri.scheme))); map.set(URI.parse('http://foo.bar/user/foo/bar'), 1); - assert.equal(map.get(URI.parse('http://foo.bar/USER/foo/bar')), 1); + assert.strictEqual(map.get(URI.parse('http://foo.bar/USER/foo/bar')), 1); map.set(URI.parse('foo://foo.bar/user/foo/bar'), 1); - assert.equal(map.get(URI.parse('foo://foo.bar/USER/foo/bar')), undefined); + assert.strictEqual(map.get(URI.parse('foo://foo.bar/USER/foo/bar')), undefined); }); test('TernarySearchTree (URI) - superstr', function () { @@ -739,48 +739,48 @@ suite('Map', () => { let iter = map.findSuperstr(URI.file('/user'))!; item = iter.next(); - assert.equal(item.value[1], 2); - assert.equal(item.done, false); + assert.strictEqual(item.value[1], 2); + assert.strictEqual(item.done, false); item = iter.next(); - assert.equal(item.value[1], 1); - assert.equal(item.done, false); + assert.strictEqual(item.value[1], 1); + assert.strictEqual(item.done, false); item = iter.next(); - assert.equal(item.value[1], 3); - assert.equal(item.done, false); + assert.strictEqual(item.value[1], 3); + assert.strictEqual(item.done, false); item = iter.next(); - assert.equal(item.value, undefined); - assert.equal(item.done, true); + assert.strictEqual(item.value, undefined); + assert.strictEqual(item.done, true); iter = map.findSuperstr(URI.file('/usr'))!; item = iter.next(); - assert.equal(item.value[1], 4); - assert.equal(item.done, false); + assert.strictEqual(item.value[1], 4); + assert.strictEqual(item.done, false); item = iter.next(); - assert.equal(item.value, undefined); - assert.equal(item.done, true); + assert.strictEqual(item.value, undefined); + assert.strictEqual(item.done, true); iter = map.findSuperstr(URI.file('/'))!; item = iter.next(); - assert.equal(item.value[1], 2); - assert.equal(item.done, false); + assert.strictEqual(item.value[1], 2); + assert.strictEqual(item.done, false); item = iter.next(); - assert.equal(item.value[1], 1); - assert.equal(item.done, false); + assert.strictEqual(item.value[1], 1); + assert.strictEqual(item.done, false); item = iter.next(); - assert.equal(item.value[1], 3); - assert.equal(item.done, false); + assert.strictEqual(item.value[1], 3); + assert.strictEqual(item.done, false); item = iter.next(); - assert.equal(item.value[1], 4); - assert.equal(item.done, false); + assert.strictEqual(item.value[1], 4); + assert.strictEqual(item.done, false); item = iter.next(); - assert.equal(item.value, undefined); - assert.equal(item.done, true); + assert.strictEqual(item.value, undefined); + assert.strictEqual(item.done, true); - assert.equal(map.findSuperstr(URI.file('/not')), undefined); - assert.equal(map.findSuperstr(URI.file('/us')), undefined); - assert.equal(map.findSuperstr(URI.file('/usrr')), undefined); - assert.equal(map.findSuperstr(URI.file('/userr')), undefined); + assert.strictEqual(map.findSuperstr(URI.file('/not')), undefined); + assert.strictEqual(map.findSuperstr(URI.file('/us')), undefined); + assert.strictEqual(map.findSuperstr(URI.file('/usrr')), undefined); + assert.strictEqual(map.findSuperstr(URI.file('/userr')), undefined); }); test('TernarySearchTree (ConfigKeySegments) - basics', function () { @@ -790,16 +790,16 @@ suite('Map', () => { trie.set('config.foo', 2); trie.set('config.foo.flip.flop', 3); - assert.equal(trie.get('config.foo.bar'), 1); - assert.equal(trie.get('config.foo'), 2); - assert.equal(trie.get('config.foo.flip.flop'), 3); + assert.strictEqual(trie.get('config.foo.bar'), 1); + assert.strictEqual(trie.get('config.foo'), 2); + assert.strictEqual(trie.get('config.foo.flip.flop'), 3); - assert.equal(trie.findSubstr('config.bar'), undefined); - assert.equal(trie.findSubstr('config.foo'), 2); - assert.equal(trie.findSubstr('config.foo.ba'), 2); - assert.equal(trie.findSubstr('config.foo.far.boo'), 2); - assert.equal(trie.findSubstr('config.foo.bar'), 1); - assert.equal(trie.findSubstr('config.foo.bar.far.boo'), 1); + assert.strictEqual(trie.findSubstr('config.bar'), undefined); + assert.strictEqual(trie.findSubstr('config.foo'), 2); + assert.strictEqual(trie.findSubstr('config.foo.ba'), 2); + assert.strictEqual(trie.findSubstr('config.foo.far.boo'), 2); + assert.strictEqual(trie.findSubstr('config.foo.bar'), 1); + assert.strictEqual(trie.findSubstr('config.foo.bar.far.boo'), 1); }); test('TernarySearchTree (ConfigKeySegments) - lookup', function () { @@ -809,11 +809,11 @@ suite('Map', () => { map.set('config.foo', 2); map.set('config.foo.flip.flop', 3); - assert.equal(map.get('foo'), undefined); - assert.equal(map.get('config'), undefined); - assert.equal(map.get('config.foo'), 2); - assert.equal(map.get('config.foo.bar'), 1); - assert.equal(map.get('config.foo.bar.boo'), undefined); + assert.strictEqual(map.get('foo'), undefined); + assert.strictEqual(map.get('config'), undefined); + assert.strictEqual(map.get('config.foo'), 2); + assert.strictEqual(map.get('config.foo.bar'), 1); + assert.strictEqual(map.get('config.foo.bar.boo'), undefined); }); test('TernarySearchTree (ConfigKeySegments) - superstr', function () { @@ -828,21 +828,21 @@ suite('Map', () => { let iter = map.findSuperstr('config'); item = iter!.next(); - assert.equal(item.value[1], 2); - assert.equal(item.done, false); + assert.strictEqual(item.value[1], 2); + assert.strictEqual(item.done, false); item = iter!.next(); - assert.equal(item.value[1], 1); - assert.equal(item.done, false); + assert.strictEqual(item.value[1], 1); + assert.strictEqual(item.done, false); item = iter!.next(); - assert.equal(item.value[1], 3); - assert.equal(item.done, false); + assert.strictEqual(item.value[1], 3); + assert.strictEqual(item.done, false); item = iter!.next(); - assert.equal(item.value, undefined); - assert.equal(item.done, true); + assert.strictEqual(item.value, undefined); + assert.strictEqual(item.done, true); - assert.equal(map.findSuperstr('foo'), undefined); - assert.equal(map.findSuperstr('config.foo.no'), undefined); - assert.equal(map.findSuperstr('config.foop'), undefined); + assert.strictEqual(map.findSuperstr('foo'), undefined); + assert.strictEqual(map.findSuperstr('config.foo.no'), undefined); + assert.strictEqual(map.findSuperstr('config.foop'), undefined); }); @@ -891,7 +891,7 @@ suite('Map', () => { const resource5 = URI.parse('some://5'); const resource6 = URI.parse('some://6'); - assert.equal(map.size, 0); + assert.strictEqual(map.size, 0); let res = map.set(resource1, 1); assert.ok(res === map); @@ -899,13 +899,13 @@ suite('Map', () => { map.set(resource3, true); const values = [...map.values()]; - assert.equal(values[0], 1); - assert.equal(values[1], '2'); - assert.equal(values[2], true); + assert.strictEqual(values[0], 1); + assert.strictEqual(values[1], '2'); + assert.strictEqual(values[2], true); let counter = 0; map.forEach((value, key, mapObj) => { - assert.equal(value, values[counter++]); + assert.strictEqual(value, values[counter++]); assert.ok(URI.isUri(key)); assert.ok(map === mapObj); }); @@ -916,23 +916,23 @@ suite('Map', () => { const date = Date.now(); map.set(resource5, date); - assert.equal(map.size, 5); - assert.equal(map.get(resource1), 1); - assert.equal(map.get(resource2), '2'); - assert.equal(map.get(resource3), true); - assert.equal(map.get(resource4), obj); - assert.equal(map.get(resource5), date); + assert.strictEqual(map.size, 5); + assert.strictEqual(map.get(resource1), 1); + assert.strictEqual(map.get(resource2), '2'); + assert.strictEqual(map.get(resource3), true); + assert.strictEqual(map.get(resource4), obj); + assert.strictEqual(map.get(resource5), date); assert.ok(!map.get(resource6)); map.delete(resource6); - assert.equal(map.size, 5); + assert.strictEqual(map.size, 5); assert.ok(map.delete(resource1)); assert.ok(map.delete(resource2)); assert.ok(map.delete(resource3)); assert.ok(map.delete(resource4)); assert.ok(map.delete(resource5)); - assert.equal(map.size, 0); + assert.strictEqual(map.size, 0); assert.ok(!map.get(resource5)); assert.ok(!map.get(resource4)); assert.ok(!map.get(resource3)); @@ -944,13 +944,13 @@ suite('Map', () => { map.set(resource3, true); assert.ok(map.has(resource1)); - assert.equal(map.get(resource1), 1); - assert.equal(map.get(resource2), '2'); - assert.equal(map.get(resource3), true); + assert.strictEqual(map.get(resource1), 1); + assert.strictEqual(map.get(resource2), '2'); + assert.strictEqual(map.get(resource3), true); map.clear(); - assert.equal(map.size, 0); + assert.strictEqual(map.size, 0); assert.ok(!map.get(resource1)); assert.ok(!map.get(resource2)); assert.ok(!map.get(resource3)); @@ -971,16 +971,16 @@ suite('Map', () => { const fileAUpper = URI.parse('file://SOME/FILEA'); map.set(fileA, 'true'); - assert.equal(map.get(fileA), 'true'); + assert.strictEqual(map.get(fileA), 'true'); assert.ok(!map.get(fileAUpper)); assert.ok(!map.get(fileB)); map.set(fileAUpper, 'false'); - assert.equal(map.get(fileAUpper), 'false'); + assert.strictEqual(map.get(fileAUpper), 'false'); - assert.equal(map.get(fileA), 'true'); + assert.strictEqual(map.get(fileA), 'true'); const windowsFile = URI.file('c:\\test with %25\\c#code'); const uncFile = URI.file('\\\\shäres\\path\\c#\\plugin.json'); @@ -988,8 +988,8 @@ suite('Map', () => { map.set(windowsFile, 'true'); map.set(uncFile, 'true'); - assert.equal(map.get(windowsFile), 'true'); - assert.equal(map.get(uncFile), 'true'); + assert.strictEqual(map.get(windowsFile), 'true'); + assert.strictEqual(map.get(uncFile), 'true'); }); test('ResourceMap - files (ignorecase)', function () { @@ -1000,16 +1000,16 @@ suite('Map', () => { const fileAUpper = URI.parse('file://SOME/FILEA'); map.set(fileA, 'true'); - assert.equal(map.get(fileA), 'true'); + assert.strictEqual(map.get(fileA), 'true'); - assert.equal(map.get(fileAUpper), 'true'); + assert.strictEqual(map.get(fileAUpper), 'true'); assert.ok(!map.get(fileB)); map.set(fileAUpper, 'false'); - assert.equal(map.get(fileAUpper), 'false'); + assert.strictEqual(map.get(fileAUpper), 'false'); - assert.equal(map.get(fileA), 'false'); + assert.strictEqual(map.get(fileA), 'false'); const windowsFile = URI.file('c:\\test with %25\\c#code'); const uncFile = URI.file('\\\\shäres\\path\\c#\\plugin.json'); @@ -1017,7 +1017,7 @@ suite('Map', () => { map.set(windowsFile, 'true'); map.set(uncFile, 'true'); - assert.equal(map.get(windowsFile), 'true'); - assert.equal(map.get(uncFile), 'true'); + assert.strictEqual(map.get(windowsFile), 'true'); + assert.strictEqual(map.get(uncFile), 'true'); }); }); diff --git a/src/vs/base/test/common/markdownString.test.ts b/src/vs/base/test/common/markdownString.test.ts index 424c2bd46..7d9d64f2b 100644 --- a/src/vs/base/test/common/markdownString.test.ts +++ b/src/vs/base/test/common/markdownString.test.ts @@ -11,13 +11,13 @@ suite('MarkdownString', () => { test('Escape leading whitespace', function () { const mds = new MarkdownString(); mds.appendText('Hello\n Not a code block'); - assert.equal(mds.value, 'Hello\n\n    Not a code block'); + assert.strictEqual(mds.value, 'Hello\n\n    Not a code block'); }); test('MarkdownString.appendText doesn\'t escape quote #109040', function () { const mds = new MarkdownString(); mds.appendText('> Text\n>More'); - assert.equal(mds.value, '\\> Text\n\n\\>More'); + assert.strictEqual(mds.value, '\\> Text\n\n\\>More'); }); test('appendText', () => { @@ -25,7 +25,7 @@ suite('MarkdownString', () => { const mds = new MarkdownString(); mds.appendText('# foo\n*bar*'); - assert.equal(mds.value, '\\# foo\n\n\\*bar\\*'); + assert.strictEqual(mds.value, '\\# foo\n\n\\*bar\\*'); }); suite('ThemeIcons', () => { @@ -36,21 +36,21 @@ suite('MarkdownString', () => { const mds = new MarkdownString(undefined, { supportThemeIcons: true }); mds.appendText('$(zap) $(not a theme icon) $(add)'); - assert.equal(mds.value, '\\\\$\\(zap\\) $\\(not a theme icon\\) \\\\$\\(add\\)'); + assert.strictEqual(mds.value, '\\\\$\\(zap\\) $\\(not a theme icon\\) \\\\$\\(add\\)'); }); test('appendMarkdown', () => { const mds = new MarkdownString(undefined, { supportThemeIcons: true }); mds.appendMarkdown('$(zap) $(not a theme icon) $(add)'); - assert.equal(mds.value, '$(zap) $(not a theme icon) $(add)'); + assert.strictEqual(mds.value, '$(zap) $(not a theme icon) $(add)'); }); test('appendMarkdown with escaped icon', () => { const mds = new MarkdownString(undefined, { supportThemeIcons: true }); mds.appendMarkdown('\\$(zap) $(not a theme icon) $(add)'); - assert.equal(mds.value, '\\$(zap) $(not a theme icon) $(add)'); + assert.strictEqual(mds.value, '\\$(zap) $(not a theme icon) $(add)'); }); }); @@ -61,21 +61,21 @@ suite('MarkdownString', () => { const mds = new MarkdownString(undefined, { supportThemeIcons: false }); mds.appendText('$(zap) $(not a theme icon) $(add)'); - assert.equal(mds.value, '$\\(zap\\) $\\(not a theme icon\\) $\\(add\\)'); + assert.strictEqual(mds.value, '$\\(zap\\) $\\(not a theme icon\\) $\\(add\\)'); }); test('appendMarkdown', () => { const mds = new MarkdownString(undefined, { supportThemeIcons: false }); mds.appendMarkdown('$(zap) $(not a theme icon) $(add)'); - assert.equal(mds.value, '$(zap) $(not a theme icon) $(add)'); + assert.strictEqual(mds.value, '$(zap) $(not a theme icon) $(add)'); }); test('appendMarkdown with escaped icon', () => { const mds = new MarkdownString(undefined, { supportThemeIcons: true }); mds.appendMarkdown('\\$(zap) $(not a theme icon) $(add)'); - assert.equal(mds.value, '\\$(zap) $(not a theme icon) $(add)'); + assert.strictEqual(mds.value, '\\$(zap) $(not a theme icon) $(add)'); }); }); diff --git a/src/vs/base/test/common/mime.test.ts b/src/vs/base/test/common/mime.test.ts index a7ac5a177..f2583fed4 100644 --- a/src/vs/base/test/common/mime.test.ts +++ b/src/vs/base/test/common/mime.test.ts @@ -11,38 +11,38 @@ suite('Mime', () => { test('Dynamically Register Text Mime', () => { let guess = guessMimeTypes(URI.file('foo.monaco')); - assert.deepEqual(guess, ['application/unknown']); + assert.deepStrictEqual(guess, ['application/unknown']); registerTextMime({ id: 'monaco', extension: '.monaco', mime: 'text/monaco' }); guess = guessMimeTypes(URI.file('foo.monaco')); - assert.deepEqual(guess, ['text/monaco', 'text/plain']); + assert.deepStrictEqual(guess, ['text/monaco', 'text/plain']); guess = guessMimeTypes(URI.file('.monaco')); - assert.deepEqual(guess, ['text/monaco', 'text/plain']); + assert.deepStrictEqual(guess, ['text/monaco', 'text/plain']); registerTextMime({ id: 'codefile', filename: 'Codefile', mime: 'text/code' }); guess = guessMimeTypes(URI.file('Codefile')); - assert.deepEqual(guess, ['text/code', 'text/plain']); + assert.deepStrictEqual(guess, ['text/code', 'text/plain']); guess = guessMimeTypes(URI.file('foo.Codefile')); - assert.deepEqual(guess, ['application/unknown']); + assert.deepStrictEqual(guess, ['application/unknown']); registerTextMime({ id: 'docker', filepattern: 'Docker*', mime: 'text/docker' }); guess = guessMimeTypes(URI.file('Docker-debug')); - assert.deepEqual(guess, ['text/docker', 'text/plain']); + assert.deepStrictEqual(guess, ['text/docker', 'text/plain']); guess = guessMimeTypes(URI.file('docker-PROD')); - assert.deepEqual(guess, ['text/docker', 'text/plain']); + assert.deepStrictEqual(guess, ['text/docker', 'text/plain']); registerTextMime({ id: 'niceregex', mime: 'text/nice-regex', firstline: /RegexesAreNice/ }); guess = guessMimeTypes(URI.file('Randomfile.noregistration'), 'RegexesAreNice'); - assert.deepEqual(guess, ['text/nice-regex', 'text/plain']); + assert.deepStrictEqual(guess, ['text/nice-regex', 'text/plain']); guess = guessMimeTypes(URI.file('Randomfile.noregistration'), 'RegexesAreNotNice'); - assert.deepEqual(guess, ['application/unknown']); + assert.deepStrictEqual(guess, ['application/unknown']); guess = guessMimeTypes(URI.file('Codefile'), 'RegexesAreNice'); - assert.deepEqual(guess, ['text/code', 'text/plain']); + assert.deepStrictEqual(guess, ['text/code', 'text/plain']); }); test('Mimes Priority', () => { @@ -50,36 +50,36 @@ suite('Mime', () => { registerTextMime({ id: 'foobar', mime: 'text/foobar', firstline: /foobar/ }); let guess = guessMimeTypes(URI.file('foo.monaco')); - assert.deepEqual(guess, ['text/monaco', 'text/plain']); + assert.deepStrictEqual(guess, ['text/monaco', 'text/plain']); guess = guessMimeTypes(URI.file('foo.monaco'), 'foobar'); - assert.deepEqual(guess, ['text/monaco', 'text/plain']); + assert.deepStrictEqual(guess, ['text/monaco', 'text/plain']); registerTextMime({ id: 'docker', filename: 'dockerfile', mime: 'text/winner' }); registerTextMime({ id: 'docker', filepattern: 'dockerfile*', mime: 'text/looser' }); guess = guessMimeTypes(URI.file('dockerfile')); - assert.deepEqual(guess, ['text/winner', 'text/plain']); + assert.deepStrictEqual(guess, ['text/winner', 'text/plain']); registerTextMime({ id: 'azure-looser', mime: 'text/azure-looser', firstline: /azure/ }); registerTextMime({ id: 'azure-winner', mime: 'text/azure-winner', firstline: /azure/ }); guess = guessMimeTypes(URI.file('azure'), 'azure'); - assert.deepEqual(guess, ['text/azure-winner', 'text/plain']); + assert.deepStrictEqual(guess, ['text/azure-winner', 'text/plain']); }); test('Specificity priority 1', () => { registerTextMime({ id: 'monaco2', extension: '.monaco2', mime: 'text/monaco2' }); registerTextMime({ id: 'monaco2', filename: 'specific.monaco2', mime: 'text/specific-monaco2' }); - assert.deepEqual(guessMimeTypes(URI.file('specific.monaco2')), ['text/specific-monaco2', 'text/plain']); - assert.deepEqual(guessMimeTypes(URI.file('foo.monaco2')), ['text/monaco2', 'text/plain']); + assert.deepStrictEqual(guessMimeTypes(URI.file('specific.monaco2')), ['text/specific-monaco2', 'text/plain']); + assert.deepStrictEqual(guessMimeTypes(URI.file('foo.monaco2')), ['text/monaco2', 'text/plain']); }); test('Specificity priority 2', () => { registerTextMime({ id: 'monaco3', filename: 'specific.monaco3', mime: 'text/specific-monaco3' }); registerTextMime({ id: 'monaco3', extension: '.monaco3', mime: 'text/monaco3' }); - assert.deepEqual(guessMimeTypes(URI.file('specific.monaco3')), ['text/specific-monaco3', 'text/plain']); - assert.deepEqual(guessMimeTypes(URI.file('foo.monaco3')), ['text/monaco3', 'text/plain']); + assert.deepStrictEqual(guessMimeTypes(URI.file('specific.monaco3')), ['text/specific-monaco3', 'text/plain']); + assert.deepStrictEqual(guessMimeTypes(URI.file('foo.monaco3')), ['text/monaco3', 'text/plain']); }); test('Mimes Priority - Longest Extension wins', () => { @@ -88,13 +88,13 @@ suite('Mime', () => { registerTextMime({ id: 'monaco', extension: '.monaco.xml.build', mime: 'text/monaco-xml-build' }); let guess = guessMimeTypes(URI.file('foo.monaco')); - assert.deepEqual(guess, ['text/monaco', 'text/plain']); + assert.deepStrictEqual(guess, ['text/monaco', 'text/plain']); guess = guessMimeTypes(URI.file('foo.monaco.xml')); - assert.deepEqual(guess, ['text/monaco-xml', 'text/plain']); + assert.deepStrictEqual(guess, ['text/monaco-xml', 'text/plain']); guess = guessMimeTypes(URI.file('foo.monaco.xml.build')); - assert.deepEqual(guess, ['text/monaco-xml-build', 'text/plain']); + assert.deepStrictEqual(guess, ['text/monaco-xml-build', 'text/plain']); }); test('Mimes Priority - User configured wins', () => { @@ -102,7 +102,7 @@ suite('Mime', () => { registerTextMime({ id: 'monaco', extension: '.monaco.xml', mime: 'text/monaco-xml' }); let guess = guessMimeTypes(URI.file('foo.monaco.xnl')); - assert.deepEqual(guess, ['text/monaco', 'text/plain']); + assert.deepStrictEqual(guess, ['text/monaco', 'text/plain']); }); test('Mimes Priority - Pattern matches on path if specified', () => { @@ -110,7 +110,7 @@ suite('Mime', () => { registerTextMime({ id: 'other', filepattern: '*ot.other.xml', mime: 'text/other' }); let guess = guessMimeTypes(URI.file('/some/path/dot.monaco.xml')); - assert.deepEqual(guess, ['text/monaco', 'text/plain']); + assert.deepStrictEqual(guess, ['text/monaco', 'text/plain']); }); test('Mimes Priority - Last registered mime wins', () => { @@ -118,12 +118,12 @@ suite('Mime', () => { registerTextMime({ id: 'other', filepattern: '**/dot.monaco.xml', mime: 'text/other' }); let guess = guessMimeTypes(URI.file('/some/path/dot.monaco.xml')); - assert.deepEqual(guess, ['text/other', 'text/plain']); + assert.deepStrictEqual(guess, ['text/other', 'text/plain']); }); test('Data URIs', () => { registerTextMime({ id: 'data', extension: '.data', mime: 'text/data' }); - assert.deepEqual(guessMimeTypes(URI.parse(`data:;label:something.data;description:data,`)), ['text/data', 'text/plain']); + assert.deepStrictEqual(guessMimeTypes(URI.parse(`data:;label:something.data;description:data,`)), ['text/data', 'text/plain']); }); }); diff --git a/src/vs/base/test/common/network.test.ts b/src/vs/base/test/common/network.test.ts index 1729d4b1c..240c1abc2 100644 --- a/src/vs/base/test/common/network.test.ts +++ b/src/vs/base/test/common/network.test.ts @@ -7,10 +7,10 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { FileAccess, Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; -import { isElectronSandboxed } from 'vs/base/common/platform'; +import { isPreferringBrowserCodeLoad } from 'vs/base/common/platform'; suite('network', () => { - const enableTest = isElectronSandboxed; + const enableTest = isPreferringBrowserCodeLoad; (!enableTest ? test.skip : test)('FileAccess: URI (native)', () => { diff --git a/src/vs/base/test/common/paging.test.ts b/src/vs/base/test/common/paging.test.ts index 3ce696633..da7ef18fa 100644 --- a/src/vs/base/test/common/paging.test.ts +++ b/src/vs/base/test/common/paging.test.ts @@ -101,7 +101,6 @@ suite('PagedModel', () => { test('preemptive cancellation works', async function () { const pager = new TestPager(() => { assert(false); - return Promise.resolve([]); }); const model = new PagedModel(pager); diff --git a/src/vs/base/test/node/path.test.ts b/src/vs/base/test/common/path.test.ts similarity index 99% rename from src/vs/base/test/node/path.test.ts rename to src/vs/base/test/common/path.test.ts index 3150f8d60..d74ce9f7c 100644 --- a/src/vs/base/test/node/path.test.ts +++ b/src/vs/base/test/common/path.test.ts @@ -29,10 +29,11 @@ import * as assert from 'assert'; import * as path from 'vs/base/common/path'; -import { isWindows } from 'vs/base/common/platform'; +import { isWeb, isWindows } from 'vs/base/common/platform'; import * as process from 'vs/base/common/process'; suite('Paths (Node Implementation)', () => { + const __filename = 'path.test.js'; test('join', () => { const failures = [] as string[]; const backslashRE = /\\/g; @@ -175,9 +176,6 @@ suite('Paths (Node Implementation)', () => { }); test('dirname', () => { - assert.strictEqual(path.dirname(path.normalize(__filename)).substr(-9), - isWindows ? 'test\\node' : 'test/node'); - assert.strictEqual(path.posix.dirname('/a/b/'), '/a'); assert.strictEqual(path.posix.dirname('/a/b'), '/a'); assert.strictEqual(path.posix.dirname('/a'), '/'); @@ -362,7 +360,7 @@ suite('Paths (Node Implementation)', () => { assert.equal(path.extname('far.boo/boo'), ''); }); - test('resolve', () => { + (isWeb && isWindows ? test.skip : test)('resolve', () => { // TODO@sbatten fails on windows & browser only const failures = [] as string[]; const slashRE = /\//g; const backslashRE = /\\/g; diff --git a/src/vs/base/test/common/processes.test.ts b/src/vs/base/test/common/processes.test.ts index ee0d25c88..b5a89e2a0 100644 --- a/src/vs/base/test/common/processes.test.ts +++ b/src/vs/base/test/common/processes.test.ts @@ -15,7 +15,6 @@ suite('Processes', () => { ELECTRON_NO_ASAR: 'x', ELECTRON_NO_ATTACH_CONSOLE: 'x', ELECTRON_RUN_AS_NODE: 'x', - GOOGLE_API_KEY: 'x', VSCODE_CLI: 'x', VSCODE_DEV: 'x', VSCODE_IPC_HOOK: 'x', diff --git a/src/vs/base/test/common/resources.test.ts b/src/vs/base/test/common/resources.test.ts index 8026e240b..a41725d83 100644 --- a/src/vs/base/test/common/resources.test.ts +++ b/src/vs/base/test/common/resources.test.ts @@ -22,10 +22,10 @@ suite('Resources', () => { ]; let distinct = distinctParents(resources, r => r); - assert.equal(distinct.length, 3); - assert.equal(distinct[0].toString(), resources[0].toString()); - assert.equal(distinct[1].toString(), resources[1].toString()); - assert.equal(distinct[2].toString(), resources[2].toString()); + assert.strictEqual(distinct.length, 3); + assert.strictEqual(distinct[0].toString(), resources[0].toString()); + assert.strictEqual(distinct[1].toString(), resources[1].toString()); + assert.strictEqual(distinct[2].toString(), resources[2].toString()); // Parent / Child resources = [ @@ -37,144 +37,144 @@ suite('Resources', () => { ]; distinct = distinctParents(resources, r => r); - assert.equal(distinct.length, 3); - assert.equal(distinct[0].toString(), resources[0].toString()); - assert.equal(distinct[1].toString(), resources[3].toString()); - assert.equal(distinct[2].toString(), resources[4].toString()); + assert.strictEqual(distinct.length, 3); + assert.strictEqual(distinct[0].toString(), resources[0].toString()); + assert.strictEqual(distinct[1].toString(), resources[3].toString()); + assert.strictEqual(distinct[2].toString(), resources[4].toString()); }); test('dirname', () => { if (isWindows) { - assert.equal(dirname(URI.file('c:\\some\\file\\test.txt')).toString(), 'file:///c%3A/some/file'); - assert.equal(dirname(URI.file('c:\\some\\file')).toString(), 'file:///c%3A/some'); - assert.equal(dirname(URI.file('c:\\some\\file\\')).toString(), 'file:///c%3A/some'); - assert.equal(dirname(URI.file('c:\\some')).toString(), 'file:///c%3A/'); - assert.equal(dirname(URI.file('C:\\some')).toString(), 'file:///c%3A/'); - assert.equal(dirname(URI.file('c:\\')).toString(), 'file:///c%3A/'); + assert.strictEqual(dirname(URI.file('c:\\some\\file\\test.txt')).toString(), 'file:///c%3A/some/file'); + assert.strictEqual(dirname(URI.file('c:\\some\\file')).toString(), 'file:///c%3A/some'); + assert.strictEqual(dirname(URI.file('c:\\some\\file\\')).toString(), 'file:///c%3A/some'); + assert.strictEqual(dirname(URI.file('c:\\some')).toString(), 'file:///c%3A/'); + assert.strictEqual(dirname(URI.file('C:\\some')).toString(), 'file:///c%3A/'); + assert.strictEqual(dirname(URI.file('c:\\')).toString(), 'file:///c%3A/'); } else { - assert.equal(dirname(URI.file('/some/file/test.txt')).toString(), 'file:///some/file'); - assert.equal(dirname(URI.file('/some/file/')).toString(), 'file:///some'); - assert.equal(dirname(URI.file('/some/file')).toString(), 'file:///some'); + assert.strictEqual(dirname(URI.file('/some/file/test.txt')).toString(), 'file:///some/file'); + assert.strictEqual(dirname(URI.file('/some/file/')).toString(), 'file:///some'); + assert.strictEqual(dirname(URI.file('/some/file')).toString(), 'file:///some'); } - assert.equal(dirname(URI.parse('foo://a/some/file/test.txt')).toString(), 'foo://a/some/file'); - assert.equal(dirname(URI.parse('foo://a/some/file/')).toString(), 'foo://a/some'); - assert.equal(dirname(URI.parse('foo://a/some/file')).toString(), 'foo://a/some'); - assert.equal(dirname(URI.parse('foo://a/some')).toString(), 'foo://a/'); - assert.equal(dirname(URI.parse('foo://a/')).toString(), 'foo://a/'); - assert.equal(dirname(URI.parse('foo://a')).toString(), 'foo://a'); + assert.strictEqual(dirname(URI.parse('foo://a/some/file/test.txt')).toString(), 'foo://a/some/file'); + assert.strictEqual(dirname(URI.parse('foo://a/some/file/')).toString(), 'foo://a/some'); + assert.strictEqual(dirname(URI.parse('foo://a/some/file')).toString(), 'foo://a/some'); + assert.strictEqual(dirname(URI.parse('foo://a/some')).toString(), 'foo://a/'); + assert.strictEqual(dirname(URI.parse('foo://a/')).toString(), 'foo://a/'); + assert.strictEqual(dirname(URI.parse('foo://a')).toString(), 'foo://a'); // does not explode (https://github.com/microsoft/vscode/issues/41987) dirname(URI.from({ scheme: 'file', authority: '/users/someone/portal.h' })); - assert.equal(dirname(URI.parse('foo://a/b/c?q')).toString(), 'foo://a/b?q'); + assert.strictEqual(dirname(URI.parse('foo://a/b/c?q')).toString(), 'foo://a/b?q'); }); test('basename', () => { if (isWindows) { - assert.equal(basename(URI.file('c:\\some\\file\\test.txt')), 'test.txt'); - assert.equal(basename(URI.file('c:\\some\\file')), 'file'); - assert.equal(basename(URI.file('c:\\some\\file\\')), 'file'); - assert.equal(basename(URI.file('C:\\some\\file\\')), 'file'); + assert.strictEqual(basename(URI.file('c:\\some\\file\\test.txt')), 'test.txt'); + assert.strictEqual(basename(URI.file('c:\\some\\file')), 'file'); + assert.strictEqual(basename(URI.file('c:\\some\\file\\')), 'file'); + assert.strictEqual(basename(URI.file('C:\\some\\file\\')), 'file'); } else { - assert.equal(basename(URI.file('/some/file/test.txt')), 'test.txt'); - assert.equal(basename(URI.file('/some/file/')), 'file'); - assert.equal(basename(URI.file('/some/file')), 'file'); - assert.equal(basename(URI.file('/some')), 'some'); + assert.strictEqual(basename(URI.file('/some/file/test.txt')), 'test.txt'); + assert.strictEqual(basename(URI.file('/some/file/')), 'file'); + assert.strictEqual(basename(URI.file('/some/file')), 'file'); + assert.strictEqual(basename(URI.file('/some')), 'some'); } - assert.equal(basename(URI.parse('foo://a/some/file/test.txt')), 'test.txt'); - assert.equal(basename(URI.parse('foo://a/some/file/')), 'file'); - assert.equal(basename(URI.parse('foo://a/some/file')), 'file'); - assert.equal(basename(URI.parse('foo://a/some')), 'some'); - assert.equal(basename(URI.parse('foo://a/')), ''); - assert.equal(basename(URI.parse('foo://a')), ''); + assert.strictEqual(basename(URI.parse('foo://a/some/file/test.txt')), 'test.txt'); + assert.strictEqual(basename(URI.parse('foo://a/some/file/')), 'file'); + assert.strictEqual(basename(URI.parse('foo://a/some/file')), 'file'); + assert.strictEqual(basename(URI.parse('foo://a/some')), 'some'); + assert.strictEqual(basename(URI.parse('foo://a/')), ''); + assert.strictEqual(basename(URI.parse('foo://a')), ''); }); test('joinPath', () => { if (isWindows) { - assert.equal(joinPath(URI.file('c:\\foo\\bar'), '/file.js').toString(), 'file:///c%3A/foo/bar/file.js'); - assert.equal(joinPath(URI.file('c:\\foo\\bar\\'), 'file.js').toString(), 'file:///c%3A/foo/bar/file.js'); - assert.equal(joinPath(URI.file('c:\\foo\\bar\\'), '/file.js').toString(), 'file:///c%3A/foo/bar/file.js'); - assert.equal(joinPath(URI.file('c:\\'), '/file.js').toString(), 'file:///c%3A/file.js'); - assert.equal(joinPath(URI.file('c:\\'), 'bar/file.js').toString(), 'file:///c%3A/bar/file.js'); - assert.equal(joinPath(URI.file('c:\\foo'), './file.js').toString(), 'file:///c%3A/foo/file.js'); - assert.equal(joinPath(URI.file('c:\\foo'), '/./file.js').toString(), 'file:///c%3A/foo/file.js'); - assert.equal(joinPath(URI.file('C:\\foo'), '../file.js').toString(), 'file:///c%3A/file.js'); - assert.equal(joinPath(URI.file('C:\\foo\\.'), '../file.js').toString(), 'file:///c%3A/file.js'); + assert.strictEqual(joinPath(URI.file('c:\\foo\\bar'), '/file.js').toString(), 'file:///c%3A/foo/bar/file.js'); + assert.strictEqual(joinPath(URI.file('c:\\foo\\bar\\'), 'file.js').toString(), 'file:///c%3A/foo/bar/file.js'); + assert.strictEqual(joinPath(URI.file('c:\\foo\\bar\\'), '/file.js').toString(), 'file:///c%3A/foo/bar/file.js'); + assert.strictEqual(joinPath(URI.file('c:\\'), '/file.js').toString(), 'file:///c%3A/file.js'); + assert.strictEqual(joinPath(URI.file('c:\\'), 'bar/file.js').toString(), 'file:///c%3A/bar/file.js'); + assert.strictEqual(joinPath(URI.file('c:\\foo'), './file.js').toString(), 'file:///c%3A/foo/file.js'); + assert.strictEqual(joinPath(URI.file('c:\\foo'), '/./file.js').toString(), 'file:///c%3A/foo/file.js'); + assert.strictEqual(joinPath(URI.file('C:\\foo'), '../file.js').toString(), 'file:///c%3A/file.js'); + assert.strictEqual(joinPath(URI.file('C:\\foo\\.'), '../file.js').toString(), 'file:///c%3A/file.js'); } else { - assert.equal(joinPath(URI.file('/foo/bar'), '/file.js').toString(), 'file:///foo/bar/file.js'); - assert.equal(joinPath(URI.file('/foo/bar'), 'file.js').toString(), 'file:///foo/bar/file.js'); - assert.equal(joinPath(URI.file('/foo/bar/'), '/file.js').toString(), 'file:///foo/bar/file.js'); - assert.equal(joinPath(URI.file('/'), '/file.js').toString(), 'file:///file.js'); - assert.equal(joinPath(URI.file('/foo/bar'), './file.js').toString(), 'file:///foo/bar/file.js'); - assert.equal(joinPath(URI.file('/foo/bar'), '/./file.js').toString(), 'file:///foo/bar/file.js'); - assert.equal(joinPath(URI.file('/foo/bar'), '../file.js').toString(), 'file:///foo/file.js'); + assert.strictEqual(joinPath(URI.file('/foo/bar'), '/file.js').toString(), 'file:///foo/bar/file.js'); + assert.strictEqual(joinPath(URI.file('/foo/bar'), 'file.js').toString(), 'file:///foo/bar/file.js'); + assert.strictEqual(joinPath(URI.file('/foo/bar/'), '/file.js').toString(), 'file:///foo/bar/file.js'); + assert.strictEqual(joinPath(URI.file('/'), '/file.js').toString(), 'file:///file.js'); + assert.strictEqual(joinPath(URI.file('/foo/bar'), './file.js').toString(), 'file:///foo/bar/file.js'); + assert.strictEqual(joinPath(URI.file('/foo/bar'), '/./file.js').toString(), 'file:///foo/bar/file.js'); + assert.strictEqual(joinPath(URI.file('/foo/bar'), '../file.js').toString(), 'file:///foo/file.js'); } - assert.equal(joinPath(URI.parse('foo://a/foo/bar')).toString(), 'foo://a/foo/bar'); - assert.equal(joinPath(URI.parse('foo://a/foo/bar'), '/file.js').toString(), 'foo://a/foo/bar/file.js'); - assert.equal(joinPath(URI.parse('foo://a/foo/bar'), 'file.js').toString(), 'foo://a/foo/bar/file.js'); - assert.equal(joinPath(URI.parse('foo://a/foo/bar/'), '/file.js').toString(), 'foo://a/foo/bar/file.js'); - assert.equal(joinPath(URI.parse('foo://a/'), '/file.js').toString(), 'foo://a/file.js'); - assert.equal(joinPath(URI.parse('foo://a/foo/bar/'), './file.js').toString(), 'foo://a/foo/bar/file.js'); - assert.equal(joinPath(URI.parse('foo://a/foo/bar/'), '/./file.js').toString(), 'foo://a/foo/bar/file.js'); - assert.equal(joinPath(URI.parse('foo://a/foo/bar/'), '../file.js').toString(), 'foo://a/foo/file.js'); + assert.strictEqual(joinPath(URI.parse('foo://a/foo/bar')).toString(), 'foo://a/foo/bar'); + assert.strictEqual(joinPath(URI.parse('foo://a/foo/bar'), '/file.js').toString(), 'foo://a/foo/bar/file.js'); + assert.strictEqual(joinPath(URI.parse('foo://a/foo/bar'), 'file.js').toString(), 'foo://a/foo/bar/file.js'); + assert.strictEqual(joinPath(URI.parse('foo://a/foo/bar/'), '/file.js').toString(), 'foo://a/foo/bar/file.js'); + assert.strictEqual(joinPath(URI.parse('foo://a/'), '/file.js').toString(), 'foo://a/file.js'); + assert.strictEqual(joinPath(URI.parse('foo://a/foo/bar/'), './file.js').toString(), 'foo://a/foo/bar/file.js'); + assert.strictEqual(joinPath(URI.parse('foo://a/foo/bar/'), '/./file.js').toString(), 'foo://a/foo/bar/file.js'); + assert.strictEqual(joinPath(URI.parse('foo://a/foo/bar/'), '../file.js').toString(), 'foo://a/foo/file.js'); - assert.equal( + assert.strictEqual( joinPath(URI.from({ scheme: 'myScheme', authority: 'authority', path: '/path', query: 'query', fragment: 'fragment' }), '/file.js').toString(), 'myScheme://authority/path/file.js?query#fragment'); }); test('normalizePath', () => { if (isWindows) { - assert.equal(normalizePath(URI.file('c:\\foo\\.\\bar')).toString(), 'file:///c%3A/foo/bar'); - assert.equal(normalizePath(URI.file('c:\\foo\\.')).toString(), 'file:///c%3A/foo'); - assert.equal(normalizePath(URI.file('c:\\foo\\.\\')).toString(), 'file:///c%3A/foo/'); - assert.equal(normalizePath(URI.file('c:\\foo\\..')).toString(), 'file:///c%3A/'); - assert.equal(normalizePath(URI.file('c:\\foo\\..\\bar')).toString(), 'file:///c%3A/bar'); - assert.equal(normalizePath(URI.file('c:\\foo\\..\\..\\bar')).toString(), 'file:///c%3A/bar'); - assert.equal(normalizePath(URI.file('c:\\foo\\foo\\..\\..\\bar')).toString(), 'file:///c%3A/bar'); - assert.equal(normalizePath(URI.file('C:\\foo\\foo\\.\\..\\..\\bar')).toString(), 'file:///c%3A/bar'); - assert.equal(normalizePath(URI.file('C:\\foo\\foo\\.\\..\\some\\..\\bar')).toString(), 'file:///c%3A/foo/bar'); + assert.strictEqual(normalizePath(URI.file('c:\\foo\\.\\bar')).toString(), 'file:///c%3A/foo/bar'); + assert.strictEqual(normalizePath(URI.file('c:\\foo\\.')).toString(), 'file:///c%3A/foo'); + assert.strictEqual(normalizePath(URI.file('c:\\foo\\.\\')).toString(), 'file:///c%3A/foo/'); + assert.strictEqual(normalizePath(URI.file('c:\\foo\\..')).toString(), 'file:///c%3A/'); + assert.strictEqual(normalizePath(URI.file('c:\\foo\\..\\bar')).toString(), 'file:///c%3A/bar'); + assert.strictEqual(normalizePath(URI.file('c:\\foo\\..\\..\\bar')).toString(), 'file:///c%3A/bar'); + assert.strictEqual(normalizePath(URI.file('c:\\foo\\foo\\..\\..\\bar')).toString(), 'file:///c%3A/bar'); + assert.strictEqual(normalizePath(URI.file('C:\\foo\\foo\\.\\..\\..\\bar')).toString(), 'file:///c%3A/bar'); + assert.strictEqual(normalizePath(URI.file('C:\\foo\\foo\\.\\..\\some\\..\\bar')).toString(), 'file:///c%3A/foo/bar'); } else { - assert.equal(normalizePath(URI.file('/foo/./bar')).toString(), 'file:///foo/bar'); - assert.equal(normalizePath(URI.file('/foo/.')).toString(), 'file:///foo'); - assert.equal(normalizePath(URI.file('/foo/./')).toString(), 'file:///foo/'); - assert.equal(normalizePath(URI.file('/foo/..')).toString(), 'file:///'); - assert.equal(normalizePath(URI.file('/foo/../bar')).toString(), 'file:///bar'); - assert.equal(normalizePath(URI.file('/foo/../../bar')).toString(), 'file:///bar'); - assert.equal(normalizePath(URI.file('/foo/foo/../../bar')).toString(), 'file:///bar'); - assert.equal(normalizePath(URI.file('/foo/foo/./../../bar')).toString(), 'file:///bar'); - assert.equal(normalizePath(URI.file('/foo/foo/./../some/../bar')).toString(), 'file:///foo/bar'); - assert.equal(normalizePath(URI.file('/f')).toString(), 'file:///f'); + assert.strictEqual(normalizePath(URI.file('/foo/./bar')).toString(), 'file:///foo/bar'); + assert.strictEqual(normalizePath(URI.file('/foo/.')).toString(), 'file:///foo'); + assert.strictEqual(normalizePath(URI.file('/foo/./')).toString(), 'file:///foo/'); + assert.strictEqual(normalizePath(URI.file('/foo/..')).toString(), 'file:///'); + assert.strictEqual(normalizePath(URI.file('/foo/../bar')).toString(), 'file:///bar'); + assert.strictEqual(normalizePath(URI.file('/foo/../../bar')).toString(), 'file:///bar'); + assert.strictEqual(normalizePath(URI.file('/foo/foo/../../bar')).toString(), 'file:///bar'); + assert.strictEqual(normalizePath(URI.file('/foo/foo/./../../bar')).toString(), 'file:///bar'); + assert.strictEqual(normalizePath(URI.file('/foo/foo/./../some/../bar')).toString(), 'file:///foo/bar'); + assert.strictEqual(normalizePath(URI.file('/f')).toString(), 'file:///f'); } - assert.equal(normalizePath(URI.parse('foo://a/foo/./bar')).toString(), 'foo://a/foo/bar'); - assert.equal(normalizePath(URI.parse('foo://a/foo/.')).toString(), 'foo://a/foo'); - assert.equal(normalizePath(URI.parse('foo://a/foo/./')).toString(), 'foo://a/foo/'); - assert.equal(normalizePath(URI.parse('foo://a/foo/..')).toString(), 'foo://a/'); - assert.equal(normalizePath(URI.parse('foo://a/foo/../bar')).toString(), 'foo://a/bar'); - assert.equal(normalizePath(URI.parse('foo://a/foo/../../bar')).toString(), 'foo://a/bar'); - assert.equal(normalizePath(URI.parse('foo://a/foo/foo/../../bar')).toString(), 'foo://a/bar'); - assert.equal(normalizePath(URI.parse('foo://a/foo/foo/./../../bar')).toString(), 'foo://a/bar'); - assert.equal(normalizePath(URI.parse('foo://a/foo/foo/./../some/../bar')).toString(), 'foo://a/foo/bar'); - assert.equal(normalizePath(URI.parse('foo://a')).toString(), 'foo://a'); - assert.equal(normalizePath(URI.parse('foo://a/')).toString(), 'foo://a/'); - assert.equal(normalizePath(URI.parse('foo://a/foo/./bar?q=1')).toString(), URI.parse('foo://a/foo/bar?q%3D1').toString()); + assert.strictEqual(normalizePath(URI.parse('foo://a/foo/./bar')).toString(), 'foo://a/foo/bar'); + assert.strictEqual(normalizePath(URI.parse('foo://a/foo/.')).toString(), 'foo://a/foo'); + assert.strictEqual(normalizePath(URI.parse('foo://a/foo/./')).toString(), 'foo://a/foo/'); + assert.strictEqual(normalizePath(URI.parse('foo://a/foo/..')).toString(), 'foo://a/'); + assert.strictEqual(normalizePath(URI.parse('foo://a/foo/../bar')).toString(), 'foo://a/bar'); + assert.strictEqual(normalizePath(URI.parse('foo://a/foo/../../bar')).toString(), 'foo://a/bar'); + assert.strictEqual(normalizePath(URI.parse('foo://a/foo/foo/../../bar')).toString(), 'foo://a/bar'); + assert.strictEqual(normalizePath(URI.parse('foo://a/foo/foo/./../../bar')).toString(), 'foo://a/bar'); + assert.strictEqual(normalizePath(URI.parse('foo://a/foo/foo/./../some/../bar')).toString(), 'foo://a/foo/bar'); + assert.strictEqual(normalizePath(URI.parse('foo://a')).toString(), 'foo://a'); + assert.strictEqual(normalizePath(URI.parse('foo://a/')).toString(), 'foo://a/'); + assert.strictEqual(normalizePath(URI.parse('foo://a/foo/./bar?q=1')).toString(), URI.parse('foo://a/foo/bar?q%3D1').toString()); }); test('isAbsolute', () => { if (isWindows) { - assert.equal(isAbsolutePath(URI.file('c:\\foo\\')), true); - assert.equal(isAbsolutePath(URI.file('C:\\foo\\')), true); - assert.equal(isAbsolutePath(URI.file('bar')), true); // URI normalizes all file URIs to be absolute + assert.strictEqual(isAbsolutePath(URI.file('c:\\foo\\')), true); + assert.strictEqual(isAbsolutePath(URI.file('C:\\foo\\')), true); + assert.strictEqual(isAbsolutePath(URI.file('bar')), true); // URI normalizes all file URIs to be absolute } else { - assert.equal(isAbsolutePath(URI.file('/foo/bar')), true); - assert.equal(isAbsolutePath(URI.file('bar')), true); // URI normalizes all file URIs to be absolute + assert.strictEqual(isAbsolutePath(URI.file('/foo/bar')), true); + assert.strictEqual(isAbsolutePath(URI.file('bar')), true); // URI normalizes all file URIs to be absolute } - assert.equal(isAbsolutePath(URI.parse('foo:foo')), false); - assert.equal(isAbsolutePath(URI.parse('foo://a/foo/.')), true); + assert.strictEqual(isAbsolutePath(URI.parse('foo:foo')), false); + assert.strictEqual(isAbsolutePath(URI.parse('foo://a/foo/.')), true); }); function assertTrailingSeparator(u1: URI, expected: boolean) { - assert.equal(hasTrailingPathSeparator(u1), expected, u1.toString()); + assert.strictEqual(hasTrailingPathSeparator(u1), expected, u1.toString()); } function assertRemoveTrailingSeparator(u1: URI, expected: URI) { @@ -237,14 +237,14 @@ suite('Resources', () => { function assertEqualURI(actual: URI, expected: URI, message?: string, ignoreCase?: boolean) { let util = ignoreCase ? extUriIgnorePathCase : extUri; if (!util.isEqual(expected, actual)) { - assert.equal(actual.toString(), expected.toString(), message); + assert.strictEqual(actual.toString(), expected.toString(), message); } } function assertRelativePath(u1: URI, u2: URI, expectedPath: string | undefined, ignoreJoin?: boolean, ignoreCase?: boolean) { let util = ignoreCase ? extUriIgnorePathCase : extUri; - assert.equal(util.relativePath(u1, u2), expectedPath, `from ${u1.toString()} to ${u2.toString()}`); + assert.strictEqual(util.relativePath(u1, u2), expectedPath, `from ${u1.toString()} to ${u2.toString()}`); if (expectedPath !== undefined && !ignoreJoin) { assertEqualURI(removeTrailingPathSeparator(joinPath(u1, expectedPath)), removeTrailingPathSeparator(u2), 'joinPath on relativePath should be equal', ignoreCase); } @@ -304,7 +304,7 @@ suite('Resources', () => { if (!p.isAbsolute(path)) { let expectedPath = isWindows ? toSlashes(path) : path; expectedPath = expectedPath.startsWith('./') ? expectedPath.substr(2) : expectedPath; - assert.equal(relativePath(u1, actual), expectedPath, `relativePath (${u1.toString()}) on actual (${actual.toString()}) should be to path (${expectedPath})`); + assert.strictEqual(relativePath(u1, actual), expectedPath, `relativePath (${u1.toString()}) on actual (${actual.toString()}) should be to path (${expectedPath})`); } } @@ -352,12 +352,12 @@ suite('Resources', () => { let util = ignoreCase ? extUriIgnorePathCase : extUri; - assert.equal(util.isEqual(u1, u2), expected, `${u1.toString()}${expected ? '===' : '!=='}${u2.toString()}`); - assert.equal(util.compare(u1, u2) === 0, expected); - assert.equal(util.getComparisonKey(u1) === util.getComparisonKey(u2), expected, `comparison keys ${u1.toString()}, ${u2.toString()}`); - assert.equal(util.isEqualOrParent(u1, u2), expected, `isEqualOrParent ${u1.toString()}, ${u2.toString()}`); + assert.strictEqual(util.isEqual(u1, u2), expected, `${u1.toString()}${expected ? '===' : '!=='}${u2.toString()}`); + assert.strictEqual(util.compare(u1, u2) === 0, expected); + assert.strictEqual(util.getComparisonKey(u1) === util.getComparisonKey(u2), expected, `comparison keys ${u1.toString()}, ${u2.toString()}`); + assert.strictEqual(util.isEqualOrParent(u1, u2), expected, `isEqualOrParent ${u1.toString()}, ${u2.toString()}`); if (!ignoreCase) { - assert.equal(u1.toString() === u2.toString(), expected); + assert.strictEqual(u1.toString() === u2.toString(), expected); } } @@ -402,31 +402,31 @@ suite('Resources', () => { let fileURI = isWindows ? URI.file('c:\\foo\\bar') : URI.file('/foo/bar'); let fileURI2 = isWindows ? URI.file('c:\\foo') : URI.file('/foo'); let fileURI2b = isWindows ? URI.file('C:\\Foo\\') : URI.file('/Foo/'); - assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI, fileURI), true, '1'); - assert.equal(extUri.isEqualOrParent(fileURI, fileURI), true, '2'); - assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI, fileURI2), true, '3'); - assert.equal(extUri.isEqualOrParent(fileURI, fileURI2), true, '4'); - assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI, fileURI2b), true, '5'); - assert.equal(extUri.isEqualOrParent(fileURI, fileURI2b), false, '6'); + assert.strictEqual(extUriIgnorePathCase.isEqualOrParent(fileURI, fileURI), true, '1'); + assert.strictEqual(extUri.isEqualOrParent(fileURI, fileURI), true, '2'); + assert.strictEqual(extUriIgnorePathCase.isEqualOrParent(fileURI, fileURI2), true, '3'); + assert.strictEqual(extUri.isEqualOrParent(fileURI, fileURI2), true, '4'); + assert.strictEqual(extUriIgnorePathCase.isEqualOrParent(fileURI, fileURI2b), true, '5'); + assert.strictEqual(extUri.isEqualOrParent(fileURI, fileURI2b), false, '6'); - assert.equal(extUri.isEqualOrParent(fileURI2, fileURI), false, '7'); - assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI2b, fileURI2), true, '8'); + assert.strictEqual(extUri.isEqualOrParent(fileURI2, fileURI), false, '7'); + assert.strictEqual(extUriIgnorePathCase.isEqualOrParent(fileURI2b, fileURI2), true, '8'); let fileURI3 = URI.parse('foo://server:453/foo/bar/goo'); let fileURI4 = URI.parse('foo://server:453/foo/'); let fileURI5 = URI.parse('foo://server:453/foo'); - assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI3, fileURI3, true), true, '11'); - assert.equal(extUri.isEqualOrParent(fileURI3, fileURI3), true, '12'); - assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI3, fileURI4, true), true, '13'); - assert.equal(extUri.isEqualOrParent(fileURI3, fileURI4), true, '14'); - assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI3, fileURI, true), false, '15'); - assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI5, fileURI5, true), true, '16'); + assert.strictEqual(extUriIgnorePathCase.isEqualOrParent(fileURI3, fileURI3, true), true, '11'); + assert.strictEqual(extUri.isEqualOrParent(fileURI3, fileURI3), true, '12'); + assert.strictEqual(extUriIgnorePathCase.isEqualOrParent(fileURI3, fileURI4, true), true, '13'); + assert.strictEqual(extUri.isEqualOrParent(fileURI3, fileURI4), true, '14'); + assert.strictEqual(extUriIgnorePathCase.isEqualOrParent(fileURI3, fileURI, true), false, '15'); + assert.strictEqual(extUriIgnorePathCase.isEqualOrParent(fileURI5, fileURI5, true), true, '16'); let fileURI6 = URI.parse('foo://server:453/foo?q=1'); let fileURI7 = URI.parse('foo://server:453/foo/bar?q=1'); - assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI6, fileURI5), false, '17'); - assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI6, fileURI6), true, '18'); - assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI7, fileURI6), true, '19'); - assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI7, fileURI5), false, '20'); + assert.strictEqual(extUriIgnorePathCase.isEqualOrParent(fileURI6, fileURI5), false, '17'); + assert.strictEqual(extUriIgnorePathCase.isEqualOrParent(fileURI6, fileURI6), true, '18'); + assert.strictEqual(extUriIgnorePathCase.isEqualOrParent(fileURI7, fileURI6), true, '19'); + assert.strictEqual(extUriIgnorePathCase.isEqualOrParent(fileURI7, fileURI5), false, '20'); }); }); diff --git a/src/vs/base/test/common/scrollable.test.ts b/src/vs/base/test/common/scrollable.test.ts index 820862f2f..bf8e6cb00 100644 --- a/src/vs/base/test/common/scrollable.test.ts +++ b/src/vs/base/test/common/scrollable.test.ts @@ -57,7 +57,7 @@ suite('SmoothScrollingOperation', () => { function assertSmoothScroll(from: number, to: number, expected: [number, number][]): void { const actual = simulateSmoothScroll(from, to); - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); } test('scroll 25 lines (40 fit)', () => { diff --git a/src/vs/base/test/common/skipList.test.ts b/src/vs/base/test/common/skipList.test.ts index f6a083e3c..ec3da0d10 100644 --- a/src/vs/base/test/common/skipList.test.ts +++ b/src/vs/base/test/common/skipList.test.ts @@ -12,45 +12,45 @@ import { binarySearch } from 'vs/base/common/arrays'; suite('SkipList', function () { function assertValues(list: SkipList, expected: V[]) { - assert.equal(list.size, expected.length); - assert.deepEqual([...list.values()], expected); + assert.strictEqual(list.size, expected.length); + assert.deepStrictEqual([...list.values()], expected); let valuesFromEntries = [...list.entries()].map(entry => entry[1]); - assert.deepEqual(valuesFromEntries, expected); + assert.deepStrictEqual(valuesFromEntries, expected); let valuesFromIter = [...list].map(entry => entry[1]); - assert.deepEqual(valuesFromIter, expected); + assert.deepStrictEqual(valuesFromIter, expected); let i = 0; list.forEach((value, _key, map) => { assert.ok(map === list); - assert.deepEqual(value, expected[i++]); + assert.deepStrictEqual(value, expected[i++]); }); } function assertKeys(list: SkipList, expected: K[]) { - assert.equal(list.size, expected.length); - assert.deepEqual([...list.keys()], expected); + assert.strictEqual(list.size, expected.length); + assert.deepStrictEqual([...list.keys()], expected); let keysFromEntries = [...list.entries()].map(entry => entry[0]); - assert.deepEqual(keysFromEntries, expected); + assert.deepStrictEqual(keysFromEntries, expected); let keysFromIter = [...list].map(entry => entry[0]); - assert.deepEqual(keysFromIter, expected); + assert.deepStrictEqual(keysFromIter, expected); let i = 0; list.forEach((_value, key, map) => { assert.ok(map === list); - assert.deepEqual(key, expected[i++]); + assert.deepStrictEqual(key, expected[i++]); }); } test('set/get/delete', function () { let list = new SkipList((a, b) => a - b); - assert.equal(list.get(3), undefined); + assert.strictEqual(list.get(3), undefined); list.set(3, 1); - assert.equal(list.get(3), 1); + assert.strictEqual(list.get(3), 1); assertValues(list, [1]); list.set(3, 3); @@ -58,17 +58,17 @@ suite('SkipList', function () { list.set(1, 1); list.set(4, 4); - assert.equal(list.get(3), 3); - assert.equal(list.get(1), 1); - assert.equal(list.get(4), 4); + assert.strictEqual(list.get(3), 3); + assert.strictEqual(list.get(1), 1); + assert.strictEqual(list.get(4), 4); assertValues(list, [1, 3, 4]); - assert.equal(list.delete(17), false); + assert.strictEqual(list.delete(17), false); - assert.equal(list.delete(1), true); - assert.equal(list.get(1), undefined); - assert.equal(list.get(3), 3); - assert.equal(list.get(4), 4); + assert.strictEqual(list.delete(1), true); + assert.strictEqual(list.get(1), undefined); + assert.strictEqual(list.get(3), 3); + assert.strictEqual(list.get(4), 4); assertValues(list, [3, 4]); }); @@ -87,7 +87,7 @@ suite('SkipList', function () { assertKeys(list, [3, 6, 7, 9, 12, 19, 21, 25]); list.set(17, true); - assert.deepEqual(list.size, 9); + assert.deepStrictEqual(list.size, 9); assertKeys(list, [3, 6, 7, 9, 12, 17, 19, 21, 25]); }); @@ -141,8 +141,7 @@ suite('SkipList', function () { } - test('perf', function () { - this.skip(); + test.skip('perf', function () { // data const max = 2 ** 16; diff --git a/src/vs/base/test/common/stream.test.ts b/src/vs/base/test/common/stream.test.ts index 0ef3b7730..0fb36467b 100644 --- a/src/vs/base/test/common/stream.test.ts +++ b/src/vs/base/test/common/stream.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { isReadableStream, newWriteableStream, Readable, consumeReadable, peekReadable, consumeStream, ReadableStream, toStream, toReadable, transform, peekStream, isReadableBufferedStream } from 'vs/base/common/stream'; +import { isReadableStream, newWriteableStream, Readable, consumeReadable, peekReadable, consumeStream, ReadableStream, toStream, toReadable, transform, peekStream, isReadableBufferedStream, observe } from 'vs/base/common/stream'; import { timeout } from 'vs/base/common/async'; suite('Stream', () => { @@ -43,37 +43,37 @@ suite('Stream', () => { chunks.push(data); }); - assert.equal(chunks[0], 'Hello'); + assert.strictEqual(chunks[0], 'Hello'); stream.write('World'); - assert.equal(chunks[1], 'World'); + assert.strictEqual(chunks[1], 'World'); - assert.equal(error, false); - assert.equal(end, false); + assert.strictEqual(error, false); + assert.strictEqual(end, false); stream.pause(); stream.write('1'); stream.write('2'); stream.write('3'); - assert.equal(chunks.length, 2); + assert.strictEqual(chunks.length, 2); stream.resume(); - assert.equal(chunks.length, 3); - assert.equal(chunks[2], '1,2,3'); + assert.strictEqual(chunks.length, 3); + assert.strictEqual(chunks[2], '1,2,3'); stream.error(new Error()); - assert.equal(error, true); + assert.strictEqual(error, true); stream.end('Final Bit'); - assert.equal(chunks.length, 4); - assert.equal(chunks[3], 'Final Bit'); + assert.strictEqual(chunks.length, 4); + assert.strictEqual(chunks[3], 'Final Bit'); stream.destroy(); stream.write('Unexpected'); - assert.equal(chunks.length, 4); + assert.strictEqual(chunks.length, 4); }); test('WriteableStream - removeListener', () => { @@ -92,22 +92,24 @@ suite('Stream', () => { stream.on('data', dataListener); stream.write('Hello'); - assert.equal(data, true); + assert.strictEqual(data, true); data = false; stream.removeListener('data', dataListener); stream.write('World'); - assert.equal(data, false); + assert.strictEqual(data, false); stream.error(new Error()); - assert.equal(error, true); + assert.strictEqual(error, true); error = false; stream.removeListener('error', errorListener); + // always leave at least one error listener to streams to avoid unexpected errors during test running + stream.on('error', () => { }); stream.error(new Error()); - assert.equal(error, false); + assert.strictEqual(error, false); }); test('WriteableStream - highWaterMark', async () => { @@ -147,14 +149,14 @@ suite('Stream', () => { assert.ok(data); await timeout(0); - assert.equal(drained1, true); - assert.equal(drained2, true); + assert.strictEqual(drained1, true); + assert.strictEqual(drained2, true); }); test('consumeReadable', () => { const readable = arrayToReadable(['1', '2', '3', '4', '5']); const consumed = consumeReadable(readable, strings => strings.join()); - assert.equal(consumed, '1,2,3,4,5'); + assert.strictEqual(consumed, '1,2,3,4,5'); }); test('peekReadable', () => { @@ -166,17 +168,17 @@ suite('Stream', () => { assert.fail('Unexpected result'); } else { const consumed = consumeReadable(consumedOrReadable, strings => strings.join()); - assert.equal(consumed, '1,2,3,4,5'); + assert.strictEqual(consumed, '1,2,3,4,5'); } } let readable = arrayToReadable(['1', '2', '3', '4', '5']); let consumedOrReadable = peekReadable(readable, strings => strings.join(), 5); - assert.equal(consumedOrReadable, '1,2,3,4,5'); + assert.strictEqual(consumedOrReadable, '1,2,3,4,5'); readable = arrayToReadable(['1', '2', '3', '4', '5']); consumedOrReadable = peekReadable(readable, strings => strings.join(), 6); - assert.equal(consumedOrReadable, '1,2,3,4,5'); + assert.strictEqual(consumedOrReadable, '1,2,3,4,5'); }); test('peekReadable - error handling', async () => { @@ -265,7 +267,7 @@ suite('Stream', () => { test('consumeStream', async () => { const stream = readableToStream(arrayToReadable(['1', '2', '3', '4', '5'])); const consumed = await consumeStream(stream, strings => strings.join()); - assert.equal(consumed, '1,2,3,4,5'); + assert.strictEqual(consumed, '1,2,3,4,5'); }); test('peekStream', async () => { @@ -273,11 +275,11 @@ suite('Stream', () => { const stream = readableToStream(arrayToReadable(['1', '2', '3', '4', '5'])); const result = await peekStream(stream, i); - assert.equal(stream, result.stream); + assert.strictEqual(stream, result.stream); if (result.ended) { assert.fail('Unexpected result, stream should not have ended yet'); } else { - assert.equal(result.buffer.length, i + 1, `maxChunks: ${i}`); + assert.strictEqual(result.buffer.length, i + 1, `maxChunks: ${i}`); const additionalResult: string[] = []; await consumeStream(stream, strings => { @@ -286,33 +288,33 @@ suite('Stream', () => { return strings.join(); }); - assert.equal([...result.buffer, ...additionalResult].join(), '1,2,3,4,5'); + assert.strictEqual([...result.buffer, ...additionalResult].join(), '1,2,3,4,5'); } } let stream = readableToStream(arrayToReadable(['1', '2', '3', '4', '5'])); let result = await peekStream(stream, 5); - assert.equal(stream, result.stream); - assert.equal(result.buffer.join(), '1,2,3,4,5'); - assert.equal(result.ended, true); + assert.strictEqual(stream, result.stream); + assert.strictEqual(result.buffer.join(), '1,2,3,4,5'); + assert.strictEqual(result.ended, true); stream = readableToStream(arrayToReadable(['1', '2', '3', '4', '5'])); result = await peekStream(stream, 6); - assert.equal(stream, result.stream); - assert.equal(result.buffer.join(), '1,2,3,4,5'); - assert.equal(result.ended, true); + assert.strictEqual(stream, result.stream); + assert.strictEqual(result.buffer.join(), '1,2,3,4,5'); + assert.strictEqual(result.ended, true); }); test('toStream', async () => { const stream = toStream('1,2,3,4,5', strings => strings.join()); const consumed = await consumeStream(stream, strings => strings.join()); - assert.equal(consumed, '1,2,3,4,5'); + assert.strictEqual(consumed, '1,2,3,4,5'); }); test('toReadable', async () => { const readable = toReadable('1,2,3,4,5'); const consumed = await consumeReadable(readable, strings => strings.join()); - assert.equal(consumed, '1,2,3,4,5'); + assert.strictEqual(consumed, '1,2,3,4,5'); }); test('transform', async () => { @@ -330,6 +332,47 @@ suite('Stream', () => { }, 0); const consumed = await consumeStream(result, strings => strings.join()); - assert.equal(consumed, '11,22,33,44,55'); + assert.strictEqual(consumed, '11,22,33,44,55'); + }); + + test('observer', async () => { + const source1 = newWriteableStream(strings => strings.join()); + setTimeout(() => source1.error(new Error())); + await observe(source1).errorOrEnd(); + + const source2 = newWriteableStream(strings => strings.join()); + setTimeout(() => source2.end('Hello Test')); + await observe(source2).errorOrEnd(); + + const source3 = newWriteableStream(strings => strings.join()); + setTimeout(() => { + source3.write('Hello Test'); + source3.error(new Error()); + }); + await observe(source3).errorOrEnd(); + + const source4 = newWriteableStream(strings => strings.join()); + setTimeout(() => { + source4.write('Hello Test'); + source4.end(); + }); + await observe(source4).errorOrEnd(); + }); + + test('events are delivered even if a listener is removed during delivery', () => { + const stream = newWriteableStream(strings => strings.join()); + + let listener1Called = false; + let listener2Called = false; + + const listener1 = () => { stream.removeListener('end', listener1); listener1Called = true; }; + const listener2 = () => { listener2Called = true; }; + stream.on('end', listener1); + stream.on('end', listener2); + stream.on('data', () => { }); + stream.end(''); + + assert.strictEqual(listener1Called, true); + assert.strictEqual(listener2Called, true); }); }); diff --git a/src/vs/base/test/common/strings.test.ts b/src/vs/base/test/common/strings.test.ts index d3366a1e5..dc8d9da5e 100644 --- a/src/vs/base/test/common/strings.test.ts +++ b/src/vs/base/test/common/strings.test.ts @@ -52,7 +52,7 @@ suite('Strings', () => { let expected = strings.compare(a.toLowerCase(), b.toLowerCase()); expected = expected > 0 ? 1 : expected < 0 ? -1 : expected; - assert.equal(actual, expected, `${a} <> ${b}`); + assert.strictEqual(actual, expected, `${a} <> ${b}`); if (recurse) { assertCompareIgnoreCase(b, a, false); @@ -89,7 +89,7 @@ suite('Strings', () => { let expected = strings.compare(a.toLowerCase().substring(aStart, aEnd), b.toLowerCase().substring(bStart, bEnd)); expected = expected > 0 ? 1 : expected < 0 ? -1 : expected; - assert.equal(actual, expected, `${a} <> ${b}`); + assert.strictEqual(actual, expected, `${a} <> ${b}`); if (recurse) { assertCompareIgnoreCase(b, a, bStart, bEnd, aStart, aEnd, false); @@ -188,36 +188,36 @@ suite('Strings', () => { }); test('containsRTL', () => { - assert.equal(strings.containsRTL('a'), false); - assert.equal(strings.containsRTL(''), false); - assert.equal(strings.containsRTL(strings.UTF8_BOM_CHARACTER + 'a'), false); - assert.equal(strings.containsRTL('hello world!'), false); - assert.equal(strings.containsRTL('a📚📚b'), false); - assert.equal(strings.containsRTL('هناك حقيقة مثبتة منذ زمن طويل'), true); - assert.equal(strings.containsRTL('זוהי עובדה מבוססת שדעתו'), true); + assert.strictEqual(strings.containsRTL('a'), false); + assert.strictEqual(strings.containsRTL(''), false); + assert.strictEqual(strings.containsRTL(strings.UTF8_BOM_CHARACTER + 'a'), false); + assert.strictEqual(strings.containsRTL('hello world!'), false); + assert.strictEqual(strings.containsRTL('a📚📚b'), false); + assert.strictEqual(strings.containsRTL('هناك حقيقة مثبتة منذ زمن طويل'), true); + assert.strictEqual(strings.containsRTL('זוהי עובדה מבוססת שדעתו'), true); }); test('containsEmoji', () => { - assert.equal(strings.containsEmoji('a'), false); - assert.equal(strings.containsEmoji(''), false); - assert.equal(strings.containsEmoji(strings.UTF8_BOM_CHARACTER + 'a'), false); - assert.equal(strings.containsEmoji('hello world!'), false); - assert.equal(strings.containsEmoji('هناك حقيقة مثبتة منذ زمن طويل'), false); - assert.equal(strings.containsEmoji('זוהי עובדה מבוססת שדעתו'), false); + assert.strictEqual(strings.containsEmoji('a'), false); + assert.strictEqual(strings.containsEmoji(''), false); + assert.strictEqual(strings.containsEmoji(strings.UTF8_BOM_CHARACTER + 'a'), false); + assert.strictEqual(strings.containsEmoji('hello world!'), false); + assert.strictEqual(strings.containsEmoji('هناك حقيقة مثبتة منذ زمن طويل'), false); + assert.strictEqual(strings.containsEmoji('זוהי עובדה מבוססת שדעתו'), false); - assert.equal(strings.containsEmoji('a📚📚b'), true); - assert.equal(strings.containsEmoji('1F600 # 😀 grinning face'), true); - assert.equal(strings.containsEmoji('1F47E # 👾 alien monster'), true); - assert.equal(strings.containsEmoji('1F467 1F3FD # 👧🏽 girl: medium skin tone'), true); - assert.equal(strings.containsEmoji('26EA # ⛪ church'), true); - assert.equal(strings.containsEmoji('231B # ⌛ hourglass'), true); - assert.equal(strings.containsEmoji('2702 # ✂ scissors'), true); - assert.equal(strings.containsEmoji('1F1F7 1F1F4 # 🇷🇴 Romania'), true); + assert.strictEqual(strings.containsEmoji('a📚📚b'), true); + assert.strictEqual(strings.containsEmoji('1F600 # 😀 grinning face'), true); + assert.strictEqual(strings.containsEmoji('1F47E # 👾 alien monster'), true); + assert.strictEqual(strings.containsEmoji('1F467 1F3FD # 👧🏽 girl: medium skin tone'), true); + assert.strictEqual(strings.containsEmoji('26EA # ⛪ church'), true); + assert.strictEqual(strings.containsEmoji('231B # ⌛ hourglass'), true); + assert.strictEqual(strings.containsEmoji('2702 # ✂ scissors'), true); + assert.strictEqual(strings.containsEmoji('1F1F7 1F1F4 # 🇷🇴 Romania'), true); }); test('isBasicASCII', () => { function assertIsBasicASCII(str: string, expected: boolean): void { - assert.equal(strings.isBasicASCII(str), expected, str + ` (${str.charCodeAt(0)})`); + assert.strictEqual(strings.isBasicASCII(str), expected, str + ` (${str.charCodeAt(0)})`); } assertIsBasicASCII('abcdefghijklmnopqrstuvwxyz', true); assertIsBasicASCII('ABCDEFGHIJKLMNOPQRSTUVWXYZ', true); @@ -245,16 +245,16 @@ suite('Strings', () => { assert.throws(() => strings.createRegExp('', false)); // Escapes appropriately - assert.equal(strings.createRegExp('abc', false).source, 'abc'); - assert.equal(strings.createRegExp('([^ ,.]*)', false).source, '\\(\\[\\^ ,\\.\\]\\*\\)'); - assert.equal(strings.createRegExp('([^ ,.]*)', true).source, '([^ ,.]*)'); + assert.strictEqual(strings.createRegExp('abc', false).source, 'abc'); + assert.strictEqual(strings.createRegExp('([^ ,.]*)', false).source, '\\(\\[\\^ ,\\.\\]\\*\\)'); + assert.strictEqual(strings.createRegExp('([^ ,.]*)', true).source, '([^ ,.]*)'); // Whole word - assert.equal(strings.createRegExp('abc', false, { wholeWord: true }).source, '\\babc\\b'); - assert.equal(strings.createRegExp('abc', true, { wholeWord: true }).source, '\\babc\\b'); - assert.equal(strings.createRegExp(' abc', true, { wholeWord: true }).source, ' abc\\b'); - assert.equal(strings.createRegExp('abc ', true, { wholeWord: true }).source, '\\babc '); - assert.equal(strings.createRegExp(' abc ', true, { wholeWord: true }).source, ' abc '); + assert.strictEqual(strings.createRegExp('abc', false, { wholeWord: true }).source, '\\babc\\b'); + assert.strictEqual(strings.createRegExp('abc', true, { wholeWord: true }).source, '\\babc\\b'); + assert.strictEqual(strings.createRegExp(' abc', true, { wholeWord: true }).source, ' abc\\b'); + assert.strictEqual(strings.createRegExp('abc ', true, { wholeWord: true }).source, '\\babc '); + assert.strictEqual(strings.createRegExp(' abc ', true, { wholeWord: true }).source, ' abc '); const regExpWithoutFlags = strings.createRegExp('abc', true); assert(!regExpWithoutFlags.global); @@ -284,15 +284,15 @@ suite('Strings', () => { }); test('getLeadingWhitespace', () => { - assert.equal(strings.getLeadingWhitespace(' foo'), ' '); - assert.equal(strings.getLeadingWhitespace(' foo', 2), ''); - assert.equal(strings.getLeadingWhitespace(' foo', 1, 1), ''); - assert.equal(strings.getLeadingWhitespace(' foo', 0, 1), ' '); - assert.equal(strings.getLeadingWhitespace(' '), ' '); - assert.equal(strings.getLeadingWhitespace(' ', 1), ' '); - assert.equal(strings.getLeadingWhitespace(' ', 0, 1), ' '); - assert.equal(strings.getLeadingWhitespace('\t\tfunction foo(){', 0, 1), '\t'); - assert.equal(strings.getLeadingWhitespace('\t\tfunction foo(){', 0, 2), '\t\t'); + assert.strictEqual(strings.getLeadingWhitespace(' foo'), ' '); + assert.strictEqual(strings.getLeadingWhitespace(' foo', 2), ''); + assert.strictEqual(strings.getLeadingWhitespace(' foo', 1, 1), ''); + assert.strictEqual(strings.getLeadingWhitespace(' foo', 0, 1), ' '); + assert.strictEqual(strings.getLeadingWhitespace(' '), ' '); + assert.strictEqual(strings.getLeadingWhitespace(' ', 1), ' '); + assert.strictEqual(strings.getLeadingWhitespace(' ', 0, 1), ' '); + assert.strictEqual(strings.getLeadingWhitespace('\t\tfunction foo(){', 0, 1), '\t'); + assert.strictEqual(strings.getLeadingWhitespace('\t\tfunction foo(){', 0, 2), '\t\t'); }); test('fuzzyContains', () => { @@ -316,11 +316,11 @@ suite('Strings', () => { }); test('stripUTF8BOM', () => { - assert.equal(strings.stripUTF8BOM(strings.UTF8_BOM_CHARACTER), ''); - assert.equal(strings.stripUTF8BOM(strings.UTF8_BOM_CHARACTER + 'foobar'), 'foobar'); - assert.equal(strings.stripUTF8BOM('foobar' + strings.UTF8_BOM_CHARACTER), 'foobar' + strings.UTF8_BOM_CHARACTER); - assert.equal(strings.stripUTF8BOM('abc'), 'abc'); - assert.equal(strings.stripUTF8BOM(''), ''); + assert.strictEqual(strings.stripUTF8BOM(strings.UTF8_BOM_CHARACTER), ''); + assert.strictEqual(strings.stripUTF8BOM(strings.UTF8_BOM_CHARACTER + 'foobar'), 'foobar'); + assert.strictEqual(strings.stripUTF8BOM('foobar' + strings.UTF8_BOM_CHARACTER), 'foobar' + strings.UTF8_BOM_CHARACTER); + assert.strictEqual(strings.stripUTF8BOM('abc'), 'abc'); + assert.strictEqual(strings.stripUTF8BOM(''), ''); }); test('containsUppercaseCharacter', () => { @@ -340,7 +340,7 @@ suite('Strings', () => { ['FöÖ', true], ['\\Foo', true], ].forEach(([str, result]) => { - assert.equal(strings.containsUppercaseCharacter(str), result, `Wrong result for ${str}`); + assert.strictEqual(strings.containsUppercaseCharacter(str), result, `Wrong result for ${str}`); }); }); @@ -352,7 +352,7 @@ suite('Strings', () => { ['Foo', true], ].forEach(([str, result]) => { - assert.equal(strings.containsUppercaseCharacter(str, true), result, `Wrong result for ${str}`); + assert.strictEqual(strings.containsUppercaseCharacter(str, true), result, `Wrong result for ${str}`); }); }); @@ -364,20 +364,20 @@ suite('Strings', () => { ['123', '123'], ['.a', '.a'], ].forEach(([inStr, result]) => { - assert.equal(strings.uppercaseFirstLetter(inStr), result, `Wrong result for ${inStr}`); + assert.strictEqual(strings.uppercaseFirstLetter(inStr), result, `Wrong result for ${inStr}`); }); }); test('getNLines', () => { - assert.equal(strings.getNLines('', 5), ''); - assert.equal(strings.getNLines('foo', 5), 'foo'); - assert.equal(strings.getNLines('foo\nbar', 5), 'foo\nbar'); - assert.equal(strings.getNLines('foo\nbar', 2), 'foo\nbar'); + assert.strictEqual(strings.getNLines('', 5), ''); + assert.strictEqual(strings.getNLines('foo', 5), 'foo'); + assert.strictEqual(strings.getNLines('foo\nbar', 5), 'foo\nbar'); + assert.strictEqual(strings.getNLines('foo\nbar', 2), 'foo\nbar'); - assert.equal(strings.getNLines('foo\nbar', 1), 'foo'); - assert.equal(strings.getNLines('foo\nbar'), 'foo'); - assert.equal(strings.getNLines('foo\nbar\nsomething', 2), 'foo\nbar'); - assert.equal(strings.getNLines('foo', 0), ''); + assert.strictEqual(strings.getNLines('foo\nbar', 1), 'foo'); + assert.strictEqual(strings.getNLines('foo\nbar'), 'foo'); + assert.strictEqual(strings.getNLines('foo\nbar\nsomething', 2), 'foo\nbar'); + assert.strictEqual(strings.getNLines('foo', 0), ''); }); test('encodeUTF8', function () { @@ -387,12 +387,12 @@ suite('Strings', () => { for (let offset = 0; offset < actual.byteLength; offset++) { actualArr[offset] = actual[offset]; } - assert.deepEqual(actualArr, expected); + assert.deepStrictEqual(actualArr, expected); } function assertDecodeUTF8(data: number[], expected: string): void { const actual = strings.decodeUTF8(new Uint8Array(data)); - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); } function assertEncodeDecodeUTF8(str: string, buff: number[]): void { @@ -415,11 +415,11 @@ suite('Strings', () => { }); test('getGraphemeBreakType', () => { - assert.equal(strings.getGraphemeBreakType(0xBC1), strings.GraphemeBreakType.SpacingMark); + assert.strictEqual(strings.getGraphemeBreakType(0xBC1), strings.GraphemeBreakType.SpacingMark); }); test('truncate', () => { - assert.equal('hello world', strings.truncate('hello world', 100)); - assert.equal('hello…', strings.truncate('hello world', 5)); + assert.strictEqual('hello world', strings.truncate('hello world', 100)); + assert.strictEqual('hello…', strings.truncate('hello world', 5)); }); }); diff --git a/src/vs/base/test/common/troubleshooting.ts b/src/vs/base/test/common/troubleshooting.ts new file mode 100644 index 000000000..1d9d99632 --- /dev/null +++ b/src/vs/base/test/common/troubleshooting.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 { IDisposable, IDisposableTracker, setDisposableTracker } from 'vs/base/common/lifecycle'; + +class DisposableTracker implements IDisposableTracker { + allDisposables: [IDisposable, string][] = []; + trackDisposable(x: IDisposable): void { + this.allDisposables.push([x, new Error().stack!]); + } + markTracked(x: IDisposable): void { + for (let idx = 0; idx < this.allDisposables.length; idx++) { + if (this.allDisposables[idx][0] === x) { + this.allDisposables.splice(idx, 1); + return; + } + } + } +} + +let currentTracker: DisposableTracker | null = null; + +export function beginTrackingDisposables(): void { + currentTracker = new DisposableTracker(); + setDisposableTracker(currentTracker); +} + +export function endTrackingDisposables(): void { + if (currentTracker) { + setDisposableTracker(null); + console.log(currentTracker!.allDisposables.map(e => `${e[0]}\n${e[1]}`).join('\n\n')); + currentTracker = null; + } +} + +export function beginLoggingFS(withStacks: boolean = false): void { + if ((self).beginLoggingFS) { + (self).beginLoggingFS(withStacks); + } +} + +export function endLoggingFS(): void { + if ((self).endLoggingFS) { + (self).endLoggingFS(); + } +} diff --git a/src/vs/base/test/common/types.test.ts b/src/vs/base/test/common/types.test.ts index 0bec27fcd..cb7dedbe2 100644 --- a/src/vs/base/test/common/types.test.ts +++ b/src/vs/base/test/common/types.test.ts @@ -57,7 +57,7 @@ suite('Types', () => { assert(!types.isObject(/test/)); assert(!types.isObject(new RegExp(''))); assert(!types.isFunction(new Date())); - assert(!types.isObject(assert)); + assert.strictEqual(types.isObject(assert), false); assert(!types.isObject(function foo() { })); assert(types.isObject({})); @@ -75,7 +75,7 @@ suite('Types', () => { assert(!types.isEmptyObject(/test/)); assert(!types.isEmptyObject(new RegExp(''))); assert(!types.isEmptyObject(new Date())); - assert(!types.isEmptyObject(assert)); + assert.strictEqual(types.isEmptyObject(assert), false); assert(!types.isEmptyObject(function foo() { /**/ })); assert(!types.isEmptyObject({ foo: 'bar' })); @@ -178,15 +178,15 @@ suite('Types', () => { assert.throws(() => types.assertAllDefined(true, undefined)); assert.throws(() => types.assertAllDefined(undefined, false)); - assert.equal(types.assertIsDefined(true), true); - assert.equal(types.assertIsDefined(false), false); - assert.equal(types.assertIsDefined('Hello'), 'Hello'); - assert.equal(types.assertIsDefined(''), ''); + assert.strictEqual(types.assertIsDefined(true), true); + assert.strictEqual(types.assertIsDefined(false), false); + assert.strictEqual(types.assertIsDefined('Hello'), 'Hello'); + assert.strictEqual(types.assertIsDefined(''), ''); const res = types.assertAllDefined(1, true, 'Hello'); - assert.equal(res[0], 1); - assert.equal(res[1], true); - assert.equal(res[2], 'Hello'); + assert.strictEqual(res[0], 1); + assert.strictEqual(res[1], true); + assert.strictEqual(res[2], 'Hello'); }); test('validateConstraints', () => { diff --git a/src/vs/base/test/common/uri.test.ts b/src/vs/base/test/common/uri.test.ts index bc4e409c0..a05e4fc3d 100644 --- a/src/vs/base/test/common/uri.test.ts +++ b/src/vs/base/test/common/uri.test.ts @@ -9,82 +9,82 @@ import { isWindows } from 'vs/base/common/platform'; suite('URI', () => { test('file#toString', () => { - assert.equal(URI.file('c:/win/path').toString(), 'file:///c%3A/win/path'); - assert.equal(URI.file('C:/win/path').toString(), 'file:///c%3A/win/path'); - assert.equal(URI.file('c:/win/path/').toString(), 'file:///c%3A/win/path/'); - assert.equal(URI.file('/c:/win/path').toString(), 'file:///c%3A/win/path'); + assert.strictEqual(URI.file('c:/win/path').toString(), 'file:///c%3A/win/path'); + assert.strictEqual(URI.file('C:/win/path').toString(), 'file:///c%3A/win/path'); + assert.strictEqual(URI.file('c:/win/path/').toString(), 'file:///c%3A/win/path/'); + assert.strictEqual(URI.file('/c:/win/path').toString(), 'file:///c%3A/win/path'); }); test('URI.file (win-special)', () => { if (isWindows) { - assert.equal(URI.file('c:\\win\\path').toString(), 'file:///c%3A/win/path'); - assert.equal(URI.file('c:\\win/path').toString(), 'file:///c%3A/win/path'); + assert.strictEqual(URI.file('c:\\win\\path').toString(), 'file:///c%3A/win/path'); + assert.strictEqual(URI.file('c:\\win/path').toString(), 'file:///c%3A/win/path'); } else { - assert.equal(URI.file('c:\\win\\path').toString(), 'file:///c%3A%5Cwin%5Cpath'); - assert.equal(URI.file('c:\\win/path').toString(), 'file:///c%3A%5Cwin/path'); + assert.strictEqual(URI.file('c:\\win\\path').toString(), 'file:///c%3A%5Cwin%5Cpath'); + assert.strictEqual(URI.file('c:\\win/path').toString(), 'file:///c%3A%5Cwin/path'); } }); test('file#fsPath (win-special)', () => { if (isWindows) { - assert.equal(URI.file('c:\\win\\path').fsPath, 'c:\\win\\path'); - assert.equal(URI.file('c:\\win/path').fsPath, 'c:\\win\\path'); + assert.strictEqual(URI.file('c:\\win\\path').fsPath, 'c:\\win\\path'); + assert.strictEqual(URI.file('c:\\win/path').fsPath, 'c:\\win\\path'); - assert.equal(URI.file('c:/win/path').fsPath, 'c:\\win\\path'); - assert.equal(URI.file('c:/win/path/').fsPath, 'c:\\win\\path\\'); - assert.equal(URI.file('C:/win/path').fsPath, 'c:\\win\\path'); - assert.equal(URI.file('/c:/win/path').fsPath, 'c:\\win\\path'); - assert.equal(URI.file('./c/win/path').fsPath, '\\.\\c\\win\\path'); + assert.strictEqual(URI.file('c:/win/path').fsPath, 'c:\\win\\path'); + assert.strictEqual(URI.file('c:/win/path/').fsPath, 'c:\\win\\path\\'); + assert.strictEqual(URI.file('C:/win/path').fsPath, 'c:\\win\\path'); + assert.strictEqual(URI.file('/c:/win/path').fsPath, 'c:\\win\\path'); + assert.strictEqual(URI.file('./c/win/path').fsPath, '\\.\\c\\win\\path'); } else { - assert.equal(URI.file('c:/win/path').fsPath, 'c:/win/path'); - assert.equal(URI.file('c:/win/path/').fsPath, 'c:/win/path/'); - assert.equal(URI.file('C:/win/path').fsPath, 'c:/win/path'); - assert.equal(URI.file('/c:/win/path').fsPath, 'c:/win/path'); - assert.equal(URI.file('./c/win/path').fsPath, '/./c/win/path'); + assert.strictEqual(URI.file('c:/win/path').fsPath, 'c:/win/path'); + assert.strictEqual(URI.file('c:/win/path/').fsPath, 'c:/win/path/'); + assert.strictEqual(URI.file('C:/win/path').fsPath, 'c:/win/path'); + assert.strictEqual(URI.file('/c:/win/path').fsPath, 'c:/win/path'); + assert.strictEqual(URI.file('./c/win/path').fsPath, '/./c/win/path'); } }); test('URI#fsPath - no `fsPath` when no `path`', () => { const value = URI.parse('file://%2Fhome%2Fticino%2Fdesktop%2Fcpluscplus%2Ftest.cpp'); - assert.equal(value.authority, '/home/ticino/desktop/cpluscplus/test.cpp'); - assert.equal(value.path, '/'); + assert.strictEqual(value.authority, '/home/ticino/desktop/cpluscplus/test.cpp'); + assert.strictEqual(value.path, '/'); if (isWindows) { - assert.equal(value.fsPath, '\\'); + assert.strictEqual(value.fsPath, '\\'); } else { - assert.equal(value.fsPath, '/'); + assert.strictEqual(value.fsPath, '/'); } }); test('http#toString', () => { - assert.equal(URI.from({ scheme: 'http', authority: 'www.msft.com', path: '/my/path' }).toString(), 'http://www.msft.com/my/path'); - assert.equal(URI.from({ scheme: 'http', authority: 'www.msft.com', path: '/my/path' }).toString(), 'http://www.msft.com/my/path'); - assert.equal(URI.from({ scheme: 'http', authority: 'www.MSFT.com', path: '/my/path' }).toString(), 'http://www.msft.com/my/path'); - assert.equal(URI.from({ scheme: 'http', authority: '', path: 'my/path' }).toString(), 'http:/my/path'); - assert.equal(URI.from({ scheme: 'http', authority: '', path: '/my/path' }).toString(), 'http:/my/path'); + assert.strictEqual(URI.from({ scheme: 'http', authority: 'www.msft.com', path: '/my/path' }).toString(), 'http://www.msft.com/my/path'); + assert.strictEqual(URI.from({ scheme: 'http', authority: 'www.msft.com', path: '/my/path' }).toString(), 'http://www.msft.com/my/path'); + assert.strictEqual(URI.from({ scheme: 'http', authority: 'www.MSFT.com', path: '/my/path' }).toString(), 'http://www.msft.com/my/path'); + assert.strictEqual(URI.from({ scheme: 'http', authority: '', path: 'my/path' }).toString(), 'http:/my/path'); + assert.strictEqual(URI.from({ scheme: 'http', authority: '', path: '/my/path' }).toString(), 'http:/my/path'); //http://a-test-site.com/#test=true - assert.equal(URI.from({ scheme: 'http', authority: 'a-test-site.com', path: '/', query: 'test=true' }).toString(), 'http://a-test-site.com/?test%3Dtrue'); - assert.equal(URI.from({ scheme: 'http', authority: 'a-test-site.com', path: '/', query: '', fragment: 'test=true' }).toString(), 'http://a-test-site.com/#test%3Dtrue'); + assert.strictEqual(URI.from({ scheme: 'http', authority: 'a-test-site.com', path: '/', query: 'test=true' }).toString(), 'http://a-test-site.com/?test%3Dtrue'); + assert.strictEqual(URI.from({ scheme: 'http', authority: 'a-test-site.com', path: '/', query: '', fragment: 'test=true' }).toString(), 'http://a-test-site.com/#test%3Dtrue'); }); test('http#toString, encode=FALSE', () => { - assert.equal(URI.from({ scheme: 'http', authority: 'a-test-site.com', path: '/', query: 'test=true' }).toString(true), 'http://a-test-site.com/?test=true'); - assert.equal(URI.from({ scheme: 'http', authority: 'a-test-site.com', path: '/', query: '', fragment: 'test=true' }).toString(true), 'http://a-test-site.com/#test=true'); - assert.equal(URI.from({ scheme: 'http', path: '/api/files/test.me', query: 't=1234' }).toString(true), 'http:/api/files/test.me?t=1234'); + assert.strictEqual(URI.from({ scheme: 'http', authority: 'a-test-site.com', path: '/', query: 'test=true' }).toString(true), 'http://a-test-site.com/?test=true'); + assert.strictEqual(URI.from({ scheme: 'http', authority: 'a-test-site.com', path: '/', query: '', fragment: 'test=true' }).toString(true), 'http://a-test-site.com/#test=true'); + assert.strictEqual(URI.from({ scheme: 'http', path: '/api/files/test.me', query: 't=1234' }).toString(true), 'http:/api/files/test.me?t=1234'); const value = URI.parse('file://shares/pröjects/c%23/#l12'); - assert.equal(value.authority, 'shares'); - assert.equal(value.path, '/pröjects/c#/'); - assert.equal(value.fragment, 'l12'); - assert.equal(value.toString(), 'file://shares/pr%C3%B6jects/c%23/#l12'); - assert.equal(value.toString(true), 'file://shares/pröjects/c%23/#l12'); + assert.strictEqual(value.authority, 'shares'); + assert.strictEqual(value.path, '/pröjects/c#/'); + assert.strictEqual(value.fragment, 'l12'); + assert.strictEqual(value.toString(), 'file://shares/pr%C3%B6jects/c%23/#l12'); + assert.strictEqual(value.toString(true), 'file://shares/pröjects/c%23/#l12'); const uri2 = URI.parse(value.toString(true)); const uri3 = URI.parse(value.toString()); - assert.equal(uri2.authority, uri3.authority); - assert.equal(uri2.path, uri3.path); - assert.equal(uri2.query, uri3.query); - assert.equal(uri2.fragment, uri3.fragment); + assert.strictEqual(uri2.authority, uri3.authority); + assert.strictEqual(uri2.path, uri3.path); + assert.strictEqual(uri2.query, uri3.query); + assert.strictEqual(uri2.fragment, uri3.fragment); }); test('with, identity', () => { @@ -101,23 +101,23 @@ suite('URI', () => { }); test('with, changes', () => { - assert.equal(URI.parse('before:some/file/path').with({ scheme: 'after' }).toString(), 'after:some/file/path'); - assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'http', path: '/api/files/test.me', query: 't=1234' }).toString(), 'http:/api/files/test.me?t%3D1234'); - assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'http', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'http:/api/files/test.me?t%3D1234'); - assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'https', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'https:/api/files/test.me?t%3D1234'); - assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'HTTP', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'HTTP:/api/files/test.me?t%3D1234'); - assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'HTTPS', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'HTTPS:/api/files/test.me?t%3D1234'); - assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'boo', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'boo:/api/files/test.me?t%3D1234'); + assert.strictEqual(URI.parse('before:some/file/path').with({ scheme: 'after' }).toString(), 'after:some/file/path'); + assert.strictEqual(URI.from({ scheme: 's' }).with({ scheme: 'http', path: '/api/files/test.me', query: 't=1234' }).toString(), 'http:/api/files/test.me?t%3D1234'); + assert.strictEqual(URI.from({ scheme: 's' }).with({ scheme: 'http', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'http:/api/files/test.me?t%3D1234'); + assert.strictEqual(URI.from({ scheme: 's' }).with({ scheme: 'https', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'https:/api/files/test.me?t%3D1234'); + assert.strictEqual(URI.from({ scheme: 's' }).with({ scheme: 'HTTP', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'HTTP:/api/files/test.me?t%3D1234'); + assert.strictEqual(URI.from({ scheme: 's' }).with({ scheme: 'HTTPS', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'HTTPS:/api/files/test.me?t%3D1234'); + assert.strictEqual(URI.from({ scheme: 's' }).with({ scheme: 'boo', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'boo:/api/files/test.me?t%3D1234'); }); test('with, remove components #8465', () => { - assert.equal(URI.parse('scheme://authority/path').with({ authority: '' }).toString(), 'scheme:/path'); - assert.equal(URI.parse('scheme:/path').with({ authority: 'authority' }).with({ authority: '' }).toString(), 'scheme:/path'); - assert.equal(URI.parse('scheme:/path').with({ authority: 'authority' }).with({ authority: null }).toString(), 'scheme:/path'); - assert.equal(URI.parse('scheme:/path').with({ authority: 'authority' }).with({ path: '' }).toString(), 'scheme://authority'); - assert.equal(URI.parse('scheme:/path').with({ authority: 'authority' }).with({ path: null }).toString(), 'scheme://authority'); - assert.equal(URI.parse('scheme:/path').with({ authority: '' }).toString(), 'scheme:/path'); - assert.equal(URI.parse('scheme:/path').with({ authority: null }).toString(), 'scheme:/path'); + assert.strictEqual(URI.parse('scheme://authority/path').with({ authority: '' }).toString(), 'scheme:/path'); + assert.strictEqual(URI.parse('scheme:/path').with({ authority: 'authority' }).with({ authority: '' }).toString(), 'scheme:/path'); + assert.strictEqual(URI.parse('scheme:/path').with({ authority: 'authority' }).with({ authority: null }).toString(), 'scheme:/path'); + assert.strictEqual(URI.parse('scheme:/path').with({ authority: 'authority' }).with({ path: '' }).toString(), 'scheme://authority'); + assert.strictEqual(URI.parse('scheme:/path').with({ authority: 'authority' }).with({ path: null }).toString(), 'scheme://authority'); + assert.strictEqual(URI.parse('scheme:/path').with({ authority: '' }).toString(), 'scheme:/path'); + assert.strictEqual(URI.parse('scheme:/path').with({ authority: null }).toString(), 'scheme:/path'); }); test('with, validation', () => { @@ -130,104 +130,104 @@ suite('URI', () => { test('parse', () => { let value = URI.parse('http:/api/files/test.me?t=1234'); - assert.equal(value.scheme, 'http'); - assert.equal(value.authority, ''); - assert.equal(value.path, '/api/files/test.me'); - assert.equal(value.query, 't=1234'); - assert.equal(value.fragment, ''); + assert.strictEqual(value.scheme, 'http'); + assert.strictEqual(value.authority, ''); + assert.strictEqual(value.path, '/api/files/test.me'); + assert.strictEqual(value.query, 't=1234'); + assert.strictEqual(value.fragment, ''); value = URI.parse('http://api/files/test.me?t=1234'); - assert.equal(value.scheme, 'http'); - assert.equal(value.authority, 'api'); - assert.equal(value.path, '/files/test.me'); - assert.equal(value.query, 't=1234'); - assert.equal(value.fragment, ''); + assert.strictEqual(value.scheme, 'http'); + assert.strictEqual(value.authority, 'api'); + assert.strictEqual(value.path, '/files/test.me'); + assert.strictEqual(value.query, 't=1234'); + assert.strictEqual(value.fragment, ''); value = URI.parse('file:///c:/test/me'); - assert.equal(value.scheme, 'file'); - assert.equal(value.authority, ''); - assert.equal(value.path, '/c:/test/me'); - assert.equal(value.fragment, ''); - assert.equal(value.query, ''); - assert.equal(value.fsPath, isWindows ? 'c:\\test\\me' : 'c:/test/me'); + assert.strictEqual(value.scheme, 'file'); + assert.strictEqual(value.authority, ''); + assert.strictEqual(value.path, '/c:/test/me'); + assert.strictEqual(value.fragment, ''); + assert.strictEqual(value.query, ''); + assert.strictEqual(value.fsPath, isWindows ? 'c:\\test\\me' : 'c:/test/me'); value = URI.parse('file://shares/files/c%23/p.cs'); - assert.equal(value.scheme, 'file'); - assert.equal(value.authority, 'shares'); - assert.equal(value.path, '/files/c#/p.cs'); - assert.equal(value.fragment, ''); - assert.equal(value.query, ''); - assert.equal(value.fsPath, isWindows ? '\\\\shares\\files\\c#\\p.cs' : '//shares/files/c#/p.cs'); + assert.strictEqual(value.scheme, 'file'); + assert.strictEqual(value.authority, 'shares'); + assert.strictEqual(value.path, '/files/c#/p.cs'); + assert.strictEqual(value.fragment, ''); + assert.strictEqual(value.query, ''); + assert.strictEqual(value.fsPath, isWindows ? '\\\\shares\\files\\c#\\p.cs' : '//shares/files/c#/p.cs'); value = URI.parse('file:///c:/Source/Z%C3%BCrich%20or%20Zurich%20(%CB%88zj%CA%8A%C9%99r%C9%AAk,/Code/resources/app/plugins/c%23/plugin.json'); - assert.equal(value.scheme, 'file'); - assert.equal(value.authority, ''); - assert.equal(value.path, '/c:/Source/Zürich or Zurich (ˈzjʊərɪk,/Code/resources/app/plugins/c#/plugin.json'); - assert.equal(value.fragment, ''); - assert.equal(value.query, ''); + assert.strictEqual(value.scheme, 'file'); + assert.strictEqual(value.authority, ''); + assert.strictEqual(value.path, '/c:/Source/Zürich or Zurich (ˈzjʊərɪk,/Code/resources/app/plugins/c#/plugin.json'); + assert.strictEqual(value.fragment, ''); + assert.strictEqual(value.query, ''); value = URI.parse('file:///c:/test %25/path'); - assert.equal(value.scheme, 'file'); - assert.equal(value.authority, ''); - assert.equal(value.path, '/c:/test %/path'); - assert.equal(value.fragment, ''); - assert.equal(value.query, ''); + assert.strictEqual(value.scheme, 'file'); + assert.strictEqual(value.authority, ''); + assert.strictEqual(value.path, '/c:/test %/path'); + assert.strictEqual(value.fragment, ''); + assert.strictEqual(value.query, ''); value = URI.parse('inmemory:'); - assert.equal(value.scheme, 'inmemory'); - assert.equal(value.authority, ''); - assert.equal(value.path, ''); - assert.equal(value.query, ''); - assert.equal(value.fragment, ''); + assert.strictEqual(value.scheme, 'inmemory'); + assert.strictEqual(value.authority, ''); + assert.strictEqual(value.path, ''); + assert.strictEqual(value.query, ''); + assert.strictEqual(value.fragment, ''); value = URI.parse('foo:api/files/test'); - assert.equal(value.scheme, 'foo'); - assert.equal(value.authority, ''); - assert.equal(value.path, 'api/files/test'); - assert.equal(value.query, ''); - assert.equal(value.fragment, ''); + assert.strictEqual(value.scheme, 'foo'); + assert.strictEqual(value.authority, ''); + assert.strictEqual(value.path, 'api/files/test'); + assert.strictEqual(value.query, ''); + assert.strictEqual(value.fragment, ''); value = URI.parse('file:?q'); - assert.equal(value.scheme, 'file'); - assert.equal(value.authority, ''); - assert.equal(value.path, '/'); - assert.equal(value.query, 'q'); - assert.equal(value.fragment, ''); + assert.strictEqual(value.scheme, 'file'); + assert.strictEqual(value.authority, ''); + assert.strictEqual(value.path, '/'); + assert.strictEqual(value.query, 'q'); + assert.strictEqual(value.fragment, ''); value = URI.parse('file:#d'); - assert.equal(value.scheme, 'file'); - assert.equal(value.authority, ''); - assert.equal(value.path, '/'); - assert.equal(value.query, ''); - assert.equal(value.fragment, 'd'); + assert.strictEqual(value.scheme, 'file'); + assert.strictEqual(value.authority, ''); + assert.strictEqual(value.path, '/'); + assert.strictEqual(value.query, ''); + assert.strictEqual(value.fragment, 'd'); value = URI.parse('f3ile:#d'); - assert.equal(value.scheme, 'f3ile'); - assert.equal(value.authority, ''); - assert.equal(value.path, ''); - assert.equal(value.query, ''); - assert.equal(value.fragment, 'd'); + assert.strictEqual(value.scheme, 'f3ile'); + assert.strictEqual(value.authority, ''); + assert.strictEqual(value.path, ''); + assert.strictEqual(value.query, ''); + assert.strictEqual(value.fragment, 'd'); value = URI.parse('foo+bar:path'); - assert.equal(value.scheme, 'foo+bar'); - assert.equal(value.authority, ''); - assert.equal(value.path, 'path'); - assert.equal(value.query, ''); - assert.equal(value.fragment, ''); + assert.strictEqual(value.scheme, 'foo+bar'); + assert.strictEqual(value.authority, ''); + assert.strictEqual(value.path, 'path'); + assert.strictEqual(value.query, ''); + assert.strictEqual(value.fragment, ''); value = URI.parse('foo-bar:path'); - assert.equal(value.scheme, 'foo-bar'); - assert.equal(value.authority, ''); - assert.equal(value.path, 'path'); - assert.equal(value.query, ''); - assert.equal(value.fragment, ''); + assert.strictEqual(value.scheme, 'foo-bar'); + assert.strictEqual(value.authority, ''); + assert.strictEqual(value.path, 'path'); + assert.strictEqual(value.query, ''); + assert.strictEqual(value.fragment, ''); value = URI.parse('foo.bar:path'); - assert.equal(value.scheme, 'foo.bar'); - assert.equal(value.authority, ''); - assert.equal(value.path, 'path'); - assert.equal(value.query, ''); - assert.equal(value.fragment, ''); + assert.strictEqual(value.scheme, 'foo.bar'); + assert.strictEqual(value.authority, ''); + assert.strictEqual(value.path, 'path'); + assert.strictEqual(value.query, ''); + assert.strictEqual(value.fragment, ''); }); test('parse, disallow //path when no authority', () => { @@ -237,132 +237,132 @@ suite('URI', () => { test('URI#file, win-speciale', () => { if (isWindows) { let value = URI.file('c:\\test\\drive'); - assert.equal(value.path, '/c:/test/drive'); - assert.equal(value.toString(), 'file:///c%3A/test/drive'); + assert.strictEqual(value.path, '/c:/test/drive'); + assert.strictEqual(value.toString(), 'file:///c%3A/test/drive'); value = URI.file('\\\\shäres\\path\\c#\\plugin.json'); - assert.equal(value.scheme, 'file'); - assert.equal(value.authority, 'shäres'); - assert.equal(value.path, '/path/c#/plugin.json'); - assert.equal(value.fragment, ''); - assert.equal(value.query, ''); - assert.equal(value.toString(), 'file://sh%C3%A4res/path/c%23/plugin.json'); + assert.strictEqual(value.scheme, 'file'); + assert.strictEqual(value.authority, 'shäres'); + assert.strictEqual(value.path, '/path/c#/plugin.json'); + assert.strictEqual(value.fragment, ''); + assert.strictEqual(value.query, ''); + assert.strictEqual(value.toString(), 'file://sh%C3%A4res/path/c%23/plugin.json'); value = URI.file('\\\\localhost\\c$\\GitDevelopment\\express'); - assert.equal(value.scheme, 'file'); - assert.equal(value.path, '/c$/GitDevelopment/express'); - assert.equal(value.fsPath, '\\\\localhost\\c$\\GitDevelopment\\express'); - assert.equal(value.query, ''); - assert.equal(value.fragment, ''); - assert.equal(value.toString(), 'file://localhost/c%24/GitDevelopment/express'); + assert.strictEqual(value.scheme, 'file'); + assert.strictEqual(value.path, '/c$/GitDevelopment/express'); + assert.strictEqual(value.fsPath, '\\\\localhost\\c$\\GitDevelopment\\express'); + assert.strictEqual(value.query, ''); + assert.strictEqual(value.fragment, ''); + assert.strictEqual(value.toString(), 'file://localhost/c%24/GitDevelopment/express'); value = URI.file('c:\\test with %\\path'); - assert.equal(value.path, '/c:/test with %/path'); - assert.equal(value.toString(), 'file:///c%3A/test%20with%20%25/path'); + assert.strictEqual(value.path, '/c:/test with %/path'); + assert.strictEqual(value.toString(), 'file:///c%3A/test%20with%20%25/path'); value = URI.file('c:\\test with %25\\path'); - assert.equal(value.path, '/c:/test with %25/path'); - assert.equal(value.toString(), 'file:///c%3A/test%20with%20%2525/path'); + assert.strictEqual(value.path, '/c:/test with %25/path'); + assert.strictEqual(value.toString(), 'file:///c%3A/test%20with%20%2525/path'); value = URI.file('c:\\test with %25\\c#code'); - assert.equal(value.path, '/c:/test with %25/c#code'); - assert.equal(value.toString(), 'file:///c%3A/test%20with%20%2525/c%23code'); + assert.strictEqual(value.path, '/c:/test with %25/c#code'); + assert.strictEqual(value.toString(), 'file:///c%3A/test%20with%20%2525/c%23code'); value = URI.file('\\\\shares'); - assert.equal(value.scheme, 'file'); - assert.equal(value.authority, 'shares'); - assert.equal(value.path, '/'); // slash is always there + assert.strictEqual(value.scheme, 'file'); + assert.strictEqual(value.authority, 'shares'); + assert.strictEqual(value.path, '/'); // slash is always there value = URI.file('\\\\shares\\'); - assert.equal(value.scheme, 'file'); - assert.equal(value.authority, 'shares'); - assert.equal(value.path, '/'); + assert.strictEqual(value.scheme, 'file'); + assert.strictEqual(value.authority, 'shares'); + assert.strictEqual(value.path, '/'); } }); test('VSCode URI module\'s driveLetterPath regex is incorrect, #32961', function () { let uri = URI.parse('file:///_:/path'); - assert.equal(uri.fsPath, isWindows ? '\\_:\\path' : '/_:/path'); + assert.strictEqual(uri.fsPath, isWindows ? '\\_:\\path' : '/_:/path'); }); test('URI#file, no path-is-uri check', () => { // we don't complain here let value = URI.file('file://path/to/file'); - assert.equal(value.scheme, 'file'); - assert.equal(value.authority, ''); - assert.equal(value.path, '/file://path/to/file'); + assert.strictEqual(value.scheme, 'file'); + assert.strictEqual(value.authority, ''); + assert.strictEqual(value.path, '/file://path/to/file'); }); test('URI#file, always slash', () => { let value = URI.file('a.file'); - assert.equal(value.scheme, 'file'); - assert.equal(value.authority, ''); - assert.equal(value.path, '/a.file'); - assert.equal(value.toString(), 'file:///a.file'); + assert.strictEqual(value.scheme, 'file'); + assert.strictEqual(value.authority, ''); + assert.strictEqual(value.path, '/a.file'); + assert.strictEqual(value.toString(), 'file:///a.file'); value = URI.parse(value.toString()); - assert.equal(value.scheme, 'file'); - assert.equal(value.authority, ''); - assert.equal(value.path, '/a.file'); - assert.equal(value.toString(), 'file:///a.file'); + assert.strictEqual(value.scheme, 'file'); + assert.strictEqual(value.authority, ''); + assert.strictEqual(value.path, '/a.file'); + assert.strictEqual(value.toString(), 'file:///a.file'); }); test('URI.toString, only scheme and query', () => { const value = URI.parse('stuff:?qüery'); - assert.equal(value.toString(), 'stuff:?q%C3%BCery'); + assert.strictEqual(value.toString(), 'stuff:?q%C3%BCery'); }); test('URI#toString, upper-case percent espaces', () => { const value = URI.parse('file://sh%c3%a4res/path'); - assert.equal(value.toString(), 'file://sh%C3%A4res/path'); + assert.strictEqual(value.toString(), 'file://sh%C3%A4res/path'); }); test('URI#toString, lower-case windows drive letter', () => { - assert.equal(URI.parse('untitled:c:/Users/jrieken/Code/abc.txt').toString(), 'untitled:c%3A/Users/jrieken/Code/abc.txt'); - assert.equal(URI.parse('untitled:C:/Users/jrieken/Code/abc.txt').toString(), 'untitled:c%3A/Users/jrieken/Code/abc.txt'); + assert.strictEqual(URI.parse('untitled:c:/Users/jrieken/Code/abc.txt').toString(), 'untitled:c%3A/Users/jrieken/Code/abc.txt'); + assert.strictEqual(URI.parse('untitled:C:/Users/jrieken/Code/abc.txt').toString(), 'untitled:c%3A/Users/jrieken/Code/abc.txt'); }); test('URI#toString, escape all the bits', () => { const value = URI.file('/Users/jrieken/Code/_samples/18500/Mödel + Other Thîngß/model.js'); - assert.equal(value.toString(), 'file:///Users/jrieken/Code/_samples/18500/M%C3%B6del%20%2B%20Other%20Th%C3%AEng%C3%9F/model.js'); + assert.strictEqual(value.toString(), 'file:///Users/jrieken/Code/_samples/18500/M%C3%B6del%20%2B%20Other%20Th%C3%AEng%C3%9F/model.js'); }); test('URI#toString, don\'t encode port', () => { let value = URI.parse('http://localhost:8080/far'); - assert.equal(value.toString(), 'http://localhost:8080/far'); + assert.strictEqual(value.toString(), 'http://localhost:8080/far'); value = URI.from({ scheme: 'http', authority: 'löcalhost:8080', path: '/far', query: undefined, fragment: undefined }); - assert.equal(value.toString(), 'http://l%C3%B6calhost:8080/far'); + assert.strictEqual(value.toString(), 'http://l%C3%B6calhost:8080/far'); }); test('URI#toString, user information in authority', () => { let value = URI.parse('http://foo:bar@localhost/far'); - assert.equal(value.toString(), 'http://foo:bar@localhost/far'); + assert.strictEqual(value.toString(), 'http://foo:bar@localhost/far'); value = URI.parse('http://foo@localhost/far'); - assert.equal(value.toString(), 'http://foo@localhost/far'); + assert.strictEqual(value.toString(), 'http://foo@localhost/far'); value = URI.parse('http://foo:bAr@localhost:8080/far'); - assert.equal(value.toString(), 'http://foo:bAr@localhost:8080/far'); + assert.strictEqual(value.toString(), 'http://foo:bAr@localhost:8080/far'); value = URI.parse('http://foo@localhost:8080/far'); - assert.equal(value.toString(), 'http://foo@localhost:8080/far'); + assert.strictEqual(value.toString(), 'http://foo@localhost:8080/far'); value = URI.from({ scheme: 'http', authority: 'föö:bör@löcalhost:8080', path: '/far', query: undefined, fragment: undefined }); - assert.equal(value.toString(), 'http://f%C3%B6%C3%B6:b%C3%B6r@l%C3%B6calhost:8080/far'); + assert.strictEqual(value.toString(), 'http://f%C3%B6%C3%B6:b%C3%B6r@l%C3%B6calhost:8080/far'); }); test('correctFileUriToFilePath2', () => { const test = (input: string, expected: string) => { const value = URI.parse(input); - assert.equal(value.fsPath, expected, 'Result for ' + input); + assert.strictEqual(value.fsPath, expected, 'Result for ' + input); const value2 = URI.file(value.fsPath); - assert.equal(value2.fsPath, expected, 'Result for ' + input); - assert.equal(value.toString(), value2.toString()); + assert.strictEqual(value2.fsPath, expected, 'Result for ' + input); + assert.strictEqual(value.toString(), value2.toString()); }; test('file:///c:/alex.txt', isWindows ? 'c:\\alex.txt' : 'c:/alex.txt'); @@ -374,104 +374,132 @@ suite('URI', () => { test('URI - http, query & toString', function () { let uri = URI.parse('https://go.microsoft.com/fwlink/?LinkId=518008'); - assert.equal(uri.query, 'LinkId=518008'); - assert.equal(uri.toString(true), 'https://go.microsoft.com/fwlink/?LinkId=518008'); - assert.equal(uri.toString(), 'https://go.microsoft.com/fwlink/?LinkId%3D518008'); + assert.strictEqual(uri.query, 'LinkId=518008'); + assert.strictEqual(uri.toString(true), 'https://go.microsoft.com/fwlink/?LinkId=518008'); + assert.strictEqual(uri.toString(), 'https://go.microsoft.com/fwlink/?LinkId%3D518008'); let uri2 = URI.parse(uri.toString()); - assert.equal(uri2.query, 'LinkId=518008'); - assert.equal(uri2.query, uri.query); + assert.strictEqual(uri2.query, 'LinkId=518008'); + assert.strictEqual(uri2.query, uri.query); uri = URI.parse('https://go.microsoft.com/fwlink/?LinkId=518008&foö&ké¥=üü'); - assert.equal(uri.query, 'LinkId=518008&foö&ké¥=üü'); - assert.equal(uri.toString(true), 'https://go.microsoft.com/fwlink/?LinkId=518008&foö&ké¥=üü'); - assert.equal(uri.toString(), 'https://go.microsoft.com/fwlink/?LinkId%3D518008%26fo%C3%B6%26k%C3%A9%C2%A5%3D%C3%BC%C3%BC'); + assert.strictEqual(uri.query, 'LinkId=518008&foö&ké¥=üü'); + assert.strictEqual(uri.toString(true), 'https://go.microsoft.com/fwlink/?LinkId=518008&foö&ké¥=üü'); + assert.strictEqual(uri.toString(), 'https://go.microsoft.com/fwlink/?LinkId%3D518008%26fo%C3%B6%26k%C3%A9%C2%A5%3D%C3%BC%C3%BC'); uri2 = URI.parse(uri.toString()); - assert.equal(uri2.query, 'LinkId=518008&foö&ké¥=üü'); - assert.equal(uri2.query, uri.query); + assert.strictEqual(uri2.query, 'LinkId=518008&foö&ké¥=üü'); + assert.strictEqual(uri2.query, uri.query); // #24849 uri = URI.parse('https://twitter.com/search?src=typd&q=%23tag'); - assert.equal(uri.toString(true), 'https://twitter.com/search?src=typd&q=%23tag'); + assert.strictEqual(uri.toString(true), 'https://twitter.com/search?src=typd&q=%23tag'); }); test('class URI cannot represent relative file paths #34449', function () { let path = '/foo/bar'; - assert.equal(URI.file(path).path, path); + assert.strictEqual(URI.file(path).path, path); path = 'foo/bar'; - assert.equal(URI.file(path).path, '/foo/bar'); + assert.strictEqual(URI.file(path).path, '/foo/bar'); path = './foo/bar'; - assert.equal(URI.file(path).path, '/./foo/bar'); // missing normalization + assert.strictEqual(URI.file(path).path, '/./foo/bar'); // missing normalization const fileUri1 = URI.parse(`file:foo/bar`); - assert.equal(fileUri1.path, '/foo/bar'); - assert.equal(fileUri1.authority, ''); + assert.strictEqual(fileUri1.path, '/foo/bar'); + assert.strictEqual(fileUri1.authority, ''); const uri = fileUri1.toString(); - assert.equal(uri, 'file:///foo/bar'); + assert.strictEqual(uri, 'file:///foo/bar'); const fileUri2 = URI.parse(uri); - assert.equal(fileUri2.path, '/foo/bar'); - assert.equal(fileUri2.authority, ''); + assert.strictEqual(fileUri2.path, '/foo/bar'); + assert.strictEqual(fileUri2.authority, ''); }); test('Ctrl click to follow hash query param url gets urlencoded #49628', function () { let input = 'http://localhost:3000/#/foo?bar=baz'; let uri = URI.parse(input); - assert.equal(uri.toString(true), input); + assert.strictEqual(uri.toString(true), input); input = 'http://localhost:3000/foo?bar=baz'; uri = URI.parse(input); - assert.equal(uri.toString(true), input); + assert.strictEqual(uri.toString(true), input); }); test('Unable to open \'%A0.txt\': URI malformed #76506', function () { let uri = URI.file('/foo/%A0.txt'); let uri2 = URI.parse(uri.toString()); - assert.equal(uri.scheme, uri2.scheme); - assert.equal(uri.path, uri2.path); + assert.strictEqual(uri.scheme, uri2.scheme); + assert.strictEqual(uri.path, uri2.path); uri = URI.file('/foo/%2e.txt'); uri2 = URI.parse(uri.toString()); - assert.equal(uri.scheme, uri2.scheme); - assert.equal(uri.path, uri2.path); + assert.strictEqual(uri.scheme, uri2.scheme); + assert.strictEqual(uri.path, uri2.path); + }); + + test('Bug in URI.isUri() that fails `thing` type comparison #114971', function () { + const uri = URI.file('/foo/bazz.txt'); + assert.strictEqual(URI.isUri(uri), true); + assert.strictEqual(URI.isUri(uri.toJSON()), false); + + // fsPath -> getter + assert.strictEqual(URI.isUri({ + scheme: 'file', + authority: '', + path: '/foo/bazz.txt', + get fsPath() { return '/foo/bazz.txt'; }, + query: '', + fragment: '', + with() { return this; }, + toString() { return ''; } + }), true); + + // fsPath -> property + assert.strictEqual(URI.isUri({ + scheme: 'file', + authority: '', + path: '/foo/bazz.txt', + fsPath: '/foo/bazz.txt', + query: '', + fragment: '', + with() { return this; }, + toString() { return ''; } + }), true); }); test('Unable to open \'%A0.txt\': URI malformed #76506', function () { - assert.equal(URI.parse('file://some/%.txt'), 'file://some/%25.txt'); - assert.equal(URI.parse('file://some/%A0.txt'), 'file://some/%25A0.txt'); + assert.strictEqual(URI.parse('file://some/%.txt').toString(), 'file://some/%25.txt'); + assert.strictEqual(URI.parse('file://some/%A0.txt').toString(), 'file://some/%25A0.txt'); }); - test('Links in markdown are broken if url contains encoded parameters #79474', function () { - this.skip(); + test.skip('Links in markdown are broken if url contains encoded parameters #79474', function () { let strIn = 'https://myhost.com/Redirect?url=http%3A%2F%2Fwww.bing.com%3Fsearch%3Dtom'; let uri1 = URI.parse(strIn); let strOut = uri1.toString(); let uri2 = URI.parse(strOut); - assert.equal(uri1.scheme, uri2.scheme); - assert.equal(uri1.authority, uri2.authority); - assert.equal(uri1.path, uri2.path); - assert.equal(uri1.query, uri2.query); - assert.equal(uri1.fragment, uri2.fragment); - assert.equal(strIn, strOut); // fails here!! + assert.strictEqual(uri1.scheme, uri2.scheme); + assert.strictEqual(uri1.authority, uri2.authority); + assert.strictEqual(uri1.path, uri2.path); + assert.strictEqual(uri1.query, uri2.query); + assert.strictEqual(uri1.fragment, uri2.fragment); + assert.strictEqual(strIn, strOut); // fails here!! }); - test('Uri#parse can break path-component #45515', function () { - this.skip(); + test.skip('Uri#parse can break path-component #45515', function () { let strIn = 'https://firebasestorage.googleapis.com/v0/b/brewlangerie.appspot.com/o/products%2FzVNZkudXJyq8bPGTXUxx%2FBetterave-Sesame.jpg?alt=media&token=0b2310c4-3ea6-4207-bbde-9c3710ba0437'; let uri1 = URI.parse(strIn); let strOut = uri1.toString(); let uri2 = URI.parse(strOut); - assert.equal(uri1.scheme, uri2.scheme); - assert.equal(uri1.authority, uri2.authority); - assert.equal(uri1.path, uri2.path); - assert.equal(uri1.query, uri2.query); - assert.equal(uri1.fragment, uri2.fragment); - assert.equal(strIn, strOut); // fails here!! + assert.strictEqual(uri1.scheme, uri2.scheme); + assert.strictEqual(uri1.authority, uri2.authority); + assert.strictEqual(uri1.path, uri2.path); + assert.strictEqual(uri1.query, uri2.query); + assert.strictEqual(uri1.fragment, uri2.fragment); + assert.strictEqual(strIn, strOut); // fails here!! }); test('URI - (de)serialize', function () { @@ -492,13 +520,13 @@ suite('URI', () => { let data = value.toJSON() as UriComponents; let clone = URI.revive(data); - assert.equal(clone.scheme, value.scheme); - assert.equal(clone.authority, value.authority); - assert.equal(clone.path, value.path); - assert.equal(clone.query, value.query); - assert.equal(clone.fragment, value.fragment); - assert.equal(clone.fsPath, value.fsPath); - assert.equal(clone.toString(), value.toString()); + assert.strictEqual(clone.scheme, value.scheme); + assert.strictEqual(clone.authority, value.authority); + assert.strictEqual(clone.path, value.path); + assert.strictEqual(clone.query, value.query); + assert.strictEqual(clone.fragment, value.fragment); + assert.strictEqual(clone.fsPath, value.fsPath); + assert.strictEqual(clone.toString(), value.toString()); } // } // console.profileEnd(); @@ -507,11 +535,11 @@ suite('URI', () => { const baseUri = URI.parse(base); const newUri = URI.joinPath(baseUri, fragment); const actual = newUri.toString(true); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); if (checkWithUrl) { const actualUrl = new URL(fragment, base).href; - assert.equal(actualUrl, expected, 'DIFFERENT from URL'); + assert.strictEqual(actualUrl, expected, 'DIFFERENT from URL'); } } test('URI#joinPath', function () { diff --git a/src/vs/base/test/common/utils.ts b/src/vs/base/test/common/utils.ts index a5ebd13e5..63b0541b4 100644 --- a/src/vs/base/test/common/utils.ts +++ b/src/vs/base/test/common/utils.ts @@ -5,47 +5,10 @@ import { join } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; -import { canceled } from 'vs/base/common/errors'; import { isWindows } from 'vs/base/common/platform'; export type ValueCallback = (value: T | Promise) => void; -export class DeferredPromise { - - private completeCallback!: ValueCallback; - private errorCallback!: (err: any) => void; - - public 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); - resolve(); - }); - } - - public error(err: any) { - return new Promise(resolve => { - this.errorCallback(err); - resolve(); - }); - } - - public cancel() { - new Promise(resolve => { - this.errorCallback(canceled()); - resolve(); - }); - } -} - export function toResource(this: any, path: string) { if (isWindows) { return URI.file(join('C:\\', btoa(this.test.fullTitle()), path)); @@ -60,7 +23,7 @@ export function suiteRepeat(n: number, description: string, callback: (this: any } } -export function testRepeat(n: number, description: string, callback: (this: any, done: MochaDone) => any): void { +export function testRepeat(n: number, description: string, callback: (this: any) => any): void { for (let i = 0; i < n; i++) { test(`${description} (iteration ${i})`, callback); } diff --git a/src/vs/base/test/common/uuid.test.ts b/src/vs/base/test/common/uuid.test.ts index ce07ab9cb..632366bc2 100644 --- a/src/vs/base/test/common/uuid.test.ts +++ b/src/vs/base/test/common/uuid.test.ts @@ -8,8 +8,8 @@ import * as uuid from 'vs/base/common/uuid'; suite('UUID', () => { test('generation', () => { const asHex = uuid.generateUuid(); - assert.equal(asHex.length, 36); - assert.equal(asHex[14], '4'); + assert.strictEqual(asHex.length, 36); + assert.strictEqual(asHex[14], '4'); assert.ok(asHex[19] === '8' || asHex[19] === '9' || asHex[19] === 'a' || asHex[19] === 'b'); }); diff --git a/src/vs/base/test/node/crypto.test.ts b/src/vs/base/test/node/crypto.test.ts index ad8dc4fa5..16cfc58fe 100644 --- a/src/vs/base/test/node/crypto.test.ts +++ b/src/vs/base/test/node/crypto.test.ts @@ -4,24 +4,29 @@ *--------------------------------------------------------------------------------------------*/ import { checksum } from 'vs/base/node/crypto'; -import { generateUuid } from 'vs/base/common/uuid'; import { join } from 'vs/base/common/path'; import { tmpdir } from 'os'; -import { mkdirp, rimraf, RimRafMode, writeFile } from 'vs/base/node/pfs'; +import { mkdirp, rimraf, writeFile } from 'vs/base/node/pfs'; +import { getRandomTestPath } from 'vs/base/test/node/testUtils'; suite('Crypto', () => { + let testDir: string; + + setup(function () { + testDir = getRandomTestPath(tmpdir(), 'vsctests', 'crypto'); + + return mkdirp(testDir); + }); + + teardown(function () { + return rimraf(testDir); + }); + test('checksum', async () => { - const id = generateUuid(); - const testDir = join(tmpdir(), 'vsctests', id); const testFile = join(testDir, 'checksum.txt'); - - await mkdirp(testDir); - await writeFile(testFile, 'Hello World'); await checksum(testFile, '0a4d55a8d778e5022fab701977c5d840bbc486d0'); - - await rimraf(testDir, RimRafMode.MOVE); }); }); diff --git a/src/vs/base/test/node/decoder.test.ts b/src/vs/base/test/node/decoder.test.ts index f4d34d02f..aa2e867c7 100644 --- a/src/vs/base/test/node/decoder.test.ts +++ b/src/vs/base/test/node/decoder.test.ts @@ -4,19 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as decoder from 'vs/base/node/decoder'; +import { LineDecoder } from 'vs/base/node/decoder'; suite('Decoder', () => { test('decoding', () => { - const lineDecoder = new decoder.LineDecoder(); + const lineDecoder = new LineDecoder(); let res = lineDecoder.write(Buffer.from('hello')); - assert.equal(res.length, 0); + assert.strictEqual(res.length, 0); res = lineDecoder.write(Buffer.from('\nworld')); - assert.equal(res[0], 'hello'); - assert.equal(res.length, 1); + assert.strictEqual(res[0], 'hello'); + assert.strictEqual(res.length, 1); - assert.equal(lineDecoder.end(), 'world'); + assert.strictEqual(lineDecoder.end(), 'world'); }); -}); \ No newline at end of file +}); diff --git a/src/vs/base/test/node/extpath.test.ts b/src/vs/base/test/node/extpath.test.ts index e435d6248..056110be8 100644 --- a/src/vs/base/test/node/extpath.test.ts +++ b/src/vs/base/test/node/extpath.test.ts @@ -4,70 +4,52 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as os from 'os'; -import * as path from 'vs/base/common/path'; -import * as uuid from 'vs/base/common/uuid'; -import * as pfs from 'vs/base/node/pfs'; +import { tmpdir } from 'os'; +import { mkdirp, rimraf } from 'vs/base/node/pfs'; import { realcaseSync, realpath, realpathSync } from 'vs/base/node/extpath'; +import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; -suite('Extpath', () => { +flakySuite('Extpath', () => { + let testDir: string; + + setup(() => { + testDir = getRandomTestPath(tmpdir(), 'vsctests', 'extpath'); + + return mkdirp(testDir, 493); + }); + + teardown(() => { + return rimraf(testDir); + }); test('realcase', async () => { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'extpath', id); - - await pfs.mkdirp(newDir, 493); // assume case insensitive file system if (process.platform === 'win32' || process.platform === 'darwin') { - const upper = newDir.toUpperCase(); + const upper = testDir.toUpperCase(); const real = realcaseSync(upper); if (real) { // can be null in case of permission errors - assert.notEqual(real, upper); - assert.equal(real.toUpperCase(), upper); - assert.equal(real, newDir); + assert.notStrictEqual(real, upper); + assert.strictEqual(real.toUpperCase(), upper); + assert.strictEqual(real, testDir); } } // linux, unix, etc. -> assume case sensitive file system else { - const real = realcaseSync(newDir); - assert.equal(real, newDir); + const real = realcaseSync(testDir); + assert.strictEqual(real, testDir); } - - await pfs.rimraf(parentDir, pfs.RimRafMode.MOVE); }); test('realpath', async () => { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'extpath', id); - - await pfs.mkdirp(newDir, 493); - - const realpathVal = await realpath(newDir); + const realpathVal = await realpath(testDir); assert.ok(realpathVal); - - await pfs.rimraf(parentDir, pfs.RimRafMode.MOVE); }); - test('realpathSync', async () => { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'extpath', id); - - await pfs.mkdirp(newDir, 493); - - let realpath!: string; - try { - realpath = realpathSync(newDir); - } catch (error) { - assert.ok(!error); - } - assert.ok(realpath!); - - await pfs.rimraf(parentDir, pfs.RimRafMode.MOVE); + test('realpathSync', () => { + const realpath = realpathSync(testDir); + assert.ok(realpath); }); }); diff --git a/src/vs/base/test/node/id.test.ts b/src/vs/base/test/node/id.test.ts index 637afa5b5..4d1241632 100644 --- a/src/vs/base/test/node/id.test.ts +++ b/src/vs/base/test/node/id.test.ts @@ -2,22 +2,21 @@ * 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 { getMachineId } from 'vs/base/node/id'; import { getMac } from 'vs/base/node/macAddress'; +import { flakySuite } from 'vs/base/test/node/testUtils'; -suite('ID', () => { +flakySuite('ID', () => { - test('getMachineId', function () { - this.timeout(20000); - return getMachineId().then(id => { - assert.ok(id); - }); + test('getMachineId', async function () { + const id = await getMachineId(); + assert.ok(id); }); - test('getMac', () => { - return getMac().then(macAddress => { - assert.ok(/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/.test(macAddress), `Expected a MAC address, got: ${macAddress}`); - }); + test('getMac', async () => { + const macAddress = await getMac(); + assert.ok(/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/.test(macAddress), `Expected a MAC address, got: ${macAddress}`); }); -}); \ No newline at end of file +}); diff --git a/src/vs/base/test/node/keytar.test.ts b/src/vs/base/test/node/keytar.test.ts index b2f7bab50..4e187264b 100644 --- a/src/vs/base/test/node/keytar.test.ts +++ b/src/vs/base/test/node/keytar.test.ts @@ -2,36 +2,30 @@ * 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 platform from 'vs/base/common/platform'; +import { isLinux } from 'vs/base/common/platform'; suite('Keytar', () => { - test('loads and is functional', function (done) { - if (platform.isLinux) { - // Skip test due to set up issue with Travis. - this.skip(); - return; - } - (async () => { - const keytar = await import('keytar'); - const name = `VSCode Test ${Math.floor(Math.random() * 1e9)}`; + (isLinux ? test.skip : test)('loads and is functional', async () => { // TODO@RMacfarlane test seems to fail on Linux (Error: Unknown or unsupported transport 'disabled' for address 'disabled:') + const keytar = await import('keytar'); + const name = `VSCode Test ${Math.floor(Math.random() * 1e9)}`; + try { + await keytar.setPassword(name, 'foo', 'bar'); + assert.strictEqual(await keytar.findPassword(name), 'bar'); + assert.strictEqual((await keytar.findCredentials(name)).length, 1); + assert.strictEqual(await keytar.getPassword(name, 'foo'), 'bar'); + await keytar.deletePassword(name, 'foo'); + assert.strictEqual(await keytar.getPassword(name, 'foo'), null); + } catch (err) { + // try to clean up try { - await keytar.setPassword(name, 'foo', 'bar'); - assert.equal(await keytar.findPassword(name), 'bar'); - assert.equal((await keytar.findCredentials(name)).length, 1); - assert.equal(await keytar.getPassword(name, 'foo'), 'bar'); await keytar.deletePassword(name, 'foo'); - assert.equal(await keytar.getPassword(name, 'foo'), undefined); - } catch (err) { - // try to clean up - try { - await keytar.deletePassword(name, 'foo'); - } finally { - // eslint-disable-next-line no-unsafe-finally - throw err; - } + } finally { + // eslint-disable-next-line no-unsafe-finally + throw err; } - })().then(done, done); + } }); }); diff --git a/src/vs/base/test/node/pfs/pfs.test.ts b/src/vs/base/test/node/pfs/pfs.test.ts index fd324076b..eeb380a62 100644 --- a/src/vs/base/test/node/pfs/pfs.test.ts +++ b/src/vs/base/test/node/pfs/pfs.test.ts @@ -4,388 +4,306 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as os from 'os'; -import * as path from 'vs/base/common/path'; import * as fs from 'fs'; -import * as uuid from 'vs/base/common/uuid'; -import * as pfs from 'vs/base/node/pfs'; +import { tmpdir } from 'os'; +import { join, sep } from 'vs/base/common/path'; +import { generateUuid } from 'vs/base/common/uuid'; +import { copy, exists, mkdirp, move, readdir, readDirsInDir, readdirWithFileTypes, readFile, renameIgnoreError, rimraf, RimRafMode, rimrafSync, statLink, writeFile, writeFileSync } from 'vs/base/node/pfs'; import { timeout } from 'vs/base/common/async'; import { getPathFromAmdModule } from 'vs/base/common/amd'; -import { isWindows } from 'vs/base/common/platform'; import { canNormalize } from 'vs/base/common/normalization'; import { VSBuffer } from 'vs/base/common/buffer'; +import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; -suite('PFS', function () { +flakySuite('PFS', function () { - // Given issues such as https://github.com/microsoft/vscode/issues/84066 - // we see random test failures when accessing the native file system. To - // diagnose further, we retry node.js file access tests up to 3 times to - // rule out any random disk issue. - this.retries(3); + let testDir: string; + + setup(() => { + testDir = getRandomTestPath(tmpdir(), 'vsctests', 'pfs'); + + return mkdirp(testDir, 493); + }); + + teardown(() => { + return rimraf(testDir); + }); test('writeFile', async () => { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'pfs', id); - const testFile = path.join(newDir, 'writefile.txt'); + const testFile = join(testDir, 'writefile.txt'); - await pfs.mkdirp(newDir, 493); - assert.ok(fs.existsSync(newDir)); + assert.ok(!(await exists(testFile))); - await pfs.writeFile(testFile, 'Hello World', (null!)); - assert.equal(fs.readFileSync(testFile), 'Hello World'); + await writeFile(testFile, 'Hello World', (null!)); - await pfs.rimraf(parentDir, pfs.RimRafMode.MOVE); + assert.strictEqual((await readFile(testFile)).toString(), 'Hello World'); }); test('writeFile - parallel write on different files works', async () => { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'pfs', id); - const testFile1 = path.join(newDir, 'writefile1.txt'); - const testFile2 = path.join(newDir, 'writefile2.txt'); - const testFile3 = path.join(newDir, 'writefile3.txt'); - const testFile4 = path.join(newDir, 'writefile4.txt'); - const testFile5 = path.join(newDir, 'writefile5.txt'); - - await pfs.mkdirp(newDir, 493); - assert.ok(fs.existsSync(newDir)); + const testFile1 = join(testDir, 'writefile1.txt'); + const testFile2 = join(testDir, 'writefile2.txt'); + const testFile3 = join(testDir, 'writefile3.txt'); + const testFile4 = join(testDir, 'writefile4.txt'); + const testFile5 = join(testDir, 'writefile5.txt'); await Promise.all([ - pfs.writeFile(testFile1, 'Hello World 1', (null!)), - pfs.writeFile(testFile2, 'Hello World 2', (null!)), - pfs.writeFile(testFile3, 'Hello World 3', (null!)), - pfs.writeFile(testFile4, 'Hello World 4', (null!)), - pfs.writeFile(testFile5, 'Hello World 5', (null!)) + writeFile(testFile1, 'Hello World 1', (null!)), + writeFile(testFile2, 'Hello World 2', (null!)), + writeFile(testFile3, 'Hello World 3', (null!)), + writeFile(testFile4, 'Hello World 4', (null!)), + writeFile(testFile5, 'Hello World 5', (null!)) ]); - assert.equal(fs.readFileSync(testFile1), 'Hello World 1'); - assert.equal(fs.readFileSync(testFile2), 'Hello World 2'); - assert.equal(fs.readFileSync(testFile3), 'Hello World 3'); - assert.equal(fs.readFileSync(testFile4), 'Hello World 4'); - assert.equal(fs.readFileSync(testFile5), 'Hello World 5'); - - await pfs.rimraf(parentDir, pfs.RimRafMode.MOVE); + assert.strictEqual(fs.readFileSync(testFile1).toString(), 'Hello World 1'); + assert.strictEqual(fs.readFileSync(testFile2).toString(), 'Hello World 2'); + assert.strictEqual(fs.readFileSync(testFile3).toString(), 'Hello World 3'); + assert.strictEqual(fs.readFileSync(testFile4).toString(), 'Hello World 4'); + assert.strictEqual(fs.readFileSync(testFile5).toString(), 'Hello World 5'); }); test('writeFile - parallel write on same files works and is sequentalized', async () => { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'pfs', id); - const testFile = path.join(newDir, 'writefile.txt'); - - await pfs.mkdirp(newDir, 493); - assert.ok(fs.existsSync(newDir)); + const testFile = join(testDir, 'writefile.txt'); await Promise.all([ - pfs.writeFile(testFile, 'Hello World 1', undefined), - pfs.writeFile(testFile, 'Hello World 2', undefined), - timeout(10).then(() => pfs.writeFile(testFile, 'Hello World 3', undefined)), - pfs.writeFile(testFile, 'Hello World 4', undefined), - timeout(10).then(() => pfs.writeFile(testFile, 'Hello World 5', undefined)) + writeFile(testFile, 'Hello World 1', undefined), + writeFile(testFile, 'Hello World 2', undefined), + timeout(10).then(() => writeFile(testFile, 'Hello World 3', undefined)), + writeFile(testFile, 'Hello World 4', undefined), + timeout(10).then(() => writeFile(testFile, 'Hello World 5', undefined)) ]); - assert.equal(fs.readFileSync(testFile), 'Hello World 5'); - - await pfs.rimraf(parentDir, pfs.RimRafMode.MOVE); + assert.strictEqual(fs.readFileSync(testFile).toString(), 'Hello World 5'); }); test('rimraf - simple - unlink', async () => { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'pfs', id); + fs.writeFileSync(join(testDir, 'somefile.txt'), 'Contents'); + fs.writeFileSync(join(testDir, 'someOtherFile.txt'), 'Contents'); - await pfs.mkdirp(newDir, 493); - fs.writeFileSync(path.join(newDir, 'somefile.txt'), 'Contents'); - fs.writeFileSync(path.join(newDir, 'someOtherFile.txt'), 'Contents'); - - await pfs.rimraf(newDir); - assert.ok(!fs.existsSync(newDir)); + await rimraf(testDir); + assert.ok(!fs.existsSync(testDir)); }); test('rimraf - simple - move', async () => { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'pfs', id); + fs.writeFileSync(join(testDir, 'somefile.txt'), 'Contents'); + fs.writeFileSync(join(testDir, 'someOtherFile.txt'), 'Contents'); - await pfs.mkdirp(newDir, 493); - fs.writeFileSync(path.join(newDir, 'somefile.txt'), 'Contents'); - fs.writeFileSync(path.join(newDir, 'someOtherFile.txt'), 'Contents'); - - await pfs.rimraf(newDir, pfs.RimRafMode.MOVE); - assert.ok(!fs.existsSync(newDir)); + await rimraf(testDir, RimRafMode.MOVE); + assert.ok(!fs.existsSync(testDir)); }); test('rimraf - recursive folder structure - unlink', async () => { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'pfs', id); + fs.writeFileSync(join(testDir, 'somefile.txt'), 'Contents'); + fs.writeFileSync(join(testDir, 'someOtherFile.txt'), 'Contents'); + fs.mkdirSync(join(testDir, 'somefolder')); + fs.writeFileSync(join(testDir, 'somefolder', 'somefile.txt'), 'Contents'); - await pfs.mkdirp(newDir, 493); - fs.writeFileSync(path.join(newDir, 'somefile.txt'), 'Contents'); - fs.writeFileSync(path.join(newDir, 'someOtherFile.txt'), 'Contents'); - fs.mkdirSync(path.join(newDir, 'somefolder')); - fs.writeFileSync(path.join(newDir, 'somefolder', 'somefile.txt'), 'Contents'); - - await pfs.rimraf(newDir); - assert.ok(!fs.existsSync(newDir)); + await rimraf(testDir); + assert.ok(!fs.existsSync(testDir)); }); test('rimraf - recursive folder structure - move', async () => { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'pfs', id); + fs.writeFileSync(join(testDir, 'somefile.txt'), 'Contents'); + fs.writeFileSync(join(testDir, 'someOtherFile.txt'), 'Contents'); + fs.mkdirSync(join(testDir, 'somefolder')); + fs.writeFileSync(join(testDir, 'somefolder', 'somefile.txt'), 'Contents'); - await pfs.mkdirp(newDir, 493); - fs.writeFileSync(path.join(newDir, 'somefile.txt'), 'Contents'); - fs.writeFileSync(path.join(newDir, 'someOtherFile.txt'), 'Contents'); - fs.mkdirSync(path.join(newDir, 'somefolder')); - fs.writeFileSync(path.join(newDir, 'somefolder', 'somefile.txt'), 'Contents'); - - await pfs.rimraf(newDir, pfs.RimRafMode.MOVE); - assert.ok(!fs.existsSync(newDir)); + await rimraf(testDir, RimRafMode.MOVE); + assert.ok(!fs.existsSync(testDir)); }); test('rimraf - simple ends with dot - move', async () => { - const id = `${uuid.generateUuid()}.`; - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'pfs', id); + fs.writeFileSync(join(testDir, 'somefile.txt'), 'Contents'); + fs.writeFileSync(join(testDir, 'someOtherFile.txt'), 'Contents'); - await pfs.mkdirp(newDir, 493); - fs.writeFileSync(path.join(newDir, 'somefile.txt'), 'Contents'); - fs.writeFileSync(path.join(newDir, 'someOtherFile.txt'), 'Contents'); - - await pfs.rimraf(newDir, pfs.RimRafMode.MOVE); - assert.ok(!fs.existsSync(newDir)); + await rimraf(testDir, RimRafMode.MOVE); + assert.ok(!fs.existsSync(testDir)); }); test('rimraf - simple ends with dot slash/backslash - move', async () => { - const id = `${uuid.generateUuid()}.`; - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'pfs', id); + fs.writeFileSync(join(testDir, 'somefile.txt'), 'Contents'); + fs.writeFileSync(join(testDir, 'someOtherFile.txt'), 'Contents'); - await pfs.mkdirp(newDir, 493); - fs.writeFileSync(path.join(newDir, 'somefile.txt'), 'Contents'); - fs.writeFileSync(path.join(newDir, 'someOtherFile.txt'), 'Contents'); - - await pfs.rimraf(`${newDir}${path.sep}`, pfs.RimRafMode.MOVE); - assert.ok(!fs.existsSync(newDir)); + await rimraf(`${testDir}${sep}`, RimRafMode.MOVE); + assert.ok(!fs.existsSync(testDir)); }); test('rimrafSync - swallows file not found error', function () { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'pfs', id); + const nonExistingDir = join(testDir, 'not-existing'); + rimrafSync(nonExistingDir); - pfs.rimrafSync(newDir); - - assert.ok(!fs.existsSync(newDir)); + assert.ok(!fs.existsSync(nonExistingDir)); }); test('rimrafSync - simple', async () => { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'pfs', id); + fs.writeFileSync(join(testDir, 'somefile.txt'), 'Contents'); + fs.writeFileSync(join(testDir, 'someOtherFile.txt'), 'Contents'); - await pfs.mkdirp(newDir, 493); + rimrafSync(testDir); - fs.writeFileSync(path.join(newDir, 'somefile.txt'), 'Contents'); - fs.writeFileSync(path.join(newDir, 'someOtherFile.txt'), 'Contents'); - - pfs.rimrafSync(newDir); - - assert.ok(!fs.existsSync(newDir)); + assert.ok(!fs.existsSync(testDir)); }); test('rimrafSync - recursive folder structure', async () => { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'pfs', id); + fs.writeFileSync(join(testDir, 'somefile.txt'), 'Contents'); + fs.writeFileSync(join(testDir, 'someOtherFile.txt'), 'Contents'); - await pfs.mkdirp(newDir, 493); - fs.writeFileSync(path.join(newDir, 'somefile.txt'), 'Contents'); - fs.writeFileSync(path.join(newDir, 'someOtherFile.txt'), 'Contents'); + fs.mkdirSync(join(testDir, 'somefolder')); + fs.writeFileSync(join(testDir, 'somefolder', 'somefile.txt'), 'Contents'); - fs.mkdirSync(path.join(newDir, 'somefolder')); - fs.writeFileSync(path.join(newDir, 'somefolder', 'somefile.txt'), 'Contents'); + rimrafSync(testDir); - pfs.rimrafSync(newDir); - - assert.ok(!fs.existsSync(newDir)); + assert.ok(!fs.existsSync(testDir)); }); - test('moveIgnoreError', async () => { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'pfs', id); - - await pfs.mkdirp(newDir, 493); - try { - await pfs.renameIgnoreError(path.join(newDir, 'foo'), path.join(newDir, 'bar')); - return pfs.rimraf(parentDir, pfs.RimRafMode.MOVE); - } - catch (error) { - assert.fail(error); - } + test('moveIgnoreError', () => { + return renameIgnoreError(join(testDir, 'foo'), join(testDir, 'bar')); }); test('copy, move and delete', async () => { - const id = uuid.generateUuid(); - const id2 = uuid.generateUuid(); + const id = generateUuid(); + const id2 = generateUuid(); const sourceDir = getPathFromAmdModule(require, './fixtures'); - const parentDir = path.join(os.tmpdir(), 'vsctests', 'pfs'); - const targetDir = path.join(parentDir, id); - const targetDir2 = path.join(parentDir, id2); + const parentDir = join(tmpdir(), 'vsctests', 'pfs'); + const targetDir = join(parentDir, id); + const targetDir2 = join(parentDir, id2); - await pfs.copy(sourceDir, targetDir); + await copy(sourceDir, targetDir); assert.ok(fs.existsSync(targetDir)); - assert.ok(fs.existsSync(path.join(targetDir, 'index.html'))); - assert.ok(fs.existsSync(path.join(targetDir, 'site.css'))); - assert.ok(fs.existsSync(path.join(targetDir, 'examples'))); - assert.ok(fs.statSync(path.join(targetDir, 'examples')).isDirectory()); - assert.ok(fs.existsSync(path.join(targetDir, 'examples', 'small.jxs'))); + assert.ok(fs.existsSync(join(targetDir, 'index.html'))); + assert.ok(fs.existsSync(join(targetDir, 'site.css'))); + assert.ok(fs.existsSync(join(targetDir, 'examples'))); + assert.ok(fs.statSync(join(targetDir, 'examples')).isDirectory()); + assert.ok(fs.existsSync(join(targetDir, 'examples', 'small.jxs'))); - await pfs.move(targetDir, targetDir2); + await move(targetDir, targetDir2); assert.ok(!fs.existsSync(targetDir)); assert.ok(fs.existsSync(targetDir2)); - assert.ok(fs.existsSync(path.join(targetDir2, 'index.html'))); - assert.ok(fs.existsSync(path.join(targetDir2, 'site.css'))); - assert.ok(fs.existsSync(path.join(targetDir2, 'examples'))); - assert.ok(fs.statSync(path.join(targetDir2, 'examples')).isDirectory()); - assert.ok(fs.existsSync(path.join(targetDir2, 'examples', 'small.jxs'))); + assert.ok(fs.existsSync(join(targetDir2, 'index.html'))); + assert.ok(fs.existsSync(join(targetDir2, 'site.css'))); + assert.ok(fs.existsSync(join(targetDir2, 'examples'))); + assert.ok(fs.statSync(join(targetDir2, 'examples')).isDirectory()); + assert.ok(fs.existsSync(join(targetDir2, 'examples', 'small.jxs'))); - await pfs.move(path.join(targetDir2, 'index.html'), path.join(targetDir2, 'index_moved.html')); + await move(join(targetDir2, 'index.html'), join(targetDir2, 'index_moved.html')); - assert.ok(!fs.existsSync(path.join(targetDir2, 'index.html'))); - assert.ok(fs.existsSync(path.join(targetDir2, 'index_moved.html'))); + assert.ok(!fs.existsSync(join(targetDir2, 'index.html'))); + assert.ok(fs.existsSync(join(targetDir2, 'index_moved.html'))); - await pfs.rimraf(parentDir, pfs.RimRafMode.MOVE); + await rimraf(parentDir); assert.ok(!fs.existsSync(parentDir)); }); - test('mkdirp', async () => { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'pfs', id); + test('copy skips over dangling symbolic links', async () => { + const id1 = generateUuid(); + const symbolicLinkTarget = join(testDir, id1); - await pfs.mkdirp(newDir, 493); + const id2 = generateUuid(); + const symbolicLink = join(testDir, id2); + + const id3 = generateUuid(); + const copyTarget = join(testDir, id3); + + await mkdirp(symbolicLinkTarget, 493); + + fs.symlinkSync(symbolicLinkTarget, symbolicLink, 'junction'); + + await rimraf(symbolicLinkTarget); + + await copy(symbolicLink, copyTarget); // this should not throw + + assert.ok(!fs.existsSync(copyTarget)); + }); + + test('mkdirp', async () => { + const newDir = join(testDir, generateUuid()); + + await mkdirp(newDir, 493); assert.ok(fs.existsSync(newDir)); - - return pfs.rimraf(parentDir, pfs.RimRafMode.MOVE); }); test('readDirsInDir', async () => { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'pfs', id); + fs.mkdirSync(join(testDir, 'somefolder1')); + fs.mkdirSync(join(testDir, 'somefolder2')); + fs.mkdirSync(join(testDir, 'somefolder3')); + fs.writeFileSync(join(testDir, 'somefile.txt'), 'Contents'); + fs.writeFileSync(join(testDir, 'someOtherFile.txt'), 'Contents'); - await pfs.mkdirp(newDir, 493); - - fs.mkdirSync(path.join(newDir, 'somefolder1')); - fs.mkdirSync(path.join(newDir, 'somefolder2')); - fs.mkdirSync(path.join(newDir, 'somefolder3')); - fs.writeFileSync(path.join(newDir, 'somefile.txt'), 'Contents'); - fs.writeFileSync(path.join(newDir, 'someOtherFile.txt'), 'Contents'); - - const result = await pfs.readDirsInDir(newDir); - assert.equal(result.length, 3); + const result = await readDirsInDir(testDir); + assert.strictEqual(result.length, 3); assert.ok(result.indexOf('somefolder1') !== -1); assert.ok(result.indexOf('somefolder2') !== -1); assert.ok(result.indexOf('somefolder3') !== -1); - - await pfs.rimraf(newDir); }); test('stat link', async () => { - if (isWindows) { - return; // Symlinks are not the same on win, and we can not create them programitically without admin privileges - } + const id1 = generateUuid(); + const directory = join(testDir, id1); - const id1 = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id1); - const directory = path.join(parentDir, 'pfs', id1); + const id2 = generateUuid(); + const symbolicLink = join(testDir, id2); - const id2 = uuid.generateUuid(); - const symbolicLink = path.join(parentDir, 'pfs', id2); + await mkdirp(directory, 493); - await pfs.mkdirp(directory, 493); + fs.symlinkSync(directory, symbolicLink, 'junction'); - fs.symlinkSync(directory, symbolicLink); - - let statAndIsLink = await pfs.statLink(directory); + let statAndIsLink = await statLink(directory); assert.ok(!statAndIsLink?.symbolicLink); - statAndIsLink = await pfs.statLink(symbolicLink); + statAndIsLink = await statLink(symbolicLink); assert.ok(statAndIsLink?.symbolicLink); assert.ok(!statAndIsLink?.symbolicLink?.dangling); - - pfs.rimrafSync(directory); }); test('stat link (non existing target)', async () => { - if (isWindows) { - return; // Symlinks are not the same on win, and we can not create them programitically without admin privileges - } + const id1 = generateUuid(); + const directory = join(testDir, id1); - const id1 = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id1); - const directory = path.join(parentDir, 'pfs', id1); + const id2 = generateUuid(); + const symbolicLink = join(testDir, id2); - const id2 = uuid.generateUuid(); - const symbolicLink = path.join(parentDir, 'pfs', id2); + await mkdirp(directory, 493); - await pfs.mkdirp(directory, 493); + fs.symlinkSync(directory, symbolicLink, 'junction'); - fs.symlinkSync(directory, symbolicLink); + await rimraf(directory); - pfs.rimrafSync(directory); - - const statAndIsLink = await pfs.statLink(symbolicLink); + const statAndIsLink = await statLink(symbolicLink); assert.ok(statAndIsLink?.symbolicLink); assert.ok(statAndIsLink?.symbolicLink?.dangling); }); test('readdir', async () => { if (canNormalize && typeof process.versions['electron'] !== 'undefined' /* needs electron */) { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'pfs', id, 'öäü'); + const id = generateUuid(); + const newDir = join(testDir, 'pfs', id, 'öäü'); - await pfs.mkdirp(newDir, 493); + await mkdirp(newDir, 493); assert.ok(fs.existsSync(newDir)); - const children = await pfs.readdir(path.join(parentDir, 'pfs', id)); - assert.equal(children.some(n => n === 'öäü'), true); // Mac always converts to NFD, so - - await pfs.rimraf(parentDir); + const children = await readdir(join(testDir, 'pfs', id)); + assert.strictEqual(children.some(n => n === 'öäü'), true); // Mac always converts to NFD, so } }); test('readdirWithFileTypes', async () => { if (canNormalize && typeof process.versions['electron'] !== 'undefined' /* needs electron */) { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const testDir = path.join(parentDir, 'pfs', id); + const newDir = join(testDir, 'öäü'); + await mkdirp(newDir, 493); - const newDir = path.join(testDir, 'öäü'); - await pfs.mkdirp(newDir, 493); - - await pfs.writeFile(path.join(testDir, 'somefile.txt'), 'contents'); + await writeFile(join(testDir, 'somefile.txt'), 'contents'); assert.ok(fs.existsSync(newDir)); - const children = await pfs.readdirWithFileTypes(testDir); + const children = await readdirWithFileTypes(testDir); - assert.equal(children.some(n => n.name === 'öäü'), true); // Mac always converts to NFD, so - assert.equal(children.some(n => n.isDirectory()), true); + assert.strictEqual(children.some(n => n.name === 'öäü'), true); // Mac always converts to NFD, so + assert.strictEqual(children.some(n => n.isDirectory()), true); - assert.equal(children.some(n => n.name === 'somefile.txt'), true); - assert.equal(children.some(n => n.isFile()), true); - - await pfs.rimraf(parentDir); + assert.strictEqual(children.some(n => n.name === 'somefile.txt'), true); + assert.strictEqual(children.some(n => n.isFile()), true); } }); @@ -416,65 +334,41 @@ suite('PFS', function () { bigData: string | Buffer | Uint8Array, bigDataValue: string ): Promise { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'pfs', id); - const testFile = path.join(newDir, 'flushed.txt'); + const testFile = join(testDir, 'flushed.txt'); - await pfs.mkdirp(newDir, 493); - assert.ok(fs.existsSync(newDir)); + assert.ok(fs.existsSync(testDir)); - await pfs.writeFile(testFile, smallData); - assert.equal(fs.readFileSync(testFile), smallDataValue); + await writeFile(testFile, smallData); + assert.strictEqual(fs.readFileSync(testFile).toString(), smallDataValue); - await pfs.writeFile(testFile, bigData); - assert.equal(fs.readFileSync(testFile), bigDataValue); - - await pfs.rimraf(parentDir); + await writeFile(testFile, bigData); + assert.strictEqual(fs.readFileSync(testFile).toString(), bigDataValue); } test('writeFile (string, error handling)', async () => { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'pfs', id); - const testFile = path.join(newDir, 'flushed.txt'); + const testFile = join(testDir, 'flushed.txt'); - await pfs.mkdirp(newDir, 493); - - assert.ok(fs.existsSync(newDir)); - - fs.mkdirSync(testFile); // this will trigger an error because testFile is now a directory! + fs.mkdirSync(testFile); // this will trigger an error later because testFile is now a directory! let expectedError: Error | undefined; try { - await pfs.writeFile(testFile, 'Hello World'); + await writeFile(testFile, 'Hello World'); } catch (error) { expectedError = error; } assert.ok(expectedError); - - await pfs.rimraf(parentDir); }); test('writeFileSync', async () => { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'pfs', id); - const testFile = path.join(newDir, 'flushed.txt'); + const testFile = join(testDir, 'flushed.txt'); - await pfs.mkdirp(newDir, 493); - - assert.ok(fs.existsSync(newDir)); - - pfs.writeFileSync(testFile, 'Hello World'); - assert.equal(fs.readFileSync(testFile), 'Hello World'); + writeFileSync(testFile, 'Hello World'); + assert.strictEqual(fs.readFileSync(testFile).toString(), 'Hello World'); const largeString = (new Array(100 * 1024)).join('Large String\n'); - pfs.writeFileSync(testFile, largeString); - assert.equal(fs.readFileSync(testFile), largeString); - - await pfs.rimraf(parentDir); + writeFileSync(testFile, largeString); + assert.strictEqual(fs.readFileSync(testFile).toString(), largeString); }); }); diff --git a/src/vs/base/test/node/port.test.ts b/src/vs/base/test/node/port.test.ts index 87d8eca9e..120044651 100644 --- a/src/vs/base/test/node/port.test.ts +++ b/src/vs/base/test/node/port.test.ts @@ -6,14 +6,10 @@ import * as assert from 'assert'; import * as net from 'net'; import * as ports from 'vs/base/node/ports'; +import { flakySuite } from 'vs/base/test/node/testUtils'; -suite('Ports', () => { - test('Finds a free port (no timeout)', function (done) { - this.timeout(1000 * 10); // higher timeout for this test - - if (process.env['VSCODE_PID']) { - return done(); // this test fails when run from within VS Code - } +flakySuite('Ports', () => { + (process.env['VSCODE_PID'] ? test.skip /* this test fails when run from within VS Code */ : test)('Finds a free port (no timeout)', function (done) { // get an initial freeport >= 7000 ports.findFreePort(7000, 100, 300000).then(initialPort => { diff --git a/src/vs/base/test/node/powershell.test.ts b/src/vs/base/test/node/powershell.test.ts new file mode 100644 index 000000000..6e88b0982 --- /dev/null +++ b/src/vs/base/test/node/powershell.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 * as fs from 'fs'; +import * as os from 'os'; +import * as platform from 'vs/base/common/platform'; +import { enumeratePowerShellInstallations, getFirstAvailablePowerShellInstallation, IPowerShellExeDetails } from 'vs/base/node/powershell'; + +function checkPath(exePath: string) { + // Check to see if the path exists + let pathCheckResult = false; + try { + const stat = fs.statSync(exePath); + pathCheckResult = stat.isFile(); + } catch { + // fs.exists throws on Windows with SymbolicLinks so we + // also use lstat to try and see if the file exists. + try { + pathCheckResult = fs.statSync(fs.readlinkSync(exePath)).isFile(); + } catch { + + } + } + + assert.strictEqual(pathCheckResult, true); +} + +if (platform.isWindows) { + suite('PowerShell finder', () => { + + test('Can find first available PowerShell', async () => { + const pwshExe = await getFirstAvailablePowerShellInstallation(); + const exePath = pwshExe?.exePath; + assert.notStrictEqual(exePath, null); + assert.notStrictEqual(pwshExe?.displayName, null); + + checkPath(exePath!); + }); + + test('Can enumerate PowerShells', async () => { + const isOS64Bit = os.arch() === 'x64'; + const pwshs = new Array(); + for await (const p of enumeratePowerShellInstallations()) { + pwshs.push(p); + } + + const powershellLog = 'Found these PowerShells:\n' + pwshs.map(p => `${p.displayName}: ${p.exePath}`).join('\n'); + assert.strictEqual(pwshs.length >= (isOS64Bit ? 2 : 1), true, powershellLog); + + for (const pwsh of pwshs) { + checkPath(pwsh.exePath); + } + + + const lastIndex = pwshs.length - 1; + const secondToLastIndex = pwshs.length - 2; + + // 64bit process on 64bit OS + if (process.arch === 'x64') { + checkPath(pwshs[secondToLastIndex].exePath); + assert.strictEqual(pwshs[secondToLastIndex].displayName, 'Windows PowerShell', powershellLog); + + checkPath(pwshs[lastIndex].exePath); + assert.strictEqual(pwshs[lastIndex].displayName, 'Windows PowerShell (x86)', powershellLog); + } else if (isOS64Bit) { + // 32bit process on 64bit OS + + // Windows PowerShell x86 comes first if vscode is 32bit + checkPath(pwshs[secondToLastIndex].exePath); + assert.strictEqual(pwshs[secondToLastIndex].displayName, 'Windows PowerShell (x86)', powershellLog); + + checkPath(pwshs[lastIndex].exePath); + assert.strictEqual(pwshs[lastIndex].displayName, 'Windows PowerShell', powershellLog); + } else { + // 32bit or ARM process + checkPath(pwshs[lastIndex].exePath); + assert.strictEqual(pwshs[lastIndex].displayName, 'Windows PowerShell (x86)', powershellLog); + } + }); + }); +} diff --git a/src/vs/base/test/node/processes/processes.test.ts b/src/vs/base/test/node/processes/processes.test.ts index 76719506d..e9f922f33 100644 --- a/src/vs/base/test/node/processes/processes.test.ts +++ b/src/vs/base/test/node/processes/processes.test.ts @@ -13,9 +13,9 @@ import { getPathFromAmdModule } from 'vs/base/common/amd'; function fork(id: string): cp.ChildProcess { const opts: any = { env: objects.mixin(objects.deepClone(process.env), { - AMD_ENTRYPOINT: id, - PIPE_LOGGING: 'true', - VERBOSE_LOGGING: true + VSCODE_AMD_ENTRYPOINT: id, + VSCODE_PIPE_LOGGING: 'true', + VSCODE_VERBOSE_LOGGING: true }) }; @@ -59,11 +59,7 @@ suite('Processes', () => { }); }); - test('buffered sending - lots of data (potential deadlock on win32)', function (done: () => void) { - if (!platform.isWindows || process.env['VSCODE_PID']) { - return done(); // test is only relevant for Windows and seems to crash randomly on some Linux builds - } - + (!platform.isWindows || process.env['VSCODE_PID'] ? test.skip : test)('buffered sending - lots of data (potential deadlock on win32)', function (done: () => void) { // test is only relevant for Windows and seems to crash randomly on some Linux builds const child = fork('vs/base/test/node/processes/fixtures/fork_large'); const sender = processes.createQueuedSender(child); diff --git a/src/vs/base/test/node/testUtils.ts b/src/vs/base/test/node/testUtils.ts index 452e8ae07..802d9fd14 100644 --- a/src/vs/base/test/node/testUtils.ts +++ b/src/vs/base/test/node/testUtils.ts @@ -3,9 +3,25 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import type { Suite } from 'mocha'; import { join } from 'vs/base/common/path'; import { generateUuid } from 'vs/base/common/uuid'; export function getRandomTestPath(tmpdir: string, ...segments: string[]): string { return join(tmpdir, ...segments, generateUuid()); } + +export function flakySuite(title: string, fn: (this: Suite) => void): Suite { + return suite(title, function () { + + // Flaky suites need retries and timeout to complete + // e.g. because they access the file system which can + // be unreliable depending on the environment. + this.retries(3); + this.timeout(1000 * 20); + + // Invoke suite ensuring that `this` is + // properly wired in. + fn.call(this); + }); +} diff --git a/src/vs/base/test/node/zip/zip.test.ts b/src/vs/base/test/node/zip/zip.test.ts index f79bddaa9..a98b2609f 100644 --- a/src/vs/base/test/node/zip/zip.test.ts +++ b/src/vs/base/test/node/zip/zip.test.ts @@ -5,24 +5,33 @@ import * as assert from 'assert'; import * as path from 'vs/base/common/path'; -import * as os from 'os'; +import { tmpdir } from 'os'; import { extract } from 'vs/base/node/zip'; -import { generateUuid } from 'vs/base/common/uuid'; -import { rimraf, exists } from 'vs/base/node/pfs'; +import { rimraf, exists, mkdirp } from 'vs/base/node/pfs'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { createCancelablePromise } from 'vs/base/common/async'; - -const fixtures = getPathFromAmdModule(require, './fixtures'); +import { getRandomTestPath } from 'vs/base/test/node/testUtils'; suite('Zip', () => { - test('extract should handle directories', () => { - const fixture = path.join(fixtures, 'extract.zip'); - const target = path.join(os.tmpdir(), generateUuid()); + let testDir: string; - return createCancelablePromise(token => extract(fixture, target, {}, token) - .then(() => exists(path.join(target, 'extension'))) - .then(exists => assert(exists)) - .then(() => rimraf(target))); + setup(() => { + testDir = getRandomTestPath(tmpdir(), 'vsctests', 'zip'); + + return mkdirp(testDir); + }); + + teardown(() => { + return rimraf(testDir); + }); + + test('extract should handle directories', async () => { + const fixtures = getPathFromAmdModule(require, './fixtures'); + const fixture = path.join(fixtures, 'extract.zip'); + + await createCancelablePromise(token => extract(fixture, testDir, {}, token)); + const doesExist = await exists(path.join(testDir, 'extension')); + assert(doesExist); }); }); diff --git a/src/vs/base/worker/defaultWorkerFactory.ts b/src/vs/base/worker/defaultWorkerFactory.ts index 51faabdf9..21b3935fc 100644 --- a/src/vs/base/worker/defaultWorkerFactory.ts +++ b/src/vs/base/worker/defaultWorkerFactory.ts @@ -6,6 +6,8 @@ import { globals } from 'vs/base/common/platform'; import { IWorker, IWorkerCallback, IWorkerFactory, logOnceWebWorkerWarning } from 'vs/base/common/worker/simpleWorker'; +const ttPolicy = window.trustedTypes?.createPolicy('defaultWorkerFactory', { createScriptURL: value => value }); + function getWorker(workerId: string, label: string): Worker | Promise { // Option for hosts to overwrite the worker script (used in the standalone editor) if (globals.MonacoEnvironment) { @@ -13,7 +15,8 @@ function getWorker(workerId: string, label: string): Worker | Promise { return globals.MonacoEnvironment.getWorker(workerId, label); } if (typeof globals.MonacoEnvironment.getWorkerUrl === 'function') { - return new Worker(globals.MonacoEnvironment.getWorkerUrl(workerId, label)); + const wokerUrl = globals.MonacoEnvironment.getWorkerUrl(workerId, label); + return new Worker(ttPolicy ? ttPolicy.createScriptURL(wokerUrl) as unknown as string : wokerUrl, { name: label }); } } // ESM-comment-begin @@ -21,7 +24,7 @@ function getWorker(workerId: string, label: string): Worker | Promise { // check if the JS lives on a different origin const workerMain = require.toUrl('./' + workerId); // explicitly using require.toUrl(), see https://github.com/microsoft/vscode/issues/107440#issuecomment-698982321 const workerUrl = getWorkerBootstrapUrl(workerMain, label); - return new Worker(workerUrl, { name: label }); + return new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: label }); } // ESM-comment-end throw new Error(`You must define a function MonacoEnvironment.getWorkerUrl or MonacoEnvironment.getWorker`); diff --git a/src/vs/code/browser/workbench/workbench-dev.html b/src/vs/code/browser/workbench/workbench-dev.html index 8b1869294..2df6f8125 100644 --- a/src/vs/code/browser/workbench/workbench-dev.html +++ b/src/vs/code/browser/workbench/workbench-dev.html @@ -3,8 +3,7 @@ @@ -56,7 +55,7 @@ @@ -55,7 +54,7 @@ diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index d1dba0721..1ed7feec9 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -265,7 +265,6 @@ class PollingURLCallbackProvider extends Disposable implements IURLCallbackProvi setTimeout(() => this.periodicFetchCallback(requestId, startTime), PollingURLCallbackProvider.FETCH_INTERVAL); } } - } class WorkspaceProvider implements IWorkspaceProvider { diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/deprecatedExtensionsCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/deprecatedExtensionsCleaner.ts new file mode 100644 index 000000000..0ffa0870e --- /dev/null +++ b/src/vs/code/electron-browser/sharedProcess/contrib/deprecatedExtensionsCleaner.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 { Disposable } from 'vs/base/common/lifecycle'; +import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; +import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; + +export class DeprecatedExtensionsCleaner extends Disposable { + + constructor( + @IExtensionManagementService private readonly extensionManagementService: ExtensionManagementService + ) { + super(); + + this._register(extensionManagementService); // TODO@sandy081 this seems fishy + + this.cleanUpDeprecatedExtensions(); + } + + private cleanUpDeprecatedExtensions(): void { + this.extensionManagementService.removeDeprecatedExtensions(); + } +} diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/localizationsUpdater.ts b/src/vs/code/electron-browser/sharedProcess/contrib/localizationsUpdater.ts new file mode 100644 index 000000000..6c9bfa812 --- /dev/null +++ b/src/vs/code/electron-browser/sharedProcess/contrib/localizationsUpdater.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; +import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; + +export class LocalizationsUpdater extends Disposable { + + constructor( + @ILocalizationsService private readonly localizationsService: LocalizationsService + ) { + super(); + + this.updateLocalizations(); + } + + private updateLocalizations(): void { + this.localizationsService.update(); + } +} diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcess.js b/src/vs/code/electron-browser/sharedProcess/sharedProcess.js index 8874e8720..2bb0a0bb7 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcess.js +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcess.js @@ -15,10 +15,7 @@ // Load shared process into window bootstrapWindow.load(['vs/code/electron-browser/sharedProcess/sharedProcessMain'], function (sharedProcess, configuration) { - sharedProcess.startup({ - machineId: configuration.machineId, - windowId: configuration.windowId - }); + return sharedProcess.main(configuration); }); diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 3dcc360b5..3aa7cce16 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.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 * as platform from 'vs/base/common/platform'; import product from 'vs/platform/product/common/product'; -import { serve, Server, connect } from 'vs/base/parts/ipc/node/ipc.net'; +import * as fs from 'fs'; +import { release } from 'os'; +import { gracefulify } from 'graceful-fs'; +import { Server as MessagePortServer } from 'vs/base/parts/ipc/electron-sandbox/ipc.mp'; +import { StaticRouter, createChannelSender, createChannelReceiver } from 'vs/base/parts/ipc/common/ipc'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; -import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { ExtensionManagementChannel, ExtensionTipsChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -23,24 +24,23 @@ import { IRequestService } from 'vs/platform/request/common/request'; import { RequestService } from 'vs/platform/request/browser/requestService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { combinedAppender, NullTelemetryService, ITelemetryAppender, NullAppender } from 'vs/platform/telemetry/common/telemetryUtils'; -import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; -import { TelemetryAppenderChannel } from 'vs/platform/telemetry/node/telemetryIpc'; -import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; +import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; +import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc'; +import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; -import { ILogService, LogLevel, ILoggerService } from 'vs/platform/log/common/log'; +import { ILogService, ILoggerService, MultiplexLogService, ConsoleLogService } from 'vs/platform/log/common/log'; import { LoggerChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; -import { combinedDisposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { combinedDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { DownloadService } from 'vs/platform/download/common/downloadService'; import { IDownloadService } from 'vs/platform/download/common/download'; -import { IChannel, IServerChannel, StaticRouter, createChannelSender, createChannelReceiver } from 'vs/base/parts/ipc/common/ipc'; import { NodeCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner'; import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner'; import { StorageDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner'; import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService, MessagePortMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; import { SpdLogService } from 'vs/platform/log/node/spdlogService'; import { DiagnosticsService, IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; import { FileService } from 'vs/platform/files/common/fileService'; @@ -48,7 +48,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService, IUserDataSyncStoreManagementService, IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration as registerUserDataSyncConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService, IUserDataSyncStoreManagementService, IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncStoreService, UserDataSyncStoreManagementService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { UserDataSyncChannel, UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, UserDataSyncMachinesServiceChannel, UserDataSyncAccountServiceChannel, UserDataSyncStoreManagementServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; @@ -67,146 +67,184 @@ import { ExtensionTipsService } from 'vs/platform/extensionManagement/electron-s import { UserDataSyncMachinesService, IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; import { ExtensionRecommendationNotificationServiceChannelClient } from 'vs/platform/extensionRecommendations/electron-sandbox/extensionRecommendationsIpc'; -import { ActiveWindowManager } from 'vs/platform/windows/common/windowTracker'; +import { ActiveWindowManager } from 'vs/platform/windows/node/windowTracker'; import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogAppender'; import { UserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataAutoSyncService'; import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; import { ExtensionsStorageSyncService, IExtensionsStorageSyncService } from 'vs/platform/userDataSync/common/extensionsStorageSync'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ISharedProcessConfiguration } from 'vs/platform/sharedProcess/node/sharedProcess'; +import { LocalizationsUpdater } from 'vs/code/electron-browser/sharedProcess/contrib/localizationsUpdater'; +import { DeprecatedExtensionsCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/deprecatedExtensionsCleaner'; +import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; -export interface ISharedProcessConfiguration { - readonly machineId: string; - readonly windowId: number; -} +class SharedProcessMain extends Disposable { -export function startup(configuration: ISharedProcessConfiguration) { - handshake(configuration); -} + private server = this._register(new MessagePortServer()); -interface ISharedProcessInitData { - sharedIPCHandle: string; - args: NativeParsedArgs; - logLevel: LogLevel; - nodeCachedDataDir?: string; - backupWorkspacesPath: string; -} + constructor(private configuration: ISharedProcessConfiguration) { + super(); -const eventPrefix = 'monacoworkbench'; + // Enable gracefulFs + gracefulify(fs); -class MainProcessService implements IMainProcessService { - - constructor( - private server: Server, - private mainRouter: StaticRouter - ) { } - - declare readonly _serviceBrand: undefined; - - getChannel(channelName: string): IChannel { - return this.server.getChannel(channelName, this.mainRouter); + this.registerListeners(); } - registerChannel(channelName: string, channel: IServerChannel): void { - this.server.registerChannel(channelName, channel); + private registerListeners(): void { + + // Dispose on exit + const onExit = () => this.dispose(); + process.once('exit', onExit); + ipcRenderer.once('vscode:electron-main->shared-process=exit', onExit); } -} -async function main(server: Server, initData: ISharedProcessInitData, configuration: ISharedProcessConfiguration): Promise { - const services = new ServiceCollection(); + async open(): Promise { - const disposables = new DisposableStore(); + // Services + const instantiationService = await this.initServices(); - const onExit = () => disposables.dispose(); - process.once('exit', onExit); - ipcRenderer.once('vscode:electron-main->shared-process=exit', onExit); + // Config + registerUserDataSyncConfiguration(); - disposables.add(server); + instantiationService.invokeFunction(accessor => { + const logService = accessor.get(ILogService); - const environmentService = new NativeEnvironmentService(initData.args); + // Log info + logService.trace('sharedProcess configuration', JSON.stringify(this.configuration)); - const mainRouter = new StaticRouter(ctx => ctx === 'main'); - const loggerClient = new LoggerChannelClient(server.getChannel('logger', mainRouter)); - const logService = new FollowerLogService(loggerClient, new SpdLogService('sharedprocess', environmentService.logsPath, initData.logLevel)); - disposables.add(logService); - logService.info('main', JSON.stringify(configuration)); + // Channels + this.initChannels(accessor); - const mainProcessService = new MainProcessService(server, mainRouter); - services.set(IMainProcessService, mainProcessService); + // Error handler + this.registerErrorHandler(logService); + }); - // Files - const fileService = new FileService(logService); - services.set(IFileService, fileService); - disposables.add(fileService); - const diskFileSystemProvider = new DiskFileSystemProvider(logService); - disposables.add(diskFileSystemProvider); - fileService.registerProvider(Schemas.file, diskFileSystemProvider); + // Instantiate Contributions + this._register(combinedDisposable( + new NodeCachedDataCleaner(this.configuration.nodeCachedDataDir), + instantiationService.createInstance(LanguagePackCachedDataCleaner), + instantiationService.createInstance(StorageDataCleaner, this.configuration.backupWorkspacesPath), + instantiationService.createInstance(LogsDataCleaner), + instantiationService.createInstance(LocalizationsUpdater), + instantiationService.createInstance(DeprecatedExtensionsCleaner) + )); + } - // Configuration - const configurationService = new ConfigurationService(environmentService.settingsResource, fileService); - disposables.add(configurationService); - await configurationService.initialize(); - - // Storage - const storageService = new NativeStorageService(new GlobalStorageDatabaseChannelClient(mainProcessService.getChannel('storage')), logService, environmentService); - await storageService.initialize(); - services.set(IStorageService, storageService); - disposables.add(toDisposable(() => storageService.flush())); - - services.set(IEnvironmentService, environmentService); - services.set(INativeEnvironmentService, environmentService); - - services.set(IProductService, { _serviceBrand: undefined, ...product }); - services.set(ILogService, logService); - services.set(IConfigurationService, configurationService); - services.set(IRequestService, new SyncDescriptor(RequestService)); - services.set(ILoggerService, new SyncDescriptor(LoggerService)); - - const nativeHostService = createChannelSender(mainProcessService.getChannel('nativeHost'), { context: configuration.windowId }); - services.set(INativeHostService, nativeHostService); - const activeWindowManager = new ActiveWindowManager(nativeHostService); - const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id)); - - services.set(IDownloadService, new SyncDescriptor(DownloadService)); - services.set(IExtensionRecommendationNotificationService, new ExtensionRecommendationNotificationServiceChannelClient(server.getChannel('IExtensionRecommendationNotificationService', activeWindowRouter))); - - const instantiationService = new InstantiationService(services); - - let telemetryService: ITelemetryService; - instantiationService.invokeFunction(accessor => { + private async initServices(): Promise { const services = new ServiceCollection(); + + // Environment + const environmentService = new NativeEnvironmentService(this.configuration.args); + services.set(IEnvironmentService, environmentService); + services.set(INativeEnvironmentService, environmentService); + + // Log + const mainRouter = new StaticRouter(ctx => ctx === 'main'); + const loggerClient = new LoggerChannelClient(this.server.getChannel('logger', mainRouter)); // we only use this for log levels + const multiplexLogger = this._register(new MultiplexLogService([ + this._register(new ConsoleLogService(this.configuration.logLevel)), + this._register(new SpdLogService('sharedprocess', environmentService.logsPath, this.configuration.logLevel)) + ])); + + const logService = this._register(new FollowerLogService(loggerClient, multiplexLogger)); + services.set(ILogService, logService); + + // Main Process + const mainProcessService = new MessagePortMainProcessService(this.server, mainRouter); + services.set(IMainProcessService, mainProcessService); + + // Files + const fileService = this._register(new FileService(logService)); + services.set(IFileService, fileService); + + const diskFileSystemProvider = this._register(new DiskFileSystemProvider(logService)); + fileService.registerProvider(Schemas.file, diskFileSystemProvider); + + // Configuration + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService)); + services.set(IConfigurationService, configurationService); + + await configurationService.initialize(); + + // Storage + const storageService = new NativeStorageService(new GlobalStorageDatabaseChannelClient(mainProcessService.getChannel('storage')), logService, environmentService); + services.set(IStorageService, storageService); + + await storageService.initialize(); + this._register(toDisposable(() => storageService.flush())); + + // Product + services.set(IProductService, { _serviceBrand: undefined, ...product }); + + // Request + services.set(IRequestService, new SyncDescriptor(RequestService)); + + // Native Host + const nativeHostService = createChannelSender(mainProcessService.getChannel('nativeHost'), { context: this.configuration.windowId }); + services.set(INativeHostService, nativeHostService); + + // Download + services.set(IDownloadService, new SyncDescriptor(DownloadService)); + + // Extension recommendations + const activeWindowManager = this._register(new ActiveWindowManager(nativeHostService)); + const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id)); + services.set(IExtensionRecommendationNotificationService, new ExtensionRecommendationNotificationServiceChannelClient(this.server.getChannel('extensionRecommendationNotification', activeWindowRouter))); + + // Logger + const loggerService = this._register(new LoggerService(logService, fileService)); + services.set(ILoggerService, loggerService); + + // Telemetry const { appRoot, extensionsPath, extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService; - let telemetryAppender: ITelemetryAppender = NullAppender; + let telemetryService: ITelemetryService; + let telemetryAppender: ITelemetryAppender; if (!extensionDevelopmentLocationURI && !environmentService.disableTelemetry && product.enableTelemetry) { - telemetryAppender = new TelemetryLogAppender(accessor.get(ILoggerService), environmentService); + telemetryAppender = new TelemetryLogAppender(loggerService, environmentService); + + // Application Insights if (product.aiConfig && product.aiConfig.asimovKey && isBuilt) { - const appInsightsAppender = new AppInsightsAppender(eventPrefix, null, product.aiConfig.asimovKey); - disposables.add(toDisposable(() => appInsightsAppender!.flush())); // Ensure the AI appender is disposed so that it flushes remaining data + const appInsightsAppender = new AppInsightsAppender('monacoworkbench', null, product.aiConfig.asimovKey); + this._register(toDisposable(() => appInsightsAppender.flush())); // Ensure the AI appender is disposed so that it flushes remaining data telemetryAppender = combinedAppender(appInsightsAppender, telemetryAppender); } - const config: ITelemetryServiceConfig = { + + telemetryService = new TelemetryService({ appender: telemetryAppender, - commonProperties: resolveCommonProperties(product.commit, product.version, configuration.machineId, product.msftInternalDomains, installSourcePath), + commonProperties: resolveCommonProperties(fileService, release(), process.arch, product.commit, product.version, this.configuration.machineId, product.msftInternalDomains, installSourcePath), sendErrorTelemetry: true, piiPaths: [appRoot, extensionsPath] - }; - - telemetryService = new TelemetryService(config, configurationService); - services.set(ITelemetryService, telemetryService); + }, configurationService); } else { telemetryService = NullTelemetryService; - services.set(ITelemetryService, NullTelemetryService); + telemetryAppender = NullAppender; } - server.registerChannel('telemetryAppender', new TelemetryAppenderChannel(telemetryAppender)); + this.server.registerChannel('telemetryAppender', new TelemetryAppenderChannel(telemetryAppender)); + services.set(ITelemetryService, telemetryService); + + // Extension Management services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); + + // Extension Gallery services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)); - services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService)); - services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService)); + + // Extension Tips services.set(IExtensionTipsService, new SyncDescriptor(ExtensionTipsService)); + // Localizations + services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService)); + + // Diagnostics + services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService)); + + // Settings Sync services.set(IUserDataSyncAccountService, new SyncDescriptor(UserDataSyncAccountService)); services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService)); - services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', client => client.ctx !== 'main'))); + services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(this.server.getChannel('userDataSyncUtil', client => client.ctx !== 'main'))); services.set(IGlobalExtensionEnablementService, new SyncDescriptor(GlobalExtensionEnablementService)); services.set(IIgnoredExtensionsManagementService, new SyncDescriptor(IgnoredExtensionsManagementService)); services.set(IExtensionsStorageSyncService, new SyncDescriptor(ExtensionsStorageSyncService)); @@ -217,114 +255,86 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(IUserDataAutoSyncEnablementService, new SyncDescriptor(UserDataAutoSyncEnablementService)); services.set(IUserDataSyncResourceEnablementService, new SyncDescriptor(UserDataSyncResourceEnablementService)); services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService)); - registerConfiguration(); - const instantiationService2 = instantiationService.createChild(services); - - instantiationService2.invokeFunction(accessor => { - - const extensionManagementService = accessor.get(IExtensionManagementService); - const channel = new ExtensionManagementChannel(extensionManagementService, () => null); - server.registerChannel('extensions', channel); - - const localizationsService = accessor.get(ILocalizationsService); - const localizationsChannel = createChannelReceiver(localizationsService); - server.registerChannel('localizations', localizationsChannel); - - const diagnosticsService = accessor.get(IDiagnosticsService); - const diagnosticsChannel = createChannelReceiver(diagnosticsService); - server.registerChannel('diagnostics', diagnosticsChannel); - - const extensionTipsService = accessor.get(IExtensionTipsService); - const extensionTipsChannel = new ExtensionTipsChannel(extensionTipsService); - server.registerChannel('extensionTipsService', extensionTipsChannel); - - const userDataSyncMachinesService = accessor.get(IUserDataSyncMachinesService); - const userDataSyncMachineChannel = new UserDataSyncMachinesServiceChannel(userDataSyncMachinesService); - server.registerChannel('userDataSyncMachines', userDataSyncMachineChannel); - - const authTokenService = accessor.get(IUserDataSyncAccountService); - const authTokenChannel = new UserDataSyncAccountServiceChannel(authTokenService); - server.registerChannel('userDataSyncAccount', authTokenChannel); - - const userDataSyncStoreManagementService = accessor.get(IUserDataSyncStoreManagementService); - const userDataSyncStoreManagementChannel = new UserDataSyncStoreManagementServiceChannel(userDataSyncStoreManagementService); - server.registerChannel('userDataSyncStoreManagement', userDataSyncStoreManagementChannel); - - const userDataSyncService = accessor.get(IUserDataSyncService); - const userDataSyncChannel = new UserDataSyncChannel(server, userDataSyncService, logService); - server.registerChannel('userDataSync', userDataSyncChannel); - - const userDataAutoSync = instantiationService2.createInstance(UserDataAutoSyncService); - const userDataAutoSyncChannel = new UserDataAutoSyncChannel(userDataAutoSync); - server.registerChannel('userDataAutoSync', userDataAutoSyncChannel); - - // clean up deprecated extensions - (extensionManagementService as ExtensionManagementService).removeDeprecatedExtensions(); - // update localizations cache - (localizationsService as LocalizationsService).update(); - // cache clean ups - disposables.add(combinedDisposable( - new NodeCachedDataCleaner(initData.nodeCachedDataDir), - instantiationService2.createInstance(LanguagePackCachedDataCleaner), - instantiationService2.createInstance(StorageDataCleaner, initData.backupWorkspacesPath), - instantiationService2.createInstance(LogsDataCleaner), - userDataAutoSync - )); - disposables.add(extensionManagementService as ExtensionManagementService); - }); - }); -} - -function setupIPC(hook: string): Promise { - function setup(retry: boolean): Promise { - return serve(hook).then(null, err => { - if (!retry || platform.isWindows || err.code !== 'EADDRINUSE') { - return Promise.reject(err); - } - - // should retry, not windows and eaddrinuse - - return connect(hook, '').then( - client => { - // we could connect to a running instance. this is not good, abort - client.dispose(); - return Promise.reject(new Error('There is an instance already running.')); - }, - err => { - // it happens on Linux and OS X that the pipe is left behind - // let's delete it, since we can't connect to it - // and the retry the whole thing - try { - fs.unlinkSync(hook); - } catch (e) { - return Promise.reject(new Error('Error deleting the shared ipc hook.')); - } - - return setup(false); - } - ); - }); + return new InstantiationService(services); } - return setup(true); + private initChannels(accessor: ServicesAccessor): void { + + // Extensions Management + const extensionManagementService = accessor.get(IExtensionManagementService); + const channel = new ExtensionManagementChannel(extensionManagementService, () => null); + this.server.registerChannel('extensions', channel); + + // Localizations + const localizationsService = accessor.get(ILocalizationsService); + const localizationsChannel = createChannelReceiver(localizationsService); + this.server.registerChannel('localizations', localizationsChannel); + + // Diagnostics + const diagnosticsService = accessor.get(IDiagnosticsService); + const diagnosticsChannel = createChannelReceiver(diagnosticsService); + this.server.registerChannel('diagnostics', diagnosticsChannel); + + // Extension Tips + const extensionTipsService = accessor.get(IExtensionTipsService); + const extensionTipsChannel = new ExtensionTipsChannel(extensionTipsService); + this.server.registerChannel('extensionTipsService', extensionTipsChannel); + + // Settings Sync + const userDataSyncMachinesService = accessor.get(IUserDataSyncMachinesService); + const userDataSyncMachineChannel = new UserDataSyncMachinesServiceChannel(userDataSyncMachinesService); + this.server.registerChannel('userDataSyncMachines', userDataSyncMachineChannel); + + const authTokenService = accessor.get(IUserDataSyncAccountService); + const authTokenChannel = new UserDataSyncAccountServiceChannel(authTokenService); + this.server.registerChannel('userDataSyncAccount', authTokenChannel); + + const userDataSyncStoreManagementService = accessor.get(IUserDataSyncStoreManagementService); + const userDataSyncStoreManagementChannel = new UserDataSyncStoreManagementServiceChannel(userDataSyncStoreManagementService); + this.server.registerChannel('userDataSyncStoreManagement', userDataSyncStoreManagementChannel); + + const userDataSyncService = accessor.get(IUserDataSyncService); + const userDataSyncChannel = new UserDataSyncChannel(this.server, userDataSyncService, accessor.get(ILogService)); + this.server.registerChannel('userDataSync', userDataSyncChannel); + + const userDataAutoSync = this._register(accessor.get(IInstantiationService).createInstance(UserDataAutoSyncService)); + const userDataAutoSyncChannel = new UserDataAutoSyncChannel(userDataAutoSync); + this.server.registerChannel('userDataAutoSync', userDataAutoSyncChannel); + } + + private registerErrorHandler(logService: ILogService): void { + + // Listen on unhandled rejection events + window.addEventListener('unhandledrejection', (event: PromiseRejectionEvent) => { + + // See https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent + onUnexpectedError(event.reason); + + // Prevent the printing of this event to the console + event.preventDefault(); + }); + + // Install handler for unexpected errors + setUnexpectedErrorHandler(error => { + const message = toErrorMessage(error, true); + if (!message) { + return; + } + + logService.error(`[uncaught exception in sharedProcess]: ${message}`); + }); + } } -async function handshake(configuration: ISharedProcessConfiguration): Promise { +export async function main(configuration: ISharedProcessConfiguration): Promise { - // receive payload from electron-main to start things - const data = await new Promise(c => { - ipcRenderer.once('vscode:electron-main->shared-process=payload', (event: unknown, r: ISharedProcessInitData) => c(r)); - - // tell electron-main we are ready to receive payload - ipcRenderer.send('vscode:shared-process->electron-main=ready-for-payload'); - }); - - // await IPC connection and signal this back to electron-main - const server = await setupIPC(data.sharedIPCHandle); + // create shared process and signal back to main that we are + // ready to accept message ports as client connections + const sharedProcess = new SharedProcessMain(configuration); ipcRenderer.send('vscode:shared-process->electron-main=ipc-ready'); // await initialization and signal this back to electron-main - await main(server, data, configuration); + await sharedProcess.open(); ipcRenderer.send('vscode:shared-process->electron-main=init-done'); } diff --git a/src/vs/code/electron-browser/workbench/workbench.html b/src/vs/code/electron-browser/workbench/workbench.html index 40737461d..f36737f2b 100644 --- a/src/vs/code/electron-browser/workbench/workbench.html +++ b/src/vs/code/electron-browser/workbench/workbench.html @@ -3,7 +3,8 @@ - + + diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js index 14c699461..64082146a 100644 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ b/src/vs/code/electron-browser/workbench/workbench.js @@ -12,8 +12,7 @@ const bootstrapWindow = bootstrapWindowLib(); // Add a perf entry right from the top - const perf = bootstrapWindow.perfLib(); - perf.mark('renderer/started'); + performance.mark('code/didStartRenderer'); // Load workbench main JS, CSS and NLS all in parallel. This is an // optimization to prevent a waterfall of loading to happen, because @@ -24,10 +23,10 @@ 'vs/nls!vs/workbench/workbench.desktop.main', 'vs/css!vs/workbench/workbench.desktop.main' ], - async function (workbench, configuration) { + function (_, configuration) { // Mark start of workbench - perf.mark('didLoadWorkbenchMain'); + performance.mark('code/didLoadWorkbenchMain'); // @ts-ignore return require('vs/workbench/electron-browser/desktop.main').main(configuration); @@ -41,19 +40,34 @@ loaderConfig.recordStats = true; }, beforeRequire: function () { - perf.mark('willLoadWorkbenchMain'); + performance.mark('code/willLoadWorkbenchMain'); } } ); + // add default trustedTypes-policy for logging and to workaround + // lib/platform limitations + window.trustedTypes?.createPolicy('default', { + createHTML(value) { + // see https://github.com/electron/electron/issues/27211 + // Electron webviews use a static innerHTML default value and + // that isn't trusted. We use a default policy to check for the + // exact value of that innerHTML-string and only allow that. + if (value === '') { + return value; + } + throw new Error('UNTRUSTED html usage, default trusted types policy should NEVER be reached'); + // console.trace('UNTRUSTED html usage, default trusted types policy should NEVER be reached'); + // return value; + } + }); //region Helpers /** * @returns {{ - * load: (modules: string[], resultCallback: (result, configuration: object) => any, options: object) => unknown, - * globals: () => typeof import('../../../base/parts/sandbox/electron-sandbox/globals'), - * perfLib: () => { mark: (name: string) => void } + * load: (modules: string[], resultCallback: (result, configuration: import('../../../platform/windows/common/windows').INativeWindowConfiguration) => any, options: object) => unknown, + * globals: () => typeof import('../../../base/parts/sandbox/electron-sandbox/globals') * }} */ function bootstrapWindowLib() { @@ -67,12 +81,11 @@ * colorScheme: ('light' | 'dark' | 'hc'), * autoDetectHighContrast?: boolean, * extensionDevelopmentPath?: string[], - * folderUri?: object, - * workspace?: object + * workspace?: import('../../../platform/workspaces/common/workspaces').IWorkspaceIdentifier | import('../../../platform/workspaces/common/workspaces').ISingleFolderWorkspaceIdentifier * }} configuration */ function showPartsSplash(configuration) { - perf.mark('willShowPartsSplash'); + performance.mark('code/willShowPartsSplash'); let data; if (typeof configuration.partsSplashPath === 'string') { @@ -147,7 +160,7 @@ splash.appendChild(activityDiv); // part: side bar (only when opening workspace/folder) - if (configuration.folderUri || configuration.workspace) { + if (configuration.workspace) { // folder or workspace -> status bar color, sidebar const sideDiv = document.createElement('div'); sideDiv.setAttribute('style', `position: absolute; height: calc(100% - ${layoutInfo.titleBarHeight}px); top: ${layoutInfo.titleBarHeight}px; ${layoutInfo.sideBarSide}: ${layoutInfo.activityBarWidth}px; width: ${layoutInfo.sideBarWidth}px; background-color: ${colorInfo.sideBarBackground};`); @@ -156,13 +169,13 @@ // part: statusbar const statusDiv = document.createElement('div'); - statusDiv.setAttribute('style', `position: absolute; width: 100%; bottom: 0; left: 0; height: ${layoutInfo.statusBarHeight}px; background-color: ${configuration.folderUri || configuration.workspace ? colorInfo.statusBarBackground : colorInfo.statusBarNoFolderBackground};`); + statusDiv.setAttribute('style', `position: absolute; width: 100%; bottom: 0; left: 0; height: ${layoutInfo.statusBarHeight}px; background-color: ${configuration.workspace ? colorInfo.statusBarBackground : colorInfo.statusBarNoFolderBackground};`); splash.appendChild(statusDiv); document.body.appendChild(splash); } - perf.mark('didShowPartsSplash'); + performance.mark('code/didShowPartsSplash'); } //#endregion diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 759c85bc7..3f85072d5 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -4,17 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import { app, ipcMain, systemPreferences, contentTracing, protocol, BrowserWindow, dialog, session } from 'electron'; -import { IProcessEnvironment, isWindows, isMacintosh, isLinux } from 'vs/base/common/platform'; +import { release } from 'os'; +import { IProcessEnvironment, isWindows, isMacintosh, isLinux, isLinuxSnap } from 'vs/base/common/platform'; import { WindowsMainService } from 'vs/platform/windows/electron-main/windowsMainService'; import { IWindowOpenable } from 'vs/platform/windows/common/windows'; -import { OpenContext } from 'vs/platform/windows/node/window'; import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { resolveShellEnv } from 'vs/code/node/shellEnv'; import { IUpdateService } from 'vs/platform/update/common/update'; import { UpdateChannel } from 'vs/platform/update/electron-main/updateIpc'; -import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron-main'; -import { Client } from 'vs/base/parts/ipc/common/ipc.net'; -import { Server, connect } from 'vs/base/parts/ipc/node/ipc.net'; +import { getDelayedChannel, StaticRouter, createChannelReceiver, createChannelSender } from 'vs/base/parts/ipc/common/ipc'; +import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron'; +import { Server as NodeIPCServer } from 'vs/base/parts/ipc/node/ipc.net'; +import { Client as MessagePortClient } from 'vs/base/parts/ipc/electron-main/ipc.mp'; import { SharedProcess } from 'vs/code/electron-main/sharedProcess'; import { LaunchMainService, ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -28,19 +29,17 @@ import { IOpenURLOptions, IURLService } from 'vs/platform/url/common/url'; import { URLHandlerChannelClient, URLHandlerRouter } from 'vs/platform/url/common/urlIpc'; import { ITelemetryService, machineIdKey } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc'; +import { TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc'; import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; -import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; -import { getDelayedChannel, StaticRouter, createChannelReceiver, createChannelSender } from 'vs/base/parts/ipc/common/ipc'; +import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; import product from 'vs/platform/product/common/product'; import { ProxyAuthHandler } from 'vs/code/electron-main/auth'; -import { ProxyAuthHandler2 } from 'vs/code/electron-main/auth2'; import { FileProtocolHandler } from 'vs/code/electron-main/protocol'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; +import { IWindowsMainService, ICodeWindow, OpenContext } from 'vs/platform/windows/electron-main/windows'; import { URI } from 'vs/base/common/uri'; import { hasWorkspaceFileExtension, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; -import { WorkspacesService } from 'vs/platform/workspaces/electron-main/workspacesService'; +import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { getMachineId } from 'vs/base/node/id'; import { Win32UpdateService } from 'vs/platform/update/electron-main/updateService.win32'; import { LinuxUpdateService } from 'vs/platform/update/electron-main/updateService.linux'; @@ -64,14 +63,13 @@ import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainSe import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; import { WorkspacesHistoryMainService, IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; import { NativeURLService } from 'vs/platform/url/common/urlService'; -import { WorkspacesMainService, IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; +import { WorkspacesManagementMainService, IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; import { statSync } from 'fs'; import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; import { ElectronExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/electron-main/extensionHostDebugIpc'; import { INativeHostMainService, NativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService'; -import { ISharedProcessMainService, SharedProcessMainService } from 'vs/platform/ipc/electron-main/sharedProcessMainService'; -import { IDialogMainService, DialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; +import { IDialogMainService, DialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService'; import { withNullAsUndefined } from 'vs/base/common/types'; import { mnemonicButtonLabel, getPathLabel } from 'vs/base/common/labels'; import { WebviewMainService } from 'vs/platform/webview/electron-main/webviewMainService'; @@ -81,7 +79,7 @@ import { stripComments } from 'vs/base/common/json'; import { generateUuid } from 'vs/base/common/uuid'; import { VSBuffer } from 'vs/base/common/buffer'; import { EncryptionMainService, IEncryptionMainService } from 'vs/platform/encryption/electron-main/encryptionMainService'; -import { ActiveWindowManager } from 'vs/platform/windows/common/windowTracker'; +import { ActiveWindowManager } from 'vs/platform/windows/node/windowTracker'; import { IKeyboardLayoutMainService, KeyboardLayoutMainService } from 'vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { DisplayMainService, IDisplayMainService } from 'vs/platform/display/electron-main/displayMainService'; @@ -90,6 +88,7 @@ import { isEqualOrParent } from 'vs/base/common/extpath'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust'; import { ExtensionUrlTrustService } from 'vs/platform/extensionManagement/node/extensionUrlTrustService'; +import { once } from 'vs/base/common/functional'; export class CodeApplication extends Disposable { private windowsMainService: IWindowsMainService | undefined; @@ -97,7 +96,7 @@ export class CodeApplication extends Disposable { private nativeHostMainService: INativeHostMainService | undefined; constructor( - private readonly mainIpcServer: Server, + private readonly mainIpcServer: NodeIPCServer, private readonly userEnv: IProcessEnvironment, @IInstantiationService private readonly instantiationService: IInstantiationService, @ILogService private readonly logService: ILogService, @@ -150,19 +149,19 @@ export class CodeApplication extends Disposable { event.preventDefault(); }); app.on('remote-get-global', (event, sender, module) => { - this.logService.trace(`App#on(remote-get-global): prevented on ${module}`); + this.logService.trace(`app#on(remote-get-global): prevented on ${module}`); event.preventDefault(); }); app.on('remote-get-builtin', (event, sender, module) => { - this.logService.trace(`App#on(remote-get-builtin): prevented on ${module}`); + this.logService.trace(`app#on(remote-get-builtin): prevented on ${module}`); if (module !== 'clipboard') { event.preventDefault(); } }); app.on('remote-get-current-window', event => { - this.logService.trace(`App#on(remote-get-current-window): prevented`); + this.logService.trace(`app#on(remote-get-current-window): prevented`); event.preventDefault(); }); @@ -171,7 +170,7 @@ export class CodeApplication extends Disposable { return; // the driver needs access to web contents } - this.logService.trace(`App#on(remote-get-current-web-contents): prevented`); + this.logService.trace(`app#on(remote-get-current-web-contents): prevented`); event.preventDefault(); }); @@ -271,9 +270,13 @@ export class CodeApplication extends Disposable { //#region Bootstrap IPC Handlers + let slowShellResolveWarningShown = false; ipcMain.on('vscode:fetchShellEnv', async event => { + + // DO NOT remove: not only usual windows are fetching the + // shell environment but also shared process, issue reporter + // etc, so we need to reply via `webContents` always const webContents = event.sender; - const window = this.windowsMainService?.getWindowByWebContents(event.sender); let replied = false; @@ -291,11 +294,19 @@ export class CodeApplication extends Disposable { } // Handle slow shell environment resolve calls: - // - a warning after 3s but continue to resolve - // - an error after 10s and stop trying to resolve + // - a warning after 3s but continue to resolve (only once in active window) + // - an error after 10s and stop trying to resolve (in every window where this happens) const cts = new CancellationTokenSource(); - const shellEnvSlowWarningHandle = setTimeout(() => window?.sendWhenReady('vscode:showShellEnvSlowWarning', cts.token), 3000); - const shellEnvTimeoutErrorHandle = setTimeout(function () { + + const shellEnvSlowWarningHandle = setTimeout(() => { + if (!slowShellResolveWarningShown) { + this.windowsMainService?.sendToFocused('vscode:showShellEnvSlowWarning', cts.token); + slowShellResolveWarningShown = true; + } + }, 3000); + + const window = this.windowsMainService?.getWindowByWebContents(event.sender); // Note: this can be `undefined` for the shared process!! + const shellEnvTimeoutErrorHandle = setTimeout(() => { cts.dispose(true); window?.sendWhenReady('vscode:showShellEnvTimeoutError', CancellationToken.None); acceptShellEnv({}); @@ -304,8 +315,11 @@ export class CodeApplication extends Disposable { // Prefer to use the args and env from the target window // when resolving the shell env. It is possible that // a first window was opened from the UI but a second - // from the CLI and that has implications for wether to + // from the CLI and that has implications for whether to // resolve the shell environment or not. + // + // Window can be undefined for e.g. the shared process + // that is not part of our windows registry! let args: NativeParsedArgs; let env: NodeJS.ProcessEnv; if (window?.config) { @@ -321,10 +335,10 @@ export class CodeApplication extends Disposable { acceptShellEnv(shellEnv); }); - ipcMain.handle('vscode:writeNlsFile', (event, path: unknown, data: unknown) => { + ipcMain.handle('vscode:writeNlsFile', async (event, path: unknown, data: unknown) => { const uri = this.validateNlsPath([path]); if (!uri || typeof data !== 'string') { - return Promise.reject('Invalid operation (vscode:writeNlsFile)'); + throw new Error('Invalid operation (vscode:writeNlsFile)'); } return this.fileService.writeFile(uri, VSBuffer.fromString(data)); @@ -333,7 +347,7 @@ export class CodeApplication extends Disposable { ipcMain.handle('vscode:readNlsFile', async (event, ...paths: unknown[]) => { const uri = this.validateNlsPath(paths); if (!uri) { - return Promise.reject('Invalid operation (vscode:readNlsFile)'); + throw new Error('Invalid operation (vscode:readNlsFile)'); } return (await this.fileService.readFile(uri)).value.toString(); @@ -427,16 +441,20 @@ export class CodeApplication extends Disposable { // Spawn shared process after the first window has opened and 3s have passed const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv); - const sharedProcessClient = sharedProcess.whenIpcReady().then(() => { - this.logService.trace('Shared process: IPC ready'); + const sharedProcessClient = (async () => { + this.logService.trace('Main->SharedProcess#connect'); - return connect(this.environmentService.sharedIPCHandle, 'main'); - }); - const sharedProcessReady = sharedProcess.whenReady().then(() => { - this.logService.trace('Shared process: init ready'); + const port = await sharedProcess.connect(); + + this.logService.trace('Main->SharedProcess#connect: connection established'); + + return new MessagePortClient(port, 'main'); + })(); + const sharedProcessReady = (async () => { + await sharedProcess.whenReady(); return sharedProcessClient; - }); + })(); this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => { this._register(new RunOnceScheduler(async () => { sharedProcess.spawn(await resolveShellEnv(this.logService, this.environmentService.args, process.env)); @@ -454,12 +472,8 @@ export class CodeApplication extends Disposable { this._register(server); } - // Setup Auth Handler (TODO@ben remove old auth handler eventually) - if (this.configurationService.getValue('window.enableExperimentalProxyLoginDialog') === false) { - this._register(new ProxyAuthHandler()); - } else { - this._register(appInstantiationService.createInstance(ProxyAuthHandler2)); - } + // Setup Auth Handler + this._register(appInstantiationService.createInstance(ProxyAuthHandler)); // Open Windows const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient, fileProtocolHandler)); @@ -487,7 +501,7 @@ export class CodeApplication extends Disposable { return machineId; } - private async createServices(machineId: string, sharedProcess: SharedProcess, sharedProcessReady: Promise>): Promise { + private async createServices(machineId: string, sharedProcess: SharedProcess, sharedProcessReady: Promise): Promise { const services = new ServiceCollection(); switch (process.platform) { @@ -496,8 +510,8 @@ export class CodeApplication extends Disposable { break; case 'linux': - if (process.env.SNAP && process.env.SNAP_REVISION) { - services.set(IUpdateService, new SyncDescriptor(SnapUpdateService, [process.env.SNAP, process.env.SNAP_REVISION])); + if (isLinuxSnap) { + services.set(IUpdateService, new SyncDescriptor(SnapUpdateService, [process.env['SNAP'], process.env['SNAP_REVISION']])); } else { services.set(IUpdateService, new SyncDescriptor(LinuxUpdateService)); } @@ -510,7 +524,6 @@ export class CodeApplication extends Disposable { services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, this.userEnv])); services.set(IDialogMainService, new SyncDescriptor(DialogMainService)); - services.set(ISharedProcessMainService, new SyncDescriptor(SharedProcessMainService, [sharedProcess])); services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService)); services.set(IDiagnosticsService, createChannelSender(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics'))))); @@ -518,9 +531,9 @@ export class CodeApplication extends Disposable { services.set(IEncryptionMainService, new SyncDescriptor(EncryptionMainService, [machineId])); services.set(IKeyboardLayoutMainService, new SyncDescriptor(KeyboardLayoutMainService)); services.set(IDisplayMainService, new SyncDescriptor(DisplayMainService)); - services.set(INativeHostMainService, new SyncDescriptor(NativeHostMainService)); + services.set(INativeHostMainService, new SyncDescriptor(NativeHostMainService, [sharedProcess])); services.set(IWebviewManagerService, new SyncDescriptor(WebviewMainService)); - services.set(IWorkspacesService, new SyncDescriptor(WorkspacesService)); + services.set(IWorkspacesService, new SyncDescriptor(WorkspacesMainService)); services.set(IMenubarMainService, new SyncDescriptor(MenubarMainService)); services.set(IExtensionUrlTrustService, new SyncDescriptor(ExtensionUrlTrustService)); @@ -533,13 +546,13 @@ export class CodeApplication extends Disposable { services.set(IWorkspacesHistoryMainService, new SyncDescriptor(WorkspacesHistoryMainService)); services.set(IURLService, new SyncDescriptor(NativeURLService)); - services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService)); + services.set(IWorkspacesManagementMainService, new SyncDescriptor(WorkspacesManagementMainService)); // Telemetry if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) { const channel = getDelayedChannel(sharedProcessReady.then(client => client.getChannel('telemetryAppender'))); const appender = new TelemetryAppenderClient(channel); - const commonProperties = resolveCommonProperties(product.commit, product.version, machineId, product.msftInternalDomains, this.environmentService.installSourcePath); + const commonProperties = resolveCommonProperties(this.fileService, release(), process.arch, product.commit, product.version, machineId, product.msftInternalDomains, this.environmentService.installSourcePath); const piiPaths = [this.environmentService.appRoot, this.environmentService.extensionsPath]; const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, sendErrorTelemetry: true }; @@ -589,7 +602,7 @@ export class CodeApplication extends Disposable { }); } - private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise>, fileProtocolHandler: FileProtocolHandler): ICodeWindow[] { + private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise, fileProtocolHandler: FileProtocolHandler): ICodeWindow[] { // Register more Main IPC services const launchMainService = accessor.get(ILaunchMainService); @@ -622,10 +635,6 @@ export class CodeApplication extends Disposable { electronIpcServer.registerChannel('nativeHost', nativeHostChannel); sharedProcessClient.then(client => client.registerChannel('nativeHost', nativeHostChannel)); - const sharedProcessMainService = accessor.get(ISharedProcessMainService); - const sharedProcessChannel = createChannelReceiver(sharedProcessMainService); - electronIpcServer.registerChannel('sharedProcess', sharedProcessChannel); - const workspacesService = accessor.get(IWorkspacesService); const workspacesChannel = createChannelReceiver(workspacesService); electronIpcServer.registerChannel('workspaces', workspacesChannel); @@ -705,6 +714,8 @@ export class CodeApplication extends Disposable { }); // Create a URL handler to open file URIs in the active window + // or open new windows. The URL handler will be invoked from + // protocol invocations outside of VSCode. const app = this; const environmentService = this.environmentService; urlService.registerHandler({ @@ -718,13 +729,15 @@ export class CodeApplication extends Disposable { // Check for URIs to open in window const windowOpenableFromProtocolLink = app.getWindowOpenableFromProtocolLink(uri); if (windowOpenableFromProtocolLink) { - windowsMainService.open({ + const [window] = windowsMainService.open({ context: OpenContext.API, cli: { ...environmentService.args }, urisToOpen: [windowOpenableFromProtocolLink], gotoLineMode: true }); + window.focus(); // this should help ensuring that the right window gets focus when multiple are opened + return true; } @@ -832,7 +845,7 @@ export class CodeApplication extends Disposable { mnemonicButtonLabel(localize({ key: 'cancel', comment: ['&& denotes a mnemonic'] }, "&&No")), ], cancelId: 1, - message: localize('confirmOpenMessage', "An external application wants to open '{0}' in {1}. Do you want to open this file or folder?", getPathLabel(uri.fsPath), product.nameShort), + message: localize('confirmOpenMessage', "An external application wants to open '{0}' in {1}. Do you want to open this file or folder?", getPathLabel(uri.fsPath, this.environmentService), product.nameShort), detail: localize('confirmOpenDetail', "If you did not initiate this request, it may represent an attempted attack on your system. Unless you took an explicit action to initiate this request, you should press 'No'"), noLink: true }); @@ -901,8 +914,25 @@ export class CodeApplication extends Disposable { // Signal phase: after window open this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen; + // Windows: install mutex + const win32MutexName = product.win32MutexName; + if (isWindows && win32MutexName) { + try { + const WindowsMutex = (require.__$__nodeRequire('windows-mutex') as typeof import('windows-mutex')).Mutex; + const mutex = new WindowsMutex(win32MutexName); + once(this.lifecycleMainService.onWillShutdown)(() => mutex.release()); + } catch (error) { + this.logService.error(error); + } + } + // Remote Authorities - this.handleRemoteAuthorities(); + protocol.registerHttpProtocol(Schemas.vscodeRemoteResource, (request, callback) => { + callback({ + url: request.url.replace(/^vscode-remote-resource:/, 'http:'), + method: request.method + }); + }); // Initialize update service const updateService = accessor.get(IUpdateService); @@ -934,19 +964,11 @@ export class CodeApplication extends Disposable { '}' ]; const newArgvString = argvString.substring(0, argvString.length - 2).concat(',\n', additionalArgvContent.join('\n')); + await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(newArgvString)); } } catch (error) { this.logService.error(error); } } - - private handleRemoteAuthorities(): void { - protocol.registerHttpProtocol(Schemas.vscodeRemoteResource, (request, callback) => { - callback({ - url: request.url.replace(/^vscode-remote-resource:/, 'http:'), - method: request.method - }); - }); - } } diff --git a/src/vs/code/electron-main/auth.ts b/src/vs/code/electron-main/auth.ts index b40960186..b9669f983 100644 --- a/src/vs/code/electron-main/auth.ts +++ b/src/vs/code/electron-main/auth.ts @@ -3,18 +3,28 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; -import { FileAccess } from 'vs/base/common/network'; -import { BrowserWindow, BrowserWindowConstructorOptions, app, AuthInfo, WebContents, Event as ElectronEvent } from 'electron'; +import { hash } from 'vs/base/common/hash'; +import { app, AuthInfo, WebContents, Event as ElectronEvent, AuthenticationResponseDetails } from 'electron'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; +import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService'; +import { IEncryptionMainService } from 'vs/platform/encryption/electron-main/encryptionMainService'; +import { generateUuid } from 'vs/base/common/uuid'; +import product from 'vs/platform/product/common/product'; +import { CancellationToken } from 'vs/base/common/cancellation'; + +interface ElectronAuthenticationResponseDetails extends AuthenticationResponseDetails { + firstAuthAttempt?: boolean; // https://github.com/electron/electron/blob/84a42a050e7d45225e69df5bd2d2bf9f1037ea41/shell/browser/login_handler.cc#L70 +} type LoginEvent = { event: ElectronEvent; - webContents: WebContents; - req: Request; authInfo: AuthInfo; - cb: (username: string, password: string) => void; + req: ElectronAuthenticationResponseDetails; + + callback: (username?: string, password?: string) => void; }; type Credentials = { @@ -22,81 +32,211 @@ type Credentials = { password: string; }; +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 class ProxyAuthHandler extends Disposable { - declare readonly _serviceBrand: undefined; + private static PROXY_CREDENTIALS_SERVICE_KEY = `${product.urlProtocol}.proxy-credentials`; - private retryCount = 0; + private pendingProxyResolve: Promise | undefined = undefined; - constructor() { + private state = ProxyAuthState.Initial; + + private sessionCredentials: Credentials | undefined = undefined; + + constructor( + @ILogService private readonly logService: ILogService, + @IWindowsMainService private readonly windowsMainService: IWindowsMainService, + @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService, + @IEncryptionMainService private readonly encryptionMainService: IEncryptionMainService + ) { super(); this.registerListeners(); } private registerListeners(): void { - const onLogin = Event.fromNodeEventEmitter(app, 'login', (event, webContents, req, authInfo, cb) => ({ event, webContents, req, authInfo, cb })); + const onLogin = Event.fromNodeEventEmitter(app, 'login', (event: ElectronEvent, webContents: WebContents, req: ElectronAuthenticationResponseDetails, authInfo: AuthInfo, callback) => ({ event, webContents, req, authInfo, callback })); this._register(onLogin(this.onLogin, this)); } - private onLogin({ event, authInfo, cb }: LoginEvent): void { + private async onLogin({ event, authInfo, req, callback }: LoginEvent): Promise { if (!authInfo.isProxy) { - return; + return; // only for proxy } - if (this.retryCount++ > 1) { - return; + 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(); - const opts: BrowserWindowConstructorOptions = { - alwaysOnTop: true, - skipTaskbar: true, - resizable: false, - width: 450, - height: 225, - show: true, - title: 'VS Code', - webPreferences: { - preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, - sandbox: true, - contextIsolation: true, - enableWebSQL: false, - enableRemoteModule: false, - spellcheck: false, - devTools: false - } - }; + let credentials: Credentials | undefined = undefined; + if (!this.pendingProxyResolve) { + this.logService.trace('auth#onLogin (proxy) - no pending proxy handling found, starting new'); - const focusedWindow = BrowserWindow.getFocusedWindow(); - if (focusedWindow) { - opts.parent = focusedWindow; - opts.modal = true; + this.pendingProxyResolve = this.resolveProxyCredentials(authInfo); + try { + credentials = await this.pendingProxyResolve; + } finally { + this.pendingProxyResolve = undefined; + } + } else { + this.logService.trace('auth#onLogin (proxy) - pending proxy handling found'); + + credentials = await this.pendingProxyResolve; } - const win = new BrowserWindow(opts); - const windowUrl = FileAccess.asBrowserUri('vs/code/electron-sandbox/proxy/auth.html', require); - const proxyUrl = `${authInfo.host}:${authInfo.port}`; - const title = localize('authRequire', "Proxy Authentication Required"); - const message = localize('proxyauth', "The proxy {0} requires authentication.", proxyUrl); + // According to Electron docs, it is fine to call back without + // username or password to signal that the authentication was handled + // by us, even though without having credentials received: + // + // > 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); + } - const onWindowClose = () => cb('', ''); - win.on('close', onWindowClose); + private async resolveProxyCredentials(authInfo: AuthInfo): Promise { + this.logService.trace('auth#resolveProxyCredentials (proxy) - enter'); - win.setMenu(null); - win.webContents.on('did-finish-load', () => { - const data = { title, message }; - win.webContents.send('vscode:openProxyAuthDialog', data); - }); - win.webContents.on('ipc-message', (event, channel, credentials: Credentials) => { - if (channel === 'vscode:proxyAuthResponse') { - const { username, password } = credentials; - cb(username, password); - win.removeListener('close', onWindowClose); - win.close(); + try { + const credentials = await this.doResolveProxyCredentials(authInfo); + if (credentials) { + this.logService.trace('auth#resolveProxyCredentials (proxy) - got credentials'); + + return credentials; + } else { + this.logService.trace('auth#resolveProxyCredentials (proxy) - did not get credentials'); } + } finally { + this.logService.trace('auth#resolveProxyCredentials (proxy) - exit'); + } + + return undefined; + } + + private async doResolveProxyCredentials(authInfo: AuthInfo): 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 })); + + // Find any previously stored credentials + let storedUsername: string | undefined = undefined; + let storedPassword: string | undefined = undefined; + try { + const encryptedSerializedProxyCredentials = await this.nativeHostMainService.getPassword(undefined, ProxyAuthHandler.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash); + if (encryptedSerializedProxyCredentials) { + const credentials: Credentials = JSON.parse(await this.encryptionMainService.decrypt(encryptedSerializedProxyCredentials)); + + storedUsername = credentials.username; + storedPassword = credentials.password; + } + } catch (error) { + this.logService.error(error); // handle errors by asking user for login via dialog + } + + // 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') { + this.logService.trace('auth#doResolveProxyCredentials (proxy) - exit - found stored credentials to use'); + this.state = ProxyAuthState.StoredCredentialsUsed; + + return { username: storedUsername, password: storedPassword }; + } + + // 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. + const window = this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow(); + if (!window) { + this.logService.trace('auth#doResolveProxyCredentials (proxy) - exit - no opened window found to show dialog in'); + + return undefined; // unexpected + } + + this.logService.trace(`auth#doResolveProxyCredentials (proxy) - asking window ${window.id} to handle proxy login`); + + // Open proxy dialog + 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 + replyChannel: `vscode:proxyAuthResponse:${generateUuid()}` + }; + window.sendWhenReady('vscode:openProxyAuthenticationDialog', CancellationToken.None, payload); + this.state = ProxyAuthState.LoginDialogShown; + + // Handle reply + const loginDialogCredentials = await new Promise(resolve => { + const proxyAuthResponseHandler = async (event: ElectronEvent, channel: string, reply: Credentials & { remember: boolean } | undefined /* canceled */) => { + if (channel === payload.replyChannel) { + this.logService.trace(`auth#doResolveProxyCredentials - exit - received credentials from window ${window.id}`); + window.win.webContents.off('ipc-message', proxyAuthResponseHandler); + + // We got credentials from the window + if (reply) { + const credentials: Credentials = { username: reply.username, password: reply.password }; + + // Update stored credentials based on `remember` flag + try { + if (reply.remember) { + const encryptedSerializedCredentials = await this.encryptionMainService.encrypt(JSON.stringify(credentials)); + await this.nativeHostMainService.setPassword(undefined, ProxyAuthHandler.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash, encryptedSerializedCredentials); + } else { + await this.nativeHostMainService.deletePassword(undefined, ProxyAuthHandler.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash); + } + } catch (error) { + this.logService.error(error); // handle gracefully + } + + resolve({ username: credentials.username, password: credentials.password }); + } + + // We did not get any credentials from the window (e.g. cancelled) + else { + resolve(undefined); + } + } + }; + + window.win.webContents.on('ipc-message', proxyAuthResponseHandler); }); - win.loadURL(windowUrl.toString(true)); + + // Remember credentials for the session in case + // the credentials are wrong and we show the dialog + // again + this.sessionCredentials = loginDialogCredentials; + + return loginDialogCredentials; } } diff --git a/src/vs/code/electron-main/auth2.ts b/src/vs/code/electron-main/auth2.ts deleted file mode 100644 index 1b84d4bc4..000000000 --- a/src/vs/code/electron-main/auth2.ts +++ /dev/null @@ -1,242 +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 { Disposable } from 'vs/base/common/lifecycle'; -import { Event } from 'vs/base/common/event'; -import { hash } from 'vs/base/common/hash'; -import { app, AuthInfo, WebContents, Event as ElectronEvent, AuthenticationResponseDetails } from 'electron'; -import { ILogService } from 'vs/platform/log/common/log'; -import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; -import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService'; -import { IEncryptionMainService } from 'vs/platform/encryption/electron-main/encryptionMainService'; -import { generateUuid } from 'vs/base/common/uuid'; -import product from 'vs/platform/product/common/product'; -import { CancellationToken } from 'vs/base/common/cancellation'; - -interface ElectronAuthenticationResponseDetails extends AuthenticationResponseDetails { - firstAuthAttempt?: boolean; // https://github.com/electron/electron/blob/84a42a050e7d45225e69df5bd2d2bf9f1037ea41/shell/browser/login_handler.cc#L70 -} - -type LoginEvent = { - event: ElectronEvent; - authInfo: AuthInfo; - req: ElectronAuthenticationResponseDetails; - - callback: (username?: string, password?: string) => void; -}; - -type Credentials = { - username: string; - password: string; -}; - -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 class ProxyAuthHandler2 extends Disposable { - - private static PROXY_CREDENTIALS_SERVICE_KEY = `${product.urlProtocol}.proxy-credentials`; - - private pendingProxyResolve: Promise | undefined = undefined; - - private state = ProxyAuthState.Initial; - - private sessionCredentials: Credentials | undefined = undefined; - - constructor( - @ILogService private readonly logService: ILogService, - @IWindowsMainService private readonly windowsMainService: IWindowsMainService, - @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService, - @IEncryptionMainService private readonly encryptionMainService: IEncryptionMainService - ) { - super(); - - this.registerListeners(); - } - - private registerListeners(): void { - const onLogin = Event.fromNodeEventEmitter(app, 'login', (event: ElectronEvent, webContents: WebContents, req: ElectronAuthenticationResponseDetails, authInfo: AuthInfo, callback) => ({ event, webContents, req, authInfo, callback })); - this._register(onLogin(this.onLogin, this)); - } - - private async onLogin({ event, authInfo, req, 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(); - - let credentials: Credentials | undefined = undefined; - if (!this.pendingProxyResolve) { - this.logService.trace('auth#onLogin (proxy) - no pending proxy handling found, starting new'); - - this.pendingProxyResolve = this.resolveProxyCredentials(authInfo); - try { - credentials = await this.pendingProxyResolve; - } finally { - this.pendingProxyResolve = undefined; - } - } else { - this.logService.trace('auth#onLogin (proxy) - pending proxy handling found'); - - credentials = await this.pendingProxyResolve; - } - - // According to Electron docs, it is fine to call back without - // username or password to signal that the authentication was handled - // by us, even though without having credentials received: - // - // > 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); - } - - private async resolveProxyCredentials(authInfo: AuthInfo): Promise { - this.logService.trace('auth#resolveProxyCredentials (proxy) - enter'); - - try { - const credentials = await this.doResolveProxyCredentials(authInfo); - if (credentials) { - this.logService.trace('auth#resolveProxyCredentials (proxy) - got credentials'); - - return credentials; - } else { - this.logService.trace('auth#resolveProxyCredentials (proxy) - did not get credentials'); - } - } finally { - this.logService.trace('auth#resolveProxyCredentials (proxy) - exit'); - } - - return undefined; - } - - private async doResolveProxyCredentials(authInfo: AuthInfo): 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 })); - - // Find any previously stored credentials - let storedUsername: string | undefined = undefined; - let storedPassword: string | undefined = undefined; - try { - const encryptedSerializedProxyCredentials = await this.nativeHostMainService.getPassword(undefined, ProxyAuthHandler2.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash); - if (encryptedSerializedProxyCredentials) { - const credentials: Credentials = JSON.parse(await this.encryptionMainService.decrypt(encryptedSerializedProxyCredentials)); - - storedUsername = credentials.username; - storedPassword = credentials.password; - } - } catch (error) { - this.logService.error(error); // handle errors by asking user for login via dialog - } - - // 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') { - this.logService.trace('auth#doResolveProxyCredentials (proxy) - exit - found stored credentials to use'); - this.state = ProxyAuthState.StoredCredentialsUsed; - - return { username: storedUsername, password: storedPassword }; - } - - // 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. - const window = this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow(); - if (!window) { - this.logService.trace('auth#doResolveProxyCredentials (proxy) - exit - no opened window found to show dialog in'); - - return undefined; // unexpected - } - - this.logService.trace(`auth#doResolveProxyCredentials (proxy) - asking window ${window.id} to handle proxy login`); - - // Open proxy dialog - 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 - replyChannel: `vscode:proxyAuthResponse:${generateUuid()}` - }; - window.sendWhenReady('vscode:openProxyAuthenticationDialog', CancellationToken.None, payload); - this.state = ProxyAuthState.LoginDialogShown; - - // Handle reply - const loginDialogCredentials = await new Promise(resolve => { - const proxyAuthResponseHandler = async (event: ElectronEvent, channel: string, reply: Credentials & { remember: boolean } | undefined /* canceled */) => { - if (channel === payload.replyChannel) { - this.logService.trace(`auth#doResolveProxyCredentials - exit - received credentials from window ${window.id}`); - window.win.webContents.off('ipc-message', proxyAuthResponseHandler); - - // We got credentials from the window - if (reply) { - const credentials: Credentials = { username: reply.username, password: reply.password }; - - // Update stored credentials based on `remember` flag - try { - if (reply.remember) { - const encryptedSerializedCredentials = await this.encryptionMainService.encrypt(JSON.stringify(credentials)); - await this.nativeHostMainService.setPassword(undefined, ProxyAuthHandler2.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash, encryptedSerializedCredentials); - } else { - await this.nativeHostMainService.deletePassword(undefined, ProxyAuthHandler2.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash); - } - } catch (error) { - this.logService.error(error); // handle gracefully - } - - resolve({ username: credentials.username, password: credentials.password }); - } - - // We did not get any credentials from the window (e.g. cancelled) - else { - resolve(undefined); - } - } - }; - - window.win.webContents.on('ipc-message', proxyAuthResponseHandler); - }); - - // Remember credentials for the session in case - // the credentials are wrong and we show the dialog - // again - this.sessionCredentials = loginDialogCredentials; - - return loginDialogCredentials; - } -} diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 783ac6fb5..f0d8b6d2d 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -5,15 +5,17 @@ import 'vs/platform/update/common/update.config.contribution'; import { app, dialog } from 'electron'; -import * as fs from 'fs'; +import { unlinkSync } from 'fs'; +import { localize } from 'vs/nls'; import { isWindows, IProcessEnvironment, isMacintosh } from 'vs/base/common/platform'; import product from 'vs/platform/product/common/product'; import { parseMainProcessArgv, addArg } from 'vs/platform/environment/node/argvHelper'; import { createWaitMarkerFile } from 'vs/platform/environment/node/waitMarkerFile'; import { mkdirp } from 'vs/base/node/pfs'; import { LifecycleMainService, ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import { Server, serve, connect, XDG_RUNTIME_DIR } from 'vs/base/parts/ipc/node/ipc.net'; import { createChannelSender } from 'vs/base/parts/ipc/common/ipc'; +import { Server as NodeIPCServer, serve as nodeIPCServe, connect as nodeIPCConnect, XDG_RUNTIME_DIR } from 'vs/base/parts/ipc/node/ipc.net'; +import { Client as NodeIPCClient } from 'vs/base/parts/ipc/common/ipc.net'; import { ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; @@ -29,17 +31,15 @@ import { ConfigurationService } from 'vs/platform/configuration/common/configura import { IRequestService } from 'vs/platform/request/common/request'; import { RequestMainService } from 'vs/platform/request/electron-main/requestMainService'; import { CodeApplication } from 'vs/code/electron-main/app'; -import { localize } from 'vs/nls'; -import { mnemonicButtonLabel } from 'vs/base/common/labels'; +import { getPathLabel, mnemonicButtonLabel } from 'vs/base/common/labels'; import { SpdLogService } from 'vs/platform/log/node/spdlogService'; import { BufferLogService } from 'vs/platform/log/common/bufferLog'; import { setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; -import { Client } from 'vs/base/parts/ipc/common/ipc.net'; import { once } from 'vs/base/common/functional'; import { ISignService } from 'vs/platform/sign/common/sign'; import { SignService } from 'vs/platform/sign/node/signService'; -import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; +import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; import { FileService } from 'vs/platform/files/common/fileService'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; @@ -53,6 +53,8 @@ import { rtrim, trim } from 'vs/base/common/strings'; import { basename, resolve } from 'vs/base/common/path'; import { coalesce, distinct } from 'vs/base/common/arrays'; import { EnvironmentMainService, IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; class ExpectedError extends Error { readonly isExpected = true; @@ -212,14 +214,14 @@ class CodeMain { return instanceEnvironment; } - private async doStartup(args: NativeParsedArgs, logService: ILogService, environmentService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, retry: boolean): Promise { + private async doStartup(args: NativeParsedArgs, logService: ILogService, environmentService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, retry: boolean): Promise { // Try to setup a server for running. If that succeeds it means // we are the first instance to startup. Otherwise it is likely // that another instance is already running. - let server: Server; + let server: NodeIPCServer; try { - server = await serve(environmentService.mainIPCHandle); + server = await nodeIPCServe(environmentService.mainIPCHandle); once(lifecycleMainService.onWillShutdown)(() => server.dispose()); } catch (error) { @@ -235,9 +237,9 @@ class CodeMain { } // there's a running instance, let's connect to it - let client: Client; + let client: NodeIPCClient; try { - client = await connect(environmentService.mainIPCHandle, 'main'); + client = await nodeIPCConnect(environmentService.mainIPCHandle, 'main'); } catch (error) { // Handle unexpected connection errors by showing a dialog to the user @@ -256,7 +258,7 @@ class CodeMain { // let's delete it, since we can't connect to it and then // retry the whole thing try { - fs.unlinkSync(environmentService.mainIPCHandle); + unlinkSync(environmentService.mainIPCHandle); } catch (error) { logService.warn('Could not delete obsolete instance handle', error); @@ -293,11 +295,7 @@ class CodeMain { // Process Info if (args.status) { return instantiationService.invokeFunction(async () => { - - // Create a diagnostic service connected to the existing shared process - const sharedProcessClient = await connect(environmentService.sharedIPCHandle, 'main'); - const diagnosticsChannel = sharedProcessClient.getChannel('diagnostics'); - const diagnosticsService = createChannelSender(diagnosticsChannel); + const diagnosticsService = new DiagnosticsService(NullTelemetryService); const mainProcessInfo = await launchService.getMainProcessInfo(); const remoteDiagnostics = await launchService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true }); const diagnostics = await diagnosticsService.getDiagnostics(mainProcessInfo, remoteDiagnostics); @@ -343,11 +341,11 @@ class CodeMain { private handleStartupDataDirError(environmentService: IEnvironmentMainService, error: NodeJS.ErrnoException): void { if (error.code === 'EACCES' || error.code === 'EPERM') { - const directories = coalesce([environmentService.userDataPath, environmentService.extensionsPath, XDG_RUNTIME_DIR]); + const directories = coalesce([environmentService.userDataPath, environmentService.extensionsPath, XDG_RUNTIME_DIR]).map(folder => getPathLabel(folder, environmentService)); this.showStartupWarningDialog( localize('startupDataDirError', "Unable to write program user data."), - localize('startupUserDataAndExtensionsDirErrorDetail', "Please make sure the following directories are writeable:\n\n{0}", directories.join('\n')) + localize('startupUserDataAndExtensionsDirErrorDetail', "{0}\n\nPlease make sure the following directories are writeable:\n\n{1}", toErrorMessage(error), directories.join('\n')) ); } } diff --git a/src/vs/code/electron-main/protocol.ts b/src/vs/code/electron-main/protocol.ts index 36d093199..5bfbebb68 100644 --- a/src/vs/code/electron-main/protocol.ts +++ b/src/vs/code/electron-main/protocol.ts @@ -11,7 +11,7 @@ import { INativeEnvironmentService } from 'vs/platform/environment/common/enviro import { session } from 'electron'; import { ILogService } from 'vs/platform/log/common/log'; import { TernarySearchTree } from 'vs/base/common/map'; -import { isLinux } from 'vs/base/common/platform'; +import { isLinux, isPreferringBrowserCodeLoad } from 'vs/base/common/platform'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; type ProtocolCallback = { (result: string | Electron.FilePathWithHeaders | { error: number }): void }; @@ -37,15 +37,17 @@ export class FileProtocolHandler extends Disposable { // Register vscode-file:// handler defaultSession.protocol.registerFileProtocol(Schemas.vscodeFileResource, (request, callback) => this.handleResourceRequest(request, callback as unknown as ProtocolCallback)); - // Block any file:// access (sandbox only) - if (environmentService.args.__sandbox) { + // Block any file:// access (explicitly enabled only) + if (isPreferringBrowserCodeLoad) { + this.logService.info(`Intercepting ${Schemas.file}: protocol and blocking it`); + defaultSession.protocol.interceptFileProtocol(Schemas.file, (request, callback) => this.handleFileRequest(request, callback as unknown as ProtocolCallback)); } // Cleanup this._register(toDisposable(() => { defaultSession.protocol.unregisterProtocol(Schemas.vscodeFileResource); - if (environmentService.args.__sandbox) { + if (isPreferringBrowserCodeLoad) { defaultSession.protocol.uninterceptProtocol(Schemas.file); } })); diff --git a/src/vs/code/electron-main/sharedProcess.ts b/src/vs/code/electron-main/sharedProcess.ts index dd721db92..812a4dd1e 100644 --- a/src/vs/code/electron-main/sharedProcess.ts +++ b/src/vs/code/electron-main/sharedProcess.ts @@ -3,25 +3,25 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { memoize } from 'vs/base/common/decorators'; +import { BrowserWindow, ipcMain, Event, MessagePortMain } from 'electron'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; -import { BrowserWindow, ipcMain, WebContents, Event as ElectronEvent } from 'electron'; -import { ISharedProcess } from 'vs/platform/ipc/electron-main/sharedProcessMainService'; import { Barrier } from 'vs/base/common/async'; import { ILogService } from 'vs/platform/log/common/log'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; -import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { Event } from 'vs/base/common/event'; import { FileAccess } from 'vs/base/common/network'; +import { browserCodeLoadingCacheStrategy } from 'vs/base/common/platform'; +import { ISharedProcess, ISharedProcessConfiguration } from 'vs/platform/sharedProcess/node/sharedProcess'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { connect as connectMessagePort } from 'vs/base/parts/ipc/electron-main/ipc.mp'; +import { assertIsDefined } from 'vs/base/common/types'; -export class SharedProcess implements ISharedProcess { +export class SharedProcess extends Disposable implements ISharedProcess { - private barrier = new Barrier(); + private readonly whenSpawnedBarrier = new Barrier(); - private window: BrowserWindow | null = null; - - private readonly _whenReady: Promise; + private window: BrowserWindow | undefined = undefined; + private windowCloseListener: ((event: Event) => void) | undefined = undefined; constructor( private readonly machineId: string, @@ -31,17 +31,125 @@ export class SharedProcess implements ISharedProcess { @ILogService private readonly logService: ILogService, @IThemeMainService private readonly themeMainService: IThemeMainService ) { - // overall ready promise when shared process signals initialization is done - this._whenReady = new Promise(c => ipcMain.once('vscode:shared-process->electron-main=init-done', () => c(undefined))); + super(); + + this.registerListeners(); } - @memoize - private get _whenIpcReady(): Promise { + private registerListeners(): void { + + // Lifecycle + this._register(this.lifecycleMainService.onWillShutdown(() => this.onWillShutdown())); + + // Shared process connections from workbench windows + ipcMain.on('vscode:createSharedProcessMessageChannel', async (e, nonce: string) => { + this.logService.trace('SharedProcess: on vscode:createSharedProcessMessageChannel'); + + // await the shared process to be overall ready + // we do not just wait for IPC ready because the + // workbench window will communicate directly + await this.whenReady(); + + // connect to the shared process window + const port = await this.connect(); + + // Check back if the requesting window meanwhile closed + // Since shared process is delayed on startup there is + // a chance that the window close before the shared process + // was ready for a connection. + if (e.sender.isDestroyed()) { + return port.close(); + } + + // send the port back to the requesting window + e.sender.postMessage('vscode:createSharedProcessMessageChannelResult', nonce, [port]); + }); + } + + private onWillShutdown(): void { + const window = this.window; + if (!window) { + return; // possibly too early before created + } + + // Signal exit to shared process when shutting down + if (!window.isDestroyed() && !window.webContents.isDestroyed()) { + window.webContents.send('vscode:electron-main->shared-process=exit'); + } + + // Shut the shared process down when we are quitting + // + // Note: because we veto the window close, we must first remove our veto. + // Otherwise the application would never quit because the shared process + // window is refusing to close! + // + if (this.windowCloseListener) { + window.removeListener('close', this.windowCloseListener); + this.windowCloseListener = undefined; + } + + // Electron seems to crash on Windows without this setTimeout :| + setTimeout(() => { + try { + window.close(); + } catch (err) { + // ignore, as electron is already shutting down + } + + this.window = undefined; + }, 0); + } + + private _whenReady: Promise | undefined = undefined; + whenReady(): Promise { + if (!this._whenReady) { + // Overall signal that the shared process window was loaded and + // all services within have been created. + this._whenReady = new Promise(resolve => ipcMain.once('vscode:shared-process->electron-main=init-done', () => { + this.logService.trace('SharedProcess: Overall ready'); + + resolve(); + })); + } + + return this._whenReady; + } + + private _whenIpcReady: Promise | undefined = undefined; + private get whenIpcReady() { + if (!this._whenIpcReady) { + this._whenIpcReady = (async () => { + + // Always wait for `spawn()` + await this.whenSpawnedBarrier.wait(); + + // Create window for shared process + this.createWindow(); + + // Listeners + this.registerWindowListeners(); + + // Wait for window indicating that IPC connections are accepted + await new Promise(resolve => ipcMain.once('vscode:shared-process->electron-main=ipc-ready', () => { + this.logService.trace('SharedProcess: IPC ready'); + + resolve(); + })); + })(); + } + + return this._whenIpcReady; + } + + private createWindow(): void { + + // shared process is a hidden window by default this.window = new BrowserWindow({ show: false, backgroundColor: this.themeMainService.getBackgroundColor(), webPreferences: { preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, + v8CacheOptions: browserCodeLoadingCacheStrategy, nodeIntegration: true, enableWebSQL: false, enableRemoteModule: false, @@ -52,118 +160,85 @@ export class SharedProcess implements ISharedProcess { disableBlinkFeatures: 'Auxclick' // do NOT change, allows us to identify this window as shared-process in the process explorer } }); - const config = { - appRoot: this.environmentService.appRoot, + + const config: ISharedProcessConfiguration = { machineId: this.machineId, + windowId: this.window.id, + appRoot: this.environmentService.appRoot, nodeCachedDataDir: this.environmentService.nodeCachedDataDir, + backupWorkspacesPath: this.environmentService.backupWorkspacesPath, userEnv: this.userEnv, - windowId: this.window.id + sharedIPCHandle: this.environmentService.sharedIPCHandle, + args: this.environmentService.args, + logLevel: this.logService.getLevel() }; - const windowUrl = (this.environmentService.sandbox ? - FileAccess._asCodeFileUri('vs/code/electron-browser/sharedProcess/sharedProcess.html', require) : - FileAccess.asBrowserUri('vs/code/electron-browser/sharedProcess/sharedProcess.html', require)) - .with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` }); - this.window.loadURL(windowUrl.toString(true)); + // Load with config + this.window.loadURL(FileAccess + .asBrowserUri('vs/code/electron-browser/sharedProcess/sharedProcess.html', require) + .with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` }) + .toString(true) + ); + } - // Prevent the window from dying - const onClose = (e: ElectronEvent) => { + private registerWindowListeners(): void { + if (!this.window) { + return; + } + + // Prevent the window from closing + this.windowCloseListener = (e: Event) => { this.logService.trace('SharedProcess#close prevented'); // We never allow to close the shared process unless we get explicitly disposed() e.preventDefault(); // Still hide the window though if visible - if (this.window && this.window.isVisible()) { + if (this.window?.isVisible()) { this.window.hide(); } }; - this.window.on('close', onClose); + this.window.on('close', this.windowCloseListener); - const disposables = new DisposableStore(); - - this.lifecycleMainService.onWillShutdown(() => { - disposables.dispose(); - - // Shut the shared process down when we are quitting - // - // Note: because we veto the window close, we must first remove our veto. - // Otherwise the application would never quit because the shared process - // window is refusing to close! - // - if (this.window) { - this.window.removeListener('close', onClose); - } - - // Electron seems to crash on Windows without this setTimeout :| - setTimeout(() => { - try { - if (this.window) { - this.window.close(); - } - } catch (err) { - // ignore, as electron is already shutting down - } - - this.window = null; - }, 0); - }); - - return new Promise(c => { - // send payload once shared process is ready to receive it - disposables.add(Event.once(Event.fromNodeEventEmitter(ipcMain, 'vscode:shared-process->electron-main=ready-for-payload', ({ sender }: { sender: WebContents }) => sender))(sender => { - sender.send('vscode:electron-main->shared-process=payload', { - sharedIPCHandle: this.environmentService.sharedIPCHandle, - args: this.environmentService.args, - logLevel: this.logService.getLevel(), - backupWorkspacesPath: this.environmentService.backupWorkspacesPath, - nodeCachedDataDir: this.environmentService.nodeCachedDataDir - }); - - // signal exit to shared process when we get disposed - disposables.add(toDisposable(() => sender.send('vscode:electron-main->shared-process=exit'))); - - // complete IPC-ready promise when shared process signals this to us - ipcMain.once('vscode:shared-process->electron-main=ipc-ready', () => c(undefined)); - })); - }); + // Crashes & Unrsponsive & Failed to load + this.window.webContents.on('render-process-gone', (event, details) => this.logService.error(`SharedProcess: crashed (detail: ${details?.reason})`)); + this.window.on('unresponsive', () => this.logService.error('SharedProcess: detected unresponsive window')); + this.window.webContents.on('did-fail-load', (event, errorCode, errorDescription) => this.logService.warn('SharedProcess: failed to load window, ', errorDescription)); } spawn(userEnv: NodeJS.ProcessEnv): void { this.userEnv = { ...this.userEnv, ...userEnv }; - this.barrier.open(); + + // Release barrier + this.whenSpawnedBarrier.open(); } - async whenReady(): Promise { - await this.barrier.wait(); - await this._whenReady; + async connect(): Promise { + + // Wait for shared process being ready to accept connection + await this.whenIpcReady; + + // Connect and return message port + const window = assertIsDefined(this.window); + return connectMessagePort(window); } - async whenIpcReady(): Promise { - await this.barrier.wait(); - await this._whenIpcReady; - } + async toggle(): Promise { - toggle(): void { - if (!this.window || this.window.isVisible()) { - this.hide(); - } else { - this.show(); + // wait for window to be created + await this.whenIpcReady; + + if (!this.window) { + return; // possibly disposed already } - } - show(): void { - if (this.window) { + if (this.window.isVisible()) { + this.window.webContents.closeDevTools(); + this.window.hide(); + } else { this.window.show(); this.window.webContents.openDevTools(); } } - - hide(): void { - if (this.window) { - this.window.webContents.closeDevTools(); - this.window.hide(); - } - } } diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index a9f47be81..6ce9e1dc8 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as os from 'os'; -import * as path from 'vs/base/common/path'; -import * as nls from 'vs/nls'; -import * as perf from 'vs/base/common/performance'; +import { release } from 'os'; +import { join } from 'vs/base/common/path'; +import { localize } from 'vs/nls'; +import { getMarks, mark } from 'vs/base/common/performance'; import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme, Event, Details } from 'electron'; +import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme, Event, RenderProcessGoneDetails } from 'electron'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -18,17 +18,17 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import product from 'vs/platform/product/common/product'; import { WindowMinimumSize, IWindowSettings, MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, zoomLevelToZoomFactor, INativeWindowConfiguration } from 'vs/platform/windows/common/windows'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; +import { browserCodeLoadingCacheStrategy, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { ICodeWindow, IWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; -import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; +import { ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { RunOnceScheduler } from 'vs/base/common/async'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; +import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; @@ -102,32 +102,32 @@ export class CodeWindow extends Disposable implements ICodeWindow { private hiddenTitleBarStyle: boolean | undefined; private showTimeoutHandle: NodeJS.Timeout | undefined; - private _lastFocusTime: number; - private _readyState: ReadyState; + private _lastFocusTime = -1; + private _readyState = ReadyState.NONE; private windowState: IWindowState; private currentMenuBarVisibility: MenuBarVisibility | undefined; private representedFilename: string | undefined; private documentEdited: boolean | undefined; - private readonly whenReadyCallbacks: { (window: ICodeWindow): void }[]; + private readonly whenReadyCallbacks: { (window: ICodeWindow): void }[] = []; private marketplaceHeadersPromise: Promise; - private readonly touchBarGroups: TouchBarSegmentedControl[]; + private readonly touchBarGroups: TouchBarSegmentedControl[] = []; - private currentHttpProxy?: string; - private currentNoProxy?: string; + private currentHttpProxy: string | undefined = undefined; + private currentNoProxy: string | undefined = undefined; constructor( config: IWindowCreationOptions, @ILogService private readonly logService: ILogService, @IEnvironmentMainService private readonly environmentService: IEnvironmentMainService, @IFileService private readonly fileService: IFileService, - @IStorageMainService private readonly storageService: IStorageMainService, + @IStorageMainService storageService: IStorageMainService, @IConfigurationService private readonly configurationService: IConfigurationService, @IThemeMainService private readonly themeMainService: IThemeMainService, - @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, + @IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService, @IBackupMainService private readonly backupMainService: IBackupMainService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IDialogMainService private readonly dialogMainService: IDialogMainService, @@ -135,11 +135,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { ) { super(); - this.touchBarGroups = []; - this._lastFocusTime = -1; - this._readyState = ReadyState.NONE; - this.whenReadyCallbacks = []; - //#region create browser window { // Load window state @@ -164,6 +159,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { title: product.nameLong, webPreferences: { preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, + v8CacheOptions: browserCodeLoadingCacheStrategy, enableWebSQL: false, enableRemoteModule: false, spellcheck: false, @@ -185,13 +181,17 @@ export class CodeWindow extends Disposable implements ICodeWindow { } }; + if (browserCodeLoadingCacheStrategy) { + this.logService.info(`window#ctor: using vscode-file protocol and V8 cache options: ${browserCodeLoadingCacheStrategy}`); + } + // Apply icon to window // Linux: always // Windows: only when running out of sources, otherwise an icon is set by us on the executable if (isLinux) { - options.icon = path.join(this.environmentService.appRoot, 'resources/linux/code.png'); + options.icon = join(this.environmentService.appRoot, 'resources/linux/code.png'); } else if (isWindows && !this.environmentService.isBuilt) { - options.icon = path.join(this.environmentService.appRoot, 'resources/win32/code_150x150.png'); + options.icon = join(this.environmentService.appRoot, 'resources/win32/code_150x150.png'); } if (isMacintosh && !this.useNativeFullScreen()) { @@ -233,7 +233,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { this._win.setSheetOffset(22); // offset dialogs by the height of the custom title bar if we have any } - // TODO@Ben (Electron 4 regression): when running on multiple displays where the target display + // TODO@bpasero (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 // correctly unless we set the bounds again (https://github.com/microsoft/vscode/issues/74872) // @@ -275,10 +275,9 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.createTouchBar(); // Request handling - const that = this; this.marketplaceHeadersPromise = resolveMarketplaceHeaders(product.version, this.environmentService, this.fileService, { - get(key) { return that.storageService.get(key); }, - store(key, value) { that.storageService.store(key, value); } + get(key) { return storageService.get(key); }, + store(key, value) { storageService.store(key, value); } }); // Eventing @@ -361,13 +360,11 @@ export class CodeWindow extends Disposable implements ICodeWindow { get lastFocusTime(): number { return this._lastFocusTime; } - get backupPath(): string | undefined { return this.currentConfig ? this.currentConfig.backupPath : undefined; } + get backupPath(): string | undefined { return this.currentConfig?.backupPath; } - get openedWorkspace(): IWorkspaceIdentifier | undefined { return this.currentConfig ? this.currentConfig.workspace : undefined; } + get openedWorkspace(): IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined { return this.currentConfig?.workspace; } - get openedFolderUri(): URI | undefined { return this.currentConfig ? this.currentConfig.folderUri : undefined; } - - get remoteAuthority(): string | undefined { return this.currentConfig ? this.currentConfig.remoteAuthority : undefined; } + get remoteAuthority(): string | undefined { return this.currentConfig?.remoteAuthority; } setReady(): void { this._readyState = ReadyState.READY; @@ -413,9 +410,10 @@ export class CodeWindow extends Disposable implements ICodeWindow { private registerListeners(): void { - // Crashes & Unrsponsive + // Crashes & Unrsponsive & Failed to load this._win.webContents.on('render-process-gone', (event, details) => this.onWindowError(WindowError.CRASHED, details)); this._win.on('unresponsive', () => this.onWindowError(WindowError.UNRESPONSIVE)); + this._win.webContents.on('did-fail-load', (event, errorCode, errorDescription) => this.logService.warn('Main: failed to load workbench window, ', errorDescription)); // Window close this._win.on('closed', () => { @@ -424,24 +422,42 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.dispose(); }); - // Prevent loading of svgs - this._win.webContents.session.webRequest.onBeforeRequest(null!, (details, callback) => { - if (details.url.indexOf('.svg') > 0) { - const uri = URI.parse(details.url); - if (uri && !uri.scheme.match(/file/i) && uri.path.endsWith('.svg')) { + const svgFileSchemes = new Set([Schemas.file, Schemas.vscodeFileResource, Schemas.vscodeRemoteResource, 'devtools']); + this._win.webContents.session.webRequest.onBeforeRequest((details, callback) => { + const uri = URI.parse(details.url); + // Prevent loading of remote svgs + if (uri && uri.path.endsWith('.svg')) { + const safeScheme = svgFileSchemes.has(uri.scheme) || + uri.path.includes(Schemas.vscodeRemoteResource); + if (!safeScheme) { return callback({ cancel: true }); } } - return callback({}); + return callback({ cancel: false }); }); - this._win.webContents.session.webRequest.onHeadersReceived(null!, (details, callback) => { + this._win.webContents.session.webRequest.onHeadersReceived((details, callback) => { const responseHeaders = details.responseHeaders as Record; - const contentType = (responseHeaders['content-type'] || responseHeaders['Content-Type']); - if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) { - return callback({ cancel: true }); + + if (contentType && Array.isArray(contentType)) { + const uri = URI.parse(details.url); + // https://github.com/microsoft/vscode/issues/97564 + // ensure local svg files have Content-Type image/svg+xml + if (uri && uri.path.endsWith('.svg')) { + if (svgFileSchemes.has(uri.scheme)) { + responseHeaders['Content-Type'] = ['image/svg+xml']; + return callback({ cancel: false, responseHeaders }); + } + } + + // remote extension schemes have the following format + // http://127.0.0.1:/vscode-remote-resource?path= + if (!uri.path.includes(Schemas.vscodeRemoteResource) && + contentType.some(x => x.toLowerCase().includes('image/svg'))) { + return callback({ cancel: true }); + } } return callback({ cancel: false }); @@ -526,16 +542,11 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.sendWhenReady('vscode:leaveFullScreen', CancellationToken.None); }); - // Window Failed to load - this._win.webContents.on('did-fail-load', (event: Event, errorCode: number, errorDescription: string, validatedURL: string, isMainFrame: boolean) => { - this.logService.warn('[electron event]: fail to load, ', errorDescription); - }); - // Handle configuration changes this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated())); // Handle Workspace events - this._register(this.workspacesMainService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e))); + this._register(this.workspacesManagementMainService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e))); // Inject headers when requests are incoming const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*']; @@ -544,9 +555,9 @@ export class CodeWindow extends Disposable implements ICodeWindow { } private onWindowError(error: WindowError.UNRESPONSIVE): void; - private onWindowError(error: WindowError.CRASHED, details: Details): void; - private onWindowError(error: WindowError, details?: Details): void { - this.logService.error(error === WindowError.CRASHED ? `[VS Code]: renderer process crashed (detail: ${details?.reason})` : '[VS Code]: detected unresponsive'); + private onWindowError(error: WindowError.CRASHED, details: RenderProcessGoneDetails): void; + private onWindowError(error: WindowError, details?: RenderProcessGoneDetails): void { + this.logService.error(error === WindowError.CRASHED ? `Main: renderer process crashed (detail: ${details?.reason})` : 'Main: detected unresponsive'); // If we run extension tests from CLI, showing a dialog is not // very helpful in this case. Rather, we bring down the test run @@ -568,7 +579,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Unresponsive if (error === WindowError.UNRESPONSIVE) { if (this.isExtensionDevelopmentHost || this.isExtensionTestHost || (this._win && this._win.webContents && this._win.webContents.isDevToolsOpened())) { - // TODO@Ben Workaround for https://github.com/microsoft/vscode/issues/56994 + // TODO@bpasero Workaround for https://github.com/microsoft/vscode/issues/56994 // In certain cases the window can report unresponsiveness because a breakpoint was hit // and the process is stopped executing. The most typical cases are: // - devtools are opened and debugging happens @@ -581,9 +592,9 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.dialogMainService.showMessageBox({ title: product.nameLong, type: 'warning', - buttons: [mnemonicButtonLabel(nls.localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(nls.localize({ key: 'wait', comment: ['&& denotes a mnemonic'] }, "&&Keep Waiting")), mnemonicButtonLabel(nls.localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))], - message: nls.localize('appStalled', "The window is no longer responding"), - detail: nls.localize('appStalledDetail', "You can reopen or close the window or keep waiting."), + buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'wait', comment: ['&& denotes a mnemonic'] }, "&&Keep Waiting")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))], + message: localize('appStalled', "The window is no longer responding"), + detail: localize('appStalledDetail', "You can reopen or close the window or keep waiting."), noLink: true }, this._win).then(result => { if (!this._win) { @@ -591,6 +602,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } if (result.response === 0) { + this._win.webContents.forcefullyCrashRenderer(); // Calling reload() immediately after calling this method will force the reload to occur in a new process this.reload(); } else if (result.response === 2) { this.destroyWindow(); @@ -602,17 +614,17 @@ export class CodeWindow extends Disposable implements ICodeWindow { else { let message: string; if (details && details.reason !== 'crashed') { - message = nls.localize('appCrashedDetails', "The window has crashed (reason: '{0}')", details?.reason); + message = localize('appCrashedDetails', "The window has crashed (reason: '{0}')", details?.reason); } else { - message = nls.localize('appCrashed', "The window has crashed", details?.reason); + message = localize('appCrashed', "The window has crashed", details?.reason); } this.dialogMainService.showMessageBox({ title: product.nameLong, type: 'warning', - buttons: [mnemonicButtonLabel(nls.localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(nls.localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))], + buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))], message, - detail: nls.localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."), + detail: localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."), noLink: true }, this._win).then(result => { if (!this._win) { @@ -643,31 +655,32 @@ export class CodeWindow extends Disposable implements ICodeWindow { } private onConfigurationUpdated(): void { + + // Menubar const newMenuBarVisibility = this.getMenuBarVisibility(); if (newMenuBarVisibility !== this.currentMenuBarVisibility) { this.currentMenuBarVisibility = newMenuBarVisibility; this.setMenuBarVisibility(newMenuBarVisibility); } - // Do not set to empty configuration at startup if setting is empty to not override configuration through CLI options: - const env = process.env; + + // Proxy let newHttpProxy = (this.configurationService.getValue('http.proxy') || '').trim() - || (env.https_proxy || process.env.HTTPS_PROXY || process.env.http_proxy || process.env.HTTP_PROXY || '').trim() // Not standardized. + || (process.env['https_proxy'] || process.env['HTTPS_PROXY'] || process.env['http_proxy'] || process.env['HTTP_PROXY'] || '').trim() // Not standardized. || undefined; + if (newHttpProxy?.endsWith('/')) { newHttpProxy = newHttpProxy.substr(0, newHttpProxy.length - 1); } - const newNoProxy = (env.no_proxy || env.NO_PROXY || '').trim() || undefined; // Not standardized. + + const newNoProxy = (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; + const proxyRules = newHttpProxy || ''; const proxyBypassRules = newNoProxy ? `${newNoProxy},` : ''; this.logService.trace(`Setting proxy to '${proxyRules}', bypassing '${proxyBypassRules}'`); - this._win.webContents.session.setProxy({ - proxyRules, - proxyBypassRules, - pacScript: '', - }); + this._win.webContents.session.setProxy({ proxyRules, proxyBypassRules, pacScript: '' }); } } @@ -677,7 +690,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } } - load(config: INativeWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void { + load(config: INativeWindowConfiguration, { isReload, disableExtensions }: { isReload?: boolean, disableExtensions?: boolean } = Object.create(null)): void { // If this window was loaded before from the command line // (as indicated by VSCODE_CLI environment), make sure to @@ -689,6 +702,15 @@ export class CodeWindow extends Disposable implements ICodeWindow { config.userEnv = { ...currentUserEnv, ...config.userEnv }; // still allow to override certain environment as passed in } + // If named pipe was instantiated for the crashpad_handler process, reuse the same + // pipe for new app instances connecting to the original app instance. + // Ref: https://github.com/microsoft/vscode/issues/115874 + if (process.env['CHROME_CRASHPAD_PIPE_NAME']) { + Object.assign(config.userEnv, { + CHROME_CRASHPAD_PIPE_NAME: process.env['CHROME_CRASHPAD_PIPE_NAME'] + }); + } + // If this is the first time the window is loaded, we associate the paths // directly with the window because we assume the loading will just work if (this._readyState === ReadyState.NONE) { @@ -728,7 +750,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } // Load URL - perf.mark('main:loadWindow'); + mark('code/willOpenNewWindow'); this._win.loadURL(this.getUrl(configuration)); // Make window visible if it did not open in N seconds because this indicates an error @@ -747,11 +769,14 @@ export class CodeWindow extends Disposable implements ICodeWindow { this._onLoad.fire(); } - reload(cli?: NativeParsedArgs): void { + async reload(cli?: NativeParsedArgs): Promise { // Copy our current config for reuse const configuration = Object.assign({}, this.currentConfig); + // Validate workspace + configuration.workspace = await this.validateWorkspace(configuration); + // Delete some properties we do not want during reload delete configuration.filesToOpenOrCreate; delete configuration.filesToDiff; @@ -761,17 +786,44 @@ export class CodeWindow extends Disposable implements ICodeWindow { // in extension development mode. These options are all development related. if (this.isExtensionDevelopmentHost && cli) { configuration.verbose = cli.verbose; + configuration.debugId = cli.debugId; configuration['inspect-extensions'] = cli['inspect-extensions']; configuration['inspect-brk-extensions'] = cli['inspect-brk-extensions']; - configuration.debugId = cli.debugId; configuration['extensions-dir'] = cli['extensions-dir']; } configuration.isInitialStartup = false; // since this is a reload // Load config - const disableExtensions = cli ? cli['disable-extensions'] : undefined; - this.load(configuration, true, disableExtensions); + this.load(configuration, { isReload: true, disableExtensions: cli?.['disable-extensions'] }); + } + + private async validateWorkspace(configuration: INativeWindowConfiguration): Promise { + + // Multi folder + if (isWorkspaceIdentifier(configuration.workspace)) { + const configPath = configuration.workspace.configPath; + if (configPath.scheme === Schemas.file) { + const workspaceExists = await this.fileService.exists(configPath); + if (!workspaceExists) { + return undefined; + } + } + } + + // Single folder + else if (isSingleFolderWorkspaceIdentifier(configuration.workspace)) { + const uri = configuration.workspace.uri; + if (uri.scheme === Schemas.file) { + const folderExists = await this.fileService.exists(uri); + if (!folderExists) { + return undefined; + } + } + } + + // Workspace is valid + return configuration.workspace; } private getUrl(windowConfiguration: INativeWindowConfiguration): string { @@ -780,6 +832,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { windowConfiguration.windowId = this._win.id; windowConfiguration.sessionId = `window:${this._win.id}`; windowConfiguration.logLevel = this.logService.getLevel(); + windowConfiguration.logsPath = this.environmentService.logsPath; // Set zoomlevel const windowConfig = this.configurationService.getValue('window'); @@ -803,14 +856,14 @@ export class CodeWindow extends Disposable implements ICodeWindow { windowConfiguration.maximized = this._win.isMaximized(); // Dump Perf Counters - windowConfiguration.perfEntries = perf.exportEntries(); + windowConfiguration.perfMarks = getMarks(); // Parts splash - windowConfiguration.partsSplashPath = path.join(this.environmentService.userDataPath, 'rapid_render.json'); + windowConfiguration.partsSplashPath = join(this.environmentService.userDataPath, 'rapid_render.json'); // OS Info windowConfiguration.os = { - release: os.release() + release: release() }; // Config (combination of process.argv and window configuration) @@ -848,10 +901,10 @@ export class CodeWindow extends Disposable implements ICodeWindow { workbench = 'vs/code/electron-browser/workbench/workbench.html'; } - return (this.environmentService.sandbox ? - FileAccess._asCodeFileUri(workbench, require) : - FileAccess.asBrowserUri(workbench, require)) - .with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` }).toString(true); + return FileAccess + .asBrowserUri(workbench, require) + .with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` }) + .toString(true); } serializeWindowState(): IWindowState { @@ -1161,7 +1214,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { if (visibility === 'toggle') { if (notify) { - this.send('vscode:showInfoMessage', nls.localize('hiddenMenuBar', "You can still access the menu bar by pressing the Alt-key.")); + this.send('vscode:showInfoMessage', localize('hiddenMenuBar', "You can still access the menu bar by pressing the Alt-key.")); } } @@ -1256,6 +1309,11 @@ export class CodeWindow extends Disposable implements ICodeWindow { send(channel: string, ...args: any[]): void { if (this._win) { + if (this._win.isDestroyed() || this._win.webContents.isDestroyed()) { + this.logService.warn(`Sending IPC message to channel ${channel} for window that is destroyed`); + return; + } + this._win.webContents.send(channel, ...args); } } diff --git a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts index 252260a57..6d339e858 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts @@ -7,11 +7,10 @@ import 'vs/css!./media/issueReporter'; import 'vs/base/browser/ui/codicons/codiconStyles'; // make sure codicon css is loaded import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { NativeHostService } from 'vs/platform/native/electron-sandbox/nativeHostService'; -import { ipcRenderer, process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { applyZoom, zoomIn, zoomOut } from 'vs/platform/windows/electron-sandbox/window'; import { $, reset, safeInnerHtml, windowOpenNoOpener } from 'vs/base/browser/dom'; import { Button } from 'vs/base/browser/ui/button/button'; -import { CodiconLabel } from 'vs/base/browser/ui/codicons/codiconLabel'; import * as collections from 'vs/base/common/collections'; import { debounce } from 'vs/base/common/decorators'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -23,9 +22,11 @@ import BaseHtml from 'vs/code/electron-sandbox/issue/issueReporterPage'; import { localize } from 'vs/nls'; import { isRemoteDiagnosticError, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IMainProcessService, MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService, ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; import { IssueReporterData, IssueReporterExtensionData, IssueReporterFeatures, IssueReporterStyles, IssueType } from 'vs/platform/issue/common/issue'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { Codicon } from 'vs/base/common/codicons'; +import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; const MAX_URL_LENGTH = 2045; @@ -82,14 +83,12 @@ export class IssueReporter extends Disposable { this.initServices(configuration); - const isSnap = process.platform === 'linux' && process.env.SNAP && process.env.SNAP_REVISION; - const targetExtension = configuration.data.extensionId ? configuration.data.enabledExtensions.find(extension => extension.id === configuration.data.extensionId) : undefined; this.issueReporterModel = new IssueReporterModel({ issueType: configuration.data.issueType || IssueType.Bug, versionInfo: { vscodeVersion: `${configuration.product.nameShort} ${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}${isSnap ? ' snap' : ''}` + os: `${this.configuration.os.type} ${this.configuration.os.arch} ${this.configuration.os.release}${platform.isLinuxSnap ? ' snap' : ''}` }, extensionsDisabled: !!configuration.disableExtensions, fileOnExtension: configuration.data.extensionId ? !targetExtension?.isBuiltin : undefined, @@ -267,7 +266,7 @@ export class IssueReporter extends Disposable { private initServices(configuration: IssueReporterConfiguration): void { const serviceCollection = new ServiceCollection(); - const mainProcessService = new MainProcessService(configuration.windowId); + const mainProcessService = new ElectronIPCMainProcessService(configuration.windowId); serviceCollection.set(IMainProcessService, mainProcessService); this.nativeHostService = new NativeHostService(configuration.windowId, mainProcessService) as INativeHostService; @@ -442,7 +441,11 @@ export class IssueReporter extends Disposable { private updatePreviewButtonState() { if (this.isPreviewEnabled()) { - this.previewButton.label = localize('previewOnGitHub', "Preview on GitHub"); + if (this.configuration.data.githubAccessToken) { + this.previewButton.label = localize('createOnGitHub', "Create on GitHub"); + } else { + this.previewButton.label = localize('previewOnGitHub', "Preview on GitHub"); + } this.previewButton.enabled = true; } else { this.previewButton.enabled = false; @@ -598,8 +601,7 @@ export class IssueReporter extends Disposable { issueState = $('span.issue-state'); const issueIcon = $('span.issue-icon'); - const codicon = new CodiconLabel(issueIcon); - codicon.text = issue.state === 'open' ? '$(issue-opened)' : '$(issue-closed)'; + issueIcon.appendChild(renderIcon(issue.state === 'open' ? Codicon.issueOpened : Codicon.issueClosed)); const issueStateLabel = $('span.issue-state.label'); issueStateLabel.textContent = issue.state === 'open' ? localize('open', "Open") : localize('closed', "Closed"); @@ -778,6 +780,35 @@ export class IssueReporter extends Disposable { return isValid; } + private async submitToGitHub(issueTitle: string, issueBody: string, gitHubDetails: { owner: string, repositoryName: string }): Promise { + const url = `https://api.github.com/repos/${gitHubDetails.owner}/${gitHubDetails.repositoryName}/issues`; + const init = { + method: 'POST', + body: JSON.stringify({ + title: issueTitle, + body: issueBody + }), + headers: new Headers({ + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${this.configuration.data.githubAccessToken}` + }) + }; + + return new Promise((resolve, reject) => { + window.fetch(url, init).then((response) => { + if (response.ok) { + response.json().then(result => { + ipcRenderer.send('vscode:openExternal', result.html_url); + ipcRenderer.send('vscode:closeIssueReporter'); + resolve(true); + }); + } else { + resolve(false); + } + }); + }); + } + private async createIssue(): Promise { if (!this.validateInputs()) { // If inputs are invalid, set focus to the first one and add listeners on them @@ -810,8 +841,16 @@ export class IssueReporter extends Disposable { this.hasBeenSubmitted = true; - const baseUrl = this.getIssueUrlWithTitle((this.getElementById('issue-title')).value); + const issueTitle = (this.getElementById('issue-title')).value; const issueBody = this.issueReporterModel.serialize(); + + const issueUrl = this.issueReporterModel.fileOnExtension() ? this.getExtensionGitHubUrl() : this.configuration.product.reportIssueUrl!; + 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); let url = baseUrl + `&body=${encodeURIComponent(issueBody)}`; if (url.length > MAX_URL_LENGTH) { @@ -841,6 +880,20 @@ export class IssueReporter extends Disposable { }); } + private parseGitHubUrl(url: string): undefined | { repositoryName: string, owner: string } { + // Assumes a GitHub url to a particular repo, https://github.com/repositoryName/owner. + // Repository name and owner cannot contain '/' + const match = /^https?:\/\/github\.com\/([^\/]*)\/([^\/]*).*/.exec(url); + if (match && match.length) { + return { + owner: match[1], + repositoryName: match[2] + }; + } + + return undefined; + } + private getExtensionGitHubUrl(): string { let repositoryUrl = ''; const bugsUrl = this.getExtensionBugsUrl(); diff --git a/src/vs/code/electron-sandbox/processExplorer/media/processExplorer.css b/src/vs/code/electron-sandbox/processExplorer/media/processExplorer.css index add9cdae2..095663c05 100644 --- a/src/vs/code/electron-sandbox/processExplorer/media/processExplorer.css +++ b/src/vs/code/electron-sandbox/processExplorer/media/processExplorer.css @@ -49,28 +49,12 @@ body { width: 90px; } -.process-item { - line-height: 22px; +.monaco-list-row:first-of-type { + border-bottom: 1px solid; } -table { - border-collapse: collapse; - width: 100%; - table-layout: fixed; -} - -th[scope='col'] { - vertical-align: bottom; - border-bottom: 1px solid #cccccc; - padding: .5rem; - border-top: 1px solid #cccccc; - cursor: default; -} - -td { - padding: .25rem; - vertical-align: top; - cursor: default; +.row { + display: flex; } .centered { @@ -79,6 +63,9 @@ td { .nameLabel{ text-align: left; + width: calc(100% - 185px); + overflow: hidden; + text-overflow: ellipsis; } .data { @@ -93,15 +80,3 @@ td { padding-left: 20px; white-space: nowrap; } - -tbody > tr:hover { - background-color: #2A2D2E; -} - -.hidden { - display: none; -} - -.header { - display: flex; -} diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorer.html b/src/vs/code/electron-sandbox/processExplorer/processExplorer.html index 517805abd..73d89eac3 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorer.html +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorer.html @@ -6,7 +6,7 @@ -
    +
    diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts index ef93a1c52..e4e7be122 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts @@ -14,38 +14,226 @@ import { applyZoom, zoomIn, zoomOut } from 'vs/platform/windows/electron-sandbox import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu'; import { popup } from 'vs/base/parts/contextmenu/electron-sandbox/contextmenu'; import { ProcessItem } from 'vs/base/common/processes'; -import { addDisposableListener, $ } from 'vs/base/browser/dom'; +import * as dom from 'vs/base/browser/dom'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; -import { MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; -import { CodiconLabel } from 'vs/base/browser/ui/codicons/codiconLabel'; +import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; import { ByteSize } from 'vs/platform/files/common/files'; +import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { IDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; +import { DataTree } from 'vs/base/browser/ui/tree/dataTree'; const DEBUG_FLAGS_PATTERN = /\s--(inspect|debug)(-brk|port)?=(\d+)?/; const DEBUG_PORT_PATTERN = /\s--(inspect|debug)-port=(\d+)/; -interface FormattedProcessItem { - cpu: number; - memory: number; - pid: string; +class ProcessListDelegate implements IListVirtualDelegate { + getHeight(element: MachineProcessInformation | ProcessItem | IRemoteDiagnosticError) { + return 22; + } + + getTemplateId(element: ProcessInformation | MachineProcessInformation | ProcessItem | IRemoteDiagnosticError) { + if (isProcessItem(element)) { + return 'process'; + } + + if (isMachineProcessInformation(element)) { + return 'machine'; + } + + if (isRemoteDiagnosticError(element)) { + return 'error'; + } + + if (isProcessInformation(element)) { + return 'header'; + } + + return ''; + } +} + +interface IProcessItemTemplateData extends IProcessRowTemplateData { + CPU: HTMLElement; + memory: HTMLElement; + PID: HTMLElement; +} + +interface IProcessRowTemplateData { + name: HTMLElement; +} + +class ProcessTreeDataSource implements IDataSource { + hasChildren(element: ProcessTree | ProcessInformation | MachineProcessInformation | ProcessItem | IRemoteDiagnosticError): boolean { + if (isRemoteDiagnosticError(element)) { + return false; + } + + if (isProcessItem(element)) { + return !!element.children?.length; + } else { + return true; + } + } + + getChildren(element: ProcessTree | ProcessInformation | MachineProcessInformation | ProcessItem | IRemoteDiagnosticError) { + if (isProcessItem(element)) { + return element.children ? element.children : []; + } + + if (isRemoteDiagnosticError(element)) { + return []; + } + + if (isProcessInformation(element)) { + // If there are multiple process roots, return these, otherwise go directly to the root process + if (element.processRoots.length > 1) { + return element.processRoots; + } else { + return [element.processRoots[0].rootProcess]; + } + } + + if (isMachineProcessInformation(element)) { + return [element.rootProcess]; + } + + return [element.processes]; + } +} + +class ProcessHeaderTreeRenderer implements ITreeRenderer { + templateId: string = 'header'; + renderTemplate(container: HTMLElement): IProcessItemTemplateData { + const data = Object.create(null); + const row = dom.append(container, dom.$('.row')); + data.name = dom.append(row, dom.$('.nameLabel')); + data.CPU = dom.append(row, dom.$('.cpu')); + data.memory = dom.append(row, dom.$('.memory')); + data.PID = dom.append(row, dom.$('.pid')); + return data; + } + renderElement(node: ITreeNode, index: number, templateData: IProcessItemTemplateData, height: number | undefined): void { + templateData.name.textContent = localize('name', "Process Name"); + templateData.CPU.textContent = localize('cpu', "CPU %"); + templateData.PID.textContent = localize('pid', "PID"); + templateData.memory.textContent = localize('memory', "Memory (MB)"); + + } + disposeTemplate(templateData: any): void { + // Nothing to do + } +} + +class MachineRenderer implements ITreeRenderer { + templateId: string = 'machine'; + renderTemplate(container: HTMLElement): IProcessRowTemplateData { + const data = Object.create(null); + const row = dom.append(container, dom.$('.row')); + data.name = dom.append(row, dom.$('.nameLabel')); + return data; + } + renderElement(node: ITreeNode, index: number, templateData: IProcessRowTemplateData, height: number | undefined): void { + templateData.name.textContent = node.element.name; + } + disposeTemplate(templateData: IProcessRowTemplateData): void { + // Nothing to do + } +} + +class ErrorRenderer implements ITreeRenderer { + templateId: string = 'error'; + renderTemplate(container: HTMLElement): IProcessRowTemplateData { + const data = Object.create(null); + const row = dom.append(container, dom.$('.row')); + data.name = dom.append(row, dom.$('.nameLabel')); + return data; + } + renderElement(node: ITreeNode, index: number, templateData: IProcessRowTemplateData, height: number | undefined): void { + templateData.name.textContent = node.element.errorMessage; + } + disposeTemplate(templateData: IProcessRowTemplateData): void { + // Nothing to do + } +} + + +class ProcessRenderer implements ITreeRenderer { + constructor(private platform: string, private totalMem: number, private mapPidToWindowTitle: Map) { } + + templateId: string = 'process'; + renderTemplate(container: HTMLElement): IProcessItemTemplateData { + const data = Object.create(null); + const row = dom.append(container, dom.$('.row')); + + data.name = dom.append(row, dom.$('.nameLabel')); + data.CPU = dom.append(row, dom.$('.cpu')); + data.memory = dom.append(row, dom.$('.memory')); + data.PID = dom.append(row, dom.$('.pid')); + + return data; + } + renderElement(node: ITreeNode, index: number, templateData: IProcessItemTemplateData, height: number | undefined): void { + const { element } = node; + + let name = element.name; + if (name === 'window') { + const windowTitle = this.mapPidToWindowTitle.get(element.pid); + name = windowTitle !== undefined ? `${name} (${this.mapPidToWindowTitle.get(element.pid)})` : name; + } + + templateData.name.textContent = name; + templateData.name.title = element.cmd; + + templateData.CPU.textContent = element.load.toFixed(0); + templateData.PID.textContent = element.pid.toFixed(0); + + const memory = this.platform === 'win32' ? element.mem : (this.totalMem * (element.mem / 100)); + templateData.memory.textContent = (memory / ByteSize.MB).toFixed(0); + } + + disposeTemplate(templateData: IProcessItemTemplateData): void { + // Nothing to do + } +} + +interface MachineProcessInformation { name: string; - formattedName: string; - cmd: string; + rootProcess: ProcessItem | IRemoteDiagnosticError +} + +interface ProcessInformation { + processRoots: MachineProcessInformation[]; +} + +interface ProcessTree { + processes: ProcessInformation; +} + +function isMachineProcessInformation(item: any): item is MachineProcessInformation { + return !!item.name && !!item.rootProcess; +} + +function isProcessInformation(item: any): item is ProcessInformation { + return !!item.processRoots; +} + +function isProcessItem(item: any): item is ProcessItem { + return !!item.pid; } class ProcessExplorer { private lastRequestTime: number; - private collapsedStateCache: Map = new Map(); - private mapPidToWindowTitle = new Map(); private listeners = new DisposableStore(); private nativeHostService: INativeHostService; + private tree: DataTree | undefined; + constructor(windowId: number, private data: ProcessExplorerData) { - const mainProcessService = new MainProcessService(windowId); + const mainProcessService = new ElectronIPCMainProcessService(windowId); this.nativeHostService = new NativeHostService(windowId, mainProcessService) as INativeHostService; this.applyStyles(data.styles); @@ -56,8 +244,19 @@ class ProcessExplorer { windows.forEach(window => this.mapPidToWindowTitle.set(window.pid, window.title)); }); - ipcRenderer.on('vscode:listProcessesResponse', (event: unknown, processRoots: [{ name: string, rootProcess: ProcessItem | IRemoteDiagnosticError }]) => { - this.updateProcessInfo(processRoots); + ipcRenderer.on('vscode:listProcessesResponse', async (event: unknown, processRoots: MachineProcessInformation[]) => { + processRoots.forEach((info, index) => { + if (isProcessItem(info.rootProcess)) { + info.rootProcess.name = index === 0 ? `${this.data.applicationName} main` : 'remote agent'; + } + }); + + if (!this.tree) { + await this.createProcessTree(processRoots); + } else { + this.tree.setInput({ processes: { processRoots } }); + } + this.requestProcessList(0); }); @@ -66,54 +265,58 @@ class ProcessExplorer { ipcRenderer.send('vscode:listProcesses'); } - private getProcessList(rootProcess: ProcessItem, isLocal: boolean, totalMem: number): FormattedProcessItem[] { - const processes: FormattedProcessItem[] = []; - const handledProcesses = new Set(); - - if (rootProcess) { - this.getProcessItem(processes, rootProcess, 0, isLocal, totalMem, handledProcesses); + private async createProcessTree(processRoots: MachineProcessInformation[]): Promise { + const container = document.getElementById('process-list'); + if (!container) { + return; } - return processes; - } + const { totalmem } = await this.nativeHostService.getOSStatistics(); - private getProcessItem(processes: FormattedProcessItem[], item: ProcessItem, indent: number, isLocal: boolean, totalMem: number, handledProcesses: Set): void { - const isRoot = (indent === 0); + const renderers = [ + new ProcessRenderer(this.data.platform, totalmem, this.mapPidToWindowTitle), + new ProcessHeaderTreeRenderer(), + new MachineRenderer(), + new ErrorRenderer() + ]; - handledProcesses.add(item.pid); + this.tree = new DataTree('processExplorer', + container, + new ProcessListDelegate(), + renderers, + new ProcessTreeDataSource(), + { + identityProvider: + { + getId: (element: ProcessTree | ProcessItem | MachineProcessInformation | ProcessInformation | IRemoteDiagnosticError) => { + if (isProcessItem(element)) { + return element.pid.toString(); + } - let name = item.name; - if (isRoot) { - name = isLocal ? `${this.data.applicationName} main` : 'remote agent'; - } + if (isRemoteDiagnosticError(element)) { + return element.hostName; + } - if (name === 'window') { - const windowTitle = this.mapPidToWindowTitle.get(item.pid); - name = windowTitle !== undefined ? `${name} (${this.mapPidToWindowTitle.get(item.pid)})` : name; - } + if (isProcessInformation(element)) { + return 'processes'; + } - // Format name with indent - const formattedName = isRoot ? name : `${' '.repeat(indent)} ${name}`; - const memory = this.data.platform === 'win32' ? item.mem : (totalMem * (item.mem / 100)); - processes.push({ - cpu: item.load, - memory: (memory / ByteSize.MB), - pid: item.pid.toFixed(0), - name, - formattedName, - cmd: item.cmd - }); + if (isMachineProcessInformation(element)) { + return element.name; + } - // Recurse into children if any - if (Array.isArray(item.children)) { - item.children.forEach(child => { - if (!child || handledProcesses.has(child.pid)) { - return; // prevent loops + return 'header'; + } } - - this.getProcessItem(processes, child, indent + 1, isLocal, totalMem, handledProcesses); }); - } + + this.tree.setInput({ processes: { processRoots } }); + this.tree.layout(window.innerHeight, window.innerWidth); + this.tree.onContextMenu(e => { + if (isProcessItem(e.element)) { + this.showContextMenu(e.element, true); + } + }); } private isDebuggable(cmd: string): boolean { @@ -121,7 +324,7 @@ class ProcessExplorer { return (matches && matches.length >= 2) || cmd.indexOf('node ') >= 0 || cmd.indexOf('node.exe') >= 0; } - private attachTo(item: FormattedProcessItem) { + private attachTo(item: ProcessItem) { const config: any = { type: 'node', request: 'attach', @@ -150,179 +353,16 @@ class ProcessExplorer { ipcRenderer.send('vscode:workbenchCommand', { id: 'debug.startFromConfig', from: 'processExplorer', args: [config] }); } - private getProcessIdWithHighestProperty(processList: any[], propertyName: string) { - let max = 0; - let maxProcessId; - processList.forEach(process => { - if (process[propertyName] > max) { - max = process[propertyName]; - maxProcessId = process.pid; - } - }); - - return maxProcessId; - } - - private updateSectionCollapsedState(shouldExpand: boolean, body: HTMLElement, twistie: CodiconLabel, sectionName: string) { - if (shouldExpand) { - body.classList.remove('hidden'); - this.collapsedStateCache.set(sectionName, false); - twistie.text = '$(chevron-down)'; - } else { - body.classList.add('hidden'); - this.collapsedStateCache.set(sectionName, true); - twistie.text = '$(chevron-right)'; - } - } - - private renderProcessFetchError(sectionName: string, errorMessage: string) { - const container = document.getElementById('process-list'); - if (!container) { - return; - } - - const body = document.createElement('tbody'); - - this.renderProcessGroupHeader(sectionName, body, container); - - const errorRow = document.createElement('tr'); - const data = document.createElement('td'); - data.textContent = errorMessage; - data.className = 'error'; - data.colSpan = 4; - errorRow.appendChild(data); - - body.appendChild(errorRow); - container.appendChild(body); - } - - private renderProcessGroupHeader(sectionName: string, body: HTMLElement, container: HTMLElement) { - const headerRow = document.createElement('tr'); - - const headerData = document.createElement('td'); - headerData.colSpan = 4; - headerRow.appendChild(headerData); - - const headerContainer = document.createElement('div'); - headerContainer.className = 'header'; - headerData.appendChild(headerContainer); - - const twistieContainer = document.createElement('div'); - const twistieCodicon = new CodiconLabel(twistieContainer); - this.updateSectionCollapsedState(!this.collapsedStateCache.get(sectionName), body, twistieCodicon, sectionName); - headerContainer.appendChild(twistieContainer); - - const headerLabel = document.createElement('span'); - headerLabel.textContent = sectionName; - headerContainer.appendChild(headerLabel); - - this.listeners.add(addDisposableListener(headerData, 'click', (e) => { - const isHidden = body.classList.contains('hidden'); - this.updateSectionCollapsedState(isHidden, body, twistieCodicon, sectionName); - })); - - container.appendChild(headerRow); - } - - private renderTableSection(sectionName: string, processList: FormattedProcessItem[], renderManySections: boolean, sectionIsLocal: boolean): void { - const container = document.getElementById('process-list'); - if (!container) { - return; - } - - const highestCPUProcess = this.getProcessIdWithHighestProperty(processList, 'cpu'); - const highestMemoryProcess = this.getProcessIdWithHighestProperty(processList, 'memory'); - - const body = document.createElement('tbody'); - - if (renderManySections) { - this.renderProcessGroupHeader(sectionName, body, container); - } - - processList.forEach(p => { - const row = document.createElement('tr'); - row.id = p.pid.toString(); - - const cpu = document.createElement('td'); - p.pid === highestCPUProcess - ? cpu.classList.add('centered', 'highest') - : cpu.classList.add('centered'); - cpu.textContent = p.cpu.toFixed(0); - - const memory = document.createElement('td'); - p.pid === highestMemoryProcess - ? memory.classList.add('centered', 'highest') - : memory.classList.add('centered'); - memory.textContent = p.memory.toFixed(0); - - const pid = document.createElement('td'); - pid.classList.add('centered'); - pid.textContent = p.pid; - - const name = document.createElement('th'); - name.scope = 'row'; - name.classList.add('data'); - name.title = p.cmd; - name.textContent = p.formattedName; - - row.append(cpu, memory, pid, name); - - this.listeners.add(addDisposableListener(row, 'contextmenu', (e) => { - this.showContextMenu(e, p, sectionIsLocal); - })); - - body.appendChild(row); - }); - - container.appendChild(body); - } - - private async updateProcessInfo(processLists: [{ name: string, rootProcess: ProcessItem | IRemoteDiagnosticError }]): Promise { - const container = document.getElementById('process-list'); - if (!container) { - return; - } - - container.innerText = ''; - this.listeners.clear(); - - const tableHead = $('thead', undefined); - const row = $('tr'); - tableHead.append(row); - - row.append($('th.cpu', { scope: 'col' }, localize('cpu', "CPU %"))); - row.append($('th.memory', { scope: 'col' }, localize('memory', "Memory (MB)"))); - row.append($('th.pid', { scope: 'col' }, localize('pid', "PID"))); - row.append($('th.nameLabel', { scope: 'col' }, localize('name', "Name"))); - - container.append(tableHead); - - const hasMultipleMachines = Object.keys(processLists).length > 1; - const { totalmem } = await this.nativeHostService.getOSStatistics(); - processLists.forEach((remote, i) => { - const isLocal = i === 0; - if (isRemoteDiagnosticError(remote.rootProcess)) { - this.renderProcessFetchError(remote.name, remote.rootProcess.errorMessage); - } else { - this.renderTableSection(remote.name, this.getProcessList(remote.rootProcess, isLocal, totalmem), hasMultipleMachines, isLocal); - } - }); - } - private applyStyles(styles: ProcessExplorerStyles): void { const styleTag = document.createElement('style'); const content: string[] = []; if (styles.hoverBackground) { - content.push(`tbody > tr:hover, table > tr:hover { background-color: ${styles.hoverBackground}; }`); + content.push(`.monaco-list-row:hover { background-color: ${styles.hoverBackground}; }`); } if (styles.hoverForeground) { - content.push(`tbody > tr:hover, table > tr:hover { color: ${styles.hoverForeground}; }`); - } - - if (styles.highlightForeground) { - content.push(`.highest { color: ${styles.highlightForeground}; }`); + content.push(`.monaco-list-row:hover { color: ${styles.hoverForeground}; }`); } styleTag.textContent = content.join('\n'); @@ -334,9 +374,7 @@ class ProcessExplorer { } } - private showContextMenu(e: MouseEvent, item: FormattedProcessItem, isLocal: boolean) { - e.preventDefault(); - + private showContextMenu(item: ProcessItem, isLocal: boolean) { const items: IContextMenuItem[] = []; const pid = Number(item.pid); @@ -417,8 +455,6 @@ class ProcessExplorer { } } - - export function startup(windowId: number, data: ProcessExplorerData): void { const platformClass = data.platform === 'win32' ? 'windows' : data.platform === 'linux' ? 'linux' : 'mac'; document.body.classList.add(platformClass); // used by our fonts diff --git a/src/vs/code/electron-sandbox/proxy/auth.html b/src/vs/code/electron-sandbox/proxy/auth.html deleted file mode 100644 index 788b68fce..000000000 --- a/src/vs/code/electron-sandbox/proxy/auth.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - -

    -
    -

    -
    -

    -

    -

    - - -

    -
    -
    - - - - - diff --git a/src/vs/code/electron-sandbox/proxy/auth.js b/src/vs/code/electron-sandbox/proxy/auth.js deleted file mode 100644 index 5e0db3c2d..000000000 --- a/src/vs/code/electron-sandbox/proxy/auth.js +++ /dev/null @@ -1,48 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -const { ipcRenderer } = window.vscode; - -function promptForCredentials(data) { - return new Promise((c, e) => { - const $title = document.getElementById('title'); - const $username = document.getElementById('username'); - const $password = document.getElementById('password'); - const $form = document.getElementById('form'); - const $cancel = document.getElementById('cancel'); - const $message = document.getElementById('message'); - - function submit() { - c({ username: $username.value, password: $password.value }); - return false; - } - - function cancel() { - c({ username: '', password: '' }); - return false; - } - - $form.addEventListener('submit', submit); - $cancel.addEventListener('click', cancel); - - document.body.addEventListener('keydown', function (e) { - switch (e.keyCode) { - case 27: e.preventDefault(); e.stopPropagation(); return cancel(); - case 13: e.preventDefault(); e.stopPropagation(); return submit(); - } - }); - - $title.textContent = data.title; - $message.textContent = data.message; - $username.focus(); - }); -} - -ipcRenderer.on('vscode:openProxyAuthDialog', async (event, data) => { - const response = await promptForCredentials(data); - ipcRenderer.send('vscode:proxyAuthResponse', response); -}); diff --git a/src/vs/code/electron-sandbox/workbench/workbench.html b/src/vs/code/electron-sandbox/workbench/workbench.html index 40737461d..f36737f2b 100644 --- a/src/vs/code/electron-sandbox/workbench/workbench.html +++ b/src/vs/code/electron-sandbox/workbench/workbench.html @@ -3,7 +3,8 @@ - + + diff --git a/src/vs/code/electron-sandbox/workbench/workbench.js b/src/vs/code/electron-sandbox/workbench/workbench.js index 6a46ef2b0..c5cb1debb 100644 --- a/src/vs/code/electron-sandbox/workbench/workbench.js +++ b/src/vs/code/electron-sandbox/workbench/workbench.js @@ -12,8 +12,7 @@ const bootstrapWindow = bootstrapWindowLib(); // Add a perf entry right from the top - const perf = bootstrapWindow.perfLib(); - perf.mark('renderer/started'); + performance.mark('code/didStartRenderer'); // Load workbench main JS, CSS and NLS all in parallel. This is an // optimization to prevent a waterfall of loading to happen, because @@ -24,10 +23,10 @@ 'vs/nls!vs/workbench/workbench.desktop.main', 'vs/css!vs/workbench/workbench.desktop.main' ], - async function (workbench, configuration) { + function (_, configuration) { // Mark start of workbench - perf.mark('didLoadWorkbenchMain'); + performance.mark('code/didLoadWorkbenchMain'); // @ts-ignore return require('vs/workbench/electron-sandbox/desktop.main').main(configuration); @@ -41,19 +40,34 @@ loaderConfig.recordStats = true; }, beforeRequire: function () { - perf.mark('willLoadWorkbenchMain'); + performance.mark('code/willLoadWorkbenchMain'); } } ); + // add default trustedTypes-policy for logging and to workaround + // lib/platform limitations + window.trustedTypes?.createPolicy('default', { + createHTML(value) { + // see https://github.com/electron/electron/issues/27211 + // Electron webviews use a static innerHTML default value and + // that isn't trusted. We use a default policy to check for the + // exact value of that innerHTML-string and only allow that. + if (value === '') { + return value; + } + throw new Error('UNTRUSTED html usage, default trusted types policy should NEVER be reached'); + // console.trace('UNTRUSTED html usage, default trusted types policy should NEVER be reached'); + // return value; + } + }); //region Helpers /** * @returns {{ - * load: (modules: string[], resultCallback: (result, configuration: object) => any, options: object) => unknown, - * globals: () => typeof import('../../../base/parts/sandbox/electron-sandbox/globals'), - * perfLib: () => { mark: (name: string) => void } + * load: (modules: string[], resultCallback: (result, configuration: import('../../../platform/windows/common/windows').INativeWindowConfiguration) => any, options: object) => unknown, + * globals: () => typeof import('../../../base/parts/sandbox/electron-sandbox/globals') * }} */ function bootstrapWindowLib() { diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index c6415a551..ac5570189 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -3,15 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as os from 'os'; -import * as fs from 'fs'; +import { homedir } from 'os'; +import { constants, existsSync, statSync, unlinkSync, chmodSync, truncateSync, readFileSync } from 'fs'; import { spawn, ChildProcess, SpawnOptions } from 'child_process'; import { buildHelpMessage, buildVersionMessage, OPTIONS } from 'vs/platform/environment/node/argv'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { parseCLIProcessArgv, addArg } from 'vs/platform/environment/node/argvHelper'; import { createWaitMarkerFile } from 'vs/platform/environment/node/waitMarkerFile'; import product from 'vs/platform/product/common/product'; -import * as paths from 'vs/base/common/path'; +import { isAbsolute, join } from 'vs/base/common/path'; import { whenDeleted, writeFileSync } from 'vs/base/node/pfs'; import { findFreePort, randomPort } from 'vs/base/node/ports'; import { isWindows, isLinux } from 'vs/base/common/platform'; @@ -69,10 +69,10 @@ export async function main(argv: string[]): Promise { // Validate if ( - !source || !target || source === target || // make sure source and target are provided and are not the same - !paths.isAbsolute(source) || !paths.isAbsolute(target) || // make sure both source and target are absolute paths - !fs.existsSync(source) || !fs.statSync(source).isFile() || // make sure source exists as file - !fs.existsSync(target) || !fs.statSync(target).isFile() // make sure target exists as file + !source || !target || source === target || // make sure source and target are provided and are not the same + !isAbsolute(source) || !isAbsolute(target) || // make sure both source and target are absolute paths + !existsSync(source) || !statSync(source).isFile() || // make sure source exists as file + !existsSync(target) || !statSync(target).isFile() // make sure target exists as file ) { throw new Error('Using --file-write with invalid arguments.'); } @@ -83,15 +83,15 @@ export async function main(argv: string[]): Promise { let targetMode: number = 0; let restoreMode = false; if (!!args['file-chmod']) { - targetMode = fs.statSync(target).mode; - if (!(targetMode & 128) /* readonly */) { - fs.chmodSync(target, targetMode | 128); + targetMode = statSync(target).mode; + if (!(targetMode & constants.S_IWUSR)) { + chmodSync(target, targetMode | constants.S_IWUSR); restoreMode = true; } } // Write source to target - const data = fs.readFileSync(source); + const data = readFileSync(source); if (isWindows) { // On Windows we use a different strategy of saving the file // by first truncating the file and then writing with r+ mode. @@ -99,7 +99,7 @@ export async function main(argv: string[]): Promise { // (see https://github.com/microsoft/vscode/issues/931) and // prevent removing alternate data streams // (see https://github.com/microsoft/vscode/issues/6363) - fs.truncateSync(target, 0); + truncateSync(target, 0); writeFileSync(target, data, { flag: 'r+' }); } else { writeFileSync(target, data); @@ -107,7 +107,7 @@ export async function main(argv: string[]): Promise { // Restore previous mode as needed if (restoreMode) { - fs.chmodSync(target, targetMode); + chmodSync(target, targetMode); } } catch (error) { error.message = `Error using --file-write: ${error.message}`; @@ -215,7 +215,7 @@ export async function main(argv: string[]): Promise { throw new Error('Failed to find free ports for profiler. Make sure to shutdown all instances of the editor first.'); } - const filenamePrefix = paths.join(os.homedir(), 'prof-' + Math.random().toString(16).slice(-4)); + const filenamePrefix = join(homedir(), 'prof-' + Math.random().toString(16).slice(-4)); addArg(argv, `--inspect-brk=${portMain}`); addArg(argv, `--remote-debugging-port=${portRenderer}`); @@ -338,7 +338,7 @@ export async function main(argv: string[]): Promise { // Make sure to delete the tmp stdin file if we have any if (stdinFilePath) { - fs.unlinkSync(stdinFilePath); + unlinkSync(stdinFilePath); } }); } diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 84fa06d76..ee189f832 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -3,11 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { release } from 'os'; +import * as fs from 'fs'; +import { gracefulify } from 'graceful-fs'; +import { isAbsolute, join } from 'vs/base/common/path'; import { raceTimeout } from 'vs/base/common/async'; -import * as semver from 'vs/base/common/semver/semver'; import product from 'vs/platform/product/common/product'; -import * as path from 'vs/base/common/path'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -15,416 +16,147 @@ import { InstantiationService } from 'vs/platform/instantiation/common/instantia import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { IExtensionManagementService, IExtensionGalleryService, IGalleryExtension, ILocalExtension, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, IExtensionGalleryService, IExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { combinedAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; -import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; +import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; import { IRequestService } from 'vs/platform/request/common/request'; import { RequestService } from 'vs/platform/request/node/requestService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; import { mkdirp, writeFile } from 'vs/base/node/pfs'; -import { getBaseLabel } from 'vs/base/common/labels'; import { IStateService } from 'vs/platform/state/node/state'; import { StateService } from 'vs/platform/state/node/stateService'; import { ILogService, getLogLevel, LogLevel, ConsoleLogService, MultiplexLogService } from 'vs/platform/log/common/log'; -import { isPromiseCanceledError } from 'vs/base/common/errors'; -import { areSameExtensions, adoptToGalleryExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { URI } from 'vs/base/common/uri'; -import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; -import { IExtensionManifest, ExtensionType, isLanguagePackExtension, EXTENSION_CATEGORIES } from 'vs/platform/extensions/common/extensions'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; import { Schemas } from 'vs/base/common/network'; import { SpdLogService } from 'vs/platform/log/node/spdlogService'; import { buildTelemetryMessage } from 'vs/platform/telemetry/node/telemetry'; import { FileService } from 'vs/platform/files/common/fileService'; import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { IProductService } from 'vs/platform/product/common/productService'; +import { ExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagementCLIService'; +import { URI } from 'vs/base/common/uri'; +import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; +import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; +import { setUnexpectedErrorHandler } from 'vs/base/common/errors'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; -const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id); -const notInstalled = (id: string) => localize('notInstalled', "Extension '{0}' is not installed.", id); -const useId = localize('useId', "Make sure you use the full extension ID, including the publisher, e.g.: {0}", 'ms-dotnettools.csharp'); - -function getId(manifest: IExtensionManifest, withVersion?: boolean): string { - if (withVersion) { - return `${manifest.publisher}.${manifest.name}@${manifest.version}`; - } else { - return `${manifest.publisher}.${manifest.name}`; - } -} - -const EXTENSION_ID_REGEX = /^([^.]+\..+)@(\d+\.\d+\.\d+(-.*)?)$/; - -export function getIdAndVersion(id: string): [string, string | undefined] { - const matches = EXTENSION_ID_REGEX.exec(id); - if (matches && matches[1]) { - return [adoptToGalleryExtensionId(matches[1]), matches[2]]; - } - return [adoptToGalleryExtensionId(id), undefined]; -} - -type InstallExtensionInfo = { id: string, version?: string, installOptions: InstallOptions }; - -export class Main { +class CliMain extends Disposable { constructor( - @IInstantiationService private readonly instantiationService: IInstantiationService, - @INativeEnvironmentService private readonly environmentService: INativeEnvironmentService, - @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, - @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, - ) { } + private argv: NativeParsedArgs + ) { + super(); - async run(argv: NativeParsedArgs): Promise { - if (argv['install-source']) { - await this.setInstallSource(argv['install-source']); - } else if (argv['list-extensions']) { - await this.listExtensions(!!argv['show-versions'], argv['category']); - } else if (argv['install-extension'] || argv['install-builtin-extension']) { - await this.installExtensions(argv['install-extension'] || [], argv['install-builtin-extension'] || [], !!argv['do-not-sync'], !!argv['force']); - } else if (argv['uninstall-extension']) { - await this.uninstallExtension(argv['uninstall-extension'], !!argv['force']); - } else if (argv['locate-extension']) { - await this.locateExtension(argv['locate-extension']); - } else if (argv['telemetry']) { - console.log(buildTelemetryMessage(this.environmentService.appRoot, this.environmentService.extensionsPath)); - } + // Enable gracefulFs + gracefulify(fs); + + this.registerListeners(); } - private setInstallSource(installSource: string): Promise { - return writeFile(this.environmentService.installSourcePath, installSource.slice(0, 30)); + private registerListeners(): void { + + // Dispose on exit + process.once('exit', () => this.dispose()); } - private async listExtensions(showVersions: boolean, category?: string): Promise { - let extensions = await this.extensionManagementService.getInstalled(ExtensionType.User); - const categories = EXTENSION_CATEGORIES.map(c => c.toLowerCase()); - if (category && category !== '') { - if (categories.indexOf(category.toLowerCase()) < 0) { - console.log('Invalid category please enter a valid category. To list valid categories run --category without a category specified'); - return; - } - extensions = extensions.filter(e => { - if (e.manifest.categories) { - const lowerCaseCategories: string[] = e.manifest.categories.map(c => c.toLowerCase()); - return lowerCaseCategories.indexOf(category.toLowerCase()) > -1; - } - return false; - }); - } else if (category === '') { - console.log('Possible Categories: '); - categories.forEach(category => { - console.log(category); - }); - return; - } - extensions.forEach(e => console.log(getId(e.manifest, showVersions))); - } + async run(): Promise { - async installExtensions(extensions: string[], builtinExtensionIds: string[], isMachineScoped: boolean, force: boolean): Promise { - const failed: string[] = []; - const installedExtensionsManifests: IExtensionManifest[] = []; - if (extensions.length) { - console.log(localize('installingExtensions', "Installing extensions...")); - } + // Services + const [instantiationService, appenders] = await this.initServices(); - const installed = await this.extensionManagementService.getInstalled(ExtensionType.User); - const checkIfNotInstalled = (id: string, version?: string): boolean => { - const installedExtension = installed.find(i => areSameExtensions(i.identifier, { id })); - if (installedExtension) { - if (!version && !force) { - console.log(localize('alreadyInstalled-checkAndUpdate', "Extension '{0}' v{1} is already installed. Use '--force' option to update to latest version or provide '@' to install a specific version, for example: '{2}@1.2.3'.", id, installedExtension.manifest.version, id)); - return false; - } - if (version && installedExtension.manifest.version === version) { - console.log(localize('alreadyInstalled', "Extension '{0}' is already installed.", `${id}@${version}`)); - return false; - } - } - return true; - }; - const vsixs: string[] = []; - const installExtensionInfos: InstallExtensionInfo[] = []; - for (const extension of extensions) { - if (/\.vsix$/i.test(extension)) { - vsixs.push(extension); - } else { - const [id, version] = getIdAndVersion(extension); - if (checkIfNotInstalled(id, version)) { - installExtensionInfos.push({ id, version, installOptions: { isBuiltin: false, isMachineScoped } }); - } - } - } - for (const extension of builtinExtensionIds) { - const [id, version] = getIdAndVersion(extension); - if (checkIfNotInstalled(id, version)) { - installExtensionInfos.push({ id, version, installOptions: { isBuiltin: true, isMachineScoped: false } }); - } - } + return instantiationService.invokeFunction(async accessor => { + const logService = accessor.get(ILogService); + const environmentService = accessor.get(INativeEnvironmentService); + const extensionManagementCLIService = accessor.get(IExtensionManagementCLIService); - if (vsixs.length) { - await Promise.all(vsixs.map(async vsix => { - try { - const manifest = await this.installVSIX(vsix, force); - if (manifest) { - installedExtensionsManifests.push(manifest); - } - } catch (err) { - console.error(err.message || err.stack || err); - failed.push(vsix); - } - })); - } + // Log info + logService.info('CLI main', this.argv); - if (installExtensionInfos.length) { + // Error handler + this.registerErrorHandler(logService); - const galleryExtensions = await this.getGalleryExtensions(installExtensionInfos); + // Run based on argv + await this.doRun(environmentService, extensionManagementCLIService); - await Promise.all(installExtensionInfos.map(async extensionInfo => { - const gallery = galleryExtensions.get(extensionInfo.id.toLowerCase()); - if (gallery) { - try { - const manifest = await this.installFromGallery(extensionInfo, gallery, installed, force); - if (manifest) { - installedExtensionsManifests.push(manifest); - } - } catch (err) { - console.error(err.message || err.stack || err); - failed.push(extensionInfo.id); - } - } else { - console.error(`${notFound(extensionInfo.version ? `${extensionInfo.id}@${extensionInfo.version}` : extensionInfo.id)}\n${useId}`); - failed.push(extensionInfo.id); - } - })); - - } - - if (installedExtensionsManifests.some(manifest => isLanguagePackExtension(manifest))) { - await this.updateLocalizationsCache(); - } - - if (failed.length) { - throw new Error(localize('installation failed', "Failed Installing Extensions: {0}", failed.join(', '))); - } - } - - private async installVSIX(vsix: string, force: boolean): Promise { - vsix = path.isAbsolute(vsix) ? vsix : path.join(process.cwd(), vsix); - const manifest = await getManifest(vsix); - const valid = await this.validate(manifest, force); - if (valid) { - try { - await this.extensionManagementService.install(URI.file(vsix)); - console.log(localize('successVsixInstall', "Extension '{0}' was successfully installed.", getBaseLabel(vsix))); - return manifest; - } catch (error) { - if (isPromiseCanceledError(error)) { - console.log(localize('cancelVsixInstall', "Cancelled installing extension '{0}'.", getBaseLabel(vsix))); - return null; - } else { - throw error; - } - } - } - return null; - } - - private async getGalleryExtensions(extensions: InstallExtensionInfo[]): Promise> { - const extensionIds = extensions.filter(({ version }) => version === undefined).map(({ id }) => id); - const extensionsWithIdAndVersion = extensions.filter(({ version }) => version !== undefined); - - const galleryExtensions = new Map(); - await Promise.all([ - (async () => { - const result = await this.extensionGalleryService.getExtensions(extensionIds, CancellationToken.None); - result.forEach(extension => galleryExtensions.set(extension.identifier.id.toLowerCase(), extension)); - })(), - Promise.all(extensionsWithIdAndVersion.map(async ({ id, version }) => { - const extension = await this.extensionGalleryService.getCompatibleExtension({ id }, version); - if (extension) { - galleryExtensions.set(extension.identifier.id.toLowerCase(), extension); - } - })) - ]); - - return galleryExtensions; - } - - private async installFromGallery({ id, version, installOptions }: InstallExtensionInfo, galleryExtension: IGalleryExtension, installed: ILocalExtension[], force: boolean): Promise { - const manifest = await this.extensionGalleryService.getManifest(galleryExtension, CancellationToken.None); - const installedExtension = installed.find(e => areSameExtensions(e.identifier, galleryExtension.identifier)); - if (installedExtension) { - if (galleryExtension.version === installedExtension.manifest.version) { - console.log(localize('alreadyInstalled', "Extension '{0}' is already installed.", version ? `${id}@${version}` : id)); - return null; - } - console.log(localize('updateMessage', "Updating the extension '{0}' to the version {1}", id, galleryExtension.version)); - } - - try { - if (installOptions.isBuiltin) { - console.log(localize('installing builtin ', "Installing builtin extension '{0}' v{1}...", id, galleryExtension.version)); - } else { - console.log(localize('installing', "Installing extension '{0}' v{1}...", id, galleryExtension.version)); - } - await this.extensionManagementService.installFromGallery(galleryExtension, installOptions); - console.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed.", id, galleryExtension.version)); - return manifest; - } catch (error) { - if (isPromiseCanceledError(error)) { - console.log(localize('cancelInstall', "Cancelled installing extension '{0}'.", id)); - return null; - } else { - throw error; - } - } - } - - private async validate(manifest: IExtensionManifest, force: boolean): Promise { - if (!manifest) { - throw new Error('Invalid vsix'); - } - - const extensionIdentifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) }; - const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User); - const newer = installedExtensions.find(local => areSameExtensions(extensionIdentifier, local.identifier) && semver.gt(local.manifest.version, manifest.version)); - - if (newer && !force) { - console.log(localize('forceDowngrade', "A newer version of extension '{0}' v{1} is already installed. Use '--force' option to downgrade to older version.", newer.identifier.id, newer.manifest.version, manifest.version)); - return false; - } - - return true; - } - - private async uninstallExtension(extensions: string[], force: boolean): Promise { - async function getExtensionId(extensionDescription: string): Promise { - if (!/\.vsix$/i.test(extensionDescription)) { - return extensionDescription; - } - - const zipPath = path.isAbsolute(extensionDescription) ? extensionDescription : path.join(process.cwd(), extensionDescription); - const manifest = await getManifest(zipPath); - return getId(manifest); - } - - const uninstalledExtensions: ILocalExtension[] = []; - for (const extension of extensions) { - const id = await getExtensionId(extension); - const installed = await this.extensionManagementService.getInstalled(); - const extensionToUninstall = installed.find(e => areSameExtensions(e.identifier, { id })); - if (!extensionToUninstall) { - throw new Error(`${notInstalled(id)}\n${useId}`); - } - if (extensionToUninstall.type === ExtensionType.System) { - console.log(localize('builtin', "Extension '{0}' is a Built-in extension and cannot be installed", id)); - return; - } - if (extensionToUninstall.isBuiltin && !force) { - console.log(localize('forceUninstall', "Extension '{0}' is marked as a Built-in extension by user. Please use '--force' option to uninstall it.", id)); - return; - } - console.log(localize('uninstalling', "Uninstalling {0}...", id)); - await this.extensionManagementService.uninstall(extensionToUninstall); - uninstalledExtensions.push(extensionToUninstall); - console.log(localize('successUninstall', "Extension '{0}' was successfully uninstalled!", id)); - } - - if (uninstalledExtensions.some(e => isLanguagePackExtension(e.manifest))) { - await this.updateLocalizationsCache(); - } - } - - private async locateExtension(extensions: string[]): Promise { - const installed = await this.extensionManagementService.getInstalled(); - extensions.forEach(e => { - installed.forEach(i => { - if (i.identifier.id === e) { - if (i.location.scheme === Schemas.file) { - console.log(i.location.fsPath); - return; - } - } - }); + // Flush the remaining data in AI adapter (with 1s timeout) + return raceTimeout(combinedAppender(...appenders).flush(), 1000); }); } - private async updateLocalizationsCache(): Promise { - const localizationService = this.instantiationService.createInstance(LocalizationsService); - await localizationService.update(); - localizationService.dispose(); - } -} + private async initServices(): Promise<[IInstantiationService, AppInsightsAppender[]]> { + const services = new ServiceCollection(); -const eventPrefix = 'monacoworkbench'; + // Environment + const environmentService = new NativeEnvironmentService(this.argv); + services.set(IEnvironmentService, environmentService); + services.set(INativeEnvironmentService, environmentService); -export async function main(argv: NativeParsedArgs): Promise { - const services = new ServiceCollection(); - const disposables = new DisposableStore(); + // Init folders + await Promise.all([environmentService.appSettingsHome.fsPath, environmentService.extensionsPath].map(path => path ? mkdirp(path) : undefined)); - const environmentService = new NativeEnvironmentService(argv); - const logLevel = getLogLevel(environmentService); - const loggers: ILogService[] = []; - loggers.push(new SpdLogService('cli', environmentService.logsPath, logLevel)); - if (logLevel === LogLevel.Trace) { - loggers.push(new ConsoleLogService(logLevel)); - } - const logService = new MultiplexLogService(loggers); - process.once('exit', () => logService.dispose()); - logService.info('main', argv); + // Log + const logLevel = getLogLevel(environmentService); + const loggers: ILogService[] = []; + loggers.push(new SpdLogService('cli', environmentService.logsPath, logLevel)); + if (logLevel === LogLevel.Trace) { + loggers.push(new ConsoleLogService(logLevel)); + } - await Promise.all([environmentService.appSettingsHome.fsPath, environmentService.extensionsPath] - .map((path): undefined | Promise => path ? mkdirp(path) : undefined)); + const logService = this._register(new MultiplexLogService(loggers)); + services.set(ILogService, logService); - // Files - const fileService = new FileService(logService); - disposables.add(fileService); - services.set(IFileService, fileService); + // Files + const fileService = this._register(new FileService(logService)); + services.set(IFileService, fileService); - const diskFileSystemProvider = new DiskFileSystemProvider(logService); - disposables.add(diskFileSystemProvider); - fileService.registerProvider(Schemas.file, diskFileSystemProvider); + const diskFileSystemProvider = this._register(new DiskFileSystemProvider(logService)); + fileService.registerProvider(Schemas.file, diskFileSystemProvider); - const configurationService = new ConfigurationService(environmentService.settingsResource, fileService); - disposables.add(configurationService); - await configurationService.initialize(); + // Configuration + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService)); + services.set(IConfigurationService, configurationService); - services.set(IEnvironmentService, environmentService); - services.set(INativeEnvironmentService, environmentService); + // Init config + await configurationService.initialize(); - services.set(ILogService, logService); - services.set(IConfigurationService, configurationService); - services.set(IStateService, new SyncDescriptor(StateService)); - services.set(IProductService, { _serviceBrand: undefined, ...product }); + // State + const stateService = new StateService(environmentService, logService); + services.set(IStateService, stateService); - const instantiationService: IInstantiationService = new InstantiationService(services); - - return instantiationService.invokeFunction(async accessor => { - const stateService = accessor.get(IStateService); + // Product + services.set(IProductService, { _serviceBrand: undefined, ...product }); const { appRoot, extensionsPath, extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService; - const services = new ServiceCollection(); + // Request services.set(IRequestService, new SyncDescriptor(RequestService)); + + // Extensions services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)); + services.set(IExtensionManagementCLIService, new SyncDescriptor(ExtensionManagementCLIService)); + // Localizations + services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService)); + + // Telemetry const appenders: AppInsightsAppender[] = []; if (isBuilt && !extensionDevelopmentLocationURI && !environmentService.disableTelemetry && product.enableTelemetry) { if (product.aiConfig && product.aiConfig.asimovKey) { - appenders.push(new AppInsightsAppender(eventPrefix, null, product.aiConfig.asimovKey)); + appenders.push(new AppInsightsAppender('monacoworkbench', null, product.aiConfig.asimovKey)); } const config: ITelemetryServiceConfig = { appender: combinedAppender(...appenders), sendErrorTelemetry: false, - commonProperties: resolveCommonProperties(product.commit, product.version, stateService.getItem('telemetry.machineId'), product.msftInternalDomains, installSourcePath), + commonProperties: resolveCommonProperties(fileService, release(), process.arch, product.commit, product.version, stateService.getItem('telemetry.machineId'), product.msftInternalDomains, installSourcePath), piiPaths: [appRoot, extensionsPath] }; @@ -434,17 +166,70 @@ export async function main(argv: NativeParsedArgs): Promise { services.set(ITelemetryService, NullTelemetryService); } - const instantiationService2 = instantiationService.createChild(services); - const main = instantiationService2.createInstance(Main); + return [new InstantiationService(services), appenders]; + } - try { - await main.run(argv); + private registerErrorHandler(logService: ILogService): void { - // Flush the remaining data in AI adapter. - // If it does not complete in 1 second, exit the process. - await raceTimeout(combinedAppender(...appenders).flush(), 1000); - } finally { - disposables.dispose(); + // Install handler for unexpected errors + setUnexpectedErrorHandler(error => { + const message = toErrorMessage(error, true); + if (!message) { + return; + } + + logService.error(`[uncaught exception in CLI]: ${message}`); + }); + } + + private async doRun(environmentService: INativeEnvironmentService, extensionManagementCLIService: IExtensionManagementCLIService): Promise { + + // Install Source + if (this.argv['install-source']) { + return this.setInstallSource(environmentService, this.argv['install-source']); } - }); + + // List Extensions + if (this.argv['list-extensions']) { + return extensionManagementCLIService.listExtensions(!!this.argv['show-versions'], this.argv['category']); + } + + // Install Extension + else if (this.argv['install-extension'] || this.argv['install-builtin-extension']) { + return extensionManagementCLIService.installExtensions(this.asExtensionIdOrVSIX(this.argv['install-extension'] || []), this.argv['install-builtin-extension'] || [], !!this.argv['do-not-sync'], !!this.argv['force']); + } + + // Uninstall Extension + else if (this.argv['uninstall-extension']) { + return extensionManagementCLIService.uninstallExtensions(this.asExtensionIdOrVSIX(this.argv['uninstall-extension']), !!this.argv['force']); + } + + // Locate Extension + else if (this.argv['locate-extension']) { + return extensionManagementCLIService.locateExtension(this.argv['locate-extension']); + } + + // Telemetry + else if (this.argv['telemetry']) { + console.log(buildTelemetryMessage(environmentService.appRoot, environmentService.extensionsPath)); + } + } + + private asExtensionIdOrVSIX(inputs: string[]): (string | URI)[] { + return inputs.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(process.cwd(), input)) : input); + } + + private setInstallSource(environmentService: INativeEnvironmentService, installSource: string): Promise { + return writeFile(environmentService.installSourcePath, installSource.slice(0, 30)); + } +} + +export async function main(argv: NativeParsedArgs): Promise { + const cliMain = new CliMain(argv); + + try { + await cliMain.run(); + } finally { + cliMain.dispose(); + } } diff --git a/src/vs/code/node/shellEnv.ts b/src/vs/code/node/shellEnv.ts index 29ef8c143..9454f1d47 100644 --- a/src/vs/code/node/shellEnv.ts +++ b/src/vs/code/node/shellEnv.ts @@ -5,11 +5,12 @@ import { spawn } from 'child_process'; import { generateUuid } from 'vs/base/common/uuid'; -import { isWindows } from 'vs/base/common/platform'; +import { isWindows, platform } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper'; import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { getSystemShell } from 'vs/base/node/shell'; /** * We need to get the environment from a user's shell. @@ -58,7 +59,7 @@ export async function resolveShellEnv(logService: ILogService, args: NativeParse let unixShellEnvPromise: Promise | undefined = undefined; async function doResolveUnixShellEnv(logService: ILogService): Promise { - const promise = new Promise((resolve, reject) => { + const promise = new Promise(async (resolve, reject) => { const runAsNode = process.env['ELECTRON_RUN_AS_NODE']; logService.trace('getUnixShellEnvironment#runAsNode', runAsNode); @@ -78,7 +79,8 @@ async function doResolveUnixShellEnv(logService: ILogService): Promise 2000); return new FontInfo({ zoomLevel: browser.getZoomLevel(), + pixelRatio: browser.getPixelRatio(), fontFamily: bareFontInfo.fontFamily, fontWeight: bareFontInfo.fontWeight, fontSize: bareFontInfo.fontSize, @@ -331,15 +333,15 @@ export class Configuration extends CommonEditorConfiguration { constructor( isSimpleWidget: boolean, - options: IEditorConstructionOptions, + options: Readonly, referenceDomElement: HTMLElement | null = null, private readonly accessibilityService: IAccessibilityService ) { super(isSimpleWidget, options); - this._elementSizeObserver = this._register(new ElementSizeObserver(referenceDomElement, options.dimension, () => this._onReferenceDomElementSizeChanged())); + this._elementSizeObserver = this._register(new ElementSizeObserver(referenceDomElement, options.dimension, () => this._recomputeOptions())); - this._register(CSSBasedConfiguration.INSTANCE.onDidChange(() => this._onCSSBasedConfigurationChanged())); + this._register(CSSBasedConfiguration.INSTANCE.onDidChange(() => this._recomputeOptions())); if (this._validatedOptions.get(EditorOption.automaticLayout)) { this._elementSizeObserver.startObserving(); @@ -351,28 +353,24 @@ export class Configuration extends CommonEditorConfiguration { this._recomputeOptions(); } - private _onReferenceDomElementSizeChanged(): void { - this._recomputeOptions(); - } - - private _onCSSBasedConfigurationChanged(): void { - this._recomputeOptions(); - } - public observeReferenceElement(dimension?: IDimension): void { this._elementSizeObserver.observe(dimension); } - public dispose(): void { - super.dispose(); + public updatePixelRatio(): void { + this._recomputeOptions(); } - private _getExtraEditorClassName(): string { + private static _getExtraEditorClassName(): string { let extra = ''; if (!browser.isSafari && !browser.isWebkitWebView) { // Use user-select: none in all browsers except Safari and native macOS WebView extra += 'no-user-select '; } + if (browser.isSafari) { + // See https://github.com/microsoft/vscode/issues/108822 + extra += 'no-minimap-shadow '; + } if (platform.isMacintosh) { extra += 'mac '; } @@ -381,7 +379,7 @@ export class Configuration extends CommonEditorConfiguration { protected _getEnvConfiguration(): IEnvConfiguration { return { - extraEditorClassName: this._getExtraEditorClassName(), + extraEditorClassName: Configuration._getExtraEditorClassName(), outerWidth: this._elementSizeObserver.getWidth(), outerHeight: this._elementSizeObserver.getHeight(), emptySelectionClipboard: browser.isWebKit || browser.isFirefox, diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index 06eecaa6a..31f3a0b68 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -110,7 +110,12 @@ export class MouseHandler extends ViewEventHandler { return; } const e = new StandardWheelEvent(browserEvent); - if (e.browserEvent!.ctrlKey || e.browserEvent!.metaKey) { + const doMouseWheelZoom = ( + platform.isMacintosh + ? (browserEvent.metaKey && !browserEvent.ctrlKey && !browserEvent.shiftKey && !browserEvent.altKey) + : (browserEvent.ctrlKey && !browserEvent.metaKey && !browserEvent.shiftKey && !browserEvent.altKey) + ); + if (doMouseWheelZoom) { const zoomLevel: number = EditorZoom.getZoomLevel(); const delta = e.deltaY > 0 ? 1 : -1; EditorZoom.setZoomLevel(zoomLevel + delta); diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index 37c1d8b05..ada5f46af 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -269,11 +269,11 @@ export class HitTestContext { const viewZoneWhitespace = context.viewLayout.getWhitespaceAtVerticalOffset(mouseVerticalOffset); if (viewZoneWhitespace) { - let viewZoneMiddle = viewZoneWhitespace.verticalOffset + viewZoneWhitespace.height / 2, - lineCount = context.model.getLineCount(), - positionBefore: Position | null = null, - position: Position | null, - positionAfter: Position | null = null; + const viewZoneMiddle = viewZoneWhitespace.verticalOffset + viewZoneWhitespace.height / 2; + const lineCount = context.model.getLineCount(); + let positionBefore: Position | null = null; + let position: Position | null; + let positionAfter: Position | null = null; if (viewZoneWhitespace.afterLineNumber !== lineCount) { // There are more lines after this view zone @@ -767,7 +767,7 @@ export class MouseTargetFactory { const lineWidth = ctx.getLineWidth(lineNumber); if (request.mouseContentHorizontalOffset > lineWidth) { - if (browser.isEdge && pos.column === 1) { + if (browser.isEdgeLegacy && pos.column === 1) { // See https://github.com/microsoft/vscode/issues/10875 const detail = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth); return request.fulfill(MouseTargetType.CONTENT_EMPTY, new Position(lineNumber, ctx.model.getLineMaxColumn(lineNumber)), undefined, detail); @@ -940,12 +940,16 @@ export class MouseTargetFactory { } } - // For inline decorations, Gecko returns the `` of the line and the offset is the `` with the inline decoration + // For inline decorations, Gecko sometimes returns the `` of the line and the offset is the `` with the inline decoration + // Some other times, it returns the `` with the inline decoration if (hitResult.offsetNode.nodeType === hitResult.offsetNode.ELEMENT_NODE) { - const parent1 = hitResult.offsetNode.parentNode; // expected to be the view line div + const parent1 = hitResult.offsetNode.parentNode; const parent1ClassName = parent1 && parent1.nodeType === parent1.ELEMENT_NODE ? (parent1).className : null; + const parent2 = parent1 ? parent1.parentNode : null; + const parent2ClassName = parent2 && parent2.nodeType === parent2.ELEMENT_NODE ? (parent2).className : null; if (parent1ClassName === ViewLine.CLASS_NAME) { + // it returned the `` of the line and the offset is the `` with the inline decoration const tokenSpan = hitResult.offsetNode.childNodes[Math.min(hitResult.offset, hitResult.offsetNode.childNodes.length - 1)]; if (tokenSpan) { const p = ctx.getPositionFromDOMInfo(tokenSpan, 0); @@ -954,6 +958,13 @@ export class MouseTargetFactory { hitTarget: null }; } + } else if (parent2ClassName === ViewLine.CLASS_NAME) { + // it returned the `` with the inline decoration + const p = ctx.getPositionFromDOMInfo(hitResult.offsetNode, 0); + return { + position: p, + hitTarget: null + }; } } @@ -1014,12 +1025,11 @@ export class MouseTargetFactory { } private static _snapToSoftTabBoundary(position: Position, viewModel: IViewModel): Position { - const minColumn = viewModel.getLineMinColumn(position.lineNumber); const lineContent = viewModel.getLineContent(position.lineNumber); const { tabSize } = viewModel.getTextModelOptions(); - const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, position.column - minColumn, tabSize, Direction.Nearest); + const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, position.column - 1, tabSize, Direction.Nearest); if (newPosition !== -1) { - return new Position(position.lineNumber, newPosition + minColumn); + return new Position(position.lineNumber, newPosition + 1); } return position; } diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 2f26618aa..b2f38304e 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -54,7 +54,7 @@ class VisibleTextAreaData { } } -const canUseZeroSizeTextarea = (browser.isEdge || browser.isFirefox); +const canUseZeroSizeTextarea = (browser.isFirefox); export class TextAreaHandler extends ViewPart { @@ -280,14 +280,8 @@ export class TextAreaHandler extends ViewPart { })); this._register(this._textAreaInput.onCompositionUpdate((e: ICompositionData) => { - if (browser.isEdge) { - // Due to isEdgeOrIE (where the textarea was not cleared initially) - // we cannot assume the text consists only of the composited text - this._visibleTextArea = this._visibleTextArea!.setWidth(0); - } else { - // adjust width by its size - this._visibleTextArea = this._visibleTextArea!.setWidth(measureText(e.data, this._fontInfo)); - } + // adjust width by its size + this._visibleTextArea = this._visibleTextArea!.setWidth(measureText(e.data, this._fontInfo)); this._render(); })); diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index bc1e26c1c..60627babe 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -13,7 +13,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; -import { ITextAreaWrapper, ITypeData, TextAreaState } from 'vs/editor/browser/controller/textAreaState'; +import { ITextAreaWrapper, ITypeData, TextAreaState, _debugComposition } from 'vs/editor/browser/controller/textAreaState'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; import { BrowserFeatures } from 'vs/base/browser/canIUse'; @@ -147,6 +147,7 @@ export class TextAreaInput extends Disposable { private readonly _host: ITextAreaInputHost; private readonly _textArea: TextAreaWrapper; private readonly _asyncTriggerCut: RunOnceScheduler; + private readonly _asyncFocusGainWriteScreenReaderContent: RunOnceScheduler; private _textAreaState: TextAreaState; private _selectionChangeListener: IDisposable | null; @@ -160,6 +161,7 @@ export class TextAreaInput extends Disposable { this._host = host; this._textArea = this._register(new TextAreaWrapper(textArea)); this._asyncTriggerCut = this._register(new RunOnceScheduler(() => this._onCut.fire(), 0)); + this._asyncFocusGainWriteScreenReaderContent = this._register(new RunOnceScheduler(() => this.writeScreenReaderContent('asyncFocusGain'), 0)); this._textAreaState = TextAreaState.EMPTY; this._selectionChangeListener = null; @@ -193,6 +195,10 @@ export class TextAreaInput extends Disposable { })); this._register(dom.addDisposableListener(textArea.domNode, 'compositionstart', (e: CompositionEvent) => { + if (_debugComposition) { + console.log(`[compositionstart]`, e); + } + if (this._isDoingComposition) { return; } @@ -209,6 +215,9 @@ export class TextAreaInput extends Disposable { ) { // Handling long press case on macOS + arrow key => pretend the character was selected if (lastKeyDown.code === 'ArrowRight' || lastKeyDown.code === 'ArrowLeft') { + if (_debugComposition) { + console.log(`[compositionstart] Handling long press case on macOS + arrow key`, e); + } moveOneCharacterLeft = true; } } @@ -221,8 +230,7 @@ export class TextAreaInput extends Disposable { this._textAreaState.selectionStartPosition ? new Position(this._textAreaState.selectionStartPosition.lineNumber, this._textAreaState.selectionStartPosition.column - 1) : null, this._textAreaState.selectionEndPosition ); - } else if (!browser.isEdge) { - // In IE we cannot set .value when handling 'compositionstart' because the entire composition will get canceled. + } else { this._setAndWriteTextAreaState('compositionstart', TextAreaState.EMPTY); } @@ -251,27 +259,10 @@ export class TextAreaInput extends Disposable { return [newState, typeInput]; }; - const compositionDataInValid = (locale: string): boolean => { - // https://github.com/microsoft/monaco-editor/issues/339 - // Multi-part Japanese compositions reset cursor in Edge/IE, Chinese and Korean IME don't have this issue. - // The reason that we can't use this path for all CJK IME is IE and Edge behave differently when handling Korean IME, - // which breaks this path of code. - if (browser.isEdge && locale === 'ja') { - return true; - } - - return false; - }; - this._register(dom.addDisposableListener(textArea.domNode, 'compositionupdate', (e: CompositionEvent) => { - if (compositionDataInValid(e.locale)) { - const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/false); - this._textAreaState = newState; - this._onType.fire(typeInput); - this._onCompositionUpdate.fire(e); - return; + if (_debugComposition) { + console.log(`[compositionupdate]`, e); } - const [newState, typeInput] = deduceComposition(e.data || ''); this._textAreaState = newState; this._onType.fire(typeInput); @@ -279,28 +270,23 @@ export class TextAreaInput extends Disposable { })); this._register(dom.addDisposableListener(textArea.domNode, 'compositionend', (e: CompositionEvent) => { + if (_debugComposition) { + console.log(`[compositionend]`, e); + } // https://github.com/microsoft/monaco-editor/issues/1663 // On iOS 13.2, Chinese system IME randomly trigger an additional compositionend event with empty data if (!this._isDoingComposition) { return; } - if (compositionDataInValid(e.locale)) { - // https://github.com/microsoft/monaco-editor/issues/339 - const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/false); - this._textAreaState = newState; - this._onType.fire(typeInput); - } else { - const [newState, typeInput] = deduceComposition(e.data || ''); - this._textAreaState = newState; - this._onType.fire(typeInput); - } - // Due to - // isEdgeOrIE (where the textarea was not cleared initially) - // and isChrome (the textarea is not updated correctly when composition ends) - // and isFirefox (the textare ais not updated correctly after inserting emojis) - // we cannot assume the text at the end consists only of the composited text - if (browser.isEdge || browser.isChrome || browser.isFirefox) { + const [newState, typeInput] = deduceComposition(e.data || ''); + this._textAreaState = newState; + this._onType.fire(typeInput); + + // isChrome: the textarea is not updated correctly when composition ends + // isFirefox: the textarea is not updated correctly after inserting emojis + // => we cannot assume the text at the end consists only of the composited text + if (browser.isChrome || browser.isFirefox) { this._textAreaState = TextAreaState.readFromTextArea(this._textArea); } @@ -375,9 +361,31 @@ export class TextAreaInput extends Disposable { })); this._register(dom.addDisposableListener(textArea.domNode, 'focus', () => { + const hadFocus = this._hasFocus; + this._setHasFocus(true); + + if (browser.isSafari && !hadFocus && this._hasFocus) { + // When "tabbing into" the textarea, immediately after dispatching the 'focus' event, + // Safari will always move the selection at offset 0 in the textarea + this._asyncFocusGainWriteScreenReaderContent.schedule(); + } })); this._register(dom.addDisposableListener(textArea.domNode, 'blur', () => { + if (this._isDoingComposition) { + // See https://github.com/microsoft/vscode/issues/112621 + // where compositionend is not triggered when the editor + // is taken off-dom during a composition + + // Clear the flag to be able to write to the textarea + this._isDoingComposition = false; + + // Clear the textarea to avoid an unwanted cursor type + this.writeScreenReaderContent('blurWithoutCompositionEnd'); + + // Fire artificial composition end + this._onCompositionEnd.fire(); + } this._setHasFocus(false); })); } @@ -513,13 +521,7 @@ export class TextAreaInput extends Disposable { } if (this._hasFocus) { - if (browser.isEdge) { - // Edge has a bug where setting the selection range while the focus event - // is dispatching doesn't work. To reproduce, "tab into" the editor. - this._setAndWriteTextAreaState('focusgain', TextAreaState.EMPTY); - } else { - this.writeScreenReaderContent('focusgain'); - } + this.writeScreenReaderContent('focusgain'); } if (this._hasFocus) { diff --git a/src/vs/editor/browser/controller/textAreaState.ts b/src/vs/editor/browser/controller/textAreaState.ts index 65c815e38..d8a2f2013 100644 --- a/src/vs/editor/browser/controller/textAreaState.ts +++ b/src/vs/editor/browser/controller/textAreaState.ts @@ -8,6 +8,8 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { EndOfLinePreference } from 'vs/editor/common/model'; +export const _debugComposition = false; + export interface ITextAreaWrapper { getValue(): string; setValue(reason: string, value: string): void; @@ -59,7 +61,9 @@ export class TextAreaState { } public writeToTextArea(reason: string, textArea: ITextAreaWrapper, select: boolean): void { - // console.log(Date.now() + ': writeToTextArea ' + reason + ': ' + this.toString()); + if (_debugComposition) { + console.log('writeToTextArea ' + reason + ': ' + this.toString()); + } textArea.setValue(reason, this.value); if (select) { textArea.setSelectionRange(reason, this.selectionStart, this.selectionEnd); @@ -105,9 +109,11 @@ export class TextAreaState { }; } - // console.log('------------------------deduceInput'); - // console.log('PREVIOUS STATE: ' + previousState.toString()); - // console.log('CURRENT STATE: ' + currentState.toString()); + if (_debugComposition) { + console.log('------------------------deduceInput'); + console.log('PREVIOUS STATE: ' + previousState.toString()); + console.log('CURRENT STATE: ' + currentState.toString()); + } let previousValue = previousState.value; let previousSelectionStart = previousState.selectionStart; @@ -133,8 +139,10 @@ export class TextAreaState { currentSelectionEnd -= prefixLength; previousSelectionEnd -= prefixLength; - // console.log('AFTER DIFFING PREVIOUS STATE: <' + previousValue + '>, selectionStart: ' + previousSelectionStart + ', selectionEnd: ' + previousSelectionEnd); - // console.log('AFTER DIFFING CURRENT STATE: <' + currentValue + '>, selectionStart: ' + currentSelectionStart + ', selectionEnd: ' + currentSelectionEnd); + if (_debugComposition) { + console.log('AFTER DIFFING PREVIOUS STATE: <' + previousValue + '>, selectionStart: ' + previousSelectionStart + ', selectionEnd: ' + previousSelectionEnd); + console.log('AFTER DIFFING CURRENT STATE: <' + currentValue + '>, selectionStart: ' + currentSelectionStart + ', selectionEnd: ' + currentSelectionEnd); + } if (couldBeEmojiInput && currentSelectionStart === currentSelectionEnd && previousValue.length > 0) { // on OSX, emojis from the emoji picker are inserted at random locations @@ -196,7 +204,9 @@ export class TextAreaState { // no current selection const replacePreviousCharacters = (previousPrefix.length - prefixLength); - // console.log('REMOVE PREVIOUS: ' + (previousPrefix.length - prefixLength) + ' chars'); + if (_debugComposition) { + console.log('REMOVE PREVIOUS: ' + (previousPrefix.length - prefixLength) + ' chars'); + } return { text: currentValue, diff --git a/src/vs/editor/browser/core/markdownRenderer.ts b/src/vs/editor/browser/core/markdownRenderer.ts index e1eb76c8a..ec24b6b5d 100644 --- a/src/vs/editor/browser/core/markdownRenderer.ts +++ b/src/vs/editor/browser/core/markdownRenderer.ts @@ -88,9 +88,7 @@ export class MarkdownRenderer { const element = document.createElement('span'); - element.innerHTML = MarkdownRenderer._ttpTokenizer - ? MarkdownRenderer._ttpTokenizer.createHTML(value, tokenization) as unknown as string - : tokenizeToString(value, tokenization); + element.innerHTML = (MarkdownRenderer._ttpTokenizer?.createHTML(value, tokenization) ?? tokenizeToString(value, tokenization)) as string; // use "good" font let fontFamily = this._options.codeBlockFontFamily; @@ -105,7 +103,7 @@ export class MarkdownRenderer { }, asyncRenderCallback: () => this._onDidRenderAsync.fire(), actionHandler: { - callback: (content) => this._openerService.open(content, { fromUserGesture: true }).catch(onUnexpectedError), + callback: (content) => this._openerService.open(content, { fromUserGesture: true, allowContributedOpeners: true }).catch(onUnexpectedError), disposeables } }; diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 43305c283..bfa07b8dc 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -361,6 +361,11 @@ export interface IEditorConstructionOptions extends IEditorOptions { } export interface IDiffEditorConstructionOptions extends IDiffEditorOptions { + /** + * The initial editor dimension (to avoid measuring the container). + */ + dimension?: editorCommon.IDimension; + /** * Place overflow widgets inside an external DOM node. * Defaults to an internal DOM node. @@ -1047,7 +1052,7 @@ export interface IDiffEditor extends editorCommon.IEditor { /** *@internal */ -export function isCodeEditor(thing: any): thing is ICodeEditor { +export function isCodeEditor(thing: unknown): thing is ICodeEditor { if (thing && typeof (thing).getEditorType === 'function') { return (thing).getEditorType() === editorCommon.EditorType.ICodeEditor; } else { @@ -1058,7 +1063,7 @@ export function isCodeEditor(thing: any): thing is ICodeEditor { /** *@internal */ -export function isDiffEditor(thing: any): thing is IDiffEditor { +export function isDiffEditor(thing: unknown): thing is IDiffEditor { if (thing && typeof (thing).getEditorType === 'function') { return (thing).getEditorType() === editorCommon.EditorType.IDiffEditor; } else { @@ -1069,8 +1074,8 @@ export function isDiffEditor(thing: any): thing is IDiffEditor { /** *@internal */ -export function isCompositeEditor(thing: any): thing is editorCommon.ICompositeCodeEditor { - return thing +export function isCompositeEditor(thing: unknown): thing is editorCommon.ICompositeCodeEditor { + return !!thing && typeof thing === 'object' && typeof (thing).onDidChangeActiveEditor === 'function'; @@ -1079,7 +1084,7 @@ export function isCompositeEditor(thing: any): thing is editorCommon.ICompositeC /** *@internal */ -export function getCodeEditor(thing: any): ICodeEditor | null { +export function getCodeEditor(thing: unknown): ICodeEditor | null { if (isCodeEditor(thing)) { return thing; } diff --git a/src/vs/editor/browser/services/bulkEditService.ts b/src/vs/editor/browser/services/bulkEditService.ts index a9e8b99dc..5a49c330f 100644 --- a/src/vs/editor/browser/services/bulkEditService.ts +++ b/src/vs/editor/browser/services/bulkEditService.ts @@ -69,11 +69,11 @@ export interface IBulkEditOptions { progress?: IProgress; token?: CancellationToken; showPreview?: boolean; - suppressPreview?: boolean; label?: string; quotableLabel?: string; undoRedoSource?: UndoRedoSource; undoRedoGroupId?: number; + confirmBeforeUndo?: boolean; } export interface IBulkEditResult { diff --git a/src/vs/editor/browser/services/codeEditorServiceImpl.ts b/src/vs/editor/browser/services/codeEditorServiceImpl.ts index c961bfd79..952b317c6 100644 --- a/src/vs/editor/browser/services/codeEditorServiceImpl.ts +++ b/src/vs/editor/browser/services/codeEditorServiceImpl.ts @@ -347,6 +347,8 @@ const _CSS_MAP: { [prop: string]: string; } = { fontStyle: 'font-style:{0};', fontWeight: 'font-weight:{0};', + fontSize: 'font-size:{0};', + fontFamily: 'font-family:{0};', textDecoration: 'text-decoration:{0};', cursor: 'cursor:{0};', letterSpacing: 'letter-spacing:{0};', @@ -357,6 +359,7 @@ const _CSS_MAP: { [prop: string]: string; } = { contentText: 'content:\'{0}\';', contentIconPath: 'content:{0};', margin: 'margin:{0};', + padding: 'padding:{0};', width: 'width:{0};', height: 'height:{0};' }; @@ -529,7 +532,7 @@ class DecorationCSSRules { cssTextArr.push(strings.format(_CSS_MAP.contentText, escaped)); } - this.collectCSSText(opts, ['fontStyle', 'fontWeight', 'textDecoration', 'color', 'opacity', 'backgroundColor', 'margin'], cssTextArr); + this.collectCSSText(opts, ['fontStyle', 'fontWeight', 'fontSize', 'fontFamily', 'textDecoration', 'color', 'opacity', 'backgroundColor', 'margin', 'padding'], cssTextArr); if (this.collectCSSText(opts, ['width', 'height'], cssTextArr)) { cssTextArr.push('display:inline-block;'); } diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index 1360871f7..dd329d44f 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -4,18 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { IDisposable } from 'vs/base/common/lifecycle'; import { LinkedList } from 'vs/base/common/linkedList'; +import { ResourceMap } from 'vs/base/common/map'; import { parse } from 'vs/base/common/marshalling'; import { Schemas } from 'vs/base/common/network'; import { normalizePath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IOpener, IOpenerService, IValidator, IExternalUriResolver, OpenOptions, ResolveExternalUriOptions, IResolvedExternalUri, IExternalOpener, matchesScheme } from 'vs/platform/opener/common/opener'; import { EditorOpenContext } from 'vs/platform/editor/common/editor'; -import { ResourceMap } from 'vs/base/common/map'; - +import { IExternalOpener, IExternalUriResolver, IOpener, IOpenerService, IResolvedExternalUri, IValidator, matchesScheme, OpenOptions, ResolveExternalUriOptions } from 'vs/platform/opener/common/opener'; class CommandOpener implements IOpener { @@ -100,15 +100,16 @@ export class OpenerService implements IOpenerService { private readonly _resolvers = new LinkedList(); private readonly _resolvedUriTargets = new ResourceMap(uri => uri.with({ path: null, fragment: null, query: null }).toString()); - private _externalOpener: IExternalOpener; + private _defaultExternalOpener: IExternalOpener; + private readonly _externalOpeners = new LinkedList(); constructor( @ICodeEditorService editorService: ICodeEditorService, @ICommandService commandService: ICommandService, ) { // Default external opener is going through window.open() - this._externalOpener = { - openExternal: href => { + this._defaultExternalOpener = { + openExternal: async href => { // ensure to open HTTP/HTTPS links into new windows // to not trigger a navigation. Any other link is // safe to be set as HREF to prevent a blank window @@ -118,11 +119,11 @@ export class OpenerService implements IOpenerService { } else { window.location.href = href; } - return Promise.resolve(true); + return true; } }; - // Default opener: maito, http(s), command, and catch-all-editors + // Default opener: any external, maito, http(s), command, and catch-all-editors this._openers.push({ open: async (target: URI | string, options?: OpenOptions) => { if (options?.openExternal || matchesScheme(target, Schemas.mailto) || matchesScheme(target, Schemas.http) || matchesScheme(target, Schemas.https)) { @@ -152,8 +153,13 @@ export class OpenerService implements IOpenerService { return { dispose: remove }; } - setExternalOpener(externalOpener: IExternalOpener): void { - this._externalOpener = externalOpener; + setDefaultExternalOpener(externalOpener: IExternalOpener): void { + this._defaultExternalOpener = externalOpener; + } + + registerExternalOpener(opener: IExternalOpener): IDisposable { + const remove = this._externalOpeners.push(opener); + return { dispose: remove }; } async open(target: URI | string, options?: OpenOptions): Promise { @@ -196,13 +202,29 @@ export class OpenerService implements IOpenerService { const uri = typeof resource === 'string' ? URI.parse(resource) : resource; const { resolved } = await this.resolveExternalUri(uri, options); + let href: string; if (typeof resource === 'string' && uri.toString() === resolved.toString()) { // open the url-string AS IS - return this._externalOpener.openExternal(resource); + href = resource; } else { // open URI using the toString(noEncode)+encodeURI-trick - return this._externalOpener.openExternal(encodeURI(resolved.toString(true))); + href = encodeURI(resolved.toString(true)); } + + if (options?.allowContributedOpeners) { + const preferredOpenerId = typeof options?.allowContributedOpeners === 'string' ? options?.allowContributedOpeners : undefined; + for (const opener of this._externalOpeners) { + const didOpen = await opener.openExternal(href, { + sourceUri: uri, + preferredOpenerId, + }, CancellationToken.None); + if (didOpen) { + return true; + } + } + } + + return this._defaultExternalOpener.openExternal(href, { sourceUri: uri }, CancellationToken.None); } dispose() { diff --git a/src/vs/editor/browser/view/domLineBreaksComputer.ts b/src/vs/editor/browser/view/domLineBreaksComputer.ts index 87c6461d5..cd486fc27 100644 --- a/src/vs/editor/browser/view/domLineBreaksComputer.ts +++ b/src/vs/editor/browser/view/domLineBreaksComputer.ts @@ -111,8 +111,8 @@ function createLineBreaks(requests: string[], fontInfo: FontInfo, tabSize: numbe allVisibleColumns[i] = tmp[1]; } const html = sb.build(); - const trustedhtml = ttPolicy ? ttPolicy.createHTML(html) : html; - containerDomNode.innerHTML = trustedhtml as unknown as string; + const trustedhtml = ttPolicy?.createHTML(html) ?? html; + containerDomNode.innerHTML = trustedhtml as string; containerDomNode.style.position = 'absolute'; containerDomNode.style.top = '10000'; diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index b656a3f8e..e0620c2a6 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; +import * as browser from 'vs/base/browser/browser'; import { Selection } from 'vs/editor/common/core/selection'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -65,6 +66,7 @@ export class View extends ViewEventHandler { private readonly _scrollbar: EditorScrollbar; private readonly _context: ViewContext; + private _configPixelRatio: number; private _selections: Selection[]; // The view lines @@ -104,6 +106,7 @@ export class View extends ViewEventHandler { // The view context is passed on to most classes (basically to reduce param. counts in ctors) this._context = new ViewContext(configuration, themeService.getColorTheme(), model); + this._configPixelRatio = this._configPixelRatio = this._context.configuration.options.get(EditorOption.pixelRatio); // Ensure the view is the first event handler in order to update the layout this._context.addEventHandler(this); @@ -298,6 +301,7 @@ export class View extends ViewEventHandler { this._scheduleRender(); } public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + this._configPixelRatio = this._context.configuration.options.get(EditorOption.pixelRatio); this.domNode.setClassName(this._getEditorClassName()); this._applyLayout(); return false; @@ -330,8 +334,8 @@ export class View extends ViewEventHandler { this._viewLines.dispose(); // Destroy view parts - for (let i = 0, len = this._viewParts.length; i < len; i++) { - this._viewParts[i].dispose(); + for (const viewPart of this._viewParts) { + viewPart.dispose(); } super.dispose(); @@ -354,8 +358,7 @@ export class View extends ViewEventHandler { private _getViewPartsToRender(): ViewPart[] { let result: ViewPart[] = [], resultLen = 0; - for (let i = 0, len = this._viewParts.length; i < len; i++) { - const viewPart = this._viewParts[i]; + for (const viewPart of this._viewParts) { if (viewPart.shouldRender()) { result[resultLen++] = viewPart; } @@ -401,16 +404,20 @@ export class View extends ViewEventHandler { const renderingContext = new RenderingContext(this._context.viewLayout, viewportData, this._viewLines); // Render the rest of the parts - for (let i = 0, len = viewPartsToRender.length; i < len; i++) { - const viewPart = viewPartsToRender[i]; + for (const viewPart of viewPartsToRender) { viewPart.prepareRender(renderingContext); } - for (let i = 0, len = viewPartsToRender.length; i < len; i++) { - const viewPart = viewPartsToRender[i]; + for (const viewPart of viewPartsToRender) { viewPart.render(renderingContext); viewPart.onDidRender(); } + + // Try to detect browser zooming and paint again if necessary + if (Math.abs(browser.getPixelRatio() - this._configPixelRatio) > 0.001) { + // looks like the pixel ratio has changed + this._context.configuration.updatePixelRatio(); + } } // --- BEGIN CodeEditor helpers @@ -462,8 +469,7 @@ export class View extends ViewEventHandler { if (everything) { // Force everything to render... this._viewLines.forceShouldRender(); - for (let i = 0, len = this._viewParts.length; i < len; i++) { - const viewPart = this._viewParts[i]; + for (const viewPart of this._viewParts) { viewPart.forceShouldRender(); } } diff --git a/src/vs/editor/browser/view/viewLayer.ts b/src/vs/editor/browser/view/viewLayer.ts index 89de8468f..0a1a7ddc0 100644 --- a/src/vs/editor/browser/view/viewLayer.ts +++ b/src/vs/editor/browser/view/viewLayer.ts @@ -506,15 +506,15 @@ class ViewLayerRenderer { ctx.lines.splice(removeIndex, removeCount); } - private _finishRenderingNewLines(ctx: IRendererContext, domNodeIsEmpty: boolean, newLinesHTML: string, wasNew: boolean[]): void { + private _finishRenderingNewLines(ctx: IRendererContext, domNodeIsEmpty: boolean, newLinesHTML: string | TrustedHTML, wasNew: boolean[]): void { if (ViewLayerRenderer._ttPolicy) { - newLinesHTML = ViewLayerRenderer._ttPolicy.createHTML(newLinesHTML) as unknown as string; // explains the ugly casts -> https://github.com/microsoft/vscode/issues/106396#issuecomment-692625393 + newLinesHTML = ViewLayerRenderer._ttPolicy.createHTML(newLinesHTML as string); } const lastChild = this.domNode.lastChild; if (domNodeIsEmpty || !lastChild) { - this.domNode.innerHTML = newLinesHTML; + this.domNode.innerHTML = newLinesHTML as string; // explains the ugly casts -> https://github.com/microsoft/vscode/issues/106396#issuecomment-692625393; } else { - lastChild.insertAdjacentHTML('afterend', newLinesHTML); + lastChild.insertAdjacentHTML('afterend', newLinesHTML as string); } let currChild = this.domNode.lastChild; @@ -527,13 +527,13 @@ class ViewLayerRenderer { } } - private _finishRenderingInvalidLines(ctx: IRendererContext, invalidLinesHTML: string, wasInvalid: boolean[]): void { + private _finishRenderingInvalidLines(ctx: IRendererContext, invalidLinesHTML: string | TrustedHTML, wasInvalid: boolean[]): void { const hugeDomNode = document.createElement('div'); if (ViewLayerRenderer._ttPolicy) { - invalidLinesHTML = ViewLayerRenderer._ttPolicy.createHTML(invalidLinesHTML) as unknown as string; + invalidLinesHTML = ViewLayerRenderer._ttPolicy.createHTML(invalidLinesHTML as string); } - hugeDomNode.innerHTML = invalidLinesHTML; + hugeDomNode.innerHTML = invalidLinesHTML as string; for (let i = 0; i < ctx.linesLength; i++) { const line = ctx.lines[i]; diff --git a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts index 6208c89fb..b3ab2991e 100644 --- a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts +++ b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts @@ -42,8 +42,8 @@ export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay { this._contentWidth = layoutInfo.contentWidth; this._selectionIsEmpty = true; this._focused = false; - this._cursorLineNumbers = []; - this._selections = []; + this._cursorLineNumbers = [1]; + this._selections = [new Selection(1, 1, 1, 1)]; this._renderData = null; this._context.addEventHandler(this); diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index d80556fb9..96754ec4b 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -44,7 +44,7 @@ const canUseFastRenderedViewLine = (function () { let monospaceAssumptionsAreValid = true; -const alwaysRenderInlineSelection = (browser.isEdge); +const alwaysRenderInlineSelection = (browser.isEdgeLegacy); export class DomReadingContext { diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.css b/src/vs/editor/browser/viewParts/minimap/minimap.css index f3692e7e4..f4663ece0 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.css +++ b/src/vs/editor/browser/viewParts/minimap/minimap.css @@ -25,3 +25,8 @@ left: -6px; width: 6px; } +.monaco-editor.no-minimap-shadow .minimap-shadow-visible { + position: absolute; + left: -1px; + width: 1px; +} diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 7ca33211a..348315fa9 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -862,6 +862,7 @@ export class Minimap extends ViewPart implements IMinimapModel { } } public onTokensColorsChanged(e: viewEvents.ViewTokensColorsChangedEvent): boolean { + this._onOptionsMaybeChanged(); return this._actual.onTokensColorsChanged(); } public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { diff --git a/src/vs/editor/browser/viewParts/selections/selections.ts b/src/vs/editor/browser/viewParts/selections/selections.ts index cdb00570c..81417f0be 100644 --- a/src/vs/editor/browser/viewParts/selections/selections.ts +++ b/src/vs/editor/browser/viewParts/selections/selections.ts @@ -60,7 +60,7 @@ function toStyled(item: LineVisibleRanges): LineVisibleRangesWithStyle { // TODO@Alex: Remove this once IE11 fixes Bug #524217 // The problem in IE11 is that it does some sort of auto-zooming to accomodate for displays with different pixel density. // Unfortunately, this auto-zooming is buggy around dealing with rounded borders -const isIEWithZoomingIssuesNearRoundedBorders = browser.isEdge; +const isIEWithZoomingIssuesNearRoundedBorders = browser.isEdgeLegacy; export class SelectionsOverlay extends DynamicViewOverlay { diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 4c87ce2bb..15cd2f345 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -24,7 +24,7 @@ import { ViewUserInputEvents } from 'vs/editor/browser/view/viewUserInputEvents' import { ConfigurationChangedEvent, EditorLayoutInfo, IEditorOptions, EditorOption, IComputedEditorOptions, FindComputedEditorOptionValueById, filterValidationDecorations } from 'vs/editor/common/config/editorOptions'; import { Cursor } from 'vs/editor/common/controller/cursor'; import { CursorColumns } from 'vs/editor/common/controller/cursorCommon'; -import { ICursorPositionChangedEvent, ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; +import { CursorChangeReason, ICursorPositionChangedEvent, ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; 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'; @@ -241,7 +241,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE constructor( domElement: HTMLElement, - options: editorBrowser.IEditorConstructionOptions, + _options: Readonly, codeEditorWidgetOptions: ICodeEditorWidgetOptions, @IInstantiationService instantiationService: IInstantiationService, @ICodeEditorService codeEditorService: ICodeEditorService, @@ -253,10 +253,11 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE ) { super(); - options = options || {}; + const options = { ..._options }; this._domElement = domElement; this._overflowWidgetsDomNode = options.overflowWidgetsDomNode; + delete options.overflowWidgetsDomNode; this._id = (++EDITOR_ID); this._decorationTypeKeysToIds = {}; this._decorationTypeSubtypes = {}; @@ -331,7 +332,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._codeEditorService.addCodeEditor(this); } - protected _createConfiguration(options: editorBrowser.IEditorConstructionOptions, accessibilityService: IAccessibilityService): editorCommon.IConfiguration { + protected _createConfiguration(options: Readonly, accessibilityService: IAccessibilityService): editorCommon.IConfiguration { return new Configuration(this.isSimpleWidget, options, this._domElement, accessibilityService); } @@ -366,7 +367,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return this._instantiationService.invokeFunction(fn); } - public updateOptions(newOptions: IEditorOptions): void { + public updateOptions(newOptions: Readonly): void { this._configuration.updateOptions(newOptions); } @@ -811,7 +812,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE ); } - public setSelections(ranges: readonly ISelection[], source: string = 'api'): void { + public setSelections(ranges: readonly ISelection[], source: string = 'api', reason = CursorChangeReason.NotSet): void { if (!this._modelData) { return; } @@ -823,7 +824,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE throw new Error('Invalid arguments'); } } - this._modelData.viewModel.setSelections(source, ranges); + this._modelData.viewModel.setSelections(source, ranges, reason); } public getContentWidth(): number { @@ -1834,6 +1835,7 @@ export class EditorModeContext extends Disposable { private readonly _hasMultipleDocumentFormattingProvider: IContextKey; private readonly _hasMultipleDocumentSelectionFormattingProvider: IContextKey; private readonly _hasSignatureHelpProvider: IContextKey; + private readonly _hasInlineHintsProvider: IContextKey; private readonly _isInWalkThrough: IContextKey; constructor( @@ -1856,6 +1858,7 @@ export class EditorModeContext extends Disposable { this._hasReferenceProvider = EditorContextKeys.hasReferenceProvider.bindTo(_contextKeyService); this._hasRenameProvider = EditorContextKeys.hasRenameProvider.bindTo(_contextKeyService); this._hasSignatureHelpProvider = EditorContextKeys.hasSignatureHelpProvider.bindTo(_contextKeyService); + this._hasInlineHintsProvider = EditorContextKeys.hasInlineHintsProvider.bindTo(_contextKeyService); this._hasDocumentFormattingProvider = EditorContextKeys.hasDocumentFormattingProvider.bindTo(_contextKeyService); this._hasDocumentSelectionFormattingProvider = EditorContextKeys.hasDocumentSelectionFormattingProvider.bindTo(_contextKeyService); this._hasMultipleDocumentFormattingProvider = EditorContextKeys.hasMultipleDocumentFormattingProvider.bindTo(_contextKeyService); @@ -1884,6 +1887,7 @@ export class EditorModeContext extends Disposable { this._register(modes.DocumentFormattingEditProviderRegistry.onDidChange(update)); this._register(modes.DocumentRangeFormattingEditProviderRegistry.onDidChange(update)); this._register(modes.SignatureHelpProviderRegistry.onDidChange(update)); + this._register(modes.InlineHintsProviderRegistry.onDidChange(update)); update(); } @@ -1935,6 +1939,7 @@ export class EditorModeContext extends Disposable { this._hasReferenceProvider.set(modes.ReferenceProviderRegistry.has(model)); this._hasRenameProvider.set(modes.RenameProviderRegistry.has(model)); this._hasSignatureHelpProvider.set(modes.SignatureHelpProviderRegistry.has(model)); + this._hasInlineHintsProvider.set(modes.InlineHintsProviderRegistry.has(model)); this._hasDocumentFormattingProvider.set(modes.DocumentFormattingEditProviderRegistry.has(model) || modes.DocumentRangeFormattingEditProviderRegistry.has(model)); this._hasDocumentSelectionFormattingProvider.set(modes.DocumentRangeFormattingEditProviderRegistry.has(model)); this._hasMultipleDocumentFormattingProvider.set(modes.DocumentFormattingEditProviderRegistry.all(model).length + modes.DocumentRangeFormattingEditProviderRegistry.all(model).length > 1); diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 0730e4f00..53615b318 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -12,15 +12,14 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import * as objects from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; import { Configuration } from 'vs/editor/browser/config/configuration'; import { StableEditorScrollState } from 'vs/editor/browser/core/editorState'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; import { DiffReview } from 'vs/editor/browser/widget/diffReview'; -import { IDiffEditorOptions, IEditorOptions, EditorLayoutInfo, EditorOption, EditorOptions, EditorFontLigatures, stringSet as validateStringSetOption, boolean as validateBooleanOption } from 'vs/editor/common/config/editorOptions'; +import { IDiffEditorOptions, EditorLayoutInfo, EditorOption, EditorOptions, EditorFontLigatures, stringSet as validateStringSetOption, boolean as validateBooleanOption } from 'vs/editor/common/config/editorOptions'; 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'; @@ -54,6 +53,11 @@ import { IViewLineTokens } from 'vs/editor/common/core/lineTokens'; import { FontInfo } from 'vs/editor/common/config/fontInfo'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; +export interface IDiffCodeEditorWidgetOptions { + originalEditor?: ICodeEditorWidgetOptions; + modifiedEditor?: ICodeEditorWidgetOptions; +} + interface IEditorDiffDecorations { decorations: IModelDeltaDecoration[]; overviewZones: OverviewRulerZone[]; @@ -109,7 +113,7 @@ class VisualEditorState { this._decorations = editor.deltaDecorations(this._decorations, []); } - public apply(editor: CodeEditorWidget, overviewRuler: editorBrowser.IOverviewRuler, newDecorations: IEditorDiffDecorationsWithZones, restoreScrollState: boolean): void { + public apply(editor: CodeEditorWidget, overviewRuler: editorBrowser.IOverviewRuler | null, newDecorations: IEditorDiffDecorationsWithZones, restoreScrollState: boolean): void { const scrollState = restoreScrollState ? StableEditorScrollState.capture(editor) : null; @@ -212,6 +216,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE private _maxComputationTime: number; private _renderIndicators: boolean; private _enableSplitViewResizing: boolean; + private _renderOverviewRuler: boolean; private _strategy!: DiffEditorWidgetStyle; private readonly _updateDecorationsRunner: RunOnceScheduler; @@ -226,7 +231,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE constructor( domElement: HTMLElement, - options: editorBrowser.IDiffEditorConstructionOptions, + options: Readonly, + codeEditorWidgetOptions: IDiffCodeEditorWidgetOptions, @IClipboardService clipboardService: IClipboardService, @IEditorWorkerService editorWorkerService: IEditorWorkerService, @IContextKeyService contextKeyService: IContextKeyService, @@ -287,6 +293,11 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._contextKeyService.createKey('isInEmbeddedDiffEditor', false); } + this._renderOverviewRuler = true; + if (typeof options.renderOverviewRuler !== 'undefined') { + this._renderOverviewRuler = Boolean(options.renderOverviewRuler); + } + this._updateDecorationsRunner = this._register(new RunOnceScheduler(() => this._updateDecorations(), 0)); this._containerDomElement = document.createElement('div'); @@ -308,7 +319,9 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._register(dom.addStandardDisposableListener(this._overviewDomElement, 'mousedown', (e) => { this._modifiedEditor.delegateVerticalScrollbarMouseDown(e); })); - this._containerDomElement.appendChild(this._overviewDomElement); + if (this._renderOverviewRuler) { + this._containerDomElement.appendChild(this._overviewDomElement); + } // Create left side this._originalDomNode = document.createElement('div'); @@ -334,7 +347,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._isVisible = true; this._isHandlingScrollEvent = false; - this._elementSizeObserver = this._register(new ElementSizeObserver(this._containerDomElement, undefined, () => this._onDidContainerSizeChanged())); + this._elementSizeObserver = this._register(new ElementSizeObserver(this._containerDomElement, options.dimension, () => this._onDidContainerSizeChanged())); if (options.automaticLayout) { this._elementSizeObserver.startObserving(); } @@ -353,8 +366,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE rightServices.set(IContextKeyService, rightContextKeyService); const rightScopedInstantiationService = instantiationService.createChild(rightServices); - this._originalEditor = this._createLeftHandSideEditor(options, leftScopedInstantiationService, leftContextKeyService); - this._modifiedEditor = this._createRightHandSideEditor(options, rightScopedInstantiationService, rightContextKeyService); + this._originalEditor = this._createLeftHandSideEditor(options, codeEditorWidgetOptions.originalEditor || {}, leftScopedInstantiationService, leftContextKeyService); + this._modifiedEditor = this._createRightHandSideEditor(options, codeEditorWidgetOptions.modifiedEditor || {}, rightScopedInstantiationService, rightContextKeyService); this._originalOverviewRuler = null; this._modifiedOverviewRuler = null; @@ -415,6 +428,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE return this._modifiedEditor.getContentHeight(); } + public getViewWidth(): number { + return this._elementSizeObserver.getWidth(); + } + private _setState(newState: editorBrowser.DiffEditorState): void { if (this._state === newState) { return; @@ -453,6 +470,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } private _recreateOverviewRulers(): void { + if (!this._renderOverviewRuler) { + return; + } + if (this._originalOverviewRuler) { this._overviewDomElement.removeChild(this._originalOverviewRuler.getDomNode()); this._originalOverviewRuler.dispose(); @@ -474,8 +495,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._layoutOverviewRulers(); } - private _createLeftHandSideEditor(options: editorBrowser.IDiffEditorConstructionOptions, instantiationService: IInstantiationService, contextKeyService: IContextKeyService): CodeEditorWidget { - const editor = this._createInnerEditor(instantiationService, this._originalDomNode, this._adjustOptionsForLeftHandSide(options)); + private _createLeftHandSideEditor(options: Readonly, codeEditorWidgetOptions: ICodeEditorWidgetOptions, instantiationService: IInstantiationService, contextKeyService: IContextKeyService): CodeEditorWidget { + const editor = this._createInnerEditor(instantiationService, this._originalDomNode, this._adjustOptionsForLeftHandSide(options), codeEditorWidgetOptions); this._register(editor.onDidScrollChange((e) => { if (this._isHandlingScrollEvent) { @@ -536,8 +557,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE return editor; } - private _createRightHandSideEditor(options: editorBrowser.IDiffEditorConstructionOptions, instantiationService: IInstantiationService, contextKeyService: IContextKeyService): CodeEditorWidget { - const editor = this._createInnerEditor(instantiationService, this._modifiedDomNode, this._adjustOptionsForRightHandSide(options)); + private _createRightHandSideEditor(options: Readonly, codeEditorWidgetOptions: ICodeEditorWidgetOptions, instantiationService: IInstantiationService, contextKeyService: IContextKeyService): CodeEditorWidget { + const editor = this._createInnerEditor(instantiationService, this._modifiedDomNode, this._adjustOptionsForRightHandSide(options), codeEditorWidgetOptions); this._register(editor.onDidScrollChange((e) => { if (this._isHandlingScrollEvent) { @@ -604,8 +625,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE return editor; } - protected _createInnerEditor(instantiationService: IInstantiationService, container: HTMLElement, options: IEditorOptions): CodeEditorWidget { - return instantiationService.createInstance(CodeEditorWidget, container, options, {}); + protected _createInnerEditor(instantiationService: IInstantiationService, container: HTMLElement, options: Readonly, editorWidgetOptions: ICodeEditorWidgetOptions): CodeEditorWidget { + return instantiationService.createInstance(CodeEditorWidget, container, options, editorWidgetOptions); } public dispose(): void { @@ -627,7 +648,9 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._modifiedOverviewRuler.dispose(); } this._overviewDomElement.removeChild(this._overviewViewportDomElement.domNode); - this._containerDomElement.removeChild(this._overviewDomElement); + if (this._renderOverviewRuler) { + this._containerDomElement.removeChild(this._overviewDomElement); + } this._containerDomElement.removeChild(this._originalDomNode); this._originalEditor.dispose(); @@ -678,7 +701,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE return this._modifiedEditor; } - public updateOptions(newOptions: IDiffEditorOptions): void { + public updateOptions(newOptions: Readonly): void { // Handle side by side let renderSideBySideChanged = false; @@ -740,6 +763,16 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE // Update class name this._containerDomElement.className = DiffEditorWidget._getClassName(this._themeService.getColorTheme(), this._renderSideBySide); } + + // renderOverviewRuler + if (typeof newOptions.renderOverviewRuler !== 'undefined' && this._renderOverviewRuler !== newOptions.renderOverviewRuler) { + this._renderOverviewRuler = newOptions.renderOverviewRuler; + if (this._renderOverviewRuler) { + this._containerDomElement.appendChild(this._overviewDomElement); + } else { + this._containerDomElement.removeChild(this._overviewDomElement); + } + } } public getModel(): editorCommon.IDiffEditorModel { @@ -749,7 +782,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE }; } - public setModel(model: editorCommon.IDiffEditorModel): void { + public setModel(model: editorCommon.IDiffEditorModel | null): void { // Guard us against partial null model if (model && (!model.original || !model.modified)) { throw new Error(!model.original ? 'DiffEditorWidget.setModel: Original model is null' : 'DiffEditorWidget.setModel: Modified model is null'); @@ -911,7 +944,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } public restoreViewState(s: editorCommon.IDiffEditorViewState): void { - if (s.original && s.modified) { + if (s && s.original && s.modified) { const diffEditorState = s; this._originalEditor.restoreViewState(diffEditorState.original); this._modifiedEditor.restoreViewState(diffEditorState.modified); @@ -969,6 +1002,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } private _layoutOverviewRulers(): void { + if (!this._renderOverviewRuler) { + return; + } + if (!this._originalOverviewRuler || !this._modifiedOverviewRuler) { return; } @@ -1079,9 +1116,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } private _updateDecorations(): void { - if (!this._originalEditor.getModel() || !this._modifiedEditor.getModel() || !this._originalOverviewRuler || !this._modifiedOverviewRuler) { + if (!this._originalEditor.getModel() || !this._modifiedEditor.getModel()) { return; } + const lineChanges = (this._diffComputationResult ? this._diffComputationResult.changes : []); const foreignOriginal = this._originalEditorState.getForeignViewZones(this._originalEditor.getWhitespaces()); @@ -1098,25 +1136,24 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } } - private _adjustOptionsForSubEditor(options: editorBrowser.IDiffEditorConstructionOptions): editorBrowser.IDiffEditorConstructionOptions { - const clonedOptions: editorBrowser.IDiffEditorConstructionOptions = objects.deepClone(options || {}); + private _adjustOptionsForSubEditor(options: Readonly): editorBrowser.IEditorConstructionOptions { + const clonedOptions = { ...options }; clonedOptions.inDiffEditor = true; clonedOptions.automaticLayout = false; - clonedOptions.scrollbar = clonedOptions.scrollbar || {}; + // Clone scrollbar options before changing them + clonedOptions.scrollbar = { ...(clonedOptions.scrollbar || {}) }; clonedOptions.scrollbar.vertical = 'visible'; clonedOptions.folding = false; clonedOptions.codeLens = this._diffCodeLens; clonedOptions.fixedOverflowWidgets = true; - clonedOptions.overflowWidgetsDomNode = options.overflowWidgetsDomNode; // clonedOptions.lineDecorationsWidth = '2ch'; - if (!clonedOptions.minimap) { - clonedOptions.minimap = {}; - } + // Clone minimap options before changing them + clonedOptions.minimap = { ...(clonedOptions.minimap || {}) }; clonedOptions.minimap.enabled = false; return clonedOptions; } - private _adjustOptionsForLeftHandSide(options: editorBrowser.IDiffEditorConstructionOptions): editorBrowser.IEditorConstructionOptions { + private _adjustOptionsForLeftHandSide(options: Readonly): editorBrowser.IEditorConstructionOptions { const result = this._adjustOptionsForSubEditor(options); if (!this._renderSideBySide) { // never wrap hidden editor @@ -1126,16 +1163,28 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } result.readOnly = !this._originalIsEditable; result.extraEditorClassName = 'original-in-monaco-diff-editor'; - return result; + return { + ...result, + dimension: { + height: 0, + width: 0 + } + }; } - private _adjustOptionsForRightHandSide(options: editorBrowser.IDiffEditorConstructionOptions): editorBrowser.IEditorConstructionOptions { + private _adjustOptionsForRightHandSide(options: Readonly): editorBrowser.IEditorConstructionOptions { const result = this._adjustOptionsForSubEditor(options); result.wordWrapOverride1 = this._diffWordWrap; result.revealHorizontalRightPadding = EditorOptions.revealHorizontalRightPadding.defaultValue + DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH; result.scrollbar!.verticalHasArrows = false; result.extraEditorClassName = 'modified-in-monaco-diff-editor'; - return result; + return { + ...result, + dimension: { + height: 0, + width: 0 + } + }; } public doLayout(): void { @@ -1164,7 +1213,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._overviewViewportDomElement.setHeight(30); this._originalEditor.layout({ width: splitPoint, height: (height - reviewHeight) }); - this._modifiedEditor.layout({ width: width - splitPoint - DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH, height: (height - reviewHeight) }); + this._modifiedEditor.layout({ width: width - splitPoint - (this._renderOverviewRuler ? DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH : 0), height: (height - reviewHeight) }); if (this._originalOverviewRuler || this._modifiedOverviewRuler) { this._layoutOverviewRulers(); @@ -1218,6 +1267,12 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE return (this._elementSizeObserver.getHeight() - this._getReviewHeight()); }, + getOptions: () => { + return { + renderOverviewRuler: this._renderOverviewRuler + }; + }, + getContainerDomNode: () => { return this._containerDomElement; }, @@ -1347,6 +1402,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE interface IDataSource { getWidth(): number; getHeight(): number; + getOptions(): { renderOverviewRuler: boolean; }; getContainerDomNode(): HTMLElement; relayoutEditors(): void; @@ -1806,7 +1862,7 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IVerti public layout(sashRatio: number | null = this._sashRatio): number { const w = this._dataSource.getWidth(); - const contentWidth = w - DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH; + const contentWidth = w - (this._dataSource.getOptions().renderOverviewRuler ? DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH : 0); let sashPosition = Math.floor((sashRatio || 0.5) * contentWidth); const midPoint = Math.floor(0.5 * contentWidth); @@ -1839,7 +1895,7 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IVerti private _onSashDrag(e: ISashEvent): void { const w = this._dataSource.getWidth(); - const contentWidth = w - DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH; + const contentWidth = w - (this._dataSource.getOptions().renderOverviewRuler ? DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH : 0); const sashPosition = this.layout((this._startSashPosition! + (e.currentX - e.startX)) / contentWidth); this._sashRatio = sashPosition / contentWidth; @@ -2370,7 +2426,7 @@ class InlineViewZonesComputer extends ViewZonesComputer { const html = sb.build(); const trustedhtml = ttPolicy ? ttPolicy.createHTML(html) : html; - domNode.innerHTML = trustedhtml as unknown as string; + domNode.innerHTML = trustedhtml as string; viewZone.minWidthInPx = (maxCharsPerLine * typicalHalfwidthCharacterWidth); if (viewLineCounts) { diff --git a/src/vs/editor/browser/widget/diffReview.ts b/src/vs/editor/browser/widget/diffReview.ts index fd53314e1..2fb0eaed0 100644 --- a/src/vs/editor/browser/widget/diffReview.ts +++ b/src/vs/editor/browser/widget/diffReview.ts @@ -80,6 +80,8 @@ const diffReviewCloseIcon = registerIcon('diff-review-close', Codicon.close, nls export class DiffReview extends Disposable { + private static _ttPolicy = window.trustedTypes?.createPolicy('diffReview', { createHTML: value => value }); + private readonly _diffEditor: DiffEditorWidget; private _isVisible: boolean; public readonly shadow: FastDomNode; @@ -734,14 +736,18 @@ export class DiffReview extends Disposable { let lineContent: string; if (modifiedLine !== 0) { - cell.insertAdjacentHTML('beforeend', - this._renderLine(modifiedModel, modifiedOptions, modifiedModelOpts.tabSize, modifiedLine) - ); + let html: string | TrustedHTML = this._renderLine(modifiedModel, modifiedOptions, modifiedModelOpts.tabSize, modifiedLine); + if (DiffReview._ttPolicy) { + html = DiffReview._ttPolicy.createHTML(html as string); + } + cell.insertAdjacentHTML('beforeend', html as string); lineContent = modifiedModel.getLineContent(modifiedLine); } else { - cell.insertAdjacentHTML('beforeend', - this._renderLine(originalModel, originalOptions, originalModelOpts.tabSize, originalLine) - ); + let html: string | TrustedHTML = this._renderLine(originalModel, originalOptions, originalModelOpts.tabSize, originalLine); + if (DiffReview._ttPolicy) { + html = DiffReview._ttPolicy.createHTML(html as string); + } + cell.insertAdjacentHTML('beforeend', html as string); lineContent = originalModel.getLineContent(originalLine); } diff --git a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts index 5dd98c444..3836a4ec0 100644 --- a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts +++ b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts @@ -82,7 +82,7 @@ export class EmbeddedDiffEditorWidget extends DiffEditorWidget { @IClipboardService clipboardService: IClipboardService, @IEditorProgressService editorProgressService: IEditorProgressService, ) { - super(domElement, parentEditor.getRawOptions(), clipboardService, editorWorkerService, contextKeyService, instantiationService, codeEditorService, themeService, notificationService, contextMenuService, editorProgressService); + super(domElement, parentEditor.getRawOptions(), {}, clipboardService, editorWorkerService, contextKeyService, instantiationService, codeEditorService, themeService, notificationService, contextMenuService, editorProgressService); this._parentEditor = parentEditor; this._overwriteOptions = options; diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index bf1d608fc..c3805bf44 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -272,7 +272,7 @@ function migrateOptions(options: IEditorOptions): void { } } -function deepCloneAndMigrateOptions(_options: IEditorOptions): IEditorOptions { +function deepCloneAndMigrateOptions(_options: Readonly): IEditorOptions { const options = objects.deepClone(_options); migrateOptions(options); return options; @@ -298,7 +298,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC private _readOptions: RawEditorOptions; protected _validatedOptions: ValidatedEditorOptions; - constructor(isSimpleWidget: boolean, _options: IEditorOptions) { + constructor(isSimpleWidget: boolean, _options: Readonly) { super(); this.isSimpleWidget = isSimpleWidget; @@ -318,8 +318,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC public observeReferenceElement(dimension?: IDimension): void { } - public dispose(): void { - super.dispose(); + public updatePixelRatio(): void { } protected _recomputeOptions(): void { @@ -348,7 +347,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC private _computeInternalOptions(): ComputedEditorOptions { const partialEnv = this._getEnvConfiguration(); - const bareFontInfo = BareFontInfo.createFromValidatedSettings(this._validatedOptions, partialEnv.zoomLevel, this.isSimpleWidget); + const bareFontInfo = BareFontInfo.createFromValidatedSettings(this._validatedOptions, partialEnv.zoomLevel, partialEnv.pixelRatio, this.isSimpleWidget); const env: IEnvironmentalOptions = { memory: this._computeOptionsMemory, outerWidth: partialEnv.outerWidth, @@ -394,7 +393,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC return true; } - public updateOptions(_newOptions: IEditorOptions): void { + public updateOptions(_newOptions: Readonly): void { if (typeof _newOptions === 'undefined') { return; } diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 1c6b481c7..bce855361 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -625,6 +625,10 @@ export interface IEditorOptions { * Controls strikethrough deprecated variables. */ showDeprecated?: boolean; + /** + * Control the behavior and rendering of the inline hints. + */ + inlineHints?: IEditorInlineHintsOptions; } /** @@ -677,6 +681,11 @@ export interface IDiffEditorOptions extends IEditorOptions { * Defaults to false */ isInEmbeddedEditor?: boolean; + /** + * Is the diff editor should render overview ruler + * Defaults to true + */ + renderOverviewRuler?: boolean; /** * Control the wrapping of the diff editor. */ @@ -2364,6 +2373,74 @@ class EditorLightbulb extends BaseEditorOption>; + +class EditorInlineHints extends BaseEditorOption { + + constructor() { + const defaults: EditorInlineHintsOptions = { enabled: true, fontSize: 0, fontFamily: EDITOR_FONT_DEFAULTS.fontFamily }; + super( + EditorOption.inlineHints, 'inlineHints', defaults, + { + 'editor.inlineHints.enabled': { + type: 'boolean', + default: defaults.enabled, + description: nls.localize('inlineHints.enable', "Enables the inline hints in the editor.") + }, + 'editor.inlineHints.fontSize': { + type: 'number', + default: defaults.fontSize, + description: nls.localize('inlineHints.fontSize', "Controls font size of inline hints in the editor. When set to `0`, the 90% of `#editor.fontSize#` is used.") + }, + 'editor.inlineHints.fontFamily': { + type: 'string', + default: defaults.fontFamily, + description: nls.localize('inlineHints.fontFamily', "Controls font family of inline hints in the editor.") + }, + } + ); + } + + public validate(_input: any): EditorInlineHintsOptions { + if (!_input || typeof _input !== 'object') { + return this.defaultValue; + } + const input = _input as IEditorInlineHintsOptions; + return { + enabled: boolean(input.enabled, this.defaultValue.enabled), + fontSize: EditorIntOption.clampedInt(input.fontSize, this.defaultValue.fontSize, 0, 100), + fontFamily: EditorStringOption.string(input.fontFamily, this.defaultValue.fontFamily) + }; + } +} + +//#endregion + //#region lineHeight class EditorLineHeight extends EditorIntOption { @@ -3273,7 +3350,7 @@ class EditorSuggest extends BaseEditorOption 0) { this._pushAutoClosedAction(autoClosedCharactersRanges, autoClosedEnclosingRanges); diff --git a/src/vs/editor/common/controller/cursorMoveOperations.ts b/src/vs/editor/common/controller/cursorMoveOperations.ts index 9229c2e35..ac505bdcd 100644 --- a/src/vs/editor/common/controller/cursorMoveOperations.ts +++ b/src/vs/editor/common/controller/cursorMoveOperations.ts @@ -39,11 +39,11 @@ export class MoveOperations { public static leftPositionAtomicSoftTabs(model: ICursorSimpleModel, lineNumber: number, column: number, tabSize: number): Position { const minColumn = model.getLineMinColumn(lineNumber); const lineContent = model.getLineContent(lineNumber); - const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, column - minColumn, tabSize, Direction.Left); - if (newPosition === -1) { + const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, column - 1, tabSize, Direction.Left); + if (newPosition === -1 || newPosition + 1 < minColumn) { return this.leftPosition(model, lineNumber, column); } - return new Position(lineNumber, minColumn + newPosition); + return new Position(lineNumber, newPosition + 1); } public static left(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number): CursorPosition { @@ -81,13 +81,12 @@ export class MoveOperations { } public static rightPositionAtomicSoftTabs(model: ICursorSimpleModel, lineNumber: number, column: number, tabSize: number, indentSize: number): Position { - const minColumn = model.getLineMinColumn(lineNumber); const lineContent = model.getLineContent(lineNumber); - const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, column - minColumn, tabSize, Direction.Right); + const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, column - 1, tabSize, Direction.Right); if (newPosition === -1) { return this.rightPosition(model, lineNumber, column); } - return new Position(lineNumber, minColumn + newPosition); + return new Position(lineNumber, newPosition + 1); } public static right(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number): CursorPosition { diff --git a/src/vs/editor/common/controller/cursorTypeOperations.ts b/src/vs/editor/common/controller/cursorTypeOperations.ts index 75da04850..0c618026d 100644 --- a/src/vs/editor/common/controller/cursorTypeOperations.ts +++ b/src/vs/editor/common/controller/cursorTypeOperations.ts @@ -351,13 +351,6 @@ export class TypeOperations { if (ir) { let oldEndViewColumn = CursorColumns.visibleColumnFromColumn2(config, model, range.getEndPosition()); const oldEndColumn = range.endColumn; - - let beforeText = '\n'; - if (indentation !== config.normalizeIndentation(ir.beforeEnter)) { - beforeText = config.normalizeIndentation(ir.beforeEnter) + lineText.substring(indentation.length, range.startColumn - 1) + '\n'; - range = new Range(range.startLineNumber, 1, range.endLineNumber, range.endColumn); - } - const newLineContent = model.getLineContent(range.endLineNumber); const firstNonWhitespace = strings.firstNonWhitespaceIndex(newLineContent); if (firstNonWhitespace >= 0) { @@ -367,7 +360,7 @@ export class TypeOperations { } if (keepPosition) { - return new ReplaceCommandWithoutChangingPosition(range, beforeText + config.normalizeIndentation(ir.afterEnter), true); + return new ReplaceCommandWithoutChangingPosition(range, '\n' + config.normalizeIndentation(ir.afterEnter), true); } else { let offset = 0; if (oldEndColumn <= firstNonWhitespace + 1) { @@ -376,7 +369,7 @@ export class TypeOperations { } offset = Math.min(oldEndViewColumn + 1 - config.normalizeIndentation(ir.afterEnter).length - 1, 0); } - return new ReplaceCommandWithOffsetCursorState(range, beforeText + config.normalizeIndentation(ir.afterEnter), 0, offset, true); + return new ReplaceCommandWithOffsetCursorState(range, '\n' + config.normalizeIndentation(ir.afterEnter), 0, offset, true); } } } diff --git a/src/vs/editor/common/diff/diffComputer.ts b/src/vs/editor/common/diff/diffComputer.ts index ebf854605..cac0acd99 100644 --- a/src/vs/editor/common/diff/diffComputer.ts +++ b/src/vs/editor/common/diff/diffComputer.ts @@ -313,6 +313,13 @@ export class DiffComputer { if (this.original.lines.length === 1 && this.original.lines[0].length === 0) { // empty original => fast path + if (this.modified.lines.length === 1 && this.modified.lines[0].length === 0) { + return { + quitEarly: false, + changes: [] + }; + } + return { quitEarly: false, changes: [{ diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index 94c4a3c48..e2372db8d 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -157,9 +157,10 @@ export interface IConfiguration extends IDisposable { setMaxLineNumber(maxLineNumber: number): void; setViewLineCount(viewLineCount: number): void; - updateOptions(newOptions: IEditorOptions): void; + updateOptions(newOptions: Readonly): void; getRawOptions(): IEditorOptions; observeReferenceElement(dimension?: IDimension): void; + updatePixelRatio(): void; setIsDominatedByLongLines(isDominatedByLongLines: boolean): void; } @@ -605,6 +606,7 @@ export interface IThemeDecorationRenderOptions { fontStyle?: string; fontWeight?: string; + fontSize?: string; textDecoration?: string; cursor?: string; color?: string | ThemeColor; @@ -629,13 +631,17 @@ export interface IContentDecorationRenderOptions { border?: string; borderColor?: string | ThemeColor; + borderRadius?: string; fontStyle?: string; fontWeight?: string; + fontSize?: string; + fontFamily?: string; textDecoration?: string; color?: string | ThemeColor; backgroundColor?: string | ThemeColor; margin?: string; + padding?: string; width?: string; height?: string; } diff --git a/src/vs/editor/common/editorContextKeys.ts b/src/vs/editor/common/editorContextKeys.ts index a3a3b3de1..2f24e3118 100644 --- a/src/vs/editor/common/editorContextKeys.ts +++ b/src/vs/editor/common/editorContextKeys.ts @@ -61,6 +61,7 @@ export namespace EditorContextKeys { export const hasReferenceProvider = new RawContextKey('editorHasReferenceProvider', false); export const hasRenameProvider = new RawContextKey('editorHasRenameProvider', false); export const hasSignatureHelpProvider = new RawContextKey('editorHasSignatureHelpProvider', false); + export const hasInlineHintsProvider = new RawContextKey('editorHasInlineHintsProvider', false); // -- mode context keys: formatting export const hasDocumentFormattingProvider = new RawContextKey('editorHasDocumentFormattingProvider', false); diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 8f4d53a84..3f1a239dc 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -600,12 +600,6 @@ export interface ITextModel { */ setValue(newValue: string): void; - /** - * Replace the entire text buffer value contained in this model. - * @internal - */ - setValueFromTextBuffer(newValue: ITextBuffer): void; - /** * Get the text stored in this model. * @param eol The end of line character preference. Defaults to `EndOfLinePreference.TextDefined`. @@ -1276,7 +1270,7 @@ export interface ITextBufferBuilder { * @internal */ export interface ITextBufferFactory { - create(defaultEOL: DefaultEndOfLine): ITextBuffer; + create(defaultEOL: DefaultEndOfLine): { textBuffer: ITextBuffer; disposable: IDisposable; }; getFirstLineText(lengthLimit: number): string; } diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder.ts index d134517ba..b65b87a0c 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CharCode } from 'vs/base/common/charCode'; +import { IDisposable } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; import { DefaultEndOfLine, ITextBuffer, ITextBufferBuilder, ITextBufferFactory } from 'vs/editor/common/model'; import { StringBuffer, createLineStarts, createLineStartsFast } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase'; @@ -38,7 +39,7 @@ export class PieceTreeTextBufferFactory implements ITextBufferFactory { return '\n'; } - public create(defaultEOL: DefaultEndOfLine): ITextBuffer { + public create(defaultEOL: DefaultEndOfLine): { textBuffer: ITextBuffer; disposable: IDisposable; } { const eol = this._getEOL(defaultEOL); let chunks = this._chunks; @@ -54,7 +55,8 @@ export class PieceTreeTextBufferFactory implements ITextBufferFactory { } } - return new PieceTreeTextBuffer(chunks, this._bom, eol, this._containsRTL, this._containsUnusualLineTerminators, this._isBasicASCII, this._normalizeEOL); + const textBuffer = new PieceTreeTextBuffer(chunks, this._bom, eol, this._containsRTL, this._containsUnusualLineTerminators, this._isBasicASCII, this._normalizeEOL); + return { textBuffer: textBuffer, disposable: textBuffer }; } public getFirstLineText(lengthLimit: number): string { diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 35dd46640..aab72a48c 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -37,6 +37,7 @@ import { EditorTheme } from 'vs/editor/common/view/viewContext'; import { IUndoRedoService, ResourceEditStackSnapshot } from 'vs/platform/undoRedo/common/undoRedo'; import { TextChange } from 'vs/editor/common/model/textChange'; import { Constants } from 'vs/base/common/uint'; +import { PieceTreeTextBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer'; function createTextBufferBuilder() { return new PieceTreeTextBufferBuilder(); @@ -106,7 +107,7 @@ export function createTextBufferFactoryFromSnapshot(snapshot: model.ITextSnapsho return builder.finish(); } -export function createTextBuffer(value: string | model.ITextBufferFactory, defaultEOL: model.DefaultEndOfLine): model.ITextBuffer { +export function createTextBuffer(value: string | model.ITextBufferFactory, defaultEOL: model.DefaultEndOfLine): { textBuffer: model.ITextBuffer; disposable: IDisposable; } { const factory = (typeof value === 'string' ? createTextBufferFactory(value) : value); return factory.create(defaultEOL); } @@ -268,6 +269,7 @@ export class TextModel extends Disposable implements model.ITextModel { private readonly _undoRedoService: IUndoRedoService; private _attachedEditorCount: number; private _buffer: model.ITextBuffer; + private _bufferDisposable: IDisposable; private _options: model.TextModelResolvedOptions; private _isDisposed: boolean; @@ -328,7 +330,9 @@ export class TextModel extends Disposable implements model.ITextModel { this._undoRedoService = undoRedoService; this._attachedEditorCount = 0; - this._buffer = createTextBuffer(source, creationOptions.defaultEOL); + const { textBuffer, disposable } = createTextBuffer(source, creationOptions.defaultEOL); + this._buffer = textBuffer; + this._bufferDisposable = disposable; this._options = TextModel.resolveOptions(this._buffer, creationOptions); @@ -386,10 +390,13 @@ export class TextModel extends Disposable implements model.ITextModel { this._tokenization.dispose(); this._isDisposed = true; super.dispose(); + this._bufferDisposable.dispose(); this._isDisposing = false; // Manually release reference to previous text buffer to avoid large leaks // in case someone leaks a TextModel reference - this._buffer = createTextBuffer('', this._options.defaultEOL); + const emptyDisposedTextBuffer = new PieceTreeTextBuffer([], '', '\n', false, false, true, true); + emptyDisposedTextBuffer.dispose(); + this._buffer = emptyDisposedTextBuffer; } private _assertNotDisposed(): void { @@ -423,8 +430,8 @@ export class TextModel extends Disposable implements model.ITextModel { return; } - const textBuffer = createTextBuffer(value, this._options.defaultEOL); - this.setValueFromTextBuffer(textBuffer); + const { textBuffer, disposable } = createTextBuffer(value, this._options.defaultEOL); + this._setValueFromTextBuffer(textBuffer, disposable); } private _createContentChanged2(range: Range, rangeOffset: number, rangeLength: number, text: string, isUndoing: boolean, isRedoing: boolean, isFlush: boolean): IModelContentChangedEvent { @@ -443,18 +450,16 @@ export class TextModel extends Disposable implements model.ITextModel { }; } - public setValueFromTextBuffer(textBuffer: model.ITextBuffer): void { + private _setValueFromTextBuffer(textBuffer: model.ITextBuffer, textBufferDisposable: IDisposable): void { this._assertNotDisposed(); - if (textBuffer === null) { - // There's nothing to do - return; - } const oldFullModelRange = this.getFullModelRange(); const oldModelValueLength = this.getValueLengthInRange(oldFullModelRange); const endLineNumber = this.getLineCount(); const endColumn = this.getLineMaxColumn(endLineNumber); this._buffer = textBuffer; + this._bufferDisposable.dispose(); + this._bufferDisposable = textBufferDisposable; this._increaseVersionId(); // Flush all tokens diff --git a/src/vs/editor/common/model/textModelTokens.ts b/src/vs/editor/common/model/textModelTokens.ts index e127f50e3..3cf7bc9a2 100644 --- a/src/vs/editor/common/model/textModelTokens.ts +++ b/src/vs/editor/common/model/textModelTokens.ts @@ -359,7 +359,7 @@ export class TextModelTokenization extends Disposable { const text = this._textModel.getLineContent(lineIndex + 1); const lineStartState = this._tokenizationStateStore.getBeginState(lineIndex); - const r = safeTokenize(languageIdentifier, this._tokenizationSupport, text, lineStartState!); + const r = safeTokenize(languageIdentifier, this._tokenizationSupport, text, true, lineStartState!); builder.add(lineIndex + 1, r.tokens); this._tokenizationStateStore.setEndState(linesLength, lineIndex, r.endState); lineIndex = this._tokenizationStateStore.invalidLineStartIndex - 1; // -1 because the outer loop increments it @@ -410,13 +410,13 @@ export class TextModelTokenization extends Disposable { const languageIdentifier = this._textModel.getLanguageIdentifier(); let state = initialState; for (let i = fakeLines.length - 1; i >= 0; i--) { - let r = safeTokenize(languageIdentifier, this._tokenizationSupport, fakeLines[i], state); + let r = safeTokenize(languageIdentifier, this._tokenizationSupport, fakeLines[i], false, state); state = r.endState; } for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) { let text = this._textModel.getLineContent(lineNumber); - let r = safeTokenize(languageIdentifier, this._tokenizationSupport, text, state); + let r = safeTokenize(languageIdentifier, this._tokenizationSupport, text, true, state); builder.add(lineNumber, r.tokens); this._tokenizationStateStore.setFakeTokens(lineNumber - 1); state = r.endState; @@ -443,12 +443,12 @@ function initializeTokenization(textModel: TextModel): [ITokenizationSupport | n return [tokenizationSupport, initialState]; } -function safeTokenize(languageIdentifier: LanguageIdentifier, tokenizationSupport: ITokenizationSupport | null, text: string, state: IState): TokenizationResult2 { +function safeTokenize(languageIdentifier: LanguageIdentifier, tokenizationSupport: ITokenizationSupport | null, text: string, hasEOL: boolean, state: IState): TokenizationResult2 { let r: TokenizationResult2 | null = null; if (tokenizationSupport) { try { - r = tokenizationSupport.tokenize2(text, state.clone(), 0); + r = tokenizationSupport.tokenize2(text, hasEOL, state.clone(), 0); } catch (e) { onUnexpectedError(e); } diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index fe0d7335d..204a4503e 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -211,9 +211,9 @@ export interface ITokenizationSupport { getInitialState(): IState; // add offsetDelta to each of the returned indices - tokenize(line: string, state: IState, offsetDelta: number): TokenizationResult; + tokenize(line: string, hasEOL: boolean, state: IState, offsetDelta: number): TokenizationResult; - tokenize2(line: string, state: IState, offsetDelta: number): TokenizationResult2; + tokenize2(line: string, hasEOL: boolean, state: IState, offsetDelta: number): TokenizationResult2; } /** @@ -1659,6 +1659,19 @@ export interface CodeLensProvider { resolveCodeLens?(model: model.ITextModel, codeLens: CodeLens, token: CancellationToken): ProviderResult; } +export interface InlineHint { + text: string; + range: IRange; + description?: string | IMarkdownString; + whitespaceBefore?: boolean; + whitespaceAfter?: boolean; +} + +export interface InlineHintsProvider { + onDidChangeInlineHints?: Event | undefined; + provideInlineHints(model: model.ITextModel, range: Range, token: CancellationToken): ProviderResult; +} + export interface SemanticTokensLegend { readonly tokenTypes: string[]; readonly tokenModifiers: string[]; @@ -1764,6 +1777,11 @@ export const TypeDefinitionProviderRegistry = new LanguageFeatureRegistry(); +/** + * @internal + */ +export const InlineHintsProviderRegistry = new LanguageFeatureRegistry(); + /** * @internal */ @@ -1876,3 +1894,14 @@ export interface ITokenizationRegistry { * @internal */ export const TokenizationRegistry = new TokenizationRegistryImpl(); + + +/** + * @internal + */ +export enum ExternalUriOpenerPriority { + None = 0, + Option = 1, + Default = 2, + Preferred = 3, +} diff --git a/src/vs/editor/common/modes/languageConfiguration.ts b/src/vs/editor/common/modes/languageConfiguration.ts index a383c57e9..3542fa74f 100644 --- a/src/vs/editor/common/modes/languageConfiguration.ts +++ b/src/vs/editor/common/modes/languageConfiguration.ts @@ -150,7 +150,7 @@ export interface OnEnterRule { /** * This rule will only execute if the text above the this line matches this regular expression. */ - oneLineAboveText?: RegExp; + previousLineText?: RegExp; /** * The action to execute. */ diff --git a/src/vs/editor/common/modes/languageConfigurationRegistry.ts b/src/vs/editor/common/modes/languageConfigurationRegistry.ts index ccd20e376..6074e34cc 100644 --- a/src/vs/editor/common/modes/languageConfigurationRegistry.ts +++ b/src/vs/editor/common/modes/languageConfigurationRegistry.ts @@ -101,11 +101,11 @@ export class RichEditSupport { return this._electricCharacter; } - public onEnter(autoIndent: EditorAutoIndentStrategy, oneLineAboveText: string, beforeEnterText: string, afterEnterText: string): EnterAction | null { + public onEnter(autoIndent: EditorAutoIndentStrategy, previousLineText: string, beforeEnterText: string, afterEnterText: string): EnterAction | null { if (!this._onEnterSupport) { return null; } - return this._onEnterSupport.onEnter(autoIndent, oneLineAboveText, beforeEnterText, afterEnterText); + return this._onEnterSupport.onEnter(autoIndent, previousLineText, beforeEnterText, afterEnterText); } private static _mergeConf(prev: LanguageConfiguration | null, current: LanguageConfiguration): LanguageConfiguration { @@ -700,17 +700,17 @@ export class LanguageConfigurationRegistryImpl { afterEnterText = endScopedLineTokens.getLineContent().substr(range.endColumn - 1 - scopedLineTokens.firstCharOffset); } - let oneLineAboveText = ''; + let previousLineText = ''; if (range.startLineNumber > 1 && scopedLineTokens.firstCharOffset === 0) { // This is not the first line and the entire line belongs to this mode const oneLineAboveScopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber - 1); if (oneLineAboveScopedLineTokens.languageId === scopedLineTokens.languageId) { // The line above ends with text belonging to the same mode - oneLineAboveText = oneLineAboveScopedLineTokens.getLineContent(); + previousLineText = oneLineAboveScopedLineTokens.getLineContent(); } } - const enterResult = richEditSupport.onEnter(autoIndent, oneLineAboveText, beforeEnterText, afterEnterText); + const enterResult = richEditSupport.onEnter(autoIndent, previousLineText, beforeEnterText, afterEnterText); if (!enterResult) { return null; } @@ -729,6 +729,8 @@ export class LanguageConfigurationRegistryImpl { } else { appendText = ''; } + } else if (indentAction === IndentAction.Indent) { + appendText = '\t' + appendText; } let indentation = this.getIndentationAtPosition(model, range.startLineNumber, range.startColumn); diff --git a/src/vs/editor/common/modes/modesRegistry.ts b/src/vs/editor/common/modes/modesRegistry.ts index c2ef63388..ffc97c246 100644 --- a/src/vs/editor/common/modes/modesRegistry.ts +++ b/src/vs/editor/common/modes/modesRegistry.ts @@ -58,11 +58,12 @@ export const ModesRegistry = new EditorModesRegistry(); Registry.add(Extensions.ModesRegistry, ModesRegistry); export const PLAINTEXT_MODE_ID = 'plaintext'; +export const PLAINTEXT_EXTENSION = '.txt'; export const PLAINTEXT_LANGUAGE_IDENTIFIER = new LanguageIdentifier(PLAINTEXT_MODE_ID, LanguageId.PlainText); ModesRegistry.registerLanguage({ id: PLAINTEXT_MODE_ID, - extensions: ['.txt'], + extensions: [PLAINTEXT_EXTENSION], aliases: [nls.localize('plainText.alias', "Plain Text"), 'text'], mimetypes: ['text/plain'] }); diff --git a/src/vs/editor/common/modes/supports/onEnter.ts b/src/vs/editor/common/modes/supports/onEnter.ts index f42cc4a4d..c03b52439 100644 --- a/src/vs/editor/common/modes/supports/onEnter.ts +++ b/src/vs/editor/common/modes/supports/onEnter.ts @@ -49,7 +49,7 @@ export class OnEnterSupport { this._regExpRules = opts.onEnterRules || []; } - public onEnter(autoIndent: EditorAutoIndentStrategy, oneLineAboveText: string, beforeEnterText: string, afterEnterText: string): EnterAction | null { + public onEnter(autoIndent: EditorAutoIndentStrategy, previousLineText: string, beforeEnterText: string, afterEnterText: string): EnterAction | null { // (1): `regExpRules` if (autoIndent >= EditorAutoIndentStrategy.Advanced) { for (let i = 0, len = this._regExpRules.length; i < len; i++) { @@ -61,8 +61,8 @@ export class OnEnterSupport { reg: rule.afterText, text: afterEnterText }, { - reg: rule.oneLineAboveText, - text: oneLineAboveText + reg: rule.previousLineText, + text: previousLineText }].every((obj): boolean => { return obj.reg ? obj.reg.test(obj.text) : true; }); diff --git a/src/vs/editor/common/modes/textToHtmlTokenizer.ts b/src/vs/editor/common/modes/textToHtmlTokenizer.ts index c48666416..a0eb66d20 100644 --- a/src/vs/editor/common/modes/textToHtmlTokenizer.ts +++ b/src/vs/editor/common/modes/textToHtmlTokenizer.ts @@ -12,12 +12,12 @@ import { NULL_STATE, nullTokenize2 } from 'vs/editor/common/modes/nullMode'; export interface IReducedTokenizationSupport { getInitialState(): IState; - tokenize2(line: string, state: IState, offsetDelta: number): TokenizationResult2; + tokenize2(line: string, hasEOL: boolean, state: IState, offsetDelta: number): TokenizationResult2; } const fallback: IReducedTokenizationSupport = { getInitialState: () => NULL_STATE, - tokenize2: (buffer: string, state: IState, deltaOffset: number) => nullTokenize2(LanguageId.Null, buffer, state, deltaOffset) + tokenize2: (buffer: string, hasEOL: boolean, state: IState, deltaOffset: number) => nullTokenize2(LanguageId.Null, buffer, state, deltaOffset) }; export function tokenizeToString(text: string, tokenizationSupport: IReducedTokenizationSupport = fallback): string { @@ -110,7 +110,7 @@ function _tokenizeToString(text: string, tokenizationSupport: IReducedTokenizati result += `
    `; } - let tokenizationResult = tokenizationSupport.tokenize2(line, currentState, 0); + let tokenizationResult = tokenizationSupport.tokenize2(line, true, currentState, 0); LineTokens.convertToEndOffset(tokenizationResult.tokens, line.length); let lineTokens = new LineTokens(tokenizationResult.tokens, line); let viewLineTokens = lineTokens.inflate(); diff --git a/src/vs/editor/common/services/getSemanticTokens.ts b/src/vs/editor/common/services/getSemanticTokens.ts new file mode 100644 index 000000000..b0317a83a --- /dev/null +++ b/src/vs/editor/common/services/getSemanticTokens.ts @@ -0,0 +1,160 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { onUnexpectedExternalError } from 'vs/base/common/errors'; +import { URI } from 'vs/base/common/uri'; +import { ITextModel } from 'vs/editor/common/model'; +import { DocumentSemanticTokensProviderRegistry, DocumentSemanticTokensProvider, SemanticTokens, SemanticTokensEdits, SemanticTokensLegend, DocumentRangeSemanticTokensProviderRegistry, DocumentRangeSemanticTokensProvider } from 'vs/editor/common/modes'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; +import { assertType } from 'vs/base/common/types'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { encodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto'; +import { Range } from 'vs/editor/common/core/range'; + +export function isSemanticTokens(v: SemanticTokens | SemanticTokensEdits): v is SemanticTokens { + return v && !!((v).data); +} + +export function isSemanticTokensEdits(v: SemanticTokens | SemanticTokensEdits): v is SemanticTokensEdits { + return v && Array.isArray((v).edits); +} + +export interface IDocumentSemanticTokensResult { + provider: DocumentSemanticTokensProvider; + request: Promise; +} + +export function getDocumentSemanticTokens(model: ITextModel, lastResultId: string | null, token: CancellationToken): IDocumentSemanticTokensResult | null { + const provider = _getDocumentSemanticTokensProvider(model); + if (!provider) { + return null; + } + return { + provider: provider, + request: Promise.resolve(provider.provideDocumentSemanticTokens(model, lastResultId, token)) + }; +} + +function _getDocumentSemanticTokensProvider(model: ITextModel): DocumentSemanticTokensProvider | null { + const result = DocumentSemanticTokensProviderRegistry.ordered(model); + return (result.length > 0 ? result[0] : null); +} + +export function getDocumentRangeSemanticTokensProvider(model: ITextModel): DocumentRangeSemanticTokensProvider | null { + const result = DocumentRangeSemanticTokensProviderRegistry.ordered(model); + return (result.length > 0 ? result[0] : null); +} + +CommandsRegistry.registerCommand('_provideDocumentSemanticTokensLegend', async (accessor, ...args): Promise => { + const [uri] = args; + assertType(uri instanceof URI); + + const model = accessor.get(IModelService).getModel(uri); + if (!model) { + return undefined; + } + + const provider = _getDocumentSemanticTokensProvider(model); + if (!provider) { + // there is no provider => fall back to a document range semantic tokens provider + return accessor.get(ICommandService).executeCommand('_provideDocumentRangeSemanticTokensLegend', uri); + } + + return provider.getLegend(); +}); + +CommandsRegistry.registerCommand('_provideDocumentSemanticTokens', async (accessor, ...args): Promise => { + const [uri] = args; + assertType(uri instanceof URI); + + const model = accessor.get(IModelService).getModel(uri); + if (!model) { + return undefined; + } + + const r = getDocumentSemanticTokens(model, null, CancellationToken.None); + if (!r) { + // there is no provider => fall back to a document range semantic tokens provider + return accessor.get(ICommandService).executeCommand('_provideDocumentRangeSemanticTokens', uri, model.getFullModelRange()); + } + + const { provider, request } = r; + + let result: SemanticTokens | SemanticTokensEdits | null | undefined; + try { + result = await request; + } catch (err) { + onUnexpectedExternalError(err); + return undefined; + } + + if (!result || !isSemanticTokens(result)) { + return undefined; + } + + const buff = encodeSemanticTokensDto({ + id: 0, + type: 'full', + data: result.data + }); + if (result.resultId) { + provider.releaseDocumentSemanticTokens(result.resultId); + } + return buff; +}); + +CommandsRegistry.registerCommand('_provideDocumentRangeSemanticTokensLegend', async (accessor, ...args): Promise => { + const [uri] = args; + assertType(uri instanceof URI); + + const model = accessor.get(IModelService).getModel(uri); + if (!model) { + return undefined; + } + + const provider = getDocumentRangeSemanticTokensProvider(model); + if (!provider) { + return undefined; + } + + return provider.getLegend(); +}); + +CommandsRegistry.registerCommand('_provideDocumentRangeSemanticTokens', async (accessor, ...args): Promise => { + const [uri, range] = args; + assertType(uri instanceof URI); + assertType(Range.isIRange(range)); + + const model = accessor.get(IModelService).getModel(uri); + if (!model) { + return undefined; + } + + const provider = getDocumentRangeSemanticTokensProvider(model); + if (!provider) { + // there is no provider + return undefined; + } + + let result: SemanticTokens | null | undefined; + try { + result = await provider.provideDocumentRangeSemanticTokens(model, Range.lift(range), CancellationToken.None); + } catch (err) { + onUnexpectedExternalError(err); + return undefined; + } + + if (!result || !isSemanticTokens(result)) { + return undefined; + } + + return encodeSemanticTokensDto({ + id: 0, + type: 'full', + data: result.data + }); +}); diff --git a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts index eb2d6795d..a80302ddb 100644 --- a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts +++ b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts @@ -87,13 +87,13 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor this._markerDecorations.clear(); } - getMarker(model: ITextModel, decoration: IModelDecoration): IMarker | null { - const markerDecorations = this._markerDecorations.get(MODEL_ID(model.uri)); + getMarker(uri: URI, decoration: IModelDecoration): IMarker | null { + const markerDecorations = this._markerDecorations.get(MODEL_ID(uri)); return markerDecorations ? (markerDecorations.getMarker(decoration) || null) : null; } - getLiveMarkers(model: ITextModel): [Range, IMarker][] { - const markerDecorations = this._markerDecorations.get(MODEL_ID(model.uri)); + getLiveMarkers(uri: URI): [Range, IMarker][] { + const markerDecorations = this._markerDecorations.get(MODEL_ID(uri)); return markerDecorations ? markerDecorations.getMarkers() : []; } diff --git a/src/vs/editor/common/services/markersDecorationService.ts b/src/vs/editor/common/services/markersDecorationService.ts index 745260c88..221f72379 100644 --- a/src/vs/editor/common/services/markersDecorationService.ts +++ b/src/vs/editor/common/services/markersDecorationService.ts @@ -8,6 +8,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IMarker } from 'vs/platform/markers/common/markers'; import { Event } from 'vs/base/common/event'; import { Range } from 'vs/editor/common/core/range'; +import { URI } from 'vs/base/common/uri'; export const IMarkerDecorationsService = createDecorator('markerDecorationsService'); @@ -16,7 +17,7 @@ export interface IMarkerDecorationsService { onDidChangeMarker: Event; - getMarker(model: ITextModel, decoration: IModelDecoration): IMarker | null; + getMarker(uri: URI, decoration: IModelDecoration): IMarker | null; - getLiveMarkers(model: ITextModel): [Range, IMarker][]; + getLiveMarkers(uri: URI): [Range, IMarker][]; } diff --git a/src/vs/editor/common/services/modeService.ts b/src/vs/editor/common/services/modeService.ts index 2739d6525..e52fbf61a 100644 --- a/src/vs/editor/common/services/modeService.ts +++ b/src/vs/editor/common/services/modeService.ts @@ -50,7 +50,7 @@ export interface IModeService { // --- instantiation create(commaSeparatedMimetypesOrCommaSeparatedIds: string | undefined): ILanguageSelection; createByLanguageName(languageName: string): ILanguageSelection; - createByFilepathOrFirstLine(rsource: URI | null, firstLine?: string): ILanguageSelection; + createByFilepathOrFirstLine(resource: URI | null, firstLine?: string): ILanguageSelection; triggerMode(commaSeparatedMimetypesOrCommaSeparatedIds: string): void; } diff --git a/src/vs/editor/common/services/modeServiceImpl.ts b/src/vs/editor/common/services/modeServiceImpl.ts index 6b2fd6f80..f5a6eba1d 100644 --- a/src/vs/editor/common/services/modeServiceImpl.ts +++ b/src/vs/editor/common/services/modeServiceImpl.ts @@ -40,23 +40,24 @@ class LanguageSelection extends Disposable implements ILanguageSelection { } } -export class ModeServiceImpl implements IModeService { +export class ModeServiceImpl extends Disposable implements IModeService { public _serviceBrand: undefined; private readonly _instantiatedModes: { [modeId: string]: IMode; }; private readonly _registry: LanguagesRegistry; - private readonly _onDidCreateMode = new Emitter(); + private readonly _onDidCreateMode = this._register(new Emitter()); public readonly onDidCreateMode: Event = this._onDidCreateMode.event; - protected readonly _onLanguagesMaybeChanged = new Emitter(); + protected readonly _onLanguagesMaybeChanged = this._register(new Emitter()); public readonly onLanguagesMaybeChanged: Event = this._onLanguagesMaybeChanged.event; constructor(warnOnOverwrite = false) { + super(); this._instantiatedModes = {}; - this._registry = new LanguagesRegistry(true, warnOnOverwrite); - this._registry.onDidChange(() => this._onLanguagesMaybeChanged.fire()); + this._registry = this._register(new LanguagesRegistry(true, warnOnOverwrite)); + this._register(this._registry.onDidChange(() => this._onLanguagesMaybeChanged.fire())); } protected _onReady(): Promise { diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 866727686..15824fd70 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -29,6 +29,7 @@ import { StringSHA1 } from 'vs/base/common/hash'; import { EditStackElement, isEditStackElement } from 'vs/editor/common/model/editStack'; import { Schemas } from 'vs/base/common/network'; import { SemanticTokensProviderStyling, toMultilineTokens2 } from 'vs/editor/common/services/semanticTokensProviderStyling'; +import { getDocumentSemanticTokens, isSemanticTokens, isSemanticTokensEdits } from 'vs/editor/common/services/getSemanticTokens'; export interface IEditorSemanticHighlightingOptions { enabled: true | false | 'configuredByTheme'; @@ -412,10 +413,11 @@ export class ModelServiceImpl extends Disposable implements IModelService { public updateModel(model: ITextModel, value: string | ITextBufferFactory): void { const options = this.getCreationOptions(model.getLanguageIdentifier().language, model.uri, model.isForSimpleWidget); - const textBuffer = createTextBuffer(value, options.defaultEOL); + const { textBuffer, disposable } = createTextBuffer(value, options.defaultEOL); // Return early if the text is already set in that form if (model.equalsTextBuffer(textBuffer)) { + disposable.dispose(); return; } @@ -428,6 +430,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { () => [] ); model.pushStackElement(); + disposable.dispose(); } private static _commonPrefix(a: ILineSequence, aLen: number, aDelta: number, b: ILineSequence, bLen: number, bDelta: number): number { @@ -513,58 +516,6 @@ export class ModelServiceImpl extends Disposable implements IModelService { if (!modelData) { return; } - const model = modelData.model; - const sharesUndoRedoStack = (this._undoRedoService.getUriComparisonKey(model.uri) !== model.uri.toString()); - let maintainUndoRedoStack = false; - let heapSize = 0; - if (sharesUndoRedoStack || (this._shouldRestoreUndoStack() && schemaShouldMaintainUndoRedoElements(resource))) { - const elements = this._undoRedoService.getElements(resource); - if (elements.past.length > 0 || elements.future.length > 0) { - for (const element of elements.past) { - if (isEditStackElement(element) && element.matchesResource(resource)) { - maintainUndoRedoStack = true; - heapSize += element.heapSize(resource); - element.setModel(resource); // remove reference from text buffer instance - } - } - for (const element of elements.future) { - if (isEditStackElement(element) && element.matchesResource(resource)) { - maintainUndoRedoStack = true; - heapSize += element.heapSize(resource); - element.setModel(resource); // remove reference from text buffer instance - } - } - } - } - - if (!maintainUndoRedoStack) { - if (!sharesUndoRedoStack) { - const initialUndoRedoSnapshot = modelData.model.getInitialUndoRedoSnapshot(); - if (initialUndoRedoSnapshot !== null) { - this._undoRedoService.restoreSnapshot(initialUndoRedoSnapshot); - } - } - modelData.model.dispose(); - return; - } - - const maxMemory = ModelServiceImpl.MAX_MEMORY_FOR_CLOSED_FILES_UNDO_STACK; - if (!sharesUndoRedoStack && heapSize > maxMemory) { - // the undo stack for this file would never fit in the configured memory, so don't bother with it. - const initialUndoRedoSnapshot = modelData.model.getInitialUndoRedoSnapshot(); - if (initialUndoRedoSnapshot !== null) { - this._undoRedoService.restoreSnapshot(initialUndoRedoSnapshot); - } - modelData.model.dispose(); - return; - } - - this._ensureDisposedModelsHeapSize(maxMemory - heapSize); - - // We only invalidate the elements, but they remain in the undo-redo service. - this._undoRedoService.setElementsValidFlag(resource, false, (element) => (isEditStackElement(element) && element.matchesResource(resource))); - this._insertDisposedModel(new DisposedModelInfo(resource, modelData.model.getInitialUndoRedoSnapshot(), Date.now(), sharesUndoRedoStack, heapSize, computeModelSha1(model), model.getVersionId(), model.getAlternativeVersionId())); - modelData.model.dispose(); } @@ -599,6 +550,50 @@ export class ModelServiceImpl extends Disposable implements IModelService { const modelId = MODEL_ID(model.uri); const modelData = this._models[modelId]; + const sharesUndoRedoStack = (this._undoRedoService.getUriComparisonKey(model.uri) !== model.uri.toString()); + let maintainUndoRedoStack = false; + let heapSize = 0; + if (sharesUndoRedoStack || (this._shouldRestoreUndoStack() && schemaShouldMaintainUndoRedoElements(model.uri))) { + const elements = this._undoRedoService.getElements(model.uri); + if (elements.past.length > 0 || elements.future.length > 0) { + for (const element of elements.past) { + if (isEditStackElement(element) && element.matchesResource(model.uri)) { + maintainUndoRedoStack = true; + heapSize += element.heapSize(model.uri); + element.setModel(model.uri); // remove reference from text buffer instance + } + } + for (const element of elements.future) { + if (isEditStackElement(element) && element.matchesResource(model.uri)) { + maintainUndoRedoStack = true; + heapSize += element.heapSize(model.uri); + element.setModel(model.uri); // remove reference from text buffer instance + } + } + } + } + + const maxMemory = ModelServiceImpl.MAX_MEMORY_FOR_CLOSED_FILES_UNDO_STACK; + if (!maintainUndoRedoStack) { + if (!sharesUndoRedoStack) { + const initialUndoRedoSnapshot = modelData.model.getInitialUndoRedoSnapshot(); + if (initialUndoRedoSnapshot !== null) { + this._undoRedoService.restoreSnapshot(initialUndoRedoSnapshot); + } + } + } else if (!sharesUndoRedoStack && heapSize > maxMemory) { + // the undo stack for this file would never fit in the configured memory, so don't bother with it. + const initialUndoRedoSnapshot = modelData.model.getInitialUndoRedoSnapshot(); + if (initialUndoRedoSnapshot !== null) { + this._undoRedoService.restoreSnapshot(initialUndoRedoSnapshot); + } + } else { + this._ensureDisposedModelsHeapSize(maxMemory - heapSize); + // We only invalidate the elements, but they remain in the undo-redo service. + this._undoRedoService.setElementsValidFlag(model.uri, false, (element) => (isEditStackElement(element) && element.matchesResource(model.uri))); + this._insertDisposedModel(new DisposedModelInfo(model.uri, modelData.model.getInitialUndoRedoSnapshot(), Date.now(), sharesUndoRedoStack, heapSize, computeModelSha1(model), model.getVersionId(), model.getAlternativeVersionId())); + } + delete this._models[modelId]; modelData.dispose(); @@ -718,7 +713,9 @@ class SemanticTokensResponse { } } -class ModelSemanticColoring extends Disposable { +export class ModelSemanticColoring extends Disposable { + + public static FETCH_DOCUMENT_SEMANTIC_TOKENS_DELAY = 300; private _isDisposed: boolean; private readonly _model: ITextModel; @@ -734,7 +731,7 @@ class ModelSemanticColoring extends Disposable { this._isDisposed = false; this._model = model; this._semanticStyling = stylingProvider; - this._fetchDocumentSemanticTokens = this._register(new RunOnceScheduler(() => this._fetchDocumentSemanticTokensNow(), 300)); + this._fetchDocumentSemanticTokens = this._register(new RunOnceScheduler(() => this._fetchDocumentSemanticTokensNow(), ModelSemanticColoring.FETCH_DOCUMENT_SEMANTIC_TOKENS_DELAY)); this._currentDocumentResponse = null; this._currentDocumentRequestCancellationTokenSource = null; this._documentProvidersChangeListeners = []; @@ -788,15 +785,21 @@ class ModelSemanticColoring extends Disposable { // there is already a request running, let it finish... return; } - const provider = this._getSemanticColoringProvider(); - if (!provider) { + + const cancellationTokenSource = new CancellationTokenSource(); + const lastResultId = this._currentDocumentResponse ? this._currentDocumentResponse.resultId || null : null; + const r = getDocumentSemanticTokens(this._model, lastResultId, cancellationTokenSource.token); + if (!r) { + // there is no provider if (this._currentDocumentResponse) { // there are semantic tokens set this._model.setSemanticTokens(null, false); } return; } - this._currentDocumentRequestCancellationTokenSource = new CancellationTokenSource(); + + const { provider, request } = r; + this._currentDocumentRequestCancellationTokenSource = cancellationTokenSource; const pendingChanges: IModelContentChangedEvent[] = []; const contentChangeListener = this._model.onDidChangeContent((e) => { @@ -805,15 +808,13 @@ class ModelSemanticColoring extends Disposable { const styling = this._semanticStyling.get(provider); - const lastResultId = this._currentDocumentResponse ? this._currentDocumentResponse.resultId || null : null; - const request = Promise.resolve(provider.provideDocumentSemanticTokens(this._model, lastResultId, this._currentDocumentRequestCancellationTokenSource.token)); - request.then((res) => { this._currentDocumentRequestCancellationTokenSource = null; contentChangeListener.dispose(); this._setDocumentSemanticTokens(provider, res || null, styling, pendingChanges); }, (err) => { - if (!err || typeof err.message !== 'string' || err.message.indexOf('busy') === -1) { + const isExpectedError = err && (errors.isPromiseCanceledError(err) || (typeof err.message === 'string' && err.message.indexOf('busy') !== -1)); + if (!isExpectedError) { errors.onUnexpectedError(err); } @@ -831,14 +832,6 @@ class ModelSemanticColoring extends Disposable { }); } - private static _isSemanticTokens(v: SemanticTokens | SemanticTokensEdits): v is SemanticTokens { - return v && !!((v).data); - } - - private static _isSemanticTokensEdits(v: SemanticTokens | SemanticTokensEdits): v is SemanticTokensEdits { - return v && Array.isArray((v).edits); - } - private static _copy(src: Uint32Array, srcOffset: number, dest: Uint32Array, destOffset: number, length: number): void { for (let i = 0; i < length; i++) { dest[destOffset + i] = src[srcOffset + i]; @@ -847,6 +840,12 @@ class ModelSemanticColoring extends Disposable { private _setDocumentSemanticTokens(provider: DocumentSemanticTokensProvider | null, tokens: SemanticTokens | SemanticTokensEdits | null, styling: SemanticTokensProviderStyling | null, pendingChanges: IModelContentChangedEvent[]): void { const currentResponse = this._currentDocumentResponse; + const rescheduleIfNeeded = () => { + if (pendingChanges.length > 0 && !this._fetchDocumentSemanticTokens.isScheduled()) { + this._fetchDocumentSemanticTokens.schedule(); + } + }; + if (this._currentDocumentResponse) { this._currentDocumentResponse.dispose(); this._currentDocumentResponse = null; @@ -864,10 +863,11 @@ class ModelSemanticColoring extends Disposable { } if (!tokens) { this._model.setSemanticTokens(null, true); + rescheduleIfNeeded(); return; } - if (ModelSemanticColoring._isSemanticTokensEdits(tokens)) { + if (isSemanticTokensEdits(tokens)) { if (!currentResponse) { // not possible! this._model.setSemanticTokens(null, true); @@ -918,7 +918,7 @@ class ModelSemanticColoring extends Disposable { } } - if (ModelSemanticColoring._isSemanticTokens(tokens)) { + if (isSemanticTokens(tokens)) { this._currentDocumentResponse = new SemanticTokensResponse(provider, tokens.resultId, tokens.data); @@ -937,21 +937,13 @@ class ModelSemanticColoring extends Disposable { } } } - - if (!this._fetchDocumentSemanticTokens.isScheduled()) { - this._fetchDocumentSemanticTokens.schedule(); - } } this._model.setSemanticTokens(result, true); - return; + } else { + this._model.setSemanticTokens(null, true); } - this._model.setSemanticTokens(null, true); - } - - private _getSemanticColoringProvider(): DocumentSemanticTokensProvider | null { - const result = DocumentSemanticTokensProviderRegistry.ordered(this._model); - return (result.length > 0 ? result[0] : null); + rescheduleIfNeeded(); } } diff --git a/src/vs/workbench/api/common/shared/semanticTokensDto.ts b/src/vs/editor/common/services/semanticTokensDto.ts similarity index 100% rename from src/vs/workbench/api/common/shared/semanticTokensDto.ts rename to src/vs/editor/common/services/semanticTokensDto.ts diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 8c772e3c3..ad85fc86c 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -287,11 +287,12 @@ export enum EditorOption { wrappingIndent = 117, wrappingStrategy = 118, showDeprecated = 119, - editorClassName = 120, - pixelRatio = 121, - tabFocusMode = 122, - layoutInfo = 123, - wrappingInfo = 124 + inlineHints = 120, + editorClassName = 121, + pixelRatio = 122, + tabFocusMode = 123, + layoutInfo = 124, + wrappingInfo = 125 } /** diff --git a/src/vs/editor/common/viewLayout/viewLayout.ts b/src/vs/editor/common/viewLayout/viewLayout.ts index 24059d23a..9d5583ee0 100644 --- a/src/vs/editor/common/viewLayout/viewLayout.ts +++ b/src/vs/editor/common/viewLayout/viewLayout.ts @@ -255,7 +255,7 @@ export class ViewLayout extends Disposable implements IViewLayout { let result = this._linesLayout.getLinesTotalHeight(); if (options.get(EditorOption.scrollBeyondLastLine)) { - result += height - options.get(EditorOption.lineHeight); + result += Math.max(0, height - options.get(EditorOption.lineHeight) - options.get(EditorOption.padding).bottom); } else { result += this._getHorizontalScrollbarHeight(width, contentWidth); } diff --git a/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts b/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts index 67a3b90fa..9640d0076 100644 --- a/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts +++ b/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts @@ -303,6 +303,19 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla breakOffsetVisibleColumn = forcedBreakOffsetVisibleColumn; } + if (breakOffset <= lastBreakingOffset) { + // Make sure that we are advancing (at least one character) + const charCode = lineText.charCodeAt(lastBreakingOffset); + if (strings.isHighSurrogate(charCode)) { + // A surrogate pair must always be considered as a single unit, so it is never to be broken + breakOffset = lastBreakingOffset + 2; + breakOffsetVisibleColumn = lastBreakingOffsetVisibleColumn + 2; + } else { + breakOffset = lastBreakingOffset + 1; + breakOffsetVisibleColumn = lastBreakingOffsetVisibleColumn + computeCharWidth(charCode, lastBreakingOffsetVisibleColumn, tabSize, columnsForFullWidthChar); + } + } + lastBreakingOffset = breakOffset; breakingOffsets[breakingOffsetsCount] = breakOffset; lastBreakingOffsetVisibleColumn = breakOffsetVisibleColumn; @@ -435,6 +448,10 @@ function computeCharWidth(charCode: number, visibleColumn: number, tabSize: numb if (strings.isFullWidthCharacter(charCode)) { return columnsForFullWidthChar; } + if (charCode < 32) { + // when using `editor.renderControlCharacters`, the substitutions are often wide + return columnsForFullWidthChar; + } return 1; } diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 50765f8ee..89ca90006 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -503,15 +503,8 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return null; } - let hiddenAreas = this.getHiddenAreas(); - let isInHiddenArea = false; - let testPosition = new Position(fromLineNumber, 1); - for (const hiddenArea of hiddenAreas) { - if (hiddenArea.containsPosition(testPosition)) { - isInHiddenArea = true; - break; - } - } + // cannot use this.getHiddenAreas() because those decorations have already seen the effect of this model change + const isInHiddenArea = (fromLineNumber > 2 && !this.lines[fromLineNumber - 2].isVisible()); let outputFromLineNumber = (fromLineNumber === 1 ? 1 : this.prefixSumComputer.getAccumulatedValue(fromLineNumber - 2) + 1); diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 932f6917e..a469a79bb 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -916,8 +916,8 @@ export class ViewModel extends Disposable implements IViewModel { public getPosition(): Position { return this._cursor.getPrimaryCursorState().modelState.position; } - public setSelections(source: string | null | undefined, selections: readonly ISelection[]): void { - this._withViewEventsCollector(eventsCollector => this._cursor.setSelections(eventsCollector, source, selections)); + public setSelections(source: string | null | undefined, selections: readonly ISelection[], reason = CursorChangeReason.NotSet): void { + this._withViewEventsCollector(eventsCollector => this._cursor.setSelections(eventsCollector, source, selections, reason)); } public saveCursorState(): ICursorState[] { return this._cursor.saveState(); diff --git a/src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts b/src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts index 0a11a1a5f..c59956eb2 100644 --- a/src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts +++ b/src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts @@ -39,20 +39,20 @@ suite('bracket matching', () => { // start on closing bracket editor.setPosition(new Position(1, 20)); bracketMatchingController.jumpToBracket(); - assert.deepEqual(editor.getPosition(), new Position(1, 9)); + assert.deepStrictEqual(editor.getPosition(), new Position(1, 9)); bracketMatchingController.jumpToBracket(); - assert.deepEqual(editor.getPosition(), new Position(1, 19)); + assert.deepStrictEqual(editor.getPosition(), new Position(1, 19)); bracketMatchingController.jumpToBracket(); - assert.deepEqual(editor.getPosition(), new Position(1, 9)); + assert.deepStrictEqual(editor.getPosition(), new Position(1, 9)); // start on opening bracket editor.setPosition(new Position(1, 23)); bracketMatchingController.jumpToBracket(); - assert.deepEqual(editor.getPosition(), new Position(1, 31)); + assert.deepStrictEqual(editor.getPosition(), new Position(1, 31)); bracketMatchingController.jumpToBracket(); - assert.deepEqual(editor.getPosition(), new Position(1, 23)); + assert.deepStrictEqual(editor.getPosition(), new Position(1, 23)); bracketMatchingController.jumpToBracket(); - assert.deepEqual(editor.getPosition(), new Position(1, 31)); + assert.deepStrictEqual(editor.getPosition(), new Position(1, 31)); bracketMatchingController.dispose(); }); @@ -71,25 +71,25 @@ suite('bracket matching', () => { // start position between brackets editor.setPosition(new Position(1, 16)); bracketMatchingController.jumpToBracket(); - assert.deepEqual(editor.getPosition(), new Position(1, 18)); + assert.deepStrictEqual(editor.getPosition(), new Position(1, 18)); bracketMatchingController.jumpToBracket(); - assert.deepEqual(editor.getPosition(), new Position(1, 14)); + assert.deepStrictEqual(editor.getPosition(), new Position(1, 14)); bracketMatchingController.jumpToBracket(); - assert.deepEqual(editor.getPosition(), new Position(1, 18)); + assert.deepStrictEqual(editor.getPosition(), new Position(1, 18)); // skip brackets in comments editor.setPosition(new Position(1, 21)); bracketMatchingController.jumpToBracket(); - assert.deepEqual(editor.getPosition(), new Position(1, 23)); + assert.deepStrictEqual(editor.getPosition(), new Position(1, 23)); bracketMatchingController.jumpToBracket(); - assert.deepEqual(editor.getPosition(), new Position(1, 24)); + assert.deepStrictEqual(editor.getPosition(), new Position(1, 24)); bracketMatchingController.jumpToBracket(); - assert.deepEqual(editor.getPosition(), new Position(1, 23)); + assert.deepStrictEqual(editor.getPosition(), new Position(1, 23)); // do not break if no brackets are available editor.setPosition(new Position(1, 26)); bracketMatchingController.jumpToBracket(); - assert.deepEqual(editor.getPosition(), new Position(1, 26)); + assert.deepStrictEqual(editor.getPosition(), new Position(1, 26)); bracketMatchingController.dispose(); }); @@ -109,32 +109,32 @@ suite('bracket matching', () => { // start position in open brackets editor.setPosition(new Position(1, 9)); bracketMatchingController.selectToBracket(true); - assert.deepEqual(editor.getPosition(), new Position(1, 20)); - assert.deepEqual(editor.getSelection(), new Selection(1, 9, 1, 20)); + assert.deepStrictEqual(editor.getPosition(), new Position(1, 20)); + assert.deepStrictEqual(editor.getSelection(), new Selection(1, 9, 1, 20)); // start position in close brackets editor.setPosition(new Position(1, 20)); bracketMatchingController.selectToBracket(true); - assert.deepEqual(editor.getPosition(), new Position(1, 20)); - assert.deepEqual(editor.getSelection(), new Selection(1, 9, 1, 20)); + assert.deepStrictEqual(editor.getPosition(), new Position(1, 20)); + assert.deepStrictEqual(editor.getSelection(), new Selection(1, 9, 1, 20)); // start position between brackets editor.setPosition(new Position(1, 16)); bracketMatchingController.selectToBracket(true); - assert.deepEqual(editor.getPosition(), new Position(1, 19)); - assert.deepEqual(editor.getSelection(), new Selection(1, 14, 1, 19)); + assert.deepStrictEqual(editor.getPosition(), new Position(1, 19)); + assert.deepStrictEqual(editor.getSelection(), new Selection(1, 14, 1, 19)); // start position outside brackets editor.setPosition(new Position(1, 21)); bracketMatchingController.selectToBracket(true); - assert.deepEqual(editor.getPosition(), new Position(1, 25)); - assert.deepEqual(editor.getSelection(), new Selection(1, 23, 1, 25)); + assert.deepStrictEqual(editor.getPosition(), new Position(1, 25)); + assert.deepStrictEqual(editor.getSelection(), new Selection(1, 23, 1, 25)); // do not break if no brackets are available editor.setPosition(new Position(1, 26)); bracketMatchingController.selectToBracket(true); - assert.deepEqual(editor.getPosition(), new Position(1, 26)); - assert.deepEqual(editor.getSelection(), new Selection(1, 26, 1, 26)); + assert.deepStrictEqual(editor.getPosition(), new Position(1, 26)); + assert.deepStrictEqual(editor.getSelection(), new Selection(1, 26, 1, 26)); bracketMatchingController.dispose(); }); @@ -159,7 +159,7 @@ suite('bracket matching', () => { editor.setPosition(new Position(3, 5)); bracketMatchingController.jumpToBracket(); - assert.deepEqual(editor.getSelection(), new Selection(5, 1, 5, 1)); + assert.deepStrictEqual(editor.getSelection(), new Selection(5, 1, 5, 1)); bracketMatchingController.dispose(); }); @@ -184,7 +184,7 @@ suite('bracket matching', () => { editor.setPosition(new Position(3, 5)); bracketMatchingController.selectToBracket(false); - assert.deepEqual(editor.getSelection(), new Selection(1, 12, 5, 1)); + assert.deepStrictEqual(editor.getSelection(), new Selection(1, 12, 5, 1)); bracketMatchingController.dispose(); }); @@ -207,7 +207,7 @@ suite('bracket matching', () => { new Selection(1, 17, 1, 17) ]); bracketMatchingController.selectToBracket(true); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 5), new Selection(1, 8, 1, 13), new Selection(1, 16, 1, 19) @@ -220,7 +220,7 @@ suite('bracket matching', () => { new Selection(1, 14, 1, 14) ]); bracketMatchingController.selectToBracket(true); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 5), new Selection(1, 8, 1, 13), new Selection(1, 16, 1, 19) @@ -233,7 +233,7 @@ suite('bracket matching', () => { new Selection(1, 19, 1, 19) ]); bracketMatchingController.selectToBracket(true); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 5), new Selection(1, 8, 1, 13), new Selection(1, 16, 1, 19) diff --git a/src/vs/editor/contrib/clipboard/clipboard.ts b/src/vs/editor/contrib/clipboard/clipboard.ts index 6860a7bb6..8a0f08ffc 100644 --- a/src/vs/editor/contrib/clipboard/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/clipboard.ts @@ -24,10 +24,11 @@ const CLIPBOARD_CONTEXT_MENU_GROUP = '9_cutcopypaste'; const supportsCut = (platform.isNative || document.queryCommandSupported('cut')); const supportsCopy = (platform.isNative || document.queryCommandSupported('copy')); // IE and Edge have trouble with setting html content in clipboard -const supportsCopyWithSyntaxHighlighting = (supportsCopy && !browser.isEdge); +const supportsCopyWithSyntaxHighlighting = (supportsCopy && !browser.isEdgeLegacy); // Firefox only supports navigator.clipboard.readText() in browser extensions. // See https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/readText#Browser_compatibility -const supportsPaste = (browser.isFirefox ? document.queryCommandSupported('paste') : true); +// When loading over http, navigator.clipboard can be undefined. See https://github.com/microsoft/monaco-editor/issues/2313 +const supportsPaste = (typeof navigator.clipboard === 'undefined' || browser.isFirefox) ? document.queryCommandSupported('paste') : true; function registerCommand(command: T): T { command.register(); diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index 3fddfdff3..da622e6ef 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -98,14 +98,17 @@ export class CodeLensContribution implements IEditorContribution { const fontFamily = this._editor.getOption(EditorOption.codeLensFontFamily); const editorFontInfo = this._editor.getOption(EditorOption.fontInfo); + const fontFamilyVar = `--codelens-font-family${this._styleClassName}`; + let newStyle = ` .monaco-editor .codelens-decoration.${this._styleClassName} { line-height: ${codeLensHeight}px; font-size: ${fontSize}px; padding-right: ${Math.round(fontSize * 0.5)}px; font-feature-settings: ${editorFontInfo.fontFeatureSettings} } .monaco-editor .codelens-decoration.${this._styleClassName} span.codicon { line-height: ${codeLensHeight}px; font-size: ${fontSize}px; } `; if (fontFamily) { - newStyle += `.monaco-editor .codelens-decoration.${this._styleClassName} { font-family: '${fontFamily}'}`; + newStyle += `.monaco-editor .codelens-decoration.${this._styleClassName} { font-family: var(${fontFamilyVar})}`; } this._styleElement.textContent = newStyle; + this._editor.getDomNode()?.style.setProperty(fontFamilyVar, fontFamily ?? 'inherit'); // this._editor.changeViewZones(accessor => { diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index f77b2dfc5..9014c904a 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -14,7 +14,7 @@ import { editorCodeLensForeground } from 'vs/editor/common/view/editorColorRegis import { CodeLensItem } from 'vs/editor/contrib/codelens/codelens'; import { editorActiveLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { renderCodicons } from 'vs/base/browser/codicons'; +import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; class CodeLensViewZone implements IViewZone { @@ -88,7 +88,7 @@ class CodeLensContentWidget implements IContentWidget { } hasSymbol = true; if (lens.command) { - const title = renderCodicons(lens.command.title.trim()); + const title = renderLabelWithIcons(lens.command.title.trim()); if (lens.command.id) { children.push(dom.$('a', { id: String(i) }, ...title)); this._commands.set(String(i), lens.command); diff --git a/src/vs/editor/contrib/colorPicker/colorContributions.ts b/src/vs/editor/contrib/colorPicker/colorContributions.ts index 4a997061b..90f91a846 100644 --- a/src/vs/editor/contrib/colorPicker/colorContributions.ts +++ b/src/vs/editor/contrib/colorPicker/colorContributions.ts @@ -47,7 +47,7 @@ export class ColorContribution extends Disposable implements IEditorContribution } const hoverController = this._editor.getContribution(ModesHoverController.ID); - if (!hoverController.contentWidget.isColorPickerVisible()) { + if (!hoverController.isColorPickerVisible()) { const range = new Range(mouseEvent.target.range.startLineNumber, mouseEvent.target.range.startColumn + 1, mouseEvent.target.range.endLineNumber, mouseEvent.target.range.endColumn + 1); hoverController.showContentHover(range, HoverStartMode.Delayed, false); } diff --git a/src/vs/editor/contrib/comment/test/lineCommentCommand.test.ts b/src/vs/editor/contrib/comment/test/lineCommentCommand.test.ts index 2a4329ba9..81b47f075 100644 --- a/src/vs/editor/contrib/comment/test/lineCommentCommand.test.ts +++ b/src/vs/editor/contrib/comment/test/lineCommentCommand.test.ts @@ -96,25 +96,25 @@ suite('Editor Contrib - Line Comment Command', () => { throw new Error(`unexpected`); } - assert.equal(r.shouldRemoveComments, false); + assert.strictEqual(r.shouldRemoveComments, false); // Does not change `commentStr` - assert.equal(r.lines[0].commentStr, '//'); - assert.equal(r.lines[1].commentStr, 'rem'); - assert.equal(r.lines[2].commentStr, '!@#'); - assert.equal(r.lines[3].commentStr, '!@#'); + assert.strictEqual(r.lines[0].commentStr, '//'); + assert.strictEqual(r.lines[1].commentStr, 'rem'); + assert.strictEqual(r.lines[2].commentStr, '!@#'); + assert.strictEqual(r.lines[3].commentStr, '!@#'); // Fills in `isWhitespace` - assert.equal(r.lines[0].ignore, true); - assert.equal(r.lines[1].ignore, true); - assert.equal(r.lines[2].ignore, false); - assert.equal(r.lines[3].ignore, false); + assert.strictEqual(r.lines[0].ignore, true); + assert.strictEqual(r.lines[1].ignore, true); + assert.strictEqual(r.lines[2].ignore, false); + assert.strictEqual(r.lines[3].ignore, false); // Fills in `commentStrOffset` - assert.equal(r.lines[0].commentStrOffset, 2); - assert.equal(r.lines[1].commentStrOffset, 4); - assert.equal(r.lines[2].commentStrOffset, 4); - assert.equal(r.lines[3].commentStrOffset, 2); + assert.strictEqual(r.lines[0].commentStrOffset, 2); + assert.strictEqual(r.lines[1].commentStrOffset, 4); + assert.strictEqual(r.lines[2].commentStrOffset, 4); + assert.strictEqual(r.lines[3].commentStrOffset, 2); r = LineCommentCommand._analyzeLines(Type.Toggle, true, createSimpleModel([ @@ -127,31 +127,31 @@ suite('Editor Contrib - Line Comment Command', () => { throw new Error(`unexpected`); } - assert.equal(r.shouldRemoveComments, true); + assert.strictEqual(r.shouldRemoveComments, true); // Does not change `commentStr` - assert.equal(r.lines[0].commentStr, '//'); - assert.equal(r.lines[1].commentStr, 'rem'); - assert.equal(r.lines[2].commentStr, '!@#'); - assert.equal(r.lines[3].commentStr, '!@#'); + assert.strictEqual(r.lines[0].commentStr, '//'); + assert.strictEqual(r.lines[1].commentStr, 'rem'); + assert.strictEqual(r.lines[2].commentStr, '!@#'); + assert.strictEqual(r.lines[3].commentStr, '!@#'); // Fills in `isWhitespace` - assert.equal(r.lines[0].ignore, true); - assert.equal(r.lines[1].ignore, false); - assert.equal(r.lines[2].ignore, false); - assert.equal(r.lines[3].ignore, false); + assert.strictEqual(r.lines[0].ignore, true); + assert.strictEqual(r.lines[1].ignore, false); + assert.strictEqual(r.lines[2].ignore, false); + assert.strictEqual(r.lines[3].ignore, false); // Fills in `commentStrOffset` - assert.equal(r.lines[0].commentStrOffset, 2); - assert.equal(r.lines[1].commentStrOffset, 4); - assert.equal(r.lines[2].commentStrOffset, 4); - assert.equal(r.lines[3].commentStrOffset, 2); + assert.strictEqual(r.lines[0].commentStrOffset, 2); + assert.strictEqual(r.lines[1].commentStrOffset, 4); + assert.strictEqual(r.lines[2].commentStrOffset, 4); + assert.strictEqual(r.lines[3].commentStrOffset, 2); // Fills in `commentStrLength` - assert.equal(r.lines[0].commentStrLength, 2); - assert.equal(r.lines[1].commentStrLength, 4); - assert.equal(r.lines[2].commentStrLength, 4); - assert.equal(r.lines[3].commentStrLength, 3); + assert.strictEqual(r.lines[0].commentStrLength, 2); + assert.strictEqual(r.lines[1].commentStrLength, 4); + assert.strictEqual(r.lines[2].commentStrLength, 4); + assert.strictEqual(r.lines[3].commentStrLength, 3); }); test('_normalizeInsertionPoint', () => { @@ -166,7 +166,7 @@ suite('Editor Contrib - Line Comment Command', () => { }); LineCommentCommand._normalizeInsertionPoint(model, offsets, 1, tabSize); const actual = offsets.map(item => item.commentStrOffset); - assert.deepEqual(actual, expected, testName); + assert.deepStrictEqual(actual, expected, testName); }; // Bug 16696:[comment] comments not aligned in this case @@ -1083,7 +1083,7 @@ suite('Editor Contrib - Line Comment in mixed modes', () => { tokenize: () => { throw new Error('not implemented'); }, - tokenize2: (line: string, state: modes.IState): TokenizationResult2 => { + tokenize2: (line: string, hasEOL: boolean, state: modes.IState): TokenizationResult2 => { let languageId = (/^ /.test(line) ? INNER_LANGUAGE_ID : OUTER_LANGUAGE_ID); let tokens = new Uint32Array(1 << 1); diff --git a/src/vs/editor/contrib/cursorUndo/test/cursorUndo.test.ts b/src/vs/editor/contrib/cursorUndo/test/cursorUndo.test.ts index 308484142..4a398b498 100644 --- a/src/vs/editor/contrib/cursorUndo/test/cursorUndo.test.ts +++ b/src/vs/editor/contrib/cursorUndo/test/cursorUndo.test.ts @@ -29,16 +29,16 @@ suite('FindController', () => { // press Delete CoreEditingCommands.DeleteRight.runEditorCommand(null, editor, {}); - assert.deepEqual(editor.getValue(), 'hell'); - assert.deepEqual(editor.getSelections(), [new Selection(1, 5, 1, 5)]); + assert.deepStrictEqual(editor.getValue(), 'hell'); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 5, 1, 5)]); // press left CoreNavigationCommands.CursorLeft.runEditorCommand(null, editor, {}); - assert.deepEqual(editor.getSelections(), [new Selection(1, 4, 1, 4)]); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 4, 1, 4)]); // press Ctrl+U cursorUndoAction.run(null!, editor, {}); - assert.deepEqual(editor.getSelections(), [new Selection(1, 5, 1, 5)]); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 5, 1, 5)]); }); }); @@ -52,12 +52,12 @@ suite('FindController', () => { // type hello editor.trigger('test', Handler.Type, { text: 'hell' }); editor.trigger('test', Handler.Type, { text: 'o' }); - assert.deepEqual(editor.getValue(), 'hello'); - assert.deepEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]); + assert.deepStrictEqual(editor.getValue(), 'hello'); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]); // press Ctrl+U cursorUndoAction.run(null!, editor, {}); - assert.deepEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]); }); }); }); diff --git a/src/vs/editor/contrib/dnd/dnd.ts b/src/vs/editor/contrib/dnd/dnd.ts index b1b4a06b7..d535bb906 100644 --- a/src/vs/editor/contrib/dnd/dnd.ts +++ b/src/vs/editor/contrib/dnd/dnd.ts @@ -20,6 +20,7 @@ import { IModelDeltaDecoration } from 'vs/editor/common/model'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; function hasTriggerModifier(e: IKeyboardEvent | IMouseEvent): boolean { if (isMacintosh) { @@ -176,8 +177,8 @@ export class DragAndDropController extends Disposable implements IEditorContribu } }); } - // Use `mouse` as the source instead of `api`. - (this._editor).setSelections(newSelections || [], 'mouse'); + // Use `mouse` as the source instead of `api` and setting the reason to explicit (to behave like any other mouse operation). + (this._editor).setSelections(newSelections || [], 'mouse', CursorChangeReason.Explicit); } else if (!this._dragSelection.containsPosition(newCursorPosition) || ( ( diff --git a/src/vs/editor/contrib/gotoSymbol/documentSymbols.ts b/src/vs/editor/contrib/documentSymbols/documentSymbols.ts similarity index 54% rename from src/vs/editor/contrib/gotoSymbol/documentSymbols.ts rename to src/vs/editor/contrib/documentSymbols/documentSymbols.ts index 51702b59e..64f5fa0de 100644 --- a/src/vs/editor/contrib/gotoSymbol/documentSymbols.ts +++ b/src/vs/editor/contrib/documentSymbols/documentSymbols.ts @@ -4,62 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; import { DocumentSymbol } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { OutlineModel, OutlineElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; +import { OutlineModel } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { assertType } from 'vs/base/common/types'; -import { Iterable } from 'vs/base/common/iterator'; export async function getDocumentSymbols(document: ITextModel, flat: boolean, token: CancellationToken): Promise { - const model = await OutlineModel.create(document, token); - const roots: DocumentSymbol[] = []; - for (const child of model.children.values()) { - if (child instanceof OutlineElement) { - roots.push(child.symbol); - } else { - roots.push(...Iterable.map(child.children.values(), child => child.symbol)); - } - } - - let flatEntries: DocumentSymbol[] = []; - if (token.isCancellationRequested) { - return flatEntries; - } - if (flat) { - flatten(flatEntries, roots, ''); - } else { - flatEntries = roots; - } - - return flatEntries.sort(compareEntriesUsingStart); -} - -function compareEntriesUsingStart(a: DocumentSymbol, b: DocumentSymbol): number { - return Range.compareRangesUsingStarts(a.range, b.range); -} - -function flatten(bucket: DocumentSymbol[], entries: DocumentSymbol[], overrideContainerLabel: string): void { - for (let entry of entries) { - bucket.push({ - kind: entry.kind, - tags: entry.tags, - name: entry.name, - detail: entry.detail, - containerName: entry.containerName || overrideContainerLabel, - range: entry.range, - selectionRange: entry.selectionRange, - children: undefined, // we flatten it... - }); - if (entry.children) { - flatten(bucket, entry.children, entry.name); - } - } + return flat + ? model.asListOfDocumentSymbols() + : model.getTopLevelSymbols(); } CommandsRegistry.registerCommand('_executeDocumentSymbolProvider', async function (accessor, ...args) { diff --git a/src/vs/editor/contrib/documentSymbols/outline.ts b/src/vs/editor/contrib/documentSymbols/outline.ts deleted file mode 100644 index 1dc949017..000000000 --- a/src/vs/editor/contrib/documentSymbols/outline.ts +++ /dev/null @@ -1,18 +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 { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; - -export const OutlineViewId = 'outline'; - -export const OutlineViewFiltered = new RawContextKey('outlineFiltered', false); -export const OutlineViewFocused = new RawContextKey('outlineFocused', false); - -export const enum OutlineConfigKeys { - 'icons' = 'outline.icons', - 'problemsEnabled' = 'outline.problems.enabled', - 'problemsColors' = 'outline.problems.colors', - 'problemsBadges' = 'outline.problems.badges' -} diff --git a/src/vs/editor/contrib/documentSymbols/outlineModel.ts b/src/vs/editor/contrib/documentSymbols/outlineModel.ts index c85081502..5638223f9 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineModel.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineModel.ts @@ -14,8 +14,8 @@ import { ITextModel } from 'vs/editor/common/model'; import { DocumentSymbol, DocumentSymbolProvider, DocumentSymbolProviderRegistry } from 'vs/editor/common/modes'; import { MarkerSeverity } from 'vs/platform/markers/common/markers'; import { Iterable } from 'vs/base/common/iterator'; -import { URI } from 'vs/base/common/uri'; import { LanguageFeatureRequestDelays } from 'vs/editor/common/modes/languageFeatureRegistry'; +import { URI } from 'vs/base/common/uri'; export abstract class TreeElement { @@ -204,8 +204,6 @@ export class OutlineGroup extends TreeElement { } } - - export class OutlineModel extends TreeElement { private static readonly _requestDurations = new LanguageFeatureRequestDelays(DocumentSymbolProviderRegistry, 350); @@ -445,4 +443,43 @@ export class OutlineModel extends TreeElement { group.updateMarker(marker.slice(0)); } } + + getTopLevelSymbols(): DocumentSymbol[] { + const roots: DocumentSymbol[] = []; + for (const child of this.children.values()) { + if (child instanceof OutlineElement) { + roots.push(child.symbol); + } else { + roots.push(...Iterable.map(child.children.values(), child => child.symbol)); + } + } + return roots.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); + } + + asListOfDocumentSymbols(): DocumentSymbol[] { + const roots = this.getTopLevelSymbols(); + const bucket: DocumentSymbol[] = []; + OutlineModel._flattenDocumentSymbols(bucket, roots, ''); + return bucket.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); + } + + private static _flattenDocumentSymbols(bucket: DocumentSymbol[], entries: DocumentSymbol[], overrideContainerLabel: string): void { + for (const entry of entries) { + bucket.push({ + kind: entry.kind, + tags: entry.tags, + name: entry.name, + detail: entry.detail, + containerName: entry.containerName || overrideContainerLabel, + range: entry.range, + selectionRange: entry.selectionRange, + children: undefined, // we flatten it... + }); + + // Recurse over children + if (entry.children) { + OutlineModel._flattenDocumentSymbols(bucket, entry.children, entry.name); + } + } + } } diff --git a/src/vs/editor/contrib/find/findWidget.css b/src/vs/editor/contrib/find/findWidget.css index 15ed9eb3f..86efb77c2 100644 --- a/src/vs/editor/contrib/find/findWidget.css +++ b/src/vs/editor/contrib/find/findWidget.css @@ -60,14 +60,14 @@ } -.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .mirror { +.monaco-editor .find-widget > .replace-part .monaco-inputbox > .ibwrapper > .mirror { padding-right: 22px; } -.monaco-editor .find-widget > .find-part .monaco-inputbox > .wrapper > .input, -.monaco-editor .find-widget > .find-part .monaco-inputbox > .wrapper > .mirror, -.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .input, -.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .mirror { +.monaco-editor .find-widget > .find-part .monaco-inputbox > .ibwrapper > .input, +.monaco-editor .find-widget > .find-part .monaco-inputbox > .ibwrapper > .mirror, +.monaco-editor .find-widget > .replace-part .monaco-inputbox > .ibwrapper > .input, +.monaco-editor .find-widget > .replace-part .monaco-inputbox > .ibwrapper > .mirror { padding-top: 2px; padding-bottom: 2px; } diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index 597af6c32..89981ba44 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -1044,7 +1044,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL // Toggle selection button this._toggleSelectionFind = this._register(new Checkbox({ - icon: ThemeIcon.asCSSIcon(findSelectionIcon), + icon: findSelectionIcon, title: NLS_TOGGLE_SELECTION_FIND_TITLE + this._keybindingLabelFor(FIND_IDS.ToggleSearchScopeCommand), isChecked: false })); diff --git a/src/vs/editor/contrib/find/test/find.test.ts b/src/vs/editor/contrib/find/test/find.test.ts index 98f31f328..55490860d 100644 --- a/src/vs/editor/contrib/find/test/find.test.ts +++ b/src/vs/editor/contrib/find/test/find.test.ts @@ -20,17 +20,17 @@ suite('Find', () => { // The cursor is at the very top, of the file, at the first ABC let searchStringAtTop = getSelectionSearchString(editor); - assert.equal(searchStringAtTop, 'ABC'); + assert.strictEqual(searchStringAtTop, 'ABC'); // Move cursor to the end of ABC editor.setPosition(new Position(1, 3)); let searchStringAfterABC = getSelectionSearchString(editor); - assert.equal(searchStringAfterABC, 'ABC'); + assert.strictEqual(searchStringAfterABC, 'ABC'); // Move cursor to DEF editor.setPosition(new Position(1, 5)); let searchStringInsideDEF = getSelectionSearchString(editor); - assert.equal(searchStringInsideDEF, 'DEF'); + assert.strictEqual(searchStringInsideDEF, 'DEF'); }); }); @@ -44,17 +44,17 @@ suite('Find', () => { // Select A of ABC editor.setSelection(new Range(1, 1, 1, 2)); let searchStringSelectionA = getSelectionSearchString(editor); - assert.equal(searchStringSelectionA, 'A'); + assert.strictEqual(searchStringSelectionA, 'A'); // Select BC of ABC editor.setSelection(new Range(1, 2, 1, 4)); let searchStringSelectionBC = getSelectionSearchString(editor); - assert.equal(searchStringSelectionBC, 'BC'); + assert.strictEqual(searchStringSelectionBC, 'BC'); // Select BC DE editor.setSelection(new Range(1, 2, 1, 7)); let searchStringSelectionBCDE = getSelectionSearchString(editor); - assert.equal(searchStringSelectionBCDE, 'BC DE'); + assert.strictEqual(searchStringSelectionBCDE, 'BC DE'); }); }); @@ -68,17 +68,17 @@ suite('Find', () => { // Select first line and newline editor.setSelection(new Range(1, 1, 2, 1)); let searchStringSelectionWholeLine = getSelectionSearchString(editor); - assert.equal(searchStringSelectionWholeLine, null); + assert.strictEqual(searchStringSelectionWholeLine, null); // Select first line and chunk of second editor.setSelection(new Range(1, 1, 2, 4)); let searchStringSelectionTwoLines = getSelectionSearchString(editor); - assert.equal(searchStringSelectionTwoLines, null); + assert.strictEqual(searchStringSelectionTwoLines, null); // Select end of first line newline and chunk of second editor.setSelection(new Range(1, 7, 2, 4)); let searchStringSelectionSpanLines = getSelectionSearchString(editor); - assert.equal(searchStringSelectionSpanLines, null); + assert.strictEqual(searchStringSelectionSpanLines, null); }); }); diff --git a/src/vs/editor/contrib/find/test/findController.test.ts b/src/vs/editor/contrib/find/test/findController.test.ts index 8d9216ea0..3f55711c1 100644 --- a/src/vs/editor/contrib/find/test/findController.test.ts +++ b/src/vs/editor/contrib/find/test/findController.test.ts @@ -102,7 +102,7 @@ suite('FindController', async () => { // I hit Ctrl+F to show the Find dialog startFindAction.run(null, editor); - assert.deepEqual(findController.getGlobalBufferTerm(), findController.getState().searchString); + assert.deepStrictEqual(findController.getGlobalBufferTerm(), findController.getState().searchString); findController.dispose(); }); }); @@ -126,9 +126,9 @@ suite('FindController', async () => { let nextMatchFindAction = new NextMatchFindAction(); nextMatchFindAction.run(null, editor); - assert.equal(findState.searchString, 'ABC'); + assert.strictEqual(findState.searchString, 'ABC'); - assert.deepEqual(fromSelection(editor.getSelection()!), [1, 1, 1, 4]); + assert.deepStrictEqual(fromSelection(editor.getSelection()!), [1, 1, 1, 4]); findController.dispose(); }); @@ -152,7 +152,7 @@ suite('FindController', async () => { findState.change({ searchString: 'ABC' }, true); - assert.deepEqual(findController.getGlobalBufferTerm(), 'ABC'); + assert.deepStrictEqual(findController.getGlobalBufferTerm(), 'ABC'); findController.dispose(); }); @@ -181,14 +181,14 @@ suite('FindController', async () => { findState.change({ searchString: 'ABC' }, true); // The first ABC is highlighted. - assert.deepEqual(fromSelection(editor.getSelection()!), [1, 1, 1, 4]); + assert.deepStrictEqual(fromSelection(editor.getSelection()!), [1, 1, 1, 4]); // I hit Esc to exit the Find dialog. findController.closeFindWidget(); findController.hasFocus = false; // The cursor is now at end of the first line, with ABC on that line highlighted. - assert.deepEqual(fromSelection(editor.getSelection()!), [1, 1, 1, 4]); + assert.deepStrictEqual(fromSelection(editor.getSelection()!), [1, 1, 1, 4]); // I hit delete to remove it and change the text to XYZ. editor.pushUndoStop(); @@ -201,16 +201,16 @@ suite('FindController', async () => { // ABC // XYZ // ABC - assert.equal(editor.getModel()!.getLineContent(1), 'XYZ'); + assert.strictEqual(editor.getModel()!.getLineContent(1), 'XYZ'); // The cursor is at end of the first line. - assert.deepEqual(fromSelection(editor.getSelection()!), [1, 4, 1, 4]); + assert.deepStrictEqual(fromSelection(editor.getSelection()!), [1, 4, 1, 4]); // I hit F3 to "Find Next" to find the next occurrence of ABC, but instead it searches for XYZ. await nextMatchFindAction.run(null, editor); - assert.equal(findState.searchString, 'ABC'); - assert.equal(findController.hasFocus, false); + assert.strictEqual(findState.searchString, 'ABC'); + assert.strictEqual(findController.hasFocus, false); findController.dispose(); }); @@ -230,10 +230,10 @@ suite('FindController', async () => { }); await nextMatchFindAction.run(null, editor); - assert.deepEqual(fromSelection(editor.getSelection()!), [1, 26, 1, 29]); + assert.deepStrictEqual(fromSelection(editor.getSelection()!), [1, 26, 1, 29]); await nextMatchFindAction.run(null, editor); - assert.deepEqual(fromSelection(editor.getSelection()!), [1, 8, 1, 11]); + assert.deepStrictEqual(fromSelection(editor.getSelection()!), [1, 8, 1, 11]); findController.dispose(); }); @@ -256,10 +256,10 @@ suite('FindController', async () => { await startFindAction.run(null, editor); await nextMatchFindAction.run(null, editor); - assert.deepEqual(fromSelection(editor.getSelection()!), [2, 9, 2, 13]); + assert.deepStrictEqual(fromSelection(editor.getSelection()!), [2, 9, 2, 13]); await nextMatchFindAction.run(null, editor); - assert.deepEqual(fromSelection(editor.getSelection()!), [1, 9, 1, 13]); + assert.deepStrictEqual(fromSelection(editor.getSelection()!), [1, 9, 1, 13]); findController.dispose(); }); @@ -288,7 +288,7 @@ suite('FindController', async () => { await nextMatchFindAction.run(null, editor); await startFindReplaceAction.run(null, editor); - assert.equal(findController.getState().searchString, testRegexString); + assert.strictEqual(findController.getState().searchString, testRegexString); findController.dispose(); }); @@ -312,16 +312,16 @@ suite('FindController', async () => { loop: true }); - assert.equal(findController.getState().searchScope, null); + assert.strictEqual(findController.getState().searchScope, null); findController.getState().change({ searchScope: [new Range(1, 1, 1, 5)] }, false); - assert.deepEqual(findController.getState().searchScope, [new Range(1, 1, 1, 5)]); + assert.deepStrictEqual(findController.getState().searchScope, [new Range(1, 1, 1, 5)]); findController.closeFindWidget(); - assert.equal(findController.getState().searchScope, null); + assert.strictEqual(findController.getState().searchScope, null); }); }); @@ -338,13 +338,13 @@ suite('FindController', async () => { findController.getState().change({ searchString: '\\b\\s{3}\\b', replaceString: ' ', isRegex: true }, false); findController.moveToNextMatch(); - assert.deepEqual(editor.getSelections()!.map(fromSelection), [ + assert.deepStrictEqual(editor.getSelections()!.map(fromSelection), [ [1, 39, 1, 42] ]); findController.replace(); - assert.deepEqual(editor.getValue(), 'HRESULT OnAmbientPropertyChange(DISPID dispid);'); + assert.deepStrictEqual(editor.getValue(), 'HRESULT OnAmbientPropertyChange(DISPID dispid);'); findController.dispose(); }); @@ -365,13 +365,13 @@ suite('FindController', async () => { findController.getState().change({ searchString: '^', replaceString: 'x', isRegex: true }, false); findController.moveToNextMatch(); - assert.deepEqual(editor.getSelections()!.map(fromSelection), [ + assert.deepStrictEqual(editor.getSelections()!.map(fromSelection), [ [2, 1, 2, 1] ]); findController.replace(); - assert.deepEqual(editor.getValue(), '\nxline2\nline3'); + assert.deepStrictEqual(editor.getValue(), '\nxline2\nline3'); findController.dispose(); }); @@ -396,7 +396,7 @@ suite('FindController', async () => { // cmd+f3 await nextSelectionMatchFindAction.run(null, editor); - assert.deepEqual(editor.getSelections()!.map(fromSelection), [ + assert.deepStrictEqual(editor.getSelections()!.map(fromSelection), [ [3, 1, 3, 9] ]); @@ -427,7 +427,7 @@ suite('FindController', async () => { // cmd+f3 await nextSelectionMatchFindAction.run(null, editor); - assert.deepEqual(editor.getSelections()!.map(fromSelection), [ + assert.deepStrictEqual(editor.getSelections()!.map(fromSelection), [ [3, 1, 3, 9] ]); @@ -458,7 +458,7 @@ suite('FindController', async () => { await startFindWithSelectionAction.run(null, editor); let findState = findController.getState(); - assert.deepEqual(findState.searchString.split(/\r\n|\r|\n/g), ['ABC', 'ABC']); + assert.deepStrictEqual(findState.searchString.split(/\r\n|\r|\n/g), ['ABC', 'ABC']); editor.setSelection(new Selection(3, 1, 3, 1)); await startFindWithSelectionAction.run(null, editor); @@ -483,7 +483,7 @@ suite('FindController', async () => { startFindWithSelectionAction.run(null, editor); let findState = findController.getState(); - assert.deepEqual(findState.searchString, 'ABC'); + assert.deepStrictEqual(findState.searchString, 'ABC'); findController.dispose(); }); }); @@ -531,7 +531,7 @@ suite('FindController query options persistence', async () => { // I type ABC. findState.change({ searchString: 'ABC' }, true); // The second ABC is highlighted as matchCase is true. - assert.deepEqual(fromSelection(editor.getSelection()!), [2, 1, 2, 4]); + assert.deepStrictEqual(fromSelection(editor.getSelection()!), [2, 1, 2, 4]); findController.dispose(); }); @@ -558,7 +558,7 @@ suite('FindController query options persistence', async () => { // I type AB. findState.change({ searchString: 'AB' }, true); // The second AB is highlighted as wholeWord is true. - assert.deepEqual(fromSelection(editor.getSelection()!), [2, 1, 2, 3]); + assert.deepStrictEqual(fromSelection(editor.getSelection()!), [2, 1, 2, 3]); findController.dispose(); }); @@ -575,7 +575,7 @@ suite('FindController query options persistence', async () => { // The cursor is at the very top, of the file, at the first ABC let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); findController.toggleRegex(); - assert.equal(queryState['editor.isRegex'], true); + assert.strictEqual(queryState['editor.isRegex'], true); findController.dispose(); }); @@ -601,13 +601,13 @@ suite('FindController query options persistence', async () => { editor.setSelection(new Range(1, 1, 2, 1)); findController.start(findConfig); - assert.deepEqual(findController.getState().searchScope, [new Selection(1, 1, 2, 1)]); + assert.deepStrictEqual(findController.getState().searchScope, [new Selection(1, 1, 2, 1)]); findController.closeFindWidget(); editor.setSelections([new Selection(1, 1, 2, 1), new Selection(2, 1, 2, 5)]); findController.start(findConfig); - assert.deepEqual(findController.getState().searchScope, [new Selection(1, 1, 2, 1), new Selection(2, 1, 2, 5)]); + assert.deepStrictEqual(findController.getState().searchScope, [new Selection(1, 1, 2, 1), new Selection(2, 1, 2, 5)]); }); }); @@ -631,7 +631,7 @@ suite('FindController query options persistence', async () => { loop: true }); - assert.deepEqual(findController.getState().searchScope, null); + assert.deepStrictEqual(findController.getState().searchScope, null); }); }); @@ -655,7 +655,7 @@ suite('FindController query options persistence', async () => { loop: true }); - assert.deepEqual(findController.getState().searchScope, [new Selection(1, 2, 1, 3)]); + assert.deepStrictEqual(findController.getState().searchScope, [new Selection(1, 2, 1, 3)]); }); }); @@ -680,7 +680,7 @@ suite('FindController query options persistence', async () => { loop: true }); - assert.deepEqual(findController.getState().searchScope, [new Selection(1, 6, 2, 1)]); + assert.deepStrictEqual(findController.getState().searchScope, [new Selection(1, 6, 2, 1)]); }); }); }); diff --git a/src/vs/editor/contrib/find/test/findModel.test.ts b/src/vs/editor/contrib/find/test/findModel.test.ts index 70c549498..10c7ae326 100644 --- a/src/vs/editor/contrib/find/test/findModel.test.ts +++ b/src/vs/editor/contrib/find/test/findModel.test.ts @@ -81,13 +81,13 @@ suite('FindModel', () => { } function assertFindState(editor: ICodeEditor, cursor: number[], highlighted: number[] | null, findDecorations: number[][]): void { - assert.deepEqual(fromRange(editor.getSelection()!), cursor, 'cursor'); + assert.deepStrictEqual(fromRange(editor.getSelection()!), cursor, 'cursor'); let expectedState = { highlighted: highlighted ? [highlighted] : [], findDecorations: findDecorations }; - assert.deepEqual(_getFindState(editor), expectedState, 'state'); + assert.deepStrictEqual(_getFindState(editor), expectedState, 'state'); } findTest('incremental find from beginning of file', (editor) => { @@ -245,7 +245,7 @@ suite('FindModel', () => { findState.change({ searchString: 'hello' }, false); let findModel = new FindModelBoundToEditorModel(editor, findState); - assert.equal(findState.matchesCount, 5); + assert.strictEqual(findState.matchesCount, 5); assertFindState( editor, [1, 1, 1, 1], @@ -275,7 +275,7 @@ suite('FindModel', () => { findState.change({ searchString: 'hello' }, false); let findModel = new FindModelBoundToEditorModel(editor, findState); - assert.equal(findState.matchesCount, 5); + assert.strictEqual(findState.matchesCount, 5); assertFindState( editor, [1, 1, 1, 1], @@ -290,7 +290,7 @@ suite('FindModel', () => { ); findState.change({ searchString: 'helloo' }, false); - assert.equal(findState.matchesCount, 0); + assert.strictEqual(findState.matchesCount, 0); assertFindState( editor, [1, 1, 1, 1], @@ -1306,7 +1306,7 @@ suite('FindModel', () => { [8, 14, 8, 19] ] ); - assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;'); findModel.replace(); assertFindState( @@ -1320,7 +1320,7 @@ suite('FindModel', () => { [8, 14, 8, 19] ] ); - assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;'); findModel.replace(); assertFindState( @@ -1333,7 +1333,7 @@ suite('FindModel', () => { [8, 14, 8, 19] ] ); - assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello world, hi!" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(6), ' cout << "hello world, hi!" << endl;'); findModel.replace(); assertFindState( @@ -1345,7 +1345,7 @@ suite('FindModel', () => { [8, 14, 8, 19] ] ); - assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;'); findModel.replace(); assertFindState( @@ -1356,7 +1356,7 @@ suite('FindModel', () => { [6, 14, 6, 19] ] ); - assert.equal(editor.getModel()!.getLineContent(8), ' cout << "hi world again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(8), ' cout << "hi world again" << endl;'); findModel.replace(); assertFindState( @@ -1365,7 +1365,7 @@ suite('FindModel', () => { null, [] ); - assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hi world, hi!" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(6), ' cout << "hi world, hi!" << endl;'); findModel.dispose(); findState.dispose(); @@ -1398,7 +1398,7 @@ suite('FindModel', () => { [11, 10, 11, 13] ] ); - assert.equal(editor.getModel()!.getLineContent(11), '// blablablaciao'); + assert.strictEqual(editor.getModel()!.getLineContent(11), '// blablablaciao'); findModel.replace(); assertFindState( @@ -1410,7 +1410,7 @@ suite('FindModel', () => { [11, 11, 11, 14] ] ); - assert.equal(editor.getModel()!.getLineContent(11), '// ciaoblablaciao'); + assert.strictEqual(editor.getModel()!.getLineContent(11), '// ciaoblablaciao'); findModel.replace(); assertFindState( @@ -1421,7 +1421,7 @@ suite('FindModel', () => { [11, 12, 11, 15] ] ); - assert.equal(editor.getModel()!.getLineContent(11), '// ciaociaoblaciao'); + assert.strictEqual(editor.getModel()!.getLineContent(11), '// ciaociaoblaciao'); findModel.replace(); assertFindState( @@ -1430,7 +1430,7 @@ suite('FindModel', () => { null, [] ); - assert.equal(editor.getModel()!.getLineContent(11), '// ciaociaociaociao'); + assert.strictEqual(editor.getModel()!.getLineContent(11), '// ciaociaociaociao'); findModel.dispose(); findState.dispose(); @@ -1467,7 +1467,7 @@ suite('FindModel', () => { [8, 14, 8, 19] ] ); - assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;'); findModel.replaceAll(); assertFindState( @@ -1476,9 +1476,9 @@ suite('FindModel', () => { null, [] ); - assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hi world, hi!" << endl;'); - assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;'); - assert.equal(editor.getModel()!.getLineContent(8), ' cout << "hi world again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(6), ' cout << "hi world, hi!" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(8), ' cout << "hi world again" << endl;'); findModel.dispose(); findState.dispose(); @@ -1517,10 +1517,10 @@ suite('FindModel', () => { [9, 1, 9, 3] ] ); - assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;'); - assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hello world again" << endl;'); - assert.equal(editor.getModel()!.getLineContent(8), ' cout << "Hello world again" << endl;'); - assert.equal(editor.getModel()!.getLineContent(9), ' cout << "helloworld again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(7), ' cout << "hello world again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(8), ' cout << "Hello world again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(9), ' cout << "helloworld again" << endl;'); findModel.dispose(); findState.dispose(); @@ -1549,7 +1549,7 @@ suite('FindModel', () => { null, [] ); - assert.equal(editor.getModel()!.getLineContent(11), '// ciaociaociaociao'); + assert.strictEqual(editor.getModel()!.getLineContent(11), '// ciaociaociaociao'); findModel.dispose(); findState.dispose(); @@ -1578,10 +1578,10 @@ suite('FindModel', () => { null, [] ); - assert.equal(editor.getModel()!.getLineContent(11), '// <'); - assert.equal(editor.getModel()!.getLineContent(12), '\t><'); - assert.equal(editor.getModel()!.getLineContent(13), '\t><'); - assert.equal(editor.getModel()!.getLineContent(14), '\t>ciao'); + assert.strictEqual(editor.getModel()!.getLineContent(11), '// <'); + assert.strictEqual(editor.getModel()!.getLineContent(12), '\t><'); + assert.strictEqual(editor.getModel()!.getLineContent(13), '\t><'); + assert.strictEqual(editor.getModel()!.getLineContent(14), '\t>ciao'); findModel.dispose(); findState.dispose(); @@ -1610,8 +1610,8 @@ suite('FindModel', () => { [] ); - assert.equal(editor.getModel()!.getLineContent(2), '#bar "cool.h"'); - assert.equal(editor.getModel()!.getLineContent(3), '#bar '); + assert.strictEqual(editor.getModel()!.getLineContent(2), '#bar "cool.h"'); + assert.strictEqual(editor.getModel()!.getLineContent(3), '#bar '); findModel.dispose(); findState.dispose(); @@ -1665,7 +1665,7 @@ suite('FindModel', () => { findModel.selectAllMatches(); - assert.deepEqual(editor!.getSelections()!.map(s => s.toString()), [ + assert.deepStrictEqual(editor!.getSelections()!.map(s => s.toString()), [ new Selection(6, 14, 6, 19), new Selection(6, 27, 6, 32), new Selection(7, 14, 7, 19), @@ -1709,14 +1709,14 @@ suite('FindModel', () => { findModel.selectAllMatches(); - assert.deepEqual(editor!.getSelections()!.map(s => s.toString()), [ + assert.deepStrictEqual(editor!.getSelections()!.map(s => s.toString()), [ new Selection(7, 14, 7, 19), new Selection(6, 14, 6, 19), new Selection(6, 27, 6, 32), new Selection(8, 14, 8, 19) ].map(s => s.toString())); - assert.deepEqual(editor!.getSelection()!.toString(), new Selection(7, 14, 7, 19).toString()); + assert.deepStrictEqual(editor!.getSelection()!.toString(), new Selection(7, 14, 7, 19).toString()); assertFindState( editor, @@ -1800,7 +1800,7 @@ suite('FindModel', () => { [8, 14, 8, 19] ] ); - assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;'); findModel.replace(); assertFindState( @@ -1812,7 +1812,7 @@ suite('FindModel', () => { [8, 14, 8, 19] ] ); - assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hi world, Hello!" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(6), ' cout << "hi world, Hello!" << endl;'); findModel.replace(); assertFindState( @@ -1823,7 +1823,7 @@ suite('FindModel', () => { [8, 14, 8, 19] ] ); - assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;'); findModel.replace(); assertFindState( @@ -1832,7 +1832,7 @@ suite('FindModel', () => { null, [] ); - assert.equal(editor.getModel()!.getLineContent(8), ' cout << "hi world again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(8), ' cout << "hi world again" << endl;'); findModel.dispose(); findState.dispose(); @@ -1871,7 +1871,7 @@ suite('FindModel', () => { ] ); - assert.equal(editor.getModel()!.getLineContent(8), ' cout << "Hello world again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(8), ' cout << "Hello world again" << endl;'); findModel.replace(); assertFindState( @@ -1883,7 +1883,7 @@ suite('FindModel', () => { [7, 14, 7, 19], ] ); - assert.equal(editor.getModel()!.getLineContent(8), ' cout << "hi world again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(8), ' cout << "hi world again" << endl;'); findModel.replace(); assertFindState( @@ -1894,7 +1894,7 @@ suite('FindModel', () => { [7, 14, 7, 19] ] ); - assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hi world, Hello!" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(6), ' cout << "hi world, Hello!" << endl;'); findModel.replace(); assertFindState( @@ -1903,7 +1903,7 @@ suite('FindModel', () => { null, [] ); - assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;'); findModel.dispose(); findState.dispose(); @@ -1927,9 +1927,9 @@ suite('FindModel', () => { findModel.replaceAll(); - assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hi world, Hello!" << endl;'); - assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;'); - assert.equal(editor.getModel()!.getLineContent(8), ' cout << "hi world again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(6), ' cout << "hi world, Hello!" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(8), ' cout << "hi world again" << endl;'); assertFindState( editor, @@ -1970,7 +1970,7 @@ suite('FindModel', () => { [8, 14, 8, 19] ] ); - assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;'); findModel.replace(); assertFindState( @@ -1982,7 +1982,7 @@ suite('FindModel', () => { [8, 14, 8, 19] ] ); - assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hilo world, Hello!" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(6), ' cout << "hilo world, Hello!" << endl;'); findModel.replace(); assertFindState( @@ -1993,7 +1993,7 @@ suite('FindModel', () => { [8, 14, 8, 19] ] ); - assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hilo world again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(7), ' cout << "hilo world again" << endl;'); findModel.replace(); assertFindState( @@ -2002,7 +2002,7 @@ suite('FindModel', () => { null, [] ); - assert.equal(editor.getModel()!.getLineContent(8), ' cout << "hilo world again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(8), ' cout << "hilo world again" << endl;'); findModel.dispose(); findState.dispose(); @@ -2027,10 +2027,10 @@ suite('FindModel', () => { findModel.replaceAll(); - assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello girl, Hello!" << endl;'); - assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hello girl again" << endl;'); - assert.equal(editor.getModel()!.getLineContent(8), ' cout << "Hello girl again" << endl;'); - assert.equal(editor.getModel()!.getLineContent(9), ' cout << "hellogirl again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(6), ' cout << "hello girl, Hello!" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(7), ' cout << "hello girl again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(8), ' cout << "Hello girl again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(9), ' cout << "hellogirl again" << endl;'); assertFindState( editor, @@ -2060,8 +2060,8 @@ suite('FindModel', () => { findModel.replaceAll(); - assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello girl, Hello!" << endl;'); - assert.equal(editor.getModel()!.getLineContent(8), ' cout << "Hello girl again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(6), ' cout << "hello girl, Hello!" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(8), ' cout << "Hello girl again" << endl;'); assertFindState( editor, @@ -2094,10 +2094,10 @@ suite('FindModel', () => { findModel.replaceAll(); - assert.equal(editor.getModel()!.getLineContent(6), ' cout << "goodbye world, Goodbye!" << endl;'); - assert.equal(editor.getModel()!.getLineContent(7), ' cout << "goodbye world again" << endl;'); - assert.equal(editor.getModel()!.getLineContent(8), ' cout << "Goodbye world again" << endl;'); - assert.equal(editor.getModel()!.getLineContent(9), ' cout << "goodbyeworld again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(6), ' cout << "goodbye world, Goodbye!" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(7), ' cout << "goodbye world again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(8), ' cout << "Goodbye world again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(9), ' cout << "goodbyeworld again" << endl;'); assertFindState( editor, @@ -2134,9 +2134,9 @@ suite('FindModel', () => { null, [] ); - assert.equal(editor.getModel()!.getLineContent(6), ' cout << " world, !" << endl;'); - assert.equal(editor.getModel()!.getLineContent(7), ' cout << " world again" << endl;'); - assert.equal(editor.getModel()!.getLineContent(8), ' cout << " world again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(6), ' cout << " world, !" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(7), ' cout << " world again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(8), ' cout << " world again" << endl;'); findModel.dispose(); findState.dispose(); @@ -2159,7 +2159,7 @@ suite('FindModel', () => { expectedText += 'a line' + i + '\n'; } expectedText += 'a '; - assert.equal(editor!.getModel()!.getValue(), expectedText); + assert.strictEqual(editor!.getModel()!.getValue(), expectedText); findModel.dispose(); findState.dispose(); @@ -2188,9 +2188,9 @@ suite('FindModel', () => { null, [] ); - assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hi world, Hello!" << endl;'); - assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;'); - assert.equal(editor.getModel()!.getLineContent(9), ' cout << "hiworld again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(6), ' cout << "hi world, Hello!" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;'); + assert.strictEqual(editor.getModel()!.getLineContent(9), ' cout << "hiworld again" << endl;'); findModel.dispose(); findState.dispose(); @@ -2219,78 +2219,78 @@ suite('FindModel', () => { findState.change({ searchString: 'hello', loop: false }, false); let findModel = new FindModelBoundToEditorModel(editor, findState); - assert.equal(findState.matchesCount, 5); + assert.strictEqual(findState.matchesCount, 5); // Test next operations - assert.equal(findState.matchesPosition, 0); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 0); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), true); findModel.moveToNextMatch(); - assert.equal(findState.matchesPosition, 1); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), false); + assert.strictEqual(findState.matchesPosition, 1); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), false); findModel.moveToNextMatch(); - assert.equal(findState.matchesPosition, 2); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 2); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), true); findModel.moveToNextMatch(); - assert.equal(findState.matchesPosition, 3); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 3); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), true); findModel.moveToNextMatch(); - assert.equal(findState.matchesPosition, 4); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 4); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), true); findModel.moveToNextMatch(); - assert.equal(findState.matchesPosition, 5); - assert.equal(findState.canNavigateForward(), false); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 5); + assert.strictEqual(findState.canNavigateForward(), false); + assert.strictEqual(findState.canNavigateBack(), true); findModel.moveToNextMatch(); - assert.equal(findState.matchesPosition, 5); - assert.equal(findState.canNavigateForward(), false); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 5); + assert.strictEqual(findState.canNavigateForward(), false); + assert.strictEqual(findState.canNavigateBack(), true); findModel.moveToNextMatch(); - assert.equal(findState.matchesPosition, 5); - assert.equal(findState.canNavigateForward(), false); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 5); + assert.strictEqual(findState.canNavigateForward(), false); + assert.strictEqual(findState.canNavigateBack(), true); // Test previous operations findModel.moveToPrevMatch(); - assert.equal(findState.matchesPosition, 4); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 4); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), true); findModel.moveToPrevMatch(); - assert.equal(findState.matchesPosition, 3); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 3); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), true); findModel.moveToPrevMatch(); - assert.equal(findState.matchesPosition, 2); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 2); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), true); findModel.moveToPrevMatch(); - assert.equal(findState.matchesPosition, 1); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), false); + assert.strictEqual(findState.matchesPosition, 1); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), false); findModel.moveToPrevMatch(); - assert.equal(findState.matchesPosition, 1); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), false); + assert.strictEqual(findState.matchesPosition, 1); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), false); findModel.moveToPrevMatch(); - assert.equal(findState.matchesPosition, 1); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), false); + assert.strictEqual(findState.matchesPosition, 1); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), false); }); @@ -2299,78 +2299,78 @@ suite('FindModel', () => { findState.change({ searchString: 'hello' }, false); let findModel = new FindModelBoundToEditorModel(editor, findState); - assert.equal(findState.matchesCount, 5); + assert.strictEqual(findState.matchesCount, 5); // Test next operations - assert.equal(findState.matchesPosition, 0); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 0); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), true); findModel.moveToNextMatch(); - assert.equal(findState.matchesPosition, 1); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 1); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), true); findModel.moveToNextMatch(); - assert.equal(findState.matchesPosition, 2); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 2); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), true); findModel.moveToNextMatch(); - assert.equal(findState.matchesPosition, 3); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 3); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), true); findModel.moveToNextMatch(); - assert.equal(findState.matchesPosition, 4); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 4); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), true); findModel.moveToNextMatch(); - assert.equal(findState.matchesPosition, 5); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 5); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), true); findModel.moveToNextMatch(); - assert.equal(findState.matchesPosition, 1); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 1); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), true); findModel.moveToNextMatch(); - assert.equal(findState.matchesPosition, 2); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 2); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), true); // Test previous operations findModel.moveToPrevMatch(); - assert.equal(findState.matchesPosition, 1); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 1); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), true); findModel.moveToPrevMatch(); - assert.equal(findState.matchesPosition, 5); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 5); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), true); findModel.moveToPrevMatch(); - assert.equal(findState.matchesPosition, 4); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 4); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), true); findModel.moveToPrevMatch(); - assert.equal(findState.matchesPosition, 3); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 3); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), true); findModel.moveToPrevMatch(); - assert.equal(findState.matchesPosition, 2); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 2); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), true); findModel.moveToPrevMatch(); - assert.equal(findState.matchesPosition, 1); - assert.equal(findState.canNavigateForward(), true); - assert.equal(findState.canNavigateBack(), true); + assert.strictEqual(findState.matchesPosition, 1); + assert.strictEqual(findState.canNavigateForward(), true); + assert.strictEqual(findState.canNavigateBack(), true); }); diff --git a/src/vs/editor/contrib/find/test/replacePattern.test.ts b/src/vs/editor/contrib/find/test/replacePattern.test.ts index 907292fd7..d94918640 100644 --- a/src/vs/editor/contrib/find/test/replacePattern.test.ts +++ b/src/vs/editor/contrib/find/test/replacePattern.test.ts @@ -13,7 +13,7 @@ suite('Replace Pattern test', () => { let testParse = (input: string, expectedPieces: ReplacePiece[]) => { let actual = parseReplaceString(input); let expected = new ReplacePattern(expectedPieces); - assert.deepEqual(actual, expected, 'Parsing ' + input); + assert.deepStrictEqual(actual, expected, 'Parsing ' + input); }; // no backslash => no treatment @@ -73,14 +73,14 @@ suite('Replace Pattern test', () => { let testParse = (input: string, expectedPieces: ReplacePiece[]) => { let actual = parseReplaceString(input); let expected = new ReplacePattern(expectedPieces); - assert.deepEqual(actual, expected, 'Parsing ' + input); + assert.deepStrictEqual(actual, expected, 'Parsing ' + input); }; function assertReplace(target: string, search: RegExp, replaceString: string, expected: string): void { let replacePattern = parseReplaceString(replaceString); let m = search.exec(target); let actual = replacePattern.buildReplaceString(m); - assert.equal(actual, expected, `${target}.replace(${search}, ${replaceString}) === ${expected}`); + assert.strictEqual(actual, expected, `${target}.replace(${search}, ${replaceString}) === ${expected}`); } // \U, \u => uppercase \L, \l => lowercase \E => cancel @@ -107,7 +107,7 @@ suite('Replace Pattern test', () => { let m = search.exec(target); let actual = replacePattern.buildReplaceString(m); - assert.deepEqual(actual, expected, `${target}.replace(${search}, ${replaceString})`); + assert.deepStrictEqual(actual, expected, `${target}.replace(${search}, ${replaceString})`); }; testJSReplaceSemantics('hi', /hi/, 'hello', 'hi'.replace(/hi/, 'hello')); @@ -136,7 +136,7 @@ suite('Replace Pattern test', () => { let m = search.exec(target); let actual = replacePattern.buildReplaceString(m); - assert.equal(actual, expected, `${target}.replace(${search}, ${replaceString}) === ${expected}`); + assert.strictEqual(actual, expected, `${target}.replace(${search}, ${replaceString}) === ${expected}`); } assertReplace('bla', /bla/, 'hello', 'hello'); @@ -162,7 +162,7 @@ suite('Replace Pattern test', () => { let m = search.exec(target); let actual = replacePattern.buildReplaceString(m); - assert.equal(actual, expected, `${target}.replace(${search}, ${replaceString}) === ${expected}`); + assert.strictEqual(actual, expected, `${target}.replace(${search}, ${replaceString}) === ${expected}`); } assertReplace('this is a bla text', /bla/, 'hello', 'hello'); assertReplace('this is a bla text', /this(?=.*bla)/, 'that', 'that'); @@ -184,14 +184,14 @@ suite('Replace Pattern test', () => { let replacePattern = parseReplaceString('a{$1}'); let matches = /a(z)?/.exec('abcd'); let actual = replacePattern.buildReplaceString(matches); - assert.equal(actual, 'a{}'); + assert.strictEqual(actual, 'a{}'); }); test('buildReplaceStringWithCasePreserved test', () => { function assertReplace(target: string[], replaceString: string, expected: string): void { let actual: string = ''; actual = buildReplaceStringWithCasePreserved(target, replaceString); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); } assertReplace(['abc'], 'Def', 'def'); @@ -219,7 +219,7 @@ suite('Replace Pattern test', () => { function assertReplace(target: string[], replaceString: string, expected: string): void { let replacePattern = parseReplaceString(replaceString); let actual = replacePattern.buildReplaceString(target, true); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); } assertReplace(['abc'], 'Def', 'def'); diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index 10a503f67..8096baa17 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -16,13 +16,12 @@ import { Color } from 'vs/base/common/color'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { ScrollType } from 'vs/editor/common/editorCommon'; -import { getBaseLabel, getPathLabel } from 'vs/base/common/labels'; +import { getBaseLabel } from 'vs/base/common/labels'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { Event, Emitter } from 'vs/base/common/event'; import { PeekViewWidget, peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/peekView/peekView'; import { basename } from 'vs/base/common/resources'; import { IAction } from 'vs/base/common/actions'; -import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -31,6 +30,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { splitLines } from 'vs/base/common/strings'; +import { ILabelService } from 'vs/platform/label/common/label'; class MessageWidget { @@ -51,6 +51,7 @@ class MessageWidget { editor: ICodeEditor, onRelatedInformation: (related: IRelatedInformation) => void, private readonly _openerService: IOpenerService, + private readonly _labelService: ILabelService ) { this._editor = editor; @@ -169,7 +170,7 @@ class MessageWidget { let relatedResource = document.createElement('a'); relatedResource.classList.add('filename'); relatedResource.innerText = `${getBaseLabel(related.resource)}(${related.startLineNumber}, ${related.startColumn}): `; - relatedResource.title = getPathLabel(related.resource, undefined); + relatedResource.title = this._labelService.getUriLabel(related.resource); this._relatedDiagnostics.set(relatedResource, related); let relatedMessage = document.createElement('span'); @@ -248,7 +249,8 @@ export class MarkerNavigationWidget extends PeekViewWidget { @IOpenerService private readonly _openerService: IOpenerService, @IMenuService private readonly _menuService: IMenuService, @IInstantiationService instantiationService: IInstantiationService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @ILabelService private readonly _labelService: ILabelService ) { super(editor, { showArrow: true, showFrame: true, isAccessible: true }, instantiationService); this._severity = MarkerSeverity.Warning; @@ -310,13 +312,6 @@ export class MarkerNavigationWidget extends PeekViewWidget { this._icon = dom.append(container, dom.$('')); } - protected _getActionBarOptions(): IActionBarOptions { - return { - ...super._getActionBarOptions(), - orientation: ActionsOrientation.HORIZONTAL - }; - } - protected _fillBody(container: HTMLElement): void { this._parentContainer = container; container.classList.add('marker-widget'); @@ -326,7 +321,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { this._container = document.createElement('div'); container.appendChild(this._container); - this._message = new MessageWidget(this._container, this.editor, related => this._onDidSelectRelatedInformation.fire(related), this._openerService); + this._message = new MessageWidget(this._container, this.editor, related => this._onDidSelectRelatedInformation.fire(related), this._openerService, this._labelService); this._disposables.add(this._message); } diff --git a/src/vs/editor/contrib/gotoSymbol/referencesModel.ts b/src/vs/editor/contrib/gotoSymbol/referencesModel.ts index 334928f3c..63f6b9527 100644 --- a/src/vs/editor/contrib/gotoSymbol/referencesModel.ts +++ b/src/vs/editor/contrib/gotoSymbol/referencesModel.ts @@ -51,7 +51,7 @@ export class OneReference { ); } else { return localize( - 'aria.oneReference.preview', "symbol in {0} on line {1} at column {2}, {3}", + { key: 'aria.oneReference.preview', comment: ['Placeholders are: 0: filename, 1:line number, 2: column number, 3: preview snippet of source code'] }, "symbol in {0} on line {1} at column {2}, {3}", basename(this.uri), this.range.startLineNumber, this.range.startColumn, preview.value ); } diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index c57fbadbb..071253cbd 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IEmptyContentData } from 'vs/editor/browser/controller/mouseTarget'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; @@ -22,11 +22,10 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { IOpenerService } from 'vs/platform/opener/common/opener'; import { editorHoverBackground, editorHoverBorder, editorHoverHighlight, textCodeBlockBackground, textLinkForeground, editorHoverStatusBarBackground, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { GotoDefinitionAtPositionEditorContribution } from 'vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class ModesHoverController implements IEditorContribution { @@ -35,22 +34,8 @@ export class ModesHoverController implements IEditorContribution { private readonly _toUnhook = new DisposableStore(); private readonly _didChangeConfigurationHandler: IDisposable; - private readonly _contentWidget = new MutableDisposable(); - private readonly _glyphWidget = new MutableDisposable(); - - get contentWidget(): ModesContentHoverWidget { - if (!this._contentWidget.value) { - this._createHoverWidgets(); - } - return this._contentWidget.value!; - } - - get glyphWidget(): ModesGlyphHoverWidget { - if (!this._glyphWidget.value) { - this._createHoverWidgets(); - } - return this._glyphWidget.value!; - } + private _contentWidget: ModesContentHoverWidget | null; + private _glyphWidget: ModesGlyphHoverWidget | null; private _isMouseDown: boolean; private _hoverClicked: boolean; @@ -64,15 +49,16 @@ export class ModesHoverController implements IEditorContribution { } constructor(private readonly _editor: ICodeEditor, + @IInstantiationService private readonly _instantiationService: IInstantiationService, @IOpenerService private readonly _openerService: IOpenerService, @IModeService private readonly _modeService: IModeService, - @IMarkerDecorationsService private readonly _markerDecorationsService: IMarkerDecorationsService, - @IKeybindingService private readonly _keybindingService: IKeybindingService, @IThemeService private readonly _themeService: IThemeService, @IContextKeyService _contextKeyService: IContextKeyService ) { this._isMouseDown = false; this._hoverClicked = false; + this._contentWidget = null; + this._glyphWidget = null; this._hookEvents(); @@ -113,8 +99,8 @@ export class ModesHoverController implements IEditorContribution { } private _onModelDecorationsChanged(): void { - this.contentWidget.onModelDecorationsChanged(); - this.glyphWidget.onModelDecorationsChanged(); + this._contentWidget?.onModelDecorationsChanged(); + this._glyphWidget?.onModelDecorationsChanged(); } private _onEditorScrollChanged(e: IScrollEvent): void { @@ -153,7 +139,7 @@ export class ModesHoverController implements IEditorContribution { private _onEditorMouseMove(mouseEvent: IEditorMouseEvent): void { let targetType = mouseEvent.target.type; - if (this._isMouseDown && this._hoverClicked && this.contentWidget.isColorPickerVisible()) { + if (this._isMouseDown && this._hoverClicked) { return; } @@ -162,10 +148,14 @@ export class ModesHoverController implements IEditorContribution { return; } + if (this._isHoverSticky && !mouseEvent.event.browserEvent.view?.getSelection()?.isCollapsed) { + // selected text within content hover widget + return; + } if ( !this._isHoverSticky && targetType === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === ModesContentHoverWidget.ID - && this._contentWidget.value?.isColorPickerVisible() + && this._contentWidget?.isColorPickerVisible() ) { // though the hover is not sticky, the color picker needs to. return; @@ -186,26 +176,31 @@ export class ModesHoverController implements IEditorContribution { } if (targetType === MouseTargetType.CONTENT_TEXT) { - this.glyphWidget.hide(); + this._glyphWidget?.hide(); if (this._isHoverEnabled && mouseEvent.target.range) { // TODO@rebornix. This should be removed if we move Color Picker out of Hover component. // Check if mouse is hovering on color decorator const hoverOnColorDecorator = [...mouseEvent.target.element?.classList.values() || []].find(className => className.startsWith('ced-colorBox')) && mouseEvent.target.range.endColumn - mouseEvent.target.range.startColumn === 1; - if (hoverOnColorDecorator) { - // shift the mouse focus by one as color decorator is a `before` decoration of next character. - this.contentWidget.startShowingAt(new Range(mouseEvent.target.range.startLineNumber, mouseEvent.target.range.startColumn + 1, mouseEvent.target.range.endLineNumber, mouseEvent.target.range.endColumn + 1), HoverStartMode.Delayed, false); - } else { - this.contentWidget.startShowingAt(mouseEvent.target.range, HoverStartMode.Delayed, false); + const showAtRange = ( + hoverOnColorDecorator // shift the mouse focus by one as color decorator is a `before` decoration of next character. + ? new Range(mouseEvent.target.range.startLineNumber, mouseEvent.target.range.startColumn + 1, mouseEvent.target.range.endLineNumber, mouseEvent.target.range.endColumn + 1) + : mouseEvent.target.range + ); + if (!this._contentWidget) { + this._contentWidget = new ModesContentHoverWidget(this._editor, this._hoverVisibleKey, this._instantiationService, this._themeService); } - + this._contentWidget.startShowingAt(showAtRange, HoverStartMode.Delayed, false); } } else if (targetType === MouseTargetType.GUTTER_GLYPH_MARGIN) { - this.contentWidget.hide(); + this._contentWidget?.hide(); if (this._isHoverEnabled && mouseEvent.target.position) { - this.glyphWidget.startShowingAt(mouseEvent.target.position.lineNumber); + if (!this._glyphWidget) { + this._glyphWidget = new ModesGlyphHoverWidget(this._editor, this._modeService, this._openerService); + } + this._glyphWidget.startShowingAt(mouseEvent.target.position.lineNumber); } } else { this._hideWidgets(); @@ -220,29 +215,32 @@ export class ModesHoverController implements IEditorContribution { } private _hideWidgets(): void { - if (!this._glyphWidget.value || !this._contentWidget.value || (this._isMouseDown && this._hoverClicked && this._contentWidget.value.isColorPickerVisible())) { + if ((this._isMouseDown && this._hoverClicked && this._contentWidget?.isColorPickerVisible())) { return; } - this._glyphWidget.value.hide(); - this._contentWidget.value.hide(); + this._hoverClicked = false; + this._glyphWidget?.hide(); + this._contentWidget?.hide(); } - private _createHoverWidgets() { - this._contentWidget.value = new ModesContentHoverWidget(this._editor, this._hoverVisibleKey, this._markerDecorationsService, this._keybindingService, this._themeService, this._modeService, this._openerService); - this._glyphWidget.value = new ModesGlyphHoverWidget(this._editor, this._modeService, this._openerService); + public isColorPickerVisible(): boolean { + return this._contentWidget?.isColorPickerVisible() || false; } public showContentHover(range: Range, mode: HoverStartMode, focus: boolean): void { - this.contentWidget.startShowingAt(range, mode, focus); + if (!this._contentWidget) { + this._contentWidget = new ModesContentHoverWidget(this._editor, this._hoverVisibleKey, this._instantiationService, this._themeService); + } + this._contentWidget.startShowingAt(range, mode, focus); } public dispose(): void { this._unhookEvents(); this._toUnhook.dispose(); this._didChangeConfigurationHandler.dispose(); - this._glyphWidget.dispose(); - this._contentWidget.dispose(); + this._glyphWidget?.dispose(); + this._contentWidget?.dispose(); } } diff --git a/src/vs/editor/contrib/hover/hoverWidgets.ts b/src/vs/editor/contrib/hover/hoverWidgets.ts index cb8c02807..ecc374f22 100644 --- a/src/vs/editor/contrib/hover/hoverWidgets.ts +++ b/src/vs/editor/contrib/hover/hoverWidgets.ts @@ -3,168 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Widget } from 'vs/base/browser/ui/widget'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import { IContentWidget, ICodeEditor, IContentWidgetPosition, ContentWidgetPositionPreference, IOverlayWidget, IOverlayWidgetPosition } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; -import { Position } from 'vs/editor/common/core/position'; -import { Range } from 'vs/editor/common/core/range'; -import { renderHoverAction, HoverWidget } from 'vs/base/browser/ui/hover/hoverWidget'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; - -export class ContentHoverWidget extends Widget implements IContentWidget { - - protected readonly _hover: HoverWidget; - private readonly _id: string; - protected _editor: ICodeEditor; - private _isVisible: boolean; - protected _showAtPosition: Position | null; - protected _showAtRange: Range | null; - private _stoleFocus: boolean; - - // Editor.IContentWidget.allowEditorOverflow - public allowEditorOverflow = true; - - protected get isVisible(): boolean { - return this._isVisible; - } - - protected set isVisible(value: boolean) { - this._isVisible = value; - this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible); - } - - constructor( - id: string, - editor: ICodeEditor, - private readonly _hoverVisibleKey: IContextKey, - private readonly _keybindingService: IKeybindingService - ) { - super(); - - this._hover = this._register(new HoverWidget()); - this._id = id; - this._editor = editor; - this._isVisible = false; - this._stoleFocus = false; - - this.onkeydown(this._hover.containerDomNode, (e: IKeyboardEvent) => { - if (e.equals(KeyCode.Escape)) { - this.hide(); - } - }); - - this._register(this._editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { - if (e.hasChanged(EditorOption.fontInfo)) { - this.updateFont(); - } - })); - - this._editor.onDidLayoutChange(e => this.layout()); - - this.layout(); - this._editor.addContentWidget(this); - this._showAtPosition = null; - this._showAtRange = null; - this._stoleFocus = false; - } - - public getId(): string { - return this._id; - } - - public getDomNode(): HTMLElement { - return this._hover.containerDomNode; - } - - public showAt(position: Position, range: Range | null, focus: boolean): void { - // Position has changed - this._showAtPosition = position; - this._showAtRange = range; - this._hoverVisibleKey.set(true); - this.isVisible = true; - - this._editor.layoutContentWidget(this); - // Simply force a synchronous render on the editor - // such that the widget does not really render with left = '0px' - this._editor.render(); - this._stoleFocus = focus; - if (focus) { - this._hover.containerDomNode.focus(); - } - } - - public hide(): void { - if (!this.isVisible) { - return; - } - - setTimeout(() => { - // Give commands a chance to see the key - if (!this.isVisible) { - this._hoverVisibleKey.set(false); - } - }, 0); - this.isVisible = false; - - this._editor.layoutContentWidget(this); - if (this._stoleFocus) { - this._editor.focus(); - } - } - - public getPosition(): IContentWidgetPosition | null { - if (this.isVisible) { - return { - position: this._showAtPosition, - range: this._showAtRange, - preference: [ - ContentWidgetPositionPreference.ABOVE, - ContentWidgetPositionPreference.BELOW - ] - }; - } - return null; - } - - public dispose(): void { - this._editor.removeContentWidget(this); - super.dispose(); - } - - private updateFont(): void { - const codeClasses: HTMLElement[] = Array.prototype.slice.call(this._hover.contentsDomNode.getElementsByClassName('code')); - codeClasses.forEach(node => this._editor.applyFontInfo(node)); - } - - protected updateContents(node: Node): void { - this._hover.contentsDomNode.textContent = ''; - this._hover.contentsDomNode.appendChild(node); - this.updateFont(); - - this._editor.layoutContentWidget(this); - this._hover.onContentsChanged(); - } - - protected _renderAction(parent: HTMLElement, actionOptions: { label: string, iconClass?: string, run: (target: HTMLElement) => void, commandId: string }): IDisposable { - const keybinding = this._keybindingService.lookupKeybinding(actionOptions.commandId); - const keybindingLabel = keybinding ? keybinding.getLabel() : null; - return renderHoverAction(parent, actionOptions, keybindingLabel); - } - - private layout(): void { - const height = Math.max(this._editor.getLayoutInfo().height / 4, 250); - const { fontSize, lineHeight } = this._editor.getOption(EditorOption.fontInfo); - - this._hover.contentsDomNode.style.fontSize = `${fontSize}px`; - this._hover.contentsDomNode.style.lineHeight = `${lineHeight}px`; - this._hover.contentsDomNode.style.maxHeight = `${height}px`; - this._hover.contentsDomNode.style.maxWidth = `${Math.max(this._editor.getLayoutInfo().width * 0.66, 500)}px`; - } -} export class GlyphHoverWidget extends Widget implements IOverlayWidget { diff --git a/src/vs/editor/contrib/hover/markdownHoverParticipant.ts b/src/vs/editor/contrib/hover/markdownHoverParticipant.ts new file mode 100644 index 000000000..ec4442dbd --- /dev/null +++ b/src/vs/editor/contrib/hover/markdownHoverParticipant.ts @@ -0,0 +1,126 @@ +/*--------------------------------------------------------------------------------------------- + * 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 * as dom from 'vs/base/browser/dom'; +import { IMarkdownString, MarkdownString, isEmptyMarkdownString, markedStringsEquals } from 'vs/base/common/htmlContent'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { Range } from 'vs/editor/common/core/range'; +import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; +import { asArray } from 'vs/base/common/arrays'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { IModelDecoration } from 'vs/editor/common/model'; +import { IEditorHover, IEditorHoverParticipant, IHoverPart } from 'vs/editor/contrib/hover/modesContentHover'; +import { HoverProviderRegistry } from 'vs/editor/common/modes'; +import { getHover } from 'vs/editor/contrib/hover/getHover'; +import { Position } from 'vs/editor/common/core/position'; +import { CancellationToken } from 'vs/base/common/cancellation'; + +const $ = dom.$; + +export class MarkdownHover implements IHoverPart { + + constructor( + public readonly range: Range, + public readonly contents: IMarkdownString[] + ) { } + + public equals(other: IHoverPart): boolean { + if (other instanceof MarkdownHover) { + return markedStringsEquals(this.contents, other.contents); + } + return false; + } +} + +export class MarkdownHoverParticipant implements IEditorHoverParticipant { + + constructor( + private readonly _editor: ICodeEditor, + private readonly _hover: IEditorHover, + @IModeService private readonly _modeService: IModeService, + @IOpenerService private readonly _openerService: IOpenerService, + ) { } + + public createLoadingMessage(range: Range): MarkdownHover { + return new MarkdownHover(range, [new MarkdownString().appendText(nls.localize('modesContentHover.loading', "Loading..."))]); + } + + public computeSync(hoverRange: Range, lineDecorations: IModelDecoration[]): MarkdownHover[] { + if (!this._editor.hasModel()) { + return []; + } + + const model = this._editor.getModel(); + const lineNumber = hoverRange.startLineNumber; + const maxColumn = model.getLineMaxColumn(lineNumber); + const result: MarkdownHover[] = []; + for (const d of lineDecorations) { + const startColumn = (d.range.startLineNumber === lineNumber) ? d.range.startColumn : 1; + const endColumn = (d.range.endLineNumber === lineNumber) ? d.range.endColumn : maxColumn; + + const hoverMessage = d.options.hoverMessage; + if (!hoverMessage || isEmptyMarkdownString(hoverMessage)) { + continue; + } + + const range = new Range(hoverRange.startLineNumber, startColumn, hoverRange.startLineNumber, endColumn); + result.push(new MarkdownHover(range, asArray(hoverMessage))); + } + + return result; + } + + public async computeAsync(range: Range, token: CancellationToken): Promise { + if (!this._editor.hasModel() || !range) { + return Promise.resolve([]); + } + + const model = this._editor.getModel(); + + if (!HoverProviderRegistry.has(model)) { + return Promise.resolve([]); + } + + const hovers = await getHover(model, new Position( + range.startLineNumber, + range.startColumn + ), token); + + const result: MarkdownHover[] = []; + for (const hover of hovers) { + if (isEmptyMarkdownString(hover.contents)) { + continue; + } + const rng = hover.range ? Range.lift(hover.range) : range; + result.push(new MarkdownHover(rng, hover.contents)); + } + return result; + } + + public renderHoverParts(hoverParts: MarkdownHover[], fragment: DocumentFragment): IDisposable { + const disposables = new DisposableStore(); + for (const hoverPart of hoverParts) { + for (const contents of hoverPart.contents) { + if (isEmptyMarkdownString(contents)) { + continue; + } + const markdownHoverElement = $('div.hover-row.markdown-hover'); + const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents')); + const renderer = disposables.add(new MarkdownRenderer({ editor: this._editor }, this._modeService, this._openerService)); + disposables.add(renderer.onDidRenderAsync(() => { + hoverContentsElement.className = 'hover-contents code-hover-contents'; + this._hover.onContentsChanged(); + })); + const renderedContents = disposables.add(renderer.render(contents)); + hoverContentsElement.appendChild(renderedContents.element); + fragment.appendChild(markdownHoverElement); + } + } + return disposables; + } +} diff --git a/src/vs/editor/contrib/hover/markerHoverParticipant.ts b/src/vs/editor/contrib/hover/markerHoverParticipant.ts new file mode 100644 index 000000000..ad739c892 --- /dev/null +++ b/src/vs/editor/contrib/hover/markerHoverParticipant.ts @@ -0,0 +1,257 @@ +/*--------------------------------------------------------------------------------------------- + * 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 * as dom from 'vs/base/browser/dom'; +import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { Range } from 'vs/editor/common/core/range'; +import { CodeActionTriggerType } from 'vs/editor/common/modes'; +import { isNonEmptyArray } from 'vs/base/common/arrays'; +import { IMarker, IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; +import { basename } from 'vs/base/common/resources'; +import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { MarkerController, NextMarkerAction } from 'vs/editor/contrib/gotoError/gotoError'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { CancelablePromise, createCancelablePromise, disposableTimeout } from 'vs/base/common/async'; +import { getCodeActions, CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; +import { QuickFixAction, QuickFixController } from 'vs/editor/contrib/codeAction/codeActionCommands'; +import { CodeActionKind, CodeActionTrigger } from 'vs/editor/contrib/codeAction/types'; +import { IModelDecoration } from 'vs/editor/common/model'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { Progress } from 'vs/platform/progress/common/progress'; +import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { renderHoverAction } from 'vs/base/browser/ui/hover/hoverWidget'; +import { IEditorHover, IEditorHoverParticipant, IHoverPart } from 'vs/editor/contrib/hover/modesContentHover'; + +const $ = dom.$; + +export class MarkerHover implements IHoverPart { + + constructor( + public readonly range: Range, + public readonly marker: IMarker, + ) { } + + public equals(other: IHoverPart): boolean { + if (other instanceof MarkerHover) { + return IMarkerData.makeKey(this.marker) === IMarkerData.makeKey(other.marker); + } + return false; + } +} + +const markerCodeActionTrigger: CodeActionTrigger = { + type: CodeActionTriggerType.Manual, + filter: { include: CodeActionKind.QuickFix } +}; + +export class MarkerHoverParticipant implements IEditorHoverParticipant { + + private recentMarkerCodeActionsInfo: { marker: IMarker, hasCodeActions: boolean } | undefined = undefined; + + constructor( + private readonly _editor: ICodeEditor, + private readonly _hover: IEditorHover, + @IMarkerDecorationsService private readonly _markerDecorationsService: IMarkerDecorationsService, + @IKeybindingService private readonly _keybindingService: IKeybindingService, + @IOpenerService private readonly _openerService: IOpenerService, + ) { } + + public computeSync(hoverRange: Range, lineDecorations: IModelDecoration[]): MarkerHover[] { + if (!this._editor.hasModel()) { + return []; + } + + const model = this._editor.getModel(); + const lineNumber = hoverRange.startLineNumber; + const maxColumn = model.getLineMaxColumn(lineNumber); + const result: MarkerHover[] = []; + for (const d of lineDecorations) { + const startColumn = (d.range.startLineNumber === lineNumber) ? d.range.startColumn : 1; + const endColumn = (d.range.endLineNumber === lineNumber) ? d.range.endColumn : maxColumn; + + const marker = this._markerDecorationsService.getMarker(model.uri, d); + if (!marker) { + continue; + } + + const range = new Range(hoverRange.startLineNumber, startColumn, hoverRange.startLineNumber, endColumn); + result.push(new MarkerHover(range, marker)); + } + + return result; + } + + public renderHoverParts(hoverParts: MarkerHover[], fragment: DocumentFragment): IDisposable { + if (!hoverParts.length) { + return Disposable.None; + } + const disposables = new DisposableStore(); + hoverParts.forEach(msg => fragment.appendChild(this.renderMarkerHover(msg, disposables))); + const markerHoverForStatusbar = hoverParts.length === 1 ? hoverParts[0] : hoverParts.sort((a, b) => MarkerSeverity.compare(a.marker.severity, b.marker.severity))[0]; + fragment.appendChild(this.renderMarkerStatusbar(markerHoverForStatusbar, disposables)); + return disposables; + } + + private renderMarkerHover(markerHover: MarkerHover, disposables: DisposableStore): HTMLElement { + const hoverElement = $('div.hover-row'); + const markerElement = dom.append(hoverElement, $('div.marker.hover-contents')); + const { source, message, code, relatedInformation } = markerHover.marker; + + this._editor.applyFontInfo(markerElement); + const messageElement = dom.append(markerElement, $('span')); + messageElement.style.whiteSpace = 'pre-wrap'; + messageElement.innerText = message; + + if (source || code) { + // Code has link + if (code && typeof code !== 'string') { + const sourceAndCodeElement = $('span'); + if (source) { + const sourceElement = dom.append(sourceAndCodeElement, $('span')); + sourceElement.innerText = source; + } + const codeLink = dom.append(sourceAndCodeElement, $('a.code-link')); + codeLink.setAttribute('href', code.target.toString()); + + disposables.add(dom.addDisposableListener(codeLink, 'click', (e) => { + this._openerService.open(code.target); + e.preventDefault(); + e.stopPropagation(); + })); + + const codeElement = dom.append(codeLink, $('span')); + codeElement.innerText = code.value; + + const detailsElement = dom.append(markerElement, sourceAndCodeElement); + detailsElement.style.opacity = '0.6'; + detailsElement.style.paddingLeft = '6px'; + } else { + const detailsElement = dom.append(markerElement, $('span')); + detailsElement.style.opacity = '0.6'; + detailsElement.style.paddingLeft = '6px'; + detailsElement.innerText = source && code ? `${source}(${code})` : source ? source : `(${code})`; + } + } + + if (isNonEmptyArray(relatedInformation)) { + for (const { message, resource, startLineNumber, startColumn } of relatedInformation) { + const relatedInfoContainer = dom.append(markerElement, $('div')); + relatedInfoContainer.style.marginTop = '8px'; + const a = dom.append(relatedInfoContainer, $('a')); + a.innerText = `${basename(resource)}(${startLineNumber}, ${startColumn}): `; + a.style.cursor = 'pointer'; + disposables.add(dom.addDisposableListener(a, 'click', (e) => { + e.stopPropagation(); + e.preventDefault(); + if (this._openerService) { + this._openerService.open(resource, { + fromUserGesture: true, + editorOptions: { selection: { startLineNumber, startColumn } } + }).catch(onUnexpectedError); + } + })); + const messageElement = dom.append(relatedInfoContainer, $('span')); + messageElement.innerText = message; + this._editor.applyFontInfo(messageElement); + } + } + + return hoverElement; + } + + private renderMarkerStatusbar(markerHover: MarkerHover, disposables: DisposableStore): HTMLElement { + const hoverElement = $('div.hover-row.status-bar'); + const actionsElement = dom.append(hoverElement, $('div.actions')); + if (markerHover.marker.severity === MarkerSeverity.Error || markerHover.marker.severity === MarkerSeverity.Warning || markerHover.marker.severity === MarkerSeverity.Info) { + disposables.add(this.renderAction(actionsElement, { + label: nls.localize('peek problem', "Peek Problem"), + commandId: NextMarkerAction.ID, + run: () => { + this._hover.hide(); + MarkerController.get(this._editor).showAtMarker(markerHover.marker); + this._editor.focus(); + } + })); + } + + if (!this._editor.getOption(EditorOption.readOnly)) { + const quickfixPlaceholderElement = dom.append(actionsElement, $('div')); + if (this.recentMarkerCodeActionsInfo) { + if (IMarkerData.makeKey(this.recentMarkerCodeActionsInfo.marker) === IMarkerData.makeKey(markerHover.marker)) { + if (!this.recentMarkerCodeActionsInfo.hasCodeActions) { + quickfixPlaceholderElement.textContent = nls.localize('noQuickFixes', "No quick fixes available"); + } + } else { + this.recentMarkerCodeActionsInfo = undefined; + } + } + const updatePlaceholderDisposable = this.recentMarkerCodeActionsInfo && !this.recentMarkerCodeActionsInfo.hasCodeActions ? Disposable.None : disposables.add(disposableTimeout(() => quickfixPlaceholderElement.textContent = nls.localize('checkingForQuickFixes', "Checking for quick fixes..."), 200)); + if (!quickfixPlaceholderElement.textContent) { + // Have some content in here to avoid flickering + quickfixPlaceholderElement.textContent = String.fromCharCode(0xA0); //   + } + const codeActionsPromise = this.getCodeActions(markerHover.marker); + disposables.add(toDisposable(() => codeActionsPromise.cancel())); + codeActionsPromise.then(actions => { + updatePlaceholderDisposable.dispose(); + this.recentMarkerCodeActionsInfo = { marker: markerHover.marker, hasCodeActions: actions.validActions.length > 0 }; + + if (!this.recentMarkerCodeActionsInfo.hasCodeActions) { + actions.dispose(); + quickfixPlaceholderElement.textContent = nls.localize('noQuickFixes', "No quick fixes available"); + return; + } + quickfixPlaceholderElement.style.display = 'none'; + + let showing = false; + disposables.add(toDisposable(() => { + if (!showing) { + actions.dispose(); + } + })); + + disposables.add(this.renderAction(actionsElement, { + label: nls.localize('quick fixes', "Quick Fix..."), + commandId: QuickFixAction.Id, + run: (target) => { + showing = true; + const controller = QuickFixController.get(this._editor); + const elementPosition = dom.getDomNodePagePosition(target); + // Hide the hover pre-emptively, otherwise the editor can close the code actions + // context menu as well when using keyboard navigation + this._hover.hide(); + controller.showCodeActions(markerCodeActionTrigger, actions, { + x: elementPosition.left + 6, + y: elementPosition.top + elementPosition.height + 6 + }); + } + })); + }); + } + + return hoverElement; + } + + private renderAction(parent: HTMLElement, actionOptions: { label: string, iconClass?: string, run: (target: HTMLElement) => void, commandId: string }): IDisposable { + const keybinding = this._keybindingService.lookupKeybinding(actionOptions.commandId); + const keybindingLabel = keybinding ? keybinding.getLabel() : null; + return renderHoverAction(parent, actionOptions, keybindingLabel); + } + + private getCodeActions(marker: IMarker): CancelablePromise { + return createCancelablePromise(cancellationToken => { + return getCodeActions( + this._editor.getModel()!, + new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn), + markerCodeActionTrigger, + Progress.None, + cancellationToken); + }); + } +} diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index ad2d6b272..ee10d9b80 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -3,228 +3,242 @@ * 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 { CancellationToken } from 'vs/base/common/cancellation'; import { Color, RGBA } from 'vs/base/common/color'; -import { IMarkdownString, MarkdownString, isEmptyMarkdownString, markedStringsEquals } from 'vs/base/common/htmlContent'; -import { IDisposable, toDisposable, DisposableStore, combinedDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IDisposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; +import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; 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 { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { DocumentColorProvider, Hover as MarkdownHover, HoverProviderRegistry, IColor, TokenizationRegistry, CodeActionTriggerType } from 'vs/editor/common/modes'; +import { DocumentColorProvider, IColor, TokenizationRegistry } from 'vs/editor/common/modes'; import { getColorPresentations } from 'vs/editor/contrib/colorPicker/color'; import { ColorDetector } from 'vs/editor/contrib/colorPicker/colorDetector'; import { ColorPickerModel } from 'vs/editor/contrib/colorPicker/colorPickerModel'; import { ColorPickerWidget } from 'vs/editor/contrib/colorPicker/colorPickerWidget'; -import { getHover } from 'vs/editor/contrib/hover/getHover'; import { HoverOperation, HoverStartMode, IHoverComputer } from 'vs/editor/contrib/hover/hoverOperation'; -import { ContentHoverWidget } from 'vs/editor/contrib/hover/hoverWidgets'; -import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { coalesce, isNonEmptyArray, asArray } from 'vs/base/common/arrays'; -import { IMarker, IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; -import { basename } from 'vs/base/common/resources'; -import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener'; -import { MarkerController, NextMarkerAction } from 'vs/editor/contrib/gotoError/gotoError'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { CancelablePromise, createCancelablePromise, disposableTimeout } from 'vs/base/common/async'; -import { getCodeActions, CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; -import { QuickFixAction, QuickFixController } from 'vs/editor/contrib/codeAction/codeActionCommands'; -import { CodeActionKind, CodeActionTrigger } from 'vs/editor/contrib/codeAction/types'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { IIdentifiedSingleEditOperation, TrackedRangeStickiness } from 'vs/editor/common/model'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { coalesce } from 'vs/base/common/arrays'; +import { IIdentifiedSingleEditOperation, IModelDecoration, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import { Constants } from 'vs/base/common/uint'; import { textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; -import { Progress } from 'vs/platform/progress/common/progress'; import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { Widget } from 'vs/base/browser/ui/widget'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { HoverWidget } from 'vs/base/browser/ui/hover/hoverWidget'; +import { MarkerHover, MarkerHoverParticipant } from 'vs/editor/contrib/hover/markerHoverParticipant'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { MarkdownHover, MarkdownHoverParticipant } from 'vs/editor/contrib/hover/markdownHoverParticipant'; -const $ = dom.$; +export interface IHoverPart { + readonly range: Range; + equals(other: IHoverPart): boolean; +} -class ColorHover { +export interface IEditorHover { + hide(): void; + onContentsChanged(): void; +} + +export interface IEditorHoverParticipant { + computeSync(hoverRange: Range, lineDecorations: IModelDecoration[]): T[]; + computeAsync?(range: Range, token: CancellationToken): Promise; + renderHoverParts(hoverParts: T[], fragment: DocumentFragment): IDisposable; +} + +class ColorHover implements IHoverPart { constructor( - public readonly range: IRange, + public readonly range: Range, public readonly color: IColor, public readonly provider: DocumentColorProvider ) { } + + equals(other: IHoverPart): boolean { + return false; + } } -class MarkerHover { - +class HoverPartInfo { constructor( - public readonly range: IRange, - public readonly marker: IMarker, + public readonly owner: IEditorHoverParticipant | null, + public readonly data: IHoverPart ) { } } -type HoverPart = MarkdownHover | ColorHover | MarkerHover; - -class ModesContentComputer implements IHoverComputer { +class ModesContentComputer implements IHoverComputer { private readonly _editor: ICodeEditor; - private _result: HoverPart[]; - private _range?: Range; + private _result: HoverPartInfo[]; + private _range: Range | null; constructor( editor: ICodeEditor, - private readonly _markerDecorationsService: IMarkerDecorationsService + private readonly _markerHoverParticipant: IEditorHoverParticipant, + private readonly _markdownHoverParticipant: MarkdownHoverParticipant ) { this._editor = editor; this._result = []; + this._range = null; } - setRange(range: Range): void { + public setRange(range: Range): void { this._range = range; this._result = []; } - clearResult(): void { + public clearResult(): void { this._result = []; } - computeAsync(token: CancellationToken): Promise { + public async computeAsync(token: CancellationToken): Promise { if (!this._editor.hasModel() || !this._range) { return Promise.resolve([]); } - const model = this._editor.getModel(); - - if (!HoverProviderRegistry.has(model)) { - return Promise.resolve([]); - } - - return getHover(model, new Position( - this._range.startLineNumber, - this._range.startColumn - ), token); + const markdownHovers = await this._markdownHoverParticipant.computeAsync(this._range, token); + return markdownHovers.map(h => new HoverPartInfo(this._markdownHoverParticipant, h)); } - computeSync(): HoverPart[] { + public computeSync(): HoverPartInfo[] { if (!this._editor.hasModel() || !this._range) { return []; } const model = this._editor.getModel(); - const lineNumber = this._range.startLineNumber; + const hoverRange = this._range; + const lineNumber = hoverRange.startLineNumber; if (lineNumber > this._editor.getModel().getLineCount()) { // Illegal line number => no results return []; } - const colorDetector = ColorDetector.get(this._editor); const maxColumn = model.getLineMaxColumn(lineNumber); - const lineDecorations = this._editor.getLineDecorations(lineNumber); - let didFindColor = false; - - const hoverRange = this._range; - const result = lineDecorations.map((d): HoverPart | null => { + const lineDecorations = this._editor.getLineDecorations(lineNumber).filter((d) => { const startColumn = (d.range.startLineNumber === lineNumber) ? d.range.startColumn : 1; const endColumn = (d.range.endLineNumber === lineNumber) ? d.range.endColumn : maxColumn; - if (startColumn > hoverRange.startColumn || hoverRange.endColumn > endColumn) { - return null; - } - - const range = new Range(hoverRange.startLineNumber, startColumn, hoverRange.startLineNumber, endColumn); - const marker = this._markerDecorationsService.getMarker(model, d); - if (marker) { - return new MarkerHover(range, marker); - } - - const colorData = colorDetector.getColorData(d.range.getStartPosition()); - - if (!didFindColor && colorData) { - didFindColor = true; - - const { color, range } = colorData.colorInfo; - return new ColorHover(range, color, colorData.provider); - } else { - if (isEmptyMarkdownString(d.options.hoverMessage)) { - return null; - } - - const contents: IMarkdownString[] = d.options.hoverMessage ? asArray(d.options.hoverMessage) : []; - return { contents, range }; + return false; } + return true; }); + let result: HoverPartInfo[] = []; + + const colorDetector = ColorDetector.get(this._editor); + for (const d of lineDecorations) { + const colorData = colorDetector.getColorData(d.range.getStartPosition()); + if (colorData) { + const { color, range } = colorData.colorInfo; + result.push(new HoverPartInfo(null, new ColorHover(Range.lift(range), color, colorData.provider))); + break; + } + } + + const markdownHovers = this._markdownHoverParticipant.computeSync(this._range, lineDecorations); + result = result.concat(markdownHovers.map(h => new HoverPartInfo(this._markdownHoverParticipant, h))); + + const markerHovers = this._markerHoverParticipant.computeSync(this._range, lineDecorations); + result = result.concat(markerHovers.map(h => new HoverPartInfo(this._markerHoverParticipant, h))); + return coalesce(result); } - onResult(result: HoverPart[], isFromSynchronousComputation: boolean): void { + public onResult(result: HoverPartInfo[], isFromSynchronousComputation: boolean): void { // Always put synchronous messages before asynchronous ones if (isFromSynchronousComputation) { - this._result = result.concat(this._result.sort((a, b) => { - if (a instanceof ColorHover) { // sort picker messages at to the top - return -1; - } else if (b instanceof ColorHover) { - return 1; - } - return 0; - })); + this._result = result.concat(this._result); } else { this._result = this._result.concat(result); } } - getResult(): HoverPart[] { + public getResult(): HoverPartInfo[] { return this._result.slice(0); } - getResultWithLoadingMessage(): HoverPart[] { - return this._result.slice(0).concat([this._getLoadingMessage()]); - } + public getResultWithLoadingMessage(): HoverPartInfo[] { + if (this._range) { + const loadingMessage = new HoverPartInfo(this._markdownHoverParticipant, this._markdownHoverParticipant.createLoadingMessage(this._range)); + return this._result.slice(0).concat([loadingMessage]); - private _getLoadingMessage(): HoverPart { - return { - range: this._range, - contents: [new MarkdownString().appendText(nls.localize('modesContentHover.loading', "Loading..."))] - }; + } + return this._result.slice(0); } } -const markerCodeActionTrigger: CodeActionTrigger = { - type: CodeActionTriggerType.Manual, - filter: { include: CodeActionKind.QuickFix } -}; - -export class ModesContentHoverWidget extends ContentHoverWidget { +export class ModesContentHoverWidget extends Widget implements IContentWidget, IEditorHover { static readonly ID = 'editor.contrib.modesContentHoverWidget'; - private _messages: HoverPart[]; + private readonly _markerHoverParticipant: IEditorHoverParticipant; + private readonly _markdownHoverParticipant: MarkdownHoverParticipant; + + private readonly _hover: HoverWidget; + private readonly _id: string; + private readonly _editor: ICodeEditor; + private _isVisible: boolean; + private _showAtPosition: Position | null; + private _showAtRange: Range | null; + private _stoleFocus: boolean; + + // IContentWidget.allowEditorOverflow + public readonly allowEditorOverflow = true; + + private _messages: HoverPartInfo[]; private _lastRange: Range | null; private readonly _computer: ModesContentComputer; - private readonly _hoverOperation: HoverOperation; + private readonly _hoverOperation: HoverOperation; private _highlightDecorations: string[]; private _isChangingDecorations: boolean; private _shouldFocus: boolean; private _colorPicker: ColorPickerWidget | null; - - private _codeLink?: HTMLElement; - - private readonly renderDisposable = this._register(new MutableDisposable()); + private _renderDisposable: IDisposable | null; constructor( editor: ICodeEditor, - _hoverVisibleKey: IContextKey, - markerDecorationsService: IMarkerDecorationsService, - keybindingService: IKeybindingService, + private readonly _hoverVisibleKey: IContextKey, + instantiationService: IInstantiationService, private readonly _themeService: IThemeService, - private readonly _modeService: IModeService, - private readonly _openerService: IOpenerService = NullOpenerService, ) { - super(ModesContentHoverWidget.ID, editor, _hoverVisibleKey, keybindingService); + super(); + + this._markerHoverParticipant = instantiationService.createInstance(MarkerHoverParticipant, editor, this); + this._markdownHoverParticipant = instantiationService.createInstance(MarkdownHoverParticipant, editor, this); + + this._hover = this._register(new HoverWidget()); + this._id = ModesContentHoverWidget.ID; + this._editor = editor; + this._isVisible = false; + this._stoleFocus = false; + this._renderDisposable = null; + + this.onkeydown(this._hover.containerDomNode, (e: IKeyboardEvent) => { + if (e.equals(KeyCode.Escape)) { + this.hide(); + } + }); + + this._register(this._editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { + if (e.hasChanged(EditorOption.fontInfo)) { + this._updateFont(); + } + })); + + this._editor.onDidLayoutChange(() => this.layout()); + + this.layout(); + this._editor.addContentWidget(this); + this._showAtPosition = null; + this._showAtRange = null; + this._stoleFocus = false; this._messages = []; this._lastRange = null; - this._computer = new ModesContentComputer(this._editor, markerDecorationsService); + this._computer = new ModesContentComputer(this._editor, this._markerHoverParticipant, this._markdownHoverParticipant); this._highlightDecorations = []; this._isChangingDecorations = false; this._shouldFocus = false; @@ -246,15 +260,15 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this._register(dom.addStandardDisposableListener(this.getDomNode(), dom.EventType.BLUR, () => { this.getDomNode().classList.remove('colorpicker-hover'); })); - this._register(editor.onDidChangeConfiguration((e) => { + this._register(editor.onDidChangeConfiguration(() => { this._hoverOperation.setHoverTime(this._editor.getOption(EditorOption.hover).delay); })); - this._register(TokenizationRegistry.onDidChange((e) => { - if (this.isVisible && this._lastRange && this._messages.length > 0) { + this._register(TokenizationRegistry.onDidChange(() => { + if (this._isVisible && this._lastRange && this._messages.length > 0) { this._messages = this._messages.map(msg => { // If a color hover is visible, we need to update the message that // created it so that the color matches the last chosen color - if (msg instanceof ColorHover && !!this._lastRange?.intersectRanges(msg.range) && this._colorPicker?.model.color) { + if (msg.data instanceof ColorHover && !!this._lastRange?.intersectRanges(msg.data.range) && this._colorPicker?.model.color) { const color = this._colorPicker.model.color; const newColor = { red: color.rgba.r / 255, @@ -262,7 +276,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { blue: color.rgba.b / 255, alpha: color.rgba.a }; - return new ColorHover(msg.range, newColor, msg.provider); + return new HoverPartInfo(msg.owner, new ColorHover(msg.data.range, newColor, msg.data.provider)); } else { return msg; } @@ -274,16 +288,81 @@ export class ModesContentHoverWidget extends ContentHoverWidget { })); } - dispose(): void { + public dispose(): void { this._hoverOperation.cancel(); + this._editor.removeContentWidget(this); super.dispose(); } - onModelDecorationsChanged(): void { + public getId(): string { + return this._id; + } + + public getDomNode(): HTMLElement { + return this._hover.containerDomNode; + } + + public showAt(position: Position, range: Range | null, focus: boolean): void { + // Position has changed + this._showAtPosition = position; + this._showAtRange = range; + this._hoverVisibleKey.set(true); + this._isVisible = true; + this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible); + + this._editor.layoutContentWidget(this); + // Simply force a synchronous render on the editor + // such that the widget does not really render with left = '0px' + this._editor.render(); + this._stoleFocus = focus; + if (focus) { + this._hover.containerDomNode.focus(); + } + } + + public getPosition(): IContentWidgetPosition | null { + if (this._isVisible) { + return { + position: this._showAtPosition, + range: this._showAtRange, + preference: [ + ContentWidgetPositionPreference.ABOVE, + ContentWidgetPositionPreference.BELOW + ] + }; + } + return null; + } + + private _updateFont(): void { + const codeClasses: HTMLElement[] = Array.prototype.slice.call(this._hover.contentsDomNode.getElementsByClassName('code')); + codeClasses.forEach(node => this._editor.applyFontInfo(node)); + } + + private _updateContents(node: Node): void { + this._hover.contentsDomNode.textContent = ''; + this._hover.contentsDomNode.appendChild(node); + this._updateFont(); + + this._editor.layoutContentWidget(this); + this._hover.onContentsChanged(); + } + + private layout(): void { + const height = Math.max(this._editor.getLayoutInfo().height / 4, 250); + const { fontSize, lineHeight } = this._editor.getOption(EditorOption.fontInfo); + + this._hover.contentsDomNode.style.fontSize = `${fontSize}px`; + this._hover.contentsDomNode.style.lineHeight = `${lineHeight}px`; + this._hover.contentsDomNode.style.maxHeight = `${height}px`; + this._hover.contentsDomNode.style.maxWidth = `${Math.max(this._editor.getLayoutInfo().width * 0.66, 500)}px`; + } + + public onModelDecorationsChanged(): void { if (this._isChangingDecorations) { return; } - if (this.isVisible) { + if (this._isVisible) { // The decorations have changed and the hover is visible, // we need to recompute the displayed text this._hoverOperation.cancel(); @@ -295,7 +374,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { } } - startShowingAt(range: Range, mode: HoverStartMode, focus: boolean): void { + public startShowingAt(range: Range, mode: HoverStartMode, focus: boolean): void { if (this._lastRange && this._lastRange.equalsRange(range)) { // We have to show the widget at the exact same range as before, so no work is needed return; @@ -303,17 +382,17 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this._hoverOperation.cancel(); - if (this.isVisible) { + if (this._isVisible) { // The range might have changed, but the hover is visible // Instead of hiding it completely, filter out messages that are still in the new range and // kick off a new computation if (!this._showAtPosition || this._showAtPosition.lineNumber !== range.startLineNumber) { this.hide(); } else { - let filteredMessages: HoverPart[] = []; + let filteredMessages: HoverPartInfo[] = []; for (let i = 0, len = this._messages.length; i < len; i++) { const msg = this._messages[i]; - const rng = msg.range; + const rng = msg.data.range; if (rng && rng.startColumn <= range.startColumn && rng.endColumn >= range.endColumn) { filteredMessages.push(msg); } @@ -335,25 +414,45 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this._hoverOperation.start(mode); } - hide(): void { + public hide(): void { this._lastRange = null; this._hoverOperation.cancel(); - super.hide(); + + if (this._isVisible) { + setTimeout(() => { + // Give commands a chance to see the key + if (!this._isVisible) { + this._hoverVisibleKey.set(false); + } + }, 0); + this._isVisible = false; + this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible); + + this._editor.layoutContentWidget(this); + if (this._stoleFocus) { + this._editor.focus(); + } + } + this._isChangingDecorations = true; this._highlightDecorations = this._editor.deltaDecorations(this._highlightDecorations, []); this._isChangingDecorations = false; - this.renderDisposable.clear(); + if (this._renderDisposable) { + this._renderDisposable.dispose(); + this._renderDisposable = null; + } this._colorPicker = null; } - isColorPickerVisible(): boolean { - if (this._colorPicker) { - return true; - } - return false; + public isColorPickerVisible(): boolean { + return !!this._colorPicker; } - private _withResult(result: HoverPart[], complete: boolean): void { + public onContentsChanged(): void { + this._hover.onContentsChanged(); + } + + private _withResult(result: HoverPartInfo[], complete: boolean): void { this._messages = result; if (this._lastRange && this._messages.length > 0) { @@ -363,20 +462,24 @@ export class ModesContentHoverWidget extends ContentHoverWidget { } } - private _renderMessages(renderRange: Range, messages: HoverPart[]): void { - this.renderDisposable.dispose(); + private _renderMessages(renderRange: Range, messages: HoverPartInfo[]): void { + if (this._renderDisposable) { + this._renderDisposable.dispose(); + this._renderDisposable = null; + } this._colorPicker = null; // update column from which to show let renderColumn = Constants.MAX_SAFE_SMALL_INTEGER; - let highlightRange: Range | null = messages[0].range ? Range.lift(messages[0].range) : null; + let highlightRange: Range | null = messages[0].data.range ? Range.lift(messages[0].data.range) : null; let fragment = document.createDocumentFragment(); - let isEmptyHoverContent = true; let containColorPicker = false; - const markdownDisposeables = new DisposableStore(); + const disposables = new DisposableStore(); const markerMessages: MarkerHover[] = []; - messages.forEach((msg) => { + const markdownParts: MarkdownHover[] = []; + messages.forEach((_msg) => { + const msg = _msg.data; if (!msg.range) { return; } @@ -464,46 +567,37 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this._colorPicker = widget; this.showAt(range.getStartPosition(), range, this._shouldFocus); - this.updateContents(fragment); + this._updateContents(fragment); this._colorPicker.layout(); - this.renderDisposable.value = combinedDisposable(colorListener, colorChangeListener, widget, markdownDisposeables); + this._renderDisposable = combinedDisposable(colorListener, colorChangeListener, widget, disposables); }); } else { if (msg instanceof MarkerHover) { markerMessages.push(msg); - isEmptyHoverContent = false; } else { - msg.contents - .filter(contents => !isEmptyMarkdownString(contents)) - .forEach(contents => { - const markdownHoverElement = $('div.hover-row.markdown-hover'); - const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents')); - const renderer = markdownDisposeables.add(new MarkdownRenderer({ editor: this._editor }, this._modeService, this._openerService)); - markdownDisposeables.add(renderer.onDidRenderAsync(() => { - hoverContentsElement.className = 'hover-contents code-hover-contents'; - this._hover.onContentsChanged(); - })); - const renderedContents = markdownDisposeables.add(renderer.render(contents)); - hoverContentsElement.appendChild(renderedContents.element); - fragment.appendChild(markdownHoverElement); - isEmptyHoverContent = false; - }); + if (msg instanceof MarkdownHover) { + markdownParts.push(msg); + } } } }); - if (markerMessages.length) { - markerMessages.forEach(msg => fragment.appendChild(this.renderMarkerHover(msg))); - const markerHoverForStatusbar = markerMessages.length === 1 ? markerMessages[0] : markerMessages.sort((a, b) => MarkerSeverity.compare(a.marker.severity, b.marker.severity))[0]; - fragment.appendChild(this.renderMarkerStatusbar(markerHoverForStatusbar)); + if (markdownParts.length > 0) { + disposables.add(this._markdownHoverParticipant.renderHoverParts(markdownParts, fragment)); } + if (markerMessages.length) { + disposables.add(this._markerHoverParticipant.renderHoverParts(markerMessages, fragment)); + } + + this._renderDisposable = disposables; + // show - if (!containColorPicker && !isEmptyHoverContent) { + if (!containColorPicker && fragment.hasChildNodes()) { this.showAt(new Position(renderRange.startLineNumber, renderColumn), highlightRange, this._shouldFocus); - this.updateContents(fragment); + this._updateContents(fragment); } this._isChangingDecorations = true; @@ -514,175 +608,17 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this._isChangingDecorations = false; } - private renderMarkerHover(markerHover: MarkerHover): HTMLElement { - const hoverElement = $('div.hover-row'); - const markerElement = dom.append(hoverElement, $('div.marker.hover-contents')); - const { source, message, code, relatedInformation } = markerHover.marker; - - this._editor.applyFontInfo(markerElement); - const messageElement = dom.append(markerElement, $('span')); - messageElement.style.whiteSpace = 'pre-wrap'; - messageElement.innerText = message; - - if (source || code) { - // Code has link - if (code && typeof code !== 'string') { - const sourceAndCodeElement = $('span'); - if (source) { - const sourceElement = dom.append(sourceAndCodeElement, $('span')); - sourceElement.innerText = source; - } - this._codeLink = dom.append(sourceAndCodeElement, $('a.code-link')); - this._codeLink.setAttribute('href', code.target.toString()); - - this._codeLink.onclick = (e) => { - this._openerService.open(code.target); - e.preventDefault(); - e.stopPropagation(); - }; - - const codeElement = dom.append(this._codeLink, $('span')); - codeElement.innerText = code.value; - - const detailsElement = dom.append(markerElement, sourceAndCodeElement); - detailsElement.style.opacity = '0.6'; - detailsElement.style.paddingLeft = '6px'; - } else { - const detailsElement = dom.append(markerElement, $('span')); - detailsElement.style.opacity = '0.6'; - detailsElement.style.paddingLeft = '6px'; - detailsElement.innerText = source && code ? `${source}(${code})` : source ? source : `(${code})`; - } - } - - if (isNonEmptyArray(relatedInformation)) { - for (const { message, resource, startLineNumber, startColumn } of relatedInformation) { - const relatedInfoContainer = dom.append(markerElement, $('div')); - relatedInfoContainer.style.marginTop = '8px'; - const a = dom.append(relatedInfoContainer, $('a')); - a.innerText = `${basename(resource)}(${startLineNumber}, ${startColumn}): `; - a.style.cursor = 'pointer'; - a.onclick = e => { - e.stopPropagation(); - e.preventDefault(); - if (this._openerService) { - this._openerService.open(resource.with({ fragment: `${startLineNumber},${startColumn}` }), { fromUserGesture: true }).catch(onUnexpectedError); - } - }; - const messageElement = dom.append(relatedInfoContainer, $('span')); - messageElement.innerText = message; - this._editor.applyFontInfo(messageElement); - } - } - - return hoverElement; - } - - private recentMarkerCodeActionsInfo: { marker: IMarker, hasCodeActions: boolean } | undefined = undefined; - private renderMarkerStatusbar(markerHover: MarkerHover): HTMLElement { - const hoverElement = $('div.hover-row.status-bar'); - const disposables = new DisposableStore(); - const actionsElement = dom.append(hoverElement, $('div.actions')); - if (markerHover.marker.severity === MarkerSeverity.Error || markerHover.marker.severity === MarkerSeverity.Warning || markerHover.marker.severity === MarkerSeverity.Info) { - disposables.add(this._renderAction(actionsElement, { - label: nls.localize('peek problem', "Peek Problem"), - commandId: NextMarkerAction.ID, - run: () => { - this.hide(); - MarkerController.get(this._editor).showAtMarker(markerHover.marker); - this._editor.focus(); - } - })); - } - - if (!this._editor.getOption(EditorOption.readOnly)) { - const quickfixPlaceholderElement = dom.append(actionsElement, $('div')); - if (this.recentMarkerCodeActionsInfo) { - if (IMarkerData.makeKey(this.recentMarkerCodeActionsInfo.marker) === IMarkerData.makeKey(markerHover.marker)) { - if (!this.recentMarkerCodeActionsInfo.hasCodeActions) { - quickfixPlaceholderElement.textContent = nls.localize('noQuickFixes', "No quick fixes available"); - } - } else { - this.recentMarkerCodeActionsInfo = undefined; - } - } - const updatePlaceholderDisposable = disposables.add(disposableTimeout(() => quickfixPlaceholderElement.textContent = nls.localize('checkingForQuickFixes', "Checking for quick fixes..."), 64)); - const codeActionsPromise = this.getCodeActions(markerHover.marker); - disposables.add(toDisposable(() => codeActionsPromise.cancel())); - codeActionsPromise.then(actions => { - updatePlaceholderDisposable.dispose(); - this.recentMarkerCodeActionsInfo = { marker: markerHover.marker, hasCodeActions: actions.validActions.length > 0 }; - - if (!this.recentMarkerCodeActionsInfo.hasCodeActions) { - actions.dispose(); - quickfixPlaceholderElement.textContent = nls.localize('noQuickFixes', "No quick fixes available"); - return; - } - quickfixPlaceholderElement.style.display = 'none'; - - let showing = false; - disposables.add(toDisposable(() => { - if (!showing) { - actions.dispose(); - } - })); - - disposables.add(this._renderAction(actionsElement, { - label: nls.localize('quick fixes', "Quick Fix..."), - commandId: QuickFixAction.Id, - run: (target) => { - showing = true; - const controller = QuickFixController.get(this._editor); - const elementPosition = dom.getDomNodePagePosition(target); - // Hide the hover pre-emptively, otherwise the editor can close the code actions - // context menu as well when using keyboard navigation - this.hide(); - controller.showCodeActions(markerCodeActionTrigger, actions, { - x: elementPosition.left + 6, - y: elementPosition.top + elementPosition.height + 6 - }); - } - })); - }); - } - - this.renderDisposable.value = disposables; - return hoverElement; - } - - private getCodeActions(marker: IMarker): CancelablePromise { - return createCancelablePromise(cancellationToken => { - return getCodeActions( - this._editor.getModel()!, - new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn), - markerCodeActionTrigger, - Progress.None, - cancellationToken); - }); - } - private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({ className: 'hoverHighlight' }); } -function hoverContentsEquals(first: HoverPart[], second: HoverPart[]): boolean { - if ((!first && second) || (first && !second) || first.length !== second.length) { +function hoverContentsEquals(first: HoverPartInfo[], second: HoverPartInfo[]): boolean { + if (first.length !== second.length) { return false; } for (let i = 0; i < first.length; i++) { - const firstElement = first[i]; - const secondElement = second[i]; - if (firstElement instanceof MarkerHover && secondElement instanceof MarkerHover) { - return IMarkerData.makeKey(firstElement.marker) === IMarkerData.makeKey(secondElement.marker); - } - if (firstElement instanceof ColorHover || secondElement instanceof ColorHover) { - return false; - } - if (firstElement instanceof MarkerHover || secondElement instanceof MarkerHover) { - return false; - } - if (!markedStringsEquals(firstElement.contents, secondElement.contents)) { + if (!first[i].data.equals(second[i].data)) { return false; } } diff --git a/src/vs/editor/contrib/inlineHints/inlineHintsController.ts b/src/vs/editor/contrib/inlineHints/inlineHintsController.ts new file mode 100644 index 000000000..43750f989 --- /dev/null +++ b/src/vs/editor/contrib/inlineHints/inlineHintsController.ts @@ -0,0 +1,227 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { onUnexpectedExternalError } from 'vs/base/common/errors'; +import { hash } from 'vs/base/common/hash'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { IContentDecorationRenderOptions, IEditorContribution } from 'vs/editor/common/editorCommon'; +import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; +import { InlineHintsProvider, InlineHintsProviderRegistry, InlineHint } from 'vs/editor/common/modes'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { flatten } from 'vs/base/common/arrays'; +import { editorInlineHintForeground, editorInlineHintBackground } from 'vs/platform/theme/common/colorRegistry'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { Range } from 'vs/editor/common/core/range'; +import { LanguageFeatureRequestDelays } from 'vs/editor/common/modes/languageFeatureRegistry'; +import { MarkdownString } from 'vs/base/common/htmlContent'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { URI } from 'vs/base/common/uri'; +import { IRange } from 'vs/base/common/range'; +import { assertType } from 'vs/base/common/types'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; + +const MAX_DECORATORS = 500; + +export interface InlineHintsData { + list: InlineHint[]; + provider: InlineHintsProvider; +} + +export async function getInlineHints(model: ITextModel, ranges: Range[], token: CancellationToken): Promise { + const datas: InlineHintsData[] = []; + const providers = InlineHintsProviderRegistry.ordered(model).reverse(); + const promises = flatten(providers.map(provider => ranges.map(range => Promise.resolve(provider.provideInlineHints(model, range, token)).then(result => { + if (result) { + datas.push({ list: result, provider }); + } + }, err => { + onUnexpectedExternalError(err); + })))); + + await Promise.all(promises); + + return datas; +} + +export class InlineHintsController implements IEditorContribution { + + static readonly ID: string = 'editor.contrib.InlineHints'; + + // static get(editor: ICodeEditor): InlineHintsController { + // return editor.getContribution(this.ID); + // } + + private readonly _disposables = new DisposableStore(); + private readonly _sessionDisposables = new DisposableStore(); + private readonly _getInlineHintsDelays = new LanguageFeatureRequestDelays(InlineHintsProviderRegistry, 250, 2500); + + private _decorationsTypeIds: string[] = []; + private _decorationIds: string[] = []; + + constructor( + private readonly _editor: ICodeEditor, + @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, + @IThemeService private readonly _themeService: IThemeService, + ) { + this._disposables.add(InlineHintsProviderRegistry.onDidChange(() => this._update())); + this._disposables.add(_themeService.onDidColorThemeChange(() => this._update())); + this._disposables.add(_editor.onDidChangeModel(() => this._update())); + this._disposables.add(_editor.onDidChangeModelLanguage(() => this._update())); + this._disposables.add(_editor.onDidChangeConfiguration(e => { + if (e.hasChanged(EditorOption.inlineHints)) { + this._update(); + } + })); + + this._update(); + } + + dispose(): void { + this._sessionDisposables.dispose(); + this._removeAllDecorations(); + this._disposables.dispose(); + } + + private _update(): void { + this._sessionDisposables.clear(); + + if (!this._editor.getOption(EditorOption.inlineHints).enabled) { + this._removeAllDecorations(); + return; + } + + const model = this._editor.getModel(); + if (!model || !InlineHintsProviderRegistry.has(model)) { + this._removeAllDecorations(); + return; + } + + const scheduler = new RunOnceScheduler(async () => { + const t1 = Date.now(); + + const cts = new CancellationTokenSource(); + this._sessionDisposables.add(toDisposable(() => cts.dispose(true))); + + const visibleRanges = this._editor.getVisibleRangesPlusViewportAboveBelow(); + const result = await getInlineHints(model, visibleRanges, cts.token); + + // update moving average + const newDelay = this._getInlineHintsDelays.update(model, Date.now() - t1); + scheduler.delay = newDelay; + + // render hints + this._updateHintsDecorators(result); + + }, this._getInlineHintsDelays.get(model)); + + this._sessionDisposables.add(scheduler); + + // update inline hints when content or scroll position changes + this._sessionDisposables.add(this._editor.onDidChangeModelContent(() => scheduler.schedule())); + this._disposables.add(this._editor.onDidScrollChange(() => scheduler.schedule())); + scheduler.schedule(); + + // update inline hints when any any provider fires an event + const providerListener = new DisposableStore(); + this._sessionDisposables.add(providerListener); + for (const provider of InlineHintsProviderRegistry.all(model)) { + if (typeof provider.onDidChangeInlineHints === 'function') { + providerListener.add(provider.onDidChangeInlineHints(() => scheduler.schedule())); + } + } + } + + private _updateHintsDecorators(hintsData: InlineHintsData[]): void { + const { fontSize, fontFamily } = this._getLayoutInfo(); + const backgroundColor = this._themeService.getColorTheme().getColor(editorInlineHintBackground); + const fontColor = this._themeService.getColorTheme().getColor(editorInlineHintForeground); + + const newDecorationsTypeIds: string[] = []; + const newDecorationsData: IModelDeltaDecoration[] = []; + + for (const { list: hints } of hintsData) { + + for (let j = 0; j < hints.length && newDecorationsData.length < MAX_DECORATORS; j++) { + const { text, range, description: hoverMessage, whitespaceBefore, whitespaceAfter } = hints[j]; + const marginBefore = whitespaceBefore ? (fontSize / 3) | 0 : 0; + const marginAfter = whitespaceAfter ? (fontSize / 3) | 0 : 0; + + const before: IContentDecorationRenderOptions = { + contentText: text, + backgroundColor: `${backgroundColor}`, + color: `${fontColor}`, + margin: `0px ${marginAfter}px 0px ${marginBefore}px`, + fontSize: `${fontSize}px`, + fontFamily: fontFamily, + padding: `0px ${(fontSize / 4) | 0}px`, + borderRadius: `${(fontSize / 4) | 0}px`, + }; + const key = 'inlineHints-' + hash(before).toString(16); + this._codeEditorService.registerDecorationType(key, { before }, undefined, this._editor); + + // decoration types are ref-counted which means we only need to + // call register und remove equally often + newDecorationsTypeIds.push(key); + + const options = this._codeEditorService.resolveDecorationOptions(key, true); + if (typeof hoverMessage === 'string') { + options.hoverMessage = new MarkdownString().appendText(hoverMessage); + } else if (hoverMessage) { + options.hoverMessage = hoverMessage; + } + + newDecorationsData.push({ + range, + options + }); + } + } + + this._decorationsTypeIds.forEach(this._codeEditorService.removeDecorationType, this._codeEditorService); + this._decorationsTypeIds = newDecorationsTypeIds; + + this._decorationIds = this._editor.deltaDecorations(this._decorationIds, newDecorationsData); + } + + private _getLayoutInfo() { + const options = this._editor.getOption(EditorOption.inlineHints); + const editorFontSize = this._editor.getOption(EditorOption.fontSize); + let fontSize = options.fontSize; + if (!fontSize || fontSize < 5 || fontSize > editorFontSize) { + fontSize = (editorFontSize * .9) | 0; + } + const fontFamily = options.fontFamily; + return { fontSize, fontFamily }; + } + + private _removeAllDecorations(): void { + this._decorationIds = this._editor.deltaDecorations(this._decorationIds, []); + this._decorationsTypeIds.forEach(this._codeEditorService.removeDecorationType, this._codeEditorService); + this._decorationsTypeIds = []; + } +} + +registerEditorContribution(InlineHintsController.ID, InlineHintsController); + +CommandsRegistry.registerCommand('_executeInlineHintProvider', async (accessor, ...args: [URI, IRange]): Promise => { + + const [uri, range] = args; + assertType(URI.isUri(uri)); + assertType(Range.isIRange(range)); + + const ref = await accessor.get(ITextModelService).createModelReference(uri); + try { + const data = await getInlineHints(ref.object.textEditorModel, [Range.lift(range)], CancellationToken.None); + return flatten(data.map(item => item.list)).sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); + + } finally { + ref.dispose(); + } +}); diff --git a/src/vs/editor/contrib/linesOperations/linesOperations.ts b/src/vs/editor/contrib/linesOperations/linesOperations.ts index 9c365d507..54fcba6ac 100644 --- a/src/vs/editor/contrib/linesOperations/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/linesOperations.ts @@ -939,43 +939,39 @@ export class TransposeAction extends EditorAction { export abstract class AbstractCaseAction extends EditorAction { public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { - let selections = editor.getSelections(); + const selections = editor.getSelections(); if (selections === null) { return; } - let model = editor.getModel(); + const model = editor.getModel(); if (model === null) { return; } - let wordSeparators = editor.getOption(EditorOption.wordSeparators); + const wordSeparators = editor.getOption(EditorOption.wordSeparators); + const textEdits: IIdentifiedSingleEditOperation[] = []; - let commands: ICommand[] = []; - - for (let i = 0, len = selections.length; i < len; i++) { - let selection = selections[i]; + for (const selection of selections) { if (selection.isEmpty()) { - let cursor = selection.getStartPosition(); + const cursor = selection.getStartPosition(); const word = editor.getConfiguredWordAtPosition(cursor); if (!word) { continue; } - let wordRange = new Range(cursor.lineNumber, word.startColumn, cursor.lineNumber, word.endColumn); - let text = model.getValueInRange(wordRange); - commands.push(new ReplaceCommandThatPreservesSelection(wordRange, this._modifyText(text, wordSeparators), - new Selection(cursor.lineNumber, cursor.column, cursor.lineNumber, cursor.column))); - + const wordRange = new Range(cursor.lineNumber, word.startColumn, cursor.lineNumber, word.endColumn); + const text = model.getValueInRange(wordRange); + textEdits.push(EditOperation.replace(wordRange, this._modifyText(text, wordSeparators))); } else { - let text = model.getValueInRange(selection); - commands.push(new ReplaceCommandThatPreservesSelection(selection, this._modifyText(text, wordSeparators), selection)); + const text = model.getValueInRange(selection); + textEdits.push(EditOperation.replace(selection, this._modifyText(text, wordSeparators))); } } editor.pushUndoStop(); - editor.executeCommands(this.id, commands); + editor.executeEdits(this.id, textEdits); editor.pushUndoStop(); } @@ -1049,6 +1045,25 @@ export class TitleCaseAction extends AbstractCaseAction { } } +export class SnakeCaseAction extends AbstractCaseAction { + constructor() { + super({ + id: 'editor.action.transformToSnakecase', + label: nls.localize('editor.transformToSnakecase', "Transform to Snake Case"), + alias: 'Transform to Snake Case', + precondition: EditorContextKeys.writable + }); + } + + protected _modifyText(text: string, wordSeparators: string): string { + return (text + .replace(/(\p{Ll})(\p{Lu})/gmu, '$1_$2') + .replace(/([^\b_])(\p{Lu})(\p{Ll})/gmu, '$1_$2$3') + .toLocaleLowerCase() + ); + } +} + registerEditorAction(CopyLinesUpAction); registerEditorAction(CopyLinesDownAction); registerEditorAction(DuplicateSelectionAction); @@ -1069,3 +1084,4 @@ registerEditorAction(TransposeAction); registerEditorAction(UpperCaseAction); registerEditorAction(LowerCaseAction); registerEditorAction(TitleCaseAction); +registerEditorAction(SnakeCaseAction); diff --git a/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts b/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts index 681c8f027..6cdc4a4d0 100644 --- a/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts +++ b/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts @@ -299,13 +299,13 @@ export class MoveLinesCommand implements ICommand { } } - private matchEnterRule(model: ITextModel, indentConverter: IIndentConverter, tabSize: number, line: number, oneLineAbove: number, oneLineAboveText?: string) { + private matchEnterRule(model: ITextModel, indentConverter: IIndentConverter, tabSize: number, line: number, oneLineAbove: number, previousLineText?: string) { let validPrecedingLine = oneLineAbove; while (validPrecedingLine >= 1) { // ship empty lines as empty lines just inherit indentation let lineContent; - if (validPrecedingLine === oneLineAbove && oneLineAboveText !== undefined) { - lineContent = oneLineAboveText; + if (validPrecedingLine === oneLineAbove && previousLineText !== undefined) { + lineContent = previousLineText; } else { lineContent = model.getLineContent(validPrecedingLine); } diff --git a/src/vs/editor/contrib/linesOperations/test/copyLinesCommand.test.ts b/src/vs/editor/contrib/linesOperations/test/copyLinesCommand.test.ts index 6777693d4..64f3c7a5d 100644 --- a/src/vs/editor/contrib/linesOperations/test/copyLinesCommand.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/copyLinesCommand.test.ts @@ -207,8 +207,8 @@ suite('Editor Contrib - Duplicate Selection', () => { withTestCodeEditor(lines.join('\n'), {}, (editor) => { editor.setSelections(selections); duplicateSelectionAction.run(null!, editor, {}); - assert.deepEqual(editor.getValue(), expectedLines.join('\n')); - assert.deepEqual(editor.getSelections()!.map(s => s.toString()), expectedSelections.map(s => s.toString())); + assert.deepStrictEqual(editor.getValue(), expectedLines.join('\n')); + assert.deepStrictEqual(editor.getSelections()!.map(s => s.toString()), expectedSelections.map(s => s.toString())); }); } diff --git a/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts b/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts index cdcb7f491..b3cbe4bdc 100644 --- a/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts @@ -8,7 +8,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; import { Handler } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { TitleCaseAction, DeleteAllLeftAction, DeleteAllRightAction, IndentLinesAction, InsertLineAfterAction, InsertLineBeforeAction, JoinLinesAction, LowerCaseAction, SortLinesAscendingAction, SortLinesDescendingAction, TransposeAction, UpperCaseAction, DeleteLinesAction } from 'vs/editor/contrib/linesOperations/linesOperations'; +import { TitleCaseAction, DeleteAllLeftAction, DeleteAllRightAction, IndentLinesAction, InsertLineAfterAction, InsertLineBeforeAction, JoinLinesAction, LowerCaseAction, SortLinesAscendingAction, SortLinesDescendingAction, TransposeAction, UpperCaseAction, DeleteLinesAction, SnakeCaseAction } from 'vs/editor/contrib/linesOperations/linesOperations'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import type { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -19,7 +19,7 @@ function assertSelection(editor: ICodeEditor, expected: Selection | Selection[]) if (!Array.isArray(expected)) { expected = [expected]; } - assert.deepEqual(editor.getSelections(), expected); + assert.deepStrictEqual(editor.getSelections(), expected); } function executeAction(action: EditorAction, editor: ICodeEditor): void { @@ -40,7 +40,7 @@ suite('Editor Contrib - Line Operations', () => { editor.setSelection(new Selection(1, 1, 3, 5)); executeAction(sortLinesAscendingAction, editor); - assert.deepEqual(model.getLinesContent(), [ + assert.deepStrictEqual(model.getLinesContent(), [ 'alpha', 'beta', 'omicron' @@ -65,7 +65,7 @@ suite('Editor Contrib - Line Operations', () => { editor.setSelections([new Selection(1, 1, 3, 5), new Selection(5, 1, 7, 5)]); executeAction(sortLinesAscendingAction, editor); - assert.deepEqual(model.getLinesContent(), [ + assert.deepStrictEqual(model.getLinesContent(), [ 'alpha', 'beta', 'omicron', @@ -79,7 +79,7 @@ suite('Editor Contrib - Line Operations', () => { new Selection(5, 1, 7, 7) ]; editor.getSelections()!.forEach((actualSelection, index) => { - assert.deepEqual(actualSelection.toString(), expectedSelections[index].toString()); + assert.deepStrictEqual(actualSelection.toString(), expectedSelections[index].toString()); }); }); }); @@ -98,7 +98,7 @@ suite('Editor Contrib - Line Operations', () => { editor.setSelection(new Selection(1, 1, 3, 7)); executeAction(sortLinesDescendingAction, editor); - assert.deepEqual(model.getLinesContent(), [ + assert.deepStrictEqual(model.getLinesContent(), [ 'omicron', 'beta', 'alpha' @@ -123,7 +123,7 @@ suite('Editor Contrib - Line Operations', () => { editor.setSelections([new Selection(1, 1, 3, 7), new Selection(5, 1, 7, 7)]); executeAction(sortLinesDescendingAction, editor); - assert.deepEqual(model.getLinesContent(), [ + assert.deepStrictEqual(model.getLinesContent(), [ 'omicron', 'beta', 'alpha', @@ -137,7 +137,7 @@ suite('Editor Contrib - Line Operations', () => { new Selection(5, 1, 7, 5) ]; editor.getSelections()!.forEach((actualSelection, index) => { - assert.deepEqual(actualSelection.toString(), expectedSelections[index].toString()); + assert.deepStrictEqual(actualSelection.toString(), expectedSelections[index].toString()); }); }); }); @@ -157,12 +157,12 @@ suite('Editor Contrib - Line Operations', () => { editor.setSelection(new Selection(1, 2, 1, 2)); executeAction(deleteAllLeftAction, editor); - assert.equal(model.getLineContent(1), 'ne'); + assert.strictEqual(model.getLineContent(1), 'ne'); editor.setSelections([new Selection(2, 2, 2, 2), new Selection(3, 2, 3, 2)]); executeAction(deleteAllLeftAction, editor); - assert.equal(model.getLineContent(2), 'wo'); - assert.equal(model.getLineContent(3), 'hree'); + assert.strictEqual(model.getLineContent(2), 'wo'); + assert.strictEqual(model.getLineContent(3), 'hree'); }); }); @@ -178,16 +178,16 @@ suite('Editor Contrib - Line Operations', () => { editor.setSelection(new Selection(2, 1, 2, 1)); executeAction(deleteAllLeftAction, editor); - assert.equal(model.getLineContent(1), 'onetwo'); + assert.strictEqual(model.getLineContent(1), 'onetwo'); editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 1, 2, 1)]); executeAction(deleteAllLeftAction, editor); - assert.equal(model.getLinesContent()[0], 'onetwothree'); - assert.equal(model.getLinesContent().length, 1); + assert.strictEqual(model.getLinesContent()[0], 'onetwothree'); + assert.strictEqual(model.getLinesContent().length, 1); editor.setSelection(new Selection(1, 1, 1, 1)); executeAction(deleteAllLeftAction, editor); - assert.equal(model.getLinesContent()[0], 'onetwothree'); + assert.strictEqual(model.getLinesContent()[0], 'onetwothree'); }); }); @@ -213,25 +213,25 @@ suite('Editor Contrib - Line Operations', () => { executeAction(deleteAllLeftAction, editor); let selections = editor.getSelections()!; - assert.equal(model.getLineContent(2), ''); - assert.equal(model.getLineContent(3), ' waso waso'); - assert.equal(model.getLineContent(5), ''); + assert.strictEqual(model.getLineContent(2), ''); + assert.strictEqual(model.getLineContent(3), ' waso waso'); + assert.strictEqual(model.getLineContent(5), ''); - assert.deepEqual([ + assert.deepStrictEqual([ selections[0].startLineNumber, selections[0].startColumn, selections[0].endLineNumber, selections[0].endColumn ], [3, 1, 3, 1]); - assert.deepEqual([ + assert.deepStrictEqual([ selections[1].startLineNumber, selections[1].startColumn, selections[1].endLineNumber, selections[1].endColumn ], [2, 1, 2, 1]); - assert.deepEqual([ + assert.deepStrictEqual([ selections[2].startLineNumber, selections[2].startColumn, selections[2].endLineNumber, @@ -241,17 +241,17 @@ suite('Editor Contrib - Line Operations', () => { executeAction(deleteAllLeftAction, editor); selections = editor.getSelections()!; - assert.equal(model.getLineContent(1), 'hi my name is Carlos Matos waso waso'); - assert.equal(selections.length, 2); + assert.strictEqual(model.getLineContent(1), 'hi my name is Carlos Matos waso waso'); + assert.strictEqual(selections.length, 2); - assert.deepEqual([ + assert.deepStrictEqual([ selections[0].startLineNumber, selections[0].startColumn, selections[0].endLineNumber, selections[0].endColumn ], [1, 27, 1, 27]); - assert.deepEqual([ + assert.deepStrictEqual([ selections[1].startLineNumber, selections[1].startColumn, selections[1].endLineNumber, @@ -277,23 +277,23 @@ suite('Editor Contrib - Line Operations', () => { editor.setSelections([new Selection(1, 2, 1, 2), new Selection(1, 4, 1, 4)]); executeAction(deleteAllLeftAction, editor); - assert.equal(model.getLineContent(1), 'lo'); + assert.strictEqual(model.getLineContent(1), 'lo'); editor.setSelections([new Selection(2, 2, 2, 2), new Selection(2, 4, 2, 5)]); executeAction(deleteAllLeftAction, editor); - assert.equal(model.getLineContent(2), 'd'); + assert.strictEqual(model.getLineContent(2), 'd'); editor.setSelections([new Selection(3, 2, 3, 5), new Selection(3, 7, 3, 7)]); executeAction(deleteAllLeftAction, editor); - assert.equal(model.getLineContent(3), 'world'); + assert.strictEqual(model.getLineContent(3), 'world'); editor.setSelections([new Selection(4, 3, 4, 3), new Selection(4, 5, 5, 4)]); executeAction(deleteAllLeftAction, editor); - assert.equal(model.getLineContent(4), 'jour'); + assert.strictEqual(model.getLineContent(4), 'jour'); editor.setSelections([new Selection(5, 3, 6, 3), new Selection(6, 5, 7, 5), new Selection(7, 7, 7, 7)]); executeAction(deleteAllLeftAction, editor); - assert.equal(model.getLineContent(5), 'world'); + assert.strictEqual(model.getLineContent(5), 'world'); }); }); @@ -310,16 +310,16 @@ suite('Editor Contrib - Line Operations', () => { editor.setSelection(new Selection(1, 1, 1, 1)); editor.trigger('keyboard', Handler.Type, { text: 'Typing some text here on line ' }); - assert.equal(model.getLineContent(1), 'Typing some text here on line one'); - assert.deepEqual(editor.getSelection(), new Selection(1, 31, 1, 31)); + assert.strictEqual(model.getLineContent(1), 'Typing some text here on line one'); + assert.deepStrictEqual(editor.getSelection(), new Selection(1, 31, 1, 31)); executeAction(deleteAllLeftAction, editor); - assert.equal(model.getLineContent(1), 'one'); - assert.deepEqual(editor.getSelection(), new Selection(1, 1, 1, 1)); + assert.strictEqual(model.getLineContent(1), 'one'); + assert.deepStrictEqual(editor.getSelection(), new Selection(1, 1, 1, 1)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), 'Typing some text here on line one'); - assert.deepEqual(editor.getSelection(), new Selection(1, 31, 1, 31)); + assert.strictEqual(model.getLineContent(1), 'Typing some text here on line one'); + assert.deepStrictEqual(editor.getSelection(), new Selection(1, 31, 1, 31)); }); }); }); @@ -345,27 +345,27 @@ suite('Editor Contrib - Line Operations', () => { editor.setSelection(new Selection(1, 2, 1, 2)); executeAction(joinLinesAction, editor); - assert.equal(model.getLineContent(1), 'hello world'); + assert.strictEqual(model.getLineContent(1), 'hello world'); assertSelection(editor, new Selection(1, 6, 1, 6)); editor.setSelection(new Selection(2, 2, 2, 2)); executeAction(joinLinesAction, editor); - assert.equal(model.getLineContent(2), 'hello world'); + assert.strictEqual(model.getLineContent(2), 'hello world'); assertSelection(editor, new Selection(2, 7, 2, 7)); editor.setSelection(new Selection(3, 2, 3, 2)); executeAction(joinLinesAction, editor); - assert.equal(model.getLineContent(3), 'hello world'); + assert.strictEqual(model.getLineContent(3), 'hello world'); assertSelection(editor, new Selection(3, 7, 3, 7)); editor.setSelection(new Selection(4, 2, 5, 3)); executeAction(joinLinesAction, editor); - assert.equal(model.getLineContent(4), 'hello world'); + assert.strictEqual(model.getLineContent(4), 'hello world'); assertSelection(editor, new Selection(4, 2, 4, 8)); editor.setSelection(new Selection(5, 1, 7, 3)); executeAction(joinLinesAction, editor); - assert.equal(model.getLineContent(5), 'hello world'); + assert.strictEqual(model.getLineContent(5), 'hello world'); assertSelection(editor, new Selection(5, 1, 5, 3)); }); }); @@ -381,8 +381,8 @@ suite('Editor Contrib - Line Operations', () => { editor.setSelection(new Selection(2, 1, 2, 1)); executeAction(joinLinesAction, editor); - assert.equal(model.getLineContent(1), 'hello'); - assert.equal(model.getLineContent(2), 'world'); + assert.strictEqual(model.getLineContent(1), 'hello'); + assert.strictEqual(model.getLineContent(2), 'world'); assertSelection(editor, new Selection(2, 6, 2, 6)); }); }); @@ -416,7 +416,7 @@ suite('Editor Contrib - Line Operations', () => { ]); executeAction(joinLinesAction, editor); - assert.equal(model.getLinesContent().join('\n'), 'hello world\nhello world\nhello world\nhello world\n\nhello world'); + assert.strictEqual(model.getLinesContent().join('\n'), 'hello world\nhello world\nhello world\nhello world\n\nhello world'); assertSelection(editor, [ /** primary cursor */ new Selection(3, 4, 3, 8), @@ -440,16 +440,16 @@ suite('Editor Contrib - Line Operations', () => { editor.setSelection(new Selection(1, 6, 1, 6)); editor.trigger('keyboard', Handler.Type, { text: ' my dear' }); - assert.equal(model.getLineContent(1), 'hello my dear'); - assert.deepEqual(editor.getSelection(), new Selection(1, 14, 1, 14)); + assert.strictEqual(model.getLineContent(1), 'hello my dear'); + assert.deepStrictEqual(editor.getSelection(), new Selection(1, 14, 1, 14)); executeAction(joinLinesAction, editor); - assert.equal(model.getLineContent(1), 'hello my dear world'); - assert.deepEqual(editor.getSelection(), new Selection(1, 14, 1, 14)); + assert.strictEqual(model.getLineContent(1), 'hello my dear world'); + assert.deepStrictEqual(editor.getSelection(), new Selection(1, 14, 1, 14)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), 'hello my dear'); - assert.deepEqual(editor.getSelection(), new Selection(1, 14, 1, 14)); + assert.strictEqual(model.getLineContent(1), 'hello my dear'); + assert.deepStrictEqual(editor.getSelection(), new Selection(1, 14, 1, 14)); }); }); }); @@ -467,27 +467,27 @@ suite('Editor Contrib - Line Operations', () => { editor.setSelection(new Selection(1, 1, 1, 1)); executeAction(transposeAction, editor); - assert.equal(model.getLineContent(1), 'hello world'); + assert.strictEqual(model.getLineContent(1), 'hello world'); assertSelection(editor, new Selection(1, 2, 1, 2)); editor.setSelection(new Selection(1, 6, 1, 6)); executeAction(transposeAction, editor); - assert.equal(model.getLineContent(1), 'hell oworld'); + assert.strictEqual(model.getLineContent(1), 'hell oworld'); assertSelection(editor, new Selection(1, 7, 1, 7)); editor.setSelection(new Selection(1, 12, 1, 12)); executeAction(transposeAction, editor); - assert.equal(model.getLineContent(1), 'hell oworl'); + assert.strictEqual(model.getLineContent(1), 'hell oworl'); assertSelection(editor, new Selection(2, 2, 2, 2)); editor.setSelection(new Selection(3, 1, 3, 1)); executeAction(transposeAction, editor); - assert.equal(model.getLineContent(3), ''); + assert.strictEqual(model.getLineContent(3), ''); assertSelection(editor, new Selection(4, 1, 4, 1)); editor.setSelection(new Selection(4, 2, 4, 2)); executeAction(transposeAction, editor); - assert.equal(model.getLineContent(4), ' '); + assert.strictEqual(model.getLineContent(4), ' '); assertSelection(editor, new Selection(4, 3, 4, 3)); } ); @@ -509,22 +509,22 @@ suite('Editor Contrib - Line Operations', () => { editor.setSelection(new Selection(1, 1, 1, 1)); executeAction(transposeAction, editor); - assert.equal(model.getLineContent(2), ''); + assert.strictEqual(model.getLineContent(2), ''); assertSelection(editor, new Selection(2, 1, 2, 1)); editor.setSelection(new Selection(3, 6, 3, 6)); executeAction(transposeAction, editor); - assert.equal(model.getLineContent(4), 'oworld'); + assert.strictEqual(model.getLineContent(4), 'oworld'); assertSelection(editor, new Selection(4, 2, 4, 2)); editor.setSelection(new Selection(6, 12, 6, 12)); executeAction(transposeAction, editor); - assert.equal(model.getLineContent(7), 'd'); + assert.strictEqual(model.getLineContent(7), 'd'); assertSelection(editor, new Selection(7, 2, 7, 2)); editor.setSelection(new Selection(8, 12, 8, 12)); executeAction(transposeAction, editor); - assert.equal(model.getLineContent(8), 'hello world'); + assert.strictEqual(model.getLineContent(8), 'hello world'); assertSelection(editor, new Selection(8, 12, 8, 12)); } ); @@ -534,52 +534,131 @@ suite('Editor Contrib - Line Operations', () => { withTestCodeEditor( [ 'hello world', - 'öçşğü' + 'öçşğü', + 'parseHTMLString', + 'getElementById', + 'insertHTML', + 'PascalCase', + 'CSSSelectorsList', + 'iD', + 'tEST', + 'öçşÖÇŞğüĞÜ', + 'audioConverter.convertM4AToMP3();', + 'snake_case', + 'Capital_Snake_Case', + `function helloWorld() { + return someGlobalObject.printHelloWorld("en", "utf-8"); + } + helloWorld();`.replace(/^\s+/gm, '') ], {}, (editor) => { let model = editor.getModel()!; let uppercaseAction = new UpperCaseAction(); let lowercaseAction = new LowerCaseAction(); let titlecaseAction = new TitleCaseAction(); + let snakecaseAction = new SnakeCaseAction(); editor.setSelection(new Selection(1, 1, 1, 12)); executeAction(uppercaseAction, editor); - assert.equal(model.getLineContent(1), 'HELLO WORLD'); + assert.strictEqual(model.getLineContent(1), 'HELLO WORLD'); assertSelection(editor, new Selection(1, 1, 1, 12)); editor.setSelection(new Selection(1, 1, 1, 12)); executeAction(lowercaseAction, editor); - assert.equal(model.getLineContent(1), 'hello world'); + assert.strictEqual(model.getLineContent(1), 'hello world'); assertSelection(editor, new Selection(1, 1, 1, 12)); editor.setSelection(new Selection(1, 3, 1, 3)); executeAction(uppercaseAction, editor); - assert.equal(model.getLineContent(1), 'HELLO world'); + assert.strictEqual(model.getLineContent(1), 'HELLO world'); assertSelection(editor, new Selection(1, 3, 1, 3)); editor.setSelection(new Selection(1, 4, 1, 4)); executeAction(lowercaseAction, editor); - assert.equal(model.getLineContent(1), 'hello world'); + assert.strictEqual(model.getLineContent(1), 'hello world'); assertSelection(editor, new Selection(1, 4, 1, 4)); editor.setSelection(new Selection(1, 1, 1, 12)); executeAction(titlecaseAction, editor); - assert.equal(model.getLineContent(1), 'Hello World'); + assert.strictEqual(model.getLineContent(1), 'Hello World'); assertSelection(editor, new Selection(1, 1, 1, 12)); editor.setSelection(new Selection(2, 1, 2, 6)); executeAction(uppercaseAction, editor); - assert.equal(model.getLineContent(2), 'ÖÇŞĞÜ'); + assert.strictEqual(model.getLineContent(2), 'ÖÇŞĞÜ'); assertSelection(editor, new Selection(2, 1, 2, 6)); editor.setSelection(new Selection(2, 1, 2, 6)); executeAction(lowercaseAction, editor); - assert.equal(model.getLineContent(2), 'öçşğü'); + assert.strictEqual(model.getLineContent(2), 'öçşğü'); assertSelection(editor, new Selection(2, 1, 2, 6)); editor.setSelection(new Selection(2, 1, 2, 6)); executeAction(titlecaseAction, editor); - assert.equal(model.getLineContent(2), 'Öçşğü'); + assert.strictEqual(model.getLineContent(2), 'Öçşğü'); assertSelection(editor, new Selection(2, 1, 2, 6)); + + editor.setSelection(new Selection(3, 1, 3, 16)); + executeAction(snakecaseAction, editor); + assert.strictEqual(model.getLineContent(3), 'parse_html_string'); + assertSelection(editor, new Selection(3, 1, 3, 18)); + + editor.setSelection(new Selection(4, 1, 4, 15)); + executeAction(snakecaseAction, editor); + assert.strictEqual(model.getLineContent(4), 'get_element_by_id'); + assertSelection(editor, new Selection(4, 1, 4, 18)); + + editor.setSelection(new Selection(5, 1, 5, 11)); + executeAction(snakecaseAction, editor); + assert.strictEqual(model.getLineContent(5), 'insert_html'); + assertSelection(editor, new Selection(5, 1, 5, 12)); + + editor.setSelection(new Selection(6, 1, 6, 11)); + executeAction(snakecaseAction, editor); + assert.strictEqual(model.getLineContent(6), 'pascal_case'); + assertSelection(editor, new Selection(6, 1, 6, 12)); + + editor.setSelection(new Selection(7, 1, 7, 17)); + executeAction(snakecaseAction, editor); + assert.strictEqual(model.getLineContent(7), 'css_selectors_list'); + assertSelection(editor, new Selection(7, 1, 7, 19)); + + editor.setSelection(new Selection(8, 1, 8, 3)); + executeAction(snakecaseAction, editor); + assert.strictEqual(model.getLineContent(8), 'i_d'); + assertSelection(editor, new Selection(8, 1, 8, 4)); + + editor.setSelection(new Selection(9, 1, 9, 5)); + executeAction(snakecaseAction, editor); + assert.strictEqual(model.getLineContent(9), 't_est'); + assertSelection(editor, new Selection(9, 1, 9, 6)); + + editor.setSelection(new Selection(10, 1, 10, 11)); + executeAction(snakecaseAction, editor); + assert.strictEqual(model.getLineContent(10), 'öçş_öç_şğü_ğü'); + assertSelection(editor, new Selection(10, 1, 10, 14)); + + editor.setSelection(new Selection(11, 1, 11, 34)); + executeAction(snakecaseAction, editor); + assert.strictEqual(model.getLineContent(11), 'audio_converter.convert_m4a_to_mp3();'); + assertSelection(editor, new Selection(11, 1, 11, 38)); + + editor.setSelection(new Selection(12, 1, 12, 11)); + executeAction(snakecaseAction, editor); + assert.strictEqual(model.getLineContent(12), 'snake_case'); + assertSelection(editor, new Selection(12, 1, 12, 11)); + + editor.setSelection(new Selection(13, 1, 13, 19)); + executeAction(snakecaseAction, editor); + assert.strictEqual(model.getLineContent(13), 'capital_snake_case'); + assertSelection(editor, new Selection(13, 1, 13, 19)); + + editor.setSelection(new Selection(14, 1, 17, 14)); + executeAction(snakecaseAction, editor); + assert.strictEqual(model.getValueInRange(new Selection(14, 1, 17, 15)), `function hello_world() { + return some_global_object.print_hello_world("en", "utf-8"); + } + hello_world();`.replace(/^\s+/gm, '')); + assertSelection(editor, new Selection(14, 1, 17, 15)); } ); @@ -597,27 +676,27 @@ suite('Editor Contrib - Line Operations', () => { editor.setSelection(new Selection(1, 1, 1, 12)); executeAction(titlecaseAction, editor); - assert.equal(model.getLineContent(1), 'Foo Bar Baz'); + assert.strictEqual(model.getLineContent(1), 'Foo Bar Baz'); editor.setSelection(new Selection(2, 1, 2, 12)); executeAction(titlecaseAction, editor); - assert.equal(model.getLineContent(2), 'Foo\'Bar\'Baz'); + assert.strictEqual(model.getLineContent(2), 'Foo\'Bar\'Baz'); editor.setSelection(new Selection(3, 1, 3, 12)); executeAction(titlecaseAction, editor); - assert.equal(model.getLineContent(3), 'Foo[Bar]Baz'); + assert.strictEqual(model.getLineContent(3), 'Foo[Bar]Baz'); editor.setSelection(new Selection(4, 1, 4, 12)); executeAction(titlecaseAction, editor); - assert.equal(model.getLineContent(4), 'Foo`Bar~Baz'); + assert.strictEqual(model.getLineContent(4), 'Foo`Bar~Baz'); editor.setSelection(new Selection(5, 1, 5, 12)); executeAction(titlecaseAction, editor); - assert.equal(model.getLineContent(5), 'Foo^Bar%Baz'); + assert.strictEqual(model.getLineContent(5), 'Foo^Bar%Baz'); editor.setSelection(new Selection(6, 1, 6, 12)); executeAction(titlecaseAction, editor); - assert.equal(model.getLineContent(6), 'Foo$Bar!Baz'); + assert.strictEqual(model.getLineContent(6), 'Foo$Bar!Baz'); } ); @@ -632,22 +711,22 @@ suite('Editor Contrib - Line Operations', () => { editor.setSelection(new Selection(1, 1, 1, 1)); executeAction(uppercaseAction, editor); - assert.equal(model.getLineContent(1), ''); + assert.strictEqual(model.getLineContent(1), ''); assertSelection(editor, new Selection(1, 1, 1, 1)); editor.setSelection(new Selection(1, 1, 1, 1)); executeAction(lowercaseAction, editor); - assert.equal(model.getLineContent(1), ''); + assert.strictEqual(model.getLineContent(1), ''); assertSelection(editor, new Selection(1, 1, 1, 1)); editor.setSelection(new Selection(2, 2, 2, 2)); executeAction(uppercaseAction, editor); - assert.equal(model.getLineContent(2), ' '); + assert.strictEqual(model.getLineContent(2), ' '); assertSelection(editor, new Selection(2, 2, 2, 2)); editor.setSelection(new Selection(2, 2, 2, 2)); executeAction(lowercaseAction, editor); - assert.equal(model.getLineContent(2), ' '); + assert.strictEqual(model.getLineContent(2), ' '); assertSelection(editor, new Selection(2, 2, 2, 2)); } ); @@ -660,18 +739,18 @@ suite('Editor Contrib - Line Operations', () => { const action = new DeleteAllRightAction(); executeAction(action, editor); - assert.deepEqual(model.getLinesContent(), ['']); - assert.deepEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]); + assert.deepStrictEqual(model.getLinesContent(), ['']); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]); editor.setSelection(new Selection(1, 1, 1, 1)); executeAction(action, editor); - assert.deepEqual(model.getLinesContent(), ['']); - assert.deepEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]); + assert.deepStrictEqual(model.getLinesContent(), ['']); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]); editor.setSelections([new Selection(1, 1, 1, 1), new Selection(1, 1, 1, 1), new Selection(1, 1, 1, 1)]); executeAction(action, editor); - assert.deepEqual(model.getLinesContent(), ['']); - assert.deepEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]); + assert.deepStrictEqual(model.getLinesContent(), ['']); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]); }); }); @@ -685,18 +764,18 @@ suite('Editor Contrib - Line Operations', () => { editor.setSelection(new Selection(1, 2, 1, 5)); executeAction(action, editor); - assert.deepEqual(model.getLinesContent(), ['ho', 'world']); - assert.deepEqual(editor.getSelections(), [new Selection(1, 2, 1, 2)]); + assert.deepStrictEqual(model.getLinesContent(), ['ho', 'world']); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 2, 1, 2)]); editor.setSelection(new Selection(1, 1, 2, 4)); executeAction(action, editor); - assert.deepEqual(model.getLinesContent(), ['ld']); - assert.deepEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]); + assert.deepStrictEqual(model.getLinesContent(), ['ld']); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]); editor.setSelection(new Selection(1, 1, 1, 3)); executeAction(action, editor); - assert.deepEqual(model.getLinesContent(), ['']); - assert.deepEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]); + assert.deepStrictEqual(model.getLinesContent(), ['']); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]); }); }); @@ -710,13 +789,13 @@ suite('Editor Contrib - Line Operations', () => { editor.setSelection(new Selection(1, 3, 1, 3)); executeAction(action, editor); - assert.deepEqual(model.getLinesContent(), ['he', 'world']); - assert.deepEqual(editor.getSelections(), [new Selection(1, 3, 1, 3)]); + assert.deepStrictEqual(model.getLinesContent(), ['he', 'world']); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 3, 1, 3)]); editor.setSelection(new Selection(2, 1, 2, 1)); executeAction(action, editor); - assert.deepEqual(model.getLinesContent(), ['he', '']); - assert.deepEqual(editor.getSelections(), [new Selection(2, 1, 2, 1)]); + assert.deepStrictEqual(model.getLinesContent(), ['he', '']); + assert.deepStrictEqual(editor.getSelections(), [new Selection(2, 1, 2, 1)]); }); }); @@ -730,18 +809,18 @@ suite('Editor Contrib - Line Operations', () => { editor.setSelection(new Selection(1, 6, 1, 6)); executeAction(action, editor); - assert.deepEqual(model.getLinesContent(), ['helloworld']); - assert.deepEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]); + assert.deepStrictEqual(model.getLinesContent(), ['helloworld']); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]); editor.setSelection(new Selection(1, 6, 1, 6)); executeAction(action, editor); - assert.deepEqual(model.getLinesContent(), ['hello']); - assert.deepEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]); + assert.deepStrictEqual(model.getLinesContent(), ['hello']); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]); editor.setSelection(new Selection(1, 6, 1, 6)); executeAction(action, editor); - assert.deepEqual(model.getLinesContent(), ['hello']); - assert.deepEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]); + assert.deepStrictEqual(model.getLinesContent(), ['hello']); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]); }); }); @@ -760,35 +839,35 @@ suite('Editor Contrib - Line Operations', () => { new Selection(3, 4, 3, 4), ]); executeAction(action, editor); - assert.deepEqual(model.getLinesContent(), ['hethere', 'wor']); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(model.getLinesContent(), ['hethere', 'wor']); + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 3, 1, 3), new Selection(2, 4, 2, 4) ]); executeAction(action, editor); - assert.deepEqual(model.getLinesContent(), ['he', 'wor']); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(model.getLinesContent(), ['he', 'wor']); + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 3, 1, 3), new Selection(2, 4, 2, 4) ]); executeAction(action, editor); - assert.deepEqual(model.getLinesContent(), ['hewor']); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(model.getLinesContent(), ['hewor']); + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 3, 1, 3), new Selection(1, 6, 1, 6) ]); executeAction(action, editor); - assert.deepEqual(model.getLinesContent(), ['he']); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(model.getLinesContent(), ['he']); + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 3, 1, 3) ]); executeAction(action, editor); - assert.deepEqual(model.getLinesContent(), ['he']); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(model.getLinesContent(), ['he']); + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 3, 1, 3) ]); }); @@ -809,20 +888,20 @@ suite('Editor Contrib - Line Operations', () => { new Selection(3, 4, 3, 4), ]); executeAction(action, editor); - assert.deepEqual(model.getLinesContent(), ['hethere', 'wor']); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(model.getLinesContent(), ['hethere', 'wor']); + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 3, 1, 3), new Selection(2, 4, 2, 4) ]); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 3, 1, 3), new Selection(1, 6, 1, 6), new Selection(3, 4, 3, 4) ]); CoreEditingCommands.Redo.runEditorCommand(null, editor, null); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 3, 1, 3), new Selection(2, 4, 2, 4) ]); @@ -847,27 +926,27 @@ suite('Editor Contrib - Line Operations', () => { } testInsertLineBefore(1, 3, (model, viewModel) => { - assert.deepEqual(viewModel.getSelection(), new Selection(1, 1, 1, 1)); - assert.equal(model.getLineContent(1), ''); - assert.equal(model.getLineContent(2), 'First line'); - assert.equal(model.getLineContent(3), 'Second line'); - assert.equal(model.getLineContent(4), 'Third line'); + assert.deepStrictEqual(viewModel.getSelection(), new Selection(1, 1, 1, 1)); + assert.strictEqual(model.getLineContent(1), ''); + assert.strictEqual(model.getLineContent(2), 'First line'); + assert.strictEqual(model.getLineContent(3), 'Second line'); + assert.strictEqual(model.getLineContent(4), 'Third line'); }); testInsertLineBefore(2, 3, (model, viewModel) => { - assert.deepEqual(viewModel.getSelection(), new Selection(2, 1, 2, 1)); - assert.equal(model.getLineContent(1), 'First line'); - assert.equal(model.getLineContent(2), ''); - assert.equal(model.getLineContent(3), 'Second line'); - assert.equal(model.getLineContent(4), 'Third line'); + assert.deepStrictEqual(viewModel.getSelection(), new Selection(2, 1, 2, 1)); + assert.strictEqual(model.getLineContent(1), 'First line'); + assert.strictEqual(model.getLineContent(2), ''); + assert.strictEqual(model.getLineContent(3), 'Second line'); + assert.strictEqual(model.getLineContent(4), 'Third line'); }); testInsertLineBefore(3, 3, (model, viewModel) => { - assert.deepEqual(viewModel.getSelection(), new Selection(3, 1, 3, 1)); - assert.equal(model.getLineContent(1), 'First line'); - assert.equal(model.getLineContent(2), 'Second line'); - assert.equal(model.getLineContent(3), ''); - assert.equal(model.getLineContent(4), 'Third line'); + assert.deepStrictEqual(viewModel.getSelection(), new Selection(3, 1, 3, 1)); + assert.strictEqual(model.getLineContent(1), 'First line'); + assert.strictEqual(model.getLineContent(2), 'Second line'); + assert.strictEqual(model.getLineContent(3), ''); + assert.strictEqual(model.getLineContent(4), 'Third line'); }); }); @@ -888,27 +967,27 @@ suite('Editor Contrib - Line Operations', () => { } testInsertLineAfter(1, 3, (model, viewModel) => { - assert.deepEqual(viewModel.getSelection(), new Selection(2, 1, 2, 1)); - assert.equal(model.getLineContent(1), 'First line'); - assert.equal(model.getLineContent(2), ''); - assert.equal(model.getLineContent(3), 'Second line'); - assert.equal(model.getLineContent(4), 'Third line'); + assert.deepStrictEqual(viewModel.getSelection(), new Selection(2, 1, 2, 1)); + assert.strictEqual(model.getLineContent(1), 'First line'); + assert.strictEqual(model.getLineContent(2), ''); + assert.strictEqual(model.getLineContent(3), 'Second line'); + assert.strictEqual(model.getLineContent(4), 'Third line'); }); testInsertLineAfter(2, 3, (model, viewModel) => { - assert.deepEqual(viewModel.getSelection(), new Selection(3, 1, 3, 1)); - assert.equal(model.getLineContent(1), 'First line'); - assert.equal(model.getLineContent(2), 'Second line'); - assert.equal(model.getLineContent(3), ''); - assert.equal(model.getLineContent(4), 'Third line'); + assert.deepStrictEqual(viewModel.getSelection(), new Selection(3, 1, 3, 1)); + assert.strictEqual(model.getLineContent(1), 'First line'); + assert.strictEqual(model.getLineContent(2), 'Second line'); + assert.strictEqual(model.getLineContent(3), ''); + assert.strictEqual(model.getLineContent(4), 'Third line'); }); testInsertLineAfter(3, 3, (model, viewModel) => { - assert.deepEqual(viewModel.getSelection(), new Selection(4, 1, 4, 1)); - assert.equal(model.getLineContent(1), 'First line'); - assert.equal(model.getLineContent(2), 'Second line'); - assert.equal(model.getLineContent(3), 'Third line'); - assert.equal(model.getLineContent(4), ''); + assert.deepStrictEqual(viewModel.getSelection(), new Selection(4, 1, 4, 1)); + assert.strictEqual(model.getLineContent(1), 'First line'); + assert.strictEqual(model.getLineContent(2), 'Second line'); + assert.strictEqual(model.getLineContent(3), 'Third line'); + assert.strictEqual(model.getLineContent(4), ''); }); }); @@ -928,11 +1007,11 @@ suite('Editor Contrib - Line Operations', () => { editor.setPosition(new Position(1, 2)); executeAction(indentLinesAction, editor); - assert.equal(model.getLineContent(1), '\tfunction baz() {'); - assert.deepEqual(editor.getSelection(), new Selection(1, 3, 1, 3)); + assert.strictEqual(model.getLineContent(1), '\tfunction baz() {'); + assert.deepStrictEqual(editor.getSelection(), new Selection(1, 3, 1, 3)); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), '\tf\tunction baz() {'); + assert.strictEqual(model.getLineContent(1), '\tf\tunction baz() {'); }); model.dispose(); @@ -953,8 +1032,8 @@ suite('Editor Contrib - Line Operations', () => { editor.setPosition(new Position(1, 1)); executeAction(indentLinesAction, editor); - assert.equal(model.getLineContent(1), '\tSome text'); - assert.deepEqual(editor.getSelection(), new Selection(1, 2, 1, 2)); + assert.strictEqual(model.getLineContent(1), '\tSome text'); + assert.deepStrictEqual(editor.getSelection(), new Selection(1, 2, 1, 2)); }); model.dispose(); @@ -972,8 +1051,8 @@ suite('Editor Contrib - Line Operations', () => { editor.setPosition(new Position(1, 1)); executeAction(indentLinesAction, editor); - assert.equal(model.getLineContent(1), ' '); - assert.deepEqual(editor.getSelection(), new Selection(1, 5, 1, 5)); + assert.strictEqual(model.getLineContent(1), ' '); + assert.deepStrictEqual(editor.getSelection(), new Selection(1, 5, 1, 5)); }); model.dispose(); @@ -995,7 +1074,7 @@ suite('Editor Contrib - Line Operations', () => { const deleteLinesAction = new DeleteLinesAction(); executeAction(deleteLinesAction, editor); - assert.equal(editor.getValue(), 'a\nc'); + assert.strictEqual(editor.getValue(), 'a\nc'); }); }); @@ -1007,8 +1086,8 @@ suite('Editor Contrib - Line Operations', () => { const deleteLinesAction = new DeleteLinesAction(); executeAction(deleteLinesAction, editor); - assert.equal(editor.getValue(), resultingText.join('\n')); - assert.deepEqual(editor.getSelections(), resultingSelections); + assert.strictEqual(editor.getValue(), resultingText.join('\n')); + assert.deepStrictEqual(editor.getSelections(), resultingSelections); }); } diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index 04e8b8aa0..8997154ce 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -57,7 +57,7 @@ function getHoverMessage(link: Link, useMetaKey: boolean): MarkdownString { nativeLabel = ` "${nativeLabelText}"`; } } - const hoverMessage = new MarkdownString('', true).appendMarkdown(`[${label}](${link.url.toString()}${nativeLabel}) (${kb})`); + const hoverMessage = new MarkdownString('', true).appendMarkdown(`[${label}](${link.url.toString(true)}${nativeLabel}) (${kb})`); return hoverMessage; } else { return new MarkdownString().appendText(`${label} (${kb})`); @@ -327,7 +327,7 @@ export class LinkDetector implements IEditorContribution { } } - return this.openerService.open(uri, { openToSide, fromUserGesture }); + return this.openerService.open(uri, { openToSide, fromUserGesture, allowContributedOpeners: true }); }, err => { const messageOrError = diff --git a/src/vs/editor/contrib/multicursor/multicursor.ts b/src/vs/editor/contrib/multicursor/multicursor.ts index 3606348fb..599948853 100644 --- a/src/vs/editor/contrib/multicursor/multicursor.ts +++ b/src/vs/editor/contrib/multicursor/multicursor.ts @@ -850,7 +850,6 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut this.updateSoon.schedule(); } else { this._setState(null); - } } else { this._update(); @@ -1016,11 +1015,6 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut }); this.decorations = this.editor.deltaDecorations(this.decorations, decorations); - - const currentFindState = CommonFindController.get(this.editor).getState(); - if (currentFindState.isRegex || currentFindState.matchCase || currentFindState.wholeWord) { - CommonFindController.get(this.editor).highlightFindOptions(true); - } } private static readonly _SELECTION_HIGHLIGHT_OVERVIEW = ModelDecorationOptions.register({ diff --git a/src/vs/editor/contrib/multicursor/test/multicursor.test.ts b/src/vs/editor/contrib/multicursor/test/multicursor.test.ts index 6e1e0a9be..5a0e7e198 100644 --- a/src/vs/editor/contrib/multicursor/test/multicursor.test.ts +++ b/src/vs/editor/contrib/multicursor/test/multicursor.test.ts @@ -25,7 +25,7 @@ suite('Multicursor', () => { editor.setSelection(new Selection(2, 1, 2, 1)); addCursorUpAction.run(null!, editor, {}); - assert.equal(viewModel.getSelections().length, 2); + assert.strictEqual(viewModel.getSelections().length, 2); editor.trigger('test', Handler.Paste, { text: '1\n2', @@ -35,8 +35,8 @@ suite('Multicursor', () => { ] }); - assert.equal(editor.getModel()!.getLineContent(1), '1abc'); - assert.equal(editor.getModel()!.getLineContent(2), '2def'); + assert.strictEqual(editor.getModel()!.getLineContent(1), '1abc'); + assert.strictEqual(editor.getModel()!.getLineContent(2), '2def'); }); }); @@ -46,7 +46,7 @@ suite('Multicursor', () => { ], {}, (editor, viewModel) => { let addCursorDownAction = new InsertCursorBelow(); addCursorDownAction.run(null!, editor, {}); - assert.equal(viewModel.getSelections().length, 1); + assert.strictEqual(viewModel.getSelections().length, 1); }); }); @@ -90,7 +90,7 @@ suite('Multicursor selection', () => { editor.setSelection(new Selection(2, 9, 2, 16)); selectHighlightsAction.run(null!, editor); - assert.deepEqual(editor.getSelections()!.map(fromRange), [ + assert.deepStrictEqual(editor.getSelections()!.map(fromRange), [ [2, 9, 2, 16], [1, 9, 1, 16], [3, 9, 3, 16], @@ -98,7 +98,7 @@ suite('Multicursor selection', () => { editor.trigger('test', 'removeSecondaryCursors', null); - assert.deepEqual(fromRange(editor.getSelection()!), [2, 9, 2, 16]); + assert.deepStrictEqual(fromRange(editor.getSelection()!), [2, 9, 2, 16]); multiCursorSelectController.dispose(); findController.dispose(); @@ -121,13 +121,13 @@ suite('Multicursor selection', () => { findController.getState().change({ searchString: 'some+thing', isRegex: true, isRevealed: true }, false); selectHighlightsAction.run(null!, editor); - assert.deepEqual(editor.getSelections()!.map(fromRange), [ + assert.deepStrictEqual(editor.getSelections()!.map(fromRange), [ [1, 1, 1, 10], [2, 1, 2, 11], [3, 1, 3, 12], ]); - assert.equal(findController.getState().searchString, 'some+thing'); + assert.strictEqual(findController.getState().searchString, 'some+thing'); multiCursorSelectController.dispose(); findController.dispose(); @@ -154,14 +154,14 @@ suite('Multicursor selection', () => { editor.setSelection(new Selection(2, 1, 3, 4)); addSelectionToNextFindMatch.run(null!, editor); - assert.deepEqual(editor.getSelections()!.map(fromRange), [ + assert.deepStrictEqual(editor.getSelections()!.map(fromRange), [ [2, 1, 3, 4], [8, 1, 9, 4] ]); editor.trigger('test', 'removeSecondaryCursors', null); - assert.deepEqual(fromRange(editor.getSelection()!), [2, 1, 3, 4]); + assert.deepStrictEqual(fromRange(editor.getSelection()!), [2, 1, 3, 4]); multiCursorSelectController.dispose(); findController.dispose(); @@ -182,7 +182,7 @@ suite('Multicursor selection', () => { editor.setSelection(new Selection(1, 1, 1, 4)); addSelectionToNextFindMatch.run(null!, editor); - assert.deepEqual(editor.getSelections()!.map(fromRange), [ + assert.deepStrictEqual(editor.getSelections()!.map(fromRange), [ [1, 1, 1, 4], [1, 4, 1, 7] ]); @@ -190,7 +190,7 @@ suite('Multicursor selection', () => { addSelectionToNextFindMatch.run(null!, editor); addSelectionToNextFindMatch.run(null!, editor); addSelectionToNextFindMatch.run(null!, editor); - assert.deepEqual(editor.getSelections()!.map(fromRange), [ + assert.deepStrictEqual(editor.getSelections()!.map(fromRange), [ [1, 1, 1, 4], [1, 4, 1, 7], [2, 1, 2, 4], @@ -199,14 +199,14 @@ suite('Multicursor selection', () => { ]); editor.trigger('test', Handler.Type, { text: 'z' }); - assert.deepEqual(editor.getSelections()!.map(fromRange), [ + assert.deepStrictEqual(editor.getSelections()!.map(fromRange), [ [1, 2, 1, 2], [1, 3, 1, 3], [2, 2, 2, 2], [3, 2, 3, 2], [3, 3, 3, 3] ]); - assert.equal(editor.getValue(), [ + assert.strictEqual(editor.getValue(), [ 'zz', 'z', 'zz', @@ -239,14 +239,14 @@ suite('Multicursor selection', () => { editor.setSelection(new Selection(2, 1, 3, 4)); addSelectionToNextFindMatch.run(null!, editor); - assert.deepEqual(editor.getSelections()!.map(fromRange), [ + assert.deepStrictEqual(editor.getSelections()!.map(fromRange), [ [2, 1, 3, 4], [8, 1, 9, 4] ]); editor.trigger('test', 'removeSecondaryCursors', null); - assert.deepEqual(fromRange(editor.getSelection()!), [2, 1, 3, 4]); + assert.deepStrictEqual(fromRange(editor.getSelection()!), [2, 1, 3, 4]); multiCursorSelectController.dispose(); findController.dispose(); @@ -284,25 +284,25 @@ suite('Multicursor selection', () => { ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 4), ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 4), new Selection(2, 1, 2, 4), ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 4), new Selection(2, 1, 2, 4), new Selection(3, 1, 3, 4), ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 4), new Selection(2, 1, 2, 4), new Selection(3, 1, 3, 4), @@ -323,20 +323,20 @@ suite('Multicursor selection', () => { ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 4), new Selection(2, 1, 2, 4), ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 4), new Selection(2, 1, 2, 4), new Selection(3, 1, 3, 4), ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 4), new Selection(2, 1, 2, 4), new Selection(3, 1, 3, 4), @@ -357,20 +357,20 @@ suite('Multicursor selection', () => { ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 4), new Selection(2, 1, 2, 4), ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 4), new Selection(2, 1, 2, 4), new Selection(3, 1, 3, 4), ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 4), new Selection(2, 1, 2, 4), new Selection(3, 1, 3, 4), @@ -392,14 +392,14 @@ suite('Multicursor selection', () => { ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 4), new Selection(2, 1, 2, 4), new Selection(3, 1, 3, 4), ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 4), new Selection(2, 1, 2, 4), new Selection(3, 1, 3, 4), @@ -421,14 +421,14 @@ suite('Multicursor selection', () => { ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 5, 1, 10), new Selection(2, 5, 2, 10), new Selection(3, 5, 3, 8), ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 5, 1, 10), new Selection(2, 5, 2, 10), new Selection(3, 5, 3, 8), @@ -450,20 +450,20 @@ suite('Multicursor selection', () => { ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 5), new Selection(2, 1, 2, 5), ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 5), new Selection(2, 1, 2, 5), new Selection(3, 1, 3, 5), ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 5), new Selection(2, 1, 2, 5), new Selection(3, 1, 3, 5), @@ -471,7 +471,7 @@ suite('Multicursor selection', () => { ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 5), new Selection(2, 1, 2, 5), new Selection(3, 1, 3, 5), @@ -480,7 +480,7 @@ suite('Multicursor selection', () => { ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 5), new Selection(2, 1, 2, 5), new Selection(3, 1, 3, 5), @@ -508,18 +508,18 @@ suite('Multicursor selection', () => { ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 4), ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 4), new Selection(4, 1, 4, 4), ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 4), new Selection(4, 1, 4, 4), new Selection(6, 2, 6, 5), @@ -534,12 +534,12 @@ suite('Multicursor selection', () => { ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 4), ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 4), new Selection(4, 1, 4, 4), ]); @@ -550,7 +550,7 @@ suite('Multicursor selection', () => { ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 4), new Selection(2, 1, 2, 4), ]); @@ -565,14 +565,14 @@ suite('Multicursor selection', () => { ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 4), new Selection(4, 1, 4, 4), new Selection(6, 2, 6, 5), ]); action.run(null!, editor); - assert.deepEqual(editor.getSelections(), [ + assert.deepStrictEqual(editor.getSelections(), [ new Selection(1, 1, 1, 4), new Selection(4, 1, 4, 4), new Selection(6, 2, 6, 5), diff --git a/src/vs/editor/contrib/parameterHints/parameterHints.css b/src/vs/editor/contrib/parameterHints/parameterHints.css index 03c4e2640..64f277ccf 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHints.css +++ b/src/vs/editor/contrib/parameterHints/parameterHints.css @@ -10,7 +10,7 @@ line-height: 1.5em; } -.monaco-editor .parameter-hints-widget > .wrapper { +.monaco-editor .parameter-hints-widget > .phwrapper { max-width: 440px; display: flex; flex-direction: row; diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts index 54b7a807b..2f41f4892 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts @@ -14,7 +14,7 @@ import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentW import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import * as modes from 'vs/editor/common/modes'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; +import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; import { Context } from 'vs/editor/contrib/parameterHints/provideSignatureHelp'; import * as nls from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -27,6 +27,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { assertIsDefined } from 'vs/base/common/types'; import { ColorScheme } from 'vs/platform/theme/common/theme'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; const $ = dom.$; @@ -81,7 +82,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { private createParamaterHintDOMNodes() { const element = $('.editor-widget.parameter-hints-widget'); - const wrapper = dom.append(element, $('.wrapper')); + const wrapper = dom.append(element, $('.phwrapper')); wrapper.tabIndex = -1; const controls = dom.append(wrapper, $('.controls')); @@ -225,8 +226,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { if (typeof activeParameter.documentation === 'string') { documentation.textContent = activeParameter.documentation; } else { - const renderedContents = this.renderDisposeables.add(this.markdownRenderer.render(activeParameter.documentation)); - renderedContents.element.classList.add('markdown-docs'); + const renderedContents = this.renderMarkdownDocs(activeParameter.documentation); documentation.appendChild(renderedContents.element); } dom.append(this.domNodes.docs, $('p', {}, documentation)); @@ -237,8 +237,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { } else if (typeof signature.documentation === 'string') { dom.append(this.domNodes.docs, $('p', {}, signature.documentation)); } else { - const renderedContents = this.renderDisposeables.add(this.markdownRenderer.render(signature.documentation)); - renderedContents.element.classList.add('markdown-docs'); + const renderedContents = this.renderMarkdownDocs(signature.documentation); dom.append(this.domNodes.docs, renderedContents.element); } @@ -265,6 +264,16 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { this.domNodes.scrollbar.scanDomNode(); } + private renderMarkdownDocs(markdown: IMarkdownString | undefined): IMarkdownRenderResult { + const renderedContents = this.renderDisposeables.add(this.markdownRenderer.render(markdown, { + asyncRenderCallback: () => { + this.domNodes?.scrollbar.scanDomNode(); + } + })); + renderedContents.element.classList.add('markdown-docs'); + return renderedContents; + } + private hasDocs(signature: modes.SignatureInformation, activeParameter: modes.ParameterInformation | undefined): boolean { if (activeParameter && typeof activeParameter.documentation === 'string' && assertIsDefined(activeParameter.documentation).length > 0) { return true; @@ -360,7 +369,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { const height = Math.max(this.editor.getLayoutInfo().height / 4, 250); const maxHeight = `${height}px`; this.domNodes.element.style.maxHeight = maxHeight; - const wrapper = this.domNodes.element.getElementsByClassName('wrapper') as HTMLCollectionOf; + const wrapper = this.domNodes.element.getElementsByClassName('phwrapper') as HTMLCollectionOf; if (wrapper.length) { wrapper[0].style.maxHeight = maxHeight; } diff --git a/src/vs/editor/contrib/parameterHints/provideSignatureHelp.ts b/src/vs/editor/contrib/parameterHints/provideSignatureHelp.ts index d513ef4b9..6cfa1e420 100644 --- a/src/vs/editor/contrib/parameterHints/provideSignatureHelp.ts +++ b/src/vs/editor/contrib/parameterHints/provideSignatureHelp.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { first } from 'vs/base/common/async'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; @@ -20,19 +19,26 @@ export const Context = { MultipleSignatures: new RawContextKey('parameterHintsMultipleSignatures', false), }; -export function provideSignatureHelp( +export async function provideSignatureHelp( model: ITextModel, position: Position, context: modes.SignatureHelpContext, token: CancellationToken -): Promise { +): Promise { const supports = modes.SignatureHelpProviderRegistry.ordered(model); - return first(supports.map(support => () => { - return Promise.resolve(support.provideSignatureHelp(model, position, token, context)) - .catch(e => onUnexpectedExternalError(e)); - })); + for (const support of supports) { + try { + const result = await support.provideSignatureHelp(model, position, token, context); + if (result) { + return result; + } + } catch (err) { + onUnexpectedExternalError(err); + } + } + return undefined; } CommandsRegistry.registerCommand('_executeSignatureHelpProvider', async (accessor, ...args: [URI, IPosition, string?]) => { diff --git a/src/vs/editor/contrib/peekView/peekView.ts b/src/vs/editor/contrib/peekView/peekView.ts index f265a4bfd..36db5b2b3 100644 --- a/src/vs/editor/contrib/peekView/peekView.ts +++ b/src/vs/editor/contrib/peekView/peekView.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/peekViewWidget'; import * as dom from 'vs/base/browser/dom'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; -import { ActionBar, IActionBarOptions } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar, ActionsOrientation, IActionBarOptions } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; import { Color } from 'vs/base/common/color'; import { Emitter } from 'vs/base/common/event'; @@ -25,8 +25,7 @@ import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { registerColor, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { Codicon } from 'vs/base/common/codicons'; -import { MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; -import { MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; export const IPeekViewService = createDecorator('IPeekViewService'); export interface IPeekViewService { @@ -107,6 +106,7 @@ export abstract class PeekViewWidget extends ZoneWidget { private readonly _onDidClose = new Emitter(); readonly onDidClose = this._onDidClose.event; + private disposed?: true; protected _headElement?: HTMLDivElement; protected _primaryHeading?: HTMLElement; @@ -125,8 +125,11 @@ export abstract class PeekViewWidget extends ZoneWidget { } dispose(): void { - super.dispose(); - this._onDidClose.fire(this); + if (!this.disposed) { + this.disposed = true; // prevent consumers who dispose on onDidClose from looping + super.dispose(); + this._onDidClose.fire(this); + } } style(styles: IPeekViewStyles): void { @@ -204,15 +207,8 @@ export abstract class PeekViewWidget extends ZoneWidget { protected _getActionBarOptions(): IActionBarOptions { return { - actionViewItemProvider: action => { - if (action instanceof MenuItemAction) { - return this.instantiationService.createInstance(MenuEntryActionViewItem, action); - } else if (action instanceof SubmenuItemAction) { - return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action); - } - - return undefined; - } + actionViewItemProvider: createActionViewItem.bind(undefined, this.instantiationService), + orientation: ActionsOrientation.HORIZONTAL }; } diff --git a/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts b/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts index 7db794fa8..ec7d2ab92 100644 --- a/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts @@ -10,7 +10,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { stripCodicons } from 'vs/base/common/codicons'; +import { stripIcons } from 'vs/base/common/iconLabels'; export abstract class AbstractEditorCommandsQuickAccessProvider extends AbstractCommandsQuickAccessProvider { @@ -41,7 +41,7 @@ export abstract class AbstractEditorCommandsQuickAccessProvider extends Abstract editorCommandPicks.push({ commandId: editorAction.id, commandAlias: editorAction.alias, - label: stripCodicons(editorAction.label) || editorAction.id, + label: stripIcons(editorAction.label) || editorAction.id, }); } diff --git a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts index 9fb3e3d3c..96c0a9be6 100644 --- a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts @@ -12,11 +12,10 @@ import { ITextModel } from 'vs/editor/common/model'; import { IRange, Range } from 'vs/editor/common/core/range'; import { AbstractEditorNavigationQuickAccessProvider, IEditorNavigationQuickAccessOptions, IQuickAccessTextEditorContext } from 'vs/editor/contrib/quickAccess/editorNavigationQuickAccess'; import { DocumentSymbol, SymbolKinds, SymbolTag, DocumentSymbolProviderRegistry, SymbolKind } from 'vs/editor/common/modes'; -import { OutlineModel, OutlineElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; +import { OutlineModel } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { trim, format } from 'vs/base/common/strings'; import { prepareQuery, IPreparedQuery, pieceToQuery, scoreFuzzy2 } from 'vs/base/common/fuzzyScorer'; import { IMatch } from 'vs/base/common/filters'; -import { Iterable } from 'vs/base/common/iterator'; import { Codicon } from 'vs/base/common/codicons'; export interface IGotoSymbolQuickPickItem extends IQuickPickItem { @@ -144,7 +143,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit // Resolve symbols from document once and reuse this // request for all filtering and typing then on - const symbolsPromise = this.getDocumentSymbols(model, true, token); + const symbolsPromise = this.getDocumentSymbols(model, token); // Set initial picks and update on type let picksCts: CancellationTokenSource | undefined = undefined; @@ -418,49 +417,9 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit return result; } - protected async getDocumentSymbols(document: ITextModel, flatten: boolean, token: CancellationToken): Promise { + protected async getDocumentSymbols(document: ITextModel, token: CancellationToken): Promise { const model = await OutlineModel.create(document, token); - if (token.isCancellationRequested) { - return []; - } - - const roots: DocumentSymbol[] = []; - for (const child of model.children.values()) { - if (child instanceof OutlineElement) { - roots.push(child.symbol); - } else { - roots.push(...Iterable.map(child.children.values(), child => child.symbol)); - } - } - - let flatEntries: DocumentSymbol[] = []; - if (flatten) { - this.flattenDocumentSymbols(flatEntries, roots, ''); - } else { - flatEntries = roots; - } - - return flatEntries.sort((symbolA, symbolB) => Range.compareRangesUsingStarts(symbolA.range, symbolB.range)); - } - - private flattenDocumentSymbols(bucket: DocumentSymbol[], entries: DocumentSymbol[], overrideContainerLabel: string): void { - for (const entry of entries) { - bucket.push({ - kind: entry.kind, - tags: entry.tags, - name: entry.name, - detail: entry.detail, - containerName: entry.containerName || overrideContainerLabel, - range: entry.range, - selectionRange: entry.selectionRange, - children: undefined, // we flatten it... - }); - - // Recurse over children - if (entry.children) { - this.flattenDocumentSymbols(bucket, entry.children, entry.name); - } - } + return token.isCancellationRequested ? [] : model.asListOfDocumentSymbols(); } } diff --git a/src/vs/editor/contrib/smartSelect/bracketSelections.ts b/src/vs/editor/contrib/smartSelect/bracketSelections.ts index 3842ad87b..af7496f4c 100644 --- a/src/vs/editor/contrib/smartSelect/bracketSelections.ts +++ b/src/vs/editor/contrib/smartSelect/bracketSelections.ts @@ -26,7 +26,7 @@ export class BracketSelectionRangeProvider implements SelectionRangeProvider { return result; } - private static readonly _maxDuration = 30; + public static _maxDuration = 30; private static readonly _maxRounds = 2; private static _bracketsRightYield(resolve: () => void, round: number, model: ITextModel, pos: Position, ranges: Map>): void { diff --git a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts index 4c566492b..862a0febe 100644 --- a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts +++ b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts @@ -16,7 +16,7 @@ import { BracketSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/bra import { provideSelectionRanges } from 'vs/editor/contrib/smartSelect/smartSelect'; import { CancellationToken } from 'vs/base/common/cancellation'; import { WordSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/wordSelections'; -import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/modelService.test'; +import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/testTextResourcePropertiesService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { NullLogService } from 'vs/platform/log/common/log'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; @@ -45,6 +45,16 @@ class MockJSMode extends MockMode { suite('SmartSelect', () => { + const OriginalBracketSelectionRangeProviderMaxDuration = BracketSelectionRangeProvider._maxDuration; + + suiteSetup(() => { + BracketSelectionRangeProvider._maxDuration = 5000; // 5 seconds + }); + + suiteTeardown(() => { + BracketSelectionRangeProvider._maxDuration = OriginalBracketSelectionRangeProviderMaxDuration; + }); + let modelService: ModelServiceImpl; let mode: MockJSMode; diff --git a/src/vs/editor/contrib/snippet/snippetVariables.ts b/src/vs/editor/contrib/snippet/snippetVariables.ts index bdfe96e7c..0113f8636 100644 --- a/src/vs/editor/contrib/snippet/snippetVariables.ts +++ b/src/vs/editor/contrib/snippet/snippetVariables.ts @@ -12,11 +12,11 @@ import { VariableResolver, Variable, Text } from 'vs/editor/contrib/snippet/snip import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { getLeadingWhitespace, commonPrefixLength, isFalsyOrWhitespace, splitLines } from 'vs/base/common/strings'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { isSingleFolderWorkspaceIdentifier, toWorkspaceIdentifier, WORKSPACE_EXTENSION, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { toWorkspaceIdentifier, WORKSPACE_EXTENSION, IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ILabelService } from 'vs/platform/label/common/label'; import { normalizeDriveLetter } from 'vs/base/common/labels'; -import { URI } from 'vs/base/common/uri'; import { OvertypingCapturer } from 'vs/editor/contrib/suggest/suggestOvertypingCapturer'; +import { generateUuid } from 'vs/base/common/uuid'; export const KnownSnippetVariableNames: { [key: string]: true } = Object.freeze({ 'CURRENT_YEAR': true, @@ -42,6 +42,7 @@ export const KnownSnippetVariableNames: { [key: string]: true } = Object.freeze( 'TM_FILENAME_BASE': true, 'TM_DIRECTORY': true, 'TM_FILEPATH': true, + 'RELATIVE_FILEPATH': true, 'BLOCK_COMMENT_START': true, 'BLOCK_COMMENT_END': true, 'LINE_COMMENT': true, @@ -49,6 +50,7 @@ export const KnownSnippetVariableNames: { [key: string]: true } = Object.freeze( 'WORKSPACE_FOLDER': true, 'RANDOM': true, 'RANDOM_HEX': true, + 'UUID': true }); export class CompositeSnippetVariableResolver implements VariableResolver { @@ -177,6 +179,8 @@ export class ModelBasedVariableResolver implements VariableResolver { } else if (name === 'TM_FILEPATH' && this._labelService) { return this._labelService.getUriLabel(this._model.uri); + } else if (name === 'RELATIVE_FILEPATH' && this._labelService) { + return this._labelService.getUriLabel(this._model.uri, { relative: true, noPrefix: true }); } return undefined; @@ -309,9 +313,9 @@ export class WorkspaceBasedVariableResolver implements VariableResolver { return undefined; } - private _resolveWorkspaceName(workspaceIdentifier: IWorkspaceIdentifier | URI): string | undefined { + private _resolveWorkspaceName(workspaceIdentifier: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): string | undefined { if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier)) { - return path.basename(workspaceIdentifier.path); + return path.basename(workspaceIdentifier.uri.path); } let filename = path.basename(workspaceIdentifier.configPath.path); @@ -320,9 +324,9 @@ export class WorkspaceBasedVariableResolver implements VariableResolver { } return filename; } - private _resoveWorkspacePath(workspaceIdentifier: IWorkspaceIdentifier | URI): string | undefined { + private _resoveWorkspacePath(workspaceIdentifier: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): string | undefined { if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier)) { - return normalizeDriveLetter(workspaceIdentifier.fsPath); + return normalizeDriveLetter(workspaceIdentifier.uri.fsPath); } let filename = path.basename(workspaceIdentifier.configPath.path); @@ -340,9 +344,10 @@ export class RandomBasedVariableResolver implements VariableResolver { if (name === 'RANDOM') { return Math.random().toString().slice(-6); - } - else if (name === 'RANDOM_HEX') { + } else if (name === 'RANDOM_HEX') { return Math.random().toString(16).slice(-6); + } else if (name === 'UUID') { + return generateUuid(); } return undefined; diff --git a/src/vs/editor/contrib/snippet/test/snippetController2.old.test.ts b/src/vs/editor/contrib/snippet/test/snippetController2.old.test.ts index 8c9be33cf..2559a4219 100644 --- a/src/vs/editor/contrib/snippet/test/snippetController2.old.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetController2.old.test.ts @@ -62,34 +62,34 @@ suite('SnippetController', () => { editor.setPosition({ lineNumber: 4, column: 2 }); snippetController.insert(template); - assert.equal(editor.getModel()!.getLineContent(4), '\tfor (var index; index < array.length; index++) {'); - assert.equal(editor.getModel()!.getLineContent(5), '\t\tvar element = array[index];'); - assert.equal(editor.getModel()!.getLineContent(6), '\t\t'); - assert.equal(editor.getModel()!.getLineContent(7), '\t}'); + assert.strictEqual(editor.getModel()!.getLineContent(4), '\tfor (var index; index < array.length; index++) {'); + assert.strictEqual(editor.getModel()!.getLineContent(5), '\t\tvar element = array[index];'); + assert.strictEqual(editor.getModel()!.getLineContent(6), '\t\t'); + assert.strictEqual(editor.getModel()!.getLineContent(7), '\t}'); editor.trigger('test', 'type', { text: 'i' }); - assert.equal(editor.getModel()!.getLineContent(4), '\tfor (var i; i < array.length; i++) {'); - assert.equal(editor.getModel()!.getLineContent(5), '\t\tvar element = array[i];'); - assert.equal(editor.getModel()!.getLineContent(6), '\t\t'); - assert.equal(editor.getModel()!.getLineContent(7), '\t}'); + assert.strictEqual(editor.getModel()!.getLineContent(4), '\tfor (var i; i < array.length; i++) {'); + assert.strictEqual(editor.getModel()!.getLineContent(5), '\t\tvar element = array[i];'); + assert.strictEqual(editor.getModel()!.getLineContent(6), '\t\t'); + assert.strictEqual(editor.getModel()!.getLineContent(7), '\t}'); snippetController.next(); editor.trigger('test', 'type', { text: 'arr' }); - assert.equal(editor.getModel()!.getLineContent(4), '\tfor (var i; i < arr.length; i++) {'); - assert.equal(editor.getModel()!.getLineContent(5), '\t\tvar element = arr[i];'); - assert.equal(editor.getModel()!.getLineContent(6), '\t\t'); - assert.equal(editor.getModel()!.getLineContent(7), '\t}'); + assert.strictEqual(editor.getModel()!.getLineContent(4), '\tfor (var i; i < arr.length; i++) {'); + assert.strictEqual(editor.getModel()!.getLineContent(5), '\t\tvar element = arr[i];'); + assert.strictEqual(editor.getModel()!.getLineContent(6), '\t\t'); + assert.strictEqual(editor.getModel()!.getLineContent(7), '\t}'); snippetController.prev(); editor.trigger('test', 'type', { text: 'j' }); - assert.equal(editor.getModel()!.getLineContent(4), '\tfor (var j; j < arr.length; j++) {'); - assert.equal(editor.getModel()!.getLineContent(5), '\t\tvar element = arr[j];'); - assert.equal(editor.getModel()!.getLineContent(6), '\t\t'); - assert.equal(editor.getModel()!.getLineContent(7), '\t}'); + assert.strictEqual(editor.getModel()!.getLineContent(4), '\tfor (var j; j < arr.length; j++) {'); + assert.strictEqual(editor.getModel()!.getLineContent(5), '\t\tvar element = arr[j];'); + assert.strictEqual(editor.getModel()!.getLineContent(6), '\t\t'); + assert.strictEqual(editor.getModel()!.getLineContent(7), '\t}'); snippetController.next(); snippetController.next(); - assert.deepEqual(editor.getPosition(), new Position(6, 3)); + assert.deepStrictEqual(editor.getPosition(), new Position(6, 3)); }); }); @@ -98,13 +98,13 @@ suite('SnippetController', () => { editor.setPosition({ lineNumber: 4, column: 2 }); snippetController.insert(template); - assert.equal(editor.getModel()!.getLineContent(4), '\tfor (var index; index < array.length; index++) {'); - assert.equal(editor.getModel()!.getLineContent(5), '\t\tvar element = array[index];'); - assert.equal(editor.getModel()!.getLineContent(6), '\t\t'); - assert.equal(editor.getModel()!.getLineContent(7), '\t}'); + assert.strictEqual(editor.getModel()!.getLineContent(4), '\tfor (var index; index < array.length; index++) {'); + assert.strictEqual(editor.getModel()!.getLineContent(5), '\t\tvar element = array[index];'); + assert.strictEqual(editor.getModel()!.getLineContent(6), '\t\t'); + assert.strictEqual(editor.getModel()!.getLineContent(7), '\t}'); snippetController.cancel(); - assert.deepEqual(editor.getPosition(), new Position(4, 16)); + assert.deepStrictEqual(editor.getPosition(), new Position(4, 16)); }); }); @@ -121,7 +121,7 @@ suite('SnippetController', () => { // text: null // }]); - // assert.equal(snippetController.isInSnippetMode(), false); + // assert.strictEqual(snippetController.isInSnippetMode(), false); // }); // }); @@ -138,7 +138,7 @@ suite('SnippetController', () => { // text: null // }]); - // assert.equal(snippetController.isInSnippetMode(), false); + // assert.strictEqual(snippetController.isInSnippetMode(), false); // }); // }); @@ -155,7 +155,7 @@ suite('SnippetController', () => { // text: '\nHello' // }]); - // assert.equal(snippetController.isInSnippetMode(), false); + // assert.strictEqual(snippetController.isInSnippetMode(), false); // }); // }); @@ -172,7 +172,7 @@ suite('SnippetController', () => { // text: '\nHello' // }]); - // assert.equal(snippetController.isInSnippetMode(), false); + // assert.strictEqual(snippetController.isInSnippetMode(), false); // }); // }); @@ -183,7 +183,7 @@ suite('SnippetController', () => { editor.getModel()!.setValue('goodbye'); - assert.equal(snippetController.isInSnippetMode(), false); + assert.strictEqual(snippetController.isInSnippetMode(), false); }); }); @@ -194,7 +194,7 @@ suite('SnippetController', () => { editor.getModel()!.undo(); - assert.equal(snippetController.isInSnippetMode(), false); + assert.strictEqual(snippetController.isInSnippetMode(), false); }); }); @@ -205,7 +205,7 @@ suite('SnippetController', () => { editor.setPosition({ lineNumber: 1, column: 1 }); - assert.equal(snippetController.isInSnippetMode(), false); + assert.strictEqual(snippetController.isInSnippetMode(), false); }); }); @@ -216,7 +216,7 @@ suite('SnippetController', () => { editor.setModel(null); - assert.equal(snippetController.isInSnippetMode(), false); + assert.strictEqual(snippetController.isInSnippetMode(), false); }); }); @@ -227,7 +227,7 @@ suite('SnippetController', () => { snippetController.dispose(); - assert.equal(snippetController.isInSnippetMode(), false); + assert.strictEqual(snippetController.isInSnippetMode(), false); }); }); @@ -241,7 +241,7 @@ suite('SnippetController', () => { codeSnippet = 'foo$0'; snippetController.insert(codeSnippet); - assert.equal(editor.getSelections()!.length, 2); + assert.strictEqual(editor.getSelections()!.length, 2); const [first, second] = editor.getSelections()!; assert.ok(first.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), first.toString()); assert.ok(second.equalsRange({ startLineNumber: 2, startColumn: 4, endLineNumber: 2, endColumn: 4 }), second.toString()); @@ -256,7 +256,7 @@ suite('SnippetController', () => { codeSnippet = 'foo$0bar'; snippetController.insert(codeSnippet); - assert.equal(editor.getSelections()!.length, 2); + assert.strictEqual(editor.getSelections()!.length, 2); const [first, second] = editor.getSelections()!; assert.ok(first.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), first.toString()); assert.ok(second.equalsRange({ startLineNumber: 2, startColumn: 4, endLineNumber: 2, endColumn: 4 }), second.toString()); @@ -271,7 +271,7 @@ suite('SnippetController', () => { codeSnippet = 'foo$0bar'; snippetController.insert(codeSnippet); - assert.equal(editor.getSelections()!.length, 2); + assert.strictEqual(editor.getSelections()!.length, 2); const [first, second] = editor.getSelections()!; assert.ok(first.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), first.toString()); assert.ok(second.equalsRange({ startLineNumber: 1, startColumn: 14, endLineNumber: 1, endColumn: 14 }), second.toString()); @@ -286,7 +286,7 @@ suite('SnippetController', () => { codeSnippet = 'foo\n$0\nbar'; snippetController.insert(codeSnippet); - assert.equal(editor.getSelections()!.length, 2); + assert.strictEqual(editor.getSelections()!.length, 2); const [first, second] = editor.getSelections()!; assert.ok(first.equalsRange({ startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }), first.toString()); assert.ok(second.equalsRange({ startLineNumber: 4, startColumn: 1, endLineNumber: 4, endColumn: 1 }), second.toString()); @@ -301,7 +301,7 @@ suite('SnippetController', () => { codeSnippet = 'foo\n$0\nbar'; snippetController.insert(codeSnippet); - assert.equal(editor.getSelections()!.length, 2); + assert.strictEqual(editor.getSelections()!.length, 2); const [first, second] = editor.getSelections()!; assert.ok(first.equalsRange({ startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }), first.toString()); assert.ok(second.equalsRange({ startLineNumber: 4, startColumn: 1, endLineNumber: 4, endColumn: 1 }), second.toString()); @@ -315,7 +315,7 @@ suite('SnippetController', () => { codeSnippet = 'xo$0r'; snippetController.insert(codeSnippet, { overwriteBefore: 1 }); - assert.equal(editor.getSelections()!.length, 1); + assert.strictEqual(editor.getSelections()!.length, 1); assert.ok(editor.getSelection()!.equalsRange({ startLineNumber: 2, startColumn: 8, endColumn: 8, endLineNumber: 2 })); }); }); @@ -328,9 +328,9 @@ suite('SnippetController', () => { codeSnippet = '{{% url_**$1** %}}'; controller.insert(codeSnippet, { overwriteBefore: 2 }); - assert.equal(editor.getSelections()!.length, 1); + assert.strictEqual(editor.getSelections()!.length, 1); assert.ok(editor.getSelection()!.equalsRange({ startLineNumber: 1, startColumn: 27, endLineNumber: 1, endColumn: 27 })); - assert.equal(editor.getModel()!.getValue(), 'example example {{% url_**** %}}'); + assert.strictEqual(editor.getModel()!.getValue(), 'example example {{% url_**** %}}'); }, ['example example sc']); @@ -346,9 +346,9 @@ suite('SnippetController', () => { controller.insert(codeSnippet, { overwriteBefore: 2 }); - assert.equal(editor.getSelections()!.length, 1); + assert.strictEqual(editor.getSelections()!.length, 1); assert.ok(editor.getSelection()!.equalsRange({ startLineNumber: 2, startColumn: 2, endLineNumber: 2, endColumn: 2 }), editor.getSelection()!.toString()); - assert.equal(editor.getModel()!.getValue(), 'afterEach((done) => {\n\ttest\n});'); + assert.strictEqual(editor.getModel()!.getValue(), 'afterEach((done) => {\n\ttest\n});'); }, ['af']); @@ -364,9 +364,9 @@ suite('SnippetController', () => { controller.insert(codeSnippet, { overwriteBefore: 2 }); - assert.equal(editor.getSelections()!.length, 1); + assert.strictEqual(editor.getSelections()!.length, 1); assert.ok(editor.getSelection()!.equalsRange({ startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }), editor.getSelection()!.toString()); - assert.equal(editor.getModel()!.getValue(), 'afterEach((done) => {\n\ttest\n});'); + assert.strictEqual(editor.getModel()!.getValue(), 'afterEach((done) => {\n\ttest\n});'); }, ['af']); @@ -380,8 +380,8 @@ suite('SnippetController', () => { controller.insert(codeSnippet, { overwriteBefore: 8 }); - assert.equal(editor.getModel()!.getValue(), 'after'); - assert.equal(editor.getSelections()!.length, 1); + assert.strictEqual(editor.getModel()!.getValue(), 'after'); + assert.strictEqual(editor.getSelections()!.length, 1); assert.ok(editor.getSelection()!.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), editor.getSelection()!.toString()); }, ['afterone']); @@ -404,7 +404,7 @@ suite('SnippetController', () => { controller.insert(codeSnippet, { overwriteBefore: 2 }); - assert.equal(editor.getSelections()!.length, 2); + assert.strictEqual(editor.getSelections()!.length, 2); const [first, second] = editor.getSelections()!; assert.ok(first.equalsRange({ startLineNumber: 5, startColumn: 3, endLineNumber: 5, endColumn: 3 }), first.toString()); @@ -429,7 +429,7 @@ suite('SnippetController', () => { controller.insert(codeSnippet, { overwriteBefore: 2 }); - assert.equal(editor.getSelections()!.length, 1); + assert.strictEqual(editor.getSelections()!.length, 1); const [first] = editor.getSelections()!; assert.ok(first.equalsRange({ startLineNumber: 2, startColumn: 3, endLineNumber: 2, endColumn: 3 }), first.toString()); @@ -465,7 +465,7 @@ suite('SnippetController', () => { codeSnippet = '_foo'; controller.insert(codeSnippet, { overwriteBefore: 1 }); - assert.equal(editor.getModel()!.getValue(), 'this._foo\nabc_foo'); + assert.strictEqual(editor.getModel()!.getValue(), 'this._foo\nabc_foo'); }, ['this._', 'abc']); @@ -478,7 +478,7 @@ suite('SnippetController', () => { codeSnippet = 'XX'; controller.insert(codeSnippet, { overwriteBefore: 1 }); - assert.equal(editor.getModel()!.getValue(), 'this.XX\nabcXX'); + assert.strictEqual(editor.getModel()!.getValue(), 'this.XX\nabcXX'); }, ['this._', 'abc']); @@ -492,7 +492,7 @@ suite('SnippetController', () => { codeSnippet = '_foo'; controller.insert(codeSnippet, { overwriteBefore: 1 }); - assert.equal(editor.getModel()!.getValue(), 'this._foo\nabc_foo\ndef_foo'); + assert.strictEqual(editor.getModel()!.getValue(), 'this._foo\nabc_foo\ndef_foo'); }, ['this._', 'abc', 'def_']); @@ -506,7 +506,7 @@ suite('SnippetController', () => { codeSnippet = '._foo'; controller.insert(codeSnippet, { overwriteBefore: 2 }); - assert.equal(editor.getModel()!.getValue(), 'this._foo\nabc._foo\ndef._foo'); + assert.strictEqual(editor.getModel()!.getValue(), 'this._foo\nabc._foo\ndef._foo'); }, ['this._', 'abc', 'def._']); @@ -520,7 +520,7 @@ suite('SnippetController', () => { codeSnippet = '._foo'; controller.insert(codeSnippet, { overwriteBefore: 2 }); - assert.equal(editor.getModel()!.getValue(), 'this._foo\nabc._foo\ndef._foo'); + assert.strictEqual(editor.getModel()!.getValue(), 'this._foo\nabc._foo\ndef._foo'); }, ['this._', 'abc', 'def._']); @@ -534,7 +534,7 @@ suite('SnippetController', () => { codeSnippet = '._foo'; controller.insert(codeSnippet, { overwriteBefore: 2 }); - assert.equal(editor.getModel()!.getValue(), 'this._._foo\na._foo\ndef._._foo'); + assert.strictEqual(editor.getModel()!.getValue(), 'this._._foo\na._foo\ndef._._foo'); }, ['this._', 'abc', 'def._']); @@ -550,7 +550,7 @@ suite('SnippetController', () => { codeSnippet = 'document'; controller.insert(codeSnippet, { overwriteBefore: 3 }); - assert.equal(editor.getModel()!.getValue(), '{document}\n{document && true}'); + assert.strictEqual(editor.getModel()!.getValue(), '{document}\n{document && true}'); }, ['{foo}', '{foo && true}']); }); @@ -565,7 +565,7 @@ suite('SnippetController', () => { codeSnippet = 'for (var ${1:i}=0; ${1:i} { codeSnippet = 'for (let ${1:i}=0; ${1:i} expected=${actual.toString()}`); } - assert.equal(s.length, 0); + assert.strictEqual(s.length, 0); } function assertContextKeys(service: MockContextKeyService, inSnippet: boolean, hasPrev: boolean, hasNext: boolean): void { - assert.equal(SnippetController2.InSnippetMode.getValue(service), inSnippet, `inSnippetMode`); - assert.equal(SnippetController2.HasPrevTabstop.getValue(service), hasPrev, `HasPrevTabstop`); - assert.equal(SnippetController2.HasNextTabstop.getValue(service), hasNext, `HasNextTabstop`); + assert.strictEqual(SnippetController2.InSnippetMode.getValue(service), inSnippet, `inSnippetMode`); + assert.strictEqual(SnippetController2.HasPrevTabstop.getValue(service), hasPrev, `HasPrevTabstop`); + assert.strictEqual(SnippetController2.HasNextTabstop.getValue(service), hasNext, `HasNextTabstop`); } let editor: ICodeEditor; @@ -40,7 +40,7 @@ suite('SnippetController2', function () { model = createTextModel('if\n $state\nfi'); editor = createTestCodeEditor({ model: model }); editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 5, 2, 5)]); - assert.equal(model.getEOL(), '\n'); + assert.strictEqual(model.getEOL(), '\n'); }); teardown(function () { @@ -78,9 +78,9 @@ suite('SnippetController2', function () { assertContextKeys(contextKeys, false, false, false); editor.trigger('test', 'type', { text: '\t' }); - assert.equal(SnippetController2.InSnippetMode.getValue(contextKeys), false); - assert.equal(SnippetController2.HasNextTabstop.getValue(contextKeys), false); - assert.equal(SnippetController2.HasPrevTabstop.getValue(contextKeys), false); + assert.strictEqual(SnippetController2.InSnippetMode.getValue(contextKeys), false); + assert.strictEqual(SnippetController2.HasNextTabstop.getValue(contextKeys), false); + assert.strictEqual(SnippetController2.HasPrevTabstop.getValue(contextKeys), false); }); test('insert, insert -> cursor moves out (left/right)', function () { @@ -111,7 +111,7 @@ suite('SnippetController2', function () { const ctrl = new SnippetController2(editor, logService, contextKeys); ctrl.insert('foo${1:bar}foo$0'); - assert.equal(SnippetController2.InSnippetMode.getValue(contextKeys), true); + assert.strictEqual(SnippetController2.InSnippetMode.getValue(contextKeys), true); assertSelections(editor, new Selection(1, 4, 1, 7), new Selection(2, 8, 2, 11)); // bad selection change diff --git a/src/vs/editor/contrib/snippet/test/snippetParser.test.ts b/src/vs/editor/contrib/snippet/test/snippetParser.test.ts index ba019cdc3..ed5dcc443 100644 --- a/src/vs/editor/contrib/snippet/test/snippetParser.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetParser.test.ts @@ -531,8 +531,8 @@ suite('SnippetParser', () => { let snippet = new SnippetParser().parse('This ${1:is ${2:nested}}$0', true); let [first, second] = snippet.placeholders; - assert.deepEqual(snippet.enclosingPlaceholders(first), []); - assert.deepEqual(snippet.enclosingPlaceholders(second), [first]); + assert.deepStrictEqual(snippet.enclosingPlaceholders(first), []); + assert.deepStrictEqual(snippet.enclosingPlaceholders(second), [first]); }); test('TextmateSnippet#offset', () => { diff --git a/src/vs/editor/contrib/snippet/test/snippetSession.test.ts b/src/vs/editor/contrib/snippet/test/snippetSession.test.ts index 458555cb0..a65de3d53 100644 --- a/src/vs/editor/contrib/snippet/test/snippetSession.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetSession.test.ts @@ -23,14 +23,14 @@ suite('SnippetSession', function () { const actual = s.shift()!; assert.ok(selection.equalsSelection(actual), `actual=${selection.toString()} <> expected=${actual.toString()}`); } - assert.equal(s.length, 0); + assert.strictEqual(s.length, 0); } setup(function () { model = createTextModel('function foo() {\n console.log(a);\n}'); editor = createTestCodeEditor({ model: model }) as IActiveCodeEditor; editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 5, 2, 5)]); - assert.equal(model.getEOL(), '\n'); + assert.strictEqual(model.getEOL(), '\n'); }); teardown(function () { @@ -43,7 +43,7 @@ suite('SnippetSession', function () { function assertNormalized(position: IPosition, input: string, expected: string): void { const snippet = new SnippetParser().parse(input); SnippetSession.adjustWhitespace(model, position, snippet, true, true); - assert.equal(snippet.toTextmateString(), expected); + assert.strictEqual(snippet.toTextmateString(), expected); } assertNormalized(new Position(1, 1), 'foo', 'foo'); @@ -73,7 +73,7 @@ suite('SnippetSession', function () { test('text edits & selection', function () { const session = new SnippetSession(editor, 'foo${1:bar}foo$0'); session.insert(); - assert.equal(editor.getModel()!.getValue(), 'foobarfoofunction foo() {\n foobarfooconsole.log(a);\n}'); + assert.strictEqual(editor.getModel()!.getValue(), 'foobarfoofunction foo() {\n foobarfooconsole.log(a);\n}'); assertSelections(editor, new Selection(1, 4, 1, 7), new Selection(2, 8, 2, 11)); session.next(); @@ -86,7 +86,7 @@ suite('SnippetSession', function () { editor.setSelections([new Selection(2, 5, 2, 5), new Selection(1, 1, 1, 1)]); session.insert(); - assert.equal(model.getValue(), 'barfunction foo() {\n barconsole.log(a);\n}'); + assert.strictEqual(model.getValue(), 'barfunction foo() {\n barconsole.log(a);\n}'); assertSelections(editor, new Selection(2, 5, 2, 8), new Selection(1, 1, 1, 4)); }); @@ -107,7 +107,7 @@ suite('SnippetSession', function () { test('snippets, just text', function () { const session = new SnippetSession(editor, 'foobar'); session.insert(); - assert.equal(model.getValue(), 'foobarfunction foo() {\n foobarconsole.log(a);\n}'); + assert.strictEqual(model.getValue(), 'foobarfunction foo() {\n foobarconsole.log(a);\n}'); assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11)); }); @@ -116,7 +116,7 @@ suite('SnippetSession', function () { const session = new SnippetSession(editor, 'foo\n\t${1:bar}\n$0'); session.insert(); - assert.equal(editor.getModel()!.getValue(), 'foo\n bar\nfunction foo() {\n foo\n bar\n console.log(a);\n}'); + assert.strictEqual(editor.getModel()!.getValue(), 'foo\n bar\nfunction foo() {\n foo\n bar\n console.log(a);\n}'); assertSelections(editor, new Selection(2, 5, 2, 8), new Selection(5, 9, 5, 12)); @@ -129,7 +129,7 @@ suite('SnippetSession', function () { editor.setSelection(new Selection(2, 5, 2, 5)); const session = new SnippetSession(editor, 'abc\n foo\n bar\n$0', { overwriteBefore: 0, overwriteAfter: 0, adjustWhitespace: false, clipboardText: undefined, overtypingCapturer: undefined }); session.insert(); - assert.equal(editor.getModel()!.getValue(), 'function foo() {\n abc\n foo\n bar\nconsole.log(a);\n}'); + assert.strictEqual(editor.getModel()!.getValue(), 'function foo() {\n abc\n foo\n bar\nconsole.log(a);\n}'); }); test('snippets, selections -> next/prev', () => { @@ -171,7 +171,7 @@ suite('SnippetSession', function () { // go to final tabstop session.next(); - assert.equal(model.getValue(), 'fX_bar_function foo() {\n fX_bar_console.log(a);\n}'); + assert.strictEqual(model.getValue(), 'fX_bar_function foo() {\n fX_bar_console.log(a);\n}'); assertSelections(editor, new Selection(1, 8, 1, 8), new Selection(2, 12, 2, 12)); }); @@ -180,7 +180,7 @@ suite('SnippetSession', function () { editor.setSelections([new Selection(1, 1, 1, 4), new Selection(1, 9, 1, 12)]); new SnippetSession(editor, 'x$0').insert(); - assert.equal(model.getValue(), 'x_bar_x'); + assert.strictEqual(model.getValue(), 'x_bar_x'); assertSelections(editor, new Selection(1, 2, 1, 2), new Selection(1, 8, 1, 8)); }); @@ -189,7 +189,7 @@ suite('SnippetSession', function () { editor.setSelections([new Selection(1, 1, 1, 4), new Selection(1, 9, 1, 12)]); new SnippetSession(editor, 'LONGER$0').insert(); - assert.equal(model.getValue(), 'LONGER_bar_LONGER'); + assert.strictEqual(model.getValue(), 'LONGER_bar_LONGER'); assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(1, 18, 1, 18)); }); @@ -203,11 +203,11 @@ suite('SnippetSession', function () { editor.trigger('test', 'type', { text: 'foo-' }); session.next(); - assert.equal(model.getValue(), 'foo_foo-bar_foo'); + assert.strictEqual(model.getValue(), 'foo_foo-bar_foo'); assertSelections(editor, new Selection(1, 12, 1, 12)); editor.trigger('test', 'type', { text: 'XXX' }); - assert.equal(model.getValue(), 'foo_foo-barXXX_foo'); + assert.strictEqual(model.getValue(), 'foo_foo-barXXX_foo'); session.prev(); assertSelections(editor, new Selection(1, 5, 1, 9)); session.next(); @@ -242,7 +242,7 @@ suite('SnippetSession', function () { editor.trigger('test', 'type', { text: '333' }); session.next(); - assert.equal(model.getValue(), '111222333function foo() {\n 111222333console.log(a);\n}'); + assert.strictEqual(model.getValue(), '111222333function foo() {\n 111222333console.log(a);\n}'); assertSelections(editor, new Selection(1, 10, 1, 10), new Selection(2, 14, 2, 14)); session.prev(); @@ -269,22 +269,22 @@ suite('SnippetSession', function () { editor.trigger('test', 'type', { text: '333' }); session.next(); - assert.equal(session.isAtLastPlaceholder, true); + assert.strictEqual(session.isAtLastPlaceholder, true); }); test('snippets, gracefully move over final tabstop', function () { const session = new SnippetSession(editor, '${1}bar$0'); session.insert(); - assert.equal(session.isAtLastPlaceholder, false); + assert.strictEqual(session.isAtLastPlaceholder, false); assertSelections(editor, new Selection(1, 1, 1, 1), new Selection(2, 5, 2, 5)); session.next(); - assert.equal(session.isAtLastPlaceholder, true); + assert.strictEqual(session.isAtLastPlaceholder, true); assertSelections(editor, new Selection(1, 4, 1, 4), new Selection(2, 8, 2, 8)); session.next(); - assert.equal(session.isAtLastPlaceholder, true); + assert.strictEqual(session.isAtLastPlaceholder, true); assertSelections(editor, new Selection(1, 4, 1, 4), new Selection(2, 8, 2, 8)); }); @@ -294,46 +294,46 @@ suite('SnippetSession', function () { assertSelections(editor, new Selection(1, 5, 1, 7), new Selection(2, 9, 2, 11)); editor.trigger('test', 'type', { text: 'XXX' }); - assert.equal(model.getValue(), 'log(XXX);function foo() {\n log(XXX);console.log(a);\n}'); + assert.strictEqual(model.getValue(), 'log(XXX);function foo() {\n log(XXX);console.log(a);\n}'); session.next(); - assert.equal(session.isAtLastPlaceholder, false); + assert.strictEqual(session.isAtLastPlaceholder, false); // assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11)); session.next(); - assert.equal(session.isAtLastPlaceholder, true); + assert.strictEqual(session.isAtLastPlaceholder, true); assertSelections(editor, new Selection(1, 10, 1, 10), new Selection(2, 14, 2, 14)); }); test('snippets, selections and snippet ranges', function () { const session = new SnippetSession(editor, '${1:foo}farboo${2:bar}$0'); session.insert(); - assert.equal(model.getValue(), 'foofarboobarfunction foo() {\n foofarboobarconsole.log(a);\n}'); + assert.strictEqual(model.getValue(), 'foofarboobarfunction foo() {\n foofarboobarconsole.log(a);\n}'); assertSelections(editor, new Selection(1, 1, 1, 4), new Selection(2, 5, 2, 8)); - assert.equal(session.isSelectionWithinPlaceholders(), true); + assert.strictEqual(session.isSelectionWithinPlaceholders(), true); editor.setSelections([new Selection(1, 1, 1, 1)]); - assert.equal(session.isSelectionWithinPlaceholders(), false); + assert.strictEqual(session.isSelectionWithinPlaceholders(), false); editor.setSelections([new Selection(1, 6, 1, 6), new Selection(2, 10, 2, 10)]); - assert.equal(session.isSelectionWithinPlaceholders(), false); // in snippet, outside placeholder + assert.strictEqual(session.isSelectionWithinPlaceholders(), false); // in snippet, outside placeholder editor.setSelections([new Selection(1, 6, 1, 6), new Selection(2, 10, 2, 10), new Selection(1, 1, 1, 1)]); - assert.equal(session.isSelectionWithinPlaceholders(), false); // in snippet, outside placeholder + assert.strictEqual(session.isSelectionWithinPlaceholders(), false); // in snippet, outside placeholder editor.setSelections([new Selection(1, 6, 1, 6), new Selection(2, 10, 2, 10), new Selection(2, 20, 2, 21)]); - assert.equal(session.isSelectionWithinPlaceholders(), false); + assert.strictEqual(session.isSelectionWithinPlaceholders(), false); // reset selection to placeholder session.next(); - assert.equal(session.isSelectionWithinPlaceholders(), true); + assert.strictEqual(session.isSelectionWithinPlaceholders(), true); assertSelections(editor, new Selection(1, 10, 1, 13), new Selection(2, 14, 2, 17)); // reset selection to placeholder session.next(); - assert.equal(session.isSelectionWithinPlaceholders(), true); - assert.equal(session.isAtLastPlaceholder, true); + assert.strictEqual(session.isSelectionWithinPlaceholders(), true); + assert.strictEqual(session.isAtLastPlaceholder, true); assertSelections(editor, new Selection(1, 13, 1, 13), new Selection(2, 17, 2, 17)); }); @@ -344,20 +344,20 @@ suite('SnippetSession', function () { const first = new SnippetSession(editor, 'foo${2:bar}foo$0'); first.insert(); - assert.equal(model.getValue(), 'foobarfoo'); + assert.strictEqual(model.getValue(), 'foobarfoo'); assertSelections(editor, new Selection(1, 4, 1, 7)); const second = new SnippetSession(editor, 'ba${1:zzzz}$0'); second.insert(); - assert.equal(model.getValue(), 'foobazzzzfoo'); + assert.strictEqual(model.getValue(), 'foobazzzzfoo'); assertSelections(editor, new Selection(1, 6, 1, 10)); second.next(); - assert.equal(second.isAtLastPlaceholder, true); + assert.strictEqual(second.isAtLastPlaceholder, true); assertSelections(editor, new Selection(1, 10, 1, 10)); first.next(); - assert.equal(first.isAtLastPlaceholder, true); + assert.strictEqual(first.isAtLastPlaceholder, true); assertSelections(editor, new Selection(1, 13, 1, 13)); }); @@ -365,11 +365,11 @@ suite('SnippetSession', function () { const session = new SnippetSession(editor, 'farboo$0'); session.insert(); - assert.equal(session.isAtLastPlaceholder, true); - assert.equal(session.isSelectionWithinPlaceholders(), false); + assert.strictEqual(session.isAtLastPlaceholder, true); + assert.strictEqual(session.isSelectionWithinPlaceholders(), false); editor.trigger('test', 'type', { text: 'XXX' }); - assert.equal(session.isSelectionWithinPlaceholders(), false); + assert.strictEqual(session.isSelectionWithinPlaceholders(), false); }); test('snippets, typing at beginning', function () { @@ -379,12 +379,12 @@ suite('SnippetSession', function () { session.insert(); editor.setSelection(new Selection(1, 2, 1, 2)); - assert.equal(session.isSelectionWithinPlaceholders(), false); - assert.equal(session.isAtLastPlaceholder, true); + assert.strictEqual(session.isSelectionWithinPlaceholders(), false); + assert.strictEqual(session.isAtLastPlaceholder, true); editor.trigger('test', 'type', { text: 'XXX' }); - assert.equal(model.getLineContent(1), 'fXXXfarboounction foo() {'); - assert.equal(session.isSelectionWithinPlaceholders(), false); + assert.strictEqual(model.getLineContent(1), 'fXXXfarboounction foo() {'); + assert.strictEqual(session.isSelectionWithinPlaceholders(), false); session.next(); assertSelections(editor, new Selection(1, 11, 1, 11)); @@ -412,7 +412,7 @@ suite('SnippetSession', function () { const session = new SnippetSession(editor, '@line=$TM_LINE_NUMBER$0'); session.insert(); - assert.equal(model.getValue(), '@line=1function foo() {\n @line=2console.log(a);\n}'); + assert.strictEqual(model.getValue(), '@line=1function foo() {\n @line=2console.log(a);\n}'); assertSelections(editor, new Selection(1, 8, 1, 8), new Selection(2, 12, 2, 12)); }); @@ -428,10 +428,10 @@ suite('SnippetSession', function () { session.next(); assertSelections(editor, new Selection(1, 22, 1, 22)); - assert.equal(session.isAtLastPlaceholder, false); + assert.strictEqual(session.isAtLastPlaceholder, false); session.next(); - assert.equal(session.isAtLastPlaceholder, true); + assert.strictEqual(session.isAtLastPlaceholder, true); assertSelections(editor, new Selection(1, 23, 1, 23)); session.prev(); @@ -456,8 +456,8 @@ suite('SnippetSession', function () { editor.trigger('test', 'type', { text: 'foo' }); session.next(); - assert.equal(model.getValue(), 'bar'); - assert.equal(session.isAtLastPlaceholder, true); + assert.strictEqual(model.getValue(), 'bar'); + assert.strictEqual(session.isAtLastPlaceholder, true); assertSelections(editor, new Selection(1, 4, 1, 4)); }); @@ -471,8 +471,8 @@ suite('SnippetSession', function () { editor.trigger('test', 'type', { text: 'foo' }); session.next(); - assert.equal(model.getValue(), 'foo baz bar'); - assert.equal(session.isAtLastPlaceholder, true); + assert.strictEqual(model.getValue(), 'foo baz bar'); + assert.strictEqual(session.isAtLastPlaceholder, true); assertSelections(editor, new Selection(1, 12, 1, 12)); }); @@ -493,8 +493,8 @@ suite('SnippetSession', function () { assertSelections(editor, new Selection(1, 16, 1, 16)); session.next(); - assert.equal(model.getValue(), 'clk : std_logic;\n'); - assert.equal(session.isAtLastPlaceholder, true); + assert.strictEqual(model.getValue(), 'clk : std_logic;\n'); + assert.strictEqual(session.isAtLastPlaceholder, true); assertSelections(editor, new Selection(2, 1, 2, 1)); }); @@ -532,8 +532,8 @@ suite('SnippetSession', function () { editor.trigger('test', 'type', { text: 'string' }); session.next(); - assert.equal(model.getValue(), expected); - assert.equal(session.isAtLastPlaceholder, true); + assert.strictEqual(model.getValue(), expected); + assert.strictEqual(session.isAtLastPlaceholder, true); assertSelections(editor, new Selection(4, 2, 4, 2)); }); @@ -556,8 +556,8 @@ suite('SnippetSession', function () { editor.trigger('test', 'type', { text: ' := \'1\'' }); session.next(); - assert.equal(model.getValue(), 'clk : std_logic := \'1\';\n'); - assert.equal(session.isAtLastPlaceholder, true); + assert.strictEqual(model.getValue(), 'clk : std_logic := \'1\';\n'); + assert.strictEqual(session.isAtLastPlaceholder, true); assertSelections(editor, new Selection(2, 1, 2, 1)); }); @@ -570,13 +570,13 @@ suite('SnippetSession', function () { assertSelections(editor, new Selection(1, 1, 1, 2), new Selection(1, 5, 1, 6)); session.next(); - assert.equal(model.getValue(), '{fff}'); + assert.strictEqual(model.getValue(), '{fff}'); assertSelections(editor, new Selection(1, 2, 1, 5)); editor.trigger('test', 'type', { text: 'ggg' }); session.next(); - assert.equal(model.getValue(), '{ggg}'); - assert.equal(session.isAtLastPlaceholder, true); + assert.strictEqual(model.getValue(), '{ggg}'); + assert.strictEqual(session.isAtLastPlaceholder, true); assertSelections(editor, new Selection(1, 6, 1, 6)); }); @@ -584,7 +584,7 @@ suite('SnippetSession', function () { editor.getModel().setValue(''); const session = new SnippetSession(editor, '${1:{}${2:fff}${1/[\\{]/}/}$0'); session.insert(); - assert.equal(editor.getModel().getValue(), '{fff{'); + assert.strictEqual(editor.getModel().getValue(), '{fff{'); assertSelections(editor, new Selection(1, 1, 1, 2), new Selection(1, 5, 1, 6)); session.next(); @@ -599,25 +599,25 @@ suite('SnippetSession', function () { editor.trigger('test', 'type', { text: '1' }); editor.trigger('test', 'type', { text: '\n' }); - assert.equal(editor.getModel()!.getValue(), 'test 1\n'); + assert.strictEqual(editor.getModel()!.getValue(), 'test 1\n'); session.merge('test ${1:replaceme}'); editor.trigger('test', 'type', { text: '2' }); editor.trigger('test', 'type', { text: '\n' }); - assert.equal(editor.getModel()!.getValue(), 'test 1\ntest 2\n'); + assert.strictEqual(editor.getModel()!.getValue(), 'test 1\ntest 2\n'); session.merge('test ${1:replaceme}'); editor.trigger('test', 'type', { text: '3' }); editor.trigger('test', 'type', { text: '\n' }); - assert.equal(editor.getModel()!.getValue(), 'test 1\ntest 2\ntest 3\n'); + assert.strictEqual(editor.getModel()!.getValue(), 'test 1\ntest 2\ntest 3\n'); session.merge('test ${1:replaceme}'); editor.trigger('test', 'type', { text: '4' }); editor.trigger('test', 'type', { text: '\n' }); - assert.equal(editor.getModel()!.getValue(), 'test 1\ntest 2\ntest 3\ntest 4\n'); + assert.strictEqual(editor.getModel()!.getValue(), 'test 1\ntest 2\ntest 3\ntest 4\n'); }); test('Snippet variable text isn\'t whitespace normalised, #31124', function () { @@ -642,7 +642,7 @@ suite('SnippetSession', function () { 'end' ].join('\n'); - assert.equal(editor.getModel()!.getValue(), expected); + assert.strictEqual(editor.getModel()!.getValue(), expected); editor.getModel()!.setValue([ 'start', @@ -665,7 +665,7 @@ suite('SnippetSession', function () { 'end' ].join('\n'); - assert.equal(editor.getModel()!.getValue(), expected); + assert.strictEqual(editor.getModel()!.getValue(), expected); }); test('Selecting text from left to right, and choosing item messes up code, #31199', function () { @@ -680,7 +680,7 @@ suite('SnippetSession', function () { editor.setSelections([new Selection(1, 9, 1, 12)]); new SnippetSession(editor, 'far', { overwriteBefore: 3, overwriteAfter: 0, adjustWhitespace: true, clipboardText: undefined, overtypingCapturer: undefined }).insert(); - assert.equal(model.getValue(), 'console.far'); + assert.strictEqual(model.getValue(), 'console.far'); }); test('Tabs don\'t get replaced with spaces in snippet transformations #103818', function () { diff --git a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts index 8e939a5ac..dff2a64be 100644 --- a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts @@ -9,11 +9,14 @@ import { Selection } from 'vs/editor/common/core/selection'; import { SelectionBasedVariableResolver, CompositeSnippetVariableResolver, ModelBasedVariableResolver, ClipboardBasedVariableResolver, TimeBasedVariableResolver, WorkspaceBasedVariableResolver } from 'vs/editor/contrib/snippet/snippetVariables'; import { SnippetParser, Variable, VariableResolver } from 'vs/editor/contrib/snippet/snippetParser'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { toWorkspaceFolders, IWorkspace, IWorkspaceContextService, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IWorkspace, IWorkspaceContextService, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ILabelService } from 'vs/platform/label/common/label'; import { mock } from 'vs/base/test/common/mock'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; +import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; +import { sep } from 'vs/base/common/path'; +import { toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces'; suite('Snippet Variables Resolver', function () { @@ -48,9 +51,9 @@ suite('Snippet Variables Resolver', function () { const variable = snippet.children[0]; variable.resolve(resolver); if (variable.children.length === 0) { - assert.equal(undefined, expected); + assert.strictEqual(undefined, expected); } else { - assert.equal(variable.toString(), expected); + assert.strictEqual(variable.toString(), expected); } } @@ -127,17 +130,17 @@ suite('Snippet Variables Resolver', function () { test('TextmateSnippet, resolve variable', function () { const snippet = new SnippetParser().parse('"$TM_CURRENT_WORD"', true); - assert.equal(snippet.toString(), '""'); + assert.strictEqual(snippet.toString(), '""'); snippet.resolveVariables(resolver); - assert.equal(snippet.toString(), '"this"'); + assert.strictEqual(snippet.toString(), '"this"'); }); test('TextmateSnippet, resolve variable with default', function () { const snippet = new SnippetParser().parse('"${TM_CURRENT_WORD:foo}"', true); - assert.equal(snippet.toString(), '"foo"'); + assert.strictEqual(snippet.toString(), '"foo"'); snippet.resolveVariables(resolver); - assert.equal(snippet.toString(), '"this"'); + assert.strictEqual(snippet.toString(), '"this"'); }); test('More useful environment variables for snippets, #32737', function () { @@ -169,14 +172,14 @@ suite('Snippet Variables Resolver', function () { .resolveVariables({ resolve(variable) { return varValue || variable.name; } }); const actual = snippet.toString(); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); } test('Variable Snippet Transform', function () { const snippet = new SnippetParser().parse('name=${TM_FILENAME/(.*)\\..+$/$1/}', true); snippet.resolveVariables(resolver); - assert.equal(snippet.toString(), 'name=text'); + assert.strictEqual(snippet.toString(), 'name=text'); assertVariableResolve2('${ThisIsAVar/([A-Z]).*(Var)/$2/}', 'Var'); assertVariableResolve2('${ThisIsAVar/([A-Z]).*(Var)/$2-${1:/downcase}/}', 'Var-t'); @@ -267,7 +270,7 @@ suite('Snippet Variables Resolver', function () { const snippet = new SnippetParser().parse(`$${varName}`); const variable = snippet.children[0]; - assert.equal(variable.resolve(resolver), true, `${varName} failed to resolve`); + assert.strictEqual(variable.resolve(resolver), true, `${varName} failed to resolve`); } test('Add time variables for snippets #41631, #43140', function () { @@ -292,10 +295,10 @@ suite('Snippet Variables Resolver', function () { const snippet = new SnippetParser().parse('${TM_LINE_NUMBER/(10)/${1:?It is:It is not}/} line 10', true); snippet.resolveVariables({ resolve() { return '10'; } }); - assert.equal(snippet.toString(), 'It is line 10'); + assert.strictEqual(snippet.toString(), 'It is line 10'); snippet.resolveVariables({ resolve() { return '11'; } }); - assert.equal(snippet.toString(), 'It is not line 10'); + assert.strictEqual(snippet.toString(), 'It is not line 10'); }); test('Add workspace name and folder variables for snippets #68261', function () { @@ -332,10 +335,55 @@ suite('Snippet Variables Resolver', function () { // workspace with config const workspaceConfigPath = URI.file('testWorkspace.code-workspace'); - workspace = new Workspace('', toWorkspaceFolders([{ path: 'folderName' }], workspaceConfigPath), workspaceConfigPath); + workspace = new Workspace('', toWorkspaceFolders([{ path: 'folderName' }], workspaceConfigPath, extUriBiasedIgnorePathCase), workspaceConfigPath); assertVariableResolve(resolver, 'WORKSPACE_NAME', 'testWorkspace'); if (!isWindows) { assertVariableResolve(resolver, 'WORKSPACE_FOLDER', '/'); } }); + + test('Add RELATIVE_FILEPATH snippet variable #114208', function () { + + let resolver: VariableResolver; + + // Mock a label service (only coded for file uris) + const workspaceLabelService = ((rootPath: string): ILabelService => { + const labelService = new class extends mock() { + getUriLabel(uri: URI, options: { relative?: boolean } = {}) { + const rootFsPath = URI.file(rootPath).fsPath + sep; + const fsPath = uri.fsPath; + if (options.relative && rootPath && fsPath.startsWith(rootFsPath)) { + return fsPath.substring(rootFsPath.length); + } + return fsPath; + } + }; + return labelService; + }); + + const model = createTextModel('', undefined, undefined, URI.parse('file:///foo/files/text.txt')); + + // empty workspace + resolver = new ModelBasedVariableResolver( + workspaceLabelService(''), + model + ); + + if (!isWindows) { + assertVariableResolve(resolver, 'RELATIVE_FILEPATH', '/foo/files/text.txt'); + } else { + assertVariableResolve(resolver, 'RELATIVE_FILEPATH', '\\foo\\files\\text.txt'); + } + + // single folder workspace + resolver = new ModelBasedVariableResolver( + workspaceLabelService('/foo'), + model + ); + if (!isWindows) { + assertVariableResolve(resolver, 'RELATIVE_FILEPATH', 'files/text.txt'); + } else { + assertVariableResolve(resolver, 'RELATIVE_FILEPATH', 'files\\text.txt'); + } + }); }); diff --git a/src/vs/editor/contrib/suggest/suggestModel.ts b/src/vs/editor/contrib/suggest/suggestModel.ts index 9a88e1ccc..747f78a4b 100644 --- a/src/vs/editor/contrib/suggest/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/suggestModel.ts @@ -443,10 +443,6 @@ export class SuggestModel implements IDisposable { this._requestToken?.dispose(); - if (this._state === State.Idle) { - return; - } - if (!this._editor.hasModel()) { return; } @@ -456,6 +452,10 @@ export class SuggestModel implements IDisposable { clipboardText = await this._clipboardService.readText(); } + if (this._state === State.Idle) { + return; + } + const model = this._editor.getModel(); let items = completions.items; diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 02f6233f2..158e38015 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -5,7 +5,6 @@ import 'vs/css!./media/suggest'; import 'vs/base/browser/ui/codicons/codiconStyles'; // The codicon symbol styles are defined here and must be loaded -import 'vs/editor/contrib/documentSymbols/outlineTree'; // The codicon symbol colors are defined here and must be loaded import * as nls from 'vs/nls'; import * as strings from 'vs/base/common/strings'; import * as dom from 'vs/base/browser/dom'; @@ -436,6 +435,7 @@ export class SuggestWidget implements IDisposable { case State.Hidden: dom.hide(this._messageElement, this._listElement, this._status.element); this._details.hide(true); + this._status.hide(); this._contentWidget.hide(); this._ctxSuggestWidgetVisible.reset(); this._ctxSuggestWidgetMultipleSuggestions.reset(); @@ -483,6 +483,7 @@ export class SuggestWidget implements IDisposable { } private _show(): void { + this._status.show(); this._contentWidget.show(); this._layout(this._persistedSize.restore()); this._ctxSuggestWidgetVisible.set(true); @@ -702,6 +703,14 @@ export class SuggestWidget implements IDisposable { this._loadingTimeout?.dispose(); this._setState(State.Hidden); this._onDidHide.fire(this); + + // ensure that a reasonable widget height is persisted so that + // accidential "resize-to-single-items" cases aren't happening + const dim = this._persistedSize.restore(); + const minPersistedHeight = Math.ceil(this.getLayoutInfo().itemHeight * 4.3); + if (dim && dim.height < minPersistedHeight) { + this._persistedSize.store(dim.with(undefined, minPersistedHeight)); + } } isFrozen(): boolean { @@ -734,12 +743,16 @@ export class SuggestWidget implements IDisposable { return; } - let height = size?.height; - let width = size?.width; - const bodyBox = dom.getClientArea(document.body); const info = this.getLayoutInfo(); + if (!size) { + size = info.defaultSize; + } + + let height = size.height; + let width = size.width; + // status bar this._status.element.style.lineHeight = `${info.itemHeight}px`; @@ -756,9 +769,6 @@ export class SuggestWidget implements IDisposable { // width math const maxWidth = bodyBox.width - info.borderHeight - 2 * info.horizontalPadding; - if (width === undefined) { - width = info.defaultSize.width; - } if (width > maxWidth) { width = maxWidth; } @@ -766,7 +776,6 @@ export class SuggestWidget implements IDisposable { // height math const fullHeight = info.statusBarHeight + this._list.contentHeight + info.borderHeight; - const preferredHeight = info.defaultSize.height; const minHeight = info.itemHeight + info.statusBarHeight; const editorBox = dom.getDomNodePagePosition(this.editor.getDomNode()); const cursorBox = this.editor.getScrolledVisiblePosition(this.editor.getPosition()); @@ -775,15 +784,12 @@ export class SuggestWidget implements IDisposable { const maxHeightAbove = Math.min(editorBox.top + cursorBox.top - info.verticalPadding, fullHeight); let maxHeight = Math.min(Math.max(maxHeightAbove, maxHeightBelow) + info.borderHeight, fullHeight); - if (height && height === this._cappedHeight?.capped) { + if (height === this._cappedHeight?.capped) { // Restore the old (wanted) height when the current // height is capped to fit height = this._cappedHeight.wanted; } - if (height === undefined) { - height = Math.min(preferredHeight, fullHeight); - } if (height < minHeight) { height = minHeight; } @@ -801,14 +807,14 @@ export class SuggestWidget implements IDisposable { this.element.enableSashes(false, true, true, false); maxHeight = maxHeightBelow; } - this.element.preferredSize = new dom.Dimension(preferredWidth, preferredHeight); + this.element.preferredSize = new dom.Dimension(preferredWidth, info.defaultSize.height); this.element.maxSize = new dom.Dimension(maxWidth, maxHeight); this.element.minSize = new dom.Dimension(220, minHeight); // Know when the height was capped to fit and remember // the wanted height for later. This is required when going // left to widen suggestions. - this._cappedHeight = size && height === fullHeight + this._cappedHeight = height === fullHeight ? { wanted: this._cappedHeight?.wanted ?? size.height, capped: height } : undefined; } diff --git a/src/vs/editor/contrib/suggest/suggestWidgetRenderer.ts b/src/vs/editor/contrib/suggest/suggestWidgetRenderer.ts index 26cdc77ae..71098fe06 100644 --- a/src/vs/editor/contrib/suggest/suggestWidgetRenderer.ts +++ b/src/vs/editor/contrib/suggest/suggestWidgetRenderer.ts @@ -117,7 +117,7 @@ export class ItemRenderer implements IListRenderer(action => { - return action instanceof MenuItemAction - ? instantiationService.createInstance(StatusBarViewItem, action) - : undefined; + return action instanceof MenuItemAction ? instantiationService.createInstance(StatusBarViewItem, action) : undefined; }); - const leftActions = new ActionBar(this.element, { actionViewItemProvider }); - const rightActions = new ActionBar(this.element, { actionViewItemProvider }); - const menu = menuService.createMenu(suggestWidgetStatusbarMenu, contextKeyService); + this._leftActions = new ActionBar(this.element, { actionViewItemProvider }); + this._rightActions = new ActionBar(this.element, { actionViewItemProvider }); - leftActions.domNode.classList.add('left'); - rightActions.domNode.classList.add('right'); + this._leftActions.domNode.classList.add('left'); + this._rightActions.domNode.classList.add('right'); + } + dispose(): void { + this._menuDisposables.dispose(); + this.element.remove(); + } + + show(): void { + const menu = this._menuService.createMenu(suggestWidgetStatusbarMenu, this._contextKeyService); const renderMenu = () => { const left: IAction[] = []; const right: IAction[] = []; @@ -68,17 +75,16 @@ export class SuggestWidgetStatus { right.push(...actions); } } - leftActions.clear(); - leftActions.push(left); - rightActions.clear(); - rightActions.push(right); + this._leftActions.clear(); + this._leftActions.push(left); + this._rightActions.clear(); + this._rightActions.push(right); }; - this._disposables.add(menu.onDidChange(() => renderMenu())); - this._disposables.add(menu); + this._menuDisposables.add(menu.onDidChange(() => renderMenu())); + this._menuDisposables.add(menu); } - dispose(): void { - this._disposables.dispose(); - this.element.remove(); + hide(): void { + this._menuDisposables.clear(); } } diff --git a/src/vs/editor/contrib/suggest/test/suggestController.test.ts b/src/vs/editor/contrib/suggest/test/suggestController.test.ts index 8644d4498..491a75739 100644 --- a/src/vs/editor/contrib/suggest/test/suggestController.test.ts +++ b/src/vs/editor/contrib/suggest/test/suggestController.test.ts @@ -57,6 +57,7 @@ suite('SuggestController', function () { createMenu() { return new class extends mock() { onDidChange = Event.None; + dispose() { } }; } }] diff --git a/src/vs/editor/contrib/suggest/test/suggestModel.test.ts b/src/vs/editor/contrib/suggest/test/suggestModel.test.ts index 3193c6787..ce99f9758 100644 --- a/src/vs/editor/contrib/suggest/test/suggestModel.test.ts +++ b/src/vs/editor/contrib/suggest/test/suggestModel.test.ts @@ -71,7 +71,7 @@ suite('SuggestModel - Context', function () { this._register(TokenizationRegistry.register(this.getLanguageIdentifier().language, { getInitialState: (): IState => NULL_STATE, tokenize: undefined!, - tokenize2: (line: string, state: IState): TokenizationResult2 => { + tokenize2: (line: string, hasEOL: boolean, state: IState): TokenizationResult2 => { const tokensArr: number[] = []; let prevLanguageId: LanguageIdentifier | undefined = undefined; for (let i = 0; i < line.length; i++) { diff --git a/src/vs/editor/contrib/viewportSemanticTokens/viewportSemanticTokens.ts b/src/vs/editor/contrib/viewportSemanticTokens/viewportSemanticTokens.ts index 44ca58390..43fdcc903 100644 --- a/src/vs/editor/contrib/viewportSemanticTokens/viewportSemanticTokens.ts +++ b/src/vs/editor/contrib/viewportSemanticTokens/viewportSemanticTokens.ts @@ -16,6 +16,7 @@ import { toMultilineTokens2, SemanticTokensProviderStyling } from 'vs/editor/com import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { isSemanticColoringEnabled, SEMANTIC_HIGHLIGHTING_SETTING_ID } from 'vs/editor/common/services/modelServiceImpl'; +import { getDocumentRangeSemanticTokensProvider } from 'vs/editor/common/services/getSemanticTokens'; class ViewportSemanticTokensContribution extends Disposable implements IEditorContribution { @@ -66,11 +67,6 @@ class ViewportSemanticTokensContribution extends Disposable implements IEditorCo })); } - private static _getSemanticColoringProvider(model: ITextModel): DocumentRangeSemanticTokensProvider | null { - const result = DocumentRangeSemanticTokensProviderRegistry.ordered(model); - return (result.length > 0 ? result[0] : null); - } - private _cancelAll(): void { for (const request of this._outstandingRequests) { request.cancel(); @@ -101,7 +97,7 @@ class ViewportSemanticTokensContribution extends Disposable implements IEditorCo } return; } - const provider = ViewportSemanticTokensContribution._getSemanticColoringProvider(model); + const provider = getDocumentRangeSemanticTokensProvider(model); if (!provider) { if (model.hasSomeSemanticTokens()) { model.setSemanticTokens(null, false); diff --git a/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts b/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts index 49433e1f6..ee2eb0738 100644 --- a/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts +++ b/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts @@ -110,7 +110,7 @@ suite('WordOperations', () => { ed => ed.getPosition()!.equals(new Position(1, 1)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('cursorWordLeft - with selection', () => { @@ -123,7 +123,7 @@ suite('WordOperations', () => { ], {}, (editor) => { editor.setPosition(new Position(5, 2)); cursorWordLeft(editor, true); - assert.deepEqual(editor.getSelection(), new Selection(5, 2, 5, 1)); + assert.deepStrictEqual(editor.getSelection(), new Selection(5, 2, 5, 1)); }); }); @@ -138,7 +138,7 @@ suite('WordOperations', () => { ed => ed.getPosition()!.equals(new Position(1, 1)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('cursorWordLeft - issue #48046: Word selection doesn\'t work as usual', () => { @@ -154,7 +154,7 @@ suite('WordOperations', () => { ed => ed.getPosition()!.equals(new Position(1, 1)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('cursorWordLeftSelect - issue #74369: cursorWordLeft and cursorWordLeftSelect do not behave consistently', () => { @@ -170,7 +170,7 @@ suite('WordOperations', () => { ed => ed.getPosition()!.equals(new Position(1, 1)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('cursorWordStartLeft', () => { @@ -185,7 +185,7 @@ suite('WordOperations', () => { ed => ed.getPosition()!.equals(new Position(1, 1)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('cursorWordStartLeft - issue #51119: regression makes VS compatibility impossible', () => { @@ -200,7 +200,7 @@ suite('WordOperations', () => { ed => ed.getPosition()!.equals(new Position(1, 1)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('issue #51275 - cursorWordStartLeft does not push undo/redo stack element', () => { @@ -212,16 +212,16 @@ suite('WordOperations', () => { withTestCodeEditor('', {}, (editor, viewModel) => { type(viewModel, 'foo bar baz'); - assert.equal(editor.getValue(), 'foo bar baz'); + assert.strictEqual(editor.getValue(), 'foo bar baz'); cursorWordStartLeft(editor); cursorWordStartLeft(editor); type(viewModel, 'q'); - assert.equal(editor.getValue(), 'foo qbar baz'); + assert.strictEqual(editor.getValue(), 'foo qbar baz'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(editor.getValue(), 'foo bar baz'); + assert.strictEqual(editor.getValue(), 'foo bar baz'); }); }); @@ -236,7 +236,7 @@ suite('WordOperations', () => { ed => ed.getPosition()!.equals(new Position(1, 1)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('cursorWordRight - simple', () => { @@ -256,7 +256,7 @@ suite('WordOperations', () => { ed => ed.getPosition()!.equals(new Position(5, 2)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('cursorWordRight - selection', () => { @@ -269,7 +269,7 @@ suite('WordOperations', () => { ], {}, (editor, _) => { editor.setPosition(new Position(1, 1)); cursorWordRight(editor, true); - assert.deepEqual(editor.getSelection(), new Selection(1, 1, 1, 8)); + assert.deepStrictEqual(editor.getSelection(), new Selection(1, 1, 1, 8)); }); }); @@ -286,7 +286,7 @@ suite('WordOperations', () => { ed => ed.getPosition()!.equals(new Position(1, 50)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('cursorWordRight - issue #41199', () => { @@ -302,7 +302,7 @@ suite('WordOperations', () => { ed => ed.getPosition()!.equals(new Position(1, 17)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('moveWordEndRight', () => { @@ -318,7 +318,7 @@ suite('WordOperations', () => { ed => ed.getPosition()!.equals(new Position(1, 50)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('moveWordStartRight', () => { @@ -335,7 +335,7 @@ suite('WordOperations', () => { ed => ed.getPosition()!.equals(new Position(1, 50)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('issue #51119: cursorWordStartRight regression makes VS compatibility impossible', () => { @@ -350,7 +350,7 @@ suite('WordOperations', () => { ed => ed.getPosition()!.equals(new Position(1, 15)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('issue #64810: cursorWordStartRight skips first word after newline', () => { @@ -365,7 +365,7 @@ suite('WordOperations', () => { ed => ed.getPosition()!.equals(new Position(2, 12)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('cursorWordAccessibilityLeft', () => { @@ -379,7 +379,7 @@ suite('WordOperations', () => { ed => ed.getPosition()!.equals(new Position(1, 1)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('cursorWordAccessibilityRight', () => { @@ -393,7 +393,7 @@ suite('WordOperations', () => { ed => ed.getPosition()!.equals(new Position(1, 50)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('deleteWordLeft for non-empty selection', () => { @@ -407,8 +407,8 @@ suite('WordOperations', () => { const model = editor.getModel()!; editor.setSelection(new Selection(3, 7, 3, 9)); deleteWordLeft(editor); - assert.equal(model.getLineContent(3), ' Thd Line🐶'); - assert.deepEqual(editor.getPosition(), new Position(3, 7)); + assert.strictEqual(model.getLineContent(3), ' Thd Line🐶'); + assert.deepStrictEqual(editor.getPosition(), new Position(3, 7)); }); }); @@ -423,8 +423,8 @@ suite('WordOperations', () => { const model = editor.getModel()!; editor.setPosition(new Position(1, 1)); deleteWordLeft(editor); - assert.equal(model.getLineContent(1), ' \tMy First Line\t '); - assert.deepEqual(editor.getPosition(), new Position(1, 1)); + assert.strictEqual(model.getLineContent(1), ' \tMy First Line\t '); + assert.deepStrictEqual(editor.getPosition(), new Position(1, 1)); }); }); @@ -439,8 +439,8 @@ suite('WordOperations', () => { const model = editor.getModel()!; editor.setPosition(new Position(3, 11)); deleteWordLeft(editor); - assert.equal(model.getLineContent(3), ' Line🐶'); - assert.deepEqual(editor.getPosition(), new Position(3, 5)); + assert.strictEqual(model.getLineContent(3), ' Line🐶'); + assert.deepStrictEqual(editor.getPosition(), new Position(3, 5)); }); }); @@ -455,8 +455,8 @@ suite('WordOperations', () => { const model = editor.getModel()!; editor.setPosition(new Position(2, 11)); deleteWordLeft(editor); - assert.equal(model.getLineContent(2), '\tMy Line'); - assert.deepEqual(editor.getPosition(), new Position(2, 5)); + assert.strictEqual(model.getLineContent(2), '\tMy Line'); + assert.deepStrictEqual(editor.getPosition(), new Position(2, 5)); }); }); @@ -471,8 +471,8 @@ suite('WordOperations', () => { const model = editor.getModel()!; editor.setPosition(new Position(1, 12)); deleteWordLeft(editor); - assert.equal(model.getLineContent(1), ' \tMy st Line\t '); - assert.deepEqual(editor.getPosition(), new Position(1, 9)); + assert.strictEqual(model.getLineContent(1), ' \tMy st Line\t '); + assert.deepStrictEqual(editor.getPosition(), new Position(1, 9)); }); }); @@ -487,8 +487,8 @@ suite('WordOperations', () => { const model = editor.getModel()!; editor.setSelection(new Selection(3, 7, 3, 9)); deleteWordRight(editor); - assert.equal(model.getLineContent(3), ' Thd Line🐶'); - assert.deepEqual(editor.getPosition(), new Position(3, 7)); + assert.strictEqual(model.getLineContent(3), ' Thd Line🐶'); + assert.deepStrictEqual(editor.getPosition(), new Position(3, 7)); }); }); @@ -503,8 +503,8 @@ suite('WordOperations', () => { const model = editor.getModel()!; editor.setPosition(new Position(5, 3)); deleteWordRight(editor); - assert.equal(model.getLineContent(5), '1'); - assert.deepEqual(editor.getPosition(), new Position(5, 2)); + assert.strictEqual(model.getLineContent(5), '1'); + assert.deepStrictEqual(editor.getPosition(), new Position(5, 2)); }); }); @@ -519,8 +519,8 @@ suite('WordOperations', () => { const model = editor.getModel()!; editor.setPosition(new Position(3, 1)); deleteWordRight(editor); - assert.equal(model.getLineContent(3), 'Third Line🐶'); - assert.deepEqual(editor.getPosition(), new Position(3, 1)); + assert.strictEqual(model.getLineContent(3), 'Third Line🐶'); + assert.deepStrictEqual(editor.getPosition(), new Position(3, 1)); }); }); @@ -535,8 +535,8 @@ suite('WordOperations', () => { const model = editor.getModel()!; editor.setPosition(new Position(2, 5)); deleteWordRight(editor); - assert.equal(model.getLineContent(2), '\tMy Line'); - assert.deepEqual(editor.getPosition(), new Position(2, 5)); + assert.strictEqual(model.getLineContent(2), '\tMy Line'); + assert.deepStrictEqual(editor.getPosition(), new Position(2, 5)); }); }); @@ -551,8 +551,8 @@ suite('WordOperations', () => { const model = editor.getModel()!; editor.setPosition(new Position(1, 11)); deleteWordRight(editor); - assert.equal(model.getLineContent(1), ' \tMy Fi Line\t '); - assert.deepEqual(editor.getPosition(), new Position(1, 11)); + assert.strictEqual(model.getLineContent(1), ' \tMy Fi Line\t '); + assert.deepStrictEqual(editor.getPosition(), new Position(1, 11)); }); }); @@ -569,7 +569,7 @@ suite('WordOperations', () => { ed => ed.getValue().length === 0 ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('deleteWordStartLeft', () => { @@ -585,7 +585,7 @@ suite('WordOperations', () => { ed => ed.getValue().length === 0 ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('deleteWordEndLeft', () => { @@ -601,7 +601,7 @@ suite('WordOperations', () => { ed => ed.getValue().length === 0 ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('deleteWordLeft - issue #24947', () => { @@ -611,7 +611,7 @@ suite('WordOperations', () => { ], {}, (editor, _) => { const model = editor.getModel()!; editor.setPosition(new Position(2, 1)); - deleteWordLeft(editor); assert.equal(model.getLineContent(1), '{}'); + deleteWordLeft(editor); assert.strictEqual(model.getLineContent(1), '{}'); }); withTestCodeEditor([ @@ -620,7 +620,7 @@ suite('WordOperations', () => { ], {}, (editor, _) => { const model = editor.getModel()!; editor.setPosition(new Position(2, 1)); - deleteWordStartLeft(editor); assert.equal(model.getLineContent(1), '{}'); + deleteWordStartLeft(editor); assert.strictEqual(model.getLineContent(1), '{}'); }); withTestCodeEditor([ @@ -629,7 +629,7 @@ suite('WordOperations', () => { ], {}, (editor, _) => { const model = editor.getModel()!; editor.setPosition(new Position(2, 1)); - deleteWordEndLeft(editor); assert.equal(model.getLineContent(1), '{}'); + deleteWordEndLeft(editor); assert.strictEqual(model.getLineContent(1), '{}'); }); }); @@ -644,7 +644,7 @@ suite('WordOperations', () => { ed => ed.getValue().length === 0 ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('deleteWordRight - issue #3882', () => { @@ -654,7 +654,7 @@ suite('WordOperations', () => { ], {}, (editor, _) => { const model = editor.getModel()!; editor.setPosition(new Position(1, 24)); - deleteWordRight(editor); assert.equal(model.getLineContent(1), 'public void Add( int x,int y )', '001'); + deleteWordRight(editor); assert.strictEqual(model.getLineContent(1), 'public void Add( int x,int y )', '001'); }); }); @@ -665,7 +665,7 @@ suite('WordOperations', () => { ], {}, (editor, _) => { const model = editor.getModel()!; editor.setPosition(new Position(1, 24)); - deleteWordStartRight(editor); assert.equal(model.getLineContent(1), 'public void Add( int x,int y )', '001'); + deleteWordStartRight(editor); assert.strictEqual(model.getLineContent(1), 'public void Add( int x,int y )', '001'); }); }); @@ -676,7 +676,7 @@ suite('WordOperations', () => { ], {}, (editor, _) => { const model = editor.getModel()!; editor.setPosition(new Position(1, 24)); - deleteWordEndRight(editor); assert.equal(model.getLineContent(1), 'public void Add( int x,int y )', '001'); + deleteWordEndRight(editor); assert.strictEqual(model.getLineContent(1), 'public void Add( int x,int y )', '001'); }); }); @@ -691,7 +691,7 @@ suite('WordOperations', () => { ed => ed.getValue().length === 0 ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('deleteWordEndRight', () => { @@ -705,7 +705,7 @@ suite('WordOperations', () => { ed => ed.getValue().length === 0 ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('deleteWordRight - issue #3882 (1): Ctrl+Delete removing entire line when used at the end of line', () => { @@ -715,7 +715,7 @@ suite('WordOperations', () => { ], {}, (editor, _) => { const model = editor.getModel()!; editor.setPosition(new Position(1, 18)); - deleteWordRight(editor); assert.equal(model.getLineContent(1), 'A line with text.And another one', '001'); + deleteWordRight(editor); assert.strictEqual(model.getLineContent(1), 'A line with text.And another one', '001'); }); }); @@ -726,7 +726,7 @@ suite('WordOperations', () => { ], {}, (editor, _) => { const model = editor.getModel()!; editor.setPosition(new Position(2, 1)); - deleteWordLeft(editor); assert.equal(model.getLineContent(1), 'A line with text. And another one', '001'); + deleteWordLeft(editor); assert.strictEqual(model.getLineContent(1), 'A line with text. And another one', '001'); }); }); @@ -748,7 +748,7 @@ suite('WordOperations', () => { withTestCodeEditor(null, { model }, (editor, _) => { editor.setPosition(new Position(1, 4)); - deleteWordLeft(editor); assert.equal(model.getLineContent(1), 'a '); + deleteWordLeft(editor); assert.strictEqual(model.getLineContent(1), 'a '); }); model.dispose(); @@ -764,7 +764,7 @@ suite('WordOperations', () => { const model = editor.getModel()!; editor.setPosition(new Position(2, 1)); deleteInsideWord(editor); - assert.equal(model.getValue(), 'Line1\nLine2'); + assert.strictEqual(model.getValue(), 'Line1\nLine2'); }); }); @@ -775,7 +775,7 @@ suite('WordOperations', () => { const model = editor.getModel()!; editor.setPosition(new Position(1, 6)); deleteInsideWord(editor); - assert.equal(model.getValue(), 'Justsome text.'); + assert.strictEqual(model.getValue(), 'Justsome text.'); }); }); @@ -786,7 +786,7 @@ suite('WordOperations', () => { const model = editor.getModel()!; editor.setPosition(new Position(1, 6)); deleteInsideWord(editor); - assert.equal(model.getValue(), 'Justsome text.'); + assert.strictEqual(model.getValue(), 'Justsome text.'); }); }); @@ -797,19 +797,19 @@ suite('WordOperations', () => { const model = editor.getModel()!; editor.setPosition(new Position(1, 6)); deleteInsideWord(editor); - assert.equal(model.getValue(), 'Just"some text.'); + assert.strictEqual(model.getValue(), 'Just"some text.'); deleteInsideWord(editor); - assert.equal(model.getValue(), '"some text.'); + assert.strictEqual(model.getValue(), '"some text.'); deleteInsideWord(editor); - assert.equal(model.getValue(), 'some text.'); + assert.strictEqual(model.getValue(), 'some text.'); deleteInsideWord(editor); - assert.equal(model.getValue(), 'text.'); + assert.strictEqual(model.getValue(), 'text.'); deleteInsideWord(editor); - assert.equal(model.getValue(), '.'); + assert.strictEqual(model.getValue(), '.'); deleteInsideWord(editor); - assert.equal(model.getValue(), ''); + assert.strictEqual(model.getValue(), ''); deleteInsideWord(editor); - assert.equal(model.getValue(), ''); + assert.strictEqual(model.getValue(), ''); }); }); @@ -820,19 +820,19 @@ suite('WordOperations', () => { const model = editor.getModel()!; editor.setPosition(new Position(1, 7)); deleteInsideWord(editor); - assert.equal(model.getValue(), 'x=3+45+6'); + assert.strictEqual(model.getValue(), 'x=3+45+6'); deleteInsideWord(editor); - assert.equal(model.getValue(), 'x=3++6'); + assert.strictEqual(model.getValue(), 'x=3++6'); deleteInsideWord(editor); - assert.equal(model.getValue(), 'x=36'); + assert.strictEqual(model.getValue(), 'x=36'); deleteInsideWord(editor); - assert.equal(model.getValue(), 'x='); + assert.strictEqual(model.getValue(), 'x='); deleteInsideWord(editor); - assert.equal(model.getValue(), 'x'); + assert.strictEqual(model.getValue(), 'x'); deleteInsideWord(editor); - assert.equal(model.getValue(), ''); + assert.strictEqual(model.getValue(), ''); deleteInsideWord(editor); - assert.equal(model.getValue(), ''); + assert.strictEqual(model.getValue(), ''); }); }); @@ -843,13 +843,13 @@ suite('WordOperations', () => { const model = editor.getModel()!; editor.setPosition(new Position(1, 7)); deleteInsideWord(editor); - assert.equal(model.getValue(), 'This interesting'); + assert.strictEqual(model.getValue(), 'This interesting'); deleteInsideWord(editor); - assert.equal(model.getValue(), 'This'); + assert.strictEqual(model.getValue(), 'This'); deleteInsideWord(editor); - assert.equal(model.getValue(), ''); + assert.strictEqual(model.getValue(), ''); deleteInsideWord(editor); - assert.equal(model.getValue(), ''); + assert.strictEqual(model.getValue(), ''); }); }); @@ -860,13 +860,13 @@ suite('WordOperations', () => { const model = editor.getModel()!; editor.setPosition(new Position(1, 7)); deleteInsideWord(editor); - assert.equal(model.getValue(), 'This interesting'); + assert.strictEqual(model.getValue(), 'This interesting'); deleteInsideWord(editor); - assert.equal(model.getValue(), 'This'); + assert.strictEqual(model.getValue(), 'This'); deleteInsideWord(editor); - assert.equal(model.getValue(), ''); + assert.strictEqual(model.getValue(), ''); deleteInsideWord(editor); - assert.equal(model.getValue(), ''); + assert.strictEqual(model.getValue(), ''); }); }); }); diff --git a/src/vs/editor/contrib/wordOperations/wordOperations.ts b/src/vs/editor/contrib/wordOperations/wordOperations.ts index 82dcbddc5..ac3f987de 100644 --- a/src/vs/editor/contrib/wordOperations/wordOperations.ts +++ b/src/vs/editor/contrib/wordOperations/wordOperations.ts @@ -55,7 +55,7 @@ export abstract class MoveWordCommand extends EditorCommand { }); model.pushStackElement(); - editor._getViewModel().setCursorStates('moveWordCommand', CursorChangeReason.NotSet, result.map(r => CursorState.fromModelSelection(r))); + editor._getViewModel().setCursorStates('moveWordCommand', CursorChangeReason.Explicit, result.map(r => CursorState.fromModelSelection(r))); if (result.length === 1) { const pos = new Position(result[0].positionLineNumber, result[0].positionColumn); editor.revealPosition(pos, ScrollType.Smooth); diff --git a/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts b/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts index 70e9c87c6..446711f2a 100644 --- a/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts +++ b/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts @@ -49,7 +49,7 @@ suite('WordPartOperations', () => { ed => ed.getPosition()!.equals(new Position(1, 1)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('cursorWordPartLeft - issue #53899: whitespace', () => { @@ -63,7 +63,7 @@ suite('WordPartOperations', () => { ed => ed.getPosition()!.equals(new Position(1, 1)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('cursorWordPartLeft - issue #53899: underscores', () => { @@ -77,7 +77,7 @@ suite('WordPartOperations', () => { ed => ed.getPosition()!.equals(new Position(1, 1)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('cursorWordPartRight - basic', () => { @@ -95,7 +95,7 @@ suite('WordPartOperations', () => { ed => ed.getPosition()!.equals(new Position(3, 9)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('cursorWordPartRight - issue #53899: whitespace', () => { @@ -109,7 +109,7 @@ suite('WordPartOperations', () => { ed => ed.getPosition()!.equals(new Position(1, 52)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('cursorWordPartRight - issue #53899: underscores', () => { @@ -123,7 +123,7 @@ suite('WordPartOperations', () => { ed => ed.getPosition()!.equals(new Position(1, 52)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('cursorWordPartRight - issue #53899: second case', () => { @@ -142,7 +142,7 @@ suite('WordPartOperations', () => { ed => ed.getPosition()!.equals(new Position(4, 7)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('issue #93239 - cursorWordPartRight', () => { @@ -158,7 +158,7 @@ suite('WordPartOperations', () => { ed => ed.getPosition()!.equals(new Position(1, 8)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('issue #93239 - cursorWordPartLeft', () => { @@ -174,7 +174,7 @@ suite('WordPartOperations', () => { ed => ed.getPosition()!.equals(new Position(1, 1)) ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('deleteWordPartLeft - basic', () => { @@ -188,7 +188,7 @@ suite('WordPartOperations', () => { ed => ed.getValue().length === 0 ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); test('deleteWordPartRight - basic', () => { @@ -202,6 +202,6 @@ suite('WordPartOperations', () => { ed => ed.getValue().length === 0 ); const actual = serializePipePositions(text, actualStops); - assert.deepEqual(actual, EXPECTED); + assert.deepStrictEqual(actual, EXPECTED); }); }); diff --git a/src/vs/editor/editor.all.ts b/src/vs/editor/editor.all.ts index 904283213..9b4870b45 100644 --- a/src/vs/editor/editor.all.ts +++ b/src/vs/editor/editor.all.ts @@ -23,12 +23,13 @@ import 'vs/editor/contrib/find/findController'; import 'vs/editor/contrib/folding/folding'; import 'vs/editor/contrib/fontZoom/fontZoom'; import 'vs/editor/contrib/format/formatActions'; -import 'vs/editor/contrib/gotoSymbol/documentSymbols'; +import 'vs/editor/contrib/documentSymbols/documentSymbols'; import 'vs/editor/contrib/gotoSymbol/goToCommands'; import 'vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition'; import 'vs/editor/contrib/gotoError/gotoError'; import 'vs/editor/contrib/hover/hover'; import 'vs/editor/contrib/indentation/indentation'; +import 'vs/editor/contrib/inlineHints/inlineHintsController'; import 'vs/editor/contrib/inPlaceReplace/inPlaceReplace'; import 'vs/editor/contrib/linesOperations/linesOperations'; import 'vs/editor/contrib/linkedEditing/linkedEditing'; diff --git a/src/vs/editor/editor.api.ts b/src/vs/editor/editor.api.ts index 0e4c48c34..6b2cc6c04 100644 --- a/src/vs/editor/editor.api.ts +++ b/src/vs/editor/editor.api.ts @@ -7,6 +7,8 @@ import { EditorOptions, WrappingIndent, EditorAutoIndentStrategy } from 'vs/edit import { createMonacoBaseAPI } from 'vs/editor/common/standalone/standaloneBase'; import { createMonacoEditorAPI } from 'vs/editor/standalone/browser/standaloneEditor'; import { createMonacoLanguagesAPI } from 'vs/editor/standalone/browser/standaloneLanguages'; +import { globals } from 'vs/base/common/platform'; +import { FormattingConflicts } from 'vs/editor/contrib/format/format'; // Set defaults for standalone editor EditorOptions.wrappingIndent.defaultValue = WrappingIndent.None; @@ -14,6 +16,10 @@ EditorOptions.glyphMargin.defaultValue = false; EditorOptions.autoIndent.defaultValue = EditorAutoIndentStrategy.Advanced; EditorOptions.overviewRulerLanes.defaultValue = 2; +// We need to register a formatter selector which simply picks the first available formatter. +// See https://github.com/microsoft/monaco-editor/issues/2327 +FormattingConflicts.setFormatterSelector((formatter, document, mode) => Promise.resolve(formatter[0])); + const api = createMonacoBaseAPI(); api.editor = createMonacoEditorAPI(); api.languages = createMonacoLanguagesAPI(); @@ -32,7 +38,9 @@ export const Token = api.Token; export const editor = api.editor; export const languages = api.languages; -self.monaco = api; +if (globals.MonacoEnvironment?.globalAPI || globals.define?.amd) { + self.monaco = api; +} if (typeof self.require !== 'undefined' && typeof self.require.config === 'function') { self.require.config({ diff --git a/src/vs/editor/standalone/browser/colorizer.ts b/src/vs/editor/standalone/browser/colorizer.ts index 22bad13e9..f9d335696 100644 --- a/src/vs/editor/standalone/browser/colorizer.ts +++ b/src/vs/editor/standalone/browser/colorizer.ts @@ -42,8 +42,8 @@ export class Colorizer { let text = domNode.firstChild ? domNode.firstChild.nodeValue : ''; domNode.className += ' ' + theme; let render = (str: string) => { - const trustedhtml = ttPolicy ? ttPolicy.createHTML(str) : str; - domNode.innerHTML = trustedhtml as unknown as string; + const trustedhtml = ttPolicy?.createHTML(str) ?? str; + domNode.innerHTML = trustedhtml as string; }; return this.colorize(modeService, text || '', mimeType, options).then(render, (err) => console.error(err)); } @@ -222,7 +222,7 @@ function _actualColorize(lines: string[], tabSize: number, tokenizationSupport: for (let i = 0, length = lines.length; i < length; i++) { let line = lines[i]; - let tokenizeResult = tokenizationSupport.tokenize2(line, state, 0); + let tokenizeResult = tokenizationSupport.tokenize2(line, true, state, 0); LineTokens.convertToEndOffset(tokenizeResult.tokens, line.length); let lineTokens = new LineTokens(tokenizeResult.tokens, line); const isBasicASCII = ViewLineRenderingData.isBasicASCII(line, /* check for basic ASCII */true); diff --git a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts index 192f92185..88b54c867 100644 --- a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts +++ b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts @@ -137,8 +137,8 @@ function getSafeTokenizationSupport(languageIdentifier: LanguageIdentifier): ITo } return { getInitialState: () => NULL_STATE, - tokenize: (line: string, state: IState, deltaOffset: number) => nullTokenize(languageIdentifier.language, line, state, deltaOffset), - tokenize2: (line: string, state: IState, deltaOffset: number) => nullTokenize2(languageIdentifier.id, line, state, deltaOffset) + tokenize: (line: string, hasEOL: boolean, state: IState, deltaOffset: number) => nullTokenize(languageIdentifier.language, line, state, deltaOffset), + tokenize2: (line: string, hasEOL: boolean, state: IState, deltaOffset: number) => nullTokenize2(languageIdentifier.id, line, state, deltaOffset) }; } @@ -293,8 +293,8 @@ class InspectTokensWidget extends Disposable implements IContentWidget { private _getTokensAtLine(lineNumber: number): ICompleteLineTokenization { let stateBeforeLine = this._getStateBeforeLine(lineNumber); - let tokenizationResult1 = this._tokenizationSupport.tokenize(this._model.getLineContent(lineNumber), stateBeforeLine, 0); - let tokenizationResult2 = this._tokenizationSupport.tokenize2(this._model.getLineContent(lineNumber), stateBeforeLine, 0); + let tokenizationResult1 = this._tokenizationSupport.tokenize(this._model.getLineContent(lineNumber), true, stateBeforeLine, 0); + let tokenizationResult2 = this._tokenizationSupport.tokenize2(this._model.getLineContent(lineNumber), true, stateBeforeLine, 0); return { startState: stateBeforeLine, @@ -308,7 +308,7 @@ class InspectTokensWidget extends Disposable implements IContentWidget { let state: IState = this._tokenizationSupport.getInitialState(); for (let i = 1; i < lineNumber; i++) { - let tokenizationResult = this._tokenizationSupport.tokenize(this._model.getLineContent(i), state, 0); + let tokenizationResult = this._tokenizationSupport.tokenize(this._model.getLineContent(i), true, state, 0); state = tokenizationResult.endState; } diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index ba51dfd9e..3410b5860 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -176,8 +176,8 @@ export class SimpleEditorProgressService implements IEditorProgressService { return SimpleEditorProgressService.NULL_PROGRESS_RUNNER; } - showWhile(promise: Promise, delay?: number): Promise { - return Promise.resolve(undefined); + async showWhile(promise: Promise, delay?: number): Promise { + await promise; } } @@ -638,12 +638,12 @@ export class SimpleWorkspaceContextService implements IWorkspaceContextService { return resource && resource.scheme === SimpleWorkspaceContextService.SCHEME; } - public isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean { + public isCurrentWorkspace(workspaceIdOrFolder: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI): boolean { return true; } } -export function applyConfigurationValues(configurationService: IConfigurationService, source: any, isDiffEditor: boolean): void { +export function updateConfigurationService(configurationService: IConfigurationService, source: any, isDiffEditor: boolean): void { if (!source) { return; } @@ -736,7 +736,7 @@ export class SimpleUriLabelService implements ILabelService { return basename(resource); } - public getWorkspaceLabel(workspace: IWorkspaceIdentifier | URI | IWorkspace, options?: { verbose: boolean; }): string { + public getWorkspaceLabel(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI | IWorkspace, options?: { verbose: boolean; }): string { return ''; } diff --git a/src/vs/editor/standalone/browser/standalone-tokens.css b/src/vs/editor/standalone/browser/standalone-tokens.css index e97572e05..174273006 100644 --- a/src/vs/editor/standalone/browser/standalone-tokens.css +++ b/src/vs/editor/standalone/browser/standalone-tokens.css @@ -23,6 +23,19 @@ margin: 0; } +/* +In certain cases, the default positioning of the aria container (left: -999em) can cause scrollbars to appear. +So here we try to avoid that by using a different technique. See https://stackoverflow.com/a/26032207 +*/ +.monaco-aria-container { + position: absolute !important; + height: 1px; + width: 1px; + left: inherit !important; + overflow: hidden; + clip: rect(1px, 1px, 1px, 1px); +} + /* The hc-black theme is already high contrast optimized */ .monaco-editor.hc-black { -ms-high-contrast-adjust: none; diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 9344ee801..4e3c939d2 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -14,7 +14,7 @@ import { InternalEditorAction } from 'vs/editor/common/editorAction'; import { IModelChangedEvent } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; -import { StandaloneKeybindingService, applyConfigurationValues } from 'vs/editor/standalone/browser/simpleServices'; +import { StandaloneKeybindingService, updateConfigurationService } from 'vs/editor/standalone/browser/simpleServices'; import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; import { IMenuItem, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; @@ -31,6 +31,9 @@ import { StandaloneCodeEditorNLS } from 'vs/editor/common/standaloneStrings'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { StandaloneThemeServiceImpl } from 'vs/editor/standalone/browser/standaloneThemeServiceImpl'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { ILanguageSelection, IModeService } from 'vs/editor/common/services/modeService'; +import { URI } from 'vs/base/common/uri'; /** * Description of an action contribution @@ -227,7 +230,7 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon constructor( domElement: HTMLElement, - options: IStandaloneEditorConstructionOptions, + _options: Readonly, @IInstantiationService instantiationService: IInstantiationService, @ICodeEditorService codeEditorService: ICodeEditorService, @ICommandService commandService: ICommandService, @@ -237,7 +240,7 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon @INotificationService notificationService: INotificationService, @IAccessibilityService accessibilityService: IAccessibilityService ) { - options = options || {}; + 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); @@ -353,7 +356,7 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon constructor( domElement: HTMLElement, - options: IStandaloneEditorConstructionOptions | undefined, + _options: Readonly | undefined, toDispose: IDisposable, @IInstantiationService instantiationService: IInstantiationService, @ICodeEditorService codeEditorService: ICodeEditorService, @@ -364,11 +367,13 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon @IStandaloneThemeService themeService: IStandaloneThemeService, @INotificationService notificationService: INotificationService, @IConfigurationService configurationService: IConfigurationService, - @IAccessibilityService accessibilityService: IAccessibilityService + @IAccessibilityService accessibilityService: IAccessibilityService, + @IModelService modelService: IModelService, + @IModeService modeService: IModeService, ) { - applyConfigurationValues(configurationService, options, false); + const options = { ..._options }; + updateConfigurationService(configurationService, options, false); const themeDomRegistration = (themeService).registerEditorContainer(domElement); - options = options || {}; if (typeof options.theme === 'string') { themeService.setTheme(options.theme); } @@ -384,7 +389,7 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon let model: ITextModel | null; if (typeof _model === 'undefined') { - model = (self).monaco.editor.createModel(options.value || '', options.language || 'text/plain'); + model = createTextModel(modelService, modeService, options.value || '', options.language || 'text/plain', undefined); this._ownsModel = true; } else { model = _model; @@ -405,8 +410,8 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon super.dispose(); } - public updateOptions(newOptions: IEditorOptions & IGlobalEditorOptions): void { - applyConfigurationValues(this._configurationService, newOptions, false); + public updateOptions(newOptions: Readonly): void { + updateConfigurationService(this._configurationService, newOptions, false); if (typeof newOptions.theme === 'string') { this._standaloneThemeService.setTheme(newOptions.theme); } @@ -437,7 +442,7 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon constructor( domElement: HTMLElement, - options: IDiffEditorConstructionOptions | undefined, + _options: Readonly | undefined, toDispose: IDisposable, @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, @@ -452,14 +457,14 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon @IEditorProgressService editorProgressService: IEditorProgressService, @IClipboardService clipboardService: IClipboardService, ) { - applyConfigurationValues(configurationService, options, true); + const options = { ..._options }; + updateConfigurationService(configurationService, options, true); const themeDomRegistration = (themeService).registerEditorContainer(domElement); - options = options || {}; if (typeof options.theme === 'string') { options.theme = themeService.setTheme(options.theme); } - super(domElement, options, clipboardService, editorWorkerService, contextKeyService, instantiationService, codeEditorService, themeService, notificationService, contextMenuService, editorProgressService); + super(domElement, options, {}, clipboardService, editorWorkerService, contextKeyService, instantiationService, codeEditorService, themeService, notificationService, contextMenuService, editorProgressService); this._contextViewService = contextViewService; this._configurationService = configurationService; @@ -475,15 +480,15 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon super.dispose(); } - public updateOptions(newOptions: IDiffEditorOptions & IGlobalEditorOptions): void { - applyConfigurationValues(this._configurationService, newOptions, true); + public updateOptions(newOptions: Readonly): void { + updateConfigurationService(this._configurationService, newOptions, true); if (typeof newOptions.theme === 'string') { this._standaloneThemeService.setTheme(newOptions.theme); } super.updateOptions(newOptions); } - protected _createInnerEditor(instantiationService: IInstantiationService, container: HTMLElement, options: IEditorOptions): CodeEditorWidget { + protected _createInnerEditor(instantiationService: IInstantiationService, container: HTMLElement, options: Readonly): CodeEditorWidget { return instantiationService.createInstance(StandaloneCodeEditor, container, options); } @@ -507,3 +512,26 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon return this.getModifiedEditor().addAction(descriptor); } } + +/** + * @internal + */ +export function createTextModel(modelService: IModelService, modeService: IModeService, value: string, language: string | undefined, uri: URI | undefined): ITextModel { + value = value || ''; + if (!language) { + const firstLF = value.indexOf('\n'); + let firstLine = value; + if (firstLF !== -1) { + firstLine = value.substring(0, firstLF); + } + return doCreateModel(modelService, value, modeService.createByFilepathOrFirstLine(uri || null, firstLine), uri); + } + return doCreateModel(modelService, value, modeService.create(language), uri); +} + +/** + * @internal + */ +function doCreateModel(modelService: IModelService, value: string, languageSelection: ILanguageSelection, uri: URI | undefined): ITextModel { + return modelService.createModel(value, languageSelection, uri); +} diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 6d5fc9c32..295f2cb68 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -18,16 +18,16 @@ import { FindMatch, ITextModel, TextModelResolvedOptions } from 'vs/editor/commo import * as modes from 'vs/editor/common/modes'; import { NULL_STATE, nullTokenize } from 'vs/editor/common/modes/nullMode'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; -import { ILanguageSelection } from 'vs/editor/common/services/modeService'; +import { IModeService } from 'vs/editor/common/services/modeService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IWebWorkerOptions, MonacoWebWorker, createWebWorker as actualCreateWebWorker } from 'vs/editor/common/services/webWorker'; import * as standaloneEnums from 'vs/editor/common/standalone/standaloneEnums'; import { Colorizer, IColorizerElementOptions, IColorizerOptions } from 'vs/editor/standalone/browser/colorizer'; import { SimpleEditorModelResolverService } from 'vs/editor/standalone/browser/simpleServices'; -import { IDiffEditorConstructionOptions, IStandaloneEditorConstructionOptions, IStandaloneCodeEditor, IStandaloneDiffEditor, StandaloneDiffEditor, StandaloneEditor } from 'vs/editor/standalone/browser/standaloneCodeEditor'; +import { IDiffEditorConstructionOptions, IStandaloneEditorConstructionOptions, IStandaloneCodeEditor, IStandaloneDiffEditor, StandaloneDiffEditor, StandaloneEditor, createTextModel } from 'vs/editor/standalone/browser/standaloneCodeEditor'; import { DynamicStandaloneServices, IEditorOverrideServices, StaticServices } from 'vs/editor/standalone/browser/standaloneServices'; import { IStandaloneThemeData, IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -42,6 +42,7 @@ import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { StandaloneThemeServiceImpl } from 'vs/editor/standalone/browser/standaloneThemeServiceImpl'; import { splitLines } from 'vs/base/common/strings'; +import { IModelService } from 'vs/editor/common/services/modelService'; type Omit = Pick>; @@ -87,7 +88,9 @@ export function create(domElement: HTMLElement, options?: IStandaloneEditorConst services.get(IStandaloneThemeService), services.get(INotificationService), services.get(IConfigurationService), - services.get(IAccessibilityService) + services.get(IAccessibilityService), + services.get(IModelService), + services.get(IModeService), ); }); } @@ -140,27 +143,18 @@ export function createDiffNavigator(diffEditor: IStandaloneDiffEditor, opts?: ID return new DiffNavigator(diffEditor, opts); } -function doCreateModel(value: string, languageSelection: ILanguageSelection, uri?: URI): ITextModel { - return StaticServices.modelService.get().createModel(value, languageSelection, uri); -} - /** * Create a new editor model. * You can specify the language that should be set for this model or let the language be inferred from the `uri`. */ export function createModel(value: string, language?: string, uri?: URI): ITextModel { - value = value || ''; - - if (!language) { - let firstLF = value.indexOf('\n'); - let firstLine = value; - if (firstLF !== -1) { - firstLine = value.substring(0, firstLF); - } - - return doCreateModel(value, StaticServices.modeService.get().createByFilepathOrFirstLine(uri || null, firstLine), uri); - } - return doCreateModel(value, StaticServices.modeService.get().create(language), uri); + return createTextModel( + StaticServices.modelService.get(), + StaticServices.modeService.get(), + value, + language, + uri + ); } /** @@ -188,6 +182,14 @@ export function getModelMarkers(filter: { owner?: string, resource?: URI, take?: return StaticServices.markerService.get().read(filter); } +/** + * Emitted when markers change for a model. + * @event + */ +export function onDidChangeMarkers(listener: (e: readonly URI[]) => void): IDisposable { + return StaticServices.markerService.get().onMarkerChanged(listener); +} + /** * Get the model that has `uri` if it exists. */ @@ -276,7 +278,7 @@ function getSafeTokenizationSupport(language: string): Omit NULL_STATE, - tokenize: (line: string, state: modes.IState, deltaOffset: number) => nullTokenize(language, line, state, deltaOffset) + tokenize: (line: string, hasEOL: boolean, state: modes.IState, deltaOffset: number) => nullTokenize(language, line, state, deltaOffset) }; } @@ -294,7 +296,7 @@ export function tokenize(text: string, languageId: string): Token[][] { let state = tokenizationSupport.getInitialState(); for (let i = 0, len = lines.length; i < len; i++) { let line = lines[i]; - let tokenizationResult = tokenizationSupport.tokenize(line, state, 0); + let tokenizationResult = tokenizationSupport.tokenize(line, true, state, 0); result[i] = tokenizationResult.tokens; state = tokenizationResult.endState; @@ -323,6 +325,13 @@ export function remeasureFonts(): void { clearAllFontInfos(); } +/** + * Register a command. + */ +export function registerCommand(id: string, handler: (accessor: any, ...args: any[]) => void): IDisposable { + return CommandsRegistry.registerCommand({ id, handler }); +} + /** * @internal */ @@ -338,6 +347,7 @@ export function createMonacoEditorAPI(): typeof monaco.editor { setModelLanguage: setModelLanguage, setModelMarkers: setModelMarkers, getModelMarkers: getModelMarkers, + onDidChangeMarkers: onDidChangeMarkers, getModels: getModels, getModel: getModel, onDidCreateModel: onDidCreateModel, @@ -353,6 +363,7 @@ export function createMonacoEditorAPI(): typeof monaco.editor { defineTheme: defineTheme, setTheme: setTheme, remeasureFonts: remeasureFonts, + registerCommand: registerCommand, // enums AccessibilitySupport: standaloneEnums.AccessibilitySupport, diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index dbf225a6c..b75a81443 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; +import { Color } from 'vs/base/common/color'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -87,14 +88,14 @@ export class EncodedTokenizationSupport2Adapter implements modes.ITokenizationSu return this._actual.getInitialState(); } - public tokenize(line: string, state: modes.IState, offsetDelta: number): TokenizationResult { + public tokenize(line: string, hasEOL: boolean, state: modes.IState, offsetDelta: number): TokenizationResult { if (typeof this._actual.tokenize === 'function') { return TokenizationSupport2Adapter.adaptTokenize(this._languageIdentifier.language, <{ tokenize(line: string, state: modes.IState): ILineTokens; }>this._actual, line, state, offsetDelta); } throw new Error('Not supported!'); } - public tokenize2(line: string, state: modes.IState): TokenizationResult2 { + public tokenize2(line: string, hasEOL: boolean, state: modes.IState): TokenizationResult2 { let result = this._actual.tokenizeEncoded(line, state); return new TokenizationResult2(result.tokens, result.endState); } @@ -157,7 +158,7 @@ export class TokenizationSupport2Adapter implements modes.ITokenizationSupport { return new TokenizationResult(tokens, endState); } - public tokenize(line: string, state: modes.IState, offsetDelta: number): TokenizationResult { + public tokenize(line: string, hasEOL: boolean, state: modes.IState, offsetDelta: number): TokenizationResult { return TokenizationSupport2Adapter.adaptTokenize(this._languageIdentifier.language, this._actual, line, state, offsetDelta); } @@ -199,7 +200,7 @@ export class TokenizationSupport2Adapter implements modes.ITokenizationSupport { return actualResult; } - public tokenize2(line: string, state: modes.IState, offsetDelta: number): TokenizationResult2 { + public tokenize2(line: string, hasEOL: boolean, state: modes.IState, offsetDelta: number): TokenizationResult2 { let actualResult = this._actual.tokenize(line, state); let tokens = this._toBinaryTokens(actualResult.tokens, offsetDelta); @@ -310,6 +311,22 @@ function isThenable(obj: any): obj is Thenable { return obj && typeof obj.then === 'function'; } +/** + * Change the color map that is used for token colors. + * Supported formats (hex): #RRGGBB, $RRGGBBAA, #RGB, #RGBA + */ +export function setColorMap(colorMap: string[] | null): void { + if (colorMap) { + const result: Color[] = [null!]; + for (let i = 1, len = colorMap.length; i < len; i++) { + result[i] = Color.fromHex(colorMap[i]); + } + StaticServices.standaloneThemeService.get().setColorMapOverride(result); + } else { + StaticServices.standaloneThemeService.get().setColorMapOverride(null); + } +} + /** * Set the tokens provider for a language (manual implementation). */ @@ -570,6 +587,7 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { // provider methods setLanguageConfiguration: setLanguageConfiguration, + setColorMap: setColorMap, setTokensProvider: setTokensProvider, setMonarchTokensProvider: setMonarchTokensProvider, registerReferenceProvider: registerReferenceProvider, diff --git a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts index 544d7b07a..f61c9de85 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts @@ -39,7 +39,11 @@ class StandaloneTheme implements IStandaloneTheme { this.themeData = standaloneThemeData; let base = standaloneThemeData.base; if (name.length > 0) { - this.id = base + ' ' + name; + if (isBuiltinTheme(name)) { + this.id = name; + } else { + this.id = base + ' ' + name; + } this.themeName = name; } else { this.id = base; @@ -199,6 +203,7 @@ export class StandaloneThemeServiceImpl extends Disposable implements IStandalon private _allCSS: string; private _globalStyleElement: HTMLStyleElement | null; private _styleElements: HTMLStyleElement[]; + private _colorMapOverride: Color[] | null; private _theme!: IStandaloneTheme; constructor() { @@ -216,6 +221,7 @@ export class StandaloneThemeServiceImpl extends Disposable implements IStandalon this._allCSS = `${this._codiconCSS}\n${this._themeCSS}`; this._globalStyleElement = null; this._styleElements = []; + this._colorMapOverride = null; this.setTheme(VS_THEME_NAME); iconRegistry.onDidChange(() => { @@ -284,6 +290,11 @@ export class StandaloneThemeServiceImpl extends Disposable implements IStandalon return this._theme; } + public setColorMapOverride(colorMapOverride: Color[] | null): void { + this._colorMapOverride = colorMapOverride; + this._updateThemeOrColorMap(); + } + public setTheme(themeName: string): string { let theme: StandaloneTheme; if (this._knownThemes.has(themeName)) { @@ -296,7 +307,11 @@ export class StandaloneThemeServiceImpl extends Disposable implements IStandalon return theme.id; } this._theme = theme; + this._updateThemeOrColorMap(); + return theme.id; + } + private _updateThemeOrColorMap(): void { let cssRules: string[] = []; let hasRule: { [rule: string]: boolean; } = {}; let ruleCollector: ICssStyleCollector = { @@ -307,19 +322,16 @@ export class StandaloneThemeServiceImpl extends Disposable implements IStandalon } } }; - themingRegistry.getThemingParticipants().forEach(p => p(theme, ruleCollector, this._environment)); + themingRegistry.getThemingParticipants().forEach(p => p(this._theme, ruleCollector, this._environment)); - let tokenTheme = theme.tokenTheme; - let colorMap = tokenTheme.getColorMap(); + const colorMap = this._colorMapOverride || this._theme.tokenTheme.getColorMap(); ruleCollector.addRule(generateTokensCSSForColorMap(colorMap)); this._themeCSS = cssRules.join('\n'); this._updateCSS(); TokenizationRegistry.setColorMap(colorMap); - this._onColorThemeChange.fire(theme); - - return theme.id; + this._onColorThemeChange.fire(this._theme); } private _updateCSS(): void { diff --git a/src/vs/editor/standalone/common/monarch/monarchCommon.ts b/src/vs/editor/standalone/common/monarch/monarchCommon.ts index 6dbf14dc8..bd96a9f08 100644 --- a/src/vs/editor/standalone/common/monarch/monarchCommon.ts +++ b/src/vs/editor/standalone/common/monarch/monarchCommon.ts @@ -22,6 +22,7 @@ export const enum MonarchBracket { export interface ILexerMin { languageId: string; + includeLF: boolean; noThrow: boolean; ignoreCase: boolean; unicode: boolean; diff --git a/src/vs/editor/standalone/common/monarch/monarchCompile.ts b/src/vs/editor/standalone/common/monarch/monarchCompile.ts index 289d045ab..aa487e7e6 100644 --- a/src/vs/editor/standalone/common/monarch/monarchCompile.ts +++ b/src/vs/editor/standalone/common/monarch/monarchCompile.ts @@ -395,6 +395,7 @@ export function compile(languageId: string, json: IMonarchLanguage): monarchComm // Create our lexer let lexer: monarchCommon.ILexer = {}; lexer.languageId = languageId; + lexer.includeLF = bool(json.includeLF, false); lexer.noThrow = false; // raise exceptions during compilation lexer.maxStack = 100; @@ -411,6 +412,7 @@ export function compile(languageId: string, json: IMonarchLanguage): monarchComm // For calling compileAction later on let lexerMin: monarchCommon.ILexerMin = json; lexerMin.languageId = languageId; + lexerMin.includeLF = lexer.includeLF; lexerMin.ignoreCase = lexer.ignoreCase; lexerMin.unicode = lexer.unicode; lexerMin.noThrow = lexer.noThrow; diff --git a/src/vs/editor/standalone/common/monarch/monarchLexer.ts b/src/vs/editor/standalone/common/monarch/monarchLexer.ts index e566f7589..dc8b86443 100644 --- a/src/vs/editor/standalone/common/monarch/monarchLexer.ts +++ b/src/vs/editor/standalone/common/monarch/monarchLexer.ts @@ -231,7 +231,7 @@ class MonarchLineState implements modes.IState { interface IMonarchTokensCollector { enterMode(startOffset: number, modeId: string): void; emit(startOffset: number, type: string): void; - nestedModeTokenize(embeddedModeLine: string, embeddedModeData: EmbeddedModeData, offsetDelta: number): modes.IState; + nestedModeTokenize(embeddedModeLine: string, hasEOL: boolean, embeddedModeData: EmbeddedModeData, offsetDelta: number): modes.IState; } class MonarchClassicTokensCollector implements IMonarchTokensCollector { @@ -261,7 +261,7 @@ class MonarchClassicTokensCollector implements IMonarchTokensCollector { this._tokens.push(new Token(startOffset, type, this._language!)); } - public nestedModeTokenize(embeddedModeLine: string, embeddedModeData: EmbeddedModeData, offsetDelta: number): modes.IState { + public nestedModeTokenize(embeddedModeLine: string, hasEOL: boolean, embeddedModeData: EmbeddedModeData, offsetDelta: number): modes.IState { const nestedModeId = embeddedModeData.modeId; const embeddedModeState = embeddedModeData.state; @@ -272,7 +272,7 @@ class MonarchClassicTokensCollector implements IMonarchTokensCollector { return embeddedModeState; } - let nestedResult = nestedModeTokenizationSupport.tokenize(embeddedModeLine, embeddedModeState, offsetDelta); + let nestedResult = nestedModeTokenizationSupport.tokenize(embeddedModeLine, hasEOL, embeddedModeState, offsetDelta); this._tokens = this._tokens.concat(nestedResult.tokens); this._lastTokenType = null; this._lastTokenLanguage = null; @@ -345,7 +345,7 @@ class MonarchModernTokensCollector implements IMonarchTokensCollector { return result; } - public nestedModeTokenize(embeddedModeLine: string, embeddedModeData: EmbeddedModeData, offsetDelta: number): modes.IState { + public nestedModeTokenize(embeddedModeLine: string, hasEOL: boolean, embeddedModeData: EmbeddedModeData, offsetDelta: number): modes.IState { const nestedModeId = embeddedModeData.modeId; const embeddedModeState = embeddedModeData.state; @@ -356,7 +356,7 @@ class MonarchModernTokensCollector implements IMonarchTokensCollector { return embeddedModeState; } - let nestedResult = nestedModeTokenizationSupport.tokenize2(embeddedModeLine, embeddedModeState, offsetDelta); + let nestedResult = nestedModeTokenizationSupport.tokenize2(embeddedModeLine, hasEOL, embeddedModeState, offsetDelta); this._prependTokens = MonarchModernTokensCollector._merge(this._prependTokens, this._tokens, nestedResult.tokens); this._tokens = []; this._currentLanguageId = 0; @@ -456,23 +456,23 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { return MonarchLineStateFactory.create(rootState, null); } - public tokenize(line: string, lineState: modes.IState, offsetDelta: number): TokenizationResult { + public tokenize(line: string, hasEOL: boolean, lineState: modes.IState, offsetDelta: number): TokenizationResult { let tokensCollector = new MonarchClassicTokensCollector(); - let endLineState = this._tokenize(line, lineState, offsetDelta, tokensCollector); + let endLineState = this._tokenize(line, hasEOL, lineState, offsetDelta, tokensCollector); return tokensCollector.finalize(endLineState); } - public tokenize2(line: string, lineState: modes.IState, offsetDelta: number): TokenizationResult2 { + public tokenize2(line: string, hasEOL: boolean, lineState: modes.IState, offsetDelta: number): TokenizationResult2 { let tokensCollector = new MonarchModernTokensCollector(this._modeService, this._standaloneThemeService.getColorTheme().tokenTheme); - let endLineState = this._tokenize(line, lineState, offsetDelta, tokensCollector); + let endLineState = this._tokenize(line, hasEOL, lineState, offsetDelta, tokensCollector); return tokensCollector.finalize(endLineState); } - private _tokenize(line: string, lineState: MonarchLineState, offsetDelta: number, collector: IMonarchTokensCollector): MonarchLineState { + private _tokenize(line: string, hasEOL: boolean, lineState: MonarchLineState, offsetDelta: number, collector: IMonarchTokensCollector): MonarchLineState { if (lineState.embeddedModeData) { - return this._nestedTokenize(line, lineState, offsetDelta, collector); + return this._nestedTokenize(line, hasEOL, lineState, offsetDelta, collector); } else { - return this._myTokenize(line, lineState, offsetDelta, collector); + return this._myTokenize(line, hasEOL, lineState, offsetDelta, collector); } } @@ -518,24 +518,24 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { return popOffset; } - private _nestedTokenize(line: string, lineState: MonarchLineState, offsetDelta: number, tokensCollector: IMonarchTokensCollector): MonarchLineState { + private _nestedTokenize(line: string, hasEOL: boolean, lineState: MonarchLineState, offsetDelta: number, tokensCollector: IMonarchTokensCollector): MonarchLineState { let popOffset = this._findLeavingNestedModeOffset(line, lineState); if (popOffset === -1) { // tokenization will not leave nested mode - let nestedEndState = tokensCollector.nestedModeTokenize(line, lineState.embeddedModeData!, offsetDelta); + let nestedEndState = tokensCollector.nestedModeTokenize(line, hasEOL, lineState.embeddedModeData!, offsetDelta); return MonarchLineStateFactory.create(lineState.stack, new EmbeddedModeData(lineState.embeddedModeData!.modeId, nestedEndState)); } let nestedModeLine = line.substring(0, popOffset); if (nestedModeLine.length > 0) { // tokenize with the nested mode - tokensCollector.nestedModeTokenize(nestedModeLine, lineState.embeddedModeData!, offsetDelta); + tokensCollector.nestedModeTokenize(nestedModeLine, false, lineState.embeddedModeData!, offsetDelta); } let restOfTheLine = line.substring(popOffset); - return this._myTokenize(restOfTheLine, lineState, offsetDelta + popOffset, tokensCollector); + return this._myTokenize(restOfTheLine, hasEOL, lineState, offsetDelta + popOffset, tokensCollector); } private _safeRuleName(rule: monarchCommon.IRule | null): string { @@ -545,9 +545,11 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { return '(unknown)'; } - private _myTokenize(line: string, lineState: MonarchLineState, offsetDelta: number, tokensCollector: IMonarchTokensCollector): MonarchLineState { + private _myTokenize(lineWithoutLF: string, hasEOL: boolean, lineState: MonarchLineState, offsetDelta: number, tokensCollector: IMonarchTokensCollector): MonarchLineState { tokensCollector.enterMode(offsetDelta, this._modeId); + const lineWithoutLFLength = lineWithoutLF.length; + const line = (hasEOL && this._lexer.includeLF ? lineWithoutLF + '\n' : lineWithoutLF); const lineLength = line.length; let embeddedModeData = lineState.embeddedModeData; @@ -563,7 +565,7 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { } let groupMatching: GroupMatching | null = null; - // See https://github.com/microsoft/monaco-editor/issues/1235: + // See https://github.com/microsoft/monaco-editor/issues/1235 // Evaluate rules at least once for an empty line let forceEvaluation = true; @@ -752,8 +754,8 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { if (pos < lineLength) { // there is content from the embedded mode on this line - const restOfLine = line.substr(pos); - return this._nestedTokenize(restOfLine, MonarchLineStateFactory.create(stack, embeddedModeData), offsetDelta + pos, tokensCollector); + const restOfLine = lineWithoutLF.substr(pos); + return this._nestedTokenize(restOfLine, hasEOL, MonarchLineStateFactory.create(stack, embeddedModeData), offsetDelta + pos, tokensCollector); } else { return MonarchLineStateFactory.create(stack, embeddedModeData); } @@ -831,7 +833,9 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { tokenType = monarchCommon.sanitize(token); } - tokensCollector.emit(pos0 + offsetDelta, tokenType); + if (pos0 < lineWithoutLFLength) { + tokensCollector.emit(pos0 + offsetDelta, tokenType); + } } if (enteringEmbeddedMode !== null) { diff --git a/src/vs/editor/standalone/common/monarch/monarchTypes.ts b/src/vs/editor/standalone/common/monarch/monarchTypes.ts index 19936be8d..5e3a798c6 100644 --- a/src/vs/editor/standalone/common/monarch/monarchTypes.ts +++ b/src/vs/editor/standalone/common/monarch/monarchTypes.ts @@ -41,6 +41,11 @@ export interface IMonarchLanguage { * attach this to every token class (by default '.' + name) */ tokenPostfix?: string; + /** + * include line feeds (in the form of a \n character) at the end of lines + * Defaults to false + */ + includeLF?: boolean; } /** diff --git a/src/vs/editor/standalone/common/standaloneThemeService.ts b/src/vs/editor/standalone/common/standaloneThemeService.ts index c70a713dd..afefd369d 100644 --- a/src/vs/editor/standalone/common/standaloneThemeService.ts +++ b/src/vs/editor/standalone/common/standaloneThemeService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Color } from 'vs/base/common/color'; import { ITokenThemeRule, TokenTheme } from 'vs/editor/common/modes/supports/tokenization'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; @@ -33,4 +34,7 @@ export interface IStandaloneThemeService extends IThemeService { defineTheme(themeName: string, themeData: IStandaloneThemeData): void; getColorTheme(): IStandaloneTheme; + + setColorMapOverride(colorMapOverride: Color[] | null): void; + } diff --git a/src/vs/editor/standalone/common/themes.ts b/src/vs/editor/standalone/common/themes.ts index 4c7761e66..9e07df46b 100644 --- a/src/vs/editor/standalone/common/themes.ts +++ b/src/vs/editor/standalone/common/themes.ts @@ -65,7 +65,7 @@ export const vs: IStandaloneThemeData = { { token: 'operator.scss', foreground: '666666' }, { token: 'operator.sql', foreground: '778899' }, { token: 'operator.swift', foreground: '666666' }, - { token: 'predefined.sql', foreground: 'FF00FF' }, + { token: 'predefined.sql', foreground: 'C700C7' }, ], colors: { [editorBackground]: '#FFFFFE', diff --git a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts index e3c7a31f1..bd4399d6c 100644 --- a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts +++ b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts @@ -68,7 +68,8 @@ suite('TokenizationSupport2Adapter', () => { tokenColorMap: [] }; } - + setColorMapOverride(colorMapOverride: Color[] | null): void { + } public getFileIconTheme(): IFileIconTheme { return { hasFileIcons: false, @@ -107,15 +108,15 @@ suite('TokenizationSupport2Adapter', () => { const adapter = new TokenizationSupport2Adapter(new MockThemeService(), languageIdentifier, new BadTokensProvider()); - const actualClassicTokens = adapter.tokenize('whatever', MockState.INSTANCE, offsetDelta); - assert.deepEqual(actualClassicTokens.tokens, expectedClassicTokens); + const actualClassicTokens = adapter.tokenize('whatever', true, MockState.INSTANCE, offsetDelta); + assert.deepStrictEqual(actualClassicTokens.tokens, expectedClassicTokens); - const actualModernTokens = adapter.tokenize2('whatever', MockState.INSTANCE, offsetDelta); + const actualModernTokens = adapter.tokenize2('whatever', true, MockState.INSTANCE, offsetDelta); const modernTokens: number[] = []; for (let i = 0; i < actualModernTokens.tokens.length; i++) { modernTokens[i] = actualModernTokens.tokens[i]; } - assert.deepEqual(modernTokens, expectedModernTokens); + assert.deepStrictEqual(modernTokens, expectedModernTokens); } test('tokens always start at index 0 (no offset delta)', () => { diff --git a/src/vs/editor/standalone/test/monarch/monarch.test.ts b/src/vs/editor/standalone/test/monarch/monarch.test.ts index 89eb03cef..61fcafe55 100644 --- a/src/vs/editor/standalone/test/monarch/monarch.test.ts +++ b/src/vs/editor/standalone/test/monarch/monarch.test.ts @@ -68,39 +68,154 @@ suite('Monarch', () => { const actualTokens: Token[][] = []; let state = tokenizer.getInitialState(); for (const line of lines) { - const result = tokenizer.tokenize(line, state, 0); + const result = tokenizer.tokenize(line, true, state, 0); actualTokens.push(result.tokens); state = result.endState; } - assert.deepEqual(actualTokens, [ + assert.deepStrictEqual(actualTokens, [ [ - { 'offset': 0, 'type': 'source.test1', 'language': 'test1' }, - { 'offset': 12, 'type': 'string.quote.test1', 'language': 'test1' }, - { 'offset': 15, 'type': 'token.sql', 'language': 'sql' }, - { 'offset': 61, 'type': 'string.quote.test1', 'language': 'test1' }, - { 'offset': 64, 'type': 'source.test1', 'language': 'test1' } + new Token(0, 'source.test1', 'test1'), + new Token(12, 'string.quote.test1', 'test1'), + new Token(15, 'token.sql', 'sql'), + new Token(61, 'string.quote.test1', 'test1'), + new Token(64, 'source.test1', 'test1') ], [ - { 'offset': 0, 'type': 'source.test1', 'language': 'test1' }, - { 'offset': 12, 'type': 'string.quote.test1', 'language': 'test1' } + new Token(0, 'source.test1', 'test1'), + new Token(12, 'string.quote.test1', 'test1') ], [ - { 'offset': 0, 'type': 'token.sql', 'language': 'sql' } + new Token(0, 'token.sql', 'sql') ], [ - { 'offset': 0, 'type': 'token.sql', 'language': 'sql' } + new Token(0, 'token.sql', 'sql') ], [ - { 'offset': 0, 'type': 'token.sql', 'language': 'sql' } + new Token(0, 'token.sql', 'sql') ], [ - { 'offset': 0, 'type': 'string.quote.test1', 'language': 'test1' }, - { 'offset': 3, 'type': 'source.test1', 'language': 'test1' } + new Token(0, 'string.quote.test1', 'test1'), + new Token(3, 'source.test1', 'test1') ] ]); innerModeTokenizationRegistration.dispose(); innerModeRegistration.dispose(); }); + test('microsoft/monaco-editor#1235: Empty Line Handling', () => { + const modeService = new ModeServiceImpl(); + const tokenizer = createMonarchTokenizer(modeService, 'test', { + tokenizer: { + root: [ + { include: '@comments' }, + ], + + comments: [ + [/\/\/$/, 'comment'], // empty single-line comment + [/\/\//, 'comment', '@comment_cpp'], + ], + + comment_cpp: [ + [/(?:[^\\]|(?:\\.))+$/, 'comment', '@pop'], + [/.+$/, 'comment'], + [/$/, 'comment', '@pop'] + // No possible rule to detect an empty line and @pop? + ], + }, + }); + + const lines = [ + `// This comment \\`, + ` continues on the following line`, + ``, + `// This comment does NOT continue \\\\`, + ` because the escape char was itself escaped`, + ``, + `// This comment DOES continue because \\\\\\`, + ` the 1st '\\' escapes the 2nd; the 3rd escapes EOL`, + ``, + `// This comment continues to the following line \\`, + ``, + `But the line was empty. This line should not be commented.`, + ]; + + const actualTokens: Token[][] = []; + let state = tokenizer.getInitialState(); + for (const line of lines) { + const result = tokenizer.tokenize(line, true, state, 0); + actualTokens.push(result.tokens); + state = result.endState; + } + + assert.deepStrictEqual(actualTokens, [ + [new Token(0, 'comment.test', 'test')], + [new Token(0, 'comment.test', 'test')], + [], + [new Token(0, 'comment.test', 'test')], + [new Token(0, 'source.test', 'test')], + [], + [new Token(0, 'comment.test', 'test')], + [new Token(0, 'comment.test', 'test')], + [], + [new Token(0, 'comment.test', 'test')], + [], + [new Token(0, 'source.test', 'test')] + ]); + + }); + + test('microsoft/monaco-editor#2265: Exit a state at end of line', () => { + const modeService = new ModeServiceImpl(); + const tokenizer = createMonarchTokenizer(modeService, 'test', { + includeLF: true, + tokenizer: { + root: [ + [/^\*/, '', '@inner'], + [/\:\*/, '', '@inner'], + [/[^*:]+/, 'string'], + [/[*:]/, 'string'] + ], + inner: [ + [/\n/, '', '@pop'], + [/\d+/, 'number'], + [/[^\d]+/, ''] + ] + } + }); + + const lines = [ + `PRINT 10 * 20`, + `*FX200, 3`, + `PRINT 2*3:*FX200, 3` + ]; + + const actualTokens: Token[][] = []; + let state = tokenizer.getInitialState(); + for (const line of lines) { + const result = tokenizer.tokenize(line, true, state, 0); + actualTokens.push(result.tokens); + state = result.endState; + } + + assert.deepStrictEqual(actualTokens, [ + [ + new Token(0, 'string.test', 'test'), + ], + [ + new Token(0, '', 'test'), + new Token(3, 'number.test', 'test'), + new Token(6, '', 'test'), + new Token(8, 'number.test', 'test'), + ], + [ + new Token(0, 'string.test', 'test'), + new Token(9, '', 'test'), + new Token(13, 'number.test', 'test'), + new Token(16, '', 'test'), + new Token(18, 'number.test', 'test'), + ] + ]); + }); + }); diff --git a/src/vs/editor/test/browser/commands/shiftCommand.test.ts b/src/vs/editor/test/browser/commands/shiftCommand.test.ts index 755aac5bc..c31f8e4bf 100644 --- a/src/vs/editor/test/browser/commands/shiftCommand.test.ts +++ b/src/vs/editor/test/browser/commands/shiftCommand.test.ts @@ -964,7 +964,7 @@ suite('Editor Commands - ShiftCommand', () => { autoIndent: EditorAutoIndentStrategy.Full, }); let actual = getEditOperation(model, op); - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); }); } @@ -979,7 +979,7 @@ suite('Editor Commands - ShiftCommand', () => { autoIndent: EditorAutoIndentStrategy.Full, }); let actual = getEditOperation(model, op); - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); }); } }); diff --git a/src/vs/editor/test/browser/commands/sideEditing.test.ts b/src/vs/editor/test/browser/commands/sideEditing.test.ts index be07a5682..f8a8bbae5 100644 --- a/src/vs/editor/test/browser/commands/sideEditing.test.ts +++ b/src/vs/editor/test/browser/commands/sideEditing.test.ts @@ -19,10 +19,10 @@ function testCommand(lines: string[], selections: Selection[], edits: IIdentifie model.applyEdits(edits); - assert.deepEqual(model.getLinesContent(), expectedLines); + assert.deepStrictEqual(model.getLinesContent(), expectedLines); let actualSelections = viewModel.getSelections(); - assert.deepEqual(actualSelections.map(s => s.toString()), expectedSelections.map(s => s.toString())); + assert.deepStrictEqual(actualSelections.map(s => s.toString()), expectedSelections.map(s => s.toString())); }); } @@ -202,7 +202,7 @@ suite('SideEditing', () => { forceMoveMarkers: editForceMoveMarkers }]); const actual = viewModel.getSelection(); - assert.deepEqual(actual.toString(), expected.toString(), msg); + assert.deepStrictEqual(actual.toString(), expected.toString(), msg); }); } diff --git a/src/vs/editor/test/browser/commands/trimTrailingWhitespaceCommand.test.ts b/src/vs/editor/test/browser/commands/trimTrailingWhitespaceCommand.test.ts index af0c64e53..d9fdfc1bd 100644 --- a/src/vs/editor/test/browser/commands/trimTrailingWhitespaceCommand.test.ts +++ b/src/vs/editor/test/browser/commands/trimTrailingWhitespaceCommand.test.ts @@ -37,14 +37,14 @@ function assertTrimTrailingWhitespaceCommand(text: string[], expected: IIdentifi return withEditorModel(text, (model) => { let op = new TrimTrailingWhitespaceCommand(new Selection(1, 1, 1, 1), []); let actual = getEditOperation(model, op); - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); }); } function assertTrimTrailingWhitespace(text: string[], cursors: Position[], expected: IIdentifiedSingleEditOperation[]): void { return withEditorModel(text, (model) => { let actual = trimTrailingWhitespace(model, cursors); - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); }); } diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index cb202a3f8..489af4718 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -115,7 +115,7 @@ function assertCursor(viewModel: ViewModel, what: Position | Selection | Selecti let actual = viewModel.getSelections().map(s => s.toString()); let expected = selections.map(s => s.toString()); - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); } suite('Editor Controller - Cursor', () => { @@ -795,11 +795,11 @@ suite('Editor Controller - Cursor', () => { viewModel.onEvent((e) => { if (e.kind === OutgoingViewModelEventKind.CursorStateChanged) { events++; - assert.deepEqual(e.selections, [new Selection(1, 2, 1, 2)]); + assert.deepStrictEqual(e.selections, [new Selection(1, 2, 1, 2)]); } }); moveTo(editor, viewModel, 1, 2); - assert.equal(events, 1, 'receives 1 event'); + assert.strictEqual(events, 1, 'receives 1 event'); }); }); @@ -809,11 +809,11 @@ suite('Editor Controller - Cursor', () => { viewModel.onEvent((e) => { if (e.kind === OutgoingViewModelEventKind.CursorStateChanged) { events++; - assert.deepEqual(e.selections, [new Selection(1, 1, 1, 2)]); + assert.deepStrictEqual(e.selections, [new Selection(1, 1, 1, 2)]); } }); moveTo(editor, viewModel, 1, 2, true); - assert.equal(events, 1, 'receives 1 event'); + assert.strictEqual(events, 1, 'receives 1 event'); }); }); @@ -1311,7 +1311,7 @@ suite('Editor Controller - Regression tests', () => { // Check that indenting maintains the selection start at column 1 CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.deepEqual(viewModel.getSelection(), new Selection(1, 1, 1, 14)); + assert.deepStrictEqual(viewModel.getSelection(), new Selection(1, 1, 1, 14)); }); model.dispose(); @@ -1330,49 +1330,49 @@ suite('Editor Controller - Regression tests', () => { withTestCodeEditor(null, { model: model }, (editor, viewModel) => { viewModel.type('\n', 'keyboard'); - assert.equal(model.getValue(EndOfLinePreference.LF), '\n', 'assert1'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\n', 'assert1'); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), '\n\t', 'assert2'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\n\t', 'assert2'); viewModel.type('\n', 'keyboard'); - assert.equal(model.getValue(EndOfLinePreference.LF), '\n\t\n\t', 'assert3'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\n\t\n\t', 'assert3'); viewModel.type('x'); - assert.equal(model.getValue(EndOfLinePreference.LF), '\n\t\n\tx', 'assert4'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\n\t\n\tx', 'assert4'); CoreNavigationCommands.CursorLeft.runCoreEditorCommand(viewModel, {}); - assert.equal(model.getValue(EndOfLinePreference.LF), '\n\t\n\tx', 'assert5'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\n\t\n\tx', 'assert5'); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), '\n\t\nx', 'assert6'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\n\t\nx', 'assert6'); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), '\n\tx', 'assert7'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\n\tx', 'assert7'); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert8'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\nx', 'assert8'); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert9'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'x', 'assert9'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert10'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\nx', 'assert10'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), '\n\t\nx', 'assert11'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\n\t\nx', 'assert11'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), '\n\t\n\tx', 'assert12'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\n\t\n\tx', 'assert12'); CoreEditingCommands.Redo.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), '\n\t\nx', 'assert13'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\n\t\nx', 'assert13'); CoreEditingCommands.Redo.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert14'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\nx', 'assert14'); CoreEditingCommands.Redo.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert15'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'x', 'assert15'); }); model.dispose(); @@ -1387,13 +1387,13 @@ suite('Editor Controller - Regression tests', () => { assertCursor(viewModel, new Position(1, 1)); model.setEOL(EndOfLineSequence.LF); - assert.equal(model.getValue(), 'Hello\nworld'); + assert.strictEqual(model.getValue(), 'Hello\nworld'); model.pushEOL(EndOfLineSequence.CRLF); - assert.equal(model.getValue(), 'Hello\r\nworld'); + assert.strictEqual(model.getValue(), 'Hello\r\nworld'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getValue(), 'Hello\nworld'); + assert.strictEqual(model.getValue(), 'Hello\nworld'); }); }); @@ -1415,10 +1415,10 @@ suite('Editor Controller - Regression tests', () => { editor.setSelection(new Selection(1, 1, 1, 2)); viewModel.type('%', 'keyboard'); - assert.equal(model.getValue(EndOfLinePreference.LF), '%\'%👁\'', 'assert1'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '%\'%👁\'', 'assert1'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), '\'👁\'', 'assert2'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\'👁\'', 'assert2'); }); model.dispose(); @@ -1433,50 +1433,50 @@ suite('Editor Controller - Regression tests', () => { viewModel.type(' ', 'keyboard'); viewModel.type('world', 'keyboard'); viewModel.type(' ', 'keyboard'); - assert.equal(model.getLineContent(1), 'Hello world '); + assert.strictEqual(model.getLineContent(1), 'Hello world '); assertCursor(viewModel, new Position(1, 13)); moveLeft(editor, viewModel); moveRight(editor, viewModel); model.pushEditOperations([], [EditOperation.replaceMove(new Range(1, 12, 1, 13), '')], () => []); - assert.equal(model.getLineContent(1), 'Hello world'); + assert.strictEqual(model.getLineContent(1), 'Hello world'); assertCursor(viewModel, new Position(1, 12)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), 'Hello world '); + assert.strictEqual(model.getLineContent(1), 'Hello world '); assertCursor(viewModel, new Selection(1, 12, 1, 13)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), 'Hello world'); + assert.strictEqual(model.getLineContent(1), 'Hello world'); assertCursor(viewModel, new Position(1, 12)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), 'Hello'); + assert.strictEqual(model.getLineContent(1), 'Hello'); assertCursor(viewModel, new Position(1, 6)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), ''); + assert.strictEqual(model.getLineContent(1), ''); assertCursor(viewModel, new Position(1, 1)); CoreEditingCommands.Redo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), 'Hello'); + assert.strictEqual(model.getLineContent(1), 'Hello'); assertCursor(viewModel, new Position(1, 6)); CoreEditingCommands.Redo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), 'Hello world'); + assert.strictEqual(model.getLineContent(1), 'Hello world'); assertCursor(viewModel, new Position(1, 12)); CoreEditingCommands.Redo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), 'Hello world '); + assert.strictEqual(model.getLineContent(1), 'Hello world '); assertCursor(viewModel, new Position(1, 13)); CoreEditingCommands.Redo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), 'Hello world'); + assert.strictEqual(model.getLineContent(1), 'Hello world'); assertCursor(viewModel, new Position(1, 12)); CoreEditingCommands.Redo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), 'Hello world'); + assert.strictEqual(model.getLineContent(1), 'Hello world'); assertCursor(viewModel, new Position(1, 12)); }); @@ -1498,7 +1498,7 @@ suite('Editor Controller - Regression tests', () => { assertCursor(viewModel, new Selection(1, 6, 1, 6)); CoreEditingCommands.Outdent.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), ' function baz() {'); + assert.strictEqual(model.getLineContent(1), ' function baz() {'); assertCursor(viewModel, new Selection(1, 5, 1, 5)); }); @@ -1518,7 +1518,7 @@ suite('Editor Controller - Regression tests', () => { assertCursor(viewModel, new Selection(1, 7, 1, 7)); CoreEditingCommands.Outdent.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), ' '); + assert.strictEqual(model.getLineContent(1), ' '); assertCursor(viewModel, new Selection(1, 5, 1, 5)); }); @@ -1540,7 +1540,7 @@ suite('Editor Controller - Regression tests', () => { assertCursor(viewModel, new Selection(1, 9, 1, 9)); CoreEditingCommands.Outdent.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), ' '); + assert.strictEqual(model.getLineContent(1), ' '); assertCursor(viewModel, new Selection(1, 5, 1, 5)); }); @@ -1568,7 +1568,7 @@ suite('Editor Controller - Regression tests', () => { assertCursor(viewModel, new Selection(7, 1, 7, 1)); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(7), '\t'); + assert.strictEqual(model.getLineContent(7), '\t'); assertCursor(viewModel, new Selection(7, 2, 7, 2)); }); @@ -1588,8 +1588,8 @@ suite('Editor Controller - Regression tests', () => { assertCursor(viewModel, new Selection(2, 1, 2, 1)); viewModel.cut('keyboard'); - assert.equal(model.getLineCount(), 1); - assert.equal(model.getLineContent(1), 'asdasd'); + assert.strictEqual(model.getLineCount(), 1); + assert.strictEqual(model.getLineContent(1), 'asdasd'); }); @@ -1604,12 +1604,12 @@ suite('Editor Controller - Regression tests', () => { assertCursor(viewModel, new Selection(2, 1, 2, 1)); viewModel.cut('keyboard'); - assert.equal(model.getLineCount(), 1); - assert.equal(model.getLineContent(1), 'asdasd'); + assert.strictEqual(model.getLineCount(), 1); + assert.strictEqual(model.getLineContent(1), 'asdasd'); viewModel.cut('keyboard'); - assert.equal(model.getLineCount(), 1); - assert.equal(model.getLineContent(1), ''); + assert.strictEqual(model.getLineCount(), 1); + assert.strictEqual(model.getLineContent(1), ''); }); }); @@ -1651,8 +1651,8 @@ suite('Editor Controller - Regression tests', () => { CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); assertCursor(viewModel, new Selection(1, 14, 1, 14)); - assert.equal(model.getLineCount(), 1); - assert.equal(model.getLineContent(1), 'function baz(;'); + assert.strictEqual(model.getLineCount(), 1); + assert.strictEqual(model.getLineContent(1), 'function baz(;'); }); model.dispose(); @@ -1671,9 +1671,9 @@ suite('Editor Controller - Regression tests', () => { viewModel.paste('line1\n', true); - assert.equal(model.getLineContent(1), 'line1'); - assert.equal(model.getLineContent(2), 'line1'); - assert.equal(model.getLineContent(3), ''); + assert.strictEqual(model.getLineContent(1), 'line1'); + assert.strictEqual(model.getLineContent(2), 'line1'); + assert.strictEqual(model.getLineContent(3), ''); }); }); @@ -1689,10 +1689,10 @@ suite('Editor Controller - Regression tests', () => { viewModel.paste('line1\n', true); - assert.equal(model.getLineContent(1), 'line1'); - assert.equal(model.getLineContent(2), 'line line1'); - assert.equal(model.getLineContent(3), ' 2'); - assert.equal(model.getLineContent(4), 'line3'); + assert.strictEqual(model.getLineContent(1), 'line1'); + assert.strictEqual(model.getLineContent(2), 'line line1'); + assert.strictEqual(model.getLineContent(3), ' 2'); + assert.strictEqual(model.getLineContent(4), 'line3'); }); }); @@ -1715,7 +1715,7 @@ suite('Editor Controller - Regression tests', () => { ] ); - assert.equal(model.getValue(), [ + assert.strictEqual(model.getValue(), [ 'a', 'bline1', 'c', @@ -1747,7 +1747,7 @@ suite('Editor Controller - Regression tests', () => { null ); - assert.equal(model.getValue(), [ + assert.strictEqual(model.getValue(), [ 'aaa', 'bbb', 'ccc', @@ -1790,7 +1790,7 @@ suite('Editor Controller - Regression tests', () => { null ); - assert.equal(model.getValue(), [ + assert.strictEqual(model.getValue(), [ 'aaa', 'bbb', 'ccc', @@ -1815,7 +1815,7 @@ suite('Editor Controller - Regression tests', () => { null ); - assert.equal(model.getValue(), [ + assert.strictEqual(model.getValue(), [ 'aline1', 'bline2', 'cline3' @@ -1839,7 +1839,7 @@ suite('Editor Controller - Regression tests', () => { null ); - assert.equal(model.getValue(), [ + assert.strictEqual(model.getValue(), [ 'aline1', 'bline2', 'cline3' @@ -1869,26 +1869,26 @@ suite('Editor Controller - Regression tests', () => { }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getValue(), [ + assert.strictEqual(model.getValue(), [ '\t just some text' ].join('\n'), '001'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getValue(), [ + assert.strictEqual(model.getValue(), [ ' some lines', ' and more lines', ' just some text', ].join('\n'), '002'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getValue(), [ + assert.strictEqual(model.getValue(), [ 'some lines', 'and more lines', 'just some text', ].join('\n'), '003'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getValue(), [ + assert.strictEqual(model.getValue(), [ 'some lines', 'and more lines', 'just some text', @@ -1911,7 +1911,7 @@ suite('Editor Controller - Regression tests', () => { viewModel.type('😍', 'keyboard'); - assert.equal(model.getValue(), [ + assert.strictEqual(model.getValue(), [ 'some lines', 'and more lines', '😍just some text', @@ -1933,7 +1933,7 @@ suite('Editor Controller - Regression tests', () => { withTestCodeEditor(null, { model: model }, (editor, viewModel) => { moveTo(editor, viewModel, 3, 2, false); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(3), '\t \tx: 3'); + assert.strictEqual(model.getLineContent(3), '\t \tx: 3'); }); model.dispose(); @@ -1954,7 +1954,7 @@ suite('Editor Controller - Regression tests', () => { moveTo(editor, viewModel, 1, 15, false); moveTo(editor, viewModel, 1, 22, true); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), 'var foo = 123;\t// this is a comment'); + assert.strictEqual(model.getLineContent(1), 'var foo = 123;\t// this is a comment'); }); model.dispose(); @@ -1982,8 +1982,8 @@ suite('Editor Controller - Regression tests', () => { CoreNavigationCommands.WordSelectDrag.runCoreEditorCommand(viewModel, args); } - assert.equal(viewModel.getSelection().startColumn, 1, 'TEST FOR ' + col); - assert.equal(viewModel.getSelection().endColumn, expectedCol, 'TEST FOR ' + col); + assert.strictEqual(viewModel.getSelection().startColumn, 1, 'TEST FOR ' + col); + assert.strictEqual(viewModel.getSelection().endColumn, expectedCol, 'TEST FOR ' + col); } assertWordRight(1, ' '.length + 1); @@ -2048,10 +2048,10 @@ suite('Editor Controller - Regression tests', () => { withTestCodeEditor(null, { model: model }, (editor, viewModel) => { CoreNavigationCommands.WordSelect.runCoreEditorCommand(viewModel, { position: new Position(1, 8) }); - assert.deepEqual(viewModel.getSelection(), new Selection(1, 6, 1, 10)); + assert.deepStrictEqual(viewModel.getSelection(), new Selection(1, 6, 1, 10)); CoreNavigationCommands.WordSelectDrag.runCoreEditorCommand(viewModel, { position: new Position(1, 8) }); - assert.deepEqual(viewModel.getSelection(), new Selection(1, 6, 1, 10)); + assert.deepStrictEqual(viewModel.getSelection(), new Selection(1, 6, 1, 10)); }); model.dispose(); @@ -2066,7 +2066,7 @@ suite('Editor Controller - Regression tests', () => { withTestCodeEditor(null, { model: model }, (editor, viewModel) => { CoreNavigationCommands.WordSelect.runCoreEditorCommand(viewModel, { position: new Position(1, 5) }); - assert.deepEqual(viewModel.getSelection(), new Selection(1, 5, 1, 8)); + assert.deepStrictEqual(viewModel.getSelection(), new Selection(1, 5, 1, 8)); }); model.dispose(); @@ -2090,11 +2090,11 @@ suite('Editor Controller - Regression tests', () => { viewModel.replacePreviousChar('せんせい', 4); viewModel.replacePreviousChar('せんせい', 4); - assert.equal(model.getLineContent(1), 'せんせい'); + assert.strictEqual(model.getLineContent(1), 'せんせい'); assertCursor(viewModel, new Position(1, 5)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), ''); + assert.strictEqual(model.getLineContent(1), ''); assertCursor(viewModel, new Position(1, 1)); }); }); @@ -2121,11 +2121,11 @@ suite('Editor Controller - Regression tests', () => { viewModel.type('n', 'keyboard'); for (let i = 0; i < LINE_CNT; i++) { - assert.equal(model.getLineContent(i + 1), 'nnasd', 'line #' + (i + 1)); + assert.strictEqual(model.getLineContent(i + 1), 'nnasd', 'line #' + (i + 1)); } - assert.equal(viewModel.getSelections().length, LINE_CNT); - assert.equal(viewModel.getSelections()[LINE_CNT - 1].startLineNumber, LINE_CNT); + assert.strictEqual(viewModel.getSelections().length, LINE_CNT); + assert.strictEqual(viewModel.getSelections()[LINE_CNT - 1].startLineNumber, LINE_CNT); }); }); @@ -2349,6 +2349,37 @@ suite('Editor Controller - Regression tests', () => { }); }); + test('issue #112301: new stickyTabStops feature interferes with word wrap', () => { + withTestCodeEditor([ + [ + 'function hello() {', + ' console.log(`this is a long console message`)', + '}', + ].join('\n') + ], { wordWrap: 'wordWrapColumn', wordWrapColumn: 32, stickyTabStops: true }, (editor, viewModel) => { + viewModel.setSelections('test', [ + new Selection(2, 31, 2, 31) + ]); + moveRight(editor, viewModel, false); + assertCursor(viewModel, new Position(2, 32)); + + moveRight(editor, viewModel, false); + assertCursor(viewModel, new Position(2, 33)); + + moveRight(editor, viewModel, false); + assertCursor(viewModel, new Position(2, 34)); + + moveLeft(editor, viewModel, false); + assertCursor(viewModel, new Position(2, 33)); + + moveLeft(editor, viewModel, false); + assertCursor(viewModel, new Position(2, 32)); + + moveLeft(editor, viewModel, false); + assertCursor(viewModel, new Position(2, 31)); + }); + }); + test('issue #44805: Should not be able to undo in readonly editor', () => { let model = createTextModel( [ @@ -2361,10 +2392,10 @@ suite('Editor Controller - Regression tests', () => { range: new Range(1, 1, 1, 1), text: 'Hello world!' }], () => [new Selection(1, 1, 1, 1)]); - assert.equal(model.getValue(EndOfLinePreference.LF), 'Hello world!'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'Hello world!'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), 'Hello world!'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'Hello world!'); }); model.dispose(); @@ -2375,7 +2406,7 @@ suite('Editor Controller - Regression tests', () => { const tokenizationSupport: ITokenizationSupport = { getInitialState: () => NULL_STATE, tokenize: undefined!, - tokenize2: (line: string, state: IState): TokenizationResult2 => { + tokenize2: (line: string, hasEOL: boolean, state: IState): TokenizationResult2 => { return new TokenizationResult2(new Uint32Array(0), state); } }; @@ -2426,8 +2457,8 @@ suite('Editor Controller - Regression tests', () => { viewModel.type('\'', 'keyboard'); - assert.equal(model.getLineContent(1), 'const a = \'foo\';'); - assert.equal(model.getLineContent(2), 'const b = \'\''); + assert.strictEqual(model.getLineContent(1), 'const a = \'foo\';'); + assert.strictEqual(model.getLineContent(2), 'const b = \'\''); }); model.dispose(); @@ -2518,7 +2549,7 @@ suite('Editor Controller - Regression tests', () => { new Selection(2, 1, 2, 1) ]); viewModel.paste('something\n', true); - assert.equal(model.getValue(), [ + assert.strictEqual(model.getValue(), [ 'abc123', 'something', '' @@ -2542,22 +2573,22 @@ suite('Editor Controller - Regression tests', () => { ]); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), 'สวัสด'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'สวัสด'); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), 'สวัส'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'สวัส'); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), 'สวั'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'สวั'); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), 'สว'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'สว'); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), 'ส'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'ส'); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), ''); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), ''); }); model.dispose(); @@ -2578,8 +2609,8 @@ suite('Editor Controller - Cursor Configuration', () => { }, (editor, model, viewModel) => { CoreNavigationCommands.MoveTo.runCoreEditorCommand(viewModel, { position: new Position(1, 21), source: 'keyboard' }); viewModel.type('\n', 'keyboard'); - assert.equal(model.getLineContent(1), ' \tMy First Line\t '); - assert.equal(model.getLineContent(2), ' '); + assert.strictEqual(model.getLineContent(1), ' \tMy First Line\t '); + assert.strictEqual(model.getLineContent(2), ' '); }); }); @@ -2602,56 +2633,56 @@ suite('Editor Controller - Cursor Configuration', () => { // Tab on column 1 CoreNavigationCommands.MoveTo.runCoreEditorCommand(viewModel, { position: new Position(2, 1) }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), ' My Second Line123'); + assert.strictEqual(model.getLineContent(2), ' My Second Line123'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); // Tab on column 2 - assert.equal(model.getLineContent(2), 'My Second Line123'); + assert.strictEqual(model.getLineContent(2), 'My Second Line123'); CoreNavigationCommands.MoveTo.runCoreEditorCommand(viewModel, { position: new Position(2, 2) }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), 'M y Second Line123'); + assert.strictEqual(model.getLineContent(2), 'M y Second Line123'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); // Tab on column 3 - assert.equal(model.getLineContent(2), 'My Second Line123'); + assert.strictEqual(model.getLineContent(2), 'My Second Line123'); CoreNavigationCommands.MoveTo.runCoreEditorCommand(viewModel, { position: new Position(2, 3) }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), 'My Second Line123'); + assert.strictEqual(model.getLineContent(2), 'My Second Line123'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); // Tab on column 4 - assert.equal(model.getLineContent(2), 'My Second Line123'); + assert.strictEqual(model.getLineContent(2), 'My Second Line123'); CoreNavigationCommands.MoveTo.runCoreEditorCommand(viewModel, { position: new Position(2, 4) }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), 'My Second Line123'); + assert.strictEqual(model.getLineContent(2), 'My Second Line123'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); // Tab on column 5 - assert.equal(model.getLineContent(2), 'My Second Line123'); + assert.strictEqual(model.getLineContent(2), 'My Second Line123'); CoreNavigationCommands.MoveTo.runCoreEditorCommand(viewModel, { position: new Position(2, 5) }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), 'My S econd Line123'); + assert.strictEqual(model.getLineContent(2), 'My S econd Line123'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); // Tab on column 5 - assert.equal(model.getLineContent(2), 'My Second Line123'); + assert.strictEqual(model.getLineContent(2), 'My Second Line123'); CoreNavigationCommands.MoveTo.runCoreEditorCommand(viewModel, { position: new Position(2, 5) }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), 'My S econd Line123'); + assert.strictEqual(model.getLineContent(2), 'My S econd Line123'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); // Tab on column 13 - assert.equal(model.getLineContent(2), 'My Second Line123'); + assert.strictEqual(model.getLineContent(2), 'My Second Line123'); CoreNavigationCommands.MoveTo.runCoreEditorCommand(viewModel, { position: new Position(2, 13) }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), 'My Second Li ne123'); + assert.strictEqual(model.getLineContent(2), 'My Second Li ne123'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); // Tab on column 14 - assert.equal(model.getLineContent(2), 'My Second Line123'); + assert.strictEqual(model.getLineContent(2), 'My Second Line123'); CoreNavigationCommands.MoveTo.runCoreEditorCommand(viewModel, { position: new Position(2, 14) }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), 'My Second Lin e123'); + assert.strictEqual(model.getLineContent(2), 'My Second Lin e123'); }); model.dispose(); @@ -2669,7 +2700,7 @@ suite('Editor Controller - Cursor Configuration', () => { assertCursor(viewModel, new Selection(1, 7, 1, 7)); viewModel.type('\n', 'keyboard'); - assert.equal(model.getValue(EndOfLinePreference.CRLF), '\thello\r\n '); + assert.strictEqual(model.getValue(EndOfLinePreference.CRLF), '\thello\r\n '); }); mode.dispose(); }); @@ -2686,7 +2717,7 @@ suite('Editor Controller - Cursor Configuration', () => { assertCursor(viewModel, new Selection(1, 7, 1, 7)); viewModel.type('\n', 'keyboard'); - assert.equal(model.getValue(EndOfLinePreference.CRLF), '\thello\r\n '); + assert.strictEqual(model.getValue(EndOfLinePreference.CRLF), '\thello\r\n '); }); mode.dispose(); }); @@ -2703,7 +2734,7 @@ suite('Editor Controller - Cursor Configuration', () => { assertCursor(viewModel, new Selection(1, 7, 1, 7)); viewModel.type('\n', 'keyboard'); - assert.equal(model.getValue(EndOfLinePreference.CRLF), '\thell(\r\n \r\n )'); + assert.strictEqual(model.getValue(EndOfLinePreference.CRLF), '\thell(\r\n \r\n )'); }); mode.dispose(); }); @@ -2721,14 +2752,14 @@ suite('Editor Controller - Cursor Configuration', () => { // Move cursor to the end, verify that we do not trim whitespaces if line has values moveTo(editor, viewModel, 1, model.getLineContent(1).length + 1); viewModel.type('\n', 'keyboard'); - assert.equal(model.getLineContent(1), ' some line abc '); - assert.equal(model.getLineContent(2), ' '); + assert.strictEqual(model.getLineContent(1), ' some line abc '); + assert.strictEqual(model.getLineContent(2), ' '); // Try to enter again, we should trimmed previous line viewModel.type('\n', 'keyboard'); - assert.equal(model.getLineContent(1), ' some line abc '); - assert.equal(model.getLineContent(2), ' '); - assert.equal(model.getLineContent(3), ' '); + assert.strictEqual(model.getLineContent(1), ' some line abc '); + assert.strictEqual(model.getLineContent(2), ' '); + assert.strictEqual(model.getLineContent(3), ' '); }); }); @@ -2740,16 +2771,47 @@ suite('Editor Controller - Cursor Configuration', () => { }, (editor, model, viewModel) => { moveTo(editor, viewModel, 1, model.getLineContent(1).length + 1); viewModel.type('\n', 'keyboard'); - assert.equal(model.getLineContent(1), ' '); - assert.equal(model.getLineContent(2), ' '); + assert.strictEqual(model.getLineContent(1), ' '); + assert.strictEqual(model.getLineContent(2), ' '); viewModel.type('\n', 'keyboard'); - assert.equal(model.getLineContent(1), ' '); - assert.equal(model.getLineContent(2), ''); - assert.equal(model.getLineContent(3), ' '); + assert.strictEqual(model.getLineContent(1), ' '); + assert.strictEqual(model.getLineContent(2), ''); + assert.strictEqual(model.getLineContent(3), ' '); }); }); + test('issue #115033: indent and appendText', () => { + const mode = new class extends MockMode { + constructor() { + super(new LanguageIdentifier('onEnterMode', 3)); + this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), { + onEnterRules: [{ + beforeText: /.*/, + action: { + indentAction: IndentAction.Indent, + appendText: 'x' + } + }] + })); + } + }(); + usingCursor({ + text: [ + 'text' + ], + languageIdentifier: mode.getLanguageIdentifier(), + }, (editor, model, viewModel) => { + + moveTo(editor, viewModel, 1, 5); + viewModel.type('\n', 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'text'); + assert.strictEqual(model.getLineContent(2), ' x'); + assertCursor(viewModel, new Position(2, 6)); + }); + mode.dispose(); + }); + test('issue #6862: Editor removes auto inserted indentation when formatting on type', () => { let mode = new OnEnterMode(IndentAction.IndentOutdent); usingCursor({ @@ -2761,9 +2823,9 @@ suite('Editor Controller - Cursor Configuration', () => { moveTo(editor, viewModel, 1, 32); viewModel.type('\n', 'keyboard'); - assert.equal(model.getLineContent(1), 'function foo (params: string) {'); - assert.equal(model.getLineContent(2), ' '); - assert.equal(model.getLineContent(3), '}'); + assert.strictEqual(model.getLineContent(1), 'function foo (params: string) {'); + assert.strictEqual(model.getLineContent(2), ' '); + assert.strictEqual(model.getLineContent(3), '}'); class TestCommand implements ICommand { @@ -2781,9 +2843,9 @@ suite('Editor Controller - Cursor Configuration', () => { } viewModel.executeCommand(new TestCommand(), 'autoFormat'); - assert.equal(model.getLineContent(1), 'function foo(params: string) {'); - assert.equal(model.getLineContent(2), ' '); - assert.equal(model.getLineContent(3), '}'); + assert.strictEqual(model.getLineContent(1), 'function foo(params: string) {'); + assert.strictEqual(model.getLineContent(2), ' '); + assert.strictEqual(model.getLineContent(3), '}'); }); mode.dispose(); }); @@ -2803,27 +2865,27 @@ suite('Editor Controller - Cursor Configuration', () => { moveTo(editor, viewModel, 3, 1); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), ' if (a) {'); - assert.equal(model.getLineContent(2), ' '); - assert.equal(model.getLineContent(3), ' '); - assert.equal(model.getLineContent(4), ''); - assert.equal(model.getLineContent(5), ' }'); + assert.strictEqual(model.getLineContent(1), ' if (a) {'); + assert.strictEqual(model.getLineContent(2), ' '); + assert.strictEqual(model.getLineContent(3), ' '); + assert.strictEqual(model.getLineContent(4), ''); + assert.strictEqual(model.getLineContent(5), ' }'); moveTo(editor, viewModel, 4, 1); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), ' if (a) {'); - assert.equal(model.getLineContent(2), ' '); - assert.equal(model.getLineContent(3), ''); - assert.equal(model.getLineContent(4), ' '); - assert.equal(model.getLineContent(5), ' }'); + assert.strictEqual(model.getLineContent(1), ' if (a) {'); + assert.strictEqual(model.getLineContent(2), ' '); + assert.strictEqual(model.getLineContent(3), ''); + assert.strictEqual(model.getLineContent(4), ' '); + assert.strictEqual(model.getLineContent(5), ' }'); moveTo(editor, viewModel, 5, model.getLineMaxColumn(5)); viewModel.type('something', 'keyboard'); - assert.equal(model.getLineContent(1), ' if (a) {'); - assert.equal(model.getLineContent(2), ' '); - assert.equal(model.getLineContent(3), ''); - assert.equal(model.getLineContent(4), ''); - assert.equal(model.getLineContent(5), ' }something'); + assert.strictEqual(model.getLineContent(1), ' if (a) {'); + assert.strictEqual(model.getLineContent(2), ' '); + assert.strictEqual(model.getLineContent(3), ''); + assert.strictEqual(model.getLineContent(4), ''); + assert.strictEqual(model.getLineContent(5), ' }something'); }); model.dispose(); @@ -2841,46 +2903,46 @@ suite('Editor Controller - Cursor Configuration', () => { // Move cursor to the end, verify that we do not trim whitespaces if line has values moveTo(editor, viewModel, 1, model.getLineContent(1).length + 1); viewModel.type('\n', 'keyboard'); - assert.equal(model.getLineContent(1), ' some line abc '); - assert.equal(model.getLineContent(2), ' '); + assert.strictEqual(model.getLineContent(1), ' some line abc '); + assert.strictEqual(model.getLineContent(2), ' '); // Try to enter again, we should trimmed previous line viewModel.type('\n', 'keyboard'); - assert.equal(model.getLineContent(1), ' some line abc '); - assert.equal(model.getLineContent(2), ''); - assert.equal(model.getLineContent(3), ' '); + assert.strictEqual(model.getLineContent(1), ' some line abc '); + assert.strictEqual(model.getLineContent(2), ''); + assert.strictEqual(model.getLineContent(3), ' '); // More whitespaces CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), ' some line abc '); - assert.equal(model.getLineContent(2), ''); - assert.equal(model.getLineContent(3), ' '); + assert.strictEqual(model.getLineContent(1), ' some line abc '); + assert.strictEqual(model.getLineContent(2), ''); + assert.strictEqual(model.getLineContent(3), ' '); // Enter and verify that trimmed again viewModel.type('\n', 'keyboard'); - assert.equal(model.getLineContent(1), ' some line abc '); - assert.equal(model.getLineContent(2), ''); - assert.equal(model.getLineContent(3), ''); - assert.equal(model.getLineContent(4), ' '); + assert.strictEqual(model.getLineContent(1), ' some line abc '); + assert.strictEqual(model.getLineContent(2), ''); + assert.strictEqual(model.getLineContent(3), ''); + assert.strictEqual(model.getLineContent(4), ' '); // Trimmed if we will keep only text moveTo(editor, viewModel, 1, 5); viewModel.type('\n', 'keyboard'); - assert.equal(model.getLineContent(1), ' '); - assert.equal(model.getLineContent(2), ' some line abc '); - assert.equal(model.getLineContent(3), ''); - assert.equal(model.getLineContent(4), ''); - assert.equal(model.getLineContent(5), ''); + assert.strictEqual(model.getLineContent(1), ' '); + assert.strictEqual(model.getLineContent(2), ' some line abc '); + assert.strictEqual(model.getLineContent(3), ''); + assert.strictEqual(model.getLineContent(4), ''); + assert.strictEqual(model.getLineContent(5), ''); // Trimmed if we will keep only text by selection moveTo(editor, viewModel, 2, 5); moveTo(editor, viewModel, 3, 1, true); viewModel.type('\n', 'keyboard'); - assert.equal(model.getLineContent(1), ' '); - assert.equal(model.getLineContent(2), ' '); - assert.equal(model.getLineContent(3), ' '); - assert.equal(model.getLineContent(4), ''); - assert.equal(model.getLineContent(5), ''); + assert.strictEqual(model.getLineContent(1), ' '); + assert.strictEqual(model.getLineContent(2), ' '); + assert.strictEqual(model.getLineContent(3), ' '); + assert.strictEqual(model.getLineContent(4), ''); + assert.strictEqual(model.getLineContent(5), ''); }); model.dispose(); @@ -2901,7 +2963,7 @@ suite('Editor Controller - Cursor Configuration', () => { moveTo(editor, viewModel, 3, model.getLineMaxColumn(3)); viewModel.type('\n', 'keyboard'); - assert.equal(model.getValue(), [ + assert.strictEqual(model.getValue(), [ ' function f() {', ' // I\'m gonna copy this line', ' return 3;', @@ -2911,7 +2973,7 @@ suite('Editor Controller - Cursor Configuration', () => { assertCursor(viewModel, new Position(4, model.getLineMaxColumn(4))); viewModel.paste(' // I\'m gonna copy this line\n', true); - assert.equal(model.getValue(), [ + assert.strictEqual(model.getValue(), [ ' function f() {', ' // I\'m gonna copy this line', ' return 3;', @@ -2941,7 +3003,7 @@ suite('Editor Controller - Cursor Configuration', () => { editor.setSelections([new Selection(4, 10, 4, 10)]); viewModel.paste(' // I\'m gonna copy this line\n', true); - assert.equal(model.getValue(), [ + assert.strictEqual(model.getValue(), [ ' function f() {', ' // I\'m gonna copy this line', ' // Another line', @@ -2968,7 +3030,7 @@ suite('Editor Controller - Cursor Configuration', () => { // DeleteLeft removes just one whitespace moveTo(editor, viewModel, 2, 9); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), ' a '); + assert.strictEqual(model.getLineContent(2), ' a '); }); model.dispose(); @@ -2987,54 +3049,54 @@ suite('Editor Controller - Cursor Configuration', () => { // DeleteLeft does not remove tab size, because some text exists before moveTo(editor, viewModel, 2, model.getLineContent(2).length + 1); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), ' a '); + assert.strictEqual(model.getLineContent(2), ' a '); // DeleteLeft removes tab size = 4 moveTo(editor, viewModel, 2, 9); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), ' a '); + assert.strictEqual(model.getLineContent(2), ' a '); // DeleteLeft removes tab size = 4 CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), 'a '); + assert.strictEqual(model.getLineContent(2), 'a '); // Undo DeleteLeft - get us back to original indentation CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), ' a '); + assert.strictEqual(model.getLineContent(2), ' a '); // Nothing is broken when cursor is in (1,1) moveTo(editor, viewModel, 1, 1); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), ' \t \t x'); + assert.strictEqual(model.getLineContent(1), ' \t \t x'); // DeleteLeft stops at tab stops even in mixed whitespace case moveTo(editor, viewModel, 1, 10); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), ' \t \t x'); + assert.strictEqual(model.getLineContent(1), ' \t \t x'); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), ' \t \tx'); + assert.strictEqual(model.getLineContent(1), ' \t \tx'); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), ' \tx'); + assert.strictEqual(model.getLineContent(1), ' \tx'); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), 'x'); + assert.strictEqual(model.getLineContent(1), 'x'); // DeleteLeft on last line moveTo(editor, viewModel, 3, model.getLineContent(3).length + 1); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(3), ''); + assert.strictEqual(model.getLineContent(3), ''); // DeleteLeft with removing new line symbol CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), 'x\n a '); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'x\n a '); // In case of selection DeleteLeft only deletes selected text moveTo(editor, viewModel, 2, 3); moveTo(editor, viewModel, 2, 4, true); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), ' a '); + assert.strictEqual(model.getLineContent(2), ' a '); }); model.dispose(); @@ -3052,55 +3114,55 @@ suite('Editor Controller - Cursor Configuration', () => { withTestCodeEditor(null, { model: model }, (editor, viewModel) => { viewModel.type('\n', 'keyboard'); - assert.equal(model.getValue(EndOfLinePreference.LF), '\n', 'assert1'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\n', 'assert1'); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), '\n\t', 'assert2'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\n\t', 'assert2'); viewModel.type('y', 'keyboard'); - assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty', 'assert2'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\n\ty', 'assert2'); viewModel.type('\n', 'keyboard'); - assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\n\t', 'assert3'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\n\ty\n\t', 'assert3'); viewModel.type('x'); - assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\n\tx', 'assert4'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\n\ty\n\tx', 'assert4'); CoreNavigationCommands.CursorLeft.runCoreEditorCommand(viewModel, {}); - assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\n\tx', 'assert5'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\n\ty\n\tx', 'assert5'); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\nx', 'assert6'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\n\ty\nx', 'assert6'); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), '\n\tyx', 'assert7'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\n\tyx', 'assert7'); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), '\n\tx', 'assert8'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\n\tx', 'assert8'); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert9'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\nx', 'assert9'); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert10'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'x', 'assert10'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert11'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\nx', 'assert11'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\nx', 'assert12'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\n\ty\nx', 'assert12'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\n\tx', 'assert13'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\n\ty\n\tx', 'assert13'); CoreEditingCommands.Redo.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\nx', 'assert14'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\n\ty\nx', 'assert14'); CoreEditingCommands.Redo.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert15'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\nx', 'assert15'); CoreEditingCommands.Redo.runEditorCommand(null, editor, null); - assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert16'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'x', 'assert16'); }); model.dispose(); @@ -3124,8 +3186,8 @@ suite('Editor Controller - Cursor Configuration', () => { const afterVersion = model.getVersionId(); const afterAltVersion = model.getAlternativeVersionId(); - assert.notEqual(beforeVersion, afterVersion); - assert.equal(beforeAltVersion, afterAltVersion); + assert.notStrictEqual(beforeVersion, afterVersion); + assert.strictEqual(beforeAltVersion, afterAltVersion); }); model.dispose(); @@ -3181,7 +3243,7 @@ suite('Editor Controller - Indentation Rules', () => { viewModel.type('}', 'keyboard'); assertCursor(viewModel, new Selection(2, 2, 2, 2)); - assert.equal(model.getLineContent(2), '}', '001'); + assert.strictEqual(model.getLineContent(2), '}', '001'); }); }); @@ -3274,7 +3336,7 @@ suite('Editor Controller - Indentation Rules', () => { viewModel.type('\n', 'keyboard'); assertCursor(viewModel, new Selection(4, 1, 4, 1)); - assert.equal(model.getLineContent(3), 'return true;', '001'); + assert.strictEqual(model.getLineContent(3), 'return true;', '001'); }); }); @@ -3295,7 +3357,7 @@ suite('Editor Controller - Indentation Rules', () => { viewModel.type('\n', 'keyboard'); assertCursor(viewModel, new Selection(5, 1, 5, 1)); - assert.equal(model.getLineContent(4), '\t}', '001'); + assert.strictEqual(model.getLineContent(4), '\t}', '001'); }); }); @@ -3363,7 +3425,7 @@ suite('Editor Controller - Indentation Rules', () => { assertCursor(viewModel, new Selection(3, 16, 3, 16)); viewModel.type('\n', 'keyboard'); - assert.equal(model.getLineContent(3), ' if (true) {'); + assert.strictEqual(model.getLineContent(3), ' if (true) {'); assertCursor(viewModel, new Selection(4, 9, 4, 9)); }); }); @@ -3388,7 +3450,7 @@ suite('Editor Controller - Indentation Rules', () => { assertCursor(viewModel, new Selection(3, 16, 3, 16)); viewModel.type('\n', 'keyboard'); - assert.equal(model.getLineContent(3), ' if (true) {'); + assert.strictEqual(model.getLineContent(3), ' if (true) {'); assertCursor(viewModel, new Selection(4, 3, 4, 3)); }); }); @@ -3411,7 +3473,7 @@ suite('Editor Controller - Indentation Rules', () => { assertCursor(viewModel, new Selection(5, 4, 5, 4)); viewModel.type('\n', 'keyboard'); - assert.equal(model.getLineContent(5), '\t\t}'); + assert.strictEqual(model.getLineContent(5), '\t\t}'); assertCursor(viewModel, new Selection(6, 3, 6, 3)); }); }); @@ -3432,7 +3494,7 @@ suite('Editor Controller - Indentation Rules', () => { viewModel.type('\n', 'keyboard'); assertCursor(viewModel, new Selection(4, 3, 4, 3)); - assert.equal(model.getLineContent(4), '\t\t true;', '001'); + assert.strictEqual(model.getLineContent(4), '\t\t true;', '001'); }); }); @@ -3452,7 +3514,7 @@ suite('Editor Controller - Indentation Rules', () => { viewModel.type('\n', 'keyboard'); assertCursor(viewModel, new Selection(4, 3, 4, 3)); - assert.equal(model.getLineContent(4), '\t\treturn true;', '001'); + assert.strictEqual(model.getLineContent(4), '\t\treturn true;', '001'); }); }); @@ -3471,7 +3533,7 @@ suite('Editor Controller - Indentation Rules', () => { viewModel.type('\n', 'keyboard'); assertCursor(viewModel, new Selection(4, 5, 4, 5)); - assert.equal(model.getLineContent(4), ' true;', '001'); + assert.strictEqual(model.getLineContent(4), ' true;', '001'); }); }); @@ -3491,14 +3553,14 @@ suite('Editor Controller - Indentation Rules', () => { viewModel.type('\n', 'keyboard'); assertCursor(viewModel, new Selection(4, 2, 4, 2)); - assert.equal(model.getLineContent(4), '\t\treturn true;', '001'); + assert.strictEqual(model.getLineContent(4), '\t\treturn true;', '001'); moveTo(editor, viewModel, 4, 1, false); assertCursor(viewModel, new Selection(4, 1, 4, 1)); viewModel.type('\n', 'keyboard'); assertCursor(viewModel, new Selection(5, 1, 5, 1)); - assert.equal(model.getLineContent(5), '\t\treturn true;', '002'); + assert.strictEqual(model.getLineContent(5), '\t\treturn true;', '002'); }); }); @@ -3518,14 +3580,14 @@ suite('Editor Controller - Indentation Rules', () => { viewModel.type('\n', 'keyboard'); assertCursor(viewModel, new Selection(4, 3, 4, 3)); - assert.equal(model.getLineContent(4), '\t\t\treturn true;', '001'); + assert.strictEqual(model.getLineContent(4), '\t\t\treturn true;', '001'); moveTo(editor, viewModel, 4, 1, false); assertCursor(viewModel, new Selection(4, 1, 4, 1)); viewModel.type('\n', 'keyboard'); assertCursor(viewModel, new Selection(5, 1, 5, 1)); - assert.equal(model.getLineContent(5), '\t\t\treturn true;', '002'); + assert.strictEqual(model.getLineContent(5), '\t\t\treturn true;', '002'); }); }); @@ -3544,12 +3606,12 @@ suite('Editor Controller - Indentation Rules', () => { viewModel.type('\n', 'keyboard'); assertCursor(viewModel, new Selection(4, 2, 4, 2)); - assert.equal(model.getLineContent(4), ' return true;', '001'); + assert.strictEqual(model.getLineContent(4), ' return true;', '001'); moveTo(editor, viewModel, 4, 3, false); viewModel.type('\n', 'keyboard'); assertCursor(viewModel, new Selection(5, 3, 5, 3)); - assert.equal(model.getLineContent(5), ' return true;', '002'); + assert.strictEqual(model.getLineContent(5), ' return true;', '002'); }); }); @@ -3577,12 +3639,12 @@ suite('Editor Controller - Indentation Rules', () => { viewModel.type('\n', 'keyboard'); assertCursor(viewModel, new Selection(4, 4, 4, 4)); - assert.equal(model.getLineContent(4), ' return true;', '001'); + assert.strictEqual(model.getLineContent(4), ' return true;', '001'); moveTo(editor, viewModel, 9, 4, false); viewModel.type('\n', 'keyboard'); assertCursor(viewModel, new Selection(10, 5, 10, 5)); - assert.equal(model.getLineContent(10), ' return true;', '001'); + assert.strictEqual(model.getLineContent(10), ' return true;', '001'); }); }); @@ -3604,7 +3666,7 @@ suite('Editor Controller - Indentation Rules', () => { viewModel.type('\n', 'keyboard'); assertCursor(viewModel, new Selection(4, 3, 4, 3)); - assert.equal(model.getLineContent(4), ' return true;', '001'); + assert.strictEqual(model.getLineContent(4), ' return true;', '001'); }); }); @@ -3626,7 +3688,7 @@ suite('Editor Controller - Indentation Rules', () => { assertCursor(viewModel, new Selection(3, 8, 2, 12)); viewModel.type('\n', 'keyboard'); - assert.equal(model.getLineContent(3), '\treturn x;'); + assert.strictEqual(model.getLineContent(3), '\treturn x;'); assertCursor(viewModel, new Position(3, 2)); }); }); @@ -3649,7 +3711,7 @@ suite('Editor Controller - Indentation Rules', () => { assertCursor(viewModel, new Selection(2, 12, 3, 8)); viewModel.type('\n', 'keyboard'); - assert.equal(model.getLineContent(3), '\treturn x;'); + assert.strictEqual(model.getLineContent(3), '\treturn x;'); assertCursor(viewModel, new Position(3, 2)); }); }); @@ -3670,9 +3732,9 @@ suite('Editor Controller - Indentation Rules', () => { assertCursor(viewModel, new Selection(5, 3, 5, 3)); viewModel.type('\n', 'keyboard'); - assert.equal(model.getLineContent(6), '\t'); + assert.strictEqual(model.getLineContent(6), '\t'); assertCursor(viewModel, new Selection(6, 2, 6, 2)); - assert.equal(model.getLineContent(5), '\t}'); + assert.strictEqual(model.getLineContent(5), '\t}'); }); }); @@ -3690,7 +3752,7 @@ suite('Editor Controller - Indentation Rules', () => { viewModel.type('\n', 'keyboard'); assertCursor(viewModel, new Selection(4, 2, 4, 2)); - assert.equal(model.getLineContent(4), '\t'); + assert.strictEqual(model.getLineContent(4), '\t'); }); }); @@ -3715,7 +3777,7 @@ suite('Editor Controller - Indentation Rules', () => { assertCursor(viewModel, new Selection(4, 1, 4, 1)); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(4), '\t\t'); + assert.strictEqual(model.getLineContent(4), '\t\t'); }); model.dispose(); @@ -3743,7 +3805,7 @@ suite('Editor Controller - Indentation Rules', () => { assertCursor(viewModel, new Selection(4, 2, 4, 2)); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(4), '\t\t\t'); + assert.strictEqual(model.getLineContent(4), '\t\t\t'); }); model.dispose(); @@ -3771,7 +3833,7 @@ suite('Editor Controller - Indentation Rules', () => { assertCursor(viewModel, new Selection(4, 1, 4, 1)); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(4), '\t\t\t'); + assert.strictEqual(model.getLineContent(4), '\t\t\t'); }); model.dispose(); @@ -3798,7 +3860,7 @@ suite('Editor Controller - Indentation Rules', () => { assertCursor(viewModel, new Selection(4, 3, 4, 3)); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(4), '\t\t\t\t'); + assert.strictEqual(model.getLineContent(4), '\t\t\t\t'); }); model.dispose(); @@ -3825,7 +3887,7 @@ suite('Editor Controller - Indentation Rules', () => { assertCursor(viewModel, new Selection(4, 4, 4, 4)); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(4), '\t\t\t\t\t'); + assert.strictEqual(model.getLineContent(4), '\t\t\t\t\t'); }); model.dispose(); @@ -3849,11 +3911,11 @@ suite('Editor Controller - Indentation Rules', () => { moveTo(editor, viewModel, 3, 1); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), ' if (a) {'); - assert.equal(model.getLineContent(2), ' '); - assert.equal(model.getLineContent(3), ' '); - assert.equal(model.getLineContent(4), ''); - assert.equal(model.getLineContent(5), ' }'); + assert.strictEqual(model.getLineContent(1), ' if (a) {'); + assert.strictEqual(model.getLineContent(2), ' '); + assert.strictEqual(model.getLineContent(3), ' '); + assert.strictEqual(model.getLineContent(4), ''); + assert.strictEqual(model.getLineContent(5), ' }'); }); model.dispose(); @@ -3880,7 +3942,7 @@ suite('Editor Controller - Indentation Rules', () => { assertCursor(viewModel, new Selection(4, 7, 4, 7)); viewModel.type('d', 'keyboard'); - assert.equal(model.getLineContent(4), ' end'); + assert.strictEqual(model.getLineContent(4), ' end'); }); rubyMode.dispose(); @@ -3903,7 +3965,7 @@ suite('Editor Controller - Indentation Rules', () => { viewModel.type('e', 'keyboard'); assertCursor(viewModel, new Selection(5, 4, 5, 4)); - assert.equal(model.getLineContent(5), '\t}e', 'This line should not decrease indent'); + assert.strictEqual(model.getLineContent(5), '\t}e', 'This line should not decrease indent'); }); }); @@ -3924,7 +3986,7 @@ suite('Editor Controller - Indentation Rules', () => { viewModel.type(' ', 'keyboard'); assertCursor(viewModel, new Selection(2, 4, 2, 4)); - assert.equal(model.getLineContent(2), '\t ) {', 'This line should not decrease indent'); + assert.strictEqual(model.getLineContent(2), '\t ) {', 'This line should not decrease indent'); }); }); @@ -3943,7 +4005,7 @@ suite('Editor Controller - Indentation Rules', () => { viewModel.type('}', 'keyboard'); assertCursor(viewModel, new Selection(3, 2, 3, 2)); - assert.equal(model.getLineContent(3), '}'); + assert.strictEqual(model.getLineContent(3), '}'); }); }); @@ -3990,7 +4052,7 @@ suite('Editor Controller - Indentation Rules', () => { assertCursor(viewModel, new Selection(7, 6, 7, 6)); viewModel.type('\n', 'keyboard'); - assert.equal(model.getValue(), + assert.strictEqual(model.getValue(), [ 'class ItemCtrl {', ' getPropertiesByItemId(id) {', @@ -4010,6 +4072,47 @@ suite('Editor Controller - Indentation Rules', () => { mode.dispose(); }); + test('issue #115304: OnEnter broken for TS', () => { + class JSMode extends MockMode { + private static readonly _id = new LanguageIdentifier('indentRulesMode', 4); + constructor() { + super(JSMode._id); + this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), { + onEnterRules: javascriptOnEnterRules + })); + } + } + + const mode = new JSMode(); + const model = createTextModel( + [ + '/** */', + 'function f() {}', + ].join('\n'), + undefined, + mode.getLanguageIdentifier() + ); + + withTestCodeEditor(null, { model: model, autoIndent: 'advanced' }, (editor, viewModel) => { + moveTo(editor, viewModel, 1, 4, false); + assertCursor(viewModel, new Selection(1, 4, 1, 4)); + + viewModel.type('\n', 'keyboard'); + assert.strictEqual(model.getValue(), + [ + '/**', + ' * ', + ' */', + 'function f() {}', + ].join('\n') + ); + assertCursor(viewModel, new Selection(2, 4, 2, 4)); + }); + + model.dispose(); + mode.dispose(); + }); + test('issue #38261: TAB key results in bizarre indentation in C++ mode ', () => { class CppMode extends MockMode { private static readonly _id = new LanguageIdentifier('indentRulesMode', 4); @@ -4054,7 +4157,7 @@ suite('Editor Controller - Indentation Rules', () => { assertCursor(viewModel, new Selection(8, 1, 8, 1)); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); - assert.equal(model.getValue(), + assert.strictEqual(model.getValue(), [ 'int main() {', ' return 0;', @@ -4067,7 +4170,7 @@ suite('Editor Controller - Indentation Rules', () => { ')', ].join('\n') ); - assert.deepEqual(viewModel.getSelection(), new Selection(8, 3, 8, 3)); + assert.deepStrictEqual(viewModel.getSelection(), new Selection(8, 3, 8, 3)); }); model.dispose(); @@ -4144,31 +4247,43 @@ suite('Editor Controller - Indentation Rules', () => { assertCursor(viewModel, new Selection(3, 19, 3, 19)); viewModel.type('\n', 'keyboard'); - assert.deepEqual(model.getLineContent(4), ' '); + assert.deepStrictEqual(model.getLineContent(4), ' '); moveTo(editor, viewModel, 5, 18, false); assertCursor(viewModel, new Selection(5, 18, 5, 18)); viewModel.type('\n', 'keyboard'); - assert.deepEqual(model.getLineContent(6), ' '); + assert.deepStrictEqual(model.getLineContent(6), ' '); moveTo(editor, viewModel, 7, 15, false); assertCursor(viewModel, new Selection(7, 15, 7, 15)); viewModel.type('\n', 'keyboard'); - assert.deepEqual(model.getLineContent(8), ' '); - assert.deepEqual(model.getLineContent(9), ' ]'); + assert.deepStrictEqual(model.getLineContent(8), ' '); + assert.deepStrictEqual(model.getLineContent(9), ' ]'); moveTo(editor, viewModel, 10, 18, false); assertCursor(viewModel, new Selection(10, 18, 10, 18)); viewModel.type('\n', 'keyboard'); - assert.deepEqual(model.getLineContent(11), ' ]'); + assert.deepStrictEqual(model.getLineContent(11), ' ]'); }); model.dispose(); mode.dispose(); }); + + test('issue #111128: Multicursor `Enter` issue with indentation', () => { + const model = createTextModel(' let a, b, c;', { detectIndentation: false, insertSpaces: false, tabSize: 4 }, mode.getLanguageIdentifier()); + withTestCodeEditor(null, { model: model }, (editor, viewModel) => { + editor.setSelections([ + new Selection(1, 11, 1, 11), + new Selection(1, 14, 1, 14), + ]); + viewModel.type('\n', 'keyboard'); + assert.strictEqual(model.getValue(), ' let a,\n\t b,\n\t c;'); + }); + }); }); interface ICursorOpts { @@ -4218,7 +4333,7 @@ suite('ElectricCharacter', () => { }, (editor, model, viewModel) => { moveTo(editor, viewModel, 2, 1); viewModel.type('*', 'keyboard'); - assert.deepEqual(model.getLineContent(2), '*'); + assert.deepStrictEqual(model.getLineContent(2), '*'); }); mode.dispose(); }); @@ -4234,7 +4349,7 @@ suite('ElectricCharacter', () => { }, (editor, model, viewModel) => { moveTo(editor, viewModel, 2, 1); viewModel.type('}', 'keyboard'); - assert.deepEqual(model.getLineContent(2), ' }'); + assert.deepStrictEqual(model.getLineContent(2), ' }'); }); mode.dispose(); }); @@ -4250,7 +4365,7 @@ suite('ElectricCharacter', () => { }, (editor, model, viewModel) => { moveTo(editor, viewModel, 2, 5); viewModel.type('}', 'keyboard'); - assert.deepEqual(model.getLineContent(2), ' }'); + assert.deepStrictEqual(model.getLineContent(2), ' }'); }); mode.dispose(); }); @@ -4268,7 +4383,7 @@ suite('ElectricCharacter', () => { }, (editor, model, viewModel) => { moveTo(editor, viewModel, 4, 1); viewModel.type('}', 'keyboard'); - assert.deepEqual(model.getLineContent(4), ' } '); + assert.deepStrictEqual(model.getLineContent(4), ' } '); }); mode.dispose(); }); @@ -4286,7 +4401,7 @@ suite('ElectricCharacter', () => { }, (editor, model, viewModel) => { moveTo(editor, viewModel, 4, 6); viewModel.type('}', 'keyboard'); - assert.deepEqual(model.getLineContent(4), ' } }'); + assert.deepStrictEqual(model.getLineContent(4), ' } }'); }); mode.dispose(); }); @@ -4302,7 +4417,7 @@ suite('ElectricCharacter', () => { }, (editor, model, viewModel) => { moveTo(editor, viewModel, 2, 1); viewModel.type('}', 'keyboard'); - assert.deepEqual(model.getLineContent(2), ' }// hello'); + assert.deepStrictEqual(model.getLineContent(2), ' }// hello'); }); mode.dispose(); }); @@ -4318,7 +4433,7 @@ suite('ElectricCharacter', () => { }, (editor, model, viewModel) => { moveTo(editor, viewModel, 2, 3); viewModel.type('}', 'keyboard'); - assert.deepEqual(model.getLineContent(2), ' }'); + assert.deepStrictEqual(model.getLineContent(2), ' }'); }); mode.dispose(); }); @@ -4334,7 +4449,7 @@ suite('ElectricCharacter', () => { }, (editor, model, viewModel) => { moveTo(editor, viewModel, 2, 2); viewModel.type('}', 'keyboard'); - assert.deepEqual(model.getLineContent(2), 'a}'); + assert.deepStrictEqual(model.getLineContent(2), 'a}'); }); mode.dispose(); }); @@ -4351,7 +4466,7 @@ suite('ElectricCharacter', () => { }, (editor, model, viewModel) => { moveTo(editor, viewModel, 2, 13); viewModel.type('*', 'keyboard'); - assert.deepEqual(model.getLineContent(2), ' ( 1 + 2 ) *'); + assert.deepStrictEqual(model.getLineContent(2), ' ( 1 + 2 ) *'); }); mode.dispose(); }); @@ -4370,8 +4485,8 @@ suite('ElectricCharacter', () => { changeText = e.changes[0].text; }); viewModel.type(')', 'keyboard'); - assert.deepEqual(model.getLineContent(1), '(div)'); - assert.deepEqual(changeText, ')'); + assert.deepStrictEqual(model.getLineContent(1), '(div)'); + assert.deepStrictEqual(changeText, ')'); }); mode.dispose(); }); @@ -4388,7 +4503,7 @@ suite('ElectricCharacter', () => { }, (editor, model, viewModel) => { moveTo(editor, viewModel, 3, 3); viewModel.type(')', 'keyboard'); - assert.deepEqual(model.getLineContent(3), '\t3)'); + assert.deepStrictEqual(model.getLineContent(3), '\t3)'); }); mode.dispose(); }); @@ -4404,7 +4519,7 @@ suite('ElectricCharacter', () => { }, (editor, model, viewModel) => { moveTo(editor, viewModel, 2, 3); viewModel.type('*', 'keyboard'); - assert.deepEqual(model.getLineContent(2), '/** */'); + assert.deepStrictEqual(model.getLineContent(2), '/** */'); }); mode.dispose(); }); @@ -4420,7 +4535,7 @@ suite('ElectricCharacter', () => { }, (editor, model, viewModel) => { moveTo(editor, viewModel, 2, 5); viewModel.type('*', 'keyboard'); - assert.deepEqual(model.getLineContent(2), ' /** */'); + assert.deepStrictEqual(model.getLineContent(2), ' /** */'); }); mode.dispose(); }); @@ -4437,7 +4552,7 @@ suite('ElectricCharacter', () => { moveTo(editor, viewModel, 2, 5); moveTo(editor, viewModel, 2, 1, true); viewModel.type('}', 'keyboard'); - assert.deepEqual(model.getLineContent(2), '}'); + assert.deepStrictEqual(model.getLineContent(2), '}'); }); mode.dispose(); }); @@ -4513,7 +4628,7 @@ suite('autoClosingPairs', () => { let expected = lineContent.substr(0, column - 1) + expectedInsert + lineContent.substr(column - 1); moveTo(editor, viewModel, lineNumber, column); viewModel.type(chr, 'keyboard'); - assert.deepEqual(model.getLineContent(lineNumber), expected, message); + assert.deepStrictEqual(model.getLineContent(lineNumber), expected, message); model.undo(); } @@ -4783,12 +4898,12 @@ suite('autoClosingPairs', () => { // type a ` viewModel.type('`', 'keyboard'); - assert.equal(model.getValue(), '`var` a = `asd`'); + assert.strictEqual(model.getValue(), '`var` a = `asd`'); // type a ( viewModel.type('(', 'keyboard'); - assert.equal(model.getValue(), '`(var)` a = `(asd)`'); + assert.strictEqual(model.getValue(), '`(var)` a = `(asd)`'); }); usingCursor({ @@ -4808,7 +4923,7 @@ suite('autoClosingPairs', () => { // type a ` viewModel.type('`', 'keyboard'); - assert.equal(model.getValue(), '` a = asd'); + assert.strictEqual(model.getValue(), '` a = asd'); }); usingCursor({ @@ -4827,11 +4942,11 @@ suite('autoClosingPairs', () => { // type a ` viewModel.type('`', 'keyboard'); - assert.equal(model.getValue(), '`var` a = asd'); + assert.strictEqual(model.getValue(), '`var` a = asd'); // type a ( viewModel.type('(', 'keyboard'); - assert.equal(model.getValue(), '`(` a = asd'); + assert.strictEqual(model.getValue(), '`(` a = asd'); }); usingCursor({ @@ -4850,11 +4965,11 @@ suite('autoClosingPairs', () => { // type a ( viewModel.type('(', 'keyboard'); - assert.equal(model.getValue(), '(var) a = asd'); + assert.strictEqual(model.getValue(), '(var) a = asd'); // type a ` viewModel.type('`', 'keyboard'); - assert.equal(model.getValue(), '(`) a = asd'); + assert.strictEqual(model.getValue(), '(`) a = asd'); }); mode.dispose(); }); @@ -5047,50 +5162,50 @@ suite('autoClosingPairs', () => { // First gif model.forceTokenization(model.getLineCount()); typeCharacters(viewModel, 'teste1 = teste\' ok'); - assert.equal(model.getLineContent(1), 'teste1 = teste\' ok'); + assert.strictEqual(model.getLineContent(1), 'teste1 = teste\' ok'); viewModel.setSelections('test', [new Selection(1, 1000, 1, 1000)]); typeCharacters(viewModel, '\n'); model.forceTokenization(model.getLineCount()); typeCharacters(viewModel, 'teste2 = teste \'ok'); - assert.equal(model.getLineContent(2), 'teste2 = teste \'ok\''); + assert.strictEqual(model.getLineContent(2), 'teste2 = teste \'ok\''); viewModel.setSelections('test', [new Selection(2, 1000, 2, 1000)]); typeCharacters(viewModel, '\n'); model.forceTokenization(model.getLineCount()); typeCharacters(viewModel, 'teste3 = teste" ok'); - assert.equal(model.getLineContent(3), 'teste3 = teste" ok'); + assert.strictEqual(model.getLineContent(3), 'teste3 = teste" ok'); viewModel.setSelections('test', [new Selection(3, 1000, 3, 1000)]); typeCharacters(viewModel, '\n'); model.forceTokenization(model.getLineCount()); typeCharacters(viewModel, 'teste4 = teste "ok'); - assert.equal(model.getLineContent(4), 'teste4 = teste "ok"'); + assert.strictEqual(model.getLineContent(4), 'teste4 = teste "ok"'); // Second gif viewModel.setSelections('test', [new Selection(4, 1000, 4, 1000)]); typeCharacters(viewModel, '\n'); model.forceTokenization(model.getLineCount()); typeCharacters(viewModel, 'teste \''); - assert.equal(model.getLineContent(5), 'teste \'\''); + assert.strictEqual(model.getLineContent(5), 'teste \'\''); viewModel.setSelections('test', [new Selection(5, 1000, 5, 1000)]); typeCharacters(viewModel, '\n'); model.forceTokenization(model.getLineCount()); typeCharacters(viewModel, 'teste "'); - assert.equal(model.getLineContent(6), 'teste ""'); + assert.strictEqual(model.getLineContent(6), 'teste ""'); viewModel.setSelections('test', [new Selection(6, 1000, 6, 1000)]); typeCharacters(viewModel, '\n'); model.forceTokenization(model.getLineCount()); typeCharacters(viewModel, 'teste\''); - assert.equal(model.getLineContent(7), 'teste\''); + assert.strictEqual(model.getLineContent(7), 'teste\''); viewModel.setSelections('test', [new Selection(7, 1000, 7, 1000)]); typeCharacters(viewModel, '\n'); model.forceTokenization(model.getLineCount()); typeCharacters(viewModel, 'teste"'); - assert.equal(model.getLineContent(8), 'teste"'); + assert.strictEqual(model.getLineContent(8), 'teste"'); }); mode.dispose(); }); @@ -5337,7 +5452,7 @@ suite('autoClosingPairs', () => { viewModel.replacePreviousChar('è', 1, 'keyboard'); viewModel.endComposition('keyboard'); - assert.equal(model.getValue(), 'è'); + assert.strictEqual(model.getValue(), 'è'); }); mode.dispose(); }); @@ -5359,7 +5474,7 @@ suite('autoClosingPairs', () => { viewModel.replacePreviousChar('\'', 1, 'keyboard'); viewModel.endComposition('keyboard'); - assert.equal(model.getValue(), '\'test\''); + assert.strictEqual(model.getValue(), '\'test\''); }); mode.dispose(); }); @@ -5376,16 +5491,16 @@ suite('autoClosingPairs', () => { viewModel.setSelections('test', [new Selection(1, 13, 1, 13)]); viewModel.type('\'', 'keyboard'); - assert.equal(model.getValue(), 'console.log(\'\');'); + assert.strictEqual(model.getValue(), 'console.log(\'\');'); viewModel.type('it', 'keyboard'); - assert.equal(model.getValue(), 'console.log(\'it\');'); + assert.strictEqual(model.getValue(), 'console.log(\'it\');'); viewModel.type('\\', 'keyboard'); - assert.equal(model.getValue(), 'console.log(\'it\\\');'); + assert.strictEqual(model.getValue(), 'console.log(\'it\\\');'); viewModel.type('\'', 'keyboard'); - assert.equal(model.getValue(), 'console.log(\'it\\\'\');'); + assert.strictEqual(model.getValue(), 'console.log(\'it\\\'\');'); }); mode.dispose(); }); @@ -5402,19 +5517,19 @@ suite('autoClosingPairs', () => { viewModel.setSelections('test', [new Selection(1, 1, 1, 1)]); viewModel.type('\\', 'keyboard'); - assert.equal(model.getValue(), '\\'); + assert.strictEqual(model.getValue(), '\\'); viewModel.type('(', 'keyboard'); - assert.equal(model.getValue(), '\\()'); + assert.strictEqual(model.getValue(), '\\()'); viewModel.type('abc', 'keyboard'); - assert.equal(model.getValue(), '\\(abc)'); + assert.strictEqual(model.getValue(), '\\(abc)'); viewModel.type('\\', 'keyboard'); - assert.equal(model.getValue(), '\\(abc\\)'); + assert.strictEqual(model.getValue(), '\\(abc\\)'); viewModel.type(')', 'keyboard'); - assert.equal(model.getValue(), '\\(abc\\)'); + assert.strictEqual(model.getValue(), '\\(abc\\)'); }); mode.dispose(); }); @@ -5439,7 +5554,7 @@ suite('autoClosingPairs', () => { viewModel.replacePreviousChar('`', 1, 'keyboard'); viewModel.endComposition('keyboard'); - assert.equal(model.getValue(), '`hello\nworld'); + assert.strictEqual(model.getValue(), '`hello\nworld'); assertCursor(viewModel, new Selection(1, 2, 2, 2)); }); mode.dispose(); @@ -5462,14 +5577,14 @@ suite('autoClosingPairs', () => { viewModel.type('\'', 'keyboard'); viewModel.replacePreviousChar('\'', 1, 'keyboard'); viewModel.endComposition('keyboard'); - assert.equal(model.getValue(), '\'\''); + assert.strictEqual(model.getValue(), '\'\''); // Typing one more ' + space viewModel.startComposition(); viewModel.type('\'', 'keyboard'); viewModel.replacePreviousChar('\'', 1, 'keyboard'); viewModel.endComposition('keyboard'); - assert.equal(model.getValue(), '\'\''); + assert.strictEqual(model.getValue(), '\'\''); // Typing ' as a closing tag model.setValue('\'abc'); @@ -5479,7 +5594,7 @@ suite('autoClosingPairs', () => { viewModel.replacePreviousChar('\'', 1, 'keyboard'); viewModel.endComposition('keyboard'); - assert.equal(model.getValue(), '\'abc\''); + assert.strictEqual(model.getValue(), '\'abc\''); // quotes before the newly added character are all paired. model.setValue('\'abc\'def '); @@ -5489,7 +5604,7 @@ suite('autoClosingPairs', () => { viewModel.replacePreviousChar('\'', 1, 'keyboard'); viewModel.endComposition('keyboard'); - assert.equal(model.getValue(), '\'abc\'def \'\''); + assert.strictEqual(model.getValue(), '\'abc\'def \'\''); // No auto closing if there is non-whitespace character after the cursor model.setValue('abc'); @@ -5507,7 +5622,7 @@ suite('autoClosingPairs', () => { viewModel.replacePreviousChar('\'', 1, 'keyboard'); viewModel.endComposition('keyboard'); - assert.equal(model.getValue(), 'abc\''); + assert.strictEqual(model.getValue(), 'abc\''); }); mode.dispose(); }); @@ -5527,7 +5642,7 @@ suite('autoClosingPairs', () => { viewModel.type('a', 'keyboard'); viewModel.replacePreviousChar('', 1, 'keyboard'); viewModel.endComposition('keyboard'); - assert.equal(model.getValue(), '{}'); + assert.strictEqual(model.getValue(), '{}'); }); mode.dispose(); }); @@ -5549,7 +5664,7 @@ suite('autoClosingPairs', () => { // type a ` viewModel.type('`', 'keyboard'); - assert.equal(model.getValue(), 'var a = `asd`'); + assert.strictEqual(model.getValue(), 'var a = `asd`'); }); mode.dispose(); }); @@ -5577,14 +5692,14 @@ suite('autoClosingPairs', () => { new Selection(1, 12, 1, 13) ]); viewModel.type('"', 'keyboard'); - assert.equal(model.getValue(EndOfLinePreference.LF), 'var x = "hi";', 'assert1'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'var x = "hi";', 'assert1'); editor.setSelections([ new Selection(1, 9, 1, 10), new Selection(1, 12, 1, 13) ]); viewModel.type('\'', 'keyboard'); - assert.equal(model.getValue(EndOfLinePreference.LF), 'var x = \'hi\';', 'assert2'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'var x = \'hi\';', 'assert2'); }); model.dispose(); @@ -5610,7 +5725,7 @@ suite('autoClosingPairs', () => { // delete left CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getValue(), 'va a = )'); + assert.strictEqual(model.getValue(), 'va a = )'); }); model.dispose(); mode.dispose(); @@ -5657,20 +5772,20 @@ suite('Undo stops', () => { withTestCodeEditor(null, { model: model }, (editor, viewModel) => { viewModel.setSelections('test', [new Selection(1, 3, 1, 3)]); viewModel.type('first', 'keyboard'); - assert.equal(model.getLineContent(1), 'A first line'); + assert.strictEqual(model.getLineContent(1), 'A first line'); assertCursor(viewModel, new Selection(1, 8, 1, 8)); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), 'A fir line'); + assert.strictEqual(model.getLineContent(1), 'A fir line'); assertCursor(viewModel, new Selection(1, 6, 1, 6)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), 'A first line'); + assert.strictEqual(model.getLineContent(1), 'A first line'); assertCursor(viewModel, new Selection(1, 8, 1, 8)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), 'A line'); + assert.strictEqual(model.getLineContent(1), 'A line'); assertCursor(viewModel, new Selection(1, 3, 1, 3)); }); }); @@ -5686,20 +5801,20 @@ suite('Undo stops', () => { withTestCodeEditor(null, { model: model }, (editor, viewModel) => { viewModel.setSelections('test', [new Selection(1, 3, 1, 3)]); viewModel.type('first', 'keyboard'); - assert.equal(model.getLineContent(1), 'A first line'); + assert.strictEqual(model.getLineContent(1), 'A first line'); assertCursor(viewModel, new Selection(1, 8, 1, 8)); CoreEditingCommands.DeleteRight.runEditorCommand(null, editor, null); CoreEditingCommands.DeleteRight.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), 'A firstine'); + assert.strictEqual(model.getLineContent(1), 'A firstine'); assertCursor(viewModel, new Selection(1, 8, 1, 8)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), 'A first line'); + assert.strictEqual(model.getLineContent(1), 'A first line'); assertCursor(viewModel, new Selection(1, 8, 1, 8)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), 'A line'); + assert.strictEqual(model.getLineContent(1), 'A line'); assertCursor(viewModel, new Selection(1, 3, 1, 3)); }); }); @@ -5721,19 +5836,19 @@ suite('Undo stops', () => { CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), ' line'); + assert.strictEqual(model.getLineContent(2), ' line'); assertCursor(viewModel, new Selection(2, 1, 2, 1)); viewModel.type('Second', 'keyboard'); - assert.equal(model.getLineContent(2), 'Second line'); + assert.strictEqual(model.getLineContent(2), 'Second line'); assertCursor(viewModel, new Selection(2, 7, 2, 7)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), ' line'); + assert.strictEqual(model.getLineContent(2), ' line'); assertCursor(viewModel, new Selection(2, 1, 2, 1)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), 'Another line'); + assert.strictEqual(model.getLineContent(2), 'Another line'); assertCursor(viewModel, new Selection(2, 8, 2, 8)); }); }); @@ -5755,7 +5870,7 @@ suite('Undo stops', () => { CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), ' line'); + assert.strictEqual(model.getLineContent(2), ' line'); assertCursor(viewModel, new Selection(2, 1, 2, 1)); CoreEditingCommands.DeleteRight.runEditorCommand(null, editor, null); @@ -5763,15 +5878,15 @@ suite('Undo stops', () => { CoreEditingCommands.DeleteRight.runEditorCommand(null, editor, null); CoreEditingCommands.DeleteRight.runEditorCommand(null, editor, null); CoreEditingCommands.DeleteRight.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), ''); + assert.strictEqual(model.getLineContent(2), ''); assertCursor(viewModel, new Selection(2, 1, 2, 1)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), ' line'); + assert.strictEqual(model.getLineContent(2), ' line'); assertCursor(viewModel, new Selection(2, 1, 2, 1)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), 'Another line'); + assert.strictEqual(model.getLineContent(2), 'Another line'); assertCursor(viewModel, new Selection(2, 8, 2, 8)); }); }); @@ -5790,19 +5905,19 @@ suite('Undo stops', () => { CoreEditingCommands.DeleteRight.runEditorCommand(null, editor, null); CoreEditingCommands.DeleteRight.runEditorCommand(null, editor, null); CoreEditingCommands.DeleteRight.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), 'Another '); + assert.strictEqual(model.getLineContent(2), 'Another '); assertCursor(viewModel, new Selection(2, 9, 2, 9)); viewModel.type('text', 'keyboard'); - assert.equal(model.getLineContent(2), 'Another text'); + assert.strictEqual(model.getLineContent(2), 'Another text'); assertCursor(viewModel, new Selection(2, 13, 2, 13)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), 'Another '); + assert.strictEqual(model.getLineContent(2), 'Another '); assertCursor(viewModel, new Selection(2, 9, 2, 9)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), 'Another line'); + assert.strictEqual(model.getLineContent(2), 'Another line'); assertCursor(viewModel, new Selection(2, 9, 2, 9)); }); }); @@ -5821,7 +5936,7 @@ suite('Undo stops', () => { CoreEditingCommands.DeleteRight.runEditorCommand(null, editor, null); CoreEditingCommands.DeleteRight.runEditorCommand(null, editor, null); CoreEditingCommands.DeleteRight.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), 'Another '); + assert.strictEqual(model.getLineContent(2), 'Another '); assertCursor(viewModel, new Selection(2, 9, 2, 9)); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); @@ -5830,15 +5945,15 @@ suite('Undo stops', () => { CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), 'An'); + assert.strictEqual(model.getLineContent(2), 'An'); assertCursor(viewModel, new Selection(2, 3, 2, 3)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), 'Another '); + assert.strictEqual(model.getLineContent(2), 'Another '); assertCursor(viewModel, new Selection(2, 9, 2, 9)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(2), 'Another line'); + assert.strictEqual(model.getLineContent(2), 'Another line'); assertCursor(viewModel, new Selection(2, 9, 2, 9)); }); }); @@ -5854,19 +5969,19 @@ suite('Undo stops', () => { withTestCodeEditor(null, { model: model }, (editor, viewModel) => { viewModel.setSelections('test', [new Selection(1, 3, 1, 3)]); viewModel.type('first and interesting', 'keyboard'); - assert.equal(model.getLineContent(1), 'A first and interesting line'); + assert.strictEqual(model.getLineContent(1), 'A first and interesting line'); assertCursor(viewModel, new Selection(1, 24, 1, 24)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), 'A first and line'); + assert.strictEqual(model.getLineContent(1), 'A first and line'); assertCursor(viewModel, new Selection(1, 12, 1, 12)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), 'A first line'); + assert.strictEqual(model.getLineContent(1), 'A first line'); assertCursor(viewModel, new Selection(1, 8, 1, 8)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getLineContent(1), 'A line'); + assert.strictEqual(model.getLineContent(1), 'A line'); assertCursor(viewModel, new Selection(1, 3, 1, 3)); }); }); @@ -5882,15 +5997,15 @@ suite('Undo stops', () => { withTestCodeEditor(null, { model: model }, (editor, viewModel) => { viewModel.setSelections('test', [new Selection(1, 3, 1, 3)]); viewModel.type('first', 'keyboard'); - assert.equal(model.getValue(), 'A first line\nAnother line'); + assert.strictEqual(model.getValue(), 'A first line\nAnother line'); assertCursor(viewModel, new Selection(1, 8, 1, 8)); model.pushEOL(EndOfLineSequence.CRLF); - assert.equal(model.getValue(), 'A first line\r\nAnother line'); + assert.strictEqual(model.getValue(), 'A first line\r\nAnother line'); assertCursor(viewModel, new Selection(1, 8, 1, 8)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getValue(), 'A line\nAnother line'); + assert.strictEqual(model.getValue(), 'A line\nAnother line'); assertCursor(viewModel, new Selection(1, 3, 1, 3)); }); }); @@ -5909,10 +6024,10 @@ suite('Undo stops', () => { new Selection(1, 7, 1, 12), ]); viewModel.type('no', 'keyboard'); - assert.equal(model.getValue(), 'hello no\nhello no'); + assert.strictEqual(model.getValue(), 'hello no\nhello no'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); - assert.equal(model.getValue(), 'hello world\nhello world'); + assert.strictEqual(model.getValue(), 'hello world\nhello world'); }); }); }); diff --git a/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts b/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts index 3c85cc22e..55abff9fa 100644 --- a/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts +++ b/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts @@ -484,11 +484,11 @@ function cursorEqual(viewModel: ViewModel, posLineNumber: number, posColumn: num } function positionEqual(position: Position, lineNumber: number, column: number) { - assert.deepEqual(position, new Position(lineNumber, column), 'position equal'); + assert.deepStrictEqual(position, new Position(lineNumber, column), 'position equal'); } function selectionEqual(selection: Selection, posLineNumber: number, posColumn: number, selLineNumber: number, selColumn: number) { - assert.deepEqual({ + assert.deepStrictEqual({ selectionStartLineNumber: selection.selectionStartLineNumber, selectionStartColumn: selection.selectionStartColumn, positionLineNumber: selection.positionLineNumber, diff --git a/src/vs/editor/test/browser/controller/textAreaState.test.ts b/src/vs/editor/test/browser/controller/textAreaState.test.ts index 163e9de54..4ae25e14a 100644 --- a/src/vs/editor/test/browser/controller/textAreaState.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaState.test.ts @@ -84,8 +84,8 @@ suite('TextAreaState', () => { let actual = TextAreaState.readFromTextArea(textArea); assertTextAreaState(actual, 'Hello world!', 1, 12); - assert.equal(actual.value, 'Hello world!'); - assert.equal(actual.selectionStart, 1); + assert.strictEqual(actual.value, 'Hello world!'); + assert.strictEqual(actual.selectionStart, 1); actual = actual.collapseSelection(); assertTextAreaState(actual, 'Hello world!', 12, 12); @@ -102,23 +102,23 @@ suite('TextAreaState', () => { let state = new TextAreaState('Hi world!', 2, 2, null, null); state.writeToTextArea('test', textArea, false); - assert.equal(textArea._value, 'Hi world!'); - assert.equal(textArea._selectionStart, 9); - assert.equal(textArea._selectionEnd, 9); + assert.strictEqual(textArea._value, 'Hi world!'); + assert.strictEqual(textArea._selectionStart, 9); + assert.strictEqual(textArea._selectionEnd, 9); state = new TextAreaState('Hi world!', 3, 3, null, null); state.writeToTextArea('test', textArea, false); - assert.equal(textArea._value, 'Hi world!'); - assert.equal(textArea._selectionStart, 9); - assert.equal(textArea._selectionEnd, 9); + assert.strictEqual(textArea._value, 'Hi world!'); + assert.strictEqual(textArea._selectionStart, 9); + assert.strictEqual(textArea._selectionEnd, 9); state = new TextAreaState('Hi world!', 0, 2, null, null); state.writeToTextArea('test', textArea, true); - assert.equal(textArea._value, 'Hi world!'); - assert.equal(textArea._selectionStart, 0); - assert.equal(textArea._selectionEnd, 2); + assert.strictEqual(textArea._value, 'Hi world!'); + assert.strictEqual(textArea._selectionStart, 0); + assert.strictEqual(textArea._selectionEnd, 2); textArea.dispose(); }); @@ -134,8 +134,8 @@ suite('TextAreaState', () => { let newState = TextAreaState.readFromTextArea(textArea); let actual = TextAreaState.deduceInput(prevState, newState, couldBeEmojiInput); - assert.equal(actual.text, expected); - assert.equal(actual.replaceCharCnt, expectedCharReplaceCnt); + assert.strictEqual(actual.text, expected); + assert.strictEqual(actual.replaceCharCnt, expectedCharReplaceCnt); textArea.dispose(); } diff --git a/src/vs/editor/test/browser/core/editorState.test.ts b/src/vs/editor/test/browser/core/editorState.test.ts index 1915af4f7..32534c2b7 100644 --- a/src/vs/editor/test/browser/core/editorState.test.ts +++ b/src/vs/editor/test/browser/core/editorState.test.ts @@ -29,7 +29,7 @@ suite('Editor Core - Editor State', () => { test('empty editor state should be valid', () => { let result = validate({}, {}); - assert.equal(result, true); + assert.strictEqual(result, true); }); test('different model URIs should be invalid', () => { @@ -38,7 +38,7 @@ suite('Editor Core - Editor State', () => { { model: { uri: URI.parse('http://test2') } } ); - assert.equal(result, false); + assert.strictEqual(result, false); }); test('different model versions should be invalid', () => { @@ -47,7 +47,7 @@ suite('Editor Core - Editor State', () => { { model: { version: 2 } } ); - assert.equal(result, false); + assert.strictEqual(result, false); }); test('different positions should be invalid', () => { @@ -56,7 +56,7 @@ suite('Editor Core - Editor State', () => { { position: new Position(2, 3) } ); - assert.equal(result, false); + assert.strictEqual(result, false); }); test('different selections should be invalid', () => { @@ -65,7 +65,7 @@ suite('Editor Core - Editor State', () => { { selection: new Selection(5, 2, 3, 4) } ); - assert.equal(result, false); + assert.strictEqual(result, false); }); test('different scroll positions should be invalid', () => { @@ -74,7 +74,7 @@ suite('Editor Core - Editor State', () => { { scroll: { left: 3, top: 2 } } ); - assert.equal(result, false); + assert.strictEqual(result, false); }); diff --git a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts index 9c8c6c6ed..37055a854 100644 --- a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts +++ b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts @@ -60,12 +60,12 @@ suite('Decoration Render Options', () => { test('register and resolve decoration type', () => { let s = new TestCodeEditorServiceImpl(themeServiceMock); s.registerDecorationType('example', options); - assert.notEqual(s.resolveDecorationOptions('example', false), undefined); + assert.notStrictEqual(s.resolveDecorationOptions('example', false), undefined); }); test('remove decoration type', () => { let s = new TestCodeEditorServiceImpl(themeServiceMock); s.registerDecorationType('example', options); - assert.notEqual(s.resolveDecorationOptions('example', false), undefined); + assert.notStrictEqual(s.resolveDecorationOptions('example', false), undefined); s.removeDecorationType('example'); assert.throws(() => s.resolveDecorationOptions('example', false)); }); @@ -95,16 +95,16 @@ suite('Decoration Render Options', () => { })); const s = new TestCodeEditorServiceImpl(themeService, styleSheet); s.registerDecorationType('example', options); - assert.equal(readStyleSheet(styleSheet), '.monaco-editor .ced-example-0 {background-color:#ff0000;border-color:transparent;box-sizing: border-box;}'); + assert.strictEqual(readStyleSheet(styleSheet), '.monaco-editor .ced-example-0 {background-color:#ff0000;border-color:transparent;box-sizing: border-box;}'); themeService.setTheme(new TestColorTheme({ editorBackground: '#EE0000', editorBorder: '#00FFFF' })); - assert.equal(readStyleSheet(styleSheet), '.monaco-editor .ced-example-0 {background-color:#ee0000;border-color:#00ffff;box-sizing: border-box;}'); + assert.strictEqual(readStyleSheet(styleSheet), '.monaco-editor .ced-example-0 {background-color:#ee0000;border-color:#00ffff;box-sizing: border-box;}'); s.removeDecorationType('example'); - assert.equal(readStyleSheet(styleSheet), ''); + assert.strictEqual(readStyleSheet(styleSheet), ''); }); test('theme overrides', () => { @@ -134,10 +134,10 @@ suite('Decoration Render Options', () => { '.vs.monaco-editor .ced-example-1 {color:#FF00FF !important;}', '.monaco-editor .ced-example-1 {color:#ff0000 !important;}' ].join('\n'); - assert.equal(readStyleSheet(styleSheet), expected); + assert.strictEqual(readStyleSheet(styleSheet), expected); s.removeDecorationType('example'); - assert.equal(readStyleSheet(styleSheet), ''); + assert.strictEqual(readStyleSheet(styleSheet), ''); }); test('css properties, gutterIconPaths', () => { diff --git a/src/vs/editor/test/browser/testCodeEditor.ts b/src/vs/editor/test/browser/testCodeEditor.ts index cc39f3044..da4aac219 100644 --- a/src/vs/editor/test/browser/testCodeEditor.ts +++ b/src/vs/editor/test/browser/testCodeEditor.ts @@ -31,10 +31,10 @@ export interface ITestCodeEditor extends IActiveCodeEditor { registerAndInstantiateContribution(id: string, ctor: new (editor: ICodeEditor, ...services: Services) => T): T; } -class TestCodeEditor extends CodeEditorWidget implements ICodeEditor { +export class TestCodeEditor extends CodeEditorWidget implements ICodeEditor { //#region testing overrides - protected _createConfiguration(options: IEditorConstructionOptions): IConfiguration { + protected _createConfiguration(options: Readonly): IConfiguration { return new TestConfiguration(options); } protected _createView(viewModel: ViewModel): [View, boolean] { @@ -52,6 +52,9 @@ class TestCodeEditor extends CodeEditorWidget implements ICodeEditor { this._contributions[id] = r; return r; } +} + +class TestCodeEditorWithAutoModelDisposal extends TestCodeEditor { public dispose() { super.dispose(); if (this._modelData) { @@ -144,7 +147,7 @@ export function createTestCodeEditor(options: TestCodeEditorCreationOptions): IT contributions: [] }; const editor = instantiationService.createInstance( - TestCodeEditor, + TestCodeEditorWithAutoModelDisposal, new TestEditorDomElement(), options, codeEditorWidgetOptions diff --git a/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts b/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts index cde867901..81aed990b 100644 --- a/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts +++ b/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts @@ -85,7 +85,7 @@ suite('MinimapCharRenderer', () => { actual[i] = imageData.data[i]; } - assert.deepEqual(actual, [ + assert.deepStrictEqual(actual, [ 0x2D, 0x2D, 0x2D, 0xFF, 0xAC, 0xAC, 0xAC, 0xFF, 0xC6, 0xC6, 0xC6, 0xFF, 0xC8, 0xC8, 0xC8, 0xFF, 0xC0, 0xC0, 0xC0, 0xFF, 0xCB, 0xCB, 0xCB, 0xFF, @@ -115,7 +115,7 @@ suite('MinimapCharRenderer', () => { actual[i] = imageData.data[i]; } - assert.deepEqual(actual, [ + assert.deepStrictEqual(actual, [ 0xCB, 0xCB, 0xCB, 0xFF, 0x81, 0x81, 0x81, 0xFF, ]); diff --git a/src/vs/editor/test/browser/view/viewLayer.test.ts b/src/vs/editor/test/browser/view/viewLayer.test.ts index bffb2f8b1..eba81ff74 100644 --- a/src/vs/editor/test/browser/view/viewLayer.test.ts +++ b/src/vs/editor/test/browser/view/viewLayer.test.ts @@ -36,7 +36,7 @@ function assertState(col: RenderedLinesCollection, state: ILinesCollec actualState.lines.push(col.getLine(lineNumber).id); actualState.pinged.push(col.getLine(lineNumber)._pinged); } - assert.deepEqual(actualState, state); + assert.deepStrictEqual(actualState, state); } suite('RenderedLinesCollection onLinesDeleted', () => { @@ -54,7 +54,7 @@ suite('RenderedLinesCollection onLinesDeleted', () => { if (actualDeleted1) { actualDeleted = actualDeleted1.map(line => line.id); } - assert.deepEqual(actualDeleted, expectedDeleted); + assert.deepStrictEqual(actualDeleted, expectedDeleted); assertState(col, expectedState); } @@ -325,7 +325,7 @@ suite('RenderedLinesCollection onLineChanged', () => { new TestLine('old9') ]); let actualPinged = col.onLinesChanged(changedLineNumber, changedLineNumber); - assert.deepEqual(actualPinged, expectedPinged); + assert.deepStrictEqual(actualPinged, expectedPinged); assertState(col, expectedState); } @@ -410,7 +410,7 @@ suite('RenderedLinesCollection onLinesInserted', () => { if (actualDeleted1) { actualDeleted = actualDeleted1.map(line => line.id); } - assert.deepEqual(actualDeleted, expectedDeleted); + assert.deepStrictEqual(actualDeleted, expectedDeleted); assertState(col, expectedState); } @@ -682,7 +682,7 @@ suite('RenderedLinesCollection onTokensChanged', () => { new TestLine('old9') ]); let actualPinged = col.onTokensChanged([{ fromLineNumber: changedFromLineNumber, toLineNumber: changedToLineNumber }]); - assert.deepEqual(actualPinged, expectedPinged); + assert.deepStrictEqual(actualPinged, expectedPinged); assertState(col, expectedState); } diff --git a/src/vs/editor/test/common/config/commonEditorConfig.test.ts b/src/vs/editor/test/common/config/commonEditorConfig.test.ts index d0edc8f44..1faca7a17 100644 --- a/src/vs/editor/test/common/config/commonEditorConfig.test.ts +++ b/src/vs/editor/test/common/config/commonEditorConfig.test.ts @@ -16,40 +16,40 @@ suite('Common Editor Config', () => { const zoom = EditorZoom; zoom.setZoomLevel(0); - assert.equal(zoom.getZoomLevel(), 0); + assert.strictEqual(zoom.getZoomLevel(), 0); zoom.setZoomLevel(-0); - assert.equal(zoom.getZoomLevel(), 0); + assert.strictEqual(zoom.getZoomLevel(), 0); zoom.setZoomLevel(5); - assert.equal(zoom.getZoomLevel(), 5); + assert.strictEqual(zoom.getZoomLevel(), 5); zoom.setZoomLevel(-1); - assert.equal(zoom.getZoomLevel(), -1); + assert.strictEqual(zoom.getZoomLevel(), -1); zoom.setZoomLevel(9); - assert.equal(zoom.getZoomLevel(), 9); + assert.strictEqual(zoom.getZoomLevel(), 9); zoom.setZoomLevel(-9); - assert.equal(zoom.getZoomLevel(), -5); + assert.strictEqual(zoom.getZoomLevel(), -5); zoom.setZoomLevel(20); - assert.equal(zoom.getZoomLevel(), 20); + assert.strictEqual(zoom.getZoomLevel(), 20); zoom.setZoomLevel(-10); - assert.equal(zoom.getZoomLevel(), -5); + assert.strictEqual(zoom.getZoomLevel(), -5); zoom.setZoomLevel(9.1); - assert.equal(zoom.getZoomLevel(), 9.1); + assert.strictEqual(zoom.getZoomLevel(), 9.1); zoom.setZoomLevel(-9.1); - assert.equal(zoom.getZoomLevel(), -5); + assert.strictEqual(zoom.getZoomLevel(), -5); zoom.setZoomLevel(Infinity); - assert.equal(zoom.getZoomLevel(), 20); + assert.strictEqual(zoom.getZoomLevel(), 20); zoom.setZoomLevel(Number.NEGATIVE_INFINITY); - assert.equal(zoom.getZoomLevel(), -5); + assert.strictEqual(zoom.getZoomLevel(), -5); }); class TestWrappingConfiguration extends TestConfiguration { @@ -69,8 +69,8 @@ suite('Common Editor Config', () => { function assertWrapping(config: TestConfiguration, isViewportWrapping: boolean, wrappingColumn: number): void { const options = config.options; const wrappingInfo = options.get(EditorOption.wrappingInfo); - assert.equal(wrappingInfo.isViewportWrapping, isViewportWrapping); - assert.equal(wrappingInfo.wrappingColumn, wrappingColumn); + assert.strictEqual(wrappingInfo.isViewportWrapping, isViewportWrapping); + assert.strictEqual(wrappingInfo.wrappingColumn, wrappingColumn); } test('wordWrap default', () => { @@ -186,26 +186,26 @@ suite('Common Editor Config', () => { }); let config = new TestConfiguration({ hover: hoverOptions }); - assert.equal(config.options.get(EditorOption.hover).enabled, true); + assert.strictEqual(config.options.get(EditorOption.hover).enabled, true); config.updateOptions({ hover: { enabled: false } }); - assert.equal(config.options.get(EditorOption.hover).enabled, false); + assert.strictEqual(config.options.get(EditorOption.hover).enabled, false); }); test('does not emit event when nothing changes', () => { const config = new TestConfiguration({ glyphMargin: true, roundedSelection: false }); let event: ConfigurationChangedEvent | null = null; config.onDidChange(e => event = e); - assert.equal(config.options.get(EditorOption.glyphMargin), true); + assert.strictEqual(config.options.get(EditorOption.glyphMargin), true); config.updateOptions({ glyphMargin: true }); config.updateOptions({ roundedSelection: false }); - assert.equal(event, null); + assert.strictEqual(event, null); }); test('issue #94931: Unable to open source file', () => { const config = new TestConfiguration({ quickSuggestions: null! }); const actual = >>config.options.get(EditorOption.quickSuggestions); - assert.deepEqual(actual, { + assert.deepStrictEqual(actual, { other: true, comments: false, strings: false @@ -216,7 +216,7 @@ suite('Common Editor Config', () => { const config = new TestConfiguration({ quickSuggestions: null! }); config.updateOptions({ quickSuggestions: { strings: true } }); const actual = >>config.options.get(EditorOption.quickSuggestions); - assert.deepEqual(actual, { + assert.deepStrictEqual(actual, { other: true, comments: false, strings: true diff --git a/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts b/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts index e7cd6b4b3..e07042281 100644 --- a/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts +++ b/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts @@ -8,41 +8,41 @@ import { CursorColumns } from 'vs/editor/common/controller/cursorCommon'; suite('CursorMove', () => { test('nextRenderTabStop', () => { - assert.equal(CursorColumns.nextRenderTabStop(0, 4), 4); - assert.equal(CursorColumns.nextRenderTabStop(1, 4), 4); - assert.equal(CursorColumns.nextRenderTabStop(2, 4), 4); - assert.equal(CursorColumns.nextRenderTabStop(3, 4), 4); - assert.equal(CursorColumns.nextRenderTabStop(4, 4), 8); - assert.equal(CursorColumns.nextRenderTabStop(5, 4), 8); - assert.equal(CursorColumns.nextRenderTabStop(6, 4), 8); - assert.equal(CursorColumns.nextRenderTabStop(7, 4), 8); - assert.equal(CursorColumns.nextRenderTabStop(8, 4), 12); + assert.strictEqual(CursorColumns.nextRenderTabStop(0, 4), 4); + assert.strictEqual(CursorColumns.nextRenderTabStop(1, 4), 4); + assert.strictEqual(CursorColumns.nextRenderTabStop(2, 4), 4); + assert.strictEqual(CursorColumns.nextRenderTabStop(3, 4), 4); + assert.strictEqual(CursorColumns.nextRenderTabStop(4, 4), 8); + assert.strictEqual(CursorColumns.nextRenderTabStop(5, 4), 8); + assert.strictEqual(CursorColumns.nextRenderTabStop(6, 4), 8); + assert.strictEqual(CursorColumns.nextRenderTabStop(7, 4), 8); + assert.strictEqual(CursorColumns.nextRenderTabStop(8, 4), 12); - assert.equal(CursorColumns.nextRenderTabStop(0, 2), 2); - assert.equal(CursorColumns.nextRenderTabStop(1, 2), 2); - assert.equal(CursorColumns.nextRenderTabStop(2, 2), 4); - assert.equal(CursorColumns.nextRenderTabStop(3, 2), 4); - assert.equal(CursorColumns.nextRenderTabStop(4, 2), 6); - assert.equal(CursorColumns.nextRenderTabStop(5, 2), 6); - assert.equal(CursorColumns.nextRenderTabStop(6, 2), 8); - assert.equal(CursorColumns.nextRenderTabStop(7, 2), 8); - assert.equal(CursorColumns.nextRenderTabStop(8, 2), 10); + assert.strictEqual(CursorColumns.nextRenderTabStop(0, 2), 2); + assert.strictEqual(CursorColumns.nextRenderTabStop(1, 2), 2); + assert.strictEqual(CursorColumns.nextRenderTabStop(2, 2), 4); + assert.strictEqual(CursorColumns.nextRenderTabStop(3, 2), 4); + assert.strictEqual(CursorColumns.nextRenderTabStop(4, 2), 6); + assert.strictEqual(CursorColumns.nextRenderTabStop(5, 2), 6); + assert.strictEqual(CursorColumns.nextRenderTabStop(6, 2), 8); + assert.strictEqual(CursorColumns.nextRenderTabStop(7, 2), 8); + assert.strictEqual(CursorColumns.nextRenderTabStop(8, 2), 10); - assert.equal(CursorColumns.nextRenderTabStop(0, 1), 1); - assert.equal(CursorColumns.nextRenderTabStop(1, 1), 2); - assert.equal(CursorColumns.nextRenderTabStop(2, 1), 3); - assert.equal(CursorColumns.nextRenderTabStop(3, 1), 4); - assert.equal(CursorColumns.nextRenderTabStop(4, 1), 5); - assert.equal(CursorColumns.nextRenderTabStop(5, 1), 6); - assert.equal(CursorColumns.nextRenderTabStop(6, 1), 7); - assert.equal(CursorColumns.nextRenderTabStop(7, 1), 8); - assert.equal(CursorColumns.nextRenderTabStop(8, 1), 9); + assert.strictEqual(CursorColumns.nextRenderTabStop(0, 1), 1); + assert.strictEqual(CursorColumns.nextRenderTabStop(1, 1), 2); + assert.strictEqual(CursorColumns.nextRenderTabStop(2, 1), 3); + assert.strictEqual(CursorColumns.nextRenderTabStop(3, 1), 4); + assert.strictEqual(CursorColumns.nextRenderTabStop(4, 1), 5); + assert.strictEqual(CursorColumns.nextRenderTabStop(5, 1), 6); + assert.strictEqual(CursorColumns.nextRenderTabStop(6, 1), 7); + assert.strictEqual(CursorColumns.nextRenderTabStop(7, 1), 8); + assert.strictEqual(CursorColumns.nextRenderTabStop(8, 1), 9); }); test('visibleColumnFromColumn', () => { function testVisibleColumnFromColumn(text: string, tabSize: number, column: number, expected: number): void { - assert.equal(CursorColumns.visibleColumnFromColumn(text, column, tabSize), expected); + assert.strictEqual(CursorColumns.visibleColumnFromColumn(text, column, tabSize), expected); } testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 1, 0); @@ -101,7 +101,7 @@ suite('CursorMove', () => { test('columnFromVisibleColumn', () => { function testColumnFromVisibleColumn(text: string, tabSize: number, visibleColumn: number, expected: number): void { - assert.equal(CursorColumns.columnFromVisibleColumn(text, visibleColumn, tabSize), expected); + assert.strictEqual(CursorColumns.columnFromVisibleColumn(text, visibleColumn, tabSize), expected); } // testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 0, 1); @@ -177,7 +177,7 @@ suite('CursorMove', () => { test('toStatusbarColumn', () => { function t(text: string, tabSize: number, column: number, expected: number): void { - assert.equal(CursorColumns.toStatusbarColumn(text, column, tabSize), expected, `<>`); + assert.strictEqual(CursorColumns.toStatusbarColumn(text, column, tabSize), expected, `<>`); } t(' spaces', 4, 1, 1); diff --git a/src/vs/editor/test/common/core/characterClassifier.test.ts b/src/vs/editor/test/common/core/characterClassifier.test.ts index de9effc74..9d3bf750c 100644 --- a/src/vs/editor/test/common/core/characterClassifier.test.ts +++ b/src/vs/editor/test/common/core/characterClassifier.test.ts @@ -11,27 +11,27 @@ suite('CharacterClassifier', () => { test('works', () => { let classifier = new CharacterClassifier(0); - assert.equal(classifier.get(-1), 0); - assert.equal(classifier.get(0), 0); - assert.equal(classifier.get(CharCode.a), 0); - assert.equal(classifier.get(CharCode.b), 0); - assert.equal(classifier.get(CharCode.z), 0); - assert.equal(classifier.get(255), 0); - assert.equal(classifier.get(1000), 0); - assert.equal(classifier.get(2000), 0); + assert.strictEqual(classifier.get(-1), 0); + assert.strictEqual(classifier.get(0), 0); + assert.strictEqual(classifier.get(CharCode.a), 0); + assert.strictEqual(classifier.get(CharCode.b), 0); + assert.strictEqual(classifier.get(CharCode.z), 0); + assert.strictEqual(classifier.get(255), 0); + assert.strictEqual(classifier.get(1000), 0); + assert.strictEqual(classifier.get(2000), 0); classifier.set(CharCode.a, 1); classifier.set(CharCode.z, 2); classifier.set(1000, 3); - assert.equal(classifier.get(-1), 0); - assert.equal(classifier.get(0), 0); - assert.equal(classifier.get(CharCode.a), 1); - assert.equal(classifier.get(CharCode.b), 0); - assert.equal(classifier.get(CharCode.z), 2); - assert.equal(classifier.get(255), 0); - assert.equal(classifier.get(1000), 3); - assert.equal(classifier.get(2000), 0); + assert.strictEqual(classifier.get(-1), 0); + assert.strictEqual(classifier.get(0), 0); + assert.strictEqual(classifier.get(CharCode.a), 1); + assert.strictEqual(classifier.get(CharCode.b), 0); + assert.strictEqual(classifier.get(CharCode.z), 2); + assert.strictEqual(classifier.get(255), 0); + assert.strictEqual(classifier.get(1000), 3); + assert.strictEqual(classifier.get(2000), 0); }); -}); \ No newline at end of file +}); diff --git a/src/vs/editor/test/common/core/lineTokens.test.ts b/src/vs/editor/test/common/core/lineTokens.test.ts index d3a9924fb..2ffff0c7e 100644 --- a/src/vs/editor/test/common/core/lineTokens.test.ts +++ b/src/vs/editor/test/common/core/lineTokens.test.ts @@ -45,64 +45,64 @@ suite('LineTokens', () => { test('basics', () => { const lineTokens = createTestLineTokens(); - assert.equal(lineTokens.getLineContent(), 'Hello world, this is a lovely day'); - assert.equal(lineTokens.getLineContent().length, 33); - assert.equal(lineTokens.getCount(), 7); + assert.strictEqual(lineTokens.getLineContent(), 'Hello world, this is a lovely day'); + assert.strictEqual(lineTokens.getLineContent().length, 33); + assert.strictEqual(lineTokens.getCount(), 7); - assert.equal(lineTokens.getStartOffset(0), 0); - assert.equal(lineTokens.getEndOffset(0), 6); - assert.equal(lineTokens.getStartOffset(1), 6); - assert.equal(lineTokens.getEndOffset(1), 13); - assert.equal(lineTokens.getStartOffset(2), 13); - assert.equal(lineTokens.getEndOffset(2), 18); - assert.equal(lineTokens.getStartOffset(3), 18); - assert.equal(lineTokens.getEndOffset(3), 21); - assert.equal(lineTokens.getStartOffset(4), 21); - assert.equal(lineTokens.getEndOffset(4), 23); - assert.equal(lineTokens.getStartOffset(5), 23); - assert.equal(lineTokens.getEndOffset(5), 30); - assert.equal(lineTokens.getStartOffset(6), 30); - assert.equal(lineTokens.getEndOffset(6), 33); + assert.strictEqual(lineTokens.getStartOffset(0), 0); + assert.strictEqual(lineTokens.getEndOffset(0), 6); + assert.strictEqual(lineTokens.getStartOffset(1), 6); + assert.strictEqual(lineTokens.getEndOffset(1), 13); + assert.strictEqual(lineTokens.getStartOffset(2), 13); + assert.strictEqual(lineTokens.getEndOffset(2), 18); + assert.strictEqual(lineTokens.getStartOffset(3), 18); + assert.strictEqual(lineTokens.getEndOffset(3), 21); + assert.strictEqual(lineTokens.getStartOffset(4), 21); + assert.strictEqual(lineTokens.getEndOffset(4), 23); + assert.strictEqual(lineTokens.getStartOffset(5), 23); + assert.strictEqual(lineTokens.getEndOffset(5), 30); + assert.strictEqual(lineTokens.getStartOffset(6), 30); + assert.strictEqual(lineTokens.getEndOffset(6), 33); }); test('findToken', () => { const lineTokens = createTestLineTokens(); - assert.equal(lineTokens.findTokenIndexAtOffset(0), 0); - assert.equal(lineTokens.findTokenIndexAtOffset(1), 0); - assert.equal(lineTokens.findTokenIndexAtOffset(2), 0); - assert.equal(lineTokens.findTokenIndexAtOffset(3), 0); - assert.equal(lineTokens.findTokenIndexAtOffset(4), 0); - assert.equal(lineTokens.findTokenIndexAtOffset(5), 0); - assert.equal(lineTokens.findTokenIndexAtOffset(6), 1); - assert.equal(lineTokens.findTokenIndexAtOffset(7), 1); - assert.equal(lineTokens.findTokenIndexAtOffset(8), 1); - assert.equal(lineTokens.findTokenIndexAtOffset(9), 1); - assert.equal(lineTokens.findTokenIndexAtOffset(10), 1); - assert.equal(lineTokens.findTokenIndexAtOffset(11), 1); - assert.equal(lineTokens.findTokenIndexAtOffset(12), 1); - assert.equal(lineTokens.findTokenIndexAtOffset(13), 2); - assert.equal(lineTokens.findTokenIndexAtOffset(14), 2); - assert.equal(lineTokens.findTokenIndexAtOffset(15), 2); - assert.equal(lineTokens.findTokenIndexAtOffset(16), 2); - assert.equal(lineTokens.findTokenIndexAtOffset(17), 2); - assert.equal(lineTokens.findTokenIndexAtOffset(18), 3); - assert.equal(lineTokens.findTokenIndexAtOffset(19), 3); - assert.equal(lineTokens.findTokenIndexAtOffset(20), 3); - assert.equal(lineTokens.findTokenIndexAtOffset(21), 4); - assert.equal(lineTokens.findTokenIndexAtOffset(22), 4); - assert.equal(lineTokens.findTokenIndexAtOffset(23), 5); - assert.equal(lineTokens.findTokenIndexAtOffset(24), 5); - assert.equal(lineTokens.findTokenIndexAtOffset(25), 5); - assert.equal(lineTokens.findTokenIndexAtOffset(26), 5); - assert.equal(lineTokens.findTokenIndexAtOffset(27), 5); - assert.equal(lineTokens.findTokenIndexAtOffset(28), 5); - assert.equal(lineTokens.findTokenIndexAtOffset(29), 5); - assert.equal(lineTokens.findTokenIndexAtOffset(30), 6); - assert.equal(lineTokens.findTokenIndexAtOffset(31), 6); - assert.equal(lineTokens.findTokenIndexAtOffset(32), 6); - assert.equal(lineTokens.findTokenIndexAtOffset(33), 6); - assert.equal(lineTokens.findTokenIndexAtOffset(34), 6); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(0), 0); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(1), 0); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(2), 0); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(3), 0); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(4), 0); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(5), 0); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(6), 1); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(7), 1); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(8), 1); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(9), 1); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(10), 1); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(11), 1); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(12), 1); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(13), 2); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(14), 2); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(15), 2); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(16), 2); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(17), 2); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(18), 3); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(19), 3); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(20), 3); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(21), 4); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(22), 4); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(23), 5); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(24), 5); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(25), 5); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(26), 5); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(27), 5); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(28), 5); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(29), 5); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(30), 6); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(31), 6); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(32), 6); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(33), 6); + assert.strictEqual(lineTokens.findTokenIndexAtOffset(34), 6); }); interface ITestViewLineToken { @@ -118,7 +118,7 @@ suite('LineTokens', () => { foreground: _actual.getForeground(i) }; } - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); } test('inflate', () => { diff --git a/src/vs/editor/test/common/core/range.test.ts b/src/vs/editor/test/common/core/range.test.ts index 5420ae452..26415e8c6 100644 --- a/src/vs/editor/test/common/core/range.test.ts +++ b/src/vs/editor/test/common/core/range.test.ts @@ -9,47 +9,47 @@ import { Range } from 'vs/editor/common/core/range'; suite('Editor Core - Range', () => { test('empty range', () => { let s = new Range(1, 1, 1, 1); - assert.equal(s.startLineNumber, 1); - assert.equal(s.startColumn, 1); - assert.equal(s.endLineNumber, 1); - assert.equal(s.endColumn, 1); - assert.equal(s.isEmpty(), true); + assert.strictEqual(s.startLineNumber, 1); + assert.strictEqual(s.startColumn, 1); + assert.strictEqual(s.endLineNumber, 1); + assert.strictEqual(s.endColumn, 1); + assert.strictEqual(s.isEmpty(), true); }); test('swap start and stop same line', () => { let s = new Range(1, 2, 1, 1); - assert.equal(s.startLineNumber, 1); - assert.equal(s.startColumn, 1); - assert.equal(s.endLineNumber, 1); - assert.equal(s.endColumn, 2); - assert.equal(s.isEmpty(), false); + assert.strictEqual(s.startLineNumber, 1); + assert.strictEqual(s.startColumn, 1); + assert.strictEqual(s.endLineNumber, 1); + assert.strictEqual(s.endColumn, 2); + assert.strictEqual(s.isEmpty(), false); }); test('swap start and stop', () => { let s = new Range(2, 1, 1, 2); - assert.equal(s.startLineNumber, 1); - assert.equal(s.startColumn, 2); - assert.equal(s.endLineNumber, 2); - assert.equal(s.endColumn, 1); - assert.equal(s.isEmpty(), false); + assert.strictEqual(s.startLineNumber, 1); + assert.strictEqual(s.startColumn, 2); + assert.strictEqual(s.endLineNumber, 2); + assert.strictEqual(s.endColumn, 1); + assert.strictEqual(s.isEmpty(), false); }); test('no swap same line', () => { let s = new Range(1, 1, 1, 2); - assert.equal(s.startLineNumber, 1); - assert.equal(s.startColumn, 1); - assert.equal(s.endLineNumber, 1); - assert.equal(s.endColumn, 2); - assert.equal(s.isEmpty(), false); + assert.strictEqual(s.startLineNumber, 1); + assert.strictEqual(s.startColumn, 1); + assert.strictEqual(s.endLineNumber, 1); + assert.strictEqual(s.endColumn, 2); + assert.strictEqual(s.isEmpty(), false); }); test('no swap', () => { let s = new Range(1, 1, 2, 1); - assert.equal(s.startLineNumber, 1); - assert.equal(s.startColumn, 1); - assert.equal(s.endLineNumber, 2); - assert.equal(s.endColumn, 1); - assert.equal(s.isEmpty(), false); + assert.strictEqual(s.startLineNumber, 1); + assert.strictEqual(s.startColumn, 1); + assert.strictEqual(s.endLineNumber, 2); + assert.strictEqual(s.endColumn, 1); + assert.strictEqual(s.isEmpty(), false); }); test('compareRangesUsingEnds', () => { @@ -93,36 +93,36 @@ suite('Editor Core - Range', () => { }); test('containsPosition', () => { - assert.equal(new Range(2, 2, 5, 10).containsPosition(new Position(1, 3)), false); - assert.equal(new Range(2, 2, 5, 10).containsPosition(new Position(2, 1)), false); - assert.equal(new Range(2, 2, 5, 10).containsPosition(new Position(2, 2)), true); - assert.equal(new Range(2, 2, 5, 10).containsPosition(new Position(2, 3)), true); - assert.equal(new Range(2, 2, 5, 10).containsPosition(new Position(3, 1)), true); - assert.equal(new Range(2, 2, 5, 10).containsPosition(new Position(5, 9)), true); - assert.equal(new Range(2, 2, 5, 10).containsPosition(new Position(5, 10)), true); - assert.equal(new Range(2, 2, 5, 10).containsPosition(new Position(5, 11)), false); - assert.equal(new Range(2, 2, 5, 10).containsPosition(new Position(6, 1)), false); + assert.strictEqual(new Range(2, 2, 5, 10).containsPosition(new Position(1, 3)), false); + assert.strictEqual(new Range(2, 2, 5, 10).containsPosition(new Position(2, 1)), false); + assert.strictEqual(new Range(2, 2, 5, 10).containsPosition(new Position(2, 2)), true); + assert.strictEqual(new Range(2, 2, 5, 10).containsPosition(new Position(2, 3)), true); + assert.strictEqual(new Range(2, 2, 5, 10).containsPosition(new Position(3, 1)), true); + assert.strictEqual(new Range(2, 2, 5, 10).containsPosition(new Position(5, 9)), true); + assert.strictEqual(new Range(2, 2, 5, 10).containsPosition(new Position(5, 10)), true); + assert.strictEqual(new Range(2, 2, 5, 10).containsPosition(new Position(5, 11)), false); + assert.strictEqual(new Range(2, 2, 5, 10).containsPosition(new Position(6, 1)), false); }); test('containsRange', () => { - assert.equal(new Range(2, 2, 5, 10).containsRange(new Range(1, 3, 2, 2)), false); - assert.equal(new Range(2, 2, 5, 10).containsRange(new Range(2, 1, 2, 2)), false); - assert.equal(new Range(2, 2, 5, 10).containsRange(new Range(2, 2, 5, 11)), false); - assert.equal(new Range(2, 2, 5, 10).containsRange(new Range(2, 2, 6, 1)), false); - assert.equal(new Range(2, 2, 5, 10).containsRange(new Range(5, 9, 6, 1)), false); - assert.equal(new Range(2, 2, 5, 10).containsRange(new Range(5, 10, 6, 1)), false); - assert.equal(new Range(2, 2, 5, 10).containsRange(new Range(2, 2, 5, 10)), true); - assert.equal(new Range(2, 2, 5, 10).containsRange(new Range(2, 3, 5, 9)), true); - assert.equal(new Range(2, 2, 5, 10).containsRange(new Range(3, 100, 4, 100)), true); + assert.strictEqual(new Range(2, 2, 5, 10).containsRange(new Range(1, 3, 2, 2)), false); + assert.strictEqual(new Range(2, 2, 5, 10).containsRange(new Range(2, 1, 2, 2)), false); + assert.strictEqual(new Range(2, 2, 5, 10).containsRange(new Range(2, 2, 5, 11)), false); + assert.strictEqual(new Range(2, 2, 5, 10).containsRange(new Range(2, 2, 6, 1)), false); + assert.strictEqual(new Range(2, 2, 5, 10).containsRange(new Range(5, 9, 6, 1)), false); + assert.strictEqual(new Range(2, 2, 5, 10).containsRange(new Range(5, 10, 6, 1)), false); + assert.strictEqual(new Range(2, 2, 5, 10).containsRange(new Range(2, 2, 5, 10)), true); + assert.strictEqual(new Range(2, 2, 5, 10).containsRange(new Range(2, 3, 5, 9)), true); + assert.strictEqual(new Range(2, 2, 5, 10).containsRange(new Range(3, 100, 4, 100)), true); }); test('areIntersecting', () => { - assert.equal(Range.areIntersecting(new Range(2, 2, 3, 2), new Range(4, 2, 5, 2)), false); - assert.equal(Range.areIntersecting(new Range(4, 2, 5, 2), new Range(2, 2, 3, 2)), false); - assert.equal(Range.areIntersecting(new Range(4, 2, 5, 2), new Range(5, 2, 6, 2)), false); - assert.equal(Range.areIntersecting(new Range(5, 2, 6, 2), new Range(4, 2, 5, 2)), false); - assert.equal(Range.areIntersecting(new Range(2, 2, 2, 7), new Range(2, 4, 2, 6)), true); - assert.equal(Range.areIntersecting(new Range(2, 2, 2, 7), new Range(2, 4, 2, 9)), true); - assert.equal(Range.areIntersecting(new Range(2, 4, 2, 9), new Range(2, 2, 2, 7)), true); + assert.strictEqual(Range.areIntersecting(new Range(2, 2, 3, 2), new Range(4, 2, 5, 2)), false); + assert.strictEqual(Range.areIntersecting(new Range(4, 2, 5, 2), new Range(2, 2, 3, 2)), false); + assert.strictEqual(Range.areIntersecting(new Range(4, 2, 5, 2), new Range(5, 2, 6, 2)), false); + assert.strictEqual(Range.areIntersecting(new Range(5, 2, 6, 2), new Range(4, 2, 5, 2)), false); + assert.strictEqual(Range.areIntersecting(new Range(2, 2, 2, 7), new Range(2, 4, 2, 6)), true); + assert.strictEqual(Range.areIntersecting(new Range(2, 2, 2, 7), new Range(2, 4, 2, 9)), true); + assert.strictEqual(Range.areIntersecting(new Range(2, 4, 2, 9), new Range(2, 2, 2, 7)), true); }); }); diff --git a/src/vs/editor/test/common/diff/diffComputer.test.ts b/src/vs/editor/test/common/diff/diffComputer.test.ts index 9361e05d6..32dd4bce0 100644 --- a/src/vs/editor/test/common/diff/diffComputer.test.ts +++ b/src/vs/editor/test/common/diff/diffComputer.test.ts @@ -64,7 +64,7 @@ function assertDiff(originalLines: string[], modifiedLines: string[], expectedCh for (let i = 0; i < changes.length; i++) { extracted.push(extractLineChangeRepresentation(changes[i], (i < expectedChanges.length ? expectedChanges[i] : null))); } - assert.deepEqual(extracted, expectedChanges); + assert.deepStrictEqual(extracted, expectedChanges); } function createLineDeletion(startLineNumber: number, endLineNumber: number, modifiedLineNumber: number): ILineChange { @@ -462,6 +462,13 @@ suite('Editor Diff - DiffComputer', () => { assertDiff(original, modified, expected, true, false, true); }); + test('empty diff 5', () => { + let original = ['']; + let modified = ['']; + let expected: ILineChange[] = []; + assertDiff(original, modified, expected, true, false, true); + }); + test('pretty diff 1', () => { let original = [ 'suite(function () {', diff --git a/src/vs/editor/test/common/mocks/testConfiguration.ts b/src/vs/editor/test/common/mocks/testConfiguration.ts index a30fbc237..ec94f2201 100644 --- a/src/vs/editor/test/common/mocks/testConfiguration.ts +++ b/src/vs/editor/test/common/mocks/testConfiguration.ts @@ -30,6 +30,7 @@ export class TestConfiguration extends CommonEditorConfiguration { protected readConfiguration(styling: BareFontInfo): FontInfo { return new FontInfo({ zoomLevel: 0, + pixelRatio: 1, fontFamily: 'mockFont', fontWeight: 'normal', fontSize: 14, diff --git a/src/vs/editor/test/common/model/benchmark/benchmarkUtils.ts b/src/vs/editor/test/common/model/benchmark/benchmarkUtils.ts index 79d4e1e46..357d3bb55 100644 --- a/src/vs/editor/test/common/model/benchmark/benchmarkUtils.ts +++ b/src/vs/editor/test/common/model/benchmark/benchmarkUtils.ts @@ -58,7 +58,7 @@ export class BenchmarkSuite { let timeDiffTotal = 0; for (let j = 0; j < this.iterations; j++) { let factory = benchmark.buildBuffer(builder); - let buffer = factory.create(DefaultEndOfLine.LF); + let buffer = factory.create(DefaultEndOfLine.LF).textBuffer; benchmark.preCycle(buffer); let start = process.hrtime(); benchmark.fn(buffer); diff --git a/src/vs/editor/test/common/model/editableTextModel.test.ts b/src/vs/editor/test/common/model/editableTextModel.test.ts index 67c8a119e..c0e4be805 100644 --- a/src/vs/editor/test/common/model/editableTextModel.test.ts +++ b/src/vs/editor/test/common/model/editableTextModel.test.ts @@ -22,10 +22,10 @@ suite('EditorModel - EditableTextModel.applyEdits updates mightContainRTL', () = let model = createEditableTextModelFromString(original.join('\n')); model.setEOL(EndOfLineSequence.LF); - assert.equal(model.mightContainRTL(), before); + assert.strictEqual(model.mightContainRTL(), before); model.applyEdits(edits); - assert.equal(model.mightContainRTL(), after); + assert.strictEqual(model.mightContainRTL(), after); model.dispose(); } @@ -68,10 +68,10 @@ suite('EditorModel - EditableTextModel.applyEdits updates mightContainNonBasicAS let model = createEditableTextModelFromString(original.join('\n')); model.setEOL(EndOfLineSequence.LF); - assert.equal(model.mightContainNonBasicASCII(), before); + assert.strictEqual(model.mightContainNonBasicASCII(), before); model.applyEdits(edits); - assert.equal(model.mightContainNonBasicASCII(), after); + assert.strictEqual(model.mightContainNonBasicASCII(), after); model.dispose(); } @@ -1043,7 +1043,7 @@ suite('EditorModel - EditableTextModel.applyEdits', () => { test('issue #1580: Changes in line endings are not correctly reflected in the extension host, leading to invalid offsets sent to external refactoring tools', () => { let model = createEditableTextModelFromString('Hello\nWorld!'); - assert.equal(model.getEOL(), '\n'); + assert.strictEqual(model.getEOL(), '\n'); let mirrorModel2 = new MirrorTextModel(null!, model.getLinesContent(), model.getEOL(), model.getVersionId()); let mirrorModel2PrevVersionId = model.getVersionId(); @@ -1058,8 +1058,8 @@ suite('EditorModel - EditableTextModel.applyEdits', () => { }); let assertMirrorModels = () => { - assert.equal(mirrorModel2.getText(), model.getValue(), 'mirror model 2 text OK'); - assert.equal(mirrorModel2.version, model.getVersionId(), 'mirror model 2 version OK'); + assert.strictEqual(mirrorModel2.getText(), model.getValue(), 'mirror model 2 text OK'); + assert.strictEqual(mirrorModel2.version, model.getVersionId(), 'mirror model 2 version OK'); }; model.setEOL(EndOfLineSequence.CRLF); @@ -1077,16 +1077,16 @@ suite('EditorModel - EditableTextModel.applyEdits', () => { { range: new Range(1, 2, 1, 2), text: '"' }, ]); - assert.equal(model.getValue(EndOfLinePreference.LF), '"\'"👁\''); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '"\'"👁\''); - assert.deepEqual(model.validateRange(new Range(1, 3, 1, 4)), new Range(1, 3, 1, 4)); + assert.deepStrictEqual(model.validateRange(new Range(1, 3, 1, 4)), new Range(1, 3, 1, 4)); model.applyEdits([ { range: new Range(1, 1, 1, 2), text: null }, { range: new Range(1, 3, 1, 4), text: null }, ]); - assert.equal(model.getValue(EndOfLinePreference.LF), '\'👁\''); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\'👁\''); model.dispose(); }); @@ -1108,7 +1108,7 @@ suite('EditorModel - EditableTextModel.applyEdits', () => { model.applyEdits(undoEdits); - assert.deepEqual(model.getValue(), 'line1\nline2\nline3\n'); + assert.deepStrictEqual(model.getValue(), 'line1\nline2\nline3\n'); model.dispose(); }); diff --git a/src/vs/editor/test/common/model/intervalTree.test.ts b/src/vs/editor/test/common/model/intervalTree.test.ts index be89c0160..58e534c09 100644 --- a/src/vs/editor/test/common/model/intervalTree.test.ts +++ b/src/vs/editor/test/common/model/intervalTree.test.ts @@ -111,7 +111,7 @@ suite('IntervalTree', () => { let actualNodes = this._tree.intervalSearch(op.begin, op.end, 0, false, 0); let actual = actualNodes.map(n => new Interval(n.cachedAbsoluteStart, n.cachedAbsoluteEnd)); let expected = this._oracle.search(new Interval(op.begin, op.end)); - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); return; } @@ -123,7 +123,7 @@ suite('IntervalTree', () => { let actual = this._tree.getAllInOrder().map(n => new Interval(n.cachedAbsoluteStart, n.cachedAbsoluteEnd)); let expected = this._oracle.intervals; - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); } public getExistingNodeId(index: number): number { @@ -500,7 +500,7 @@ suite('IntervalTree', () => { function assertIntervalSearch(start: number, end: number, expected: [number, number][]): void { let actualNodes = T.intervalSearch(start, end, 0, false, 0); let actual = actualNodes.map((n) => <[number, number]>[n.cachedAbsoluteStart, n.cachedAbsoluteEnd]); - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); } test('cormen 1->2', () => { @@ -559,7 +559,7 @@ suite('IntervalTree', () => { let node = new IntervalNode('', nodeStart, nodeEnd); setNodeStickiness(node, nodeStickiness); nodeAcceptEdit(node, start, end, textLength, forceMoveMarkers); - assert.deepEqual([node.start, node.end], [expectedNodeStart, expectedNodeEnd], msg); + assert.deepStrictEqual([node.start, node.end], [expectedNodeStart, expectedNodeEnd], msg); } test('nodeAcceptEdit', () => { diff --git a/src/vs/editor/test/common/model/linesTextBuffer/linesTextBuffer.test.ts b/src/vs/editor/test/common/model/linesTextBuffer/linesTextBuffer.test.ts index 4c9bd0d18..f385fdda9 100644 --- a/src/vs/editor/test/common/model/linesTextBuffer/linesTextBuffer.test.ts +++ b/src/vs/editor/test/common/model/linesTextBuffer/linesTextBuffer.test.ts @@ -33,7 +33,7 @@ suite('PieceTreeTextBuffer._getInverseEdits', () => { function assertInverseEdits(ops: IValidatedEditOperation[], expected: Range[]): void { let actual = PieceTreeTextBuffer._getInverseEditRanges(ops); - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); } test('single insert', () => { @@ -282,10 +282,10 @@ suite('PieceTreeTextBuffer._toSingleEditOperation', () => { } function testToSingleEditOperation(original: string[], edits: IValidatedEditOperation[], expected: IValidatedEditOperation): void { - const textBuffer = createTextBufferFactory(original.join('\n')).create(DefaultEndOfLine.LF); + const textBuffer = createTextBufferFactory(original.join('\n')).create(DefaultEndOfLine.LF).textBuffer; const actual = textBuffer._toSingleEditOperation(edits); - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); } test('one edit op is unchanged', () => { diff --git a/src/vs/editor/test/common/model/linesTextBuffer/linesTextBufferBuilder.test.ts b/src/vs/editor/test/common/model/linesTextBuffer/linesTextBufferBuilder.test.ts index dafa53c07..83b4a90d9 100644 --- a/src/vs/editor/test/common/model/linesTextBuffer/linesTextBufferBuilder.test.ts +++ b/src/vs/editor/test/common/model/linesTextBuffer/linesTextBufferBuilder.test.ts @@ -10,11 +10,11 @@ import { PieceTreeTextBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/ import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; export function testTextBufferFactory(text: string, eol: string, mightContainNonBasicASCII: boolean, mightContainRTL: boolean): void { - const textBuffer = createTextBufferFactory(text).create(DefaultEndOfLine.LF); + const textBuffer = createTextBufferFactory(text).create(DefaultEndOfLine.LF).textBuffer; - assert.equal(textBuffer.mightContainNonBasicASCII(), mightContainNonBasicASCII); - assert.equal(textBuffer.mightContainRTL(), mightContainRTL); - assert.equal(textBuffer.getEOL(), eol); + assert.strictEqual(textBuffer.mightContainNonBasicASCII(), mightContainNonBasicASCII); + assert.strictEqual(textBuffer.mightContainRTL(), mightContainRTL); + assert.strictEqual(textBuffer.getEOL(), eol); } suite('ModelBuilder', () => { diff --git a/src/vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils.ts b/src/vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils.ts index 4f87bf638..069b5553b 100644 --- a/src/vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils.ts +++ b/src/vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils.ts @@ -6,7 +6,7 @@ import { CharCode } from 'vs/base/common/charCode'; import { splitLines } from 'vs/base/common/strings'; import { Range } from 'vs/editor/common/core/range'; -import { DefaultEndOfLine, ITextBuffer, ITextBufferBuilder, ValidAnnotatedEditOperation } from 'vs/editor/common/model'; +import { ValidAnnotatedEditOperation } from 'vs/editor/common/model'; export function getRandomInt(min: number, max: number): number { return Math.floor(Math.random() * (max - min + 1)) + min; @@ -127,25 +127,6 @@ export function generateRandomReplaces(chunks: string[], editCnt: number, search return ops; } -export function createMockText(lineCount: number, minColumn: number, maxColumn: number) { - let fixedEOL = getRandomEOLSequence(); - let lines: string[] = []; - for (let i = 0; i < lineCount; i++) { - if (i !== 0) { - lines.push(fixedEOL); - } - lines.push(getRandomString(minColumn, maxColumn)); - } - return lines.join(''); -} - -export function createMockBuffer(str: string, bufferBuilder: ITextBufferBuilder): ITextBuffer { - bufferBuilder.acceptChunk(str); - let bufferFactory = bufferBuilder.finish(); - let buffer = bufferFactory.create(DefaultEndOfLine.LF); - return buffer; -} - export function generateRandomChunkWithLF(minLength: number, maxLength: number): string { let length = getRandomInt(minLength, maxLength); let r = ''; diff --git a/src/vs/editor/test/common/model/model.line.test.ts b/src/vs/editor/test/common/model/model.line.test.ts index 139b169bf..b07f24810 100644 --- a/src/vs/editor/test/common/model/model.line.test.ts +++ b/src/vs/editor/test/common/model/model.line.test.ts @@ -39,13 +39,13 @@ function assertLineTokens(__actual: LineTokens, _expected: TestToken[]): void { type: token.getType() }; }; - assert.deepEqual(actual, expected.map(decode)); + assert.deepStrictEqual(actual, expected.map(decode)); } suite('ModelLine - getIndentLevel', () => { function assertIndentLevel(text: string, expected: number, tabSize: number = 4): void { let actual = TextModel.computeIndentLevel(text, tabSize); - assert.equal(actual, expected, text); + assert.strictEqual(actual, expected, text); } test('getIndentLevel', () => { @@ -126,7 +126,7 @@ suite('ModelLinesTokens', () => { for (let lineIndex = 0; lineIndex < expected.length; lineIndex++) { const actualLine = model.getLineContent(lineIndex + 1); const actualTokens = model.getLineTokens(lineIndex + 1); - assert.equal(actualLine, expected[lineIndex].text); + assert.strictEqual(actualLine, expected[lineIndex].text); assertLineTokens(actualTokens, expected[lineIndex].tokens); } } diff --git a/src/vs/editor/test/common/model/model.modes.test.ts b/src/vs/editor/test/common/model/model.modes.test.ts index ce313b77a..1d255659e 100644 --- a/src/vs/editor/test/common/model/model.modes.test.ts +++ b/src/vs/editor/test/common/model/model.modes.test.ts @@ -21,14 +21,14 @@ suite('Editor Model - Model Modes 1', () => { let calledFor: string[] = []; function checkAndClear(arr: string[]) { - assert.deepEqual(calledFor, arr); + assert.deepStrictEqual(calledFor, arr); calledFor = []; } const tokenizationSupport: modes.ITokenizationSupport = { getInitialState: () => NULL_STATE, tokenize: undefined!, - tokenize2: (line: string, state: modes.IState): TokenizationResult2 => { + tokenize2: (line: string, hasEOL: boolean, state: modes.IState): TokenizationResult2 => { calledFor.push(line.charAt(0)); return new TokenizationResult2(new Uint32Array(0), state); } @@ -106,7 +106,7 @@ suite('Editor Model - Model Modes 1', () => { checkAndClear(['1', '2', '3', '4', '5']); thisModel.applyEdits([EditOperation.insert(new Position(1, 1), '0\n-\n+')]); - assert.equal(thisModel.getLineCount(), 7); + assert.strictEqual(thisModel.getLineCount(), 7); thisModel.forceTokenization(7); checkAndClear(['0', '-', '+']); @@ -174,14 +174,14 @@ suite('Editor Model - Model Modes 2', () => { let calledFor: string[] = []; function checkAndClear(arr: string[]): void { - assert.deepEqual(calledFor, arr); + assert.deepStrictEqual(calledFor, arr); calledFor = []; } const tokenizationSupport: modes.ITokenizationSupport = { getInitialState: () => new ModelState2(''), tokenize: undefined!, - tokenize2: (line: string, state: modes.IState): TokenizationResult2 => { + tokenize2: (line: string, hasEOL: boolean, state: modes.IState): TokenizationResult2 => { calledFor.push(line); (state).prevLineContent = line; return new TokenizationResult2(new Uint32Array(0), state); diff --git a/src/vs/editor/test/common/model/model.test.ts b/src/vs/editor/test/common/model/model.test.ts index ed6787bc5..bfdcfc946 100644 --- a/src/vs/editor/test/common/model/model.test.ts +++ b/src/vs/editor/test/common/model/model.test.ts @@ -46,50 +46,50 @@ suite('Editor Model - Model', () => { // --------- insert text test('model getValue', () => { - assert.equal(thisModel.getValue(), 'My First Line\n\t\tMy Second Line\n Third Line\n\n1'); + assert.strictEqual(thisModel.getValue(), 'My First Line\n\t\tMy Second Line\n Third Line\n\n1'); }); test('model insert empty text', () => { thisModel.applyEdits([EditOperation.insert(new Position(1, 1), '')]); - assert.equal(thisModel.getLineCount(), 5); - assert.equal(thisModel.getLineContent(1), 'My First Line'); + assert.strictEqual(thisModel.getLineCount(), 5); + assert.strictEqual(thisModel.getLineContent(1), 'My First Line'); }); test('model insert text without newline 1', () => { thisModel.applyEdits([EditOperation.insert(new Position(1, 1), 'foo ')]); - assert.equal(thisModel.getLineCount(), 5); - assert.equal(thisModel.getLineContent(1), 'foo My First Line'); + assert.strictEqual(thisModel.getLineCount(), 5); + assert.strictEqual(thisModel.getLineContent(1), 'foo My First Line'); }); test('model insert text without newline 2', () => { thisModel.applyEdits([EditOperation.insert(new Position(1, 3), ' foo')]); - assert.equal(thisModel.getLineCount(), 5); - assert.equal(thisModel.getLineContent(1), 'My foo First Line'); + assert.strictEqual(thisModel.getLineCount(), 5); + assert.strictEqual(thisModel.getLineContent(1), 'My foo First Line'); }); test('model insert text with one newline', () => { thisModel.applyEdits([EditOperation.insert(new Position(1, 3), ' new line\nNo longer')]); - assert.equal(thisModel.getLineCount(), 6); - assert.equal(thisModel.getLineContent(1), 'My new line'); - assert.equal(thisModel.getLineContent(2), 'No longer First Line'); + assert.strictEqual(thisModel.getLineCount(), 6); + assert.strictEqual(thisModel.getLineContent(1), 'My new line'); + assert.strictEqual(thisModel.getLineContent(2), 'No longer First Line'); }); test('model insert text with two newlines', () => { thisModel.applyEdits([EditOperation.insert(new Position(1, 3), ' new line\nOne more line in the middle\nNo longer')]); - assert.equal(thisModel.getLineCount(), 7); - assert.equal(thisModel.getLineContent(1), 'My new line'); - assert.equal(thisModel.getLineContent(2), 'One more line in the middle'); - assert.equal(thisModel.getLineContent(3), 'No longer First Line'); + assert.strictEqual(thisModel.getLineCount(), 7); + assert.strictEqual(thisModel.getLineContent(1), 'My new line'); + assert.strictEqual(thisModel.getLineContent(2), 'One more line in the middle'); + assert.strictEqual(thisModel.getLineContent(3), 'No longer First Line'); }); test('model insert text with many newlines', () => { thisModel.applyEdits([EditOperation.insert(new Position(1, 3), '\n\n\n\n')]); - assert.equal(thisModel.getLineCount(), 9); - assert.equal(thisModel.getLineContent(1), 'My'); - assert.equal(thisModel.getLineContent(2), ''); - assert.equal(thisModel.getLineContent(3), ''); - assert.equal(thisModel.getLineContent(4), ''); - assert.equal(thisModel.getLineContent(5), ' First Line'); + assert.strictEqual(thisModel.getLineCount(), 9); + assert.strictEqual(thisModel.getLineContent(1), 'My'); + assert.strictEqual(thisModel.getLineContent(2), ''); + assert.strictEqual(thisModel.getLineContent(3), ''); + assert.strictEqual(thisModel.getLineContent(4), ''); + assert.strictEqual(thisModel.getLineContent(5), ' First Line'); }); @@ -111,7 +111,7 @@ suite('Editor Model - Model', () => { e = _e; }); thisModel.applyEdits([EditOperation.insert(new Position(1, 1), 'foo ')]); - assert.deepEqual(e, new ModelRawContentChangedEvent( + assert.deepStrictEqual(e, new ModelRawContentChangedEvent( [ new ModelRawLineChanged(1, 'foo My First Line') ], @@ -130,7 +130,7 @@ suite('Editor Model - Model', () => { e = _e; }); thisModel.applyEdits([EditOperation.insert(new Position(1, 3), ' new line\nNo longer')]); - assert.deepEqual(e, new ModelRawContentChangedEvent( + assert.deepStrictEqual(e, new ModelRawContentChangedEvent( [ new ModelRawLineChanged(1, 'My new line'), new ModelRawLinesInserted(2, 2, ['No longer First Line']), @@ -146,47 +146,47 @@ suite('Editor Model - Model', () => { test('model delete empty text', () => { thisModel.applyEdits([EditOperation.delete(new Range(1, 1, 1, 1))]); - assert.equal(thisModel.getLineCount(), 5); - assert.equal(thisModel.getLineContent(1), 'My First Line'); + assert.strictEqual(thisModel.getLineCount(), 5); + assert.strictEqual(thisModel.getLineContent(1), 'My First Line'); }); test('model delete text from one line', () => { thisModel.applyEdits([EditOperation.delete(new Range(1, 1, 1, 2))]); - assert.equal(thisModel.getLineCount(), 5); - assert.equal(thisModel.getLineContent(1), 'y First Line'); + assert.strictEqual(thisModel.getLineCount(), 5); + assert.strictEqual(thisModel.getLineContent(1), 'y First Line'); }); test('model delete text from one line 2', () => { thisModel.applyEdits([EditOperation.insert(new Position(1, 1), 'a')]); - assert.equal(thisModel.getLineContent(1), 'aMy First Line'); + assert.strictEqual(thisModel.getLineContent(1), 'aMy First Line'); thisModel.applyEdits([EditOperation.delete(new Range(1, 2, 1, 4))]); - assert.equal(thisModel.getLineCount(), 5); - assert.equal(thisModel.getLineContent(1), 'a First Line'); + assert.strictEqual(thisModel.getLineCount(), 5); + assert.strictEqual(thisModel.getLineContent(1), 'a First Line'); }); test('model delete all text from a line', () => { thisModel.applyEdits([EditOperation.delete(new Range(1, 1, 1, 14))]); - assert.equal(thisModel.getLineCount(), 5); - assert.equal(thisModel.getLineContent(1), ''); + assert.strictEqual(thisModel.getLineCount(), 5); + assert.strictEqual(thisModel.getLineContent(1), ''); }); test('model delete text from two lines', () => { thisModel.applyEdits([EditOperation.delete(new Range(1, 4, 2, 6))]); - assert.equal(thisModel.getLineCount(), 4); - assert.equal(thisModel.getLineContent(1), 'My Second Line'); + assert.strictEqual(thisModel.getLineCount(), 4); + assert.strictEqual(thisModel.getLineContent(1), 'My Second Line'); }); test('model delete text from many lines', () => { thisModel.applyEdits([EditOperation.delete(new Range(1, 4, 3, 5))]); - assert.equal(thisModel.getLineCount(), 3); - assert.equal(thisModel.getLineContent(1), 'My Third Line'); + assert.strictEqual(thisModel.getLineCount(), 3); + assert.strictEqual(thisModel.getLineContent(1), 'My Third Line'); }); test('model delete everything', () => { thisModel.applyEdits([EditOperation.delete(new Range(1, 1, 5, 2))]); - assert.equal(thisModel.getLineCount(), 1); - assert.equal(thisModel.getLineContent(1), ''); + assert.strictEqual(thisModel.getLineCount(), 1); + assert.strictEqual(thisModel.getLineContent(1), ''); }); // --------- delete text eventing @@ -207,7 +207,7 @@ suite('Editor Model - Model', () => { e = _e; }); thisModel.applyEdits([EditOperation.delete(new Range(1, 1, 1, 2))]); - assert.deepEqual(e, new ModelRawContentChangedEvent( + assert.deepStrictEqual(e, new ModelRawContentChangedEvent( [ new ModelRawLineChanged(1, 'y First Line'), ], @@ -226,7 +226,7 @@ suite('Editor Model - Model', () => { e = _e; }); thisModel.applyEdits([EditOperation.delete(new Range(1, 1, 1, 14))]); - assert.deepEqual(e, new ModelRawContentChangedEvent( + assert.deepStrictEqual(e, new ModelRawContentChangedEvent( [ new ModelRawLineChanged(1, ''), ], @@ -245,7 +245,7 @@ suite('Editor Model - Model', () => { e = _e; }); thisModel.applyEdits([EditOperation.delete(new Range(1, 4, 2, 6))]); - assert.deepEqual(e, new ModelRawContentChangedEvent( + assert.deepStrictEqual(e, new ModelRawContentChangedEvent( [ new ModelRawLineChanged(1, 'My Second Line'), new ModelRawLinesDeleted(2, 2), @@ -265,7 +265,7 @@ suite('Editor Model - Model', () => { e = _e; }); thisModel.applyEdits([EditOperation.delete(new Range(1, 4, 3, 5))]); - assert.deepEqual(e, new ModelRawContentChangedEvent( + assert.deepStrictEqual(e, new ModelRawContentChangedEvent( [ new ModelRawLineChanged(1, 'My Third Line'), new ModelRawLinesDeleted(2, 3), @@ -279,31 +279,31 @@ suite('Editor Model - Model', () => { // --------- getValueInRange test('getValueInRange', () => { - assert.equal(thisModel.getValueInRange(new Range(1, 1, 1, 1)), ''); - assert.equal(thisModel.getValueInRange(new Range(1, 1, 1, 2)), 'M'); - assert.equal(thisModel.getValueInRange(new Range(1, 2, 1, 3)), 'y'); - assert.equal(thisModel.getValueInRange(new Range(1, 1, 1, 14)), 'My First Line'); - assert.equal(thisModel.getValueInRange(new Range(1, 1, 2, 1)), 'My First Line\n'); - assert.equal(thisModel.getValueInRange(new Range(1, 1, 2, 2)), 'My First Line\n\t'); - assert.equal(thisModel.getValueInRange(new Range(1, 1, 2, 3)), 'My First Line\n\t\t'); - assert.equal(thisModel.getValueInRange(new Range(1, 1, 2, 17)), 'My First Line\n\t\tMy Second Line'); - assert.equal(thisModel.getValueInRange(new Range(1, 1, 3, 1)), 'My First Line\n\t\tMy Second Line\n'); - assert.equal(thisModel.getValueInRange(new Range(1, 1, 4, 1)), 'My First Line\n\t\tMy Second Line\n Third Line\n'); + assert.strictEqual(thisModel.getValueInRange(new Range(1, 1, 1, 1)), ''); + assert.strictEqual(thisModel.getValueInRange(new Range(1, 1, 1, 2)), 'M'); + assert.strictEqual(thisModel.getValueInRange(new Range(1, 2, 1, 3)), 'y'); + assert.strictEqual(thisModel.getValueInRange(new Range(1, 1, 1, 14)), 'My First Line'); + assert.strictEqual(thisModel.getValueInRange(new Range(1, 1, 2, 1)), 'My First Line\n'); + assert.strictEqual(thisModel.getValueInRange(new Range(1, 1, 2, 2)), 'My First Line\n\t'); + assert.strictEqual(thisModel.getValueInRange(new Range(1, 1, 2, 3)), 'My First Line\n\t\t'); + assert.strictEqual(thisModel.getValueInRange(new Range(1, 1, 2, 17)), 'My First Line\n\t\tMy Second Line'); + assert.strictEqual(thisModel.getValueInRange(new Range(1, 1, 3, 1)), 'My First Line\n\t\tMy Second Line\n'); + assert.strictEqual(thisModel.getValueInRange(new Range(1, 1, 4, 1)), 'My First Line\n\t\tMy Second Line\n Third Line\n'); }); // --------- getValueLengthInRange test('getValueLengthInRange', () => { - assert.equal(thisModel.getValueLengthInRange(new Range(1, 1, 1, 1)), ''.length); - assert.equal(thisModel.getValueLengthInRange(new Range(1, 1, 1, 2)), 'M'.length); - assert.equal(thisModel.getValueLengthInRange(new Range(1, 2, 1, 3)), 'y'.length); - assert.equal(thisModel.getValueLengthInRange(new Range(1, 1, 1, 14)), 'My First Line'.length); - assert.equal(thisModel.getValueLengthInRange(new Range(1, 1, 2, 1)), 'My First Line\n'.length); - assert.equal(thisModel.getValueLengthInRange(new Range(1, 1, 2, 2)), 'My First Line\n\t'.length); - assert.equal(thisModel.getValueLengthInRange(new Range(1, 1, 2, 3)), 'My First Line\n\t\t'.length); - assert.equal(thisModel.getValueLengthInRange(new Range(1, 1, 2, 17)), 'My First Line\n\t\tMy Second Line'.length); - assert.equal(thisModel.getValueLengthInRange(new Range(1, 1, 3, 1)), 'My First Line\n\t\tMy Second Line\n'.length); - assert.equal(thisModel.getValueLengthInRange(new Range(1, 1, 4, 1)), 'My First Line\n\t\tMy Second Line\n Third Line\n'.length); + assert.strictEqual(thisModel.getValueLengthInRange(new Range(1, 1, 1, 1)), ''.length); + assert.strictEqual(thisModel.getValueLengthInRange(new Range(1, 1, 1, 2)), 'M'.length); + assert.strictEqual(thisModel.getValueLengthInRange(new Range(1, 2, 1, 3)), 'y'.length); + assert.strictEqual(thisModel.getValueLengthInRange(new Range(1, 1, 1, 14)), 'My First Line'.length); + assert.strictEqual(thisModel.getValueLengthInRange(new Range(1, 1, 2, 1)), 'My First Line\n'.length); + assert.strictEqual(thisModel.getValueLengthInRange(new Range(1, 1, 2, 2)), 'My First Line\n\t'.length); + assert.strictEqual(thisModel.getValueLengthInRange(new Range(1, 1, 2, 3)), 'My First Line\n\t\t'.length); + assert.strictEqual(thisModel.getValueLengthInRange(new Range(1, 1, 2, 17)), 'My First Line\n\t\tMy Second Line'.length); + assert.strictEqual(thisModel.getValueLengthInRange(new Range(1, 1, 3, 1)), 'My First Line\n\t\tMy Second Line\n'.length); + assert.strictEqual(thisModel.getValueLengthInRange(new Range(1, 1, 4, 1)), 'My First Line\n\t\tMy Second Line\n Third Line\n'.length); }); // --------- setValue @@ -316,7 +316,7 @@ suite('Editor Model - Model', () => { e = _e; }); thisModel.setValue('new value'); - assert.deepEqual(e, new ModelRawContentChangedEvent( + assert.deepStrictEqual(e, new ModelRawContentChangedEvent( [ new ModelRawFlush() ], @@ -332,8 +332,8 @@ suite('Editor Model - Model', () => { { range: new Range(1, 1, 1, 1), text: 'b' }, ], true); - assert.deepEqual(res[0].range, new Range(2, 1, 2, 2)); - assert.deepEqual(res[1].range, new Range(1, 1, 1, 2)); + assert.deepStrictEqual(res[0].range, new Range(2, 1, 2, 2)); + assert.deepStrictEqual(res[1].range, new Range(1, 1, 1, 2)); }); }); @@ -358,17 +358,17 @@ suite('Editor Model - Model Line Separators', () => { }); test('model getValue', () => { - assert.equal(thisModel.getValue(), 'My First Line\u2028\t\tMy Second Line\n Third Line\u2028\n1'); + assert.strictEqual(thisModel.getValue(), 'My First Line\u2028\t\tMy Second Line\n Third Line\u2028\n1'); }); test('model lines', () => { - assert.equal(thisModel.getLineCount(), 3); + assert.strictEqual(thisModel.getLineCount(), 3); }); test('Bug 13333:Model should line break on lonely CR too', () => { let model = createTextModel('Hello\rWorld!\r\nAnother line'); - assert.equal(model.getLineCount(), 3); - assert.equal(model.getValue(), 'Hello\r\nWorld!\r\nAnother line'); + assert.strictEqual(model.getLineCount(), 3); + assert.strictEqual(model.getValue(), 'Hello\r\nWorld!\r\nAnother line'); model.dispose(); }); }); @@ -389,7 +389,7 @@ suite('Editor Model - Words', () => { this._register(TokenizationRegistry.register(this.getLanguageIdentifier().language, { getInitialState: (): IState => NULL_STATE, tokenize: undefined!, - tokenize2: (line: string, state: IState): TokenizationResult2 => { + tokenize2: (line: string, hasEOL: boolean, state: IState): TokenizationResult2 => { const tokensArr: number[] = []; let prevLanguageId: LanguageIdentifier | undefined = undefined; for (let i = 0; i < line.length; i++) { @@ -434,17 +434,17 @@ suite('Editor Model - Words', () => { const thisModel = createTextModel(text.join('\n')); disposables.push(thisModel); - assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 1)), { word: 'This', startColumn: 1, endColumn: 5 }); - assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 2)), { word: 'This', startColumn: 1, endColumn: 5 }); - assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 4)), { word: 'This', startColumn: 1, endColumn: 5 }); - assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 5)), { word: 'This', startColumn: 1, endColumn: 5 }); - assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 6)), { word: 'text', startColumn: 6, endColumn: 10 }); - assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 19)), { word: 'some', startColumn: 15, endColumn: 19 }); - assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 20)), null); - assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 21)), { word: 'words', startColumn: 21, endColumn: 26 }); - assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 26)), { word: 'words', startColumn: 21, endColumn: 26 }); - assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 27)), null); - assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 28)), null); + assert.deepStrictEqual(thisModel.getWordAtPosition(new Position(1, 1)), { word: 'This', startColumn: 1, endColumn: 5 }); + assert.deepStrictEqual(thisModel.getWordAtPosition(new Position(1, 2)), { word: 'This', startColumn: 1, endColumn: 5 }); + assert.deepStrictEqual(thisModel.getWordAtPosition(new Position(1, 4)), { word: 'This', startColumn: 1, endColumn: 5 }); + assert.deepStrictEqual(thisModel.getWordAtPosition(new Position(1, 5)), { word: 'This', startColumn: 1, endColumn: 5 }); + assert.deepStrictEqual(thisModel.getWordAtPosition(new Position(1, 6)), { word: 'text', startColumn: 6, endColumn: 10 }); + assert.deepStrictEqual(thisModel.getWordAtPosition(new Position(1, 19)), { word: 'some', startColumn: 15, endColumn: 19 }); + assert.deepStrictEqual(thisModel.getWordAtPosition(new Position(1, 20)), null); + assert.deepStrictEqual(thisModel.getWordAtPosition(new Position(1, 21)), { word: 'words', startColumn: 21, endColumn: 26 }); + assert.deepStrictEqual(thisModel.getWordAtPosition(new Position(1, 26)), { word: 'words', startColumn: 21, endColumn: 26 }); + assert.deepStrictEqual(thisModel.getWordAtPosition(new Position(1, 27)), null); + assert.deepStrictEqual(thisModel.getWordAtPosition(new Position(1, 28)), null); }); test('getWordAtPosition at embedded language boundaries', () => { @@ -455,13 +455,13 @@ suite('Editor Model - Words', () => { const model = createTextModel('abab', undefined, outerMode.getLanguageIdentifier()); disposables.push(model); - assert.deepEqual(model.getWordAtPosition(new Position(1, 1)), { word: 'ab', startColumn: 1, endColumn: 3 }); - assert.deepEqual(model.getWordAtPosition(new Position(1, 2)), { word: 'ab', startColumn: 1, endColumn: 3 }); - assert.deepEqual(model.getWordAtPosition(new Position(1, 3)), { word: 'ab', startColumn: 1, endColumn: 3 }); - assert.deepEqual(model.getWordAtPosition(new Position(1, 4)), { word: 'xx', startColumn: 4, endColumn: 6 }); - assert.deepEqual(model.getWordAtPosition(new Position(1, 5)), { word: 'xx', startColumn: 4, endColumn: 6 }); - assert.deepEqual(model.getWordAtPosition(new Position(1, 6)), { word: 'xx', startColumn: 4, endColumn: 6 }); - assert.deepEqual(model.getWordAtPosition(new Position(1, 7)), { word: 'ab', startColumn: 7, endColumn: 9 }); + assert.deepStrictEqual(model.getWordAtPosition(new Position(1, 1)), { word: 'ab', startColumn: 1, endColumn: 3 }); + assert.deepStrictEqual(model.getWordAtPosition(new Position(1, 2)), { word: 'ab', startColumn: 1, endColumn: 3 }); + assert.deepStrictEqual(model.getWordAtPosition(new Position(1, 3)), { word: 'ab', startColumn: 1, endColumn: 3 }); + assert.deepStrictEqual(model.getWordAtPosition(new Position(1, 4)), { word: 'xx', startColumn: 4, endColumn: 6 }); + assert.deepStrictEqual(model.getWordAtPosition(new Position(1, 5)), { word: 'xx', startColumn: 4, endColumn: 6 }); + assert.deepStrictEqual(model.getWordAtPosition(new Position(1, 6)), { word: 'xx', startColumn: 4, endColumn: 6 }); + assert.deepStrictEqual(model.getWordAtPosition(new Position(1, 7)), { word: 'ab', startColumn: 7, endColumn: 9 }); }); test('issue #61296: VS code freezes when editing CSS file with emoji', () => { @@ -480,13 +480,13 @@ suite('Editor Model - Words', () => { const thisModel = createTextModel('.🐷-a-b', undefined, MODE_ID); disposables.push(thisModel); - assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 1)), { word: '.', startColumn: 1, endColumn: 2 }); - assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 2)), { word: '.', startColumn: 1, endColumn: 2 }); - assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 3)), null); - assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 4)), { word: '-a-b', startColumn: 4, endColumn: 8 }); - assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 5)), { word: '-a-b', startColumn: 4, endColumn: 8 }); - assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 6)), { word: '-a-b', startColumn: 4, endColumn: 8 }); - assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 7)), { word: '-a-b', startColumn: 4, endColumn: 8 }); - assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 8)), { word: '-a-b', startColumn: 4, endColumn: 8 }); + assert.deepStrictEqual(thisModel.getWordAtPosition(new Position(1, 1)), { word: '.', startColumn: 1, endColumn: 2 }); + assert.deepStrictEqual(thisModel.getWordAtPosition(new Position(1, 2)), { word: '.', startColumn: 1, endColumn: 2 }); + assert.deepStrictEqual(thisModel.getWordAtPosition(new Position(1, 3)), null); + assert.deepStrictEqual(thisModel.getWordAtPosition(new Position(1, 4)), { word: '-a-b', startColumn: 4, endColumn: 8 }); + assert.deepStrictEqual(thisModel.getWordAtPosition(new Position(1, 5)), { word: '-a-b', startColumn: 4, endColumn: 8 }); + assert.deepStrictEqual(thisModel.getWordAtPosition(new Position(1, 6)), { word: '-a-b', startColumn: 4, endColumn: 8 }); + assert.deepStrictEqual(thisModel.getWordAtPosition(new Position(1, 7)), { word: '-a-b', startColumn: 4, endColumn: 8 }); + assert.deepStrictEqual(thisModel.getWordAtPosition(new Position(1, 8)), { word: '-a-b', startColumn: 4, endColumn: 8 }); }); }); diff --git a/src/vs/editor/test/common/model/modelDecorations.test.ts b/src/vs/editor/test/common/model/modelDecorations.test.ts index 4f7690f11..da7b00939 100644 --- a/src/vs/editor/test/common/model/modelDecorations.test.ts +++ b/src/vs/editor/test/common/model/modelDecorations.test.ts @@ -28,7 +28,7 @@ function modelHasDecorations(model: TextModel, decorations: ILightWeightDecorati }); } modelDecorations.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); - assert.deepEqual(modelDecorations, decorations); + assert.deepStrictEqual(modelDecorations, decorations); } function modelHasDecoration(model: TextModel, startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, className: string) { @@ -39,7 +39,7 @@ function modelHasDecoration(model: TextModel, startLineNumber: number, startColu } function modelHasNoDecorations(model: TextModel) { - assert.equal(model.getAllDecorations().length, 0, 'Model has no decoration'); + assert.strictEqual(model.getAllDecorations().length, 0, 'Model has no decoration'); } function addDecoration(model: TextModel, startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, className: string): string { @@ -60,7 +60,7 @@ function lineHasDecorations(model: TextModel, lineNumber: number, decorations: { className: decs[i].options.className }); } - assert.deepEqual(lineDecorations, decorations, 'Line decorations'); + assert.deepStrictEqual(lineDecorations, decorations, 'Line decorations'); } function lineHasNoDecorations(model: TextModel, lineNumber: number) { @@ -122,12 +122,12 @@ suite('Editor Model - Model Decorations', () => { addDecoration(thisModel, 1, 1, 2, 1, 'myType'); let line1Decorations = thisModel.getLineDecorations(1); - assert.equal(line1Decorations.length, 1); - assert.equal(line1Decorations[0].options.className, 'myType'); + assert.strictEqual(line1Decorations.length, 1); + assert.strictEqual(line1Decorations[0].options.className, 'myType'); let line2Decorations = thisModel.getLineDecorations(1); - assert.equal(line2Decorations.length, 1); - assert.equal(line2Decorations[0].options.className, 'myType'); + assert.strictEqual(line2Decorations.length, 1); + assert.strictEqual(line2Decorations[0].options.className, 'myType'); lineHasNoDecorations(thisModel, 3); lineHasNoDecorations(thisModel, 4); @@ -138,16 +138,16 @@ suite('Editor Model - Model Decorations', () => { addDecoration(thisModel, 1, 2, 3, 2, 'myType'); let line1Decorations = thisModel.getLineDecorations(1); - assert.equal(line1Decorations.length, 1); - assert.equal(line1Decorations[0].options.className, 'myType'); + assert.strictEqual(line1Decorations.length, 1); + assert.strictEqual(line1Decorations[0].options.className, 'myType'); let line2Decorations = thisModel.getLineDecorations(1); - assert.equal(line2Decorations.length, 1); - assert.equal(line2Decorations[0].options.className, 'myType'); + assert.strictEqual(line2Decorations.length, 1); + assert.strictEqual(line2Decorations[0].options.className, 'myType'); let line3Decorations = thisModel.getLineDecorations(1); - assert.equal(line3Decorations.length, 1); - assert.equal(line3Decorations[0].options.className, 'myType'); + assert.strictEqual(line3Decorations.length, 1); + assert.strictEqual(line3Decorations[0].options.className, 'myType'); lineHasNoDecorations(thisModel, 4); lineHasNoDecorations(thisModel, 5); @@ -209,7 +209,7 @@ suite('Editor Model - Model Decorations', () => { listenerCalled++; }); addDecoration(thisModel, 1, 2, 3, 2, 'myType'); - assert.equal(listenerCalled, 1, 'listener called'); + assert.strictEqual(listenerCalled, 1, 'listener called'); }); test('decorations emit event on change', () => { @@ -221,7 +221,7 @@ suite('Editor Model - Model Decorations', () => { thisModel.changeDecorations((changeAccessor) => { changeAccessor.changeDecoration(decId, new Range(1, 1, 1, 2)); }); - assert.equal(listenerCalled, 1, 'listener called'); + assert.strictEqual(listenerCalled, 1, 'listener called'); }); test('decorations emit event on remove', () => { @@ -233,7 +233,7 @@ suite('Editor Model - Model Decorations', () => { thisModel.changeDecorations((changeAccessor) => { changeAccessor.removeDecoration(decId); }); - assert.equal(listenerCalled, 1, 'listener called'); + assert.strictEqual(listenerCalled, 1, 'listener called'); }); test('decorations emit event when inserting one line text before it', () => { @@ -245,7 +245,7 @@ suite('Editor Model - Model Decorations', () => { }); thisModel.applyEdits([EditOperation.insert(new Position(1, 1), 'Hallo ')]); - assert.equal(listenerCalled, 1, 'listener called'); + assert.strictEqual(listenerCalled, 1, 'listener called'); }); test('decorations do not emit event on no-op deltaDecorations', () => { @@ -260,7 +260,7 @@ suite('Editor Model - Model Decorations', () => { accessor.deltaDecorations([], []); }); - assert.equal(listenerCalled, 0, 'listener not called'); + assert.strictEqual(listenerCalled, 0, 'listener not called'); }); // --------- editing text & effects on decorations @@ -429,7 +429,7 @@ suite('Decorations and editing', () => { forceMoveMarkers: editForceMoveMarkers }]); const actual = model.getDecorationRange(id); - assert.deepEqual(actual, expectedDecRange, msg); + assert.deepStrictEqual(actual, expectedDecRange, msg); model.dispose(); } @@ -1155,20 +1155,20 @@ suite('deltaDecorations', () => { let initialIds = model.deltaDecorations([], decorations.map(toModelDeltaDecoration)); let actualDecorations = readModelDecorations(model, initialIds); - assert.equal(initialIds.length, decorations.length, 'returns expected cnt of ids'); - assert.equal(initialIds.length, model.getAllDecorations().length, 'does not leak decorations'); + assert.strictEqual(initialIds.length, decorations.length, 'returns expected cnt of ids'); + assert.strictEqual(initialIds.length, model.getAllDecorations().length, 'does not leak decorations'); actualDecorations.sort((a, b) => strcmp(a.id, b.id)); decorations.sort((a, b) => strcmp(a.id, b.id)); - assert.deepEqual(actualDecorations, decorations); + assert.deepStrictEqual(actualDecorations, decorations); let newIds = model.deltaDecorations(initialIds, newDecorations.map(toModelDeltaDecoration)); let actualNewDecorations = readModelDecorations(model, newIds); - assert.equal(newIds.length, newDecorations.length, 'returns expected cnt of ids'); - assert.equal(newIds.length, model.getAllDecorations().length, 'does not leak decorations'); + assert.strictEqual(newIds.length, newDecorations.length, 'returns expected cnt of ids'); + assert.strictEqual(newIds.length, model.getAllDecorations().length, 'does not leak decorations'); actualNewDecorations.sort((a, b) => strcmp(a.id, b.id)); newDecorations.sort((a, b) => strcmp(a.id, b.id)); - assert.deepEqual(actualDecorations, decorations); + assert.deepStrictEqual(actualDecorations, decorations); model.dispose(); } @@ -1188,8 +1188,8 @@ suite('deltaDecorations', () => { toModelDeltaDecoration(decoration('b', 2, 1, 2, 13)) ]); - assert.deepEqual(model.getDecorationRange(ids[0]), range(1, 1, 1, 12)); - assert.deepEqual(model.getDecorationRange(ids[1]), range(2, 1, 2, 13)); + assert.deepStrictEqual(model.getDecorationRange(ids[0]), range(1, 1, 1, 12)); + assert.deepStrictEqual(model.getDecorationRange(ids[1]), range(2, 1, 2, 13)); model.dispose(); }); @@ -1294,7 +1294,7 @@ suite('deltaDecorations', () => { let actualDecoration = model.getDecorationOptions(ids[0]); - assert.deepEqual(actualDecoration!.hoverMessage, { value: 'hello2' }); + assert.deepStrictEqual(actualDecoration!.hoverMessage, { value: 'hello2' }); model.dispose(); }); @@ -1326,16 +1326,16 @@ suite('deltaDecorations', () => { toModelDeltaDecoration(decoration('b', 2, 1, 2, 13)) ]); - assert.deepEqual(model.getDecorationRange(ids[0]), range(1, 1, 1, 12)); - assert.deepEqual(model.getDecorationRange(ids[1]), range(2, 1, 2, 13)); + assert.deepStrictEqual(model.getDecorationRange(ids[0]), range(1, 1, 1, 12)); + assert.deepStrictEqual(model.getDecorationRange(ids[1]), range(2, 1, 2, 13)); ids = model.deltaDecorations(ids, [ toModelDeltaDecoration(decoration('a', 1, 1, 1, 12)), toModelDeltaDecoration(decoration('b', 2, 1, 2, 13)) ]); - assert.deepEqual(model.getDecorationRange(ids[0]), range(1, 1, 1, 12)); - assert.deepEqual(model.getDecorationRange(ids[1]), range(2, 1, 2, 13)); + assert.deepStrictEqual(model.getDecorationRange(ids[0]), range(1, 1, 1, 12)); + assert.deepStrictEqual(model.getDecorationRange(ids[1]), range(2, 1, 2, 13)); model.dispose(); }); @@ -1365,7 +1365,7 @@ suite('deltaDecorations', () => { let inRangeClassNames = inRange.map(d => d.options.className); inRangeClassNames.sort(); - assert.deepEqual(inRangeClassNames, ['x1', 'x2', 'x3', 'x4']); + assert.deepStrictEqual(inRangeClassNames, ['x1', 'x2', 'x3', 'x4']); model.dispose(); }); @@ -1383,7 +1383,7 @@ suite('deltaDecorations', () => { forceMoveMarkers: false }]); const actual = model.getDecorationRange(id); - assert.deepEqual(actual, new Range(1, 1, 1, 1)); + assert.deepStrictEqual(actual, new Range(1, 1, 1, 1)); model.dispose(); }); diff --git a/src/vs/editor/test/common/model/modelEditOperation.test.ts b/src/vs/editor/test/common/model/modelEditOperation.test.ts index 058c5d3d5..d6c2af9b9 100644 --- a/src/vs/editor/test/common/model/modelEditOperation.test.ts +++ b/src/vs/editor/test/common/model/modelEditOperation.test.ts @@ -52,19 +52,19 @@ suite('Editor Model - Model Edit Operation', () => { let inverseEditOp = model.applyEdits(editOp, true); - assert.equal(model.getLineCount(), editedLines.length); + assert.strictEqual(model.getLineCount(), editedLines.length); for (let i = 0; i < editedLines.length; i++) { - assert.equal(model.getLineContent(i + 1), editedLines[i]); + assert.strictEqual(model.getLineContent(i + 1), editedLines[i]); } let originalOp = model.applyEdits(inverseEditOp, true); - assert.equal(model.getLineCount(), 5); - assert.equal(model.getLineContent(1), LINE1); - assert.equal(model.getLineContent(2), LINE2); - assert.equal(model.getLineContent(3), LINE3); - assert.equal(model.getLineContent(4), LINE4); - assert.equal(model.getLineContent(5), LINE5); + assert.strictEqual(model.getLineCount(), 5); + assert.strictEqual(model.getLineContent(1), LINE1); + assert.strictEqual(model.getLineContent(2), LINE2); + assert.strictEqual(model.getLineContent(3), LINE3); + assert.strictEqual(model.getLineContent(4), LINE4); + assert.strictEqual(model.getLineContent(5), LINE5); const simplifyEdit = (edit: IIdentifiedSingleEditOperation) => { return { @@ -75,7 +75,7 @@ suite('Editor Model - Model Edit Operation', () => { isAutoWhitespaceEdit: edit.isAutoWhitespaceEdit || false }; }; - assert.deepEqual(originalOp.map(simplifyEdit), editOp.map(simplifyEdit)); + assert.deepStrictEqual(originalOp.map(simplifyEdit), editOp.map(simplifyEdit)); } test('Insert inline', () => { diff --git a/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts b/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts index 019b23f4f..04fc6d743 100644 --- a/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts +++ b/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts @@ -77,11 +77,11 @@ function trimLineFeed(text: string): string { function testLinesContent(str: string, pieceTable: PieceTreeBase) { let lines = splitLines(str); - assert.equal(pieceTable.getLineCount(), lines.length); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLineCount(), lines.length); + assert.strictEqual(pieceTable.getLinesRawContent(), str); for (let i = 0; i < lines.length; i++) { - assert.equal(pieceTable.getLineContent(i + 1), lines[i]); - assert.equal( + assert.strictEqual(pieceTable.getLineContent(i + 1), lines[i]); + assert.strictEqual( trimLineFeed( pieceTable.getValueInRange( new Range( @@ -136,16 +136,16 @@ function testLineStarts(str: string, pieceTable: PieceTreeBase) { } while (m); for (let i = 0; i < lineStarts.length; i++) { - assert.deepEqual( + assert.deepStrictEqual( pieceTable.getPositionAt(lineStarts[i]), new Position(i + 1, 1) ); - assert.equal(pieceTable.getOffsetAt(i + 1, 1), lineStarts[i]); + assert.strictEqual(pieceTable.getOffsetAt(i + 1, 1), lineStarts[i]); } for (let i = 1; i < lineStarts.length; i++) { let pos = pieceTable.getPositionAt(lineStarts[i] - 1); - assert.equal( + assert.strictEqual( pieceTable.getOffsetAt(pos.lineNumber, pos.column), lineStarts[i] - 1 ); @@ -158,7 +158,7 @@ function createTextBuffer(val: string[], normalizeEOL: boolean = true): PieceTre bufferBuilder.acceptChunk(chunk); } let factory = bufferBuilder.finish(normalizeEOL); - return (factory.create(DefaultEndOfLine.LF)).getPieceTree(); + return (factory.create(DefaultEndOfLine.LF).textBuffer).getPieceTree(); } function assertTreeInvariants(T: PieceTreeBase): void { @@ -219,12 +219,12 @@ suite('inserts and deletes', () => { ]); pieceTable.insert(34, 'This is some more text to insert at offset 34.'); - assert.equal( + assert.strictEqual( pieceTable.getLinesRawContent(), 'This is a document with some text.This is some more text to insert at offset 34.' ); pieceTable.delete(42, 5); - assert.equal( + assert.strictEqual( pieceTable.getLinesRawContent(), 'This is a document with some text.This is more text to insert at offset 34.' ); @@ -235,28 +235,28 @@ suite('inserts and deletes', () => { let pt = createTextBuffer(['']); pt.insert(0, 'AAA'); - assert.equal(pt.getLinesRawContent(), 'AAA'); + assert.strictEqual(pt.getLinesRawContent(), 'AAA'); pt.insert(0, 'BBB'); - assert.equal(pt.getLinesRawContent(), 'BBBAAA'); + assert.strictEqual(pt.getLinesRawContent(), 'BBBAAA'); pt.insert(6, 'CCC'); - assert.equal(pt.getLinesRawContent(), 'BBBAAACCC'); + assert.strictEqual(pt.getLinesRawContent(), 'BBBAAACCC'); pt.insert(5, 'DDD'); - assert.equal(pt.getLinesRawContent(), 'BBBAADDDACCC'); + assert.strictEqual(pt.getLinesRawContent(), 'BBBAADDDACCC'); assertTreeInvariants(pt); }); test('more deletes', () => { let pt = createTextBuffer(['012345678']); pt.delete(8, 1); - assert.equal(pt.getLinesRawContent(), '01234567'); + assert.strictEqual(pt.getLinesRawContent(), '01234567'); pt.delete(0, 1); - assert.equal(pt.getLinesRawContent(), '1234567'); + assert.strictEqual(pt.getLinesRawContent(), '1234567'); pt.delete(5, 1); - assert.equal(pt.getLinesRawContent(), '123457'); + assert.strictEqual(pt.getLinesRawContent(), '123457'); pt.delete(5, 1); - assert.equal(pt.getLinesRawContent(), '12345'); + assert.strictEqual(pt.getLinesRawContent(), '12345'); pt.delete(0, 5); - assert.equal(pt.getLinesRawContent(), ''); + assert.strictEqual(pt.getLinesRawContent(), ''); assertTreeInvariants(pt); }); @@ -265,17 +265,17 @@ suite('inserts and deletes', () => { let pieceTable = createTextBuffer(['']); pieceTable.insert(0, 'ceLPHmFzvCtFeHkCBej '); str = str.substring(0, 0) + 'ceLPHmFzvCtFeHkCBej ' + str.substring(0); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); pieceTable.insert(8, 'gDCEfNYiBUNkSwtvB K '); str = str.substring(0, 8) + 'gDCEfNYiBUNkSwtvB K ' + str.substring(8); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); pieceTable.insert(38, 'cyNcHxjNPPoehBJldLS '); str = str.substring(0, 38) + 'cyNcHxjNPPoehBJldLS ' + str.substring(38); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); pieceTable.insert(59, 'ejMx\nOTgWlbpeDExjOk '); str = str.substring(0, 59) + 'ejMx\nOTgWlbpeDExjOk ' + str.substring(59); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); assertTreeInvariants(pieceTable); }); @@ -293,7 +293,7 @@ suite('inserts and deletes', () => { pieceTable.insert(10, 'Gbtp '); str = str.substring(0, 10) + 'Gbtp ' + str.substring(10); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); assertTreeInvariants(pieceTable); }); @@ -310,7 +310,7 @@ suite('inserts and deletes', () => { str = str.substring(0, 2) + 'GGZB' + str.substring(2); pieceTable.insert(12, 'wXpq'); str = str.substring(0, 12) + 'wXpq' + str.substring(12); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); }); test('random delete 1', () => { @@ -319,30 +319,30 @@ suite('inserts and deletes', () => { pieceTable.insert(0, 'vfb'); str = str.substring(0, 0) + 'vfb' + str.substring(0); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); pieceTable.insert(0, 'zRq'); str = str.substring(0, 0) + 'zRq' + str.substring(0); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); pieceTable.delete(5, 1); str = str.substring(0, 5) + str.substring(5 + 1); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); pieceTable.insert(1, 'UNw'); str = str.substring(0, 1) + 'UNw' + str.substring(1); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); pieceTable.delete(4, 3); str = str.substring(0, 4) + str.substring(4 + 3); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); pieceTable.delete(1, 4); str = str.substring(0, 1) + str.substring(1 + 4); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); pieceTable.delete(0, 1); str = str.substring(0, 0) + str.substring(0 + 1); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); assertTreeInvariants(pieceTable); }); @@ -368,7 +368,7 @@ suite('inserts and deletes', () => { str = str.substring(0, 6) + str.substring(6 + 7); pieceTable.delete(3, 5); str = str.substring(0, 3) + str.substring(3 + 5); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); assertTreeInvariants(pieceTable); }); @@ -401,7 +401,7 @@ suite('inserts and deletes', () => { str = str.substring(0, 5) + str.substring(5 + 8); pieceTable.delete(3, 4); str = str.substring(0, 3) + str.substring(3 + 4); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); assertTreeInvariants(pieceTable); }); @@ -427,7 +427,7 @@ suite('inserts and deletes', () => { pieceTable.insert(5, '\n\na\r'); str = str.substring(0, 5) + '\n\na\r' + str.substring(5); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); assertTreeInvariants(pieceTable); }); @@ -455,7 +455,7 @@ suite('inserts and deletes', () => { pieceTable.insert(2, 'a\ra\n'); str = str.substring(0, 2) + 'a\ra\n' + str.substring(2); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); assertTreeInvariants(pieceTable); }); @@ -480,11 +480,11 @@ suite('inserts and deletes', () => { str = str.substring(0, 5) + '\n\na\r' + str.substring(5); pieceTable.insert(10, '\r\r\n\r'); str = str.substring(0, 10) + '\r\r\n\r' + str.substring(10); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); pieceTable.delete(21, 3); str = str.substring(0, 21) + str.substring(21 + 3); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); assertTreeInvariants(pieceTable); }); @@ -512,7 +512,7 @@ suite('inserts and deletes', () => { pieceTable.insert(3, 'a\naa'); str = str.substring(0, 3) + 'a\naa' + str.substring(3); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); assertTreeInvariants(pieceTable); }); test('random insert/delete \\r bug 5', () => { @@ -539,7 +539,7 @@ suite('inserts and deletes', () => { pieceTable.insert(15, '\n\r\r\r'); str = str.substring(0, 15) + '\n\r\r\r' + str.substring(15); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); assertTreeInvariants(pieceTable); }); }); @@ -548,22 +548,22 @@ suite('prefix sum for line feed', () => { test('basic', () => { let pieceTable = createTextBuffer(['1\n2\n3\n4']); - assert.equal(pieceTable.getLineCount(), 4); - assert.deepEqual(pieceTable.getPositionAt(0), new Position(1, 1)); - assert.deepEqual(pieceTable.getPositionAt(1), new Position(1, 2)); - assert.deepEqual(pieceTable.getPositionAt(2), new Position(2, 1)); - assert.deepEqual(pieceTable.getPositionAt(3), new Position(2, 2)); - assert.deepEqual(pieceTable.getPositionAt(4), new Position(3, 1)); - assert.deepEqual(pieceTable.getPositionAt(5), new Position(3, 2)); - assert.deepEqual(pieceTable.getPositionAt(6), new Position(4, 1)); + assert.strictEqual(pieceTable.getLineCount(), 4); + assert.deepStrictEqual(pieceTable.getPositionAt(0), new Position(1, 1)); + assert.deepStrictEqual(pieceTable.getPositionAt(1), new Position(1, 2)); + assert.deepStrictEqual(pieceTable.getPositionAt(2), new Position(2, 1)); + assert.deepStrictEqual(pieceTable.getPositionAt(3), new Position(2, 2)); + assert.deepStrictEqual(pieceTable.getPositionAt(4), new Position(3, 1)); + assert.deepStrictEqual(pieceTable.getPositionAt(5), new Position(3, 2)); + assert.deepStrictEqual(pieceTable.getPositionAt(6), new Position(4, 1)); - assert.equal(pieceTable.getOffsetAt(1, 1), 0); - assert.equal(pieceTable.getOffsetAt(1, 2), 1); - assert.equal(pieceTable.getOffsetAt(2, 1), 2); - assert.equal(pieceTable.getOffsetAt(2, 2), 3); - assert.equal(pieceTable.getOffsetAt(3, 1), 4); - assert.equal(pieceTable.getOffsetAt(3, 2), 5); - assert.equal(pieceTable.getOffsetAt(4, 1), 6); + assert.strictEqual(pieceTable.getOffsetAt(1, 1), 0); + assert.strictEqual(pieceTable.getOffsetAt(1, 2), 1); + assert.strictEqual(pieceTable.getOffsetAt(2, 1), 2); + assert.strictEqual(pieceTable.getOffsetAt(2, 2), 3); + assert.strictEqual(pieceTable.getOffsetAt(3, 1), 4); + assert.strictEqual(pieceTable.getOffsetAt(3, 2), 5); + assert.strictEqual(pieceTable.getOffsetAt(4, 1), 6); assertTreeInvariants(pieceTable); }); @@ -571,9 +571,9 @@ suite('prefix sum for line feed', () => { let pieceTable = createTextBuffer(['a\nb\nc\nde']); pieceTable.insert(8, 'fh\ni\njk'); - assert.equal(pieceTable.getLineCount(), 6); - assert.deepEqual(pieceTable.getPositionAt(9), new Position(4, 4)); - assert.equal(pieceTable.getOffsetAt(1, 1), 0); + assert.strictEqual(pieceTable.getLineCount(), 6); + assert.deepStrictEqual(pieceTable.getPositionAt(9), new Position(4, 4)); + assert.strictEqual(pieceTable.getOffsetAt(1, 1), 0); assertTreeInvariants(pieceTable); }); @@ -581,22 +581,22 @@ suite('prefix sum for line feed', () => { let pieceTable = createTextBuffer(['a\nb\nc\nde']); pieceTable.insert(7, 'fh\ni\njk'); - assert.equal(pieceTable.getLineCount(), 6); - assert.deepEqual(pieceTable.getPositionAt(6), new Position(4, 1)); - assert.deepEqual(pieceTable.getPositionAt(7), new Position(4, 2)); - assert.deepEqual(pieceTable.getPositionAt(8), new Position(4, 3)); - assert.deepEqual(pieceTable.getPositionAt(9), new Position(4, 4)); - assert.deepEqual(pieceTable.getPositionAt(12), new Position(6, 1)); - assert.deepEqual(pieceTable.getPositionAt(13), new Position(6, 2)); - assert.deepEqual(pieceTable.getPositionAt(14), new Position(6, 3)); + assert.strictEqual(pieceTable.getLineCount(), 6); + assert.deepStrictEqual(pieceTable.getPositionAt(6), new Position(4, 1)); + assert.deepStrictEqual(pieceTable.getPositionAt(7), new Position(4, 2)); + assert.deepStrictEqual(pieceTable.getPositionAt(8), new Position(4, 3)); + assert.deepStrictEqual(pieceTable.getPositionAt(9), new Position(4, 4)); + assert.deepStrictEqual(pieceTable.getPositionAt(12), new Position(6, 1)); + assert.deepStrictEqual(pieceTable.getPositionAt(13), new Position(6, 2)); + assert.deepStrictEqual(pieceTable.getPositionAt(14), new Position(6, 3)); - assert.equal(pieceTable.getOffsetAt(4, 1), 6); - assert.equal(pieceTable.getOffsetAt(4, 2), 7); - assert.equal(pieceTable.getOffsetAt(4, 3), 8); - assert.equal(pieceTable.getOffsetAt(4, 4), 9); - assert.equal(pieceTable.getOffsetAt(6, 1), 12); - assert.equal(pieceTable.getOffsetAt(6, 2), 13); - assert.equal(pieceTable.getOffsetAt(6, 3), 14); + assert.strictEqual(pieceTable.getOffsetAt(4, 1), 6); + assert.strictEqual(pieceTable.getOffsetAt(4, 2), 7); + assert.strictEqual(pieceTable.getOffsetAt(4, 3), 8); + assert.strictEqual(pieceTable.getOffsetAt(4, 4), 9); + assert.strictEqual(pieceTable.getOffsetAt(6, 1), 12); + assert.strictEqual(pieceTable.getOffsetAt(6, 2), 13); + assert.strictEqual(pieceTable.getOffsetAt(6, 3), 14); assertTreeInvariants(pieceTable); }); @@ -604,23 +604,23 @@ suite('prefix sum for line feed', () => { let pieceTable = createTextBuffer(['a\nb\nc\ndefh\ni\njk']); pieceTable.delete(7, 2); - assert.equal(pieceTable.getLinesRawContent(), 'a\nb\nc\ndh\ni\njk'); - assert.equal(pieceTable.getLineCount(), 6); - assert.deepEqual(pieceTable.getPositionAt(6), new Position(4, 1)); - assert.deepEqual(pieceTable.getPositionAt(7), new Position(4, 2)); - assert.deepEqual(pieceTable.getPositionAt(8), new Position(4, 3)); - assert.deepEqual(pieceTable.getPositionAt(9), new Position(5, 1)); - assert.deepEqual(pieceTable.getPositionAt(11), new Position(6, 1)); - assert.deepEqual(pieceTable.getPositionAt(12), new Position(6, 2)); - assert.deepEqual(pieceTable.getPositionAt(13), new Position(6, 3)); + assert.strictEqual(pieceTable.getLinesRawContent(), 'a\nb\nc\ndh\ni\njk'); + assert.strictEqual(pieceTable.getLineCount(), 6); + assert.deepStrictEqual(pieceTable.getPositionAt(6), new Position(4, 1)); + assert.deepStrictEqual(pieceTable.getPositionAt(7), new Position(4, 2)); + assert.deepStrictEqual(pieceTable.getPositionAt(8), new Position(4, 3)); + assert.deepStrictEqual(pieceTable.getPositionAt(9), new Position(5, 1)); + assert.deepStrictEqual(pieceTable.getPositionAt(11), new Position(6, 1)); + assert.deepStrictEqual(pieceTable.getPositionAt(12), new Position(6, 2)); + assert.deepStrictEqual(pieceTable.getPositionAt(13), new Position(6, 3)); - assert.equal(pieceTable.getOffsetAt(4, 1), 6); - assert.equal(pieceTable.getOffsetAt(4, 2), 7); - assert.equal(pieceTable.getOffsetAt(4, 3), 8); - assert.equal(pieceTable.getOffsetAt(5, 1), 9); - assert.equal(pieceTable.getOffsetAt(6, 1), 11); - assert.equal(pieceTable.getOffsetAt(6, 2), 12); - assert.equal(pieceTable.getOffsetAt(6, 3), 13); + assert.strictEqual(pieceTable.getOffsetAt(4, 1), 6); + assert.strictEqual(pieceTable.getOffsetAt(4, 2), 7); + assert.strictEqual(pieceTable.getOffsetAt(4, 3), 8); + assert.strictEqual(pieceTable.getOffsetAt(5, 1), 9); + assert.strictEqual(pieceTable.getOffsetAt(6, 1), 11); + assert.strictEqual(pieceTable.getOffsetAt(6, 2), 12); + assert.strictEqual(pieceTable.getOffsetAt(6, 3), 13); assertTreeInvariants(pieceTable); }); @@ -629,23 +629,23 @@ suite('prefix sum for line feed', () => { pieceTable.insert(8, 'fh\ni\njk'); pieceTable.delete(7, 2); - assert.equal(pieceTable.getLinesRawContent(), 'a\nb\nc\ndh\ni\njk'); - assert.equal(pieceTable.getLineCount(), 6); - assert.deepEqual(pieceTable.getPositionAt(6), new Position(4, 1)); - assert.deepEqual(pieceTable.getPositionAt(7), new Position(4, 2)); - assert.deepEqual(pieceTable.getPositionAt(8), new Position(4, 3)); - assert.deepEqual(pieceTable.getPositionAt(9), new Position(5, 1)); - assert.deepEqual(pieceTable.getPositionAt(11), new Position(6, 1)); - assert.deepEqual(pieceTable.getPositionAt(12), new Position(6, 2)); - assert.deepEqual(pieceTable.getPositionAt(13), new Position(6, 3)); + assert.strictEqual(pieceTable.getLinesRawContent(), 'a\nb\nc\ndh\ni\njk'); + assert.strictEqual(pieceTable.getLineCount(), 6); + assert.deepStrictEqual(pieceTable.getPositionAt(6), new Position(4, 1)); + assert.deepStrictEqual(pieceTable.getPositionAt(7), new Position(4, 2)); + assert.deepStrictEqual(pieceTable.getPositionAt(8), new Position(4, 3)); + assert.deepStrictEqual(pieceTable.getPositionAt(9), new Position(5, 1)); + assert.deepStrictEqual(pieceTable.getPositionAt(11), new Position(6, 1)); + assert.deepStrictEqual(pieceTable.getPositionAt(12), new Position(6, 2)); + assert.deepStrictEqual(pieceTable.getPositionAt(13), new Position(6, 3)); - assert.equal(pieceTable.getOffsetAt(4, 1), 6); - assert.equal(pieceTable.getOffsetAt(4, 2), 7); - assert.equal(pieceTable.getOffsetAt(4, 3), 8); - assert.equal(pieceTable.getOffsetAt(5, 1), 9); - assert.equal(pieceTable.getOffsetAt(6, 1), 11); - assert.equal(pieceTable.getOffsetAt(6, 2), 12); - assert.equal(pieceTable.getOffsetAt(6, 3), 13); + assert.strictEqual(pieceTable.getOffsetAt(4, 1), 6); + assert.strictEqual(pieceTable.getOffsetAt(4, 2), 7); + assert.strictEqual(pieceTable.getOffsetAt(4, 3), 8); + assert.strictEqual(pieceTable.getOffsetAt(5, 1), 9); + assert.strictEqual(pieceTable.getOffsetAt(6, 1), 11); + assert.strictEqual(pieceTable.getOffsetAt(6, 2), 12); + assert.strictEqual(pieceTable.getOffsetAt(6, 3), 13); assertTreeInvariants(pieceTable); }); @@ -661,7 +661,7 @@ suite('prefix sum for line feed', () => { str = str.substring(0, 14) + 'X ZZ\nYZZYZXXY Y XY\n ' + str.substring(14); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); testLineStarts(str, pieceTable); assertTreeInvariants(pieceTable); }); @@ -675,7 +675,7 @@ suite('prefix sum for line feed', () => { pieceTable.insert(3, 'XXY \n\nY Y YYY ZYXY '); str = str.substring(0, 3) + 'XXY \n\nY Y YYY ZYXY ' + str.substring(3); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); testLineStarts(str, pieceTable); assertTreeInvariants(pieceTable); }); @@ -803,12 +803,12 @@ suite('get text in range', () => { pieceTable.delete(7, 2); // 'a\nb\nc\ndh\ni\njk' - assert.equal(pieceTable.getValueInRange(new Range(1, 1, 1, 3)), 'a\n'); - assert.equal(pieceTable.getValueInRange(new Range(2, 1, 2, 3)), 'b\n'); - assert.equal(pieceTable.getValueInRange(new Range(3, 1, 3, 3)), 'c\n'); - assert.equal(pieceTable.getValueInRange(new Range(4, 1, 4, 4)), 'dh\n'); - assert.equal(pieceTable.getValueInRange(new Range(5, 1, 5, 3)), 'i\n'); - assert.equal(pieceTable.getValueInRange(new Range(6, 1, 6, 3)), 'jk'); + assert.strictEqual(pieceTable.getValueInRange(new Range(1, 1, 1, 3)), 'a\n'); + assert.strictEqual(pieceTable.getValueInRange(new Range(2, 1, 2, 3)), 'b\n'); + assert.strictEqual(pieceTable.getValueInRange(new Range(3, 1, 3, 3)), 'c\n'); + assert.strictEqual(pieceTable.getValueInRange(new Range(4, 1, 4, 4)), 'dh\n'); + assert.strictEqual(pieceTable.getValueInRange(new Range(5, 1, 5, 3)), 'i\n'); + assert.strictEqual(pieceTable.getValueInRange(new Range(6, 1, 6, 3)), 'jk'); assertTreeInvariants(pieceTable); }); @@ -902,18 +902,18 @@ suite('get text in range', () => { test('get line content', () => { let pieceTable = createTextBuffer(['1']); - assert.equal(pieceTable.getLineRawContent(1), '1'); + assert.strictEqual(pieceTable.getLineRawContent(1), '1'); pieceTable.insert(1, '2'); - assert.equal(pieceTable.getLineRawContent(1), '12'); + assert.strictEqual(pieceTable.getLineRawContent(1), '12'); assertTreeInvariants(pieceTable); }); test('get line content basic', () => { let pieceTable = createTextBuffer(['1\n2\n3\n4']); - assert.equal(pieceTable.getLineRawContent(1), '1\n'); - assert.equal(pieceTable.getLineRawContent(2), '2\n'); - assert.equal(pieceTable.getLineRawContent(3), '3\n'); - assert.equal(pieceTable.getLineRawContent(4), '4'); + assert.strictEqual(pieceTable.getLineRawContent(1), '1\n'); + assert.strictEqual(pieceTable.getLineRawContent(2), '2\n'); + assert.strictEqual(pieceTable.getLineRawContent(3), '3\n'); + assert.strictEqual(pieceTable.getLineRawContent(4), '4'); assertTreeInvariants(pieceTable); }); @@ -923,12 +923,12 @@ suite('get text in range', () => { pieceTable.delete(7, 2); // 'a\nb\nc\ndh\ni\njk' - assert.equal(pieceTable.getLineRawContent(1), 'a\n'); - assert.equal(pieceTable.getLineRawContent(2), 'b\n'); - assert.equal(pieceTable.getLineRawContent(3), 'c\n'); - assert.equal(pieceTable.getLineRawContent(4), 'dh\n'); - assert.equal(pieceTable.getLineRawContent(5), 'i\n'); - assert.equal(pieceTable.getLineRawContent(6), 'jk'); + assert.strictEqual(pieceTable.getLineRawContent(1), 'a\n'); + assert.strictEqual(pieceTable.getLineRawContent(2), 'b\n'); + assert.strictEqual(pieceTable.getLineRawContent(3), 'c\n'); + assert.strictEqual(pieceTable.getLineRawContent(4), 'dh\n'); + assert.strictEqual(pieceTable.getLineRawContent(5), 'i\n'); + assert.strictEqual(pieceTable.getLineRawContent(6), 'jk'); assertTreeInvariants(pieceTable); }); @@ -973,7 +973,7 @@ suite('CRLF', () => { pieceTable.insert(0, 'a\r\nb'); pieceTable.delete(0, 2); - assert.equal(pieceTable.getLineCount(), 2); + assert.strictEqual(pieceTable.getLineCount(), 2); assertTreeInvariants(pieceTable); }); @@ -982,7 +982,7 @@ suite('CRLF', () => { pieceTable.insert(0, 'a\r\nb'); pieceTable.delete(2, 2); - assert.equal(pieceTable.getLineCount(), 2); + assert.strictEqual(pieceTable.getLineCount(), 2); assertTreeInvariants(pieceTable); }); @@ -999,7 +999,7 @@ suite('CRLF', () => { str = str.substring(0, 2) + str.substring(2 + 3); let lines = splitLines(str); - assert.equal(pieceTable.getLineCount(), lines.length); + assert.strictEqual(pieceTable.getLineCount(), lines.length); assertTreeInvariants(pieceTable); }); test('random bug 2', () => { @@ -1014,7 +1014,7 @@ suite('CRLF', () => { str = str.substring(0, 4) + str.substring(4 + 1); let lines = splitLines(str); - assert.equal(pieceTable.getLineCount(), lines.length); + assert.strictEqual(pieceTable.getLineCount(), lines.length); assertTreeInvariants(pieceTable); }); test('random bug 3', () => { @@ -1035,7 +1035,7 @@ suite('CRLF', () => { str = str.substring(0, 3) + '\r\r\r\n' + str.substring(3); let lines = splitLines(str); - assert.equal(pieceTable.getLineCount(), lines.length); + assert.strictEqual(pieceTable.getLineCount(), lines.length); assertTreeInvariants(pieceTable); }); test('random bug 4', () => { @@ -1185,14 +1185,14 @@ suite('centralized lineStarts with CRLF', () => { test('delete CR in CRLF 1', () => { let pieceTable = createTextBuffer(['a\r\nb'], false); pieceTable.delete(2, 2); - assert.equal(pieceTable.getLineCount(), 2); + assert.strictEqual(pieceTable.getLineCount(), 2); assertTreeInvariants(pieceTable); }); test('delete CR in CRLF 2', () => { let pieceTable = createTextBuffer(['a\r\nb']); pieceTable.delete(0, 2); - assert.equal(pieceTable.getLineCount(), 2); + assert.strictEqual(pieceTable.getLineCount(), 2); assertTreeInvariants(pieceTable); }); @@ -1207,7 +1207,7 @@ suite('centralized lineStarts with CRLF', () => { str = str.substring(0, 2) + str.substring(2 + 3); let lines = splitLines(str); - assert.equal(pieceTable.getLineCount(), lines.length); + assert.strictEqual(pieceTable.getLineCount(), lines.length); assertTreeInvariants(pieceTable); }); test('random bug 2', () => { @@ -1220,7 +1220,7 @@ suite('centralized lineStarts with CRLF', () => { str = str.substring(0, 4) + str.substring(4 + 1); let lines = splitLines(str); - assert.equal(pieceTable.getLineCount(), lines.length); + assert.strictEqual(pieceTable.getLineCount(), lines.length); assertTreeInvariants(pieceTable); }); @@ -1240,7 +1240,7 @@ suite('centralized lineStarts with CRLF', () => { str = str.substring(0, 3) + '\r\r\r\n' + str.substring(3); let lines = splitLines(str); - assert.equal(pieceTable.getLineCount(), lines.length); + assert.strictEqual(pieceTable.getLineCount(), lines.length); assertTreeInvariants(pieceTable); }); @@ -1386,7 +1386,7 @@ suite('centralized lineStarts with CRLF', () => { pieceTable.insert(7, '\r\r\r\r'); str = str.substring(0, 7) + '\r\r\r\r' + str.substring(7); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); testLineStarts(str, pieceTable); assertTreeInvariants(pieceTable); }); @@ -1407,7 +1407,7 @@ suite('centralized lineStarts with CRLF', () => { pieceTable.delete(11, 2); str = str.substring(0, 11) + str.substring(11 + 2); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); testLineStarts(str, pieceTable); assertTreeInvariants(pieceTable); }); @@ -1424,7 +1424,7 @@ suite('centralized lineStarts with CRLF', () => { pieceTable.delete(1, 2); str = str.substring(0, 1) + str.substring(1 + 2); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); testLineStarts(str, pieceTable); assertTreeInvariants(pieceTable); }); @@ -1437,7 +1437,7 @@ suite('centralized lineStarts with CRLF', () => { pieceTable.insert(3, '\r\n\n\n'); str = str.substring(0, 3) + '\r\n\n\n' + str.substring(3); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); testLineStarts(str, pieceTable); assertTreeInvariants(pieceTable); }); @@ -1469,7 +1469,7 @@ suite('random is unsupervised', () => { pieceTable.insert(0, 'VZXXZYZX\r'); str = str.substring(0, 0) + 'VZXXZYZX\r' + str.substring(0); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); testLineStarts(str, pieceTable); testLinesContent(str, pieceTable); @@ -1507,7 +1507,7 @@ suite('random is unsupervised', () => { } // console.log(output); - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); testLineStarts(str, pieceTable); testLinesContent(str, pieceTable); @@ -1543,7 +1543,7 @@ suite('random is unsupervised', () => { } } - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); testLineStarts(str, pieceTable); testLinesContent(str, pieceTable); assertTreeInvariants(pieceTable); @@ -1577,7 +1577,7 @@ suite('random is unsupervised', () => { testLinesContent(str, pieceTable); } - assert.equal(pieceTable.getLinesRawContent(), str); + assert.strictEqual(pieceTable.getLinesRawContent(), str); testLineStarts(str, pieceTable); testLinesContent(str, pieceTable); assertTreeInvariants(pieceTable); @@ -1612,33 +1612,33 @@ suite('buffer api', () => { test('getLineCharCode - issue #45735', () => { let pieceTable = createTextBuffer(['LINE1\nline2']); - assert.equal(pieceTable.getLineCharCode(1, 0), 'L'.charCodeAt(0), 'L'); - assert.equal(pieceTable.getLineCharCode(1, 1), 'I'.charCodeAt(0), 'I'); - assert.equal(pieceTable.getLineCharCode(1, 2), 'N'.charCodeAt(0), 'N'); - assert.equal(pieceTable.getLineCharCode(1, 3), 'E'.charCodeAt(0), 'E'); - assert.equal(pieceTable.getLineCharCode(1, 4), '1'.charCodeAt(0), '1'); - assert.equal(pieceTable.getLineCharCode(1, 5), '\n'.charCodeAt(0), '\\n'); - assert.equal(pieceTable.getLineCharCode(2, 0), 'l'.charCodeAt(0), 'l'); - assert.equal(pieceTable.getLineCharCode(2, 1), 'i'.charCodeAt(0), 'i'); - assert.equal(pieceTable.getLineCharCode(2, 2), 'n'.charCodeAt(0), 'n'); - assert.equal(pieceTable.getLineCharCode(2, 3), 'e'.charCodeAt(0), 'e'); - assert.equal(pieceTable.getLineCharCode(2, 4), '2'.charCodeAt(0), '2'); + assert.strictEqual(pieceTable.getLineCharCode(1, 0), 'L'.charCodeAt(0), 'L'); + assert.strictEqual(pieceTable.getLineCharCode(1, 1), 'I'.charCodeAt(0), 'I'); + assert.strictEqual(pieceTable.getLineCharCode(1, 2), 'N'.charCodeAt(0), 'N'); + assert.strictEqual(pieceTable.getLineCharCode(1, 3), 'E'.charCodeAt(0), 'E'); + assert.strictEqual(pieceTable.getLineCharCode(1, 4), '1'.charCodeAt(0), '1'); + assert.strictEqual(pieceTable.getLineCharCode(1, 5), '\n'.charCodeAt(0), '\\n'); + assert.strictEqual(pieceTable.getLineCharCode(2, 0), 'l'.charCodeAt(0), 'l'); + assert.strictEqual(pieceTable.getLineCharCode(2, 1), 'i'.charCodeAt(0), 'i'); + assert.strictEqual(pieceTable.getLineCharCode(2, 2), 'n'.charCodeAt(0), 'n'); + assert.strictEqual(pieceTable.getLineCharCode(2, 3), 'e'.charCodeAt(0), 'e'); + assert.strictEqual(pieceTable.getLineCharCode(2, 4), '2'.charCodeAt(0), '2'); }); test('getLineCharCode - issue #47733', () => { let pieceTable = createTextBuffer(['', 'LINE1\n', 'line2']); - assert.equal(pieceTable.getLineCharCode(1, 0), 'L'.charCodeAt(0), 'L'); - assert.equal(pieceTable.getLineCharCode(1, 1), 'I'.charCodeAt(0), 'I'); - assert.equal(pieceTable.getLineCharCode(1, 2), 'N'.charCodeAt(0), 'N'); - assert.equal(pieceTable.getLineCharCode(1, 3), 'E'.charCodeAt(0), 'E'); - assert.equal(pieceTable.getLineCharCode(1, 4), '1'.charCodeAt(0), '1'); - assert.equal(pieceTable.getLineCharCode(1, 5), '\n'.charCodeAt(0), '\\n'); - assert.equal(pieceTable.getLineCharCode(2, 0), 'l'.charCodeAt(0), 'l'); - assert.equal(pieceTable.getLineCharCode(2, 1), 'i'.charCodeAt(0), 'i'); - assert.equal(pieceTable.getLineCharCode(2, 2), 'n'.charCodeAt(0), 'n'); - assert.equal(pieceTable.getLineCharCode(2, 3), 'e'.charCodeAt(0), 'e'); - assert.equal(pieceTable.getLineCharCode(2, 4), '2'.charCodeAt(0), '2'); + assert.strictEqual(pieceTable.getLineCharCode(1, 0), 'L'.charCodeAt(0), 'L'); + assert.strictEqual(pieceTable.getLineCharCode(1, 1), 'I'.charCodeAt(0), 'I'); + assert.strictEqual(pieceTable.getLineCharCode(1, 2), 'N'.charCodeAt(0), 'N'); + assert.strictEqual(pieceTable.getLineCharCode(1, 3), 'E'.charCodeAt(0), 'E'); + assert.strictEqual(pieceTable.getLineCharCode(1, 4), '1'.charCodeAt(0), '1'); + assert.strictEqual(pieceTable.getLineCharCode(1, 5), '\n'.charCodeAt(0), '\\n'); + assert.strictEqual(pieceTable.getLineCharCode(2, 0), 'l'.charCodeAt(0), 'l'); + assert.strictEqual(pieceTable.getLineCharCode(2, 1), 'i'.charCodeAt(0), 'i'); + assert.strictEqual(pieceTable.getLineCharCode(2, 2), 'n'.charCodeAt(0), 'n'); + assert.strictEqual(pieceTable.getLineCharCode(2, 3), 'e'.charCodeAt(0), 'e'); + assert.strictEqual(pieceTable.getLineCharCode(2, 4), '2'.charCodeAt(0), '2'); }); }); @@ -1771,7 +1771,7 @@ suite('snapshot', () => { ]); const snapshot = model.createSnapshot(); const snapshot1 = model.createSnapshot(); - assert.equal(model.getLinesContent().join('\n'), getValueInSnapshot(snapshot)); + assert.strictEqual(model.getLinesContent().join('\n'), getValueInSnapshot(snapshot)); model.applyEdits([ { @@ -1786,7 +1786,7 @@ suite('snapshot', () => { } ]); - assert.equal(model.getLinesContent().join('\n'), getValueInSnapshot(snapshot1)); + assert.strictEqual(model.getLinesContent().join('\n'), getValueInSnapshot(snapshot1)); }); test('immutable snapshot 1', () => { @@ -1806,7 +1806,7 @@ suite('snapshot', () => { } ]); - assert.equal(model.getLinesContent().join('\n'), getValueInSnapshot(snapshot)); + assert.strictEqual(model.getLinesContent().join('\n'), getValueInSnapshot(snapshot)); }); test('immutable snapshot 2', () => { @@ -1826,7 +1826,7 @@ suite('snapshot', () => { } ]); - assert.equal(model.getLinesContent().join('\n'), getValueInSnapshot(snapshot)); + assert.strictEqual(model.getLinesContent().join('\n'), getValueInSnapshot(snapshot)); }); test('immutable snapshot 3', () => { @@ -1845,7 +1845,7 @@ suite('snapshot', () => { } ]); - assert.notEqual(model.getLinesContent().join('\n'), getValueInSnapshot(snapshot)); + assert.notStrictEqual(model.getLinesContent().join('\n'), getValueInSnapshot(snapshot)); }); }); @@ -1854,7 +1854,7 @@ suite('chunk based search', () => { let pieceTree = createTextBuffer(['']); pieceTree.delete(0, 1); let ret = pieceTree.findMatchesLineByLine(new Range(1, 1, 1, 1), new SearchData(/abc/, new WordCharacterClassifier(',./'), 'abc'), true, 1000); - assert.equal(ret.length, 0); + assert.strictEqual(ret.length, 0); }); test('#45770. FindInNode should not cross node boundary.', () => { @@ -1873,11 +1873,11 @@ suite('chunk based search', () => { pieceTree.insert(16, ' '); let ret = pieceTree.findMatchesLineByLine(new Range(1, 1, 4, 13), new SearchData(/\[/gi, new WordCharacterClassifier(',./'), '['), true, 1000); - assert.equal(ret.length, 3); + assert.strictEqual(ret.length, 3); - assert.deepEqual(ret[0].range, new Range(2, 3, 2, 4)); - assert.deepEqual(ret[1].range, new Range(3, 3, 3, 4)); - assert.deepEqual(ret[2].range, new Range(4, 3, 4, 4)); + assert.deepStrictEqual(ret[0].range, new Range(2, 3, 2, 4)); + assert.deepStrictEqual(ret[1].range, new Range(3, 3, 3, 4)); + assert.deepStrictEqual(ret[2].range, new Range(4, 3, 4, 4)); }); test('search searching from the middle', () => { @@ -1889,12 +1889,12 @@ suite('chunk based search', () => { ]); pieceTree.delete(4, 1); let ret = pieceTree.findMatchesLineByLine(new Range(2, 3, 2, 6), new SearchData(/a/gi, null, 'a'), true, 1000); - assert.equal(ret.length, 1); - assert.deepEqual(ret[0].range, new Range(2, 3, 2, 4)); + assert.strictEqual(ret.length, 1); + assert.deepStrictEqual(ret[0].range, new Range(2, 3, 2, 4)); pieceTree.delete(4, 1); ret = pieceTree.findMatchesLineByLine(new Range(2, 2, 2, 5), new SearchData(/a/gi, null, 'a'), true, 1000); - assert.equal(ret.length, 1); - assert.deepEqual(ret[0].range, new Range(2, 2, 2, 3)); + assert.strictEqual(ret.length, 1); + assert.deepStrictEqual(ret[0].range, new Range(2, 2, 2, 3)); }); }); diff --git a/src/vs/editor/test/common/model/textChange.test.ts b/src/vs/editor/test/common/model/textChange.test.ts index d6d42dfc6..bbc29ea57 100644 --- a/src/vs/editor/test/common/model/textChange.test.ts +++ b/src/vs/editor/test/common/model/textChange.test.ts @@ -75,7 +75,7 @@ suite('TextChangeCompressor', () => { }; }); let actualDoResult = getResultingContent(initialText, compressedDoTextEdits); - assert.equal(actualDoResult, finalText); + assert.strictEqual(actualDoResult, finalText); let compressedUndoTextEdits: IGeneratedEdit[] = compressedTextChanges.map((change) => { return { @@ -85,7 +85,7 @@ suite('TextChangeCompressor', () => { }; }); let actualUndoResult = getResultingContent(finalText, compressedUndoTextEdits); - assert.equal(actualUndoResult, initialText); + assert.strictEqual(actualUndoResult, initialText); } test('simple 1', () => { diff --git a/src/vs/editor/test/common/model/textModel.test.ts b/src/vs/editor/test/common/model/textModel.test.ts index 2da334170..2d8e6bbde 100644 --- a/src/vs/editor/test/common/model/textModel.test.ts +++ b/src/vs/editor/test/common/model/textModel.test.ts @@ -22,8 +22,8 @@ function testGuessIndentation(defaultInsertSpaces: boolean, defaultTabSize: numb let r = m.getOptions(); m.dispose(); - assert.equal(r.insertSpaces, expectedInsertSpaces, msg); - assert.equal(r.tabSize, expectedTabSize, msg); + assert.strictEqual(r.insertSpaces, expectedInsertSpaces, msg); + assert.strictEqual(r.tabSize, expectedTabSize, msg); } function assertGuess(expectedInsertSpaces: boolean | undefined, expectedTabSize: number | undefined | [number], text: string[], msg?: string): void { @@ -75,14 +75,14 @@ suite('TextModelData.fromString', () => { } function testTextModelDataFromString(text: string, expected: ITextBufferData): void { - const textBuffer = createTextBuffer(text, TextModel.DEFAULT_CREATION_OPTIONS.defaultEOL); + const textBuffer = createTextBuffer(text, TextModel.DEFAULT_CREATION_OPTIONS.defaultEOL).textBuffer; let actual: ITextBufferData = { EOL: textBuffer.getEOL(), lines: textBuffer.getLinesContent(), containsRTL: textBuffer.mightContainRTL(), isBasicASCII: !textBuffer.mightContainNonBasicASCII() }; - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); } test('one line text', () => { @@ -164,30 +164,30 @@ suite('Editor Model - TextModel', () => { test('getValueLengthInRange', () => { let m = createTextModel('My First Line\r\nMy Second Line\r\nMy Third Line'); - assert.equal(m.getValueLengthInRange(new Range(1, 1, 1, 1)), ''.length); - assert.equal(m.getValueLengthInRange(new Range(1, 1, 1, 2)), 'M'.length); - assert.equal(m.getValueLengthInRange(new Range(1, 2, 1, 3)), 'y'.length); - assert.equal(m.getValueLengthInRange(new Range(1, 1, 1, 14)), 'My First Line'.length); - assert.equal(m.getValueLengthInRange(new Range(1, 1, 2, 1)), 'My First Line\r\n'.length); - assert.equal(m.getValueLengthInRange(new Range(1, 2, 2, 1)), 'y First Line\r\n'.length); - assert.equal(m.getValueLengthInRange(new Range(1, 2, 2, 2)), 'y First Line\r\nM'.length); - assert.equal(m.getValueLengthInRange(new Range(1, 2, 2, 1000)), 'y First Line\r\nMy Second Line'.length); - assert.equal(m.getValueLengthInRange(new Range(1, 2, 3, 1)), 'y First Line\r\nMy Second Line\r\n'.length); - assert.equal(m.getValueLengthInRange(new Range(1, 2, 3, 1000)), 'y First Line\r\nMy Second Line\r\nMy Third Line'.length); - assert.equal(m.getValueLengthInRange(new Range(1, 1, 1000, 1000)), 'My First Line\r\nMy Second Line\r\nMy Third Line'.length); + assert.strictEqual(m.getValueLengthInRange(new Range(1, 1, 1, 1)), ''.length); + assert.strictEqual(m.getValueLengthInRange(new Range(1, 1, 1, 2)), 'M'.length); + assert.strictEqual(m.getValueLengthInRange(new Range(1, 2, 1, 3)), 'y'.length); + assert.strictEqual(m.getValueLengthInRange(new Range(1, 1, 1, 14)), 'My First Line'.length); + assert.strictEqual(m.getValueLengthInRange(new Range(1, 1, 2, 1)), 'My First Line\r\n'.length); + assert.strictEqual(m.getValueLengthInRange(new Range(1, 2, 2, 1)), 'y First Line\r\n'.length); + assert.strictEqual(m.getValueLengthInRange(new Range(1, 2, 2, 2)), 'y First Line\r\nM'.length); + assert.strictEqual(m.getValueLengthInRange(new Range(1, 2, 2, 1000)), 'y First Line\r\nMy Second Line'.length); + assert.strictEqual(m.getValueLengthInRange(new Range(1, 2, 3, 1)), 'y First Line\r\nMy Second Line\r\n'.length); + assert.strictEqual(m.getValueLengthInRange(new Range(1, 2, 3, 1000)), 'y First Line\r\nMy Second Line\r\nMy Third Line'.length); + assert.strictEqual(m.getValueLengthInRange(new Range(1, 1, 1000, 1000)), 'My First Line\r\nMy Second Line\r\nMy Third Line'.length); m = createTextModel('My First Line\nMy Second Line\nMy Third Line'); - assert.equal(m.getValueLengthInRange(new Range(1, 1, 1, 1)), ''.length); - assert.equal(m.getValueLengthInRange(new Range(1, 1, 1, 2)), 'M'.length); - assert.equal(m.getValueLengthInRange(new Range(1, 2, 1, 3)), 'y'.length); - assert.equal(m.getValueLengthInRange(new Range(1, 1, 1, 14)), 'My First Line'.length); - assert.equal(m.getValueLengthInRange(new Range(1, 1, 2, 1)), 'My First Line\n'.length); - assert.equal(m.getValueLengthInRange(new Range(1, 2, 2, 1)), 'y First Line\n'.length); - assert.equal(m.getValueLengthInRange(new Range(1, 2, 2, 2)), 'y First Line\nM'.length); - assert.equal(m.getValueLengthInRange(new Range(1, 2, 2, 1000)), 'y First Line\nMy Second Line'.length); - assert.equal(m.getValueLengthInRange(new Range(1, 2, 3, 1)), 'y First Line\nMy Second Line\n'.length); - assert.equal(m.getValueLengthInRange(new Range(1, 2, 3, 1000)), 'y First Line\nMy Second Line\nMy Third Line'.length); - assert.equal(m.getValueLengthInRange(new Range(1, 1, 1000, 1000)), 'My First Line\nMy Second Line\nMy Third Line'.length); + assert.strictEqual(m.getValueLengthInRange(new Range(1, 1, 1, 1)), ''.length); + assert.strictEqual(m.getValueLengthInRange(new Range(1, 1, 1, 2)), 'M'.length); + assert.strictEqual(m.getValueLengthInRange(new Range(1, 2, 1, 3)), 'y'.length); + assert.strictEqual(m.getValueLengthInRange(new Range(1, 1, 1, 14)), 'My First Line'.length); + assert.strictEqual(m.getValueLengthInRange(new Range(1, 1, 2, 1)), 'My First Line\n'.length); + assert.strictEqual(m.getValueLengthInRange(new Range(1, 2, 2, 1)), 'y First Line\n'.length); + assert.strictEqual(m.getValueLengthInRange(new Range(1, 2, 2, 2)), 'y First Line\nM'.length); + assert.strictEqual(m.getValueLengthInRange(new Range(1, 2, 2, 1000)), 'y First Line\nMy Second Line'.length); + assert.strictEqual(m.getValueLengthInRange(new Range(1, 2, 3, 1)), 'y First Line\nMy Second Line\n'.length); + assert.strictEqual(m.getValueLengthInRange(new Range(1, 2, 3, 1000)), 'y First Line\nMy Second Line\nMy Third Line'.length); + assert.strictEqual(m.getValueLengthInRange(new Range(1, 1, 1000, 1000)), 'My First Line\nMy Second Line\nMy Third Line'.length); }); test('guess indentation 1', () => { @@ -664,69 +664,69 @@ suite('Editor Model - TextModel', () => { let m = createTextModel('line one\nline two'); - assert.deepEqual(m.validatePosition(new Position(0, 0)), new Position(1, 1)); - assert.deepEqual(m.validatePosition(new Position(0, 1)), new Position(1, 1)); + assert.deepStrictEqual(m.validatePosition(new Position(0, 0)), new Position(1, 1)); + assert.deepStrictEqual(m.validatePosition(new Position(0, 1)), new Position(1, 1)); - assert.deepEqual(m.validatePosition(new Position(1, 1)), new Position(1, 1)); - assert.deepEqual(m.validatePosition(new Position(1, 2)), new Position(1, 2)); - assert.deepEqual(m.validatePosition(new Position(1, 30)), new Position(1, 9)); + assert.deepStrictEqual(m.validatePosition(new Position(1, 1)), new Position(1, 1)); + assert.deepStrictEqual(m.validatePosition(new Position(1, 2)), new Position(1, 2)); + assert.deepStrictEqual(m.validatePosition(new Position(1, 30)), new Position(1, 9)); - assert.deepEqual(m.validatePosition(new Position(2, 0)), new Position(2, 1)); - assert.deepEqual(m.validatePosition(new Position(2, 1)), new Position(2, 1)); - assert.deepEqual(m.validatePosition(new Position(2, 2)), new Position(2, 2)); - assert.deepEqual(m.validatePosition(new Position(2, 30)), new Position(2, 9)); + assert.deepStrictEqual(m.validatePosition(new Position(2, 0)), new Position(2, 1)); + assert.deepStrictEqual(m.validatePosition(new Position(2, 1)), new Position(2, 1)); + assert.deepStrictEqual(m.validatePosition(new Position(2, 2)), new Position(2, 2)); + assert.deepStrictEqual(m.validatePosition(new Position(2, 30)), new Position(2, 9)); - assert.deepEqual(m.validatePosition(new Position(3, 0)), new Position(2, 9)); - assert.deepEqual(m.validatePosition(new Position(3, 1)), new Position(2, 9)); - assert.deepEqual(m.validatePosition(new Position(3, 30)), new Position(2, 9)); + assert.deepStrictEqual(m.validatePosition(new Position(3, 0)), new Position(2, 9)); + assert.deepStrictEqual(m.validatePosition(new Position(3, 1)), new Position(2, 9)); + assert.deepStrictEqual(m.validatePosition(new Position(3, 30)), new Position(2, 9)); - assert.deepEqual(m.validatePosition(new Position(30, 30)), new Position(2, 9)); + assert.deepStrictEqual(m.validatePosition(new Position(30, 30)), new Position(2, 9)); - assert.deepEqual(m.validatePosition(new Position(-123.123, -0.5)), new Position(1, 1)); - assert.deepEqual(m.validatePosition(new Position(Number.MIN_VALUE, Number.MIN_VALUE)), new Position(1, 1)); + assert.deepStrictEqual(m.validatePosition(new Position(-123.123, -0.5)), new Position(1, 1)); + assert.deepStrictEqual(m.validatePosition(new Position(Number.MIN_VALUE, Number.MIN_VALUE)), new Position(1, 1)); - assert.deepEqual(m.validatePosition(new Position(Number.MAX_VALUE, Number.MAX_VALUE)), new Position(2, 9)); - assert.deepEqual(m.validatePosition(new Position(123.23, 47.5)), new Position(2, 9)); + assert.deepStrictEqual(m.validatePosition(new Position(Number.MAX_VALUE, Number.MAX_VALUE)), new Position(2, 9)); + assert.deepStrictEqual(m.validatePosition(new Position(123.23, 47.5)), new Position(2, 9)); }); test('validatePosition around high-low surrogate pairs 1', () => { let m = createTextModel('a📚b'); - assert.deepEqual(m.validatePosition(new Position(0, 0)), new Position(1, 1)); - assert.deepEqual(m.validatePosition(new Position(0, 1)), new Position(1, 1)); - assert.deepEqual(m.validatePosition(new Position(0, 7)), new Position(1, 1)); + assert.deepStrictEqual(m.validatePosition(new Position(0, 0)), new Position(1, 1)); + assert.deepStrictEqual(m.validatePosition(new Position(0, 1)), new Position(1, 1)); + assert.deepStrictEqual(m.validatePosition(new Position(0, 7)), new Position(1, 1)); - assert.deepEqual(m.validatePosition(new Position(1, 1)), new Position(1, 1)); - assert.deepEqual(m.validatePosition(new Position(1, 2)), new Position(1, 2)); - assert.deepEqual(m.validatePosition(new Position(1, 3)), new Position(1, 2)); - assert.deepEqual(m.validatePosition(new Position(1, 4)), new Position(1, 4)); - assert.deepEqual(m.validatePosition(new Position(1, 5)), new Position(1, 5)); - assert.deepEqual(m.validatePosition(new Position(1, 30)), new Position(1, 5)); + assert.deepStrictEqual(m.validatePosition(new Position(1, 1)), new Position(1, 1)); + assert.deepStrictEqual(m.validatePosition(new Position(1, 2)), new Position(1, 2)); + assert.deepStrictEqual(m.validatePosition(new Position(1, 3)), new Position(1, 2)); + assert.deepStrictEqual(m.validatePosition(new Position(1, 4)), new Position(1, 4)); + assert.deepStrictEqual(m.validatePosition(new Position(1, 5)), new Position(1, 5)); + assert.deepStrictEqual(m.validatePosition(new Position(1, 30)), new Position(1, 5)); - assert.deepEqual(m.validatePosition(new Position(2, 0)), new Position(1, 5)); - assert.deepEqual(m.validatePosition(new Position(2, 1)), new Position(1, 5)); - assert.deepEqual(m.validatePosition(new Position(2, 2)), new Position(1, 5)); - assert.deepEqual(m.validatePosition(new Position(2, 30)), new Position(1, 5)); + assert.deepStrictEqual(m.validatePosition(new Position(2, 0)), new Position(1, 5)); + assert.deepStrictEqual(m.validatePosition(new Position(2, 1)), new Position(1, 5)); + assert.deepStrictEqual(m.validatePosition(new Position(2, 2)), new Position(1, 5)); + assert.deepStrictEqual(m.validatePosition(new Position(2, 30)), new Position(1, 5)); - assert.deepEqual(m.validatePosition(new Position(-123.123, -0.5)), new Position(1, 1)); - assert.deepEqual(m.validatePosition(new Position(Number.MIN_VALUE, Number.MIN_VALUE)), new Position(1, 1)); + assert.deepStrictEqual(m.validatePosition(new Position(-123.123, -0.5)), new Position(1, 1)); + assert.deepStrictEqual(m.validatePosition(new Position(Number.MIN_VALUE, Number.MIN_VALUE)), new Position(1, 1)); - assert.deepEqual(m.validatePosition(new Position(Number.MAX_VALUE, Number.MAX_VALUE)), new Position(1, 5)); - assert.deepEqual(m.validatePosition(new Position(123.23, 47.5)), new Position(1, 5)); + assert.deepStrictEqual(m.validatePosition(new Position(Number.MAX_VALUE, Number.MAX_VALUE)), new Position(1, 5)); + assert.deepStrictEqual(m.validatePosition(new Position(123.23, 47.5)), new Position(1, 5)); }); test('validatePosition around high-low surrogate pairs 2', () => { let m = createTextModel('a📚📚b'); - assert.deepEqual(m.validatePosition(new Position(1, 1)), new Position(1, 1)); - assert.deepEqual(m.validatePosition(new Position(1, 2)), new Position(1, 2)); - assert.deepEqual(m.validatePosition(new Position(1, 3)), new Position(1, 2)); - assert.deepEqual(m.validatePosition(new Position(1, 4)), new Position(1, 4)); - assert.deepEqual(m.validatePosition(new Position(1, 5)), new Position(1, 4)); - assert.deepEqual(m.validatePosition(new Position(1, 6)), new Position(1, 6)); - assert.deepEqual(m.validatePosition(new Position(1, 7)), new Position(1, 7)); + assert.deepStrictEqual(m.validatePosition(new Position(1, 1)), new Position(1, 1)); + assert.deepStrictEqual(m.validatePosition(new Position(1, 2)), new Position(1, 2)); + assert.deepStrictEqual(m.validatePosition(new Position(1, 3)), new Position(1, 2)); + assert.deepStrictEqual(m.validatePosition(new Position(1, 4)), new Position(1, 4)); + assert.deepStrictEqual(m.validatePosition(new Position(1, 5)), new Position(1, 4)); + assert.deepStrictEqual(m.validatePosition(new Position(1, 6)), new Position(1, 6)); + assert.deepStrictEqual(m.validatePosition(new Position(1, 7)), new Position(1, 7)); }); @@ -734,133 +734,133 @@ suite('Editor Model - TextModel', () => { let m = createTextModel('line one\nline two'); - assert.deepEqual(m.validatePosition(new Position(NaN, 1)), new Position(1, 1)); - assert.deepEqual(m.validatePosition(new Position(1, NaN)), new Position(1, 1)); + assert.deepStrictEqual(m.validatePosition(new Position(NaN, 1)), new Position(1, 1)); + assert.deepStrictEqual(m.validatePosition(new Position(1, NaN)), new Position(1, 1)); - assert.deepEqual(m.validatePosition(new Position(NaN, NaN)), new Position(1, 1)); - assert.deepEqual(m.validatePosition(new Position(2, NaN)), new Position(2, 1)); - assert.deepEqual(m.validatePosition(new Position(NaN, 3)), new Position(1, 3)); + assert.deepStrictEqual(m.validatePosition(new Position(NaN, NaN)), new Position(1, 1)); + assert.deepStrictEqual(m.validatePosition(new Position(2, NaN)), new Position(2, 1)); + assert.deepStrictEqual(m.validatePosition(new Position(NaN, 3)), new Position(1, 3)); }); test('issue #71480: validatePosition handle floats', () => { let m = createTextModel('line one\nline two'); - assert.deepEqual(m.validatePosition(new Position(0.2, 1)), new Position(1, 1), 'a'); - assert.deepEqual(m.validatePosition(new Position(1.2, 1)), new Position(1, 1), 'b'); - assert.deepEqual(m.validatePosition(new Position(1.5, 2)), new Position(1, 2), 'c'); - assert.deepEqual(m.validatePosition(new Position(1.8, 3)), new Position(1, 3), 'd'); - assert.deepEqual(m.validatePosition(new Position(1, 0.3)), new Position(1, 1), 'e'); - assert.deepEqual(m.validatePosition(new Position(2, 0.8)), new Position(2, 1), 'f'); - assert.deepEqual(m.validatePosition(new Position(1, 1.2)), new Position(1, 1), 'g'); - assert.deepEqual(m.validatePosition(new Position(2, 1.5)), new Position(2, 1), 'h'); + assert.deepStrictEqual(m.validatePosition(new Position(0.2, 1)), new Position(1, 1), 'a'); + assert.deepStrictEqual(m.validatePosition(new Position(1.2, 1)), new Position(1, 1), 'b'); + assert.deepStrictEqual(m.validatePosition(new Position(1.5, 2)), new Position(1, 2), 'c'); + assert.deepStrictEqual(m.validatePosition(new Position(1.8, 3)), new Position(1, 3), 'd'); + assert.deepStrictEqual(m.validatePosition(new Position(1, 0.3)), new Position(1, 1), 'e'); + assert.deepStrictEqual(m.validatePosition(new Position(2, 0.8)), new Position(2, 1), 'f'); + assert.deepStrictEqual(m.validatePosition(new Position(1, 1.2)), new Position(1, 1), 'g'); + assert.deepStrictEqual(m.validatePosition(new Position(2, 1.5)), new Position(2, 1), 'h'); }); test('issue #71480: validateRange handle floats', () => { let m = createTextModel('line one\nline two'); - assert.deepEqual(m.validateRange(new Range(0.2, 1.5, 0.8, 2.5)), new Range(1, 1, 1, 1)); - assert.deepEqual(m.validateRange(new Range(1.2, 1.7, 1.8, 2.2)), new Range(1, 1, 1, 2)); + assert.deepStrictEqual(m.validateRange(new Range(0.2, 1.5, 0.8, 2.5)), new Range(1, 1, 1, 1)); + assert.deepStrictEqual(m.validateRange(new Range(1.2, 1.7, 1.8, 2.2)), new Range(1, 1, 1, 2)); }); test('validateRange around high-low surrogate pairs 1', () => { let m = createTextModel('a📚b'); - assert.deepEqual(m.validateRange(new Range(0, 0, 0, 1)), new Range(1, 1, 1, 1)); - assert.deepEqual(m.validateRange(new Range(0, 0, 0, 7)), new Range(1, 1, 1, 1)); + assert.deepStrictEqual(m.validateRange(new Range(0, 0, 0, 1)), new Range(1, 1, 1, 1)); + assert.deepStrictEqual(m.validateRange(new Range(0, 0, 0, 7)), new Range(1, 1, 1, 1)); - assert.deepEqual(m.validateRange(new Range(1, 1, 1, 1)), new Range(1, 1, 1, 1)); - assert.deepEqual(m.validateRange(new Range(1, 1, 1, 2)), new Range(1, 1, 1, 2)); - assert.deepEqual(m.validateRange(new Range(1, 1, 1, 3)), new Range(1, 1, 1, 4)); - assert.deepEqual(m.validateRange(new Range(1, 1, 1, 4)), new Range(1, 1, 1, 4)); - assert.deepEqual(m.validateRange(new Range(1, 1, 1, 5)), new Range(1, 1, 1, 5)); + assert.deepStrictEqual(m.validateRange(new Range(1, 1, 1, 1)), new Range(1, 1, 1, 1)); + assert.deepStrictEqual(m.validateRange(new Range(1, 1, 1, 2)), new Range(1, 1, 1, 2)); + assert.deepStrictEqual(m.validateRange(new Range(1, 1, 1, 3)), new Range(1, 1, 1, 4)); + assert.deepStrictEqual(m.validateRange(new Range(1, 1, 1, 4)), new Range(1, 1, 1, 4)); + assert.deepStrictEqual(m.validateRange(new Range(1, 1, 1, 5)), new Range(1, 1, 1, 5)); - assert.deepEqual(m.validateRange(new Range(1, 2, 1, 2)), new Range(1, 2, 1, 2)); - assert.deepEqual(m.validateRange(new Range(1, 2, 1, 3)), new Range(1, 2, 1, 4)); - assert.deepEqual(m.validateRange(new Range(1, 2, 1, 4)), new Range(1, 2, 1, 4)); - assert.deepEqual(m.validateRange(new Range(1, 2, 1, 5)), new Range(1, 2, 1, 5)); + assert.deepStrictEqual(m.validateRange(new Range(1, 2, 1, 2)), new Range(1, 2, 1, 2)); + assert.deepStrictEqual(m.validateRange(new Range(1, 2, 1, 3)), new Range(1, 2, 1, 4)); + assert.deepStrictEqual(m.validateRange(new Range(1, 2, 1, 4)), new Range(1, 2, 1, 4)); + assert.deepStrictEqual(m.validateRange(new Range(1, 2, 1, 5)), new Range(1, 2, 1, 5)); - assert.deepEqual(m.validateRange(new Range(1, 3, 1, 3)), new Range(1, 2, 1, 2)); - assert.deepEqual(m.validateRange(new Range(1, 3, 1, 4)), new Range(1, 2, 1, 4)); - assert.deepEqual(m.validateRange(new Range(1, 3, 1, 5)), new Range(1, 2, 1, 5)); + assert.deepStrictEqual(m.validateRange(new Range(1, 3, 1, 3)), new Range(1, 2, 1, 2)); + assert.deepStrictEqual(m.validateRange(new Range(1, 3, 1, 4)), new Range(1, 2, 1, 4)); + assert.deepStrictEqual(m.validateRange(new Range(1, 3, 1, 5)), new Range(1, 2, 1, 5)); - assert.deepEqual(m.validateRange(new Range(1, 4, 1, 4)), new Range(1, 4, 1, 4)); - assert.deepEqual(m.validateRange(new Range(1, 4, 1, 5)), new Range(1, 4, 1, 5)); + assert.deepStrictEqual(m.validateRange(new Range(1, 4, 1, 4)), new Range(1, 4, 1, 4)); + assert.deepStrictEqual(m.validateRange(new Range(1, 4, 1, 5)), new Range(1, 4, 1, 5)); - assert.deepEqual(m.validateRange(new Range(1, 5, 1, 5)), new Range(1, 5, 1, 5)); + assert.deepStrictEqual(m.validateRange(new Range(1, 5, 1, 5)), new Range(1, 5, 1, 5)); }); test('validateRange around high-low surrogate pairs 2', () => { let m = createTextModel('a📚📚b'); - assert.deepEqual(m.validateRange(new Range(0, 0, 0, 1)), new Range(1, 1, 1, 1)); - assert.deepEqual(m.validateRange(new Range(0, 0, 0, 7)), new Range(1, 1, 1, 1)); + assert.deepStrictEqual(m.validateRange(new Range(0, 0, 0, 1)), new Range(1, 1, 1, 1)); + assert.deepStrictEqual(m.validateRange(new Range(0, 0, 0, 7)), new Range(1, 1, 1, 1)); - assert.deepEqual(m.validateRange(new Range(1, 1, 1, 1)), new Range(1, 1, 1, 1)); - assert.deepEqual(m.validateRange(new Range(1, 1, 1, 2)), new Range(1, 1, 1, 2)); - assert.deepEqual(m.validateRange(new Range(1, 1, 1, 3)), new Range(1, 1, 1, 4)); - assert.deepEqual(m.validateRange(new Range(1, 1, 1, 4)), new Range(1, 1, 1, 4)); - assert.deepEqual(m.validateRange(new Range(1, 1, 1, 5)), new Range(1, 1, 1, 6)); - assert.deepEqual(m.validateRange(new Range(1, 1, 1, 6)), new Range(1, 1, 1, 6)); - assert.deepEqual(m.validateRange(new Range(1, 1, 1, 7)), new Range(1, 1, 1, 7)); + assert.deepStrictEqual(m.validateRange(new Range(1, 1, 1, 1)), new Range(1, 1, 1, 1)); + assert.deepStrictEqual(m.validateRange(new Range(1, 1, 1, 2)), new Range(1, 1, 1, 2)); + assert.deepStrictEqual(m.validateRange(new Range(1, 1, 1, 3)), new Range(1, 1, 1, 4)); + assert.deepStrictEqual(m.validateRange(new Range(1, 1, 1, 4)), new Range(1, 1, 1, 4)); + assert.deepStrictEqual(m.validateRange(new Range(1, 1, 1, 5)), new Range(1, 1, 1, 6)); + assert.deepStrictEqual(m.validateRange(new Range(1, 1, 1, 6)), new Range(1, 1, 1, 6)); + assert.deepStrictEqual(m.validateRange(new Range(1, 1, 1, 7)), new Range(1, 1, 1, 7)); - assert.deepEqual(m.validateRange(new Range(1, 2, 1, 2)), new Range(1, 2, 1, 2)); - assert.deepEqual(m.validateRange(new Range(1, 2, 1, 3)), new Range(1, 2, 1, 4)); - assert.deepEqual(m.validateRange(new Range(1, 2, 1, 4)), new Range(1, 2, 1, 4)); - assert.deepEqual(m.validateRange(new Range(1, 2, 1, 5)), new Range(1, 2, 1, 6)); - assert.deepEqual(m.validateRange(new Range(1, 2, 1, 6)), new Range(1, 2, 1, 6)); - assert.deepEqual(m.validateRange(new Range(1, 2, 1, 7)), new Range(1, 2, 1, 7)); + assert.deepStrictEqual(m.validateRange(new Range(1, 2, 1, 2)), new Range(1, 2, 1, 2)); + assert.deepStrictEqual(m.validateRange(new Range(1, 2, 1, 3)), new Range(1, 2, 1, 4)); + assert.deepStrictEqual(m.validateRange(new Range(1, 2, 1, 4)), new Range(1, 2, 1, 4)); + assert.deepStrictEqual(m.validateRange(new Range(1, 2, 1, 5)), new Range(1, 2, 1, 6)); + assert.deepStrictEqual(m.validateRange(new Range(1, 2, 1, 6)), new Range(1, 2, 1, 6)); + assert.deepStrictEqual(m.validateRange(new Range(1, 2, 1, 7)), new Range(1, 2, 1, 7)); - assert.deepEqual(m.validateRange(new Range(1, 3, 1, 3)), new Range(1, 2, 1, 2)); - assert.deepEqual(m.validateRange(new Range(1, 3, 1, 4)), new Range(1, 2, 1, 4)); - assert.deepEqual(m.validateRange(new Range(1, 3, 1, 5)), new Range(1, 2, 1, 6)); - assert.deepEqual(m.validateRange(new Range(1, 3, 1, 6)), new Range(1, 2, 1, 6)); - assert.deepEqual(m.validateRange(new Range(1, 3, 1, 7)), new Range(1, 2, 1, 7)); + assert.deepStrictEqual(m.validateRange(new Range(1, 3, 1, 3)), new Range(1, 2, 1, 2)); + assert.deepStrictEqual(m.validateRange(new Range(1, 3, 1, 4)), new Range(1, 2, 1, 4)); + assert.deepStrictEqual(m.validateRange(new Range(1, 3, 1, 5)), new Range(1, 2, 1, 6)); + assert.deepStrictEqual(m.validateRange(new Range(1, 3, 1, 6)), new Range(1, 2, 1, 6)); + assert.deepStrictEqual(m.validateRange(new Range(1, 3, 1, 7)), new Range(1, 2, 1, 7)); - assert.deepEqual(m.validateRange(new Range(1, 4, 1, 4)), new Range(1, 4, 1, 4)); - assert.deepEqual(m.validateRange(new Range(1, 4, 1, 5)), new Range(1, 4, 1, 6)); - assert.deepEqual(m.validateRange(new Range(1, 4, 1, 6)), new Range(1, 4, 1, 6)); - assert.deepEqual(m.validateRange(new Range(1, 4, 1, 7)), new Range(1, 4, 1, 7)); + assert.deepStrictEqual(m.validateRange(new Range(1, 4, 1, 4)), new Range(1, 4, 1, 4)); + assert.deepStrictEqual(m.validateRange(new Range(1, 4, 1, 5)), new Range(1, 4, 1, 6)); + assert.deepStrictEqual(m.validateRange(new Range(1, 4, 1, 6)), new Range(1, 4, 1, 6)); + assert.deepStrictEqual(m.validateRange(new Range(1, 4, 1, 7)), new Range(1, 4, 1, 7)); - assert.deepEqual(m.validateRange(new Range(1, 5, 1, 5)), new Range(1, 4, 1, 4)); - assert.deepEqual(m.validateRange(new Range(1, 5, 1, 6)), new Range(1, 4, 1, 6)); - assert.deepEqual(m.validateRange(new Range(1, 5, 1, 7)), new Range(1, 4, 1, 7)); + assert.deepStrictEqual(m.validateRange(new Range(1, 5, 1, 5)), new Range(1, 4, 1, 4)); + assert.deepStrictEqual(m.validateRange(new Range(1, 5, 1, 6)), new Range(1, 4, 1, 6)); + assert.deepStrictEqual(m.validateRange(new Range(1, 5, 1, 7)), new Range(1, 4, 1, 7)); - assert.deepEqual(m.validateRange(new Range(1, 6, 1, 6)), new Range(1, 6, 1, 6)); - assert.deepEqual(m.validateRange(new Range(1, 6, 1, 7)), new Range(1, 6, 1, 7)); + assert.deepStrictEqual(m.validateRange(new Range(1, 6, 1, 6)), new Range(1, 6, 1, 6)); + assert.deepStrictEqual(m.validateRange(new Range(1, 6, 1, 7)), new Range(1, 6, 1, 7)); - assert.deepEqual(m.validateRange(new Range(1, 7, 1, 7)), new Range(1, 7, 1, 7)); + assert.deepStrictEqual(m.validateRange(new Range(1, 7, 1, 7)), new Range(1, 7, 1, 7)); }); test('modifyPosition', () => { let m = createTextModel('line one\nline two'); - assert.deepEqual(m.modifyPosition(new Position(1, 1), 0), new Position(1, 1)); - assert.deepEqual(m.modifyPosition(new Position(0, 0), 0), new Position(1, 1)); - assert.deepEqual(m.modifyPosition(new Position(30, 1), 0), new Position(2, 9)); + assert.deepStrictEqual(m.modifyPosition(new Position(1, 1), 0), new Position(1, 1)); + assert.deepStrictEqual(m.modifyPosition(new Position(0, 0), 0), new Position(1, 1)); + assert.deepStrictEqual(m.modifyPosition(new Position(30, 1), 0), new Position(2, 9)); - assert.deepEqual(m.modifyPosition(new Position(1, 1), 17), new Position(2, 9)); - assert.deepEqual(m.modifyPosition(new Position(1, 1), 1), new Position(1, 2)); - assert.deepEqual(m.modifyPosition(new Position(1, 1), 3), new Position(1, 4)); - assert.deepEqual(m.modifyPosition(new Position(1, 2), 10), new Position(2, 3)); - assert.deepEqual(m.modifyPosition(new Position(1, 5), 13), new Position(2, 9)); - assert.deepEqual(m.modifyPosition(new Position(1, 2), 16), new Position(2, 9)); + assert.deepStrictEqual(m.modifyPosition(new Position(1, 1), 17), new Position(2, 9)); + assert.deepStrictEqual(m.modifyPosition(new Position(1, 1), 1), new Position(1, 2)); + assert.deepStrictEqual(m.modifyPosition(new Position(1, 1), 3), new Position(1, 4)); + assert.deepStrictEqual(m.modifyPosition(new Position(1, 2), 10), new Position(2, 3)); + assert.deepStrictEqual(m.modifyPosition(new Position(1, 5), 13), new Position(2, 9)); + assert.deepStrictEqual(m.modifyPosition(new Position(1, 2), 16), new Position(2, 9)); - assert.deepEqual(m.modifyPosition(new Position(2, 9), -17), new Position(1, 1)); - assert.deepEqual(m.modifyPosition(new Position(1, 2), -1), new Position(1, 1)); - assert.deepEqual(m.modifyPosition(new Position(1, 4), -3), new Position(1, 1)); - assert.deepEqual(m.modifyPosition(new Position(2, 3), -10), new Position(1, 2)); - assert.deepEqual(m.modifyPosition(new Position(2, 9), -13), new Position(1, 5)); - assert.deepEqual(m.modifyPosition(new Position(2, 9), -16), new Position(1, 2)); + assert.deepStrictEqual(m.modifyPosition(new Position(2, 9), -17), new Position(1, 1)); + assert.deepStrictEqual(m.modifyPosition(new Position(1, 2), -1), new Position(1, 1)); + assert.deepStrictEqual(m.modifyPosition(new Position(1, 4), -3), new Position(1, 1)); + assert.deepStrictEqual(m.modifyPosition(new Position(2, 3), -10), new Position(1, 2)); + assert.deepStrictEqual(m.modifyPosition(new Position(2, 9), -13), new Position(1, 5)); + assert.deepStrictEqual(m.modifyPosition(new Position(2, 9), -16), new Position(1, 2)); - assert.deepEqual(m.modifyPosition(new Position(1, 2), 17), new Position(2, 9)); - assert.deepEqual(m.modifyPosition(new Position(1, 2), 100), new Position(2, 9)); + assert.deepStrictEqual(m.modifyPosition(new Position(1, 2), 17), new Position(2, 9)); + assert.deepStrictEqual(m.modifyPosition(new Position(1, 2), 100), new Position(2, 9)); - assert.deepEqual(m.modifyPosition(new Position(1, 2), -2), new Position(1, 1)); - assert.deepEqual(m.modifyPosition(new Position(1, 2), -100), new Position(1, 1)); - assert.deepEqual(m.modifyPosition(new Position(2, 2), -100), new Position(1, 1)); - assert.deepEqual(m.modifyPosition(new Position(2, 9), -18), new Position(1, 1)); + assert.deepStrictEqual(m.modifyPosition(new Position(1, 2), -2), new Position(1, 1)); + assert.deepStrictEqual(m.modifyPosition(new Position(1, 2), -100), new Position(1, 1)); + assert.deepStrictEqual(m.modifyPosition(new Position(2, 2), -100), new Position(1, 1)); + assert.deepStrictEqual(m.modifyPosition(new Position(2, 9), -18), new Position(1, 1)); }); test('normalizeIndentation 1', () => { @@ -870,27 +870,27 @@ suite('Editor Model - TextModel', () => { } ); - assert.equal(model.normalizeIndentation('\t'), '\t'); - assert.equal(model.normalizeIndentation(' '), '\t'); - assert.equal(model.normalizeIndentation(' '), ' '); - assert.equal(model.normalizeIndentation(' '), ' '); - assert.equal(model.normalizeIndentation(' '), ' '); - assert.equal(model.normalizeIndentation(''), ''); - assert.equal(model.normalizeIndentation(' \t '), '\t\t'); - assert.equal(model.normalizeIndentation(' \t '), '\t '); - assert.equal(model.normalizeIndentation(' \t '), '\t '); - assert.equal(model.normalizeIndentation(' \t'), '\t '); + assert.strictEqual(model.normalizeIndentation('\t'), '\t'); + assert.strictEqual(model.normalizeIndentation(' '), '\t'); + assert.strictEqual(model.normalizeIndentation(' '), ' '); + assert.strictEqual(model.normalizeIndentation(' '), ' '); + assert.strictEqual(model.normalizeIndentation(' '), ' '); + assert.strictEqual(model.normalizeIndentation(''), ''); + assert.strictEqual(model.normalizeIndentation(' \t '), '\t\t'); + assert.strictEqual(model.normalizeIndentation(' \t '), '\t '); + assert.strictEqual(model.normalizeIndentation(' \t '), '\t '); + assert.strictEqual(model.normalizeIndentation(' \t'), '\t '); - assert.equal(model.normalizeIndentation('\ta'), '\ta'); - assert.equal(model.normalizeIndentation(' a'), '\ta'); - assert.equal(model.normalizeIndentation(' a'), ' a'); - assert.equal(model.normalizeIndentation(' a'), ' a'); - assert.equal(model.normalizeIndentation(' a'), ' a'); - assert.equal(model.normalizeIndentation('a'), 'a'); - assert.equal(model.normalizeIndentation(' \t a'), '\t\ta'); - assert.equal(model.normalizeIndentation(' \t a'), '\t a'); - assert.equal(model.normalizeIndentation(' \t a'), '\t a'); - assert.equal(model.normalizeIndentation(' \ta'), '\t a'); + assert.strictEqual(model.normalizeIndentation('\ta'), '\ta'); + assert.strictEqual(model.normalizeIndentation(' a'), '\ta'); + assert.strictEqual(model.normalizeIndentation(' a'), ' a'); + assert.strictEqual(model.normalizeIndentation(' a'), ' a'); + assert.strictEqual(model.normalizeIndentation(' a'), ' a'); + assert.strictEqual(model.normalizeIndentation('a'), 'a'); + assert.strictEqual(model.normalizeIndentation(' \t a'), '\t\ta'); + assert.strictEqual(model.normalizeIndentation(' \t a'), '\t a'); + assert.strictEqual(model.normalizeIndentation(' \t a'), '\t a'); + assert.strictEqual(model.normalizeIndentation(' \ta'), '\t a'); model.dispose(); }); @@ -898,16 +898,16 @@ suite('Editor Model - TextModel', () => { test('normalizeIndentation 2', () => { let model = createTextModel(''); - assert.equal(model.normalizeIndentation('\ta'), ' a'); - assert.equal(model.normalizeIndentation(' a'), ' a'); - assert.equal(model.normalizeIndentation(' a'), ' a'); - assert.equal(model.normalizeIndentation(' a'), ' a'); - assert.equal(model.normalizeIndentation(' a'), ' a'); - assert.equal(model.normalizeIndentation('a'), 'a'); - assert.equal(model.normalizeIndentation(' \t a'), ' a'); - assert.equal(model.normalizeIndentation(' \t a'), ' a'); - assert.equal(model.normalizeIndentation(' \t a'), ' a'); - assert.equal(model.normalizeIndentation(' \ta'), ' a'); + assert.strictEqual(model.normalizeIndentation('\ta'), ' a'); + assert.strictEqual(model.normalizeIndentation(' a'), ' a'); + assert.strictEqual(model.normalizeIndentation(' a'), ' a'); + assert.strictEqual(model.normalizeIndentation(' a'), ' a'); + assert.strictEqual(model.normalizeIndentation(' a'), ' a'); + assert.strictEqual(model.normalizeIndentation('a'), 'a'); + assert.strictEqual(model.normalizeIndentation(' \t a'), ' a'); + assert.strictEqual(model.normalizeIndentation(' \t a'), ' a'); + assert.strictEqual(model.normalizeIndentation(' \t a'), ' a'); + assert.strictEqual(model.normalizeIndentation(' \ta'), ' a'); model.dispose(); }); @@ -928,18 +928,18 @@ suite('Editor Model - TextModel', () => { '' ].join('\n')); - assert.equal(model.getLineFirstNonWhitespaceColumn(1), 1, '1'); - assert.equal(model.getLineFirstNonWhitespaceColumn(2), 2, '2'); - assert.equal(model.getLineFirstNonWhitespaceColumn(3), 2, '3'); - assert.equal(model.getLineFirstNonWhitespaceColumn(4), 3, '4'); - assert.equal(model.getLineFirstNonWhitespaceColumn(5), 3, '5'); - assert.equal(model.getLineFirstNonWhitespaceColumn(6), 0, '6'); - assert.equal(model.getLineFirstNonWhitespaceColumn(7), 0, '7'); - assert.equal(model.getLineFirstNonWhitespaceColumn(8), 0, '8'); - assert.equal(model.getLineFirstNonWhitespaceColumn(9), 0, '9'); - assert.equal(model.getLineFirstNonWhitespaceColumn(10), 4, '10'); - assert.equal(model.getLineFirstNonWhitespaceColumn(11), 0, '11'); - assert.equal(model.getLineFirstNonWhitespaceColumn(12), 0, '12'); + assert.strictEqual(model.getLineFirstNonWhitespaceColumn(1), 1, '1'); + assert.strictEqual(model.getLineFirstNonWhitespaceColumn(2), 2, '2'); + assert.strictEqual(model.getLineFirstNonWhitespaceColumn(3), 2, '3'); + assert.strictEqual(model.getLineFirstNonWhitespaceColumn(4), 3, '4'); + assert.strictEqual(model.getLineFirstNonWhitespaceColumn(5), 3, '5'); + assert.strictEqual(model.getLineFirstNonWhitespaceColumn(6), 0, '6'); + assert.strictEqual(model.getLineFirstNonWhitespaceColumn(7), 0, '7'); + assert.strictEqual(model.getLineFirstNonWhitespaceColumn(8), 0, '8'); + assert.strictEqual(model.getLineFirstNonWhitespaceColumn(9), 0, '9'); + assert.strictEqual(model.getLineFirstNonWhitespaceColumn(10), 4, '10'); + assert.strictEqual(model.getLineFirstNonWhitespaceColumn(11), 0, '11'); + assert.strictEqual(model.getLineFirstNonWhitespaceColumn(12), 0, '12'); }); test('getLineLastNonWhitespaceColumn', () => { @@ -958,24 +958,24 @@ suite('Editor Model - TextModel', () => { '' ].join('\n')); - assert.equal(model.getLineLastNonWhitespaceColumn(1), 4, '1'); - assert.equal(model.getLineLastNonWhitespaceColumn(2), 4, '2'); - assert.equal(model.getLineLastNonWhitespaceColumn(3), 4, '3'); - assert.equal(model.getLineLastNonWhitespaceColumn(4), 4, '4'); - assert.equal(model.getLineLastNonWhitespaceColumn(5), 4, '5'); - assert.equal(model.getLineLastNonWhitespaceColumn(6), 0, '6'); - assert.equal(model.getLineLastNonWhitespaceColumn(7), 0, '7'); - assert.equal(model.getLineLastNonWhitespaceColumn(8), 0, '8'); - assert.equal(model.getLineLastNonWhitespaceColumn(9), 0, '9'); - assert.equal(model.getLineLastNonWhitespaceColumn(10), 4, '10'); - assert.equal(model.getLineLastNonWhitespaceColumn(11), 0, '11'); - assert.equal(model.getLineLastNonWhitespaceColumn(12), 0, '12'); + assert.strictEqual(model.getLineLastNonWhitespaceColumn(1), 4, '1'); + assert.strictEqual(model.getLineLastNonWhitespaceColumn(2), 4, '2'); + assert.strictEqual(model.getLineLastNonWhitespaceColumn(3), 4, '3'); + assert.strictEqual(model.getLineLastNonWhitespaceColumn(4), 4, '4'); + assert.strictEqual(model.getLineLastNonWhitespaceColumn(5), 4, '5'); + assert.strictEqual(model.getLineLastNonWhitespaceColumn(6), 0, '6'); + assert.strictEqual(model.getLineLastNonWhitespaceColumn(7), 0, '7'); + assert.strictEqual(model.getLineLastNonWhitespaceColumn(8), 0, '8'); + assert.strictEqual(model.getLineLastNonWhitespaceColumn(9), 0, '9'); + assert.strictEqual(model.getLineLastNonWhitespaceColumn(10), 4, '10'); + assert.strictEqual(model.getLineLastNonWhitespaceColumn(11), 0, '11'); + assert.strictEqual(model.getLineLastNonWhitespaceColumn(12), 0, '12'); }); test('#50471. getValueInRange with invalid range', () => { let m = createTextModel('My First Line\r\nMy Second Line\r\nMy Third Line'); - assert.equal(m.getValueInRange(new Range(1, NaN, 1, 3)), 'My'); - assert.equal(m.getValueInRange(new Range(NaN, NaN, NaN, NaN)), ''); + assert.strictEqual(m.getValueInRange(new Range(1, NaN, 1, 3)), 'My'); + assert.strictEqual(m.getValueInRange(new Range(NaN, NaN, NaN, NaN)), ''); }); }); @@ -983,26 +983,26 @@ suite('TextModel.mightContainRTL', () => { test('nope', () => { let model = createTextModel('hello world!'); - assert.equal(model.mightContainRTL(), false); + assert.strictEqual(model.mightContainRTL(), false); }); test('yes', () => { let model = createTextModel('Hello,\nזוהי עובדה מבוססת שדעתו'); - assert.equal(model.mightContainRTL(), true); + assert.strictEqual(model.mightContainRTL(), true); }); test('setValue resets 1', () => { let model = createTextModel('hello world!'); - assert.equal(model.mightContainRTL(), false); + assert.strictEqual(model.mightContainRTL(), false); model.setValue('Hello,\nזוהי עובדה מבוססת שדעתו'); - assert.equal(model.mightContainRTL(), true); + assert.strictEqual(model.mightContainRTL(), true); }); test('setValue resets 2', () => { let model = createTextModel('Hello,\nهناك حقيقة مثبتة منذ زمن طويل'); - assert.equal(model.mightContainRTL(), true); + assert.strictEqual(model.mightContainRTL(), true); model.setValue('hello world!'); - assert.equal(model.mightContainRTL(), false); + assert.strictEqual(model.mightContainRTL(), false); }); }); @@ -1012,24 +1012,24 @@ suite('TextModel.createSnapshot', () => { test('empty file', () => { let model = createTextModel(''); let snapshot = model.createSnapshot(); - assert.equal(snapshot.read(), null); + assert.strictEqual(snapshot.read(), null); model.dispose(); }); test('file with BOM', () => { let model = createTextModel(UTF8_BOM_CHARACTER + 'Hello'); - assert.equal(model.getLineContent(1), 'Hello'); + assert.strictEqual(model.getLineContent(1), 'Hello'); let snapshot = model.createSnapshot(true); - assert.equal(snapshot.read(), UTF8_BOM_CHARACTER + 'Hello'); - assert.equal(snapshot.read(), null); + assert.strictEqual(snapshot.read(), UTF8_BOM_CHARACTER + 'Hello'); + assert.strictEqual(snapshot.read(), null); model.dispose(); }); test('regular file', () => { let model = createTextModel('My First Line\n\t\tMy Second Line\n Third Line\n\n1'); let snapshot = model.createSnapshot(); - assert.equal(snapshot.read(), 'My First Line\n\t\tMy Second Line\n Third Line\n\n1'); - assert.equal(snapshot.read(), null); + assert.strictEqual(snapshot.read(), 'My First Line\n\t\tMy Second Line\n Third Line\n\n1'); + assert.strictEqual(snapshot.read(), null); model.dispose(); }); @@ -1054,10 +1054,10 @@ suite('TextModel.createSnapshot', () => { // all good } else { actual += tmp2; - assert.equal(snapshot.read(), null); + assert.strictEqual(snapshot.read(), null); } - assert.equal(actual, text); + assert.strictEqual(actual, text); model.dispose(); }); diff --git a/src/vs/editor/test/common/model/textModelSearch.test.ts b/src/vs/editor/test/common/model/textModelSearch.test.ts index 715cd3032..558664746 100644 --- a/src/vs/editor/test/common/model/textModelSearch.test.ts +++ b/src/vs/editor/test/common/model/textModelSearch.test.ts @@ -19,31 +19,31 @@ suite('TextModelSearch', () => { const usualWordSeparators = getMapForWordSeparators(USUAL_WORD_SEPARATORS); function assertFindMatch(actual: FindMatch | null, expectedRange: Range, expectedMatches: string[] | null = null): void { - assert.deepEqual(actual, new FindMatch(expectedRange, expectedMatches)); + assert.deepStrictEqual(actual, new FindMatch(expectedRange, expectedMatches)); } function _assertFindMatches(model: TextModel, searchParams: SearchParams, expectedMatches: FindMatch[]): void { let actual = TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), false, 1000); - assert.deepEqual(actual, expectedMatches, 'findMatches OK'); + assert.deepStrictEqual(actual, expectedMatches, 'findMatches OK'); // test `findNextMatch` let startPos = new Position(1, 1); let match = TextModelSearch.findNextMatch(model, searchParams, startPos, false); - assert.deepEqual(match, expectedMatches[0], `findNextMatch ${startPos}`); + assert.deepStrictEqual(match, expectedMatches[0], `findNextMatch ${startPos}`); for (const expectedMatch of expectedMatches) { startPos = expectedMatch.range.getStartPosition(); match = TextModelSearch.findNextMatch(model, searchParams, startPos, false); - assert.deepEqual(match, expectedMatch, `findNextMatch ${startPos}`); + assert.deepStrictEqual(match, expectedMatch, `findNextMatch ${startPos}`); } // test `findPrevMatch` startPos = new Position(model.getLineCount(), model.getLineMaxColumn(model.getLineCount())); match = TextModelSearch.findPreviousMatch(model, searchParams, startPos, false); - assert.deepEqual(match, expectedMatches[expectedMatches.length - 1], `findPrevMatch ${startPos}`); + assert.deepStrictEqual(match, expectedMatches[expectedMatches.length - 1], `findPrevMatch ${startPos}`); for (const expectedMatch of expectedMatches) { startPos = expectedMatch.range.getEndPosition(); match = TextModelSearch.findPreviousMatch(model, searchParams, startPos, false); - assert.deepEqual(match, expectedMatch, `findPrevMatch ${startPos}`); + assert.deepStrictEqual(match, expectedMatch, `findPrevMatch ${startPos}`); } } @@ -486,7 +486,7 @@ suite('TextModelSearch', () => { let searchParams = new SearchParams('(l(in)e)', true, false, null); let actual = TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), true, 100); - assert.deepEqual(actual, [ + assert.deepStrictEqual(actual, [ new FindMatch(new Range(1, 5, 1, 9), ['line', 'line', 'in']), new FindMatch(new Range(1, 10, 1, 14), ['line', 'line', 'in']), new FindMatch(new Range(2, 5, 2, 9), ['line', 'line', 'in']), @@ -501,7 +501,7 @@ suite('TextModelSearch', () => { let searchParams = new SearchParams('(l(in)e)\\n', true, false, null); let actual = TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), true, 100); - assert.deepEqual(actual, [ + assert.deepStrictEqual(actual, [ new FindMatch(new Range(1, 10, 2, 1), ['line\n', 'line', 'in']), new FindMatch(new Range(2, 5, 3, 1), ['line\n', 'line', 'in']), ]); @@ -556,7 +556,7 @@ suite('TextModelSearch', () => { test('\\n matches \\r\\n', () => { let model = createTextModel('a\r\nb\r\nc\r\nd\r\ne\r\nf\r\ng\r\nh\r\ni'); - assert.equal(model.getEOL(), '\r\n'); + assert.strictEqual(model.getEOL(), '\r\n'); let searchParams = new SearchParams('h\\n', true, false, null); let actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 1), true); @@ -579,12 +579,12 @@ suite('TextModelSearch', () => { test('\\r can never be found', () => { let model = createTextModel('a\r\nb\r\nc\r\nd\r\ne\r\nf\r\ng\r\nh\r\ni'); - assert.equal(model.getEOL(), '\r\n'); + assert.strictEqual(model.getEOL(), '\r\n'); let searchParams = new SearchParams('\\r\\n', true, false, null); let actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 1), true); - assert.equal(actual, null); - assert.deepEqual(TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), true, 1000), []); + assert.strictEqual(actual, null); + assert.deepStrictEqual(TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), true, 1000), []); model.dispose(); }); @@ -596,8 +596,8 @@ suite('TextModelSearch', () => { if (expected === null) { assert.ok(actual === null); } else { - assert.deepEqual(actual!.regex, expected.regex); - assert.deepEqual(actual!.simpleSearch, expected.simpleSearch); + assert.deepStrictEqual(actual!.regex, expected.regex); + assert.deepStrictEqual(actual!.simpleSearch, expected.simpleSearch); if (wordSeparators) { assert.ok(actual!.wordSeparators !== null); } else { @@ -769,7 +769,7 @@ suite('TextModelSearch', () => { let searchParams = new SearchParams('\\d*', true, false, null); let actual = TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), true, 100); - assert.deepEqual(actual, [ + assert.deepStrictEqual(actual, [ new FindMatch(new Range(1, 1, 1, 3), ['10']), new FindMatch(new Range(1, 3, 1, 3), ['']), new FindMatch(new Range(1, 4, 1, 7), ['243']), diff --git a/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/src/vs/editor/test/common/model/textModelWithTokens.test.ts index efb1cf1de..e4560fd82 100644 --- a/src/vs/editor/test/common/model/textModelWithTokens.test.ts +++ b/src/vs/editor/test/common/model/textModelWithTokens.test.ts @@ -100,7 +100,7 @@ suite('TextModelWithTokens', () => { column: column }); - assert.deepEqual(toRelaxedFoundBracket(actual), toRelaxedFoundBracket(currentExpectedBracket), 'findPrevBracket of ' + lineNumber + ', ' + column); + assert.deepStrictEqual(toRelaxedFoundBracket(actual), toRelaxedFoundBracket(currentExpectedBracket), 'findPrevBracket of ' + lineNumber + ', ' + column); } } } @@ -126,7 +126,7 @@ suite('TextModelWithTokens', () => { column: column }); - assert.deepEqual(toRelaxedFoundBracket(actual), toRelaxedFoundBracket(currentExpectedBracket), 'findNextBracket of ' + lineNumber + ', ' + column); + assert.deepStrictEqual(toRelaxedFoundBracket(actual), toRelaxedFoundBracket(currentExpectedBracket), 'findNextBracket of ' + lineNumber + ', ' + column); } } } @@ -148,12 +148,12 @@ suite('TextModelWithTokens', () => { function assertIsNotBracket(model: TextModel, lineNumber: number, column: number) { const match = model.matchBracket(new Position(lineNumber, column)); - assert.equal(match, null, 'is not matching brackets at ' + lineNumber + ', ' + column); + assert.strictEqual(match, null, 'is not matching brackets at ' + lineNumber + ', ' + column); } function assertIsBracket(model: TextModel, testPosition: Position, expected: [Range, Range]): void { const actual = model.matchBracket(testPosition); - assert.deepEqual(actual, expected, 'matches brackets at ' + testPosition); + assert.deepStrictEqual(actual, expected, 'matches brackets at ' + testPosition); } suite('TextModelWithTokens - bracket matching', () => { @@ -351,7 +351,7 @@ suite('TextModelWithTokens', () => { const tokenizationSupport: ITokenizationSupport = { getInitialState: () => NULL_STATE, tokenize: undefined!, - tokenize2: (line, state) => { + tokenize2: (line, hasEOL, state) => { switch (line) { case 'function hello() {': { const tokens = new Uint32Array([ @@ -399,8 +399,8 @@ suite('TextModelWithTokens', () => { model.forceTokenization(2); model.forceTokenization(3); - assert.deepEqual(model.matchBracket(new Position(2, 23)), null); - assert.deepEqual(model.matchBracket(new Position(2, 20)), null); + assert.deepStrictEqual(model.matchBracket(new Position(2, 23)), null); + assert.deepStrictEqual(model.matchBracket(new Position(2, 20)), null); model.dispose(); registration1.dispose(); @@ -434,7 +434,7 @@ suite('TextModelWithTokens regression tests', () => { foreground: token.getForeground() }; }; - assert.deepEqual(actual, expected.map(decode)); + assert.deepStrictEqual(actual, expected.map(decode)); } let _tokenId = 10; @@ -446,7 +446,7 @@ suite('TextModelWithTokens regression tests', () => { const tokenizationSupport: ITokenizationSupport = { getInitialState: () => NULL_STATE, tokenize: undefined!, - tokenize2: (line, state) => { + tokenize2: (line, hasEOL, state) => { let myId = ++_tokenId; let tokens = new Uint32Array(2); tokens[0] = 0; @@ -512,7 +512,7 @@ suite('TextModelWithTokens regression tests', () => { ].join('\n'), undefined, languageIdentifier); let actual = model.matchBracket(new Position(4, 1)); - assert.deepEqual(actual, [new Range(4, 1, 4, 7), new Range(9, 1, 9, 11)]); + assert.deepStrictEqual(actual, [new Range(4, 1, 4, 7), new Range(9, 1, 9, 11)]); model.dispose(); registration.dispose(); @@ -537,7 +537,7 @@ suite('TextModelWithTokens regression tests', () => { ].join('\n'), undefined, languageIdentifier); let actual = model.matchBracket(new Position(3, 9)); - assert.deepEqual(actual, [new Range(3, 6, 3, 17), new Range(2, 6, 2, 14)]); + assert.deepStrictEqual(actual, [new Range(3, 6, 3, 17), new Range(2, 6, 2, 14)]); model.dispose(); registration.dispose(); @@ -550,7 +550,7 @@ suite('TextModelWithTokens regression tests', () => { const tokenizationSupport: ITokenizationSupport = { getInitialState: () => NULL_STATE, tokenize: undefined!, - tokenize2: (line, state) => { + tokenize2: (line, hasEOL, state) => { let tokens = new Uint32Array(2); tokens[0] = 0; tokens[1] = ( @@ -565,7 +565,7 @@ suite('TextModelWithTokens regression tests', () => { let model = createTextModel('A model with one line', undefined, outerMode); model.forceTokenization(1); - assert.equal(model.getLanguageIdAtPosition(1, 1), innerMode.id); + assert.strictEqual(model.getLanguageIdAtPosition(1, 1), innerMode.id); model.dispose(); registration.dispose(); @@ -586,7 +586,7 @@ suite('TextModel.getLineIndentGuide', () => { actual[line - 1] = [actualIndents[line - 1], activeIndentGuide.startLineNumber, activeIndentGuide.endLineNumber, activeIndentGuide.indent, model.getLineContent(line)]; } - assert.deepEqual(actual, lines); + assert.deepStrictEqual(actual, lines); model.dispose(); } @@ -764,7 +764,7 @@ suite('TextModel.getLineIndentGuide', () => { ].join('\n')); const actual = model.getActiveIndentGuide(2, 4, 9); - assert.deepEqual(actual, { startLineNumber: 2, endLineNumber: 9, indent: 1 }); + assert.deepStrictEqual(actual, { startLineNumber: 2, endLineNumber: 9, indent: 1 }); model.dispose(); }); diff --git a/src/vs/editor/test/common/model/tokensStore.test.ts b/src/vs/editor/test/common/model/tokensStore.test.ts index ee8b438b4..e883d551e 100644 --- a/src/vs/editor/test/common/model/tokensStore.test.ts +++ b/src/vs/editor/test/common/model/tokensStore.test.ts @@ -104,7 +104,7 @@ suite('TokensStore', () => { model.applyEdits(edits); const actualState = extractState(model); - assert.deepEqual(actualState, rawFinalState); + assert.deepStrictEqual(actualState, rawFinalState); model.dispose(); } @@ -191,7 +191,7 @@ suite('TokensStore', () => { decodedTokens.push(lineTokens.getEndOffset(i), lineTokens.getMetadata(i)); } - assert.deepEqual(decodedTokens, [ + assert.deepStrictEqual(decodedTokens, [ 20, 16793600, 24, 17022976, 25, 16793600, @@ -252,7 +252,7 @@ suite('TokensStore', () => { ]); const lineTokens = store.addSemanticTokens(10, new LineTokens(new Uint32Array([12, 1]), `enum Enum1 {`)); - assert.equal(lineTokens.getCount(), 3); + assert.strictEqual(lineTokens.getCount(), 3); }); test('partial tokens 2', () => { @@ -293,7 +293,7 @@ suite('TokensStore', () => { ]); const lineTokens = store.addSemanticTokens(20, new LineTokens(new Uint32Array([12, 1]), `enum Enum1 {`)); - assert.equal(lineTokens.getCount(), 3); + assert.strictEqual(lineTokens.getCount(), 3); }); test('partial tokens 3', () => { @@ -320,7 +320,7 @@ suite('TokensStore', () => { ]); const lineTokens = store.addSemanticTokens(5, new LineTokens(new Uint32Array([12, 1]), `enum Enum1 {`)); - assert.equal(lineTokens.getCount(), 3); + assert.strictEqual(lineTokens.getCount(), 3); }); test('issue #94133: Semantic colors stick around when using (only) range provider', () => { @@ -337,7 +337,7 @@ suite('TokensStore', () => { store.setPartial(new Range(1, 1, 1, 20), []); const lineTokens = store.addSemanticTokens(1, new LineTokens(new Uint32Array([12, 1]), `enum Enum1 {`)); - assert.equal(lineTokens.getCount(), 1); + assert.strictEqual(lineTokens.getCount(), 1); }); test('bug', () => { @@ -385,7 +385,7 @@ suite('TokensStore', () => { ); const lineTokens = store.addSemanticTokens(36451, new LineTokens(new Uint32Array([60, 1]), ` if (flags & ModifierFlags.Ambient) {`)); - assert.equal(lineTokens.getCount(), 7); + assert.strictEqual(lineTokens.getCount(), 7); }); @@ -424,7 +424,7 @@ suite('TokensStore', () => { ]), `const hello = 123;`)); const actual = toArr(lineTokens); - assert.deepEqual(actual, [ + assert.deepStrictEqual(actual, [ 5, createTMMetadata(5, FontStyle.Bold, 53), 6, createTMMetadata(1, FontStyle.None, 53), 11, createTMMetadata(1, FontStyle.None, 53), diff --git a/src/vs/editor/test/common/modes/languageConfiguration.test.ts b/src/vs/editor/test/common/modes/languageConfiguration.test.ts index e898568a7..3acba6ddf 100644 --- a/src/vs/editor/test/common/modes/languageConfiguration.test.ts +++ b/src/vs/editor/test/common/modes/languageConfiguration.test.ts @@ -11,81 +11,81 @@ suite('StandardAutoClosingPairConditional', () => { test('Missing notIn', () => { let v = new StandardAutoClosingPairConditional({ open: '{', close: '}' }); - assert.equal(v.isOK(StandardTokenType.Other), true); - assert.equal(v.isOK(StandardTokenType.Comment), true); - assert.equal(v.isOK(StandardTokenType.String), true); - assert.equal(v.isOK(StandardTokenType.RegEx), true); + assert.strictEqual(v.isOK(StandardTokenType.Other), true); + assert.strictEqual(v.isOK(StandardTokenType.Comment), true); + assert.strictEqual(v.isOK(StandardTokenType.String), true); + assert.strictEqual(v.isOK(StandardTokenType.RegEx), true); }); test('Empty notIn', () => { let v = new StandardAutoClosingPairConditional({ open: '{', close: '}', notIn: [] }); - assert.equal(v.isOK(StandardTokenType.Other), true); - assert.equal(v.isOK(StandardTokenType.Comment), true); - assert.equal(v.isOK(StandardTokenType.String), true); - assert.equal(v.isOK(StandardTokenType.RegEx), true); + assert.strictEqual(v.isOK(StandardTokenType.Other), true); + assert.strictEqual(v.isOK(StandardTokenType.Comment), true); + assert.strictEqual(v.isOK(StandardTokenType.String), true); + assert.strictEqual(v.isOK(StandardTokenType.RegEx), true); }); test('Invalid notIn', () => { let v = new StandardAutoClosingPairConditional({ open: '{', close: '}', notIn: ['bla'] }); - assert.equal(v.isOK(StandardTokenType.Other), true); - assert.equal(v.isOK(StandardTokenType.Comment), true); - assert.equal(v.isOK(StandardTokenType.String), true); - assert.equal(v.isOK(StandardTokenType.RegEx), true); + assert.strictEqual(v.isOK(StandardTokenType.Other), true); + assert.strictEqual(v.isOK(StandardTokenType.Comment), true); + assert.strictEqual(v.isOK(StandardTokenType.String), true); + assert.strictEqual(v.isOK(StandardTokenType.RegEx), true); }); test('notIn in strings', () => { let v = new StandardAutoClosingPairConditional({ open: '{', close: '}', notIn: ['string'] }); - assert.equal(v.isOK(StandardTokenType.Other), true); - assert.equal(v.isOK(StandardTokenType.Comment), true); - assert.equal(v.isOK(StandardTokenType.String), false); - assert.equal(v.isOK(StandardTokenType.RegEx), true); + assert.strictEqual(v.isOK(StandardTokenType.Other), true); + assert.strictEqual(v.isOK(StandardTokenType.Comment), true); + assert.strictEqual(v.isOK(StandardTokenType.String), false); + assert.strictEqual(v.isOK(StandardTokenType.RegEx), true); }); test('notIn in comments', () => { let v = new StandardAutoClosingPairConditional({ open: '{', close: '}', notIn: ['comment'] }); - assert.equal(v.isOK(StandardTokenType.Other), true); - assert.equal(v.isOK(StandardTokenType.Comment), false); - assert.equal(v.isOK(StandardTokenType.String), true); - assert.equal(v.isOK(StandardTokenType.RegEx), true); + assert.strictEqual(v.isOK(StandardTokenType.Other), true); + assert.strictEqual(v.isOK(StandardTokenType.Comment), false); + assert.strictEqual(v.isOK(StandardTokenType.String), true); + assert.strictEqual(v.isOK(StandardTokenType.RegEx), true); }); test('notIn in regex', () => { let v = new StandardAutoClosingPairConditional({ open: '{', close: '}', notIn: ['regex'] }); - assert.equal(v.isOK(StandardTokenType.Other), true); - assert.equal(v.isOK(StandardTokenType.Comment), true); - assert.equal(v.isOK(StandardTokenType.String), true); - assert.equal(v.isOK(StandardTokenType.RegEx), false); + assert.strictEqual(v.isOK(StandardTokenType.Other), true); + assert.strictEqual(v.isOK(StandardTokenType.Comment), true); + assert.strictEqual(v.isOK(StandardTokenType.String), true); + assert.strictEqual(v.isOK(StandardTokenType.RegEx), false); }); test('notIn in strings nor comments', () => { let v = new StandardAutoClosingPairConditional({ open: '{', close: '}', notIn: ['string', 'comment'] }); - assert.equal(v.isOK(StandardTokenType.Other), true); - assert.equal(v.isOK(StandardTokenType.Comment), false); - assert.equal(v.isOK(StandardTokenType.String), false); - assert.equal(v.isOK(StandardTokenType.RegEx), true); + assert.strictEqual(v.isOK(StandardTokenType.Other), true); + assert.strictEqual(v.isOK(StandardTokenType.Comment), false); + assert.strictEqual(v.isOK(StandardTokenType.String), false); + assert.strictEqual(v.isOK(StandardTokenType.RegEx), true); }); test('notIn in strings nor regex', () => { let v = new StandardAutoClosingPairConditional({ open: '{', close: '}', notIn: ['string', 'regex'] }); - assert.equal(v.isOK(StandardTokenType.Other), true); - assert.equal(v.isOK(StandardTokenType.Comment), true); - assert.equal(v.isOK(StandardTokenType.String), false); - assert.equal(v.isOK(StandardTokenType.RegEx), false); + assert.strictEqual(v.isOK(StandardTokenType.Other), true); + assert.strictEqual(v.isOK(StandardTokenType.Comment), true); + assert.strictEqual(v.isOK(StandardTokenType.String), false); + assert.strictEqual(v.isOK(StandardTokenType.RegEx), false); }); test('notIn in comments nor regex', () => { let v = new StandardAutoClosingPairConditional({ open: '{', close: '}', notIn: ['comment', 'regex'] }); - assert.equal(v.isOK(StandardTokenType.Other), true); - assert.equal(v.isOK(StandardTokenType.Comment), false); - assert.equal(v.isOK(StandardTokenType.String), true); - assert.equal(v.isOK(StandardTokenType.RegEx), false); + assert.strictEqual(v.isOK(StandardTokenType.Other), true); + assert.strictEqual(v.isOK(StandardTokenType.Comment), false); + assert.strictEqual(v.isOK(StandardTokenType.String), true); + assert.strictEqual(v.isOK(StandardTokenType.RegEx), false); }); test('notIn in strings, comments nor regex', () => { let v = new StandardAutoClosingPairConditional({ open: '{', close: '}', notIn: ['string', 'comment', 'regex'] }); - assert.equal(v.isOK(StandardTokenType.Other), true); - assert.equal(v.isOK(StandardTokenType.Comment), false); - assert.equal(v.isOK(StandardTokenType.String), false); - assert.equal(v.isOK(StandardTokenType.RegEx), false); + assert.strictEqual(v.isOK(StandardTokenType.Other), true); + assert.strictEqual(v.isOK(StandardTokenType.Comment), false); + assert.strictEqual(v.isOK(StandardTokenType.String), false); + assert.strictEqual(v.isOK(StandardTokenType.RegEx), false); }); }); diff --git a/src/vs/editor/test/common/modes/languageSelector.test.ts b/src/vs/editor/test/common/modes/languageSelector.test.ts index 0886c65bb..dc06141f6 100644 --- a/src/vs/editor/test/common/modes/languageSelector.test.ts +++ b/src/vs/editor/test/common/modes/languageSelector.test.ts @@ -15,18 +15,18 @@ suite('LanguageSelector', function () { }; test('score, invalid selector', function () { - assert.equal(score({}, model.uri, model.language, true), 0); - assert.equal(score(undefined!, model.uri, model.language, true), 0); - assert.equal(score(null!, model.uri, model.language, true), 0); - assert.equal(score('', model.uri, model.language, true), 0); + assert.strictEqual(score({}, model.uri, model.language, true), 0); + assert.strictEqual(score(undefined!, model.uri, model.language, true), 0); + assert.strictEqual(score(null!, model.uri, model.language, true), 0); + assert.strictEqual(score('', model.uri, model.language, true), 0); }); test('score, any language', function () { - assert.equal(score({ language: '*' }, model.uri, model.language, true), 5); - assert.equal(score('*', model.uri, model.language, true), 5); + assert.strictEqual(score({ language: '*' }, model.uri, model.language, true), 5); + assert.strictEqual(score('*', model.uri, model.language, true), 5); - assert.equal(score('*', URI.parse('foo:bar'), model.language, true), 5); - assert.equal(score('farboo', URI.parse('foo:bar'), model.language, true), 10); + assert.strictEqual(score('*', URI.parse('foo:bar'), model.language, true), 5); + assert.strictEqual(score('farboo', URI.parse('foo:bar'), model.language, true), 10); }); test('score, default schemes', function () { @@ -34,50 +34,50 @@ suite('LanguageSelector', function () { const uri = URI.parse('git:foo/file.txt'); const language = 'farboo'; - assert.equal(score('*', uri, language, true), 5); - assert.equal(score('farboo', uri, language, true), 10); - assert.equal(score({ language: 'farboo', scheme: '' }, uri, language, true), 10); - assert.equal(score({ language: 'farboo', scheme: 'git' }, uri, language, true), 10); - assert.equal(score({ language: 'farboo', scheme: '*' }, uri, language, true), 10); - assert.equal(score({ language: 'farboo' }, uri, language, true), 10); - assert.equal(score({ language: '*' }, uri, language, true), 5); + assert.strictEqual(score('*', uri, language, true), 5); + assert.strictEqual(score('farboo', uri, language, true), 10); + assert.strictEqual(score({ language: 'farboo', scheme: '' }, uri, language, true), 10); + assert.strictEqual(score({ language: 'farboo', scheme: 'git' }, uri, language, true), 10); + assert.strictEqual(score({ language: 'farboo', scheme: '*' }, uri, language, true), 10); + assert.strictEqual(score({ language: 'farboo' }, uri, language, true), 10); + assert.strictEqual(score({ language: '*' }, uri, language, true), 5); - assert.equal(score({ scheme: '*' }, uri, language, true), 5); - assert.equal(score({ scheme: 'git' }, uri, language, true), 10); + assert.strictEqual(score({ scheme: '*' }, uri, language, true), 5); + assert.strictEqual(score({ scheme: 'git' }, uri, language, true), 10); }); test('score, filter', function () { - assert.equal(score('farboo', model.uri, model.language, true), 10); - assert.equal(score({ language: 'farboo' }, model.uri, model.language, true), 10); - assert.equal(score({ language: 'farboo', scheme: 'file' }, model.uri, model.language, true), 10); - assert.equal(score({ language: 'farboo', scheme: 'http' }, model.uri, model.language, true), 0); + assert.strictEqual(score('farboo', model.uri, model.language, true), 10); + assert.strictEqual(score({ language: 'farboo' }, model.uri, model.language, true), 10); + assert.strictEqual(score({ language: 'farboo', scheme: 'file' }, model.uri, model.language, true), 10); + assert.strictEqual(score({ language: 'farboo', scheme: 'http' }, model.uri, model.language, true), 0); - assert.equal(score({ pattern: '**/*.fb' }, model.uri, model.language, true), 10); - assert.equal(score({ pattern: '**/*.fb', scheme: 'file' }, model.uri, model.language, true), 10); - assert.equal(score({ pattern: '**/*.fb' }, URI.parse('foo:bar'), model.language, true), 0); - assert.equal(score({ pattern: '**/*.fb', scheme: 'foo' }, URI.parse('foo:bar'), model.language, true), 0); + assert.strictEqual(score({ pattern: '**/*.fb' }, model.uri, model.language, true), 10); + assert.strictEqual(score({ pattern: '**/*.fb', scheme: 'file' }, model.uri, model.language, true), 10); + assert.strictEqual(score({ pattern: '**/*.fb' }, URI.parse('foo:bar'), model.language, true), 0); + assert.strictEqual(score({ pattern: '**/*.fb', scheme: 'foo' }, URI.parse('foo:bar'), model.language, true), 0); let doc = { uri: URI.parse('git:/my/file.js'), langId: 'javascript' }; - assert.equal(score('javascript', doc.uri, doc.langId, true), 10); // 0; - assert.equal(score({ language: 'javascript', scheme: 'git' }, doc.uri, doc.langId, true), 10); // 10; - assert.equal(score('*', doc.uri, doc.langId, true), 5); // 5 - assert.equal(score('fooLang', doc.uri, doc.langId, true), 0); // 0 - assert.equal(score(['fooLang', '*'], doc.uri, doc.langId, true), 5); // 5 + assert.strictEqual(score('javascript', doc.uri, doc.langId, true), 10); // 0; + assert.strictEqual(score({ language: 'javascript', scheme: 'git' }, doc.uri, doc.langId, true), 10); // 10; + assert.strictEqual(score('*', doc.uri, doc.langId, true), 5); // 5 + assert.strictEqual(score('fooLang', doc.uri, doc.langId, true), 0); // 0 + assert.strictEqual(score(['fooLang', '*'], doc.uri, doc.langId, true), 5); // 5 }); test('score, max(filters)', function () { let match = { language: 'farboo', scheme: 'file' }; let fail = { language: 'farboo', scheme: 'http' }; - assert.equal(score(match, model.uri, model.language, true), 10); - assert.equal(score(fail, model.uri, model.language, true), 0); - assert.equal(score([match, fail], model.uri, model.language, true), 10); - assert.equal(score([fail, fail], model.uri, model.language, true), 0); - assert.equal(score(['farboo', '*'], model.uri, model.language, true), 10); - assert.equal(score(['*', 'farboo'], model.uri, model.language, true), 10); + assert.strictEqual(score(match, model.uri, model.language, true), 10); + assert.strictEqual(score(fail, model.uri, model.language, true), 0); + assert.strictEqual(score([match, fail], model.uri, model.language, true), 10); + assert.strictEqual(score([fail, fail], model.uri, model.language, true), 0); + assert.strictEqual(score(['farboo', '*'], model.uri, model.language, true), 10); + assert.strictEqual(score(['*', 'farboo'], model.uri, model.language, true), 10); }); test('score hasAccessToAllModels', function () { @@ -85,14 +85,14 @@ suite('LanguageSelector', function () { uri: URI.parse('file:/my/file.js'), langId: 'javascript' }; - assert.equal(score('javascript', doc.uri, doc.langId, false), 0); - assert.equal(score({ language: 'javascript', scheme: 'file' }, doc.uri, doc.langId, false), 0); - assert.equal(score('*', doc.uri, doc.langId, false), 0); - assert.equal(score('fooLang', doc.uri, doc.langId, false), 0); - assert.equal(score(['fooLang', '*'], doc.uri, doc.langId, false), 0); + assert.strictEqual(score('javascript', doc.uri, doc.langId, false), 0); + assert.strictEqual(score({ language: 'javascript', scheme: 'file' }, doc.uri, doc.langId, false), 0); + assert.strictEqual(score('*', doc.uri, doc.langId, false), 0); + assert.strictEqual(score('fooLang', doc.uri, doc.langId, false), 0); + assert.strictEqual(score(['fooLang', '*'], doc.uri, doc.langId, false), 0); - assert.equal(score({ language: 'javascript', scheme: 'file', hasAccessToAllModels: true }, doc.uri, doc.langId, false), 10); - assert.equal(score(['fooLang', '*', { language: '*', hasAccessToAllModels: true }], doc.uri, doc.langId, false), 5); + assert.strictEqual(score({ language: 'javascript', scheme: 'file', hasAccessToAllModels: true }, doc.uri, doc.langId, false), 10); + assert.strictEqual(score(['fooLang', '*', { language: '*', hasAccessToAllModels: true }], doc.uri, doc.langId, false), 5); }); test('Document selector match - unexpected result value #60232', function () { @@ -102,7 +102,7 @@ suite('LanguageSelector', function () { pattern: '**/*.interface.json' }; let value = score(selector, URI.parse('file:///C:/Users/zlhe/Desktop/test.interface.json'), 'json', true); - assert.equal(value, 10); + assert.strictEqual(value, 10); }); test('Document selector match - platform paths #99938', function () { @@ -113,6 +113,6 @@ suite('LanguageSelector', function () { } }; let value = score(selector, URI.file('/home/user/Desktop/test.json'), 'json', true); - assert.equal(value, 10); + assert.strictEqual(value, 10); }); }); diff --git a/src/vs/editor/test/common/modes/linkComputer.test.ts b/src/vs/editor/test/common/modes/linkComputer.test.ts index 9cf9ca77c..5bb34c911 100644 --- a/src/vs/editor/test/common/modes/linkComputer.test.ts +++ b/src/vs/editor/test/common/modes/linkComputer.test.ts @@ -49,7 +49,7 @@ function assertLink(text: string, extractedLink: string): void { } let r = myComputeLinks([text]); - assert.deepEqual(r, [{ + assert.deepStrictEqual(r, [{ range: { startLineNumber: 1, startColumn: startColumn, @@ -64,7 +64,7 @@ suite('Editor Modes - Link Computer', () => { test('Null model', () => { let r = computeLinks(null); - assert.deepEqual(r, []); + assert.deepStrictEqual(r, []); }); test('Parsing', () => { diff --git a/src/vs/editor/test/common/modes/supports/characterPair.test.ts b/src/vs/editor/test/common/modes/supports/characterPair.test.ts index e244d6b47..ce1c31ee9 100644 --- a/src/vs/editor/test/common/modes/supports/characterPair.test.ts +++ b/src/vs/editor/test/common/modes/supports/characterPair.test.ts @@ -13,44 +13,44 @@ suite('CharacterPairSupport', () => { test('only autoClosingPairs', () => { let characaterPairSupport = new CharacterPairSupport({ autoClosingPairs: [{ open: 'a', close: 'b' }] }); - assert.deepEqual(characaterPairSupport.getAutoClosingPairs(), [{ open: 'a', close: 'b', _standardTokenMask: 0 }]); - assert.deepEqual(characaterPairSupport.getSurroundingPairs(), [{ open: 'a', close: 'b', _standardTokenMask: 0 }]); + assert.deepStrictEqual(characaterPairSupport.getAutoClosingPairs(), [new StandardAutoClosingPairConditional({ open: 'a', close: 'b' })]); + assert.deepStrictEqual(characaterPairSupport.getSurroundingPairs(), [new StandardAutoClosingPairConditional({ open: 'a', close: 'b' })]); }); test('only empty autoClosingPairs', () => { let characaterPairSupport = new CharacterPairSupport({ autoClosingPairs: [] }); - assert.deepEqual(characaterPairSupport.getAutoClosingPairs(), []); - assert.deepEqual(characaterPairSupport.getSurroundingPairs(), []); + assert.deepStrictEqual(characaterPairSupport.getAutoClosingPairs(), []); + assert.deepStrictEqual(characaterPairSupport.getSurroundingPairs(), []); }); test('only brackets', () => { let characaterPairSupport = new CharacterPairSupport({ brackets: [['a', 'b']] }); - assert.deepEqual(characaterPairSupport.getAutoClosingPairs(), [{ open: 'a', close: 'b', _standardTokenMask: 0 }]); - assert.deepEqual(characaterPairSupport.getSurroundingPairs(), [{ open: 'a', close: 'b', _standardTokenMask: 0 }]); + assert.deepStrictEqual(characaterPairSupport.getAutoClosingPairs(), [new StandardAutoClosingPairConditional({ open: 'a', close: 'b' })]); + assert.deepStrictEqual(characaterPairSupport.getSurroundingPairs(), [new StandardAutoClosingPairConditional({ open: 'a', close: 'b' })]); }); test('only empty brackets', () => { let characaterPairSupport = new CharacterPairSupport({ brackets: [] }); - assert.deepEqual(characaterPairSupport.getAutoClosingPairs(), []); - assert.deepEqual(characaterPairSupport.getSurroundingPairs(), []); + assert.deepStrictEqual(characaterPairSupport.getAutoClosingPairs(), []); + assert.deepStrictEqual(characaterPairSupport.getSurroundingPairs(), []); }); test('only surroundingPairs', () => { let characaterPairSupport = new CharacterPairSupport({ surroundingPairs: [{ open: 'a', close: 'b' }] }); - assert.deepEqual(characaterPairSupport.getAutoClosingPairs(), []); - assert.deepEqual(characaterPairSupport.getSurroundingPairs(), [{ open: 'a', close: 'b' }]); + assert.deepStrictEqual(characaterPairSupport.getAutoClosingPairs(), []); + assert.deepStrictEqual(characaterPairSupport.getSurroundingPairs(), [{ open: 'a', close: 'b' }]); }); test('only empty surroundingPairs', () => { let characaterPairSupport = new CharacterPairSupport({ surroundingPairs: [] }); - assert.deepEqual(characaterPairSupport.getAutoClosingPairs(), []); - assert.deepEqual(characaterPairSupport.getSurroundingPairs(), []); + assert.deepStrictEqual(characaterPairSupport.getAutoClosingPairs(), []); + assert.deepStrictEqual(characaterPairSupport.getSurroundingPairs(), []); }); test('brackets is ignored when having autoClosingPairs', () => { let characaterPairSupport = new CharacterPairSupport({ autoClosingPairs: [], brackets: [['a', 'b']] }); - assert.deepEqual(characaterPairSupport.getAutoClosingPairs(), []); - assert.deepEqual(characaterPairSupport.getSurroundingPairs(), []); + assert.deepStrictEqual(characaterPairSupport.getAutoClosingPairs(), []); + assert.deepStrictEqual(characaterPairSupport.getSurroundingPairs(), []); }); function findAutoClosingPair(characterPairSupport: CharacterPairSupport, character: string): StandardAutoClosingPairConditional | undefined { @@ -67,64 +67,64 @@ suite('CharacterPairSupport', () => { test('shouldAutoClosePair in empty line', () => { let sup = new CharacterPairSupport({ autoClosingPairs: [{ open: '{', close: '}', notIn: ['string', 'comment'] }] }); - assert.equal(testShouldAutoClose(sup, [], 'a', 1), false); - assert.equal(testShouldAutoClose(sup, [], '{', 1), true); + assert.strictEqual(testShouldAutoClose(sup, [], 'a', 1), false); + assert.strictEqual(testShouldAutoClose(sup, [], '{', 1), true); }); test('shouldAutoClosePair in not interesting line 1', () => { let sup = new CharacterPairSupport({ autoClosingPairs: [{ open: '{', close: '}', notIn: ['string', 'comment'] }] }); - assert.equal(testShouldAutoClose(sup, [{ text: 'do', type: StandardTokenType.Other }], '{', 3), true); - assert.equal(testShouldAutoClose(sup, [{ text: 'do', type: StandardTokenType.Other }], 'a', 3), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: 'do', type: StandardTokenType.Other }], '{', 3), true); + assert.strictEqual(testShouldAutoClose(sup, [{ text: 'do', type: StandardTokenType.Other }], 'a', 3), false); }); test('shouldAutoClosePair in not interesting line 2', () => { let sup = new CharacterPairSupport({ autoClosingPairs: [{ open: '{', close: '}' }] }); - assert.equal(testShouldAutoClose(sup, [{ text: 'do', type: StandardTokenType.String }], '{', 3), true); - assert.equal(testShouldAutoClose(sup, [{ text: 'do', type: StandardTokenType.String }], 'a', 3), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: 'do', type: StandardTokenType.String }], '{', 3), true); + assert.strictEqual(testShouldAutoClose(sup, [{ text: 'do', type: StandardTokenType.String }], 'a', 3), false); }); test('shouldAutoClosePair in interesting line 1', () => { let sup = new CharacterPairSupport({ autoClosingPairs: [{ open: '{', close: '}', notIn: ['string', 'comment'] }] }); - assert.equal(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], '{', 1), false); - assert.equal(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], 'a', 1), false); - assert.equal(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], '{', 2), false); - assert.equal(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], 'a', 2), false); - assert.equal(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], '{', 3), false); - assert.equal(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], 'a', 3), false); - assert.equal(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], '{', 4), false); - assert.equal(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], 'a', 4), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], '{', 1), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], 'a', 1), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], '{', 2), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], 'a', 2), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], '{', 3), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], 'a', 3), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], '{', 4), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], 'a', 4), false); }); test('shouldAutoClosePair in interesting line 2', () => { let sup = new CharacterPairSupport({ autoClosingPairs: [{ open: '{', close: '}', notIn: ['string', 'comment'] }] }); - assert.equal(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], '{', 1), true); - assert.equal(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], 'a', 1), false); - assert.equal(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], '{', 2), true); - assert.equal(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], 'a', 2), false); - assert.equal(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], '{', 3), true); - assert.equal(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], 'a', 3), false); - assert.equal(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], '{', 4), false); - assert.equal(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], 'a', 4), false); - assert.equal(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], '{', 5), false); - assert.equal(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], 'a', 5), false); - assert.equal(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], '{', 6), false); - assert.equal(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], 'a', 6), false); - assert.equal(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], '{', 7), true); - assert.equal(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], 'a', 7), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], '{', 1), true); + assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], 'a', 1), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], '{', 2), true); + assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], 'a', 2), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], '{', 3), true); + assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], 'a', 3), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], '{', 4), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], 'a', 4), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], '{', 5), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], 'a', 5), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], '{', 6), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], 'a', 6), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], '{', 7), true); + assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], 'a', 7), false); }); test('shouldAutoClosePair in interesting line 3', () => { let sup = new CharacterPairSupport({ autoClosingPairs: [{ open: '{', close: '}', notIn: ['string', 'comment'] }] }); - assert.equal(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], '{', 1), true); - assert.equal(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], 'a', 1), false); - assert.equal(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], '{', 2), true); - assert.equal(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], 'a', 2), false); - assert.equal(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], '{', 3), false); - assert.equal(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], 'a', 3), false); - assert.equal(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], '{', 4), false); - assert.equal(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], 'a', 4), false); - assert.equal(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], '{', 5), false); - assert.equal(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], 'a', 5), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], '{', 1), true); + assert.strictEqual(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], 'a', 1), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], '{', 2), true); + assert.strictEqual(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], 'a', 2), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], '{', 3), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], 'a', 3), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], '{', 4), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], 'a', 4), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], '{', 5), false); + assert.strictEqual(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], 'a', 5), false); }); }); diff --git a/src/vs/editor/test/common/modes/supports/electricCharacter.test.ts b/src/vs/editor/test/common/modes/supports/electricCharacter.test.ts index 22b818c6b..ba609bdb4 100644 --- a/src/vs/editor/test/common/modes/supports/electricCharacter.test.ts +++ b/src/vs/editor/test/common/modes/supports/electricCharacter.test.ts @@ -18,12 +18,12 @@ suite('Editor Modes - Auto Indentation', () => { function testDoesNothing(electricCharacterSupport: BracketElectricCharacterSupport, line: TokenText[], character: string, offset: number): void { let actual = _testOnElectricCharacter(electricCharacterSupport, line, character, offset); - assert.deepEqual(actual, null); + assert.deepStrictEqual(actual, null); } function testMatchBracket(electricCharacterSupport: BracketElectricCharacterSupport, line: TokenText[], character: string, offset: number, matchOpenBracket: string): void { let actual = _testOnElectricCharacter(electricCharacterSupport, line, character, offset); - assert.deepEqual(actual, { matchOpenBracket: matchOpenBracket }); + assert.deepStrictEqual(actual, { matchOpenBracket: matchOpenBracket }); } test('getElectricCharacters uses all sources and dedups', () => { @@ -34,7 +34,7 @@ suite('Editor Modes - Auto Indentation', () => { ]) ); - assert.deepEqual(sup.getElectricCharacters(), ['}', ')']); + assert.deepStrictEqual(sup.getElectricCharacters(), ['}', ')']); }); test('matchOpenBracket', () => { diff --git a/src/vs/editor/test/common/modes/supports/javascriptOnEnterRules.ts b/src/vs/editor/test/common/modes/supports/javascriptOnEnterRules.ts index 722204807..83d8a6ced 100644 --- a/src/vs/editor/test/common/modes/supports/javascriptOnEnterRules.ts +++ b/src/vs/editor/test/common/modes/supports/javascriptOnEnterRules.ts @@ -18,7 +18,7 @@ export const javascriptOnEnterRules = [ }, { // e.g. * ...| beforeText: /^(\t|[ ])*[ ]\*([ ]([^\*]|\*(?!\/))*)?$/, - oneLineAboveText: /(?=^(\s*(\/\*\*|\*)).*)(?=(?!(\s*\*\/)))/, + previousLineText: /(?=^(\s*(\/\*\*|\*)).*)(?=(?!(\s*\*\/)))/, action: { indentAction: IndentAction.None, appendText: '* ' } }, { // e.g. */| diff --git a/src/vs/editor/test/common/modes/supports/onEnter.test.ts b/src/vs/editor/test/common/modes/supports/onEnter.test.ts index f799c66e7..1af36e9ef 100644 --- a/src/vs/editor/test/common/modes/supports/onEnter.test.ts +++ b/src/vs/editor/test/common/modes/supports/onEnter.test.ts @@ -21,9 +21,9 @@ suite('OnEnter', () => { let testIndentAction = (beforeText: string, afterText: string, expected: IndentAction) => { let actual = support.onEnter(EditorAutoIndentStrategy.Advanced, '', beforeText, afterText); if (expected === IndentAction.None) { - assert.equal(actual, null); + assert.strictEqual(actual, null); } else { - assert.equal(actual!.indentAction, expected); + assert.strictEqual(actual!.indentAction, expected); } }; @@ -51,18 +51,18 @@ suite('OnEnter', () => { let support = new OnEnterSupport({ onEnterRules: javascriptOnEnterRules }); - let testIndentAction = (oneLineAboveText: string, beforeText: string, afterText: string, expectedIndentAction: IndentAction | null, expectedAppendText: string | null, removeText: number = 0) => { - let actual = support.onEnter(EditorAutoIndentStrategy.Advanced, oneLineAboveText, beforeText, afterText); + let testIndentAction = (previousLineText: string, beforeText: string, afterText: string, expectedIndentAction: IndentAction | null, expectedAppendText: string | null, removeText: number = 0) => { + let actual = support.onEnter(EditorAutoIndentStrategy.Advanced, previousLineText, beforeText, afterText); if (expectedIndentAction === null) { - assert.equal(actual, null, 'isNull:' + beforeText); + assert.strictEqual(actual, null, 'isNull:' + beforeText); } else { - assert.equal(actual !== null, true, 'isNotNull:' + beforeText); - assert.equal(actual!.indentAction, expectedIndentAction, 'indentAction:' + beforeText); + assert.strictEqual(actual !== null, true, 'isNotNull:' + beforeText); + assert.strictEqual(actual!.indentAction, expectedIndentAction, 'indentAction:' + beforeText); if (expectedAppendText !== null) { - assert.equal(actual!.appendText, expectedAppendText, 'appendText:' + beforeText); + assert.strictEqual(actual!.appendText, expectedAppendText, 'appendText:' + beforeText); } if (removeText !== 0) { - assert.equal(actual!.removeText, removeText, 'removeText:' + beforeText); + assert.strictEqual(actual!.removeText, removeText, 'removeText:' + beforeText); } } }; diff --git a/src/vs/editor/test/common/modes/supports/richEditBrackets.test.ts b/src/vs/editor/test/common/modes/supports/richEditBrackets.test.ts index 40ae7e628..cf6bb9997 100644 --- a/src/vs/editor/test/common/modes/supports/richEditBrackets.test.ts +++ b/src/vs/editor/test/common/modes/supports/richEditBrackets.test.ts @@ -19,61 +19,61 @@ suite('richEditBrackets', () => { test('findPrevBracketInToken one char 1', () => { let result = findPrevBracketInRange(/(\{)|(\})/i, '{', 0, 1); - assert.equal(result!.startColumn, 1); - assert.equal(result!.endColumn, 2); + assert.strictEqual(result!.startColumn, 1); + assert.strictEqual(result!.endColumn, 2); }); test('findPrevBracketInToken one char 2', () => { let result = findPrevBracketInRange(/(\{)|(\})/i, '{{', 0, 1); - assert.equal(result!.startColumn, 1); - assert.equal(result!.endColumn, 2); + assert.strictEqual(result!.startColumn, 1); + assert.strictEqual(result!.endColumn, 2); }); test('findPrevBracketInToken one char 3', () => { let result = findPrevBracketInRange(/(\{)|(\})/i, '{hello world!', 0, 13); - assert.equal(result!.startColumn, 1); - assert.equal(result!.endColumn, 2); + assert.strictEqual(result!.startColumn, 1); + assert.strictEqual(result!.endColumn, 2); }); test('findPrevBracketInToken more chars 1', () => { let result = findPrevBracketInRange(/(olleh)/i, 'hello world!', 0, 12); - assert.equal(result!.startColumn, 1); - assert.equal(result!.endColumn, 6); + assert.strictEqual(result!.startColumn, 1); + assert.strictEqual(result!.endColumn, 6); }); test('findPrevBracketInToken more chars 2', () => { let result = findPrevBracketInRange(/(olleh)/i, 'hello world!', 0, 5); - assert.equal(result!.startColumn, 1); - assert.equal(result!.endColumn, 6); + assert.strictEqual(result!.startColumn, 1); + assert.strictEqual(result!.endColumn, 6); }); test('findPrevBracketInToken more chars 3', () => { let result = findPrevBracketInRange(/(olleh)/i, ' hello world!', 0, 6); - assert.equal(result!.startColumn, 2); - assert.equal(result!.endColumn, 7); + assert.strictEqual(result!.startColumn, 2); + assert.strictEqual(result!.endColumn, 7); }); test('findNextBracketInToken one char', () => { let result = findNextBracketInRange(/(\{)|(\})/i, '{', 0, 1); - assert.equal(result!.startColumn, 1); - assert.equal(result!.endColumn, 2); + assert.strictEqual(result!.startColumn, 1); + assert.strictEqual(result!.endColumn, 2); }); test('findNextBracketInToken more chars', () => { let result = findNextBracketInRange(/(world)/i, 'hello world!', 0, 12); - assert.equal(result!.startColumn, 7); - assert.equal(result!.endColumn, 12); + assert.strictEqual(result!.startColumn, 7); + assert.strictEqual(result!.endColumn, 12); }); test('findNextBracketInToken with emoty result', () => { let result = findNextBracketInRange(/(\{)|(\})/i, '', 0, 0); - assert.equal(result, null); + assert.strictEqual(result, null); }); test('issue #3894: [Handlebars] Curly braces edit issues', () => { let result = findPrevBracketInRange(/(\-\-!<)|(>\-\-)|(\{\{)|(\}\})/i, '{{asd}}', 0, 2); - assert.equal(result!.startColumn, 1); - assert.equal(result!.endColumn, 3); + assert.strictEqual(result!.startColumn, 1); + assert.strictEqual(result!.endColumn, 3); }); }); diff --git a/src/vs/editor/test/common/modes/supports/tokenization.test.ts b/src/vs/editor/test/common/modes/supports/tokenization.test.ts index 9f2bd1b99..a8f904085 100644 --- a/src/vs/editor/test/common/modes/supports/tokenization.test.ts +++ b/src/vs/editor/test/common/modes/supports/tokenization.test.ts @@ -24,7 +24,7 @@ suite('Token theme matching', () => { let actual = theme._match('punctuation.definition.string.begin.html'); - assert.deepEqual(actual, new ThemeTrieElementRule(FontStyle.None, _D, _B)); + assert.deepStrictEqual(actual, new ThemeTrieElementRule(FontStyle.None, _D, _B)); }); test('can match', () => { @@ -55,7 +55,7 @@ suite('Token theme matching', () => { function assertMatch(scopeName: string, expected: ThemeTrieElementRule): void { let actual = theme._match(scopeName); - assert.deepEqual(actual, expected, 'when matching <<' + scopeName + '>>'); + assert.deepStrictEqual(actual, expected, 'when matching <<' + scopeName + '>>'); } function assertSimpleMatch(scopeName: string, fontStyle: FontStyle, foreground: number, background: number): void { @@ -152,7 +152,7 @@ suite('Token theme parsing', () => { new ParsedTokenThemeRule('constant.numeric.dec', 10, FontStyle.None, '0000ff', null), ]; - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); }); }); @@ -162,7 +162,7 @@ suite('Token theme resolving', () => { let actual = ['bar', 'z', 'zu', 'a', 'ab', ''].sort(strcmp); let expected = ['', 'a', 'ab', 'bar', 'z', 'zu']; - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); }); test('always has defaults', () => { @@ -170,8 +170,8 @@ suite('Token theme resolving', () => { let colorMap = new ColorMap(); const _A = colorMap.getId('000000'); const _B = colorMap.getId('ffffff'); - assert.deepEqual(actual.getColorMap(), colorMap.getColorMap()); - assert.deepEqual(actual.getThemeTrieElement(), new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _A, _B))); + assert.deepStrictEqual(actual.getColorMap(), colorMap.getColorMap()); + assert.deepStrictEqual(actual.getThemeTrieElement(), new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _A, _B))); }); test('respects incoming defaults 1', () => { @@ -181,8 +181,8 @@ suite('Token theme resolving', () => { let colorMap = new ColorMap(); const _A = colorMap.getId('000000'); const _B = colorMap.getId('ffffff'); - assert.deepEqual(actual.getColorMap(), colorMap.getColorMap()); - assert.deepEqual(actual.getThemeTrieElement(), new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _A, _B))); + assert.deepStrictEqual(actual.getColorMap(), colorMap.getColorMap()); + assert.deepStrictEqual(actual.getThemeTrieElement(), new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _A, _B))); }); test('respects incoming defaults 2', () => { @@ -192,8 +192,8 @@ suite('Token theme resolving', () => { let colorMap = new ColorMap(); const _A = colorMap.getId('000000'); const _B = colorMap.getId('ffffff'); - assert.deepEqual(actual.getColorMap(), colorMap.getColorMap()); - assert.deepEqual(actual.getThemeTrieElement(), new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _A, _B))); + assert.deepStrictEqual(actual.getColorMap(), colorMap.getColorMap()); + assert.deepStrictEqual(actual.getThemeTrieElement(), new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _A, _B))); }); test('respects incoming defaults 3', () => { @@ -203,8 +203,8 @@ suite('Token theme resolving', () => { let colorMap = new ColorMap(); const _A = colorMap.getId('000000'); const _B = colorMap.getId('ffffff'); - assert.deepEqual(actual.getColorMap(), colorMap.getColorMap()); - assert.deepEqual(actual.getThemeTrieElement(), new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.Bold, _A, _B))); + assert.deepStrictEqual(actual.getColorMap(), colorMap.getColorMap()); + assert.deepStrictEqual(actual.getThemeTrieElement(), new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.Bold, _A, _B))); }); test('respects incoming defaults 4', () => { @@ -214,8 +214,8 @@ suite('Token theme resolving', () => { let colorMap = new ColorMap(); const _A = colorMap.getId('ff0000'); const _B = colorMap.getId('ffffff'); - assert.deepEqual(actual.getColorMap(), colorMap.getColorMap()); - assert.deepEqual(actual.getThemeTrieElement(), new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _A, _B))); + assert.deepStrictEqual(actual.getColorMap(), colorMap.getColorMap()); + assert.deepStrictEqual(actual.getThemeTrieElement(), new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _A, _B))); }); test('respects incoming defaults 5', () => { @@ -225,8 +225,8 @@ suite('Token theme resolving', () => { let colorMap = new ColorMap(); const _A = colorMap.getId('000000'); const _B = colorMap.getId('ff0000'); - assert.deepEqual(actual.getColorMap(), colorMap.getColorMap()); - assert.deepEqual(actual.getThemeTrieElement(), new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _A, _B))); + assert.deepStrictEqual(actual.getColorMap(), colorMap.getColorMap()); + assert.deepStrictEqual(actual.getThemeTrieElement(), new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _A, _B))); }); test('can merge incoming defaults', () => { @@ -238,8 +238,8 @@ suite('Token theme resolving', () => { let colorMap = new ColorMap(); const _A = colorMap.getId('00ff00'); const _B = colorMap.getId('ff0000'); - assert.deepEqual(actual.getColorMap(), colorMap.getColorMap()); - assert.deepEqual(actual.getThemeTrieElement(), new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.Bold, _A, _B))); + assert.deepStrictEqual(actual.getColorMap(), colorMap.getColorMap()); + assert.deepStrictEqual(actual.getThemeTrieElement(), new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.Bold, _A, _B))); }); test('defaults are inherited', () => { @@ -251,7 +251,7 @@ suite('Token theme resolving', () => { const _A = colorMap.getId('F8F8F2'); const _B = colorMap.getId('272822'); const _C = colorMap.getId('ff0000'); - assert.deepEqual(actual.getColorMap(), colorMap.getColorMap()); + assert.deepStrictEqual(actual.getColorMap(), colorMap.getColorMap()); let root = new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _A, _B), { 'var': new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _C, _B)) }); @@ -268,7 +268,7 @@ suite('Token theme resolving', () => { const _A = colorMap.getId('F8F8F2'); const _B = colorMap.getId('272822'); const _C = colorMap.getId('ff0000'); - assert.deepEqual(actual.getColorMap(), colorMap.getColorMap()); + assert.deepStrictEqual(actual.getColorMap(), colorMap.getColorMap()); let root = new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _A, _B), { 'var': new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.Bold, _C, _B)) }); @@ -286,7 +286,7 @@ suite('Token theme resolving', () => { const _B = colorMap.getId('272822'); const _C = colorMap.getId('ff0000'); const _D = colorMap.getId('00ff00'); - assert.deepEqual(actual.getColorMap(), colorMap.getColorMap()); + assert.deepStrictEqual(actual.getColorMap(), colorMap.getColorMap()); let root = new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _A, _B), { 'var': new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.Bold, _C, _B), { 'identifier': new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.Bold, _D, _B)) @@ -314,7 +314,7 @@ suite('Token theme resolving', () => { const _E = colorMap.getId('300000'); const _F = colorMap.getId('ff0000'); const _G = colorMap.getId('00ff00'); - assert.deepEqual(actual.getColorMap(), colorMap.getColorMap()); + assert.deepStrictEqual(actual.getColorMap(), colorMap.getColorMap()); let root = new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _A, _B), { 'var': new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.Bold, _F, _B), { 'identifier': new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.Bold, _G, _B)) @@ -341,6 +341,6 @@ suite('Token theme resolving', () => { colorMap.getId('FFFFFF'); colorMap.getId('0F0F0F'); colorMap.getId('F8F8F2'); - assert.deepEqual(actual.getColorMap(), colorMap.getColorMap()); + assert.deepStrictEqual(actual.getColorMap(), colorMap.getColorMap()); }); }); diff --git a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts index fb4c4028b..aaa8f060f 100644 --- a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts +++ b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts @@ -31,7 +31,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ]; let expectedStr = `
    ${toStr(expected)}
    `; - assert.equal(actual, expectedStr); + assert.strictEqual(actual, expectedStr); mode.dispose(); }); @@ -61,7 +61,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { let expectedStr2 = toStr(expected2); let expectedStr = `
    ${expectedStr1}
    ${expectedStr2}
    `; - assert.equal(actual, expectedStr); + assert.strictEqual(actual, expectedStr); mode.dispose(); }); @@ -104,7 +104,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ]); const colorMap = [null!, '#000000', '#ffffff', '#ff0000', '#00ff00', '#0000ff']; - assert.equal( + assert.strictEqual( tokenizeLineToHTML(text, lineTokens, colorMap, 0, 17, 4, true), [ '
    ', @@ -117,7 +117,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ].join('') ); - assert.equal( + assert.strictEqual( tokenizeLineToHTML(text, lineTokens, colorMap, 0, 12, 4, true), [ '
    ', @@ -130,7 +130,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ].join('') ); - assert.equal( + assert.strictEqual( tokenizeLineToHTML(text, lineTokens, colorMap, 0, 11, 4, true), [ '
    ', @@ -142,7 +142,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ].join('') ); - assert.equal( + assert.strictEqual( tokenizeLineToHTML(text, lineTokens, colorMap, 1, 11, 4, true), [ '
    ', @@ -154,7 +154,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ].join('') ); - assert.equal( + assert.strictEqual( tokenizeLineToHTML(text, lineTokens, colorMap, 4, 11, 4, true), [ '
    ', @@ -165,7 +165,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ].join('') ); - assert.equal( + assert.strictEqual( tokenizeLineToHTML(text, lineTokens, colorMap, 5, 11, 4, true), [ '
    ', @@ -175,7 +175,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ].join('') ); - assert.equal( + assert.strictEqual( tokenizeLineToHTML(text, lineTokens, colorMap, 5, 10, 4, true), [ '
    ', @@ -184,7 +184,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ].join('') ); - assert.equal( + assert.strictEqual( tokenizeLineToHTML(text, lineTokens, colorMap, 6, 9, 4, true), [ '
    ', @@ -237,7 +237,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ]); const colorMap = [null!, '#000000', '#ffffff', '#ff0000', '#00ff00', '#0000ff']; - assert.equal( + assert.strictEqual( tokenizeLineToHTML(text, lineTokens, colorMap, 0, 21, 4, true), [ '
    ', @@ -251,7 +251,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ].join('') ); - assert.equal( + assert.strictEqual( tokenizeLineToHTML(text, lineTokens, colorMap, 0, 17, 4, true), [ '
    ', @@ -265,7 +265,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ].join('') ); - assert.equal( + assert.strictEqual( tokenizeLineToHTML(text, lineTokens, colorMap, 0, 3, 4, true), [ '
    ', @@ -287,7 +287,7 @@ class Mode extends MockMode { this._register(TokenizationRegistry.register(this.getId(), { getInitialState: (): IState => null!, tokenize: undefined!, - tokenize2: (line: string, state: IState): TokenizationResult2 => { + tokenize2: (line: string, hasEOL: boolean, state: IState): TokenizationResult2 => { let tokensArr: number[] = []; let prevColor: ColorId = -1; for (let i = 0; i < line.length; i++) { diff --git a/src/vs/editor/test/common/services/editorSimpleWorker.test.ts b/src/vs/editor/test/common/services/editorSimpleWorker.test.ts index 54f73ed0e..bcfafe9d0 100644 --- a/src/vs/editor/test/common/services/editorSimpleWorker.test.ts +++ b/src/vs/editor/test/common/services/editorSimpleWorker.test.ts @@ -43,13 +43,13 @@ suite('EditorSimpleWorker', () => { function assertPositionAt(offset: number, line: number, column: number) { let position = model.positionAt(offset); - assert.equal(position.lineNumber, line); - assert.equal(position.column, column); + assert.strictEqual(position.lineNumber, line); + assert.strictEqual(position.column, column); } function assertOffsetAt(lineNumber: number, column: number, offset: number) { let actual = model.offsetAt({ lineNumber, column }); - assert.equal(actual, offset); + assert.strictEqual(actual, offset); } test('ICommonModel#offsetAt', () => { @@ -83,16 +83,16 @@ suite('EditorSimpleWorker', () => { test('ICommonModel#validatePosition, issue #15882', function () { let model = worker.addModel(['{"id": "0001","type": "donut","name": "Cake","image":{"url": "images/0001.jpg","width": 200,"height": 200},"thumbnail":{"url": "images/thumbnails/0001.jpg","width": 32,"height": 32}}']); - assert.equal(model.offsetAt({ lineNumber: 1, column: 2 }), 1); + assert.strictEqual(model.offsetAt({ lineNumber: 1, column: 2 }), 1); }); test('MoreMinimal', () => { return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: 'This is line One', range: new Range(1, 1, 1, 17) }]).then(edits => { - assert.equal(edits.length, 1); + assert.strictEqual(edits.length, 1); const [first] = edits; - assert.equal(first.text, 'O'); - assert.deepEqual(first.range, { startLineNumber: 1, startColumn: 14, endLineNumber: 1, endColumn: 15 }); + assert.strictEqual(first.text, 'O'); + assert.deepStrictEqual(first.range, { startLineNumber: 1, startColumn: 14, endLineNumber: 1, endColumn: 15 }); }); }); @@ -105,7 +105,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) }]).then(edits => { - assert.equal(edits.length, 0); + assert.strictEqual(edits.length, 0); }); }); @@ -118,10 +118,10 @@ suite('EditorSimpleWorker', () => { ], '\n'); return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: '{\r\n\t"b":1\r\n}', range: new Range(1, 1, 3, 2) }]).then(edits => { - assert.equal(edits.length, 1); + assert.strictEqual(edits.length, 1); const [first] = edits; - assert.equal(first.text, 'b'); - assert.deepEqual(first.range, { startLineNumber: 2, startColumn: 3, endLineNumber: 2, endColumn: 4 }); + assert.strictEqual(first.text, 'b'); + assert.deepStrictEqual(first.range, { startLineNumber: 2, startColumn: 3, endLineNumber: 2, endColumn: 4 }); }); }); @@ -134,10 +134,10 @@ suite('EditorSimpleWorker', () => { ]); return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: '\n', range: new Range(3, 2, 4, 1000) }]).then(edits => { - assert.equal(edits.length, 1); + assert.strictEqual(edits.length, 1); const [first] = edits; - assert.equal(first.text, '\n'); - assert.deepEqual(first.range, { startLineNumber: 3, startColumn: 2, endLineNumber: 3, endColumn: 2 }); + assert.strictEqual(first.text, '\n'); + assert.deepStrictEqual(first.range, { startLineNumber: 3, startColumn: 2, endLineNumber: 3, endColumn: 2 }); }); }); @@ -151,7 +151,7 @@ suite('EditorSimpleWorker', () => { ]); const value = model.getValueInRange({ startLineNumber: 3, startColumn: 1, endLineNumber: 4, endColumn: 1 }); - assert.equal(value, '}'); + assert.strictEqual(value, '}'); }); @@ -165,11 +165,10 @@ suite('EditorSimpleWorker', () => { return worker.textualSuggest([model.uri.toString()], 'f', '[a-z]+', 'img').then((result) => { if (!result) { assert.ok(false); - return; } - assert.equal(result.words.length, 1); - assert.equal(typeof result.duration, 'number'); - assert.equal(result.words[0], 'foobar'); + assert.strictEqual(result.words.length, 1); + assert.strictEqual(typeof result.duration, 'number'); + assert.strictEqual(result.words[0], 'foobar'); }); }); @@ -187,6 +186,6 @@ suite('EditorSimpleWorker', () => { let words: string[] = [...model.words(/[a-z]+/img)]; - assert.deepEqual(words, ['one', 'line', 'two', 'line', 'past', 'empty', 'single', 'and', 'now', 'we', 'are', 'done']); + assert.deepStrictEqual(words, ['one', 'line', 'two', 'line', 'past', 'empty', 'single', 'and', 'now', 'we', 'are', 'done']); }); }); diff --git a/src/vs/editor/test/common/services/languagesRegistry.test.ts b/src/vs/editor/test/common/services/languagesRegistry.test.ts index 09ef74cd0..d0eac05b5 100644 --- a/src/vs/editor/test/common/services/languagesRegistry.test.ts +++ b/src/vs/editor/test/common/services/languagesRegistry.test.ts @@ -19,7 +19,7 @@ suite('LanguagesRegistry', () => { mimetypes: ['outputModeMimeType'], }]); - assert.deepEqual(registry.getRegisteredLanguageNames(), []); + assert.deepStrictEqual(registry.getRegisteredLanguageNames(), []); }); test('mode with alias does have a name', () => { @@ -32,8 +32,8 @@ suite('LanguagesRegistry', () => { mimetypes: ['bla'], }]); - assert.deepEqual(registry.getRegisteredLanguageNames(), ['ModeName']); - assert.deepEqual(registry.getLanguageName('modeId'), 'ModeName'); + assert.deepStrictEqual(registry.getRegisteredLanguageNames(), ['ModeName']); + assert.deepStrictEqual(registry.getLanguageName('modeId'), 'ModeName'); }); test('mode without alias gets a name', () => { @@ -45,8 +45,8 @@ suite('LanguagesRegistry', () => { mimetypes: ['bla'], }]); - assert.deepEqual(registry.getRegisteredLanguageNames(), ['modeId']); - assert.deepEqual(registry.getLanguageName('modeId'), 'modeId'); + assert.deepStrictEqual(registry.getRegisteredLanguageNames(), ['modeId']); + assert.deepStrictEqual(registry.getLanguageName('modeId'), 'modeId'); }); test('bug #4360: f# not shown in status bar', () => { @@ -66,8 +66,8 @@ suite('LanguagesRegistry', () => { mimetypes: ['bla'], }]); - assert.deepEqual(registry.getRegisteredLanguageNames(), ['ModeName']); - assert.deepEqual(registry.getLanguageName('modeId'), 'ModeName'); + assert.deepStrictEqual(registry.getRegisteredLanguageNames(), ['ModeName']); + assert.deepStrictEqual(registry.getLanguageName('modeId'), 'ModeName'); }); test('issue #5278: Extension cannot override language name anymore', () => { @@ -87,8 +87,8 @@ suite('LanguagesRegistry', () => { mimetypes: ['bla'], }]); - assert.deepEqual(registry.getRegisteredLanguageNames(), ['BetterModeName']); - assert.deepEqual(registry.getLanguageName('modeId'), 'BetterModeName'); + assert.deepStrictEqual(registry.getRegisteredLanguageNames(), ['BetterModeName']); + assert.deepStrictEqual(registry.getLanguageName('modeId'), 'BetterModeName'); }); test('mimetypes are generated if necessary', () => { @@ -98,7 +98,7 @@ suite('LanguagesRegistry', () => { id: 'modeId' }]); - assert.deepEqual(registry.getMimeForMode('modeId'), 'text/x-modeId'); + assert.deepStrictEqual(registry.getMimeForMode('modeId'), 'text/x-modeId'); }); test('first mimetype wins', () => { @@ -109,7 +109,7 @@ suite('LanguagesRegistry', () => { mimetypes: ['text/modeId', 'text/modeId2'] }]); - assert.deepEqual(registry.getMimeForMode('modeId'), 'text/modeId'); + assert.deepStrictEqual(registry.getMimeForMode('modeId'), 'text/modeId'); }); test('first mimetype wins 2', () => { @@ -124,7 +124,7 @@ suite('LanguagesRegistry', () => { mimetypes: ['text/modeId'] }]); - assert.deepEqual(registry.getMimeForMode('modeId'), 'text/x-modeId'); + assert.deepStrictEqual(registry.getMimeForMode('modeId'), 'text/x-modeId'); }); test('aliases', () => { @@ -134,42 +134,42 @@ suite('LanguagesRegistry', () => { id: 'a' }]); - assert.deepEqual(registry.getRegisteredLanguageNames(), ['a']); - assert.deepEqual(registry.getModeIdsFromLanguageName('a'), ['a']); - assert.deepEqual(registry.getModeIdForLanguageNameLowercase('a'), 'a'); - assert.deepEqual(registry.getLanguageName('a'), 'a'); + assert.deepStrictEqual(registry.getRegisteredLanguageNames(), ['a']); + assert.deepStrictEqual(registry.getModeIdsFromLanguageName('a'), ['a']); + assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('a'), 'a'); + assert.deepStrictEqual(registry.getLanguageName('a'), 'a'); registry._registerLanguages([{ id: 'a', aliases: ['A1', 'A2'] }]); - assert.deepEqual(registry.getRegisteredLanguageNames(), ['A1']); - assert.deepEqual(registry.getModeIdsFromLanguageName('a'), []); - assert.deepEqual(registry.getModeIdsFromLanguageName('A1'), ['a']); - assert.deepEqual(registry.getModeIdsFromLanguageName('A2'), []); - assert.deepEqual(registry.getModeIdForLanguageNameLowercase('a'), 'a'); - assert.deepEqual(registry.getModeIdForLanguageNameLowercase('a1'), 'a'); - assert.deepEqual(registry.getModeIdForLanguageNameLowercase('a2'), 'a'); - assert.deepEqual(registry.getLanguageName('a'), 'A1'); + assert.deepStrictEqual(registry.getRegisteredLanguageNames(), ['A1']); + assert.deepStrictEqual(registry.getModeIdsFromLanguageName('a'), []); + assert.deepStrictEqual(registry.getModeIdsFromLanguageName('A1'), ['a']); + assert.deepStrictEqual(registry.getModeIdsFromLanguageName('A2'), []); + assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('a'), 'a'); + assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('a1'), 'a'); + assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('a2'), 'a'); + assert.deepStrictEqual(registry.getLanguageName('a'), 'A1'); registry._registerLanguages([{ id: 'a', aliases: ['A3', 'A4'] }]); - assert.deepEqual(registry.getRegisteredLanguageNames(), ['A3']); - assert.deepEqual(registry.getModeIdsFromLanguageName('a'), []); - assert.deepEqual(registry.getModeIdsFromLanguageName('A1'), []); - assert.deepEqual(registry.getModeIdsFromLanguageName('A2'), []); - assert.deepEqual(registry.getModeIdsFromLanguageName('A3'), ['a']); - assert.deepEqual(registry.getModeIdsFromLanguageName('A4'), []); - assert.deepEqual(registry.getModeIdForLanguageNameLowercase('a'), 'a'); - assert.deepEqual(registry.getModeIdForLanguageNameLowercase('a1'), 'a'); - assert.deepEqual(registry.getModeIdForLanguageNameLowercase('a2'), 'a'); - assert.deepEqual(registry.getModeIdForLanguageNameLowercase('a3'), 'a'); - assert.deepEqual(registry.getModeIdForLanguageNameLowercase('a4'), 'a'); - assert.deepEqual(registry.getLanguageName('a'), 'A3'); + assert.deepStrictEqual(registry.getRegisteredLanguageNames(), ['A3']); + assert.deepStrictEqual(registry.getModeIdsFromLanguageName('a'), []); + assert.deepStrictEqual(registry.getModeIdsFromLanguageName('A1'), []); + assert.deepStrictEqual(registry.getModeIdsFromLanguageName('A2'), []); + assert.deepStrictEqual(registry.getModeIdsFromLanguageName('A3'), ['a']); + assert.deepStrictEqual(registry.getModeIdsFromLanguageName('A4'), []); + assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('a'), 'a'); + assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('a1'), 'a'); + assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('a2'), 'a'); + assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('a3'), 'a'); + assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('a4'), 'a'); + assert.deepStrictEqual(registry.getLanguageName('a'), 'A3'); }); test('empty aliases array means no alias', () => { @@ -179,23 +179,23 @@ suite('LanguagesRegistry', () => { id: 'a' }]); - assert.deepEqual(registry.getRegisteredLanguageNames(), ['a']); - assert.deepEqual(registry.getModeIdsFromLanguageName('a'), ['a']); - assert.deepEqual(registry.getModeIdForLanguageNameLowercase('a'), 'a'); - assert.deepEqual(registry.getLanguageName('a'), 'a'); + assert.deepStrictEqual(registry.getRegisteredLanguageNames(), ['a']); + assert.deepStrictEqual(registry.getModeIdsFromLanguageName('a'), ['a']); + assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('a'), 'a'); + assert.deepStrictEqual(registry.getLanguageName('a'), 'a'); registry._registerLanguages([{ id: 'b', aliases: [] }]); - assert.deepEqual(registry.getRegisteredLanguageNames(), ['a']); - assert.deepEqual(registry.getModeIdsFromLanguageName('a'), ['a']); - assert.deepEqual(registry.getModeIdsFromLanguageName('b'), []); - assert.deepEqual(registry.getModeIdForLanguageNameLowercase('a'), 'a'); - assert.deepEqual(registry.getModeIdForLanguageNameLowercase('b'), 'b'); - assert.deepEqual(registry.getLanguageName('a'), 'a'); - assert.deepEqual(registry.getLanguageName('b'), null); + assert.deepStrictEqual(registry.getRegisteredLanguageNames(), ['a']); + assert.deepStrictEqual(registry.getModeIdsFromLanguageName('a'), ['a']); + assert.deepStrictEqual(registry.getModeIdsFromLanguageName('b'), []); + assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('a'), 'a'); + assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('b'), 'b'); + assert.deepStrictEqual(registry.getLanguageName('a'), 'a'); + assert.deepStrictEqual(registry.getLanguageName('b'), null); }); test('extensions', () => { @@ -207,18 +207,18 @@ suite('LanguagesRegistry', () => { extensions: ['aExt'] }]); - assert.deepEqual(registry.getExtensions('a'), []); - assert.deepEqual(registry.getExtensions('aname'), []); - assert.deepEqual(registry.getExtensions('aName'), ['aExt']); + assert.deepStrictEqual(registry.getExtensions('a'), []); + assert.deepStrictEqual(registry.getExtensions('aname'), []); + assert.deepStrictEqual(registry.getExtensions('aName'), ['aExt']); registry._registerLanguages([{ id: 'a', extensions: ['aExt2'] }]); - assert.deepEqual(registry.getExtensions('a'), []); - assert.deepEqual(registry.getExtensions('aname'), []); - assert.deepEqual(registry.getExtensions('aName'), ['aExt', 'aExt2']); + assert.deepStrictEqual(registry.getExtensions('a'), []); + assert.deepStrictEqual(registry.getExtensions('aname'), []); + assert.deepStrictEqual(registry.getExtensions('aName'), ['aExt', 'aExt2']); }); test('extensions of primary language registration come first', () => { @@ -229,7 +229,7 @@ suite('LanguagesRegistry', () => { extensions: ['aExt3'] }]); - assert.deepEqual(registry.getExtensions('a')[0], 'aExt3'); + assert.deepStrictEqual(registry.getExtensions('a')[0], 'aExt3'); registry._registerLanguages([{ id: 'a', @@ -237,14 +237,14 @@ suite('LanguagesRegistry', () => { extensions: ['aExt'] }]); - assert.deepEqual(registry.getExtensions('a')[0], 'aExt'); + assert.deepStrictEqual(registry.getExtensions('a')[0], 'aExt'); registry._registerLanguages([{ id: 'a', extensions: ['aExt2'] }]); - assert.deepEqual(registry.getExtensions('a')[0], 'aExt'); + assert.deepStrictEqual(registry.getExtensions('a')[0], 'aExt'); }); test('filenames', () => { @@ -256,18 +256,18 @@ suite('LanguagesRegistry', () => { filenames: ['aFilename'] }]); - assert.deepEqual(registry.getFilenames('a'), []); - assert.deepEqual(registry.getFilenames('aname'), []); - assert.deepEqual(registry.getFilenames('aName'), ['aFilename']); + assert.deepStrictEqual(registry.getFilenames('a'), []); + assert.deepStrictEqual(registry.getFilenames('aname'), []); + assert.deepStrictEqual(registry.getFilenames('aName'), ['aFilename']); registry._registerLanguages([{ id: 'a', filenames: ['aFilename2'] }]); - assert.deepEqual(registry.getFilenames('a'), []); - assert.deepEqual(registry.getFilenames('aname'), []); - assert.deepEqual(registry.getFilenames('aName'), ['aFilename', 'aFilename2']); + assert.deepStrictEqual(registry.getFilenames('a'), []); + assert.deepStrictEqual(registry.getFilenames('aname'), []); + assert.deepStrictEqual(registry.getFilenames('aName'), ['aFilename', 'aFilename2']); }); test('configuration', () => { @@ -279,17 +279,17 @@ suite('LanguagesRegistry', () => { configuration: URI.file('/path/to/aFilename') }]); - assert.deepEqual(registry.getConfigurationFiles('a'), [URI.file('/path/to/aFilename')]); - assert.deepEqual(registry.getConfigurationFiles('aname'), []); - assert.deepEqual(registry.getConfigurationFiles('aName'), []); + assert.deepStrictEqual(registry.getConfigurationFiles('a'), [URI.file('/path/to/aFilename')]); + assert.deepStrictEqual(registry.getConfigurationFiles('aname'), []); + assert.deepStrictEqual(registry.getConfigurationFiles('aName'), []); registry._registerLanguages([{ id: 'a', configuration: URI.file('/path/to/aFilename2') }]); - assert.deepEqual(registry.getConfigurationFiles('a'), [URI.file('/path/to/aFilename'), URI.file('/path/to/aFilename2')]); - assert.deepEqual(registry.getConfigurationFiles('aname'), []); - assert.deepEqual(registry.getConfigurationFiles('aName'), []); + assert.deepStrictEqual(registry.getConfigurationFiles('a'), [URI.file('/path/to/aFilename'), URI.file('/path/to/aFilename2')]); + assert.deepStrictEqual(registry.getConfigurationFiles('aname'), []); + assert.deepStrictEqual(registry.getConfigurationFiles('aName'), []); }); }); diff --git a/src/vs/editor/test/common/services/modelService.test.ts b/src/vs/editor/test/common/services/modelService.test.ts index 52672ffe3..23e0f8f2a 100644 --- a/src/vs/editor/test/common/services/modelService.test.ts +++ b/src/vs/editor/test/common/services/modelService.test.ts @@ -11,18 +11,26 @@ import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { createStringBuilder } from 'vs/editor/common/core/stringBuilder'; -import { DefaultEndOfLine } from 'vs/editor/common/model'; +import { DefaultEndOfLine, ITextModel } from 'vs/editor/common/model'; import { createTextBuffer } from 'vs/editor/common/model/textModel'; -import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ModelSemanticColoring, ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; +import { TestColorTheme, TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { NullLogService } from 'vs/platform/log/common/log'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DocumentSemanticTokensProvider, DocumentSemanticTokensProviderRegistry, SemanticTokens, SemanticTokensEdits, SemanticTokensLegend } from 'vs/editor/common/modes'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Barrier, timeout } from 'vs/base/common/async'; +import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; +import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/testTextResourcePropertiesService'; const GENERATE_TESTS = false; @@ -47,9 +55,9 @@ suite('ModelService', () => { const model2 = modelService.createModel('farboo', null, URI.file(platform.isWindows ? 'c:\\myroot\\myfile.txt' : '/myroot/myfile.txt')); const model3 = modelService.createModel('farboo', null, URI.file(platform.isWindows ? 'c:\\other\\myfile.txt' : '/other/myfile.txt')); - assert.equal(model1.getOptions().defaultEOL, DefaultEndOfLine.LF); - assert.equal(model2.getOptions().defaultEOL, DefaultEndOfLine.CRLF); - assert.equal(model3.getOptions().defaultEOL, DefaultEndOfLine.LF); + assert.strictEqual(model1.getOptions().defaultEOL, DefaultEndOfLine.LF); + assert.strictEqual(model2.getOptions().defaultEOL, DefaultEndOfLine.CRLF); + assert.strictEqual(model3.getOptions().defaultEOL, DefaultEndOfLine.LF); }); test('_computeEdits no change', function () { @@ -71,11 +79,11 @@ suite('ModelService', () => { 'and finished with the fourth.', //29 ].join('\n'), DefaultEndOfLine.LF - ); + ).textBuffer; const actual = ModelServiceImpl._computeEdits(model, textBuffer); - assert.deepEqual(actual, []); + assert.deepStrictEqual(actual, []); }); test('_computeEdits first line changed', function () { @@ -97,11 +105,11 @@ suite('ModelService', () => { 'and finished with the fourth.', //29 ].join('\n'), DefaultEndOfLine.LF - ); + ).textBuffer; const actual = ModelServiceImpl._computeEdits(model, textBuffer); - assert.deepEqual(actual, [ + assert.deepStrictEqual(actual, [ EditOperation.replaceMove(new Range(1, 1, 2, 1), 'This is line One\n') ]); }); @@ -125,11 +133,11 @@ suite('ModelService', () => { 'and finished with the fourth.', //29 ].join('\r\n'), DefaultEndOfLine.LF - ); + ).textBuffer; const actual = ModelServiceImpl._computeEdits(model, textBuffer); - assert.deepEqual(actual, []); + assert.deepStrictEqual(actual, []); }); test('_computeEdits EOL and other change 1', function () { @@ -151,11 +159,11 @@ suite('ModelService', () => { 'and finished with the fourth.', //29 ].join('\r\n'), DefaultEndOfLine.LF - ); + ).textBuffer; const actual = ModelServiceImpl._computeEdits(model, textBuffer); - assert.deepEqual(actual, [ + assert.deepStrictEqual(actual, [ EditOperation.replaceMove( new Range(1, 1, 4, 1), [ @@ -186,11 +194,11 @@ suite('ModelService', () => { '' ].join('\r\n'), DefaultEndOfLine.LF - ); + ).textBuffer; const actual = ModelServiceImpl._computeEdits(model, textBuffer); - assert.deepEqual(actual, [ + assert.deepStrictEqual(actual, [ EditOperation.replaceMove(new Range(3, 2, 3, 2), '\r\n') ]); }); @@ -317,7 +325,7 @@ suite('ModelService', () => { const model1 = modelService.createModel('text', null, resource); // make an edit model1.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]); - assert.equal(model1.getValue(), 'text1'); + assert.strictEqual(model1.getValue(), 'text1'); // dispose it modelService.destroyModel(resource); @@ -325,7 +333,7 @@ suite('ModelService', () => { const model2 = modelService.createModel('text1', null, resource); // undo model2.undo(); - assert.equal(model2.getValue(), 'text'); + assert.strictEqual(model2.getValue(), 'text'); }); test('maintains version id and alternative version id for same resource and same content', () => { @@ -335,7 +343,7 @@ suite('ModelService', () => { const model1 = modelService.createModel('text', null, resource); // make an edit model1.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]); - assert.equal(model1.getValue(), 'text1'); + assert.strictEqual(model1.getValue(), 'text1'); const versionId = model1.getVersionId(); const alternativeVersionId = model1.getAlternativeVersionId(); // dispose it @@ -343,8 +351,8 @@ suite('ModelService', () => { // create a new model with the same content const model2 = modelService.createModel('text1', null, resource); - assert.equal(model2.getVersionId(), versionId); - assert.equal(model2.getAlternativeVersionId(), alternativeVersionId); + assert.strictEqual(model2.getVersionId(), versionId); + assert.strictEqual(model2.getAlternativeVersionId(), alternativeVersionId); }); test('does not maintain undo for same resource and different content', () => { @@ -354,7 +362,7 @@ suite('ModelService', () => { const model1 = modelService.createModel('text', null, resource); // make an edit model1.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]); - assert.equal(model1.getValue(), 'text1'); + assert.strictEqual(model1.getValue(), 'text1'); // dispose it modelService.destroyModel(resource); @@ -362,7 +370,7 @@ suite('ModelService', () => { const model2 = modelService.createModel('text2', null, resource); // undo model2.undo(); - assert.equal(model2.getValue(), 'text2'); + assert.strictEqual(model2.getValue(), 'text2'); }); test('setValue should clear undo stack', () => { @@ -370,17 +378,98 @@ suite('ModelService', () => { const model = modelService.createModel('text', null, resource); model.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]); - assert.equal(model.getValue(), 'text1'); + assert.strictEqual(model.getValue(), 'text1'); model.setValue('text2'); model.undo(); - assert.equal(model.getValue(), 'text2'); + assert.strictEqual(model.getValue(), 'text2'); + }); +}); + +suite('ModelSemanticColoring', () => { + + const disposables = new DisposableStore(); + const ORIGINAL_FETCH_DOCUMENT_SEMANTIC_TOKENS_DELAY = ModelSemanticColoring.FETCH_DOCUMENT_SEMANTIC_TOKENS_DELAY; + let modelService: IModelService; + let modeService: IModeService; + + setup(() => { + ModelSemanticColoring.FETCH_DOCUMENT_SEMANTIC_TOKENS_DELAY = 0; + + const configService = new TestConfigurationService({ editor: { semanticHighlighting: true } }); + const themeService = new TestThemeService(); + themeService.setTheme(new TestColorTheme({}, ColorScheme.DARK, true)); + modelService = disposables.add(new ModelServiceImpl( + configService, + new TestTextResourcePropertiesService(configService), + themeService, + new NullLogService(), + new UndoRedoService(new TestDialogService(), new TestNotificationService()) + )); + modeService = disposables.add(new ModeServiceImpl(false)); + }); + + teardown(() => { + disposables.clear(); + ModelSemanticColoring.FETCH_DOCUMENT_SEMANTIC_TOKENS_DELAY = ORIGINAL_FETCH_DOCUMENT_SEMANTIC_TOKENS_DELAY; + }); + + test('DocumentSemanticTokens should be fetched when the result is empty if there are pending changes', async () => { + + disposables.add(ModesRegistry.registerLanguage({ id: 'testMode' })); + + const inFirstCall = new Barrier(); + const delayFirstResult = new Barrier(); + const secondResultProvided = new Barrier(); + let callCount = 0; + + disposables.add(DocumentSemanticTokensProviderRegistry.register('testMode', new class implements DocumentSemanticTokensProvider { + getLegend(): SemanticTokensLegend { + return { tokenTypes: ['class'], tokenModifiers: [] }; + } + async provideDocumentSemanticTokens(model: ITextModel, lastResultId: string | null, token: CancellationToken): Promise { + callCount++; + if (callCount === 1) { + assert.ok('called once'); + inFirstCall.open(); + await delayFirstResult.wait(); + await timeout(0); // wait for the simple scheduler to fire to check that we do actually get rescheduled + return null; + } + if (callCount === 2) { + assert.ok('called twice'); + secondResultProvided.open(); + return null; + } + assert.fail('Unexpected call'); + } + releaseDocumentSemanticTokens(resultId: string | undefined): void { + } + })); + + const textModel = disposables.add(modelService.createModel('Hello world', modeService.create('testMode'))); + + // wait for the provider to be called + await inFirstCall.wait(); + + // the provider is now in the provide call + // change the text buffer while the provider is running + textModel.applyEdits([{ range: new Range(1, 1, 1, 1), text: 'x' }]); + + // let the provider finish its first result + delayFirstResult.open(); + + // we need to check that the provider is called again, even if it returns null + await secondResultProvided.wait(); + + // assert that it got called twice + assert.strictEqual(callCount, 2); }); }); function assertComputeEdits(lines1: string[], lines2: string[]): void { const model = createTextModel(lines1.join('\n')); - const textBuffer = createTextBuffer(lines2.join('\n'), DefaultEndOfLine.LF); + const textBuffer = createTextBuffer(lines2.join('\n'), DefaultEndOfLine.LF).textBuffer; // compute required edits // let start = Date.now(); @@ -390,7 +479,7 @@ function assertComputeEdits(lines1: string[], lines2: string[]): void { // apply edits model.pushEditOperations([], edits, null); - assert.equal(model.getValue(), lines2.join('\n')); + assert.strictEqual(model.getValue(), lines2.join('\n')); } function getRandomInt(min: number, max: number): number { @@ -439,21 +528,3 @@ assertComputeEdits(file1, file2); } } } - -export class TestTextResourcePropertiesService implements ITextResourcePropertiesService { - - declare readonly _serviceBrand: undefined; - - constructor( - @IConfigurationService private readonly configurationService: IConfigurationService, - ) { - } - - getEOL(resource: URI, language?: string): string { - const eol = this.configurationService.getValue('files.eol', { overrideIdentifier: language, resource }); - if (eol && eol !== 'auto') { - return eol; - } - return (platform.isLinux || platform.isMacintosh) ? '\n' : '\r\n'; - } -} diff --git a/src/vs/workbench/test/common/api/semanticTokensDto.test.ts b/src/vs/editor/test/common/services/semanticTokensDto.test.ts similarity index 95% rename from src/vs/workbench/test/common/api/semanticTokensDto.test.ts rename to src/vs/editor/test/common/services/semanticTokensDto.test.ts index 091bc24e2..9cedebd29 100644 --- a/src/vs/workbench/test/common/api/semanticTokensDto.test.ts +++ b/src/vs/editor/test/common/services/semanticTokensDto.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IFullSemanticTokensDto, IDeltaSemanticTokensDto, encodeSemanticTokensDto, ISemanticTokensDto, decodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokensDto'; +import { IFullSemanticTokensDto, IDeltaSemanticTokensDto, encodeSemanticTokensDto, ISemanticTokensDto, decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto'; import { VSBuffer } from 'vs/base/common/buffer'; suite('SemanticTokensDto', () => { @@ -25,7 +25,7 @@ suite('SemanticTokensDto', () => { data: toArr(dto.data) }; }; - assert.deepEqual(convert(actual), convert(expected)); + assert.deepStrictEqual(convert(actual), convert(expected)); } function assertEqualDelta(actual: IDeltaSemanticTokensDto, expected: IDeltaSemanticTokensDto): void { @@ -46,7 +46,7 @@ suite('SemanticTokensDto', () => { deltas: dto.deltas.map(convertOne) }; }; - assert.deepEqual(convert(actual), convert(expected)); + assert.deepStrictEqual(convert(actual), convert(expected)); } function testRoundTrip(value: ISemanticTokensDto): void { diff --git a/src/vs/editor/test/common/services/testTextResourcePropertiesService.ts b/src/vs/editor/test/common/services/testTextResourcePropertiesService.ts new file mode 100644 index 000000000..f5ac31d5f --- /dev/null +++ b/src/vs/editor/test/common/services/testTextResourcePropertiesService.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 platform from 'vs/base/common/platform'; +import { URI } from 'vs/base/common/uri'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; + +export class TestTextResourcePropertiesService implements ITextResourcePropertiesService { + + declare readonly _serviceBrand: undefined; + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + ) { + } + + getEOL(resource: URI, language?: string): string { + const eol = this.configurationService.getValue('files.eol', { overrideIdentifier: language, resource }); + if (eol && eol !== 'auto') { + return eol; + } + return (platform.isLinux || platform.isMacintosh) ? '\n' : '\r\n'; + } +} diff --git a/src/vs/editor/test/common/view/overviewZoneManager.test.ts b/src/vs/editor/test/common/view/overviewZoneManager.test.ts index 39c104fbb..ee8c8ed7d 100644 --- a/src/vs/editor/test/common/view/overviewZoneManager.test.ts +++ b/src/vs/editor/test/common/view/overviewZoneManager.test.ts @@ -26,7 +26,7 @@ suite('Editor View - OverviewZoneManager', () => { ]); // one line = 12, but cap is at 6 - assert.deepEqual(manager.resolveColorZones(), [ + assert.deepStrictEqual(manager.resolveColorZones(), [ new ColorZone(12, 24, 1), // new ColorZone(120, 132, 2), // 120 -> 132 new ColorZone(360, 384, 3), // 360 -> 372 [360 -> 384] @@ -52,7 +52,7 @@ suite('Editor View - OverviewZoneManager', () => { ]); // one line = 6, cap is at 6 - assert.deepEqual(manager.resolveColorZones(), [ + assert.deepStrictEqual(manager.resolveColorZones(), [ new ColorZone(6, 12, 1), // new ColorZone(60, 66, 2), // 60 -> 66 new ColorZone(180, 192, 3), // 180 -> 192 @@ -78,7 +78,7 @@ suite('Editor View - OverviewZoneManager', () => { ]); // one line = 6, cap is at 12 - assert.deepEqual(manager.resolveColorZones(), [ + assert.deepStrictEqual(manager.resolveColorZones(), [ new ColorZone(12, 24, 1), // new ColorZone(120, 132, 2), // 120 -> 132 new ColorZone(360, 384, 3), // 360 -> 384 diff --git a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts index 910165a0e..a0fa69bc8 100644 --- a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts +++ b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts @@ -95,7 +95,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { maxDigitWidth: input.maxDigitWidth, pixelRatio: input.pixelRatio, }); - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); } test('EditorLayoutProvider 1', () => { diff --git a/src/vs/editor/test/common/viewLayout/lineDecorations.test.ts b/src/vs/editor/test/common/viewLayout/lineDecorations.test.ts index 8be7627bc..e8939e784 100644 --- a/src/vs/editor/test/common/viewLayout/lineDecorations.test.ts +++ b/src/vs/editor/test/common/viewLayout/lineDecorations.test.ts @@ -17,7 +17,7 @@ suite('Editor ViewLayout - ViewLineParts', () => { new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular) ]); - assert.deepEqual(result, [ + assert.deepStrictEqual(result, [ new DecorationSegment(0, 1, 'c1', 0), new DecorationSegment(2, 2, 'c2 c1', 0), new DecorationSegment(3, 9, 'c1', 0), @@ -31,7 +31,7 @@ suite('Editor ViewLayout - ViewLineParts', () => { new LineDecoration(20, 21, 'inline-folded', InlineDecorationType.Regular), ]); - assert.deepEqual(result, [ + assert.deepStrictEqual(result, [ new DecorationSegment(14, 18, 'mtkw', 0), new DecorationSegment(19, 19, 'mtkw inline-folded', 0) ]); @@ -43,7 +43,7 @@ suite('Editor ViewLayout - ViewLineParts', () => { new InlineDecoration(new Range(2, 12, 3, 30), 'detected-link', InlineDecorationType.Regular) ], 3, 12, 500); - assert.deepEqual(result, [ + assert.deepStrictEqual(result, [ new LineDecoration(12, 30, 'detected-link', InlineDecorationType.Regular), ]); }); @@ -54,7 +54,7 @@ suite('Editor ViewLayout - ViewLineParts', () => { new InlineDecoration(new Range(4, 0, 4, 1), 'after', InlineDecorationType.After), ], 4, 1, 500); - assert.deepEqual(result, [ + assert.deepStrictEqual(result, [ new LineDecoration(1, 2, 'before', InlineDecorationType.Before), new LineDecoration(0, 1, 'after', InlineDecorationType.After), ]); @@ -62,7 +62,7 @@ suite('Editor ViewLayout - ViewLineParts', () => { test('ViewLineParts', () => { - assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ + assert.deepStrictEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ new LineDecoration(1, 2, 'c1', InlineDecorationType.Regular), new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular) ]), [ @@ -70,7 +70,7 @@ suite('Editor ViewLayout - ViewLineParts', () => { new DecorationSegment(2, 2, 'c2', 0) ]); - assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ + assert.deepStrictEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ new LineDecoration(1, 3, 'c1', InlineDecorationType.Regular), new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular) ]), [ @@ -78,7 +78,7 @@ suite('Editor ViewLayout - ViewLineParts', () => { new DecorationSegment(2, 2, 'c2', 0) ]); - assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ + assert.deepStrictEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ new LineDecoration(1, 4, 'c1', InlineDecorationType.Regular), new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular) ]), [ @@ -86,7 +86,7 @@ suite('Editor ViewLayout - ViewLineParts', () => { new DecorationSegment(2, 2, 'c1 c2', 0) ]); - assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ + assert.deepStrictEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ new LineDecoration(1, 4, 'c1', InlineDecorationType.Regular), new LineDecoration(1, 4, 'c1*', InlineDecorationType.Regular), new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular) @@ -95,7 +95,7 @@ suite('Editor ViewLayout - ViewLineParts', () => { new DecorationSegment(2, 2, 'c1 c1* c2', 0) ]); - assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ + assert.deepStrictEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ new LineDecoration(1, 4, 'c1', InlineDecorationType.Regular), new LineDecoration(1, 4, 'c1*', InlineDecorationType.Regular), new LineDecoration(1, 4, 'c1**', InlineDecorationType.Regular), @@ -105,7 +105,7 @@ suite('Editor ViewLayout - ViewLineParts', () => { new DecorationSegment(2, 2, 'c1 c1* c1** c2', 0) ]); - assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ + assert.deepStrictEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ new LineDecoration(1, 4, 'c1', InlineDecorationType.Regular), new LineDecoration(1, 4, 'c1*', InlineDecorationType.Regular), new LineDecoration(1, 4, 'c1**', InlineDecorationType.Regular), @@ -116,7 +116,7 @@ suite('Editor ViewLayout - ViewLineParts', () => { new DecorationSegment(2, 2, 'c1 c1* c1** c2 c2*', 0) ]); - assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ + assert.deepStrictEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ new LineDecoration(1, 4, 'c1', InlineDecorationType.Regular), new LineDecoration(1, 4, 'c1*', InlineDecorationType.Regular), new LineDecoration(1, 4, 'c1**', InlineDecorationType.Regular), diff --git a/src/vs/editor/test/common/viewLayout/linesLayout.test.ts b/src/vs/editor/test/common/viewLayout/linesLayout.test.ts index 3205a10ce..6d619cf60 100644 --- a/src/vs/editor/test/common/viewLayout/linesLayout.test.ts +++ b/src/vs/editor/test/common/viewLayout/linesLayout.test.ts @@ -34,105 +34,105 @@ suite('Editor ViewLayout - LinesLayout', () => { // lines: [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] // whitespace: - - assert.equal(linesLayout.getLinesTotalHeight(), 100); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 0); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 10); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(3), 20); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(4), 30); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(5), 40); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(6), 50); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(7), 60); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(8), 70); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(9), 80); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(10), 90); + assert.strictEqual(linesLayout.getLinesTotalHeight(), 100); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(1), 0); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(2), 10); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(3), 20); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(4), 30); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(5), 40); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(6), 50); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(7), 60); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(8), 70); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(9), 80); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(10), 90); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(0), 1); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(1), 1); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(5), 1); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(9), 1); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(10), 2); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(11), 2); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(15), 2); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(19), 2); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(20), 3); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(21), 3); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(29), 3); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(0), 1); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(1), 1); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(5), 1); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(9), 1); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(10), 2); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(11), 2); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(15), 2); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(19), 2); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(20), 3); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(21), 3); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(29), 3); // Add whitespace of height 5px after 2nd line insertWhitespace(linesLayout, 2, 0, 5, 0); // lines: [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] // whitespace: a(2,5) - assert.equal(linesLayout.getLinesTotalHeight(), 105); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 0); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 10); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(3), 25); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(4), 35); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(5), 45); + assert.strictEqual(linesLayout.getLinesTotalHeight(), 105); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(1), 0); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(2), 10); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(3), 25); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(4), 35); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(5), 45); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(0), 1); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(1), 1); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(9), 1); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(10), 2); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(20), 3); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(21), 3); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(24), 3); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(25), 3); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(35), 4); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(45), 5); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(104), 10); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(105), 10); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(0), 1); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(1), 1); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(9), 1); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(10), 2); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(20), 3); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(21), 3); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(24), 3); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(25), 3); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(35), 4); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(45), 5); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(104), 10); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(105), 10); // Add two more whitespaces of height 5px insertWhitespace(linesLayout, 3, 0, 5, 0); insertWhitespace(linesLayout, 4, 0, 5, 0); // lines: [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] // whitespace: a(2,5), b(3, 5), c(4, 5) - assert.equal(linesLayout.getLinesTotalHeight(), 115); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 0); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 10); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(3), 25); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(4), 40); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(5), 55); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(6), 65); + assert.strictEqual(linesLayout.getLinesTotalHeight(), 115); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(1), 0); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(2), 10); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(3), 25); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(4), 40); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(5), 55); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(6), 65); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(0), 1); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(1), 1); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(9), 1); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(10), 2); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(19), 2); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(20), 3); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(34), 3); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(35), 4); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(49), 4); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(50), 5); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(64), 5); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(65), 6); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(0), 1); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(1), 1); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(9), 1); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(10), 2); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(19), 2); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(20), 3); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(34), 3); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(35), 4); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(49), 4); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(50), 5); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(64), 5); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(65), 6); - assert.equal(linesLayout.getVerticalOffsetForWhitespaceIndex(0), 20); // 20 -> 25 - assert.equal(linesLayout.getVerticalOffsetForWhitespaceIndex(1), 35); // 35 -> 40 - assert.equal(linesLayout.getVerticalOffsetForWhitespaceIndex(2), 50); + assert.strictEqual(linesLayout.getVerticalOffsetForWhitespaceIndex(0), 20); // 20 -> 25 + assert.strictEqual(linesLayout.getVerticalOffsetForWhitespaceIndex(1), 35); // 35 -> 40 + assert.strictEqual(linesLayout.getVerticalOffsetForWhitespaceIndex(2), 50); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(0), 0); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(19), 0); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(20), 0); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(21), 0); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(22), 0); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(23), 0); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(24), 0); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(25), 1); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(26), 1); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(34), 1); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(35), 1); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(36), 1); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(39), 1); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(40), 2); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(41), 2); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(49), 2); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(50), 2); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(51), 2); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(54), 2); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(55), -1); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(1000), -1); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(0), 0); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(19), 0); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(20), 0); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(21), 0); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(22), 0); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(23), 0); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(24), 0); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(25), 1); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(26), 1); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(34), 1); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(35), 1); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(36), 1); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(39), 1); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(40), 2); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(41), 2); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(49), 2); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(50), 2); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(51), 2); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(54), 2); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(55), -1); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(1000), -1); }); @@ -144,94 +144,94 @@ suite('Editor ViewLayout - LinesLayout', () => { // 10 lines // whitespace: - a(2,5) - assert.equal(linesLayout.getLinesTotalHeight(), 15); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 0); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 1); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(3), 7); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(4), 8); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(5), 9); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(6), 10); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(7), 11); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(8), 12); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(9), 13); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(10), 14); + assert.strictEqual(linesLayout.getLinesTotalHeight(), 15); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(1), 0); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(2), 1); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(3), 7); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(4), 8); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(5), 9); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(6), 10); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(7), 11); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(8), 12); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(9), 13); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(10), 14); // Change whitespace height // 10 lines // whitespace: - a(2,10) changeOneWhitespace(linesLayout, a, 2, 10); - assert.equal(linesLayout.getLinesTotalHeight(), 20); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 0); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 1); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(3), 12); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(4), 13); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(5), 14); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(6), 15); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(7), 16); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(8), 17); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(9), 18); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(10), 19); + assert.strictEqual(linesLayout.getLinesTotalHeight(), 20); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(1), 0); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(2), 1); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(3), 12); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(4), 13); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(5), 14); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(6), 15); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(7), 16); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(8), 17); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(9), 18); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(10), 19); // Change whitespace position // 10 lines // whitespace: - a(5,10) changeOneWhitespace(linesLayout, a, 5, 10); - assert.equal(linesLayout.getLinesTotalHeight(), 20); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 0); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 1); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(3), 2); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(4), 3); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(5), 4); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(6), 15); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(7), 16); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(8), 17); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(9), 18); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(10), 19); + assert.strictEqual(linesLayout.getLinesTotalHeight(), 20); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(1), 0); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(2), 1); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(3), 2); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(4), 3); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(5), 4); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(6), 15); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(7), 16); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(8), 17); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(9), 18); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(10), 19); // Pretend that lines 5 and 6 were deleted // 8 lines // whitespace: - a(4,10) linesLayout.onLinesDeleted(5, 6); - assert.equal(linesLayout.getLinesTotalHeight(), 18); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 0); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 1); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(3), 2); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(4), 3); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(5), 14); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(6), 15); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(7), 16); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(8), 17); + assert.strictEqual(linesLayout.getLinesTotalHeight(), 18); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(1), 0); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(2), 1); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(3), 2); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(4), 3); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(5), 14); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(6), 15); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(7), 16); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(8), 17); // Insert two lines at the beginning // 10 lines // whitespace: - a(6,10) linesLayout.onLinesInserted(1, 2); - assert.equal(linesLayout.getLinesTotalHeight(), 20); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 0); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 1); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(3), 2); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(4), 3); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(5), 4); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(6), 5); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(7), 16); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(8), 17); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(9), 18); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(10), 19); + assert.strictEqual(linesLayout.getLinesTotalHeight(), 20); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(1), 0); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(2), 1); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(3), 2); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(4), 3); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(5), 4); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(6), 5); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(7), 16); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(8), 17); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(9), 18); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(10), 19); // Remove whitespace // 10 lines removeWhitespace(linesLayout, a); - assert.equal(linesLayout.getLinesTotalHeight(), 10); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 0); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 1); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(3), 2); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(4), 3); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(5), 4); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(6), 5); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(7), 6); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(8), 7); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(9), 8); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(10), 9); + assert.strictEqual(linesLayout.getLinesTotalHeight(), 10); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(1), 0); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(2), 1); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(3), 2); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(4), 3); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(5), 4); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(6), 5); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(7), 6); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(8), 7); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(9), 8); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(10), 9); }); test('LinesLayout Padding', () => { @@ -240,93 +240,93 @@ suite('Editor ViewLayout - LinesLayout', () => { // lines: [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] // whitespace: - - assert.equal(linesLayout.getLinesTotalHeight(), 135); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 15); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 25); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(3), 35); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(4), 45); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(5), 55); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(6), 65); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(7), 75); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(8), 85); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(9), 95); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(10), 105); + assert.strictEqual(linesLayout.getLinesTotalHeight(), 135); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(1), 15); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(2), 25); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(3), 35); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(4), 45); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(5), 55); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(6), 65); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(7), 75); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(8), 85); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(9), 95); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(10), 105); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(0), 1); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(10), 1); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(15), 1); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(24), 1); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(25), 2); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(34), 2); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(35), 3); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(0), 1); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(10), 1); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(15), 1); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(24), 1); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(25), 2); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(34), 2); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(35), 3); // Add whitespace of height 5px after 2nd line insertWhitespace(linesLayout, 2, 0, 5, 0); // lines: [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] // whitespace: a(2,5) - assert.equal(linesLayout.getLinesTotalHeight(), 140); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 15); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 25); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(3), 40); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(4), 50); + assert.strictEqual(linesLayout.getLinesTotalHeight(), 140); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(1), 15); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(2), 25); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(3), 40); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(4), 50); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(0), 1); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(10), 1); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(25), 2); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(34), 2); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(35), 3); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(39), 3); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(40), 3); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(41), 3); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(49), 3); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(50), 4); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(0), 1); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(10), 1); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(25), 2); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(34), 2); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(35), 3); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(39), 3); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(40), 3); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(41), 3); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(49), 3); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(50), 4); // Add two more whitespaces of height 5px insertWhitespace(linesLayout, 3, 0, 5, 0); insertWhitespace(linesLayout, 4, 0, 5, 0); // lines: [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] // whitespace: a(2,5), b(3, 5), c(4, 5) - assert.equal(linesLayout.getLinesTotalHeight(), 150); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 15); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 25); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(3), 40); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(4), 55); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(5), 70); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(6), 80); + assert.strictEqual(linesLayout.getLinesTotalHeight(), 150); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(1), 15); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(2), 25); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(3), 40); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(4), 55); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(5), 70); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(6), 80); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(0), 1); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(15), 1); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(24), 1); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(30), 2); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(35), 3); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(39), 3); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(40), 3); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(49), 3); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(50), 4); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(54), 4); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(55), 4); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(64), 4); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(65), 5); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(69), 5); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(70), 5); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(80), 6); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(0), 1); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(15), 1); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(24), 1); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(30), 2); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(35), 3); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(39), 3); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(40), 3); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(49), 3); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(50), 4); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(54), 4); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(55), 4); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(64), 4); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(65), 5); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(69), 5); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(70), 5); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(80), 6); - assert.equal(linesLayout.getVerticalOffsetForWhitespaceIndex(0), 35); // 35 -> 40 - assert.equal(linesLayout.getVerticalOffsetForWhitespaceIndex(1), 50); // 50 -> 55 - assert.equal(linesLayout.getVerticalOffsetForWhitespaceIndex(2), 65); + assert.strictEqual(linesLayout.getVerticalOffsetForWhitespaceIndex(0), 35); // 35 -> 40 + assert.strictEqual(linesLayout.getVerticalOffsetForWhitespaceIndex(1), 50); // 50 -> 55 + assert.strictEqual(linesLayout.getVerticalOffsetForWhitespaceIndex(2), 65); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(0), 0); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(34), 0); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(35), 0); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(39), 0); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(40), 1); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(49), 1); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(50), 1); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(54), 1); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(55), 2); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(64), 2); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(65), 2); - assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(70), -1); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(0), 0); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(34), 0); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(35), 0); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(39), 0); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(40), 1); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(49), 1); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(50), 1); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(54), 1); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(55), 2); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(64), 2); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(65), 2); + assert.strictEqual(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(70), -1); }); test('LinesLayout getLineNumberAtOrAfterVerticalOffset', () => { @@ -335,47 +335,47 @@ suite('Editor ViewLayout - LinesLayout', () => { // 10 lines // whitespace: - a(6,10) - assert.equal(linesLayout.getLinesTotalHeight(), 20); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 0); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 1); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(3), 2); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(4), 3); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(5), 4); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(6), 5); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(7), 16); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(8), 17); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(9), 18); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(10), 19); + assert.strictEqual(linesLayout.getLinesTotalHeight(), 20); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(1), 0); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(2), 1); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(3), 2); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(4), 3); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(5), 4); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(6), 5); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(7), 16); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(8), 17); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(9), 18); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(10), 19); // Do some hit testing // line [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // vertical: [0, 1, 2, 3, 4, 5, 16, 17, 18, 19] - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(-100), 1); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(-1), 1); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(0), 1); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(1), 2); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(2), 3); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(3), 4); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(4), 5); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(5), 6); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(6), 7); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(7), 7); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(8), 7); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(9), 7); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(10), 7); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(11), 7); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(12), 7); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(13), 7); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(14), 7); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(15), 7); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(16), 7); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(17), 8); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(18), 9); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(19), 10); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(20), 10); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(21), 10); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(22), 10); - assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(23), 10); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(-100), 1); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(-1), 1); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(0), 1); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(1), 2); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(2), 3); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(3), 4); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(4), 5); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(5), 6); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(6), 7); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(7), 7); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(8), 7); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(9), 7); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(10), 7); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(11), 7); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(12), 7); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(13), 7); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(14), 7); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(15), 7); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(16), 7); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(17), 8); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(18), 9); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(19), 10); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(20), 10); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(21), 10); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(22), 10); + assert.strictEqual(linesLayout.getLineNumberAtOrAfterVerticalOffset(23), 10); }); test('LinesLayout getCenteredLineInViewport', () => { @@ -384,81 +384,81 @@ suite('Editor ViewLayout - LinesLayout', () => { // 10 lines // whitespace: - a(6,10) - assert.equal(linesLayout.getLinesTotalHeight(), 20); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 0); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 1); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(3), 2); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(4), 3); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(5), 4); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(6), 5); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(7), 16); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(8), 17); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(9), 18); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(10), 19); + assert.strictEqual(linesLayout.getLinesTotalHeight(), 20); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(1), 0); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(2), 1); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(3), 2); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(4), 3); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(5), 4); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(6), 5); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(7), 16); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(8), 17); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(9), 18); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(10), 19); // Find centered line in viewport 1 // line [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // vertical: [0, 1, 2, 3, 4, 5, 16, 17, 18, 19] - assert.equal(linesLayout.getLinesViewportData(0, 1).centeredLineNumber, 1); - assert.equal(linesLayout.getLinesViewportData(0, 2).centeredLineNumber, 2); - assert.equal(linesLayout.getLinesViewportData(0, 3).centeredLineNumber, 2); - assert.equal(linesLayout.getLinesViewportData(0, 4).centeredLineNumber, 3); - assert.equal(linesLayout.getLinesViewportData(0, 5).centeredLineNumber, 3); - assert.equal(linesLayout.getLinesViewportData(0, 6).centeredLineNumber, 4); - assert.equal(linesLayout.getLinesViewportData(0, 7).centeredLineNumber, 4); - assert.equal(linesLayout.getLinesViewportData(0, 8).centeredLineNumber, 5); - assert.equal(linesLayout.getLinesViewportData(0, 9).centeredLineNumber, 5); - assert.equal(linesLayout.getLinesViewportData(0, 10).centeredLineNumber, 6); - assert.equal(linesLayout.getLinesViewportData(0, 11).centeredLineNumber, 6); - assert.equal(linesLayout.getLinesViewportData(0, 12).centeredLineNumber, 6); - assert.equal(linesLayout.getLinesViewportData(0, 13).centeredLineNumber, 6); - assert.equal(linesLayout.getLinesViewportData(0, 14).centeredLineNumber, 6); - assert.equal(linesLayout.getLinesViewportData(0, 15).centeredLineNumber, 6); - assert.equal(linesLayout.getLinesViewportData(0, 16).centeredLineNumber, 6); - assert.equal(linesLayout.getLinesViewportData(0, 17).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(0, 18).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(0, 19).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(0, 20).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(0, 21).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(0, 22).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(0, 23).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(0, 24).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(0, 25).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(0, 26).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(0, 27).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(0, 28).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(0, 29).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(0, 30).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(0, 31).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(0, 32).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(0, 33).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(0, 1).centeredLineNumber, 1); + assert.strictEqual(linesLayout.getLinesViewportData(0, 2).centeredLineNumber, 2); + assert.strictEqual(linesLayout.getLinesViewportData(0, 3).centeredLineNumber, 2); + assert.strictEqual(linesLayout.getLinesViewportData(0, 4).centeredLineNumber, 3); + assert.strictEqual(linesLayout.getLinesViewportData(0, 5).centeredLineNumber, 3); + assert.strictEqual(linesLayout.getLinesViewportData(0, 6).centeredLineNumber, 4); + assert.strictEqual(linesLayout.getLinesViewportData(0, 7).centeredLineNumber, 4); + assert.strictEqual(linesLayout.getLinesViewportData(0, 8).centeredLineNumber, 5); + assert.strictEqual(linesLayout.getLinesViewportData(0, 9).centeredLineNumber, 5); + assert.strictEqual(linesLayout.getLinesViewportData(0, 10).centeredLineNumber, 6); + assert.strictEqual(linesLayout.getLinesViewportData(0, 11).centeredLineNumber, 6); + assert.strictEqual(linesLayout.getLinesViewportData(0, 12).centeredLineNumber, 6); + assert.strictEqual(linesLayout.getLinesViewportData(0, 13).centeredLineNumber, 6); + assert.strictEqual(linesLayout.getLinesViewportData(0, 14).centeredLineNumber, 6); + assert.strictEqual(linesLayout.getLinesViewportData(0, 15).centeredLineNumber, 6); + assert.strictEqual(linesLayout.getLinesViewportData(0, 16).centeredLineNumber, 6); + assert.strictEqual(linesLayout.getLinesViewportData(0, 17).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(0, 18).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(0, 19).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(0, 20).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(0, 21).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(0, 22).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(0, 23).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(0, 24).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(0, 25).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(0, 26).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(0, 27).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(0, 28).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(0, 29).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(0, 30).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(0, 31).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(0, 32).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(0, 33).centeredLineNumber, 7); // Find centered line in viewport 2 // line [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // vertical: [0, 1, 2, 3, 4, 5, 16, 17, 18, 19] - assert.equal(linesLayout.getLinesViewportData(0, 20).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(1, 20).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(2, 20).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(3, 20).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(4, 20).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(5, 20).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(6, 20).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(7, 20).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(8, 20).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(9, 20).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(10, 20).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(11, 20).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(12, 20).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(13, 20).centeredLineNumber, 7); - assert.equal(linesLayout.getLinesViewportData(14, 20).centeredLineNumber, 8); - assert.equal(linesLayout.getLinesViewportData(15, 20).centeredLineNumber, 8); - assert.equal(linesLayout.getLinesViewportData(16, 20).centeredLineNumber, 9); - assert.equal(linesLayout.getLinesViewportData(17, 20).centeredLineNumber, 9); - assert.equal(linesLayout.getLinesViewportData(18, 20).centeredLineNumber, 10); - assert.equal(linesLayout.getLinesViewportData(19, 20).centeredLineNumber, 10); - assert.equal(linesLayout.getLinesViewportData(20, 23).centeredLineNumber, 10); - assert.equal(linesLayout.getLinesViewportData(21, 23).centeredLineNumber, 10); - assert.equal(linesLayout.getLinesViewportData(22, 23).centeredLineNumber, 10); + assert.strictEqual(linesLayout.getLinesViewportData(0, 20).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(1, 20).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(2, 20).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(3, 20).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(4, 20).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(5, 20).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(6, 20).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(7, 20).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(8, 20).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(9, 20).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(10, 20).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(11, 20).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(12, 20).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(13, 20).centeredLineNumber, 7); + assert.strictEqual(linesLayout.getLinesViewportData(14, 20).centeredLineNumber, 8); + assert.strictEqual(linesLayout.getLinesViewportData(15, 20).centeredLineNumber, 8); + assert.strictEqual(linesLayout.getLinesViewportData(16, 20).centeredLineNumber, 9); + assert.strictEqual(linesLayout.getLinesViewportData(17, 20).centeredLineNumber, 9); + assert.strictEqual(linesLayout.getLinesViewportData(18, 20).centeredLineNumber, 10); + assert.strictEqual(linesLayout.getLinesViewportData(19, 20).centeredLineNumber, 10); + assert.strictEqual(linesLayout.getLinesViewportData(20, 23).centeredLineNumber, 10); + assert.strictEqual(linesLayout.getLinesViewportData(21, 23).centeredLineNumber, 10); + assert.strictEqual(linesLayout.getLinesViewportData(22, 23).centeredLineNumber, 10); }); test('LinesLayout getLinesViewportData 1', () => { @@ -467,131 +467,131 @@ suite('Editor ViewLayout - LinesLayout', () => { // 10 lines // whitespace: - a(6,100) - assert.equal(linesLayout.getLinesTotalHeight(), 200); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 0); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 10); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(3), 20); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(4), 30); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(5), 40); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(6), 50); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(7), 160); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(8), 170); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(9), 180); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(10), 190); + assert.strictEqual(linesLayout.getLinesTotalHeight(), 200); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(1), 0); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(2), 10); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(3), 20); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(4), 30); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(5), 40); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(6), 50); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(7), 160); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(8), 170); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(9), 180); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(10), 190); // viewport 0->50 let viewportData = linesLayout.getLinesViewportData(0, 50); - assert.equal(viewportData.startLineNumber, 1); - assert.equal(viewportData.endLineNumber, 5); - assert.equal(viewportData.completelyVisibleStartLineNumber, 1); - assert.equal(viewportData.completelyVisibleEndLineNumber, 5); - assert.deepEqual(viewportData.relativeVerticalOffset, [0, 10, 20, 30, 40]); + assert.strictEqual(viewportData.startLineNumber, 1); + assert.strictEqual(viewportData.endLineNumber, 5); + assert.strictEqual(viewportData.completelyVisibleStartLineNumber, 1); + assert.strictEqual(viewportData.completelyVisibleEndLineNumber, 5); + assert.deepStrictEqual(viewportData.relativeVerticalOffset, [0, 10, 20, 30, 40]); // viewport 1->51 viewportData = linesLayout.getLinesViewportData(1, 51); - assert.equal(viewportData.startLineNumber, 1); - assert.equal(viewportData.endLineNumber, 6); - assert.equal(viewportData.completelyVisibleStartLineNumber, 2); - assert.equal(viewportData.completelyVisibleEndLineNumber, 5); - assert.deepEqual(viewportData.relativeVerticalOffset, [0, 10, 20, 30, 40, 50]); + assert.strictEqual(viewportData.startLineNumber, 1); + assert.strictEqual(viewportData.endLineNumber, 6); + assert.strictEqual(viewportData.completelyVisibleStartLineNumber, 2); + assert.strictEqual(viewportData.completelyVisibleEndLineNumber, 5); + assert.deepStrictEqual(viewportData.relativeVerticalOffset, [0, 10, 20, 30, 40, 50]); // viewport 5->55 viewportData = linesLayout.getLinesViewportData(5, 55); - assert.equal(viewportData.startLineNumber, 1); - assert.equal(viewportData.endLineNumber, 6); - assert.equal(viewportData.completelyVisibleStartLineNumber, 2); - assert.equal(viewportData.completelyVisibleEndLineNumber, 5); - assert.deepEqual(viewportData.relativeVerticalOffset, [0, 10, 20, 30, 40, 50]); + assert.strictEqual(viewportData.startLineNumber, 1); + assert.strictEqual(viewportData.endLineNumber, 6); + assert.strictEqual(viewportData.completelyVisibleStartLineNumber, 2); + assert.strictEqual(viewportData.completelyVisibleEndLineNumber, 5); + assert.deepStrictEqual(viewportData.relativeVerticalOffset, [0, 10, 20, 30, 40, 50]); // viewport 10->60 viewportData = linesLayout.getLinesViewportData(10, 60); - assert.equal(viewportData.startLineNumber, 2); - assert.equal(viewportData.endLineNumber, 6); - assert.equal(viewportData.completelyVisibleStartLineNumber, 2); - assert.equal(viewportData.completelyVisibleEndLineNumber, 6); - assert.deepEqual(viewportData.relativeVerticalOffset, [10, 20, 30, 40, 50]); + assert.strictEqual(viewportData.startLineNumber, 2); + assert.strictEqual(viewportData.endLineNumber, 6); + assert.strictEqual(viewportData.completelyVisibleStartLineNumber, 2); + assert.strictEqual(viewportData.completelyVisibleEndLineNumber, 6); + assert.deepStrictEqual(viewportData.relativeVerticalOffset, [10, 20, 30, 40, 50]); // viewport 50->100 viewportData = linesLayout.getLinesViewportData(50, 100); - assert.equal(viewportData.startLineNumber, 6); - assert.equal(viewportData.endLineNumber, 6); - assert.equal(viewportData.completelyVisibleStartLineNumber, 6); - assert.equal(viewportData.completelyVisibleEndLineNumber, 6); - assert.deepEqual(viewportData.relativeVerticalOffset, [50]); + assert.strictEqual(viewportData.startLineNumber, 6); + assert.strictEqual(viewportData.endLineNumber, 6); + assert.strictEqual(viewportData.completelyVisibleStartLineNumber, 6); + assert.strictEqual(viewportData.completelyVisibleEndLineNumber, 6); + assert.deepStrictEqual(viewportData.relativeVerticalOffset, [50]); // viewport 60->110 viewportData = linesLayout.getLinesViewportData(60, 110); - assert.equal(viewportData.startLineNumber, 7); - assert.equal(viewportData.endLineNumber, 7); - assert.equal(viewportData.completelyVisibleStartLineNumber, 7); - assert.equal(viewportData.completelyVisibleEndLineNumber, 7); - assert.deepEqual(viewportData.relativeVerticalOffset, [160]); + assert.strictEqual(viewportData.startLineNumber, 7); + assert.strictEqual(viewportData.endLineNumber, 7); + assert.strictEqual(viewportData.completelyVisibleStartLineNumber, 7); + assert.strictEqual(viewportData.completelyVisibleEndLineNumber, 7); + assert.deepStrictEqual(viewportData.relativeVerticalOffset, [160]); // viewport 65->115 viewportData = linesLayout.getLinesViewportData(65, 115); - assert.equal(viewportData.startLineNumber, 7); - assert.equal(viewportData.endLineNumber, 7); - assert.equal(viewportData.completelyVisibleStartLineNumber, 7); - assert.equal(viewportData.completelyVisibleEndLineNumber, 7); - assert.deepEqual(viewportData.relativeVerticalOffset, [160]); + assert.strictEqual(viewportData.startLineNumber, 7); + assert.strictEqual(viewportData.endLineNumber, 7); + assert.strictEqual(viewportData.completelyVisibleStartLineNumber, 7); + assert.strictEqual(viewportData.completelyVisibleEndLineNumber, 7); + assert.deepStrictEqual(viewportData.relativeVerticalOffset, [160]); // viewport 50->159 viewportData = linesLayout.getLinesViewportData(50, 159); - assert.equal(viewportData.startLineNumber, 6); - assert.equal(viewportData.endLineNumber, 6); - assert.equal(viewportData.completelyVisibleStartLineNumber, 6); - assert.equal(viewportData.completelyVisibleEndLineNumber, 6); - assert.deepEqual(viewportData.relativeVerticalOffset, [50]); + assert.strictEqual(viewportData.startLineNumber, 6); + assert.strictEqual(viewportData.endLineNumber, 6); + assert.strictEqual(viewportData.completelyVisibleStartLineNumber, 6); + assert.strictEqual(viewportData.completelyVisibleEndLineNumber, 6); + assert.deepStrictEqual(viewportData.relativeVerticalOffset, [50]); // viewport 50->160 viewportData = linesLayout.getLinesViewportData(50, 160); - assert.equal(viewportData.startLineNumber, 6); - assert.equal(viewportData.endLineNumber, 6); - assert.equal(viewportData.completelyVisibleStartLineNumber, 6); - assert.equal(viewportData.completelyVisibleEndLineNumber, 6); - assert.deepEqual(viewportData.relativeVerticalOffset, [50]); + assert.strictEqual(viewportData.startLineNumber, 6); + assert.strictEqual(viewportData.endLineNumber, 6); + assert.strictEqual(viewportData.completelyVisibleStartLineNumber, 6); + assert.strictEqual(viewportData.completelyVisibleEndLineNumber, 6); + assert.deepStrictEqual(viewportData.relativeVerticalOffset, [50]); // viewport 51->161 viewportData = linesLayout.getLinesViewportData(51, 161); - assert.equal(viewportData.startLineNumber, 6); - assert.equal(viewportData.endLineNumber, 7); - assert.equal(viewportData.completelyVisibleStartLineNumber, 7); - assert.equal(viewportData.completelyVisibleEndLineNumber, 7); - assert.deepEqual(viewportData.relativeVerticalOffset, [50, 160]); + assert.strictEqual(viewportData.startLineNumber, 6); + assert.strictEqual(viewportData.endLineNumber, 7); + assert.strictEqual(viewportData.completelyVisibleStartLineNumber, 7); + assert.strictEqual(viewportData.completelyVisibleEndLineNumber, 7); + assert.deepStrictEqual(viewportData.relativeVerticalOffset, [50, 160]); // viewport 150->169 viewportData = linesLayout.getLinesViewportData(150, 169); - assert.equal(viewportData.startLineNumber, 7); - assert.equal(viewportData.endLineNumber, 7); - assert.equal(viewportData.completelyVisibleStartLineNumber, 7); - assert.equal(viewportData.completelyVisibleEndLineNumber, 7); - assert.deepEqual(viewportData.relativeVerticalOffset, [160]); + assert.strictEqual(viewportData.startLineNumber, 7); + assert.strictEqual(viewportData.endLineNumber, 7); + assert.strictEqual(viewportData.completelyVisibleStartLineNumber, 7); + assert.strictEqual(viewportData.completelyVisibleEndLineNumber, 7); + assert.deepStrictEqual(viewportData.relativeVerticalOffset, [160]); // viewport 159->169 viewportData = linesLayout.getLinesViewportData(159, 169); - assert.equal(viewportData.startLineNumber, 7); - assert.equal(viewportData.endLineNumber, 7); - assert.equal(viewportData.completelyVisibleStartLineNumber, 7); - assert.equal(viewportData.completelyVisibleEndLineNumber, 7); - assert.deepEqual(viewportData.relativeVerticalOffset, [160]); + assert.strictEqual(viewportData.startLineNumber, 7); + assert.strictEqual(viewportData.endLineNumber, 7); + assert.strictEqual(viewportData.completelyVisibleStartLineNumber, 7); + assert.strictEqual(viewportData.completelyVisibleEndLineNumber, 7); + assert.deepStrictEqual(viewportData.relativeVerticalOffset, [160]); // viewport 160->169 viewportData = linesLayout.getLinesViewportData(160, 169); - assert.equal(viewportData.startLineNumber, 7); - assert.equal(viewportData.endLineNumber, 7); - assert.equal(viewportData.completelyVisibleStartLineNumber, 7); - assert.equal(viewportData.completelyVisibleEndLineNumber, 7); - assert.deepEqual(viewportData.relativeVerticalOffset, [160]); + assert.strictEqual(viewportData.startLineNumber, 7); + assert.strictEqual(viewportData.endLineNumber, 7); + assert.strictEqual(viewportData.completelyVisibleStartLineNumber, 7); + assert.strictEqual(viewportData.completelyVisibleEndLineNumber, 7); + assert.deepStrictEqual(viewportData.relativeVerticalOffset, [160]); // viewport 160->1000 viewportData = linesLayout.getLinesViewportData(160, 1000); - assert.equal(viewportData.startLineNumber, 7); - assert.equal(viewportData.endLineNumber, 10); - assert.equal(viewportData.completelyVisibleStartLineNumber, 7); - assert.equal(viewportData.completelyVisibleEndLineNumber, 10); - assert.deepEqual(viewportData.relativeVerticalOffset, [160, 170, 180, 190]); + assert.strictEqual(viewportData.startLineNumber, 7); + assert.strictEqual(viewportData.endLineNumber, 10); + assert.strictEqual(viewportData.completelyVisibleStartLineNumber, 7); + assert.strictEqual(viewportData.completelyVisibleEndLineNumber, 10); + assert.deepStrictEqual(viewportData.relativeVerticalOffset, [160, 170, 180, 190]); }); test('LinesLayout getLinesViewportData 2 & getWhitespaceViewportData', () => { @@ -601,27 +601,27 @@ suite('Editor ViewLayout - LinesLayout', () => { // 10 lines // whitespace: - a(6,100), b(7, 50) - assert.equal(linesLayout.getLinesTotalHeight(), 250); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 0); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 10); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(3), 20); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(4), 30); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(5), 40); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(6), 50); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(7), 160); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(8), 220); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(9), 230); - assert.equal(linesLayout.getVerticalOffsetForLineNumber(10), 240); + assert.strictEqual(linesLayout.getLinesTotalHeight(), 250); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(1), 0); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(2), 10); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(3), 20); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(4), 30); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(5), 40); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(6), 50); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(7), 160); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(8), 220); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(9), 230); + assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(10), 240); // viewport 50->160 let viewportData = linesLayout.getLinesViewportData(50, 160); - assert.equal(viewportData.startLineNumber, 6); - assert.equal(viewportData.endLineNumber, 6); - assert.equal(viewportData.completelyVisibleStartLineNumber, 6); - assert.equal(viewportData.completelyVisibleEndLineNumber, 6); - assert.deepEqual(viewportData.relativeVerticalOffset, [50]); + assert.strictEqual(viewportData.startLineNumber, 6); + assert.strictEqual(viewportData.endLineNumber, 6); + assert.strictEqual(viewportData.completelyVisibleStartLineNumber, 6); + assert.strictEqual(viewportData.completelyVisibleEndLineNumber, 6); + assert.deepStrictEqual(viewportData.relativeVerticalOffset, [50]); let whitespaceData = linesLayout.getWhitespaceViewportData(50, 160); - assert.deepEqual(whitespaceData, [{ + assert.deepStrictEqual(whitespaceData, [{ id: a, afterLineNumber: 6, verticalOffset: 60, @@ -630,13 +630,13 @@ suite('Editor ViewLayout - LinesLayout', () => { // viewport 50->219 viewportData = linesLayout.getLinesViewportData(50, 219); - assert.equal(viewportData.startLineNumber, 6); - assert.equal(viewportData.endLineNumber, 7); - assert.equal(viewportData.completelyVisibleStartLineNumber, 6); - assert.equal(viewportData.completelyVisibleEndLineNumber, 7); - assert.deepEqual(viewportData.relativeVerticalOffset, [50, 160]); + assert.strictEqual(viewportData.startLineNumber, 6); + assert.strictEqual(viewportData.endLineNumber, 7); + assert.strictEqual(viewportData.completelyVisibleStartLineNumber, 6); + assert.strictEqual(viewportData.completelyVisibleEndLineNumber, 7); + assert.deepStrictEqual(viewportData.relativeVerticalOffset, [50, 160]); whitespaceData = linesLayout.getWhitespaceViewportData(50, 219); - assert.deepEqual(whitespaceData, [{ + assert.deepStrictEqual(whitespaceData, [{ id: a, afterLineNumber: 6, verticalOffset: 60, @@ -650,19 +650,19 @@ suite('Editor ViewLayout - LinesLayout', () => { // viewport 50->220 viewportData = linesLayout.getLinesViewportData(50, 220); - assert.equal(viewportData.startLineNumber, 6); - assert.equal(viewportData.endLineNumber, 7); - assert.equal(viewportData.completelyVisibleStartLineNumber, 6); - assert.equal(viewportData.completelyVisibleEndLineNumber, 7); - assert.deepEqual(viewportData.relativeVerticalOffset, [50, 160]); + assert.strictEqual(viewportData.startLineNumber, 6); + assert.strictEqual(viewportData.endLineNumber, 7); + assert.strictEqual(viewportData.completelyVisibleStartLineNumber, 6); + assert.strictEqual(viewportData.completelyVisibleEndLineNumber, 7); + assert.deepStrictEqual(viewportData.relativeVerticalOffset, [50, 160]); // viewport 50->250 viewportData = linesLayout.getLinesViewportData(50, 250); - assert.equal(viewportData.startLineNumber, 6); - assert.equal(viewportData.endLineNumber, 10); - assert.equal(viewportData.completelyVisibleStartLineNumber, 6); - assert.equal(viewportData.completelyVisibleEndLineNumber, 10); - assert.deepEqual(viewportData.relativeVerticalOffset, [50, 160, 220, 230, 240]); + assert.strictEqual(viewportData.startLineNumber, 6); + assert.strictEqual(viewportData.endLineNumber, 10); + assert.strictEqual(viewportData.completelyVisibleStartLineNumber, 6); + assert.strictEqual(viewportData.completelyVisibleEndLineNumber, 10); + assert.deepStrictEqual(viewportData.relativeVerticalOffset, [50, 160, 220, 230, 240]); }); test('LinesLayout getWhitespaceAtVerticalOffset', () => { @@ -671,40 +671,40 @@ suite('Editor ViewLayout - LinesLayout', () => { let b = insertWhitespace(linesLayout, 7, 0, 50, 0); let whitespace = linesLayout.getWhitespaceAtVerticalOffset(0); - assert.equal(whitespace, null); + assert.strictEqual(whitespace, null); whitespace = linesLayout.getWhitespaceAtVerticalOffset(59); - assert.equal(whitespace, null); + assert.strictEqual(whitespace, null); whitespace = linesLayout.getWhitespaceAtVerticalOffset(60); - assert.equal(whitespace!.id, a); + assert.strictEqual(whitespace!.id, a); whitespace = linesLayout.getWhitespaceAtVerticalOffset(61); - assert.equal(whitespace!.id, a); + assert.strictEqual(whitespace!.id, a); whitespace = linesLayout.getWhitespaceAtVerticalOffset(159); - assert.equal(whitespace!.id, a); + assert.strictEqual(whitespace!.id, a); whitespace = linesLayout.getWhitespaceAtVerticalOffset(160); - assert.equal(whitespace, null); + assert.strictEqual(whitespace, null); whitespace = linesLayout.getWhitespaceAtVerticalOffset(161); - assert.equal(whitespace, null); + assert.strictEqual(whitespace, null); whitespace = linesLayout.getWhitespaceAtVerticalOffset(169); - assert.equal(whitespace, null); + assert.strictEqual(whitespace, null); whitespace = linesLayout.getWhitespaceAtVerticalOffset(170); - assert.equal(whitespace!.id, b); + assert.strictEqual(whitespace!.id, b); whitespace = linesLayout.getWhitespaceAtVerticalOffset(171); - assert.equal(whitespace!.id, b); + assert.strictEqual(whitespace!.id, b); whitespace = linesLayout.getWhitespaceAtVerticalOffset(219); - assert.equal(whitespace!.id, b); + assert.strictEqual(whitespace!.id, b); whitespace = linesLayout.getWhitespaceAtVerticalOffset(220); - assert.equal(whitespace, null); + assert.strictEqual(whitespace, null); }); test('LinesLayout', () => { @@ -714,230 +714,230 @@ suite('Editor ViewLayout - LinesLayout', () => { // Insert a whitespace after line number 2, of height 10 const a = insertWhitespace(linesLayout, 2, 0, 10, 0); // whitespaces: a(2, 10) - assert.equal(linesLayout.getWhitespacesCount(), 1); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); - assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 10); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 10); - assert.equal(linesLayout.getWhitespacesTotalHeight(), 10); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 10); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 10); + assert.strictEqual(linesLayout.getWhitespacesCount(), 1); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(0), 10); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(0), 10); + assert.strictEqual(linesLayout.getWhitespacesTotalHeight(), 10); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 10); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 10); // Insert a whitespace again after line number 2, of height 20 let b = insertWhitespace(linesLayout, 2, 0, 20, 0); // whitespaces: a(2, 10), b(2, 20) - assert.equal(linesLayout.getWhitespacesCount(), 2); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); - assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 10); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); - assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 20); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 10); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 30); - assert.equal(linesLayout.getWhitespacesTotalHeight(), 30); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 30); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 30); + assert.strictEqual(linesLayout.getWhitespacesCount(), 2); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(0), 10); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(1), 20); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(0), 10); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(1), 30); + assert.strictEqual(linesLayout.getWhitespacesTotalHeight(), 30); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 30); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 30); // Change last inserted whitespace height to 30 changeOneWhitespace(linesLayout, b, 2, 30); // whitespaces: a(2, 10), b(2, 30) - assert.equal(linesLayout.getWhitespacesCount(), 2); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); - assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 10); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); - assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 30); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 10); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 40); - assert.equal(linesLayout.getWhitespacesTotalHeight(), 40); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 40); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 40); + assert.strictEqual(linesLayout.getWhitespacesCount(), 2); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(0), 10); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(1), 30); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(0), 10); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(1), 40); + assert.strictEqual(linesLayout.getWhitespacesTotalHeight(), 40); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 40); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 40); // Remove last inserted whitespace removeWhitespace(linesLayout, b); // whitespaces: a(2, 10) - assert.equal(linesLayout.getWhitespacesCount(), 1); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); - assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 10); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 10); - assert.equal(linesLayout.getWhitespacesTotalHeight(), 10); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 10); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 10); + assert.strictEqual(linesLayout.getWhitespacesCount(), 1); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(0), 10); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(0), 10); + assert.strictEqual(linesLayout.getWhitespacesTotalHeight(), 10); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 10); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 10); // Add a whitespace before the first line of height 50 b = insertWhitespace(linesLayout, 0, 0, 50, 0); // whitespaces: b(0, 50), a(2, 10) - assert.equal(linesLayout.getWhitespacesCount(), 2); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); - assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 50); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); - assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 10); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 50); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 60); - assert.equal(linesLayout.getWhitespacesTotalHeight(), 60); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 60); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 60); + assert.strictEqual(linesLayout.getWhitespacesCount(), 2); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(0), 50); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(1), 10); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(0), 50); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(1), 60); + assert.strictEqual(linesLayout.getWhitespacesTotalHeight(), 60); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 60); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 60); // Add a whitespace after line 4 of height 20 insertWhitespace(linesLayout, 4, 0, 20, 0); // whitespaces: b(0, 50), a(2, 10), c(4, 20) - assert.equal(linesLayout.getWhitespacesCount(), 3); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); - assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 50); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); - assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 10); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 4); - assert.equal(linesLayout.getHeightForWhitespaceIndex(2), 20); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 50); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 60); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(2), 80); - assert.equal(linesLayout.getWhitespacesTotalHeight(), 80); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 60); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 60); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 80); + assert.strictEqual(linesLayout.getWhitespacesCount(), 3); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(0), 50); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(1), 10); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 4); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(2), 20); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(0), 50); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(1), 60); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(2), 80); + assert.strictEqual(linesLayout.getWhitespacesTotalHeight(), 80); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 60); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 60); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 80); // Add a whitespace after line 3 of height 30 insertWhitespace(linesLayout, 3, 0, 30, 0); // whitespaces: b(0, 50), a(2, 10), d(3, 30), c(4, 20) - assert.equal(linesLayout.getWhitespacesCount(), 4); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); - assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 50); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); - assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 10); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 3); - assert.equal(linesLayout.getHeightForWhitespaceIndex(2), 30); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(3), 4); - assert.equal(linesLayout.getHeightForWhitespaceIndex(3), 20); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 50); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 60); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(2), 90); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(3), 110); - assert.equal(linesLayout.getWhitespacesTotalHeight(), 110); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 60); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 90); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 110); + assert.strictEqual(linesLayout.getWhitespacesCount(), 4); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(0), 50); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(1), 10); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 3); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(2), 30); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(3), 4); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(3), 20); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(0), 50); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(1), 60); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(2), 90); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(3), 110); + assert.strictEqual(linesLayout.getWhitespacesTotalHeight(), 110); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 60); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 90); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 110); // Change whitespace after line 2 to height of 100 changeOneWhitespace(linesLayout, a, 2, 100); // whitespaces: b(0, 50), a(2, 100), d(3, 30), c(4, 20) - assert.equal(linesLayout.getWhitespacesCount(), 4); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); - assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 50); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); - assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 100); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 3); - assert.equal(linesLayout.getHeightForWhitespaceIndex(2), 30); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(3), 4); - assert.equal(linesLayout.getHeightForWhitespaceIndex(3), 20); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 50); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 150); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(2), 180); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(3), 200); - assert.equal(linesLayout.getWhitespacesTotalHeight(), 200); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 150); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 180); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 200); + assert.strictEqual(linesLayout.getWhitespacesCount(), 4); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(0), 50); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(1), 100); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 3); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(2), 30); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(3), 4); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(3), 20); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(0), 50); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(1), 150); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(2), 180); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(3), 200); + assert.strictEqual(linesLayout.getWhitespacesTotalHeight(), 200); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 150); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 180); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 200); // Remove whitespace after line 2 removeWhitespace(linesLayout, a); // whitespaces: b(0, 50), d(3, 30), c(4, 20) - assert.equal(linesLayout.getWhitespacesCount(), 3); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); - assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 50); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 30); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 4); - assert.equal(linesLayout.getHeightForWhitespaceIndex(2), 20); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 50); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 80); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(2), 100); - assert.equal(linesLayout.getWhitespacesTotalHeight(), 100); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 50); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 80); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 100); + assert.strictEqual(linesLayout.getWhitespacesCount(), 3); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(0), 50); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(1), 30); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 4); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(2), 20); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(0), 50); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(1), 80); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(2), 100); + assert.strictEqual(linesLayout.getWhitespacesTotalHeight(), 100); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 50); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 80); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 100); // Remove whitespace before line 1 removeWhitespace(linesLayout, b); // whitespaces: d(3, 30), c(4, 20) - assert.equal(linesLayout.getWhitespacesCount(), 2); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); - assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 30); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 4); - assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 20); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 30); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 50); - assert.equal(linesLayout.getWhitespacesTotalHeight(), 50); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 0); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 30); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 50); + assert.strictEqual(linesLayout.getWhitespacesCount(), 2); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(0), 30); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 4); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(1), 20); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(0), 30); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(1), 50); + assert.strictEqual(linesLayout.getWhitespacesTotalHeight(), 50); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 0); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 30); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 50); // Delete line 1 linesLayout.onLinesDeleted(1, 1); // whitespaces: d(2, 30), c(3, 20) - assert.equal(linesLayout.getWhitespacesCount(), 2); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); - assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 30); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 20); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 30); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 50); - assert.equal(linesLayout.getWhitespacesTotalHeight(), 50); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 30); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 50); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 50); + assert.strictEqual(linesLayout.getWhitespacesCount(), 2); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(0), 30); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(1), 20); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(0), 30); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(1), 50); + assert.strictEqual(linesLayout.getWhitespacesTotalHeight(), 50); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 30); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 50); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 50); // Insert a line before line 1 linesLayout.onLinesInserted(1, 1); // whitespaces: d(3, 30), c(4, 20) - assert.equal(linesLayout.getWhitespacesCount(), 2); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); - assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 30); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 4); - assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 20); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 30); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 50); - assert.equal(linesLayout.getWhitespacesTotalHeight(), 50); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 0); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 30); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 50); + assert.strictEqual(linesLayout.getWhitespacesCount(), 2); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(0), 30); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 4); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(1), 20); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(0), 30); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(1), 50); + assert.strictEqual(linesLayout.getWhitespacesTotalHeight(), 50); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 0); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 30); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 50); // Delete line 4 linesLayout.onLinesDeleted(4, 4); // whitespaces: d(3, 30), c(3, 20) - assert.equal(linesLayout.getWhitespacesCount(), 2); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); - assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 30); - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 20); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 30); - assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 50); - assert.equal(linesLayout.getWhitespacesTotalHeight(), 50); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 0); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 50); - assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 50); + assert.strictEqual(linesLayout.getWhitespacesCount(), 2); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(0), 30); + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.strictEqual(linesLayout.getHeightForWhitespaceIndex(1), 20); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(0), 30); + assert.strictEqual(linesLayout.getWhitespacesAccumulatedHeight(1), 50); + assert.strictEqual(linesLayout.getWhitespacesTotalHeight(), 50); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 0); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 50); + assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 50); }); test('LinesLayout findInsertionIndex', () => { @@ -949,114 +949,114 @@ suite('Editor ViewLayout - LinesLayout', () => { let arr: EditorWhitespace[]; arr = makeInternalWhitespace([]); - assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); - assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 0); - assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 0); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 1, 0), 0); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 2, 0), 0); arr = makeInternalWhitespace([1]); - assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); - assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); - assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 2, 0), 1); arr = makeInternalWhitespace([1, 3]); - assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); - assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); - assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); - assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2); - assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 3, 0), 2); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 4, 0), 2); arr = makeInternalWhitespace([1, 3, 5]); - assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); - assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); - assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); - assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2); - assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); - assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3); - assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 3, 0), 2); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 5, 0), 3); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 6, 0), 3); arr = makeInternalWhitespace([1, 3, 5], 3); - assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); - assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 0); - assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); - assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 1); - assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); - assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 2); - assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 1, 0), 0); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 3, 0), 1); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 5, 0), 2); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 6, 0), 3); arr = makeInternalWhitespace([1, 3, 5, 7]); - assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); - assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); - assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); - assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2); - assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); - assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3); - assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3); - assert.equal(LinesLayout.findInsertionIndex(arr, 7, 0), 4); - assert.equal(LinesLayout.findInsertionIndex(arr, 8, 0), 4); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 3, 0), 2); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 5, 0), 3); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 6, 0), 3); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 7, 0), 4); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 8, 0), 4); arr = makeInternalWhitespace([1, 3, 5, 7, 9]); - assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); - assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); - assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); - assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2); - assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); - assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3); - assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3); - assert.equal(LinesLayout.findInsertionIndex(arr, 7, 0), 4); - assert.equal(LinesLayout.findInsertionIndex(arr, 8, 0), 4); - assert.equal(LinesLayout.findInsertionIndex(arr, 9, 0), 5); - assert.equal(LinesLayout.findInsertionIndex(arr, 10, 0), 5); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 3, 0), 2); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 5, 0), 3); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 6, 0), 3); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 7, 0), 4); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 8, 0), 4); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 9, 0), 5); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 10, 0), 5); arr = makeInternalWhitespace([1, 3, 5, 7, 9, 11]); - assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); - assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); - assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); - assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2); - assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); - assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3); - assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3); - assert.equal(LinesLayout.findInsertionIndex(arr, 7, 0), 4); - assert.equal(LinesLayout.findInsertionIndex(arr, 8, 0), 4); - assert.equal(LinesLayout.findInsertionIndex(arr, 9, 0), 5); - assert.equal(LinesLayout.findInsertionIndex(arr, 10, 0), 5); - assert.equal(LinesLayout.findInsertionIndex(arr, 11, 0), 6); - assert.equal(LinesLayout.findInsertionIndex(arr, 12, 0), 6); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 3, 0), 2); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 5, 0), 3); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 6, 0), 3); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 7, 0), 4); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 8, 0), 4); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 9, 0), 5); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 10, 0), 5); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 11, 0), 6); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 12, 0), 6); arr = makeInternalWhitespace([1, 3, 5, 7, 9, 11, 13]); - assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); - assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); - assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); - assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2); - assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); - assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3); - assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3); - assert.equal(LinesLayout.findInsertionIndex(arr, 7, 0), 4); - assert.equal(LinesLayout.findInsertionIndex(arr, 8, 0), 4); - assert.equal(LinesLayout.findInsertionIndex(arr, 9, 0), 5); - assert.equal(LinesLayout.findInsertionIndex(arr, 10, 0), 5); - assert.equal(LinesLayout.findInsertionIndex(arr, 11, 0), 6); - assert.equal(LinesLayout.findInsertionIndex(arr, 12, 0), 6); - assert.equal(LinesLayout.findInsertionIndex(arr, 13, 0), 7); - assert.equal(LinesLayout.findInsertionIndex(arr, 14, 0), 7); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 3, 0), 2); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 5, 0), 3); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 6, 0), 3); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 7, 0), 4); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 8, 0), 4); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 9, 0), 5); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 10, 0), 5); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 11, 0), 6); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 12, 0), 6); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 13, 0), 7); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 14, 0), 7); arr = makeInternalWhitespace([1, 3, 5, 7, 9, 11, 13, 15]); - assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); - assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); - assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); - assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2); - assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); - assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3); - assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3); - assert.equal(LinesLayout.findInsertionIndex(arr, 7, 0), 4); - assert.equal(LinesLayout.findInsertionIndex(arr, 8, 0), 4); - assert.equal(LinesLayout.findInsertionIndex(arr, 9, 0), 5); - assert.equal(LinesLayout.findInsertionIndex(arr, 10, 0), 5); - assert.equal(LinesLayout.findInsertionIndex(arr, 11, 0), 6); - assert.equal(LinesLayout.findInsertionIndex(arr, 12, 0), 6); - assert.equal(LinesLayout.findInsertionIndex(arr, 13, 0), 7); - assert.equal(LinesLayout.findInsertionIndex(arr, 14, 0), 7); - assert.equal(LinesLayout.findInsertionIndex(arr, 15, 0), 8); - assert.equal(LinesLayout.findInsertionIndex(arr, 16, 0), 8); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 3, 0), 2); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 5, 0), 3); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 6, 0), 3); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 7, 0), 4); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 8, 0), 4); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 9, 0), 5); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 10, 0), 5); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 11, 0), 6); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 12, 0), 6); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 13, 0), 7); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 14, 0), 7); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 15, 0), 8); + assert.strictEqual(LinesLayout.findInsertionIndex(arr, 16, 0), 8); }); test('LinesLayout changeWhitespaceAfterLineNumber & getFirstWhitespaceIndexAfterLineNumber', () => { @@ -1066,121 +1066,121 @@ suite('Editor ViewLayout - LinesLayout', () => { const b = insertWhitespace(linesLayout, 7, 0, 1, 0); const c = insertWhitespace(linesLayout, 3, 0, 1, 0); - assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); - assert.equal(linesLayout.getIdForWhitespaceIndex(1), c); // 3 - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7 - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(1), c); // 3 + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(2), b); // 7 + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 1); // c - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 1); // c - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 1); // c + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 1); // c + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- // Do not really move a changeOneWhitespace(linesLayout, a, 1, 1); - assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 1 - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 1); - assert.equal(linesLayout.getIdForWhitespaceIndex(1), c); // 3 - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7 - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(0), a); // 1 + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 1); + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(1), c); // 3 + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(2), b); // 7 + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 1); // c - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 1); // c + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- // Do not really move a changeOneWhitespace(linesLayout, a, 2, 1); - assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 2 - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); - assert.equal(linesLayout.getIdForWhitespaceIndex(1), c); // 3 - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7 - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(0), a); // 2 + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(1), c); // 3 + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(2), b); // 7 + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 0); // a - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 0); // a + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- // Change a to conflict with c => a gets placed after c changeOneWhitespace(linesLayout, a, 3, 1); - assert.equal(linesLayout.getIdForWhitespaceIndex(0), c); // 3 - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); - assert.equal(linesLayout.getIdForWhitespaceIndex(1), a); // 3 - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7 - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(0), c); // 3 + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(1), a); // 3 + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(2), b); // 7 + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // c - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 0); // c - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 0); // c - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // c + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 0); // c + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 0); // c + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- // Make a no-op changeOneWhitespace(linesLayout, c, 3, 1); - assert.equal(linesLayout.getIdForWhitespaceIndex(0), c); // 3 - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); - assert.equal(linesLayout.getIdForWhitespaceIndex(1), a); // 3 - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7 - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(0), c); // 3 + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(1), a); // 3 + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(2), b); // 7 + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // c - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 0); // c - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 0); // c - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // c + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 0); // c + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 0); // c + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- // Conflict c with b => c gets placed after b changeOneWhitespace(linesLayout, c, 7, 1); - assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 3 - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); - assert.equal(linesLayout.getIdForWhitespaceIndex(1), b); // 7 - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 7); - assert.equal(linesLayout.getIdForWhitespaceIndex(2), c); // 7 - assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(0), a); // 3 + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(1), b); // 7 + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 7); + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(2), c); // 7 + assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 0); // a - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 0); // a - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 1); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 1); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 1); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 1); // b - assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 0); // a + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 0); // a + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 1); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 1); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 1); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 1); // b + assert.strictEqual(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- }); test('LinesLayout Bug', () => { @@ -1189,53 +1189,53 @@ suite('Editor ViewLayout - LinesLayout', () => { const a = insertWhitespace(linesLayout, 0, 0, 1, 0); const b = insertWhitespace(linesLayout, 7, 0, 1, 0); - assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(linesLayout.getIdForWhitespaceIndex(1), b); // 7 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(1), b); // 7 const c = insertWhitespace(linesLayout, 3, 0, 1, 0); - assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(linesLayout.getIdForWhitespaceIndex(1), c); // 3 - assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(1), c); // 3 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(2), b); // 7 const d = insertWhitespace(linesLayout, 2, 0, 1, 0); - assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(linesLayout.getIdForWhitespaceIndex(1), d); // 2 - assert.equal(linesLayout.getIdForWhitespaceIndex(2), c); // 3 - assert.equal(linesLayout.getIdForWhitespaceIndex(3), b); // 7 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(1), d); // 2 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(2), c); // 3 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(3), b); // 7 const e = insertWhitespace(linesLayout, 8, 0, 1, 0); - assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(linesLayout.getIdForWhitespaceIndex(1), d); // 2 - assert.equal(linesLayout.getIdForWhitespaceIndex(2), c); // 3 - assert.equal(linesLayout.getIdForWhitespaceIndex(3), b); // 7 - assert.equal(linesLayout.getIdForWhitespaceIndex(4), e); // 8 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(1), d); // 2 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(2), c); // 3 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(3), b); // 7 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(4), e); // 8 const f = insertWhitespace(linesLayout, 11, 0, 1, 0); - assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(linesLayout.getIdForWhitespaceIndex(1), d); // 2 - assert.equal(linesLayout.getIdForWhitespaceIndex(2), c); // 3 - assert.equal(linesLayout.getIdForWhitespaceIndex(3), b); // 7 - assert.equal(linesLayout.getIdForWhitespaceIndex(4), e); // 8 - assert.equal(linesLayout.getIdForWhitespaceIndex(5), f); // 11 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(1), d); // 2 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(2), c); // 3 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(3), b); // 7 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(4), e); // 8 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(5), f); // 11 const g = insertWhitespace(linesLayout, 10, 0, 1, 0); - assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(linesLayout.getIdForWhitespaceIndex(1), d); // 2 - assert.equal(linesLayout.getIdForWhitespaceIndex(2), c); // 3 - assert.equal(linesLayout.getIdForWhitespaceIndex(3), b); // 7 - assert.equal(linesLayout.getIdForWhitespaceIndex(4), e); // 8 - assert.equal(linesLayout.getIdForWhitespaceIndex(5), g); // 10 - assert.equal(linesLayout.getIdForWhitespaceIndex(6), f); // 11 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(1), d); // 2 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(2), c); // 3 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(3), b); // 7 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(4), e); // 8 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(5), g); // 10 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(6), f); // 11 const h = insertWhitespace(linesLayout, 0, 0, 1, 0); - assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(linesLayout.getIdForWhitespaceIndex(1), h); // 0 - assert.equal(linesLayout.getIdForWhitespaceIndex(2), d); // 2 - assert.equal(linesLayout.getIdForWhitespaceIndex(3), c); // 3 - assert.equal(linesLayout.getIdForWhitespaceIndex(4), b); // 7 - assert.equal(linesLayout.getIdForWhitespaceIndex(5), e); // 8 - assert.equal(linesLayout.getIdForWhitespaceIndex(6), g); // 10 - assert.equal(linesLayout.getIdForWhitespaceIndex(7), f); // 11 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(1), h); // 0 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(2), d); // 2 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(3), c); // 3 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(4), b); // 7 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(5), e); // 8 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(6), g); // 10 + assert.strictEqual(linesLayout.getIdForWhitespaceIndex(7), f); // 11 }); }); diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index 46f369013..672a4a86f 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -48,7 +48,7 @@ suite('viewLineRenderer.renderLine', () => { null )); - assert.equal(_actual.html, '' + expected + ''); + assert.strictEqual(_actual.html, '' + expected + ''); assertCharacterMapping(_actual.characterMapping, expectedCharOffsetInPart, expectedPartLengts); } @@ -101,7 +101,7 @@ suite('viewLineRenderer.renderLine', () => { null )); - assert.equal(_actual.html, '' + expected + ''); + assert.strictEqual(_actual.html, '' + expected + ''); assertCharacterMapping(_actual.characterMapping, expectedCharOffsetInPart, expectedPartLengts); } @@ -167,7 +167,7 @@ suite('viewLineRenderer.renderLine', () => { '' ].join(''); - assert.equal(_actual.html, '' + expectedOutput + ''); + assert.strictEqual(_actual.html, '' + expectedOutput + ''); assertCharacterMapping(_actual.characterMapping, [ [0], @@ -252,7 +252,7 @@ suite('viewLineRenderer.renderLine', () => { null )); - assert.equal(_actual.html, '' + expectedOutput + ''); + assert.strictEqual(_actual.html, '' + expectedOutput + ''); assertCharacterMapping(_actual.characterMapping, expectedOffsetsArr, [4, 4, 6, 1, 5, 1, 4, 1, 1, 1, 3, 15, 2, 3]); }); @@ -318,7 +318,7 @@ suite('viewLineRenderer.renderLine', () => { null )); - assert.equal(_actual.html, '' + expectedOutput + ''); + assert.strictEqual(_actual.html, '' + expectedOutput + ''); assertCharacterMapping(_actual.characterMapping, expectedOffsetsArr, [12, 12, 24, 1, 21, 2, 1, 20, 1, 1]); }); @@ -384,7 +384,7 @@ suite('viewLineRenderer.renderLine', () => { null )); - assert.equal(_actual.html, '' + expectedOutput + ''); + assert.strictEqual(_actual.html, '' + expectedOutput + ''); assertCharacterMapping(_actual.characterMapping, expectedOffsetsArr, [12, 12, 24, 1, 21, 2, 1, 20, 1, 1]); }); @@ -444,7 +444,7 @@ suite('viewLineRenderer.renderLine', () => { null )); - assert.equal(actual.html, '' + expectedOutput + ''); + assert.strictEqual(actual.html, '' + expectedOutput + ''); assertCharacterMapping2(actual.characterMapping, expectedCharacterMapping); }); @@ -487,8 +487,8 @@ suite('viewLineRenderer.renderLine', () => { null )); - assert.equal(_actual.html, '' + expectedOutput + ''); - assert.equal(_actual.containsRTL, true); + assert.strictEqual(_actual.html, '' + expectedOutput + ''); + assert.strictEqual(_actual.containsRTL, true); }); test('issue #6885: Splits large tokens', () => { @@ -520,7 +520,7 @@ suite('viewLineRenderer.renderLine', () => { false, null )); - assert.equal(actual.html, '' + expectedOutput.join('') + '', message); + assert.strictEqual(actual.html, '' + expectedOutput.join('') + '', message); } // A token with 49 chars @@ -624,7 +624,7 @@ suite('viewLineRenderer.renderLine', () => { true, null )); - assert.equal(actual.html, '' + expectedOutput.join('') + '', message); + assert.strictEqual(actual.html, '' + expectedOutput.join('') + '', message); } // A token with 101 chars @@ -669,7 +669,7 @@ suite('viewLineRenderer.renderLine', () => { let expectedOutput = [ 'a𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷', ]; - assert.equal(actual.html, '' + expectedOutput.join('') + ''); + assert.strictEqual(actual.html, '' + expectedOutput.join('') + ''); }); test('issue #6885: Does not split large tokens in RTL text', () => { @@ -699,8 +699,8 @@ suite('viewLineRenderer.renderLine', () => { false, null )); - assert.equal(actual.html, '' + expectedOutput.join('') + ''); - assert.equal(actual.containsRTL, true); + assert.strictEqual(actual.html, '' + expectedOutput.join('') + ''); + assert.strictEqual(actual.containsRTL, true); }); test('issue #95685: Uses unicode replacement character for Paragraph Separator', () => { @@ -730,7 +730,7 @@ suite('viewLineRenderer.renderLine', () => { false, null )); - assert.equal(actual.html, '' + expectedOutput.join('') + ''); + assert.strictEqual(actual.html, '' + expectedOutput.join('') + ''); }); test('issue #19673: Monokai Theme bad-highlighting in line wrap', () => { @@ -780,7 +780,7 @@ suite('viewLineRenderer.renderLine', () => { null )); - assert.equal(_actual.html, '' + expectedOutput + ''); + assert.strictEqual(_actual.html, '' + expectedOutput + ''); }); interface ICharMappingData { @@ -807,7 +807,7 @@ suite('viewLineRenderer.renderLine', () => { function assertCharacterMapping2(actual: CharacterMapping, expected: CharacterMapping): void { const _actual = decodeCharacterMapping(actual); const _expected = decodeCharacterMapping(expected); - assert.deepEqual(_actual, _expected); + assert.deepStrictEqual(_actual, _expected); } function assertCharacterMapping(actual: CharacterMapping, expectedCharPartOffsets: number[][], expectedPartLengths: number[]): void { @@ -830,7 +830,7 @@ suite('viewLineRenderer.renderLine', () => { for (let i = 0; i < tmp.length; i++) { actualCharOffset[i] = tmp[i]; } - assert.deepEqual(actualCharOffset, expectedCharAbsoluteOffset); + assert.deepStrictEqual(actualCharOffset, expectedCharAbsoluteOffset); } function assertCharPartOffsets(actual: CharacterMapping, expected: number[][]): void { @@ -844,7 +844,7 @@ suite('viewLineRenderer.renderLine', () => { let actualPartIndex = CharacterMapping.getPartIndex(_actualPartData); let actualCharIndex = CharacterMapping.getCharIndex(_actualPartData); - assert.deepEqual( + assert.deepStrictEqual( { partIndex: actualPartIndex, charIndex: actualCharIndex }, { partIndex: partIndex, charIndex: charIndex }, `character mapping for offset ${charOffset}` @@ -853,7 +853,7 @@ suite('viewLineRenderer.renderLine', () => { // here let actualOffset = actual.partDataToCharOffset(partIndex, part[part.length - 1] + 1, charIndex); - assert.equal( + assert.strictEqual( actualOffset, charOffset, `character mapping for part ${partIndex}, ${charIndex}` @@ -863,7 +863,7 @@ suite('viewLineRenderer.renderLine', () => { } } - assert.equal(actual.length, charOffset); + assert.strictEqual(actual.length, charOffset); } }); @@ -892,7 +892,7 @@ suite('viewLineRenderer.renderLine 2', () => { selections )); - assert.deepEqual(actual.html, expected); + assert.deepStrictEqual(actual.html, expected); } test('issue #18616: Inline decorations ending at the text length are no longer rendered', () => { @@ -927,7 +927,7 @@ suite('viewLineRenderer.renderLine 2', () => { '' ].join(''); - assert.deepEqual(actual.html, expected); + assert.deepStrictEqual(actual.html, expected); }); test('issue #19207: Link in Monokai is not rendered correctly', () => { @@ -976,7 +976,7 @@ suite('viewLineRenderer.renderLine 2', () => { '' ].join(''); - assert.deepEqual(actual.html, expected); + assert.deepStrictEqual(actual.html, expected); }); test('createLineParts simple', () => { @@ -1477,7 +1477,7 @@ suite('viewLineRenderer.renderLine 2', () => { // bb--------- // -cccccc---- - assert.deepEqual(actual.html, [ + assert.deepStrictEqual(actual.html, [ '', 'H', 'e', @@ -1522,7 +1522,7 @@ suite('viewLineRenderer.renderLine 2', () => { '' ].join(''); - assert.deepEqual(actual.html, expected); + assert.deepStrictEqual(actual.html, expected); }); test('issue #32436: Non-monospace font + visible whitespace + After decorator causes line to "jump"', () => { @@ -1559,7 +1559,7 @@ suite('viewLineRenderer.renderLine 2', () => { '' ].join(''); - assert.deepEqual(actual.html, expected); + assert.deepStrictEqual(actual.html, expected); }); test('issue #30133: Empty lines don\'t render inline decorations', () => { @@ -1594,7 +1594,7 @@ suite('viewLineRenderer.renderLine 2', () => { '' ].join(''); - assert.deepEqual(actual.html, expected); + assert.deepStrictEqual(actual.html, expected); }); test('issue #37208: Collapsing bullet point containing emoji in Markdown document results in [??] character', () => { @@ -1628,7 +1628,7 @@ suite('viewLineRenderer.renderLine 2', () => { '' ].join(''); - assert.deepEqual(actual.html, expected); + assert.deepStrictEqual(actual.html, expected); }); test('issue #37401 #40127: Allow both before and after decorations on empty line', () => { @@ -1665,7 +1665,7 @@ suite('viewLineRenderer.renderLine 2', () => { '' ].join(''); - assert.deepEqual(actual.html, expected); + assert.deepStrictEqual(actual.html, expected); }); test('issue #38935: GitLens end-of-line blame no longer rendering', () => { @@ -1702,7 +1702,7 @@ suite('viewLineRenderer.renderLine 2', () => { '' ].join(''); - assert.deepEqual(actual.html, expected); + assert.deepStrictEqual(actual.html, expected); }); test('issue #22832: Consider fullwidth characters when rendering tabs', () => { @@ -1735,7 +1735,7 @@ suite('viewLineRenderer.renderLine 2', () => { '' ].join(''); - assert.deepEqual(actual.html, expected); + assert.deepStrictEqual(actual.html, expected); }); test('issue #22832: Consider fullwidth characters when rendering tabs (render whitespace)', () => { @@ -1774,7 +1774,7 @@ suite('viewLineRenderer.renderLine 2', () => { '' ].join(''); - assert.deepEqual(actual.html, expected); + assert.deepStrictEqual(actual.html, expected); }); test('issue #22352: COMBINING ACUTE ACCENT (U+0301)', () => { @@ -1807,7 +1807,7 @@ suite('viewLineRenderer.renderLine 2', () => { '' ].join(''); - assert.deepEqual(actual.html, expected); + assert.deepStrictEqual(actual.html, expected); }); test('issue #22352: Partially Broken Complex Script Rendering of Tamil', () => { @@ -1842,7 +1842,7 @@ suite('viewLineRenderer.renderLine 2', () => { '' ].join(''); - assert.deepEqual(actual.html, expected); + assert.deepStrictEqual(actual.html, expected); }); test('issue #42700: Hindi characters are not being rendered properly', () => { @@ -1877,7 +1877,7 @@ suite('viewLineRenderer.renderLine 2', () => { '' ].join(''); - assert.deepEqual(actual.html, expected); + assert.deepStrictEqual(actual.html, expected); }); test('issue #38123: editor.renderWhitespace: "boundary" renders whitespace at line wrap point when line is wrapped', () => { @@ -1909,7 +1909,7 @@ suite('viewLineRenderer.renderLine 2', () => { '' ].join(''); - assert.deepEqual(actual.html, expected); + assert.deepStrictEqual(actual.html, expected); }); test('issue #33525: Long line with ligatures takes a long time to paint decorations', () => { @@ -1945,7 +1945,7 @@ suite('viewLineRenderer.renderLine 2', () => { '' ].join(''); - assert.deepEqual(actual.html, expected); + assert.deepStrictEqual(actual.html, expected); }); test('issue #33525: Long line with ligatures takes a long time to paint decorations - not possible', () => { @@ -1977,7 +1977,7 @@ suite('viewLineRenderer.renderLine 2', () => { '' ].join(''); - assert.deepEqual(actual.html, expected); + assert.deepStrictEqual(actual.html, expected); }); test('issue #91936: Semantic token color highlighting fails on line with selected text', () => { @@ -2062,7 +2062,7 @@ suite('viewLineRenderer.renderLine 2', () => { '' ].join(''); - assert.deepEqual(actual.html, expected); + assert.deepStrictEqual(actual.html, expected); }); @@ -2092,7 +2092,7 @@ suite('viewLineRenderer.renderLine 2', () => { return (partIndex: number, partLength: number, offset: number, expected: number) => { let charOffset = renderLineOutput.characterMapping.partDataToCharOffset(partIndex, partLength, offset); let actual = charOffset + 1; - assert.equal(actual, expected, 'getColumnOfLinePartOffset for ' + partIndex + ' @ ' + offset); + assert.strictEqual(actual, expected, 'getColumnOfLinePartOffset for ' + partIndex + ' @ ' + offset); }; } diff --git a/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts b/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts index 670f42407..eec0e2b27 100644 --- a/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts +++ b/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts @@ -47,6 +47,7 @@ function toAnnotatedText(text: string, lineBreakData: LineBreakData | null): str function getLineBreakData(factory: ILineBreaksComputerFactory, tabSize: number, breakAfter: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent, text: string, previousLineBreakData: LineBreakData | null): LineBreakData | null { const fontInfo = new FontInfo({ zoomLevel: 0, + pixelRatio: 1, fontFamily: 'testFontFamily', fontWeight: 'normal', fontSize: 14, @@ -55,7 +56,7 @@ function getLineBreakData(factory: ILineBreaksComputerFactory, tabSize: number, letterSpacing: 0, isMonospace: true, typicalHalfwidthCharacterWidth: 7, - typicalFullwidthCharacterWidth: 14, + typicalFullwidthCharacterWidth: 7 * columnsForFullWidthChar, canUseHalfwidthRightwardsArrow: true, spaceWidth: 7, middotWidth: 7, @@ -74,7 +75,7 @@ function assertLineBreaks(factory: ILineBreaksComputerFactory, tabSize: number, const lineBreakData = getLineBreakData(factory, tabSize, breakAfter, 2, wrappingIndent, text, null); const actualAnnotatedText = toAnnotatedText(text, lineBreakData); - assert.equal(actualAnnotatedText, annotatedText); + assert.strictEqual(actualAnnotatedText, annotatedText); return lineBreakData; } @@ -122,28 +123,41 @@ suite('Editor ViewModel - MonospaceLineBreaksComputer', () => { assertLineBreaks(factory, 4, 5, 'aa.(.|).aaa'); }); - function assertIncrementalLineBreaks(factory: ILineBreaksComputerFactory, text: string, tabSize: number, breakAfter1: number, annotatedText1: string, breakAfter2: number, annotatedText2: string, wrappingIndent = WrappingIndent.None): void { + function assertLineBreakDataEqual(a: LineBreakData | null, b: LineBreakData | null): void { + if (!a || !b) { + assert.deepStrictEqual(a, b); + return; + } + assert.deepStrictEqual(a.breakOffsets, b.breakOffsets); + assert.deepStrictEqual(a.wrappedTextIndentLength, b.wrappedTextIndentLength); + for (let i = 0; i < a.breakOffsetsVisibleColumn.length; i++) { + const diff = a.breakOffsetsVisibleColumn[i] - b.breakOffsetsVisibleColumn[i]; + assert.ok(diff < 0.001); + } + } + + function assertIncrementalLineBreaks(factory: ILineBreaksComputerFactory, text: string, tabSize: number, breakAfter1: number, annotatedText1: string, breakAfter2: number, annotatedText2: string, wrappingIndent = WrappingIndent.None, columnsForFullWidthChar: number = 2): void { // sanity check the test - assert.equal(text, parseAnnotatedText(annotatedText1).text); - assert.equal(text, parseAnnotatedText(annotatedText2).text); + assert.strictEqual(text, parseAnnotatedText(annotatedText1).text); + assert.strictEqual(text, parseAnnotatedText(annotatedText2).text); // check that the direct mapping is ok for 1 - const directLineBreakData1 = getLineBreakData(factory, tabSize, breakAfter1, 2, wrappingIndent, text, null); - assert.equal(toAnnotatedText(text, directLineBreakData1), annotatedText1); + const directLineBreakData1 = getLineBreakData(factory, tabSize, breakAfter1, columnsForFullWidthChar, wrappingIndent, text, null); + assert.strictEqual(toAnnotatedText(text, directLineBreakData1), annotatedText1); // check that the direct mapping is ok for 2 - const directLineBreakData2 = getLineBreakData(factory, tabSize, breakAfter2, 2, wrappingIndent, text, null); - assert.equal(toAnnotatedText(text, directLineBreakData2), annotatedText2); + const directLineBreakData2 = getLineBreakData(factory, tabSize, breakAfter2, columnsForFullWidthChar, wrappingIndent, text, null); + assert.strictEqual(toAnnotatedText(text, directLineBreakData2), annotatedText2); // check that going from 1 to 2 is ok - const lineBreakData2from1 = getLineBreakData(factory, tabSize, breakAfter2, 2, wrappingIndent, text, directLineBreakData1); - assert.equal(toAnnotatedText(text, lineBreakData2from1), annotatedText2); - assert.deepEqual(lineBreakData2from1, directLineBreakData2); + const lineBreakData2from1 = getLineBreakData(factory, tabSize, breakAfter2, columnsForFullWidthChar, wrappingIndent, text, directLineBreakData1); + assert.strictEqual(toAnnotatedText(text, lineBreakData2from1), annotatedText2); + assertLineBreakDataEqual(lineBreakData2from1, directLineBreakData2); // check that going from 2 to 1 is ok - const lineBreakData1from2 = getLineBreakData(factory, tabSize, breakAfter1, 2, wrappingIndent, text, directLineBreakData2); - assert.equal(toAnnotatedText(text, lineBreakData1from2), annotatedText1); - assert.deepEqual(lineBreakData1from2, directLineBreakData1); + const lineBreakData1from2 = getLineBreakData(factory, tabSize, breakAfter1, columnsForFullWidthChar, wrappingIndent, text, directLineBreakData2); + assert.strictEqual(toAnnotatedText(text, lineBreakData1from2), annotatedText1); + assertLineBreakDataEqual(lineBreakData1from2, directLineBreakData1); } test('MonospaceLineBreaksComputer incremental 1', () => { @@ -216,6 +230,19 @@ suite('Editor ViewModel - MonospaceLineBreaksComputer', () => { ); }); + test('issue #110392: Occasional crash when resize with panel on the right', () => { + const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue); + assertIncrementalLineBreaks( + factory, + '你好 **hello** **hello** **hello-world** hey there!', + 4, + 15, '你好 **hello** |**hello** |**hello-world**| hey there!', + 1, '你|好| |*|*|h|e|l|l|o|*|*| |*|*|h|e|l|l|o|*|*| |*|*|h|e|l|l|o|-|w|o|r|l|d|*|*| |h|e|y| |t|h|e|r|e|!', + WrappingIndent.Same, + 1.6605405405405405 + ); + }); + test('MonospaceLineBreaksComputer - CJK and Kinsoku Shori', () => { let factory = new MonospaceLineBreaksComputerFactory('(', '\t)'); assertLineBreaks(factory, 4, 5, 'aa \u5b89|\u5b89'); @@ -239,7 +266,7 @@ suite('Editor ViewModel - MonospaceLineBreaksComputer', () => { test('issue #35162: wrappingIndent not consistently working', () => { let factory = new MonospaceLineBreaksComputerFactory('', '\t '); let mapper = assertLineBreaks(factory, 4, 24, ' t h i s |i s |a l |o n |g l |i n |e', WrappingIndent.Indent); - assert.equal(mapper!.wrappedTextIndentLength, ' '.length); + assert.strictEqual(mapper!.wrappedTextIndentLength, ' '.length); }); test('issue #75494: surrogate pairs', () => { @@ -260,11 +287,16 @@ suite('Editor ViewModel - MonospaceLineBreaksComputer', () => { test('MonospaceLineBreaksComputer - WrappingIndent.DeepIndent', () => { let factory = new MonospaceLineBreaksComputerFactory('', '\t '); let mapper = assertLineBreaks(factory, 4, 26, ' W e A r e T e s t |i n g D e |e p I n d |e n t a t |i o n', WrappingIndent.DeepIndent); - assert.equal(mapper!.wrappedTextIndentLength, ' '.length); + assert.strictEqual(mapper!.wrappedTextIndentLength, ' '.length); }); test('issue #33366: Word wrap algorithm behaves differently around punctuation', () => { const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue); assertLineBreaks(factory, 4, 23, 'this is a line of |text, text that sits |on a line', WrappingIndent.Same); }); + + test('issue #112382: Word wrap doesn\'t work well with control characters', () => { + const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue); + assertLineBreaks(factory, 4, 6, '\x06\x06\x06|\x06\x06\x06', WrappingIndent.Same); + }); }); diff --git a/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts b/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts index 65cc26efd..80df1a94e 100644 --- a/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts +++ b/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts @@ -22,158 +22,158 @@ suite('Editor ViewModel - PrefixSumComputer', () => { let indexOfResult: PrefixSumIndexOfResult; let psc = new PrefixSumComputer(toUint32Array([1, 1, 2, 1, 3])); - assert.equal(psc.getTotalValue(), 8); - assert.equal(psc.getAccumulatedValue(-1), 0); - assert.equal(psc.getAccumulatedValue(0), 1); - assert.equal(psc.getAccumulatedValue(1), 2); - assert.equal(psc.getAccumulatedValue(2), 4); - assert.equal(psc.getAccumulatedValue(3), 5); - assert.equal(psc.getAccumulatedValue(4), 8); + assert.strictEqual(psc.getTotalValue(), 8); + assert.strictEqual(psc.getAccumulatedValue(-1), 0); + assert.strictEqual(psc.getAccumulatedValue(0), 1); + assert.strictEqual(psc.getAccumulatedValue(1), 2); + assert.strictEqual(psc.getAccumulatedValue(2), 4); + assert.strictEqual(psc.getAccumulatedValue(3), 5); + assert.strictEqual(psc.getAccumulatedValue(4), 8); indexOfResult = psc.getIndexOf(0); - assert.equal(indexOfResult.index, 0); - assert.equal(indexOfResult.remainder, 0); + assert.strictEqual(indexOfResult.index, 0); + assert.strictEqual(indexOfResult.remainder, 0); indexOfResult = psc.getIndexOf(1); - assert.equal(indexOfResult.index, 1); - assert.equal(indexOfResult.remainder, 0); + assert.strictEqual(indexOfResult.index, 1); + assert.strictEqual(indexOfResult.remainder, 0); indexOfResult = psc.getIndexOf(2); - assert.equal(indexOfResult.index, 2); - assert.equal(indexOfResult.remainder, 0); + assert.strictEqual(indexOfResult.index, 2); + assert.strictEqual(indexOfResult.remainder, 0); indexOfResult = psc.getIndexOf(3); - assert.equal(indexOfResult.index, 2); - assert.equal(indexOfResult.remainder, 1); + assert.strictEqual(indexOfResult.index, 2); + assert.strictEqual(indexOfResult.remainder, 1); indexOfResult = psc.getIndexOf(4); - assert.equal(indexOfResult.index, 3); - assert.equal(indexOfResult.remainder, 0); + assert.strictEqual(indexOfResult.index, 3); + assert.strictEqual(indexOfResult.remainder, 0); indexOfResult = psc.getIndexOf(5); - assert.equal(indexOfResult.index, 4); - assert.equal(indexOfResult.remainder, 0); + assert.strictEqual(indexOfResult.index, 4); + assert.strictEqual(indexOfResult.remainder, 0); indexOfResult = psc.getIndexOf(6); - assert.equal(indexOfResult.index, 4); - assert.equal(indexOfResult.remainder, 1); + assert.strictEqual(indexOfResult.index, 4); + assert.strictEqual(indexOfResult.remainder, 1); indexOfResult = psc.getIndexOf(7); - assert.equal(indexOfResult.index, 4); - assert.equal(indexOfResult.remainder, 2); + assert.strictEqual(indexOfResult.index, 4); + assert.strictEqual(indexOfResult.remainder, 2); indexOfResult = psc.getIndexOf(8); - assert.equal(indexOfResult.index, 4); - assert.equal(indexOfResult.remainder, 3); + assert.strictEqual(indexOfResult.index, 4); + assert.strictEqual(indexOfResult.remainder, 3); // [1, 2, 2, 1, 3] psc.changeValue(1, 2); - assert.equal(psc.getTotalValue(), 9); - assert.equal(psc.getAccumulatedValue(0), 1); - assert.equal(psc.getAccumulatedValue(1), 3); - assert.equal(psc.getAccumulatedValue(2), 5); - assert.equal(psc.getAccumulatedValue(3), 6); - assert.equal(psc.getAccumulatedValue(4), 9); + assert.strictEqual(psc.getTotalValue(), 9); + assert.strictEqual(psc.getAccumulatedValue(0), 1); + assert.strictEqual(psc.getAccumulatedValue(1), 3); + assert.strictEqual(psc.getAccumulatedValue(2), 5); + assert.strictEqual(psc.getAccumulatedValue(3), 6); + assert.strictEqual(psc.getAccumulatedValue(4), 9); // [1, 0, 2, 1, 3] psc.changeValue(1, 0); - assert.equal(psc.getTotalValue(), 7); - assert.equal(psc.getAccumulatedValue(0), 1); - assert.equal(psc.getAccumulatedValue(1), 1); - assert.equal(psc.getAccumulatedValue(2), 3); - assert.equal(psc.getAccumulatedValue(3), 4); - assert.equal(psc.getAccumulatedValue(4), 7); + assert.strictEqual(psc.getTotalValue(), 7); + assert.strictEqual(psc.getAccumulatedValue(0), 1); + assert.strictEqual(psc.getAccumulatedValue(1), 1); + assert.strictEqual(psc.getAccumulatedValue(2), 3); + assert.strictEqual(psc.getAccumulatedValue(3), 4); + assert.strictEqual(psc.getAccumulatedValue(4), 7); indexOfResult = psc.getIndexOf(0); - assert.equal(indexOfResult.index, 0); - assert.equal(indexOfResult.remainder, 0); + assert.strictEqual(indexOfResult.index, 0); + assert.strictEqual(indexOfResult.remainder, 0); indexOfResult = psc.getIndexOf(1); - assert.equal(indexOfResult.index, 2); - assert.equal(indexOfResult.remainder, 0); + assert.strictEqual(indexOfResult.index, 2); + assert.strictEqual(indexOfResult.remainder, 0); indexOfResult = psc.getIndexOf(2); - assert.equal(indexOfResult.index, 2); - assert.equal(indexOfResult.remainder, 1); + assert.strictEqual(indexOfResult.index, 2); + assert.strictEqual(indexOfResult.remainder, 1); indexOfResult = psc.getIndexOf(3); - assert.equal(indexOfResult.index, 3); - assert.equal(indexOfResult.remainder, 0); + assert.strictEqual(indexOfResult.index, 3); + assert.strictEqual(indexOfResult.remainder, 0); indexOfResult = psc.getIndexOf(4); - assert.equal(indexOfResult.index, 4); - assert.equal(indexOfResult.remainder, 0); + assert.strictEqual(indexOfResult.index, 4); + assert.strictEqual(indexOfResult.remainder, 0); indexOfResult = psc.getIndexOf(5); - assert.equal(indexOfResult.index, 4); - assert.equal(indexOfResult.remainder, 1); + assert.strictEqual(indexOfResult.index, 4); + assert.strictEqual(indexOfResult.remainder, 1); indexOfResult = psc.getIndexOf(6); - assert.equal(indexOfResult.index, 4); - assert.equal(indexOfResult.remainder, 2); + assert.strictEqual(indexOfResult.index, 4); + assert.strictEqual(indexOfResult.remainder, 2); indexOfResult = psc.getIndexOf(7); - assert.equal(indexOfResult.index, 4); - assert.equal(indexOfResult.remainder, 3); + assert.strictEqual(indexOfResult.index, 4); + assert.strictEqual(indexOfResult.remainder, 3); // [1, 0, 0, 1, 3] psc.changeValue(2, 0); - assert.equal(psc.getTotalValue(), 5); - assert.equal(psc.getAccumulatedValue(0), 1); - assert.equal(psc.getAccumulatedValue(1), 1); - assert.equal(psc.getAccumulatedValue(2), 1); - assert.equal(psc.getAccumulatedValue(3), 2); - assert.equal(psc.getAccumulatedValue(4), 5); + assert.strictEqual(psc.getTotalValue(), 5); + assert.strictEqual(psc.getAccumulatedValue(0), 1); + assert.strictEqual(psc.getAccumulatedValue(1), 1); + assert.strictEqual(psc.getAccumulatedValue(2), 1); + assert.strictEqual(psc.getAccumulatedValue(3), 2); + assert.strictEqual(psc.getAccumulatedValue(4), 5); indexOfResult = psc.getIndexOf(0); - assert.equal(indexOfResult.index, 0); - assert.equal(indexOfResult.remainder, 0); + assert.strictEqual(indexOfResult.index, 0); + assert.strictEqual(indexOfResult.remainder, 0); indexOfResult = psc.getIndexOf(1); - assert.equal(indexOfResult.index, 3); - assert.equal(indexOfResult.remainder, 0); + assert.strictEqual(indexOfResult.index, 3); + assert.strictEqual(indexOfResult.remainder, 0); indexOfResult = psc.getIndexOf(2); - assert.equal(indexOfResult.index, 4); - assert.equal(indexOfResult.remainder, 0); + assert.strictEqual(indexOfResult.index, 4); + assert.strictEqual(indexOfResult.remainder, 0); indexOfResult = psc.getIndexOf(3); - assert.equal(indexOfResult.index, 4); - assert.equal(indexOfResult.remainder, 1); + assert.strictEqual(indexOfResult.index, 4); + assert.strictEqual(indexOfResult.remainder, 1); indexOfResult = psc.getIndexOf(4); - assert.equal(indexOfResult.index, 4); - assert.equal(indexOfResult.remainder, 2); + assert.strictEqual(indexOfResult.index, 4); + assert.strictEqual(indexOfResult.remainder, 2); indexOfResult = psc.getIndexOf(5); - assert.equal(indexOfResult.index, 4); - assert.equal(indexOfResult.remainder, 3); + assert.strictEqual(indexOfResult.index, 4); + assert.strictEqual(indexOfResult.remainder, 3); // [1, 0, 0, 0, 3] psc.changeValue(3, 0); - assert.equal(psc.getTotalValue(), 4); - assert.equal(psc.getAccumulatedValue(0), 1); - assert.equal(psc.getAccumulatedValue(1), 1); - assert.equal(psc.getAccumulatedValue(2), 1); - assert.equal(psc.getAccumulatedValue(3), 1); - assert.equal(psc.getAccumulatedValue(4), 4); + assert.strictEqual(psc.getTotalValue(), 4); + assert.strictEqual(psc.getAccumulatedValue(0), 1); + assert.strictEqual(psc.getAccumulatedValue(1), 1); + assert.strictEqual(psc.getAccumulatedValue(2), 1); + assert.strictEqual(psc.getAccumulatedValue(3), 1); + assert.strictEqual(psc.getAccumulatedValue(4), 4); indexOfResult = psc.getIndexOf(0); - assert.equal(indexOfResult.index, 0); - assert.equal(indexOfResult.remainder, 0); + assert.strictEqual(indexOfResult.index, 0); + assert.strictEqual(indexOfResult.remainder, 0); indexOfResult = psc.getIndexOf(1); - assert.equal(indexOfResult.index, 4); - assert.equal(indexOfResult.remainder, 0); + assert.strictEqual(indexOfResult.index, 4); + assert.strictEqual(indexOfResult.remainder, 0); indexOfResult = psc.getIndexOf(2); - assert.equal(indexOfResult.index, 4); - assert.equal(indexOfResult.remainder, 1); + assert.strictEqual(indexOfResult.index, 4); + assert.strictEqual(indexOfResult.remainder, 1); indexOfResult = psc.getIndexOf(3); - assert.equal(indexOfResult.index, 4); - assert.equal(indexOfResult.remainder, 2); + assert.strictEqual(indexOfResult.index, 4); + assert.strictEqual(indexOfResult.remainder, 2); indexOfResult = psc.getIndexOf(4); - assert.equal(indexOfResult.index, 4); - assert.equal(indexOfResult.remainder, 3); + assert.strictEqual(indexOfResult.index, 4); + assert.strictEqual(indexOfResult.remainder, 3); // [1, 1, 0, 1, 1] psc.changeValue(1, 1); psc.changeValue(3, 1); psc.changeValue(4, 1); - assert.equal(psc.getTotalValue(), 4); - assert.equal(psc.getAccumulatedValue(0), 1); - assert.equal(psc.getAccumulatedValue(1), 2); - assert.equal(psc.getAccumulatedValue(2), 2); - assert.equal(psc.getAccumulatedValue(3), 3); - assert.equal(psc.getAccumulatedValue(4), 4); + assert.strictEqual(psc.getTotalValue(), 4); + assert.strictEqual(psc.getAccumulatedValue(0), 1); + assert.strictEqual(psc.getAccumulatedValue(1), 2); + assert.strictEqual(psc.getAccumulatedValue(2), 2); + assert.strictEqual(psc.getAccumulatedValue(3), 3); + assert.strictEqual(psc.getAccumulatedValue(4), 4); indexOfResult = psc.getIndexOf(0); - assert.equal(indexOfResult.index, 0); - assert.equal(indexOfResult.remainder, 0); + assert.strictEqual(indexOfResult.index, 0); + assert.strictEqual(indexOfResult.remainder, 0); indexOfResult = psc.getIndexOf(1); - assert.equal(indexOfResult.index, 1); - assert.equal(indexOfResult.remainder, 0); + assert.strictEqual(indexOfResult.index, 1); + assert.strictEqual(indexOfResult.remainder, 0); indexOfResult = psc.getIndexOf(2); - assert.equal(indexOfResult.index, 3); - assert.equal(indexOfResult.remainder, 0); + assert.strictEqual(indexOfResult.index, 3); + assert.strictEqual(indexOfResult.remainder, 0); indexOfResult = psc.getIndexOf(3); - assert.equal(indexOfResult.index, 4); - assert.equal(indexOfResult.remainder, 0); + assert.strictEqual(indexOfResult.index, 4); + assert.strictEqual(indexOfResult.remainder, 0); indexOfResult = psc.getIndexOf(4); - assert.equal(indexOfResult.index, 4); - assert.equal(indexOfResult.remainder, 1); + assert.strictEqual(indexOfResult.index, 4); + assert.strictEqual(indexOfResult.remainder, 1); }); }); diff --git a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts index 3156be0e1..3f7978952 100644 --- a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts +++ b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts @@ -25,42 +25,42 @@ suite('Editor ViewModel - SplitLinesCollection', () => { let model1 = createModel('My First LineMy Second LineAnd another one'); let line1 = createSplitLine([13, 14, 15], [13, 13 + 14, 13 + 14 + 15], 0); - assert.equal(line1.getViewLineCount(), 3); - assert.equal(line1.getViewLineContent(model1, 1, 0), 'My First Line'); - assert.equal(line1.getViewLineContent(model1, 1, 1), 'My Second Line'); - assert.equal(line1.getViewLineContent(model1, 1, 2), 'And another one'); - assert.equal(line1.getViewLineMaxColumn(model1, 1, 0), 14); - assert.equal(line1.getViewLineMaxColumn(model1, 1, 1), 15); - assert.equal(line1.getViewLineMaxColumn(model1, 1, 2), 16); + assert.strictEqual(line1.getViewLineCount(), 3); + assert.strictEqual(line1.getViewLineContent(model1, 1, 0), 'My First Line'); + assert.strictEqual(line1.getViewLineContent(model1, 1, 1), 'My Second Line'); + assert.strictEqual(line1.getViewLineContent(model1, 1, 2), 'And another one'); + assert.strictEqual(line1.getViewLineMaxColumn(model1, 1, 0), 14); + assert.strictEqual(line1.getViewLineMaxColumn(model1, 1, 1), 15); + assert.strictEqual(line1.getViewLineMaxColumn(model1, 1, 2), 16); for (let col = 1; col <= 14; col++) { - assert.equal(line1.getModelColumnOfViewPosition(0, col), col, 'getInputColumnOfOutputPosition(0, ' + col + ')'); + assert.strictEqual(line1.getModelColumnOfViewPosition(0, col), col, 'getInputColumnOfOutputPosition(0, ' + col + ')'); } for (let col = 1; col <= 15; col++) { - assert.equal(line1.getModelColumnOfViewPosition(1, col), 13 + col, 'getInputColumnOfOutputPosition(1, ' + col + ')'); + assert.strictEqual(line1.getModelColumnOfViewPosition(1, col), 13 + col, 'getInputColumnOfOutputPosition(1, ' + col + ')'); } for (let col = 1; col <= 16; col++) { - assert.equal(line1.getModelColumnOfViewPosition(2, col), 13 + 14 + col, 'getInputColumnOfOutputPosition(2, ' + col + ')'); + assert.strictEqual(line1.getModelColumnOfViewPosition(2, col), 13 + 14 + col, 'getInputColumnOfOutputPosition(2, ' + col + ')'); } for (let col = 1; col <= 13; col++) { - assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(0, col), 'getOutputPositionOfInputPosition(' + col + ')'); + assert.deepStrictEqual(line1.getViewPositionOfModelPosition(0, col), pos(0, col), 'getOutputPositionOfInputPosition(' + col + ')'); } for (let col = 1 + 13; col <= 14 + 13; col++) { - assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(1, col - 13), 'getOutputPositionOfInputPosition(' + col + ')'); + assert.deepStrictEqual(line1.getViewPositionOfModelPosition(0, col), pos(1, col - 13), 'getOutputPositionOfInputPosition(' + col + ')'); } for (let col = 1 + 13 + 14; col <= 15 + 14 + 13; col++) { - assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(2, col - 13 - 14), 'getOutputPositionOfInputPosition(' + col + ')'); + assert.deepStrictEqual(line1.getViewPositionOfModelPosition(0, col), pos(2, col - 13 - 14), 'getOutputPositionOfInputPosition(' + col + ')'); } model1 = createModel('My First LineMy Second LineAnd another one'); line1 = createSplitLine([13, 14, 15], [13, 13 + 14, 13 + 14 + 15], 4); - assert.equal(line1.getViewLineCount(), 3); - assert.equal(line1.getViewLineContent(model1, 1, 0), 'My First Line'); - assert.equal(line1.getViewLineContent(model1, 1, 1), ' My Second Line'); - assert.equal(line1.getViewLineContent(model1, 1, 2), ' And another one'); - assert.equal(line1.getViewLineMaxColumn(model1, 1, 0), 14); - assert.equal(line1.getViewLineMaxColumn(model1, 1, 1), 19); - assert.equal(line1.getViewLineMaxColumn(model1, 1, 2), 20); + assert.strictEqual(line1.getViewLineCount(), 3); + assert.strictEqual(line1.getViewLineContent(model1, 1, 0), 'My First Line'); + assert.strictEqual(line1.getViewLineContent(model1, 1, 1), ' My Second Line'); + assert.strictEqual(line1.getViewLineContent(model1, 1, 2), ' And another one'); + assert.strictEqual(line1.getViewLineMaxColumn(model1, 1, 0), 14); + assert.strictEqual(line1.getViewLineMaxColumn(model1, 1, 1), 19); + assert.strictEqual(line1.getViewLineMaxColumn(model1, 1, 2), 20); let actualViewColumnMapping: number[][] = []; for (let lineIndex = 0; lineIndex < line1.getViewLineCount(); lineIndex++) { @@ -70,20 +70,20 @@ suite('Editor ViewModel - SplitLinesCollection', () => { } actualViewColumnMapping.push(actualLineViewColumnMapping); } - assert.deepEqual(actualViewColumnMapping, [ + assert.deepStrictEqual(actualViewColumnMapping, [ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], [14, 14, 14, 14, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28], [28, 28, 28, 28, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43], ]); for (let col = 1; col <= 13; col++) { - assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(0, col), '6.getOutputPositionOfInputPosition(' + col + ')'); + assert.deepStrictEqual(line1.getViewPositionOfModelPosition(0, col), pos(0, col), '6.getOutputPositionOfInputPosition(' + col + ')'); } for (let col = 1 + 13; col <= 14 + 13; col++) { - assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(1, 4 + col - 13), '7.getOutputPositionOfInputPosition(' + col + ')'); + assert.deepStrictEqual(line1.getViewPositionOfModelPosition(0, col), pos(1, 4 + col - 13), '7.getOutputPositionOfInputPosition(' + col + ')'); } for (let col = 1 + 13 + 14; col <= 15 + 14 + 13; col++) { - assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(2, 4 + col - 13 - 14), '8.getOutputPositionOfInputPosition(' + col + ')'); + assert.deepStrictEqual(line1.getViewPositionOfModelPosition(0, col), pos(2, 4 + col - 13 - 14), '8.getOutputPositionOfInputPosition(' + col + ')'); } }); @@ -136,65 +136,65 @@ suite('Editor ViewModel - SplitLinesCollection', () => { ].join('\n'); withSplitLinesCollection(text, (model, linesCollection) => { - assert.equal(linesCollection.getViewLineCount(), 6); + assert.strictEqual(linesCollection.getViewLineCount(), 6); // getOutputIndentGuide - assert.deepEqual(linesCollection.getViewLinesIndentGuides(-1, -1), [0]); - assert.deepEqual(linesCollection.getViewLinesIndentGuides(0, 0), [0]); - assert.deepEqual(linesCollection.getViewLinesIndentGuides(1, 1), [0]); - assert.deepEqual(linesCollection.getViewLinesIndentGuides(2, 2), [1]); - assert.deepEqual(linesCollection.getViewLinesIndentGuides(3, 3), [0]); - assert.deepEqual(linesCollection.getViewLinesIndentGuides(4, 4), [0]); - assert.deepEqual(linesCollection.getViewLinesIndentGuides(5, 5), [1]); - assert.deepEqual(linesCollection.getViewLinesIndentGuides(6, 6), [0]); - assert.deepEqual(linesCollection.getViewLinesIndentGuides(7, 7), [0]); + assert.deepStrictEqual(linesCollection.getViewLinesIndentGuides(-1, -1), [0]); + assert.deepStrictEqual(linesCollection.getViewLinesIndentGuides(0, 0), [0]); + assert.deepStrictEqual(linesCollection.getViewLinesIndentGuides(1, 1), [0]); + assert.deepStrictEqual(linesCollection.getViewLinesIndentGuides(2, 2), [1]); + assert.deepStrictEqual(linesCollection.getViewLinesIndentGuides(3, 3), [0]); + assert.deepStrictEqual(linesCollection.getViewLinesIndentGuides(4, 4), [0]); + assert.deepStrictEqual(linesCollection.getViewLinesIndentGuides(5, 5), [1]); + assert.deepStrictEqual(linesCollection.getViewLinesIndentGuides(6, 6), [0]); + assert.deepStrictEqual(linesCollection.getViewLinesIndentGuides(7, 7), [0]); - assert.deepEqual(linesCollection.getViewLinesIndentGuides(0, 7), [0, 1, 0, 0, 1, 0]); + assert.deepStrictEqual(linesCollection.getViewLinesIndentGuides(0, 7), [0, 1, 0, 0, 1, 0]); // getOutputLineContent - assert.equal(linesCollection.getViewLineContent(-1), 'int main() {'); - assert.equal(linesCollection.getViewLineContent(0), 'int main() {'); - assert.equal(linesCollection.getViewLineContent(1), 'int main() {'); - assert.equal(linesCollection.getViewLineContent(2), '\tprintf("Hello world!");'); - assert.equal(linesCollection.getViewLineContent(3), '}'); - assert.equal(linesCollection.getViewLineContent(4), 'int main() {'); - assert.equal(linesCollection.getViewLineContent(5), '\tprintf("Hello world!");'); - assert.equal(linesCollection.getViewLineContent(6), '}'); - assert.equal(linesCollection.getViewLineContent(7), '}'); + assert.strictEqual(linesCollection.getViewLineContent(-1), 'int main() {'); + assert.strictEqual(linesCollection.getViewLineContent(0), 'int main() {'); + assert.strictEqual(linesCollection.getViewLineContent(1), 'int main() {'); + assert.strictEqual(linesCollection.getViewLineContent(2), '\tprintf("Hello world!");'); + assert.strictEqual(linesCollection.getViewLineContent(3), '}'); + assert.strictEqual(linesCollection.getViewLineContent(4), 'int main() {'); + assert.strictEqual(linesCollection.getViewLineContent(5), '\tprintf("Hello world!");'); + assert.strictEqual(linesCollection.getViewLineContent(6), '}'); + assert.strictEqual(linesCollection.getViewLineContent(7), '}'); // getOutputLineMinColumn - assert.equal(linesCollection.getViewLineMinColumn(-1), 1); - assert.equal(linesCollection.getViewLineMinColumn(0), 1); - assert.equal(linesCollection.getViewLineMinColumn(1), 1); - assert.equal(linesCollection.getViewLineMinColumn(2), 1); - assert.equal(linesCollection.getViewLineMinColumn(3), 1); - assert.equal(linesCollection.getViewLineMinColumn(4), 1); - assert.equal(linesCollection.getViewLineMinColumn(5), 1); - assert.equal(linesCollection.getViewLineMinColumn(6), 1); - assert.equal(linesCollection.getViewLineMinColumn(7), 1); + assert.strictEqual(linesCollection.getViewLineMinColumn(-1), 1); + assert.strictEqual(linesCollection.getViewLineMinColumn(0), 1); + assert.strictEqual(linesCollection.getViewLineMinColumn(1), 1); + assert.strictEqual(linesCollection.getViewLineMinColumn(2), 1); + assert.strictEqual(linesCollection.getViewLineMinColumn(3), 1); + assert.strictEqual(linesCollection.getViewLineMinColumn(4), 1); + assert.strictEqual(linesCollection.getViewLineMinColumn(5), 1); + assert.strictEqual(linesCollection.getViewLineMinColumn(6), 1); + assert.strictEqual(linesCollection.getViewLineMinColumn(7), 1); // getOutputLineMaxColumn - assert.equal(linesCollection.getViewLineMaxColumn(-1), 13); - assert.equal(linesCollection.getViewLineMaxColumn(0), 13); - assert.equal(linesCollection.getViewLineMaxColumn(1), 13); - assert.equal(linesCollection.getViewLineMaxColumn(2), 25); - assert.equal(linesCollection.getViewLineMaxColumn(3), 2); - assert.equal(linesCollection.getViewLineMaxColumn(4), 13); - assert.equal(linesCollection.getViewLineMaxColumn(5), 25); - assert.equal(linesCollection.getViewLineMaxColumn(6), 2); - assert.equal(linesCollection.getViewLineMaxColumn(7), 2); + assert.strictEqual(linesCollection.getViewLineMaxColumn(-1), 13); + assert.strictEqual(linesCollection.getViewLineMaxColumn(0), 13); + assert.strictEqual(linesCollection.getViewLineMaxColumn(1), 13); + assert.strictEqual(linesCollection.getViewLineMaxColumn(2), 25); + assert.strictEqual(linesCollection.getViewLineMaxColumn(3), 2); + assert.strictEqual(linesCollection.getViewLineMaxColumn(4), 13); + assert.strictEqual(linesCollection.getViewLineMaxColumn(5), 25); + assert.strictEqual(linesCollection.getViewLineMaxColumn(6), 2); + assert.strictEqual(linesCollection.getViewLineMaxColumn(7), 2); // convertOutputPositionToInputPosition - assert.deepEqual(linesCollection.convertViewPositionToModelPosition(-1, 1), new Position(1, 1)); - assert.deepEqual(linesCollection.convertViewPositionToModelPosition(0, 1), new Position(1, 1)); - assert.deepEqual(linesCollection.convertViewPositionToModelPosition(1, 1), new Position(1, 1)); - assert.deepEqual(linesCollection.convertViewPositionToModelPosition(2, 1), new Position(2, 1)); - assert.deepEqual(linesCollection.convertViewPositionToModelPosition(3, 1), new Position(3, 1)); - assert.deepEqual(linesCollection.convertViewPositionToModelPosition(4, 1), new Position(4, 1)); - assert.deepEqual(linesCollection.convertViewPositionToModelPosition(5, 1), new Position(5, 1)); - assert.deepEqual(linesCollection.convertViewPositionToModelPosition(6, 1), new Position(6, 1)); - assert.deepEqual(linesCollection.convertViewPositionToModelPosition(7, 1), new Position(6, 1)); - assert.deepEqual(linesCollection.convertViewPositionToModelPosition(8, 1), new Position(6, 1)); + assert.deepStrictEqual(linesCollection.convertViewPositionToModelPosition(-1, 1), new Position(1, 1)); + assert.deepStrictEqual(linesCollection.convertViewPositionToModelPosition(0, 1), new Position(1, 1)); + assert.deepStrictEqual(linesCollection.convertViewPositionToModelPosition(1, 1), new Position(1, 1)); + assert.deepStrictEqual(linesCollection.convertViewPositionToModelPosition(2, 1), new Position(2, 1)); + assert.deepStrictEqual(linesCollection.convertViewPositionToModelPosition(3, 1), new Position(3, 1)); + assert.deepStrictEqual(linesCollection.convertViewPositionToModelPosition(4, 1), new Position(4, 1)); + assert.deepStrictEqual(linesCollection.convertViewPositionToModelPosition(5, 1), new Position(5, 1)); + assert.deepStrictEqual(linesCollection.convertViewPositionToModelPosition(6, 1), new Position(6, 1)); + assert.deepStrictEqual(linesCollection.convertViewPositionToModelPosition(7, 1), new Position(6, 1)); + assert.deepStrictEqual(linesCollection.convertViewPositionToModelPosition(8, 1), new Position(6, 1)); }); }); @@ -216,7 +216,7 @@ suite('Editor ViewModel - SplitLinesCollection', () => { ]); let viewLineCount = linesCollection.getViewLineCount(); - assert.equal(viewLineCount, 1, 'getOutputLineCount()'); + assert.strictEqual(viewLineCount, 1, 'getOutputLineCount()'); let modelLineCount = model.getLineCount(); for (let lineNumber = 0; lineNumber <= modelLineCount + 1; lineNumber++) { @@ -244,7 +244,7 @@ suite('Editor ViewModel - SplitLinesCollection', () => { viewColumn = viewMaxColumn; } let validViewPosition = new Position(viewLineNumber, viewColumn); - assert.equal(viewPosition.toString(), validViewPosition.toString(), 'model->view for ' + lineNumber + ', ' + column); + assert.strictEqual(viewPosition.toString(), validViewPosition.toString(), 'model->view for ' + lineNumber + ', ' + column); } } @@ -254,7 +254,7 @@ suite('Editor ViewModel - SplitLinesCollection', () => { for (let column = lineMinColumn - 1; column <= lineMaxColumn + 1; column++) { let modelPosition = linesCollection.convertViewPositionToModelPosition(lineNumber, column); let validModelPosition = model.validatePosition(modelPosition); - assert.equal(modelPosition.toString(), validModelPosition.toString(), 'view->model for ' + lineNumber + ', ' + column); + assert.strictEqual(modelPosition.toString(), validModelPosition.toString(), 'view->model for ' + lineNumber + ', ' + column); } } }); @@ -333,7 +333,7 @@ suite('SplitLinesCollection', () => { const tokenizationSupport: modes.ITokenizationSupport = { getInitialState: () => NULL_STATE, tokenize: undefined!, - tokenize2: (line: string, state: modes.IState): TokenizationResult2 => { + tokenize2: (line: string, hasEOL: boolean, state: modes.IState): TokenizationResult2 => { let tokens = _tokens[_lineIndex++]; let result = new Uint32Array(2 * tokens.length); @@ -374,7 +374,7 @@ suite('SplitLinesCollection', () => { value: _actual.getForeground(i) }; } - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); } interface ITestMinimapLineRenderingData { @@ -391,16 +391,15 @@ suite('SplitLinesCollection', () => { } if (expected === null) { assert.ok(false); - return; } - assert.equal(actual.content, expected.content); - assert.equal(actual.minColumn, expected.minColumn); - assert.equal(actual.maxColumn, expected.maxColumn); + assert.strictEqual(actual.content, expected.content); + assert.strictEqual(actual.minColumn, expected.minColumn); + assert.strictEqual(actual.maxColumn, expected.maxColumn); assertViewLineTokens(actual.tokens, expected.tokens); } function assertMinimapLinesRenderingData(actual: ViewLineData[], expected: Array): void { - assert.equal(actual.length, expected.length); + assert.strictEqual(actual.length, expected.length); for (let i = 0; i < expected.length; i++) { assertMinimapLineRenderingData(actual[i], expected[i]); } @@ -429,15 +428,15 @@ suite('SplitLinesCollection', () => { test('getViewLinesData - no wrapping', () => { withSplitLinesCollection(model!, 'off', 0, (splitLinesCollection) => { - assert.equal(splitLinesCollection.getViewLineCount(), 8); - assert.equal(splitLinesCollection.modelPositionIsVisible(1, 1), true); - assert.equal(splitLinesCollection.modelPositionIsVisible(2, 1), true); - assert.equal(splitLinesCollection.modelPositionIsVisible(3, 1), true); - assert.equal(splitLinesCollection.modelPositionIsVisible(4, 1), true); - assert.equal(splitLinesCollection.modelPositionIsVisible(5, 1), true); - assert.equal(splitLinesCollection.modelPositionIsVisible(6, 1), true); - assert.equal(splitLinesCollection.modelPositionIsVisible(7, 1), true); - assert.equal(splitLinesCollection.modelPositionIsVisible(8, 1), true); + assert.strictEqual(splitLinesCollection.getViewLineCount(), 8); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(1, 1), true); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(2, 1), true); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(3, 1), true); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(4, 1), true); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(5, 1), true); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(6, 1), true); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(7, 1), true); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(8, 1), true); let _expected: ITestMinimapLineRenderingData[] = [ { @@ -541,15 +540,15 @@ suite('SplitLinesCollection', () => { ]); splitLinesCollection.setHiddenAreas([new Range(2, 1, 4, 1)]); - assert.equal(splitLinesCollection.getViewLineCount(), 5); - assert.equal(splitLinesCollection.modelPositionIsVisible(1, 1), true); - assert.equal(splitLinesCollection.modelPositionIsVisible(2, 1), false); - assert.equal(splitLinesCollection.modelPositionIsVisible(3, 1), false); - assert.equal(splitLinesCollection.modelPositionIsVisible(4, 1), false); - assert.equal(splitLinesCollection.modelPositionIsVisible(5, 1), true); - assert.equal(splitLinesCollection.modelPositionIsVisible(6, 1), true); - assert.equal(splitLinesCollection.modelPositionIsVisible(7, 1), true); - assert.equal(splitLinesCollection.modelPositionIsVisible(8, 1), true); + assert.strictEqual(splitLinesCollection.getViewLineCount(), 5); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(1, 1), true); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(2, 1), false); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(3, 1), false); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(4, 1), false); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(5, 1), true); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(6, 1), true); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(7, 1), true); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(8, 1), true); assertAllMinimapLinesRenderingData(splitLinesCollection, [ _expected[0], @@ -563,15 +562,15 @@ suite('SplitLinesCollection', () => { test('getViewLinesData - with wrapping', () => { withSplitLinesCollection(model!, 'wordWrapColumn', 30, (splitLinesCollection) => { - assert.equal(splitLinesCollection.getViewLineCount(), 12); - assert.equal(splitLinesCollection.modelPositionIsVisible(1, 1), true); - assert.equal(splitLinesCollection.modelPositionIsVisible(2, 1), true); - assert.equal(splitLinesCollection.modelPositionIsVisible(3, 1), true); - assert.equal(splitLinesCollection.modelPositionIsVisible(4, 1), true); - assert.equal(splitLinesCollection.modelPositionIsVisible(5, 1), true); - assert.equal(splitLinesCollection.modelPositionIsVisible(6, 1), true); - assert.equal(splitLinesCollection.modelPositionIsVisible(7, 1), true); - assert.equal(splitLinesCollection.modelPositionIsVisible(8, 1), true); + assert.strictEqual(splitLinesCollection.getViewLineCount(), 12); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(1, 1), true); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(2, 1), true); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(3, 1), true); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(4, 1), true); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(5, 1), true); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(6, 1), true); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(7, 1), true); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(8, 1), true); let _expected: ITestMinimapLineRenderingData[] = [ { @@ -711,15 +710,15 @@ suite('SplitLinesCollection', () => { ]); splitLinesCollection.setHiddenAreas([new Range(2, 1, 4, 1)]); - assert.equal(splitLinesCollection.getViewLineCount(), 8); - assert.equal(splitLinesCollection.modelPositionIsVisible(1, 1), true); - assert.equal(splitLinesCollection.modelPositionIsVisible(2, 1), false); - assert.equal(splitLinesCollection.modelPositionIsVisible(3, 1), false); - assert.equal(splitLinesCollection.modelPositionIsVisible(4, 1), false); - assert.equal(splitLinesCollection.modelPositionIsVisible(5, 1), true); - assert.equal(splitLinesCollection.modelPositionIsVisible(6, 1), true); - assert.equal(splitLinesCollection.modelPositionIsVisible(7, 1), true); - assert.equal(splitLinesCollection.modelPositionIsVisible(8, 1), true); + assert.strictEqual(splitLinesCollection.getViewLineCount(), 8); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(1, 1), true); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(2, 1), false); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(3, 1), false); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(4, 1), false); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(5, 1), true); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(6, 1), true); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(7, 1), true); + assert.strictEqual(splitLinesCollection.modelPositionIsVisible(8, 1), true); assertAllMinimapLinesRenderingData(splitLinesCollection, [ _expected[0], diff --git a/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts b/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts index cd9906811..9b2862773 100644 --- a/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts +++ b/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; -import { InlineDecorationType } from 'vs/editor/common/viewModel/viewModel'; +import { InlineDecoration, InlineDecorationType } from 'vs/editor/common/viewModel/viewModel'; import { testViewModel } from 'vs/editor/test/common/viewModel/testViewModel'; suite('ViewModelDecorations', () => { @@ -19,11 +19,11 @@ suite('ViewModelDecorations', () => { wordWrapColumn: 13 }; testViewModel(text, opts, (viewModel, model) => { - assert.equal(viewModel.getLineContent(1), 'hello world, '); - assert.equal(viewModel.getLineContent(2), 'this is a '); - assert.equal(viewModel.getLineContent(3), 'buffer that '); - assert.equal(viewModel.getLineContent(4), 'will be '); - assert.equal(viewModel.getLineContent(5), 'wrapped'); + assert.strictEqual(viewModel.getLineContent(1), 'hello world, '); + assert.strictEqual(viewModel.getLineContent(2), 'this is a '); + assert.strictEqual(viewModel.getLineContent(3), 'buffer that '); + assert.strictEqual(viewModel.getLineContent(4), 'will be '); + assert.strictEqual(viewModel.getLineContent(5), 'wrapped'); model.changeDecorations((accessor) => { let createOpts = (id: string) => { @@ -79,7 +79,7 @@ suite('ViewModelDecorations', () => { return dec.options.className; }).filter(Boolean); - assert.deepEqual(actualDecorations, [ + assert.deepStrictEqual(actualDecorations, [ 'dec1', 'dec2', 'dec3', @@ -102,112 +102,28 @@ suite('ViewModelDecorations', () => { ).inlineDecorations; // view line 2: (1,14 -> 1,24) - assert.deepEqual(inlineDecorations1, [ - { - range: new Range(1, 2, 2, 2), - inlineClassName: 'i-dec3', - type: InlineDecorationType.Regular - }, - { - range: new Range(2, 2, 2, 2), - inlineClassName: 'a-dec3', - type: InlineDecorationType.After - }, - { - range: new Range(1, 2, 3, 13), - inlineClassName: 'i-dec4', - type: InlineDecorationType.Regular - }, - { - range: new Range(1, 2, 5, 8), - inlineClassName: 'i-dec5', - type: InlineDecorationType.Regular - }, - { - range: new Range(2, 1, 2, 1), - inlineClassName: 'i-dec6', - type: InlineDecorationType.Regular - }, - { - range: new Range(2, 1, 2, 1), - inlineClassName: 'b-dec6', - type: InlineDecorationType.Before - }, - { - range: new Range(2, 1, 2, 1), - inlineClassName: 'a-dec6', - type: InlineDecorationType.After - }, - { - range: new Range(2, 1, 2, 3), - inlineClassName: 'i-dec7', - type: InlineDecorationType.Regular - }, - { - range: new Range(2, 1, 2, 1), - inlineClassName: 'b-dec7', - type: InlineDecorationType.Before - }, - { - range: new Range(2, 3, 2, 3), - inlineClassName: 'a-dec7', - type: InlineDecorationType.After - }, - { - range: new Range(2, 1, 3, 13), - inlineClassName: 'i-dec8', - type: InlineDecorationType.Regular - }, - { - range: new Range(2, 1, 2, 1), - inlineClassName: 'b-dec8', - type: InlineDecorationType.Before - }, - { - range: new Range(2, 1, 5, 8), - inlineClassName: 'i-dec9', - type: InlineDecorationType.Regular - }, - { - range: new Range(2, 1, 2, 1), - inlineClassName: 'b-dec9', - type: InlineDecorationType.Before - }, - { - range: new Range(2, 3, 2, 5), - inlineClassName: 'i-dec10', - type: InlineDecorationType.Regular - }, - { - range: new Range(2, 3, 2, 3), - inlineClassName: 'b-dec10', - type: InlineDecorationType.Before - }, - { - range: new Range(2, 5, 2, 5), - inlineClassName: 'a-dec10', - type: InlineDecorationType.After - }, - { - range: new Range(2, 3, 3, 13), - inlineClassName: 'i-dec11', - type: InlineDecorationType.Regular - }, - { - range: new Range(2, 3, 2, 3), - inlineClassName: 'b-dec11', - type: InlineDecorationType.Before - }, - { - range: new Range(2, 3, 5, 8), - inlineClassName: 'i-dec12', - type: InlineDecorationType.Regular - }, - { - range: new Range(2, 3, 2, 3), - inlineClassName: 'b-dec12', - type: InlineDecorationType.Before - }, + assert.deepStrictEqual(inlineDecorations1, [ + new InlineDecoration(new Range(1, 2, 2, 2), 'i-dec3', InlineDecorationType.Regular), + new InlineDecoration(new Range(2, 2, 2, 2), 'a-dec3', InlineDecorationType.After), + new InlineDecoration(new Range(1, 2, 3, 13), 'i-dec4', InlineDecorationType.Regular), + new InlineDecoration(new Range(1, 2, 5, 8), 'i-dec5', InlineDecorationType.Regular), + new InlineDecoration(new Range(2, 1, 2, 1), 'i-dec6', InlineDecorationType.Regular), + new InlineDecoration(new Range(2, 1, 2, 1), 'b-dec6', InlineDecorationType.Before), + new InlineDecoration(new Range(2, 1, 2, 1), 'a-dec6', InlineDecorationType.After), + new InlineDecoration(new Range(2, 1, 2, 3), 'i-dec7', InlineDecorationType.Regular), + new InlineDecoration(new Range(2, 1, 2, 1), 'b-dec7', InlineDecorationType.Before), + new InlineDecoration(new Range(2, 3, 2, 3), 'a-dec7', InlineDecorationType.After), + new InlineDecoration(new Range(2, 1, 3, 13), 'i-dec8', InlineDecorationType.Regular), + new InlineDecoration(new Range(2, 1, 2, 1), 'b-dec8', InlineDecorationType.Before), + new InlineDecoration(new Range(2, 1, 5, 8), 'i-dec9', InlineDecorationType.Regular), + new InlineDecoration(new Range(2, 1, 2, 1), 'b-dec9', InlineDecorationType.Before), + new InlineDecoration(new Range(2, 3, 2, 5), 'i-dec10', InlineDecorationType.Regular), + new InlineDecoration(new Range(2, 3, 2, 3), 'b-dec10', InlineDecorationType.Before), + new InlineDecoration(new Range(2, 5, 2, 5), 'a-dec10', InlineDecorationType.After), + new InlineDecoration(new Range(2, 3, 3, 13), 'i-dec11', InlineDecorationType.Regular), + new InlineDecoration(new Range(2, 3, 2, 3), 'b-dec11', InlineDecorationType.Before), + new InlineDecoration(new Range(2, 3, 5, 8), 'i-dec12', InlineDecorationType.Regular), + new InlineDecoration(new Range(2, 3, 2, 3), 'b-dec12', InlineDecorationType.Before), ]); let inlineDecorations2 = viewModel.getViewLineRenderingData( @@ -216,52 +132,16 @@ suite('ViewModelDecorations', () => { ).inlineDecorations; // view line 3 (24 -> 36) - assert.deepEqual(inlineDecorations2, [ - { - range: new Range(1, 2, 3, 13), - inlineClassName: 'i-dec4', - type: InlineDecorationType.Regular - }, - { - range: new Range(3, 13, 3, 13), - inlineClassName: 'a-dec4', - type: InlineDecorationType.After - }, - { - range: new Range(1, 2, 5, 8), - inlineClassName: 'i-dec5', - type: InlineDecorationType.Regular - }, - { - range: new Range(2, 1, 3, 13), - inlineClassName: 'i-dec8', - type: InlineDecorationType.Regular - }, - { - range: new Range(3, 13, 3, 13), - inlineClassName: 'a-dec8', - type: InlineDecorationType.After - }, - { - range: new Range(2, 1, 5, 8), - inlineClassName: 'i-dec9', - type: InlineDecorationType.Regular - }, - { - range: new Range(2, 3, 3, 13), - inlineClassName: 'i-dec11', - type: InlineDecorationType.Regular - }, - { - range: new Range(3, 13, 3, 13), - inlineClassName: 'a-dec11', - type: InlineDecorationType.After - }, - { - range: new Range(2, 3, 5, 8), - inlineClassName: 'i-dec12', - type: InlineDecorationType.Regular - }, + assert.deepStrictEqual(inlineDecorations2, [ + new InlineDecoration(new Range(1, 2, 3, 13), 'i-dec4', InlineDecorationType.Regular), + new InlineDecoration(new Range(3, 13, 3, 13), 'a-dec4', InlineDecorationType.After), + new InlineDecoration(new Range(1, 2, 5, 8), 'i-dec5', InlineDecorationType.Regular), + new InlineDecoration(new Range(2, 1, 3, 13), 'i-dec8', InlineDecorationType.Regular), + new InlineDecoration(new Range(3, 13, 3, 13), 'a-dec8', InlineDecorationType.After), + new InlineDecoration(new Range(2, 1, 5, 8), 'i-dec9', InlineDecorationType.Regular), + new InlineDecoration(new Range(2, 3, 3, 13), 'i-dec11', InlineDecorationType.Regular), + new InlineDecoration(new Range(3, 13, 3, 13), 'a-dec11', InlineDecorationType.After), + new InlineDecoration(new Range(2, 3, 5, 8), 'i-dec12', InlineDecorationType.Regular), ]); }); }); @@ -275,11 +155,11 @@ suite('ViewModelDecorations', () => { wordWrapColumn: 13 }; testViewModel(text, opts, (viewModel, model) => { - assert.equal(viewModel.getLineContent(1), 'hello world, '); - assert.equal(viewModel.getLineContent(2), 'this is a '); - assert.equal(viewModel.getLineContent(3), 'buffer that '); - assert.equal(viewModel.getLineContent(4), 'will be '); - assert.equal(viewModel.getLineContent(5), 'wrapped'); + assert.strictEqual(viewModel.getLineContent(1), 'hello world, '); + assert.strictEqual(viewModel.getLineContent(2), 'this is a '); + assert.strictEqual(viewModel.getLineContent(3), 'buffer that '); + assert.strictEqual(viewModel.getLineContent(4), 'will be '); + assert.strictEqual(viewModel.getLineContent(5), 'wrapped'); model.changeDecorations((accessor) => { accessor.addDecoration( @@ -293,19 +173,19 @@ suite('ViewModelDecorations', () => { let decorations = viewModel.getDecorationsInViewport( new Range(2, viewModel.getLineMinColumn(2), 3, viewModel.getLineMaxColumn(3)) ).filter(x => Boolean(x.options.beforeContentClassName)); - assert.deepEqual(decorations, []); + assert.deepStrictEqual(decorations, []); let inlineDecorations1 = viewModel.getViewLineRenderingData( new Range(2, viewModel.getLineMinColumn(2), 3, viewModel.getLineMaxColumn(3)), 2 ).inlineDecorations; - assert.deepEqual(inlineDecorations1, []); + assert.deepStrictEqual(inlineDecorations1, []); let inlineDecorations2 = viewModel.getViewLineRenderingData( new Range(2, viewModel.getLineMinColumn(2), 3, viewModel.getLineMaxColumn(3)), 3 ).inlineDecorations; - assert.deepEqual(inlineDecorations2, []); + assert.deepStrictEqual(inlineDecorations2, []); }); }); @@ -329,17 +209,9 @@ suite('ViewModelDecorations', () => { new Range(1, 1, 1, 1), 1 ).inlineDecorations; - assert.deepEqual(inlineDecorations, [ - { - range: new Range(1, 1, 1, 1), - inlineClassName: 'before1', - type: InlineDecorationType.Before - }, - { - range: new Range(1, 1, 1, 1), - inlineClassName: 'after1', - type: InlineDecorationType.After - } + assert.deepStrictEqual(inlineDecorations, [ + new InlineDecoration(new Range(1, 1, 1, 1), 'before1', InlineDecorationType.Before), + new InlineDecoration(new Range(1, 1, 1, 1), 'after1', InlineDecorationType.After) ]); }); }); diff --git a/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts b/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts index 6fe30a128..d556f3da7 100644 --- a/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts +++ b/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts @@ -18,7 +18,7 @@ suite('ViewModel', () => { lineNumbersMinChars: 1 }; testViewModel(text, opts, (viewModel, model) => { - assert.equal(viewModel.getLineCount(), 1); + assert.strictEqual(viewModel.getLineCount(), 1); viewModel.setViewport(1, 1, 1); @@ -38,14 +38,14 @@ suite('ViewModel', () => { ].join('\n') }]); - assert.equal(viewModel.getLineCount(), 10); + assert.strictEqual(viewModel.getLineCount(), 10); }); }); test('issue #44805: SplitLinesCollection: attempt to access a \'newer\' model', () => { const text = ['']; testViewModel(text, {}, (viewModel, model) => { - assert.equal(viewModel.getLineCount(), 1); + assert.strictEqual(viewModel.getLineCount(), 1); model.pushEditOperations([], [{ range: new Range(1, 1, 1, 1), @@ -74,7 +74,7 @@ suite('ViewModel', () => { model.undo(); viewLineCount.push(viewModel.getLineCount()); - assert.deepEqual(viewLineCount, [4, 1, 1, 1, 1]); + assert.deepStrictEqual(viewLineCount, [4, 1, 1, 1, 1]); }); }); @@ -85,7 +85,7 @@ suite('ViewModel', () => { 'line3' ]; testViewModel(text, {}, (viewModel, model) => { - assert.equal(viewModel.getLineCount(), 3); + assert.strictEqual(viewModel.getLineCount(), 3); viewModel.setHiddenAreas([new Range(1, 1, 3, 1)]); assert.ok(viewModel.getVisibleRanges() !== null); }); @@ -96,7 +96,7 @@ suite('ViewModel', () => { '' ]; testViewModel(text, {}, (viewModel, model) => { - assert.equal(viewModel.getLineCount(), 1); + assert.strictEqual(viewModel.getLineCount(), 1); model.pushEditOperations([], [{ range: new Range(1, 1, 1, 1), @@ -104,7 +104,7 @@ suite('ViewModel', () => { }], () => ([])); viewModel.setHiddenAreas([new Range(1, 1, 1, 1)]); - assert.equal(viewModel.getLineCount(), 2); + assert.strictEqual(viewModel.getLineCount(), 2); model.undo(); assert.ok(viewModel.getVisibleRanges() !== null); @@ -114,7 +114,7 @@ suite('ViewModel', () => { function assertGetPlainTextToCopy(text: string[], ranges: Range[], emptySelectionClipboard: boolean, expected: string | string[]): void { testViewModel(text, {}, (viewModel, model) => { let actual = viewModel.getPlainTextToCopy(ranges, emptySelectionClipboard, false); - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); }); } @@ -259,7 +259,39 @@ suite('ViewModel', () => { testViewModel(USUAL_TEXT, {}, (viewModel, model) => { model.setEOL(EndOfLineSequence.LF); let actual = viewModel.getPlainTextToCopy([new Range(2, 1, 5, 1)], true, true); - assert.deepEqual(actual, 'line2\r\nline3\r\nline4\r\n'); + assert.deepStrictEqual(actual, 'line2\r\nline3\r\nline4\r\n'); }); }); + + test('issue #40926: Incorrect spacing when inserting new line after multiple folded blocks of code', () => { + testViewModel( + [ + 'foo = {', + ' foobar: function() {', + ' this.foobar();', + ' },', + ' foobar: function() {', + ' this.foobar();', + ' },', + ' foobar: function() {', + ' this.foobar();', + ' },', + '}', + ], {}, (viewModel, model) => { + viewModel.setHiddenAreas([ + new Range(3, 1, 3, 1), + new Range(6, 1, 6, 1), + new Range(9, 1, 9, 1), + ]); + + model.applyEdits([ + { range: new Range(4, 7, 4, 7), text: '\n ' }, + { range: new Range(7, 7, 7, 7), text: '\n ' }, + { range: new Range(10, 7, 10, 7), text: '\n ' } + ]); + + assert.strictEqual(viewModel.getLineCount(), 11); + } + ); + }); }); diff --git a/src/vs/editor/test/node/classification/typescript.test.ts b/src/vs/editor/test/node/classification/typescript.test.ts index 5e399061a..19c7b901e 100644 --- a/src/vs/editor/test/node/classification/typescript.test.ts +++ b/src/vs/editor/test/node/classification/typescript.test.ts @@ -126,7 +126,7 @@ function executeTest(fileName: string, parseFunc: IParseFunc): void { actual[3 * actualIndex] + actual[3 * actualIndex + 1] >= assertion.startOffset + assertion.length, `Line ${assertion.testLineNumber} : length : ${actual[3 * actualIndex]} + ${actual[3 * actualIndex + 1]} >= ${assertion.startOffset} + ${assertion.length}.` ); - assert.equal( + assert.strictEqual( actual[3 * actualIndex + 2], assertion.tokenType, `Line ${assertion.testLineNumber} : tokenType`); diff --git a/src/vs/loader.js b/src/vs/loader.js index 76db97736..a9ac0018c 100644 --- a/src/vs/loader.js +++ b/src/vs/loader.js @@ -36,7 +36,7 @@ var AMDLoader; this._detect(); return this._isWindows; }, - enumerable: true, + enumerable: false, configurable: true }); Object.defineProperty(Environment.prototype, "isNode", { @@ -44,7 +44,7 @@ var AMDLoader; this._detect(); return this._isNode; }, - enumerable: true, + enumerable: false, configurable: true }); Object.defineProperty(Environment.prototype, "isElectronRenderer", { @@ -52,7 +52,7 @@ var AMDLoader; this._detect(); return this._isElectronRenderer; }, - enumerable: true, + enumerable: false, configurable: true }); Object.defineProperty(Environment.prototype, "isWebWorker", { @@ -60,7 +60,7 @@ var AMDLoader; this._detect(); return this._isWebWorker; }, - enumerable: true, + enumerable: false, configurable: true }); Environment.prototype._detect = function () { @@ -199,6 +199,7 @@ var AMDLoader; return obj; } if (!Array.isArray(obj) && Object.getPrototypeOf(obj) !== Object.prototype) { + // only clone "simple" objects return obj; } var result = Array.isArray(obj) ? [] : {}; @@ -740,8 +741,8 @@ var AMDLoader; // nothing } }; - require.resolve = function resolve(request) { - return Module._resolveFilename(request, mod); + require.resolve = function resolve(request, options) { + return Module._resolveFilename(request, mod, false, options); }; require.main = process.mainModule; require.extensions = Module._extensions; @@ -862,7 +863,7 @@ var AMDLoader; } }; NodeScriptLoader.prototype._getCachedDataPath = function (config, filename) { - var hash = this._crypto.createHash('md5').update(filename, 'utf8').update(config.seed, 'utf8').digest('hex'); + var hash = this._crypto.createHash('md5').update(filename, 'utf8').update(config.seed, 'utf8').update(process.arch, '').digest('hex'); var basename = this._path.basename(filename).replace(/\.js$/, ''); return this._path.join(config.path, basename + "-" + hash + ".code"); }; @@ -1217,6 +1218,7 @@ var AMDLoader; this._requireFunc = requireFunc; this._moduleIdProvider = new ModuleIdProvider(); this._config = new AMDLoader.Configuration(this._env); + this._hasDependencyCycle = false; this._modules2 = []; this._knownModules2 = []; this._inverseDependencies2 = []; @@ -1561,6 +1563,9 @@ var AMDLoader; result.getStats = function () { return _this.getLoaderEvents(); }; + result.hasDependencyCycle = function () { + return _this._hasDependencyCycle; + }; result.config = function (params, shouldOverwrite) { if (shouldOverwrite === void 0) { shouldOverwrite = false; } _this.configure(params, shouldOverwrite); @@ -1666,6 +1671,7 @@ var AMDLoader; continue; } if (this._hasDependencyPath(dependency.id, module.id)) { + this._hasDependencyCycle = true; console.warn('There is a dependency cycle between \'' + this._moduleIdProvider.getStrModuleId(dependency.id) + '\' and \'' + this._moduleIdProvider.getStrModuleId(module.id) + '\'. The cyclic path follows:'); var cyclePath = this._findCyclePath(dependency.id, module.id, 0) || []; cyclePath.reverse(); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 2cb6913f7..227452ee5 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -10,6 +10,7 @@ declare namespace monaco { export type Thenable = PromiseLike; export interface Environment { + globalAPI?: boolean; baseUrl?: string; getWorker?(workerId: string, label: string): Worker; getWorkerUrl?(workerId: string, label: string): string; @@ -897,6 +898,12 @@ declare namespace monaco.editor { take?: number; }): IMarker[]; + /** + * Emitted when markers change for a model. + * @event + */ + export function onDidChangeMarkers(listener: (e: readonly Uri[]) => void): IDisposable; + /** * Get the model that has `uri` if it exists. */ @@ -969,6 +976,11 @@ declare namespace monaco.editor { */ export function remeasureFonts(): void; + /** + * Register a command. + */ + export function registerCommand(id: string, handler: (accessor: any, ...args: any[]) => void): IDisposable; + export type BuiltinTheme = 'vs' | 'vs-dark' | 'hc-black'; export interface IStandaloneThemeData { @@ -3176,6 +3188,10 @@ declare namespace monaco.editor { * Controls strikethrough deprecated variables. */ showDeprecated?: boolean; + /** + * Control the behavior and rendering of the inline hints. + */ + inlineHints?: IEditorInlineHintsOptions; } /** @@ -3222,6 +3238,11 @@ declare namespace monaco.editor { * Defaults to false */ isInEmbeddedEditor?: boolean; + /** + * Is the diff editor should render overview ruler + * Defaults to true + */ + renderOverviewRuler?: boolean; /** * Control the wrapping of the diff editor. */ @@ -3522,6 +3543,29 @@ declare namespace monaco.editor { export type EditorLightbulbOptions = Readonly>; + /** + * Configuration options for editor inlineHints + */ + export interface IEditorInlineHintsOptions { + /** + * Enable the inline hints. + * Defaults to true. + */ + enabled?: boolean; + /** + * Font size of inline hints. + * Default to 90% of the editor font size. + */ + fontSize?: number; + /** + * Font family of inline hints. + * Defaults to editor font family. + */ + fontFamily?: string; + } + + export type EditorInlineHintsOptions = Readonly>; + /** * Configuration options for editor minimap */ @@ -4023,11 +4067,12 @@ declare namespace monaco.editor { wrappingIndent = 117, wrappingStrategy = 118, showDeprecated = 119, - editorClassName = 120, - pixelRatio = 121, - tabFocusMode = 122, - layoutInfo = 123, - wrappingInfo = 124 + inlineHints = 120, + editorClassName = 121, + pixelRatio = 122, + tabFocusMode = 123, + layoutInfo = 124, + wrappingInfo = 125 } export const EditorOptions: { acceptSuggestionOnCommitCharacter: IEditorOption; @@ -4128,6 +4173,7 @@ declare namespace monaco.editor { showFoldingControls: IEditorOption; showUnused: IEditorOption; showDeprecated: IEditorOption; + inlineHints: IEditorOption; snippetSuggestions: IEditorOption; smartSelect: IEditorOption; smoothScrolling: IEditorOption; @@ -4492,6 +4538,10 @@ declare namespace monaco.editor { } export interface IDiffEditorConstructionOptions extends IDiffEditorOptions { + /** + * The initial editor dimension (to avoid measuring the container). + */ + dimension?: IDimension; /** * Place overflow widgets inside an external DOM node. * Defaults to an internal DOM node. @@ -4935,6 +4985,7 @@ declare namespace monaco.editor { export class FontInfo extends BareFontInfo { readonly _editorStylingBrand: void; + readonly version: number; readonly isTrusted: boolean; readonly isMonospace: boolean; readonly typicalHalfwidthCharacterWidth: number; @@ -4949,6 +5000,7 @@ declare namespace monaco.editor { export class BareFontInfo { readonly _bareFontInfoBrand: void; readonly zoomLevel: number; + readonly pixelRatio: number; readonly fontFamily: string; readonly fontWeight: string; readonly fontSize: number; @@ -5075,6 +5127,12 @@ declare namespace monaco.languages { tokenize?(line: string, state: IState): ILineTokens; } + /** + * Change the color map that is used for token colors. + * Supported formats (hex): #RRGGBB, $RRGGBBAA, #RGB, #RGBA + */ + export function setColorMap(colorMap: string[] | null): void; + /** * Set the tokens provider for a language (manual implementation). */ @@ -5366,7 +5424,7 @@ declare namespace monaco.languages { /** * This rule will only execute if the text above the this line matches this regular expression. */ - oneLineAboveText?: RegExp; + previousLineText?: RegExp; /** * The action to execute. */ @@ -6352,6 +6410,19 @@ declare namespace monaco.languages { resolveCodeLens?(model: editor.ITextModel, codeLens: CodeLens, token: CancellationToken): ProviderResult; } + export interface InlineHint { + text: string; + range: IRange; + description?: string | IMarkdownString; + whitespaceBefore?: boolean; + whitespaceAfter?: boolean; + } + + export interface InlineHintsProvider { + onDidChangeInlineHints?: IEvent | undefined; + provideInlineHints(model: editor.ITextModel, range: Range, token: CancellationToken): ProviderResult; + } + export interface SemanticTokensLegend { readonly tokenTypes: string[]; readonly tokenModifiers: string[]; @@ -6429,6 +6500,11 @@ declare namespace monaco.languages { * attach this to every token class (by default '.' + name) */ tokenPostfix?: string; + /** + * include line feeds (in the form of a \n character) at the end of lines + * Defaults to false + */ + includeLF?: boolean; } /** diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.css b/src/vs/platform/actions/browser/menuEntryActionViewItem.css new file mode 100644 index 000000000..5ef85311a --- /dev/null +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.css @@ -0,0 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-action-bar .action-item.menu-entry .action-label { + background-image: var(--menu-entry-icon-light); + display: inline-flex; +} + +.vs-dark .monaco-action-bar .action-item.menu-entry .action-label, +.hc-black .monaco-action-bar .action-item.menu-entry .action-label { + background-image: var(--menu-entry-icon-dark); +} diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 1344ea900..fbfdd035a 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createCSSRule, asCSSUrl, ModifierKeyEmitter } from 'vs/base/browser/dom'; +import 'vs/css!./menuEntryActionViewItem'; +import { asCSSUrl, ModifierKeyEmitter } from 'vs/base/browser/dom'; import { domEvent } from 'vs/base/browser/event'; import { IAction, Separator } from 'vs/base/common/actions'; -import { IdGenerator } from 'vs/base/common/idGenerator'; import { IDisposable, toDisposable, MutableDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ICommandAction, IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemAction, Icon } from 'vs/platform/actions/common/actions'; @@ -17,6 +17,7 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; import { isWindows, isLinux } from 'vs/base/common/platform'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export function createAndFillInContextMenuActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, isPrimaryGroup?: (group: string) => boolean): IDisposable { const groups = menu.getActions(options); @@ -44,8 +45,7 @@ function asDisposable(groups: ReadonlyArray<[string, ReadonlyArray]>, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, useAlternativeActions: boolean, isPrimaryGroup: (group: string) => boolean = group => group === 'navigation'): void { - for (let tuple of groups) { - let [group, actions] = tuple; + for (let [group, actions] of groups) { if (useAlternativeActions) { actions = actions.map(a => (a instanceof MenuItemAction) && !!a.alt ? a.alt : a); } @@ -66,10 +66,6 @@ function fillInActions(groups: ReadonlyArray<[string, ReadonlyArray(); - export class MenuEntryActionViewItem extends ActionViewItem { private _wantsAltCommand: boolean = false; @@ -85,7 +81,7 @@ export class MenuEntryActionViewItem extends ActionViewItem { this._altKey = ModifierKeyEmitter.getInstance(); } - protected get _commandAction(): IAction { + protected get _commandAction(): MenuItemAction { return this._wantsAltCommand && (this._action).alt || this._action; } @@ -93,12 +89,14 @@ export class MenuEntryActionViewItem extends ActionViewItem { event.preventDefault(); event.stopPropagation(); - this.actionRunner.run(this._commandAction, this._context) - .then(undefined, err => this._notificationService.error(err)); + this.actionRunner + .run(this._commandAction, this._context) + .catch(err => this._notificationService.error(err)); } render(container: HTMLElement): void { super.render(container); + container.classList.add('menu-entry'); this._updateItemClass(this._action.item); @@ -167,46 +165,39 @@ export class MenuEntryActionViewItem extends ActionViewItem { private _updateItemClass(item: ICommandAction): void { this._itemClassDispose.value = undefined; + const { element, label } = this; + if (!element || !label) { + return; + } + const icon = this._commandAction.checked && (item.toggled as { icon?: Icon })?.icon ? (item.toggled as { icon: Icon }).icon : item.icon; + if (!icon) { + return; + } + if (ThemeIcon.isThemeIcon(icon)) { // theme icons const iconClass = ThemeIcon.asClassName(icon); - if (this.label && iconClass) { - this.label.classList.add(...iconClass.split(' ')); - this._itemClassDispose.value = toDisposable(() => { - if (this.label) { - this.label.classList.remove(...iconClass.split(' ')); - } - }); + label.classList.add(...iconClass.split(' ')); + this._itemClassDispose.value = toDisposable(() => { + label.classList.remove(...iconClass.split(' ')); + }); + + } else { + // icon path/url + if (icon.light) { + label.style.setProperty('--menu-entry-icon-light', asCSSUrl(icon.light)); } - - } else if (icon) { - // icon path - let iconClass: string; - - if (icon.dark?.scheme) { - - const iconPathMapKey = icon.dark.toString(); - - if (ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) { - iconClass = ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!; - } else { - iconClass = ids.nextId(); - createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(icon.light || icon.dark)}`); - createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(icon.dark)}`); - ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass); - } - - if (this.label) { - this.label.classList.add('icon', ...iconClass.split(' ')); - this._itemClassDispose.value = toDisposable(() => { - if (this.label) { - this.label.classList.remove('icon', ...iconClass.split(' ')); - } - }); - } + if (icon.dark) { + label.style.setProperty('--menu-entry-icon-dark', asCSSUrl(icon.dark)); } + label.classList.add('icon'); + this._itemClassDispose.value = toDisposable(() => { + label.classList.remove('icon'); + label.style.removeProperty('--menu-entry-icon-light'); + label.style.removeProperty('--menu-entry-icon-dark'); + }); } } } @@ -215,29 +206,41 @@ export class SubmenuEntryActionViewItem extends DropdownMenuActionViewItem { constructor( action: SubmenuItemAction, - @INotificationService _notificationService: INotificationService, - @IContextMenuService _contextMenuService: IContextMenuService + @IContextMenuService contextMenuService: IContextMenuService ) { - let classNames: string | string[] | undefined; + super(action, { getActions: () => action.actions }, contextMenuService, { + menuAsChild: true, + classNames: ThemeIcon.isThemeIcon(action.item.icon) ? ThemeIcon.asClassName(action.item.icon) : undefined, + }); + } - if (action.item.icon) { - if (ThemeIcon.isThemeIcon(action.item.icon)) { - classNames = ThemeIcon.asClassName(action.item.icon)!; - } else if (action.item.icon.dark?.scheme) { - const iconPathMapKey = action.item.icon.dark.toString(); - - if (ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) { - classNames = ['icon', ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!]; - } else { - const className = ids.nextId(); - classNames = ['icon', className]; - createCSSRule(`.icon.${className}`, `background-image: ${asCSSUrl(action.item.icon.light || action.item.icon.dark)}`); - createCSSRule(`.vs-dark .icon.${className}, .hc-black .icon.${className}`, `background-image: ${asCSSUrl(action.item.icon.dark)}`); - ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, className); + render(container: HTMLElement): void { + super.render(container); + if (this.element) { + container.classList.add('menu-entry'); + const { icon } = (this._action).item; + if (icon && !ThemeIcon.isThemeIcon(icon)) { + this.element.classList.add('icon'); + if (icon.light) { + this.element.style.setProperty('--menu-entry-icon-light', asCSSUrl(icon.light)); + } + if (icon.dark) { + this.element.style.setProperty('--menu-entry-icon-dark', asCSSUrl(icon.dark)); } } } - - super(action, action.actions, _contextMenuService, { classNames: classNames, menuAsChild: true }); + } +} + +/** + * Creates action view items for menu actions or submenu actions. + */ +export function createActionViewItem(instaService: IInstantiationService, action: IAction): undefined | MenuEntryActionViewItem | SubmenuEntryActionViewItem { + if (action instanceof MenuItemAction) { + return instaService.createInstance(MenuEntryActionViewItem, action); + } else if (action instanceof SubmenuItemAction) { + return instaService.createInstance(SubmenuEntryActionViewItem, action); + } else { + return undefined; } } diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index c06a3511b..514fc0bd6 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -16,22 +16,36 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { UriDto } from 'vs/base/common/types'; import { Iterable } from 'vs/base/common/iterator'; import { LinkedList } from 'vs/base/common/linkedList'; +import { CSSIcon } from 'vs/base/common/codicons'; export interface ILocalizedString { + /** + * The localized value of the string. + */ value: string; + /** + * The original (non localized value of the string) + */ original: string; } +export interface ICommandActionTitle extends ILocalizedString { + /** + * The title with a mnemonic designation. && precedes the mnemonic. + */ + mnemonicTitle?: string; +} + export type Icon = { dark?: URI; light?: URI; } | ThemeIcon; export interface ICommandAction { id: string; - title: string | ILocalizedString; + title: string | ICommandActionTitle; category?: string | ILocalizedString; - tooltip?: string | ILocalizedString; + tooltip?: string; icon?: Icon; precondition?: ContextKeyExpression; - toggled?: ContextKeyExpression | { condition: ContextKeyExpression, icon?: Icon, tooltip?: string | ILocalizedString }; + toggled?: ContextKeyExpression | { condition: ContextKeyExpression, icon?: Icon, tooltip?: string }; } export type ISerializableCommandAction = UriDto; @@ -45,7 +59,7 @@ export interface IMenuItem { } export interface ISubmenuItem { - title: string | ILocalizedString; + title: string | ICommandActionTitle; submenu: MenuId; icon?: Icon; when?: ContextKeyExpression; @@ -112,6 +126,7 @@ export class MenuId { static readonly TunnelInline = new MenuId('TunnelInline'); static readonly TunnelTitle = new MenuId('TunnelTitle'); static readonly ViewItemContext = new MenuId('ViewItemContext'); + static readonly ViewContainerTitle = new MenuId('ViewContainerTitle'); static readonly ViewContainerTitleContext = new MenuId('ViewContainerTitleContext'); static readonly ViewTitle = new MenuId('ViewTitle'); static readonly ViewTitleContext = new MenuId('ViewTitleContext'); @@ -132,6 +147,7 @@ export class MenuId { static readonly TimelineTitle = new MenuId('TimelineTitle'); static readonly TimelineTitleContext = new MenuId('TimelineTitleContext'); static readonly AccountsContext = new MenuId('AccountsContext'); + static readonly PanelTitle = new MenuId('PanelTitle'); readonly id: number; readonly _debugName: string; @@ -148,7 +164,7 @@ export interface IMenuActionOptions { } export interface IMenu extends IDisposable { - readonly onDidChange: Event; + readonly onDidChange: Event; getActions(options?: IMenuActionOptions): [string, Array][]; } @@ -334,61 +350,71 @@ export class SubmenuItemAction extends SubmenuAction { } } -export class MenuItemAction extends ExecuteCommandAction { +// implements IAction, does NOT extend Action, so that no one +// subscribes to events of Action or modified properties +export class MenuItemAction implements IAction { readonly item: ICommandAction; readonly alt: MenuItemAction | undefined; - private _options: IMenuActionOptions; + private readonly _options: IMenuActionOptions | undefined; + + readonly id: string; + readonly label: string; + readonly tooltip: string; + readonly class: string | undefined; + readonly enabled: boolean; + readonly checked: boolean; constructor( item: ICommandAction, alt: ICommandAction | undefined, - options: IMenuActionOptions, + options: IMenuActionOptions | undefined, @IContextKeyService contextKeyService: IContextKeyService, - @ICommandService commandService: ICommandService + @ICommandService private _commandService: ICommandService ) { - typeof item.title === 'string' ? super(item.id, item.title, commandService) : super(item.id, item.title.value, commandService); - - this._cssClass = undefined; - this._enabled = !item.precondition || contextKeyService.contextMatchesRules(item.precondition); - this._tooltip = item.tooltip ? typeof item.tooltip === 'string' ? item.tooltip : item.tooltip.value : undefined; + this.id = item.id; + this.label = typeof item.title === 'string' ? item.title : item.title.value; + this.tooltip = item.tooltip ?? ''; + this.enabled = !item.precondition || contextKeyService.contextMatchesRules(item.precondition); + this.checked = false; if (item.toggled) { const toggled = ((item.toggled as { condition: ContextKeyExpression }).condition ? item.toggled : { condition: item.toggled }) as { condition: ContextKeyExpression, icon?: Icon, tooltip?: string | ILocalizedString }; - this._checked = contextKeyService.contextMatchesRules(toggled.condition); - if (this._checked && toggled.tooltip) { - this._tooltip = typeof toggled.tooltip === 'string' ? toggled.tooltip : toggled.tooltip.value; + this.checked = contextKeyService.contextMatchesRules(toggled.condition); + if (this.checked && toggled.tooltip) { + this.tooltip = typeof toggled.tooltip === 'string' ? toggled.tooltip : toggled.tooltip.value; } } - this._options = options || {}; - this.item = item; - this.alt = alt ? new MenuItemAction(alt, undefined, this._options, contextKeyService, commandService) : undefined; + this.alt = alt ? new MenuItemAction(alt, undefined, options, contextKeyService, _commandService) : undefined; + this._options = options; + if (ThemeIcon.isThemeIcon(item.icon)) { + this.class = CSSIcon.asClassName(item.icon); + } } dispose(): void { - if (this.alt) { - this.alt.dispose(); - } - super.dispose(); + // there is NOTHING to dispose and the MenuItemAction should + // never have anything to dispose as it is a convenience type + // to bridge into the rendering world. } run(...args: any[]): Promise { let runArgs: any[] = []; - if (this._options.arg) { + if (this._options?.arg) { runArgs = [...runArgs, this._options.arg]; } - if (this._options.shouldForwardArgs) { + if (this._options?.shouldForwardArgs) { runArgs = [...runArgs, ...args]; } - return super.run(...runArgs); + return this._commandService.executeCommand(this.id, ...runArgs); } } diff --git a/src/vs/platform/actions/common/menuService.ts b/src/vs/platform/actions/common/menuService.ts index 6ea4c5a08..eaabf117b 100644 --- a/src/vs/platform/actions/common/menuService.ts +++ b/src/vs/platform/actions/common/menuService.ts @@ -3,11 +3,12 @@ * 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 { DisposableStore } from 'vs/base/common/lifecycle'; import { IMenu, IMenuActionOptions, IMenuItem, IMenuService, isIMenuItem, ISubmenuItem, MenuId, MenuItemAction, MenuRegistry, SubmenuItemAction, ILocalizedString } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IContextKeyService, IContextKeyChangeEvent, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; export class MenuService implements IMenuService { @@ -29,9 +30,11 @@ type MenuItemGroup = [string, Array]; class Menu implements IMenu { - private readonly _onDidChange = new Emitter(); private readonly _dispoables = new DisposableStore(); + private readonly _onDidChange = new Emitter(); + readonly onDidChange: Event = this._onDidChange.event; + private _menuGroups: MenuItemGroup[] = []; private _contextKeys: Set = new Set(); @@ -45,19 +48,23 @@ class Menu implements IMenu { // rebuild this menu whenever the menu registry reports an // event for this MenuId - this._dispoables.add(Event.debounce( - Event.filter(MenuRegistry.onDidChangeMenu, set => set.has(this._id)), - () => { }, - 50 - )(this._build, this)); + const rebuildMenuSoon = new RunOnceScheduler(() => this._build(), 50); + this._dispoables.add(rebuildMenuSoon); + this._dispoables.add(MenuRegistry.onDidChangeMenu(e => { + if (e.has(_id)) { + rebuildMenuSoon.schedule(); + } + })); // when context keys change we need to check if the menu also // has changed - this._dispoables.add(Event.debounce( - this._contextKeyService.onDidChangeContext, - (last, event) => last || event.affectsSome(this._contextKeys), - 50 - )(e => e && this._onDidChange.fire(undefined), this)); + const fireChangeSoon = new RunOnceScheduler(() => this._onDidChange.fire(this), 50); + this._dispoables.add(fireChangeSoon); + this._dispoables.add(_contextKeyService.onDidChangeContext(e => { + if (e.affectsSome(this._contextKeys)) { + fireChangeSoon.schedule(); + } + })); } dispose(): void { @@ -88,25 +95,22 @@ class Menu implements IMenu { // keep keys for eventing Menu._fillInKbExprKeys(item.when, this._contextKeys); - // keep precondition keys for event if applicable - if (isIMenuItem(item) && item.command.precondition) { - Menu._fillInKbExprKeys(item.command.precondition, this._contextKeys); - } - - // keep toggled keys for event if applicable - if (isIMenuItem(item) && item.command.toggled) { - const toggledExpression: ContextKeyExpression = (item.command.toggled as { condition: ContextKeyExpression }).condition || item.command.toggled; - Menu._fillInKbExprKeys(toggledExpression, this._contextKeys); + if (isIMenuItem(item)) { + // keep precondition keys for event if applicable + if (item.command.precondition) { + Menu._fillInKbExprKeys(item.command.precondition, this._contextKeys); + } + // keep toggled keys for event if applicable + if (item.command.toggled) { + const toggledExpression: ContextKeyExpression = (item.command.toggled as { condition: ContextKeyExpression }).condition || item.command.toggled; + Menu._fillInKbExprKeys(toggledExpression, this._contextKeys); + } } } this._onDidChange.fire(this); } - get onDidChange(): Event { - return this._onDidChange.event; - } - - getActions(options: IMenuActionOptions): [string, Array][] { + getActions(options?: IMenuActionOptions): [string, Array][] { const result: [string, Array][] = []; for (let group of this._menuGroups) { const [id, items] = group; diff --git a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts index 42e2636d8..75b2cac37 100644 --- a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts +++ b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts @@ -20,39 +20,14 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ import { ConsoleLogMainService } from 'vs/platform/log/common/log'; import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { createHash } from 'crypto'; -import { getRandomTestPath } from 'vs/base/test/node/testUtils'; +import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; -suite('BackupMainService', () => { +flakySuite('BackupMainService', () => { function assertEqualUris(actual: URI[], expected: URI[]) { - assert.deepEqual(actual.map(a => a.toString()), expected.map(a => a.toString())); - } - - const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backupservice'); - const backupHome = path.join(parentDir, 'Backups'); - const backupWorkspacesPath = path.join(backupHome, 'workspaces.json'); - - const environmentService = new EnvironmentMainService(parseArgs(process.argv, OPTIONS)); - - class TestBackupMainService extends BackupMainService { - - constructor(backupHome: string, backupWorkspacesPath: string, configService: TestConfigurationService) { - super(environmentService, configService, new ConsoleLogMainService()); - - this.backupHome = backupHome; - this.workspacesJsonPath = backupWorkspacesPath; - } - - toBackupPath(arg: URI | string): string { - const id = arg instanceof URI ? super.getFolderHash(arg) : arg; - return path.join(this.backupHome, id); - } - - getFolderHash(folderUri: URI): string { - return super.getFolderHash(folderUri); - } + assert.deepStrictEqual(actual.map(a => a.toString()), expected.map(a => a.toString())); } function toWorkspace(path: string): IWorkspaceIdentifier { @@ -79,20 +54,23 @@ suite('BackupMainService', () => { }; } - async function ensureFolderExists(uri: URI): Promise { + function ensureFolderExists(uri: URI): Promise { if (!fs.existsSync(uri.fsPath)) { fs.mkdirSync(uri.fsPath); } + const backupFolder = service.toBackupPath(uri); - await createBackupFolder(backupFolder); + return createBackupFolder(backupFolder); } async function ensureWorkspaceExists(workspace: IWorkspaceIdentifier): Promise { if (!fs.existsSync(workspace.configPath.fsPath)) { await pfs.writeFile(workspace.configPath.fsPath, 'Hello'); } + const backupFolder = service.toBackupPath(workspace.id); await createBackupFolder(backupFolder); + return workspace; } @@ -111,29 +89,52 @@ suite('BackupMainService', () => { const fooFile = URI.file(platform.isWindows ? 'C:\\foo' : '/foo'); const barFile = URI.file(platform.isWindows ? 'C:\\bar' : '/bar'); - const existingTestFolder1 = URI.file(path.join(parentDir, 'folder1')); - - let service: TestBackupMainService; + let service: BackupMainService & { toBackupPath(arg: URI | string): string, getFolderHash(folderUri: URI): string }; let configService: TestConfigurationService; - setup(async () => { + let environmentService: EnvironmentMainService; + let testDir: string; + let backupHome: string; + let backupWorkspacesPath: string; + let existingTestFolder1: URI; + + setup(async () => { + testDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backupmainservice'); + backupHome = path.join(testDir, 'Backups'); + backupWorkspacesPath = path.join(backupHome, 'workspaces.json'); + existingTestFolder1 = URI.file(path.join(testDir, 'folder1')); + + environmentService = new EnvironmentMainService(parseArgs(process.argv, OPTIONS)); - // Delete any existing backups completely and then re-create it. - await pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); await pfs.mkdirp(backupHome); configService = new TestConfigurationService(); - service = new TestBackupMainService(backupHome, backupWorkspacesPath, configService); + service = new class TestBackupMainService extends BackupMainService { + constructor() { + super(environmentService, configService, new ConsoleLogMainService()); + + this.backupHome = backupHome; + this.workspacesJsonPath = backupWorkspacesPath; + } + + toBackupPath(arg: URI | string): string { + const id = arg instanceof URI ? super.getFolderHash(arg) : arg; + return path.join(this.backupHome, id); + } + + getFolderHash(folderUri: URI): string { + return super.getFolderHash(folderUri); + } + }; return service.initialize(); }); teardown(() => { - return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); + return pfs.rimraf(testDir); }); test('service validates backup workspaces on startup and cleans up (folder workspaces)', async function () { - this.timeout(1000 * 10); // increase timeout for this test // 1) backup workspace path does not exist service.registerFolderBackupSync(fooFile); @@ -170,22 +171,21 @@ suite('BackupMainService', () => { fs.mkdirSync(service.toBackupPath(barFile)); fs.mkdirSync(fileBackups); service.registerFolderBackupSync(fooFile); - assert.equal(service.getFolderBackupPaths().length, 1); - assert.equal(service.getEmptyWindowBackupPaths().length, 0); + assert.strictEqual(service.getFolderBackupPaths().length, 1); + assert.strictEqual(service.getEmptyWindowBackupPaths().length, 0); fs.writeFileSync(path.join(fileBackups, 'backup.txt'), ''); await service.initialize(); - assert.equal(service.getFolderBackupPaths().length, 0); - assert.equal(service.getEmptyWindowBackupPaths().length, 1); + assert.strictEqual(service.getFolderBackupPaths().length, 0); + assert.strictEqual(service.getEmptyWindowBackupPaths().length, 1); }); test('service validates backup workspaces on startup and cleans up (root workspaces)', async function () { - this.timeout(1000 * 10); // increase timeout for this test // 1) backup workspace path does not exist service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath)); await service.initialize(); - assert.deepEqual(service.getWorkspaceBackups(), []); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); // 2) backup workspace path exists with empty contents within fs.mkdirSync(service.toBackupPath(fooFile)); @@ -193,7 +193,7 @@ suite('BackupMainService', () => { service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath)); await service.initialize(); - assert.deepEqual(service.getWorkspaceBackups(), []); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); assert.ok(!fs.existsSync(service.toBackupPath(barFile))); @@ -205,7 +205,7 @@ suite('BackupMainService', () => { service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath)); await service.initialize(); - assert.deepEqual(service.getWorkspaceBackups(), []); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); assert.ok(!fs.existsSync(service.toBackupPath(barFile))); @@ -216,12 +216,12 @@ suite('BackupMainService', () => { fs.mkdirSync(service.toBackupPath(barFile)); fs.mkdirSync(fileBackups); service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); - assert.equal(service.getWorkspaceBackups().length, 1); - assert.equal(service.getEmptyWindowBackupPaths().length, 0); + assert.strictEqual(service.getWorkspaceBackups().length, 1); + assert.strictEqual(service.getEmptyWindowBackupPaths().length, 0); fs.writeFileSync(path.join(fileBackups, 'backup.txt'), ''); await service.initialize(); - assert.equal(service.getWorkspaceBackups().length, 0); - assert.equal(service.getEmptyWindowBackupPaths().length, 1); + assert.strictEqual(service.getWorkspaceBackups().length, 0); + assert.strictEqual(service.getEmptyWindowBackupPaths().length, 1); }); test('service supports to migrate backup data from another location', () => { @@ -237,7 +237,7 @@ suite('BackupMainService', () => { assert.ok(!fs.existsSync(backupPathToMigrate)); const emptyBackups = service.getEmptyWindowBackupPaths(); - assert.equal(0, emptyBackups.length); + assert.strictEqual(0, emptyBackups.length); }); test('service backup migration makes sure to preserve existing backups', () => { @@ -258,8 +258,8 @@ suite('BackupMainService', () => { assert.ok(!fs.existsSync(backupPathToMigrate)); const emptyBackups = service.getEmptyWindowBackupPaths(); - assert.equal(1, emptyBackups.length); - assert.equal(1, fs.readdirSync(path.join(backupHome, emptyBackups[0].backupFolder!)).length); + assert.strictEqual(1, emptyBackups.length); + assert.strictEqual(1, fs.readdirSync(path.join(backupHome, emptyBackups[0].backupFolder!)).length); }); suite('loadSync', () => { @@ -315,121 +315,120 @@ suite('BackupMainService', () => { }); test('getWorkspaceBackups() should return [] when workspaces.json doesn\'t exist', () => { - assert.deepEqual(service.getWorkspaceBackups(), []); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); }); test('getWorkspaceBackups() should return [] when workspaces.json is not properly formed JSON', async () => { fs.writeFileSync(backupWorkspacesPath, ''); await service.initialize(); - assert.deepEqual(service.getWorkspaceBackups(), []); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); fs.writeFileSync(backupWorkspacesPath, '{]'); await service.initialize(); - assert.deepEqual(service.getWorkspaceBackups(), []); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); fs.writeFileSync(backupWorkspacesPath, 'foo'); await service.initialize(); - assert.deepEqual(service.getWorkspaceBackups(), []); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); }); test('getWorkspaceBackups() should return [] when folderWorkspaces in workspaces.json is absent', async () => { fs.writeFileSync(backupWorkspacesPath, '{}'); await service.initialize(); - assert.deepEqual(service.getWorkspaceBackups(), []); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); }); test('getWorkspaceBackups() should return [] when rootWorkspaces in workspaces.json is not a object array', async () => { fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":{}}'); await service.initialize(); - assert.deepEqual(service.getWorkspaceBackups(), []); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":{"foo": ["bar"]}}'); await service.initialize(); - assert.deepEqual(service.getWorkspaceBackups(), []); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":{"foo": []}}'); await service.initialize(); - assert.deepEqual(service.getWorkspaceBackups(), []); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":{"foo": "bar"}}'); await service.initialize(); - assert.deepEqual(service.getWorkspaceBackups(), []); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":"foo"}'); await service.initialize(); - assert.deepEqual(service.getWorkspaceBackups(), []); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":1}'); await service.initialize(); - assert.deepEqual(service.getWorkspaceBackups(), []); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); }); test('getWorkspaceBackups() should return [] when rootURIWorkspaces in workspaces.json is not a object array', async () => { fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":{}}'); await service.initialize(); - assert.deepEqual(service.getWorkspaceBackups(), []); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":{"foo": ["bar"]}}'); await service.initialize(); - assert.deepEqual(service.getWorkspaceBackups(), []); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":{"foo": []}}'); await service.initialize(); - assert.deepEqual(service.getWorkspaceBackups(), []); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":{"foo": "bar"}}'); await service.initialize(); - assert.deepEqual(service.getWorkspaceBackups(), []); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":"foo"}'); await service.initialize(); - assert.deepEqual(service.getWorkspaceBackups(), []); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":1}'); await service.initialize(); - assert.deepEqual(service.getWorkspaceBackups(), []); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); }); test('getWorkspaceBackups() should return [] when files.hotExit = "onExitAndWindowClose"', async () => { const upperFooPath = fooFile.fsPath.toUpperCase(); service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(upperFooPath)); - assert.equal(service.getWorkspaceBackups().length, 1); + assert.strictEqual(service.getWorkspaceBackups().length, 1); assertEqualUris(service.getWorkspaceBackups().map(r => r.workspace.configPath), [URI.file(upperFooPath)]); configService.setUserConfiguration('files.hotExit', HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE); await service.initialize(); - assert.deepEqual(service.getWorkspaceBackups(), []); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); }); test('getEmptyWorkspaceBackupPaths() should return [] when workspaces.json doesn\'t exist', () => { - assert.deepEqual(service.getEmptyWindowBackupPaths(), []); + assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []); }); test('getEmptyWorkspaceBackupPaths() should return [] when workspaces.json is not properly formed JSON', async () => { fs.writeFileSync(backupWorkspacesPath, ''); await service.initialize(); - assert.deepEqual(service.getEmptyWindowBackupPaths(), []); + assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []); fs.writeFileSync(backupWorkspacesPath, '{]'); await service.initialize(); - assert.deepEqual(service.getEmptyWindowBackupPaths(), []); + assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []); fs.writeFileSync(backupWorkspacesPath, 'foo'); await service.initialize(); - assert.deepEqual(service.getEmptyWindowBackupPaths(), []); + assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []); }); test('getEmptyWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is absent', async () => { fs.writeFileSync(backupWorkspacesPath, '{}'); await service.initialize(); - assert.deepEqual(service.getEmptyWindowBackupPaths(), []); + assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []); }); test('getEmptyWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is not a string array', async function () { - this.timeout(5000); fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{}}'); await service.initialize(); - assert.deepEqual(service.getEmptyWindowBackupPaths(), []); + assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []); fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{"foo": ["bar"]}}'); await service.initialize(); - assert.deepEqual(service.getEmptyWindowBackupPaths(), []); + assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []); fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{"foo": []}}'); await service.initialize(); - assert.deepEqual(service.getEmptyWindowBackupPaths(), []); + assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []); fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{"foo": "bar"}}'); await service.initialize(); - assert.deepEqual(service.getEmptyWindowBackupPaths(), []); + assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []); fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":"foo"}'); await service.initialize(); - assert.deepEqual(service.getEmptyWindowBackupPaths(), []); + assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []); fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":1}'); await service.initialize(); - assert.deepEqual(service.getEmptyWindowBackupPaths(), []); + assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []); }); }); @@ -448,7 +447,7 @@ suite('BackupMainService', () => { const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); const json = JSON.parse(buffer); - assert.deepEqual(json.folderURIWorkspaces, [existingTestFolder1.toString()]); + assert.deepStrictEqual(json.folderURIWorkspaces, [existingTestFolder1.toString()]); }); test('should ignore duplicates on Windows and Mac (folder workspace)', async () => { @@ -464,14 +463,13 @@ suite('BackupMainService', () => { await service.initialize(); const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); const json = JSON.parse(buffer); - assert.deepEqual(json.folderURIWorkspaces, [existingTestFolder1.toString()]); + assert.deepStrictEqual(json.folderURIWorkspaces, [existingTestFolder1.toString()]); }); test('should ignore duplicates on Windows and Mac (root workspace)', async () => { - - const workspacePath = path.join(parentDir, 'Foo.code-workspace'); - const workspacePath1 = path.join(parentDir, 'FOO.code-workspace'); - const workspacePath2 = path.join(parentDir, 'foo.code-workspace'); + const workspacePath = path.join(testDir, 'Foo.code-workspace'); + const workspacePath1 = path.join(testDir, 'FOO.code-workspace'); + const workspacePath2 = path.join(testDir, 'foo.code-workspace'); const workspace1 = await ensureWorkspaceExists(toWorkspace(workspacePath)); const workspace2 = await ensureWorkspaceExists(toWorkspace(workspacePath1)); @@ -487,11 +485,11 @@ suite('BackupMainService', () => { const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); const json = JSON.parse(buffer); - assert.equal(json.rootURIWorkspaces.length, platform.isLinux ? 3 : 1); + assert.strictEqual(json.rootURIWorkspaces.length, platform.isLinux ? 3 : 1); if (platform.isLinux) { - assert.deepEqual(json.rootURIWorkspaces.map(r => r.configURIPath), [URI.file(workspacePath).toString(), URI.file(workspacePath1).toString(), URI.file(workspacePath2).toString()]); + assert.deepStrictEqual(json.rootURIWorkspaces.map(r => r.configURIPath), [URI.file(workspacePath).toString(), URI.file(workspacePath1).toString(), URI.file(workspacePath2).toString()]); } else { - assert.deepEqual(json.rootURIWorkspaces.map(r => r.configURIPath), [URI.file(workspacePath).toString()], 'should return the first duplicated entry'); + assert.deepStrictEqual(json.rootURIWorkspaces.map(r => r.configURIPath), [URI.file(workspacePath).toString()], 'should return the first duplicated entry'); } }); }); @@ -503,7 +501,7 @@ suite('BackupMainService', () => { assertEqualUris(service.getFolderBackupPaths(), [fooFile, barFile]); const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); const json = JSON.parse(buffer); - assert.deepEqual(json.folderURIWorkspaces, [fooFile.toString(), barFile.toString()]); + assert.deepStrictEqual(json.folderURIWorkspaces, [fooFile.toString(), barFile.toString()]); }); test('should persist paths to workspaces.json (root workspace)', async () => { @@ -513,15 +511,15 @@ suite('BackupMainService', () => { service.registerWorkspaceBackupSync(ws2); assertEqualUris(service.getWorkspaceBackups().map(b => b.workspace.configPath), [fooFile, barFile]); - assert.equal(ws1.workspace.id, service.getWorkspaceBackups()[0].workspace.id); - assert.equal(ws2.workspace.id, service.getWorkspaceBackups()[1].workspace.id); + assert.strictEqual(ws1.workspace.id, service.getWorkspaceBackups()[0].workspace.id); + assert.strictEqual(ws2.workspace.id, service.getWorkspaceBackups()[1].workspace.id); const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); const json = JSON.parse(buffer); - assert.deepEqual(json.rootURIWorkspaces.map(b => b.configURIPath), [fooFile.toString(), barFile.toString()]); - assert.equal(ws1.workspace.id, json.rootURIWorkspaces[0].id); - assert.equal(ws2.workspace.id, json.rootURIWorkspaces[1].id); + assert.deepStrictEqual(json.rootURIWorkspaces.map(b => b.configURIPath), [fooFile.toString(), barFile.toString()]); + assert.strictEqual(ws1.workspace.id, json.rootURIWorkspaces[0].id); + assert.strictEqual(ws2.workspace.id, json.rootURIWorkspaces[1].id); }); }); @@ -531,7 +529,7 @@ suite('BackupMainService', () => { const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); const json = JSON.parse(buffer); - assert.deepEqual(json.folderURIWorkspaces, [URI.file(fooFile.fsPath.toUpperCase()).toString()]); + assert.deepStrictEqual(json.folderURIWorkspaces, [URI.file(fooFile.fsPath.toUpperCase()).toString()]); }); test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive (root workspace)', async () => { @@ -541,7 +539,7 @@ suite('BackupMainService', () => { const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); const json = (JSON.parse(buffer)); - assert.deepEqual(json.rootURIWorkspaces.map(b => b.configURIPath), [URI.file(upperFooPath).toString()]); + assert.deepStrictEqual(json.rootURIWorkspaces.map(b => b.configURIPath), [URI.file(upperFooPath).toString()]); }); suite('removeBackupPathSync', () => { @@ -552,12 +550,12 @@ suite('BackupMainService', () => { const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); const json = (JSON.parse(buffer)); - assert.deepEqual(json.folderURIWorkspaces, [barFile.toString()]); + assert.deepStrictEqual(json.folderURIWorkspaces, [barFile.toString()]); service.unregisterFolderBackupSync(barFile); const content = await pfs.readFile(backupWorkspacesPath, 'utf-8'); const json2 = (JSON.parse(content)); - assert.deepEqual(json2.folderURIWorkspaces, []); + assert.deepStrictEqual(json2.folderURIWorkspaces, []); }); test('should remove folder workspaces from workspaces.json (root workspace)', async () => { @@ -569,12 +567,12 @@ suite('BackupMainService', () => { const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); const json = (JSON.parse(buffer)); - assert.deepEqual(json.rootURIWorkspaces.map(r => r.configURIPath), [barFile.toString()]); + assert.deepStrictEqual(json.rootURIWorkspaces.map(r => r.configURIPath), [barFile.toString()]); service.unregisterWorkspaceBackupSync(ws2.workspace); const content = await pfs.readFile(backupWorkspacesPath, 'utf-8'); const json2 = (JSON.parse(content)); - assert.deepEqual(json2.rootURIWorkspaces, []); + assert.deepStrictEqual(json2.rootURIWorkspaces, []); }); test('should remove empty workspaces from workspaces.json', async () => { @@ -584,12 +582,12 @@ suite('BackupMainService', () => { const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); const json = (JSON.parse(buffer)); - assert.deepEqual(json.emptyWorkspaceInfos, [{ backupFolder: 'bar' }]); + assert.deepStrictEqual(json.emptyWorkspaceInfos, [{ backupFolder: 'bar' }]); service.unregisterEmptyWindowBackupSync('bar'); const content = await pfs.readFile(backupWorkspacesPath, 'utf-8'); const json2 = (JSON.parse(content)); - assert.deepEqual(json2.emptyWorkspaceInfos, []); + assert.deepStrictEqual(json2.emptyWorkspaceInfos, []); }); test('should fail gracefully when removing a path that doesn\'t exist', async () => { @@ -603,24 +601,18 @@ suite('BackupMainService', () => { service.unregisterEmptyWindowBackupSync('test'); const content = await pfs.readFile(backupWorkspacesPath, 'utf-8'); const json = (JSON.parse(content)); - assert.deepEqual(json.folderURIWorkspaces, [existingTestFolder1.toString()]); + assert.deepStrictEqual(json.folderURIWorkspaces, [existingTestFolder1.toString()]); }); }); suite('getWorkspaceHash', () => { - - test('should ignore case on Windows and Mac', () => { - // Skip test on Linux - if (platform.isLinux) { - return; - } - + (platform.isLinux ? test.skip : test)('should ignore case on Windows and Mac', () => { if (platform.isMacintosh) { - assert.equal(service.getFolderHash(URI.file('/foo')), service.getFolderHash(URI.file('/FOO'))); + assert.strictEqual(service.getFolderHash(URI.file('/foo')), service.getFolderHash(URI.file('/FOO'))); } if (platform.isWindows) { - assert.equal(service.getFolderHash(URI.file('c:\\foo')), service.getFolderHash(URI.file('C:\\FOO'))); + assert.strictEqual(service.getFolderHash(URI.file('c:\\foo')), service.getFolderHash(URI.file('C:\\FOO'))); } }); }); @@ -631,9 +623,9 @@ suite('BackupMainService', () => { service.registerFolderBackupSync(URI.file(fooFile.fsPath.toUpperCase())); if (platform.isLinux) { - assert.equal(service.getFolderBackupPaths().length, 2); + assert.strictEqual(service.getFolderBackupPaths().length, 2); } else { - assert.equal(service.getFolderBackupPaths().length, 1); + assert.strictEqual(service.getFolderBackupPaths().length, 1); } }); @@ -642,9 +634,9 @@ suite('BackupMainService', () => { service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath.toUpperCase())); if (platform.isLinux) { - assert.equal(service.getWorkspaceBackups().length, 2); + assert.strictEqual(service.getWorkspaceBackups().length, 2); } else { - assert.equal(service.getWorkspaceBackups().length, 1); + assert.strictEqual(service.getWorkspaceBackups().length, 1); } }); @@ -653,16 +645,16 @@ suite('BackupMainService', () => { // same case service.registerFolderBackupSync(fooFile); service.unregisterFolderBackupSync(fooFile); - assert.equal(service.getFolderBackupPaths().length, 0); + assert.strictEqual(service.getFolderBackupPaths().length, 0); // mixed case service.registerFolderBackupSync(fooFile); service.unregisterFolderBackupSync(URI.file(fooFile.fsPath.toUpperCase())); if (platform.isLinux) { - assert.equal(service.getFolderBackupPaths().length, 1); + assert.strictEqual(service.getFolderBackupPaths().length, 1); } else { - assert.equal(service.getFolderBackupPaths().length, 0); + assert.strictEqual(service.getFolderBackupPaths().length, 0); } }); }); @@ -674,7 +666,7 @@ suite('BackupMainService', () => { const backupWorkspaceInfo = toWorkspaceBackupInfo(fooFile.fsPath); const workspaceBackupPath = service.registerWorkspaceBackupSync(backupWorkspaceInfo); - assert.equal(((await service.getDirtyWorkspaces()).length), 0); + assert.strictEqual(((await service.getDirtyWorkspaces()).length), 0); try { await pfs.mkdirp(path.join(folderBackupPath, Schemas.file)); @@ -683,13 +675,13 @@ suite('BackupMainService', () => { // ignore - folder might exist already } - assert.equal(((await service.getDirtyWorkspaces()).length), 0); + assert.strictEqual(((await service.getDirtyWorkspaces()).length), 0); fs.writeFileSync(path.join(folderBackupPath, Schemas.file, '594a4a9d82a277a899d4713a5b08f504'), ''); fs.writeFileSync(path.join(workspaceBackupPath, Schemas.untitled, '594a4a9d82a277a899d4713a5b08f504'), ''); const dirtyWorkspaces = await service.getDirtyWorkspaces(); - assert.equal(dirtyWorkspaces.length, 2); + assert.strictEqual(dirtyWorkspaces.length, 2); let found = 0; for (const dirtyWorkpspace of dirtyWorkspaces) { @@ -704,7 +696,7 @@ suite('BackupMainService', () => { } } - assert.equal(found, 2); + assert.strictEqual(found, 2); }); }); }); diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 5af10fc31..c6928a8ac 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -16,7 +16,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { IFileService } from 'vs/platform/files/common/files'; -import { dirname } from 'vs/base/common/resources'; +import { IExtUri } from 'vs/base/common/resources'; export class ConfigurationModel implements IConfigurationModel { @@ -348,11 +348,12 @@ export class UserSettings extends Disposable { constructor( private readonly userSettingsResource: URI, private readonly scopes: ConfigurationScope[] | undefined, + extUri: IExtUri, private readonly fileService: IFileService ) { super(); this.parser = new ConfigurationModelParser(this.userSettingsResource.toString(), this.scopes); - this._register(this.fileService.watch(dirname(this.userSettingsResource))); + this._register(this.fileService.watch(extUri.dirname(this.userSettingsResource))); this._register(Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.userSettingsResource))(() => this._onDidChange.fire())); } diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 07680c0f2..7504cb721 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -112,6 +112,13 @@ export interface IConfigurationPropertySchema extends IJSONSchema { scope?: ConfigurationScope; included?: boolean; tags?: string[]; + /** + * When enabled this setting is ignored during sync and user can override this. + */ + ignoreSync?: boolean; + /** + * When enabled this setting is ignored during sync and user cannot override this. + */ disallowSyncIgnore?: boolean; enumItemLabels?: string[]; } diff --git a/src/vs/platform/configuration/common/configurationService.ts b/src/vs/platform/configuration/common/configurationService.ts index 2e8b85b1f..3fd1df3f5 100644 --- a/src/vs/platform/configuration/common/configurationService.ts +++ b/src/vs/platform/configuration/common/configurationService.ts @@ -12,6 +12,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { RunOnceScheduler } from 'vs/base/common/async'; +import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable { @@ -29,7 +30,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe fileService: IFileService ) { super(); - this.userConfiguration = this._register(new UserSettings(this.settingsResource, undefined, fileService)); + this.userConfiguration = this._register(new UserSettings(this.settingsResource, undefined, extUriBiasedIgnorePathCase, fileService)); this.configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel()); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reloadConfiguration(), 50)); diff --git a/src/vs/platform/contextkey/browser/contextKeyService.ts b/src/vs/platform/contextkey/browser/contextKeyService.ts index 21320c0d6..df2e5b70d 100644 --- a/src/vs/platform/contextkey/browser/contextKeyService.ts +++ b/src/vs/platform/contextkey/browser/contextKeyService.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter, Event, PauseableEmitter } from 'vs/base/common/event'; +import { Emitter, PauseableEmitter } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; -import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { TernarySearchTree } from 'vs/base/common/map'; import { distinct } from 'vs/base/common/objects'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -244,12 +244,14 @@ class CompositeContextKeyChangeEvent implements IContextKeyChangeEvent { } export abstract class AbstractContextKeyService implements IContextKeyService { - public _serviceBrand: undefined; + declare _serviceBrand: undefined; protected _isDisposed: boolean; - protected _onDidChangeContext = new PauseableEmitter({ merge: input => new CompositeContextKeyChangeEvent(input) }); protected _myContextId: number; + protected _onDidChangeContext = new PauseableEmitter({ merge: input => new CompositeContextKeyChangeEvent(input) }); + readonly onDidChangeContext = this._onDidChangeContext.event; + constructor(myContextId: number) { this._isDisposed = false; this._myContextId = myContextId; @@ -268,9 +270,6 @@ export abstract class AbstractContextKeyService implements IContextKeyService { return new ContextKey(this, key, defaultValue); } - public get onDidChangeContext(): Event { - return this._onDidChangeContext.event; - } bufferChangeEvents(callback: Function): void { this._onDidChangeContext.pause(); @@ -371,6 +370,7 @@ export class ContextKeyService extends AbstractContextKeyService implements ICon } public dispose(): void { + this._onDidChangeContext.dispose(); this._isDisposed = true; this._toDispose.dispose(); } @@ -407,34 +407,34 @@ class ScopedContextKeyService extends AbstractContextKeyService { private _parent: AbstractContextKeyService; private _domNode: IContextKeyServiceTarget | undefined; - private _parentChangeListener: IDisposable | undefined; + private readonly _parentChangeListener = new MutableDisposable(); constructor(parent: AbstractContextKeyService, domNode?: IContextKeyServiceTarget) { super(parent.createChildContext()); this._parent = parent; - this.updateParentChangeListener(); + this._updateParentChangeListener(); if (domNode) { this._domNode = domNode; if (this._domNode.hasAttribute(KEYBINDING_CONTEXT_ATTR)) { - console.error('Element already has context attribute'); + let extraInfo = ''; + if ((this._domNode as HTMLElement).classList) { + extraInfo = Array.from((this._domNode as HTMLElement).classList.values()).join(', '); + } + + console.error(`Element already has context attribute${extraInfo ? ': ' + extraInfo : ''}`); } this._domNode.setAttribute(KEYBINDING_CONTEXT_ATTR, String(this._myContextId)); } } - private updateParentChangeListener(): void { - if (this._parentChangeListener) { - this._parentChangeListener.dispose(); - } - - this._parentChangeListener = this._parent.onDidChangeContext(e => { - // Forward parent events to this listener. Parent will change. - this._onDidChangeContext.fire(e); - }); + private _updateParentChangeListener(): void { + // Forward parent events to this listener. Parent will change. + this._parentChangeListener.value = this._parent.onDidChangeContext(this._onDidChangeContext.fire, this._onDidChangeContext); } public dispose(): void { + this._onDidChangeContext.dispose(); this._isDisposed = true; this._parent.disposeContext(this._myContextId); this._parentChangeListener?.dispose(); @@ -444,10 +444,6 @@ class ScopedContextKeyService extends AbstractContextKeyService { } } - public get onDidChangeContext(): Event { - return this._onDidChangeContext.event; - } - public getContextValuesContainer(contextId: number): Context { if (this._isDisposed) { return NullContext.INSTANCE; @@ -473,7 +469,7 @@ class ScopedContextKeyService extends AbstractContextKeyService { const thisContainer = this._parent.getContextValuesContainer(this._myContextId); const oldAllValues = thisContainer.collectAllValues(); this._parent = parentContextKeyService; - this.updateParentChangeListener(); + this._updateParentChangeListener(); const newParentContainer = this._parent.getContextValuesContainer(this._parent.contextId); thisContainer.updateParent(newParentContainer); diff --git a/src/vs/platform/contextkey/common/contextkey.ts b/src/vs/platform/contextkey/common/contextkey.ts index ea7c83876..fde5606ea 100644 --- a/src/vs/platform/contextkey/common/contextkey.ts +++ b/src/vs/platform/contextkey/common/contextkey.ts @@ -1278,11 +1278,11 @@ export class RawContextKey extends ContextKeyDefinedExpr { return ContextKeyExpr.not(this.key); } - public isEqualTo(value: string): ContextKeyExpression { + public isEqualTo(value: any): ContextKeyExpression { return ContextKeyExpr.equals(this.key, value); } - public notEqualsTo(value: string): ContextKeyExpression { + public notEqualsTo(value: any): ContextKeyExpression { return ContextKeyExpr.notEquals(this.key, value); } } diff --git a/src/vs/platform/contextkey/test/browser/contextkey.test.ts b/src/vs/platform/contextkey/test/browser/contextkey.test.ts index d436b6cdf..aca26b1dd 100644 --- a/src/vs/platform/contextkey/test/browser/contextkey.test.ts +++ b/src/vs/platform/contextkey/test/browser/contextkey.test.ts @@ -34,10 +34,10 @@ suite('ContextKeyService', () => { assert.ok(e.affectsSome(new Set(['testC'])), 'testC changed'); assert.ok(!e.affectsSome(new Set(['testD'])), 'testD did not change'); - assert.equal(child.getContextKeyValue('testA'), 3); - assert.equal(child.getContextKeyValue('testB'), undefined); - assert.equal(child.getContextKeyValue('testC'), 4); - assert.equal(child.getContextKeyValue('testD'), 0); + assert.strictEqual(child.getContextKeyValue('testA'), 3); + assert.strictEqual(child.getContextKeyValue('testB'), undefined); + assert.strictEqual(child.getContextKeyValue('testC'), 4); + assert.strictEqual(child.getContextKeyValue('testD'), 0); } catch (err) { reject(err); return; diff --git a/src/vs/platform/contextkey/test/common/contextkey.test.ts b/src/vs/platform/contextkey/test/common/contextkey.test.ts index 91a548be6..1abe1a076 100644 --- a/src/vs/platform/contextkey/test/common/contextkey.test.ts +++ b/src/vs/platform/contextkey/test/common/contextkey.test.ts @@ -67,7 +67,7 @@ suite('ContextKeyExpr', () => { function testExpression(expr: string, expected: boolean): void { // console.log(expr + ' ' + expected); let rules = ContextKeyExpr.deserialize(expr); - assert.equal(rules!.evaluate(context), expected, expr); + assert.strictEqual(rules!.evaluate(context), expected, expr); } function testBatch(expr: string, value: any): void { /* eslint-disable eqeqeq */ @@ -153,17 +153,17 @@ suite('ContextKeyExpr', () => { test('ContextKeyInExpr', () => { const ainb = ContextKeyExpr.deserialize('a in b')!; - assert.equal(ainb.evaluate(createContext({ 'a': 3, 'b': [3, 2, 1] })), true); - assert.equal(ainb.evaluate(createContext({ 'a': 3, 'b': [1, 2, 3] })), true); - assert.equal(ainb.evaluate(createContext({ 'a': 3, 'b': [1, 2] })), false); - assert.equal(ainb.evaluate(createContext({ 'a': 3 })), false); - assert.equal(ainb.evaluate(createContext({ 'a': 3, 'b': null })), false); - assert.equal(ainb.evaluate(createContext({ 'a': 'x', 'b': ['x'] })), true); - assert.equal(ainb.evaluate(createContext({ 'a': 'x', 'b': ['y'] })), false); - assert.equal(ainb.evaluate(createContext({ 'a': 'x', 'b': {} })), false); - assert.equal(ainb.evaluate(createContext({ 'a': 'x', 'b': { 'x': false } })), true); - assert.equal(ainb.evaluate(createContext({ 'a': 'x', 'b': { 'x': true } })), true); - assert.equal(ainb.evaluate(createContext({ 'a': 'prototype', 'b': {} })), false); + assert.strictEqual(ainb.evaluate(createContext({ 'a': 3, 'b': [3, 2, 1] })), true); + assert.strictEqual(ainb.evaluate(createContext({ 'a': 3, 'b': [1, 2, 3] })), true); + assert.strictEqual(ainb.evaluate(createContext({ 'a': 3, 'b': [1, 2] })), false); + assert.strictEqual(ainb.evaluate(createContext({ 'a': 3 })), false); + assert.strictEqual(ainb.evaluate(createContext({ 'a': 3, 'b': null })), false); + assert.strictEqual(ainb.evaluate(createContext({ 'a': 'x', 'b': ['x'] })), true); + assert.strictEqual(ainb.evaluate(createContext({ 'a': 'x', 'b': ['y'] })), false); + assert.strictEqual(ainb.evaluate(createContext({ 'a': 'x', 'b': {} })), false); + assert.strictEqual(ainb.evaluate(createContext({ 'a': 'x', 'b': { 'x': false } })), true); + assert.strictEqual(ainb.evaluate(createContext({ 'a': 'x', 'b': { 'x': true } })), true); + assert.strictEqual(ainb.evaluate(createContext({ 'a': 'prototype', 'b': {} })), false); }); test('issue #106524: distributing AND should normalize', () => { @@ -184,13 +184,13 @@ suite('ContextKeyExpr', () => { ContextKeyExpr.has('c') ) ); - assert.equal(actual!.equals(expected!), true); + assert.strictEqual(actual!.equals(expected!), true); }); test('Greater, GreaterEquals, Smaller, SmallerEquals evaluate', () => { function checkEvaluate(expr: string, ctx: any, expected: any): void { const _expr = ContextKeyExpr.deserialize(expr)!; - assert.equal(_expr.evaluate(createContext(ctx)), expected); + assert.strictEqual(_expr.evaluate(createContext(ctx)), expected); } checkEvaluate('a>1', {}, false); @@ -236,7 +236,7 @@ suite('ContextKeyExpr', () => { function checkNegate(expr: string, expected: string): void { const a = ContextKeyExpr.deserialize(expr)!; const b = a.negate(); - assert.equal(b.serialize(), expected); + assert.strictEqual(b.serialize(), expected); } checkNegate('a>1', 'a <= 1'); diff --git a/src/vs/platform/contextview/browser/contextMenuHandler.ts b/src/vs/platform/contextview/browser/contextMenuHandler.ts index 1788290cc..0b515dc51 100644 --- a/src/vs/platform/contextview/browser/contextMenuHandler.ts +++ b/src/vs/platform/contextview/browser/contextMenuHandler.ts @@ -158,7 +158,7 @@ export class ContextMenuHandler { } private onDidActionRun(e: IRunEvent): void { - if (e.error && this.notificationService) { + if (e.error) { this.notificationService.error(e.error); } } diff --git a/src/vs/platform/debug/common/extensionHostDebug.ts b/src/vs/platform/debug/common/extensionHostDebug.ts index b263bdd9c..b30c4e44e 100644 --- a/src/vs/platform/debug/common/extensionHostDebug.ts +++ b/src/vs/platform/debug/common/extensionHostDebug.ts @@ -5,7 +5,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; -import { IRemoteConsoleLog } from 'vs/base/common/console'; import { IProcessEnvironment } from 'vs/base/common/platform'; export const IExtensionHostDebugService = createDecorator('extensionHostDebugService'); @@ -16,11 +15,6 @@ export interface IAttachSessionEvent { port: number; } -export interface ILogToSessionEvent { - sessionId: string; - log: IRemoteConsoleLog; -} - export interface ITerminateSessionEvent { sessionId: string; subId?: string; @@ -50,9 +44,6 @@ export interface IExtensionHostDebugService { attachSession(sessionId: string, port: number, subId?: string): void; readonly onAttachSession: Event; - logToSession(sessionId: string, log: IRemoteConsoleLog): void; - readonly onLogToSession: Event; - terminateSession(sessionId: string, subId?: string): void; readonly onTerminateSession: Event; diff --git a/src/vs/platform/debug/common/extensionHostDebugIpc.ts b/src/vs/platform/debug/common/extensionHostDebugIpc.ts index 60011be13..09c2a1916 100644 --- a/src/vs/platform/debug/common/extensionHostDebugIpc.ts +++ b/src/vs/platform/debug/common/extensionHostDebugIpc.ts @@ -4,9 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IReloadSessionEvent, ICloseSessionEvent, IAttachSessionEvent, ILogToSessionEvent, ITerminateSessionEvent, IExtensionHostDebugService, IOpenExtensionWindowResult } from 'vs/platform/debug/common/extensionHostDebug'; +import { IReloadSessionEvent, ICloseSessionEvent, IAttachSessionEvent, ITerminateSessionEvent, IExtensionHostDebugService, IOpenExtensionWindowResult } from 'vs/platform/debug/common/extensionHostDebug'; import { Event, Emitter } from 'vs/base/common/event'; -import { IRemoteConsoleLog } from 'vs/base/common/console'; import { Disposable } from 'vs/base/common/lifecycle'; import { IProcessEnvironment } from 'vs/base/common/platform'; @@ -17,7 +16,6 @@ export class ExtensionHostDebugBroadcastChannel implements IServerChan private readonly _onCloseEmitter = new Emitter(); private readonly _onReloadEmitter = new Emitter(); private readonly _onTerminateEmitter = new Emitter(); - private readonly _onLogToEmitter = new Emitter(); private readonly _onAttachEmitter = new Emitter(); call(ctx: TContext, command: string, arg?: any): Promise { @@ -28,8 +26,6 @@ export class ExtensionHostDebugBroadcastChannel implements IServerChan return Promise.resolve(this._onReloadEmitter.fire({ sessionId: arg[0] })); case 'terminate': return Promise.resolve(this._onTerminateEmitter.fire({ sessionId: arg[0] })); - case 'log': - return Promise.resolve(this._onLogToEmitter.fire({ sessionId: arg[0], log: arg[1] })); case 'attach': return Promise.resolve(this._onAttachEmitter.fire({ sessionId: arg[0], port: arg[1], subId: arg[2] })); } @@ -44,8 +40,6 @@ export class ExtensionHostDebugBroadcastChannel implements IServerChan return this._onReloadEmitter.event; case 'terminate': return this._onTerminateEmitter.event; - case 'log': - return this._onLogToEmitter.event; case 'attach': return this._onAttachEmitter.event; } @@ -85,14 +79,6 @@ export class ExtensionHostDebugChannelClient extends Disposable implements IExte return this.channel.listen('attach'); } - logToSession(sessionId: string, log: IRemoteConsoleLog): void { - this.channel.call('log', [sessionId, log]); - } - - get onLogToSession(): Event { - return this.channel.listen('log'); - } - terminateSession(sessionId: string, subId?: string): void { this.channel.call('terminate', [sessionId, subId]); } diff --git a/src/vs/platform/debug/electron-main/extensionHostDebugIpc.ts b/src/vs/platform/debug/electron-main/extensionHostDebugIpc.ts index d05bd7ca0..f0ac255cb 100644 --- a/src/vs/platform/debug/electron-main/extensionHostDebugIpc.ts +++ b/src/vs/platform/debug/electron-main/extensionHostDebugIpc.ts @@ -8,8 +8,7 @@ import { IProcessEnvironment } from 'vs/base/common/platform'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { createServer, AddressInfo } from 'net'; import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; -import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; -import { OpenContext } from 'vs/platform/windows/node/window'; +import { IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows'; export class ElectronExtensionHostDebugBroadcastChannel extends ExtensionHostDebugBroadcastChannel { diff --git a/src/vs/platform/diagnostics/node/diagnosticsService.ts b/src/vs/platform/diagnostics/node/diagnosticsService.ts index 2fe576f1b..2f2711440 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsService.ts @@ -314,10 +314,10 @@ export class DiagnosticsService implements IDiagnosticsService { if (isLinux) { systemInfo.linuxEnv = { - desktopSession: process.env.DESKTOP_SESSION, - xdgSessionDesktop: process.env.XDG_SESSION_DESKTOP, - xdgCurrentDesktop: process.env.XDG_CURRENT_DESKTOP, - xdgSessionType: process.env.XDG_SESSION_TYPE + desktopSession: process.env['DESKTOP_SESSION'], + xdgSessionDesktop: process.env['XDG_SESSION_DESKTOP'], + xdgCurrentDesktop: process.env['XDG_CURRENT_DESKTOP'], + xdgSessionType: process.env['XDG_SESSION_TYPE'] }; } diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index 8f7bb194d..25e49ef1c 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -181,6 +181,7 @@ export interface IDialogOptions { cancelId?: number; detail?: string; checkbox?: ICheckbox; + useCustom?: boolean; } export interface IInput { diff --git a/src/vs/platform/dialogs/electron-main/dialogs.ts b/src/vs/platform/dialogs/electron-main/dialogMainService.ts similarity index 100% rename from src/vs/platform/dialogs/electron-main/dialogs.ts rename to src/vs/platform/dialogs/electron-main/dialogMainService.ts diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index e719e129c..019410e6a 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -28,6 +28,7 @@ export interface NativeParsedArgs { 'prof-startup'?: boolean; 'prof-startup-prefix'?: string; 'prof-append-timers'?: string; + 'prof-v8-extensions'?: boolean; verbose?: boolean; trace?: boolean; 'trace-category-filter'?: string; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 7263bcbf7..7b6a60b57 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -53,8 +53,8 @@ export const OPTIONS: OptionDescriptions> = { 'extensions-download-dir': { type: 'string' }, 'builtin-extensions-dir': { type: 'string' }, 'list-extensions': { type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") }, - 'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extension.") }, - 'category': { type: 'string', cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extension.") }, + 'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extensions.") }, + 'category': { type: 'string', cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extensions.") }, 'install-extension': { type: 'string[]', cat: 'e', args: 'extension-id[@version] | path-to-vsix', description: localize('installExtension', "Installs or updates the extension. The identifier of an extension is always `${publisher}.${name}`. Use `--force` argument to update to latest version. To install a specific version provide `@${version}`. For example: 'vscode.csharp@1.2.3'.") }, 'uninstall-extension': { type: 'string[]', cat: 'e', args: 'extension-id', description: localize('uninstallExtension', "Uninstalls an extension.") }, 'enable-proposed-api': { type: 'string[]', cat: 'e', args: 'extension-id', description: localize('experimentalApis', "Enables proposed API features for extensions. Can receive one or more extension IDs to enable individually.") }, @@ -66,6 +66,7 @@ export const OPTIONS: OptionDescriptions> = { 'prof-startup': { type: 'boolean', cat: 't', description: localize('prof-startup', "Run CPU profiler during startup") }, 'prof-append-timers': { type: 'string' }, 'prof-startup-prefix': { type: 'string' }, + 'prof-v8-extensions': { type: 'boolean' }, 'disable-extensions': { type: 'boolean', deprecates: 'disableExtensions', cat: 't', description: localize('disableExtensions', "Disable all installed extensions.") }, 'disable-extension': { type: 'string[]', cat: 't', args: 'extension-id', description: localize('disableExtension', "Disable an extension.") }, 'sync': { type: 'string', cat: 't', description: localize('turn sync', "Turn sync on or off"), args: ['on', 'off'] }, @@ -149,10 +150,6 @@ export function parseArgs(args: string[], options: OptionDescriptions, err const string: string[] = []; const boolean: string[] = []; for (let optionId in options) { - if (optionId[0] === '_') { - continue; - } - const o = options[optionId]; if (o.alias) { alias[optionId] = o.alias; diff --git a/src/vs/platform/environment/test/node/environmentService.test.ts b/src/vs/platform/environment/test/node/environmentService.test.ts index d30dc8a68..ca33c2260 100644 --- a/src/vs/platform/environment/test/node/environmentService.test.ts +++ b/src/vs/platform/environment/test/node/environmentService.test.ts @@ -13,35 +13,35 @@ suite('EnvironmentService', () => { test('parseExtensionHostPort when built', () => { const parse = (a: string[]) => parseExtensionHostPort(parseArgs(a, OPTIONS), true); - assert.deepEqual(parse([]), { port: null, break: false, debugId: undefined }); - assert.deepEqual(parse(['--debugPluginHost']), { port: null, break: false, debugId: undefined }); - assert.deepEqual(parse(['--debugPluginHost=1234']), { port: 1234, break: false, debugId: undefined }); - assert.deepEqual(parse(['--debugBrkPluginHost']), { port: null, break: false, debugId: undefined }); - assert.deepEqual(parse(['--debugBrkPluginHost=5678']), { port: 5678, break: true, debugId: undefined }); - assert.deepEqual(parse(['--debugPluginHost=1234', '--debugBrkPluginHost=5678', '--debugId=7']), { port: 5678, break: true, debugId: '7' }); + assert.deepStrictEqual(parse([]), { port: null, break: false, debugId: undefined }); + assert.deepStrictEqual(parse(['--debugPluginHost']), { port: null, break: false, debugId: undefined }); + assert.deepStrictEqual(parse(['--debugPluginHost=1234']), { port: 1234, break: false, debugId: undefined }); + assert.deepStrictEqual(parse(['--debugBrkPluginHost']), { port: null, break: false, debugId: undefined }); + assert.deepStrictEqual(parse(['--debugBrkPluginHost=5678']), { port: 5678, break: true, debugId: undefined }); + assert.deepStrictEqual(parse(['--debugPluginHost=1234', '--debugBrkPluginHost=5678', '--debugId=7']), { port: 5678, break: true, debugId: '7' }); - assert.deepEqual(parse(['--inspect-extensions']), { port: null, break: false, debugId: undefined }); - assert.deepEqual(parse(['--inspect-extensions=1234']), { port: 1234, break: false, debugId: undefined }); - assert.deepEqual(parse(['--inspect-brk-extensions']), { port: null, break: false, debugId: undefined }); - assert.deepEqual(parse(['--inspect-brk-extensions=5678']), { port: 5678, break: true, debugId: undefined }); - assert.deepEqual(parse(['--inspect-extensions=1234', '--inspect-brk-extensions=5678', '--debugId=7']), { port: 5678, break: true, debugId: '7' }); + assert.deepStrictEqual(parse(['--inspect-extensions']), { port: null, break: false, debugId: undefined }); + assert.deepStrictEqual(parse(['--inspect-extensions=1234']), { port: 1234, break: false, debugId: undefined }); + assert.deepStrictEqual(parse(['--inspect-brk-extensions']), { port: null, break: false, debugId: undefined }); + assert.deepStrictEqual(parse(['--inspect-brk-extensions=5678']), { port: 5678, break: true, debugId: undefined }); + assert.deepStrictEqual(parse(['--inspect-extensions=1234', '--inspect-brk-extensions=5678', '--debugId=7']), { port: 5678, break: true, debugId: '7' }); }); test('parseExtensionHostPort when unbuilt', () => { const parse = (a: string[]) => parseExtensionHostPort(parseArgs(a, OPTIONS), false); - assert.deepEqual(parse([]), { port: 5870, break: false, debugId: undefined }); - assert.deepEqual(parse(['--debugPluginHost']), { port: 5870, break: false, debugId: undefined }); - assert.deepEqual(parse(['--debugPluginHost=1234']), { port: 1234, break: false, debugId: undefined }); - assert.deepEqual(parse(['--debugBrkPluginHost']), { port: 5870, break: false, debugId: undefined }); - assert.deepEqual(parse(['--debugBrkPluginHost=5678']), { port: 5678, break: true, debugId: undefined }); - assert.deepEqual(parse(['--debugPluginHost=1234', '--debugBrkPluginHost=5678', '--debugId=7']), { port: 5678, break: true, debugId: '7' }); + assert.deepStrictEqual(parse([]), { port: 5870, break: false, debugId: undefined }); + assert.deepStrictEqual(parse(['--debugPluginHost']), { port: 5870, break: false, debugId: undefined }); + assert.deepStrictEqual(parse(['--debugPluginHost=1234']), { port: 1234, break: false, debugId: undefined }); + assert.deepStrictEqual(parse(['--debugBrkPluginHost']), { port: 5870, break: false, debugId: undefined }); + assert.deepStrictEqual(parse(['--debugBrkPluginHost=5678']), { port: 5678, break: true, debugId: undefined }); + assert.deepStrictEqual(parse(['--debugPluginHost=1234', '--debugBrkPluginHost=5678', '--debugId=7']), { port: 5678, break: true, debugId: '7' }); - assert.deepEqual(parse(['--inspect-extensions']), { port: 5870, break: false, debugId: undefined }); - assert.deepEqual(parse(['--inspect-extensions=1234']), { port: 1234, break: false, debugId: undefined }); - assert.deepEqual(parse(['--inspect-brk-extensions']), { port: 5870, break: false, debugId: undefined }); - assert.deepEqual(parse(['--inspect-brk-extensions=5678']), { port: 5678, break: true, debugId: undefined }); - assert.deepEqual(parse(['--inspect-extensions=1234', '--inspect-brk-extensions=5678', '--debugId=7']), { port: 5678, break: true, debugId: '7' }); + assert.deepStrictEqual(parse(['--inspect-extensions']), { port: 5870, break: false, debugId: undefined }); + assert.deepStrictEqual(parse(['--inspect-extensions=1234']), { port: 1234, break: false, debugId: undefined }); + assert.deepStrictEqual(parse(['--inspect-brk-extensions']), { port: 5870, break: false, debugId: undefined }); + assert.deepStrictEqual(parse(['--inspect-brk-extensions=5678']), { port: 5678, break: true, debugId: undefined }); + assert.deepStrictEqual(parse(['--inspect-extensions=1234', '--inspect-brk-extensions=5678', '--debugId=7']), { port: 5678, break: true, debugId: '7' }); }); test('userDataPath', () => { @@ -57,10 +57,10 @@ suite('EnvironmentService', () => { test('careful with boolean file names', function () { let actual = parseArgs(['-r', 'arg.txt'], OPTIONS); assert(actual['reuse-window']); - assert.deepEqual(actual._, ['arg.txt']); + assert.deepStrictEqual(actual._, ['arg.txt']); actual = parseArgs(['-r', 'true.txt'], OPTIONS); assert(actual['reuse-window']); - assert.deepEqual(actual._, ['true.txt']); + assert.deepStrictEqual(actual._, ['true.txt']); }); }); diff --git a/src/vs/platform/environment/test/node/nativeModules.test.ts b/src/vs/platform/environment/test/node/nativeModules.test.ts index b64282f62..28013dfcd 100644 --- a/src/vs/platform/environment/test/node/nativeModules.test.ts +++ b/src/vs/platform/environment/test/node/nativeModules.test.ts @@ -37,11 +37,6 @@ suite('Native Modules (all platforms)', () => { assert.ok(typeof spdlog.createRotatingLogger === 'function', testErrorMessage('spdlog')); }); - test('v8-inspect-profiler', async () => { - const profiler = await import('v8-inspect-profiler'); - assert.ok(typeof profiler.startProfiling === 'function', testErrorMessage('v8-inspect-profiler')); - }); - test('vscode-nsfw', async () => { const nsfWatcher = await import('vscode-nsfw'); assert.ok(typeof nsfWatcher === 'function', testErrorMessage('vscode-nsfw')); @@ -90,7 +85,7 @@ suite('Native Modules (all platforms)', () => { test('vscode-windows-ca-certs', async () => { // @ts-ignore Windows only const windowsCerts = await import('vscode-windows-ca-certs'); - const store = windowsCerts(); + const store = new windowsCerts.Crypt32(); assert.ok(windowsCerts, testErrorMessage('vscode-windows-ca-certs')); let certCount = 0; try { diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 3f0dd835c..45d3b68c4 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -9,7 +9,7 @@ import { getGalleryExtensionId, getGalleryExtensionTelemetryData, adoptToGallery import { getOrDefault } from 'vs/base/common/objects'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IPager } from 'vs/base/common/paging'; -import { IRequestService, asJson, asText } from 'vs/platform/request/common/request'; +import { IRequestService, asJson, asText, isSuccess } from 'vs/platform/request/common/request'; import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request'; import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -147,6 +147,35 @@ const DefaultQueryState: IQueryState = { assetTypes: [] }; +type GalleryServiceQueryClassification = { + filterTypes: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + sortBy: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + sortOrder: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + duration: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', 'isMeasurement': true }; + success: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + requestBodySize: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + responseBodySize?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + statusCode?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + errorCode?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + count?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; +}; + +type QueryTelemetryData = { + filterTypes: string[]; + sortBy: string; + sortOrder: string; +}; + +type GalleryServiceQueryEvent = QueryTelemetryData & { + duration: number; + success: boolean; + requestBodySize: string; + responseBodySize?: string; + statusCode?: string; + errorCode?: string; + count?: string; +}; + class Query { constructor(private state = DefaultQueryState) { } @@ -196,6 +225,14 @@ class Query { const criterium = this.state.criteria.filter(criterium => criterium.filterType === FilterType.SearchText)[0]; return criterium && criterium.value ? criterium.value : ''; } + + get telemetryData(): QueryTelemetryData { + return { + filterTypes: this.state.criteria.map(criterium => String(criterium.filterType)), + sortBy: String(this.sortBy), + sortOrder: String(this.sortOrder) + }; + } } function getStatistic(statistics: IRawGalleryExtensionStatistics[], name: string): number { @@ -447,20 +484,9 @@ export class ExtensionGalleryService implements IExtensionGalleryService { throw new Error('No extension gallery service configured.'); } - const type = options.names ? 'ids' : (options.text ? 'text' : 'all'); let text = options.text || ''; const pageSize = getOrDefault(options, o => o.pageSize, 50); - type GalleryServiceQueryClassification = { - type: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - text: { classification: 'CustomerContent', purpose: 'FeatureInsight' }; - }; - type GalleryServiceQueryEvent = { - type: string; - text: string; - }; - this.telemetryService.publicLog2('galleryService:query', { type, text }); - let query = new Query() .withFlags(Flags.IncludeLatestVersionOnly, Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeFiles, Flags.IncludeVersionProperties) .withPage(1, pageSize) @@ -543,27 +569,49 @@ export class ExtensionGalleryService implements IExtensionGalleryService { 'Content-Length': String(data.length) }; - const context = await this.requestService.request({ - type: 'POST', - url: this.api('/extensionquery'), - data, - headers - }, token); + const startTime = new Date().getTime(); + let context: IRequestContext | undefined, error: any, total: number = 0; - if (context.res.statusCode && context.res.statusCode >= 400 && context.res.statusCode < 500) { - return { galleryExtensions: [], total: 0 }; + try { + context = await this.requestService.request({ + type: 'POST', + url: this.api('/extensionquery'), + data, + headers + }, token); + + if (context.res.statusCode && context.res.statusCode >= 400 && context.res.statusCode < 500) { + return { galleryExtensions: [], total }; + } + + const result = await asJson(context); + if (result) { + const r = result.results[0]; + const galleryExtensions = r.extensions; + const resultCount = r.resultMetadata && r.resultMetadata.filter(m => m.metadataType === 'ResultCount')[0]; + total = resultCount && resultCount.metadataItems.filter(i => i.name === 'TotalCount')[0].count || 0; + + return { galleryExtensions, total }; + } + return { galleryExtensions: [], total }; + + } catch (e) { + error = e; + throw e; + } finally { + this.telemetryService.publicLog2('galleryService:query', { + ...query.telemetryData, + requestBodySize: String(data.length), + duration: new Date().getTime() - startTime, + success: !!context && isSuccess(context), + responseBodySize: context?.res.headers['Content-Length'], + statusCode: context ? String(context.res.statusCode) : undefined, + errorCode: error + ? isPromiseCanceledError(error) ? 'canceled' : getErrorMessage(error).startsWith('XHR timeout') ? 'timeout' : 'failed' + : undefined, + count: String(total) + }); } - - const result = await asJson(context); - if (result) { - const r = result.results[0]; - const galleryExtensions = r.extensions; - const resultCount = r.resultMetadata && r.resultMetadata.filter(m => m.metadataType === 'ResultCount')[0]; - const total = resultCount && resultCount.metadataItems.filter(i => i.name === 'TotalCount')[0].count || 0; - - return { galleryExtensions, total }; - } - return { galleryExtensions: [], total: 0 }; } async reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise { diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 0e46a92d6..50531800e 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -278,3 +278,19 @@ export const ExtensionsLocalizedLabel = { value: ExtensionsLabel, original: 'Ext export const ExtensionsChannelId = 'extensions'; export const PreferencesLabel = localize('preferences', "Preferences"); export const PreferencesLocalizedLabel = { value: PreferencesLabel, original: 'Preferences' }; + + +export interface CLIOutput { + log(s: string): void; + error(s: string): void; +} + +export const IExtensionManagementCLIService = createDecorator('IExtensionManagementCLIService'); +export interface IExtensionManagementCLIService { + readonly _serviceBrand: undefined; + + listExtensions(showVersions: boolean, category?: string, output?: CLIOutput): Promise; + installExtensions(extensions: (string | URI)[], builtinExtensionIds: string[], isMachineScoped: boolean, force: boolean, output?: CLIOutput): Promise; + uninstallExtensions(extensions: (string | URI)[], force: boolean, output?: CLIOutput): Promise; + locateExtension(extensions: string[], output?: CLIOutput): Promise; +} diff --git a/src/vs/platform/extensionManagement/common/extensionManagementCLIService.ts b/src/vs/platform/extensionManagement/common/extensionManagementCLIService.ts new file mode 100644 index 000000000..532a550c5 --- /dev/null +++ b/src/vs/platform/extensionManagement/common/extensionManagementCLIService.ts @@ -0,0 +1,347 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { isPromiseCanceledError } from 'vs/base/common/errors'; +import { URI } from 'vs/base/common/uri'; +import { gt } from 'vs/base/common/semver/semver'; +import { CLIOutput, IExtensionGalleryService, IExtensionManagementCLIService, IExtensionManagementService, IGalleryExtension, ILocalExtension, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { ExtensionType, EXTENSION_CATEGORIES, IExtensionManifest, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; +import { getBaseLabel } from 'vs/base/common/labels'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Schemas } from 'vs/base/common/network'; +import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; + +const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id); +const useId = localize('useId', "Make sure you use the full extension ID, including the publisher, e.g.: {0}", 'ms-dotnettools.csharp'); + + +function getId(manifest: IExtensionManifest, withVersion?: boolean): string { + if (withVersion) { + return `${manifest.publisher}.${manifest.name}@${manifest.version}`; + } else { + return `${manifest.publisher}.${manifest.name}`; + } +} + +const EXTENSION_ID_REGEX = /^([^.]+\..+)@(\d+\.\d+\.\d+(-.*)?)$/; + +export function getIdAndVersion(id: string): [string, string | undefined] { + const matches = EXTENSION_ID_REGEX.exec(id); + if (matches && matches[1]) { + return [adoptToGalleryExtensionId(matches[1]), matches[2]]; + } + return [adoptToGalleryExtensionId(id), undefined]; +} + +type InstallExtensionInfo = { id: string, version?: string, installOptions: InstallOptions }; + + +export class ExtensionManagementCLIService implements IExtensionManagementCLIService { + + _serviceBrand: any; + + constructor( + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + @ILocalizationsService private readonly localizationsService: ILocalizationsService + ) { } + + protected get location(): string | undefined { + return undefined; + } + + public async listExtensions(showVersions: boolean, category?: string, output: CLIOutput = console): Promise { + let extensions = await this.extensionManagementService.getInstalled(ExtensionType.User); + const categories = EXTENSION_CATEGORIES.map(c => c.toLowerCase()); + if (category && category !== '') { + if (categories.indexOf(category.toLowerCase()) < 0) { + output.log('Invalid category please enter a valid category. To list valid categories run --category without a category specified'); + return; + } + extensions = extensions.filter(e => { + if (e.manifest.categories) { + const lowerCaseCategories: string[] = e.manifest.categories.map(c => c.toLowerCase()); + return lowerCaseCategories.indexOf(category.toLowerCase()) > -1; + } + return false; + }); + } else if (category === '') { + output.log('Possible Categories: '); + categories.forEach(category => { + output.log(category); + }); + return; + } + if (this.location) { + output.log(localize('listFromLocation', "Extensions installed on {0}:", this.location)); + } + + extensions = extensions.sort((e1, e2) => e1.identifier.id.localeCompare(e2.identifier.id)); + let lastId: string | undefined = undefined; + for (let extension of extensions) { + if (lastId !== extension.identifier.id) { + lastId = extension.identifier.id; + output.log(getId(extension.manifest, showVersions)); + } + } + } + + public async installExtensions(extensions: (string | URI)[], builtinExtensionIds: string[], isMachineScoped: boolean, force: boolean, output: CLIOutput = console): Promise { + const failed: string[] = []; + const installedExtensionsManifests: IExtensionManifest[] = []; + if (extensions.length) { + output.log(this.location ? localize('installingExtensionsOnLocation', "Installing extensions on {0}...", this.location) : localize('installingExtensions', "Installing extensions...")); + } + + const installed = await this.extensionManagementService.getInstalled(ExtensionType.User); + const checkIfNotInstalled = (id: string, version?: string): boolean => { + const installedExtension = installed.find(i => areSameExtensions(i.identifier, { id })); + if (installedExtension) { + if (!version && !force) { + output.log(localize('alreadyInstalled-checkAndUpdate', "Extension '{0}' v{1} is already installed. Use '--force' option to update to latest version or provide '@' to install a specific version, for example: '{2}@1.2.3'.", id, installedExtension.manifest.version, id)); + return false; + } + if (version && installedExtension.manifest.version === version) { + output.log(localize('alreadyInstalled', "Extension '{0}' is already installed.", `${id}@${version}`)); + return false; + } + } + return true; + }; + const vsixs: URI[] = []; + const installExtensionInfos: InstallExtensionInfo[] = []; + for (const extension of extensions) { + if (extension instanceof URI) { + vsixs.push(extension); + } else { + const [id, version] = getIdAndVersion(extension); + if (checkIfNotInstalled(id, version)) { + installExtensionInfos.push({ id, version, installOptions: { isBuiltin: false, isMachineScoped } }); + } + } + } + for (const extension of builtinExtensionIds) { + const [id, version] = getIdAndVersion(extension); + if (checkIfNotInstalled(id, version)) { + installExtensionInfos.push({ id, version, installOptions: { isBuiltin: true, isMachineScoped: false } }); + } + } + + if (vsixs.length) { + await Promise.all(vsixs.map(async vsix => { + try { + const manifest = await this.installVSIX(vsix, force, output); + if (manifest) { + installedExtensionsManifests.push(manifest); + } + } catch (err) { + output.error(err.message || err.stack || err); + failed.push(vsix.toString()); + } + })); + } + + if (installExtensionInfos.length) { + + const galleryExtensions = await this.getGalleryExtensions(installExtensionInfos); + + await Promise.all(installExtensionInfos.map(async extensionInfo => { + const gallery = galleryExtensions.get(extensionInfo.id.toLowerCase()); + if (gallery) { + try { + const manifest = await this.installFromGallery(extensionInfo, gallery, installed, force, output); + if (manifest) { + installedExtensionsManifests.push(manifest); + } + } catch (err) { + output.error(err.message || err.stack || err); + failed.push(extensionInfo.id); + } + } else { + output.error(`${notFound(extensionInfo.version ? `${extensionInfo.id}@${extensionInfo.version}` : extensionInfo.id)}\n${useId}`); + failed.push(extensionInfo.id); + } + })); + + } + + if (installedExtensionsManifests.some(manifest => isLanguagePackExtension(manifest))) { + await this.updateLocalizationsCache(); + } + + if (failed.length) { + throw new Error(localize('installation failed', "Failed Installing Extensions: {0}", failed.join(', '))); + } + } + + private async installVSIX(vsix: URI, force: boolean, output: CLIOutput): Promise { + + const manifest = await this.extensionManagementService.getManifest(vsix); + if (!manifest) { + throw new Error('Invalid vsix'); + } + + const valid = await this.validateVSIX(manifest, force, output); + if (valid) { + try { + await this.extensionManagementService.install(vsix); + output.log(localize('successVsixInstall', "Extension '{0}' was successfully installed.", getBaseLabel(vsix))); + return manifest; + } catch (error) { + if (isPromiseCanceledError(error)) { + output.log(localize('cancelVsixInstall', "Cancelled installing extension '{0}'.", getBaseLabel(vsix))); + return null; + } else { + throw error; + } + } + } + return null; + } + + private async getGalleryExtensions(extensions: InstallExtensionInfo[]): Promise> { + const extensionIds = extensions.filter(({ version }) => version === undefined).map(({ id }) => id); + const extensionsWithIdAndVersion = extensions.filter(({ version }) => version !== undefined); + + const galleryExtensions = new Map(); + await Promise.all([ + (async () => { + const result = await this.extensionGalleryService.getExtensions(extensionIds, CancellationToken.None); + result.forEach(extension => galleryExtensions.set(extension.identifier.id.toLowerCase(), extension)); + })(), + Promise.all(extensionsWithIdAndVersion.map(async ({ id, version }) => { + const extension = await this.extensionGalleryService.getCompatibleExtension({ id }, version); + if (extension) { + galleryExtensions.set(extension.identifier.id.toLowerCase(), extension); + } + })) + ]); + + return galleryExtensions; + } + + private async installFromGallery({ id, version, installOptions }: InstallExtensionInfo, galleryExtension: IGalleryExtension, installed: ILocalExtension[], force: boolean, output: CLIOutput): Promise { + const manifest = await this.extensionGalleryService.getManifest(galleryExtension, CancellationToken.None); + if (manifest && !this.validateExtensionKind(manifest, output)) { + return null; + } + + const installedExtension = installed.find(e => areSameExtensions(e.identifier, galleryExtension.identifier)); + if (installedExtension) { + if (galleryExtension.version === installedExtension.manifest.version) { + output.log(localize('alreadyInstalled', "Extension '{0}' is already installed.", version ? `${id}@${version}` : id)); + return null; + } + output.log(localize('updateMessage', "Updating the extension '{0}' to the version {1}", id, galleryExtension.version)); + } + + try { + if (installOptions.isBuiltin) { + output.log(localize('installing builtin ', "Installing builtin extension '{0}' v{1}...", id, galleryExtension.version)); + } else { + output.log(localize('installing', "Installing extension '{0}' v{1}...", id, galleryExtension.version)); + } + + await this.extensionManagementService.installFromGallery(galleryExtension, installOptions); + output.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed.", id, galleryExtension.version)); + return manifest; + } catch (error) { + if (isPromiseCanceledError(error)) { + output.log(localize('cancelInstall', "Cancelled installing extension '{0}'.", id)); + return null; + } else { + throw error; + } + } + } + + protected validateExtensionKind(_manifest: IExtensionManifest, output: CLIOutput): boolean { + return true; + } + + private async validateVSIX(manifest: IExtensionManifest, force: boolean, output: CLIOutput): Promise { + const extensionIdentifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) }; + const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User); + const newer = installedExtensions.find(local => areSameExtensions(extensionIdentifier, local.identifier) && gt(local.manifest.version, manifest.version)); + + if (newer && !force) { + output.log(localize('forceDowngrade', "A newer version of extension '{0}' v{1} is already installed. Use '--force' option to downgrade to older version.", newer.identifier.id, newer.manifest.version, manifest.version)); + return false; + } + + return this.validateExtensionKind(manifest, output); + } + + public async uninstallExtensions(extensions: (string | URI)[], force: boolean, output: CLIOutput = console): Promise { + const getExtensionId = async (extensionDescription: string | URI): Promise => { + if (extensionDescription instanceof URI) { + const manifest = await this.extensionManagementService.getManifest(extensionDescription); + return getId(manifest); + } + return extensionDescription; + }; + + const uninstalledExtensions: ILocalExtension[] = []; + for (const extension of extensions) { + const id = await getExtensionId(extension); + const installed = await this.extensionManagementService.getInstalled(); + const extensionsToUninstall = installed.filter(e => areSameExtensions(e.identifier, { id })); + if (!extensionsToUninstall.length) { + throw new Error(`${this.notInstalled(id)}\n${useId}`); + } + if (extensionsToUninstall.some(e => e.type === ExtensionType.System)) { + output.log(localize('builtin', "Extension '{0}' is a Built-in extension and cannot be uninstalled", id)); + return; + } + if (!force && extensionsToUninstall.some(e => e.isBuiltin)) { + output.log(localize('forceUninstall', "Extension '{0}' is marked as a Built-in extension by user. Please use '--force' option to uninstall it.", id)); + return; + } + output.log(localize('uninstalling', "Uninstalling {0}...", id)); + for (const extensionToUninstall of extensionsToUninstall) { + await this.extensionManagementService.uninstall(extensionToUninstall); + uninstalledExtensions.push(extensionToUninstall); + } + + if (this.location) { + output.log(localize('successUninstallFromLocation', "Extension '{0}' was successfully uninstalled from {1}!", id, this.location)); + } else { + output.log(localize('successUninstall', "Extension '{0}' was successfully uninstalled!", id)); + } + + } + + if (uninstalledExtensions.some(e => isLanguagePackExtension(e.manifest))) { + await this.updateLocalizationsCache(); + } + } + + public async locateExtension(extensions: string[], output: CLIOutput = console): Promise { + const installed = await this.extensionManagementService.getInstalled(); + extensions.forEach(e => { + installed.forEach(i => { + if (i.identifier.id === e) { + if (i.location.scheme === Schemas.file) { + output.log(i.location.fsPath); + return; + } + } + }); + }); + } + + + private updateLocalizationsCache(): Promise { + return this.localizationsService.update(); + } + + private notInstalled(id: string) { + return this.location ? localize('notInstalleddOnLocation', "Extension '{0}' is not installed on {1}.", id, this.location) : localize('notInstalled', "Extension '{0}' is not installed.", id); + } + +} diff --git a/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts b/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts index 20d24771d..393745571 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts @@ -42,12 +42,16 @@ export class ExtensionIdentifierWithVersion implements IExtensionIdentifierWithV } } +export function getExtensionId(publisher: string, name: string): string { + return `${publisher}.${name}`; +} + export function adoptToGalleryExtensionId(id: string): string { return id.toLocaleLowerCase(); } export function getGalleryExtensionId(publisher: string, name: string): string { - return `${publisher.toLocaleLowerCase()}.${name.toLocaleLowerCase()}`; + return adoptToGalleryExtensionId(getExtensionId(publisher, name)); } export function groupByExtension(extensions: T[], getExtensionIdentifier: (t: T) => IExtensionIdentifier): T[][] { diff --git a/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts b/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts index 7d17d9ef3..a9ec2f35a 100644 --- a/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts +++ b/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts @@ -21,6 +21,8 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionRecommendationNotificationService, RecommendationsNotificationResult, RecommendationSource } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; import { localize } from 'vs/nls'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { Event } from 'vs/base/common/event'; +import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; type ExeExtensionRecommendationsClassification = { extensionId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; @@ -52,6 +54,7 @@ export class ExtensionTipsService extends BaseExtensionTipsService { @ITelemetryService private readonly telemetryService: ITelemetryService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IStorageService private readonly storageService: IStorageService, + @INativeHostService private readonly nativeHostService: INativeHostService, @IExtensionRecommendationNotificationService private readonly extensionRecommendationNotificationService: IExtensionRecommendationNotificationService, @IFileService fileService: IFileService, @IProductService productService: IProductService, @@ -172,6 +175,11 @@ export class ExtensionTipsService extends BaseExtensionTipsService { case RecommendationsNotificationResult.Ignored: this.highImportanceTipsByExe.delete(exeName); break; + case RecommendationsNotificationResult.IncompatibleWindow: + // Recommended in incompatible window. Schedule the prompt after active window change + const onActiveWindowChange = Event.once(Event.latch(Event.any(this.nativeHostService.onDidOpenWindow, this.nativeHostService.onDidFocusWindow))); + this._register(onActiveWindowChange(() => this.promptHighImportanceExeBasedTip())); + break; case RecommendationsNotificationResult.TooMany: // Too many notifications. Schedule the prompt after one hour const disposable = this._register(disposableTimeout(() => { disposable.dispose(); this.promptHighImportanceExeBasedTip(); }, 60 * 60 * 1000 /* 1 hour */)); @@ -217,6 +225,12 @@ export class ExtensionTipsService extends BaseExtensionTipsService { this.promptMediumImportanceExeBasedTip(); break; + case RecommendationsNotificationResult.IncompatibleWindow: + // Recommended in incompatible window. Schedule the prompt after active window change + const onActiveWindowChange = Event.once(Event.latch(Event.any(this.nativeHostService.onDidOpenWindow, this.nativeHostService.onDidFocusWindow))); + this._register(onActiveWindowChange(() => this.promptMediumImportanceExeBasedTip())); + break; + case RecommendationsNotificationResult.TooMany: // Too many notifications. Schedule the prompt after one hour const disposable2 = this._register(disposableTimeout(() => { disposable2.dispose(); this.promptMediumImportanceExeBasedTip(); }, 60 * 60 * 1000 /* 1 hour */)); diff --git a/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts b/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts new file mode 100644 index 000000000..c1085576c --- /dev/null +++ b/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.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 * as assert from 'assert'; +import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/common/extensionGalleryService'; +import { isUUID } from 'vs/base/common/uuid'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IFileService } from 'vs/platform/files/common/files'; +import { FileService } from 'vs/platform/files/common/fileService'; +import { NullLogService } from 'vs/platform/log/common/log'; +import product from 'vs/platform/product/common/product'; +import { InMemoryStorageService, IStorageService } from 'vs/platform/storage/common/storage'; +import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; +import { URI } from 'vs/base/common/uri'; +import { joinPath } from 'vs/base/common/resources'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { mock } from 'vs/base/test/common/mock'; + +class EnvironmentServiceMock extends mock() { + constructor(readonly serviceMachineIdResource: URI) { + super(); + } +} + +suite('Extension Gallery Service', () => { + const disposables: DisposableStore = new DisposableStore(); + let fileService: IFileService, environmentService: IEnvironmentService, storageService: IStorageService; + + setup(() => { + const serviceMachineIdResource = joinPath(URI.file('tests').with({ scheme: 'vscode-tests' }), 'machineid'); + environmentService = new EnvironmentServiceMock(serviceMachineIdResource); + fileService = disposables.add(new FileService(new NullLogService())); + const fileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); + fileService.registerProvider(serviceMachineIdResource.scheme, fileSystemProvider); + storageService = new InMemoryStorageService(); + }); + + teardown(() => disposables.clear()); + + test('marketplace machine id', async () => { + const headers = await resolveMarketplaceHeaders(product.version, environmentService, fileService, storageService); + assert.ok(isUUID(headers['X-Market-User-Id'])); + const headers2 = await resolveMarketplaceHeaders(product.version, environmentService, fileService, storageService); + assert.equal(headers['X-Market-User-Id'], headers2['X-Market-User-Id']); + }); +}); diff --git a/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts b/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts deleted file mode 100644 index e2a7ed43a..000000000 --- a/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.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. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import * as os from 'os'; -import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; -import { getRandomTestPath } from 'vs/base/test/node/testUtils'; -import { join } from 'vs/base/common/path'; -import { mkdirp, RimRafMode, rimraf } from 'vs/base/node/pfs'; -import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/common/extensionGalleryService'; -import { isUUID } from 'vs/base/common/uuid'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IFileService } from 'vs/platform/files/common/files'; -import { FileService } from 'vs/platform/files/common/fileService'; -import { NullLogService } from 'vs/platform/log/common/log'; -import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; -import { Schemas } from 'vs/base/common/network'; -import product from 'vs/platform/product/common/product'; -import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; -import { IStorageService } from 'vs/platform/storage/common/storage'; - -suite('Extension Gallery Service', () => { - const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'extensiongalleryservice'); - const marketplaceHome = join(parentDir, 'Marketplace'); - let fileService: IFileService; - let disposables: DisposableStore; - - setup(done => { - - disposables = new DisposableStore(); - fileService = new FileService(new NullLogService()); - disposables.add(fileService); - - const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); - disposables.add(diskFileSystemProvider); - fileService.registerProvider(Schemas.file, diskFileSystemProvider); - - // Delete any existing backups completely and then re-create it. - rimraf(marketplaceHome, RimRafMode.MOVE).then(() => { - mkdirp(marketplaceHome).then(() => { - done(); - }, error => done(error)); - }, error => done(error)); - }); - - teardown(done => { - disposables.clear(); - rimraf(marketplaceHome, RimRafMode.MOVE).then(done, done); - }); - - test('marketplace machine id', () => { - const args = ['--user-data-dir', marketplaceHome]; - const environmentService = new NativeEnvironmentService(parseArgs(args, OPTIONS)); - const storageService: IStorageService = new TestStorageService(); - - return resolveMarketplaceHeaders(product.version, environmentService, fileService, storageService).then(headers => { - assert.ok(isUUID(headers['X-Market-User-Id'])); - - return resolveMarketplaceHeaders(product.version, environmentService, fileService, storageService).then(headers2 => { - assert.equal(headers['X-Market-User-Id'], headers2['X-Market-User-Id']); - }); - }); - }); -}); diff --git a/src/vs/platform/extensionRecommendations/common/extensionRecommendations.ts b/src/vs/platform/extensionRecommendations/common/extensionRecommendations.ts index cea1c09e2..c3eff4b7a 100644 --- a/src/vs/platform/extensionRecommendations/common/extensionRecommendations.ts +++ b/src/vs/platform/extensionRecommendations/common/extensionRecommendations.ts @@ -11,10 +11,19 @@ export const enum RecommendationSource { EXE = 3 } +export function RecommendationSourceToString(source: RecommendationSource) { + switch (source) { + case RecommendationSource.FILE: return 'file'; + case RecommendationSource.WORKSPACE: return 'workspace'; + case RecommendationSource.EXE: return 'exe'; + } +} + export const enum RecommendationsNotificationResult { Ignored = 'ignored', Cancelled = 'cancelled', TooMany = 'toomany', + IncompatibleWindow = 'incompatibleWindow', Accepted = 'reacted', } diff --git a/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts b/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts index b5bdc6235..c863c64bf 100644 --- a/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts +++ b/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts @@ -8,14 +8,24 @@ import { IFileSystemProviderWithFileReadWriteCapability, FileSystemProviderCapab import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { VSBuffer } from 'vs/base/common/buffer'; -import { joinPath, extUri, dirname } from 'vs/base/common/resources'; +import { Throttler } from 'vs/base/common/async'; import { localize } from 'vs/nls'; import * as browser from 'vs/base/browser/browser'; +import { joinPath } from 'vs/base/common/resources'; const INDEXEDDB_VSCODE_DB = 'vscode-web-db'; export const INDEXEDDB_USERDATA_OBJECT_STORE = 'vscode-userdata-store'; export const INDEXEDDB_LOGS_OBJECT_STORE = 'vscode-logs-store'; +// Standard FS Errors (expected to be thrown in production when invalid FS operations are requested) +const ERR_FILE_NOT_FOUND = createFileSystemProviderError(localize('fileNotExists', "File does not exist"), FileSystemProviderErrorCode.FileNotFound); +const ERR_FILE_IS_DIR = createFileSystemProviderError(localize('fileIsDirectory', "File is Directory"), FileSystemProviderErrorCode.FileIsADirectory); +const ERR_FILE_NOT_DIR = createFileSystemProviderError(localize('fileNotDirectory', "File is not a directory"), FileSystemProviderErrorCode.FileNotADirectory); +const ERR_DIR_NOT_EMPTY = createFileSystemProviderError(localize('dirIsNotEmpty', "Directory is not empty"), FileSystemProviderErrorCode.Unknown); + +// Arbitrary Internal Errors (should never be thrown in production) +const ERR_UNKNOWN_INTERNAL = (message: string) => createFileSystemProviderError(localize('internal', "Internal error occured in IndexedDB File System Provider. ({0})", message), FileSystemProviderErrorCode.Unknown); + export class IndexedDB { private indexedDBPromise: Promise; @@ -38,7 +48,7 @@ export class IndexedDB { } private openIndexedDB(name: string, version: number, stores: string[]): Promise { - if (browser.isEdge) { + if (browser.isEdgeLegacy) { return Promise.resolve(null); } return new Promise((c, e) => { @@ -65,13 +75,140 @@ export class IndexedDB { }; }); } - } export interface IIndexedDBFileSystemProvider extends Disposable, IFileSystemProviderWithFileReadWriteCapability { reset(): Promise; } +type DirEntry = [string, FileType]; + +type IndexedDBFileSystemEntry = + | { + path: string, + type: FileType.Directory, + children: Map, + } + | { + path: string, + type: FileType.File, + size: number | undefined, + }; + +class IndexedDBFileSystemNode { + public type: FileType; + + constructor(private entry: IndexedDBFileSystemEntry) { + this.type = entry.type; + } + + + read(path: string) { + return this.doRead(path.split('/').filter(p => p.length)); + } + + private doRead(pathParts: string[]): IndexedDBFileSystemEntry | undefined { + if (pathParts.length === 0) { return this.entry; } + if (this.entry.type !== FileType.Directory) { + throw ERR_UNKNOWN_INTERNAL('Internal error reading from IndexedDBFSNode -- expected directory at ' + this.entry.path); + } + const next = this.entry.children.get(pathParts[0]); + + if (!next) { return undefined; } + return next.doRead(pathParts.slice(1)); + } + + delete(path: string) { + const toDelete = path.split('/').filter(p => p.length); + if (toDelete.length === 0) { + if (this.entry.type !== FileType.Directory) { + throw ERR_UNKNOWN_INTERNAL(`Internal error deleting from IndexedDBFSNode. Expected root entry to be directory`); + } + this.entry.children.clear(); + } else { + return this.doDelete(toDelete, path); + } + } + + private doDelete = (pathParts: string[], originalPath: string) => { + if (pathParts.length === 0) { + throw ERR_UNKNOWN_INTERNAL(`Internal error deleting from IndexedDBFSNode -- got no deletion path parts (encountered while deleting ${originalPath})`); + } + else if (this.entry.type !== FileType.Directory) { + throw ERR_UNKNOWN_INTERNAL('Internal error deleting from IndexedDBFSNode -- expected directory at ' + this.entry.path); + } + else if (pathParts.length === 1) { + this.entry.children.delete(pathParts[0]); + } + else { + const next = this.entry.children.get(pathParts[0]); + if (!next) { + throw ERR_UNKNOWN_INTERNAL('Internal error deleting from IndexedDBFSNode -- expected entry at ' + this.entry.path + '/' + next); + } + next.doDelete(pathParts.slice(1), originalPath); + } + }; + + add(path: string, entry: { type: 'file', size?: number } | { type: 'dir' }) { + this.doAdd(path.split('/').filter(p => p.length), entry, path); + } + + private doAdd(pathParts: string[], entry: { type: 'file', size?: number } | { type: 'dir' }, originalPath: string) { + if (pathParts.length === 0) { + throw ERR_UNKNOWN_INTERNAL(`Internal error creating IndexedDBFSNode -- adding empty path (encountered while adding ${originalPath})`); + } + else if (this.entry.type !== FileType.Directory) { + throw ERR_UNKNOWN_INTERNAL(`Internal error creating IndexedDBFSNode -- parent is not a directory (encountered while adding ${originalPath})`); + } + else if (pathParts.length === 1) { + const next = pathParts[0]; + const existing = this.entry.children.get(next); + if (entry.type === 'dir') { + if (existing?.entry.type === FileType.File) { + throw ERR_UNKNOWN_INTERNAL(`Internal error creating IndexedDBFSNode -- overwriting file with directory: ${this.entry.path}/${next} (encountered while adding ${originalPath})`); + } + this.entry.children.set(next, existing ?? new IndexedDBFileSystemNode({ + type: FileType.Directory, + path: this.entry.path + '/' + next, + children: new Map(), + })); + } else { + if (existing?.entry.type === FileType.Directory) { + throw ERR_UNKNOWN_INTERNAL(`Internal error creating IndexedDBFSNode -- overwriting directory with file: ${this.entry.path}/${next} (encountered while adding ${originalPath})`); + } + this.entry.children.set(next, new IndexedDBFileSystemNode({ + type: FileType.File, + path: this.entry.path + '/' + next, + size: entry.size, + })); + } + } + else if (pathParts.length > 1) { + const next = pathParts[0]; + let childNode = this.entry.children.get(next); + if (!childNode) { + childNode = new IndexedDBFileSystemNode({ + children: new Map(), + path: this.entry.path + '/' + next, + type: FileType.Directory + }); + this.entry.children.set(next, childNode); + } + else if (childNode.type === FileType.File) { + throw ERR_UNKNOWN_INTERNAL(`Internal error creating IndexedDBFSNode -- overwriting file entry with directory: ${this.entry.path}/${next} (encountered while adding ${originalPath})`); + } + childNode.doAdd(pathParts.slice(1), entry, originalPath); + } + } + + print(indentation = '') { + console.log(indentation + this.entry.path); + if (this.entry.type === FileType.Directory) { + this.entry.children.forEach(child => child.print(indentation + ' ')); + } + } +} + class IndexedDBFileSystemProvider extends Disposable implements IIndexedDBFileSystemProvider { readonly capabilities: FileSystemProviderCapabilities = @@ -83,11 +220,14 @@ class IndexedDBFileSystemProvider extends Disposable implements IIndexedDBFileSy readonly onDidChangeFile: Event = this._onDidChangeFile.event; private readonly versions: Map = new Map(); - private readonly dirs: Set = new Set(); - constructor(private readonly scheme: string, private readonly database: IDBDatabase, private readonly store: string) { + private cachedFiletree: Promise | undefined; + private writeManyThrottler: Throttler; + + constructor(scheme: string, private readonly database: IDBDatabase, private readonly store: string) { super(); - this.dirs.add('/'); + this.writeManyThrottler = new Throttler(); + } watch(resource: URI, opts: IWatchOptions): IDisposable { @@ -98,29 +238,22 @@ class IndexedDBFileSystemProvider extends Disposable implements IIndexedDBFileSy try { const resourceStat = await this.stat(resource); if (resourceStat.type === FileType.File) { - throw createFileSystemProviderError(localize('fileNotDirectory', "File is not a directory"), FileSystemProviderErrorCode.FileNotADirectory); + throw ERR_FILE_NOT_DIR; } } catch (error) { /* Ignore */ } - - // Make sure parent dir exists - await this.stat(dirname(resource)); - - this.dirs.add(resource.path); + (await this.getFiletree()).add(resource.path, { type: 'dir' }); } async stat(resource: URI): Promise { - try { - const content = await this.readFile(resource); + const content = (await this.getFiletree()).read(resource.path); + if (content?.type === FileType.File) { return { type: FileType.File, ctime: 0, mtime: this.versions.get(resource.toString()) || 0, - size: content.byteLength + size: content.size ?? (await this.readFile(resource)).byteLength }; - } catch (e) { - } - const files = await this.readdir(resource); - if (files.length) { + } else if (content?.type === FileType.Directory) { return { type: FileType.Directory, ctime: 0, @@ -128,75 +261,112 @@ class IndexedDBFileSystemProvider extends Disposable implements IIndexedDBFileSy size: 0 }; } - if (this.dirs.has(resource.path)) { - return { - type: FileType.Directory, - ctime: 0, - mtime: 0, - size: 0 - }; + else { + throw ERR_FILE_NOT_FOUND; } - throw createFileSystemProviderError(localize('fileNotExists', "File does not exist"), FileSystemProviderErrorCode.FileNotFound); } - async readdir(resource: URI): Promise<[string, FileType][]> { - const hasKey = await this.hasKey(resource.path); - if (hasKey) { - throw createFileSystemProviderError(localize('fileNotDirectory', "File is not a directory"), FileSystemProviderErrorCode.FileNotADirectory); + async readdir(resource: URI): Promise { + const entry = (await this.getFiletree()).read(resource.path); + if (!entry) { + // Dirs aren't saved to disk, so empty dirs will be lost on reload. + // Thus we have two options for what happens when you try to read a dir and nothing is found: + // - Throw FileSystemProviderErrorCode.FileNotFound + // - Return [] + // We choose to return [] as creating a dir then reading it (even after reload) should not throw an error. + return []; } - const keys = await this.getAllKeys(); - const files: Map = new Map(); - for (const key of keys) { - const keyResource = this.toResource(key); - if (extUri.isEqualOrParent(keyResource, resource)) { - const path = extUri.relativePath(resource, keyResource); - if (path) { - const keySegments = path.split('/'); - files.set(keySegments[0], [keySegments[0], keySegments.length === 1 ? FileType.File : FileType.Directory]); - } - } + if (entry.type !== FileType.Directory) { + throw ERR_FILE_NOT_DIR; + } + else { + return [...entry.children.entries()].map(([name, node]) => [name, node.type]); } - return [...files.values()]; } async readFile(resource: URI): Promise { - const hasKey = await this.hasKey(resource.path); - if (!hasKey) { - throw createFileSystemProviderError(localize('fileNotFound', "File not found"), FileSystemProviderErrorCode.FileNotFound); - } - const value = await this.getValue(resource.path); - if (typeof value === 'string') { - return VSBuffer.fromString(value).buffer; - } else { - return value; - } + const buffer = await new Promise((c, e) => { + const transaction = this.database.transaction([this.store]); + const objectStore = transaction.objectStore(this.store); + const request = objectStore.get(resource.path); + request.onerror = () => e(request.error); + request.onsuccess = () => { + if (request.result instanceof Uint8Array) { + c(request.result); + } else if (typeof request.result === 'string') { + c(VSBuffer.fromString(request.result).buffer); + } + else { + if (request.result === undefined) { + e(ERR_FILE_NOT_FOUND); + } else { + e(ERR_UNKNOWN_INTERNAL(`IndexedDB entry at "${resource.path}" in unexpected format`)); + } + } + }; + }); + + (await this.getFiletree()).add(resource.path, { type: 'file', size: buffer.byteLength }); + return buffer; } async writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { - const hasKey = await this.hasKey(resource.path); - if (!hasKey) { - const files = await this.readdir(resource); - if (files.length) { - throw createFileSystemProviderError(localize('fileIsDirectory', "File is Directory"), FileSystemProviderErrorCode.FileIsADirectory); - } + const existing = await this.stat(resource).catch(() => undefined); + if (existing?.type === FileType.Directory) { + throw ERR_FILE_IS_DIR; } - await this.setValue(resource.path, content); + + this.fileWriteBatch.push({ content, resource }); + await this.writeManyThrottler.queue(() => this.writeMany()); + (await this.getFiletree()).add(resource.path, { type: 'file', size: content.byteLength }); this.versions.set(resource.toString(), (this.versions.get(resource.toString()) || 0) + 1); this._onDidChangeFile.fire([{ resource, type: FileChangeType.UPDATED }]); } async delete(resource: URI, opts: FileDeleteOptions): Promise { - const hasKey = await this.hasKey(resource.path); - if (hasKey) { - await this.deleteKey(resource.path); - this.versions.delete(resource.path); - this._onDidChangeFile.fire([{ resource, type: FileChangeType.DELETED }]); - return; + let stat: IStat; + try { + stat = await this.stat(resource); + } catch (e) { + if (e.code === FileSystemProviderErrorCode.FileNotFound) { + return; + } + throw e; } + let toDelete: string[]; if (opts.recursive) { - const files = await this.readdir(resource); - await Promise.all(files.map(([key]) => this.delete(joinPath(resource, key), opts))); + const tree = (await this.tree(resource)); + toDelete = tree.map(([path]) => path); + } else { + if (stat.type === FileType.Directory && (await this.readdir(resource)).length) { + throw ERR_DIR_NOT_EMPTY; + } + toDelete = [resource.path]; + } + await this.deleteKeys(toDelete); + (await this.getFiletree()).delete(resource.path); + toDelete.forEach(key => this.versions.delete(key)); + this._onDidChangeFile.fire(toDelete.map(path => ({ resource: resource.with({ path }), type: FileChangeType.DELETED }))); + } + + private async tree(resource: URI): Promise { + if ((await this.stat(resource)).type === FileType.Directory) { + const topLevelEntries = (await this.readdir(resource)).map(([key, type]) => { + return [joinPath(resource, key).path, type] as [string, FileType]; + }); + let allEntries = topLevelEntries; + await Promise.all(topLevelEntries.map( + async ([key, type]) => { + if (type === FileType.Directory) { + const childEntries = (await this.tree(resource.with({ path: key }))); + allEntries = allEntries.concat(childEntries); + } + })); + return allEntries; + } else { + const entries: DirEntry[] = [[resource.path, FileType.File]]; + return entries; } } @@ -204,58 +374,57 @@ class IndexedDBFileSystemProvider extends Disposable implements IIndexedDBFileSy return Promise.reject(new Error('Not Supported')); } - private toResource(key: string): URI { - return URI.file(key).with({ scheme: this.scheme }); + private getFiletree(): Promise { + if (!this.cachedFiletree) { + this.cachedFiletree = new Promise((c, e) => { + const transaction = this.database.transaction([this.store]); + const objectStore = transaction.objectStore(this.store); + const request = objectStore.getAllKeys(); + request.onerror = () => e(request.error); + request.onsuccess = () => { + const rootNode = new IndexedDBFileSystemNode({ + children: new Map(), + path: '', + type: FileType.Directory + }); + const keys = request.result.map(key => key.toString()); + keys.forEach(key => rootNode.add(key, { type: 'file' })); + c(rootNode); + }; + }); + } + return this.cachedFiletree; } - async getAllKeys(): Promise { - return new Promise(async (c, e) => { - const transaction = this.database.transaction([this.store]); - const objectStore = transaction.objectStore(this.store); - const request = objectStore.getAllKeys(); - request.onerror = () => e(request.error); - request.onsuccess = () => c(request.result); - }); - } + private fileWriteBatch: { resource: URI, content: Uint8Array }[] = []; + private async writeMany() { + return new Promise((c, e) => { + const fileBatch = this.fileWriteBatch; + this.fileWriteBatch = []; + if (fileBatch.length === 0) { return c(); } - hasKey(key: string): Promise { - return new Promise(async (c, e) => { - const transaction = this.database.transaction([this.store]); - const objectStore = transaction.objectStore(this.store); - const request = objectStore.getKey(key); - request.onerror = () => e(request.error); - request.onsuccess = () => { - c(!!request.result); - }; - }); - } - - getValue(key: string): Promise { - return new Promise(async (c, e) => { - const transaction = this.database.transaction([this.store]); - const objectStore = transaction.objectStore(this.store); - const request = objectStore.get(key); - request.onerror = () => e(request.error); - request.onsuccess = () => c(request.result || ''); - }); - } - - setValue(key: string, value: Uint8Array): Promise { - return new Promise(async (c, e) => { const transaction = this.database.transaction([this.store], 'readwrite'); + transaction.onerror = () => e(transaction.error); const objectStore = transaction.objectStore(this.store); - const request = objectStore.put(value, key); - request.onerror = () => e(request.error); + let request: IDBRequest = undefined!; + for (const entry of fileBatch) { + request = objectStore.put(entry.content, entry.resource.path); + } request.onsuccess = () => c(); }); } - deleteKey(key: string): Promise { + private deleteKeys(keys: string[]): Promise { return new Promise(async (c, e) => { + if (keys.length === 0) { return c(); } const transaction = this.database.transaction([this.store], 'readwrite'); + transaction.onerror = () => e(transaction.error); const objectStore = transaction.objectStore(this.store); - const request = objectStore.delete(key); - request.onerror = () => e(request.error); + let request: IDBRequest = undefined!; + for (const key of keys) { + request = objectStore.delete(key); + } + request.onsuccess = () => c(); }); } diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index 23001b2a2..dbc98c13b 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -3,18 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; +import { mark } from 'vs/base/common/performance'; import { Disposable, IDisposable, toDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent, ETAG_DISABLED, hasFileReadStreamCapability, IFileSystemProviderWithFileReadStreamCapability, ensureFileSystemProviderError, IFileSystemProviderCapabilitiesChangeEvent } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; -import { isAbsolutePath, dirname, basename, joinPath, IExtUri, extUri, extUriIgnorePathCase } from 'vs/base/common/resources'; -import { localize } from 'vs/nls'; +import { IExtUri, extUri, extUriIgnorePathCase, isAbsolutePath } from 'vs/base/common/resources'; import { TernarySearchTree } from 'vs/base/common/map'; import { isNonEmptyArray, coalesce } from 'vs/base/common/arrays'; -import { getBaseLabel } from 'vs/base/common/labels'; import { ILogService } from 'vs/platform/log/common/log'; -import { VSBuffer, VSBufferReadable, readableToBuffer, bufferToReadable, streamToBuffer, bufferToStream, VSBufferReadableStream, VSBufferReadableBufferedStream, bufferedStreamToBuffer, newWriteableBufferStream } from 'vs/base/common/buffer'; -import { isReadableStream, transform, peekReadable, peekStream, isReadableBufferedStream } from 'vs/base/common/stream'; +import { VSBuffer, VSBufferReadable, readableToBuffer, bufferToReadable, streamToBuffer, VSBufferReadableStream, VSBufferReadableBufferedStream, bufferedStreamToBuffer, newWriteableBufferStream } from 'vs/base/common/buffer'; +import { isReadableStream, transform, peekReadable, peekStream, isReadableBufferedStream, newWriteableStream, IReadableStreamObservable, observe } from 'vs/base/common/stream'; import { Queue } from 'vs/base/common/async'; import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; import { Schemas } from 'vs/base/common/network'; @@ -49,6 +49,8 @@ export class FileService extends Disposable implements IFileService { throw new Error(`A filesystem provider for the scheme '${scheme}' is already registered.`); } + mark(`code/registerFilesystem/${scheme}`); + // Add provider with event this.provider.set(scheme, provider); this._onDidChangeFileSystemProviderRegistrations.fire({ added: true, scheme, provider }); @@ -102,7 +104,7 @@ export class FileService extends Disposable implements IFileService { return !!(provider && (provider.capabilities & capability)); } - listCapabilities(): Iterable<{ scheme: string, capabilities: FileSystemProviderCapabilities }> { + listCapabilities(): Iterable<{ scheme: string, capabilities: FileSystemProviderCapabilities; }> { return Iterable.map(this.provider, ([scheme, provider]) => ({ scheme, capabilities: provider.capabilities })); } @@ -215,14 +217,15 @@ export class FileService extends Disposable implements IFileService { }); } - private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat | { type: FileType } & Partial, siblings: number | undefined, resolveMetadata: boolean, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise; + private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat | { type: FileType; } & Partial, siblings: number | undefined, resolveMetadata: boolean, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise; private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat, siblings: number | undefined, resolveMetadata: true, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise; - private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat | { type: FileType } & Partial, siblings: number | undefined, resolveMetadata: boolean, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise { + private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat | { type: FileType; } & Partial, siblings: number | undefined, resolveMetadata: boolean, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise { + const { providerExtUri } = this.getExtUri(provider); // convert to file stat const fileStat: IFileStat = { resource, - name: getBaseLabel(resource), + name: providerExtUri.basename(resource), isFile: (stat.type & FileType.File) !== 0, isDirectory: (stat.type & FileType.Directory) !== 0, isSymbolicLink: (stat.type & FileType.SymbolicLink) !== 0, @@ -238,7 +241,7 @@ export class FileService extends Disposable implements IFileService { const entries = await provider.readdir(resource); const resolvedEntries = await Promise.all(entries.map(async ([name, type]) => { try { - const childResource = joinPath(resource, name); + const childResource = providerExtUri.joinPath(resource, name); const childStat = resolveMetadata ? await provider.stat(childResource) : { type }; return await this.toFileStat(provider, childResource, childStat, entries.length, resolveMetadata, recurse); @@ -263,8 +266,8 @@ export class FileService extends Disposable implements IFileService { return fileStat; } - async resolveAll(toResolve: { resource: URI, options?: IResolveFileOptions }[]): Promise; - async resolveAll(toResolve: { resource: URI, options: IResolveMetadataFileOptions }[]): Promise; + async resolveAll(toResolve: { resource: URI, options?: IResolveFileOptions; }[]): Promise; + async resolveAll(toResolve: { resource: URI, options: IResolveMetadataFileOptions; }[]): Promise; async resolveAll(toResolve: { resource: URI; options?: IResolveFileOptions; }[]): Promise { return Promise.all(toResolve.map(async entry => { try { @@ -327,6 +330,7 @@ export class FileService extends Disposable implements IFileService { async writeFile(resource: URI, bufferOrReadableOrStream: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: IWriteFileOptions): Promise { const provider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(resource), resource); + const { providerExtUri } = this.getExtUri(provider); try { @@ -335,7 +339,7 @@ export class FileService extends Disposable implements IFileService { // mkdir recursively as needed if (!stat) { - await this.mkdirp(provider, dirname(resource)); + await this.mkdirp(provider, providerExtUri.dirname(resource)); } // optimization: if the provider has unbuffered write capability and the data @@ -435,7 +439,7 @@ export class FileService extends Disposable implements IFileService { return this.doReadAsFileStream(provider, resource, options); } - private async doReadAsFileStream(provider: IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability | IFileSystemProviderWithFileReadStreamCapability, resource: URI, options?: IReadFileOptions & { preferUnbuffered?: boolean }): Promise { + private async doReadAsFileStream(provider: IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability | IFileSystemProviderWithFileReadStreamCapability, resource: URI, options?: IReadFileOptions & { preferUnbuffered?: boolean; }): Promise { // install a cancellation token that gets cancelled // when any error occurs. this allows us to resolve @@ -450,6 +454,8 @@ export class FileService extends Disposable implements IFileService { throw error; }); + let fileStreamObserver: IReadableStreamObservable | undefined = undefined; + try { // if the etag is provided, we await the result of the validation @@ -460,30 +466,41 @@ export class FileService extends Disposable implements IFileService { await statPromise; } - let fileStreamPromise: Promise; + let fileStream: VSBufferReadableStream | undefined = undefined; // read unbuffered (only if either preferred, or the provider has no buffered read capability) if (!(hasOpenReadWriteCloseCapability(provider) || hasFileReadStreamCapability(provider)) || (hasReadWriteCapability(provider) && options?.preferUnbuffered)) { - fileStreamPromise = this.readFileUnbuffered(provider, resource, options); + fileStream = this.readFileUnbuffered(provider, resource, options); } // read streamed (always prefer over primitive buffered read) else if (hasFileReadStreamCapability(provider)) { - fileStreamPromise = Promise.resolve(this.readFileStreamed(provider, resource, cancellableSource.token, options)); + fileStream = this.readFileStreamed(provider, resource, cancellableSource.token, options); } // read buffered else { - fileStreamPromise = Promise.resolve(this.readFileBuffered(provider, resource, cancellableSource.token, options)); + fileStream = this.readFileBuffered(provider, resource, cancellableSource.token, options); } - const [fileStat, fileStream] = await Promise.all([statPromise, fileStreamPromise]); + // observe the stream for the error case below + fileStreamObserver = observe(fileStream); + + const fileStat = await statPromise; return { ...fileStat, value: fileStream }; } catch (error) { + + // Await the stream to finish so that we exit this method + // in a consistent state with file handles closed + // (https://github.com/microsoft/vscode/issues/114024) + if (fileStreamObserver) { + await fileStreamObserver.errorOrEnd(); + } + throw new FileOperationError(localize('err.read', "Unable to read file '{0}' ({1})", this.resourceForError(resource), ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options); } } @@ -509,23 +526,36 @@ export class FileService extends Disposable implements IFileService { return stream; } - private async readFileUnbuffered(provider: IFileSystemProviderWithFileReadWriteCapability, resource: URI, options?: IReadFileOptions): Promise { - let buffer = await provider.readFile(resource); + private readFileUnbuffered(provider: IFileSystemProviderWithFileReadWriteCapability, resource: URI, options?: IReadFileOptions): VSBufferReadableStream { + const stream = newWriteableStream(data => VSBuffer.concat(data)); - // respect position option - if (options && typeof options.position === 'number') { - buffer = buffer.slice(options.position); - } + // Read the file into the stream async but do not wait for + // this to complete because streams work via events + (async () => { + try { + let buffer = await provider.readFile(resource); - // respect length option - if (options && typeof options.length === 'number') { - buffer = buffer.slice(0, options.length); - } + // respect position option + if (options && typeof options.position === 'number') { + buffer = buffer.slice(options.position); + } - // Throw if file is too large to load - this.validateReadFileLimits(resource, buffer.byteLength, options); + // respect length option + if (options && typeof options.length === 'number') { + buffer = buffer.slice(0, options.length); + } - return bufferToStream(VSBuffer.wrap(buffer)); + // Throw if file is too large to load + this.validateReadFileLimits(resource, buffer.byteLength, options); + + // End stream with data + stream.end(VSBuffer.wrap(buffer)); + } catch (err) { + stream.error(err); + } + })(); + + return stream; } private async validateReadFile(resource: URI, options?: IReadFileOptions): Promise { @@ -634,7 +664,7 @@ export class FileService extends Disposable implements IFileService { } // create parent folders - await this.mkdirp(targetProvider, dirname(target)); + await this.mkdirp(targetProvider, this.getExtUri(targetProvider).providerExtUri.dirname(target)); // copy source => target if (mode === 'copy') { @@ -671,7 +701,6 @@ export class FileService extends Disposable implements IFileService { // across providers: copy to target & delete at source else { await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'copy', overwrite); - await this.del(source, { recursive: true }); return 'copy'; @@ -710,7 +739,7 @@ export class FileService extends Disposable implements IFileService { // create children in target if (Array.isArray(sourceFolder.children)) { await Promise.all(sourceFolder.children.map(async sourceChild => { - const targetChild = joinPath(targetFolder, sourceChild.name); + const targetChild = this.getExtUri(targetProvider).providerExtUri.joinPath(targetFolder, sourceChild.name); if (sourceChild.isDirectory) { return this.doCopyFolder(sourceProvider, await this.resolve(sourceChild.resource), targetProvider, targetChild); } else { @@ -720,21 +749,21 @@ export class FileService extends Disposable implements IFileService { } } - private async doValidateMoveCopy(sourceProvider: IFileSystemProvider, source: URI, targetProvider: IFileSystemProvider, target: URI, mode: 'move' | 'copy', overwrite?: boolean): Promise<{ exists: boolean, isSameResourceWithDifferentPathCase: boolean }> { + private async doValidateMoveCopy(sourceProvider: IFileSystemProvider, source: URI, targetProvider: IFileSystemProvider, target: URI, mode: 'move' | 'copy', overwrite?: boolean): Promise<{ exists: boolean, isSameResourceWithDifferentPathCase: boolean; }> { let isSameResourceWithDifferentPathCase = false; // Check if source is equal or parent to target (requires providers to be the same) if (sourceProvider === targetProvider) { - const { extUri, isPathCaseSensitive } = this.getExtUri(sourceProvider); + const { providerExtUri, isPathCaseSensitive } = this.getExtUri(sourceProvider); if (!isPathCaseSensitive) { - isSameResourceWithDifferentPathCase = extUri.isEqual(source, target); + isSameResourceWithDifferentPathCase = providerExtUri.isEqual(source, target); } if (isSameResourceWithDifferentPathCase && mode === 'copy') { throw new Error(localize('unableToMoveCopyError1', "Unable to copy when source '{0}' is same as target '{1}' with different path case on a case insensitive file system", this.resourceForError(source), this.resourceForError(target))); } - if (!isSameResourceWithDifferentPathCase && extUri.isEqualOrParent(target, source)) { + if (!isSameResourceWithDifferentPathCase && providerExtUri.isEqualOrParent(target, source)) { throw new Error(localize('unableToMoveCopyError2', "Unable to move/copy when source '{0}' is parent of target '{1}'.", this.resourceForError(source), this.resourceForError(target))); } } @@ -751,8 +780,8 @@ export class FileService extends Disposable implements IFileService { // Special case: if the target is a parent of the source, we cannot delete // it as it would delete the source as well. In this case we have to throw if (sourceProvider === targetProvider) { - const { extUri } = this.getExtUri(sourceProvider); - if (extUri.isEqualOrParent(source, target)) { + const { providerExtUri } = this.getExtUri(sourceProvider); + if (providerExtUri.isEqualOrParent(source, target)) { throw new Error(localize('unableToMoveCopyError4', "Unable to move/copy '{0}' into '{1}' since a file would replace the folder it is contained in.", this.resourceForError(source), this.resourceForError(target))); } } @@ -761,11 +790,11 @@ export class FileService extends Disposable implements IFileService { return { exists, isSameResourceWithDifferentPathCase }; } - private getExtUri(provider: IFileSystemProvider): { extUri: IExtUri, isPathCaseSensitive: boolean } { + private getExtUri(provider: IFileSystemProvider): { providerExtUri: IExtUri, isPathCaseSensitive: boolean; } { const isPathCaseSensitive = this.isPathCaseSensitive(provider); return { - extUri: isPathCaseSensitive ? extUri : extUriIgnorePathCase, + providerExtUri: isPathCaseSensitive ? extUri : extUriIgnorePathCase, isPathCaseSensitive }; } @@ -791,8 +820,8 @@ export class FileService extends Disposable implements IFileService { const directoriesToCreate: string[] = []; // mkdir until we reach root - const { extUri } = this.getExtUri(provider); - while (!extUri.isEqual(directory, dirname(directory))) { + const { providerExtUri } = this.getExtUri(provider); + while (!providerExtUri.isEqual(directory, providerExtUri.dirname(directory))) { try { const stat = await provider.stat(directory); if ((stat.type & FileType.Directory) === 0) { @@ -808,16 +837,16 @@ export class FileService extends Disposable implements IFileService { } // Upon error, remember directories that need to be created - directoriesToCreate.push(basename(directory)); + directoriesToCreate.push(providerExtUri.basename(directory)); // Continue up - directory = dirname(directory); + directory = providerExtUri.dirname(directory); } } // Create directories as needed for (let i = directoriesToCreate.length - 1; i >= 0; i--) { - directory = joinPath(directory, directoriesToCreate[i]); + directory = providerExtUri.joinPath(directory, directoriesToCreate[i]); try { await provider.mkdir(directory); @@ -894,11 +923,11 @@ export class FileService extends Disposable implements IFileService { private readonly _onDidFilesChange = this._register(new Emitter()); readonly onDidFilesChange = this._onDidFilesChange.event; - private readonly activeWatchers = new Map(); + private readonly activeWatchers = new Map(); watch(resource: URI, options: IWatchOptions = { recursive: false, excludes: [] }): IDisposable { let watchDisposed = false; - let watchDisposable = toDisposable(() => watchDisposed = true); + let disposeWatch = () => { watchDisposed = true; }; // Watch and wire in disposable which is async but // check if we got disposed meanwhile and forward @@ -906,11 +935,11 @@ export class FileService extends Disposable implements IFileService { if (watchDisposed) { dispose(disposable); } else { - watchDisposable = disposable; + disposeWatch = () => dispose(disposable); } }, error => this.logService.error(error)); - return toDisposable(() => dispose(watchDisposable)); + return toDisposable(() => disposeWatch()); } async doWatch(resource: URI, options: IWatchOptions): Promise { @@ -940,12 +969,12 @@ export class FileService extends Disposable implements IFileService { } private toWatchKey(provider: IFileSystemProvider, resource: URI, options: IWatchOptions): string { - const { extUri } = this.getExtUri(provider); + const { providerExtUri } = this.getExtUri(provider); return [ - extUri.getComparisonKey(resource), // lowercase path if the provider is case insensitive - String(options.recursive), // use recursive: true | false as part of the key - options.excludes.join() // use excludes as part of the key + providerExtUri.getComparisonKey(resource), // lowercase path if the provider is case insensitive + String(options.recursive), // use recursive: true | false as part of the key + options.excludes.join() // use excludes as part of the key ].join(); } @@ -963,8 +992,8 @@ export class FileService extends Disposable implements IFileService { private readonly writeQueues: Map> = new Map(); private ensureWriteQueue(provider: IFileSystemProvider, resource: URI): Queue { - const { extUri } = this.getExtUri(provider); - const queueKey = extUri.getComparisonKey(resource); + const { providerExtUri } = this.getExtUri(provider); + const queueKey = providerExtUri.getComparisonKey(resource); // ensure to never write to the same resource without finishing // the one write. this ensures a write finishes consistently diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 4ae154345..9b91c3dc1 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -278,7 +278,7 @@ export interface IFileSystemProvider { readonly capabilities: FileSystemProviderCapabilities; readonly onDidChangeCapabilities: Event; - readonly onDidErrorOccur?: Event; // TODO@ben remove once file watchers are solid + readonly onDidErrorOccur?: Event; // TODO@bpasero remove once file watchers are solid readonly onDidChangeFile: Event; watch(resource: URI, opts: IWatchOptions): IDisposable; @@ -947,9 +947,9 @@ export function etag(stat: { mtime: number | undefined, size: number | undefined return stat.mtime.toString(29) + stat.size.toString(31); } -export function whenProviderRegistered(file: URI, fileService: IFileService): Promise { +export async function whenProviderRegistered(file: URI, fileService: IFileService): Promise { if (fileService.canHandleResource(URI.from({ scheme: file.scheme }))) { - return Promise.resolve(); + return; } return new Promise(resolve => { diff --git a/src/vs/platform/files/common/io.ts b/src/vs/platform/files/common/io.ts index 141241159..592b51168 100644 --- a/src/vs/platform/files/common/io.ts +++ b/src/vs/platform/files/common/io.ts @@ -58,10 +58,11 @@ async function doReadFileIntoStream(provider: IFileSystemProviderWithOpenRead // open handle through provider const handle = await provider.open(resource, { create: false }); - // Check for cancellation - throwIfCancelled(token); - try { + + // Check for cancellation + throwIfCancelled(token); + let totalBytesRead = 0; let bytesRead = 0; let allowedRemainingBytes = (options && typeof options.length === 'number') ? options.length : undefined; diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index 89408a3f9..75811f569 100644 --- a/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -522,7 +522,7 @@ export class DiskFileSystemProvider extends Disposable implements return this.watchRecursive(resource, opts.excludes); } - return this.watchNonRecursive(resource); // TODO@ben ideally the same watcher can be used in both cases + return this.watchNonRecursive(resource); // TODO@bpasero ideally the same watcher can be used in both cases } private watchRecursive(resource: URI, excludes: string[]): IDisposable { diff --git a/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts b/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts index 51a4b1a65..70c4cfd61 100644 --- a/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts +++ b/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as glob from 'vs/base/common/glob'; -import * as extpath from 'vs/base/common/extpath'; -import * as path from 'vs/base/common/path'; -import * as platform from 'vs/base/common/platform'; -import { IDiskFileChange, normalizeFileChanges, ILogMessage } from 'vs/platform/files/node/watcher/watcher'; import * as nsfw from 'vscode-nsfw'; +import * as glob from 'vs/base/common/glob'; +import { join } from 'vs/base/common/path'; +import { isMacintosh } from 'vs/base/common/platform'; +import { isEqualOrParent } from 'vs/base/common/extpath'; +import { IDiskFileChange, normalizeFileChanges, ILogMessage } from 'vs/platform/files/node/watcher/watcher'; import { IWatcherService, IWatcherRequest } from 'vs/platform/files/node/watcher/nsfw/watcher'; import { ThrottledDelayer } from 'vs/base/common/async'; import { FileChangeType } from 'vs/platform/files/common/files'; @@ -111,7 +111,7 @@ export class NsfwWatcherService extends Disposable implements IWatcherService { // We have to detect this case and massage the events to correct this. let realBasePathDiffers = false; let realBasePathLength = request.path.length; - if (platform.isMacintosh) { + if (isMacintosh) { try { // First check for symbolic link @@ -141,7 +141,7 @@ export class NsfwWatcherService extends Disposable implements IWatcherService { for (const e of events) { // Logging if (this.verboseLogging) { - const logPath = e.action === nsfw.actions.RENAMED ? path.join(e.directory, e.oldFile || '') + ' -> ' + e.newFile : path.join(e.directory, e.file || ''); + const logPath = e.action === nsfw.actions.RENAMED ? join(e.directory, e.oldFile || '') + ' -> ' + e.newFile : join(e.directory, e.file || ''); this.log(`${e.action === nsfw.actions.CREATED ? '[CREATED]' : e.action === nsfw.actions.DELETED ? '[DELETED]' : e.action === nsfw.actions.MODIFIED ? '[CHANGED]' : '[RENAMED]'} ${logPath}`); } @@ -149,20 +149,20 @@ export class NsfwWatcherService extends Disposable implements IWatcherService { let absolutePath: string; if (e.action === nsfw.actions.RENAMED) { // Rename fires when a file's name changes within a single directory - absolutePath = path.join(e.directory, e.oldFile || ''); + absolutePath = join(e.directory, e.oldFile || ''); if (!this.isPathIgnored(absolutePath, this.pathWatchers[request.path].ignored)) { undeliveredFileEvents.push({ type: FileChangeType.DELETED, path: absolutePath }); } else if (this.verboseLogging) { this.log(` >> ignored ${absolutePath}`); } - absolutePath = path.join(e.newDirectory || e.directory, e.newFile || ''); + absolutePath = join(e.newDirectory || e.directory, e.newFile || ''); if (!this.isPathIgnored(absolutePath, this.pathWatchers[request.path].ignored)) { undeliveredFileEvents.push({ type: FileChangeType.ADDED, path: absolutePath }); } else if (this.verboseLogging) { this.log(` >> ignored ${absolutePath}`); } } else { - absolutePath = path.join(e.directory, e.file || ''); + absolutePath = join(e.directory, e.file || ''); if (!this.isPathIgnored(absolutePath, this.pathWatchers[request.path].ignored)) { undeliveredFileEvents.push({ type: nsfwActionToRawChangeType[e.action], @@ -179,7 +179,7 @@ export class NsfwWatcherService extends Disposable implements IWatcherService { const events = undeliveredFileEvents; undeliveredFileEvents = []; - if (platform.isMacintosh) { + if (isMacintosh) { events.forEach(e => { // Mac uses NFD unicode form on disk, but we want NFC @@ -230,7 +230,7 @@ export class NsfwWatcherService extends Disposable implements IWatcherService { // Normalizes a set of root paths by removing any root paths that are // sub-paths of other roots. return roots.filter(r => roots.every(other => { - return !(r.path.length > other.path.length && extpath.isEqualOrParent(r.path, other.path)); + return !(r.path.length > other.path.length && isEqualOrParent(r.path, other.path)); })); } diff --git a/src/vs/platform/files/node/watcher/nsfw/test/nsfwWatcherService.test.ts b/src/vs/platform/files/node/watcher/nsfw/test/nsfwWatcherService.test.ts index 9ad083fc9..0469c3f63 100644 --- a/src/vs/platform/files/node/watcher/nsfw/test/nsfwWatcherService.test.ts +++ b/src/vs/platform/files/node/watcher/nsfw/test/nsfwWatcherService.test.ts @@ -30,28 +30,28 @@ suite('NSFW Watcher Service', async () => { test('should not impacts roots that don\'t overlap', () => { const service = new TestNsfwWatcherService(); if (platform.isWindows) { - assert.deepEqual(service.normalizeRoots(['C:\\a']), ['C:\\a']); - assert.deepEqual(service.normalizeRoots(['C:\\a', 'C:\\b']), ['C:\\a', 'C:\\b']); - assert.deepEqual(service.normalizeRoots(['C:\\a', 'C:\\b', 'C:\\c\\d\\e']), ['C:\\a', 'C:\\b', 'C:\\c\\d\\e']); + assert.deepStrictEqual(service.normalizeRoots(['C:\\a']), ['C:\\a']); + assert.deepStrictEqual(service.normalizeRoots(['C:\\a', 'C:\\b']), ['C:\\a', 'C:\\b']); + assert.deepStrictEqual(service.normalizeRoots(['C:\\a', 'C:\\b', 'C:\\c\\d\\e']), ['C:\\a', 'C:\\b', 'C:\\c\\d\\e']); } else { - assert.deepEqual(service.normalizeRoots(['/a']), ['/a']); - assert.deepEqual(service.normalizeRoots(['/a', '/b']), ['/a', '/b']); - assert.deepEqual(service.normalizeRoots(['/a', '/b', '/c/d/e']), ['/a', '/b', '/c/d/e']); + assert.deepStrictEqual(service.normalizeRoots(['/a']), ['/a']); + assert.deepStrictEqual(service.normalizeRoots(['/a', '/b']), ['/a', '/b']); + assert.deepStrictEqual(service.normalizeRoots(['/a', '/b', '/c/d/e']), ['/a', '/b', '/c/d/e']); } }); test('should remove sub-folders of other roots', () => { const service = new TestNsfwWatcherService(); if (platform.isWindows) { - assert.deepEqual(service.normalizeRoots(['C:\\a', 'C:\\a\\b']), ['C:\\a']); - assert.deepEqual(service.normalizeRoots(['C:\\a', 'C:\\b', 'C:\\a\\b']), ['C:\\a', 'C:\\b']); - assert.deepEqual(service.normalizeRoots(['C:\\b\\a', 'C:\\a', 'C:\\b', 'C:\\a\\b']), ['C:\\a', 'C:\\b']); - assert.deepEqual(service.normalizeRoots(['C:\\a', 'C:\\a\\b', 'C:\\a\\c\\d']), ['C:\\a']); + assert.deepStrictEqual(service.normalizeRoots(['C:\\a', 'C:\\a\\b']), ['C:\\a']); + assert.deepStrictEqual(service.normalizeRoots(['C:\\a', 'C:\\b', 'C:\\a\\b']), ['C:\\a', 'C:\\b']); + assert.deepStrictEqual(service.normalizeRoots(['C:\\b\\a', 'C:\\a', 'C:\\b', 'C:\\a\\b']), ['C:\\a', 'C:\\b']); + assert.deepStrictEqual(service.normalizeRoots(['C:\\a', 'C:\\a\\b', 'C:\\a\\c\\d']), ['C:\\a']); } else { - assert.deepEqual(service.normalizeRoots(['/a', '/a/b']), ['/a']); - assert.deepEqual(service.normalizeRoots(['/a', '/b', '/a/b']), ['/a', '/b']); - assert.deepEqual(service.normalizeRoots(['/b/a', '/a', '/b', '/a/b']), ['/a', '/b']); - assert.deepEqual(service.normalizeRoots(['/a', '/a/b', '/a/c/d']), ['/a']); + assert.deepStrictEqual(service.normalizeRoots(['/a', '/a/b']), ['/a']); + assert.deepStrictEqual(service.normalizeRoots(['/a', '/b', '/a/b']), ['/a', '/b']); + assert.deepStrictEqual(service.normalizeRoots(['/b/a', '/a', '/b', '/a/b']), ['/a', '/b']); + assert.deepStrictEqual(service.normalizeRoots(['/a', '/a/b', '/a/c/d']), ['/a']); } }); }); diff --git a/src/vs/platform/files/node/watcher/nsfw/watcherService.ts b/src/vs/platform/files/node/watcher/nsfw/watcherService.ts index 79fa3ba25..3b0cd433f 100644 --- a/src/vs/platform/files/node/watcher/nsfw/watcherService.ts +++ b/src/vs/platform/files/node/watcher/nsfw/watcherService.ts @@ -39,9 +39,9 @@ export class FileWatcher extends Disposable { serverName: 'File Watcher (nsfw)', args: ['--type=watcherService'], env: { - AMD_ENTRYPOINT: 'vs/platform/files/node/watcher/nsfw/watcherApp', - PIPE_LOGGING: 'true', - VERBOSE_LOGGING: 'true' // transmit console logs from server to client + VSCODE_AMD_ENTRYPOINT: 'vs/platform/files/node/watcher/nsfw/watcherApp', + VSCODE_PIPE_LOGGING: 'true', + VSCODE_VERBOSE_LOGGING: 'true' // transmit console logs from server to client } } )); diff --git a/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts b/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts index 895e5dfa9..25276aa2a 100644 --- a/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts +++ b/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts @@ -6,9 +6,8 @@ import * as chokidar from 'chokidar'; import * as fs from 'fs'; import * as gracefulFs from 'graceful-fs'; -gracefulFs.gracefulify(fs); -import * as extpath from 'vs/base/common/extpath'; import * as glob from 'vs/base/common/glob'; +import { isEqualOrParent } from 'vs/base/common/extpath'; import { FileChangeType } from 'vs/platform/files/common/files'; import { ThrottledDelayer } from 'vs/base/common/async'; import { normalizeNFC } from 'vs/base/common/normalization'; @@ -20,6 +19,8 @@ import { Emitter, Event } from 'vs/base/common/event'; import { equals } from 'vs/base/common/arrays'; import { Disposable } from 'vs/base/common/lifecycle'; +gracefulFs.gracefulify(fs); // enable gracefulFs + process.noAsar = true; // disable ASAR support in watcher process interface IWatcher { @@ -311,7 +312,7 @@ function isIgnored(path: string, requests: ExtendedWatcherRequest[]): boolean { return false; } - if (extpath.isEqualOrParent(path, request.path)) { + if (isEqualOrParent(path, request.path)) { if (!request.parsedPattern) { if (request.excludes && request.excludes.length > 0) { const pattern = `{${request.excludes.join(',')}}`; @@ -343,7 +344,7 @@ export function normalizeRoots(requests: IWatcherRequest[]): { [basePath: string for (const request of requests) { const basePath = request.path; const ignored = (request.excludes || []).sort(); - if (prevRequest && (extpath.isEqualOrParent(basePath, prevRequest.path))) { + if (prevRequest && (isEqualOrParent(basePath, prevRequest.path))) { if (!isEqualIgnore(ignored, prevRequest.excludes)) { result[prevRequest.path].push({ path: basePath, excludes: ignored }); } diff --git a/src/vs/platform/files/node/watcher/unix/test/chockidarWatcherService.test.ts b/src/vs/platform/files/node/watcher/unix/test/chockidarWatcherService.test.ts index 4c780ec6b..8b475de9a 100644 --- a/src/vs/platform/files/node/watcher/unix/test/chockidarWatcherService.test.ts +++ b/src/vs/platform/files/node/watcher/unix/test/chockidarWatcherService.test.ts @@ -20,18 +20,18 @@ suite('Chokidar normalizeRoots', async () => { function assertNormalizedRootPath(inputPaths: string[], expectedPaths: string[]) { const requests = inputPaths.map(path => newRequest(path)); const actual = normalizeRoots(requests); - assert.deepEqual(Object.keys(actual).sort(), expectedPaths); + assert.deepStrictEqual(Object.keys(actual).sort(), expectedPaths); } function assertNormalizedRequests(inputRequests: IWatcherRequest[], expectedRequests: { [path: string]: IWatcherRequest[] }) { const actual = normalizeRoots(inputRequests); const actualPath = Object.keys(actual).sort(); const expectedPaths = Object.keys(expectedRequests).sort(); - assert.deepEqual(actualPath, expectedPaths); + assert.deepStrictEqual(actualPath, expectedPaths); for (let path of actualPath) { let a = expectedRequests[path].sort((r1, r2) => r1.path.localeCompare(r2.path)); let e = expectedRequests[path].sort((r1, r2) => r1.path.localeCompare(r2.path)); - assert.deepEqual(a, e); + assert.deepStrictEqual(a, e); } } diff --git a/src/vs/platform/files/node/watcher/unix/watcherService.ts b/src/vs/platform/files/node/watcher/unix/watcherService.ts index e561cc8e5..75f1bcf90 100644 --- a/src/vs/platform/files/node/watcher/unix/watcherService.ts +++ b/src/vs/platform/files/node/watcher/unix/watcherService.ts @@ -40,9 +40,9 @@ export class FileWatcher extends Disposable { serverName: 'File Watcher (chokidar)', args: ['--type=watcherService'], env: { - AMD_ENTRYPOINT: 'vs/platform/files/node/watcher/unix/watcherApp', - PIPE_LOGGING: 'true', - VERBOSE_LOGGING: 'true' // transmit console logs from server to client + VSCODE_AMD_ENTRYPOINT: 'vs/platform/files/node/watcher/unix/watcherApp', + VSCODE_PIPE_LOGGING: 'true', + VSCODE_VERBOSE_LOGGING: 'true' // transmit console logs from server to client } } )); diff --git a/src/vs/platform/files/test/browser/fileService.test.ts b/src/vs/platform/files/test/browser/fileService.test.ts index edc32f0a3..e8d53c1cf 100644 --- a/src/vs/platform/files/test/browser/fileService.test.ts +++ b/src/vs/platform/files/test/browser/fileService.test.ts @@ -19,7 +19,7 @@ suite('File Service', () => { const resource = URI.parse('test://foo/bar'); const provider = new NullFileSystemProvider(); - assert.equal(service.canHandleResource(resource), false); + assert.strictEqual(service.canHandleResource(resource), false); const registrations: IFileSystemProviderRegistrationEvent[] = []; service.onDidChangeFileSystemProviderRegistrations(e => { @@ -47,33 +47,33 @@ suite('File Service', () => { await service.activateProvider('test'); - assert.equal(service.canHandleResource(resource), true); + assert.strictEqual(service.canHandleResource(resource), true); - assert.equal(registrations.length, 1); - assert.equal(registrations[0].scheme, 'test'); - assert.equal(registrations[0].added, true); + assert.strictEqual(registrations.length, 1); + assert.strictEqual(registrations[0].scheme, 'test'); + assert.strictEqual(registrations[0].added, true); assert.ok(registrationDisposable); - assert.equal(capabilityChanges.length, 0); + assert.strictEqual(capabilityChanges.length, 0); provider.setCapabilities(FileSystemProviderCapabilities.FileFolderCopy); - assert.equal(capabilityChanges.length, 1); + assert.strictEqual(capabilityChanges.length, 1); provider.setCapabilities(FileSystemProviderCapabilities.Readonly); - assert.equal(capabilityChanges.length, 2); + assert.strictEqual(capabilityChanges.length, 2); await service.activateProvider('test'); - assert.equal(callCount, 2); // activation is called again + assert.strictEqual(callCount, 2); // activation is called again - assert.equal(service.hasCapability(resource, FileSystemProviderCapabilities.Readonly), true); - assert.equal(service.hasCapability(resource, FileSystemProviderCapabilities.FileOpenReadWriteClose), false); + assert.strictEqual(service.hasCapability(resource, FileSystemProviderCapabilities.Readonly), true); + assert.strictEqual(service.hasCapability(resource, FileSystemProviderCapabilities.FileOpenReadWriteClose), false); registrationDisposable!.dispose(); - assert.equal(service.canHandleResource(resource), false); + assert.strictEqual(service.canHandleResource(resource), false); - assert.equal(registrations.length, 2); - assert.equal(registrations[1].scheme, 'test'); - assert.equal(registrations[1].added, false); + assert.strictEqual(registrations.length, 2); + assert.strictEqual(registrations[1].scheme, 'test'); + assert.strictEqual(registrations[1].added, false); }); test('watch', async () => { @@ -91,9 +91,9 @@ suite('File Service', () => { const watcher1Disposable = service.watch(resource1); await timeout(0); // service.watch() is async - assert.equal(disposeCounter, 0); + assert.strictEqual(disposeCounter, 0); watcher1Disposable.dispose(); - assert.equal(disposeCounter, 1); + assert.strictEqual(disposeCounter, 1); disposeCounter = 0; const resource2 = URI.parse('test://foo/bar2'); @@ -102,13 +102,13 @@ suite('File Service', () => { const watcher2Disposable3 = service.watch(resource2); await timeout(0); // service.watch() is async - assert.equal(disposeCounter, 0); + assert.strictEqual(disposeCounter, 0); watcher2Disposable1.dispose(); - assert.equal(disposeCounter, 0); + assert.strictEqual(disposeCounter, 0); watcher2Disposable2.dispose(); - assert.equal(disposeCounter, 0); + assert.strictEqual(disposeCounter, 0); watcher2Disposable3.dispose(); - assert.equal(disposeCounter, 1); + assert.strictEqual(disposeCounter, 1); disposeCounter = 0; const resource3 = URI.parse('test://foo/bar3'); @@ -116,10 +116,10 @@ suite('File Service', () => { const watcher3Disposable2 = service.watch(resource3, { recursive: true, excludes: [] }); await timeout(0); // service.watch() is async - assert.equal(disposeCounter, 0); + assert.strictEqual(disposeCounter, 0); watcher3Disposable1.dispose(); - assert.equal(disposeCounter, 1); + assert.strictEqual(disposeCounter, 1); watcher3Disposable2.dispose(); - assert.equal(disposeCounter, 2); + assert.strictEqual(disposeCounter, 2); }); }); diff --git a/src/vs/platform/files/test/browser/indexedDBFileService.test.ts b/src/vs/platform/files/test/browser/indexedDBFileService.test.ts index abb3034a3..67ccfd9a6 100644 --- a/src/vs/platform/files/test/browser/indexedDBFileService.test.ts +++ b/src/vs/platform/files/test/browser/indexedDBFileService.test.ts @@ -6,20 +6,20 @@ import * as assert from 'assert'; import { FileService } from 'vs/platform/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; -import { posix } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; -import { FileOperation, FileOperationEvent } from 'vs/platform/files/common/files'; +import { FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FileSystemProviderErrorCode, FileType, IFileStatWithMetadata } from 'vs/platform/files/common/files'; import { NullLogService } from 'vs/platform/log/common/log'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IIndexedDBFileSystemProvider, IndexedDB, INDEXEDDB_LOGS_OBJECT_STORE, INDEXEDDB_USERDATA_OBJECT_STORE } from 'vs/platform/files/browser/indexedDBFileSystemProvider'; import { assertIsDefined } from 'vs/base/common/types'; - -// FileService doesn't work with \ leading a path. Windows join swaps /'s for \'s, -// making /-style absolute paths fail isAbsolute checks. -const join = posix.join; +import { basename, joinPath } from 'vs/base/common/resources'; +import { bufferToReadable, bufferToStream, VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; suite('IndexedDB File Service', function () { + // IDB sometimes under pressure in build machines. + this.retries(3); + const logSchema = 'logs'; let service: FileService; @@ -27,12 +27,43 @@ suite('IndexedDB File Service', function () { let userdataFileProvider: IIndexedDBFileSystemProvider; const testDir = '/'; - const makeLogfileURI = (path: string) => URI.from({ scheme: logSchema, path }); - const makeUserdataURI = (path: string) => URI.from({ scheme: Schemas.userData, path }); + const logfileURIFromPaths = (paths: string[]) => joinPath(URI.from({ scheme: logSchema, path: testDir }), ...paths); + const userdataURIFromPaths = (paths: readonly string[]) => joinPath(URI.from({ scheme: Schemas.userData, path: testDir }), ...paths); const disposables = new DisposableStore(); - setup(async () => { + const initFixtures = async () => { + await Promise.all( + [['fixtures', 'resolver', 'examples'], + ['fixtures', 'resolver', 'other', 'deep'], + ['fixtures', 'service', 'deep'], + ['batched']] + .map(path => userdataURIFromPaths(path)) + .map(uri => service.createFolder(uri))); + await Promise.all( + ([ + [['fixtures', 'resolver', 'examples', 'company.js'], 'class company {}'], + [['fixtures', 'resolver', 'examples', 'conway.js'], 'export function conway() {}'], + [['fixtures', 'resolver', 'examples', 'employee.js'], 'export const employee = "jax"'], + [['fixtures', 'resolver', 'examples', 'small.js'], ''], + [['fixtures', 'resolver', 'other', 'deep', 'company.js'], 'class company {}'], + [['fixtures', 'resolver', 'other', 'deep', 'conway.js'], 'export function conway() {}'], + [['fixtures', 'resolver', 'other', 'deep', 'employee.js'], 'export const employee = "jax"'], + [['fixtures', 'resolver', 'other', 'deep', 'small.js'], ''], + [['fixtures', 'resolver', 'index.html'], '

    p

    '], + [['fixtures', 'resolver', 'site.css'], '.p {color: red;}'], + [['fixtures', 'service', 'deep', 'company.js'], 'class company {}'], + [['fixtures', 'service', 'deep', 'conway.js'], 'export function conway() {}'], + [['fixtures', 'service', 'deep', 'employee.js'], 'export const employee = "jax"'], + [['fixtures', 'service', 'deep', 'small.js'], ''], + [['fixtures', 'service', 'binary.txt'], '

    p

    '], + ] as const) + .map(([path, contents]) => [userdataURIFromPaths(path), contents] as const) + .map(([uri, contents]) => service.createFile(uri, VSBuffer.fromString(contents))) + ); + }; + + const reload = async () => { const logService = new NullLogService(); service = new FileService(logService); @@ -45,33 +76,302 @@ suite('IndexedDB File Service', function () { userdataFileProvider = assertIsDefined(await new IndexedDB().createFileSystemProvider(logSchema, INDEXEDDB_USERDATA_OBJECT_STORE)); disposables.add(service.registerProvider(Schemas.userData, userdataFileProvider)); disposables.add(userdataFileProvider); + }; + + setup(async () => { + await reload(); }); teardown(async () => { disposables.clear(); + await logFileProvider.delete(logfileURIFromPaths([]), { recursive: true, useTrash: false }); + await userdataFileProvider.delete(userdataURIFromPaths([]), { recursive: true, useTrash: false }); + }); - await logFileProvider.delete(makeLogfileURI(testDir), { recursive: true, useTrash: false }); - await userdataFileProvider.delete(makeUserdataURI(testDir), { recursive: true, useTrash: false }); + test('root is always present', async () => { + assert.strictEqual((await userdataFileProvider.stat(userdataURIFromPaths([]))).type, FileType.Directory); + await userdataFileProvider.delete(userdataURIFromPaths([]), { recursive: true, useTrash: false }); + assert.strictEqual((await userdataFileProvider.stat(userdataURIFromPaths([]))).type, FileType.Directory); }); test('createFolder', async () => { let event: FileOperationEvent | undefined; disposables.add(service.onDidRunOperation(e => event = e)); - const parent = await service.resolve(makeUserdataURI(testDir)); + const parent = await service.resolve(userdataURIFromPaths([])); + const newFolderResource = joinPath(parent.resource, 'newFolder'); - const newFolderResource = makeUserdataURI(join(parent.resource.path, 'newFolder')); - - assert.equal((await userdataFileProvider.readdir(parent.resource)).length, 0); + assert.strictEqual((await userdataFileProvider.readdir(parent.resource)).length, 0); const newFolder = await service.createFolder(newFolderResource); - assert.equal(newFolder.name, 'newFolder'); - // Invalid.. dirs dont exist in our IDBFSB. - // assert.equal((await userdataFileProvider.readdir(parent.resource)).length, 1); + assert.strictEqual(newFolder.name, 'newFolder'); + assert.strictEqual((await userdataFileProvider.readdir(parent.resource)).length, 1); + assert.strictEqual((await userdataFileProvider.stat(newFolderResource)).type, FileType.Directory); assert.ok(event); - assert.equal(event!.resource.path, newFolderResource.path); - assert.equal(event!.operation, FileOperation.CREATE); - assert.equal(event!.target!.resource.path, newFolderResource.path); - assert.equal(event!.target!.isDirectory, true); + assert.strictEqual(event!.resource.path, newFolderResource.path); + assert.strictEqual(event!.operation, FileOperation.CREATE); + assert.strictEqual(event!.target!.resource.path, newFolderResource.path); + assert.strictEqual(event!.target!.isDirectory, true); + }); + + test('createFolder: creating multiple folders at once', async () => { + let event: FileOperationEvent; + disposables.add(service.onDidRunOperation(e => event = e)); + + const multiFolderPaths = ['a', 'couple', 'of', 'folders']; + const parent = await service.resolve(userdataURIFromPaths([])); + const newFolderResource = joinPath(parent.resource, ...multiFolderPaths); + + const newFolder = await service.createFolder(newFolderResource); + + const lastFolderName = multiFolderPaths[multiFolderPaths.length - 1]; + assert.strictEqual(newFolder.name, lastFolderName); + assert.strictEqual((await userdataFileProvider.stat(newFolderResource)).type, FileType.Directory); + + assert.ok(event!); + assert.strictEqual(event!.resource.path, newFolderResource.path); + assert.strictEqual(event!.operation, FileOperation.CREATE); + assert.strictEqual(event!.target!.resource.path, newFolderResource.path); + assert.strictEqual(event!.target!.isDirectory, true); + }); + + test('exists', async () => { + let exists = await service.exists(userdataURIFromPaths([])); + assert.strictEqual(exists, true); + + exists = await service.exists(userdataURIFromPaths(['hello'])); + assert.strictEqual(exists, false); + }); + + test('resolve - file', async () => { + await initFixtures(); + + const resource = userdataURIFromPaths(['fixtures', 'resolver', 'index.html']); + const resolved = await service.resolve(resource); + + assert.strictEqual(resolved.name, 'index.html'); + assert.strictEqual(resolved.isFile, true); + assert.strictEqual(resolved.isDirectory, false); + assert.strictEqual(resolved.isSymbolicLink, false); + assert.strictEqual(resolved.resource.toString(), resource.toString()); + assert.strictEqual(resolved.children, undefined); + assert.ok(resolved.size! > 0); + }); + + test('resolve - directory', async () => { + await initFixtures(); + + const testsElements = ['examples', 'other', 'index.html', 'site.css']; + + const resource = userdataURIFromPaths(['fixtures', 'resolver']); + const result = await service.resolve(resource); + + assert.ok(result); + assert.strictEqual(result.resource.toString(), resource.toString()); + assert.strictEqual(result.name, 'resolver'); + assert.ok(result.children); + assert.ok(result.children!.length > 0); + assert.ok(result!.isDirectory); + assert.strictEqual(result.children!.length, testsElements.length); + + assert.ok(result.children!.every(entry => { + return testsElements.some(name => { + return basename(entry.resource) === name; + }); + })); + + result.children!.forEach(value => { + assert.ok(basename(value.resource)); + if (['examples', 'other'].indexOf(basename(value.resource)) >= 0) { + assert.ok(value.isDirectory); + assert.strictEqual(value.mtime, undefined); + assert.strictEqual(value.ctime, undefined); + } else if (basename(value.resource) === 'index.html') { + assert.ok(!value.isDirectory); + assert.ok(!value.children); + assert.strictEqual(value.mtime, undefined); + assert.strictEqual(value.ctime, undefined); + } else if (basename(value.resource) === 'site.css') { + assert.ok(!value.isDirectory); + assert.ok(!value.children); + assert.strictEqual(value.mtime, undefined); + assert.strictEqual(value.ctime, undefined); + } else { + assert.ok(!'Unexpected value ' + basename(value.resource)); + } + }); + }); + + test('createFile', async () => { + return assertCreateFile(contents => VSBuffer.fromString(contents)); + }); + + test('createFile (readable)', async () => { + return assertCreateFile(contents => bufferToReadable(VSBuffer.fromString(contents))); + }); + + test('createFile (stream)', async () => { + return assertCreateFile(contents => bufferToStream(VSBuffer.fromString(contents))); + }); + + async function assertCreateFile(converter: (content: string) => VSBuffer | VSBufferReadable | VSBufferReadableStream): Promise { + let event: FileOperationEvent; + disposables.add(service.onDidRunOperation(e => event = e)); + + const contents = 'Hello World'; + const resource = userdataURIFromPaths(['test.txt']); + + assert.strictEqual(await service.canCreateFile(resource), true); + const fileStat = await service.createFile(resource, converter(contents)); + assert.strictEqual(fileStat.name, 'test.txt'); + assert.strictEqual((await userdataFileProvider.stat(fileStat.resource)).type, FileType.File); + assert.strictEqual(new TextDecoder().decode(await userdataFileProvider.readFile(fileStat.resource)), contents); + + assert.ok(event!); + assert.strictEqual(event!.resource.path, resource.path); + assert.strictEqual(event!.operation, FileOperation.CREATE); + assert.strictEqual(event!.target!.resource.path, resource.path); + } + + const makeBatchTester = (size: number, name: string) => { + const batch = Array.from({ length: 50 }).map((_, i) => ({ contents: `Hello${i}`, resource: userdataURIFromPaths(['batched', name, `Hello${i}.txt`]) })); + let stats: Promise | undefined = undefined; + return { + async create() { + return stats = Promise.all(batch.map(entry => service.createFile(entry.resource, VSBuffer.fromString(entry.contents)))); + }, + async assertContentsCorrect() { + await Promise.all(batch.map(async (entry, i) => { + if (!stats) { throw Error('read called before create'); } + const stat = (await stats!)[i]; + assert.strictEqual(stat.name, `Hello${i}.txt`); + assert.strictEqual((await userdataFileProvider.stat(stat.resource)).type, FileType.File); + assert.strictEqual(new TextDecoder().decode(await userdataFileProvider.readFile(stat.resource)), entry.contents); + })); + }, + async delete() { + await service.del(userdataURIFromPaths(['batched', name]), { recursive: true, useTrash: false }); + }, + async assertContentsEmpty() { + if (!stats) { throw Error('assertContentsEmpty called before create'); } + await Promise.all((await stats).map(async stat => { + const newStat = await userdataFileProvider.stat(stat.resource).catch(e => e.code); + assert.strictEqual(newStat, FileSystemProviderErrorCode.FileNotFound); + })); + } + }; + }; + + test('createFile (small batch)', async () => { + const tester = makeBatchTester(50, 'smallBatch'); + await tester.create(); + await tester.assertContentsCorrect(); + await tester.delete(); + await tester.assertContentsEmpty(); + }); + + test('createFile (mixed parallel/sequential)', async () => { + const single1 = makeBatchTester(1, 'single1'); + const single2 = makeBatchTester(1, 'single2'); + + const batch1 = makeBatchTester(20, 'batch1'); + const batch2 = makeBatchTester(20, 'batch2'); + + single1.create(); + batch1.create(); + await Promise.all([single1.assertContentsCorrect(), batch1.assertContentsCorrect()]); + single2.create(); + batch2.create(); + await Promise.all([single2.assertContentsCorrect(), batch2.assertContentsCorrect()]); + await Promise.all([single1.assertContentsCorrect(), batch1.assertContentsCorrect()]); + + await (Promise.all([single1.delete(), single2.delete(), batch1.delete(), batch2.delete()])); + await (Promise.all([single1.assertContentsEmpty(), single2.assertContentsEmpty(), batch1.assertContentsEmpty(), batch2.assertContentsEmpty()])); + }); + + test('deleteFile', async () => { + await initFixtures(); + + let event: FileOperationEvent; + disposables.add(service.onDidRunOperation(e => event = e)); + + const anotherResource = userdataURIFromPaths(['fixtures', 'service', 'deep', 'company.js']); + const resource = userdataURIFromPaths(['fixtures', 'service', 'deep', 'conway.js']); + const source = await service.resolve(resource); + + assert.strictEqual(await service.canDelete(source.resource, { useTrash: false }), true); + await service.del(source.resource, { useTrash: false }); + + assert.strictEqual(await service.exists(source.resource), false); + assert.strictEqual(await service.exists(anotherResource), true); + + assert.ok(event!); + assert.strictEqual(event!.resource.path, resource.path); + assert.strictEqual(event!.operation, FileOperation.DELETE); + + { + let error: Error | undefined = undefined; + try { + await service.del(source.resource, { useTrash: false }); + } catch (e) { + error = e; + } + + assert.ok(error); + assert.strictEqual((error).fileOperationResult, FileOperationResult.FILE_NOT_FOUND); + } + await reload(); + { + let error: Error | undefined = undefined; + try { + await service.del(source.resource, { useTrash: false }); + } catch (e) { + error = e; + } + + assert.ok(error); + assert.strictEqual((error).fileOperationResult, FileOperationResult.FILE_NOT_FOUND); + } + }); + + test('deleteFolder (recursive)', async () => { + await initFixtures(); + let event: FileOperationEvent; + disposables.add(service.onDidRunOperation(e => event = e)); + + const resource = userdataURIFromPaths(['fixtures', 'service', 'deep']); + const subResource1 = userdataURIFromPaths(['fixtures', 'service', 'deep', 'company.js']); + const subResource2 = userdataURIFromPaths(['fixtures', 'service', 'deep', 'conway.js']); + assert.strictEqual(await service.exists(subResource1), true); + assert.strictEqual(await service.exists(subResource2), true); + + const source = await service.resolve(resource); + + assert.strictEqual(await service.canDelete(source.resource, { recursive: true, useTrash: false }), true); + await service.del(source.resource, { recursive: true, useTrash: false }); + + assert.strictEqual(await service.exists(source.resource), false); + assert.strictEqual(await service.exists(subResource1), false); + assert.strictEqual(await service.exists(subResource2), false); + assert.ok(event!); + assert.strictEqual(event!.resource.fsPath, resource.fsPath); + assert.strictEqual(event!.operation, FileOperation.DELETE); + }); + + + test('deleteFolder (non recursive)', async () => { + await initFixtures(); + const resource = userdataURIFromPaths(['fixtures', 'service', 'deep']); + const source = await service.resolve(resource); + + assert.ok((await service.canDelete(source.resource)) instanceof Error); + + let error; + try { + await service.del(source.resource); + } catch (e) { + error = e; + } + assert.ok(error); }); }); diff --git a/src/vs/platform/files/test/electron-browser/diskFileService.test.ts b/src/vs/platform/files/test/electron-browser/diskFileService.test.ts index 4f7a0b94c..4c8f13fc8 100644 --- a/src/vs/platform/files/test/electron-browser/diskFileService.test.ts +++ b/src/vs/platform/files/test/electron-browser/diskFileService.test.ts @@ -8,11 +8,10 @@ import { tmpdir } from 'os'; import { FileService } from 'vs/platform/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; -import { getRandomTestPath } from 'vs/base/test/node/testUtils'; -import { generateUuid } from 'vs/base/common/uuid'; +import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; import { join, basename, dirname, posix } from 'vs/base/common/path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; -import { copy, rimraf, symlink, RimRafMode, rimrafSync } from 'vs/base/node/pfs'; +import { copy, rimraf, symlink, rimrafSync } from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; import { existsSync, statSync, readdirSync, readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync, createReadStream } from 'fs'; import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities, FileChangeType, IFileChange, FileChangesEvent, FileOperationError, etag, IStat, IFileStatWithMetadata } from 'vs/platform/files/common/files'; @@ -118,26 +117,18 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider { } } -suite('Disk File Service', function () { +flakySuite('Disk File Service', function () { - const parentDir = getRandomTestPath(tmpdir(), 'vsctests', 'diskfileservice'); const testSchema = 'test'; let service: FileService; let fileProvider: TestDiskFileSystemProvider; let testProvider: TestDiskFileSystemProvider; + let testDir: string; const disposables = new DisposableStore(); - // Given issues such as https://github.com/microsoft/vscode/issues/78602 - // and https://github.com/microsoft/vscode/issues/92334 we see random test - // failures when accessing the native file system. To diagnose further, we - // retry node.js file access tests up to 3 times to rule out any random disk - // issue and increase the timeout. - this.retries(3); - this.timeout(1000 * 10); - setup(async () => { const logService = new NullLogService(); @@ -152,17 +143,17 @@ suite('Disk File Service', function () { disposables.add(service.registerProvider(testSchema, testProvider)); disposables.add(testProvider); - const id = generateUuid(); - testDir = join(parentDir, id); + testDir = getRandomTestPath(tmpdir(), 'vsctests', 'diskfileservice'); + const sourceDir = getPathFromAmdModule(require, './fixtures/service'); await copy(sourceDir, testDir); }); - teardown(async () => { + teardown(() => { disposables.clear(); - await rimraf(parentDir, RimRafMode.MOVE); + return rimraf(testDir); }); test('createFolder', async () => { @@ -175,14 +166,14 @@ suite('Disk File Service', function () { const newFolder = await service.createFolder(newFolderResource); - assert.equal(newFolder.name, 'newFolder'); - assert.equal(existsSync(newFolder.resource.fsPath), true); + assert.strictEqual(newFolder.name, 'newFolder'); + assert.strictEqual(existsSync(newFolder.resource.fsPath), true); assert.ok(event); - assert.equal(event!.resource.fsPath, newFolderResource.fsPath); - assert.equal(event!.operation, FileOperation.CREATE); - assert.equal(event!.target!.resource.fsPath, newFolderResource.fsPath); - assert.equal(event!.target!.isDirectory, true); + assert.strictEqual(event!.resource.fsPath, newFolderResource.fsPath); + assert.strictEqual(event!.operation, FileOperation.CREATE); + assert.strictEqual(event!.target!.resource.fsPath, newFolderResource.fsPath); + assert.strictEqual(event!.target!.isDirectory, true); }); test('createFolder: creating multiple folders at once', async () => { @@ -197,34 +188,34 @@ suite('Disk File Service', function () { const newFolder = await service.createFolder(newFolderResource); const lastFolderName = multiFolderPaths[multiFolderPaths.length - 1]; - assert.equal(newFolder.name, lastFolderName); - assert.equal(existsSync(newFolder.resource.fsPath), true); + assert.strictEqual(newFolder.name, lastFolderName); + assert.strictEqual(existsSync(newFolder.resource.fsPath), true); assert.ok(event!); - assert.equal(event!.resource.fsPath, newFolderResource.fsPath); - assert.equal(event!.operation, FileOperation.CREATE); - assert.equal(event!.target!.resource.fsPath, newFolderResource.fsPath); - assert.equal(event!.target!.isDirectory, true); + assert.strictEqual(event!.resource.fsPath, newFolderResource.fsPath); + assert.strictEqual(event!.operation, FileOperation.CREATE); + assert.strictEqual(event!.target!.resource.fsPath, newFolderResource.fsPath); + assert.strictEqual(event!.target!.isDirectory, true); }); test('exists', async () => { let exists = await service.exists(URI.file(testDir)); - assert.equal(exists, true); + assert.strictEqual(exists, true); exists = await service.exists(URI.file(testDir + 'something')); - assert.equal(exists, false); + assert.strictEqual(exists, false); }); test('resolve - file', async () => { const resource = URI.file(getPathFromAmdModule(require, './fixtures/resolver/index.html')); const resolved = await service.resolve(resource); - assert.equal(resolved.name, 'index.html'); - assert.equal(resolved.isFile, true); - assert.equal(resolved.isDirectory, false); - assert.equal(resolved.isSymbolicLink, false); - assert.equal(resolved.resource.toString(), resource.toString()); - assert.equal(resolved.children, undefined); + assert.strictEqual(resolved.name, 'index.html'); + assert.strictEqual(resolved.isFile, true); + assert.strictEqual(resolved.isDirectory, false); + assert.strictEqual(resolved.isSymbolicLink, false); + assert.strictEqual(resolved.resource.toString(), resource.toString()); + assert.strictEqual(resolved.children, undefined); assert.ok(resolved.mtime! > 0); assert.ok(resolved.ctime! > 0); assert.ok(resolved.size! > 0); @@ -237,14 +228,14 @@ suite('Disk File Service', function () { const result = await service.resolve(resource); assert.ok(result); - assert.equal(result.resource.toString(), resource.toString()); - assert.equal(result.name, 'resolver'); + assert.strictEqual(result.resource.toString(), resource.toString()); + assert.strictEqual(result.name, 'resolver'); assert.ok(result.children); assert.ok(result.children!.length > 0); assert.ok(result!.isDirectory); assert.ok(result.mtime! > 0); assert.ok(result.ctime! > 0); - assert.equal(result.children!.length, testsElements.length); + assert.strictEqual(result.children!.length, testsElements.length); assert.ok(result.children!.every(entry => { return testsElements.some(name => { @@ -256,18 +247,18 @@ suite('Disk File Service', function () { assert.ok(basename(value.resource.fsPath)); if (['examples', 'other'].indexOf(basename(value.resource.fsPath)) >= 0) { assert.ok(value.isDirectory); - assert.equal(value.mtime, undefined); - assert.equal(value.ctime, undefined); + assert.strictEqual(value.mtime, undefined); + assert.strictEqual(value.ctime, undefined); } else if (basename(value.resource.fsPath) === 'index.html') { assert.ok(!value.isDirectory); assert.ok(!value.children); - assert.equal(value.mtime, undefined); - assert.equal(value.ctime, undefined); + assert.strictEqual(value.mtime, undefined); + assert.strictEqual(value.ctime, undefined); } else if (basename(value.resource.fsPath) === 'site.css') { assert.ok(!value.isDirectory); assert.ok(!value.children); - assert.equal(value.mtime, undefined); - assert.equal(value.ctime, undefined); + assert.strictEqual(value.mtime, undefined); + assert.strictEqual(value.ctime, undefined); } else { assert.ok(!'Unexpected value ' + basename(value.resource.fsPath)); } @@ -280,13 +271,13 @@ suite('Disk File Service', function () { const result = await service.resolve(URI.file(getPathFromAmdModule(require, './fixtures/resolver')), { resolveMetadata: true }); assert.ok(result); - assert.equal(result.name, 'resolver'); + assert.strictEqual(result.name, 'resolver'); assert.ok(result.children); assert.ok(result.children!.length > 0); assert.ok(result!.isDirectory); assert.ok(result.mtime! > 0); assert.ok(result.ctime! > 0); - assert.equal(result.children!.length, testsElements.length); + assert.strictEqual(result.children!.length, testsElements.length); assert.ok(result.children!.every(entry => { return testsElements.some(name => { @@ -320,10 +311,10 @@ suite('Disk File Service', function () { test('resolve - directory with resolveTo', async () => { const resolved = await service.resolve(URI.file(testDir), { resolveTo: [URI.file(join(testDir, 'deep'))] }); - assert.equal(resolved.children!.length, 8); + assert.strictEqual(resolved.children!.length, 8); const deep = (getByName(resolved, 'deep')!); - assert.equal(deep.children!.length, 4); + assert.strictEqual(deep.children!.length, 4); }); test('resolve - directory - resolveTo single directory', async () => { @@ -336,7 +327,7 @@ suite('Disk File Service', function () { assert.ok(result.isDirectory); const children = result.children!; - assert.equal(children.length, 4); + assert.strictEqual(children.length, 4); const other = getByName(result, 'other'); assert.ok(other); @@ -345,7 +336,7 @@ suite('Disk File Service', function () { const deep = getByName(other!, 'deep'); assert.ok(deep); assert.ok(deep!.children!.length > 0); - assert.equal(deep!.children!.length, 4); + assert.strictEqual(deep!.children!.length, 4); }); test('resolve directory - resolveTo multiple directories', async () => { @@ -363,7 +354,7 @@ suite('Disk File Service', function () { assert.ok(result.isDirectory); const children = result.children!; - assert.equal(children.length, 4); + assert.strictEqual(children.length, 4); const other = getByName(result, 'other'); assert.ok(other); @@ -372,12 +363,12 @@ suite('Disk File Service', function () { const deep = getByName(other!, 'deep'); assert.ok(deep); assert.ok(deep!.children!.length > 0); - assert.equal(deep!.children!.length, 4); + assert.strictEqual(deep!.children!.length, 4); const examples = getByName(result, 'examples'); assert.ok(examples); assert.ok(examples!.children!.length > 0); - assert.equal(examples!.children!.length, 4); + assert.strictEqual(examples!.children!.length, 4); }); test('resolve directory - resolveSingleChildFolders', async () => { @@ -390,12 +381,12 @@ suite('Disk File Service', function () { assert.ok(result.isDirectory); const children = result.children!; - assert.equal(children.length, 1); + assert.strictEqual(children.length, 1); let deep = getByName(result, 'deep'); assert.ok(deep); assert.ok(deep!.children!.length > 0); - assert.equal(deep!.children!.length, 4); + assert.strictEqual(deep!.children!.length, 4); }); test('resolves', async () => { @@ -405,41 +396,41 @@ suite('Disk File Service', function () { ]); const r1 = (res[0].stat!); - assert.equal(r1.children!.length, 8); + assert.strictEqual(r1.children!.length, 8); const deep = (getByName(r1, 'deep')!); - assert.equal(deep.children!.length, 4); + assert.strictEqual(deep.children!.length, 4); const r2 = (res[1].stat!); - assert.equal(r2.children!.length, 4); - assert.equal(r2.name, 'deep'); + assert.strictEqual(r2.children!.length, 4); + assert.strictEqual(r2.name, 'deep'); }); - (isWindows /* not reliable on windows */ ? test.skip : test)('resolve - folder symbolic link', async () => { + test('resolve - folder symbolic link', async () => { const link = URI.file(join(testDir, 'deep-link')); - await symlink(join(testDir, 'deep'), link.fsPath); + await symlink(join(testDir, 'deep'), link.fsPath, 'junction'); const resolved = await service.resolve(link); - assert.equal(resolved.children!.length, 4); - assert.equal(resolved.isDirectory, true); - assert.equal(resolved.isSymbolicLink, true); + assert.strictEqual(resolved.children!.length, 4); + assert.strictEqual(resolved.isDirectory, true); + assert.strictEqual(resolved.isSymbolicLink, true); }); - (isWindows /* not reliable on windows */ ? test.skip : test)('resolve - file symbolic link', async () => { + (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 symlink(join(testDir, 'lorem.txt'), link.fsPath); const resolved = await service.resolve(link); - assert.equal(resolved.isDirectory, false); - assert.equal(resolved.isSymbolicLink, true); + assert.strictEqual(resolved.isDirectory, false); + assert.strictEqual(resolved.isSymbolicLink, true); }); - (isWindows /* not reliable on windows */ ? test.skip : test)('resolve - symbolic link pointing to non-existing file does not break', async () => { - await symlink(join(testDir, 'foo'), join(testDir, 'bar')); + test('resolve - symbolic link pointing to non-existing file does not break', async () => { + await symlink(join(testDir, 'foo'), join(testDir, 'bar'), 'junction'); const resolved = await service.resolve(URI.file(testDir)); - assert.equal(resolved.isDirectory, true); - assert.equal(resolved.children!.length, 9); + assert.strictEqual(resolved.isDirectory, true); + assert.strictEqual(resolved.children!.length, 9); const resolvedLink = resolved.children?.find(child => child.name === 'bar' && child.isSymbolicLink); assert.ok(resolvedLink); @@ -463,14 +454,14 @@ suite('Disk File Service', function () { const resource = URI.file(join(testDir, 'deep', 'conway.js')); const source = await service.resolve(resource); - assert.equal(await service.canDelete(source.resource, { useTrash }), true); + assert.strictEqual(await service.canDelete(source.resource, { useTrash }), true); await service.del(source.resource, { useTrash }); - assert.equal(existsSync(source.resource.fsPath), false); + assert.strictEqual(existsSync(source.resource.fsPath), false); assert.ok(event!); - assert.equal(event!.resource.fsPath, resource.fsPath); - assert.equal(event!.operation, FileOperation.DELETE); + assert.strictEqual(event!.resource.fsPath, resource.fsPath); + assert.strictEqual(event!.operation, FileOperation.DELETE); let error: Error | undefined = undefined; try { @@ -480,10 +471,10 @@ suite('Disk File Service', function () { } assert.ok(error); - assert.equal((error).fileOperationResult, FileOperationResult.FILE_NOT_FOUND); + assert.strictEqual((error).fileOperationResult, FileOperationResult.FILE_NOT_FOUND); } - (isWindows /* not reliable on windows */ ? test.skip : test)('deleteFile - symbolic link (exists)', async () => { + (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 symlink(target.fsPath, link.fsPath); @@ -493,19 +484,19 @@ suite('Disk File Service', function () { let event: FileOperationEvent; disposables.add(service.onDidRunOperation(e => event = e)); - assert.equal(await service.canDelete(source.resource), true); + assert.strictEqual(await service.canDelete(source.resource), true); await service.del(source.resource); - assert.equal(existsSync(source.resource.fsPath), false); + assert.strictEqual(existsSync(source.resource.fsPath), false); assert.ok(event!); - assert.equal(event!.resource.fsPath, link.fsPath); - assert.equal(event!.operation, FileOperation.DELETE); + assert.strictEqual(event!.resource.fsPath, link.fsPath); + assert.strictEqual(event!.operation, FileOperation.DELETE); - assert.equal(existsSync(target.fsPath), true); // target the link pointed to is never deleted + assert.strictEqual(existsSync(target.fsPath), true); // target the link pointed to is never deleted }); - (isWindows /* not reliable on windows */ ? test.skip : test)('deleteFile - symbolic link (pointing to non-existing file)', async () => { + (isWindows ? test.skip /* windows: cannot create file symbolic link without elevated context */ : test)('deleteFile - symbolic link (pointing to non-existing file)', async () => { const target = URI.file(join(testDir, 'foo')); const link = URI.file(join(testDir, 'bar')); await symlink(target.fsPath, link.fsPath); @@ -513,14 +504,14 @@ suite('Disk File Service', function () { let event: FileOperationEvent; disposables.add(service.onDidRunOperation(e => event = e)); - assert.equal(await service.canDelete(link), true); + assert.strictEqual(await service.canDelete(link), true); await service.del(link); - assert.equal(existsSync(link.fsPath), false); + assert.strictEqual(existsSync(link.fsPath), false); assert.ok(event!); - assert.equal(event!.resource.fsPath, link.fsPath); - assert.equal(event!.operation, FileOperation.DELETE); + assert.strictEqual(event!.resource.fsPath, link.fsPath); + assert.strictEqual(event!.operation, FileOperation.DELETE); }); test('deleteFolder (recursive)', async () => { @@ -538,13 +529,13 @@ suite('Disk File Service', function () { const resource = URI.file(join(testDir, 'deep')); const source = await service.resolve(resource); - assert.equal(await service.canDelete(source.resource, { recursive: true, useTrash }), true); + assert.strictEqual(await service.canDelete(source.resource, { recursive: true, useTrash }), true); await service.del(source.resource, { recursive: true, useTrash }); - assert.equal(existsSync(source.resource.fsPath), false); + assert.strictEqual(existsSync(source.resource.fsPath), false); assert.ok(event!); - assert.equal(event!.resource.fsPath, resource.fsPath); - assert.equal(event!.operation, FileOperation.DELETE); + assert.strictEqual(event!.resource.fsPath, resource.fsPath); + assert.strictEqual(event!.operation, FileOperation.DELETE); } test('deleteFolder (non recursive)', async () => { @@ -572,20 +563,20 @@ suite('Disk File Service', function () { const target = URI.file(join(dirname(source.fsPath), 'other.html')); - assert.equal(await service.canMove(source, target), true); + assert.strictEqual(await service.canMove(source, target), true); const renamed = await service.move(source, target); - assert.equal(existsSync(renamed.resource.fsPath), true); - assert.equal(existsSync(source.fsPath), false); + assert.strictEqual(existsSync(renamed.resource.fsPath), true); + assert.strictEqual(existsSync(source.fsPath), false); assert.ok(event!); - assert.equal(event!.resource.fsPath, source.fsPath); - assert.equal(event!.operation, FileOperation.MOVE); - assert.equal(event!.target!.resource.fsPath, renamed.resource.fsPath); + assert.strictEqual(event!.resource.fsPath, source.fsPath); + assert.strictEqual(event!.operation, FileOperation.MOVE); + assert.strictEqual(event!.target!.resource.fsPath, renamed.resource.fsPath); const targetContents = readFileSync(target.fsPath); - assert.equal(sourceContents.byteLength, targetContents.byteLength); - assert.equal(sourceContents.toString(), targetContents.toString()); + assert.strictEqual(sourceContents.byteLength, targetContents.byteLength); + assert.strictEqual(sourceContents.toString(), targetContents.toString()); }); test('move - across providers (buffered => buffered)', async () => { @@ -653,20 +644,20 @@ suite('Disk File Service', function () { const target = URI.file(join(dirname(source.fsPath), 'other.html')).with({ scheme: testSchema }); - assert.equal(await service.canMove(source, target), true); + assert.strictEqual(await service.canMove(source, target), true); const renamed = await service.move(source, target); - assert.equal(existsSync(renamed.resource.fsPath), true); - assert.equal(existsSync(source.fsPath), false); + assert.strictEqual(existsSync(renamed.resource.fsPath), true); + assert.strictEqual(existsSync(source.fsPath), false); assert.ok(event!); - assert.equal(event!.resource.fsPath, source.fsPath); - assert.equal(event!.operation, FileOperation.COPY); - assert.equal(event!.target!.resource.fsPath, renamed.resource.fsPath); + assert.strictEqual(event!.resource.fsPath, source.fsPath); + assert.strictEqual(event!.operation, FileOperation.COPY); + assert.strictEqual(event!.target!.resource.fsPath, renamed.resource.fsPath); const targetContents = readFileSync(target.fsPath); - assert.equal(sourceContents.byteLength, targetContents.byteLength); - assert.equal(sourceContents.toString(), targetContents.toString()); + assert.strictEqual(sourceContents.byteLength, targetContents.byteLength); + assert.strictEqual(sourceContents.toString(), targetContents.toString()); } test('move - multi folder', async () => { @@ -678,15 +669,15 @@ suite('Disk File Service', function () { const source = URI.file(join(testDir, 'index.html')); - assert.equal(await service.canMove(source, URI.file(join(dirname(source.fsPath), renameToPath))), true); + assert.strictEqual(await service.canMove(source, URI.file(join(dirname(source.fsPath), renameToPath))), true); const renamed = await service.move(source, URI.file(join(dirname(source.fsPath), renameToPath))); - assert.equal(existsSync(renamed.resource.fsPath), true); - assert.equal(existsSync(source.fsPath), false); + assert.strictEqual(existsSync(renamed.resource.fsPath), true); + assert.strictEqual(existsSync(source.fsPath), false); assert.ok(event!); - assert.equal(event!.resource.fsPath, source.fsPath); - assert.equal(event!.operation, FileOperation.MOVE); - assert.equal(event!.target!.resource.fsPath, renamed.resource.fsPath); + assert.strictEqual(event!.resource.fsPath, source.fsPath); + assert.strictEqual(event!.operation, FileOperation.MOVE); + assert.strictEqual(event!.target!.resource.fsPath, renamed.resource.fsPath); }); test('move - directory', async () => { @@ -695,15 +686,15 @@ suite('Disk File Service', function () { const source = URI.file(join(testDir, 'deep')); - assert.equal(await service.canMove(source, URI.file(join(dirname(source.fsPath), 'deeper'))), true); + assert.strictEqual(await service.canMove(source, URI.file(join(dirname(source.fsPath), 'deeper'))), true); const renamed = await service.move(source, URI.file(join(dirname(source.fsPath), 'deeper'))); - assert.equal(existsSync(renamed.resource.fsPath), true); - assert.equal(existsSync(source.fsPath), false); + assert.strictEqual(existsSync(renamed.resource.fsPath), true); + assert.strictEqual(existsSync(source.fsPath), false); assert.ok(event!); - assert.equal(event!.resource.fsPath, source.fsPath); - assert.equal(event!.operation, FileOperation.MOVE); - assert.equal(event!.target!.resource.fsPath, renamed.resource.fsPath); + assert.strictEqual(event!.resource.fsPath, source.fsPath); + assert.strictEqual(event!.operation, FileOperation.MOVE); + assert.strictEqual(event!.target!.resource.fsPath, renamed.resource.fsPath); }); test('move - directory - across providers (buffered => buffered)', async () => { @@ -743,20 +734,20 @@ suite('Disk File Service', function () { const target = URI.file(join(dirname(source.fsPath), 'deeper')).with({ scheme: testSchema }); - assert.equal(await service.canMove(source, target), true); + assert.strictEqual(await service.canMove(source, target), true); const renamed = await service.move(source, target); - assert.equal(existsSync(renamed.resource.fsPath), true); - assert.equal(existsSync(source.fsPath), false); + assert.strictEqual(existsSync(renamed.resource.fsPath), true); + assert.strictEqual(existsSync(source.fsPath), false); assert.ok(event!); - assert.equal(event!.resource.fsPath, source.fsPath); - assert.equal(event!.operation, FileOperation.COPY); - assert.equal(event!.target!.resource.fsPath, renamed.resource.fsPath); + assert.strictEqual(event!.resource.fsPath, source.fsPath); + assert.strictEqual(event!.operation, FileOperation.COPY); + assert.strictEqual(event!.target!.resource.fsPath, renamed.resource.fsPath); const targetChildren = readdirSync(target.fsPath); - assert.equal(sourceChildren.length, targetChildren.length); + assert.strictEqual(sourceChildren.length, targetChildren.length); for (let i = 0; i < sourceChildren.length; i++) { - assert.equal(sourceChildren[i], targetChildren[i]); + assert.strictEqual(sourceChildren[i], targetChildren[i]); } } @@ -768,18 +759,18 @@ suite('Disk File Service', function () { assert.ok(source.size > 0); const renamedResource = URI.file(join(dirname(source.resource.fsPath), 'INDEX.html')); - assert.equal(await service.canMove(source.resource, renamedResource), true); + assert.strictEqual(await service.canMove(source.resource, renamedResource), true); let renamed = await service.move(source.resource, renamedResource); - assert.equal(existsSync(renamedResource.fsPath), true); - assert.equal(basename(renamedResource.fsPath), 'INDEX.html'); + assert.strictEqual(existsSync(renamedResource.fsPath), true); + assert.strictEqual(basename(renamedResource.fsPath), 'INDEX.html'); assert.ok(event!); - assert.equal(event!.resource.fsPath, source.resource.fsPath); - assert.equal(event!.operation, FileOperation.MOVE); - assert.equal(event!.target!.resource.fsPath, renamedResource.fsPath); + assert.strictEqual(event!.resource.fsPath, source.resource.fsPath); + assert.strictEqual(event!.operation, FileOperation.MOVE); + assert.strictEqual(event!.target!.resource.fsPath, renamedResource.fsPath); renamed = await service.resolve(renamedResource, { resolveMetadata: true }); - assert.equal(source.size, renamed.size); + assert.strictEqual(source.size, renamed.size); }); test('move - same file', async () => { @@ -789,18 +780,18 @@ suite('Disk File Service', function () { const source = await service.resolve(URI.file(join(testDir, 'index.html')), { resolveMetadata: true }); assert.ok(source.size > 0); - assert.equal(await service.canMove(source.resource, URI.file(source.resource.fsPath)), true); + assert.strictEqual(await service.canMove(source.resource, URI.file(source.resource.fsPath)), true); let renamed = await service.move(source.resource, URI.file(source.resource.fsPath)); - assert.equal(existsSync(renamed.resource.fsPath), true); - assert.equal(basename(renamed.resource.fsPath), 'index.html'); + assert.strictEqual(existsSync(renamed.resource.fsPath), true); + assert.strictEqual(basename(renamed.resource.fsPath), 'index.html'); assert.ok(event!); - assert.equal(event!.resource.fsPath, source.resource.fsPath); - assert.equal(event!.operation, FileOperation.MOVE); - assert.equal(event!.target!.resource.fsPath, renamed.resource.fsPath); + assert.strictEqual(event!.resource.fsPath, source.resource.fsPath); + assert.strictEqual(event!.operation, FileOperation.MOVE); + assert.strictEqual(event!.target!.resource.fsPath, renamed.resource.fsPath); renamed = await service.resolve(renamed.resource, { resolveMetadata: true }); - assert.equal(source.size, renamed.size); + assert.strictEqual(source.size, renamed.size); }); test('move - same file #2', async () => { @@ -813,18 +804,18 @@ suite('Disk File Service', function () { const targetParent = URI.file(testDir); const target = targetParent.with({ path: posix.join(targetParent.path, posix.basename(source.resource.path)) }); - assert.equal(await service.canMove(source.resource, target), true); + assert.strictEqual(await service.canMove(source.resource, target), true); let renamed = await service.move(source.resource, target); - assert.equal(existsSync(renamed.resource.fsPath), true); - assert.equal(basename(renamed.resource.fsPath), 'index.html'); + assert.strictEqual(existsSync(renamed.resource.fsPath), true); + assert.strictEqual(basename(renamed.resource.fsPath), 'index.html'); assert.ok(event!); - assert.equal(event!.resource.fsPath, source.resource.fsPath); - assert.equal(event!.operation, FileOperation.MOVE); - assert.equal(event!.target!.resource.fsPath, renamed.resource.fsPath); + assert.strictEqual(event!.resource.fsPath, source.resource.fsPath); + assert.strictEqual(event!.operation, FileOperation.MOVE); + assert.strictEqual(event!.target!.resource.fsPath, renamed.resource.fsPath); renamed = await service.resolve(renamed.resource, { resolveMetadata: true }); - assert.equal(source.size, renamed.size); + assert.strictEqual(source.size, renamed.size); }); test('move - source parent of target', async () => { @@ -848,7 +839,7 @@ suite('Disk File Service', function () { assert.ok(!event!); source = await service.resolve(source.resource, { resolveMetadata: true }); - assert.equal(originalSize, source.size); + assert.strictEqual(originalSize, source.size); }); test('move - FILE_MOVE_CONFLICT', async () => { @@ -868,11 +859,11 @@ suite('Disk File Service', function () { error = e; } - assert.equal(error.fileOperationResult, FileOperationResult.FILE_MOVE_CONFLICT); + assert.strictEqual(error.fileOperationResult, FileOperationResult.FILE_MOVE_CONFLICT); assert.ok(!event!); source = await service.resolve(source.resource, { resolveMetadata: true }); - assert.equal(originalSize, source.size); + assert.strictEqual(originalSize, source.size); }); test('move - overwrite folder with file', async () => { @@ -894,17 +885,17 @@ suite('Disk File Service', function () { const f = await service.createFolder(folderResource); const source = URI.file(join(testDir, 'deep', 'conway.js')); - assert.equal(await service.canMove(source, f.resource, true), true); + assert.strictEqual(await service.canMove(source, f.resource, true), true); const moved = await service.move(source, f.resource, true); - assert.equal(existsSync(moved.resource.fsPath), true); + assert.strictEqual(existsSync(moved.resource.fsPath), true); assert.ok(statSync(moved.resource.fsPath).isFile); assert.ok(createEvent!); assert.ok(deleteEvent!); assert.ok(moveEvent!); - assert.equal(moveEvent!.resource.fsPath, source.fsPath); - assert.equal(moveEvent!.target!.resource.fsPath, moved.resource.fsPath); - assert.equal(deleteEvent!.resource.fsPath, folderResource.fsPath); + assert.strictEqual(moveEvent!.resource.fsPath, source.fsPath); + assert.strictEqual(moveEvent!.target!.resource.fsPath, moved.resource.fsPath); + assert.strictEqual(deleteEvent!.resource.fsPath, folderResource.fsPath); }); test('copy', async () => { @@ -949,21 +940,21 @@ suite('Disk File Service', function () { const source = await service.resolve(URI.file(join(testDir, sourceName))); const target = URI.file(join(testDir, 'other.html')); - assert.equal(await service.canCopy(source.resource, target), true); + assert.strictEqual(await service.canCopy(source.resource, target), true); const copied = await service.copy(source.resource, target); - assert.equal(existsSync(copied.resource.fsPath), true); - assert.equal(existsSync(source.resource.fsPath), true); + assert.strictEqual(existsSync(copied.resource.fsPath), true); + assert.strictEqual(existsSync(source.resource.fsPath), true); assert.ok(event!); - assert.equal(event!.resource.fsPath, source.resource.fsPath); - assert.equal(event!.operation, FileOperation.COPY); - assert.equal(event!.target!.resource.fsPath, copied.resource.fsPath); + assert.strictEqual(event!.resource.fsPath, source.resource.fsPath); + assert.strictEqual(event!.operation, FileOperation.COPY); + assert.strictEqual(event!.target!.resource.fsPath, copied.resource.fsPath); const sourceContents = readFileSync(source.resource.fsPath); const targetContents = readFileSync(target.fsPath); - assert.equal(sourceContents.byteLength, targetContents.byteLength); - assert.equal(sourceContents.toString(), targetContents.toString()); + assert.strictEqual(sourceContents.byteLength, targetContents.byteLength); + assert.strictEqual(sourceContents.toString(), targetContents.toString()); } test('copy - overwrite folder with file', async () => { @@ -985,17 +976,17 @@ suite('Disk File Service', function () { const f = await service.createFolder(folderResource); const source = URI.file(join(testDir, 'deep', 'conway.js')); - assert.equal(await service.canCopy(source, f.resource, true), true); + assert.strictEqual(await service.canCopy(source, f.resource, true), true); const copied = await service.copy(source, f.resource, true); - assert.equal(existsSync(copied.resource.fsPath), true); + assert.strictEqual(existsSync(copied.resource.fsPath), true); assert.ok(statSync(copied.resource.fsPath).isFile); assert.ok(createEvent!); assert.ok(deleteEvent!); assert.ok(copyEvent!); - assert.equal(copyEvent!.resource.fsPath, source.fsPath); - assert.equal(copyEvent!.target!.resource.fsPath, copied.resource.fsPath); - assert.equal(deleteEvent!.resource.fsPath, folderResource.fsPath); + assert.strictEqual(copyEvent!.resource.fsPath, source.fsPath); + assert.strictEqual(copyEvent!.target!.resource.fsPath, copied.resource.fsPath); + assert.strictEqual(deleteEvent!.resource.fsPath, folderResource.fsPath); }); test('copy - MIX CASE same target - no overwrite', async () => { @@ -1017,17 +1008,17 @@ suite('Disk File Service', function () { if (isLinux) { assert.ok(!error); - assert.equal(canCopy, true); + assert.strictEqual(canCopy, true); - assert.equal(existsSync(copied!.resource.fsPath), true); + assert.strictEqual(existsSync(copied!.resource.fsPath), true); assert.ok(readdirSync(testDir).some(f => f === 'INDEX.html')); - assert.equal(source.size, copied!.size); + assert.strictEqual(source.size, copied!.size); } else { assert.ok(error); assert.ok(canCopy instanceof Error); source = await service.resolve(source.resource, { resolveMetadata: true }); - assert.equal(originalSize, source.size); + assert.strictEqual(originalSize, source.size); } }); @@ -1050,17 +1041,17 @@ suite('Disk File Service', function () { if (isLinux) { assert.ok(!error); - assert.equal(canCopy, true); + assert.strictEqual(canCopy, true); - assert.equal(existsSync(copied!.resource.fsPath), true); + assert.strictEqual(existsSync(copied!.resource.fsPath), true); assert.ok(readdirSync(testDir).some(f => f === 'INDEX.html')); - assert.equal(source.size, copied!.size); + assert.strictEqual(source.size, copied!.size); } else { assert.ok(error); assert.ok(canCopy instanceof Error); source = await service.resolve(source.resource, { resolveMetadata: true }); - assert.equal(originalSize, source.size); + assert.strictEqual(originalSize, source.size); } }); @@ -1069,18 +1060,18 @@ suite('Disk File Service', function () { assert.ok(source1.size > 0); const renamed = await service.move(source1.resource, URI.file(join(dirname(source1.resource.fsPath), 'CONWAY.js'))); - assert.equal(existsSync(renamed.resource.fsPath), true); + assert.strictEqual(existsSync(renamed.resource.fsPath), true); assert.ok(readdirSync(testDir).some(f => f === 'CONWAY.js')); - assert.equal(source1.size, renamed.size); + assert.strictEqual(source1.size, renamed.size); const source2 = await service.resolve(URI.file(join(testDir, 'deep', 'conway.js')), { resolveMetadata: true }); const target = URI.file(join(testDir, basename(source2.resource.path))); - assert.equal(await service.canCopy(source2.resource, target, true), true); + assert.strictEqual(await service.canCopy(source2.resource, target, true), true); const res = await service.copy(source2.resource, target, true); - assert.equal(existsSync(res.resource.fsPath), true); + assert.strictEqual(existsSync(res.resource.fsPath), true); assert.ok(readdirSync(testDir).some(f => f === 'conway.js')); - assert.equal(source2.size, res.size); + assert.strictEqual(source2.size, res.size); }); test('copy - same file', async () => { @@ -1090,18 +1081,18 @@ suite('Disk File Service', function () { const source = await service.resolve(URI.file(join(testDir, 'index.html')), { resolveMetadata: true }); assert.ok(source.size > 0); - assert.equal(await service.canCopy(source.resource, URI.file(source.resource.fsPath)), true); + assert.strictEqual(await service.canCopy(source.resource, URI.file(source.resource.fsPath)), true); let copied = await service.copy(source.resource, URI.file(source.resource.fsPath)); - assert.equal(existsSync(copied.resource.fsPath), true); - assert.equal(basename(copied.resource.fsPath), 'index.html'); + assert.strictEqual(existsSync(copied.resource.fsPath), true); + assert.strictEqual(basename(copied.resource.fsPath), 'index.html'); assert.ok(event!); - assert.equal(event!.resource.fsPath, source.resource.fsPath); - assert.equal(event!.operation, FileOperation.COPY); - assert.equal(event!.target!.resource.fsPath, copied.resource.fsPath); + assert.strictEqual(event!.resource.fsPath, source.resource.fsPath); + assert.strictEqual(event!.operation, FileOperation.COPY); + assert.strictEqual(event!.target!.resource.fsPath, copied.resource.fsPath); copied = await service.resolve(source.resource, { resolveMetadata: true }); - assert.equal(source.size, copied.size); + assert.strictEqual(source.size, copied.size); }); test('copy - same file #2', async () => { @@ -1114,18 +1105,18 @@ suite('Disk File Service', function () { const targetParent = URI.file(testDir); const target = targetParent.with({ path: posix.join(targetParent.path, posix.basename(source.resource.path)) }); - assert.equal(await service.canCopy(source.resource, URI.file(target.fsPath)), true); + assert.strictEqual(await service.canCopy(source.resource, URI.file(target.fsPath)), true); let copied = await service.copy(source.resource, URI.file(target.fsPath)); - assert.equal(existsSync(copied.resource.fsPath), true); - assert.equal(basename(copied.resource.fsPath), 'index.html'); + assert.strictEqual(existsSync(copied.resource.fsPath), true); + assert.strictEqual(basename(copied.resource.fsPath), 'index.html'); assert.ok(event!); - assert.equal(event!.resource.fsPath, source.resource.fsPath); - assert.equal(event!.operation, FileOperation.COPY); - assert.equal(event!.target!.resource.fsPath, copied.resource.fsPath); + assert.strictEqual(event!.resource.fsPath, source.resource.fsPath); + assert.strictEqual(event!.operation, FileOperation.COPY); + assert.strictEqual(event!.target!.resource.fsPath, copied.resource.fsPath); copied = await service.resolve(source.resource, { resolveMetadata: true }); - assert.equal(source.size, copied.size); + assert.strictEqual(source.size, copied.size); }); test('readFile - small file - default', () => { @@ -1193,7 +1184,7 @@ suite('Disk File Service', function () { async function testReadFile(resource: URI): Promise { const content = await service.readFile(resource); - assert.equal(content.value.toString(), readFileSync(resource.fsPath)); + assert.strictEqual(content.value.toString(), readFileSync(resource.fsPath).toString()); } test('readFileStream - small file - default', () => { @@ -1221,7 +1212,7 @@ suite('Disk File Service', function () { async function testReadFileStream(resource: URI): Promise { const content = await service.readFileStream(resource); - assert.equal((await streamToBuffer(content.value)).toString(), readFileSync(resource.fsPath)); + assert.strictEqual((await streamToBuffer(content.value)).toString(), readFileSync(resource.fsPath).toString()); } test('readFile - Files are intermingled #38331 - default', async () => { @@ -1260,8 +1251,8 @@ suite('Disk File Service', function () { service.readFile(resource2) ]); - assert.equal(result[0].value.toString(), value1.value.toString()); - assert.equal(result[1].value.toString(), value2.value.toString()); + assert.strictEqual(result[0].value.toString(), value1.value.toString()); + assert.strictEqual(result[1].value.toString(), value2.value.toString()); } test('readFile - from position (ASCII) - default', async () => { @@ -1291,7 +1282,7 @@ suite('Disk File Service', function () { const contents = await service.readFile(resource, { position: 6 }); - assert.equal(contents.value.toString(), 'File'); + assert.strictEqual(contents.value.toString(), 'File'); } test('readFile - from position (with umlaut) - default', async () => { @@ -1321,7 +1312,7 @@ suite('Disk File Service', function () { const contents = await service.readFile(resource, { position: Buffer.from('Small File with Ü').length }); - assert.equal(contents.value.toString(), 'mlaut'); + assert.strictEqual(contents.value.toString(), 'mlaut'); } test('readFile - 3 bytes (ASCII) - default', async () => { @@ -1351,7 +1342,7 @@ suite('Disk File Service', function () { const contents = await service.readFile(resource, { length: 3 }); - assert.equal(contents.value.toString(), 'Sma'); + assert.strictEqual(contents.value.toString(), 'Sma'); } test('readFile - 20000 bytes (large) - default', async () => { @@ -1403,7 +1394,7 @@ suite('Disk File Service', function () { const contents = await service.readFile(resource, { length }); - assert.equal(contents.value.byteLength, length); + assert.strictEqual(contents.value.byteLength, length); } test('readFile - FILE_IS_DIRECTORY', async () => { @@ -1417,7 +1408,7 @@ suite('Disk File Service', function () { } assert.ok(error); - assert.equal(error!.fileOperationResult, FileOperationResult.FILE_IS_DIRECTORY); + assert.strictEqual(error!.fileOperationResult, FileOperationResult.FILE_IS_DIRECTORY); }); (isWindows /* error code does not seem to be supported on windows */ ? test.skip : test)('readFile - FILE_NOT_DIRECTORY', async () => { @@ -1431,7 +1422,7 @@ suite('Disk File Service', function () { } assert.ok(error); - assert.equal(error!.fileOperationResult, FileOperationResult.FILE_NOT_DIRECTORY); + assert.strictEqual(error!.fileOperationResult, FileOperationResult.FILE_NOT_DIRECTORY); }); test('readFile - FILE_NOT_FOUND', async () => { @@ -1445,7 +1436,7 @@ suite('Disk File Service', function () { } assert.ok(error); - assert.equal(error!.fileOperationResult, FileOperationResult.FILE_NOT_FOUND); + assert.strictEqual(error!.fileOperationResult, FileOperationResult.FILE_NOT_FOUND); }); test('readFile - FILE_NOT_MODIFIED_SINCE - default', async () => { @@ -1484,8 +1475,8 @@ suite('Disk File Service', function () { } assert.ok(error); - assert.equal(error!.fileOperationResult, FileOperationResult.FILE_NOT_MODIFIED_SINCE); - assert.equal(fileProvider.totalBytesRead, 0); + assert.strictEqual(error!.fileOperationResult, FileOperationResult.FILE_NOT_MODIFIED_SINCE); + assert.strictEqual(fileProvider.totalBytesRead, 0); } test('readFile - FILE_NOT_MODIFIED_SINCE does not fire wrongly - https://github.com/microsoft/vscode/issues/72909', async () => { @@ -1546,26 +1537,26 @@ suite('Disk File Service', function () { } assert.ok(error); - assert.equal(error!.fileOperationResult, FileOperationResult.FILE_EXCEEDS_MEMORY_LIMIT); + assert.strictEqual(error!.fileOperationResult, FileOperationResult.FILE_EXCEEDS_MEMORY_LIMIT); } - (isWindows ? test.skip /* flaky test */ : test)('readFile - FILE_TOO_LARGE - default', async () => { + test('readFile - FILE_TOO_LARGE - default', async () => { return testFileTooLarge(); }); - (isWindows ? test.skip /* flaky test */ : test)('readFile - FILE_TOO_LARGE - buffered', async () => { + test('readFile - FILE_TOO_LARGE - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); return testFileTooLarge(); }); - (isWindows ? test.skip /* flaky test */ : test)('readFile - FILE_TOO_LARGE - unbuffered', async () => { + test('readFile - FILE_TOO_LARGE - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); return testFileTooLarge(); }); - (isWindows ? test.skip /* flaky test */ : test)('readFile - FILE_TOO_LARGE - streamed', async () => { + test('readFile - FILE_TOO_LARGE - streamed', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); return testFileTooLarge(); @@ -1590,9 +1581,23 @@ suite('Disk File Service', function () { } assert.ok(error); - assert.equal(error!.fileOperationResult, FileOperationResult.FILE_TOO_LARGE); + assert.strictEqual(error!.fileOperationResult, FileOperationResult.FILE_TOO_LARGE); } + (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 symlink(join(testDir, 'small.js'), link.fsPath); + + let error: FileOperationError | undefined = undefined; + try { + await service.readFile(link); + } catch (err) { + error = err; + } + + assert.ok(error); + }); + test('createFile', async () => { return assertCreateFile(contents => VSBuffer.fromString(contents)); }); @@ -1612,16 +1617,16 @@ suite('Disk File Service', function () { const contents = 'Hello World'; const resource = URI.file(join(testDir, 'test.txt')); - assert.equal(await service.canCreateFile(resource), true); + assert.strictEqual(await service.canCreateFile(resource), true); const fileStat = await service.createFile(resource, converter(contents)); - assert.equal(fileStat.name, 'test.txt'); - assert.equal(existsSync(fileStat.resource.fsPath), true); - assert.equal(readFileSync(fileStat.resource.fsPath), contents); + assert.strictEqual(fileStat.name, 'test.txt'); + assert.strictEqual(existsSync(fileStat.resource.fsPath), true); + assert.strictEqual(readFileSync(fileStat.resource.fsPath).toString(), contents); assert.ok(event!); - assert.equal(event!.resource.fsPath, resource.fsPath); - assert.equal(event!.operation, FileOperation.CREATE); - assert.equal(event!.target!.resource.fsPath, resource.fsPath); + assert.strictEqual(event!.resource.fsPath, resource.fsPath); + assert.strictEqual(event!.operation, FileOperation.CREATE); + assert.strictEqual(event!.target!.resource.fsPath, resource.fsPath); } test('createFile (does not overwrite by default)', async () => { @@ -1651,16 +1656,16 @@ suite('Disk File Service', function () { writeFileSync(resource.fsPath, ''); // create file - assert.equal(await service.canCreateFile(resource, { overwrite: true }), true); + assert.strictEqual(await service.canCreateFile(resource, { overwrite: true }), true); const fileStat = await service.createFile(resource, VSBuffer.fromString(contents), { overwrite: true }); - assert.equal(fileStat.name, 'test.txt'); - assert.equal(existsSync(fileStat.resource.fsPath), true); - assert.equal(readFileSync(fileStat.resource.fsPath), contents); + assert.strictEqual(fileStat.name, 'test.txt'); + assert.strictEqual(existsSync(fileStat.resource.fsPath), true); + assert.strictEqual(readFileSync(fileStat.resource.fsPath).toString(), contents); assert.ok(event!); - assert.equal(event!.resource.fsPath, resource.fsPath); - assert.equal(event!.operation, FileOperation.CREATE); - assert.equal(event!.target!.resource.fsPath, resource.fsPath); + assert.strictEqual(event!.resource.fsPath, resource.fsPath); + assert.strictEqual(event!.operation, FileOperation.CREATE); + assert.strictEqual(event!.target!.resource.fsPath, resource.fsPath); }); test('writeFile - default', async () => { @@ -1682,13 +1687,13 @@ suite('Disk File Service', function () { async function testWriteFile() { const resource = URI.file(join(testDir, 'small.txt')); - const content = readFileSync(resource.fsPath); - assert.equal(content, 'Small File'); + const content = readFileSync(resource.fsPath).toString(); + assert.strictEqual(content, 'Small File'); const newContent = 'Updates to the small file'; await service.writeFile(resource, VSBuffer.fromString(newContent)); - assert.equal(readFileSync(resource.fsPath), newContent); + assert.strictEqual(readFileSync(resource.fsPath).toString(), newContent); } test('writeFile (large file) - default', async () => { @@ -1714,9 +1719,9 @@ suite('Disk File Service', function () { const newContent = content.toString() + content.toString(); const fileStat = await service.writeFile(resource, VSBuffer.fromString(newContent)); - assert.equal(fileStat.name, 'lorem.txt'); + assert.strictEqual(fileStat.name, 'lorem.txt'); - assert.equal(readFileSync(resource.fsPath), newContent); + assert.strictEqual(readFileSync(resource.fsPath).toString(), newContent); } test('writeFile - buffered - readonly throws', async () => { @@ -1734,8 +1739,8 @@ suite('Disk File Service', function () { async function testWriteFileReadonlyThrows() { const resource = URI.file(join(testDir, 'small.txt')); - const content = readFileSync(resource.fsPath); - assert.equal(content, 'Small File'); + const content = readFileSync(resource.fsPath).toString(); + assert.strictEqual(content, 'Small File'); const newContent = 'Updates to the small file'; @@ -1757,7 +1762,7 @@ suite('Disk File Service', function () { await Promise.all(['0', '00', '000', '0000', '00000'].map(async offset => { const fileStat = await service.writeFile(resource, VSBuffer.fromString(offset + newContent)); - assert.equal(fileStat.name, 'lorem.txt'); + assert.strictEqual(fileStat.name, 'lorem.txt'); })); const fileContent = readFileSync(resource.fsPath).toString(); @@ -1783,13 +1788,13 @@ suite('Disk File Service', function () { async function testWriteFileReadable() { const resource = URI.file(join(testDir, 'small.txt')); - const content = readFileSync(resource.fsPath); - assert.equal(content, 'Small File'); + const content = readFileSync(resource.fsPath).toString(); + assert.strictEqual(content, 'Small File'); const newContent = 'Updates to the small file'; await service.writeFile(resource, toLineByLineReadable(newContent)); - assert.equal(readFileSync(resource.fsPath), newContent); + assert.strictEqual(readFileSync(resource.fsPath).toString(), newContent); } test('writeFile (large file - readable) - default', async () => { @@ -1815,9 +1820,9 @@ suite('Disk File Service', function () { const newContent = content.toString() + content.toString(); const fileStat = await service.writeFile(resource, toLineByLineReadable(newContent)); - assert.equal(fileStat.name, 'lorem.txt'); + assert.strictEqual(fileStat.name, 'lorem.txt'); - assert.equal(readFileSync(resource.fsPath), newContent); + assert.strictEqual(readFileSync(resource.fsPath).toString(), newContent); } test('writeFile (stream) - default', async () => { @@ -1841,10 +1846,10 @@ suite('Disk File Service', function () { const target = URI.file(join(testDir, 'small-copy.txt')); const fileStat = await service.writeFile(target, streamToBufferReadableStream(createReadStream(source.fsPath))); - assert.equal(fileStat.name, 'small-copy.txt'); + assert.strictEqual(fileStat.name, 'small-copy.txt'); const targetContents = readFileSync(target.fsPath).toString(); - assert.equal(readFileSync(source.fsPath).toString(), targetContents); + assert.strictEqual(readFileSync(source.fsPath).toString(), targetContents); } test('writeFile (large file - stream) - default', async () => { @@ -1868,10 +1873,10 @@ suite('Disk File Service', function () { const target = URI.file(join(testDir, 'lorem-copy.txt')); const fileStat = await service.writeFile(target, streamToBufferReadableStream(createReadStream(source.fsPath))); - assert.equal(fileStat.name, 'lorem-copy.txt'); + assert.strictEqual(fileStat.name, 'lorem-copy.txt'); const targetContents = readFileSync(target.fsPath).toString(); - assert.equal(readFileSync(source.fsPath).toString(), targetContents); + assert.strictEqual(readFileSync(source.fsPath).toString(), targetContents); } test('writeFile (file is created including parents)', async () => { @@ -1879,9 +1884,9 @@ suite('Disk File Service', function () { const content = 'File is created including parent'; const fileStat = await service.writeFile(resource, VSBuffer.fromString(content)); - assert.equal(fileStat.name, 'newfile.txt'); + assert.strictEqual(fileStat.name, 'newfile.txt'); - assert.equal(readFileSync(resource.fsPath), content); + assert.strictEqual(readFileSync(resource.fsPath).toString(), content); }); test('writeFile (error when folder is encountered)', async () => { @@ -1902,13 +1907,13 @@ suite('Disk File Service', function () { const stat = await service.resolve(resource); - const content = readFileSync(resource.fsPath); - assert.equal(content, 'Small File'); + const content = readFileSync(resource.fsPath).toString(); + assert.strictEqual(content, 'Small File'); const newContent = 'Updates to the small file'; await service.writeFile(resource, VSBuffer.fromString(newContent), { etag: stat.etag, mtime: stat.mtime }); - assert.equal(readFileSync(resource.fsPath), newContent); + assert.strictEqual(readFileSync(resource.fsPath).toString(), newContent); }); test('writeFile - error when writing to file that has been updated meanwhile', async () => { @@ -1917,7 +1922,7 @@ suite('Disk File Service', function () { const stat = await service.resolve(resource); const content = readFileSync(resource.fsPath).toString(); - assert.equal(content, 'Small File'); + assert.strictEqual(content, 'Small File'); const newContent = 'Updates to the small file'; await service.writeFile(resource, VSBuffer.fromString(newContent), { etag: stat.etag, mtime: stat.mtime }); @@ -1936,7 +1941,7 @@ suite('Disk File Service', function () { assert.ok(error); assert.ok(error instanceof FileOperationError); - assert.equal(error!.fileOperationResult, FileOperationResult.FILE_MODIFIED_SINCE); + assert.strictEqual(error!.fileOperationResult, FileOperationResult.FILE_MODIFIED_SINCE); }); test('writeFile - no error when writing to file where size is the same', async () => { @@ -1945,7 +1950,7 @@ suite('Disk File Service', function () { const stat = await service.resolve(resource); const content = readFileSync(resource.fsPath).toString(); - assert.equal(content, 'Small File'); + assert.strictEqual(content, 'Small File'); const newContent = content; // same content await service.writeFile(resource, VSBuffer.fromString(newContent), { etag: stat.etag, mtime: stat.mtime }); @@ -2008,73 +2013,72 @@ suite('Disk File Service', function () { const runWatchTests = isLinux; - (runWatchTests ? test : test.skip)('watch - file', done => { + (runWatchTests ? test : test.skip)('watch - file', async () => { const toWatch = URI.file(join(testDir, 'index-watch1.html')); writeFileSync(toWatch.fsPath, 'Init'); - assertWatch(toWatch, [[FileChangeType.UPDATED, toWatch]], done); - + const promise = assertWatch(toWatch, [[FileChangeType.UPDATED, toWatch]]); setTimeout(() => writeFileSync(toWatch.fsPath, 'Changes'), 50); + await promise; }); - (runWatchTests && !isWindows /* symbolic links not reliable on windows */ ? test : test.skip)('watch - file symbolic link', async done => { + (runWatchTests && !isWindows /* windows: cannot create file symbolic link without elevated context */ ? test : test.skip)('watch - file symbolic link', async () => { const toWatch = URI.file(join(testDir, 'lorem.txt-linked')); await symlink(join(testDir, 'lorem.txt'), toWatch.fsPath); - assertWatch(toWatch, [[FileChangeType.UPDATED, toWatch]], done); - + const promise = assertWatch(toWatch, [[FileChangeType.UPDATED, toWatch]]); setTimeout(() => writeFileSync(toWatch.fsPath, 'Changes'), 50); + await promise; }); - (runWatchTests ? test : test.skip)('watch - file - multiple writes', done => { + (runWatchTests ? test : test.skip)('watch - file - multiple writes', async () => { const toWatch = URI.file(join(testDir, 'index-watch1.html')); writeFileSync(toWatch.fsPath, 'Init'); - assertWatch(toWatch, [[FileChangeType.UPDATED, toWatch]], done); - + const promise = assertWatch(toWatch, [[FileChangeType.UPDATED, toWatch]]); setTimeout(() => writeFileSync(toWatch.fsPath, 'Changes 1'), 0); setTimeout(() => writeFileSync(toWatch.fsPath, 'Changes 2'), 10); setTimeout(() => writeFileSync(toWatch.fsPath, 'Changes 3'), 20); + await promise; }); - (runWatchTests ? test : test.skip)('watch - file - delete file', done => { + (runWatchTests ? test : test.skip)('watch - file - delete file', async () => { const toWatch = URI.file(join(testDir, 'index-watch1.html')); writeFileSync(toWatch.fsPath, 'Init'); - assertWatch(toWatch, [[FileChangeType.DELETED, toWatch]], done); - + const promise = assertWatch(toWatch, [[FileChangeType.DELETED, toWatch]]); setTimeout(() => unlinkSync(toWatch.fsPath), 50); + await promise; }); - (runWatchTests ? test : test.skip)('watch - file - rename file', done => { + (runWatchTests ? test : test.skip)('watch - file - rename file', async () => { const toWatch = URI.file(join(testDir, 'index-watch1.html')); const toWatchRenamed = URI.file(join(testDir, 'index-watch1-renamed.html')); writeFileSync(toWatch.fsPath, 'Init'); - assertWatch(toWatch, [[FileChangeType.DELETED, toWatch]], done); - + const promise = assertWatch(toWatch, [[FileChangeType.DELETED, toWatch]]); setTimeout(() => renameSync(toWatch.fsPath, toWatchRenamed.fsPath), 50); + await promise; }); - (runWatchTests ? test : test.skip)('watch - file - rename file (different case)', done => { + (runWatchTests ? test : test.skip)('watch - file - rename file (different case)', async () => { const toWatch = URI.file(join(testDir, 'index-watch1.html')); const toWatchRenamed = URI.file(join(testDir, 'INDEX-watch1.html')); writeFileSync(toWatch.fsPath, 'Init'); - if (isLinux) { - assertWatch(toWatch, [[FileChangeType.DELETED, toWatch]], done); - } else { - assertWatch(toWatch, [[FileChangeType.UPDATED, toWatch]], done); // case insensitive file system treat this as change - } + const promise = isLinux + ? assertWatch(toWatch, [[FileChangeType.DELETED, toWatch]]) + : assertWatch(toWatch, [[FileChangeType.UPDATED, toWatch]]); // case insensitive file system treat this as change setTimeout(() => renameSync(toWatch.fsPath, toWatchRenamed.fsPath), 50); + await promise; }); - (runWatchTests ? test : test.skip)('watch - file (atomic save)', function (done) { + (runWatchTests ? test : test.skip)('watch - file (atomic save)', async () => { const toWatch = URI.file(join(testDir, 'index-watch2.html')); writeFileSync(toWatch.fsPath, 'Init'); - assertWatch(toWatch, [[FileChangeType.UPDATED, toWatch]], done); + const promise = assertWatch(toWatch, [[FileChangeType.UPDATED, toWatch]]); setTimeout(() => { // Simulate atomic save by deleting the file, creating it under different name @@ -2084,79 +2088,81 @@ suite('Disk File Service', function () { writeFileSync(renamed, 'Changes'); renameSync(renamed, toWatch.fsPath); }, 50); + + await promise; }); - (runWatchTests ? test : test.skip)('watch - folder (non recursive) - change file', done => { + (runWatchTests ? test : test.skip)('watch - folder (non recursive) - change file', async () => { const watchDir = URI.file(join(testDir, 'watch3')); mkdirSync(watchDir.fsPath); const file = URI.file(join(watchDir.fsPath, 'index.html')); writeFileSync(file.fsPath, 'Init'); - assertWatch(watchDir, [[FileChangeType.UPDATED, file]], done); - + const promise = assertWatch(watchDir, [[FileChangeType.UPDATED, file]]); setTimeout(() => writeFileSync(file.fsPath, 'Changes'), 50); + await promise; }); - (runWatchTests ? test : test.skip)('watch - folder (non recursive) - add file', done => { + (runWatchTests ? test : test.skip)('watch - folder (non recursive) - add file', async () => { const watchDir = URI.file(join(testDir, 'watch4')); mkdirSync(watchDir.fsPath); const file = URI.file(join(watchDir.fsPath, 'index.html')); - assertWatch(watchDir, [[FileChangeType.ADDED, file]], done); - + const promise = assertWatch(watchDir, [[FileChangeType.ADDED, file]]); setTimeout(() => writeFileSync(file.fsPath, 'Changes'), 50); + await promise; }); - (runWatchTests ? test : test.skip)('watch - folder (non recursive) - delete file', done => { + (runWatchTests ? test : test.skip)('watch - folder (non recursive) - delete file', async () => { const watchDir = URI.file(join(testDir, 'watch5')); mkdirSync(watchDir.fsPath); const file = URI.file(join(watchDir.fsPath, 'index.html')); writeFileSync(file.fsPath, 'Init'); - assertWatch(watchDir, [[FileChangeType.DELETED, file]], done); - + const promise = assertWatch(watchDir, [[FileChangeType.DELETED, file]]); setTimeout(() => unlinkSync(file.fsPath), 50); + await promise; }); - (runWatchTests ? test : test.skip)('watch - folder (non recursive) - add folder', done => { + (runWatchTests ? test : test.skip)('watch - folder (non recursive) - add folder', async () => { const watchDir = URI.file(join(testDir, 'watch6')); mkdirSync(watchDir.fsPath); const folder = URI.file(join(watchDir.fsPath, 'folder')); - assertWatch(watchDir, [[FileChangeType.ADDED, folder]], done); - + const promise = assertWatch(watchDir, [[FileChangeType.ADDED, folder]]); setTimeout(() => mkdirSync(folder.fsPath), 50); + await promise; }); - (runWatchTests ? test : test.skip)('watch - folder (non recursive) - delete folder', done => { + (runWatchTests ? test : test.skip)('watch - folder (non recursive) - delete folder', async () => { const watchDir = URI.file(join(testDir, 'watch7')); mkdirSync(watchDir.fsPath); const folder = URI.file(join(watchDir.fsPath, 'folder')); mkdirSync(folder.fsPath); - assertWatch(watchDir, [[FileChangeType.DELETED, folder]], done); - + const promise = assertWatch(watchDir, [[FileChangeType.DELETED, folder]]); setTimeout(() => rimrafSync(folder.fsPath), 50); + await promise; }); - (runWatchTests && !isWindows /* symbolic links not reliable on windows */ ? test : test.skip)('watch - folder (non recursive) - symbolic link - change file', async done => { + (runWatchTests ? test : test.skip)('watch - folder (non recursive) - symbolic link - change file', async () => { const watchDir = URI.file(join(testDir, 'deep-link')); - await symlink(join(testDir, 'deep'), watchDir.fsPath); + await symlink(join(testDir, 'deep'), watchDir.fsPath, 'junction'); const file = URI.file(join(watchDir.fsPath, 'index.html')); writeFileSync(file.fsPath, 'Init'); - assertWatch(watchDir, [[FileChangeType.UPDATED, file]], done); - + const promise = assertWatch(watchDir, [[FileChangeType.UPDATED, file]]); setTimeout(() => writeFileSync(file.fsPath, 'Changes'), 50); + await promise; }); - (runWatchTests ? test : test.skip)('watch - folder (non recursive) - rename file', done => { + (runWatchTests ? test : test.skip)('watch - folder (non recursive) - rename file', async () => { const watchDir = URI.file(join(testDir, 'watch8')); mkdirSync(watchDir.fsPath); @@ -2165,12 +2171,12 @@ suite('Disk File Service', function () { const fileRenamed = URI.file(join(watchDir.fsPath, 'index-renamed.html')); - assertWatch(watchDir, [[FileChangeType.DELETED, file], [FileChangeType.ADDED, fileRenamed]], done); - + const promise = assertWatch(watchDir, [[FileChangeType.DELETED, file], [FileChangeType.ADDED, fileRenamed]]); setTimeout(() => renameSync(file.fsPath, fileRenamed.fsPath), 50); + await promise; }); - (runWatchTests && isLinux /* this test requires a case sensitive file system */ ? test : test.skip)('watch - folder (non recursive) - rename file (different case)', done => { + (runWatchTests && isLinux /* this test requires a case sensitive file system */ ? test : test.skip)('watch - folder (non recursive) - rename file (different case)', async () => { const watchDir = URI.file(join(testDir, 'watch8')); mkdirSync(watchDir.fsPath); @@ -2179,46 +2185,48 @@ suite('Disk File Service', function () { const fileRenamed = URI.file(join(watchDir.fsPath, 'INDEX.html')); - assertWatch(watchDir, [[FileChangeType.DELETED, file], [FileChangeType.ADDED, fileRenamed]], done); - + const promise = assertWatch(watchDir, [[FileChangeType.DELETED, file], [FileChangeType.ADDED, fileRenamed]]); setTimeout(() => renameSync(file.fsPath, fileRenamed.fsPath), 50); + await promise; }); - function assertWatch(toWatch: URI, expected: [FileChangeType, URI][], done: MochaDone): void { - const watcherDisposable = service.watch(toWatch); + function assertWatch(toWatch: URI, expected: [FileChangeType, URI][]): Promise { + return new Promise((resolve, reject) => { + const watcherDisposable = service.watch(toWatch); - function toString(type: FileChangeType): string { - switch (type) { - case FileChangeType.ADDED: return 'added'; - case FileChangeType.DELETED: return 'deleted'; - case FileChangeType.UPDATED: return 'updated'; - } - } - - function printEvents(event: FileChangesEvent): string { - return event.changes.map(change => `Change: type ${toString(change.type)} path ${change.resource.toString()}`).join('\n'); - } - - const listenerDisposable = service.onDidFilesChange(event => { - watcherDisposable.dispose(); - listenerDisposable.dispose(); - - try { - assert.equal(event.changes.length, expected.length, `Expected ${expected.length} events, but got ${event.changes.length}. Details (${printEvents(event)})`); - - if (expected.length === 1) { - assert.equal(event.changes[0].type, expected[0][0], `Expected ${toString(expected[0][0])} but got ${toString(event.changes[0].type)}. Details (${printEvents(event)})`); - assert.equal(event.changes[0].resource.fsPath, expected[0][1].fsPath); - } else { - for (const expect of expected) { - assert.equal(hasChange(event.changes, expect[0], expect[1]), true, `Unable to find ${toString(expect[0])} for ${expect[1].fsPath}. Details (${printEvents(event)})`); - } + function toString(type: FileChangeType): string { + switch (type) { + case FileChangeType.ADDED: return 'added'; + case FileChangeType.DELETED: return 'deleted'; + case FileChangeType.UPDATED: return 'updated'; } - - done(); - } catch (error) { - done(error); } + + function printEvents(event: FileChangesEvent): string { + return event.changes.map(change => `Change: type ${toString(change.type)} path ${change.resource.toString()}`).join('\n'); + } + + const listenerDisposable = service.onDidFilesChange(event => { + watcherDisposable.dispose(); + listenerDisposable.dispose(); + + try { + assert.strictEqual(event.changes.length, expected.length, `Expected ${expected.length} events, but got ${event.changes.length}. Details (${printEvents(event)})`); + + if (expected.length === 1) { + assert.strictEqual(event.changes[0].type, expected[0][0], `Expected ${toString(expected[0][0])} but got ${toString(event.changes[0].type)}. Details (${printEvents(event)})`); + assert.strictEqual(event.changes[0].resource.fsPath, expected[0][1].fsPath); + } else { + for (const expect of expected) { + assert.strictEqual(hasChange(event.changes, expect[0], expect[1]), true, `Unable to find ${toString(expect[0])} for ${expect[1].fsPath}. Details (${printEvents(event)})`); + } + } + + resolve(); + } catch (error) { + reject(error); + } + }); }); } @@ -2234,7 +2242,7 @@ suite('Disk File Service', function () { let fd = await fileProvider.open(resource, { create: false }); for (let i = 0; i < 3; i++) { await fileProvider.read(fd, 0, buffer.buffer, 0, 26); - assert.equal(buffer.slice(0, 26).toString(), 'Lorem ipsum dolor sit amet'); + assert.strictEqual(buffer.slice(0, 26).toString(), 'Lorem ipsum dolor sit amet'); } await fileProvider.close(fd); @@ -2245,31 +2253,31 @@ suite('Disk File Service', function () { let posInFile = 0; await fileProvider.read(fd, posInFile, buffer.buffer, 0, 26); - assert.equal(buffer.slice(0, 26).toString(), 'Lorem ipsum dolor sit amet'); + assert.strictEqual(buffer.slice(0, 26).toString(), 'Lorem ipsum dolor sit amet'); posInFile += 26; await fileProvider.read(fd, posInFile, buffer.buffer, 0, 1); - assert.equal(buffer.slice(0, 1).toString(), ','); + assert.strictEqual(buffer.slice(0, 1).toString(), ','); posInFile += 1; await fileProvider.read(fd, posInFile, buffer.buffer, 0, 12); - assert.equal(buffer.slice(0, 12).toString(), ' consectetur'); + assert.strictEqual(buffer.slice(0, 12).toString(), ' consectetur'); posInFile += 12; await fileProvider.read(fd, 98 /* no longer in sequence of posInFile */, buffer.buffer, 0, 9); - assert.equal(buffer.slice(0, 9).toString(), 'fermentum'); + assert.strictEqual(buffer.slice(0, 9).toString(), 'fermentum'); await fileProvider.read(fd, 27, buffer.buffer, 0, 12); - assert.equal(buffer.slice(0, 12).toString(), ' consectetur'); + assert.strictEqual(buffer.slice(0, 12).toString(), ' consectetur'); await fileProvider.read(fd, 26, buffer.buffer, 0, 1); - assert.equal(buffer.slice(0, 1).toString(), ','); + assert.strictEqual(buffer.slice(0, 1).toString(), ','); await fileProvider.read(fd, 0, buffer.buffer, 0, 26); - assert.equal(buffer.slice(0, 26).toString(), 'Lorem ipsum dolor sit amet'); + assert.strictEqual(buffer.slice(0, 26).toString(), 'Lorem ipsum dolor sit amet'); await fileProvider.read(fd, posInFile /* back in sequence */, buffer.buffer, 0, 11); - assert.equal(buffer.slice(0, 11).toString(), ' adipiscing'); + assert.strictEqual(buffer.slice(0, 11).toString(), ' adipiscing'); await fileProvider.close(fd); }); @@ -2289,7 +2297,7 @@ suite('Disk File Service', function () { posInFileWrite += initialContents.byteLength; await fileProvider.read(fdRead, posInFileRead, buffer.buffer, 0, 26); - assert.equal(buffer.slice(0, 26).toString(), 'Lorem ipsum dolor sit amet'); + assert.strictEqual(buffer.slice(0, 26).toString(), 'Lorem ipsum dolor sit amet'); posInFileRead += 26; const contents = VSBuffer.fromString('Hello World'); @@ -2298,19 +2306,19 @@ suite('Disk File Service', function () { posInFileWrite += contents.byteLength; await fileProvider.read(fdRead, posInFileRead, buffer.buffer, 0, contents.byteLength); - assert.equal(buffer.slice(0, contents.byteLength).toString(), 'Hello World'); + assert.strictEqual(buffer.slice(0, contents.byteLength).toString(), 'Hello World'); posInFileRead += contents.byteLength; await fileProvider.write(fdWrite, 6, contents.buffer, 0, contents.byteLength); await fileProvider.read(fdRead, 0, buffer.buffer, 0, 11); - assert.equal(buffer.slice(0, 11).toString(), 'Lorem Hello'); + assert.strictEqual(buffer.slice(0, 11).toString(), 'Lorem Hello'); await fileProvider.write(fdWrite, posInFileWrite, contents.buffer, 0, contents.byteLength); posInFileWrite += contents.byteLength; await fileProvider.read(fdRead, posInFileWrite - contents.byteLength, buffer.buffer, 0, contents.byteLength); - assert.equal(buffer.slice(0, contents.byteLength).toString(), 'Hello World'); + assert.strictEqual(buffer.slice(0, contents.byteLength).toString(), 'Hello World'); await fileProvider.close(fdWrite); await fileProvider.close(fdRead); diff --git a/src/vs/platform/files/test/electron-browser/normalizer.test.ts b/src/vs/platform/files/test/electron-browser/normalizer.test.ts index 98aa1162c..df672c5f0 100644 --- a/src/vs/platform/files/test/electron-browser/normalizer.test.ts +++ b/src/vs/platform/files/test/electron-browser/normalizer.test.ts @@ -64,7 +64,7 @@ suite('Normalizer', () => { watch.onDidFilesChange(e => { assert.ok(e); - assert.equal(e.changes.length, 3); + assert.strictEqual(e.changes.length, 3); assert.ok(e.contains(added, FileChangeType.ADDED)); assert.ok(e.contains(updated, FileChangeType.UPDATED)); assert.ok(e.contains(deleted, FileChangeType.DELETED)); @@ -103,7 +103,7 @@ suite('Normalizer', () => { watch.onDidFilesChange(e => { assert.ok(e); - assert.equal(e.changes.length, 5); + assert.strictEqual(e.changes.length, 5); assert.ok(e.contains(deletedFolderA, FileChangeType.DELETED)); assert.ok(e.contains(deletedFolderB, FileChangeType.DELETED)); @@ -133,7 +133,7 @@ suite('Normalizer', () => { watch.onDidFilesChange(e => { assert.ok(e); - assert.equal(e.changes.length, 1); + assert.strictEqual(e.changes.length, 1); assert.ok(e.contains(unrelated, FileChangeType.UPDATED)); @@ -158,7 +158,7 @@ suite('Normalizer', () => { watch.onDidFilesChange(e => { assert.ok(e); - assert.equal(e.changes.length, 2); + assert.strictEqual(e.changes.length, 2); assert.ok(e.contains(deleted, FileChangeType.UPDATED)); assert.ok(e.contains(unrelated, FileChangeType.UPDATED)); @@ -184,7 +184,7 @@ suite('Normalizer', () => { watch.onDidFilesChange(e => { assert.ok(e); - assert.equal(e.changes.length, 2); + assert.strictEqual(e.changes.length, 2); assert.ok(e.contains(created, FileChangeType.ADDED)); assert.ok(!e.contains(created, FileChangeType.UPDATED)); @@ -213,7 +213,7 @@ suite('Normalizer', () => { watch.onDidFilesChange(e => { assert.ok(e); - assert.equal(e.changes.length, 2); + assert.strictEqual(e.changes.length, 2); assert.ok(e.contains(deleted, FileChangeType.DELETED)); assert.ok(!e.contains(updated, FileChangeType.UPDATED)); diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index 0fd23fd50..00f0cb9d9 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -132,15 +132,31 @@ export class InstantiationService implements IInstantiationService { private _getOrCreateServiceInstance(id: ServiceIdentifier, _trace: Trace): T { let thing = this._getServiceInstanceOrDescriptor(id); if (thing instanceof SyncDescriptor) { - return this._createAndCacheServiceInstance(id, thing, _trace.branch(id, true)); + return this._safeCreateAndCacheServiceInstance(id, thing, _trace.branch(id, true)); } else { _trace.branch(id, false); return thing; } } + private readonly _activeInstantiations = new Set>(); + + + private _safeCreateAndCacheServiceInstance(id: ServiceIdentifier, desc: SyncDescriptor, _trace: Trace): T { + if (this._activeInstantiations.has(id)) { + throw new Error(`illegal state - RECURSIVELY instantiating service '${id}'`); + } + this._activeInstantiations.add(id); + try { + return this._createAndCacheServiceInstance(id, desc, _trace); + } finally { + this._activeInstantiations.delete(id); + } + } + private _createAndCacheServiceInstance(id: ServiceIdentifier, desc: SyncDescriptor, _trace: Trace): T { - type Triple = { id: ServiceIdentifier, desc: SyncDescriptor, _trace: Trace }; + + type Triple = { id: ServiceIdentifier, desc: SyncDescriptor, _trace: Trace; }; const graph = new Graph(data => data.id.toString()); let cycleCount = 0; @@ -195,7 +211,6 @@ export class InstantiationService implements IInstantiationService { graph.removeNode(data); } } - return this._getServiceInstanceOrDescriptor(id); } diff --git a/src/vs/platform/ipc/electron-browser/sharedProcessService.ts b/src/vs/platform/ipc/electron-browser/sharedProcessService.ts index 59fb5a6bd..60299f96e 100644 --- a/src/vs/platform/ipc/electron-browser/sharedProcessService.ts +++ b/src/vs/platform/ipc/electron-browser/sharedProcessService.ts @@ -4,7 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { Event } from 'vs/base/common/event'; +import { IpcRendererEvent } from 'vs/base/parts/sandbox/electron-sandbox/electronTypes'; +import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { Client as MessagePortClient } from 'vs/base/parts/ipc/common/ipc.mp'; +import { IChannel, IServerChannel, getDelayedChannel } from 'vs/base/parts/ipc/common/ipc'; +import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; +import { generateUuid } from 'vs/base/common/uuid'; +import { ILogService } from 'vs/platform/log/common/log'; +import { Disposable } from 'vs/base/common/lifecycle'; export const ISharedProcessService = createDecorator('sharedProcessService'); @@ -14,7 +22,47 @@ export interface ISharedProcessService { getChannel(channelName: string): IChannel; registerChannel(channelName: string, channel: IServerChannel): void; - - whenSharedProcessReady(): Promise; - toggleSharedProcessWindow(): Promise; +} + +export class SharedProcessService extends Disposable implements ISharedProcessService { + + declare readonly _serviceBrand: undefined; + + private readonly withSharedProcessConnection: Promise; + + constructor( + @INativeHostService private readonly nativeHostService: INativeHostService, + @ILogService private readonly logService: ILogService + ) { + super(); + + this.withSharedProcessConnection = this.connect(); + } + + private async connect(): Promise { + this.logService.trace('Renderer->SharedProcess#connect'); + + // Ask to create message channel inside the window + // and send over a UUID to correlate the response + const nonce = generateUuid(); + ipcRenderer.send('vscode:createSharedProcessMessageChannel', nonce); + + // Wait until the main side has returned the `MessagePort` + // We need to filter by the `nonce` to ensure we listen + // to the right response. + const onMessageChannelResult = Event.fromNodeEventEmitter<{ nonce: string, port: MessagePort }>(ipcRenderer, 'vscode:createSharedProcessMessageChannelResult', (e: IpcRendererEvent, nonce: string) => ({ nonce, port: e.ports[0] })); + const { port } = await Event.toPromise(Event.once(Event.filter(onMessageChannelResult, e => e.nonce === nonce))); + + this.logService.trace('Renderer->SharedProcess#connect: connection established'); + + return this._register(new MessagePortClient(port, `window:${this.nativeHostService.windowId}`)); + } + + getChannel(channelName: string): IChannel { + return getDelayedChannel(this.withSharedProcessConnection.then(connection => connection.getChannel(channelName))); + } + + registerChannel(channelName: string, channel: IServerChannel): void { + this.withSharedProcessConnection.then(connection => connection.registerChannel(channelName, channel)); + } } diff --git a/src/vs/platform/ipc/electron-main/sharedProcessMainService.ts b/src/vs/platform/ipc/electron-main/sharedProcessMainService.ts deleted file mode 100644 index 97decd56a..000000000 --- a/src/vs/platform/ipc/electron-main/sharedProcessMainService.ts +++ /dev/null @@ -1,36 +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 { createDecorator } from 'vs/platform/instantiation/common/instantiation'; - -export const ISharedProcessMainService = createDecorator('sharedProcessMainService'); - -export interface ISharedProcessMainService { - - readonly _serviceBrand: undefined; - - whenSharedProcessReady(): Promise; - toggleSharedProcessWindow(): Promise; -} - -export interface ISharedProcess { - whenReady(): Promise; - toggle(): void; -} - -export class SharedProcessMainService implements ISharedProcessMainService { - - declare readonly _serviceBrand: undefined; - - constructor(private sharedProcess: ISharedProcess) { } - - whenSharedProcessReady(): Promise { - return this.sharedProcess.whenReady(); - } - - async toggleSharedProcessWindow(): Promise { - return this.sharedProcess.toggle(); - } -} diff --git a/src/vs/platform/ipc/electron-sandbox/mainProcessService.ts b/src/vs/platform/ipc/electron-sandbox/mainProcessService.ts index b2c78a332..2f326523c 100644 --- a/src/vs/platform/ipc/electron-sandbox/mainProcessService.ts +++ b/src/vs/platform/ipc/electron-sandbox/mainProcessService.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { Client } from 'vs/base/parts/ipc/electron-sandbox/ipc.electron-sandbox'; +import { IChannel, IServerChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; +import { Client as IPCElectronClient } from 'vs/base/parts/ipc/electron-sandbox/ipc.electron'; import { Disposable } from 'vs/base/common/lifecycle'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { Server as MessagePortServer } from 'vs/base/parts/ipc/electron-sandbox/ipc.mp'; export const IMainProcessService = createDecorator('mainProcessService'); @@ -19,18 +20,21 @@ export interface IMainProcessService { registerChannel(channelName: string, channel: IServerChannel): void; } -export class MainProcessService extends Disposable implements IMainProcessService { +/** + * An implementation of `IMainProcessService` that leverages Electron's IPC. + */ +export class ElectronIPCMainProcessService extends Disposable implements IMainProcessService { declare readonly _serviceBrand: undefined; - private mainProcessConnection: Client; + private mainProcessConnection: IPCElectronClient; constructor( windowId: number ) { super(); - this.mainProcessConnection = this._register(new Client(`window:${windowId}`)); + this.mainProcessConnection = this._register(new IPCElectronClient(`window:${windowId}`)); } getChannel(channelName: string): IChannel { @@ -41,3 +45,24 @@ export class MainProcessService extends Disposable implements IMainProcessServic this.mainProcessConnection.registerChannel(channelName, channel); } } + +/** + * An implementation of `IMainProcessService` that leverages MessagePorts. + */ +export class MessagePortMainProcessService implements IMainProcessService { + + declare readonly _serviceBrand: undefined; + + constructor( + private server: MessagePortServer, + private router: StaticRouter + ) { } + + getChannel(channelName: string): IChannel { + return this.server.getChannel(channelName, this.router); + } + + registerChannel(channelName: string, channel: IServerChannel): void { + this.server.registerChannel(channelName, channel); + } +} diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/common/issue.ts index e981a34d5..fc36369c6 100644 --- a/src/vs/platform/issue/common/issue.ts +++ b/src/vs/platform/issue/common/issue.ts @@ -56,6 +56,7 @@ export interface IssueReporterData extends WindowData { issueType?: IssueType; extensionId?: string; experiments?: string; + githubAccessToken: string; readonly issueTitle?: string; readonly issueBody?: string; } @@ -72,7 +73,6 @@ export interface IssueReporterFeatures { export interface ProcessExplorerStyles extends WindowStyles { hoverBackground?: string; hoverForeground?: string; - highlightForeground?: string; } export interface ProcessExplorerData extends WindowData { diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index da98e2f94..994f4559d 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -17,7 +17,7 @@ import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; import { IWindowState } from 'vs/platform/windows/electron-main/windows'; import { listProcesses } from 'vs/base/node/ps'; -import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; +import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { zoomLevelToZoomFactor } from 'vs/platform/windows/common/windows'; import { FileAccess } from 'vs/base/common/network'; @@ -185,120 +185,118 @@ export class IssueMainService implements ICommonIssueService { } } - openReporter(data: IssueReporterData): Promise { - return new Promise(_ => { - if (!this._issueWindow) { - this._issueParentWindow = BrowserWindow.getFocusedWindow(); - if (this._issueParentWindow) { - const position = this.getWindowPosition(this._issueParentWindow, 700, 800); + async openReporter(data: IssueReporterData): Promise { + if (!this._issueWindow) { + this._issueParentWindow = BrowserWindow.getFocusedWindow(); + if (this._issueParentWindow) { + const position = this.getWindowPosition(this._issueParentWindow, 700, 800); - this._issueWindow = new BrowserWindow({ - fullscreen: false, - width: position.width, - height: position.height, - minWidth: 300, - minHeight: 200, - x: position.x, - y: position.y, - title: localize('issueReporter', "Issue Reporter"), - backgroundColor: data.styles.backgroundColor || DEFAULT_BACKGROUND_COLOR, - webPreferences: { - preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, - enableWebSQL: false, - enableRemoteModule: false, - spellcheck: false, - nativeWindowOpen: true, - zoomFactor: zoomLevelToZoomFactor(data.zoomLevel), - sandbox: true, - contextIsolation: true - } - }); + this._issueWindow = new BrowserWindow({ + fullscreen: false, + width: position.width, + height: position.height, + minWidth: 300, + minHeight: 200, + x: position.x, + y: position.y, + title: localize('issueReporter', "Issue Reporter"), + backgroundColor: data.styles.backgroundColor || DEFAULT_BACKGROUND_COLOR, + webPreferences: { + preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, + v8CacheOptions: 'bypassHeatCheck', + enableWebSQL: false, + enableRemoteModule: false, + spellcheck: false, + nativeWindowOpen: true, + zoomFactor: zoomLevelToZoomFactor(data.zoomLevel), + sandbox: true, + contextIsolation: true + } + }); - this._issueWindow.setMenuBarVisibility(false); // workaround for now, until a menu is implemented + this._issueWindow.setMenuBarVisibility(false); // workaround for now, until a menu is implemented - // Modified when testing UI - const features: IssueReporterFeatures = {}; + // Modified when testing UI + const features: IssueReporterFeatures = {}; - this.logService.trace('issueService#openReporter: opening issue reporter'); - this._issueWindow.loadURL(this.getIssueReporterPath(data, features)); + this.logService.trace('issueService#openReporter: opening issue reporter'); + this._issueWindow.loadURL(this.getIssueReporterPath(data, features)); - this._issueWindow.on('close', () => this._issueWindow = null); + this._issueWindow.on('close', () => this._issueWindow = null); - this._issueParentWindow.on('closed', () => { - if (this._issueWindow) { - this._issueWindow.close(); - this._issueWindow = null; - } - }); - } + this._issueParentWindow.on('closed', () => { + if (this._issueWindow) { + this._issueWindow.close(); + this._issueWindow = null; + } + }); } + } - if (this._issueWindow) { - this._issueWindow.focus(); - } - }); + if (this._issueWindow) { + this._issueWindow.focus(); + } } - openProcessExplorer(data: ProcessExplorerData): Promise { - return new Promise(_ => { - // Create as singleton - if (!this._processExplorerWindow) { - this._processExplorerParentWindow = BrowserWindow.getFocusedWindow(); - if (this._processExplorerParentWindow) { - const position = this.getWindowPosition(this._processExplorerParentWindow, 800, 500); - this._processExplorerWindow = new BrowserWindow({ - skipTaskbar: true, - resizable: true, - fullscreen: false, - width: position.width, - height: position.height, - minWidth: 300, - minHeight: 200, - x: position.x, - y: position.y, - backgroundColor: data.styles.backgroundColor, - title: localize('processExplorer', "Process Explorer"), - webPreferences: { - preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, - enableWebSQL: false, - enableRemoteModule: false, - spellcheck: false, - nativeWindowOpen: true, - zoomFactor: zoomLevelToZoomFactor(data.zoomLevel), - sandbox: true, - contextIsolation: true - } - }); + async openProcessExplorer(data: ProcessExplorerData): Promise { + // Create as singleton + if (!this._processExplorerWindow) { + this._processExplorerParentWindow = BrowserWindow.getFocusedWindow(); + if (this._processExplorerParentWindow) { + const position = this.getWindowPosition(this._processExplorerParentWindow, 800, 500); + this._processExplorerWindow = new BrowserWindow({ + skipTaskbar: true, + resizable: true, + fullscreen: false, + width: position.width, + height: position.height, + minWidth: 300, + minHeight: 200, + x: position.x, + y: position.y, + backgroundColor: data.styles.backgroundColor, + title: localize('processExplorer', "Process Explorer"), + webPreferences: { + preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, + v8CacheOptions: 'bypassHeatCheck', + enableWebSQL: false, + enableRemoteModule: false, + spellcheck: false, + nativeWindowOpen: true, + zoomFactor: zoomLevelToZoomFactor(data.zoomLevel), + sandbox: true, + contextIsolation: true + } + }); - this._processExplorerWindow.setMenuBarVisibility(false); + this._processExplorerWindow.setMenuBarVisibility(false); - const windowConfiguration = { - appRoot: this.environmentService.appRoot, - windowId: this._processExplorerWindow.id, - userEnv: this.userEnv, - machineId: this.machineId, - data - }; + const windowConfiguration = { + appRoot: this.environmentService.appRoot, + windowId: this._processExplorerWindow.id, + userEnv: this.userEnv, + machineId: this.machineId, + data + }; - this._processExplorerWindow.loadURL( - toWindowUrl('vs/code/electron-sandbox/processExplorer/processExplorer.html', windowConfiguration)); + this._processExplorerWindow.loadURL( + toWindowUrl('vs/code/electron-sandbox/processExplorer/processExplorer.html', windowConfiguration)); - this._processExplorerWindow.on('close', () => this._processExplorerWindow = null); + this._processExplorerWindow.on('close', () => this._processExplorerWindow = null); - this._processExplorerParentWindow.on('close', () => { - if (this._processExplorerWindow) { - this._processExplorerWindow.close(); - this._processExplorerWindow = null; - } - }); - } + this._processExplorerParentWindow.on('close', () => { + if (this._processExplorerWindow) { + this._processExplorerWindow.close(); + this._processExplorerWindow = null; + } + }); } + } - // Focus - if (this._processExplorerWindow) { - this._processExplorerWindow.focus(); - } - }); + // Focus + if (this._processExplorerWindow) { + this._processExplorerWindow.focus(); + } } public async getSystemStatus(): Promise { @@ -414,7 +412,7 @@ export class IssueMainService implements ICommonIssueService { }, product: { nameShort: product.nameShort, - version: product.version, + version: !!product.darwinUniversalAssetId ? `${product.version} (Universal)` : product.version, commit: product.commit, date: product.date, reportIssueUrl: product.reportIssueUrl @@ -436,7 +434,7 @@ function toWindowUrl(modulePathToHtml: string, windowConfiguration: T): strin } return FileAccess - ._asCodeFileUri(modulePathToHtml, require) + .asBrowserUri(modulePathToHtml, require, true) .with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` }) .toString(true); } diff --git a/src/vs/platform/keybinding/common/keybindingResolver.ts b/src/vs/platform/keybinding/common/keybindingResolver.ts index 3582ce6d6..ca31e62e5 100644 --- a/src/vs/platform/keybinding/common/keybindingResolver.ts +++ b/src/vs/platform/keybinding/common/keybindingResolver.ts @@ -3,9 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isNonEmptyArray } from 'vs/base/common/arrays'; -import { MenuRegistry } from 'vs/platform/actions/common/actions'; -import { CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; import { IContext, ContextKeyExpression, ContextKeyExprType } from 'vs/platform/contextkey/common/contextkey'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; @@ -340,39 +337,6 @@ export class KeybindingResolver { } return rules.evaluate(context); } - - public static getAllUnboundCommands(boundCommands: Map): string[] { - const unboundCommands: string[] = []; - const seenMap: Map = new Map(); - const addCommand = (id: string, includeCommandWithArgs: boolean) => { - if (seenMap.has(id)) { - return; - } - seenMap.set(id, true); - if (id[0] === '_' || id.indexOf('vscode.') === 0) { // private command - return; - } - if (boundCommands.get(id) === true) { - return; - } - if (!includeCommandWithArgs) { - const command = CommandsRegistry.getCommand(id); - if (command && typeof command.description === 'object' - && isNonEmptyArray((command.description).args)) { // command with args - return; - } - } - unboundCommands.push(id); - }; - for (const id of MenuRegistry.getCommands().keys()) { - addCommand(id, true); - } - for (const id of CommandsRegistry.getCommands().keys()) { - addCommand(id, false); - } - - return unboundCommands; - } } function printWhenExplanation(when: ContextKeyExpression | undefined): string { diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index cbbb54344..d627e6626 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -211,13 +211,13 @@ suite('AbstractKeybindingService', () => { // send Ctrl/Cmd + K let shouldPreventDefault = kbService.testDispatch(KeyMod.CtrlCmd | KeyCode.KEY_K); - assert.equal(shouldPreventDefault, true); - assert.deepEqual(executeCommandCalls, []); - assert.deepEqual(showMessageCalls, []); - assert.deepEqual(statusMessageCalls, [ + assert.strictEqual(shouldPreventDefault, true); + assert.deepStrictEqual(executeCommandCalls, []); + assert.deepStrictEqual(showMessageCalls, []); + assert.deepStrictEqual(statusMessageCalls, [ `(${toUsLabel(KeyMod.CtrlCmd | KeyCode.KEY_K)}) was pressed. Waiting for second key of chord...` ]); - assert.deepEqual(statusMessageCallsDisposed, []); + assert.deepStrictEqual(statusMessageCallsDisposed, []); executeCommandCalls = []; showMessageCalls = []; statusMessageCalls = []; @@ -225,13 +225,13 @@ suite('AbstractKeybindingService', () => { // send backspace shouldPreventDefault = kbService.testDispatch(KeyCode.Backspace); - assert.equal(shouldPreventDefault, true); - assert.deepEqual(executeCommandCalls, []); - assert.deepEqual(showMessageCalls, []); - assert.deepEqual(statusMessageCalls, [ + assert.strictEqual(shouldPreventDefault, true); + assert.deepStrictEqual(executeCommandCalls, []); + assert.deepStrictEqual(showMessageCalls, []); + assert.deepStrictEqual(statusMessageCalls, [ `The key combination (${toUsLabel(KeyMod.CtrlCmd | KeyCode.KEY_K)}, ${toUsLabel(KeyCode.Backspace)}) is not a command.` ]); - assert.deepEqual(statusMessageCallsDisposed, [ + assert.deepStrictEqual(statusMessageCallsDisposed, [ `(${toUsLabel(KeyMod.CtrlCmd | KeyCode.KEY_K)}) was pressed. Waiting for second key of chord...` ]); executeCommandCalls = []; @@ -241,14 +241,14 @@ suite('AbstractKeybindingService', () => { // send backspace shouldPreventDefault = kbService.testDispatch(KeyCode.Backspace); - assert.equal(shouldPreventDefault, true); - assert.deepEqual(executeCommandCalls, [{ + assert.strictEqual(shouldPreventDefault, true); + assert.deepStrictEqual(executeCommandCalls, [{ commandId: 'simpleCommand', args: [null] }]); - assert.deepEqual(showMessageCalls, []); - assert.deepEqual(statusMessageCalls, []); - assert.deepEqual(statusMessageCallsDisposed, []); + assert.deepStrictEqual(showMessageCalls, []); + assert.deepStrictEqual(statusMessageCalls, []); + assert.deepStrictEqual(statusMessageCallsDisposed, []); executeCommandCalls = []; showMessageCalls = []; statusMessageCalls = []; @@ -273,11 +273,11 @@ suite('AbstractKeybindingService', () => { function assertIsIgnored(keybinding: number): void { let shouldPreventDefault = kbService.testDispatch(keybinding); - assert.equal(shouldPreventDefault, false); - assert.deepEqual(executeCommandCalls, []); - assert.deepEqual(showMessageCalls, []); - assert.deepEqual(statusMessageCalls, []); - assert.deepEqual(statusMessageCallsDisposed, []); + assert.strictEqual(shouldPreventDefault, false); + assert.deepStrictEqual(executeCommandCalls, []); + assert.deepStrictEqual(showMessageCalls, []); + assert.deepStrictEqual(statusMessageCalls, []); + assert.deepStrictEqual(statusMessageCallsDisposed, []); executeCommandCalls = []; showMessageCalls = []; statusMessageCalls = []; @@ -310,14 +310,14 @@ suite('AbstractKeybindingService', () => { key1: true }); let shouldPreventDefault = kbService.testDispatch(KeyMod.CtrlCmd | KeyCode.KEY_K); - assert.equal(shouldPreventDefault, true); - assert.deepEqual(executeCommandCalls, [{ + assert.strictEqual(shouldPreventDefault, true); + assert.deepStrictEqual(executeCommandCalls, [{ commandId: 'simpleCommand', args: [null] }]); - assert.deepEqual(showMessageCalls, []); - assert.deepEqual(statusMessageCalls, []); - assert.deepEqual(statusMessageCallsDisposed, []); + assert.deepStrictEqual(showMessageCalls, []); + assert.deepStrictEqual(statusMessageCalls, []); + assert.deepStrictEqual(statusMessageCallsDisposed, []); executeCommandCalls = []; showMessageCalls = []; statusMessageCalls = []; @@ -326,13 +326,13 @@ suite('AbstractKeybindingService', () => { // send Ctrl/Cmd + K currentContextValue = createContext({}); shouldPreventDefault = kbService.testDispatch(KeyMod.CtrlCmd | KeyCode.KEY_K); - assert.equal(shouldPreventDefault, true); - assert.deepEqual(executeCommandCalls, []); - assert.deepEqual(showMessageCalls, []); - assert.deepEqual(statusMessageCalls, [ + assert.strictEqual(shouldPreventDefault, true); + assert.deepStrictEqual(executeCommandCalls, []); + assert.deepStrictEqual(showMessageCalls, []); + assert.deepStrictEqual(statusMessageCalls, [ `(${toUsLabel(KeyMod.CtrlCmd | KeyCode.KEY_K)}) was pressed. Waiting for second key of chord...` ]); - assert.deepEqual(statusMessageCallsDisposed, []); + assert.deepStrictEqual(statusMessageCallsDisposed, []); executeCommandCalls = []; showMessageCalls = []; statusMessageCalls = []; @@ -341,14 +341,14 @@ suite('AbstractKeybindingService', () => { // send Ctrl/Cmd + X currentContextValue = createContext({}); shouldPreventDefault = kbService.testDispatch(KeyMod.CtrlCmd | KeyCode.KEY_X); - assert.equal(shouldPreventDefault, true); - assert.deepEqual(executeCommandCalls, [{ + assert.strictEqual(shouldPreventDefault, true); + assert.deepStrictEqual(executeCommandCalls, [{ commandId: 'chordCommand', args: [null] }]); - assert.deepEqual(showMessageCalls, []); - assert.deepEqual(statusMessageCalls, []); - assert.deepEqual(statusMessageCallsDisposed, [ + assert.deepStrictEqual(showMessageCalls, []); + assert.deepStrictEqual(statusMessageCalls, []); + assert.deepStrictEqual(statusMessageCallsDisposed, [ `(${toUsLabel(KeyMod.CtrlCmd | KeyCode.KEY_K)}) was pressed. Waiting for second key of chord...` ]); executeCommandCalls = []; @@ -370,14 +370,14 @@ suite('AbstractKeybindingService', () => { // send Ctrl/Cmd + K currentContextValue = createContext({}); let shouldPreventDefault = kbService.testDispatch(KeyMod.CtrlCmd | KeyCode.KEY_K); - assert.equal(shouldPreventDefault, true); - assert.deepEqual(executeCommandCalls, [{ + assert.strictEqual(shouldPreventDefault, true); + assert.deepStrictEqual(executeCommandCalls, [{ commandId: 'simpleCommand', args: [null] }]); - assert.deepEqual(showMessageCalls, []); - assert.deepEqual(statusMessageCalls, []); - assert.deepEqual(statusMessageCallsDisposed, []); + assert.deepStrictEqual(showMessageCalls, []); + assert.deepStrictEqual(statusMessageCalls, []); + assert.deepStrictEqual(statusMessageCallsDisposed, []); executeCommandCalls = []; showMessageCalls = []; statusMessageCalls = []; @@ -388,14 +388,14 @@ suite('AbstractKeybindingService', () => { key1: true }); shouldPreventDefault = kbService.testDispatch(KeyMod.CtrlCmd | KeyCode.KEY_K); - assert.equal(shouldPreventDefault, true); - assert.deepEqual(executeCommandCalls, [{ + assert.strictEqual(shouldPreventDefault, true); + assert.deepStrictEqual(executeCommandCalls, [{ commandId: 'simpleCommand', args: [null] }]); - assert.deepEqual(showMessageCalls, []); - assert.deepEqual(statusMessageCalls, []); - assert.deepEqual(statusMessageCallsDisposed, []); + assert.deepStrictEqual(showMessageCalls, []); + assert.deepStrictEqual(statusMessageCalls, []); + assert.deepStrictEqual(statusMessageCallsDisposed, []); executeCommandCalls = []; showMessageCalls = []; statusMessageCalls = []; @@ -406,11 +406,11 @@ suite('AbstractKeybindingService', () => { key1: true }); shouldPreventDefault = kbService.testDispatch(KeyMod.CtrlCmd | KeyCode.KEY_X); - assert.equal(shouldPreventDefault, false); - assert.deepEqual(executeCommandCalls, []); - assert.deepEqual(showMessageCalls, []); - assert.deepEqual(statusMessageCalls, []); - assert.deepEqual(statusMessageCallsDisposed, []); + assert.strictEqual(shouldPreventDefault, false); + assert.deepStrictEqual(executeCommandCalls, []); + assert.deepStrictEqual(showMessageCalls, []); + assert.deepStrictEqual(statusMessageCalls, []); + assert.deepStrictEqual(statusMessageCallsDisposed, []); executeCommandCalls = []; showMessageCalls = []; statusMessageCalls = []; @@ -428,14 +428,14 @@ suite('AbstractKeybindingService', () => { // send Ctrl/Cmd + K currentContextValue = createContext({}); let shouldPreventDefault = kbService.testDispatch(KeyMod.CtrlCmd | KeyCode.KEY_K); - assert.equal(shouldPreventDefault, false); - assert.deepEqual(executeCommandCalls, [{ + assert.strictEqual(shouldPreventDefault, false); + assert.deepStrictEqual(executeCommandCalls, [{ commandId: 'simpleCommand', args: [null] }]); - assert.deepEqual(showMessageCalls, []); - assert.deepEqual(statusMessageCalls, []); - assert.deepEqual(statusMessageCallsDisposed, []); + assert.deepStrictEqual(showMessageCalls, []); + assert.deepStrictEqual(statusMessageCalls, []); + assert.deepStrictEqual(statusMessageCallsDisposed, []); executeCommandCalls = []; showMessageCalls = []; statusMessageCalls = []; diff --git a/src/vs/platform/keybinding/test/common/keybindingLabels.test.ts b/src/vs/platform/keybinding/test/common/keybindingLabels.test.ts index a321d9b38..f1ef51b64 100644 --- a/src/vs/platform/keybinding/test/common/keybindingLabels.test.ts +++ b/src/vs/platform/keybinding/test/common/keybindingLabels.test.ts @@ -11,7 +11,7 @@ suite('KeybindingLabels', () => { function assertUSLabel(OS: OperatingSystem, keybinding: number, expected: string): void { const usResolvedKeybinding = new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS); - assert.equal(usResolvedKeybinding.getLabel(), expected); + assert.strictEqual(usResolvedKeybinding.getLabel(), expected); } test('Windows US label', () => { @@ -116,7 +116,7 @@ suite('KeybindingLabels', () => { test('Aria label', () => { function assertAriaLabel(OS: OperatingSystem, keybinding: number, expected: string): void { const usResolvedKeybinding = new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS); - assert.equal(usResolvedKeybinding.getAriaLabel(), expected); + assert.strictEqual(usResolvedKeybinding.getAriaLabel(), expected); } assertAriaLabel(OperatingSystem.Windows, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Control+Shift+Alt+Windows+A'); @@ -127,7 +127,7 @@ suite('KeybindingLabels', () => { test('Electron Accelerator label', () => { function assertElectronAcceleratorLabel(OS: OperatingSystem, keybinding: number, expected: string | null): void { const usResolvedKeybinding = new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS); - assert.equal(usResolvedKeybinding.getElectronAccelerator(), expected); + assert.strictEqual(usResolvedKeybinding.getElectronAccelerator(), expected); } assertElectronAcceleratorLabel(OperatingSystem.Windows, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Ctrl+Shift+Alt+Super+A'); @@ -154,7 +154,7 @@ suite('KeybindingLabels', () => { test('User Settings label', () => { function assertElectronAcceleratorLabel(OS: OperatingSystem, keybinding: number, expected: string): void { const usResolvedKeybinding = new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS); - assert.equal(usResolvedKeybinding.getUserSettingsLabel(), expected); + assert.strictEqual(usResolvedKeybinding.getUserSettingsLabel(), expected); } assertElectronAcceleratorLabel(OperatingSystem.Windows, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'ctrl+shift+alt+win+a'); diff --git a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts index 2820e0ea4..33472df8a 100644 --- a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts +++ b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts @@ -43,12 +43,12 @@ suite('KeybindingResolver', () => { let contextRules = ContextKeyExpr.equals('bar', 'baz'); let keybindingItem = kbItem(keybinding, 'yes', null, contextRules, true); - assert.equal(KeybindingResolver.contextMatchesRules(createContext({ bar: 'baz' }), contextRules), true); - assert.equal(KeybindingResolver.contextMatchesRules(createContext({ bar: 'bz' }), contextRules), false); + assert.strictEqual(KeybindingResolver.contextMatchesRules(createContext({ bar: 'baz' }), contextRules), true); + assert.strictEqual(KeybindingResolver.contextMatchesRules(createContext({ bar: 'bz' }), contextRules), false); let resolver = new KeybindingResolver([keybindingItem], [], () => { }); - assert.equal(resolver.resolve(createContext({ bar: 'baz' }), null, getDispatchStr(runtimeKeybinding))!.commandId, 'yes'); - assert.equal(resolver.resolve(createContext({ bar: 'bz' }), null, getDispatchStr(runtimeKeybinding)), null); + assert.strictEqual(resolver.resolve(createContext({ bar: 'baz' }), null, getDispatchStr(runtimeKeybinding))!.commandId, 'yes'); + assert.strictEqual(resolver.resolve(createContext({ bar: 'bz' }), null, getDispatchStr(runtimeKeybinding)), null); }); test('resolve key with arguments', function () { @@ -59,7 +59,7 @@ suite('KeybindingResolver', () => { let keybindingItem = kbItem(keybinding, 'yes', commandArgs, contextRules, true); let resolver = new KeybindingResolver([keybindingItem], [], () => { }); - assert.equal(resolver.resolve(createContext({ bar: 'baz' }), null, getDispatchStr(runtimeKeybinding))!.commandArgs, commandArgs); + assert.strictEqual(resolver.resolve(createContext({ bar: 'baz' }), null, getDispatchStr(runtimeKeybinding))!.commandArgs, commandArgs); }); test('KeybindingResolver.combine simple 1', function () { @@ -70,7 +70,7 @@ suite('KeybindingResolver', () => { kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), false) ]; let actual = KeybindingResolver.combine(defaults, overrides); - assert.deepEqual(actual, [ + assert.deepStrictEqual(actual, [ kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), false), ]); @@ -85,7 +85,7 @@ suite('KeybindingResolver', () => { kbItem(KeyCode.KEY_C, 'yes3', null, ContextKeyExpr.equals('3', 'c'), false) ]; let actual = KeybindingResolver.combine(defaults, overrides); - assert.deepEqual(actual, [ + assert.deepStrictEqual(actual, [ kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true), kbItem(KeyCode.KEY_C, 'yes3', null, ContextKeyExpr.equals('3', 'c'), false), @@ -101,7 +101,7 @@ suite('KeybindingResolver', () => { kbItem(KeyCode.KEY_A, '-yes1', null, ContextKeyExpr.equals('1', 'b'), false) ]; let actual = KeybindingResolver.combine(defaults, overrides); - assert.deepEqual(actual, [ + assert.deepStrictEqual(actual, [ kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) ]); @@ -116,7 +116,7 @@ suite('KeybindingResolver', () => { kbItem(KeyCode.KEY_B, '-yes1', null, ContextKeyExpr.equals('1', 'a'), false) ]; let actual = KeybindingResolver.combine(defaults, overrides); - assert.deepEqual(actual, [ + assert.deepStrictEqual(actual, [ kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) ]); @@ -131,7 +131,7 @@ suite('KeybindingResolver', () => { kbItem(KeyCode.KEY_A, '-yes1', null, ContextKeyExpr.equals('1', 'a'), false) ]; let actual = KeybindingResolver.combine(defaults, overrides); - assert.deepEqual(actual, [ + assert.deepStrictEqual(actual, [ kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) ]); }); @@ -145,7 +145,7 @@ suite('KeybindingResolver', () => { kbItem(0, '-yes1', null, ContextKeyExpr.equals('1', 'a'), false) ]; let actual = KeybindingResolver.combine(defaults, overrides); - assert.deepEqual(actual, [ + assert.deepStrictEqual(actual, [ kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) ]); }); @@ -159,7 +159,7 @@ suite('KeybindingResolver', () => { kbItem(KeyCode.KEY_A, '-yes1', null, null!, false) ]; let actual = KeybindingResolver.combine(defaults, overrides); - assert.deepEqual(actual, [ + assert.deepStrictEqual(actual, [ kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) ]); }); @@ -173,7 +173,7 @@ suite('KeybindingResolver', () => { kbItem(0, '-yes1', null, null!, false) ]; let actual = KeybindingResolver.combine(defaults, overrides); - assert.deepEqual(actual, [ + assert.deepStrictEqual(actual, [ kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) ]); }); @@ -187,17 +187,17 @@ suite('KeybindingResolver', () => { kbItem(KeyCode.KEY_A, '-yes1', null, null!, false) ]; let actual = KeybindingResolver.combine(defaults, overrides); - assert.deepEqual(actual, [ + assert.deepStrictEqual(actual, [ kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) ]); }); test('contextIsEntirelyIncluded', () => { const assertIsIncluded = (a: string | null, b: string | null) => { - assert.equal(KeybindingResolver.whenIsEntirelyIncluded(ContextKeyExpr.deserialize(a), ContextKeyExpr.deserialize(b)), true); + assert.strictEqual(KeybindingResolver.whenIsEntirelyIncluded(ContextKeyExpr.deserialize(a), ContextKeyExpr.deserialize(b)), true); }; const assertIsNotIncluded = (a: string | null, b: string | null) => { - assert.equal(KeybindingResolver.whenIsEntirelyIncluded(ContextKeyExpr.deserialize(a), ContextKeyExpr.deserialize(b)), false); + assert.strictEqual(KeybindingResolver.whenIsEntirelyIncluded(ContextKeyExpr.deserialize(a), ContextKeyExpr.deserialize(b)), false); }; assertIsIncluded('key1', null); @@ -314,11 +314,11 @@ suite('KeybindingResolver', () => { let testKey = (commandId: string, expectedKeys: number[]) => { // Test lookup let lookupResult = resolver.lookupKeybindings(commandId); - assert.equal(lookupResult.length, expectedKeys.length, 'Length mismatch @ commandId ' + commandId + '; GOT: ' + JSON.stringify(lookupResult, null, '\t')); + assert.strictEqual(lookupResult.length, expectedKeys.length, 'Length mismatch @ commandId ' + commandId + '; GOT: ' + JSON.stringify(lookupResult, null, '\t')); for (let i = 0, len = lookupResult.length; i < len; i++) { const expected = new USLayoutResolvedKeybinding(createKeybinding(expectedKeys[i], OS)!, OS); - assert.equal(lookupResult[i].resolvedKeybinding!.getUserSettingsLabel(), expected.getUserSettingsLabel(), 'value mismatch @ commandId ' + commandId); + assert.strictEqual(lookupResult[i].resolvedKeybinding!.getUserSettingsLabel(), expected.getUserSettingsLabel(), 'value mismatch @ commandId ' + commandId); } }; @@ -333,14 +333,14 @@ suite('KeybindingResolver', () => { // if it's the final part, then we should find a valid command, // and there should not be a chord. assert.ok(result !== null, `Enters chord for ${commandId} at part ${i}`); - assert.equal(result!.commandId, commandId, `Enters chord for ${commandId} at part ${i}`); - assert.equal(result!.enterChord, false, `Enters chord for ${commandId} at part ${i}`); + assert.strictEqual(result!.commandId, commandId, `Enters chord for ${commandId} at part ${i}`); + assert.strictEqual(result!.enterChord, false, `Enters chord for ${commandId} at part ${i}`); } else { // if it's not the final part, then we should not find a valid command, // and there should be a chord. assert.ok(result !== null, `Enters chord for ${commandId} at part ${i}`); - assert.equal(result!.commandId, null, `Enters chord for ${commandId} at part ${i}`); - assert.equal(result!.enterChord, true, `Enters chord for ${commandId} at part ${i}`); + assert.strictEqual(result!.commandId, null, `Enters chord for ${commandId} at part ${i}`); + assert.strictEqual(result!.enterChord, true, `Enters chord for ${commandId} at part ${i}`); } previousPart = part; } diff --git a/src/vs/platform/label/common/label.ts b/src/vs/platform/label/common/label.ts index d1739d0a6..e9c0c56e7 100644 --- a/src/vs/platform/label/common/label.ts +++ b/src/vs/platform/label/common/label.ts @@ -8,7 +8,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { IWorkspace } from 'vs/platform/workspace/common/workspace'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; export const ILabelService = createDecorator('labelService'); @@ -23,7 +23,7 @@ export interface ILabelService { */ getUriLabel(resource: URI, options?: { relative?: boolean, noPrefix?: boolean, endWithSeparator?: boolean }): string; getUriBasenameLabel(resource: URI): string; - getWorkspaceLabel(workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWorkspace), options?: { verbose: boolean }): string; + getWorkspaceLabel(workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI | IWorkspace), options?: { verbose: boolean }): string; getHostLabel(scheme: string, authority?: string): string; getSeparator(scheme: string, authority?: string): '/' | '\\'; diff --git a/src/vs/platform/launch/electron-main/launchMainService.ts b/src/vs/platform/launch/electron-main/launchMainService.ts index 9b3d65663..0ede527e5 100644 --- a/src/vs/platform/launch/electron-main/launchMainService.ts +++ b/src/vs/platform/launch/electron-main/launchMainService.ts @@ -9,10 +9,9 @@ import { IProcessEnvironment, isMacintosh } from 'vs/base/common/platform'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWindowSettings } from 'vs/platform/windows/common/windows'; -import { OpenContext } from 'vs/platform/windows/node/window'; -import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; +import { IWindowsMainService, ICodeWindow, OpenContext } from 'vs/platform/windows/electron-main/windows'; import { whenDeleted } from 'vs/base/node/pfs'; -import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; +import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { URI } from 'vs/base/common/uri'; import { BrowserWindow, ipcMain, Event as IpcEvent, app } from 'electron'; @@ -21,6 +20,7 @@ import { IDiagnosticInfoOptions, IDiagnosticInfo, IRemoteDiagnosticInfo, IRemote import { IMainProcessInfo, IWindowInfo } from 'vs/platform/launch/node/launch'; import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; export const ID = 'launchMainService'; export const ILaunchMainService = createDecorator(ID); @@ -35,23 +35,6 @@ export interface IRemoteDiagnosticOptions { includeWorkspaceMetadata?: boolean; } -function parseOpenUrl(args: NativeParsedArgs): { uri: URI, url: string }[] { - if (args['open-url'] && args._urls && args._urls.length > 0) { - // --open-url must contain -- followed by the url(s) - // process.argv is used over args._ as args._ are resolved to file paths at this point - return coalesce(args._urls - .map(url => { - try { - return { uri: URI.parse(url), url }; - } catch (err) { - return null; - } - })); - } - - return []; -} - export interface ILaunchMainService { readonly _serviceBrand: undefined; start(args: NativeParsedArgs, userEnv: IProcessEnvironment): Promise; @@ -68,7 +51,7 @@ export class LaunchMainService implements ILaunchMainService { @ILogService private readonly logService: ILogService, @IWindowsMainService private readonly windowsMainService: IWindowsMainService, @IURLService private readonly urlService: IURLService, - @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, + @IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService, @IConfigurationService private readonly configurationService: IConfigurationService ) { } @@ -89,7 +72,7 @@ export class LaunchMainService implements ILaunchMainService { } // Check early for open-url which is handled in URL service - const urlsToOpen = parseOpenUrl(args); + const urlsToOpen = this.parseOpenUrl(args); if (urlsToOpen.length) { let whenWindowReady: Promise = Promise.resolve(); @@ -113,7 +96,24 @@ export class LaunchMainService implements ILaunchMainService { } } - private startOpenWindow(args: NativeParsedArgs, userEnv: IProcessEnvironment): Promise { + private parseOpenUrl(args: NativeParsedArgs): { uri: URI, url: string }[] { + if (args['open-url'] && args._urls && args._urls.length > 0) { + // --open-url must contain -- followed by the url(s) + // process.argv is used over args._ as args._ are resolved to file paths at this point + return coalesce(args._urls + .map(url => { + try { + return { uri: URI.parse(url), url }; + } catch (err) { + return null; + } + })); + } + + return []; + } + + private async startOpenWindow(args: NativeParsedArgs, userEnv: IProcessEnvironment): Promise { const context = isLaunchedFromCli(userEnv) ? OpenContext.CLI : OpenContext.DESKTOP; let usedWindows: ICodeWindow[] = []; @@ -205,17 +205,15 @@ export class LaunchMainService implements ILaunchMainService { whenDeleted(waitMarkerFileURI.fsPath) ]).then(() => undefined, () => undefined); } - - return Promise.resolve(undefined); } - getMainProcessId(): Promise { + async getMainProcessId(): Promise { this.logService.trace('Received request for process ID from other instance.'); - return Promise.resolve(process.pid); + return process.pid; } - getMainProcessInfo(): Promise { + async getMainProcessInfo(): Promise { this.logService.trace('Received request for main process info from other instance.'); const windows: IWindowInfo[] = []; @@ -228,18 +226,18 @@ export class LaunchMainService implements ILaunchMainService { } }); - return Promise.resolve({ + return { mainPID: process.pid, mainArguments: process.argv.slice(1), windows, screenReader: !!app.accessibilitySupportEnabled, gpuFeatureStatus: app.getGPUFeatureStatus() - }); + }; } - getRemoteDiagnostics(options: IRemoteDiagnosticOptions): Promise<(IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]> { + async getRemoteDiagnostics(options: IRemoteDiagnosticOptions): Promise<(IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]> { const windows = this.windowsMainService.getWindows(); - const promises: Promise[] = windows.map(window => { + const diagnostics: Array = await Promise.all(windows.map(window => { return new Promise((resolve) => { const remoteAuthority = window.remoteAuthority; if (remoteAuthority) { @@ -267,27 +265,26 @@ export class LaunchMainService implements ILaunchMainService { resolve(undefined); } }); - }); + })); - return Promise.all(promises).then(diagnostics => diagnostics.filter((x): x is IRemoteDiagnosticInfo | IRemoteDiagnosticError => !!x)); + return diagnostics.filter((x): x is IRemoteDiagnosticInfo | IRemoteDiagnosticError => !!x); } private getFolderURIs(window: ICodeWindow): URI[] { const folderURIs: URI[] = []; - if (window.openedFolderUri) { - folderURIs.push(window.openedFolderUri); - } else if (window.openedWorkspace) { - // workspace folders can only be shown for local workspaces - const workspaceConfigPath = window.openedWorkspace.configPath; - const resolvedWorkspace = this.workspacesMainService.resolveLocalWorkspaceSync(workspaceConfigPath); + const workspace = window.openedWorkspace; + if (isSingleFolderWorkspaceIdentifier(workspace)) { + folderURIs.push(workspace.uri); + } else if (isWorkspaceIdentifier(workspace)) { + const resolvedWorkspace = this.workspacesManagementMainService.resolveLocalWorkspaceSync(workspace.configPath); // workspace folders can only be shown for local (resolved) workspaces if (resolvedWorkspace) { const rootFolders = resolvedWorkspace.folders; rootFolders.forEach(root => { folderURIs.push(root.uri); }); } else { - //TODO: can we add the workspace file here? + //TODO@RMacfarlane: can we add the workspace file here? } } @@ -296,13 +293,14 @@ export class LaunchMainService implements ILaunchMainService { private codeWindowToInfo(window: ICodeWindow): IWindowInfo { const folderURIs = this.getFolderURIs(window); + return this.browserWindowToInfo(window.win, folderURIs, window.remoteAuthority); } - private browserWindowToInfo(win: BrowserWindow, folderURIs: URI[] = [], remoteAuthority?: string): IWindowInfo { + private browserWindowToInfo(window: BrowserWindow, folderURIs: URI[] = [], remoteAuthority?: string): IWindowInfo { return { - pid: win.webContents.getOSProcessId(), - title: win.getTitle(), + pid: window.webContents.getOSProcessId(), + title: window.getTitle(), folderURIs, remoteAuthority }; diff --git a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts index 62db3f751..6d82c1224 100644 --- a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts +++ b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts @@ -576,7 +576,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe if (window && !window.isDestroyed()) { let whenWindowClosed: Promise; if (window.webContents && !window.webContents.isDestroyed()) { - whenWindowClosed = new Promise(c => window.once('closed', c)); + whenWindowClosed = new Promise(resolve => window.once('closed', resolve)); } else { whenWindowClosed = Promise.resolve(); } diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index b553a0581..1d515e5bf 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -429,12 +429,14 @@ export interface IResourceNavigatorOptions { export interface SelectionKeyboardEvent extends KeyboardEvent { preserveFocus?: boolean; + pinned?: boolean; __forceEvent?: boolean; } -export function getSelectionKeyboardEvent(typeArg = 'keydown', preserveFocus?: boolean): SelectionKeyboardEvent { +export function getSelectionKeyboardEvent(typeArg = 'keydown', preserveFocus?: boolean, pinned?: boolean): SelectionKeyboardEvent { const e = new KeyboardEvent(typeArg); (e).preserveFocus = preserveFocus; + (e).pinned = pinned; (e).__forceEvent = true; return e; @@ -478,8 +480,9 @@ abstract class ResourceNavigator extends Disposable { const focus = this.widget.getFocus(); this.widget.setSelection(focus, event.browserEvent); - const preserveFocus = typeof (event.browserEvent as SelectionKeyboardEvent).preserveFocus === 'boolean' ? (event.browserEvent as SelectionKeyboardEvent).preserveFocus! : true; - const pinned = !preserveFocus; + const selectionKeyboardEvent = event.browserEvent as SelectionKeyboardEvent; + const preserveFocus = typeof selectionKeyboardEvent.preserveFocus === 'boolean' ? selectionKeyboardEvent.preserveFocus! : true; + const pinned = typeof selectionKeyboardEvent.pinned === 'boolean' ? selectionKeyboardEvent.pinned! : !preserveFocus; const sideBySide = false; this._open(this.getSelectedElement(), preserveFocus, pinned, sideBySide, event.browserEvent); @@ -490,8 +493,9 @@ abstract class ResourceNavigator extends Disposable { return; } - const preserveFocus = typeof (event.browserEvent as SelectionKeyboardEvent).preserveFocus === 'boolean' ? (event.browserEvent as SelectionKeyboardEvent).preserveFocus! : true; - const pinned = !preserveFocus; + const selectionKeyboardEvent = event.browserEvent as SelectionKeyboardEvent; + const preserveFocus = typeof selectionKeyboardEvent.preserveFocus === 'boolean' ? selectionKeyboardEvent.preserveFocus! : true; + const pinned = typeof selectionKeyboardEvent.pinned === 'boolean' ? selectionKeyboardEvent.pinned! : !preserveFocus; const sideBySide = false; this._open(this.getSelectedElement(), preserveFocus, pinned, sideBySide, event.browserEvent); @@ -826,7 +830,7 @@ function workbenchTreeDataPreamble(keyboardNavigationSettingKey); + const keyboardNavigation = options.simpleKeyboardNavigation || accessibilityOn ? 'simple' : configurationService.getValue(keyboardNavigationSettingKey); const horizontalScrolling = options.horizontalScrolling !== undefined ? options.horizontalScrolling : configurationService.getValue(horizontalScrollingKey); const [workbenchListOptions, disposable] = toWorkbenchListOptions(options, configurationService, keybindingService); const additionalScrollHeight = options.additionalScrollHeight; diff --git a/src/vs/platform/localizations/common/localizations.ts b/src/vs/platform/localizations/common/localizations.ts index 509c4ada8..27fa81bae 100644 --- a/src/vs/platform/localizations/common/localizations.ts +++ b/src/vs/platform/localizations/common/localizations.ts @@ -25,6 +25,8 @@ export interface ILocalizationsService { readonly onDidLanguagesChange: Event; getLanguageIds(): Promise; + + update(): Promise; } export function isValidLocalization(localization: ILocalization): boolean { diff --git a/src/vs/platform/log/node/loggerService.ts b/src/vs/platform/log/node/loggerService.ts index 7120d99d2..49fe8ad45 100644 --- a/src/vs/platform/log/node/loggerService.ts +++ b/src/vs/platform/log/node/loggerService.ts @@ -9,8 +9,8 @@ import { URI } from 'vs/base/common/uri'; import { basename, extname, dirname } from 'vs/base/common/resources'; import { Schemas } from 'vs/base/common/network'; import { FileLogService } from 'vs/platform/log/common/fileLogService'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { SpdLogService } from 'vs/platform/log/node/spdlogService'; +import { IFileService } from 'vs/platform/files/common/files'; export class LoggerService extends Disposable implements ILoggerService { @@ -20,7 +20,7 @@ export class LoggerService extends Disposable implements ILoggerService { constructor( @ILogService private logService: ILogService, - @IInstantiationService private instantiationService: IInstantiationService, + @IFileService private fileService: IFileService ) { super(); this._register(logService.onDidChangeLogLevel(level => this.loggers.forEach(logger => logger.setLevel(level)))); @@ -34,7 +34,7 @@ export class LoggerService extends Disposable implements ILoggerService { const ext = extname(resource); logger = new SpdLogService(baseName.substring(0, baseName.length - ext.length), dirname(resource).fsPath, this.logService.getLevel()); } else { - logger = this.instantiationService.createInstance(FileLogService, basename(resource), resource, this.logService.getLevel()); + logger = new FileLogService(basename(resource), resource, this.logService.getLevel(), this.fileService); } this.loggers.set(resource.toString(), logger); } diff --git a/src/vs/platform/markers/common/markerService.ts b/src/vs/platform/markers/common/markerService.ts index 537ade060..1c0c67c25 100644 --- a/src/vs/platform/markers/common/markerService.ts +++ b/src/vs/platform/markers/common/markerService.ts @@ -145,14 +145,11 @@ export class MarkerService implements IMarkerService { readonly onMarkerChanged: Event = Event.debounce(this._onMarkerChanged.event, MarkerService._debouncer, 0); private readonly _data = new DoubleResourceMap(); - private readonly _stats: MarkerStats; - - constructor() { - this._stats = new MarkerStats(this); - } + private readonly _stats = new MarkerStats(this); dispose(): void { this._stats.dispose(); + this._onMarkerChanged.dispose(); } getStatistics(): MarkerStatistics { diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 3039b01a1..32f6b799e 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -6,9 +6,8 @@ import * as nls from 'vs/nls'; import { isMacintosh, language } from 'vs/base/common/platform'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; -import { app, Menu, MenuItem, BrowserWindow, MenuItemConstructorOptions, WebContents, Event, KeyboardEvent } from 'electron'; +import { app, Menu, MenuItem, BrowserWindow, MenuItemConstructorOptions, WebContents, KeyboardEvent } from 'electron'; import { getTitleBarStyle, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, IWindowOpenable } from 'vs/platform/windows/common/windows'; -import { OpenContext } from 'vs/platform/windows/node/window'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUpdateService, StateType } from 'vs/platform/update/common/update'; @@ -16,7 +15,7 @@ import product from 'vs/platform/product/common/product'; import { RunOnceScheduler } from 'vs/base/common/async'; import { ILogService } from 'vs/platform/log/common/log'; import { mnemonicMenuLabel } from 'vs/base/common/labels'; -import { IWindowsMainService, IWindowsCountChangedEvent } from 'vs/platform/windows/electron-main/windows'; +import { IWindowsMainService, IWindowsCountChangedEvent, OpenContext } from 'vs/platform/windows/electron-main/windows'; import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; import { IMenubarData, IMenubarKeybinding, MenubarMenuItem, isMenubarMenuItemSeparator, isMenubarMenuItemSubmenu, isMenubarMenuItemAction, IMenubarMenu, isMenubarMenuItemUriAction } from 'vs/platform/menubar/common/menubar'; import { URI } from 'vs/base/common/uri'; @@ -62,7 +61,7 @@ export class Menubar { private keybindings: { [commandId: string]: IMenubarKeybinding }; - private readonly fallbackMenuHandlers: { [id: string]: (menuItem: MenuItem, browserWindow: BrowserWindow | undefined, event: Event) => void } = Object.create(null); + private readonly fallbackMenuHandlers: { [id: string]: (menuItem: MenuItem, browserWindow: BrowserWindow | undefined, event: KeyboardEvent) => void } = Object.create(null); constructor( @IUpdateService private readonly updateService: IUpdateService, @@ -608,45 +607,18 @@ export class Menubar { } } - private static _menuItemIsTriggeredViaKeybinding(event: KeyboardEvent, userSettingsLabel: string): boolean { - // The event coming in from Electron will inform us only about the modifier keys pressed. - // The strategy here is to check if the modifier keys match those of the keybinding, - // since it is highly unlikely to use modifier keys when clicking with the mouse - if (!userSettingsLabel) { - // There is no keybinding - return false; - } - - let ctrlRequired = /ctrl/.test(userSettingsLabel); - let shiftRequired = /shift/.test(userSettingsLabel); - let altRequired = /alt/.test(userSettingsLabel); - let metaRequired = /cmd/.test(userSettingsLabel) || /super/.test(userSettingsLabel); - - if (!ctrlRequired && !shiftRequired && !altRequired && !metaRequired) { - // This keybinding does not use any modifier keys, so we cannot use this heuristic - return false; - } - - return ( - ctrlRequired === event.ctrlKey - && shiftRequired === event.shiftKey - && altRequired === event.altKey - && metaRequired === event.metaKey - ); - } - private createMenuItem(label: string, commandId: string | string[], enabled?: boolean, checked?: boolean): MenuItem; private createMenuItem(label: string, click: () => void, enabled?: boolean, checked?: boolean): MenuItem; private createMenuItem(arg1: string, arg2: any, arg3?: boolean, arg4?: boolean): MenuItem { const label = this.mnemonicLabel(arg1); - const click: () => void = (typeof arg2 === 'function') ? arg2 : (menuItem: MenuItem & IMenuItemWithKeybinding, win: BrowserWindow, event: Event) => { + const click: () => void = (typeof arg2 === 'function') ? arg2 : (menuItem: MenuItem & IMenuItemWithKeybinding, win: BrowserWindow, event: KeyboardEvent) => { const userSettingsLabel = menuItem ? menuItem.userSettingsLabel : null; let commandId = arg2; if (Array.isArray(arg2)) { commandId = this.isOptionClick(event) ? arg2[1] : arg2[0]; // support alternative action if we got multiple action Ids and the option key was pressed while invoking } - if (userSettingsLabel && Menubar._menuItemIsTriggeredViaKeybinding(event, userSettingsLabel)) { + if (userSettingsLabel && event.triggeredByAccelerator) { this.runActionInRenderer({ type: 'keybinding', userSettingsLabel }); } else { this.runActionInRenderer({ type: 'commandId', commandId }); @@ -706,8 +678,8 @@ export class Menubar { return new MenuItem(this.withKeybinding(commandId, options)); } - private makeContextAwareClickHandler(click: () => void, contextSpecificHandlers: IMenuItemClickHandler): () => void { - return () => { + private makeContextAwareClickHandler(click: (menuItem: MenuItem, win: BrowserWindow, event: KeyboardEvent) => void, contextSpecificHandlers: IMenuItemClickHandler): (menuItem: MenuItem, win: BrowserWindow | undefined, event: KeyboardEvent) => void { + return (menuItem: MenuItem, win: BrowserWindow | undefined, event: KeyboardEvent) => { // No Active Window const activeWindow = BrowserWindow.getFocusedWindow(); @@ -722,7 +694,7 @@ export class Menubar { } // Finally execute command in Window - click(); + click(menuItem, win || activeWindow, event); }; } diff --git a/src/vs/platform/native/common/native.ts b/src/vs/platform/native/common/native.ts index 60baf2da4..2c8414ba8 100644 --- a/src/vs/platform/native/common/native.ts +++ b/src/vs/platform/native/common/native.ts @@ -49,7 +49,7 @@ export interface ICommonNativeHostService { readonly onDidChangeColorScheme: Event; - readonly onDidChangePassword: Event; + readonly onDidChangePassword: Event<{ service: string, account: string }>; // Window getWindows(): Promise; @@ -137,6 +137,7 @@ export interface ICommonNativeHostService { // Development openDevTools(options?: OpenDevToolsOptions): Promise; toggleDevTools(): Promise; + toggleSharedProcessWindow(): Promise; sendInputEvent(event: MouseInputEvent): Promise; // Connectivity diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index e24efa83c..50c4460ad 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -4,18 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; +import { IWindowsMainService, ICodeWindow, OpenContext } from 'vs/platform/windows/electron-main/windows'; import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, Menu, BrowserWindow, app, clipboard, powerMonitor, nativeTheme } from 'electron'; -import { OpenContext } from 'vs/platform/windows/node/window'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IOpenedWindow, IOpenWindowOptions, IWindowOpenable, IOpenEmptyWindowOptions, IColorScheme } from 'vs/platform/windows/common/windows'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; -import { isMacintosh, isWindows, isLinux } from 'vs/base/common/platform'; +import { isMacintosh, isWindows, isLinux, isLinuxSnap } from 'vs/base/common/platform'; import { ICommonNativeHostService, IOSProperties, IOSStatistics } from 'vs/platform/native/common/native'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { AddFirstParameterToFunctions } from 'vs/base/common/types'; -import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; +import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService'; import { dirExists } from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; import { ITelemetryData, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -28,6 +27,7 @@ import { dirname, join } from 'vs/base/common/path'; import product from 'vs/platform/product/common/product'; import { memoize } from 'vs/base/common/decorators'; import { Disposable } from 'vs/base/common/lifecycle'; +import { ISharedProcess } from 'vs/platform/sharedProcess/node/sharedProcess'; export interface INativeHostMainService extends AddFirstParameterToFunctions /* only methods, not events */, number | undefined /* window ID */> { } @@ -43,6 +43,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain declare readonly _serviceBrand: undefined; constructor( + private sharedProcess: ISharedProcess, @IWindowsMainService private readonly windowsMainService: IWindowsMainService, @IDialogMainService private readonly dialogMainService: IDialogMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @@ -91,7 +92,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain private readonly _onDidChangeColorScheme = this._register(new Emitter()); readonly onDidChangeColorScheme = this._onDidChangeColorScheme.event; - private readonly _onDidChangePassword = this._register(new Emitter()); + private readonly _onDidChangePassword = this._register(new Emitter<{ account: string, service: string }>()); readonly onDidChangePassword = this._onDidChangePassword.event; //#endregion @@ -104,7 +105,6 @@ export class NativeHostMainService extends Disposable implements INativeHostMain return windows.map(window => ({ id: window.id, workspace: window.openedWorkspace, - folderUri: window.openedFolderUri, title: window.win.getTitle(), filename: window.getRepresentedFilename(), dirty: window.isDocumentEdited() @@ -334,8 +334,8 @@ export class NativeHostMainService extends Disposable implements INativeHostMain } async openExternal(windowId: number | undefined, url: string): Promise { - if (isLinux && process.env.SNAP && process.env.SNAP_REVISION) { - NativeHostMainService._safeSnapOpenExternal(url); + if (isLinuxSnap) { + this.safeSnapOpenExternal(url); } else { shell.openExternal(url); } @@ -343,7 +343,9 @@ export class NativeHostMainService extends Disposable implements INativeHostMain return true; } - private static _safeSnapOpenExternal(url: string): void { + private safeSnapOpenExternal(url: string): void { + + // Remove some environment variables before opening to avoid issues... const gdkPixbufModuleFile = process.env['GDK_PIXBUF_MODULE_FILE']; const gdkPixbufModuleDir = process.env['GDK_PIXBUF_MODULEDIR']; delete process.env['GDK_PIXBUF_MODULE_FILE']; @@ -351,6 +353,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain shell.openExternal(url); + // ...but restore them after process.env['GDK_PIXBUF_MODULE_FILE'] = gdkPixbufModuleFile; process.env['GDK_PIXBUF_MODULEDIR'] = gdkPixbufModuleDir; } @@ -626,6 +629,10 @@ export class NativeHostMainService extends Disposable implements INativeHostMain } } + async toggleSharedProcessWindow(): Promise { + return this.sharedProcess.toggle(); + } + //#endregion //#region Registry (windows) @@ -703,7 +710,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain await keytar.setPassword(service, account, password); } - this._onDidChangePassword.fire(); + this._onDidChangePassword.fire({ service, account }); } async deletePassword(windowId: number | undefined, service: string, account: string): Promise { @@ -711,7 +718,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain const didDelete = await keytar.deletePassword(service, account); if (didDelete) { - this._onDidChangePassword.fire(); + this._onDidChangePassword.fire({ service, account }); } return didDelete; diff --git a/src/vs/platform/opener/browser/link.ts b/src/vs/platform/opener/browser/link.ts index 7b6863cd0..ca887940f 100644 --- a/src/vs/platform/opener/browser/link.ts +++ b/src/vs/platform/opener/browser/link.ts @@ -20,11 +20,13 @@ export interface ILinkDescriptor { export interface ILinkStyles { readonly textLinkForeground?: Color; + readonly disabled?: boolean; } export class Link extends Disposable { readonly el: HTMLAnchorElement; + private disabled: boolean; private styles: ILinkStyles = { textLinkForeground: Color.fromHex('#006AB1') }; @@ -50,9 +52,12 @@ export class Link extends Disposable { this._register(onOpen(e => { EventHelper.stop(e, true); - openerService.open(link.href); + if (!this.disabled) { + openerService.open(link.href); + } })); + this.disabled = false; this.applyStyles(); } @@ -62,6 +67,26 @@ export class Link extends Disposable { } private applyStyles(): void { - this.el.style.color = this.styles.textLinkForeground?.toString() || ''; + const color = this.styles.textLinkForeground?.toString(); + if (color) { + this.el.style.color = color; + } + if (typeof this.styles.disabled === 'boolean' && this.styles.disabled !== this.disabled) { + if (this.styles.disabled) { + this.el.setAttribute('aria-disabled', 'true'); + this.el.tabIndex = -1; + this.el.style.pointerEvents = 'none'; + this.el.style.opacity = '0.4'; + this.el.style.cursor = 'default'; + this.disabled = true; + } else { + this.el.setAttribute('aria-disabled', 'false'); + this.el.tabIndex = 0; + this.el.style.pointerEvents = 'auto'; + this.el.style.opacity = '1'; + this.el.style.cursor = 'pointer'; + this.disabled = false; + } + } } } diff --git a/src/vs/platform/opener/common/opener.ts b/src/vs/platform/opener/common/opener.ts index d82651f93..ff6740cf6 100644 --- a/src/vs/platform/opener/common/opener.ts +++ b/src/vs/platform/opener/common/opener.ts @@ -3,15 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI } from 'vs/base/common/uri'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { equalsIgnoreCase, startsWithIgnoreCase } from 'vs/base/common/strings'; +import { URI } from 'vs/base/common/uri'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export const IOpenerService = createDecorator('openerService'); -type OpenInternalOptions = { +export type OpenInternalOptions = { /** * Signals that the intent is to open an editor to the side @@ -31,7 +32,11 @@ type OpenInternalOptions = { readonly fromUserGesture?: boolean; }; -type OpenExternalOptions = { readonly openExternal?: boolean; readonly allowTunneling?: boolean }; +export type OpenExternalOptions = { + readonly openExternal?: boolean; + readonly allowTunneling?: boolean; + readonly allowContributedOpeners?: boolean | string; +}; export type OpenOptions = OpenInternalOptions & OpenExternalOptions; @@ -46,7 +51,8 @@ export interface IOpener { } export interface IExternalOpener { - openExternal(href: string): Promise; + openExternal(href: string, ctx: { sourceUri: URI, preferredOpenerId?: string }, token: CancellationToken): Promise; + dispose?(): void; } export interface IValidator { @@ -81,7 +87,12 @@ export interface IOpenerService { * Sets the handler for opening externally. If not provided, * a default handler will be used. */ - setExternalOpener(opener: IExternalOpener): void; + setDefaultExternalOpener(opener: IExternalOpener): void; + + /** + * Registers a new opener external resources openers. + */ + registerExternalOpener(opener: IExternalOpener): IDisposable; /** * Opens a resource, like a webaddress, a document uri, or executes command. @@ -97,15 +108,16 @@ export interface IOpenerService { resolveExternalUri(resource: URI, options?: ResolveExternalUriOptions): Promise; } -export const NullOpenerService: IOpenerService = Object.freeze({ +export const NullOpenerService = Object.freeze({ _serviceBrand: undefined, registerOpener() { return Disposable.None; }, registerValidator() { return Disposable.None; }, registerExternalUriResolver() { return Disposable.None; }, - setExternalOpener() { }, + setDefaultExternalOpener() { }, + registerExternalOpener() { return Disposable.None; }, async open() { return false; }, async resolveExternalUri(uri: URI) { return { resolved: uri, dispose() { } }; }, -}); +} as IOpenerService); export function matchesScheme(target: URI | string, scheme: string) { if (URI.isUri(target)) { diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 56fbc68ad..537db557d 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -20,7 +20,7 @@ if (isWeb || typeof require === 'undefined' || typeof require.__$__nodeRequire ! // Running out of sources if (Object.keys(product).length === 0) { Object.assign(product, { - version: '1.52.0-dev', + version: '1.53.0-dev', nameShort: isWeb ? 'Code Web - OSS Dev' : 'Code - OSS Dev', nameLong: isWeb ? 'Code Web - OSS Dev' : 'Code - OSS Dev', applicationName: 'code-oss', diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts index 74a8c744a..92b8c1a6d 100644 --- a/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts @@ -129,6 +129,8 @@ export interface IProductConfiguration { readonly linkProtectionTrustedDomains?: readonly string[]; readonly 'configurationSync.store'?: ConfigurationSyncStore; + + readonly darwinUniversalAssetId?: string; } export type ImportantExtensionTip = { name: string; languages?: string[]; pattern?: string; isExtensionPack?: boolean }; diff --git a/src/vs/platform/remote/browser/browserSocketFactory.ts b/src/vs/platform/remote/browser/browserSocketFactory.ts index 3715cbb8e..2f343e841 100644 --- a/src/vs/platform/remote/browser/browserSocketFactory.ts +++ b/src/vs/platform/remote/browser/browserSocketFactory.ts @@ -208,7 +208,7 @@ export class BrowserSocketFactory implements ISocketFactory { } connect(host: string, port: number, query: string, callback: IConnectCallback): void { - const socket = this._webSocketFactory.create(`ws://${host}:${port}/?${query}&skipWebSocketFrames=false`); + const socket = this._webSocketFactory.create(`ws://${/:/.test(host) ? `[${host}]` : host}:${port}/?${query}&skipWebSocketFrames=false`); const errorListener = socket.onError((err) => callback(err, undefined)); socket.onOpen(() => { errorListener.dispose(); diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index fdd5890c6..82791249f 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -8,7 +8,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { Disposable } from 'vs/base/common/lifecycle'; import { VSBuffer } from 'vs/base/common/buffer'; -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors'; import { ISignService } from 'vs/platform/sign/common/sign'; @@ -86,96 +86,124 @@ export interface ISocketFactory { connect(host: string, port: number, query: string, callback: IConnectCallback): void; } -async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined): Promise<{ protocol: PersistentProtocol; ownsProtocol: boolean; }> { - const logPrefix = connectLogPrefix(options, connectionType); - const { protocol, ownsProtocol } = await new Promise<{ protocol: PersistentProtocol; ownsProtocol: boolean; }>((c, e) => { - options.logService.trace(`${logPrefix} 1/6. invoking socketFactory.connect().`); - options.socketFactory.connect( - options.host, - options.port, - `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, - (err: any, socket: ISocket | undefined) => { - if (err || !socket) { - options.logService.error(`${logPrefix} socketFactory.connect() failed. Error:`); - options.logService.error(err); - e(err); - return; - } +async function readOneControlMessage(protocol: PersistentProtocol): Promise { + const raw = await Event.toPromise(protocol.onControlMessage); + const msg = JSON.parse(raw.toString()); + const error = getErrorFromMessage(msg); + if (error) { + throw error; + } + return msg; +} - options.logService.trace(`${logPrefix} 2/6. socketFactory.connect() was successful.`); - if (options.reconnectionProtocol) { - options.reconnectionProtocol.beginAcceptReconnection(socket, null); - c({ protocol: options.reconnectionProtocol, ownsProtocol: false }); - } else { - c({ protocol: new PersistentProtocol(socket, null), ownsProtocol: true }); - } +function waitWithTimeout(promise: Promise, timeout: number): Promise { + return new Promise((resolve, reject) => { + const timeoutToken = setTimeout(() => { + const error: any = new Error('Timeout'); + error.code = 'ETIMEDOUT'; + error.syscall = 'connect'; + reject(error); + }, timeout); + + promise.then( + (result) => { + clearTimeout(timeoutToken); + resolve(result); + }, + (error) => { + clearTimeout(timeoutToken); + reject(error); } ); }); +} - return new Promise<{ protocol: PersistentProtocol; ownsProtocol: boolean; }>((c, e) => { +function createSocket(socketFactory: ISocketFactory, host: string, port: number, query: string): Promise { + return new Promise((resolve, reject) => { + socketFactory.connect(host, port, query, (err: any, socket: ISocket | undefined) => { + if (err || !socket) { + return reject(err); + } + resolve(socket); + }); + }); +} - const errorTimeoutToken = setTimeout(() => { - const error: any = new Error('handshake timeout'); - error.code = 'ETIMEDOUT'; - error.syscall = 'connect'; +async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined): Promise<{ protocol: PersistentProtocol; ownsProtocol: boolean; }> { + const logPrefix = connectLogPrefix(options, connectionType); + + options.logService.trace(`${logPrefix} 1/6. invoking socketFactory.connect().`); + + let socket: ISocket; + try { + socket = await createSocket(options.socketFactory, options.host, options.port, `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`); + } catch (error) { + options.logService.error(`${logPrefix} socketFactory.connect() failed. Error:`); + options.logService.error(error); + throw error; + } + + options.logService.trace(`${logPrefix} 2/6. socketFactory.connect() was successful.`); + + let protocol: PersistentProtocol; + let ownsProtocol: boolean; + if (options.reconnectionProtocol) { + options.reconnectionProtocol.beginAcceptReconnection(socket, null); + protocol = options.reconnectionProtocol; + ownsProtocol = false; + } else { + protocol = new PersistentProtocol(socket, null); + ownsProtocol = true; + } + + options.logService.trace(`${logPrefix} 3/6. sending AuthRequest control message.`); + const authRequest: AuthRequest = { + type: 'auth', + auth: options.connectionToken || '00000000000000000000' + }; + protocol.sendControl(VSBuffer.fromString(JSON.stringify(authRequest))); + + try { + const msg = await waitWithTimeout(readOneControlMessage(protocol), 10000); + + if (msg.type !== 'sign' || typeof msg.data !== 'string') { + const error: any = new Error('Unexpected handshake message'); + error.code = 'VSCODE_CONNECTION_ERROR'; + throw error; + } + + options.logService.trace(`${logPrefix} 4/6. received SignRequest control message.`); + + const signed = await options.signService.sign(msg.data); + const connTypeRequest: ConnectionTypeRequest = { + type: 'connectionType', + commit: options.commit, + signedData: signed, + desiredConnectionType: connectionType + }; + if (args) { + connTypeRequest.args = args; + } + + options.logService.trace(`${logPrefix} 5/6. sending ConnectionTypeRequest control message.`); + protocol.sendControl(VSBuffer.fromString(JSON.stringify(connTypeRequest))); + + return { protocol, ownsProtocol }; + + } catch (error) { + if (error && error.code === 'ETIMEDOUT') { options.logService.error(`${logPrefix} the handshake took longer than 10 seconds. Error:`); options.logService.error(error); - if (ownsProtocol) { - safeDisposeProtocolAndSocket(protocol); - } - e(error); - }, 10000); - - const messageRegistration = protocol.onControlMessage(async raw => { - const msg = JSON.parse(raw.toString()); - // Stop listening for further events - messageRegistration.dispose(); - - const error = getErrorFromMessage(msg); - if (error) { - options.logService.error(`${logPrefix} received error control message when negotiating connection. Error:`); - options.logService.error(error); - if (ownsProtocol) { - safeDisposeProtocolAndSocket(protocol); - } - return e(error); - } - - if (msg.type === 'sign') { - options.logService.trace(`${logPrefix} 4/6. received SignRequest control message.`); - const signed = await options.signService.sign(msg.data); - const connTypeRequest: ConnectionTypeRequest = { - type: 'connectionType', - commit: options.commit, - signedData: signed, - desiredConnectionType: connectionType - }; - if (args) { - connTypeRequest.args = args; - } - options.logService.trace(`${logPrefix} 5/6. sending ConnectionTypeRequest control message.`); - protocol.sendControl(VSBuffer.fromString(JSON.stringify(connTypeRequest))); - clearTimeout(errorTimeoutToken); - c({ protocol, ownsProtocol }); - } else { - const error = new Error('handshake error'); - options.logService.error(`${logPrefix} received unexpected control message. Error:`); - options.logService.error(error); - if (ownsProtocol) { - safeDisposeProtocolAndSocket(protocol); - } - e(error); - } - }); - - options.logService.trace(`${logPrefix} 3/6. sending AuthRequest control message.`); - const authRequest: AuthRequest = { - type: 'auth', - auth: options.connectionToken || '00000000000000000000' - }; - protocol.sendControl(VSBuffer.fromString(JSON.stringify(authRequest))); - }); + } + if (error && error.code === 'VSCODE_CONNECTION_ERROR') { + options.logService.error(`${logPrefix} received error control message when negotiating connection. Error:`); + options.logService.error(error); + } + if (ownsProtocol) { + safeDisposeProtocolAndSocket(protocol); + } + throw error; + } } interface IManagementConnectionResult { @@ -287,7 +315,7 @@ export async function connectRemoteAgentManagement(options: IConnectionOptions, } catch (err) { options.logService.error(`[remote-connection] An error occurred in the very first connect attempt, it will be treated as a permanent error! Error:`); options.logService.error(err); - PersistentConnection.triggerPermanentFailure(); + PersistentConnection.triggerPermanentFailure(0, 0, RemoteAuthorityResolverError.isHandled(err)); throw err; } } @@ -301,7 +329,7 @@ export async function connectRemoteAgentExtensionHost(options: IConnectionOption } catch (err) { options.logService.error(`[remote-connection] An error occurred in the very first connect attempt, it will be treated as a permanent error! Error:`); options.logService.error(err); - PersistentConnection.triggerPermanentFailure(); + PersistentConnection.triggerPermanentFailure(0, 0, RemoteAuthorityResolverError.isHandled(err)); throw err; } } @@ -333,10 +361,16 @@ export const enum PersistentConnectionEventType { } export class ConnectionLostEvent { public readonly type = PersistentConnectionEventType.ConnectionLost; + constructor( + public readonly reconnectionToken: string, + public readonly millisSinceLastIncomingData: number + ) { } } export class ReconnectionWaitEvent { public readonly type = PersistentConnectionEventType.ReconnectionWait; constructor( + public readonly reconnectionToken: string, + public readonly millisSinceLastIncomingData: number, public readonly durationSeconds: number, private readonly cancellableTimer: CancelablePromise ) { } @@ -347,22 +381,44 @@ export class ReconnectionWaitEvent { } export class ReconnectionRunningEvent { public readonly type = PersistentConnectionEventType.ReconnectionRunning; + constructor( + public readonly reconnectionToken: string, + public readonly millisSinceLastIncomingData: number, + public readonly attempt: number + ) { } } export class ConnectionGainEvent { public readonly type = PersistentConnectionEventType.ConnectionGain; + constructor( + public readonly reconnectionToken: string, + public readonly millisSinceLastIncomingData: number, + public readonly attempt: number + ) { } } export class ReconnectionPermanentFailureEvent { public readonly type = PersistentConnectionEventType.ReconnectionPermanentFailure; + constructor( + public readonly reconnectionToken: string, + public readonly millisSinceLastIncomingData: number, + public readonly attempt: number, + public readonly handled: boolean + ) { } } export type PersistentConnectionEvent = ConnectionGainEvent | ConnectionLostEvent | ReconnectionWaitEvent | ReconnectionRunningEvent | ReconnectionPermanentFailureEvent; abstract class PersistentConnection extends Disposable { - public static triggerPermanentFailure(): void { + public static triggerPermanentFailure(millisSinceLastIncomingData: number, attempt: number, handled: boolean): void { this._permanentFailure = true; - this._instances.forEach(instance => instance._gotoPermanentFailure()); + this._permanentFailureMillisSinceLastIncomingData = millisSinceLastIncomingData; + this._permanentFailureAttempt = attempt; + this._permanentFailureHandled = handled; + this._instances.forEach(instance => instance._gotoPermanentFailure(this._permanentFailureMillisSinceLastIncomingData, this._permanentFailureAttempt, this._permanentFailureHandled)); } private static _permanentFailure: boolean = false; + private static _permanentFailureMillisSinceLastIncomingData: number = 0; + private static _permanentFailureAttempt: number = 0; + private static _permanentFailureHandled: boolean = false; private static _instances: PersistentConnection[] = []; private readonly _onDidStateChange = this._register(new Emitter()); @@ -381,7 +437,7 @@ abstract class PersistentConnection extends Disposable { this.protocol = protocol; this._isReconnecting = false; - this._onDidStateChange.fire(new ConnectionGainEvent()); + this._onDidStateChange.fire(new ConnectionGainEvent(this.reconnectionToken, 0, 0)); this._register(protocol.onSocketClose(() => this._beginReconnecting())); this._register(protocol.onSocketTimeout(() => this._beginReconnecting())); @@ -389,7 +445,7 @@ abstract class PersistentConnection extends Disposable { PersistentConnection._instances.push(this); if (PersistentConnection._permanentFailure) { - this._gotoPermanentFailure(); + this._gotoPermanentFailure(PersistentConnection._permanentFailureMillisSinceLastIncomingData, PersistentConnection._permanentFailureAttempt, PersistentConnection._permanentFailureHandled); } } @@ -413,21 +469,23 @@ abstract class PersistentConnection extends Disposable { } const logPrefix = commonLogPrefix(this._connectionType, this.reconnectionToken, true); this._options.logService.info(`${logPrefix} starting reconnecting loop. You can get more information with the trace log level.`); - this._onDidStateChange.fire(new ConnectionLostEvent()); - const TIMES = [5, 5, 10, 10, 10, 10, 10, 30]; + this._onDidStateChange.fire(new ConnectionLostEvent(this.reconnectionToken, this.protocol.getMillisSinceLastIncomingData())); + const TIMES = [0, 5, 5, 10, 10, 10, 10, 10, 30]; const disconnectStartTime = Date.now(); let attempt = -1; do { attempt++; const waitTime = (attempt < TIMES.length ? TIMES[attempt] : TIMES[TIMES.length - 1]); try { - const sleepPromise = sleep(waitTime); - this._onDidStateChange.fire(new ReconnectionWaitEvent(waitTime, sleepPromise)); + if (waitTime > 0) { + const sleepPromise = sleep(waitTime); + this._onDidStateChange.fire(new ReconnectionWaitEvent(this.reconnectionToken, this.protocol.getMillisSinceLastIncomingData(), waitTime, sleepPromise)); - this._options.logService.info(`${logPrefix} waiting for ${waitTime} seconds before reconnecting...`); - try { - await sleepPromise; - } catch { } // User canceled timer + this._options.logService.info(`${logPrefix} waiting for ${waitTime} seconds before reconnecting...`); + try { + await sleepPromise; + } catch { } // User canceled timer + } if (PersistentConnection._permanentFailure) { this._options.logService.error(`${logPrefix} permanent failure occurred while running the reconnecting loop.`); @@ -435,26 +493,26 @@ abstract class PersistentConnection extends Disposable { } // connection was lost, let's try to re-establish it - this._onDidStateChange.fire(new ReconnectionRunningEvent()); + this._onDidStateChange.fire(new ReconnectionRunningEvent(this.reconnectionToken, this.protocol.getMillisSinceLastIncomingData(), attempt + 1)); this._options.logService.info(`${logPrefix} resolving connection...`); const simpleOptions = await resolveConnectionOptions(this._options, this.reconnectionToken, this.protocol); this._options.logService.info(`${logPrefix} connecting to ${simpleOptions.host}:${simpleOptions.port}...`); await connectWithTimeLimit(simpleOptions.logService, this._reconnect(simpleOptions), RECONNECT_TIMEOUT); this._options.logService.info(`${logPrefix} reconnected!`); - this._onDidStateChange.fire(new ConnectionGainEvent()); + this._onDidStateChange.fire(new ConnectionGainEvent(this.reconnectionToken, this.protocol.getMillisSinceLastIncomingData(), attempt + 1)); break; } catch (err) { if (err.code === 'VSCODE_CONNECTION_ERROR') { this._options.logService.error(`${logPrefix} A permanent error occurred in the reconnecting loop! Will give up now! Error:`); this._options.logService.error(err); - PersistentConnection.triggerPermanentFailure(); + PersistentConnection.triggerPermanentFailure(this.protocol.getMillisSinceLastIncomingData(), attempt + 1, false); break; } if (Date.now() - disconnectStartTime > ProtocolConstants.ReconnectionGraceTime) { this._options.logService.error(`${logPrefix} An error occurred while reconnecting, but it will be treated as a permanent error because the reconnection grace time has expired! Will give up now! Error:`); this._options.logService.error(err); - PersistentConnection.triggerPermanentFailure(); + PersistentConnection.triggerPermanentFailure(this.protocol.getMillisSinceLastIncomingData(), attempt + 1, false); break; } if (RemoteAuthorityResolverError.isTemporarilyNotAvailable(err)) { @@ -475,16 +533,22 @@ abstract class PersistentConnection extends Disposable { // try again! continue; } + if (err instanceof RemoteAuthorityResolverError) { + this._options.logService.error(`${logPrefix} A RemoteAuthorityResolverError occurred while trying to reconnect. Will give up now! Error:`); + this._options.logService.error(err); + PersistentConnection.triggerPermanentFailure(this.protocol.getMillisSinceLastIncomingData(), attempt + 1, RemoteAuthorityResolverError.isHandled(err)); + break; + } this._options.logService.error(`${logPrefix} An unknown error occurred while trying to reconnect, since this is an unknown case, it will be treated as a permanent error! Will give up now! Error:`); this._options.logService.error(err); - PersistentConnection.triggerPermanentFailure(); + PersistentConnection.triggerPermanentFailure(this.protocol.getMillisSinceLastIncomingData(), attempt + 1, false); break; } } while (!PersistentConnection._permanentFailure); } - private _gotoPermanentFailure(): void { - this._onDidStateChange.fire(new ReconnectionPermanentFailureEvent()); + private _gotoPermanentFailure(millisSinceLastIncomingData: number, attempt: number, handled: boolean): void { + this._onDidStateChange.fire(new ReconnectionPermanentFailureEvent(this.reconnectionToken, millisSinceLastIncomingData, attempt, handled)); safeDisposeProtocolAndSocket(this.protocol); } diff --git a/src/vs/platform/remote/common/remoteAgentEnvironment.ts b/src/vs/platform/remote/common/remoteAgentEnvironment.ts index 3052141ff..fd2752809 100644 --- a/src/vs/platform/remote/common/remoteAgentEnvironment.ts +++ b/src/vs/platform/remote/common/remoteAgentEnvironment.ts @@ -5,6 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { OperatingSystem } from 'vs/base/common/platform'; +import * as performance from 'vs/base/common/performance'; export interface IRemoteAgentEnvironment { pid: number; @@ -18,6 +19,7 @@ export interface IRemoteAgentEnvironment { workspaceStorageHome: URI; userHome: URI; os: OperatingSystem; + marks: performance.PerformanceMark[]; } export interface RemoteAgentConnectionContext { diff --git a/src/vs/platform/remote/common/remoteHosts.ts b/src/vs/platform/remote/common/remoteHosts.ts index 1f3ab36f3..86f2c4f26 100644 --- a/src/vs/platform/remote/common/remoteHosts.ts +++ b/src/vs/platform/remote/common/remoteHosts.ts @@ -19,7 +19,7 @@ export function getRemoteName(authority: string | undefined): string | undefined } const pos = authority.indexOf('+'); if (pos < 0) { - // funky? bad authority? + // e.g. localhost:8000 return authority; } return authority.substr(0, pos); diff --git a/src/vs/platform/remote/common/tunnel.ts b/src/vs/platform/remote/common/tunnel.ts index 980c3203a..b4009323b 100644 --- a/src/vs/platform/remote/common/tunnel.ts +++ b/src/vs/platform/remote/common/tunnel.ts @@ -5,6 +5,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { isWindows, OperatingSystem } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; @@ -17,36 +18,64 @@ export interface RemoteTunnel { readonly tunnelRemoteHost: string; readonly tunnelLocalPort?: number; readonly localAddress: string; - dispose(silent?: boolean): void; + readonly public: boolean; + dispose(silent?: boolean): Promise; } export interface TunnelOptions { - remoteAddress: { port: number, host: string }; + remoteAddress: { port: number, host: string; }; localAddressPort?: number; label?: string; + public?: boolean; } export interface TunnelCreationOptions { elevationRequired?: boolean; } +export interface TunnelProviderFeatures { + elevation: boolean; + public: boolean; +} + export interface ITunnelProvider { - forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise | undefined; + forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise | undefined; +} + +export interface ITunnel { + remoteAddress: { port: number, host: string }; + + /** + * The complete local address(ex. localhost:1234) + */ + localAddress: string; + + public?: boolean; + + /** + * Implementers of Tunnel should fire onDidDispose when dispose is called. + */ + onDidDispose: Event; + + dispose(): Promise | void; } export interface ITunnelService { readonly _serviceBrand: undefined; readonly tunnels: Promise; + readonly canMakePublic: boolean; readonly onTunnelOpened: Event; - readonly onTunnelClosed: Event<{ host: string, port: number }>; + readonly onTunnelClosed: Event<{ host: string, port: number; }>; + readonly canElevate: boolean; - openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number): Promise | undefined; + canTunnel(uri: URI): boolean; + openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded?: boolean, isPublic?: boolean): Promise | undefined; closeTunnel(remoteHost: string, remotePort: number): Promise; - setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable; + setTunnelProvider(provider: ITunnelProvider | undefined, features: TunnelProviderFeatures): IDisposable; } -export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address: string, port: number } | undefined { +export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address: string, port: number; } | undefined { if (uri.scheme !== 'http' && uri.scheme !== 'https') { return undefined; } @@ -74,51 +103,88 @@ function getOtherLocalhost(host: string): string | undefined { return (host === 'localhost') ? '127.0.0.1' : ((host === '127.0.0.1') ? 'localhost' : undefined); } +export function isPortPrivileged(port: number, os?: OperatingSystem): boolean { + if (os) { + return os !== OperatingSystem.Windows && (port < 1024); + } else { + return !isWindows && (port < 1024); + } +} + export abstract class AbstractTunnelService implements ITunnelService { declare readonly _serviceBrand: undefined; private _onTunnelOpened: Emitter = new Emitter(); public onTunnelOpened: Event = this._onTunnelOpened.event; - private _onTunnelClosed: Emitter<{ host: string, port: number }> = new Emitter(); - public onTunnelClosed: Event<{ host: string, port: number }> = this._onTunnelClosed.event; - protected readonly _tunnels = new Map }>>(); + private _onTunnelClosed: Emitter<{ host: string, port: number; }> = new Emitter(); + public onTunnelClosed: Event<{ host: string, port: number; }> = this._onTunnelClosed.event; + protected readonly _tunnels = new Map; }>>(); protected _tunnelProvider: ITunnelProvider | undefined; + protected _canElevate: boolean = false; + private _canMakePublic: boolean = false; public constructor( @ILogService protected readonly logService: ILogService ) { } - setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable { + setTunnelProvider(provider: ITunnelProvider | undefined, features: TunnelProviderFeatures): IDisposable { + this._tunnelProvider = provider; if (!provider) { + // clear features + this._canElevate = false; + this._canMakePublic = false; return { dispose: () => { } }; } - this._tunnelProvider = provider; + this._canElevate = features.elevation; + this._canMakePublic = features.public; return { dispose: () => { this._tunnelProvider = undefined; + this._canElevate = false; + this._canMakePublic = false; } }; } - public get tunnels(): Promise { - const promises: Promise[] = []; - Array.from(this._tunnels.values()).forEach(portMap => Array.from(portMap.values()).forEach(x => promises.push(x.value))); - return Promise.all(promises); + public get canElevate(): boolean { + return this._canElevate; } - dispose(): void { + public get canMakePublic() { + return this._canMakePublic; + } + + public get tunnels(): Promise { + return new Promise(async (resolve) => { + const tunnels: RemoteTunnel[] = []; + const tunnelArray = Array.from(this._tunnels.values()); + for (let portMap of tunnelArray) { + const portArray = Array.from(portMap.values()); + for (let x of portArray) { + const tunnelValue = await x.value; + if (tunnelValue) { + tunnels.push(tunnelValue); + } + } + } + resolve(tunnels); + }); + } + + async dispose(): Promise { for (const portMap of this._tunnels.values()) { for (const { value } of portMap.values()) { - value.then(tunnel => tunnel.dispose()); + await value.then(tunnel => tunnel?.dispose()); } portMap.clear(); } this._tunnels.clear(); } - openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort: number): Promise | undefined { + openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded: boolean = false, isPublic: boolean = false): Promise | undefined { + this.logService.trace(`openTunnel request for ${remoteHost}:${remotePort} on local port ${localPort}.`); if (!addressProvider) { return undefined; } @@ -127,12 +193,19 @@ export abstract class AbstractTunnelService implements ITunnelService { remoteHost = 'localhost'; } - const resolvedTunnel = this.retainOrCreateTunnel(addressProvider, remoteHost, remotePort, localPort); + const resolvedTunnel = this.retainOrCreateTunnel(addressProvider, remoteHost, remotePort, localPort, elevateIfNeeded, isPublic); if (!resolvedTunnel) { + this.logService.trace(`Tunnel was not created.`); return resolvedTunnel; } return resolvedTunnel.then(tunnel => { + if (!tunnel) { + this.logService.trace('New tunnel is undefined.'); + this.removeEmptyTunnelFromMap(remoteHost!, remotePort); + return undefined; + } + this.logService.trace('New tunnel established.'); const newTunnel = this.makeTunnel(tunnel); if (tunnel.tunnelRemoteHost !== remoteHost || tunnel.tunnelRemotePort !== remotePort) { this.logService.warn('Created tunnel does not match requirements of requested tunnel. Host or port mismatch.'); @@ -148,24 +221,28 @@ export abstract class AbstractTunnelService implements ITunnelService { tunnelRemoteHost: tunnel.tunnelRemoteHost, tunnelLocalPort: tunnel.tunnelLocalPort, localAddress: tunnel.localAddress, - dispose: () => { + public: tunnel.public, + dispose: async () => { const existingHost = this._tunnels.get(tunnel.tunnelRemoteHost); if (existingHost) { const existing = existingHost.get(tunnel.tunnelRemotePort); if (existing) { existing.refcount--; - this.tryDisposeTunnel(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort, existing); + await this.tryDisposeTunnel(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort, existing); } } } }; } - private async tryDisposeTunnel(remoteHost: string, remotePort: number, tunnel: { refcount: number, readonly value: Promise }): Promise { + private async tryDisposeTunnel(remoteHost: string, remotePort: number, tunnel: { refcount: number, readonly value: Promise }): Promise { if (tunnel.refcount <= 0) { - const disposePromise: Promise = tunnel.value.then(tunnel => { - tunnel.dispose(true); - this._onTunnelClosed.fire({ host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }); + this.logService.trace(`Tunnel is being disposed ${remoteHost}:${remotePort}.`); + const disposePromise: Promise = tunnel.value.then(async (tunnel) => { + if (tunnel) { + await tunnel.dispose(true); + this._onTunnelClosed.fire({ host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }); + } }); if (this._tunnels.has(remoteHost)) { this._tunnels.get(remoteHost)!.delete(remotePort); @@ -183,16 +260,30 @@ export abstract class AbstractTunnelService implements ITunnelService { } } - protected addTunnelToMap(remoteHost: string, remotePort: number, tunnel: Promise) { + protected addTunnelToMap(remoteHost: string, remotePort: number, tunnel: Promise) { if (!this._tunnels.has(remoteHost)) { this._tunnels.set(remoteHost, new Map()); } this._tunnels.get(remoteHost)!.set(remotePort, { refcount: 1, value: tunnel }); } - protected getTunnelFromMap(remoteHost: string, remotePort: number): { refcount: number, readonly value: Promise } | undefined { + private async removeEmptyTunnelFromMap(remoteHost: string, remotePort: number) { + const hostMap = this._tunnels.get(remoteHost); + if (hostMap) { + const tunnel = hostMap.get(remotePort); + const tunnelResult = await tunnel; + if (!tunnelResult) { + hostMap.delete(remotePort); + } + if (hostMap.size === 0) { + this._tunnels.delete(remoteHost); + } + } + } + + protected getTunnelFromMap(remoteHost: string, remotePort: number): { refcount: number, readonly value: Promise } | undefined { const otherLocalhost = getOtherLocalhost(remoteHost); - let portMap: Map }> | undefined; + let portMap: Map }> | undefined; if (otherLocalhost) { const firstMap = this._tunnels.get(remoteHost); const secondMap = this._tunnels.get(otherLocalhost); @@ -207,31 +298,25 @@ export abstract class AbstractTunnelService implements ITunnelService { return portMap ? portMap.get(remotePort) : undefined; } - protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort?: number): Promise | undefined; + canTunnel(uri: URI): boolean { + return !!extractLocalHostUriMetaDataForPortMapping(uri); + } - protected isPortPrivileged(port: number): boolean { - return port < 1024; + protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean): Promise | undefined; + + protected createWithProvider(tunnelProvider: ITunnelProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean): Promise | undefined { + this.logService.trace(`Creating tunnel with provider ${remoteHost}:${remotePort} on local port ${localPort}.`); + + const preferredLocalPort = localPort === undefined ? remotePort : localPort; + const creationInfo = { elevationRequired: elevateIfNeeded ? isPortPrivileged(preferredLocalPort) : false }; + const tunnelOptions: TunnelOptions = { remoteAddress: { host: remoteHost, port: remotePort }, localAddressPort: localPort, public: isPublic }; + const tunnel = tunnelProvider.forwardPort(tunnelOptions, creationInfo); + this.logService.trace('Tunnel created by provider.'); + if (tunnel) { + this.addTunnelToMap(remoteHost, remotePort, tunnel); + } + return tunnel; } } -export class TunnelService extends AbstractTunnelService { - protected retainOrCreateTunnel(_addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort?: number | undefined): Promise | undefined { - const existing = this.getTunnelFromMap(remoteHost, remotePort); - if (existing) { - ++existing.refcount; - return existing.value; - } - if (this._tunnelProvider) { - const preferredLocalPort = localPort === undefined ? remotePort : localPort; - const tunnelOptions = { remoteAddress: { host: remoteHost, port: remotePort }, localAddressPort: localPort }; - const creationInfo = { elevationRequired: this.isPortPrivileged(preferredLocalPort) }; - const tunnel = this._tunnelProvider.forwardPort(tunnelOptions, creationInfo); - if (tunnel) { - this.addTunnelToMap(remoteHost, remotePort, tunnel); - } - return tunnel; - } - return undefined; - } -} diff --git a/src/vs/platform/remote/node/nodeSocketFactory.ts b/src/vs/platform/remote/node/nodeSocketFactory.ts index 44d689961..0a95e23eb 100644 --- a/src/vs/platform/remote/node/nodeSocketFactory.ts +++ b/src/vs/platform/remote/node/nodeSocketFactory.ts @@ -22,7 +22,7 @@ export const nodeSocketFactory = new class implements ISocketFactory { const nonce = buffer.toString('base64'); let headers = [ - `GET ws://${host}:${port}/?${query}&skipWebSocketFrames=true HTTP/1.1`, + `GET ws://${/:/.test(host) ? `[${host}]` : host}:${port}/?${query}&skipWebSocketFrames=true HTTP/1.1`, `Connection: Upgrade`, `Upgrade: websocket`, `Sec-WebSocket-Key: ${nonce}` diff --git a/src/vs/platform/remote/node/tunnelService.ts b/src/vs/platform/remote/node/tunnelService.ts index 19ec1efd6..80ef45f22 100644 --- a/src/vs/platform/remote/node/tunnelService.ts +++ b/src/vs/platform/remote/node/tunnelService.ts @@ -10,7 +10,7 @@ import { findFreePortFaster } from 'vs/base/node/ports'; import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; -import { connectRemoteAgentTunnel, IConnectionOptions, IAddressProvider } from 'vs/platform/remote/common/remoteAgentConnection'; +import { connectRemoteAgentTunnel, IConnectionOptions, IAddressProvider, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; import { AbstractTunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory'; import { ISignService } from 'vs/platform/sign/common/sign'; @@ -26,6 +26,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { public tunnelLocalPort!: number; public tunnelRemoteHost: string; public localAddress!: string; + public readonly public = false; private readonly _options: IConnectionOptions; private readonly _server: net.Server; @@ -57,7 +58,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { this.tunnelRemoteHost = tunnelRemoteHost; } - public dispose(): void { + public async dispose(): Promise { super.dispose(); this._server.removeListener('listening', this._listeningListener); this._server.removeListener('connection', this._connectionListener); @@ -129,8 +130,9 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { } } -export class TunnelService extends AbstractTunnelService { +export class BaseTunnelService extends AbstractTunnelService { public constructor( + private readonly socketFactory: ISocketFactory, @ILogService logService: ILogService, @ISignService private readonly signService: ISignService, @IProductService private readonly productService: IProductService @@ -138,7 +140,7 @@ export class TunnelService extends AbstractTunnelService { super(logService); } - protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort?: number): Promise | undefined { + protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean): Promise | undefined { const existing = this.getTunnelFromMap(remoteHost, remotePort); if (existing) { ++existing.refcount; @@ -146,18 +148,12 @@ export class TunnelService extends AbstractTunnelService { } if (this._tunnelProvider) { - const preferredLocalPort = localPort === undefined ? remotePort : localPort; - const creationInfo = { elevationRequired: this.isPortPrivileged(preferredLocalPort) }; - const tunnelOptions = { remoteAddress: { host: remoteHost, port: remotePort }, localAddressPort: localPort }; - const tunnel = this._tunnelProvider.forwardPort(tunnelOptions, creationInfo); - if (tunnel) { - this.addTunnelToMap(remoteHost, remotePort, tunnel); - } - return tunnel; + return this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, isPublic); } else { + this.logService.trace(`Creating tunnel without provider ${remoteHost}:${remotePort} on local port ${localPort}.`); const options: IConnectionOptions = { commit: this.productService.commit, - socketFactory: nodeSocketFactory, + socketFactory: this.socketFactory, addressProvider, signService: this.signService, logService: this.logService, @@ -165,8 +161,19 @@ export class TunnelService extends AbstractTunnelService { }; const tunnel = createRemoteTunnel(options, remoteHost, remotePort, localPort); + this.logService.trace('Tunnel created without provider.'); this.addTunnelToMap(remoteHost, remotePort, tunnel); return tunnel; } } } + +export class TunnelService extends BaseTunnelService { + public constructor( + @ILogService logService: ILogService, + @ISignService signService: ISignService, + @IProductService productService: IProductService + ) { + super(nodeSocketFactory, logService, signService, productService); + } +} diff --git a/src/vs/platform/sharedProcess/node/sharedProcess.ts b/src/vs/platform/sharedProcess/node/sharedProcess.ts new file mode 100644 index 000000000..8a0ea62d7 --- /dev/null +++ b/src/vs/platform/sharedProcess/node/sharedProcess.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 { NativeParsedArgs } from 'vs/platform/environment/common/argv'; +import { LogLevel } from 'vs/platform/log/common/log'; + +export interface ISharedProcess { + + /** + * Toggles the visibility of the otherwise hidden + * shared process window. + */ + toggle(): Promise; +} + +export interface ISharedProcessConfiguration { + readonly machineId: string; + readonly windowId: number; + + readonly appRoot: string; + + readonly userEnv: NodeJS.ProcessEnv; + + readonly sharedIPCHandle: string; + + readonly args: NativeParsedArgs; + + readonly logLevel: LogLevel; + + readonly nodeCachedDataDir?: string; + readonly backupWorkspacesPath: string; +} diff --git a/src/vs/platform/state/node/state.ts b/src/vs/platform/state/node/state.ts index b07335f95..9fd896f91 100644 --- a/src/vs/platform/state/node/state.ts +++ b/src/vs/platform/state/node/state.ts @@ -12,6 +12,8 @@ export interface IStateService { getItem(key: string, defaultValue: T): T; getItem(key: string, defaultValue?: T): T | undefined; + setItem(key: string, data?: object | string | number | boolean | undefined | null): void; + removeItem(key: string): void; } diff --git a/src/vs/platform/state/node/stateService.ts b/src/vs/platform/state/node/stateService.ts index 0def1cf9c..cb46b77fa 100644 --- a/src/vs/platform/state/node/stateService.ts +++ b/src/vs/platform/state/node/stateService.ts @@ -143,7 +143,7 @@ export class StateService implements IStateService { } getItem(key: string, defaultValue: T): T; - getItem(key: string, defaultValue: T | undefined): T | undefined; + getItem(key: string, defaultValue?: T): T | undefined; getItem(key: string, defaultValue?: T): T | undefined { return this.fileStorage.getItem(key, defaultValue); } diff --git a/src/vs/platform/state/test/node/state.test.ts b/src/vs/platform/state/test/node/state.test.ts index 82166e204..dc4acc6ae 100644 --- a/src/vs/platform/state/test/node/state.test.ts +++ b/src/vs/platform/state/test/node/state.test.ts @@ -4,31 +4,37 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as os from 'os'; -import * as path from 'vs/base/common/path'; -import { getRandomTestPath } from 'vs/base/test/node/testUtils'; +import { tmpdir } from 'os'; +import { join } from 'vs/base/common/path'; +import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; import { FileStorage } from 'vs/platform/state/node/stateService'; -import { mkdirp, rimraf, RimRafMode, writeFileSync } from 'vs/base/node/pfs'; +import { mkdirp, rimraf, writeFileSync } from 'vs/base/node/pfs'; -suite('StateService', () => { - const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'stateservice'); - const storageFile = path.join(parentDir, 'storage.json'); +flakySuite('StateService', () => { - teardown(async () => { - await rimraf(parentDir, RimRafMode.MOVE); + let testDir: string; + + setup(() => { + testDir = getRandomTestPath(tmpdir(), 'vsctests', 'stateservice'); + + return mkdirp(testDir); }); - test('Basics', async () => { - await mkdirp(parentDir); + teardown(() => { + return rimraf(testDir); + }); + + test('Basics', async function () { + const storageFile = join(testDir, 'storage.json'); writeFileSync(storageFile, ''); let service = new FileStorage(storageFile, () => null); service.setItem('some.key', 'some.value'); - assert.equal(service.getItem('some.key'), 'some.value'); + assert.strictEqual(service.getItem('some.key'), 'some.value'); service.removeItem('some.key'); - assert.equal(service.getItem('some.key', 'some.default'), 'some.default'); + assert.strictEqual(service.getItem('some.key', 'some.default'), 'some.default'); assert.ok(!service.getItem('some.unknonw.key')); @@ -36,15 +42,15 @@ suite('StateService', () => { service = new FileStorage(storageFile, () => null); - assert.equal(service.getItem('some.other.key'), 'some.other.value'); + assert.strictEqual(service.getItem('some.other.key'), 'some.other.value'); service.setItem('some.other.key', 'some.other.value'); - assert.equal(service.getItem('some.other.key'), 'some.other.value'); + assert.strictEqual(service.getItem('some.other.key'), 'some.other.value'); service.setItem('some.undefined.key', undefined); - assert.equal(service.getItem('some.undefined.key', 'some.default'), 'some.default'); + assert.strictEqual(service.getItem('some.undefined.key', 'some.default'), 'some.default'); service.setItem('some.null.key', null); - assert.equal(service.getItem('some.null.key', 'some.default'), 'some.default'); + assert.strictEqual(service.getItem('some.null.key', 'some.default'), 'some.default'); }); -}); \ No newline at end of file +}); diff --git a/src/vs/platform/storage/node/storageIpc.ts b/src/vs/platform/storage/node/storageIpc.ts index 8645255d9..67195995c 100644 --- a/src/vs/platform/storage/node/storageIpc.ts +++ b/src/vs/platform/storage/node/storageIpc.ts @@ -200,12 +200,10 @@ export class GlobalStorageDatabaseChannelClient extends Disposable implements IS return this.channel.call('updateItems', serializableRequest); } - close(): Promise { + async close(): Promise { // when we are about to close, we start to ignore main-side changes since we close anyway dispose(this.onDidChangeItemsOnMainListener); - - return Promise.resolve(); // global storage is closed on the main side } dispose(): void { diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts index 97cb9b161..5be6557bd 100644 --- a/src/vs/platform/storage/node/storageService.ts +++ b/src/vs/platform/storage/node/storageService.ts @@ -12,7 +12,7 @@ import { mark } from 'vs/base/common/performance'; import { join } from 'vs/base/common/path'; import { copy, exists, mkdirp, writeFile } from 'vs/base/node/pfs'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IWorkspaceInitializationPayload, isWorkspaceIdentifier, isSingleFolderWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; +import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; import { assertIsDefined } from 'vs/base/common/types'; import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; @@ -83,7 +83,7 @@ export class NativeStorageService extends AbstractStorageService { const useInMemoryStorage = !!this.environmentService.extensionTestsLocationURI; // no storage during extension tests! // Create workspace storage and initialize - mark('willInitWorkspaceStorage'); + mark('code/willInitWorkspaceStorage'); try { const workspaceStorage = this.createWorkspaceStorage( useInMemoryStorage ? SQLiteStorageDatabase.IN_MEMORY_PATH : join(result.path, NativeStorageService.WORKSPACE_STORAGE_NAME), @@ -99,7 +99,7 @@ export class NativeStorageService extends AbstractStorageService { workspaceStorage.set(IS_NEW_KEY, false); } } finally { - mark('didInitWorkspaceStorage'); + mark('code/didInitWorkspaceStorage'); } } catch (error) { this.logService.error(`[storage] initializeWorkspaceStorage(): Unable to init workspace storage due to ${error}`); @@ -148,23 +148,22 @@ export class NativeStorageService extends AbstractStorageService { private ensureWorkspaceStorageFolderMeta(payload: IWorkspaceInitializationPayload): void { let meta: object | undefined = undefined; - if (isSingleFolderWorkspaceInitializationPayload(payload)) { - meta = { folder: payload.folder.toString() }; + if (isSingleFolderWorkspaceIdentifier(payload)) { + meta = { folder: payload.uri.toString() }; } else if (isWorkspaceIdentifier(payload)) { - meta = { configuration: payload.configPath }; + meta = { workspace: payload.configPath.toString() }; } if (meta) { - const logService = this.logService; - const workspaceStorageMetaPath = join(this.getWorkspaceStorageFolderPath(payload), NativeStorageService.WORKSPACE_META_NAME); - (async function () { + (async () => { try { + const workspaceStorageMetaPath = join(this.getWorkspaceStorageFolderPath(payload), NativeStorageService.WORKSPACE_META_NAME); const storageExists = await exists(workspaceStorageMetaPath); if (!storageExists) { await writeFile(workspaceStorageMetaPath, JSON.stringify(meta, undefined, 2)); } } catch (error) { - logService.error(error); + this.logService.error(error); } })(); } diff --git a/src/vs/platform/storage/test/common/storageService.test.ts b/src/vs/platform/storage/test/common/storageService.test.ts index 886efb2f8..182ff12af 100644 --- a/src/vs/platform/storage/test/common/storageService.test.ts +++ b/src/vs/platform/storage/test/common/storageService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { strictEqual, ok, equal } from 'assert'; +import { strictEqual, ok } from 'assert'; import { StorageScope, InMemoryStorageService, StorageTarget, IStorageValueChangeEvent, IStorageTargetChangeEvent } from 'vs/platform/storage/common/storage'; suite('StorageService', function () { @@ -32,15 +32,15 @@ suite('StorageService', function () { storage.store('test.get', 'foobar', scope, StorageTarget.MACHINE); strictEqual(storage.get('test.get', scope, (undefined)!), 'foobar'); let storageValueChangeEvent = storageValueChangeEvents.find(e => e.key === 'test.get'); - equal(storageValueChangeEvent?.scope, scope); - equal(storageValueChangeEvent?.key, 'test.get'); + strictEqual(storageValueChangeEvent?.scope, scope); + strictEqual(storageValueChangeEvent?.key, 'test.get'); storageValueChangeEvents = []; storage.store('test.get', '', scope, StorageTarget.MACHINE); strictEqual(storage.get('test.get', scope, (undefined)!), ''); storageValueChangeEvent = storageValueChangeEvents.find(e => e.key === 'test.get'); - equal(storageValueChangeEvent!.scope, scope); - equal(storageValueChangeEvent!.key, 'test.get'); + strictEqual(storageValueChangeEvent!.scope, scope); + strictEqual(storageValueChangeEvent!.key, 'test.get'); storage.store('test.getNumber', 5, scope, StorageTarget.MACHINE); strictEqual(storage.getNumber('test.getNumber', scope, (undefined)!), 5); @@ -79,8 +79,8 @@ suite('StorageService', function () { storage.remove('test.remove', scope); ok(!storage.get('test.remove', scope, (undefined)!)); let storageValueChangeEvent = storageValueChangeEvents.find(e => e.key === 'test.remove'); - equal(storageValueChangeEvent?.scope, scope); - equal(storageValueChangeEvent?.key, 'test.remove'); + strictEqual(storageValueChangeEvent?.scope, scope); + strictEqual(storageValueChangeEvent?.key, 'test.remove'); } test('Keys (in-memory)', () => { @@ -107,20 +107,20 @@ suite('StorageService', function () { storage.store('test.target1', 'value1', scope, target); strictEqual(storage.keys(scope, target).length, 1); - equal(storageTargetEvent?.scope, scope); - equal(storageValueChangeEvent?.key, 'test.target1'); - equal(storageValueChangeEvent?.scope, scope); - equal(storageValueChangeEvent?.target, target); + strictEqual(storageTargetEvent?.scope, scope); + strictEqual(storageValueChangeEvent?.key, 'test.target1'); + strictEqual(storageValueChangeEvent?.scope, scope); + strictEqual(storageValueChangeEvent?.target, target); storageTargetEvent = undefined; storageValueChangeEvent = Object.create(null); storage.store('test.target1', 'otherValue1', scope, target); strictEqual(storage.keys(scope, target).length, 1); - equal(storageTargetEvent, undefined); - equal(storageValueChangeEvent?.key, 'test.target1'); - equal(storageValueChangeEvent?.scope, scope); - equal(storageValueChangeEvent?.target, target); + strictEqual(storageTargetEvent, undefined); + strictEqual(storageValueChangeEvent?.key, 'test.target1'); + strictEqual(storageValueChangeEvent?.scope, scope); + strictEqual(storageValueChangeEvent?.target, target); storage.store('test.target2', 'value2', scope, target); storage.store('test.target3', 'value3', scope, target); @@ -142,9 +142,9 @@ suite('StorageService', function () { storage.remove('test.target4', scope); strictEqual(storage.keys(scope, target).length, keysLength); - equal(storageTargetEvent?.scope, scope); - equal(storageValueChangeEvent?.key, 'test.target4'); - equal(storageValueChangeEvent?.scope, scope); + strictEqual(storageTargetEvent?.scope, scope); + strictEqual(storageValueChangeEvent?.key, 'test.target4'); + strictEqual(storageValueChangeEvent?.scope, scope); } } @@ -171,7 +171,7 @@ suite('StorageService', function () { storage.store('test.target1', undefined, scope, target); strictEqual(storage.keys(scope, target).length, 0); - equal(storageTargetEvent?.scope, scope); + strictEqual(storageTargetEvent?.scope, scope); storage.store('test.target1', '', scope, target); strictEqual(storage.keys(scope, target).length, 1); diff --git a/src/vs/platform/storage/test/electron-browser/storage.test.ts b/src/vs/platform/storage/test/electron-browser/storage.test.ts index 4e5e10a4e..56f0a638d 100644 --- a/src/vs/platform/storage/test/electron-browser/storage.test.ts +++ b/src/vs/platform/storage/test/electron-browser/storage.test.ts @@ -3,12 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { equal } from 'assert'; +import { strictEqual } from 'assert'; import { FileStorageDatabase } from 'vs/platform/storage/browser/storageService'; -import { generateUuid } from 'vs/base/common/uuid'; import { join } from 'vs/base/common/path'; import { tmpdir } from 'os'; -import { rimraf, RimRafMode } from 'vs/base/node/pfs'; +import { rimraf } from 'vs/base/node/pfs'; import { NullLogService } from 'vs/platform/log/common/log'; import { Storage } from 'vs/base/parts/storage/common/storage'; import { URI } from 'vs/base/common/uri'; @@ -20,11 +19,10 @@ import { Schemas } from 'vs/base/common/network'; suite('Storage', () => { - const parentDir = getRandomTestPath(tmpdir(), 'vsctests', 'storageservice'); + let testDir: string; let fileService: FileService; let fileProvider: DiskFileSystemProvider; - let testDir: string; const disposables = new DisposableStore(); @@ -38,14 +36,13 @@ suite('Storage', () => { disposables.add(fileService.registerProvider(Schemas.file, fileProvider)); disposables.add(fileProvider); - const id = generateUuid(); - testDir = join(parentDir, id); + testDir = getRandomTestPath(tmpdir(), 'vsctests', 'storageservice'); }); - teardown(async () => { + teardown(() => { disposables.clear(); - await rimraf(parentDir, RimRafMode.MOVE); + return rimraf(testDir); }); test('File Based Storage', async () => { @@ -57,9 +54,9 @@ suite('Storage', () => { storage.set('barNumber', 55); storage.set('barBoolean', true); - equal(storage.get('bar'), 'foo'); - equal(storage.get('barNumber'), '55'); - equal(storage.get('barBoolean'), 'true'); + strictEqual(storage.get('bar'), 'foo'); + strictEqual(storage.get('barNumber'), '55'); + strictEqual(storage.get('barBoolean'), 'true'); await storage.close(); @@ -67,17 +64,17 @@ suite('Storage', () => { await storage.init(); - equal(storage.get('bar'), 'foo'); - equal(storage.get('barNumber'), '55'); - equal(storage.get('barBoolean'), 'true'); + strictEqual(storage.get('bar'), 'foo'); + strictEqual(storage.get('barNumber'), '55'); + strictEqual(storage.get('barBoolean'), 'true'); storage.delete('bar'); storage.delete('barNumber'); storage.delete('barBoolean'); - equal(storage.get('bar', 'undefined'), 'undefined'); - equal(storage.get('barNumber', 'undefinedNumber'), 'undefinedNumber'); - equal(storage.get('barBoolean', 'undefinedBoolean'), 'undefinedBoolean'); + strictEqual(storage.get('bar', 'undefined'), 'undefined'); + strictEqual(storage.get('barNumber', 'undefinedNumber'), 'undefinedNumber'); + strictEqual(storage.get('barBoolean', 'undefinedBoolean'), 'undefinedBoolean'); await storage.close(); @@ -85,8 +82,8 @@ suite('Storage', () => { await storage.init(); - equal(storage.get('bar', 'undefined'), 'undefined'); - equal(storage.get('barNumber', 'undefinedNumber'), 'undefinedNumber'); - equal(storage.get('barBoolean', 'undefinedBoolean'), 'undefinedBoolean'); + strictEqual(storage.get('bar', 'undefined'), 'undefined'); + strictEqual(storage.get('barNumber', 'undefinedNumber'), 'undefinedNumber'); + strictEqual(storage.get('barBoolean', 'undefinedBoolean'), 'undefinedBoolean'); }); }); diff --git a/src/vs/platform/storage/test/node/storageService.test.ts b/src/vs/platform/storage/test/node/storageService.test.ts index e0a410ef6..6677ab24d 100644 --- a/src/vs/platform/storage/test/node/storageService.test.ts +++ b/src/vs/platform/storage/test/node/storageService.test.ts @@ -3,33 +3,33 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { equal } from 'assert'; +import { strictEqual } from 'assert'; import { StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { NativeStorageService } from 'vs/platform/storage/node/storageService'; -import { generateUuid } from 'vs/base/common/uuid'; -import { join } from 'vs/base/common/path'; import { tmpdir } from 'os'; -import { mkdirp, rimraf, RimRafMode } from 'vs/base/node/pfs'; +import { mkdirp, rimraf } from 'vs/base/node/pfs'; import { NullLogService } from 'vs/platform/log/common/log'; import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { InMemoryStorageDatabase } from 'vs/base/parts/storage/common/storage'; import { URI } from 'vs/base/common/uri'; +import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; -suite('NativeStorageService', function () { +flakySuite('NativeStorageService', function () { - function uniqueStorageDir(): string { - const id = generateUuid(); + let testDir: string; - return join(tmpdir(), 'vsctests', id, 'storage2', id); - } + setup(() => { + testDir = getRandomTestPath(tmpdir(), 'vsctests', 'storageservice'); - test('Migrate Data', async () => { + return mkdirp(testDir); + }); - // Given issues such as https://github.com/microsoft/vscode/issues/108113 - // we see random test failures when accessing the native file system. - this.retries(3); - this.timeout(1000 * 20); + teardown(() => { + return rimraf(testDir); + }); + + test('Migrate Data', async function () { class StorageTestEnvironmentService extends NativeEnvironmentService { @@ -46,27 +46,23 @@ suite('NativeStorageService', function () { } } - const storageDir = uniqueStorageDir(); - await mkdirp(storageDir); - - const storage = new NativeStorageService(new InMemoryStorageDatabase(), new NullLogService(), new StorageTestEnvironmentService(URI.file(storageDir), storageDir)); + const storage = new NativeStorageService(new InMemoryStorageDatabase(), new NullLogService(), new StorageTestEnvironmentService(URI.file(testDir), testDir)); await storage.initialize({ id: String(Date.now()) }); storage.store('bar', 'foo', StorageScope.WORKSPACE, StorageTarget.MACHINE); storage.store('barNumber', 55, StorageScope.WORKSPACE, StorageTarget.MACHINE); storage.store('barBoolean', true, StorageScope.GLOBAL, StorageTarget.MACHINE); - equal(storage.get('bar', StorageScope.WORKSPACE), 'foo'); - equal(storage.getNumber('barNumber', StorageScope.WORKSPACE), 55); - equal(storage.getBoolean('barBoolean', StorageScope.GLOBAL), true); + strictEqual(storage.get('bar', StorageScope.WORKSPACE), 'foo'); + strictEqual(storage.getNumber('barNumber', StorageScope.WORKSPACE), 55); + strictEqual(storage.getBoolean('barBoolean', StorageScope.GLOBAL), true); await storage.migrate({ id: String(Date.now() + 100) }); - equal(storage.get('bar', StorageScope.WORKSPACE), 'foo'); - equal(storage.getNumber('barNumber', StorageScope.WORKSPACE), 55); - equal(storage.getBoolean('barBoolean', StorageScope.GLOBAL), true); + strictEqual(storage.get('bar', StorageScope.WORKSPACE), 'foo'); + strictEqual(storage.getNumber('barNumber', StorageScope.WORKSPACE), 55); + strictEqual(storage.getBoolean('barBoolean', StorageScope.GLOBAL), true); await storage.close(); - await rimraf(storageDir, RimRafMode.MOVE); }); }); diff --git a/src/vs/platform/telemetry/node/commonProperties.ts b/src/vs/platform/telemetry/common/commonProperties.ts similarity index 78% rename from src/vs/platform/telemetry/node/commonProperties.ts rename to src/vs/platform/telemetry/common/commonProperties.ts index d681c0c77..46f08b5d3 100644 --- a/src/vs/platform/telemetry/node/commonProperties.ts +++ b/src/vs/platform/telemetry/common/commonProperties.ts @@ -3,12 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as Platform from 'vs/base/common/platform'; -import * as os from 'os'; -import * as uuid from 'vs/base/common/uuid'; -import { readFile } from 'vs/base/node/pfs'; +import { IFileService } from 'vs/platform/files/common/files'; +import { isLinuxSnap, PlatformToString, platform } from 'vs/base/common/platform'; +import { platform as nodePlatform, env } from 'vs/base/common/process'; +import { generateUuid } from 'vs/base/common/uuid'; +import { URI } from 'vs/base/common/uri'; export async function resolveCommonProperties( + fileService: IFileService, + release: string, + arch: string, commit: string | undefined, version: string | undefined, machineId: string | undefined, @@ -21,19 +25,19 @@ export async function resolveCommonProperties( // __GDPR__COMMON__ "common.machineId" : { "endPoint": "MacAddressHash", "classification": "EndUserPseudonymizedInformation", "purpose": "FeatureInsight" } result['common.machineId'] = machineId; // __GDPR__COMMON__ "sessionID" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - result['sessionID'] = uuid.generateUuid() + Date.now(); + result['sessionID'] = generateUuid() + Date.now(); // __GDPR__COMMON__ "commitHash" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } result['commitHash'] = commit; // __GDPR__COMMON__ "version" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } result['version'] = version; // __GDPR__COMMON__ "common.platformVersion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - result['common.platformVersion'] = (os.release() || '').replace(/^(\d+)(\.\d+)?(\.\d+)?(.*)/, '$1$2$3'); + result['common.platformVersion'] = (release || '').replace(/^(\d+)(\.\d+)?(\.\d+)?(.*)/, '$1$2$3'); // __GDPR__COMMON__ "common.platform" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - result['common.platform'] = Platform.PlatformToString(Platform.platform); + result['common.platform'] = PlatformToString(platform); // __GDPR__COMMON__ "common.nodePlatform" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } - result['common.nodePlatform'] = process.platform; + result['common.nodePlatform'] = nodePlatform; // __GDPR__COMMON__ "common.nodeArch" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } - result['common.nodeArch'] = process.arch; + result['common.nodeArch'] = arch; // __GDPR__COMMON__ "common.product" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } result['common.product'] = product || 'desktop'; @@ -64,16 +68,16 @@ export async function resolveCommonProperties( } }); - if (process.platform === 'linux' && process.env.SNAP && process.env.SNAP_REVISION) { + if (isLinuxSnap) { // __GDPR__COMMON__ "common.snap" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } result['common.snap'] = 'true'; } try { - const contents = await readFile(installSourcePath, 'utf8'); + const contents = await fileService.readFile(URI.file(installSourcePath)); // __GDPR__COMMON__ "common.source" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - result['common.source'] = contents.slice(0, 30); + result['common.source'] = contents.value.toString().slice(0, 30); } catch (error) { // ignore error } @@ -82,10 +86,11 @@ export async function resolveCommonProperties( } function verifyMicrosoftInternalDomain(domainList: readonly string[]): boolean { - if (!process || !process.env || !process.env['USERDNSDOMAIN']) { + const userDnsDomain = env['USERDNSDOMAIN']; + if (!userDnsDomain) { return false; } - const domain = process.env['USERDNSDOMAIN']!.toLowerCase(); + const domain = userDnsDomain.toLowerCase(); return domainList.some(msftDomain => domain === msftDomain); } diff --git a/src/vs/platform/telemetry/node/telemetryIpc.ts b/src/vs/platform/telemetry/common/telemetryIpc.ts similarity index 100% rename from src/vs/platform/telemetry/node/telemetryIpc.ts rename to src/vs/platform/telemetry/common/telemetryIpc.ts diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index b5239353a..7bf7747c4 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -332,6 +332,12 @@ export const editorHoverStatusBarBackground = registerColor('editorHoverWidget.s */ export const editorActiveLinkForeground = registerColor('editorLink.activeForeground', { dark: '#4E94CE', light: Color.blue, hc: Color.cyan }, nls.localize('activeLinkForeground', 'Color of active links.')); +/** + * Inline hints + */ +export const editorInlineHintForeground = registerColor('editorInlineHint.foreground', { dark: editorWidgetBackground, light: editorWidgetForeground, hc: editorWidgetBackground }, nls.localize('editorInlineHintForeground', 'Foreground color of inline hints')); +export const editorInlineHintBackground = registerColor('editorInlineHint.background', { dark: editorWidgetForeground, light: editorWidgetBackground, hc: editorWidgetForeground }, nls.localize('editorInlineHintBackground', 'Background color of inline hints')); + /** * Editor lighbulb icon colors */ diff --git a/src/vs/platform/theme/common/iconRegistry.ts b/src/vs/platform/theme/common/iconRegistry.ts index a19a41a84..4079cc4e2 100644 --- a/src/vs/platform/theme/common/iconRegistry.ts +++ b/src/vs/platform/theme/common/iconRegistry.ts @@ -12,7 +12,6 @@ import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/plat import { RunOnceScheduler } from 'vs/base/common/async'; import * as Codicons from 'vs/base/common/codicons'; - // ------ API types @@ -190,11 +189,6 @@ class IconRegistry implements IIconRegistry { public toString() { const sorter = (i1: IconContribution, i2: IconContribution) => { - const isThemeIcon1 = ThemeIcon.isThemeIcon(i1.defaults); - const isThemeIcon2 = ThemeIcon.isThemeIcon(i2.defaults); - if (isThemeIcon1 !== isThemeIcon2) { - return isThemeIcon1 ? -1 : 1; - } return i1.id.localeCompare(i2.id); }; const classNames = (i: IconContribution) => { @@ -205,18 +199,24 @@ class IconRegistry implements IIconRegistry { }; let reference = []; - let docCss = []; + reference.push(`| preview | identifier | default codicon id | description`); + reference.push(`| ----------- | --------------------------------- | --------------------------------- | --------------------------------- |`); const contributions = Object.keys(this.iconsById).map(key => this.iconsById[key]); - for (const i of contributions.sort(sorter)) { - reference.push(`||${i.id}|${ThemeIcon.isThemeIcon(i.defaults) ? i.defaults.id : ''}|${i.description || ''}|`); - - if (!ThemeIcon.isThemeIcon((i.defaults))) { - docCss.push(`.codicon-${i.id}:before { content: "${i.defaults.character}" }`); - } + for (const i of contributions.filter(i => !!i.description).sort(sorter)) { + reference.push(`||${i.id}|${ThemeIcon.isThemeIcon(i.defaults) ? i.defaults.id : i.id}|${i.description || ''}|`); } - return reference.join('\n') + '\n\n' + docCss.join('\n'); + + reference.push(`| preview | identifier `); + reference.push(`| ----------- | --------------------------------- |`); + + for (const i of contributions.filter(i => !ThemeIcon.isThemeIcon(i.defaults)).sort(sorter)) { + reference.push(`||${i.id}|`); + + } + + return reference.join('\n'); } } @@ -262,3 +262,5 @@ export const widgetClose = registerIcon('widget-close', Codicons.Codicon.close, export const gotoPreviousLocation = registerIcon('goto-previous-location', Codicons.Codicon.arrowUp, localize('previousChangeIcon', 'Icon for goto previous editor location.')); export const gotoNextLocation = registerIcon('goto-next-location', Codicons.Codicon.arrowDown, localize('nextChangeIcon', 'Icon for goto next editor location.')); + +export const syncing = ThemeIcon.modify(Codicons.Codicon.sync, 'spin'); diff --git a/src/vs/platform/theme/common/themeService.ts b/src/vs/platform/theme/common/themeService.ts index f82c45767..1096c6be5 100644 --- a/src/vs/platform/theme/common/themeService.ts +++ b/src/vs/platform/theme/common/themeService.ts @@ -11,7 +11,7 @@ import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { Event, Emitter } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ColorScheme } from 'vs/platform/theme/common/theme'; -import { CSSIcon } from 'vs/base/common/codicons'; +import { Codicon, CSSIcon } from 'vs/base/common/codicons'; export const IThemeService = createDecorator('themeService'); @@ -70,50 +70,13 @@ export namespace ThemeIcon { return ti1.id === ti2.id && ti1.color?.id === ti2.color?.id; } - const _regexAsClassName = /^(codicon\/)?([a-z-]+)(~[a-z]+)?$/i; - - export function asClassNameArray(icon: ThemeIcon): string[] { - const match = _regexAsClassName.exec(icon.id); - if (!match) { - return ['codicon', 'codicon-error']; - } - let [, , name, modifier] = match; - let className = `codicon-${name}`; - if (modifier) { - return ['codicon', className, modifier.substr(1)]; - } - return ['codicon', className]; - } - - - export function asClassName(icon: ThemeIcon): string { - return asClassNameArray(icon).join(' '); - } - - export function asCSSSelector(icon: ThemeIcon): string { - return '.' + asClassNameArray(icon).join('.'); - } - - export function asCSSIcon(icon: ThemeIcon): CSSIcon { - return { - classNames: asClassName(icon) - }; - } - - export function asCodiconLabel(icon: ThemeIcon): string { - return '$(' + icon.id + ')'; - } - - export function revive(icon: any): ThemeIcon | undefined { - if (ThemeIcon.isThemeIcon(icon)) { - return { id: icon.id, color: icon.color ? { id: icon.color.id } : undefined }; - } - return undefined; - } + export const asClassNameArray: (icon: ThemeIcon) => string[] = CSSIcon.asClassNameArray; + export const asClassName: (icon: ThemeIcon) => string = CSSIcon.asClassName; + export const asCSSSelector: (icon: ThemeIcon) => string = CSSIcon.asCSSSelector; } -export const FileThemeIcon = { id: 'file' }; -export const FolderThemeIcon = { id: 'folder' }; +export const FileThemeIcon = Codicon.file; +export const FolderThemeIcon = Codicon.folder; export function getThemeTypeSelector(type: ColorScheme): string { switch (type) { diff --git a/src/vs/platform/theme/test/common/testThemeService.ts b/src/vs/platform/theme/test/common/testThemeService.ts index 2bdd34d8e..e21487ef0 100644 --- a/src/vs/platform/theme/test/common/testThemeService.ts +++ b/src/vs/platform/theme/test/common/testThemeService.ts @@ -12,8 +12,11 @@ export class TestColorTheme implements IColorTheme { public readonly label = 'test'; - constructor(private colors: { [id: string]: string; } = {}, public type = ColorScheme.DARK) { - } + constructor( + private colors: { [id: string]: string; } = {}, + public type = ColorScheme.DARK, + public readonly semanticHighlighting = false + ) { } getColor(color: string, useDefault?: boolean): Color | undefined { let value = this.colors[color]; @@ -31,8 +34,6 @@ export class TestColorTheme implements IColorTheme { return undefined; } - readonly semanticHighlighting = false; - get tokenColorMap(): string[] { return []; } diff --git a/src/vs/platform/undoRedo/common/undoRedo.ts b/src/vs/platform/undoRedo/common/undoRedo.ts index 021003bab..80512775d 100644 --- a/src/vs/platform/undoRedo/common/undoRedo.ts +++ b/src/vs/platform/undoRedo/common/undoRedo.ts @@ -18,6 +18,10 @@ export interface IResourceUndoRedoElement { readonly type: UndoRedoElementType.Resource; readonly resource: URI; readonly label: string; + /** + * Show a message to the user confirming when trying to undo this element + */ + readonly confirmBeforeUndo?: boolean; undo(): Promise | void; redo(): Promise | void; } @@ -26,6 +30,10 @@ export interface IWorkspaceUndoRedoElement { readonly type: UndoRedoElementType.Workspace; readonly resources: readonly URI[]; readonly label: string; + /** + * Show a message to the user confirming when trying to undo this element + */ + readonly confirmBeforeUndo?: boolean; undo(): Promise | void; redo(): Promise | void; diff --git a/src/vs/platform/undoRedo/common/undoRedoService.ts b/src/vs/platform/undoRedo/common/undoRedoService.ts index 2fb802fbc..7e205e707 100644 --- a/src/vs/platform/undoRedo/common/undoRedoService.ts +++ b/src/vs/platform/undoRedo/common/undoRedoService.ts @@ -27,6 +27,7 @@ class ResourceStackElement { public readonly type = UndoRedoElementType.Resource; public readonly actual: IUndoRedoElement; public readonly label: string; + public readonly confirmBeforeUndo: boolean; public readonly resourceLabel: string; public readonly strResource: string; @@ -41,6 +42,7 @@ class ResourceStackElement { constructor(actual: IUndoRedoElement, resourceLabel: string, strResource: string, groupId: number, groupOrder: number, sourceId: number, sourceOrder: number) { this.actual = actual; this.label = actual.label; + this.confirmBeforeUndo = actual.confirmBeforeUndo || false; this.resourceLabel = resourceLabel; this.strResource = strResource; this.resourceLabels = [this.resourceLabel]; @@ -129,6 +131,7 @@ class WorkspaceStackElement { public readonly type = UndoRedoElementType.Workspace; public readonly actual: IWorkspaceUndoRedoElement; public readonly label: string; + public readonly confirmBeforeUndo: boolean; public readonly resourceLabels: string[]; public readonly strResources: string[]; @@ -142,6 +145,7 @@ class WorkspaceStackElement { constructor(actual: IWorkspaceUndoRedoElement, resourceLabels: string[], strResources: string[], groupId: number, groupOrder: number, sourceId: number, sourceOrder: number) { this.actual = actual; this.label = actual.label; + this.confirmBeforeUndo = actual.confirmBeforeUndo || false; this.resourceLabels = resourceLabels; this.strResources = strResources; this.groupId = groupId; @@ -811,7 +815,7 @@ export class UndoRedoService implements IUndoRedoService { if (element.canSplit()) { this._splitPastWorkspaceElement(element, ignoreResources); this._notificationService.info(message); - return new WorkspaceVerificationError(this._undo(strResource)); + return new WorkspaceVerificationError(this._undo(strResource, 0, true)); } else { // Cannot safely split this workspace element => flush all undo/redo stacks for (const strResource of element.strResources) { @@ -899,13 +903,13 @@ export class UndoRedoService implements IUndoRedoService { return null; } - private _workspaceUndo(strResource: string, element: WorkspaceStackElement): Promise | void { + private _workspaceUndo(strResource: string, element: WorkspaceStackElement, undoConfirmed: boolean): Promise | void { const affectedEditStacks = this._getAffectedEditStacks(element); const verificationError = this._checkWorkspaceUndo(strResource, element, affectedEditStacks, /*invalidated resources will be checked after the prepare call*/false); if (verificationError) { return verificationError.returnValue; } - return this._confirmAndExecuteWorkspaceUndo(strResource, element, affectedEditStacks); + return this._confirmAndExecuteWorkspaceUndo(strResource, element, affectedEditStacks, undoConfirmed); } private _isPartOfUndoGroup(element: WorkspaceStackElement): boolean { @@ -933,7 +937,7 @@ export class UndoRedoService implements IUndoRedoService { return false; } - private async _confirmAndExecuteWorkspaceUndo(strResource: string, element: WorkspaceStackElement, editStackSnapshot: EditStackSnapshot): Promise { + private async _confirmAndExecuteWorkspaceUndo(strResource: string, element: WorkspaceStackElement, editStackSnapshot: EditStackSnapshot, undoConfirmed: boolean): Promise { if (element.canSplit() && !this._isPartOfUndoGroup(element)) { // this element can be split @@ -959,7 +963,7 @@ export class UndoRedoService implements IUndoRedoService { if (result.choice === 1) { // choice: undo this file this._splitPastWorkspaceElement(element, null); - return this._undo(strResource); + return this._undo(strResource, 0, true); } // choice: undo in all files @@ -969,6 +973,8 @@ export class UndoRedoService implements IUndoRedoService { if (verificationError1) { return verificationError1.returnValue; } + + undoConfirmed = true; } // prepare @@ -989,10 +995,10 @@ export class UndoRedoService implements IUndoRedoService { for (const editStack of editStackSnapshot.editStacks) { editStack.moveBackward(element); } - return this._safeInvokeWithLocks(element, () => element.actual.undo(), editStackSnapshot, cleanup, () => this._continueUndoInGroup(element.groupId)); + return this._safeInvokeWithLocks(element, () => element.actual.undo(), editStackSnapshot, cleanup, () => this._continueUndoInGroup(element.groupId, undoConfirmed)); } - private _resourceUndo(editStack: ResourceEditStack, element: ResourceStackElement): Promise | void { + private _resourceUndo(editStack: ResourceEditStack, element: ResourceStackElement, undoConfirmed: boolean): Promise | void { if (!element.isValid) { // invalid element => immediately flush edit stack! editStack.flushAllElements(); @@ -1008,7 +1014,7 @@ export class UndoRedoService implements IUndoRedoService { } return this._invokeResourcePrepare(element, (cleanup) => { editStack.moveBackward(element); - return this._safeInvokeWithLocks(element, () => element.actual.undo(), new EditStackSnapshot([editStack]), cleanup, () => this._continueUndoInGroup(element.groupId)); + return this._safeInvokeWithLocks(element, () => element.actual.undo(), new EditStackSnapshot([editStack]), cleanup, () => this._continueUndoInGroup(element.groupId, undoConfirmed)); }); } @@ -1037,29 +1043,29 @@ export class UndoRedoService implements IUndoRedoService { return [matchedElement, matchedStrResource]; } - private _continueUndoInGroup(groupId: number): Promise | void { + private _continueUndoInGroup(groupId: number, undoConfirmed: boolean): Promise | void { if (!groupId) { return; } const [, matchedStrResource] = this._findClosestUndoElementInGroup(groupId); if (matchedStrResource) { - return this._undo(matchedStrResource); + return this._undo(matchedStrResource, 0, undoConfirmed); } } public undo(resourceOrSource: URI | UndoRedoSource): Promise | void { if (resourceOrSource instanceof UndoRedoSource) { const [, matchedStrResource] = this._findClosestUndoElementWithSource(resourceOrSource.id); - return matchedStrResource ? this._undo(matchedStrResource, resourceOrSource.id) : undefined; + return matchedStrResource ? this._undo(matchedStrResource, resourceOrSource.id, false) : undefined; } if (typeof resourceOrSource === 'string') { - return this._undo(resourceOrSource); + return this._undo(resourceOrSource, 0, false); } - return this._undo(this.getUriComparisonKey(resourceOrSource)); + return this._undo(this.getUriComparisonKey(resourceOrSource), 0, false); } - private _undo(strResource: string, sourceId: number = 0): Promise | void { + private _undo(strResource: string, sourceId: number = 0, undoConfirmed: boolean): Promise | void { if (!this._editStacks.has(strResource)) { return; } @@ -1075,20 +1081,21 @@ export class UndoRedoService implements IUndoRedoService { const [matchedElement, matchedStrResource] = this._findClosestUndoElementInGroup(element.groupId); if (element !== matchedElement && matchedStrResource) { // there is an element in the same group that should be undone before this one - return this._undo(matchedStrResource); + return this._undo(matchedStrResource, sourceId, undoConfirmed); } } - if (element.sourceId !== sourceId) { - // Hit a different source, prompt for confirmation - return this._confirmDifferentSourceAndContinueUndo(strResource, element); + const shouldPromptForConfirmation = (element.sourceId !== sourceId || element.confirmBeforeUndo); + if (shouldPromptForConfirmation && !undoConfirmed) { + // Hit a different source or the element asks for prompt before undo, prompt for confirmation + return this._confirmAndContinueUndo(strResource, sourceId, element); } try { if (element.type === UndoRedoElementType.Workspace) { - return this._workspaceUndo(strResource, element); + return this._workspaceUndo(strResource, element, undoConfirmed); } else { - return this._resourceUndo(editStack, element); + return this._resourceUndo(editStack, element, undoConfirmed); } } finally { if (DEBUG) { @@ -1097,7 +1104,7 @@ export class UndoRedoService implements IUndoRedoService { } } - private async _confirmDifferentSourceAndContinueUndo(strResource: string, element: StackElement): Promise { + private async _confirmAndContinueUndo(strResource: string, sourceId: number, element: StackElement): Promise { const result = await this._dialogService.show( Severity.Info, nls.localize('confirmDifferentSource', "Would you like to undo '{0}'?", element.label), @@ -1116,7 +1123,7 @@ export class UndoRedoService implements IUndoRedoService { } // choice: undo - return this._undo(strResource, element.sourceId); + return this._undo(strResource, sourceId, true); } private _findClosestRedoElementWithSource(sourceId: number): [StackElement | null, string | null] { diff --git a/src/vs/platform/undoRedo/test/common/undoRedoService.test.ts b/src/vs/platform/undoRedo/test/common/undoRedoService.test.ts index 4c0a48e58..49f494454 100644 --- a/src/vs/platform/undoRedo/test/common/undoRedoService.test.ts +++ b/src/vs/platform/undoRedo/test/common/undoRedoService.test.ts @@ -23,9 +23,9 @@ suite('UndoRedoService', () => { const resource = URI.file('test.txt'); const service = createUndoRedoService(); - assert.equal(service.canUndo(resource), false); - assert.equal(service.canRedo(resource), false); - assert.equal(service.hasElements(resource), false); + assert.strictEqual(service.canUndo(resource), false); + assert.strictEqual(service.canRedo(resource), false); + assert.strictEqual(service.hasElements(resource), false); assert.ok(service.getLastElement(resource) === null); let undoCall1 = 0; @@ -39,27 +39,27 @@ suite('UndoRedoService', () => { }; service.pushElement(element1); - assert.equal(undoCall1, 0); - assert.equal(redoCall1, 0); - assert.equal(service.canUndo(resource), true); - assert.equal(service.canRedo(resource), false); - assert.equal(service.hasElements(resource), true); + assert.strictEqual(undoCall1, 0); + assert.strictEqual(redoCall1, 0); + assert.strictEqual(service.canUndo(resource), true); + assert.strictEqual(service.canRedo(resource), false); + assert.strictEqual(service.hasElements(resource), true); assert.ok(service.getLastElement(resource) === element1); service.undo(resource); - assert.equal(undoCall1, 1); - assert.equal(redoCall1, 0); - assert.equal(service.canUndo(resource), false); - assert.equal(service.canRedo(resource), true); - assert.equal(service.hasElements(resource), true); + assert.strictEqual(undoCall1, 1); + assert.strictEqual(redoCall1, 0); + assert.strictEqual(service.canUndo(resource), false); + assert.strictEqual(service.canRedo(resource), true); + assert.strictEqual(service.hasElements(resource), true); assert.ok(service.getLastElement(resource) === null); service.redo(resource); - assert.equal(undoCall1, 1); - assert.equal(redoCall1, 1); - assert.equal(service.canUndo(resource), true); - assert.equal(service.canRedo(resource), false); - assert.equal(service.hasElements(resource), true); + assert.strictEqual(undoCall1, 1); + assert.strictEqual(redoCall1, 1); + assert.strictEqual(service.canUndo(resource), true); + assert.strictEqual(service.canRedo(resource), false); + assert.strictEqual(service.hasElements(resource), true); assert.ok(service.getLastElement(resource) === element1); let undoCall2 = 0; @@ -73,24 +73,24 @@ suite('UndoRedoService', () => { }; service.pushElement(element2); - assert.equal(undoCall1, 1); - assert.equal(redoCall1, 1); - assert.equal(undoCall2, 0); - assert.equal(redoCall2, 0); - assert.equal(service.canUndo(resource), true); - assert.equal(service.canRedo(resource), false); - assert.equal(service.hasElements(resource), true); + assert.strictEqual(undoCall1, 1); + assert.strictEqual(redoCall1, 1); + assert.strictEqual(undoCall2, 0); + assert.strictEqual(redoCall2, 0); + assert.strictEqual(service.canUndo(resource), true); + assert.strictEqual(service.canRedo(resource), false); + assert.strictEqual(service.hasElements(resource), true); assert.ok(service.getLastElement(resource) === element2); service.undo(resource); - assert.equal(undoCall1, 1); - assert.equal(redoCall1, 1); - assert.equal(undoCall2, 1); - assert.equal(redoCall2, 0); - assert.equal(service.canUndo(resource), true); - assert.equal(service.canRedo(resource), true); - assert.equal(service.hasElements(resource), true); + assert.strictEqual(undoCall1, 1); + assert.strictEqual(redoCall1, 1); + assert.strictEqual(undoCall2, 1); + assert.strictEqual(redoCall2, 0); + assert.strictEqual(service.canUndo(resource), true); + assert.strictEqual(service.canRedo(resource), true); + assert.strictEqual(service.hasElements(resource), true); assert.ok(service.getLastElement(resource) === null); let undoCall3 = 0; @@ -104,28 +104,28 @@ suite('UndoRedoService', () => { }; service.pushElement(element3); - assert.equal(undoCall1, 1); - assert.equal(redoCall1, 1); - assert.equal(undoCall2, 1); - assert.equal(redoCall2, 0); - assert.equal(undoCall3, 0); - assert.equal(redoCall3, 0); - assert.equal(service.canUndo(resource), true); - assert.equal(service.canRedo(resource), false); - assert.equal(service.hasElements(resource), true); + assert.strictEqual(undoCall1, 1); + assert.strictEqual(redoCall1, 1); + assert.strictEqual(undoCall2, 1); + assert.strictEqual(redoCall2, 0); + assert.strictEqual(undoCall3, 0); + assert.strictEqual(redoCall3, 0); + assert.strictEqual(service.canUndo(resource), true); + assert.strictEqual(service.canRedo(resource), false); + assert.strictEqual(service.hasElements(resource), true); assert.ok(service.getLastElement(resource) === element3); service.undo(resource); - assert.equal(undoCall1, 1); - assert.equal(redoCall1, 1); - assert.equal(undoCall2, 1); - assert.equal(redoCall2, 0); - assert.equal(undoCall3, 1); - assert.equal(redoCall3, 0); - assert.equal(service.canUndo(resource), true); - assert.equal(service.canRedo(resource), true); - assert.equal(service.hasElements(resource), true); + assert.strictEqual(undoCall1, 1); + assert.strictEqual(redoCall1, 1); + assert.strictEqual(undoCall2, 1); + assert.strictEqual(redoCall2, 0); + assert.strictEqual(undoCall3, 1); + assert.strictEqual(redoCall3, 0); + assert.strictEqual(service.canUndo(resource), true); + assert.strictEqual(service.canRedo(resource), true); + assert.strictEqual(service.hasElements(resource), true); assert.ok(service.getLastElement(resource) === null); }); @@ -169,50 +169,50 @@ suite('UndoRedoService', () => { }; service.pushElement(element1); - assert.equal(service.canUndo(resource1), true); - assert.equal(service.canRedo(resource1), false); - assert.equal(service.hasElements(resource1), true); + assert.strictEqual(service.canUndo(resource1), true); + assert.strictEqual(service.canRedo(resource1), false); + assert.strictEqual(service.hasElements(resource1), true); assert.ok(service.getLastElement(resource1) === element1); - assert.equal(service.canUndo(resource2), true); - assert.equal(service.canRedo(resource2), false); - assert.equal(service.hasElements(resource2), true); + assert.strictEqual(service.canUndo(resource2), true); + assert.strictEqual(service.canRedo(resource2), false); + assert.strictEqual(service.hasElements(resource2), true); assert.ok(service.getLastElement(resource2) === element1); await service.undo(resource1); - assert.equal(undoCall1, 1); - assert.equal(redoCall1, 0); - assert.equal(service.canUndo(resource1), false); - assert.equal(service.canRedo(resource1), true); - assert.equal(service.hasElements(resource1), true); + assert.strictEqual(undoCall1, 1); + assert.strictEqual(redoCall1, 0); + assert.strictEqual(service.canUndo(resource1), false); + assert.strictEqual(service.canRedo(resource1), true); + assert.strictEqual(service.hasElements(resource1), true); assert.ok(service.getLastElement(resource1) === null); - assert.equal(service.canUndo(resource2), false); - assert.equal(service.canRedo(resource2), true); - assert.equal(service.hasElements(resource2), true); + assert.strictEqual(service.canUndo(resource2), false); + assert.strictEqual(service.canRedo(resource2), true); + assert.strictEqual(service.hasElements(resource2), true); assert.ok(service.getLastElement(resource2) === null); await service.redo(resource2); - assert.equal(undoCall1, 1); - assert.equal(redoCall1, 1); - assert.equal(undoCall11, 0); - assert.equal(redoCall11, 0); - assert.equal(undoCall12, 0); - assert.equal(redoCall12, 0); - assert.equal(service.canUndo(resource1), true); - assert.equal(service.canRedo(resource1), false); - assert.equal(service.hasElements(resource1), true); + assert.strictEqual(undoCall1, 1); + assert.strictEqual(redoCall1, 1); + assert.strictEqual(undoCall11, 0); + assert.strictEqual(redoCall11, 0); + assert.strictEqual(undoCall12, 0); + assert.strictEqual(redoCall12, 0); + assert.strictEqual(service.canUndo(resource1), true); + assert.strictEqual(service.canRedo(resource1), false); + assert.strictEqual(service.hasElements(resource1), true); assert.ok(service.getLastElement(resource1) === element1); - assert.equal(service.canUndo(resource2), true); - assert.equal(service.canRedo(resource2), false); - assert.equal(service.hasElements(resource2), true); + assert.strictEqual(service.canUndo(resource2), true); + assert.strictEqual(service.canRedo(resource2), false); + assert.strictEqual(service.hasElements(resource2), true); assert.ok(service.getLastElement(resource2) === element1); }); test('UndoRedoGroup.None uses id 0', () => { - assert.equal(UndoRedoGroup.None.id, 0); - assert.equal(UndoRedoGroup.None.nextOrder(), 0); - assert.equal(UndoRedoGroup.None.nextOrder(), 0); + assert.strictEqual(UndoRedoGroup.None.id, 0); + assert.strictEqual(UndoRedoGroup.None.nextOrder(), 0); + assert.strictEqual(UndoRedoGroup.None.nextOrder(), 0); }); }); diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts index 2e18cc8cd..ddfa1ee4a 100644 --- a/src/vs/platform/update/electron-main/updateService.darwin.ts +++ b/src/vs/platform/update/electron-main/updateService.darwin.ts @@ -15,6 +15,7 @@ import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/e import { ILogService } from 'vs/platform/log/common/log'; import { AbstractUpdateService, createUpdateURL, UpdateNotAvailableClassification } from 'vs/platform/update/electron-main/abstractUpdateService'; import { IRequestService } from 'vs/platform/request/common/request'; +import product from 'vs/platform/product/common/product'; export class DarwinUpdateService extends AbstractUpdateService { @@ -56,7 +57,12 @@ export class DarwinUpdateService extends AbstractUpdateService { } protected buildUpdateFeedUrl(quality: string): string | undefined { - const assetID = process.arch === 'x64' ? 'darwin' : 'darwin-arm64'; + let assetID: string; + if (!product.darwinUniversalAssetId) { + assetID = process.arch === 'x64' ? 'darwin' : 'darwin-arm64'; + } else { + assetID = product.darwinUniversalAssetId; + } const url = createUpdateURL(assetID, quality); try { electron.autoUpdater.setFeedURL({ url }); diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index 3ffa7bf7b..d1c834165 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -56,7 +56,7 @@ export class Win32UpdateService extends AbstractUpdateService { @memoize get cachePath(): Promise { const result = path.join(tmpdir(), `vscode-update-${product.target}-${process.arch}`); - return pfs.mkdirp(result, undefined).then(() => result); + return pfs.mkdirp(result).then(() => result); } constructor( diff --git a/src/vs/platform/userDataSync/common/extensionsStorageSync.ts b/src/vs/platform/userDataSync/common/extensionsStorageSync.ts index 37846e053..3e8f5400e 100644 --- a/src/vs/platform/userDataSync/common/extensionsStorageSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsStorageSync.ts @@ -5,6 +5,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; +import { adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; @@ -32,7 +33,7 @@ export class ExtensionsStorageSyncService extends Disposable implements IExtensi declare readonly _serviceBrand: undefined; private static toKey(extension: IExtensionIdWithVersion): string { - return `extensionKeys/${extension.id}@${extension.version}`; + return `extensionKeys/${adoptToGalleryExtensionId(extension.id)}@${extension.version}`; } private static fromKey(key: string): IExtensionIdWithVersion | undefined { diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index e716e1355..74fe8d9c0 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -11,7 +11,7 @@ import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { areSameExtensions, getExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IFileService } from 'vs/platform/files/common/files'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { merge } from 'vs/platform/userDataSync/common/extensionsMerge'; @@ -25,7 +25,7 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { CancellationToken } from 'vs/base/common/cancellation'; import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; import { getErrorMessage } from 'vs/base/common/errors'; -import { forEach, IStringDictionary } from 'vs/base/common/collections'; +import { IStringDictionary } from 'vs/base/common/collections'; import { IExtensionsStorageSyncService } from 'vs/platform/userDataSync/common/extensionsStorageSync'; interface IExtensionResourceMergeResult extends IAcceptResult { @@ -73,6 +73,15 @@ async function parseAndMigrateExtensions(syncData: ISyncData, extensionManagemen return extensions; } +function getExtensionStorageState(publisher: string, name: string, storageService: IStorageService): IStringDictionary { + const extensionStorageValue = storageService.get(getExtensionId(publisher, name) /* use the same id used in extension host */, StorageScope.GLOBAL) || '{}'; + return JSON.parse(extensionStorageValue); +} + +function storeExtensionStorageState(publisher: string, name: string, extensionState: IStringDictionary, storageService: IStorageService): void { + storageService.store(getExtensionId(publisher, name) /* use the same id used in extension host */, JSON.stringify(extensionState), StorageScope.GLOBAL, StorageTarget.MACHINE); +} + export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { private static readonly EXTENSIONS_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'extensions', path: `/extensions.json` }); @@ -99,7 +108,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService, - @IIgnoredExtensionsManagementService private readonly extensionSyncManagementService: IIgnoredExtensionsManagementService, + @IIgnoredExtensionsManagementService private readonly ignoredExtensionsManagementService: IIgnoredExtensionsManagementService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IConfigurationService configurationService: IConfigurationService, @@ -125,7 +134,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const installedExtensions = await this.extensionManagementService.getInstalled(); const localExtensions = this.getLocalExtensions(installedExtensions); - const ignoredExtensions = this.extensionSyncManagementService.getIgnoredExtensions(installedExtensions); + const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions); if (remoteExtensions) { this.logService.trace(`${this.syncResourceLogLabel}: Merging remote extensions with local extensions...`); @@ -209,7 +218,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse private async acceptLocal(resourcePreview: IExtensionResourcePreview): Promise { const installedExtensions = await this.extensionManagementService.getInstalled(); - const ignoredExtensions = this.extensionSyncManagementService.getIgnoredExtensions(installedExtensions); + const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions); const mergeResult = merge(resourcePreview.localExtensions, null, null, resourcePreview.skippedExtensions, ignoredExtensions); const { added, removed, updated, remote } = mergeResult; return { @@ -225,7 +234,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse private async acceptRemote(resourcePreview: IExtensionResourcePreview): Promise { const installedExtensions = await this.extensionManagementService.getInstalled(); - const ignoredExtensions = this.extensionSyncManagementService.getIgnoredExtensions(installedExtensions); + const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions); const remoteExtensions = resourcePreview.remoteContent ? JSON.parse(resourcePreview.remoteContent) : null; if (remoteExtensions !== null) { const mergeResult = merge(resourcePreview.localExtensions, remoteExtensions, resourcePreview.localExtensions, [], ignoredExtensions); @@ -285,7 +294,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse async resolveContent(uri: URI): Promise { if (this.extUri.isEqual(uri, ExtensionsSynchroniser.EXTENSIONS_DATA_URI)) { const installedExtensions = await this.extensionManagementService.getInstalled(); - const ignoredExtensions = this.extensionSyncManagementService.getIgnoredExtensions(installedExtensions); + const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions); const localExtensions = this.getLocalExtensions(installedExtensions).filter(e => !ignoredExtensions.some(id => areSameExtensions({ id }, e.identifier))); return this.format(localExtensions); } @@ -363,7 +372,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse // Builtin Extension Sync: Enablement & State if (installedExtension && installedExtension.isBuiltin) { if (e.state && installedExtension.manifest.version === e.version) { - this.updateExtensionState(e.state, e.identifier.id, installedExtension.manifest.version); + this.updateExtensionState(e.state, installedExtension.manifest.publisher, installedExtension.manifest.name, installedExtension.manifest.version); } if (e.disabled) { this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id); @@ -382,14 +391,16 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const extension = await this.extensionGalleryService.getCompatibleExtension(e.identifier); /* Update extension state only if - * extension is installed and version is same as synced version or - * extension is not installed and installable + * extension is installed and version is same as synced version or + * extension is not installed and installable */ if (e.state && (installedExtension ? installedExtension.manifest.version === e.version /* Installed and has same version */ : !!extension /* Installable */) ) { - this.updateExtensionState(e.state, e.identifier.id, installedExtension?.manifest.version); + const publisher = installedExtension ? installedExtension.manifest.publisher : extension!.publisher; + const name = installedExtension ? installedExtension.manifest.name : extension!.name; + this.updateExtensionState(e.state, publisher, name, installedExtension?.manifest.version); } if (extension) { @@ -436,15 +447,15 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return newSkippedExtensions; } - private updateExtensionState(state: IStringDictionary, id: string, version?: string): void { - const extensionState = JSON.parse(this.storageService.get(id, StorageScope.GLOBAL) || '{}'); - const keys = version ? this.extensionsStorageSyncService.getKeysForSync({ id, version }) : undefined; + private updateExtensionState(state: IStringDictionary, publisher: string, name: string, version: string | undefined): void { + const extensionState = getExtensionStorageState(publisher, name, this.storageService); + const keys = version ? this.extensionsStorageSyncService.getKeysForSync({ id: getGalleryExtensionId(publisher, name), version }) : undefined; if (keys) { - keys.forEach(key => extensionState[key] = state[key]); + keys.forEach(key => { extensionState[key] = state[key]; }); } else { - forEach(state, ({ key, value }) => extensionState[key] = value); + Object.keys(state).forEach(key => extensionState[key] = state[key]); } - this.storageService.store(id, JSON.stringify(extensionState), StorageScope.GLOBAL, StorageTarget.MACHINE); + storeExtensionStorageState(publisher, name, extensionState, this.storageService); } private parseExtensions(syncData: ISyncData): ISyncExtension[] { @@ -465,8 +476,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse try { const keys = this.extensionsStorageSyncService.getKeysForSync({ id: identifier.id, version: manifest.version }); if (keys) { - const extensionStorageValue = this.storageService.get(identifier.id, StorageScope.GLOBAL) || '{}'; - const extensionStorageState = JSON.parse(extensionStorageValue); + const extensionStorageState = getExtensionStorageState(manifest.publisher, manifest.name, this.storageService); syncExntesion.state = Object.keys(extensionStorageState).reduce((state: IStringDictionary, key) => { if (keys.includes(key)) { state[key] = extensionStorageState[key]; @@ -490,6 +500,7 @@ export class ExtensionsInitializer extends AbstractInitializer { @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService, @IStorageService private readonly storageService: IStorageService, + @IIgnoredExtensionsManagementService private readonly ignoredExtensionsManagementService: IIgnoredExtensionsManagementService, @IFileService fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @@ -511,12 +522,18 @@ export class ExtensionsInitializer extends AbstractInitializer { const newlyEnabledExtensions: ILocalExtension[] = []; const installedExtensions = await this.extensionManagementService.getInstalled(); const newExtensionsToSync = new Map(); - const installedExtensionsToSync: ISyncExtension[] = []; + const installedExtensionsToSync: { syncExtension: ISyncExtension, installedExtension: ILocalExtension }[] = []; const toInstall: { names: string[], uuids: string[] } = { names: [], uuids: [] }; const toDisable: IExtensionIdentifier[] = []; for (const extension of remoteExtensions) { - if (installedExtensions.some(i => areSameExtensions(i.identifier, extension.identifier))) { - installedExtensionsToSync.push(extension); + if (this.ignoredExtensionsManagementService.hasToNeverSyncExtension(extension.identifier.id)) { + // Skip extension ignored to sync + continue; + } + + const installedExtension = installedExtensions.find(i => areSameExtensions(i.identifier, extension.identifier)); + if (installedExtension) { + installedExtensionsToSync.push({ syncExtension: extension, installedExtension }); if (extension.disabled) { toDisable.push(extension.identifier); } @@ -528,17 +545,39 @@ export class ExtensionsInitializer extends AbstractInitializer { } else { toInstall.names.push(extension.identifier.id); } + if (extension.disabled) { + toDisable.push(extension.identifier); + } } } } + // 1. Initialise already installed extensions state + for (const { syncExtension, installedExtension } of installedExtensionsToSync) { + if (syncExtension.state) { + const extensionState = getExtensionStorageState(installedExtension.manifest.publisher, installedExtension.manifest.name, this.storageService); + Object.keys(syncExtension.state).forEach(key => extensionState[key] = syncExtension.state![key]); + storeExtensionStorageState(installedExtension.manifest.publisher, installedExtension.manifest.name, extensionState, this.storageService); + } + } + + // 2. Initialise extensions enablement + if (toDisable.length) { + for (const identifier of toDisable) { + this.logService.trace(`Disabling extension...`, identifier.id); + await this.extensionEnablementService.disableExtension(identifier); + this.logService.info(`Disabling extension`, identifier.id); + } + } + + // 3. Install extensions if (toInstall.names.length || toInstall.uuids.length) { const galleryExtensions = (await this.galleryService.query({ ids: toInstall.uuids, names: toInstall.names, pageSize: toInstall.uuids.length + toInstall.names.length }, CancellationToken.None)).firstPage; for (const galleryExtension of galleryExtensions) { try { const extensionToSync = newExtensionsToSync.get(galleryExtension.identifier.id.toLowerCase())!; if (extensionToSync.state) { - this.storageService.store(extensionToSync.identifier.id, JSON.stringify(extensionToSync.state), StorageScope.GLOBAL, StorageTarget.MACHINE); + storeExtensionStorageState(galleryExtension.publisher, galleryExtension.name, extensionToSync.state, this.storageService); } this.logService.trace(`Installing extension...`, galleryExtension.identifier.id); const local = await this.extensionManagementService.installFromGallery(galleryExtension, { isMachineScoped: false } /* pass options to prevent install and sync dialog in web */); @@ -552,22 +591,6 @@ export class ExtensionsInitializer extends AbstractInitializer { } } - if (toDisable.length) { - for (const identifier of toDisable) { - this.logService.trace(`Enabling extension...`, identifier.id); - await this.extensionEnablementService.disableExtension(identifier); - this.logService.info(`Enabled extension`, identifier.id); - } - } - - for (const extensionToSync of installedExtensionsToSync) { - if (extensionToSync.state) { - const extensionState = JSON.parse(this.storageService.get(extensionToSync.identifier.id, StorageScope.GLOBAL) || '{}'); - forEach(extensionToSync.state, ({ key, value }) => extensionState[key] = value); - this.storageService.store(extensionToSync.identifier.id, JSON.stringify(extensionState), StorageScope.GLOBAL, StorageTarget.MACHINE); - } - } - return newlyEnabledExtensions; } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 6d9d58d1b..310fea185 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -31,9 +31,10 @@ export function getDisallowedIgnoredSettings(): string[] { export function getDefaultIgnoredSettings(): 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 disallowedSettings = getDisallowedIgnoredSettings(); - return distinct([CONFIGURATION_SYNC_STORE_KEY, ...machineSettings, ...disallowedSettings]); + return distinct([CONFIGURATION_SYNC_STORE_KEY, ...ignoreSyncSettings, ...machineSettings, ...disallowedSettings]); } export function registerConfiguration(): IDisposable { @@ -52,10 +53,6 @@ export function registerConfiguration(): IDisposable { scope: ConfigurationScope.APPLICATION, tags: ['sync', 'usesOnlineServices'] }, - 'sync.keybindingsPerPlatform': { - type: 'boolean', - deprecationMessage: localize('sync.keybindingsPerPlatform.deprecated', "Deprecated, use settingsSync.keybindingsPerPlatform instead"), - }, 'settingsSync.ignoredExtensions': { 'type': 'array', markdownDescription: localize('settingsSync.ignoredExtensions', "List of extensions to be ignored while synchronizing. The identifier of an extension is always `${publisher}.${name}`. For example: `vscode.csharp`."), @@ -70,10 +67,6 @@ export function registerConfiguration(): IDisposable { disallowSyncIgnore: true, tags: ['sync', 'usesOnlineServices'] }, - 'sync.ignoredExtensions': { - 'type': 'array', - deprecationMessage: localize('sync.ignoredExtensions.deprecated', "Deprecated, use settingsSync.ignoredExtensions instead"), - }, 'settingsSync.ignoredSettings': { 'type': 'array', description: localize('settingsSync.ignoredSettings', "Configure settings to be ignored while synchronizing."), @@ -84,10 +77,6 @@ export function registerConfiguration(): IDisposable { uniqueItems: true, disallowSyncIgnore: true, tags: ['sync', 'usesOnlineServices'] - }, - 'sync.ignoredSettings': { - 'type': 'array', - deprecationMessage: localize('sync.ignoredSettings.deprecated', "Deprecated, use settingsSync.ignoredSettings instead"), } } }); diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 2cf4ac375..ad5494bc4 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -445,131 +445,171 @@ class ManualSyncTask extends Disposable implements IManualSyncTask { } async preview(): Promise<[SyncResource, ISyncResourcePreview][]> { - if (this.isDisposed) { - throw new Error('Disposed'); + try { + if (this.isDisposed) { + throw new Error('Disposed'); + } + if (!this.previewsPromise) { + this.previewsPromise = createCancelablePromise(token => this.getPreviews(token)); + } + if (!this.previews) { + this.previews = await this.previewsPromise; + } + return this.previews; + } catch (error) { + this.logService.error(error); + throw error; } - if (!this.previewsPromise) { - this.previewsPromise = createCancelablePromise(token => this.getPreviews(token)); - } - if (!this.previews) { - this.previews = await this.previewsPromise; - } - return this.previews; } async accept(resource: URI, content?: string | null): Promise<[SyncResource, ISyncResourcePreview][]> { - return this.performAction(resource, sychronizer => sychronizer.accept(resource, content)); + try { + return await this.performAction(resource, sychronizer => sychronizer.accept(resource, content)); + } catch (error) { + this.logService.error(error); + throw error; + } } async merge(resource?: URI): Promise<[SyncResource, ISyncResourcePreview][]> { - if (resource) { - return this.performAction(resource, sychronizer => sychronizer.merge(resource)); - } else { - return this.mergeAll(); + try { + if (resource) { + return await this.performAction(resource, sychronizer => sychronizer.merge(resource)); + } else { + return await this.mergeAll(); + } + } catch (error) { + this.logService.error(error); + throw error; } } async discard(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]> { - return this.performAction(resource, sychronizer => sychronizer.discard(resource)); + try { + return await this.performAction(resource, sychronizer => sychronizer.discard(resource)); + } catch (error) { + this.logService.error(error); + throw error; + } } async discardConflicts(): Promise<[SyncResource, ISyncResourcePreview][]> { - if (!this.previews) { - throw new Error('Missing preview. Create preview and try again.'); - } - if (this.synchronizingResources.length) { - throw new Error('Cannot discard while synchronizing resources'); - } + try { + if (!this.previews) { + throw new Error('Missing preview. Create preview and try again.'); + } + if (this.synchronizingResources.length) { + throw new Error('Cannot discard while synchronizing resources'); + } - const conflictResources: URI[] = []; - for (const [, syncResourcePreview] of this.previews) { - for (const resourcePreview of syncResourcePreview.resourcePreviews) { - if (resourcePreview.mergeState === MergeState.Conflict) { - conflictResources.push(resourcePreview.previewResource); + const conflictResources: URI[] = []; + for (const [, syncResourcePreview] of this.previews) { + for (const resourcePreview of syncResourcePreview.resourcePreviews) { + if (resourcePreview.mergeState === MergeState.Conflict) { + conflictResources.push(resourcePreview.previewResource); + } } } - } - for (const resource of conflictResources) { - await this.discard(resource); + for (const resource of conflictResources) { + await this.discard(resource); + } + return this.previews; + } catch (error) { + this.logService.error(error); + throw error; } - return this.previews; } async apply(): Promise<[SyncResource, ISyncResourcePreview][]> { - if (!this.previews) { - throw new Error('You need to create preview before applying'); - } - if (this.synchronizingResources.length) { - throw new Error('Cannot pull while synchronizing resources'); - } - const previews: [SyncResource, ISyncResourcePreview][] = []; - for (const [syncResource, preview] of this.previews) { - this.synchronizingResources.push([syncResource, preview.resourcePreviews.map(r => r.localResource)]); - this._onSynchronizeResources.fire(this.synchronizingResources); + try { + if (!this.previews) { + throw new Error('You need to create preview before applying'); + } + if (this.synchronizingResources.length) { + throw new Error('Cannot pull while synchronizing resources'); + } + const previews: [SyncResource, ISyncResourcePreview][] = []; + for (const [syncResource, preview] of this.previews) { + this.synchronizingResources.push([syncResource, preview.resourcePreviews.map(r => r.localResource)]); + this._onSynchronizeResources.fire(this.synchronizingResources); - const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!; + const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!; - /* merge those which are not yet merged */ - for (const resourcePreview of preview.resourcePreviews) { - if ((resourcePreview.localChange !== Change.None || resourcePreview.remoteChange !== Change.None) && resourcePreview.mergeState === MergeState.Preview) { - await synchroniser.merge(resourcePreview.previewResource); + /* merge those which are not yet merged */ + for (const resourcePreview of preview.resourcePreviews) { + if ((resourcePreview.localChange !== Change.None || resourcePreview.remoteChange !== Change.None) && resourcePreview.mergeState === MergeState.Preview) { + await synchroniser.merge(resourcePreview.previewResource); + } } - } - /* apply */ - const newPreview = await synchroniser.apply(false, this.syncHeaders); - if (newPreview) { - previews.push(this.toSyncResourcePreview(synchroniser.resource, newPreview)); - } + /* apply */ + const newPreview = await synchroniser.apply(false, this.syncHeaders); + if (newPreview) { + previews.push(this.toSyncResourcePreview(synchroniser.resource, newPreview)); + } - this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1); - this._onSynchronizeResources.fire(this.synchronizingResources); + this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1); + this._onSynchronizeResources.fire(this.synchronizingResources); + } + this.previews = previews; + return this.previews; + } catch (error) { + this.logService.error(error); + throw error; } - this.previews = previews; - return this.previews; } async pull(): Promise { - if (!this.previews) { - throw new Error('You need to create preview before applying'); - } - if (this.synchronizingResources.length) { - throw new Error('Cannot pull while synchronizing resources'); - } - for (const [syncResource, preview] of this.previews) { - this.synchronizingResources.push([syncResource, preview.resourcePreviews.map(r => r.localResource)]); - this._onSynchronizeResources.fire(this.synchronizingResources); - const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!; - for (const resourcePreview of preview.resourcePreviews) { - await synchroniser.accept(resourcePreview.remoteResource); + try { + if (!this.previews) { + throw new Error('You need to create preview before applying'); } - await synchroniser.apply(true, this.syncHeaders); - this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1); - this._onSynchronizeResources.fire(this.synchronizingResources); + if (this.synchronizingResources.length) { + throw new Error('Cannot pull while synchronizing resources'); + } + for (const [syncResource, preview] of this.previews) { + this.synchronizingResources.push([syncResource, preview.resourcePreviews.map(r => r.localResource)]); + this._onSynchronizeResources.fire(this.synchronizingResources); + const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!; + for (const resourcePreview of preview.resourcePreviews) { + await synchroniser.accept(resourcePreview.remoteResource); + } + await synchroniser.apply(true, this.syncHeaders); + this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1); + this._onSynchronizeResources.fire(this.synchronizingResources); + } + this.previews = []; + } catch (error) { + this.logService.error(error); + throw error; } - this.previews = []; } async push(): Promise { - if (!this.previews) { - throw new Error('You need to create preview before applying'); - } - if (this.synchronizingResources.length) { - throw new Error('Cannot pull while synchronizing resources'); - } - for (const [syncResource, preview] of this.previews) { - this.synchronizingResources.push([syncResource, preview.resourcePreviews.map(r => r.localResource)]); - this._onSynchronizeResources.fire(this.synchronizingResources); - const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!; - for (const resourcePreview of preview.resourcePreviews) { - await synchroniser.accept(resourcePreview.localResource); + try { + if (!this.previews) { + throw new Error('You need to create preview before applying'); } - await synchroniser.apply(true, this.syncHeaders); - this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1); - this._onSynchronizeResources.fire(this.synchronizingResources); + if (this.synchronizingResources.length) { + throw new Error('Cannot pull while synchronizing resources'); + } + for (const [syncResource, preview] of this.previews) { + this.synchronizingResources.push([syncResource, preview.resourcePreviews.map(r => r.localResource)]); + this._onSynchronizeResources.fire(this.synchronizingResources); + const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!; + for (const resourcePreview of preview.resourcePreviews) { + await synchroniser.accept(resourcePreview.localResource); + } + await synchroniser.apply(true, this.syncHeaders); + this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1); + this._onSynchronizeResources.fire(this.synchronizingResources); + } + this.previews = []; + } catch (error) { + this.logService.error(error); + throw error; } - this.previews = []; } async stop(): Promise { diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index 409586895..a4bde40f3 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, } from 'vs/base/common/lifecycle'; +import { Disposable, toDisposable, } from 'vs/base/common/lifecycle'; import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, ServerResource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest, IResourceRefHandle, HEADER_OPERATION_ID, HEADER_EXECUTION_ID, CONFIGURATION_SYNC_STORE_KEY, IAuthenticationProvider, IUserDataSyncStoreManagementService, UserDataSyncStoreType, IUserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSync'; import { IRequestService, asText, isSuccess as isSuccessContext, asJson } from 'vs/platform/request/common/request'; import { joinPath, relativePath } from 'vs/base/common/resources'; @@ -180,6 +180,12 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync /* A requests session that limits requests per sessions */ this.session = new RequestsSession(REQUEST_SESSION_LIMIT, REQUEST_SESSION_INTERVAL, this.requestService, this.logService); this.initDonotMakeRequestsUntil(); + this._register(toDisposable(() => { + if (this.resetDonotMakeRequestsUntilPromise) { + this.resetDonotMakeRequestsUntilPromise.cancel(); + this.resetDonotMakeRequestsUntilPromise = undefined; + } + })); } setAuthToken(token: string, type: string): void { @@ -210,6 +216,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync if (this._donotMakeRequestsUntil) { this.storageService.store(DONOT_MAKE_REQUESTS_UNTIL_KEY, this._donotMakeRequestsUntil.getTime(), StorageScope.GLOBAL, StorageTarget.MACHINE); this.resetDonotMakeRequestsUntilPromise = createCancelablePromise(token => timeout(this._donotMakeRequestsUntil!.getTime() - Date.now(), token).then(() => this.setDonotMakeRequestsUntil(undefined))); + this.resetDonotMakeRequestsUntilPromise.then(null, e => null /* ignore error */); } else { this.storageService.remove(DONOT_MAKE_REQUESTS_UNTIL_KEY, StorageScope.GLOBAL); } diff --git a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts index 5d2731d5e..247391119 100644 --- a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts +++ b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts @@ -181,7 +181,7 @@ suite('TestSynchronizer - Auto Sync', () => { teardown(() => disposableStore.clear()); test('status is syncing', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); const actual: SyncStatus[] = []; disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); @@ -198,7 +198,7 @@ suite('TestSynchronizer - Auto Sync', () => { }); test('status is set correctly when sync is finished', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncBarrier.open(); const actual: SyncStatus[] = []; @@ -210,7 +210,7 @@ suite('TestSynchronizer - Auto Sync', () => { }); test('status is set correctly when sync has errors', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasError: true, hasConflicts: false }; testObject.syncBarrier.open(); @@ -227,7 +227,7 @@ suite('TestSynchronizer - Auto Sync', () => { }); test('status is set to hasConflicts when asked to sync if there are conflicts', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); @@ -238,7 +238,7 @@ suite('TestSynchronizer - Auto Sync', () => { }); test('sync should not run if syncing already', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); const promise = Event.toPromise(testObject.onDoSyncCall.event); testObject.sync(await client.manifest()); @@ -255,7 +255,7 @@ suite('TestSynchronizer - Auto Sync', () => { }); test('sync should not run if disabled', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); client.instantiationService.get(IUserDataSyncResourceEnablementService).setResourceEnablement(testObject.resource, false); const actual: SyncStatus[] = []; @@ -268,7 +268,7 @@ suite('TestSynchronizer - Auto Sync', () => { }); test('sync should not run if there are conflicts', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); await testObject.sync(await client.manifest()); @@ -282,7 +282,7 @@ suite('TestSynchronizer - Auto Sync', () => { }); test('accept preview during conflicts', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); @@ -300,7 +300,7 @@ suite('TestSynchronizer - Auto Sync', () => { }); test('accept remote during conflicts', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncBarrier.open(); await testObject.sync(await client.manifest()); const fileService = client.instantiationService.get(IFileService); @@ -323,7 +323,7 @@ suite('TestSynchronizer - Auto Sync', () => { }); test('accept local during conflicts', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncBarrier.open(); await testObject.sync(await client.manifest()); const fileService = client.instantiationService.get(IFileService); @@ -345,7 +345,7 @@ suite('TestSynchronizer - Auto Sync', () => { }); test('accept new content during conflicts', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncBarrier.open(); await testObject.sync(await client.manifest()); const fileService = client.instantiationService.get(IFileService); @@ -368,7 +368,7 @@ suite('TestSynchronizer - Auto Sync', () => { }); test('accept delete during conflicts', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncBarrier.open(); await testObject.sync(await client.manifest()); const fileService = client.instantiationService.get(IFileService); @@ -390,7 +390,7 @@ suite('TestSynchronizer - Auto Sync', () => { }); test('accept deleted local during conflicts', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncBarrier.open(); await testObject.sync(await client.manifest()); const fileService = client.instantiationService.get(IFileService); @@ -411,7 +411,7 @@ suite('TestSynchronizer - Auto Sync', () => { }); test('accept deleted remote during conflicts', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncBarrier.open(); const fileService = client.instantiationService.get(IFileService); await fileService.writeFile(testObject.localResource, VSBuffer.fromString('some content')); @@ -431,7 +431,7 @@ suite('TestSynchronizer - Auto Sync', () => { }); test('request latest data on precondition failure', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); // Sync once testObject.syncBarrier.open(); await testObject.sync(await client.manifest()); @@ -458,7 +458,7 @@ suite('TestSynchronizer - Auto Sync', () => { }); test('no requests are made to server when local change is triggered', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncBarrier.open(); await testObject.sync(await client.manifest()); @@ -471,7 +471,7 @@ suite('TestSynchronizer - Auto Sync', () => { }); test('status is reset when getting latest remote data fails', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.failWhenGettingLatestRemoteUserData = true; try { @@ -502,7 +502,7 @@ suite('TestSynchronizer - Manual Sync', () => { teardown(() => disposableStore.clear()); test('preview', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); @@ -514,7 +514,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('preview -> merge', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); @@ -528,7 +528,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('preview -> accept', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); @@ -542,7 +542,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('preview -> merge -> accept', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); @@ -557,7 +557,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('preview -> merge -> apply', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); await testObject.sync(await client.manifest()); @@ -577,7 +577,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('preview -> accept -> apply', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); await testObject.sync(await client.manifest()); @@ -597,7 +597,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('preview -> merge -> accept -> apply', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); await testObject.sync(await client.manifest()); @@ -617,7 +617,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('preview -> accept', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); @@ -630,7 +630,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('preview -> accept -> apply', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); await testObject.sync(await client.manifest()); @@ -650,7 +650,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('preivew -> merge -> discard', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); @@ -665,7 +665,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('preivew -> merge -> discard -> accept', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); @@ -681,7 +681,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('preivew -> accept -> discard', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); @@ -696,7 +696,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('preivew -> accept -> discard -> accept', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); @@ -712,7 +712,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('preivew -> accept -> discard -> merge', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); @@ -728,7 +728,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('preivew -> merge -> accept -> discard', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); @@ -744,7 +744,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('preivew -> merge -> discard -> accept -> apply', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); await testObject.sync(await client.manifest()); @@ -764,7 +764,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('preivew -> accept -> discard -> accept -> apply', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); await testObject.sync(await client.manifest()); @@ -785,7 +785,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('preivew -> accept -> discard -> merge -> apply', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); await testObject.sync(await client.manifest()); @@ -808,7 +808,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('conflicts: preview', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); @@ -820,7 +820,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('conflicts: preview -> merge', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); @@ -834,7 +834,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('conflicts: preview -> merge -> discard', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); @@ -849,7 +849,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('conflicts: preview -> accept', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); @@ -864,7 +864,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('conflicts: preview -> merge -> accept -> apply', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); await testObject.sync(await client.manifest()); @@ -887,7 +887,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('conflicts: preview -> accept', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); @@ -901,7 +901,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('conflicts: preview -> accept -> apply', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); await testObject.sync(await client.manifest()); @@ -923,7 +923,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('conflicts: preivew -> merge -> discard', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); @@ -938,7 +938,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('conflicts: preivew -> merge -> discard -> accept', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); @@ -954,7 +954,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('conflicts: preivew -> accept -> discard', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); @@ -969,7 +969,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('conflicts: preivew -> accept -> discard -> accept', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); @@ -985,7 +985,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('conflicts: preivew -> accept -> discard -> merge', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); @@ -1001,7 +1001,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('conflicts: preivew -> merge -> discard -> merge', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); @@ -1017,7 +1017,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('conflicts: preivew -> merge -> accept -> discard', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); @@ -1033,7 +1033,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('conflicts: preivew -> merge -> discard -> accept -> apply', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); await testObject.sync(await client.manifest()); @@ -1053,7 +1053,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); test('conflicts: preivew -> accept -> discard -> accept -> apply', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); await testObject.sync(await client.manifest()); diff --git a/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts b/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts index 49df9ea7e..a12085fe7 100644 --- a/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts @@ -40,7 +40,7 @@ suite('UserDataAutoSyncService', () => { await (await client.instantiationService.get(IUserDataSyncService).createSyncTask()).run(); target.reset(); - const testObject: UserDataAutoSyncService = client.instantiationService.createInstance(TestUserDataAutoSyncService); + const testObject: UserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); // Trigger auto sync with settings change await testObject.triggerSync([SyncResource.Settings], false, false); @@ -62,7 +62,7 @@ suite('UserDataAutoSyncService', () => { await (await client.instantiationService.get(IUserDataSyncService).createSyncTask()).run(); target.reset(); - const testObject: UserDataAutoSyncService = client.instantiationService.createInstance(TestUserDataAutoSyncService); + const testObject: UserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); // Trigger auto sync with settings change multiple times for (let counter = 0; counter < 2; counter++) { @@ -88,7 +88,7 @@ suite('UserDataAutoSyncService', () => { await (await client.instantiationService.get(IUserDataSyncService).createSyncTask()).run(); target.reset(); - const testObject: UserDataAutoSyncService = client.instantiationService.createInstance(TestUserDataAutoSyncService); + const testObject: UserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); // Trigger auto sync with window focus once await testObject.triggerSync(['windowFocus'], true, false); @@ -110,7 +110,7 @@ suite('UserDataAutoSyncService', () => { await (await client.instantiationService.get(IUserDataSyncService).createSyncTask()).run(); target.reset(); - const testObject: UserDataAutoSyncService = client.instantiationService.createInstance(TestUserDataAutoSyncService); + const testObject: UserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); // Trigger auto sync with window focus multiple times for (let counter = 0; counter < 2; counter++) { @@ -129,7 +129,7 @@ suite('UserDataAutoSyncService', () => { const target = new UserDataSyncTestServer(); const client = disposableStore.add(new UserDataSyncClient(target)); await client.setUp(); - const testObject: TestUserDataAutoSyncService = client.instantiationService.createInstance(TestUserDataAutoSyncService); + const testObject: TestUserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); await testObject.sync(); @@ -165,7 +165,7 @@ suite('UserDataAutoSyncService', () => { const target = new UserDataSyncTestServer(); const client = disposableStore.add(new UserDataSyncClient(target)); await client.setUp(); - const testObject: TestUserDataAutoSyncService = client.instantiationService.createInstance(TestUserDataAutoSyncService); + const testObject: TestUserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); // Sync once and reset requests await testObject.sync(); @@ -185,7 +185,7 @@ suite('UserDataAutoSyncService', () => { const target = new UserDataSyncTestServer(); const client = disposableStore.add(new UserDataSyncClient(target)); await client.setUp(); - const testObject: TestUserDataAutoSyncService = client.instantiationService.createInstance(TestUserDataAutoSyncService); + const testObject: TestUserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); // Sync once and reset requests await testObject.sync(); @@ -220,7 +220,7 @@ suite('UserDataAutoSyncService', () => { const target = new UserDataSyncTestServer(); const client = disposableStore.add(new UserDataSyncClient(target)); await client.setUp(); - const testObject: TestUserDataAutoSyncService = client.instantiationService.createInstance(TestUserDataAutoSyncService); + const testObject: TestUserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); // Sync once and reset requests await testObject.sync(); @@ -250,7 +250,7 @@ suite('UserDataAutoSyncService', () => { // Set up and sync from the test client const testClient = disposableStore.add(new UserDataSyncClient(target)); await testClient.setUp(); - const testObject: TestUserDataAutoSyncService = testClient.instantiationService.createInstance(TestUserDataAutoSyncService); + const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); await testObject.sync(); // Reset from the first client @@ -279,7 +279,7 @@ suite('UserDataAutoSyncService', () => { // Set up and sync from the test client const testClient = disposableStore.add(new UserDataSyncClient(target)); await testClient.setUp(); - const testObject: TestUserDataAutoSyncService = testClient.instantiationService.createInstance(TestUserDataAutoSyncService); + const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); await testObject.sync(); // Disable current machine @@ -311,7 +311,7 @@ suite('UserDataAutoSyncService', () => { // Set up and sync from the test client const testClient = disposableStore.add(new UserDataSyncClient(target)); await testClient.setUp(); - const testObject: TestUserDataAutoSyncService = testClient.instantiationService.createInstance(TestUserDataAutoSyncService); + const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); await testObject.sync(); // Remove current machine @@ -339,7 +339,7 @@ suite('UserDataAutoSyncService', () => { // Set up and sync from the test client const testClient = disposableStore.add(new UserDataSyncClient(target)); await testClient.setUp(); - const testObject: TestUserDataAutoSyncService = testClient.instantiationService.createInstance(TestUserDataAutoSyncService); + const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); await testObject.sync(); // Reset from the first client @@ -371,7 +371,7 @@ suite('UserDataAutoSyncService', () => { // Set up and sync from the test client const testClient = disposableStore.add(new UserDataSyncClient(target)); await testClient.setUp(); - const testObject: TestUserDataAutoSyncService = testClient.instantiationService.createInstance(TestUserDataAutoSyncService); + const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); const errorPromise = Event.toPromise(testObject.onError); while (target.requests.length < 5) { @@ -389,7 +389,7 @@ suite('UserDataAutoSyncService', () => { // Set up and sync from the test client const testClient = disposableStore.add(new UserDataSyncClient(target)); await testClient.setUp(); - const testObject: TestUserDataAutoSyncService = testClient.instantiationService.createInstance(TestUserDataAutoSyncService); + const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); while (target.requests.length < 5) { await testObject.sync(); @@ -407,7 +407,7 @@ suite('UserDataAutoSyncService', () => { // Set up and sync from the test client const testClient = disposableStore.add(new UserDataSyncClient(target)); await testClient.setUp(); - const testObject: TestUserDataAutoSyncService = testClient.instantiationService.createInstance(TestUserDataAutoSyncService); + const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); await testObject.triggerSync(['some reason'], true, true); assert.equal(target.requestsWithAllHeaders[0].headers!['Cache-Control'], 'no-cache'); @@ -419,7 +419,7 @@ suite('UserDataAutoSyncService', () => { // Set up and sync from the test client const testClient = disposableStore.add(new UserDataSyncClient(target)); await testClient.setUp(); - const testObject: TestUserDataAutoSyncService = testClient.instantiationService.createInstance(TestUserDataAutoSyncService); + const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); await testObject.triggerSync(['some reason'], true, false); assert.equal(target.requestsWithAllHeaders[0].headers!['Cache-Control'], undefined); diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 322046976..b9d4381d7 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -83,9 +83,9 @@ export class UserDataSyncClient extends Disposable { fileService.registerProvider(Schemas.inMemory, new InMemoryFileSystemProvider()); this.instantiationService.stub(IFileService, fileService); - this.instantiationService.stub(IStorageService, new InMemoryStorageService()); + this.instantiationService.stub(IStorageService, this._register(new InMemoryStorageService())); - const configurationService = new ConfigurationService(environmentService.settingsResource, fileService); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService)); await configurationService.initialize(); this.instantiationService.stub(IConfigurationService, configurationService); @@ -93,20 +93,20 @@ export class UserDataSyncClient extends Disposable { this.instantiationService.stub(IUserDataSyncLogService, logService); this.instantiationService.stub(ITelemetryService, NullTelemetryService); - this.instantiationService.stub(IUserDataSyncStoreManagementService, this.instantiationService.createInstance(UserDataSyncStoreManagementService)); - this.instantiationService.stub(IUserDataSyncStoreService, this.instantiationService.createInstance(UserDataSyncStoreService)); + this.instantiationService.stub(IUserDataSyncStoreManagementService, this._register(this.instantiationService.createInstance(UserDataSyncStoreManagementService))); + this.instantiationService.stub(IUserDataSyncStoreService, this._register(this.instantiationService.createInstance(UserDataSyncStoreService))); - const userDataSyncAccountService: IUserDataSyncAccountService = this.instantiationService.createInstance(UserDataSyncAccountService); + const userDataSyncAccountService: IUserDataSyncAccountService = this._register(this.instantiationService.createInstance(UserDataSyncAccountService)); await userDataSyncAccountService.updateAccount({ authenticationProviderId: 'authenticationProviderId', token: 'token' }); this.instantiationService.stub(IUserDataSyncAccountService, userDataSyncAccountService); - this.instantiationService.stub(IUserDataSyncMachinesService, this.instantiationService.createInstance(UserDataSyncMachinesService)); - this.instantiationService.stub(IUserDataSyncBackupStoreService, this.instantiationService.createInstance(UserDataSyncBackupStoreService)); + this.instantiationService.stub(IUserDataSyncMachinesService, this._register(this.instantiationService.createInstance(UserDataSyncMachinesService))); + this.instantiationService.stub(IUserDataSyncBackupStoreService, this._register(this.instantiationService.createInstance(UserDataSyncBackupStoreService))); this.instantiationService.stub(IUserDataSyncUtilService, new TestUserDataSyncUtilService()); - this.instantiationService.stub(IUserDataSyncResourceEnablementService, this.instantiationService.createInstance(UserDataSyncResourceEnablementService)); + this.instantiationService.stub(IUserDataSyncResourceEnablementService, this._register(this.instantiationService.createInstance(UserDataSyncResourceEnablementService))); - this.instantiationService.stub(IGlobalExtensionEnablementService, this.instantiationService.createInstance(GlobalExtensionEnablementService)); - this.instantiationService.stub(IExtensionsStorageSyncService, this.instantiationService.createInstance(ExtensionsStorageSyncService)); + this.instantiationService.stub(IGlobalExtensionEnablementService, this._register(this.instantiationService.createInstance(GlobalExtensionEnablementService))); + this.instantiationService.stub(IExtensionsStorageSyncService, this._register(this.instantiationService.createInstance(ExtensionsStorageSyncService))); this.instantiationService.stub(IIgnoredExtensionsManagementService, this.instantiationService.createInstance(IgnoredExtensionsManagementService)); this.instantiationService.stub(IExtensionManagementService, >{ async getInstalled() { return []; }, @@ -118,8 +118,8 @@ export class UserDataSyncClient extends Disposable { async getCompatibleExtension() { return null; } }); - this.instantiationService.stub(IUserDataAutoSyncEnablementService, this.instantiationService.createInstance(UserDataAutoSyncEnablementService)); - this.instantiationService.stub(IUserDataSyncService, this.instantiationService.createInstance(UserDataSyncService)); + this.instantiationService.stub(IUserDataAutoSyncEnablementService, this._register(this.instantiationService.createInstance(UserDataAutoSyncEnablementService))); + this.instantiationService.stub(IUserDataSyncService, this._register(this.instantiationService.createInstance(UserDataSyncService))); if (!empty) { await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({}))); diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts b/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts index 2c7d1734d..4550c939b 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts @@ -58,7 +58,7 @@ suite('UserDataSyncStoreManagementService', () => { authenticationProviders: [{ id: 'configuredAuthProvider', scopes: [] }] }; - const testObject: IUserDataSyncStoreManagementService = client.instantiationService.createInstance(UserDataSyncStoreManagementService); + const testObject: IUserDataSyncStoreManagementService = disposableStore.add(client.instantiationService.createInstance(UserDataSyncStoreManagementService)); assert.equal(testObject.userDataSyncStore?.url.toString(), expected.url.toString()); assert.equal(testObject.userDataSyncStore?.defaultUrl.toString(), expected.defaultUrl.toString()); @@ -419,7 +419,7 @@ suite('UserDataSyncStoreService', () => { await testObject.manifest(); } catch (e) { } - const target = client.instantiationService.createInstance(UserDataSyncStoreService); + const target = disposableStore.add(client.instantiationService.createInstance(UserDataSyncStoreService)); assert.equal(target.donotMakeRequestsUntil?.getTime(), testObject.donotMakeRequestsUntil?.getTime()); }); @@ -434,7 +434,7 @@ suite('UserDataSyncStoreService', () => { } catch (e) { } await timeout(300); - const target = client.instantiationService.createInstance(UserDataSyncStoreService); + const target = disposableStore.add(client.instantiationService.createInstance(UserDataSyncStoreService)); assert.ok(!target.donotMakeRequestsUntil); }); diff --git a/src/vs/platform/webview/common/resourceLoader.ts b/src/vs/platform/webview/common/resourceLoader.ts index ee64c8ef4..46f0217fa 100644 --- a/src/vs/platform/webview/common/resourceLoader.ts +++ b/src/vs/platform/webview/common/resourceLoader.ts @@ -9,6 +9,7 @@ import { isUNC } from 'vs/base/common/extpath'; import { Schemas } from 'vs/base/common/network'; import { sep } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; +import { ILogService } from 'vs/platform/log/common/log'; import { IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IRequestService } from 'vs/platform/request/common/request'; import { getWebviewContentMimeType } from 'vs/platform/webview/common/mimeTypes'; @@ -24,7 +25,8 @@ export namespace WebviewResourceResponse { constructor( public readonly stream: VSBufferReadableStream, - public readonly mimeType: string + public readonly etag: string | undefined, + public readonly mimeType: string, ) { } } @@ -35,7 +37,7 @@ export namespace WebviewResourceResponse { } interface FileReader { - readFileStream(resource: URI): Promise; + readFileStream(resource: URI): Promise<{ stream: VSBufferReadableStream, etag?: string }>; } export async function loadLocalResource( @@ -48,8 +50,14 @@ export async function loadLocalResource( }, fileReader: FileReader, requestService: IRequestService, + logService: ILogService, ): Promise { + logService.debug(`loadLocalResource - being. requestUri=${requestUri}`); + let resourceToLoad = getResourceToLoad(requestUri, options.roots); + + logService.debug(`loadLocalResource - found resource to load. requestUri=${requestUri}, resourceToLoad=${resourceToLoad}`); + if (!resourceToLoad) { return WebviewResourceResponse.AccessDenied; } @@ -63,17 +71,23 @@ export async function loadLocalResource( if (resourceToLoad.scheme === Schemas.http || resourceToLoad.scheme === Schemas.https) { const response = await requestService.request({ url: resourceToLoad.toString(true) }, CancellationToken.None); + logService.debug(`loadLocalResource - Loaded over http(s). requestUri=${requestUri}, response=${response.res.statusCode}`); + if (response.res.statusCode === 200) { - return new WebviewResourceResponse.StreamSuccess(response.stream, mime); + return new WebviewResourceResponse.StreamSuccess(response.stream, undefined, mime); } return WebviewResourceResponse.Failed; } try { const contents = await fileReader.readFileStream(resourceToLoad); - return new WebviewResourceResponse.StreamSuccess(contents, mime); + logService.debug(`loadLocalResource - Loaded using fileReader. requestUri=${requestUri}`); + + return new WebviewResourceResponse.StreamSuccess(contents.stream, contents.etag, mime); } catch (err) { + logService.debug(`loadLocalResource - Error using fileReader. requestUri=${requestUri}`); console.log(err); + return WebviewResourceResponse.Failed; } } diff --git a/src/vs/platform/webview/common/webviewPortMapping.ts b/src/vs/platform/webview/common/webviewPortMapping.ts index 581543130..4e9c22976 100644 --- a/src/vs/platform/webview/common/webviewPortMapping.ts +++ b/src/vs/platform/webview/common/webviewPortMapping.ts @@ -19,7 +19,7 @@ export interface IWebviewPortMapping { */ export class WebviewPortMappingManager implements IDisposable { - private readonly _tunnels = new Map>(); + private readonly _tunnels = new Map(); constructor( private readonly _getExtensionLocation: () => URI | undefined, @@ -60,19 +60,19 @@ export class WebviewPortMappingManager implements IDisposable { return undefined; } - dispose() { + async dispose() { for (const tunnel of this._tunnels.values()) { - tunnel.then(tunnel => tunnel.dispose()); + await tunnel.dispose(); } this._tunnels.clear(); } - private getOrCreateTunnel(remoteAuthority: IAddress, remotePort: number): Promise | undefined { + private async getOrCreateTunnel(remoteAuthority: IAddress, remotePort: number): Promise { const existing = this._tunnels.get(remotePort); if (existing) { return existing; } - const tunnel = this.tunnelService.openTunnel({ getAddress: async () => remoteAuthority }, undefined, remotePort); + const tunnel = await this.tunnelService.openTunnel({ getAddress: async () => remoteAuthority }, undefined, remotePort); if (tunnel) { this._tunnels.set(remotePort, tunnel); } diff --git a/src/vs/platform/webview/electron-main/webviewMainService.ts b/src/vs/platform/webview/electron-main/webviewMainService.ts index 03a6e306c..92bd47e2d 100644 --- a/src/vs/platform/webview/electron-main/webviewMainService.ts +++ b/src/vs/platform/webview/electron-main/webviewMainService.ts @@ -8,6 +8,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { IRequestService } from 'vs/platform/request/common/request'; import { IWebviewManagerService, RegisterWebviewMetadata, WebviewWebContentsId, WebviewWindowId } from 'vs/platform/webview/common/webviewManagerService'; @@ -24,12 +25,13 @@ export class WebviewMainService extends Disposable implements IWebviewManagerSer constructor( @IFileService fileService: IFileService, + @ILogService logService: ILogService, @IRequestService requestService: IRequestService, @ITunnelService tunnelService: ITunnelService, @IWindowsMainService private readonly windowsMainService: IWindowsMainService, ) { super(); - this.protocolProvider = this._register(new WebviewProtocolProvider(fileService, requestService, windowsMainService)); + this.protocolProvider = this._register(new WebviewProtocolProvider(fileService, logService, requestService, windowsMainService)); this.portMappingProvider = this._register(new WebviewPortMappingProvider(tunnelService)); } diff --git a/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts b/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts index a019d267e..a8c2e0fef 100644 --- a/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts +++ b/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts @@ -10,6 +10,7 @@ import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { FileAccess, Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; import { IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IRequestService } from 'vs/platform/request/common/request'; import { loadLocalResource, webviewPartitionId, WebviewResourceResponse } from 'vs/platform/webview/common/resourceLoader'; @@ -38,8 +39,9 @@ export class WebviewProtocolProvider extends Disposable { constructor( @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService, @IRequestService private readonly requestService: IRequestService, - @IWindowsMainService readonly windowsMainService: IWindowsMainService, + @IWindowsMainService private readonly windowsMainService: IWindowsMainService, ) { super(); @@ -125,7 +127,10 @@ export class WebviewProtocolProvider extends Disposable { } } - private async handleWebviewRequest(request: Electron.Request, callback: any) { + private async handleWebviewRequest( + request: Electron.ProtocolRequest, + callback: (response: string | Electron.ProtocolResponse) => void + ) { try { const uri = URI.parse(request.url); const entry = WebviewProtocolProvider.validWebviewFilePaths.get(uri.path); @@ -144,8 +149,8 @@ export class WebviewProtocolProvider extends Disposable { } private async handleWebviewResourceRequest( - request: Electron.Request, - callback: (stream?: NodeJS.ReadableStream | Electron.StreamProtocolResponse | undefined) => void + request: Electron.ProtocolRequest, + callback: (stream: NodeJS.ReadableStream | Electron.ProtocolResponse) => void ) { try { const uri = URI.parse(request.url); @@ -170,10 +175,14 @@ export class WebviewProtocolProvider extends Disposable { }; } - const fileService = { - readFileStream: async (resource: URI): Promise => { + const fileReader = { + readFileStream: async (resource: URI): Promise<{ stream: VSBufferReadableStream, etag?: string }> => { if (resource.scheme === Schemas.file) { - return (await this.fileService.readFileStream(resource)).value; + const result = (await this.fileService.readFileStream(resource)); + return { + stream: result.value, + etag: result.etag + }; } // Unknown uri scheme. Try delegating the file read back to the renderer @@ -196,7 +205,7 @@ export class WebviewProtocolProvider extends Disposable { throw new FileOperationError('Could not read file', FileOperationResult.FILE_NOT_FOUND); } - return bufferToStream(result); + return { stream: bufferToStream(result), etag: undefined }; } }; @@ -205,29 +214,55 @@ export class WebviewProtocolProvider extends Disposable { roots: metadata.localResourceRoots, remoteConnectionData: metadata.remoteConnectionData, rewriteUri, - }, fileService, this.requestService); + }, fileReader, this.requestService, this.logService); if (result.type === WebviewResourceResponse.Type.Success) { + const cacheHeaders: Record = result.etag ? { + 'ETag': result.etag, + 'Cache-Control': 'no-cache' + } : {}; + + const ifNoneMatch = request.headers['If-None-Match']; + if (ifNoneMatch && result.etag === ifNoneMatch) { + /* + * Note that the server generating a 304 response MUST + * generate any of the following header fields that would + * have been sent in a 200 (OK) response to the same request: + * Cache-Control, Content-Location, Date, ETag, Expires, and Vary. + * (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match) + */ + return callback({ + statusCode: 304, // not modified + data: undefined, // The request fails if `data` is not set + headers: { + 'Content-Type': result.mimeType, + 'Access-Control-Allow-Origin': '*', + ...cacheHeaders + } + }); + } + return callback({ statusCode: 200, data: this.streamToNodeReadable(result.stream), headers: { 'Content-Type': result.mimeType, 'Access-Control-Allow-Origin': '*', + ...cacheHeaders } }); } if (result.type === WebviewResourceResponse.Type.AccessDenied) { console.error('Webview: Cannot load resource outside of protocol root'); - return callback({ data: null, statusCode: 401 }); + return callback({ data: undefined, statusCode: 401 }); } } } catch { // noop } - return callback({ data: null, statusCode: 404 }); + return callback({ data: undefined, statusCode: 404 }); } public didLoadResource(requestId: number, content: VSBuffer | undefined) { diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 40c952136..f97741d19 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -9,7 +9,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { LogLevel } from 'vs/platform/log/common/log'; -import { ExportData } from 'vs/base/common/performance'; +import { PerformanceMark } from 'vs/base/common/performance'; export const WindowMinimumSize = { WIDTH: 400, @@ -18,38 +18,37 @@ export const WindowMinimumSize = { }; export interface IBaseOpenWindowsOptions { - forceReuseWindow?: boolean; + readonly forceReuseWindow?: boolean; } export interface IOpenWindowOptions extends IBaseOpenWindowsOptions { - forceNewWindow?: boolean; - preferNewWindow?: boolean; + readonly forceNewWindow?: boolean; + readonly preferNewWindow?: boolean; - noRecentEntry?: boolean; + readonly noRecentEntry?: boolean; - addMode?: boolean; + readonly addMode?: boolean; - diffMode?: boolean; - gotoLineMode?: boolean; + readonly diffMode?: boolean; + readonly gotoLineMode?: boolean; - waitMarkerFileURI?: URI; + readonly waitMarkerFileURI?: URI; } export interface IAddFoldersRequest { - foldersToAdd: UriComponents[]; + readonly foldersToAdd: UriComponents[]; } export interface IOpenedWindow { - id: number; - workspace?: IWorkspaceIdentifier; - folderUri?: ISingleFolderWorkspaceIdentifier; - title: string; - filename?: string; - dirty: boolean; + readonly id: number; + readonly workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier; + readonly title: string; + readonly filename?: string; + readonly dirty: boolean; } export interface IOpenEmptyWindowOptions extends IBaseOpenWindowsOptions { - remoteAuthority?: string; + readonly remoteAuthority?: string; } export type IWindowOpenable = IWorkspaceToOpen | IFolderToOpen | IFileToOpen; @@ -59,15 +58,15 @@ export interface IBaseWindowOpenable { } export interface IWorkspaceToOpen extends IBaseWindowOpenable { - workspaceUri: URI; + readonly workspaceUri: URI; } export interface IFolderToOpen extends IBaseWindowOpenable { - folderUri: URI; + readonly folderUri: URI; } export interface IFileToOpen extends IBaseWindowOpenable { - fileUri: URI; + readonly fileUri: URI; } export function isWorkspaceToOpen(uriToOpen: IWindowOpenable): uriToOpen is IWorkspaceToOpen { @@ -96,26 +95,25 @@ export function getMenuBarVisibility(configurationService: IConfigurationService } export interface IWindowsConfiguration { - window: IWindowSettings; + readonly window: IWindowSettings; } export interface IWindowSettings { - openFilesInNewWindow: 'on' | 'off' | 'default'; - openFoldersInNewWindow: 'on' | 'off' | 'default'; - openWithoutArgumentsInNewWindow: 'on' | 'off'; - restoreWindows: 'preserve' | 'all' | 'folders' | 'one' | 'none'; - restoreFullscreen: boolean; - zoomLevel: number; - titleBarStyle: 'native' | 'custom'; - autoDetectHighContrast: boolean; - menuBarVisibility: MenuBarVisibility; - newWindowDimensions: 'default' | 'inherit' | 'offset' | 'maximized' | 'fullscreen'; - nativeTabs: boolean; - nativeFullScreen: boolean; - enableMenuBarMnemonics: boolean; - closeWhenEmpty: boolean; - clickThroughInactive: boolean; - enableExperimentalProxyLoginDialog: boolean; + readonly openFilesInNewWindow: 'on' | 'off' | 'default'; + readonly openFoldersInNewWindow: 'on' | 'off' | 'default'; + readonly openWithoutArgumentsInNewWindow: 'on' | 'off'; + readonly restoreWindows: 'preserve' | 'all' | 'folders' | 'one' | 'none'; + readonly restoreFullscreen: boolean; + readonly zoomLevel: number; + readonly titleBarStyle: 'native' | 'custom'; + readonly autoDetectHighContrast: boolean; + readonly menuBarVisibility: MenuBarVisibility; + readonly newWindowDimensions: 'default' | 'inherit' | 'offset' | 'maximized' | 'fullscreen'; + readonly nativeTabs: boolean; + readonly nativeFullScreen: boolean; + readonly enableMenuBarMnemonics: boolean; + readonly closeWhenEmpty: boolean; + readonly clickThroughInactive: boolean; } export function getTitleBarStyle(configurationService: IConfigurationService): 'native' | 'custom' { @@ -154,24 +152,24 @@ export interface IPath extends IPathData { export interface IPathData { // the file path to open within the instance - fileUri?: UriComponents; + readonly fileUri?: UriComponents; // the line number in the file path to open - lineNumber?: number; + readonly lineNumber?: number; // the column number in the file path to open - columnNumber?: number; + readonly columnNumber?: number; // a hint that the file exists. if true, the // file exists, if false it does not. with // undefined the state is unknown. - exists?: boolean; + readonly exists?: boolean; // Specifies if the file should be only be opened if it exists - openOnlyIfExists?: boolean; + readonly openOnlyIfExists?: boolean; // Specifies an optional id to override the editor used to edit the resource, e.g. custom editor. - overrideId?: string; + readonly overrideId?: string; } export interface IPathsToWaitFor extends IPathsToWaitForData { @@ -180,36 +178,36 @@ export interface IPathsToWaitFor extends IPathsToWaitForData { } interface IPathsToWaitForData { - paths: IPathData[]; - waitMarkerFileUri: UriComponents; + readonly paths: IPathData[]; + readonly waitMarkerFileUri: UriComponents; } export interface IOpenFileRequest { - filesToOpenOrCreate?: IPathData[]; - filesToDiff?: IPathData[]; + readonly filesToOpenOrCreate?: IPathData[]; + readonly filesToDiff?: IPathData[]; } /** * Additional context for the request on native only. */ export interface INativeOpenFileRequest extends IOpenFileRequest { - termProgram?: string; - filesToWait?: IPathsToWaitForData; + readonly termProgram?: string; + readonly filesToWait?: IPathsToWaitForData; } export interface INativeRunActionInWindowRequest { - id: string; - from: 'menu' | 'touchbar' | 'mouse'; - args?: any[]; + readonly id: string; + readonly from: 'menu' | 'touchbar' | 'mouse'; + readonly args?: any[]; } export interface INativeRunKeybindingInWindowRequest { - userSettingsLabel: string; + readonly userSettingsLabel: string; } export interface IColorScheme { - dark: boolean; - highContrast: boolean; + readonly dark: boolean; + readonly highContrast: boolean; } export interface IWindowConfiguration { @@ -225,7 +223,7 @@ export interface IWindowConfiguration { } export interface IOSConfiguration { - release: string; + readonly release: string; } export interface INativeWindowConfiguration extends IWindowConfiguration, NativeParsedArgs { @@ -241,8 +239,7 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native nodeCachedDataDir?: string; partsSplashPath: string; - workspace?: IWorkspaceIdentifier; - folderUri?: ISingleFolderWorkspaceIdentifier; + workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier; isInitialStartup?: boolean; logLevel: LogLevel; @@ -250,7 +247,7 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native fullscreen?: boolean; maximized?: boolean; accessibilitySupport?: boolean; - perfEntries: ExportData; + perfMarks: PerformanceMark[]; userEnv: IProcessEnvironment; filesToWait?: IPathsToWaitFor; diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 83eccca73..8bd104650 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -4,18 +4,38 @@ *--------------------------------------------------------------------------------------------*/ import { IWindowOpenable, IOpenEmptyWindowOptions, INativeWindowConfiguration } from 'vs/platform/windows/common/windows'; -import { OpenContext } from 'vs/platform/windows/node/window'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProcessEnvironment } from 'vs/base/common/platform'; -import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { URI } from 'vs/base/common/uri'; import { Rectangle, BrowserWindow, WebContents } from 'electron'; import { IDisposable } from 'vs/base/common/lifecycle'; import { CancellationToken } from 'vs/base/common/cancellation'; +export const enum OpenContext { + + // opening when running from the command line + CLI, + + // macOS only: opening from the dock (also when opening files to a running instance from desktop) + DOCK, + + // opening from the main application window + MENU, + + // opening from a file or folder dialog + DIALOG, + + // opening from the OS's UI + DESKTOP, + + // opening through the API + API +} + export interface IWindowState { width?: number; height?: number; @@ -45,8 +65,8 @@ export interface ICodeWindow extends IDisposable { readonly win: BrowserWindow; readonly config: INativeWindowConfiguration | undefined; - readonly openedFolderUri?: URI; - readonly openedWorkspace?: IWorkspaceIdentifier; + readonly openedWorkspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier; + readonly backupPath?: string; readonly remoteAuthority?: string; @@ -64,7 +84,7 @@ export interface ICodeWindow extends IDisposable { addTabbedWindow(window: ICodeWindow): void; - load(config: INativeWindowConfiguration, isReload?: boolean): void; + load(config: INativeWindowConfiguration, options?: { isReload?: boolean }): void; reload(cli?: NativeParsedArgs): void; focus(options?: { force: boolean }): void; @@ -104,9 +124,11 @@ export interface IWindowsMainService { readonly _serviceBrand: undefined; + readonly onWindowsCountChanged: Event; + readonly onWindowOpened: Event; readonly onWindowReady: Event; - readonly onWindowsCountChanged: Event; + readonly onWindowDestroyed: Event; open(openConfig: IOpenConfiguration): ICodeWindow[]; openEmptyWindow(openConfig: IOpenEmptyConfiguration, options?: IOpenEmptyWindowOptions): ICodeWindow[]; @@ -115,13 +137,14 @@ export interface IWindowsMainService { sendToFocused(channel: string, ...args: any[]): void; sendToAll(channel: string, payload?: any, windowIdsToIgnore?: number[]): void; + getWindows(): ICodeWindow[]; + getWindowCount(): number; + getFocusedWindow(): ICodeWindow | undefined; getLastActiveWindow(): ICodeWindow | undefined; getWindowById(windowId: number): ICodeWindow | undefined; getWindowByWebContents(webContents: WebContents): ICodeWindow | undefined; - getWindows(): ICodeWindow[]; - getWindowCount(): number; } export interface IBaseOpenConfiguration { diff --git a/src/vs/platform/windows/electron-main/windowsFinder.ts b/src/vs/platform/windows/electron-main/windowsFinder.ts new file mode 100644 index 000000000..30f760183 --- /dev/null +++ b/src/vs/platform/windows/electron-main/windowsFinder.ts @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IWorkspaceIdentifier, IResolvedWorkspace, isWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; +import { ICodeWindow } from 'vs/platform/windows/electron-main/windows'; + +export function findWindowOnFile(windows: ICodeWindow[], fileUri: URI, localWorkspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | null): ICodeWindow | undefined { + + // First check for windows with workspaces that have a parent folder of the provided path opened + for (const window of windows) { + const workspace = window.openedWorkspace; + if (isWorkspaceIdentifier(workspace)) { + const resolvedWorkspace = localWorkspaceResolver(workspace); + + // resolved workspace: folders are known and can be compared with + if (resolvedWorkspace) { + if (resolvedWorkspace.folders.some(folder => extUriBiasedIgnorePathCase.isEqualOrParent(fileUri, folder.uri))) { + return window; + } + } + + // unresolved: can only compare with workspace location + else { + if (extUriBiasedIgnorePathCase.isEqualOrParent(fileUri, workspace.configPath)) { + return window; + } + } + } + } + + // Then go with single folder windows that are parent of the provided file path + const singleFolderWindowsOnFilePath = windows.filter(window => isSingleFolderWorkspaceIdentifier(window.openedWorkspace) && extUriBiasedIgnorePathCase.isEqualOrParent(fileUri, window.openedWorkspace.uri)); + if (singleFolderWindowsOnFilePath.length) { + return singleFolderWindowsOnFilePath.sort((windowA, windowB) => -((windowA.openedWorkspace as ISingleFolderWorkspaceIdentifier).uri.path.length - (windowB.openedWorkspace as ISingleFolderWorkspaceIdentifier).uri.path.length))[0]; + } + + return undefined; +} + +export function findWindowOnWorkspaceOrFolder(windows: ICodeWindow[], folderOrWorkspaceConfigUri: URI): ICodeWindow | undefined { + + for (const window of windows) { + + // check for workspace config path + if (isWorkspaceIdentifier(window.openedWorkspace) && extUriBiasedIgnorePathCase.isEqual(window.openedWorkspace.configPath, folderOrWorkspaceConfigUri)) { + return window; + } + + // check for folder path + if (isSingleFolderWorkspaceIdentifier(window.openedWorkspace) && extUriBiasedIgnorePathCase.isEqual(window.openedWorkspace.uri, folderOrWorkspaceConfigUri)) { + return window; + } + } + + return undefined; +} + + +export function findWindowOnExtensionDevelopmentPath(windows: ICodeWindow[], extensionDevelopmentPaths: string[]): ICodeWindow | undefined { + + const matches = (uriString: string): boolean => { + return extensionDevelopmentPaths.some(path => extUriBiasedIgnorePathCase.isEqual(URI.file(path), URI.file(uriString))); + }; + + for (const window of windows) { + + // match on extension development path. the path can be one or more paths + // so we check if any of the paths match on any of the provided ones + if (window.config?.extensionDevelopmentPath?.some(path => matches(path))) { + return window; + } + } + + return undefined; +} diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 6bcedb5c7..eebedf006 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { statSync, unlink } from 'fs'; +import { statSync } from 'fs'; import { basename, normalize, join, posix } from 'vs/base/common/path'; import { localize } from 'vs/nls'; import { coalesce, distinct, firstOrDefault } from 'vs/base/common/arrays'; @@ -12,166 +12,126 @@ import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { IStateService } from 'vs/platform/state/node/state'; -import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window'; -import { screen, BrowserWindow, MessageBoxOptions, Display, app, WebContents } from 'electron'; +import { CodeWindow } from 'vs/code/electron-main/window'; +import { BrowserWindow, MessageBoxOptions, WebContents } from 'electron'; import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWindowSettings, IPath, isFileToOpen, isWorkspaceToOpen, isFolderToOpen, IWindowOpenable, IOpenEmptyWindowOptions, IAddFoldersRequest, IPathsToWaitFor, INativeWindowConfiguration } from 'vs/platform/windows/common/windows'; -import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri, OpenContext } from 'vs/platform/windows/node/window'; +import { IWindowSettings, IPath, isFileToOpen, isWorkspaceToOpen, isFolderToOpen, IWindowOpenable, IOpenEmptyWindowOptions, IAddFoldersRequest, IPathsToWaitFor, INativeWindowConfiguration, INativeOpenFileRequest } from 'vs/platform/windows/common/windows'; +import { findWindowOnFile, findWindowOnWorkspaceOrFolder, findWindowOnExtensionDevelopmentPath } from 'vs/platform/windows/electron-main/windowsFinder'; import { Emitter } from 'vs/base/common/event'; import product from 'vs/platform/product/common/product'; -import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode, IOpenEmptyConfiguration } from 'vs/platform/windows/electron-main/windows'; +import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IOpenEmptyConfiguration, OpenContext } from 'vs/platform/windows/electron-main/windows'; import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; -import { IProcessEnvironment, isMacintosh, isWindows } from 'vs/base/common/platform'; -import { IWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, hasWorkspaceFileExtension, IRecent } from 'vs/platform/workspaces/common/workspaces'; +import { IProcessEnvironment, isMacintosh } from 'vs/base/common/platform'; +import { IWorkspaceIdentifier, hasWorkspaceFileExtension, IRecent, isWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { normalizePath, originalFSPath, removeTrailingPathSeparator, extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; -import { restoreWindowsState, WindowsStateStorageData, getWindowsStateStoreData } from 'vs/platform/windows/electron-main/windowsStateStorage'; -import { getWorkspaceIdentifier, IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; +import { IWindowState, WindowsStateHandler } from 'vs/platform/windows/electron-main/windowsStateHandler'; +import { getSingleFolderWorkspaceIdentifier, getWorkspaceIdentifier, IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; import { once } from 'vs/base/common/functional'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; +import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { isWindowsDriveLetter, toSlashes, parseLineAndColumnAware } from 'vs/base/common/extpath'; +import { isWindowsDriveLetter, toSlashes, parseLineAndColumnAware, sanitizeFilePath } from 'vs/base/common/extpath'; import { CharCode } from 'vs/base/common/charCode'; import { getPathLabel } from 'vs/base/common/labels'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IFileService } from 'vs/platform/files/common/files'; -export interface IWindowState { - workspace?: IWorkspaceIdentifier; - folderUri?: URI; - backupPath?: string; - remoteAuthority?: string; - uiState: ISingleWindowState; -} - -export interface IWindowsState { - lastActiveWindow?: IWindowState; - lastPluginDevelopmentHostWindow?: IWindowState; - openedWindows: IWindowState[]; -} - -interface INewWindowState extends ISingleWindowState { - hasDefaultState?: boolean; -} +//#region Helper Interfaces type RestoreWindowsSetting = 'preserve' | 'all' | 'folders' | 'one' | 'none'; interface IOpenBrowserWindowOptions { - userEnv?: IProcessEnvironment; - cli?: NativeParsedArgs; + readonly userEnv?: IProcessEnvironment; + readonly cli?: NativeParsedArgs; - workspace?: IWorkspaceIdentifier; - folderUri?: URI; + readonly workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier; - remoteAuthority?: string; + readonly remoteAuthority?: string; - initialStartup?: boolean; + readonly initialStartup?: boolean; - filesToOpen?: IFilesToOpen; + readonly filesToOpen?: IFilesToOpen; - forceNewWindow?: boolean; - forceNewTabbedWindow?: boolean; - windowToUse?: ICodeWindow; + readonly forceNewWindow?: boolean; + readonly forceNewTabbedWindow?: boolean; + readonly windowToUse?: ICodeWindow; - emptyWindowBackupInfo?: IEmptyWindowBackupInfo; + readonly emptyWindowBackupInfo?: IEmptyWindowBackupInfo; } -interface IPathParseOptions { - ignoreFileNotFound?: boolean; - gotoLineMode?: boolean; - remoteAuthority?: string; +interface IPathResolveOptions { + readonly ignoreFileNotFound?: boolean; + readonly gotoLineMode?: boolean; + readonly remoteAuthority?: string; } interface IFilesToOpen { + readonly remoteAuthority?: string; + filesToOpenOrCreate: IPath[]; filesToDiff: IPath[]; filesToWait?: IPathsToWaitFor; - remoteAuthority?: string; } interface IPathToOpen extends IPath { - // the workspace for a Code instance to open - workspace?: IWorkspaceIdentifier; + // the workspace to open + readonly workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier; - // the folder path for a Code instance to open - folderUri?: URI; - - // the backup path for a Code instance to use - backupPath?: string; + // the backup path to use + readonly backupPath?: string; // the remote authority for the Code instance to open. Undefined if not remote. - remoteAuthority?: string; + readonly remoteAuthority?: string; // optional label for the recent history label?: string; } -function isFolderPathToOpen(path: IPathToOpen): path is IFolderPathToOpen { - return !!path.folderUri; +interface IWorkspacePathToOpen extends IPathToOpen { + readonly workspace: IWorkspaceIdentifier; } -interface IFolderPathToOpen { - - // the folder path for a Code instance to open - folderUri: URI; - - // the backup path for a Code instance to use - backupPath?: string; - - // the remote authority for the Code instance to open. Undefined if not remote. - remoteAuthority?: string; - - // optional label for the recent history - label?: string; +interface ISingleFolderWorkspacePathToOpen extends IPathToOpen { + readonly workspace: ISingleFolderWorkspaceIdentifier; } -function isWorkspacePathToOpen(path: IPathToOpen): path is IWorkspacePathToOpen { - return !!path.workspace; +function isWorkspacePathToOpen(path: IPathToOpen | undefined): path is IWorkspacePathToOpen { + return isWorkspaceIdentifier(path?.workspace); } -interface IWorkspacePathToOpen { - - // the workspace for a Code instance to open - workspace: IWorkspaceIdentifier; - - // the backup path for a Code instance to use - backupPath?: string; - - // the remote authority for the Code instance to open. Undefined if not remote. - remoteAuthority?: string; - - // optional label for the recent history - label?: string; +function isSingleFolderWorkspacePathToOpen(path: IPathToOpen | undefined): path is ISingleFolderWorkspacePathToOpen { + return isSingleFolderWorkspaceIdentifier(path?.workspace); } +//#endregion + export class WindowsMainService extends Disposable implements IWindowsMainService { declare readonly _serviceBrand: undefined; - private static readonly windowsStateStorageKey = 'windowsState'; - private static readonly WINDOWS: ICodeWindow[] = []; - private readonly windowsState: IWindowsState; - private lastClosedWindowState?: IWindowState; - - private shuttingDown = false; - private readonly _onWindowOpened = this._register(new Emitter()); readonly onWindowOpened = this._onWindowOpened.event; private readonly _onWindowReady = this._register(new Emitter()); readonly onWindowReady = this._onWindowReady.event; + private readonly _onWindowDestroyed = this._register(new Emitter()); + readonly onWindowDestroyed = this._onWindowDestroyed.event; + private readonly _onWindowsCountChanged = this._register(new Emitter()); readonly onWindowsCountChanged = this._onWindowsCountChanged.event; + private readonly windowsStateHandler = this._register(new WindowsStateHandler(this, this.stateService, this.lifecycleMainService, this.logService, this.configurationService)); + constructor( private readonly machineId: string, private readonly initialUserEnv: IProcessEnvironment, @@ -182,190 +142,20 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic @IBackupMainService private readonly backupMainService: IBackupMainService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkspacesHistoryMainService private readonly workspacesHistoryMainService: IWorkspacesHistoryMainService, - @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, + @IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IDialogMainService private readonly dialogMainService: IDialogMainService + @IDialogMainService private readonly dialogMainService: IDialogMainService, + @IFileService private readonly fileService: IFileService ) { super(); - this.windowsState = restoreWindowsState(this.stateService.getItem(WindowsMainService.windowsStateStorageKey)); - if (!Array.isArray(this.windowsState.openedWindows)) { - this.windowsState.openedWindows = []; - } - this.lifecycleMainService.when(LifecycleMainPhase.Ready).then(() => this.registerListeners()); - this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.installWindowsMutex()); - } - - private installWindowsMutex(): void { - const win32MutexName = product.win32MutexName; - if (isWindows && win32MutexName) { - try { - const WindowsMutex = (require.__$__nodeRequire('windows-mutex') as typeof import('windows-mutex')).Mutex; - const mutex = new WindowsMutex(win32MutexName); - once(this.lifecycleMainService.onWillShutdown)(() => mutex.release()); - } catch (e) { - this.logService.error(e); - } - } } private registerListeners(): void { - // 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', () => { - if (!this.shuttingDown) { - this.saveWindowsState(); - } - }); - - // Handle various lifecycle events around windows - this.lifecycleMainService.onBeforeWindowClose(window => this.onBeforeWindowClose(window)); - this.lifecycleMainService.onBeforeShutdown(() => this.onBeforeShutdown()); - this.onWindowsCountChanged(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.lastClosedWindowState = undefined; - } - }); - // Signal a window is ready after having entered a workspace - this._register(this.workspacesMainService.onWorkspaceEntered(event => { - this._onWindowReady.fire(event.window); - })); - } - - // Note that onBeforeShutdown() and onBeforeWindowClose() are fired in different order depending on the OS: - // - macOS: since the app will not quit when closing the last window, you will always first get - // the onBeforeShutdown() event followed by N onBeforeWindowClose() events for each window - // - other: on other OS, closing the last window will quit the app so the order depends on the - // user interaction: closing the last window will first trigger onBeforeWindowClose() - // and then onBeforeShutdown(). Using the quit action however will first issue onBeforeShutdown() - // and then onBeforeWindowClose(). - // - // Here is the behavior on different OS depending on action taken (Electron 1.7.x): - // - // Legend - // - quit(N): quit application with N windows opened - // - close(1): close one window via the window close button - // - closeAll: close all windows via the taskbar command - // - onBeforeShutdown(N): number of windows reported in this event handler - // - onBeforeWindowClose(N, M): number of windows reported and quitRequested boolean in this event handler - // - // macOS - // - quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true) - // - quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true) - // - quit(0): onBeforeShutdown(0) - // - close(1): onBeforeWindowClose(1, false) - // - // Windows - // - quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true) - // - quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true) - // - close(1): onBeforeWindowClose(2, false)[not last window] - // - close(1): onBeforeWindowClose(1, false), onBeforeShutdown(0)[last window] - // - closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0) - // - // Linux - // - quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true) - // - quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true) - // - close(1): onBeforeWindowClose(2, false)[not last window] - // - close(1): onBeforeWindowClose(1, false), onBeforeShutdown(0)[last window] - // - closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0) - // - private onBeforeShutdown(): void { - this.shuttingDown = true; - - this.saveWindowsState(); - } - - private saveWindowsState(): void { - const currentWindowsState: IWindowsState = { - openedWindows: [], - lastPluginDevelopmentHostWindow: this.windowsState.lastPluginDevelopmentHostWindow, - lastActiveWindow: this.lastClosedWindowState - }; - - // 1.) Find a last active window (pick any other first window otherwise) - if (!currentWindowsState.lastActiveWindow) { - let activeWindow = this.getLastActiveWindow(); - if (!activeWindow || activeWindow.isExtensionDevelopmentHost) { - activeWindow = WindowsMainService.WINDOWS.find(window => !window.isExtensionDevelopmentHost); - } - - if (activeWindow) { - currentWindowsState.lastActiveWindow = this.toWindowState(activeWindow); - } - } - - // 2.) Find extension host window - const extensionHostWindow = WindowsMainService.WINDOWS.find(window => window.isExtensionDevelopmentHost && !window.isExtensionTestHost); - if (extensionHostWindow) { - currentWindowsState.lastPluginDevelopmentHostWindow = this.toWindowState(extensionHostWindow); - } - - // 3.) All windows (except extension host) for N >= 2 to support `restoreWindows: all` or for auto update - // - // Careful here: asking a window for its window state after it has been closed returns bogus values (width: 0, height: 0) - // so if we ever want to persist the UI state of the last closed window (window count === 1), it has - // to come from the stored lastClosedWindowState on Win/Linux at least - if (this.getWindowCount() > 1) { - currentWindowsState.openedWindows = WindowsMainService.WINDOWS.filter(window => !window.isExtensionDevelopmentHost).map(window => this.toWindowState(window)); - } - - // Persist - const state = getWindowsStateStoreData(currentWindowsState); - this.stateService.setItem(WindowsMainService.windowsStateStorageKey, state); - - if (this.shuttingDown) { - this.logService.trace('onBeforeShutdown', state); - } - } - - // See note on #onBeforeShutdown() for details how these events are flowing - private onBeforeWindowClose(win: ICodeWindow): void { - if (this.lifecycleMainService.quitRequested) { - return; // during quit, many windows close in parallel so let it be handled in the before-quit handler - } - - // On Window close, update our stored UI state of this window - const state: IWindowState = this.toWindowState(win); - if (win.isExtensionDevelopmentHost && !win.isExtensionTestHost) { - this.windowsState.lastPluginDevelopmentHostWindow = state; // do not let test run window state overwrite our extension development state - } - - // Any non extension host window with same workspace or folder - else if (!win.isExtensionDevelopmentHost && (!!win.openedWorkspace || !!win.openedFolderUri)) { - this.windowsState.openedWindows.forEach(o => { - const sameWorkspace = win.openedWorkspace && o.workspace && o.workspace.id === win.openedWorkspace.id; - const sameFolder = win.openedFolderUri && o.folderUri && extUriBiasedIgnorePathCase.isEqual(o.folderUri, win.openedFolderUri); - - if (sameWorkspace || sameFolder) { - o.uiState = state.uiState; - } - }); - } - - // On Windows and Linux closing the last window will trigger quit. Since we are storing all UI state - // before quitting, we need to remember the UI state of this window to be able to persist it. - // On macOS we keep the last closed window state ready in case the user wants to quit right after or - // wants to open another window, in which case we use this state over the persisted one. - if (this.getWindowCount() === 1) { - this.lastClosedWindowState = state; - } - } - - private toWindowState(win: ICodeWindow): IWindowState { - return { - workspace: win.openedWorkspace, - folderUri: win.openedFolderUri, - backupPath: win.backupPath, - remoteAuthority: win.remoteAuthority, - uiState: win.serializeWindowState() - }; + this._register(this.workspacesManagementMainService.onWorkspaceEntered(event => this._onWindowReady.fire(event.window))); } openEmptyWindow(openConfig: IOpenEmptyConfiguration, options?: IOpenEmptyWindowOptions): ICodeWindow[] { @@ -375,18 +165,22 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic cli = { ...cli, remote }; } + const forceEmpty = true; const forceReuseWindow = options?.forceReuseWindow; const forceNewWindow = !forceReuseWindow; - return this.open({ ...openConfig, cli, forceEmpty: true, forceNewWindow, forceReuseWindow }); + return this.open({ ...openConfig, cli, forceEmpty, forceNewWindow, forceReuseWindow }); } open(openConfig: IOpenConfiguration): ICodeWindow[] { this.logService.trace('windowsManager#open'); - openConfig = this.validateOpenConfig(openConfig); - const foldersToAdd: IFolderPathToOpen[] = []; - const foldersToOpen: IFolderPathToOpen[] = []; + if (openConfig.addMode && (openConfig.initialStartup || !this.getLastActiveWindow())) { + openConfig.addMode = false; // Make sure addMode is only enabled if we have an active window + } + + const foldersToAdd: ISingleFolderWorkspacePathToOpen[] = []; + const foldersToOpen: ISingleFolderWorkspacePathToOpen[] = []; const workspacesToOpen: IWorkspacePathToOpen[] = []; const workspacesToRestore: IWorkspacePathToOpen[] = []; const emptyToRestore: IEmptyWindowBackupInfo[] = []; @@ -397,7 +191,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const pathsToOpen = this.getPathsToOpen(openConfig); this.logService.trace('windowsManager#open pathsToOpen', pathsToOpen); for (const path of pathsToOpen) { - if (isFolderPathToOpen(path)) { + if (isSingleFolderWorkspacePathToOpen(path)) { if (openConfig.addMode) { // When run with --add, take the folders that are to be opened as // folders that should be added to the currently active window. @@ -431,13 +225,11 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic filesToOpen.filesToWait = { paths: [...filesToOpen.filesToDiff, ...filesToOpen.filesToOpenOrCreate], waitMarkerFileUri: openConfig.waitMarkerFileURI }; } - // // These are windows to restore because of hot-exit or from previous session (only performed once on startup!) - // if (openConfig.initialStartup) { // Untitled workspaces are always restored - workspacesToRestore.push(...this.workspacesMainService.getUntitledWorkspacesSync()); + workspacesToRestore.push(...this.workspacesManagementMainService.getUntitledWorkspacesSync()); workspacesToOpen.push(...workspacesToRestore); // Empty windows with backups are always restored @@ -461,13 +253,13 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // Otherwise, find a good window based on open params else { - const focusLastActive = this.windowsState.lastActiveWindow && !openConfig.forceEmpty && !openConfig.cli._.length && !openConfig.cli['file-uri'] && !openConfig.cli['folder-uri'] && !(openConfig.urisToOpen && openConfig.urisToOpen.length); + const focusLastActive = this.windowsStateHandler.state.lastActiveWindow && !openConfig.forceEmpty && !openConfig.cli._.length && !openConfig.cli['file-uri'] && !openConfig.cli['folder-uri'] && !(openConfig.urisToOpen && openConfig.urisToOpen.length); let focusLastOpened = true; let focusLastWindow = true; // 2.) focus last active window if we are not instructed to open any paths if (focusLastActive) { - const lastActiveWindow = usedWindows.filter(window => this.windowsState.lastActiveWindow && window.backupPath === this.windowsState.lastActiveWindow.backupPath); + const lastActiveWindow = usedWindows.filter(window => this.windowsStateHandler.state.lastActiveWindow && window.backupPath === this.windowsStateHandler.state.lastActiveWindow.backupPath); if (lastActiveWindow.length) { lastActiveWindow[0].focus(); focusLastOpened = false; @@ -504,11 +296,11 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const isDiff = filesToOpen && filesToOpen.filesToDiff.length > 0; if (!usedWindows.some(window => window.isExtensionDevelopmentHost) && !isDiff && !openConfig.noRecentEntry) { const recents: IRecent[] = []; - for (let pathToOpen of pathsToOpen) { - if (pathToOpen.workspace) { + for (const pathToOpen of pathsToOpen) { + if (isWorkspacePathToOpen(pathToOpen)) { recents.push({ label: pathToOpen.label, workspace: pathToOpen.workspace }); - } else if (pathToOpen.folderUri) { - recents.push({ label: pathToOpen.label, folderUri: pathToOpen.folderUri }); + } else if (isSingleFolderWorkspacePathToOpen(pathToOpen)) { + recents.push({ label: pathToOpen.label, folderUri: pathToOpen.workspace.uri }); } else if (pathToOpen.fileUri) { recents.push({ label: pathToOpen.label, fileUri: pathToOpen.fileUri }); } @@ -522,33 +314,34 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // process can continue. We do this by deleting the waitMarkerFilePath. const waitMarkerFileURI = openConfig.waitMarkerFileURI; if (openConfig.context === OpenContext.CLI && waitMarkerFileURI && usedWindows.length === 1 && usedWindows[0]) { - usedWindows[0].whenClosedOrLoaded.then(() => unlink(waitMarkerFileURI.fsPath, () => undefined)); + usedWindows[0].whenClosedOrLoaded.then(() => this.fileService.del(waitMarkerFileURI), () => undefined); } return usedWindows; } - private validateOpenConfig(config: IOpenConfiguration): IOpenConfiguration { - - // Make sure addMode is only enabled if we have an active window - if (config.addMode && (config.initialStartup || !this.getLastActiveWindow())) { - config.addMode = false; - } - - return config; - } - private doOpen( openConfig: IOpenConfiguration, workspacesToOpen: IWorkspacePathToOpen[], - foldersToOpen: IFolderPathToOpen[], + foldersToOpen: ISingleFolderWorkspacePathToOpen[], emptyToRestore: IEmptyWindowBackupInfo[], emptyToOpen: number, filesToOpen: IFilesToOpen | undefined, - foldersToAdd: IFolderPathToOpen[] + foldersToAdd: ISingleFolderWorkspacePathToOpen[] ): { windows: ICodeWindow[], filesOpenedInWindow: ICodeWindow | undefined } { + + // Keep track of used windows and remember + // if files have been opened in one of them const usedWindows: ICodeWindow[] = []; let filesOpenedInWindow: ICodeWindow | undefined = undefined; + function addUsedWindow(window: ICodeWindow, openedFiles?: boolean): void { + usedWindows.push(window); + + if (openedFiles) { + filesOpenedInWindow = window; + filesToOpen = undefined; // reset `filesToOpen` since files have been opened + } + } // Settings can decide if files/folders open in new window or not let { openFolderInNewWindow, openFilesInNewWindow } = this.shouldOpenNewWindow(openConfig); @@ -558,57 +351,59 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const authority = foldersToAdd[0].remoteAuthority; const lastActiveWindow = this.getLastActiveWindowForAuthority(authority); if (lastActiveWindow) { - usedWindows.push(this.doAddFoldersToExistingWindow(lastActiveWindow, foldersToAdd.map(f => f.folderUri))); + addUsedWindow(this.doAddFoldersToExistingWindow(lastActiveWindow, foldersToAdd.map(folderToAdd => folderToAdd.workspace.uri))); } } - // Handle files to open/diff or to create when we dont open a folder and we do not restore any folder/untitled from hot-exit - const potentialWindowsCount = foldersToOpen.length + workspacesToOpen.length + emptyToRestore.length; - if (potentialWindowsCount === 0 && filesToOpen) { + // Handle files to open/diff or to create when we dont open a folder and we do not restore any + // folder/untitled from hot-exit by trying to open them in the window that fits best + const potentialNewWindowsCount = foldersToOpen.length + workspacesToOpen.length + emptyToRestore.length; + if (filesToOpen && potentialNewWindowsCount === 0) { // Find suitable window or folder path to open files in const fileToCheck = filesToOpen.filesToOpenOrCreate[0] || filesToOpen.filesToDiff[0]; // only look at the windows with correct authority - const windows = WindowsMainService.WINDOWS.filter(window => filesToOpen && window.remoteAuthority === filesToOpen.remoteAuthority); + const windows = this.getWindows().filter(window => filesToOpen && window.remoteAuthority === filesToOpen.remoteAuthority); - const bestWindowOrFolder = findBestWindowOrFolderForFile({ - windows, - newWindow: openFilesInNewWindow, - context: openConfig.context, - fileUri: fileToCheck?.fileUri, - localWorkspaceResolver: workspace => workspace.configPath.scheme === Schemas.file ? this.workspacesMainService.resolveLocalWorkspaceSync(workspace.configPath) : null - }); + // figure out a good window to open the files in if any + // with a fallback to the last active window. + // + // in case `openFilesInNewWindow` is enforced, we skip + // this step. + let windowToUseForFiles: ICodeWindow | undefined = undefined; + if (fileToCheck?.fileUri && !openFilesInNewWindow) { + if (openConfig.context === OpenContext.DESKTOP || openConfig.context === OpenContext.CLI || openConfig.context === OpenContext.DOCK) { + windowToUseForFiles = findWindowOnFile(windows, fileToCheck.fileUri, workspace => workspace.configPath.scheme === Schemas.file ? this.workspacesManagementMainService.resolveLocalWorkspaceSync(workspace.configPath) : null); + } + + if (!windowToUseForFiles) { + windowToUseForFiles = this.doGetLastActiveWindow(windows); + } + } // We found a window to open the files in - if (bestWindowOrFolder instanceof CodeWindow) { + if (windowToUseForFiles) { // Window is workspace - if (bestWindowOrFolder.openedWorkspace) { - workspacesToOpen.push({ workspace: bestWindowOrFolder.openedWorkspace, remoteAuthority: bestWindowOrFolder.remoteAuthority }); + if (isWorkspaceIdentifier(windowToUseForFiles.openedWorkspace)) { + workspacesToOpen.push({ workspace: windowToUseForFiles.openedWorkspace, remoteAuthority: windowToUseForFiles.remoteAuthority }); } // Window is single folder - else if (bestWindowOrFolder.openedFolderUri) { - foldersToOpen.push({ folderUri: bestWindowOrFolder.openedFolderUri, remoteAuthority: bestWindowOrFolder.remoteAuthority }); + else if (isSingleFolderWorkspaceIdentifier(windowToUseForFiles.openedWorkspace)) { + foldersToOpen.push({ workspace: windowToUseForFiles.openedWorkspace, remoteAuthority: windowToUseForFiles.remoteAuthority }); } // Window is empty else { - - // Do open files - const window = this.doOpenFilesInExistingWindow(openConfig, bestWindowOrFolder, filesToOpen); - usedWindows.push(window); - - // Reset `filesToOpen` because we handled them and also remember window we used - filesToOpen = undefined; - filesOpenedInWindow = window; + addUsedWindow(this.doOpenFilesInExistingWindow(openConfig, windowToUseForFiles, filesToOpen), true); } } // Finally, if no window or folder is found, just open the files in an empty window else { - const window = this.openInBrowserWindow({ + addUsedWindow(this.openInBrowserWindow({ userEnv: openConfig.userEnv, cli: openConfig.cli, initialStartup: openConfig.initialStartup, @@ -616,12 +411,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic forceNewWindow: true, remoteAuthority: filesToOpen.remoteAuthority, forceNewTabbedWindow: openConfig.forceNewTabbedWindow - }); - usedWindows.push(window); - - // Reset `filesToOpen` because we handled them and also remember window we used - filesToOpen = undefined; - filesOpenedInWindow = window; + }), true); } } @@ -630,27 +420,20 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic if (allWorkspacesToOpen.length > 0) { // Check for existing instances - const windowsOnWorkspace = coalesce(allWorkspacesToOpen.map(workspaceToOpen => findWindowOnWorkspace(WindowsMainService.WINDOWS, workspaceToOpen.workspace))); + const windowsOnWorkspace = coalesce(allWorkspacesToOpen.map(workspaceToOpen => findWindowOnWorkspaceOrFolder(this.getWindows(), workspaceToOpen.workspace.configPath))); if (windowsOnWorkspace.length > 0) { const windowOnWorkspace = windowsOnWorkspace[0]; const filesToOpenInWindow = (filesToOpen?.remoteAuthority === windowOnWorkspace.remoteAuthority) ? filesToOpen : undefined; // Do open files - const window = this.doOpenFilesInExistingWindow(openConfig, windowOnWorkspace, filesToOpenInWindow); - usedWindows.push(window); - - // Reset `filesToOpen` because we handled them and also remember window we used - if (filesToOpenInWindow) { - filesToOpen = undefined; - filesOpenedInWindow = window; - } + addUsedWindow(this.doOpenFilesInExistingWindow(openConfig, windowOnWorkspace, filesToOpenInWindow), !!filesToOpenInWindow); openFolderInNewWindow = true; // any other folders to open must open in new window then } // Open remaining ones allWorkspacesToOpen.forEach(workspaceToOpen => { - if (windowsOnWorkspace.some(win => win.openedWorkspace && win.openedWorkspace.id === workspaceToOpen.workspace.id)) { + if (windowsOnWorkspace.some(window => window.openedWorkspace && window.openedWorkspace.id === workspaceToOpen.workspace.id)) { return; // ignore folders that are already open } @@ -658,46 +441,31 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const filesToOpenInWindow = (filesToOpen?.remoteAuthority === remoteAuthority) ? filesToOpen : undefined; // Do open folder - const window = this.doOpenFolderOrWorkspace(openConfig, workspaceToOpen, openFolderInNewWindow, filesToOpenInWindow); - usedWindows.push(window); - - // Reset `filesToOpen` because we handled them and also remember window we used - if (filesToOpenInWindow) { - filesToOpen = undefined; - filesOpenedInWindow = window; - } + addUsedWindow(this.doOpenFolderOrWorkspace(openConfig, workspaceToOpen, openFolderInNewWindow, filesToOpenInWindow), !!filesToOpenInWindow); openFolderInNewWindow = true; // any other folders to open must open in new window then }); } // Handle folders to open (instructed and to restore) - const allFoldersToOpen = distinct(foldersToOpen, folder => extUriBiasedIgnorePathCase.getComparisonKey(folder.folderUri)); // prevent duplicates + const allFoldersToOpen = distinct(foldersToOpen, folder => extUriBiasedIgnorePathCase.getComparisonKey(folder.workspace.uri)); // prevent duplicates if (allFoldersToOpen.length > 0) { // Check for existing instances - const windowsOnFolderPath = coalesce(allFoldersToOpen.map(folderToOpen => findWindowOnWorkspace(WindowsMainService.WINDOWS, folderToOpen.folderUri))); + const windowsOnFolderPath = coalesce(allFoldersToOpen.map(folderToOpen => findWindowOnWorkspaceOrFolder(this.getWindows(), folderToOpen.workspace.uri))); if (windowsOnFolderPath.length > 0) { const windowOnFolderPath = windowsOnFolderPath[0]; const filesToOpenInWindow = filesToOpen?.remoteAuthority === windowOnFolderPath.remoteAuthority ? filesToOpen : undefined; // Do open files - const window = this.doOpenFilesInExistingWindow(openConfig, windowOnFolderPath, filesToOpenInWindow); - usedWindows.push(window); - - // Reset `filesToOpen` because we handled them and also remember window we used - if (filesToOpenInWindow) { - filesToOpen = undefined; - filesOpenedInWindow = window; - } + addUsedWindow(this.doOpenFilesInExistingWindow(openConfig, windowOnFolderPath, filesToOpenInWindow), !!filesToOpenInWindow); openFolderInNewWindow = true; // any other folders to open must open in new window then } // Open remaining ones allFoldersToOpen.forEach(folderToOpen => { - - if (windowsOnFolderPath.some(win => extUriBiasedIgnorePathCase.isEqual(win.openedFolderUri, folderToOpen.folderUri))) { + if (windowsOnFolderPath.some(window => isSingleFolderWorkspaceIdentifier(window.openedWorkspace) && extUriBiasedIgnorePathCase.isEqual(window.openedWorkspace.uri, folderToOpen.workspace.uri))) { return; // ignore folders that are already open } @@ -705,14 +473,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const filesToOpenInWindow = (filesToOpen?.remoteAuthority === remoteAuthority) ? filesToOpen : undefined; // Do open folder - const window = this.doOpenFolderOrWorkspace(openConfig, folderToOpen, openFolderInNewWindow, filesToOpenInWindow); - usedWindows.push(window); - - // Reset `filesToOpen` because we handled them and also remember window we used - if (filesToOpenInWindow) { - filesToOpen = undefined; - filesOpenedInWindow = window; - } + addUsedWindow(this.doOpenFolderOrWorkspace(openConfig, folderToOpen, openFolderInNewWindow, filesToOpenInWindow), !!filesToOpenInWindow); openFolderInNewWindow = true; // any other folders to open must open in new window then }); @@ -725,7 +486,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const remoteAuthority = emptyWindowBackupInfo.remoteAuthority; const filesToOpenInWindow = (filesToOpen?.remoteAuthority === remoteAuthority) ? filesToOpen : undefined; - const window = this.openInBrowserWindow({ + addUsedWindow(this.openInBrowserWindow({ userEnv: openConfig.userEnv, cli: openConfig.cli, initialStartup: openConfig.initialStartup, @@ -734,14 +495,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic forceNewWindow: true, forceNewTabbedWindow: openConfig.forceNewTabbedWindow, emptyWindowBackupInfo - }); - usedWindows.push(window); - - // Reset `filesToOpen` because we handled them and also remember window we used - if (filesToOpenInWindow) { - filesToOpen = undefined; - filesOpenedInWindow = window; - } + }), !!filesToOpenInWindow); openFolderInNewWindow = true; // any other folders to open must open in new window then }); @@ -756,14 +510,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const remoteAuthority = filesToOpen ? filesToOpen.remoteAuthority : (openConfig.cli && openConfig.cli.remote || undefined); for (let i = 0; i < emptyToOpen; i++) { - const window = this.doOpenEmpty(openConfig, openFolderInNewWindow, remoteAuthority, filesToOpen); - usedWindows.push(window); - - // Reset `filesToOpen` because we handled them and also remember window we used - if (filesToOpen) { - filesToOpen = undefined; - filesOpenedInWindow = window; - } + addUsedWindow(this.doOpenEmpty(openConfig, openFolderInNewWindow, remoteAuthority, filesToOpen), !!filesToOpen); // any other window to open must open in new window then openFolderInNewWindow = true; @@ -778,23 +525,20 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic window.focus(); // make sure window has focus - const params: { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[], filesToWait?: IPathsToWaitFor, termProgram?: string } = {}; - if (filesToOpen) { - params.filesToOpenOrCreate = filesToOpen.filesToOpenOrCreate; - params.filesToDiff = filesToOpen.filesToDiff; - params.filesToWait = filesToOpen.filesToWait; - } - - if (configuration.userEnv) { - params.termProgram = configuration.userEnv['TERM_PROGRAM']; - } - + const params: INativeOpenFileRequest = { + filesToOpenOrCreate: filesToOpen?.filesToOpenOrCreate, + filesToDiff: filesToOpen?.filesToDiff, + filesToWait: filesToOpen?.filesToWait, + termProgram: configuration?.userEnv?.['TERM_PROGRAM'] + }; window.sendWhenReady('vscode:openFiles', CancellationToken.None, params); return window; } private doAddFoldersToExistingWindow(window: ICodeWindow, foldersToAdd: URI[]): ICodeWindow { + this.logService.trace('windowsManager#doAddFoldersToExistingWindow'); + window.focus(); // make sure window has focus const request: IAddFoldersRequest = { foldersToAdd }; @@ -820,50 +564,57 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic }); } - private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IPathToOpen, forceNewWindow: boolean, filesToOpen: IFilesToOpen | undefined, windowToUse?: ICodeWindow): ICodeWindow { + private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IWorkspacePathToOpen | ISingleFolderWorkspacePathToOpen, forceNewWindow: boolean, filesToOpen: IFilesToOpen | undefined, windowToUse?: ICodeWindow): ICodeWindow { if (!forceNewWindow && !windowToUse && typeof openConfig.contextWindowId === 'number') { windowToUse = this.getWindowById(openConfig.contextWindowId); // fix for https://github.com/microsoft/vscode/issues/49587 } return this.openInBrowserWindow({ + workspace: folderOrWorkspace.workspace, userEnv: openConfig.userEnv, cli: openConfig.cli, initialStartup: openConfig.initialStartup, - workspace: folderOrWorkspace.workspace, - folderUri: folderOrWorkspace.folderUri, - filesToOpen, remoteAuthority: folderOrWorkspace.remoteAuthority, forceNewWindow, forceNewTabbedWindow: openConfig.forceNewTabbedWindow, + filesToOpen, windowToUse }); } private getPathsToOpen(openConfig: IOpenConfiguration): IPathToOpen[] { - let windowsToOpen: IPathToOpen[]; + let pathsToOpen: IPathToOpen[]; let isCommandLineOrAPICall = false; let restoredWindows = false; // Extract paths: from API if (openConfig.urisToOpen && openConfig.urisToOpen.length > 0) { - windowsToOpen = this.doExtractPathsFromAPI(openConfig); + pathsToOpen = this.doExtractPathsFromAPI(openConfig); isCommandLineOrAPICall = true; } // Check for force empty else if (openConfig.forceEmpty) { - windowsToOpen = [Object.create(null)]; + pathsToOpen = [Object.create(null)]; } // Extract paths: from CLI else if (openConfig.cli._.length || openConfig.cli['folder-uri'] || openConfig.cli['file-uri']) { - windowsToOpen = this.doExtractPathsFromCLI(openConfig.cli); + pathsToOpen = this.doExtractPathsFromCLI(openConfig.cli); + if (pathsToOpen.length === 0) { + pathsToOpen.push(Object.create(null)); // add an empty window if we did not have windows to open from command line + } + isCommandLineOrAPICall = true; } - // Extract windows: from previous session + // Extract paths: from previous session else { - windowsToOpen = this.doGetWindowsFromLastSession(); + pathsToOpen = this.doGetPathsFromLastSession(); + if (pathsToOpen.length === 0) { + pathsToOpen.push(Object.create(null)); // add an empty window if we did not have windows to restore + } + restoredWindows = true; } @@ -872,15 +623,15 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // If we are in `addMode`, we should not do this because in that case all // folders should be added to the existing window. if (!openConfig.addMode && isCommandLineOrAPICall) { - const foldersToOpen = windowsToOpen.filter(path => !!path.folderUri); + const foldersToOpen = pathsToOpen.filter(path => isSingleFolderWorkspacePathToOpen(path)) as ISingleFolderWorkspacePathToOpen[]; if (foldersToOpen.length > 1) { const remoteAuthority = foldersToOpen[0].remoteAuthority; - if (foldersToOpen.every(f => f.remoteAuthority === remoteAuthority)) { // only if all folder have the same authority - const workspace = this.workspacesMainService.createUntitledWorkspaceSync(foldersToOpen.map(folder => ({ uri: folder.folderUri! }))); + if (foldersToOpen.every(folderToOpen => folderToOpen.remoteAuthority === remoteAuthority)) { // only if all folder have the same authority + const workspace = this.workspacesManagementMainService.createUntitledWorkspaceSync(foldersToOpen.map(folder => ({ uri: folder.workspace.uri }))); // Add workspace and remove folders thereby - windowsToOpen.push({ workspace, remoteAuthority }); - windowsToOpen = windowsToOpen.filter(path => !path.folderUri); + pathsToOpen.push({ workspace, remoteAuthority }); + pathsToOpen = pathsToOpen.filter(path => !isSingleFolderWorkspacePathToOpen(path)); } } } @@ -891,63 +642,57 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // Use `unshift` to ensure any new window to open comes last // for proper focus treatment. if (openConfig.initialStartup && !restoredWindows && this.configurationService.getValue('window')?.restoreWindows === 'preserve') { - windowsToOpen.unshift(...this.doGetWindowsFromLastSession().filter(window => window.workspace || window.folderUri || window.backupPath)); + pathsToOpen.unshift(...this.doGetPathsFromLastSession().filter(path => isWorkspacePathToOpen(path) || isSingleFolderWorkspacePathToOpen(path) || path.backupPath)); } - return windowsToOpen; + return pathsToOpen; } private doExtractPathsFromAPI(openConfig: IOpenConfiguration): IPathToOpen[] { const pathsToOpen: IPathToOpen[] = []; - const parseOptions: IPathParseOptions = { gotoLineMode: openConfig.gotoLineMode }; - for (const pathToOpen of openConfig.urisToOpen || []) { - if (!pathToOpen) { - continue; - } + const pathResolveOptions: IPathResolveOptions = { gotoLineMode: openConfig.gotoLineMode }; + for (const pathToOpen of coalesce(openConfig.urisToOpen || [])) { + const path = this.resolveOpenable(pathToOpen, pathResolveOptions); - const path = this.parseUri(pathToOpen, parseOptions); + // Path exists if (path) { path.label = pathToOpen.label; pathsToOpen.push(path); - } else { - const uri = this.resourceFromURIToOpen(pathToOpen); + } - // Warn about the invalid URI or path - let message, detail; - if (uri.scheme === Schemas.file) { - message = localize('pathNotExistTitle', "Path does not exist"); - detail = localize('pathNotExistDetail', "The path '{0}' does not seem to exist anymore on disk.", getPathLabel(uri.fsPath, this.environmentService)); - } else { - message = localize('uriInvalidTitle', "URI can not be opened"); - detail = localize('uriInvalidDetail', "The URI '{0}' is not valid and can not be opened.", uri.toString()); - } + // Path does not exist: show a warning box + else { + const uri = this.resourceFromOpenable(pathToOpen); const options: MessageBoxOptions = { title: product.nameLong, type: 'info', buttons: [localize('ok', "OK")], - message, - detail, + message: uri.scheme === Schemas.file ? localize('pathNotExistTitle', "Path does not exist") : localize('uriInvalidTitle', "URI can not be opened"), + detail: uri.scheme === Schemas.file ? + localize('pathNotExistDetail', "The path '{0}' does not seem to exist anymore on disk.", getPathLabel(uri.fsPath, this.environmentService)) : + localize('uriInvalidDetail', "The URI '{0}' is not valid and can not be opened.", uri.toString()), noLink: true }; this.dialogMainService.showMessageBox(options, withNullAsUndefined(BrowserWindow.getFocusedWindow())); } } + return pathsToOpen; } private doExtractPathsFromCLI(cli: NativeParsedArgs): IPath[] { const pathsToOpen: IPathToOpen[] = []; - const parseOptions: IPathParseOptions = { ignoreFileNotFound: true, gotoLineMode: cli.goto, remoteAuthority: cli.remote || undefined }; + const pathResolveOptions: IPathResolveOptions = { ignoreFileNotFound: true, gotoLineMode: cli.goto, remoteAuthority: cli.remote || undefined }; // folder uris const folderUris = cli['folder-uri']; if (folderUris) { - for (let f of folderUris) { - const folderUri = this.argToUri(f); + for (const rawFolderUri of folderUris) { + const folderUri = this.cliArgToUri(rawFolderUri); if (folderUri) { - const path = this.parseUri({ folderUri }, parseOptions); + const path = this.resolveOpenable({ folderUri }, pathResolveOptions); if (path) { pathsToOpen.push(path); } @@ -958,10 +703,10 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // file uris const fileUris = cli['file-uri']; if (fileUris) { - for (let f of fileUris) { - const fileUri = this.argToUri(f); + for (const rawFileUri of fileUris) { + const fileUri = this.cliArgToUri(rawFileUri); if (fileUri) { - const path = this.parseUri(hasWorkspaceFileExtension(f) ? { workspaceUri: fileUri } : { fileUri }, parseOptions); + const path = this.resolveOpenable(hasWorkspaceFileExtension(rawFileUri) ? { workspaceUri: fileUri } : { fileUri }, pathResolveOptions); if (path) { pathsToOpen.push(path); } @@ -970,30 +715,42 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } // folder or file paths - const cliArgs = cli._; - for (let cliArg of cliArgs) { - const path = this.parsePath(cliArg, parseOptions); + const cliPaths = cli._; + for (const cliPath of cliPaths) { + const path = this.doResolveFileOpenable(cliPath, pathResolveOptions); if (path) { pathsToOpen.push(path); } } - if (pathsToOpen.length) { - return pathsToOpen; - } - - // No path provided, return empty to open empty - return [Object.create(null)]; + return pathsToOpen; } - private doGetWindowsFromLastSession(): IPathToOpen[] { + private cliArgToUri(arg: string): URI | undefined { + try { + const uri = URI.parse(arg); + if (!uri.scheme) { + this.logService.error(`Invalid URI input string, scheme missing: ${arg}`); + + return undefined; + } + + return uri; + } catch (e) { + this.logService.error(`Invalid URI input string: ${arg}, ${e.message}`); + } + + return undefined; + } + + private doGetPathsFromLastSession(): IPathToOpen[] { const restoreWindowsSetting = this.getRestoreWindowsSetting(); switch (restoreWindowsSetting) { - // none: we always open an empty window + // none: no window to restore case 'none': - return [Object.create(null)]; + return []; // one: restore last opened workspace/folder or empty window // all: restore all windows @@ -1004,48 +761,41 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic case 'folders': // Collect previously opened windows - const openedWindows: IWindowState[] = []; + const lastSessionWindows: IWindowState[] = []; if (restoreWindowsSetting !== 'one') { - openedWindows.push(...this.windowsState.openedWindows); + lastSessionWindows.push(...this.windowsStateHandler.state.openedWindows); } - if (this.windowsState.lastActiveWindow) { - openedWindows.push(this.windowsState.lastActiveWindow); + if (this.windowsStateHandler.state.lastActiveWindow) { + lastSessionWindows.push(this.windowsStateHandler.state.lastActiveWindow); } - const windowsToOpen: IPathToOpen[] = []; - for (const openedWindow of openedWindows) { + const pathsToOpen: IPathToOpen[] = []; + for (const lastSessionWindow of lastSessionWindows) { // Workspaces - if (openedWindow.workspace) { - const pathToOpen = this.parseUri({ workspaceUri: openedWindow.workspace.configPath }, { remoteAuthority: openedWindow.remoteAuthority }); - if (pathToOpen?.workspace) { - windowsToOpen.push(pathToOpen); + if (lastSessionWindow.workspace) { + const pathToOpen = this.resolveOpenable({ workspaceUri: lastSessionWindow.workspace.configPath }, { remoteAuthority: lastSessionWindow.remoteAuthority }); + if (isWorkspacePathToOpen(pathToOpen)) { + pathsToOpen.push(pathToOpen); } } // Folders - else if (openedWindow.folderUri) { - const pathToOpen = this.parseUri({ folderUri: openedWindow.folderUri }, { remoteAuthority: openedWindow.remoteAuthority }); - if (pathToOpen?.folderUri) { - windowsToOpen.push(pathToOpen); + else if (lastSessionWindow.folderUri) { + const pathToOpen = this.resolveOpenable({ folderUri: lastSessionWindow.folderUri }, { remoteAuthority: lastSessionWindow.remoteAuthority }); + if (isSingleFolderWorkspacePathToOpen(pathToOpen)) { + pathsToOpen.push(pathToOpen); } } // Empty window, potentially editors open to be restored - else if (restoreWindowsSetting !== 'folders' && openedWindow.backupPath) { - windowsToOpen.push({ backupPath: openedWindow.backupPath, remoteAuthority: openedWindow.remoteAuthority }); + else if (restoreWindowsSetting !== 'folders' && lastSessionWindow.backupPath) { + pathsToOpen.push({ backupPath: lastSessionWindow.backupPath, remoteAuthority: lastSessionWindow.remoteAuthority }); } } - if (windowsToOpen.length > 0) { - return windowsToOpen; - } - - break; + return pathsToOpen; } - - // Always fallback to empty window - return [Object.create(null)]; } private getRestoreWindowsSetting(): RestoreWindowsSetting { @@ -1064,75 +814,48 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic return restoreWindows; } - private argToUri(arg: string): URI | undefined { - try { - const uri = URI.parse(arg); - if (!uri.scheme) { - this.logService.error(`Invalid URI input string, scheme missing: ${arg}`); - return undefined; - } + private resolveOpenable(openable: IWindowOpenable, options: IPathResolveOptions = {}): IPathToOpen | undefined { - return uri; - } catch (e) { - this.logService.error(`Invalid URI input string: ${arg}, ${e.message}`); + // handle file:// openables with some extra validation + let uri = this.resourceFromOpenable(openable); + if (uri.scheme === Schemas.file) { + return this.doResolveFileOpenable(openable, options); } - return undefined; + // handle non file:// openables + return this.doResolveRemoteOpenable(openable, options); } - private parseUri(toOpen: IWindowOpenable, options: IPathParseOptions = {}): IPathToOpen | undefined { - if (!toOpen) { - return undefined; - } - - let uri = this.resourceFromURIToOpen(toOpen); - if (uri.scheme === Schemas.file) { - return this.parsePath(uri.fsPath, options, isFileToOpen(toOpen)); - } + private doResolveRemoteOpenable(openable: IWindowOpenable, options: IPathResolveOptions): IPathToOpen | undefined { + let uri = this.resourceFromOpenable(openable); // open remote if either specified in the cli or if it's a remotehost URI const remoteAuthority = options.remoteAuthority || getRemoteAuthority(uri); // normalize URI - uri = normalizePath(uri); - - // remove trailing slash - uri = removeTrailingPathSeparator(uri); + uri = removeTrailingPathSeparator(normalizePath(uri)); // File - if (isFileToOpen(toOpen)) { + if (isFileToOpen(openable)) { if (options.gotoLineMode) { - const parsedPath = parseLineAndColumnAware(uri.path); - return { - fileUri: uri.with({ path: parsedPath.path }), - lineNumber: parsedPath.line, - columnNumber: parsedPath.column, - remoteAuthority - }; + const { path, line, column } = parseLineAndColumnAware(uri.path); + + return { fileUri: uri.with({ path }), lineNumber: line, columnNumber: column, remoteAuthority }; } - return { - fileUri: uri, - remoteAuthority - }; + return { fileUri: uri, remoteAuthority }; } // Workspace - else if (isWorkspaceToOpen(toOpen)) { - return { - workspace: getWorkspaceIdentifier(uri), - remoteAuthority - }; + else if (isWorkspaceToOpen(openable)) { + return { workspace: getWorkspaceIdentifier(uri), remoteAuthority }; } // Folder - return { - folderUri: uri, - remoteAuthority - }; + return { workspace: getSingleFolderWorkspaceIdentifier(uri), remoteAuthority }; } - private resourceFromURIToOpen(openable: IWindowOpenable): URI { + private resourceFromOpenable(openable: IWindowOpenable): URI { if (isWorkspaceToOpen(openable)) { return openable.workspaceUri; } @@ -1144,101 +867,113 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic return openable.fileUri; } - private parsePath(anyPath: string, options: IPathParseOptions, forceOpenWorkspaceAsFile?: boolean): IPathToOpen | undefined { - if (!anyPath) { - return undefined; + private doResolveFileOpenable(path: string, options: IPathResolveOptions): IPathToOpen | undefined; + private doResolveFileOpenable(openable: IWindowOpenable, options: IPathResolveOptions): IPathToOpen | undefined; + private doResolveFileOpenable(pathOrOpenable: string | IWindowOpenable, options: IPathResolveOptions): IPathToOpen | undefined { + let path: string; + let forceOpenWorkspaceAsFile = false; + if (typeof pathOrOpenable === 'string') { + path = pathOrOpenable; + } else { + path = this.resourceFromOpenable(pathOrOpenable).fsPath; + forceOpenWorkspaceAsFile = isFileToOpen(pathOrOpenable); } + // Extract line/col information from path let lineNumber: number | undefined; let columnNumber: number | undefined; if (options.gotoLineMode) { - const parsedPath = parseLineAndColumnAware(anyPath); + const parsedPath = parseLineAndColumnAware(path); lineNumber = parsedPath.line; columnNumber = parsedPath.column; - anyPath = parsedPath.path; + path = parsedPath.path; } - // open remote if either specified in the cli even if it is a local file. + // With remote: resolve path as remote URI const remoteAuthority = options.remoteAuthority; if (remoteAuthority) { - const first = anyPath.charCodeAt(0); - - // make absolute - if (first !== CharCode.Slash) { - if (isWindowsDriveLetter(first) && anyPath.charCodeAt(anyPath.charCodeAt(1)) === CharCode.Colon) { - anyPath = toSlashes(anyPath); - } - - anyPath = `/${anyPath}`; - } - - const uri = URI.from({ scheme: Schemas.vscodeRemote, authority: remoteAuthority, path: anyPath }); - - // guess the file type: If it ends with a slash it's a folder. If it has a file extension, it's a file or a workspace. By defaults it's a folder. - if (anyPath.charCodeAt(anyPath.length - 1) !== CharCode.Slash) { - if (hasWorkspaceFileExtension(anyPath)) { - if (forceOpenWorkspaceAsFile) { - return { fileUri: uri, remoteAuthority }; - } - } else if (posix.basename(anyPath).indexOf('.') !== -1) { // file name starts with a dot or has an file extension - return { fileUri: uri, remoteAuthority }; - } - } - - return { folderUri: uri, remoteAuthority }; + return this.doResolvePathRemote(path, remoteAuthority, forceOpenWorkspaceAsFile); } - let candidate = normalize(anyPath); + // Without remote: resolve path as local URI + return this.doResolvePathLocal(path, options, lineNumber, columnNumber, forceOpenWorkspaceAsFile); + } + + private doResolvePathRemote(path: string, remoteAuthority: string, forceOpenWorkspaceAsFile?: boolean): IPathToOpen | undefined { + const first = path.charCodeAt(0); + + // make absolute + if (first !== CharCode.Slash) { + if (isWindowsDriveLetter(first) && path.charCodeAt(path.charCodeAt(1)) === CharCode.Colon) { + path = toSlashes(path); + } + + path = `/${path}`; + } + + const uri = URI.from({ scheme: Schemas.vscodeRemote, authority: remoteAuthority, path: path }); + + // guess the file type: + // - if it ends with a slash it's a folder + // - if it has a file extension, it's a file or a workspace + // - by defaults it's a folder + if (path.charCodeAt(path.length - 1) !== CharCode.Slash) { + + // file name ends with .code-workspace + if (hasWorkspaceFileExtension(path)) { + if (forceOpenWorkspaceAsFile) { + return { fileUri: uri, remoteAuthority }; + } + return { workspace: getWorkspaceIdentifier(uri), remoteAuthority }; + } + + // file name starts with a dot or has an file extension + else if (posix.basename(path).indexOf('.') !== -1) { + return { fileUri: uri, remoteAuthority }; + } + } + + return { workspace: getSingleFolderWorkspaceIdentifier(uri), remoteAuthority }; + } + + private doResolvePathLocal(path: string, options: IPathResolveOptions, lineNumber: number | undefined, columnNumber: number | undefined, forceOpenWorkspaceAsFile?: boolean): IPathToOpen | undefined { + + // Ensure the path is normalized and absolute + path = sanitizeFilePath(normalize(path), process.env['VSCODE_CWD'] || process.cwd()); try { - const candidateStat = statSync(candidate); - if (candidateStat.isFile()) { + const pathStat = statSync(path); + if (pathStat.isFile()) { // Workspace (unless disabled via flag) if (!forceOpenWorkspaceAsFile) { - const workspace = this.workspacesMainService.resolveLocalWorkspaceSync(URI.file(candidate)); + const workspace = this.workspacesManagementMainService.resolveLocalWorkspaceSync(URI.file(path)); if (workspace) { - return { - workspace: { id: workspace.id, configPath: workspace.configPath }, - remoteAuthority: workspace.remoteAuthority, - exists: true - }; + return { workspace: { id: workspace.id, configPath: workspace.configPath }, remoteAuthority: workspace.remoteAuthority, exists: true }; } } // File - return { - fileUri: URI.file(candidate), - lineNumber, - columnNumber, - remoteAuthority, - exists: true - }; + return { fileUri: URI.file(path), lineNumber, columnNumber, exists: true }; } // Folder (we check for isDirectory() because e.g. paths like /dev/null // are neither file nor folder but some external tools might pass them // over to us) - else if (candidateStat.isDirectory()) { - return { - folderUri: URI.file(candidate), - remoteAuthority, - exists: true - }; + else if (pathStat.isDirectory()) { + return { workspace: getSingleFolderWorkspaceIdentifier(URI.file(path), pathStat), exists: true }; } } catch (error) { - const fileUri = URI.file(candidate); - this.workspacesHistoryMainService.removeRecentlyOpened([fileUri]); // since file does not seem to exist anymore, remove from recent + const fileUri = URI.file(path); + + // since file does not seem to exist anymore, remove from recent + this.workspacesHistoryMainService.removeRecentlyOpened([fileUri]); // assume this is a file that does not yet exist if (options?.ignoreFileNotFound) { - return { - fileUri, - remoteAuthority, - exists: false - }; + return { fileUri, exists: false }; } } @@ -1287,12 +1022,12 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic return { openFolderInNewWindow: !!openFolderInNewWindow, openFilesInNewWindow }; } - openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string[], openConfig: IOpenConfiguration): ICodeWindow[] { + openExtensionDevelopmentHostWindow(extensionDevelopmentPaths: string[], openConfig: IOpenConfiguration): ICodeWindow[] { // Reload an existing extension development host window on the same path // We currently do not allow more than one extension development window // on the same extension path. - const existingWindow = findWindowOnExtensionDevelopmentPath(WindowsMainService.WINDOWS, extensionDevelopmentPath); + const existingWindow = findWindowOnExtensionDevelopmentPath(this.getWindows(), extensionDevelopmentPaths); if (existingWindow) { this.lifecycleMainService.reload(existingWindow, openConfig.cli); existingWindow.focus(); // make sure it gets focus and is restored @@ -1306,10 +1041,10 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // Fill in previously opened workspace unless an explicit path is provided and we are not unit testing if (!cliArgs.length && !folderUris.length && !fileUris.length && !openConfig.cli.extensionTestsPath) { - const extensionDevelopmentWindowState = this.windowsState.lastPluginDevelopmentHostWindow; + const extensionDevelopmentWindowState = this.windowsStateHandler.state.lastPluginDevelopmentHostWindow; const workspaceToOpen = extensionDevelopmentWindowState && (extensionDevelopmentWindowState.workspace || extensionDevelopmentWindowState.folderUri); if (workspaceToOpen) { - if (isSingleFolderWorkspaceIdentifier(workspaceToOpen)) { + if (URI.isUri(workspaceToOpen)) { if (workspaceToOpen.scheme === Schemas.file) { cliArgs = [workspaceToOpen.fsPath]; } else { @@ -1326,9 +1061,9 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } let authority = ''; - for (let p of extensionDevelopmentPath) { - if (p.match(/^[a-zA-Z][a-zA-Z0-9\+\-\.]+:/)) { - const url = URI.parse(p); + for (const extensionDevelopmentPath of extensionDevelopmentPaths) { + if (extensionDevelopmentPath.match(/^[a-zA-Z][a-zA-Z0-9\+\-\.]+:/)) { + const url = URI.parse(extensionDevelopmentPath); if (url.scheme === Schemas.vscodeRemote) { if (authority) { if (url.authority !== authority) { @@ -1347,7 +1082,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic cliArgs = cliArgs.filter(path => { const uri = URI.file(path); - if (!!findWindowOnWorkspaceOrFolderUri(WindowsMainService.WINDOWS, uri)) { + if (!!findWindowOnWorkspaceOrFolder(this.getWindows(), uri)) { return false; } @@ -1355,8 +1090,8 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic }); folderUris = folderUris.filter(folderUriStr => { - const folderUri = this.argToUri(folderUriStr); - if (!!findWindowOnWorkspaceOrFolderUri(WindowsMainService.WINDOWS, folderUri)) { + const folderUri = this.cliArgToUri(folderUriStr); + if (folderUri && !!findWindowOnWorkspaceOrFolder(this.getWindows(), folderUri)) { return false; } @@ -1364,8 +1099,8 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic }); fileUris = fileUris.filter(fileUriStr => { - const fileUri = this.argToUri(fileUriStr); - if (!!findWindowOnWorkspaceOrFolderUri(WindowsMainService.WINDOWS, fileUri)) { + const fileUri = this.cliArgToUri(fileUriStr); + if (fileUri && !!findWindowOnWorkspaceOrFolder(this.getWindows(), fileUri)) { return false; } @@ -1398,7 +1133,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow { - // Build INativeWindowConfiguration from config and options + // Build `INativeWindowConfiguration` from config and options const configuration = { ...options.cli } as INativeWindowConfiguration; configuration.appRoot = this.environmentService.appRoot; configuration.machineId = this.machineId; @@ -1408,7 +1143,6 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic configuration.userEnv = { ...this.initialUserEnv, ...options.userEnv }; configuration.isInitialStartup = options.initialStartup; configuration.workspace = options.workspace; - configuration.folderUri = options.folderUri; configuration.remoteAuthority = options.remoteAuthority; const filesToOpen = options.filesToOpen; @@ -1436,32 +1170,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // New window if (!window) { - const windowConfig = this.configurationService.getValue('window'); - const state = this.getNewWindowState(configuration); - - // Window state is not from a previous session: only allow fullscreen if we inherit it or user wants fullscreen - let allowFullscreen: boolean; - if (state.hasDefaultState) { - allowFullscreen = !!(windowConfig?.newWindowDimensions && ['fullscreen', 'inherit', 'offset'].indexOf(windowConfig.newWindowDimensions) >= 0); - } - - // Window state is from a previous session: only allow fullscreen when we got updated or user wants to restore - else { - allowFullscreen = !!(this.lifecycleMainService.wasRestarted || windowConfig?.restoreFullscreen); - - if (allowFullscreen && isMacintosh && WindowsMainService.WINDOWS.some(win => win.isFullScreen)) { - // macOS: Electron does not allow to restore multiple windows in - // fullscreen. As such, if we already restored a window in that - // state, we cannot allow more fullscreen windows. See - // https://github.com/microsoft/vscode/issues/41691 and - // https://github.com/electron/electron/issues/13077 - allowFullscreen = false; - } - } - - if (state.mode === WindowMode.Fullscreen && !allowFullscreen) { - state.mode = WindowMode.Normal; - } + const state = this.windowsStateHandler.getNewWindowState(configuration); // Create the window const createdWindow = window = this.instantiationService.createInstance(CodeWindow, { @@ -1485,12 +1194,12 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic this._onWindowOpened.fire(createdWindow); // Indicate number change via event - this._onWindowsCountChanged.fire({ oldCount: WindowsMainService.WINDOWS.length - 1, newCount: WindowsMainService.WINDOWS.length }); + this._onWindowsCountChanged.fire({ oldCount: this.getWindowCount() - 1, newCount: this.getWindowCount() }); // Window Events once(createdWindow.onReady)(() => this._onWindowReady.fire(createdWindow)); once(createdWindow.onClose)(() => this.onWindowClosed(createdWindow)); - once(createdWindow.onDestroy)(() => this.onBeforeWindowClose(createdWindow)); // try to save state before destroy because close will not fire + once(createdWindow.onDestroy)(() => this._onWindowDestroyed.fire(createdWindow)); createdWindow.win.webContents.removeAllListeners('devtools-reload-page'); // remove built in listener so we can handle this on our own createdWindow.win.webContents.on('devtools-reload-page', () => this.lifecycleMainService.reload(createdWindow)); @@ -1534,10 +1243,10 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // Register window for backups if (!configuration.extensionDevelopmentPath) { - if (configuration.workspace) { + if (isWorkspaceIdentifier(configuration.workspace)) { configuration.backupPath = this.backupMainService.registerWorkspaceBackupSync({ workspace: configuration.workspace, remoteAuthority: configuration.remoteAuthority }); - } else if (configuration.folderUri) { - configuration.backupPath = this.backupMainService.registerFolderBackupSync(configuration.folderUri); + } else if (isSingleFolderWorkspaceIdentifier(configuration.workspace)) { + configuration.backupPath = this.backupMainService.registerFolderBackupSync(configuration.workspace.uri); } else { const backupFolder = options.emptyWindowBackupInfo && options.emptyWindowBackupInfo.backupFolder; configuration.backupPath = this.backupMainService.registerEmptyWindowBackupSync(backupFolder, configuration.remoteAuthority); @@ -1548,162 +1257,37 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic window.load(configuration); } - private getNewWindowState(configuration: INativeWindowConfiguration): INewWindowState { - const lastActive = this.getLastActiveWindow(); - - // Restore state unless we are running extension tests - if (!configuration.extensionTestsPath) { - - // extension development host Window - load from stored settings if any - if (!!configuration.extensionDevelopmentPath && this.windowsState.lastPluginDevelopmentHostWindow) { - return this.windowsState.lastPluginDevelopmentHostWindow.uiState; - } - - // Known Workspace - load from stored settings - const workspace = configuration.workspace; - if (workspace) { - const stateForWorkspace = this.windowsState.openedWindows.filter(o => o.workspace && o.workspace.id === workspace.id).map(o => o.uiState); - if (stateForWorkspace.length) { - return stateForWorkspace[0]; - } - } - - // Known Folder - load from stored settings - if (configuration.folderUri) { - const stateForFolder = this.windowsState.openedWindows.filter(o => o.folderUri && extUriBiasedIgnorePathCase.isEqual(o.folderUri, configuration.folderUri)).map(o => o.uiState); - if (stateForFolder.length) { - return stateForFolder[0]; - } - } - - // Empty windows with backups - else if (configuration.backupPath) { - const stateForEmptyWindow = this.windowsState.openedWindows.filter(o => o.backupPath === configuration.backupPath).map(o => o.uiState); - if (stateForEmptyWindow.length) { - return stateForEmptyWindow[0]; - } - } - - // First Window - const lastActiveState = this.lastClosedWindowState || this.windowsState.lastActiveWindow; - if (!lastActive && lastActiveState) { - return lastActiveState.uiState; - } - } - - // - // In any other case, we do not have any stored settings for the window state, so we come up with something smart - // - - // 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(); - - // 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 && lastActive) { - displayToUse = screen.getDisplayMatching(lastActive.getBounds()); - } - - // fallback to primary display or first display - if (!displayToUse) { - displayToUse = screen.getPrimaryDisplay() || displays[0]; - } - } - - // Compute x/y based on display bounds - // Note: important to use Math.round() because Electron does not seem to be too happy about - // display coordinates that are not absolute numbers. - let state = defaultWindowState(); - state.x = Math.round(displayToUse.bounds.x + (displayToUse.bounds.width / 2) - (state.width! / 2)); - state.y = Math.round(displayToUse.bounds.y + (displayToUse.bounds.height / 2) - (state.height! / 2)); - - // Check for newWindowDimensions setting and adjust accordingly - const windowConfig = this.configurationService.getValue('window'); - let ensureNoOverlap = true; - if (windowConfig?.newWindowDimensions) { - if (windowConfig.newWindowDimensions === 'maximized') { - state.mode = WindowMode.Maximized; - ensureNoOverlap = false; - } else if (windowConfig.newWindowDimensions === 'fullscreen') { - state.mode = WindowMode.Fullscreen; - ensureNoOverlap = false; - } else if ((windowConfig.newWindowDimensions === 'inherit' || windowConfig.newWindowDimensions === 'offset') && lastActive) { - const lastActiveState = lastActive.serializeWindowState(); - if (lastActiveState.mode === WindowMode.Fullscreen) { - state.mode = WindowMode.Fullscreen; // only take mode (fixes https://github.com/microsoft/vscode/issues/19331) - } else { - state = lastActiveState; - } - - ensureNoOverlap = state.mode !== WindowMode.Fullscreen && windowConfig.newWindowDimensions === 'offset'; - } - } - - if (ensureNoOverlap) { - state = this.ensureNoOverlap(state); - } - - (state as INewWindowState).hasDefaultState = true; // flag as default state - - return state; - } - - private ensureNoOverlap(state: ISingleWindowState): ISingleWindowState { - if (WindowsMainService.WINDOWS.length === 0) { - return state; - } - - state.x = typeof state.x === 'number' ? state.x : 0; - state.y = typeof state.y === 'number' ? state.y : 0; - - const existingWindowBounds = WindowsMainService.WINDOWS.map(win => win.getBounds()); - while (existingWindowBounds.some(b => b.x === state.x || b.y === state.y)) { - state.x += 30; - state.y += 30; - } - - return state; - } - - private onWindowClosed(win: ICodeWindow): void { + private onWindowClosed(window: ICodeWindow): void { // Remove from our list so that Electron can clean it up - const index = WindowsMainService.WINDOWS.indexOf(win); + const index = WindowsMainService.WINDOWS.indexOf(window); WindowsMainService.WINDOWS.splice(index, 1); // Emit - this._onWindowsCountChanged.fire({ oldCount: WindowsMainService.WINDOWS.length + 1, newCount: WindowsMainService.WINDOWS.length }); + this._onWindowsCountChanged.fire({ oldCount: this.getWindowCount() + 1, newCount: this.getWindowCount() }); } getFocusedWindow(): ICodeWindow | undefined { - const win = BrowserWindow.getFocusedWindow(); - if (win) { - return this.getWindowById(win.id); + const window = BrowserWindow.getFocusedWindow(); + if (window) { + return this.getWindowById(window.id); } return undefined; } getLastActiveWindow(): ICodeWindow | undefined { - return getLastActiveWindow(WindowsMainService.WINDOWS); + return this.doGetLastActiveWindow(this.getWindows()); } private getLastActiveWindowForAuthority(remoteAuthority: string | undefined): ICodeWindow | undefined { - return getLastActiveWindow(WindowsMainService.WINDOWS.filter(window => window.remoteAuthority === remoteAuthority)); + return this.doGetLastActiveWindow(this.getWindows().filter(window => window.remoteAuthority === remoteAuthority)); + } + + private doGetLastActiveWindow(windows: ICodeWindow[]): ICodeWindow | undefined { + const lastFocusedDate = Math.max.apply(Math, windows.map(window => window.lastFocusTime)); + + return windows.find(window => window.lastFocusTime === lastFocusedDate); } sendToFocused(channel: string, ...args: any[]): void { @@ -1715,7 +1299,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } sendToAll(channel: string, payload?: any, windowIdsToIgnore?: number[]): void { - for (const window of WindowsMainService.WINDOWS) { + for (const window of this.getWindows()) { if (windowIdsToIgnore && windowIdsToIgnore.indexOf(window.id) >= 0) { continue; // do not send if we are instructed to ignore it } @@ -1724,10 +1308,18 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } } - getWindowById(windowId: number): ICodeWindow | undefined { - const res = WindowsMainService.WINDOWS.filter(window => window.id === windowId); + getWindows(): ICodeWindow[] { + return WindowsMainService.WINDOWS; + } - return firstOrDefault(res); + getWindowCount(): number { + return WindowsMainService.WINDOWS.length; + } + + getWindowById(windowId: number): ICodeWindow | undefined { + const windows = this.getWindows().filter(window => window.id === windowId); + + return firstOrDefault(windows); } getWindowByWebContents(webContents: WebContents): ICodeWindow | undefined { @@ -1738,12 +1330,4 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic return this.getWindowById(browserWindow.id); } - - getWindows(): ICodeWindow[] { - return WindowsMainService.WINDOWS; - } - - getWindowCount(): number { - return WindowsMainService.WINDOWS.length; - } } diff --git a/src/vs/platform/windows/electron-main/windowsStateHandler.ts b/src/vs/platform/windows/electron-main/windowsStateHandler.ts new file mode 100644 index 000000000..9b9558fa9 --- /dev/null +++ b/src/vs/platform/windows/electron-main/windowsStateHandler.ts @@ -0,0 +1,450 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { app, Display, screen } from 'electron'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { isMacintosh } from 'vs/base/common/platform'; +import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { defaultWindowState } from 'vs/code/electron-main/window'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +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 { INativeWindowConfiguration, IWindowSettings } from 'vs/platform/windows/common/windows'; +import { ICodeWindow, IWindowsMainService, IWindowState as IWindowUIState, WindowMode } from 'vs/platform/windows/electron-main/windows'; +import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; + +export interface IWindowState { + workspace?: IWorkspaceIdentifier; + folderUri?: URI; + backupPath?: string; + remoteAuthority?: string; + uiState: IWindowUIState; +} + +export interface IWindowsState { + lastActiveWindow?: IWindowState; + lastPluginDevelopmentHostWindow?: IWindowState; + openedWindows: IWindowState[]; +} + +interface INewWindowState extends IWindowUIState { + hasDefaultState?: boolean; +} + +interface ISerializedWindowsState { + readonly lastActiveWindow?: ISerializedWindowState; + readonly lastPluginDevelopmentHostWindow?: ISerializedWindowState; + readonly openedWindows: ISerializedWindowState[]; +} + +interface ISerializedWindowState { + readonly workspaceIdentifier?: { id: string; configURIPath: string }; + readonly folder?: string; + readonly backupPath?: string; + readonly remoteAuthority?: string; + readonly uiState: IWindowUIState; +} + +export class WindowsStateHandler extends Disposable { + + private static readonly windowsStateStorageKey = 'windowsState'; + + get state() { return this._state; } + private readonly _state = restoreWindowsState(this.stateService.getItem(WindowsStateHandler.windowsStateStorageKey)); + + private lastClosedState: IWindowState | undefined = undefined; + + private shuttingDown = false; + + constructor( + @IWindowsMainService private readonly windowsMainService: IWindowsMainService, + @IStateService private readonly stateService: IStateService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, + @ILogService private readonly logService: ILogService, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { + super(); + + this.registerListeners(); + } + + private registerListeners(): void { + + // 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', () => { + if (!this.shuttingDown) { + this.saveWindowsState(); + } + }); + + // Handle various lifecycle events around windows + this.lifecycleMainService.onBeforeWindowClose(window => this.onBeforeWindowClose(window)); + this.lifecycleMainService.onBeforeShutdown(() => this.onBeforeShutdown()); + this.windowsMainService.onWindowsCountChanged(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.onWindowDestroyed(window => this.onBeforeWindowClose(window)); + } + + // Note that onBeforeShutdown() and onBeforeWindowClose() are fired in different order depending on the OS: + // - macOS: since the app will not quit when closing the last window, you will always first get + // the onBeforeShutdown() event followed by N onBeforeWindowClose() events for each window + // - other: on other OS, closing the last window will quit the app so the order depends on the + // user interaction: closing the last window will first trigger onBeforeWindowClose() + // and then onBeforeShutdown(). Using the quit action however will first issue onBeforeShutdown() + // and then onBeforeWindowClose(). + // + // Here is the behavior on different OS depending on action taken (Electron 1.7.x): + // + // Legend + // - quit(N): quit application with N windows opened + // - close(1): close one window via the window close button + // - closeAll: close all windows via the taskbar command + // - onBeforeShutdown(N): number of windows reported in this event handler + // - onBeforeWindowClose(N, M): number of windows reported and quitRequested boolean in this event handler + // + // macOS + // - quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true) + // - quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true) + // - quit(0): onBeforeShutdown(0) + // - close(1): onBeforeWindowClose(1, false) + // + // Windows + // - quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true) + // - quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true) + // - close(1): onBeforeWindowClose(2, false)[not last window] + // - close(1): onBeforeWindowClose(1, false), onBeforeShutdown(0)[last window] + // - closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0) + // + // Linux + // - quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true) + // - quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true) + // - close(1): onBeforeWindowClose(2, false)[not last window] + // - close(1): onBeforeWindowClose(1, false), onBeforeShutdown(0)[last window] + // - closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0) + // + private onBeforeShutdown(): void { + this.shuttingDown = true; + + this.saveWindowsState(); + } + + private saveWindowsState(): void { + const currentWindowsState: IWindowsState = { + openedWindows: [], + lastPluginDevelopmentHostWindow: this._state.lastPluginDevelopmentHostWindow, + lastActiveWindow: this.lastClosedState + }; + + // 1.) Find a last active window (pick any other first window otherwise) + if (!currentWindowsState.lastActiveWindow) { + let activeWindow = this.windowsMainService.getLastActiveWindow(); + if (!activeWindow || activeWindow.isExtensionDevelopmentHost) { + activeWindow = this.windowsMainService.getWindows().find(window => !window.isExtensionDevelopmentHost); + } + + if (activeWindow) { + currentWindowsState.lastActiveWindow = this.toWindowState(activeWindow); + } + } + + // 2.) Find extension host window + const extensionHostWindow = this.windowsMainService.getWindows().find(window => window.isExtensionDevelopmentHost && !window.isExtensionTestHost); + if (extensionHostWindow) { + currentWindowsState.lastPluginDevelopmentHostWindow = this.toWindowState(extensionHostWindow); + } + + // 3.) All windows (except extension host) for N >= 2 to support `restoreWindows: all` or for auto update + // + // Careful here: asking a window for its window state after it has been closed returns bogus values (width: 0, height: 0) + // so if we ever want to persist the UI state of the last closed window (window count === 1), it has + // to come from the stored lastClosedWindowState on Win/Linux at least + if (this.windowsMainService.getWindowCount() > 1) { + currentWindowsState.openedWindows = this.windowsMainService.getWindows().filter(window => !window.isExtensionDevelopmentHost).map(window => this.toWindowState(window)); + } + + // Persist + const state = getWindowsStateStoreData(currentWindowsState); + this.stateService.setItem(WindowsStateHandler.windowsStateStorageKey, state); + + if (this.shuttingDown) { + this.logService.trace('[WindowsStateHandler] onBeforeShutdown', state); + } + } + + // See note on #onBeforeShutdown() for details how these events are flowing + private onBeforeWindowClose(window: ICodeWindow): void { + if (this.lifecycleMainService.quitRequested) { + return; // during quit, many windows close in parallel so let it be handled in the before-quit handler + } + + // On Window close, update our stored UI state of this window + const state: IWindowState = this.toWindowState(window); + if (window.isExtensionDevelopmentHost && !window.isExtensionTestHost) { + this._state.lastPluginDevelopmentHostWindow = state; // do not let test run window state overwrite our extension development state + } + + // Any non extension host window with same workspace or folder + else if (!window.isExtensionDevelopmentHost && window.openedWorkspace) { + this._state.openedWindows.forEach(openedWindow => { + const sameWorkspace = isWorkspaceIdentifier(window.openedWorkspace) && openedWindow.workspace?.id === window.openedWorkspace.id; + const sameFolder = isSingleFolderWorkspaceIdentifier(window.openedWorkspace) && openedWindow.folderUri && extUriBiasedIgnorePathCase.isEqual(openedWindow.folderUri, window.openedWorkspace.uri); + + if (sameWorkspace || sameFolder) { + openedWindow.uiState = state.uiState; + } + }); + } + + // On Windows and Linux closing the last window will trigger quit. Since we are storing all UI state + // before quitting, we need to remember the UI state of this window to be able to persist it. + // On macOS we keep the last closed window state ready in case the user wants to quit right after or + // wants to open another window, in which case we use this state over the persisted one. + if (this.windowsMainService.getWindowCount() === 1) { + this.lastClosedState = state; + } + } + + private toWindowState(window: ICodeWindow): IWindowState { + return { + workspace: isWorkspaceIdentifier(window.openedWorkspace) ? window.openedWorkspace : undefined, + folderUri: isSingleFolderWorkspaceIdentifier(window.openedWorkspace) ? window.openedWorkspace.uri : undefined, + backupPath: window.backupPath, + remoteAuthority: window.remoteAuthority, + uiState: window.serializeWindowState() + }; + } + + getNewWindowState(configuration: INativeWindowConfiguration): INewWindowState { + const state = this.doGetNewWindowState(configuration); + const windowConfig = this.configurationService.getValue('window'); + + // Window state is not from a previous session: only allow fullscreen if we inherit it or user wants fullscreen + let allowFullscreen: boolean; + if (state.hasDefaultState) { + allowFullscreen = !!(windowConfig?.newWindowDimensions && ['fullscreen', 'inherit', 'offset'].indexOf(windowConfig.newWindowDimensions) >= 0); + } + + // Window state is from a previous session: only allow fullscreen when we got updated or user wants to restore + else { + allowFullscreen = !!(this.lifecycleMainService.wasRestarted || windowConfig?.restoreFullscreen); + + if (allowFullscreen && isMacintosh && this.windowsMainService.getWindows().some(window => window.isFullScreen)) { + // macOS: Electron does not allow to restore multiple windows in + // fullscreen. As such, if we already restored a window in that + // state, we cannot allow more fullscreen windows. See + // https://github.com/microsoft/vscode/issues/41691 and + // https://github.com/electron/electron/issues/13077 + allowFullscreen = false; + } + } + + if (state.mode === WindowMode.Fullscreen && !allowFullscreen) { + state.mode = WindowMode.Normal; + } + + return state; + } + + private doGetNewWindowState(configuration: INativeWindowConfiguration): INewWindowState { + const lastActive = this.windowsMainService.getLastActiveWindow(); + + // Restore state unless we are running extension tests + if (!configuration.extensionTestsPath) { + + // extension development host Window - load from stored settings if any + if (!!configuration.extensionDevelopmentPath && this.state.lastPluginDevelopmentHostWindow) { + return this.state.lastPluginDevelopmentHostWindow.uiState; + } + + // Known Workspace - load from stored settings + const workspace = configuration.workspace; + if (isWorkspaceIdentifier(workspace)) { + const stateForWorkspace = this.state.openedWindows.filter(openedWindow => openedWindow.workspace && openedWindow.workspace.id === workspace.id).map(openedWindow => openedWindow.uiState); + if (stateForWorkspace.length) { + return stateForWorkspace[0]; + } + } + + // Known Folder - load from stored settings + if (isSingleFolderWorkspaceIdentifier(workspace)) { + const stateForFolder = this.state.openedWindows.filter(openedWindow => openedWindow.folderUri && extUriBiasedIgnorePathCase.isEqual(openedWindow.folderUri, workspace.uri)).map(openedWindow => openedWindow.uiState); + if (stateForFolder.length) { + return stateForFolder[0]; + } + } + + // Empty windows with backups + else if (configuration.backupPath) { + const stateForEmptyWindow = this.state.openedWindows.filter(openedWindow => openedWindow.backupPath === configuration.backupPath).map(openedWindow => openedWindow.uiState); + if (stateForEmptyWindow.length) { + return stateForEmptyWindow[0]; + } + } + + // First Window + const lastActiveState = this.lastClosedState || this.state.lastActiveWindow; + if (!lastActive && lastActiveState) { + return lastActiveState.uiState; + } + } + + // + // In any other case, we do not have any stored settings for the window state, so we come up with something smart + // + + // 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(); + + // 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 && lastActive) { + displayToUse = screen.getDisplayMatching(lastActive.getBounds()); + } + + // fallback to primary display or first display + if (!displayToUse) { + displayToUse = screen.getPrimaryDisplay() || displays[0]; + } + } + + // Compute x/y based on display bounds + // Note: important to use Math.round() because Electron does not seem to be too happy about + // display coordinates that are not absolute numbers. + let state = defaultWindowState(); + state.x = Math.round(displayToUse.bounds.x + (displayToUse.bounds.width / 2) - (state.width! / 2)); + state.y = Math.round(displayToUse.bounds.y + (displayToUse.bounds.height / 2) - (state.height! / 2)); + + // Check for newWindowDimensions setting and adjust accordingly + const windowConfig = this.configurationService.getValue('window'); + let ensureNoOverlap = true; + if (windowConfig?.newWindowDimensions) { + if (windowConfig.newWindowDimensions === 'maximized') { + state.mode = WindowMode.Maximized; + ensureNoOverlap = false; + } else if (windowConfig.newWindowDimensions === 'fullscreen') { + state.mode = WindowMode.Fullscreen; + ensureNoOverlap = false; + } else if ((windowConfig.newWindowDimensions === 'inherit' || windowConfig.newWindowDimensions === 'offset') && lastActive) { + const lastActiveState = lastActive.serializeWindowState(); + if (lastActiveState.mode === WindowMode.Fullscreen) { + state.mode = WindowMode.Fullscreen; // only take mode (fixes https://github.com/microsoft/vscode/issues/19331) + } else { + state = lastActiveState; + } + + ensureNoOverlap = state.mode !== WindowMode.Fullscreen && windowConfig.newWindowDimensions === 'offset'; + } + } + + if (ensureNoOverlap) { + state = this.ensureNoOverlap(state); + } + + (state as INewWindowState).hasDefaultState = true; // flag as default state + + return state; + } + + private ensureNoOverlap(state: IWindowUIState): IWindowUIState { + if (this.windowsMainService.getWindows().length === 0) { + return state; + } + + state.x = typeof state.x === 'number' ? state.x : 0; + state.y = typeof state.y === 'number' ? state.y : 0; + + const existingWindowBounds = this.windowsMainService.getWindows().map(window => window.getBounds()); + while (existingWindowBounds.some(bounds => bounds.x === state.x || bounds.y === state.y)) { + state.x += 30; + state.y += 30; + } + + return state; + } +} + +export function restoreWindowsState(data: ISerializedWindowsState | undefined): IWindowsState { + const result: IWindowsState = { openedWindows: [] }; + const windowsState = data || { openedWindows: [] }; + + if (windowsState.lastActiveWindow) { + result.lastActiveWindow = restoreWindowState(windowsState.lastActiveWindow); + } + + if (windowsState.lastPluginDevelopmentHostWindow) { + result.lastPluginDevelopmentHostWindow = restoreWindowState(windowsState.lastPluginDevelopmentHostWindow); + } + + if (Array.isArray(windowsState.openedWindows)) { + result.openedWindows = windowsState.openedWindows.map(windowState => restoreWindowState(windowState)); + } + + return result; +} + +function restoreWindowState(windowState: ISerializedWindowState): IWindowState { + const result: IWindowState = { uiState: windowState.uiState }; + if (windowState.backupPath) { + result.backupPath = windowState.backupPath; + } + + if (windowState.remoteAuthority) { + result.remoteAuthority = windowState.remoteAuthority; + } + + if (windowState.folder) { + result.folderUri = URI.parse(windowState.folder); + } + + if (windowState.workspaceIdentifier) { + result.workspace = { id: windowState.workspaceIdentifier.id, configPath: URI.parse(windowState.workspaceIdentifier.configURIPath) }; + } + + return result; +} + +export function getWindowsStateStoreData(windowsState: IWindowsState): IWindowsState { + return { + lastActiveWindow: windowsState.lastActiveWindow && serializeWindowState(windowsState.lastActiveWindow), + lastPluginDevelopmentHostWindow: windowsState.lastPluginDevelopmentHostWindow && serializeWindowState(windowsState.lastPluginDevelopmentHostWindow), + openedWindows: windowsState.openedWindows.map(ws => serializeWindowState(ws)) + }; +} + +function serializeWindowState(windowState: IWindowState): ISerializedWindowState { + return { + workspaceIdentifier: windowState.workspace && { id: windowState.workspace.id, configURIPath: windowState.workspace.configPath.toString() }, + folder: windowState.folderUri && windowState.folderUri.toString(), + backupPath: windowState.backupPath, + remoteAuthority: windowState.remoteAuthority, + uiState: windowState.uiState + }; +} diff --git a/src/vs/platform/windows/electron-main/windowsStateStorage.ts b/src/vs/platform/windows/electron-main/windowsStateStorage.ts deleted file mode 100644 index 165333950..000000000 --- a/src/vs/platform/windows/electron-main/windowsStateStorage.ts +++ /dev/null @@ -1,93 +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, UriComponents } from 'vs/base/common/uri'; -import { IWindowState as IWindowUIState } from 'vs/platform/windows/electron-main/windows'; -import { IWindowState, IWindowsState } from 'vs/platform/windows/electron-main/windowsMainService'; - -export type WindowsStateStorageData = object; - -interface ISerializedWindowsState { - lastActiveWindow?: ISerializedWindowState; - lastPluginDevelopmentHostWindow?: ISerializedWindowState; - openedWindows: ISerializedWindowState[]; -} - -interface ISerializedWindowState { - workspaceIdentifier?: { id: string; configURIPath: string }; - folder?: string; - backupPath?: string; - remoteAuthority?: string; - uiState: IWindowUIState; - - // deprecated - folderUri?: UriComponents; - folderPath?: string; - workspace?: { id: string; configPath: string }; -} - -export function restoreWindowsState(data: WindowsStateStorageData | undefined): IWindowsState { - const result: IWindowsState = { openedWindows: [] }; - const windowsState = data as ISerializedWindowsState || { openedWindows: [] }; - - if (windowsState.lastActiveWindow) { - result.lastActiveWindow = restoreWindowState(windowsState.lastActiveWindow); - } - - if (windowsState.lastPluginDevelopmentHostWindow) { - result.lastPluginDevelopmentHostWindow = restoreWindowState(windowsState.lastPluginDevelopmentHostWindow); - } - - if (Array.isArray(windowsState.openedWindows)) { - result.openedWindows = windowsState.openedWindows.map(windowState => restoreWindowState(windowState)); - } - - return result; -} - -function restoreWindowState(windowState: ISerializedWindowState): IWindowState { - const result: IWindowState = { uiState: windowState.uiState }; - if (windowState.backupPath) { - result.backupPath = windowState.backupPath; - } - - if (windowState.remoteAuthority) { - result.remoteAuthority = windowState.remoteAuthority; - } - - if (windowState.folder) { - result.folderUri = URI.parse(windowState.folder); - } else if (windowState.folderUri) { - result.folderUri = URI.revive(windowState.folderUri); - } else if (windowState.folderPath) { - result.folderUri = URI.file(windowState.folderPath); - } - - if (windowState.workspaceIdentifier) { - result.workspace = { id: windowState.workspaceIdentifier.id, configPath: URI.parse(windowState.workspaceIdentifier.configURIPath) }; - } else if (windowState.workspace) { - result.workspace = { id: windowState.workspace.id, configPath: URI.file(windowState.workspace.configPath) }; - } - - return result; -} - -export function getWindowsStateStoreData(windowsState: IWindowsState): WindowsStateStorageData { - return { - lastActiveWindow: windowsState.lastActiveWindow && serializeWindowState(windowsState.lastActiveWindow), - lastPluginDevelopmentHostWindow: windowsState.lastPluginDevelopmentHostWindow && serializeWindowState(windowsState.lastPluginDevelopmentHostWindow), - openedWindows: windowsState.openedWindows.map(ws => serializeWindowState(ws)) - }; -} - -function serializeWindowState(windowState: IWindowState): ISerializedWindowState { - return { - workspaceIdentifier: windowState.workspace && { id: windowState.workspace.id, configURIPath: windowState.workspace.configPath.toString() }, - folder: windowState.folderUri && windowState.folderUri.toString(), - backupPath: windowState.backupPath, - remoteAuthority: windowState.remoteAuthority, - uiState: windowState.uiState - }; -} diff --git a/src/vs/platform/windows/node/window.ts b/src/vs/platform/windows/node/window.ts deleted file mode 100644 index 6859b36ab..000000000 --- a/src/vs/platform/windows/node/window.ts +++ /dev/null @@ -1,150 +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 'vs/base/common/uri'; -import * as platform from 'vs/base/common/platform'; -import * as extpath from 'vs/base/common/extpath'; -import { IWorkspaceIdentifier, IResolvedWorkspace, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; - -export const enum OpenContext { - - // opening when running from the command line - CLI, - - // macOS only: opening from the dock (also when opening files to a running instance from desktop) - DOCK, - - // opening from the main application window - MENU, - - // opening from a file or folder dialog - DIALOG, - - // opening from the OS's UI - DESKTOP, - - // opening through the API - API -} - -export interface IWindowContext { - openedWorkspace?: IWorkspaceIdentifier; - openedFolderUri?: URI; - - extensionDevelopmentPath?: string[]; - lastFocusTime: number; -} - -export interface IBestWindowOrFolderOptions { - windows: W[]; - newWindow: boolean; - context: OpenContext; - fileUri?: URI; - codeSettingsFolder?: string; - localWorkspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | null; -} - -export function findBestWindowOrFolderForFile({ windows, newWindow, context, fileUri, localWorkspaceResolver: workspaceResolver }: IBestWindowOrFolderOptions): W | undefined { - if (!newWindow && fileUri && (context === OpenContext.DESKTOP || context === OpenContext.CLI || context === OpenContext.DOCK)) { - const windowOnFilePath = findWindowOnFilePath(windows, fileUri, workspaceResolver); - if (windowOnFilePath) { - return windowOnFilePath; - } - } - return !newWindow ? getLastActiveWindow(windows) : undefined; -} - -function findWindowOnFilePath(windows: W[], fileUri: URI, localWorkspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | null): W | null { - - // First check for windows with workspaces that have a parent folder of the provided path opened - for (const window of windows) { - const workspace = window.openedWorkspace; - if (workspace) { - const resolvedWorkspace = localWorkspaceResolver(workspace); - if (resolvedWorkspace) { - // workspace could be resolved: It's in the local file system - if (resolvedWorkspace.folders.some(folder => extUriBiasedIgnorePathCase.isEqualOrParent(fileUri, folder.uri))) { - return window; - } - } else { - // use the config path instead - if (extUriBiasedIgnorePathCase.isEqualOrParent(fileUri, workspace.configPath)) { - return window; - } - } - } - } - - // Then go with single folder windows that are parent of the provided file path - const singleFolderWindowsOnFilePath = windows.filter(window => window.openedFolderUri && extUriBiasedIgnorePathCase.isEqualOrParent(fileUri, window.openedFolderUri)); - if (singleFolderWindowsOnFilePath.length) { - return singleFolderWindowsOnFilePath.sort((a, b) => -(a.openedFolderUri!.path.length - b.openedFolderUri!.path.length))[0]; - } - - return null; -} - -export function getLastActiveWindow(windows: W[]): W | undefined { - const lastFocusedDate = Math.max.apply(Math, windows.map(window => window.lastFocusTime)); - - return windows.find(window => window.lastFocusTime === lastFocusedDate); -} - -export function findWindowOnWorkspace(windows: W[], workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier)): W | null { - if (isSingleFolderWorkspaceIdentifier(workspace)) { - for (const window of windows) { - // match on folder - if (isSingleFolderWorkspaceIdentifier(workspace)) { - if (window.openedFolderUri && extUriBiasedIgnorePathCase.isEqual(window.openedFolderUri, workspace)) { - return window; - } - } - } - } else if (isWorkspaceIdentifier(workspace)) { - for (const window of windows) { - // match on workspace - if (window.openedWorkspace && window.openedWorkspace.id === workspace.id) { - return window; - } - } - } - return null; -} - -export function findWindowOnExtensionDevelopmentPath(windows: W[], extensionDevelopmentPaths: string[]): W | null { - - const matches = (uriString: string): boolean => { - return extensionDevelopmentPaths.some(p => extpath.isEqual(p, uriString, !platform.isLinux /* ignorecase */)); - }; - - for (const window of windows) { - // match on extension development path. The path can be one or more paths or uri strings, using paths.isEqual is not 100% correct but good enough - const currPaths = window.extensionDevelopmentPath; - if (currPaths?.some(p => matches(p))) { - return window; - } - } - - return null; -} - -export function findWindowOnWorkspaceOrFolderUri(windows: W[], uri: URI | undefined): W | null { - if (!uri) { - return null; - } - for (const window of windows) { - // check for workspace config path - if (window.openedWorkspace && extUriBiasedIgnorePathCase.isEqual(window.openedWorkspace.configPath, uri)) { - return window; - } - - // check for folder path - if (window.openedFolderUri && extUriBiasedIgnorePathCase.isEqual(window.openedFolderUri, uri)) { - return window; - } - } - return null; -} diff --git a/src/vs/platform/windows/common/windowTracker.ts b/src/vs/platform/windows/node/windowTracker.ts similarity index 100% rename from src/vs/platform/windows/common/windowTracker.ts rename to src/vs/platform/windows/node/windowTracker.ts diff --git a/src/vs/platform/windows/test/electron-main/window.test.ts b/src/vs/platform/windows/test/electron-main/window.test.ts new file mode 100644 index 000000000..42f458f97 --- /dev/null +++ b/src/vs/platform/windows/test/electron-main/window.test.ts @@ -0,0 +1,109 @@ +/*--------------------------------------------------------------------------------------------- + * 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 path from 'vs/base/common/path'; +import { findWindowOnFile } from 'vs/platform/windows/electron-main/windowsFinder'; +import { ICodeWindow, IWindowState } from 'vs/platform/windows/electron-main/windows'; +import { IWorkspaceIdentifier, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces'; +import { URI } from 'vs/base/common/uri'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Event } from 'vs/base/common/event'; +import { UriDto } from 'vs/base/common/types'; +import { ICommandAction } from 'vs/platform/actions/common/actions'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; +import { INativeWindowConfiguration } from 'vs/platform/windows/common/windows'; + +const fixturesFolder = getPathFromAmdModule(require, './fixtures'); + +const testWorkspace: IWorkspaceIdentifier = { + id: Date.now().toString(), + configPath: URI.file(path.join(fixturesFolder, 'workspaces.json')) +}; + +const testWorkspaceFolders = toWorkspaceFolders([{ path: path.join(fixturesFolder, 'vscode_workspace_1_folder') }, { path: path.join(fixturesFolder, 'vscode_workspace_2_folder') }], testWorkspace.configPath, extUriBiasedIgnorePathCase); +const localWorkspaceResolver = (workspace: any) => { return workspace === testWorkspace ? { id: testWorkspace.id, configPath: workspace.configPath, folders: testWorkspaceFolders } : null; }; + +function createTestCodeWindow(options: { lastFocusTime: number, openedFolderUri?: URI, openedWorkspace?: IWorkspaceIdentifier }): ICodeWindow { + return new class implements ICodeWindow { + onLoad: Event = Event.None; + onReady: Event = Event.None; + onClose: Event = Event.None; + onDestroy: Event = Event.None; + whenClosedOrLoaded: Promise = Promise.resolve(); + id: number = -1; + win: Electron.BrowserWindow = undefined!; + config: INativeWindowConfiguration | undefined; + openedWorkspace = options.openedFolderUri ? { id: '', uri: options.openedFolderUri } : options.openedWorkspace; + backupPath?: string | undefined; + remoteAuthority?: string | undefined; + isExtensionDevelopmentHost = false; + isExtensionTestHost = false; + lastFocusTime = options.lastFocusTime; + isFullScreen = false; + isReady = true; + hasHiddenTitleBarStyle = false; + + ready(): Promise { throw new Error('Method not implemented.'); } + setReady(): void { throw new Error('Method not implemented.'); } + addTabbedWindow(window: ICodeWindow): void { throw new Error('Method not implemented.'); } + load(config: INativeWindowConfiguration, options: { isReload?: boolean }): void { throw new Error('Method not implemented.'); } + reload(cli?: NativeParsedArgs): void { throw new Error('Method not implemented.'); } + focus(options?: { force: boolean; }): void { throw new Error('Method not implemented.'); } + close(): void { throw new Error('Method not implemented.'); } + getBounds(): Electron.Rectangle { throw new Error('Method not implemented.'); } + send(channel: string, ...args: any[]): void { throw new Error('Method not implemented.'); } + sendWhenReady(channel: string, token: CancellationToken, ...args: any[]): void { throw new Error('Method not implemented.'); } + toggleFullScreen(): void { throw new Error('Method not implemented.'); } + isMinimized(): boolean { throw new Error('Method not implemented.'); } + setRepresentedFilename(name: string): void { throw new Error('Method not implemented.'); } + getRepresentedFilename(): string | undefined { throw new Error('Method not implemented.'); } + setDocumentEdited(edited: boolean): void { throw new Error('Method not implemented.'); } + isDocumentEdited(): boolean { throw new Error('Method not implemented.'); } + handleTitleDoubleClick(): void { throw new Error('Method not implemented.'); } + updateTouchBar(items: UriDto[][]): void { throw new Error('Method not implemented.'); } + serializeWindowState(): IWindowState { throw new Error('Method not implemented'); } + dispose(): void { } + }; +} + +const vscodeFolderWindow: ICodeWindow = createTestCodeWindow({ lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'vscode_folder')) }); +const lastActiveWindow: ICodeWindow = createTestCodeWindow({ lastFocusTime: 3, openedFolderUri: undefined }); +const noVscodeFolderWindow: ICodeWindow = createTestCodeWindow({ lastFocusTime: 2, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder')) }); +const windows: ICodeWindow[] = [ + vscodeFolderWindow, + lastActiveWindow, + noVscodeFolderWindow, +]; + +suite('WindowsFinder', () => { + + test('New window without folder when no windows exist', () => { + assert.strictEqual(findWindowOnFile([], URI.file('nonexisting'), localWorkspaceResolver), undefined); + assert.strictEqual(findWindowOnFile([], URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt')), localWorkspaceResolver), undefined); + }); + + test('Existing window with folder', () => { + assert.strictEqual(findWindowOnFile(windows, URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt')), localWorkspaceResolver), noVscodeFolderWindow); + + assert.strictEqual(findWindowOnFile(windows, URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')), localWorkspaceResolver), vscodeFolderWindow); + + const window: ICodeWindow = createTestCodeWindow({ lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'nested_folder')) }); + assert.strictEqual(findWindowOnFile([window], URI.file(path.join(fixturesFolder, 'vscode_folder', 'nested_folder', 'subfolder', 'file.txt')), localWorkspaceResolver), window); + }); + + test('More specific existing window wins', () => { + const window: ICodeWindow = createTestCodeWindow({ lastFocusTime: 2, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder')) }); + const nestedFolderWindow: ICodeWindow = createTestCodeWindow({ lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder')) }); + assert.strictEqual(findWindowOnFile([window, nestedFolderWindow], URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder', 'subfolder', 'file.txt')), localWorkspaceResolver), nestedFolderWindow); + }); + + test('Workspace folder wins', () => { + const window: ICodeWindow = createTestCodeWindow({ lastFocusTime: 1, openedWorkspace: testWorkspace }); + assert.strictEqual(findWindowOnFile([window], URI.file(path.join(fixturesFolder, 'vscode_workspace_2_folder', 'nested_vscode_folder', 'subfolder', 'file.txt')), localWorkspaceResolver), window); + }); +}); diff --git a/src/vs/platform/windows/test/electron-main/windowsStateStorage.test.ts b/src/vs/platform/windows/test/electron-main/windowsStateHandler.test.ts similarity index 68% rename from src/vs/platform/windows/test/electron-main/windowsStateStorage.test.ts rename to src/vs/platform/windows/test/electron-main/windowsStateHandler.test.ts index 419caee0d..956ab942b 100644 --- a/src/vs/platform/windows/test/electron-main/windowsStateStorage.test.ts +++ b/src/vs/platform/windows/test/electron-main/windowsStateHandler.test.ts @@ -6,12 +6,10 @@ import * as assert from 'assert'; import * as os from 'os'; import * as path from 'vs/base/common/path'; - -import { restoreWindowsState, getWindowsStateStoreData } from 'vs/platform/windows/electron-main/windowsStateStorage'; +import { restoreWindowsState, getWindowsStateStoreData, IWindowsState, IWindowState } from 'vs/platform/windows/electron-main/windowsStateHandler'; import { IWindowState as IWindowUIState, WindowMode } from 'vs/platform/windows/electron-main/windows'; import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; -import { IWindowsState, IWindowState } from 'vs/platform/windows/electron-main/windowsMainService'; function getUIState(): IWindowUIState { return { @@ -30,15 +28,15 @@ function toWorkspace(uri: URI): IWorkspaceIdentifier { }; } function assertEqualURI(u1: URI | undefined, u2: URI | undefined, message?: string): void { - assert.equal(u1 && u1.toString(), u2 && u2.toString(), message); + assert.strictEqual(u1 && u1.toString(), u2 && u2.toString(), message); } function assertEqualWorkspace(w1: IWorkspaceIdentifier | undefined, w2: IWorkspaceIdentifier | undefined, message?: string): void { if (!w1 || !w2) { - assert.equal(w1, w2, message); + assert.strictEqual(w1, w2, message); return; } - assert.equal(w1.id, w2.id, message); + assert.strictEqual(w1.id, w2.id, message); assertEqualURI(w1.configPath, w2.configPath, message); } @@ -47,9 +45,9 @@ function assertEqualWindowState(expected: IWindowState | undefined, actual: IWin assert.deepEqual(expected, actual, message); return; } - assert.equal(expected.backupPath, actual.backupPath, message); + assert.strictEqual(expected.backupPath, actual.backupPath, message); assertEqualURI(expected.folderUri, actual.folderUri, message); - assert.equal(expected.remoteAuthority, actual.remoteAuthority, message); + assert.strictEqual(expected.remoteAuthority, actual.remoteAuthority, message); assertEqualWorkspace(expected.workspace, actual.workspace, message); assert.deepEqual(expected.uiState, actual.uiState, message); } @@ -57,7 +55,7 @@ function assertEqualWindowState(expected: IWindowState | undefined, actual: IWin function assertEqualWindowsState(expected: IWindowsState, actual: IWindowsState, message?: string) { assertEqualWindowState(expected.lastPluginDevelopmentHostWindow, actual.lastPluginDevelopmentHostWindow, message); assertEqualWindowState(expected.lastActiveWindow, actual.lastActiveWindow, message); - assert.equal(expected.openedWindows.length, actual.openedWindows.length, message); + assert.strictEqual(expected.openedWindows.length, actual.openedWindows.length, message); for (let i = 0; i < expected.openedWindows.length; i++) { assertEqualWindowState(expected.openedWindows[i], actual.openedWindows[i], message); } @@ -117,94 +115,6 @@ suite('Windows State Storing', () => { assertRestoring(windowState, 'lastPluginDevelopmentHostWindow'); }); - test('open 1_31', () => { - const v1_31_workspace = `{ - "openedWindows": [], - "lastActiveWindow": { - "workspace": { - "id": "a41787288b5e9cc1a61ba2dd84cd0d80", - "configPath": "/home/user/workspaces/code-and-docs.code-workspace" - }, - "backupPath": "/home/user/.config/Code - Insiders/Backups/a41787288b5e9cc1a61ba2dd84cd0d80", - "uiState": { - "mode": 0, - "x": 0, - "y": 27, - "width": 2560, - "height": 1364 - } - } - }`; - - let windowsState = restoreWindowsState(JSON.parse(v1_31_workspace)); - let expected: IWindowsState = { - openedWindows: [], - lastActiveWindow: { - backupPath: '/home/user/.config/Code - Insiders/Backups/a41787288b5e9cc1a61ba2dd84cd0d80', - uiState: { mode: WindowMode.Maximized, x: 0, y: 27, width: 2560, height: 1364 }, - workspace: { id: 'a41787288b5e9cc1a61ba2dd84cd0d80', configPath: URI.file('/home/user/workspaces/code-and-docs.code-workspace') } - } - }; - - assertEqualWindowsState(expected, windowsState, 'v1_31_workspace'); - - const v1_31_folder = `{ - "openedWindows": [], - "lastPluginDevelopmentHostWindow": { - "folderUri": { - "$mid": 1, - "fsPath": "/home/user/workspaces/testing/customdata", - "external": "file:///home/user/workspaces/testing/customdata", - "path": "/home/user/workspaces/testing/customdata", - "scheme": "file" - }, - "uiState": { - "mode": 1, - "x": 593, - "y": 617, - "width": 1625, - "height": 595 - } - } - }`; - - windowsState = restoreWindowsState(JSON.parse(v1_31_folder)); - expected = { - openedWindows: [], - lastPluginDevelopmentHostWindow: { - uiState: { mode: WindowMode.Normal, x: 593, y: 617, width: 1625, height: 595 }, - folderUri: URI.parse('file:///home/user/workspaces/testing/customdata') - } - }; - assertEqualWindowsState(expected, windowsState, 'v1_31_folder'); - - const v1_31_empty_window = ` { - "openedWindows": [ - ], - "lastActiveWindow": { - "backupPath": "C:\\\\Users\\\\Mike\\\\AppData\\\\Roaming\\\\Code\\\\Backups\\\\1549538599815", - "uiState": { - "mode": 0, - "x": -8, - "y": -8, - "width": 2576, - "height": 1344 - } - } - }`; - - windowsState = restoreWindowsState(JSON.parse(v1_31_empty_window)); - expected = { - openedWindows: [], - lastActiveWindow: { - backupPath: 'C:\\Users\\Mike\\AppData\\Roaming\\Code\\Backups\\1549538599815', - uiState: { mode: WindowMode.Maximized, x: -8, y: -8, width: 2576, height: 1344 } - } - }; - assertEqualWindowsState(expected, windowsState, 'v1_31_empty_window'); - - }); - test('open 1_32', () => { const v1_32_workspace = `{ "openedWindows": [], @@ -286,7 +196,5 @@ suite('Windows State Storing', () => { } }; assertEqualWindowsState(expected, windowsState, 'v1_32_empty_window'); - }); - }); diff --git a/src/vs/platform/windows/test/node/window.test.ts b/src/vs/platform/windows/test/node/window.test.ts deleted file mode 100644 index 576d30d13..000000000 --- a/src/vs/platform/windows/test/node/window.test.ts +++ /dev/null @@ -1,127 +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 assert from 'assert'; -import * as path from 'vs/base/common/path'; -import { IBestWindowOrFolderOptions, IWindowContext, findBestWindowOrFolderForFile, OpenContext } from 'vs/platform/windows/node/window'; -import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; -import { URI } from 'vs/base/common/uri'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; - -const fixturesFolder = getPathFromAmdModule(require, './fixtures'); - -const testWorkspace: IWorkspaceIdentifier = { - id: Date.now().toString(), - configPath: URI.file(path.join(fixturesFolder, 'workspaces.json')) -}; - -const testWorkspaceFolders = toWorkspaceFolders([{ path: path.join(fixturesFolder, 'vscode_workspace_1_folder') }, { path: path.join(fixturesFolder, 'vscode_workspace_2_folder') }], testWorkspace.configPath); - -function options(custom?: Partial>): IBestWindowOrFolderOptions { - return { - windows: [], - newWindow: false, - context: OpenContext.CLI, - codeSettingsFolder: '_vscode', - localWorkspaceResolver: workspace => { return workspace === testWorkspace ? { id: testWorkspace.id, configPath: workspace.configPath, folders: testWorkspaceFolders } : null; }, - ...custom - }; -} - -const vscodeFolderWindow: IWindowContext = { lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'vscode_folder')) }; -const lastActiveWindow: IWindowContext = { lastFocusTime: 3, openedFolderUri: undefined }; -const noVscodeFolderWindow: IWindowContext = { lastFocusTime: 2, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder')) }; -const windows: IWindowContext[] = [ - vscodeFolderWindow, - lastActiveWindow, - noVscodeFolderWindow, -]; - -suite('WindowsFinder', () => { - - test('New window without folder when no windows exist', () => { - assert.equal(findBestWindowOrFolderForFile(options()), null); - assert.equal(findBestWindowOrFolderForFile(options({ - fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt')) - })), null); - assert.equal(findBestWindowOrFolderForFile(options({ - fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')), - newWindow: true - })), null); - assert.equal(findBestWindowOrFolderForFile(options({ - fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')), - })), null); - assert.equal(findBestWindowOrFolderForFile(options({ - fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')), - context: OpenContext.API - })), null); - assert.equal(findBestWindowOrFolderForFile(options({ - fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')) - })), null); - assert.equal(findBestWindowOrFolderForFile(options({ - fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'new_folder', 'new_file.txt')) - })), null); - }); - - test('New window without folder when windows exist', () => { - assert.equal(findBestWindowOrFolderForFile(options({ - windows, - fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt')), - newWindow: true - })), null); - }); - - test('Last active window', () => { - assert.equal(findBestWindowOrFolderForFile(options({ - windows - })), lastActiveWindow); - assert.equal(findBestWindowOrFolderForFile(options({ - windows, - fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder2', 'file.txt')) - })), lastActiveWindow); - assert.equal(findBestWindowOrFolderForFile(options({ - windows: [lastActiveWindow, noVscodeFolderWindow], - fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')), - })), lastActiveWindow); - assert.equal(findBestWindowOrFolderForFile(options({ - windows, - fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt')), - context: OpenContext.API - })), lastActiveWindow); - }); - - test('Existing window with folder', () => { - assert.equal(findBestWindowOrFolderForFile(options({ - windows, - fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt')) - })), noVscodeFolderWindow); - assert.equal(findBestWindowOrFolderForFile(options({ - windows, - fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')) - })), vscodeFolderWindow); - const window: IWindowContext = { lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'nested_folder')) }; - assert.equal(findBestWindowOrFolderForFile(options({ - windows: [window], - fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'nested_folder', 'subfolder', 'file.txt')) - })), window); - }); - - test('More specific existing window wins', () => { - const window: IWindowContext = { lastFocusTime: 2, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder')) }; - const nestedFolderWindow: IWindowContext = { lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder')) }; - assert.equal(findBestWindowOrFolderForFile(options({ - windows: [window, nestedFolderWindow], - fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder', 'subfolder', 'file.txt')) - })), nestedFolderWindow); - }); - - test('Workspace folder wins', () => { - const window: IWindowContext = { lastFocusTime: 1, openedWorkspace: testWorkspace }; - assert.equal(findBestWindowOrFolderForFile(options({ - windows: [window], - fileUri: URI.file(path.join(fixturesFolder, 'vscode_workspace_2_folder', 'nested_vscode_folder', 'subfolder', 'file.txt')) - })), window); - }); -}); diff --git a/src/vs/platform/workspace/common/workspace.ts b/src/vs/platform/workspace/common/workspace.ts index 72ca615f9..38a11c92f 100644 --- a/src/vs/platform/workspace/common/workspace.ts +++ b/src/vs/platform/workspace/common/workspace.ts @@ -4,16 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import * as resources from 'vs/base/common/resources'; +import { joinPath, basenameOrAuthority } from 'vs/base/common/resources'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { TernarySearchTree } from 'vs/base/common/map'; import { Event } from 'vs/base/common/event'; -import { IWorkspaceIdentifier, IStoredWorkspaceFolder, isRawFileWorkspaceFolder, isRawUriWorkspaceFolder, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, IStoredWorkspaceFolder, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IWorkspaceFolderProvider } from 'vs/base/common/labels'; export const IWorkspaceContextService = createDecorator('contextService'); export interface IWorkspaceContextService extends IWorkspaceFolderProvider { + readonly _serviceBrand: undefined; /** @@ -58,9 +59,9 @@ export interface IWorkspaceContextService extends IWorkspaceFolderProvider { getWorkspaceFolder(resource: URI): IWorkspaceFolder | null; /** - * Return `true` if the current workspace has the given identifier otherwise `false`. + * Return `true` if the current workspace has the given identifier or root URI otherwise `false`. */ - isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean; + isCurrentWorkspace(workspaceIdOrFolder: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI): boolean; /** * Returns if the provided resource is inside the workspace or not. @@ -80,14 +81,6 @@ export interface IWorkspaceFoldersChangeEvent { changed: IWorkspaceFolder[]; } -export namespace IWorkspace { - export function isIWorkspace(thing: unknown): thing is IWorkspace { - return !!(thing && typeof thing === 'object' - && typeof (thing as IWorkspace).id === 'string' - && Array.isArray((thing as IWorkspace).folders)); - } -} - export interface IWorkspace { /** @@ -106,6 +99,14 @@ export interface IWorkspace { readonly configuration?: URI | null; } +export function isWorkspace(thing: unknown): thing is IWorkspace { + const candidate = thing as IWorkspace | undefined; + + return !!(candidate && typeof candidate === 'object' + && typeof candidate.id === 'string' + && Array.isArray(candidate.folders)); +} + export interface IWorkspaceFolderData { /** @@ -125,15 +126,6 @@ export interface IWorkspaceFolderData { readonly index: number; } -export namespace IWorkspaceFolder { - export function isIWorkspaceFolder(thing: unknown): thing is IWorkspaceFolder { - return !!(thing && typeof thing === 'object' - && URI.isUri((thing as IWorkspaceFolder).uri) - && typeof (thing as IWorkspaceFolder).name === 'string' - && typeof (thing as IWorkspaceFolder).toResource === 'function'); - } -} - export interface IWorkspaceFolder extends IWorkspaceFolderData { /** @@ -142,6 +134,15 @@ export interface IWorkspaceFolder extends IWorkspaceFolderData { toResource: (relativePath: string) => URI; } +export function isWorkspaceFolder(thing: unknown): thing is IWorkspaceFolder { + const candidate = thing as IWorkspaceFolder; + + return !!(candidate && typeof candidate === 'object' + && URI.isUri(candidate.uri) + && typeof candidate.name === 'string' + && typeof candidate.toResource === 'function'); +} + export class Workspace implements IWorkspace { private _foldersMap: TernarySearchTree = TernarySearchTree.forUris(this._ignorePathCasing); @@ -222,7 +223,7 @@ export class WorkspaceFolder implements IWorkspaceFolder { } toResource(relativePath: string): URI { - return resources.joinPath(this.uri, relativePath); + return joinPath(this.uri, relativePath); } toJSON(): IWorkspaceFolderData { @@ -231,43 +232,5 @@ export class WorkspaceFolder implements IWorkspaceFolder { } export function toWorkspaceFolder(resource: URI): WorkspaceFolder { - return new WorkspaceFolder({ uri: resource, index: 0, name: resources.basenameOrAuthority(resource) }, { uri: resource.toString() }); -} - -export function toWorkspaceFolders(configuredFolders: IStoredWorkspaceFolder[], workspaceConfigFile: URI): WorkspaceFolder[] { - let result: WorkspaceFolder[] = []; - let seen: Set = new Set(); - - const relativeTo = resources.dirname(workspaceConfigFile); - for (let configuredFolder of configuredFolders) { - let uri: URI | null = null; - if (isRawFileWorkspaceFolder(configuredFolder)) { - if (configuredFolder.path) { - uri = resources.resolvePath(relativeTo, configuredFolder.path); - } - } else if (isRawUriWorkspaceFolder(configuredFolder)) { - try { - uri = URI.parse(configuredFolder.uri); - // this makes sure all workspace folder are absolute - if (uri.path[0] !== '/') { - uri = uri.with({ path: '/' + uri.path }); - } - } catch (e) { - console.warn(e); - // ignore - } - } - if (uri) { - // remove duplicates - let comparisonKey = resources.getComparisonKey(uri); - if (!seen.has(comparisonKey)) { - seen.add(comparisonKey); - - const name = configuredFolder.name || resources.basenameOrAuthority(uri); - result.push(new WorkspaceFolder({ uri, name, index: result.length }, configuredFolder)); - } - } - } - - return result; + return new WorkspaceFolder({ uri: resource, index: 0, name: basenameOrAuthority(resource) }, { uri: resource.toString() }); } diff --git a/src/vs/platform/workspace/test/common/workspace.test.ts b/src/vs/platform/workspace/test/common/workspace.test.ts index 1d3f78f7b..9b26cb220 100644 --- a/src/vs/platform/workspace/test/common/workspace.test.ts +++ b/src/vs/platform/workspace/test/common/workspace.test.ts @@ -5,10 +5,11 @@ import * as assert from 'assert'; import * as path from 'vs/base/common/path'; -import { Workspace, toWorkspaceFolders, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { URI } from 'vs/base/common/uri'; -import { IRawFileWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; +import { IRawFileWorkspaceFolder, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces'; import { isLinux, isWindows } from 'vs/base/common/platform'; +import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; suite('Workspace', () => { @@ -70,7 +71,7 @@ suite('Workspace', () => { }); test('toWorkspaceFolders with single absolute folder', () => { - const actual = toWorkspaceFolders([{ path: '/src/test' }], workspaceConfigUri); + const actual = toWorkspaceFolders([{ path: '/src/test' }], workspaceConfigUri, extUriBiasedIgnorePathCase); assert.equal(actual.length, 1); assert.equal(actual[0].uri.fsPath, testFolderUri.fsPath); @@ -80,7 +81,7 @@ suite('Workspace', () => { }); test('toWorkspaceFolders with single relative folder', () => { - const actual = toWorkspaceFolders([{ path: './test' }], workspaceConfigUri); + const actual = toWorkspaceFolders([{ path: './test' }], workspaceConfigUri, extUriBiasedIgnorePathCase); assert.equal(actual.length, 1); assert.equal(actual[0].uri.fsPath, testFolderUri.fsPath); @@ -90,7 +91,7 @@ suite('Workspace', () => { }); test('toWorkspaceFolders with single absolute folder with name', () => { - const actual = toWorkspaceFolders([{ path: '/src/test', name: 'hello' }], workspaceConfigUri); + const actual = toWorkspaceFolders([{ path: '/src/test', name: 'hello' }], workspaceConfigUri, extUriBiasedIgnorePathCase); assert.equal(actual.length, 1); @@ -101,7 +102,7 @@ suite('Workspace', () => { }); test('toWorkspaceFolders with multiple unique absolute folders', () => { - const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '/src/test3' }, { path: '/src/test1' }], workspaceConfigUri); + const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '/src/test3' }, { path: '/src/test1' }], workspaceConfigUri, extUriBiasedIgnorePathCase); assert.equal(actual.length, 3); assert.equal(actual[0].uri.fsPath, test2FolderUri.fsPath); @@ -121,7 +122,7 @@ suite('Workspace', () => { }); test('toWorkspaceFolders with multiple unique absolute folders with names', () => { - const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '/src/test3', name: 'noName' }, { path: '/src/test1' }], workspaceConfigUri); + const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '/src/test3', name: 'noName' }, { path: '/src/test1' }], workspaceConfigUri, extUriBiasedIgnorePathCase); assert.equal(actual.length, 3); assert.equal(actual[0].uri.fsPath, test2FolderUri.fsPath); @@ -141,7 +142,7 @@ suite('Workspace', () => { }); test('toWorkspaceFolders with multiple unique absolute and relative folders', () => { - const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '/abc/test3', name: 'noName' }, { path: './test1' }], workspaceConfigUri); + const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '/abc/test3', name: 'noName' }, { path: './test1' }], workspaceConfigUri, extUriBiasedIgnorePathCase); assert.equal(actual.length, 3); assert.equal(actual[0].uri.fsPath, test2FolderUri.fsPath); @@ -161,7 +162,7 @@ suite('Workspace', () => { }); test('toWorkspaceFolders with multiple absolute folders with duplicates', () => { - const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '/src/test2', name: 'noName' }, { path: '/src/test1' }], workspaceConfigUri); + const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '/src/test2', name: 'noName' }, { path: '/src/test1' }], workspaceConfigUri, extUriBiasedIgnorePathCase); assert.equal(actual.length, 2); assert.equal(actual[0].uri.fsPath, test2FolderUri.fsPath); @@ -176,7 +177,7 @@ suite('Workspace', () => { }); test('toWorkspaceFolders with multiple absolute and relative folders with duplicates', () => { - const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '/src/test3', name: 'noName' }, { path: './test3' }, { path: '/abc/test1' }], workspaceConfigUri); + const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '/src/test3', name: 'noName' }, { path: './test3' }, { path: '/abc/test1' }], workspaceConfigUri, extUriBiasedIgnorePathCase); assert.equal(actual.length, 3); assert.equal(actual[0].uri.fsPath, test2FolderUri.fsPath); @@ -196,7 +197,7 @@ suite('Workspace', () => { }); test('toWorkspaceFolders with multiple absolute and relative folders with invalid paths', () => { - const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '', name: 'noName' }, { path: './test3' }, { path: '/abc/test1' }], workspaceConfigUri); + const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '', name: 'noName' }, { path: './test3' }, { path: '/abc/test1' }], workspaceConfigUri, extUriBiasedIgnorePathCase); assert.equal(actual.length, 3); assert.equal(actual[0].uri.fsPath, test2FolderUri.fsPath); diff --git a/src/vs/platform/workspaces/common/workspaces.ts b/src/vs/platform/workspaces/common/workspaces.ts index d6643801a..66413f2e4 100644 --- a/src/vs/platform/workspaces/common/workspaces.ts +++ b/src/vs/platform/workspaces/common/workspaces.ts @@ -5,11 +5,11 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { localize } from 'vs/nls'; -import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceFolder, IWorkspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { URI, UriComponents } from 'vs/base/common/uri'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; import { extname, isAbsolute } from 'vs/base/common/path'; -import { dirname, resolvePath, isEqualAuthority, relativePath, extname as resourceExtname, extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; +import { extname as resourceExtname, extUriBiasedIgnorePathCase, IExtUri } from 'vs/base/common/resources'; import * as jsonEdit from 'vs/base/common/jsonEdit'; import * as json from 'vs/base/common/json'; import { Schemas } from 'vs/base/common/network'; @@ -22,9 +22,16 @@ import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; export const WORKSPACE_EXTENSION = 'code-workspace'; +const WORKSPACE_SUFFIX = `.${WORKSPACE_EXTENSION}`; export const WORKSPACE_FILTER = [{ name: localize('codeWorkspace', "Code Workspace"), extensions: [WORKSPACE_EXTENSION] }]; export const UNTITLED_WORKSPACE_NAME = 'workspace.json'; +export function hasWorkspaceFileExtension(path: string | URI) { + const ext = (typeof path === 'string') ? extname(path) : resourceExtname(path); + + return ext === WORKSPACE_SUFFIX; +} + export const IWorkspacesService = createDecorator('workspacesService'); export interface IWorkspacesService { @@ -48,6 +55,8 @@ export interface IWorkspacesService { getDirtyWorkspaces(): Promise>; } +//#region Workspaces Recently Opened + export interface IRecentlyOpened { workspaces: Array; files: IRecentFile[]; @@ -61,7 +70,7 @@ export interface IRecentWorkspace { } export interface IRecentFolder { - folderUri: ISingleFolderWorkspaceIdentifier; + folderUri: URI; label?: string; } @@ -82,36 +91,114 @@ export function isRecentFile(curr: IRecent): curr is IRecentFile { return curr.hasOwnProperty('fileUri'); } -/** - * A single folder workspace identifier is just the path to the folder. - */ -export type ISingleFolderWorkspaceIdentifier = URI; +//#endregion -export interface IWorkspaceIdentifier { +//#region Identifiers / Payload + +export interface IBaseWorkspaceIdentifier { + + /** + * Every workspace (multi-root, single folder or empty) + * has a unique identifier. It is not possible to open + * a workspace with the same `id` in multiple windows + */ id: string; +} + +/** + * A single folder workspace identifier is a path to a folder + id. + */ +export interface ISingleFolderWorkspaceIdentifier extends IBaseWorkspaceIdentifier { + + /** + * Folder path as `URI`. + */ + uri: URI; +} + +export function isSingleFolderWorkspaceIdentifier(obj: unknown): obj is ISingleFolderWorkspaceIdentifier { + const singleFolderIdentifier = obj as ISingleFolderWorkspaceIdentifier | undefined; + + return typeof singleFolderIdentifier?.id === 'string' && URI.isUri(singleFolderIdentifier.uri); +} + +/** + * A multi-root workspace identifier is a path to a workspace file + id. + */ +export interface IWorkspaceIdentifier extends IBaseWorkspaceIdentifier { + + /** + * Workspace config file path as `URI`. + */ configPath: URI; } -export function reviveWorkspaceIdentifier(workspace: { id: string, configPath: UriComponents; }): IWorkspaceIdentifier { - return { id: workspace.id, configPath: URI.revive(workspace.configPath) }; +export function toWorkspaceIdentifier(workspace: IWorkspace): IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined { + + // Multi root + if (workspace.configuration) { + return { + id: workspace.id, + configPath: workspace.configuration + }; + } + + // Single folder + if (workspace.folders.length === 1) { + return { + id: workspace.id, + uri: workspace.folders[0].uri + }; + } + + // Empty workspace + return undefined; } -export function isStoredWorkspaceFolder(thing: unknown): thing is IStoredWorkspaceFolder { - return isRawFileWorkspaceFolder(thing) || isRawUriWorkspaceFolder(thing); +export function isWorkspaceIdentifier(obj: unknown): obj is IWorkspaceIdentifier { + const workspaceIdentifier = obj as IWorkspaceIdentifier | undefined; + + return typeof workspaceIdentifier?.id === 'string' && URI.isUri(workspaceIdentifier.configPath); } -export function isRawFileWorkspaceFolder(thing: any): thing is IRawFileWorkspaceFolder { - return thing - && typeof thing === 'object' - && typeof thing.path === 'string' - && (!thing.name || typeof thing.name === 'string'); +export function reviveIdentifier(identifier: { id: string, uri?: UriComponents, configPath?: UriComponents } | undefined): IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined { + if (identifier?.uri) { + return { id: identifier.id, uri: URI.revive(identifier.uri) }; + } + + if (identifier?.configPath) { + return { id: identifier.id, configPath: URI.revive(identifier.configPath) }; + } + + return undefined; } -export function isRawUriWorkspaceFolder(thing: any): thing is IRawUriWorkspaceFolder { - return thing - && typeof thing === 'object' - && typeof thing.uri === 'string' - && (!thing.name || typeof thing.name === 'string'); +export function isUntitledWorkspace(path: URI, environmentService: IEnvironmentService): boolean { + return extUriBiasedIgnorePathCase.isEqualOrParent(path, environmentService.untitledWorkspacesHome); +} + +export interface IEmptyWorkspaceInitializationPayload extends IBaseWorkspaceIdentifier { } + +export type IWorkspaceInitializationPayload = IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceInitializationPayload; + +//#endregion + +//#region Workspace File Utilities + +export function isStoredWorkspaceFolder(obj: unknown): obj is IStoredWorkspaceFolder { + return isRawFileWorkspaceFolder(obj) || isRawUriWorkspaceFolder(obj); +} + +export function isRawFileWorkspaceFolder(obj: unknown): obj is IRawFileWorkspaceFolder { + const candidate = obj as IRawFileWorkspaceFolder | undefined; + + return typeof candidate?.path === 'string' && (!candidate.name || typeof candidate.name === 'string'); +} + +export function isRawUriWorkspaceFolder(obj: unknown): obj is IRawUriWorkspaceFolder { + const candidate = obj as IRawUriWorkspaceFolder | undefined; + + return typeof candidate?.uri === 'string' && (!candidate.name || typeof candidate.name === 'string'); } export interface IRawFileWorkspaceFolder { @@ -151,56 +238,6 @@ export interface IEnterWorkspaceResult { backupPath?: string; } -export function isSingleFolderWorkspaceIdentifier(obj: unknown): obj is ISingleFolderWorkspaceIdentifier { - return obj instanceof URI; -} - -export function isWorkspaceIdentifier(obj: unknown): obj is IWorkspaceIdentifier { - const workspaceIdentifier = obj as IWorkspaceIdentifier; - - return workspaceIdentifier && typeof workspaceIdentifier.id === 'string' && workspaceIdentifier.configPath instanceof URI; -} - -export function toWorkspaceIdentifier(workspace: IWorkspace): IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined { - if (workspace.configuration) { - return { - configPath: workspace.configuration, - id: workspace.id - }; - } - - if (workspace.folders.length === 1) { - return workspace.folders[0].uri; - } - - // Empty workspace - return undefined; -} - -export function isUntitledWorkspace(path: URI, environmentService: IEnvironmentService): boolean { - return extUriBiasedIgnorePathCase.isEqualOrParent(path, environmentService.untitledWorkspacesHome); -} - -export type IMultiFolderWorkspaceInitializationPayload = IWorkspaceIdentifier; -export interface ISingleFolderWorkspaceInitializationPayload { id: string; folder: ISingleFolderWorkspaceIdentifier; } -export interface IEmptyWorkspaceInitializationPayload { id: string; } - -export type IWorkspaceInitializationPayload = IMultiFolderWorkspaceInitializationPayload | ISingleFolderWorkspaceInitializationPayload | IEmptyWorkspaceInitializationPayload; - -export function isSingleFolderWorkspaceInitializationPayload(obj: any): obj is ISingleFolderWorkspaceInitializationPayload { - return isSingleFolderWorkspaceIdentifier((obj.folder as ISingleFolderWorkspaceIdentifier)); -} - -const WORKSPACE_SUFFIX = '.' + WORKSPACE_EXTENSION; - -export function hasWorkspaceFileExtension(path: string | URI) { - const ext = (typeof path === 'string') ? extname(path) : resourceExtname(path); - - return ext === WORKSPACE_SUFFIX; -} - -const SLASH = '/'; - /** * Given a folder URI and the workspace config folder, computes the IStoredWorkspaceFolder using * a relative or absolute path or a uri. @@ -212,12 +249,12 @@ const SLASH = '/'; * @param targetConfigFolderURI the folder where the workspace is living in * @param useSlashForPath if set, use forward slashes for file paths on windows */ -export function getStoredWorkspaceFolder(folderURI: URI, forceAbsolute: boolean, folderName: string | undefined, targetConfigFolderURI: URI, useSlashForPath = !isWindows): IStoredWorkspaceFolder { +export function getStoredWorkspaceFolder(folderURI: URI, forceAbsolute: boolean, folderName: string | undefined, targetConfigFolderURI: URI, useSlashForPath = !isWindows, extUri: IExtUri): IStoredWorkspaceFolder { if (folderURI.scheme !== targetConfigFolderURI.scheme) { return { name: folderName, uri: folderURI.toString(true) }; } - let folderPath = !forceAbsolute ? relativePath(targetConfigFolderURI, folderURI) : undefined; + let folderPath = !forceAbsolute ? extUri.relativePath(targetConfigFolderURI, folderURI) : undefined; if (folderPath !== undefined) { if (folderPath.length === 0) { folderPath = '.'; @@ -241,7 +278,7 @@ export function getStoredWorkspaceFolder(folderURI: URI, forceAbsolute: boolean, } } } else { - if (!isEqualAuthority(folderURI.authority, targetConfigFolderURI.authority)) { + if (!extUri.isEqualAuthority(folderURI.authority, targetConfigFolderURI.authority)) { return { name: folderName, uri: folderURI.toString(true) }; } folderPath = folderURI.path; @@ -251,21 +288,59 @@ export function getStoredWorkspaceFolder(folderURI: URI, forceAbsolute: boolean, return { name: folderName, path: folderPath }; } +export function toWorkspaceFolders(configuredFolders: IStoredWorkspaceFolder[], workspaceConfigFile: URI, extUri: IExtUri): WorkspaceFolder[] { + let result: WorkspaceFolder[] = []; + let seen: Set = new Set(); + + const relativeTo = extUri.dirname(workspaceConfigFile); + for (let configuredFolder of configuredFolders) { + let uri: URI | null = null; + if (isRawFileWorkspaceFolder(configuredFolder)) { + if (configuredFolder.path) { + uri = extUri.resolvePath(relativeTo, configuredFolder.path); + } + } else if (isRawUriWorkspaceFolder(configuredFolder)) { + try { + uri = URI.parse(configuredFolder.uri); + // this makes sure all workspace folder are absolute + if (uri.path[0] !== '/') { + uri = uri.with({ path: '/' + uri.path }); + } + } catch (e) { + console.warn(e); + // ignore + } + } + if (uri) { + // remove duplicates + let comparisonKey = extUri.getComparisonKey(uri); + if (!seen.has(comparisonKey)) { + seen.add(comparisonKey); + + const name = configuredFolder.name || extUri.basenameOrAuthority(uri); + result.push(new WorkspaceFolder({ uri, name, index: result.length }, configuredFolder)); + } + } + } + + return result; +} + /** * Rewrites the content of a workspace file to be saved at a new location. * Throws an exception if file is not a valid workspace file */ -export function rewriteWorkspaceFileForNewLocation(rawWorkspaceContents: string, configPathURI: URI, isFromUntitledWorkspace: boolean, targetConfigPathURI: URI) { +export function rewriteWorkspaceFileForNewLocation(rawWorkspaceContents: string, configPathURI: URI, isFromUntitledWorkspace: boolean, targetConfigPathURI: URI, extUri: IExtUri) { let storedWorkspace = doParseStoredWorkspace(configPathURI, rawWorkspaceContents); - const sourceConfigFolder = dirname(configPathURI); - const targetConfigFolder = dirname(targetConfigPathURI); + const sourceConfigFolder = extUri.dirname(configPathURI); + const targetConfigFolder = extUri.dirname(targetConfigPathURI); const rewrittenFolders: IStoredWorkspaceFolder[] = []; const slashForPath = useSlashForPath(storedWorkspace.folders); for (const folder of storedWorkspace.folders) { - const folderURI = isRawFileWorkspaceFolder(folder) ? resolvePath(sourceConfigFolder, folder.path) : URI.parse(folder.uri); + const folderURI = isRawFileWorkspaceFolder(folder) ? extUri.resolvePath(sourceConfigFolder, folder.path) : URI.parse(folder.uri); let absolute; if (isFromUntitledWorkspace) { // if it was an untitled workspace, try to make paths relative @@ -274,7 +349,7 @@ export function rewriteWorkspaceFileForNewLocation(rawWorkspaceContents: string, // for existing workspaces, preserve whether a path was absolute or relative absolute = !isRawFileWorkspaceFolder(folder) || isAbsolute(folder.path); } - rewrittenFolders.push(getStoredWorkspaceFolder(folderURI, absolute, folder.name, targetConfigFolder, slashForPath)); + rewrittenFolders.push(getStoredWorkspaceFolder(folderURI, absolute, folder.name, targetConfigFolder, slashForPath, extUri)); } // Preserve as much of the existing workspace as possible by using jsonEdit @@ -308,12 +383,14 @@ function doParseStoredWorkspace(path: URI, contents: string): IStoredWorkspace { export function useSlashForPath(storedFolders: IStoredWorkspaceFolder[]): boolean { if (isWindows) { - return storedFolders.some(folder => isRawFileWorkspaceFolder(folder) && folder.path.indexOf(SLASH) >= 0); + return storedFolders.some(folder => isRawFileWorkspaceFolder(folder) && folder.path.indexOf('/') >= 0); } return true; } +//#endregion + //#region Workspace Storage interface ISerializedRecentlyOpened { diff --git a/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts index 7a5baa225..adef3f004 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts @@ -3,18 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; -import * as arrays from 'vs/base/common/arrays'; +import { localize } from 'vs/nls'; +import { coalesce } from 'vs/base/common/arrays'; import { IStateService } from 'vs/platform/state/node/state'; -import { app, JumpListCategory } from 'electron'; +import { app, JumpListCategory, JumpListItem } from 'electron'; import { ILogService } from 'vs/platform/log/common/log'; import { getBaseLabel, getPathLabel, splitName } from 'vs/base/common/labels'; import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; import { isWindows, isMacintosh } from 'vs/base/common/platform'; -import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IRecentlyOpened, isRecentWorkspace, isRecentFolder, IRecent, isRecentFile, IRecentFolder, IRecentWorkspace, IRecentFile, toStoreData, restoreRecentlyOpened, RecentlyOpenedStorageData, WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; -import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; +import { IWorkspaceIdentifier, IRecentlyOpened, isRecentWorkspace, isRecentFolder, IRecent, isRecentFile, IRecentFolder, IRecentWorkspace, IRecentFile, toStoreData, restoreRecentlyOpened, RecentlyOpenedStorageData, WORKSPACE_EXTENSION, isWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; import { ThrottledDelayer } from 'vs/base/common/async'; -import { isEqual, dirname, originalFSPath, basename, extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; +import { dirname, originalFSPath, basename, extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; @@ -57,15 +57,15 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa declare readonly _serviceBrand: undefined; - private readonly _onRecentlyOpenedChange = new Emitter(); + private readonly _onRecentlyOpenedChange = this._register(new Emitter()); readonly onRecentlyOpenedChange: CommonEvent = this._onRecentlyOpenedChange.event; - private macOSRecentDocumentsUpdater = this._register(new ThrottledDelayer(800)); + private readonly macOSRecentDocumentsUpdater = this._register(new ThrottledDelayer(800)); constructor( @IStateService private readonly stateService: IStateService, @ILogService private readonly logService: ILogService, - @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, + @IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService, @IEnvironmentMainService private readonly environmentService: IEnvironmentMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService ) { @@ -80,7 +80,7 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.handleWindowsJumpList()); // Add to history when entering workspace - this._register(this.workspacesMainService.onWorkspaceEntered(event => this.addRecentlyOpened([{ workspace: event.workspace }]))); + this._register(this.workspacesManagementMainService.onWorkspaceEntered(event => this.addRecentlyOpened([{ workspace: event.workspace }]))); } private handleWindowsJumpList(): void { @@ -89,40 +89,40 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa } this.updateWindowsJumpList(); - this.onRecentlyOpenedChange(() => this.updateWindowsJumpList()); + this._register(this.onRecentlyOpenedChange(() => this.updateWindowsJumpList())); } - addRecentlyOpened(newlyAdded: IRecent[]): void { + addRecentlyOpened(recentToAdd: IRecent[]): void { const workspaces: Array = []; const files: IRecentFile[] = []; - for (let curr of newlyAdded) { + for (let recent of recentToAdd) { // Workspace - if (isRecentWorkspace(curr)) { - if (!this.workspacesMainService.isUntitledWorkspace(curr.workspace) && indexOfWorkspace(workspaces, curr.workspace) === -1) { - workspaces.push(curr); + if (isRecentWorkspace(recent)) { + if (!this.workspacesManagementMainService.isUntitledWorkspace(recent.workspace) && indexOfWorkspace(workspaces, recent.workspace) === -1) { + workspaces.push(recent); } } // Folder - else if (isRecentFolder(curr)) { - if (indexOfFolder(workspaces, curr.folderUri) === -1) { - workspaces.push(curr); + else if (isRecentFolder(recent)) { + if (indexOfFolder(workspaces, recent.folderUri) === -1) { + workspaces.push(recent); } } // File else { - const alreadyExistsInHistory = indexOfFile(files, curr.fileUri) >= 0; - const shouldBeFiltered = curr.fileUri.scheme === Schemas.file && WorkspacesHistoryMainService.COMMON_FILES_FILTER.indexOf(basename(curr.fileUri)) >= 0; + const alreadyExistsInHistory = indexOfFile(files, recent.fileUri) >= 0; + const shouldBeFiltered = recent.fileUri.scheme === Schemas.file && WorkspacesHistoryMainService.COMMON_FILES_FILTER.indexOf(basename(recent.fileUri)) >= 0; if (!alreadyExistsInHistory && !shouldBeFiltered) { - files.push(curr); + files.push(recent); // Add to recent documents (Windows only, macOS later) - if (isWindows && curr.fileUri.scheme === Schemas.file) { - app.addRecentDocument(curr.fileUri.fsPath); + if (isWindows && recent.fileUri.scheme === Schemas.file) { + app.addRecentDocument(recent.fileUri.fsPath); } } } @@ -147,14 +147,15 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa } } - removeRecentlyOpened(toRemove: URI[]): void { + removeRecentlyOpened(recentToRemove: URI[]): void { const keep = (recent: IRecent) => { const uri = location(recent); - for (const resource of toRemove) { - if (isEqual(resource, uri)) { + for (const resourceToRemove of recentToRemove) { + if (extUriBiasedIgnorePathCase.isEqual(resourceToRemove, uri)) { return false; } } + return true; }; @@ -246,13 +247,10 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa // Add current workspace to beginning if set const currentWorkspace = include?.config?.workspace; - if (currentWorkspace && !this.workspacesMainService.isUntitledWorkspace(currentWorkspace)) { + if (isWorkspaceIdentifier(currentWorkspace) && !this.workspacesManagementMainService.isUntitledWorkspace(currentWorkspace)) { workspaces.push({ workspace: currentWorkspace }); - } - - const currentFolder = include?.config?.folderUri; - if (currentFolder) { - workspaces.push({ folderUri: currentFolder }); + } else if (isSingleFolderWorkspaceIdentifier(currentWorkspace)) { + workspaces.push({ folderUri: currentWorkspace.uri }); } // Add currently files to open to the beginning if any @@ -319,8 +317,8 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa items: [ { type: 'task', - title: nls.localize('newWindow', "New Window"), - description: nls.localize('newWindowDesc', "Opens a new window"), + title: localize('newWindow', "New Window"), + description: localize('newWindowDesc', "Opens a new window"), program: process.execPath, args: '-n', // force new window iconPath: process.execPath, @@ -330,57 +328,59 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa }); // Recent Workspaces - try { - if (this.getRecentlyOpened().workspaces.length > 0) { + if (this.getRecentlyOpened().workspaces.length > 0) { - // The user might have meanwhile removed items from the jump list and we have to respect that - // so we need to update our list of recent paths with the choice of the user to not add them again - // Also: Windows will not show our custom category at all if there is any entry which was removed - // by the user! See https://github.com/microsoft/vscode/issues/15052 - let toRemove: URI[] = []; - for (let item of app.getJumpListSettings().removedItems) { - const args = item.args; - if (args) { - const match = /^--(folder|file)-uri\s+"([^"]+)"$/.exec(args); - if (match) { - toRemove.push(URI.parse(match[2])); - } + // The user might have meanwhile removed items from the jump list and we have to respect that + // so we need to update our list of recent paths with the choice of the user to not add them again + // Also: Windows will not show our custom category at all if there is any entry which was removed + // by the user! See https://github.com/microsoft/vscode/issues/15052 + let toRemove: URI[] = []; + for (let item of app.getJumpListSettings().removedItems) { + const args = item.args; + if (args) { + const match = /^--(folder|file)-uri\s+"([^"]+)"$/.exec(args); + if (match) { + toRemove.push(URI.parse(match[2])); } } - this.removeRecentlyOpened(toRemove); + } + this.removeRecentlyOpened(toRemove); - // Add entries + // Add entries + let hasWorkspaces = false; + const items: JumpListItem[] = coalesce(this.getRecentlyOpened().workspaces.slice(0, 7 /* limit number of entries here */).map(recent => { + const workspace = isRecentWorkspace(recent) ? recent.workspace : recent.folderUri; + const title = recent.label ? splitName(recent.label).name : this.getSimpleWorkspaceLabel(workspace, this.environmentService.untitledWorkspacesHome); + + let description; + let args; + if (URI.isUri(workspace)) { + description = localize('folderDesc', "{0} {1}", getBaseLabel(workspace), getPathLabel(dirname(workspace), this.environmentService)); + args = `--folder-uri "${workspace.toString()}"`; + } else { + hasWorkspaces = true; + description = localize('workspaceDesc', "{0} {1}", getBaseLabel(workspace.configPath), getPathLabel(dirname(workspace.configPath), this.environmentService)); + args = `--file-uri "${workspace.configPath.toString()}"`; + } + + return { + type: 'task', + title: title.substr(0, 255), // Windows seems to be picky around the length of entries + description: description.substr(0, 255), // (see https://github.com/microsoft/vscode/issues/111177) + program: process.execPath, + args, + iconPath: 'explorer.exe', // simulate folder icon + iconIndex: 0 + }; + })); + + if (items.length > 0) { jumpList.push({ type: 'custom', - name: nls.localize('recentFolders', "Recent Workspaces"), - items: arrays.coalesce(this.getRecentlyOpened().workspaces.slice(0, 7 /* limit number of entries here */).map(recent => { - const workspace = isRecentWorkspace(recent) ? recent.workspace : recent.folderUri; - const title = recent.label ? splitName(recent.label).name : this.getSimpleWorkspaceLabel(workspace, this.environmentService.untitledWorkspacesHome); - - let description; - let args; - if (isSingleFolderWorkspaceIdentifier(workspace)) { - description = nls.localize('folderDesc', "{0} {1}", getBaseLabel(workspace), getPathLabel(dirname(workspace), this.environmentService)); - args = `--folder-uri "${workspace.toString()}"`; - } else { - description = nls.localize('workspaceDesc', "{0} {1}", getBaseLabel(workspace.configPath), getPathLabel(dirname(workspace.configPath), this.environmentService)); - args = `--file-uri "${workspace.configPath.toString()}"`; - } - - return { - type: 'task', - title, - description, - program: process.execPath, - args, - iconPath: 'explorer.exe', // simulate folder icon - iconIndex: 0 - }; - })) + name: hasWorkspaces ? localize('recentFoldersAndWorkspaces', "Recent Folders & Workspaces") : localize('recentFolders', "Recent Folders"), + items }); } - } catch (error) { - this.logService.warn('updateWindowsJumpList#recentWorkspaces', error); // https://github.com/microsoft/vscode/issues/111177 } // Recent @@ -396,21 +396,24 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa } private getSimpleWorkspaceLabel(workspace: IWorkspaceIdentifier | URI, workspaceHome: URI): string { - if (isSingleFolderWorkspaceIdentifier(workspace)) { + + // Single Folder + if (URI.isUri(workspace)) { return basename(workspace); } // Workspace: Untitled if (extUriBiasedIgnorePathCase.isEqualOrParent(workspace.configPath, workspaceHome)) { - return nls.localize('untitledWorkspace', "Untitled (Workspace)"); + return localize('untitledWorkspace', "Untitled (Workspace)"); } + // Workspace: normal let filename = basename(workspace.configPath); if (filename.endsWith(WORKSPACE_EXTENSION)) { filename = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1); } - return nls.localize('workspaceName', "{0} (Workspace)", filename); + return localize('workspaceName', "{0} (Workspace)", filename); } } @@ -430,10 +433,10 @@ function indexOfWorkspace(arr: IRecent[], candidate: IWorkspaceIdentifier): numb return arr.findIndex(workspace => isRecentWorkspace(workspace) && workspace.workspace.id === candidate.id); } -function indexOfFolder(arr: IRecent[], candidate: ISingleFolderWorkspaceIdentifier): number { - return arr.findIndex(folder => isRecentFolder(folder) && isEqual(folder.folderUri, candidate)); +function indexOfFolder(arr: IRecent[], candidate: URI): number { + return arr.findIndex(folder => isRecentFolder(folder) && extUriBiasedIgnorePathCase.isEqual(folder.folderUri, candidate)); } function indexOfFile(arr: IRecentFile[], candidate: URI): number { - return arr.findIndex(file => isEqual(file.fileUri, candidate)); + return arr.findIndex(file => extUriBiasedIgnorePathCase.isEqual(file.fileUri, candidate)); } diff --git a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts index f4fa065fb..5328a07dd 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts @@ -3,337 +3,79 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IUntitledWorkspaceInfo, getStoredWorkspaceFolder, IEnterWorkspaceResult, isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces'; -import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; -import { join, dirname } from 'vs/base/common/path'; -import { mkdirp, writeFile, rimrafSync, readdirSync, writeFileSync } from 'vs/base/node/pfs'; -import { readFileSync, existsSync, mkdirSync } from 'fs'; -import { isLinux } from 'vs/base/common/platform'; -import { Event, Emitter } from 'vs/base/common/event'; -import { ILogService } from 'vs/platform/log/common/log'; -import { createHash } from 'crypto'; -import * as json from 'vs/base/common/json'; -import { toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; +import { AddFirstParameterToFunctions } from 'vs/base/common/types'; +import { IWorkspacesService, IEnterWorkspaceResult, IWorkspaceFolderCreationData, IWorkspaceIdentifier, IRecentlyOpened, IRecent } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; -import { Schemas } from 'vs/base/common/network'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { originalFSPath, joinPath, basename, extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ICodeWindow } from 'vs/platform/windows/electron-main/windows'; -import { localize } from 'vs/nls'; -import product from 'vs/platform/product/common/product'; -import { MessageBoxOptions, BrowserWindow } from 'electron'; -import { withNullAsUndefined } from 'vs/base/common/types'; +import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; +import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; +import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; -import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; -import { findWindowOnWorkspace } from 'vs/platform/windows/node/window'; -export const IWorkspacesMainService = createDecorator('workspacesMainService'); - -export interface IWorkspaceEnteredEvent { - window: ICodeWindow; - workspace: IWorkspaceIdentifier; -} - -export interface IWorkspacesMainService { - - readonly _serviceBrand: undefined; - - readonly onUntitledWorkspaceDeleted: Event; - readonly onWorkspaceEntered: Event; - - enterWorkspace(intoWindow: ICodeWindow, openedWindows: ICodeWindow[], path: URI): Promise; - - createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise; - createUntitledWorkspaceSync(folders?: IWorkspaceFolderCreationData[]): IWorkspaceIdentifier; - - deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise; - deleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void; - - getUntitledWorkspacesSync(): IUntitledWorkspaceInfo[]; - isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean; - - resolveLocalWorkspaceSync(path: URI): IResolvedWorkspace | null; - getWorkspaceIdentifier(workspacePath: URI): Promise; -} - -export interface IStoredWorkspace { - folders: IStoredWorkspaceFolder[]; - remoteAuthority?: string; -} - -export class WorkspacesMainService extends Disposable implements IWorkspacesMainService { +export class WorkspacesMainService implements AddFirstParameterToFunctions /* only methods, not events */, number /* window ID */> { declare readonly _serviceBrand: undefined; - private readonly untitledWorkspacesHome: URI; // local URI that contains all untitled workspaces - - private readonly _onUntitledWorkspaceDeleted = this._register(new Emitter()); - readonly onUntitledWorkspaceDeleted: Event = this._onUntitledWorkspaceDeleted.event; - - private readonly _onWorkspaceEntered = this._register(new Emitter()); - readonly onWorkspaceEntered: Event = this._onWorkspaceEntered.event; - constructor( - @IEnvironmentMainService private readonly environmentService: IEnvironmentMainService, - @ILogService private readonly logService: ILogService, - @IBackupMainService private readonly backupMainService: IBackupMainService, - @IDialogMainService private readonly dialogMainService: IDialogMainService + @IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService, + @IWindowsMainService private readonly windowsMainService: IWindowsMainService, + @IWorkspacesHistoryMainService private readonly workspacesHistoryMainService: IWorkspacesHistoryMainService, + @IBackupMainService private readonly backupMainService: IBackupMainService ) { - super(); - - this.untitledWorkspacesHome = environmentService.untitledWorkspacesHome; } - resolveLocalWorkspaceSync(uri: URI): IResolvedWorkspace | null { - if (!this.isWorkspacePath(uri)) { - return null; // does not look like a valid workspace config file - } - if (uri.scheme !== Schemas.file) { - return null; - } + //#region Workspace Management - let contents: string; - try { - contents = readFileSync(uri.fsPath, 'utf8'); - } catch (error) { - return null; // invalid workspace - } - - return this.doResolveWorkspace(uri, contents); - } - - private isWorkspacePath(uri: URI): boolean { - return isUntitledWorkspace(uri, this.environmentService) || hasWorkspaceFileExtension(uri); - } - - private doResolveWorkspace(path: URI, contents: string): IResolvedWorkspace | null { - try { - const workspace = this.doParseStoredWorkspace(path, contents); - const workspaceIdentifier = getWorkspaceIdentifier(path); - return { - id: workspaceIdentifier.id, - configPath: workspaceIdentifier.configPath, - folders: toWorkspaceFolders(workspace.folders, workspaceIdentifier.configPath), - remoteAuthority: workspace.remoteAuthority - }; - } catch (error) { - this.logService.warn(error.toString()); + async enterWorkspace(windowId: number, path: URI): Promise { + const window = this.windowsMainService.getWindowById(windowId); + if (window) { + return this.workspacesManagementMainService.enterWorkspace(window, this.windowsMainService.getWindows(), path); } return null; } - private doParseStoredWorkspace(path: URI, contents: string): IStoredWorkspace { - - // Parse workspace file - let storedWorkspace: IStoredWorkspace = json.parse(contents); // use fault tolerant parser - - // Filter out folders which do not have a path or uri set - if (storedWorkspace && Array.isArray(storedWorkspace.folders)) { - storedWorkspace.folders = storedWorkspace.folders.filter(folder => isStoredWorkspaceFolder(folder)); - } else { - throw new Error(`${path.toString(true)} looks like an invalid workspace file.`); - } - - return storedWorkspace; + createUntitledWorkspace(windowId: number, folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise { + return this.workspacesManagementMainService.createUntitledWorkspace(folders, remoteAuthority); } - async createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise { - const { workspace, storedWorkspace } = this.newUntitledWorkspace(folders, remoteAuthority); - const configPath = workspace.configPath.fsPath; - - await mkdirp(dirname(configPath)); - await writeFile(configPath, JSON.stringify(storedWorkspace, null, '\t')); - - return workspace; + deleteUntitledWorkspace(windowId: number, workspace: IWorkspaceIdentifier): Promise { + return this.workspacesManagementMainService.deleteUntitledWorkspace(workspace); } - createUntitledWorkspaceSync(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): IWorkspaceIdentifier { - const { workspace, storedWorkspace } = this.newUntitledWorkspace(folders, remoteAuthority); - const configPath = workspace.configPath.fsPath; - - const configPathDir = dirname(configPath); - if (!existsSync(configPathDir)) { - const configPathDirDir = dirname(configPathDir); - if (!existsSync(configPathDirDir)) { - mkdirSync(configPathDirDir); - } - mkdirSync(configPathDir); - } - - writeFileSync(configPath, JSON.stringify(storedWorkspace, null, '\t')); - - return workspace; + getWorkspaceIdentifier(windowId: number, workspacePath: URI): Promise { + return this.workspacesManagementMainService.getWorkspaceIdentifier(workspacePath); } - private newUntitledWorkspace(folders: IWorkspaceFolderCreationData[] = [], remoteAuthority?: string): { workspace: IWorkspaceIdentifier, storedWorkspace: IStoredWorkspace } { - const randomId = (Date.now() + Math.round(Math.random() * 1000)).toString(); - const untitledWorkspaceConfigFolder = joinPath(this.untitledWorkspacesHome, randomId); - const untitledWorkspaceConfigPath = joinPath(untitledWorkspaceConfigFolder, UNTITLED_WORKSPACE_NAME); + //#endregion - const storedWorkspaceFolder: IStoredWorkspaceFolder[] = []; + //#region Workspaces History - for (const folder of folders) { - storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, true, folder.name, untitledWorkspaceConfigFolder)); - } + readonly onRecentlyOpenedChange = this.workspacesHistoryMainService.onRecentlyOpenedChange; - return { - workspace: getWorkspaceIdentifier(untitledWorkspaceConfigPath), - storedWorkspace: { folders: storedWorkspaceFolder, remoteAuthority } - }; + async getRecentlyOpened(windowId: number): Promise { + return this.workspacesHistoryMainService.getRecentlyOpened(this.windowsMainService.getWindowById(windowId)); } - async getWorkspaceIdentifier(configPath: URI): Promise { - return getWorkspaceIdentifier(configPath); + async addRecentlyOpened(windowId: number, recents: IRecent[]): Promise { + return this.workspacesHistoryMainService.addRecentlyOpened(recents); } - isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean { - return isUntitledWorkspace(workspace.configPath, this.environmentService); + async removeRecentlyOpened(windowId: number, paths: URI[]): Promise { + return this.workspacesHistoryMainService.removeRecentlyOpened(paths); } - deleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void { - if (!this.isUntitledWorkspace(workspace)) { - return; // only supported for untitled workspaces - } - - // Delete from disk - this.doDeleteUntitledWorkspaceSync(workspace); - - // Event - this._onUntitledWorkspaceDeleted.fire(workspace); + async clearRecentlyOpened(windowId: number): Promise { + return this.workspacesHistoryMainService.clearRecentlyOpened(); } - async deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise { - this.deleteUntitledWorkspaceSync(workspace); + //#endregion + + + //#region Dirty Workspaces + + async getDirtyWorkspaces(): Promise> { + return this.backupMainService.getDirtyWorkspaces(); } - private doDeleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void { - const configPath = originalFSPath(workspace.configPath); - try { - - // Delete Workspace - rimrafSync(dirname(configPath)); - - // Mark Workspace Storage to be deleted - const workspaceStoragePath = join(this.environmentService.workspaceStorageHome.fsPath, workspace.id); - if (existsSync(workspaceStoragePath)) { - writeFileSync(join(workspaceStoragePath, 'obsolete'), ''); - } - } catch (error) { - this.logService.warn(`Unable to delete untitled workspace ${configPath} (${error}).`); - } - } - - getUntitledWorkspacesSync(): IUntitledWorkspaceInfo[] { - let untitledWorkspaces: IUntitledWorkspaceInfo[] = []; - try { - const untitledWorkspacePaths = readdirSync(this.untitledWorkspacesHome.fsPath).map(folder => joinPath(this.untitledWorkspacesHome, folder, UNTITLED_WORKSPACE_NAME)); - for (const untitledWorkspacePath of untitledWorkspacePaths) { - const workspace = getWorkspaceIdentifier(untitledWorkspacePath); - const resolvedWorkspace = this.resolveLocalWorkspaceSync(untitledWorkspacePath); - if (!resolvedWorkspace) { - this.doDeleteUntitledWorkspaceSync(workspace); - } else { - untitledWorkspaces.push({ workspace, remoteAuthority: resolvedWorkspace.remoteAuthority }); - } - } - } catch (error) { - if (error.code !== 'ENOENT') { - this.logService.warn(`Unable to read folders in ${this.untitledWorkspacesHome} (${error}).`); - } - } - return untitledWorkspaces; - } - - async enterWorkspace(window: ICodeWindow, windows: ICodeWindow[], path: URI): Promise { - if (!window || !window.win || !window.isReady) { - return null; // return early if the window is not ready or disposed - } - - const isValid = await this.isValidTargetWorkspacePath(window, windows, path); - if (!isValid) { - return null; // return early if the workspace is not valid - } - - const result = this.doEnterWorkspace(window, getWorkspaceIdentifier(path)); - if (!result) { - return null; - } - - // Emit as event - this._onWorkspaceEntered.fire({ window, workspace: result.workspace }); - - return result; - } - - private async isValidTargetWorkspacePath(window: ICodeWindow, windows: ICodeWindow[], path?: URI): Promise { - if (!path) { - return true; - } - - if (window.openedWorkspace && extUriBiasedIgnorePathCase.isEqual(window.openedWorkspace.configPath, path)) { - return false; // window is already opened on a workspace with that path - } - - // Prevent overwriting a workspace that is currently opened in another window - if (findWindowOnWorkspace(windows, getWorkspaceIdentifier(path))) { - const options: MessageBoxOptions = { - title: product.nameLong, - type: 'info', - buttons: [localize('ok', "OK")], - message: localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(path)), - detail: localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again."), - noLink: true - }; - - await this.dialogMainService.showMessageBox(options, withNullAsUndefined(BrowserWindow.getFocusedWindow())); - - return false; - } - - return true; // OK - } - - private doEnterWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult | null { - if (!window.config) { - return null; - } - - window.focus(); - - // Register window for backups and migrate current backups over - let backupPath: string | undefined; - if (!window.config.extensionDevelopmentPath) { - backupPath = this.backupMainService.registerWorkspaceBackupSync({ workspace, remoteAuthority: window.remoteAuthority }, window.config.backupPath); - } - - // if the window was opened on an untitled workspace, delete it. - if (window.openedWorkspace && this.isUntitledWorkspace(window.openedWorkspace)) { - this.deleteUntitledWorkspaceSync(window.openedWorkspace); - } - - // Update window configuration properly based on transition to workspace - window.config.folderUri = undefined; - window.config.workspace = workspace; - window.config.backupPath = backupPath; - - return { workspace, backupPath }; - } -} - -function getWorkspaceId(configPath: URI): string { - let workspaceConfigPath = configPath.scheme === Schemas.file ? originalFSPath(configPath) : configPath.toString(); - if (!isLinux) { - workspaceConfigPath = workspaceConfigPath.toLowerCase(); // sanitize for platform file system - } - - return createHash('md5').update(workspaceConfigPath).digest('hex'); -} - -export function getWorkspaceIdentifier(configPath: URI): IWorkspaceIdentifier { - return { - configPath, - id: getWorkspaceId(configPath) - }; + //#endregion } diff --git a/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts new file mode 100644 index 000000000..306d1daa5 --- /dev/null +++ b/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts @@ -0,0 +1,394 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { toWorkspaceFolders, IWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IUntitledWorkspaceInfo, getStoredWorkspaceFolder, IEnterWorkspaceResult, isUntitledWorkspace, isWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; +import { join, dirname } from 'vs/base/common/path'; +import { mkdirp, writeFile, rimrafSync, readdirSync, writeFileSync } from 'vs/base/node/pfs'; +import { readFileSync, existsSync, mkdirSync, statSync, Stats } from 'fs'; +import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; +import { Event, Emitter } from 'vs/base/common/event'; +import { ILogService } from 'vs/platform/log/common/log'; +import { createHash } from 'crypto'; +import { parse } from 'vs/base/common/json'; +import { URI } from 'vs/base/common/uri'; +import { Schemas } from 'vs/base/common/network'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { originalFSPath, joinPath, basename, extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ICodeWindow } from 'vs/platform/windows/electron-main/windows'; +import { localize } from 'vs/nls'; +import product from 'vs/platform/product/common/product'; +import { MessageBoxOptions, BrowserWindow } from 'electron'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; +import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService'; +import { findWindowOnWorkspaceOrFolder } from 'vs/platform/windows/electron-main/windowsFinder'; + +export const IWorkspacesManagementMainService = createDecorator('workspacesManagementMainService'); + +export interface IWorkspaceEnteredEvent { + window: ICodeWindow; + workspace: IWorkspaceIdentifier; +} + +export interface IWorkspacesManagementMainService { + + readonly _serviceBrand: undefined; + + readonly onUntitledWorkspaceDeleted: Event; + readonly onWorkspaceEntered: Event; + + enterWorkspace(intoWindow: ICodeWindow, openedWindows: ICodeWindow[], path: URI): Promise; + + createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise; + createUntitledWorkspaceSync(folders?: IWorkspaceFolderCreationData[]): IWorkspaceIdentifier; + + deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise; + deleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void; + + getUntitledWorkspacesSync(): IUntitledWorkspaceInfo[]; + isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean; + + resolveLocalWorkspaceSync(path: URI): IResolvedWorkspace | null; + getWorkspaceIdentifier(workspacePath: URI): Promise; +} + +export interface IStoredWorkspace { + folders: IStoredWorkspaceFolder[]; + remoteAuthority?: string; +} + +export class WorkspacesManagementMainService extends Disposable implements IWorkspacesManagementMainService { + + declare readonly _serviceBrand: undefined; + + private readonly untitledWorkspacesHome = this.environmentService.untitledWorkspacesHome; // local URI that contains all untitled workspaces + + private readonly _onUntitledWorkspaceDeleted = this._register(new Emitter()); + readonly onUntitledWorkspaceDeleted: Event = this._onUntitledWorkspaceDeleted.event; + + private readonly _onWorkspaceEntered = this._register(new Emitter()); + readonly onWorkspaceEntered: Event = this._onWorkspaceEntered.event; + + constructor( + @IEnvironmentMainService private readonly environmentService: IEnvironmentMainService, + @ILogService private readonly logService: ILogService, + @IBackupMainService private readonly backupMainService: IBackupMainService, + @IDialogMainService private readonly dialogMainService: IDialogMainService + ) { + super(); + } + + resolveLocalWorkspaceSync(uri: URI): IResolvedWorkspace | null { + if (!this.isWorkspacePath(uri)) { + return null; // does not look like a valid workspace config file + } + if (uri.scheme !== Schemas.file) { + return null; + } + + let contents: string; + try { + contents = readFileSync(uri.fsPath, 'utf8'); + } catch (error) { + return null; // invalid workspace + } + + return this.doResolveWorkspace(uri, contents); + } + + private isWorkspacePath(uri: URI): boolean { + return isUntitledWorkspace(uri, this.environmentService) || hasWorkspaceFileExtension(uri); + } + + private doResolveWorkspace(path: URI, contents: string): IResolvedWorkspace | null { + try { + const workspace = this.doParseStoredWorkspace(path, contents); + const workspaceIdentifier = getWorkspaceIdentifier(path); + return { + id: workspaceIdentifier.id, + configPath: workspaceIdentifier.configPath, + folders: toWorkspaceFolders(workspace.folders, workspaceIdentifier.configPath, extUriBiasedIgnorePathCase), + remoteAuthority: workspace.remoteAuthority + }; + } catch (error) { + this.logService.warn(error.toString()); + } + + return null; + } + + private doParseStoredWorkspace(path: URI, contents: string): IStoredWorkspace { + + // Parse workspace file + const storedWorkspace: IStoredWorkspace = parse(contents); // use fault tolerant parser + + // Filter out folders which do not have a path or uri set + if (storedWorkspace && Array.isArray(storedWorkspace.folders)) { + storedWorkspace.folders = storedWorkspace.folders.filter(folder => isStoredWorkspaceFolder(folder)); + } else { + throw new Error(`${path.toString(true)} looks like an invalid workspace file.`); + } + + return storedWorkspace; + } + + async createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise { + const { workspace, storedWorkspace } = this.newUntitledWorkspace(folders, remoteAuthority); + const configPath = workspace.configPath.fsPath; + + await mkdirp(dirname(configPath)); + await writeFile(configPath, JSON.stringify(storedWorkspace, null, '\t')); + + return workspace; + } + + createUntitledWorkspaceSync(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): IWorkspaceIdentifier { + const { workspace, storedWorkspace } = this.newUntitledWorkspace(folders, remoteAuthority); + const configPath = workspace.configPath.fsPath; + + const configPathDir = dirname(configPath); + if (!existsSync(configPathDir)) { + const configPathDirDir = dirname(configPathDir); + if (!existsSync(configPathDirDir)) { + mkdirSync(configPathDirDir); + } + mkdirSync(configPathDir); + } + + writeFileSync(configPath, JSON.stringify(storedWorkspace, null, '\t')); + + return workspace; + } + + private newUntitledWorkspace(folders: IWorkspaceFolderCreationData[] = [], remoteAuthority?: string): { workspace: IWorkspaceIdentifier, storedWorkspace: IStoredWorkspace } { + const randomId = (Date.now() + Math.round(Math.random() * 1000)).toString(); + const untitledWorkspaceConfigFolder = joinPath(this.untitledWorkspacesHome, randomId); + const untitledWorkspaceConfigPath = joinPath(untitledWorkspaceConfigFolder, UNTITLED_WORKSPACE_NAME); + + const storedWorkspaceFolder: IStoredWorkspaceFolder[] = []; + + for (const folder of folders) { + storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, true, folder.name, untitledWorkspaceConfigFolder, !isWindows, extUriBiasedIgnorePathCase)); + } + + return { + workspace: getWorkspaceIdentifier(untitledWorkspaceConfigPath), + storedWorkspace: { folders: storedWorkspaceFolder, remoteAuthority } + }; + } + + async getWorkspaceIdentifier(configPath: URI): Promise { + return getWorkspaceIdentifier(configPath); + } + + isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean { + return isUntitledWorkspace(workspace.configPath, this.environmentService); + } + + deleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void { + if (!this.isUntitledWorkspace(workspace)) { + return; // only supported for untitled workspaces + } + + // Delete from disk + this.doDeleteUntitledWorkspaceSync(workspace); + + // Event + this._onUntitledWorkspaceDeleted.fire(workspace); + } + + async deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise { + this.deleteUntitledWorkspaceSync(workspace); + } + + private doDeleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void { + const configPath = originalFSPath(workspace.configPath); + try { + + // Delete Workspace + rimrafSync(dirname(configPath)); + + // Mark Workspace Storage to be deleted + const workspaceStoragePath = join(this.environmentService.workspaceStorageHome.fsPath, workspace.id); + if (existsSync(workspaceStoragePath)) { + writeFileSync(join(workspaceStoragePath, 'obsolete'), ''); + } + } catch (error) { + this.logService.warn(`Unable to delete untitled workspace ${configPath} (${error}).`); + } + } + + getUntitledWorkspacesSync(): IUntitledWorkspaceInfo[] { + const untitledWorkspaces: IUntitledWorkspaceInfo[] = []; + try { + const untitledWorkspacePaths = readdirSync(this.untitledWorkspacesHome.fsPath).map(folder => joinPath(this.untitledWorkspacesHome, folder, UNTITLED_WORKSPACE_NAME)); + for (const untitledWorkspacePath of untitledWorkspacePaths) { + const workspace = getWorkspaceIdentifier(untitledWorkspacePath); + const resolvedWorkspace = this.resolveLocalWorkspaceSync(untitledWorkspacePath); + if (!resolvedWorkspace) { + this.doDeleteUntitledWorkspaceSync(workspace); + } else { + untitledWorkspaces.push({ workspace, remoteAuthority: resolvedWorkspace.remoteAuthority }); + } + } + } catch (error) { + if (error.code !== 'ENOENT') { + this.logService.warn(`Unable to read folders in ${this.untitledWorkspacesHome} (${error}).`); + } + } + + return untitledWorkspaces; + } + + async enterWorkspace(window: ICodeWindow, windows: ICodeWindow[], path: URI): Promise { + if (!window || !window.win || !window.isReady) { + return null; // return early if the window is not ready or disposed + } + + const isValid = await this.isValidTargetWorkspacePath(window, windows, path); + if (!isValid) { + return null; // return early if the workspace is not valid + } + + const result = this.doEnterWorkspace(window, getWorkspaceIdentifier(path)); + if (!result) { + return null; + } + + // Emit as event + this._onWorkspaceEntered.fire({ window, workspace: result.workspace }); + + return result; + } + + private async isValidTargetWorkspacePath(window: ICodeWindow, windows: ICodeWindow[], workspacePath?: URI): Promise { + if (!workspacePath) { + return true; + } + + if (isWorkspaceIdentifier(window.openedWorkspace) && extUriBiasedIgnorePathCase.isEqual(window.openedWorkspace.configPath, workspacePath)) { + return false; // window is already opened on a workspace with that path + } + + // Prevent overwriting a workspace that is currently opened in another window + if (findWindowOnWorkspaceOrFolder(windows, workspacePath)) { + const options: MessageBoxOptions = { + title: product.nameLong, + type: 'info', + buttons: [localize('ok', "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."), + noLink: true + }; + + await this.dialogMainService.showMessageBox(options, withNullAsUndefined(BrowserWindow.getFocusedWindow())); + + return false; + } + + return true; // OK + } + + private doEnterWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult | null { + if (!window.config) { + return null; + } + + window.focus(); + + // Register window for backups and migrate current backups over + let backupPath: string | undefined; + if (!window.config.extensionDevelopmentPath) { + backupPath = this.backupMainService.registerWorkspaceBackupSync({ workspace, remoteAuthority: window.remoteAuthority }, window.config.backupPath); + } + + // if the window was opened on an untitled workspace, delete it. + if (isWorkspaceIdentifier(window.openedWorkspace) && this.isUntitledWorkspace(window.openedWorkspace)) { + this.deleteUntitledWorkspaceSync(window.openedWorkspace); + } + + // Update window configuration properly based on transition to workspace + window.config.workspace = workspace; + window.config.backupPath = backupPath; + + return { workspace, backupPath }; + } +} + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// NOTE: DO NOT CHANGE. IDENTIFIERS HAVE TO REMAIN STABLE +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +export function getWorkspaceIdentifier(configPath: URI): IWorkspaceIdentifier { + + function getWorkspaceId(): string { + let configPathStr = configPath.scheme === Schemas.file ? originalFSPath(configPath) : configPath.toString(); + if (!isLinux) { + configPathStr = configPathStr.toLowerCase(); // sanitize for platform file system + } + + return createHash('md5').update(configPathStr).digest('hex'); + } + + return { + id: getWorkspaceId(), + configPath + }; +} + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// NOTE: DO NOT CHANGE. IDENTIFIERS HAVE TO REMAIN STABLE +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +export function getSingleFolderWorkspaceIdentifier(folderUri: URI): ISingleFolderWorkspaceIdentifier | undefined; +export function getSingleFolderWorkspaceIdentifier(folderUri: URI, folderStat: Stats): ISingleFolderWorkspaceIdentifier; +export function getSingleFolderWorkspaceIdentifier(folderUri: URI, folderStat?: Stats): ISingleFolderWorkspaceIdentifier | undefined { + + function getFolderId(): string | undefined { + + // Remote: produce a hash from the entire URI + if (folderUri.scheme !== Schemas.file) { + return createHash('md5').update(folderUri.toString()).digest('hex'); + } + + // Local: produce a hash from the path and include creation time as salt + if (!folderStat) { + try { + folderStat = statSync(folderUri.fsPath); + } catch (error) { + return undefined; // folder does not exist + } + } + + let ctime: number | undefined; + if (isLinux) { + ctime = folderStat.ino; // Linux: birthtime is ctime, so we cannot use it! We use the ino instead! + } else if (isMacintosh) { + ctime = folderStat.birthtime.getTime(); // macOS: birthtime is fine to use as is + } else if (isWindows) { + if (typeof folderStat.birthtimeMs === 'number') { + ctime = Math.floor(folderStat.birthtimeMs); // Windows: fix precision issue in node.js 8.x to get 7.x results (see https://github.com/nodejs/node/issues/19897) + } else { + ctime = folderStat.birthtime.getTime(); + } + } + + // we use the ctime as extra salt to the ID so that we catch the case of a folder getting + // deleted and recreated. in that case we do not want to carry over previous state + return createHash('md5').update(folderUri.fsPath).update(ctime ? String(ctime) : '').digest('hex'); + } + + const folderId = getFolderId(); + if (typeof folderId === 'string') { + return { + id: folderId, + uri: folderUri + }; + } + + return undefined; // invalid folder +} diff --git a/src/vs/platform/workspaces/electron-main/workspacesService.ts b/src/vs/platform/workspaces/electron-main/workspacesService.ts deleted file mode 100644 index 8d1d22d9e..000000000 --- a/src/vs/platform/workspaces/electron-main/workspacesService.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 { AddFirstParameterToFunctions } from 'vs/base/common/types'; -import { IWorkspacesService, IEnterWorkspaceResult, IWorkspaceFolderCreationData, IWorkspaceIdentifier, IRecentlyOpened, IRecent } from 'vs/platform/workspaces/common/workspaces'; -import { URI } from 'vs/base/common/uri'; -import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; -import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; -import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; -import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; - -export class WorkspacesService implements AddFirstParameterToFunctions /* only methods, not events */, number /* window ID */> { - - declare readonly _serviceBrand: undefined; - - constructor( - @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, - @IWindowsMainService private readonly windowsMainService: IWindowsMainService, - @IWorkspacesHistoryMainService private readonly workspacesHistoryMainService: IWorkspacesHistoryMainService, - @IBackupMainService private readonly backupMainService: IBackupMainService - ) { - } - - //#region Workspace Management - - async enterWorkspace(windowId: number, path: URI): Promise { - const window = this.windowsMainService.getWindowById(windowId); - if (window) { - return this.workspacesMainService.enterWorkspace(window, this.windowsMainService.getWindows(), path); - } - - return null; - } - - createUntitledWorkspace(windowId: number, folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise { - return this.workspacesMainService.createUntitledWorkspace(folders, remoteAuthority); - } - - deleteUntitledWorkspace(windowId: number, workspace: IWorkspaceIdentifier): Promise { - return this.workspacesMainService.deleteUntitledWorkspace(workspace); - } - - getWorkspaceIdentifier(windowId: number, workspacePath: URI): Promise { - return this.workspacesMainService.getWorkspaceIdentifier(workspacePath); - } - - //#endregion - - //#region Workspaces History - - readonly onRecentlyOpenedChange = this.workspacesHistoryMainService.onRecentlyOpenedChange; - - async getRecentlyOpened(windowId: number): Promise { - return this.workspacesHistoryMainService.getRecentlyOpened(this.windowsMainService.getWindowById(windowId)); - } - - async addRecentlyOpened(windowId: number, recents: IRecent[]): Promise { - return this.workspacesHistoryMainService.addRecentlyOpened(recents); - } - - async removeRecentlyOpened(windowId: number, paths: URI[]): Promise { - return this.workspacesHistoryMainService.removeRecentlyOpened(paths); - } - - async clearRecentlyOpened(windowId: number): Promise { - return this.workspacesHistoryMainService.clearRecentlyOpened(); - } - - //#endregion - - - //#region Dirty Workspaces - - async getDirtyWorkspaces(): Promise> { - return this.backupMainService.getDirtyWorkspaces(); - } - - //#endregion -} diff --git a/src/vs/platform/workspaces/test/common/workspaces.test.ts b/src/vs/platform/workspaces/test/common/workspaces.test.ts new file mode 100644 index 000000000..2c0a75656 --- /dev/null +++ b/src/vs/platform/workspaces/test/common/workspaces.test.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { URI } from 'vs/base/common/uri'; +import { hasWorkspaceFileExtension, toWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; + +suite('Workspaces', () => { + test('hasWorkspaceFileExtension', () => { + assert.strictEqual(hasWorkspaceFileExtension('something'), false); + assert.strictEqual(hasWorkspaceFileExtension('something.code-workspace'), true); + }); + + test('toWorkspaceIdentifier', () => { + let identifier = toWorkspaceIdentifier({ id: 'id', folders: [] }); + assert.ok(!identifier); + assert.ok(!isSingleFolderWorkspaceIdentifier(identifier)); + assert.ok(!isWorkspaceIdentifier(identifier)); + + identifier = toWorkspaceIdentifier({ id: 'id', folders: [{ index: 0, name: 'test', toResource: () => URI.file('test'), uri: URI.file('test') }] }); + assert.ok(identifier); + assert.ok(isSingleFolderWorkspaceIdentifier(identifier)); + assert.ok(!isWorkspaceIdentifier(identifier)); + + identifier = toWorkspaceIdentifier({ id: 'id', configuration: URI.file('test.code-workspace'), folders: [] }); + assert.ok(identifier); + assert.ok(!isSingleFolderWorkspaceIdentifier(identifier)); + assert.ok(isWorkspaceIdentifier(identifier)); + }); +}); diff --git a/src/vs/platform/workspaces/test/electron-main/workspacesHistoryStorage.test.ts b/src/vs/platform/workspaces/test/electron-main/workspacesHistoryStorage.test.ts index c16373206..6f5027448 100644 --- a/src/vs/platform/workspaces/test/electron-main/workspacesHistoryStorage.test.ts +++ b/src/vs/platform/workspaces/test/electron-main/workspacesHistoryStorage.test.ts @@ -17,25 +17,25 @@ function toWorkspace(uri: URI): IWorkspaceIdentifier { }; } function assertEqualURI(u1: URI | undefined, u2: URI | undefined, message?: string): void { - assert.equal(u1 && u1.toString(), u2 && u2.toString(), message); + assert.strictEqual(u1 && u1.toString(), u2 && u2.toString(), message); } function assertEqualWorkspace(w1: IWorkspaceIdentifier | undefined, w2: IWorkspaceIdentifier | undefined, message?: string): void { if (!w1 || !w2) { - assert.equal(w1, w2, message); + assert.strictEqual(w1, w2, message); return; } - assert.equal(w1.id, w2.id, message); + assert.strictEqual(w1.id, w2.id, message); assertEqualURI(w1.configPath, w2.configPath, message); } function assertEqualRecentlyOpened(actual: IRecentlyOpened, expected: IRecentlyOpened, message?: string) { - assert.equal(actual.files.length, expected.files.length, message); + assert.strictEqual(actual.files.length, expected.files.length, message); for (let i = 0; i < actual.files.length; i++) { assertEqualURI(actual.files[i].fileUri, expected.files[i].fileUri, message); - assert.equal(actual.files[i].label, expected.files[i].label); + assert.strictEqual(actual.files[i].label, expected.files[i].label); } - assert.equal(actual.workspaces.length, expected.workspaces.length, message); + assert.strictEqual(actual.workspaces.length, expected.workspaces.length, message); for (let i = 0; i < actual.workspaces.length; i++) { let expectedRecent = expected.workspaces[i]; let actualRecent = actual.workspaces[i]; @@ -44,7 +44,7 @@ function assertEqualRecentlyOpened(actual: IRecentlyOpened, expected: IRecentlyO } else { assertEqualWorkspace(actualRecent.workspace, (expectedRecent).workspace, message); } - assert.equal(actualRecent.label, expectedRecent.label); + assert.strictEqual(actualRecent.label, expectedRecent.label); } } @@ -129,8 +129,5 @@ suite('History Storage', () => { }; assertEqualRecentlyOpened(windowsState, expected, 'v1_33'); - }); - - }); diff --git a/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts b/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts similarity index 77% rename from src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts rename to src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts index 07304aa32..2cbc34f48 100644 --- a/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts +++ b/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts @@ -10,15 +10,15 @@ import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; import { EnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; -import { WorkspacesMainService, IStoredWorkspace } from 'vs/platform/workspaces/electron-main/workspacesMainService'; +import { WorkspacesManagementMainService, IStoredWorkspace, getSingleFolderWorkspaceIdentifier, getWorkspaceIdentifier } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; import { WORKSPACE_EXTENSION, IRawFileWorkspaceFolder, IWorkspaceFolderCreationData, IRawUriWorkspaceFolder, rewriteWorkspaceFileForNewLocation, IWorkspaceIdentifier, IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; import { NullLogService } from 'vs/platform/log/common/log'; import { URI } from 'vs/base/common/uri'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { isWindows } from 'vs/base/common/platform'; import { normalizeDriveLetter } from 'vs/base/common/labels'; -import { dirname, joinPath } from 'vs/base/common/resources'; -import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; +import { dirname, extUriBiasedIgnorePathCase, joinPath } from 'vs/base/common/resources'; +import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; import { IBackupMainService, IWorkspaceBackupInfo } from 'vs/platform/backup/electron-main/backup'; import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; @@ -104,15 +104,7 @@ export class TestBackupMainService implements IBackupMainService { } } -suite('WorkspacesMainService', () => { - const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'workspacesservice'); - const untitledWorkspacesHomePath = path.join(parentDir, 'Workspaces'); - - class TestEnvironmentService extends EnvironmentMainService { - get untitledWorkspacesHome(): URI { - return URI.file(untitledWorkspacesHomePath); - } - } +suite('WorkspacesManagementMainService', () => { function createUntitledWorkspace(folders: string[], names?: string[]) { return service.createUntitledWorkspace(folders.map((folder, index) => ({ uri: URI.file(folder), name: names ? names[index] : undefined } as IWorkspaceFolderCreationData))); @@ -138,22 +130,31 @@ suite('WorkspacesMainService', () => { return service.createUntitledWorkspaceSync(folders.map((folder, index) => ({ uri: URI.file(folder), name: names ? names[index] : undefined } as IWorkspaceFolderCreationData))); } - const environmentService = new TestEnvironmentService(parseArgs(process.argv, OPTIONS)); - const logService = new NullLogService(); - - let service: WorkspacesMainService; + let testDir: string; + let untitledWorkspacesHomePath: string; + let environmentService: EnvironmentMainService; + let service: WorkspacesManagementMainService; setup(async () => { - service = new WorkspacesMainService(environmentService, logService, new TestBackupMainService(), new TestDialogMainService()); + testDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'workspacesmanagementmainservice'); + untitledWorkspacesHomePath = path.join(testDir, 'Workspaces'); - // Delete any existing backups completely and then re-create it. - await pfs.rimraf(untitledWorkspacesHomePath, pfs.RimRafMode.MOVE); + environmentService = new class TestEnvironmentService extends EnvironmentMainService { + constructor() { + super(parseArgs(process.argv, OPTIONS)); + } + get untitledWorkspacesHome(): URI { + return URI.file(untitledWorkspacesHomePath); + } + }; + + service = new WorkspacesManagementMainService(environmentService, new NullLogService(), new TestBackupMainService(), new TestDialogMainService()); return pfs.mkdirp(untitledWorkspacesHomePath); }); teardown(() => { - return pfs.rimraf(untitledWorkspacesHomePath, pfs.RimRafMode.MOVE); + return pfs.rimraf(testDir); }); function assertPathEquals(p1: string, p2: string): void { @@ -162,11 +163,11 @@ suite('WorkspacesMainService', () => { p2 = normalizeDriveLetter(p2); } - assert.equal(p1, p2); + assert.strictEqual(p1, p2); } function assertEqualURI(u1: URI, u2: URI): void { - assert.equal(u1.toString(), u2.toString()); + assert.strictEqual(u1.toString(), u2.toString()); } test('createWorkspace (folders)', async () => { @@ -176,7 +177,7 @@ suite('WorkspacesMainService', () => { assert.ok(service.isUntitledWorkspace(workspace)); const ws = (JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace); - assert.equal(ws.folders.length, 2); + assert.strictEqual(ws.folders.length, 2); assertPathEquals((ws.folders[0]).path, process.cwd()); assertPathEquals((ws.folders[1]).path, os.tmpdir()); assert.ok(!(ws.folders[0]).name); @@ -190,11 +191,11 @@ suite('WorkspacesMainService', () => { assert.ok(service.isUntitledWorkspace(workspace)); const ws = (JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace); - assert.equal(ws.folders.length, 2); + assert.strictEqual(ws.folders.length, 2); assertPathEquals((ws.folders[0]).path, process.cwd()); assertPathEquals((ws.folders[1]).path, os.tmpdir()); - assert.equal((ws.folders[0]).name, 'currentworkingdirectory'); - assert.equal((ws.folders[1]).name, 'tempdir'); + assert.strictEqual((ws.folders[0]).name, 'currentworkingdirectory'); + assert.strictEqual((ws.folders[1]).name, 'tempdir'); }); test('createUntitledWorkspace (folders as other resource URIs)', async () => { @@ -207,12 +208,12 @@ suite('WorkspacesMainService', () => { assert.ok(service.isUntitledWorkspace(workspace)); const ws = (JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace); - assert.equal(ws.folders.length, 2); - assert.equal((ws.folders[0]).uri, folder1URI.toString(true)); - assert.equal((ws.folders[1]).uri, folder2URI.toString(true)); + assert.strictEqual(ws.folders.length, 2); + assert.strictEqual((ws.folders[0]).uri, folder1URI.toString(true)); + assert.strictEqual((ws.folders[1]).uri, folder2URI.toString(true)); assert.ok(!(ws.folders[0]).name); assert.ok(!(ws.folders[1]).name); - assert.equal(ws.remoteAuthority, 'server'); + assert.strictEqual(ws.remoteAuthority, 'server'); }); test('createWorkspaceSync (folders)', () => { @@ -222,7 +223,7 @@ suite('WorkspacesMainService', () => { assert.ok(service.isUntitledWorkspace(workspace)); const ws = JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace; - assert.equal(ws.folders.length, 2); + assert.strictEqual(ws.folders.length, 2); assertPathEquals((ws.folders[0]).path, process.cwd()); assertPathEquals((ws.folders[1]).path, os.tmpdir()); @@ -237,12 +238,12 @@ suite('WorkspacesMainService', () => { assert.ok(service.isUntitledWorkspace(workspace)); const ws = JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace; - assert.equal(ws.folders.length, 2); + assert.strictEqual(ws.folders.length, 2); assertPathEquals((ws.folders[0]).path, process.cwd()); assertPathEquals((ws.folders[1]).path, os.tmpdir()); - assert.equal((ws.folders[0]).name, 'currentworkingdirectory'); - assert.equal((ws.folders[1]).name, 'tempdir'); + assert.strictEqual((ws.folders[0]).name, 'currentworkingdirectory'); + assert.strictEqual((ws.folders[1]).name, 'tempdir'); }); test('createUntitledWorkspaceSync (folders as other resource URIs)', () => { @@ -255,9 +256,9 @@ suite('WorkspacesMainService', () => { assert.ok(service.isUntitledWorkspace(workspace)); const ws = JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace; - assert.equal(ws.folders.length, 2); - assert.equal((ws.folders[0]).uri, folder1URI.toString(true)); - assert.equal((ws.folders[1]).uri, folder2URI.toString(true)); + assert.strictEqual(ws.folders.length, 2); + assert.strictEqual((ws.folders[0]).uri, folder1URI.toString(true)); + assert.strictEqual((ws.folders[1]).uri, folder2URI.toString(true)); assert.ok(!(ws.folders[0]).name); assert.ok(!(ws.folders[1]).name); @@ -273,7 +274,7 @@ suite('WorkspacesMainService', () => { workspace.configPath = URI.file(newPath); const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); - assert.equal(2, resolved!.folders.length); + assert.strictEqual(2, resolved!.folders.length); assertEqualURI(resolved!.configPath, workspace.configPath); assert.ok(resolved!.id); fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ something: 'something' })); // invalid workspace @@ -325,39 +326,39 @@ suite('WorkspacesMainService', () => { let origConfigPath = URI.file(firstConfigPath); let workspaceConfigPath = URI.file(path.join(tmpDir, 'inside', 'myworkspace1.code-workspace')); - let newContent = rewriteWorkspaceFileForNewLocation(origContent, origConfigPath, false, workspaceConfigPath); + let newContent = rewriteWorkspaceFileForNewLocation(origContent, origConfigPath, false, workspaceConfigPath, extUriBiasedIgnorePathCase); let ws = (JSON.parse(newContent) as IStoredWorkspace); - assert.equal(ws.folders.length, 3); + assert.strictEqual(ws.folders.length, 3); assertPathEquals((ws.folders[0]).path, folder1); // absolute path because outside of tmpdir assertPathEquals((ws.folders[1]).path, '.'); assertPathEquals((ws.folders[2]).path, 'somefolder'); origConfigPath = workspaceConfigPath; workspaceConfigPath = URI.file(path.join(tmpDir, 'myworkspace2.code-workspace')); - newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, false, workspaceConfigPath); + newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, false, workspaceConfigPath, extUriBiasedIgnorePathCase); ws = (JSON.parse(newContent) as IStoredWorkspace); - assert.equal(ws.folders.length, 3); + assert.strictEqual(ws.folders.length, 3); assertPathEquals((ws.folders[0]).path, folder1); assertPathEquals((ws.folders[1]).path, 'inside'); assertPathEquals((ws.folders[2]).path, isWindows ? 'inside\\somefolder' : 'inside/somefolder'); origConfigPath = workspaceConfigPath; workspaceConfigPath = URI.file(path.join(tmpDir, 'other', 'myworkspace2.code-workspace')); - newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, false, workspaceConfigPath); + newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, false, workspaceConfigPath, extUriBiasedIgnorePathCase); ws = (JSON.parse(newContent) as IStoredWorkspace); - assert.equal(ws.folders.length, 3); + assert.strictEqual(ws.folders.length, 3); assertPathEquals((ws.folders[0]).path, folder1); assertPathEquals((ws.folders[1]).path, isWindows ? '..\\inside' : '../inside'); assertPathEquals((ws.folders[2]).path, isWindows ? '..\\inside\\somefolder' : '../inside/somefolder'); origConfigPath = workspaceConfigPath; workspaceConfigPath = URI.parse('foo://foo/bar/myworkspace2.code-workspace'); - newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, false, workspaceConfigPath); + newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, false, workspaceConfigPath, extUriBiasedIgnorePathCase); ws = (JSON.parse(newContent) as IStoredWorkspace); - assert.equal(ws.folders.length, 3); - assert.equal((ws.folders[0]).uri, URI.file(folder1).toString(true)); - assert.equal((ws.folders[1]).uri, URI.file(tmpInsideDir).toString(true)); - assert.equal((ws.folders[2]).uri, URI.file(path.join(tmpInsideDir, 'somefolder')).toString(true)); + assert.strictEqual(ws.folders.length, 3); + assert.strictEqual((ws.folders[0]).uri, URI.file(folder1).toString(true)); + assert.strictEqual((ws.folders[1]).uri, URI.file(tmpInsideDir).toString(true)); + assert.strictEqual((ws.folders[2]).uri, URI.file(path.join(tmpInsideDir, 'somefolder')).toString(true)); fs.unlinkSync(firstConfigPath); }); @@ -369,8 +370,8 @@ suite('WorkspacesMainService', () => { let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); origContent = `// this is a comment\n${origContent}`; - let newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, false, workspaceConfigPath); - assert.equal(0, newContent.indexOf('// this is a comment')); + let newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, false, workspaceConfigPath, extUriBiasedIgnorePathCase); + assert.strictEqual(0, newContent.indexOf('// this is a comment')); service.deleteUntitledWorkspaceSync(workspace); }); @@ -381,26 +382,22 @@ suite('WorkspacesMainService', () => { let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); origContent = origContent.replace(/[\\]/g, '/'); // convert backslash to slash - const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, false, workspaceConfigPath); + const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, false, workspaceConfigPath, extUriBiasedIgnorePathCase); const ws = (JSON.parse(newContent) as IStoredWorkspace); assert.ok(ws.folders.every(f => (f).path.indexOf('\\') < 0)); service.deleteUntitledWorkspaceSync(workspace); }); - test.skip('rewriteWorkspaceFileForNewLocation (unc paths)', async () => { - if (!isWindows) { - return Promise.resolve(); - } - + (!isWindows ? test.skip : test)('rewriteWorkspaceFileForNewLocation (unc paths)', async () => { const workspaceLocation = path.join(os.tmpdir(), 'wsloc'); const folder1Location = 'x:\\foo'; const folder2Location = '\\\\server\\share2\\some\\path'; - const folder3Location = path.join(os.tmpdir(), 'wsloc', 'inner', 'more'); + const folder3Location = path.join(workspaceLocation, 'inner', 'more'); const workspace = await createUntitledWorkspace([folder1Location, folder2Location, folder3Location]); const workspaceConfigPath = URI.file(path.join(workspaceLocation, `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); - const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, false, workspaceConfigPath); + const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, true, workspaceConfigPath, extUriBiasedIgnorePathCase); const ws = (JSON.parse(newContent) as IStoredWorkspace); assertPathEquals((ws.folders[0]).path, folder1Location); assertPathEquals((ws.folders[1]).path, folder2Location); @@ -422,17 +419,15 @@ suite('WorkspacesMainService', () => { }); test('getUntitledWorkspaceSync', async function () { - this.retries(3); - let untitled = service.getUntitledWorkspacesSync(); - assert.equal(untitled.length, 0); + assert.strictEqual(untitled.length, 0); const untitledOne = await createUntitledWorkspace([process.cwd(), os.tmpdir()]); assert.ok(fs.existsSync(untitledOne.configPath.fsPath)); untitled = service.getUntitledWorkspacesSync(); - assert.equal(1, untitled.length); - assert.equal(untitledOne.id, untitled[0].workspace.id); + assert.strictEqual(1, untitled.length); + assert.strictEqual(untitledOne.id, untitled[0].workspace.id); const untitledTwo = await createUntitledWorkspace([os.tmpdir(), process.cwd()]); assert.ok(fs.existsSync(untitledTwo.configPath.fsPath)); @@ -444,14 +439,50 @@ suite('WorkspacesMainService', () => { if (untitled.length === 1) { assert.fail(`Unexpected workspaces count of 1 (expected 2), all workspaces:\n ${fs.readdirSync(untitledHome.fsPath).map(name => fs.readFileSync(joinPath(untitledHome, name, 'workspace.json').fsPath, 'utf8'))}, before getUntitledWorkspacesSync: ${beforeGettingUntitledWorkspaces}`); } - assert.equal(2, untitled.length); + assert.strictEqual(2, untitled.length); service.deleteUntitledWorkspaceSync(untitledOne); untitled = service.getUntitledWorkspacesSync(); - assert.equal(1, untitled.length); + assert.strictEqual(1, untitled.length); service.deleteUntitledWorkspaceSync(untitledTwo); untitled = service.getUntitledWorkspacesSync(); - assert.equal(0, untitled.length); + assert.strictEqual(0, untitled.length); + }); + + test('getSingleWorkspaceIdentifier', async function () { + const nonLocalUri = URI.parse('myscheme://server/work/p/f1'); + const nonLocalUriId = getSingleFolderWorkspaceIdentifier(nonLocalUri); + assert.ok(nonLocalUriId?.id); + + const localNonExistingUri = URI.file(path.join(testDir, 'f1')); + const localNonExistingUriId = getSingleFolderWorkspaceIdentifier(localNonExistingUri); + assert.ok(!localNonExistingUriId); + + fs.mkdirSync(path.join(testDir, 'f1')); + + const localExistingUri = URI.file(path.join(testDir, 'f1')); + const localExistingUriId = getSingleFolderWorkspaceIdentifier(localExistingUri); + assert.ok(localExistingUriId?.id); + }); + + test('workspace identifiers are stable', function () { + + // workspace identifier (local) + assert.strictEqual(getWorkspaceIdentifier(URI.file('/hello/test')).id, isWindows /* slash vs backslash */ ? '9f3efb614e2cd7924e4b8076e6c72233' : 'e36736311be12ff6d695feefe415b3e8'); + + // single folder identifier (local) + const fakeStat = { + ino: 1611312115129, + birthtimeMs: 1611312115129, + birthtime: new Date(1611312115129) + }; + assert.strictEqual(getSingleFolderWorkspaceIdentifier(URI.file('/hello/test'), fakeStat as fs.Stats)?.id, isWindows /* slash vs backslash */ ? '9a8441e897e5174fa388bc7ef8f7a710' : '1d726b3d516dc2a6d343abf4797eaaef'); + + // workspace identifier (remote) + assert.strictEqual(getWorkspaceIdentifier(URI.parse('vscode-remote:/hello/test')).id, '786de4f224d57691f218dc7f31ee2ee3'); + + // single folder identifier (remote) + assert.strictEqual(getSingleFolderWorkspaceIdentifier(URI.parse('vscode-remote:/hello/test'))?.id, '786de4f224d57691f218dc7f31ee2ee3'); }); }); diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index e1c2dd30c..7c5c6828b 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -1450,6 +1450,21 @@ declare module 'vscode' { dispose(): void; } + /** + * An error type that should be used to signal cancellation of an operation. + * + * This type can be used in response to a [cancellation token](#CancellationToken) + * being cancelled or when an operation is being cancelled by the + * executor of that operation. + */ + export class CancellationError extends Error { + + /** + * Creates a new cancellation error. + */ + constructor(); + } + /** * Represents a type which can release resources, such * as event listening or a timer. @@ -1700,8 +1715,8 @@ declare module 'vscode' { /** * Options to configure the behaviour of a file open dialog. * - * * Note 1: A dialog can select files, folders, or both. This is not true for Windows - * which enforces to open either files or folder, but *not both*. + * * Note 1: On Windows and Linux, a file dialog cannot be both a file selector and a folder selector, so if you + * set both `canSelectFiles` and `canSelectFolders` to `true` on these platforms, a folder selector will be shown. * * Note 2: Explicitly setting `canSelectFiles` and `canSelectFolders` to `false` is futile * and the editor then silently adjusts the options to select files. */ @@ -3870,8 +3885,8 @@ declare module 'vscode' { * * Note that `sortText` is only used for the initial ordering of completion * items. When having a leading word (prefix) ordering is based on how - * well completion match that prefix and the initial ordering is only used - * when completions match equal. The prefix is defined by the + * well completions match that prefix and the initial ordering is only used + * when completions match equally well. The prefix is defined by the * [`range`](#CompletionItem.range)-property and can therefore be different * for each completion. */ @@ -3884,7 +3899,6 @@ declare module 'vscode' { * * Note that the filter text is matched against the leading word (prefix) which is defined * by the [`range`](#CompletionItem.range)-property. - * prefix. */ filterText?: string; @@ -4683,6 +4697,10 @@ declare module 'vscode' { * This rule will only execute if the text after the cursor matches this regular expression. */ afterText?: RegExp; + /** + * This rule will only execute if the text above the current line matches this regular expression. + */ + previousLineText?: RegExp; /** * The action to execute. */ @@ -5173,9 +5191,9 @@ declare module 'vscode' { set(uri: Uri, diagnostics: ReadonlyArray | undefined): void; /** - * Replace all entries in this collection. + * Replace diagnostics for multiple resources in this collection. * - * Diagnostics of multiple tuples of the same uri will be merged, e.g + * _Note_ that multiple tuples of the same uri will be merged, e.g * `[[file1, [d1]], [file1, [d2]]]` is equivalent to `[[file1, [d1, d2]]]`. * If a diagnostics item is `undefined` as in `[file1, undefined]` * all previous but not subsequent diagnostics are removed. @@ -5419,6 +5437,18 @@ declare module 'vscode' { */ color: string | ThemeColor | undefined; + /** + * The background color for this entry. + * + * *Note*: only `new ThemeColor('statusBarItem.errorBackground')` is + * supported for now. More background colors may be supported in the + * future. + * + * *Note*: when a background color is set, the statusbar may override + * the `color` choice to ensure the entry is readable in all themes. + */ + backgroundColor: ThemeColor | undefined; + /** * [`Command`](#Command) or identifier of a command to run on click. * @@ -5795,6 +5825,11 @@ declare module 'vscode' { setKeysForSync(keys: string[]): void; }; + /** + * A storage utility for secrets. + */ + readonly secrets: SecretStorage; + /** * The uri of the directory containing the extension. */ @@ -5932,6 +5967,48 @@ declare module 'vscode' { update(key: string, value: any): Thenable; } + /** + * The event data that is fired when a secret is added or removed. + */ + export interface SecretStorageChangeEvent { + /** + * The key of the secret that has changed. + */ + readonly key: string; + } + + /** + * Represents a storage utility for secrets, information that is + * sensitive. + */ + export interface SecretStorage { + /** + * Retrieve a secret that was stored with key. Returns undefined if there + * is no password matching that key. + * @param key The key the secret was stored under. + * @returns The stored value or `undefined`. + */ + get(key: string): Thenable; + + /** + * Store a secret under a given key. + * @param key The key to store the secret under. + * @param value The secret. + */ + store(key: string, value: string): Thenable; + + /** + * Remove a secret from storage. + * @param key The key the secret was stored under. + */ + delete(key: string): Thenable; + + /** + * Fires when a secret is stored or deleted. + */ + onDidChange: Event; + } + /** * Represents a color theme kind. */ @@ -7732,7 +7809,7 @@ declare module 'vscode' { * your extension should first check to see if any backups exist for the resource. If there is a backup, your * extension should load the file contents from there instead of from the resource in the workspace. * - * `backup` is triggered approximately one second after the the user stops editing the document. If the user + * `backup` is triggered approximately one second after the user stops editing the document. If the user * rapidly edits the document, `backup` will not be invoked until the editing stops. * * `backup` is not invoked when `auto save` is enabled (since auto save already persists the resource). @@ -8855,7 +8932,8 @@ declare module 'vscode' { getParent?(element: T): ProviderResult; /** - * Called only on hover to resolve the [TreeItem](#TreeItem.tooltip) property if it is undefined. + * Called on hover to resolve the [TreeItem](#TreeItem.tooltip) property if it is undefined. + * Called on tree item click/open to resolve the [TreeItem](#TreeItem.command) property if it is undefined. * Only properties that were undefined can be resolved in `resolveTreeItem`. * Functionality may be expanded later to include being called to resolve other missing * properties on selection and/or on open. @@ -8865,15 +8943,16 @@ declare module 'vscode' { * onDidChangeTreeData should not be triggered from within resolveTreeItem. * * *Note* that this function is called when tree items are already showing in the UI. - * Because of that, no property that changes the presentation (label, description, command, etc.) + * Because of that, no property that changes the presentation (label, description, etc.) * can be changed. * - * @param element The object associated with the TreeItem * @param item Undefined properties of `item` should be set then `item` should be returned. + * @param element The object associated with the TreeItem. + * @param token A cancellation token. * @return The resolved tree item or a thenable that resolves to such. It is OK to return the given * `item`. When no result is returned, the given `item` will be used. */ - resolveTreeItem?(item: TreeItem, element: T): ProviderResult; + resolveTreeItem?(item: TreeItem, element: T, token: CancellationToken): ProviderResult; } export class TreeItem { @@ -9194,7 +9273,7 @@ declare module 'vscode' { * Implement to handle when the number of rows and columns that fit into the terminal panel * changes, for example when font size changes or when the panel is resized. The initial * state of a terminal's dimensions should be treated as `undefined` until this is triggered - * as the size of a terminal isn't know until it shows up in the user interface. + * as the size of a terminal isn't known until it shows up in the user interface. * * When dimensions are overridden by * [onDidOverrideDimensions](#Pseudoterminal.onDidOverrideDimensions), `setDimensions` will @@ -11865,8 +11944,6 @@ declare module 'vscode' { export const onDidChange: Event; } - //#region Comments - /** * Collapsible state of a [comment thread](#CommentThread) */ @@ -12133,7 +12210,7 @@ declare module 'vscode' { /** * Optional reaction handler for creating and deleting reactions on a [comment](#Comment). */ - reactionHandler?: (comment: Comment, reaction: CommentReaction) => Promise; + reactionHandler?: (comment: Comment, reaction: CommentReaction) => Thenable; /** * Dispose this comment controller. diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 19e8d8f10..0aa96cf73 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -16,7 +16,7 @@ declare module 'vscode' { - // #region auth provider: https://github.com/microsoft/vscode/issues/88309 + //#region auth provider: https://github.com/microsoft/vscode/issues/88309 /** * An [event](#Event) which fires when an [AuthenticationProvider](#AuthenticationProvider) is added or removed. @@ -54,28 +54,9 @@ declare module 'vscode' { } /** - * **WARNING** When writing an AuthenticationProvider, `id` should be treated as part of your extension's - * API, changing it is a breaking change for all extensions relying on the provider. The id is - * treated case-sensitively. + * A provider for performing authentication to a service. */ export interface AuthenticationProvider { - /** - * Used as an identifier for extensions trying to work with a particular - * provider: 'microsoft', 'github', etc. id must be unique, registering - * another provider with the same id will fail. - */ - readonly id: string; - - /** - * The human-readable name of the provider. - */ - readonly label: string; - - /** - * Whether it is possible to be signed into multiple accounts at once with this provider - */ - readonly supportsMultipleAccounts: boolean; - /** * An [event](#Event) which fires when the array of sessions has changed, or data * within a session has changed. @@ -85,31 +66,48 @@ declare module 'vscode' { /** * Returns an array of current sessions. */ + // eslint-disable-next-line vscode-dts-provider-naming getSessions(): Thenable>; /** * Prompts a user to login. */ + // eslint-disable-next-line vscode-dts-provider-naming login(scopes: string[]): Thenable; /** * Removes the session corresponding to session id. * @param sessionId The session id to log out of */ + // eslint-disable-next-line vscode-dts-provider-naming logout(sessionId: string): Thenable; } + /** + * Options for creating an [AuthenticationProvider](#AuthentcationProvider). + */ + export interface AuthenticationProviderOptions { + /** + * Whether it is possible to be signed into multiple accounts at once with this provider. + * If not specified, will default to false. + */ + readonly supportsMultipleAccounts?: boolean; + } + export namespace authentication { /** * Register an authentication provider. * * There can only be one provider per id and an error is being thrown when an id - * has already been used by another provider. + * has already been used by another provider. Ids are case-sensitive. * + * @param id The unique identifier of the provider. + * @param label The human-readable name of the provider. * @param provider The authentication provider provider. + * @params options Additional options for the provider. * @return A [disposable](#Disposable) that unregisters this provider when being disposed. */ - export function registerAuthenticationProvider(provider: AuthenticationProvider): Disposable; + export function registerAuthenticationProvider(id: string, label: string, provider: AuthenticationProvider, options?: AuthenticationProviderOptions): Disposable; /** * @deprecated - getSession should now trigger extension activation. @@ -117,63 +115,32 @@ declare module 'vscode' { */ export const onDidChangeAuthenticationProviders: Event; - /** - * @deprecated - * The ids of the currently registered authentication providers. - * @returns An array of the ids of authentication providers that are currently registered. - */ - export function getProviderIds(): Thenable>; - - /** - * @deprecated - * An array of the ids of authentication providers that are currently registered. - */ - export const providerIds: ReadonlyArray; - /** * An array of the information of authentication providers that are currently registered. */ export const providers: ReadonlyArray; /** - * @deprecated * Logout of a specific session. * @param providerId The id of the provider to use * @param sessionId The session id to remove * provider */ export function logout(providerId: string, sessionId: string): Thenable; - - /** - * Retrieve a password that was stored with key. Returns undefined if there - * is no password matching that key. - * @param key The key the password was stored under. - */ - export function getPassword(key: string): Thenable; - - /** - * Store a password under a given key. - * @param key The key to store the password under - * @param value The password - */ - export function setPassword(key: string, value: string): Thenable; - - /** - * Remove a password from storage. - * @param key The key the password was stored under. - */ - export function deletePassword(key: string): Thenable; - - /** - * Fires when a password is set or deleted. - */ - export const onDidChangePassword: Event; } //#endregion + // eslint-disable-next-line vscode-dts-region-comments //#region @alexdima - resolvers + export interface MessageOptions { + /** + * Do not render a native message box. + */ + useCustom?: boolean; + } + export interface RemoteAuthorityResolverContext { resolveAttempt: number; } @@ -195,18 +162,20 @@ declare module 'vscode' { // The desired local port. If this port can't be used, then another will be chosen. localAddressPort?: number; label?: string; + public?: boolean; } export interface TunnelDescription { remoteAddress: { port: number, host: string; }; //The complete local address(ex. localhost:1234) localAddress: { port: number, host: string; } | string; + public?: boolean; } export interface Tunnel extends TunnelDescription { // Implementers of Tunnel should fire onDidDispose when dispose is called. onDidDispose: Event; - dispose(): void; + dispose(): void | Thenable; } /** @@ -251,10 +220,19 @@ declare module 'vscode' { */ tunnelFactory?: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => Thenable | undefined; - /** + /**p * Provides filtering for candidate ports. */ showCandidatePort?: (host: string, port: number, detail: string) => Thenable; + + /** + * Lets the resolver declare which tunnel factory features it supports. + * UNDER DISCUSSION! MAY CHANGE SOON. + */ + tunnelFeatures?: { + elevation: boolean; + public: boolean; + }; } export namespace workspace { @@ -751,6 +729,7 @@ declare module 'vscode' { //#endregion + // eslint-disable-next-line vscode-dts-region-comments //#region debug /** @@ -771,8 +750,7 @@ declare module 'vscode' { //#endregion - - + // eslint-disable-next-line vscode-dts-region-comments //#region @joaomoreno: SCM validation /** @@ -823,6 +801,7 @@ declare module 'vscode' { //#endregion + // eslint-disable-next-line vscode-dts-region-comments //#region @joaomoreno: SCM selected provider export interface SourceControl { @@ -898,6 +877,7 @@ declare module 'vscode' { //#endregion + // eslint-disable-next-line vscode-dts-region-comments //#region @jrieken -> exclusive document filters export interface DocumentFilter { @@ -906,18 +886,9 @@ declare module 'vscode' { //#endregion - //#region @alexdima - OnEnter enhancement - export interface OnEnterRule { - /** - * This rule will only execute if the text above the this line matches this regular expression. - */ - oneLineAboveText?: RegExp; - } - //#endregion - - //#region Tree View: https://github.com/microsoft/vscode/issues/61313 + //#region Tree View: https://github.com/microsoft/vscode/issues/61313 @alexr00 export interface TreeView extends Disposable { - reveal(element: T | undefined, options?: { select?: boolean, focus?: boolean, expand?: boolean | number }): Thenable; + reveal(element: T | undefined, options?: { select?: boolean, focus?: boolean, expand?: boolean | number; }): Thenable; } //#endregion @@ -1001,6 +972,7 @@ declare module 'vscode' { * * @return Thenable indicating that the webview editor has been moved. */ + // eslint-disable-next-line vscode-dts-provider-naming moveCustomTextEditor?(newDocument: TextDocument, existingWebviewPanel: WebviewPanel, token: CancellationToken): Thenable; } @@ -1017,7 +989,7 @@ declare module 'vscode' { //#endregion - //#region @rebornix: Notebook + //#region notebook https://github.com/microsoft/vscode/issues/106744 export enum CellKind { Markdown = 1, @@ -1055,7 +1027,7 @@ declare module 'vscode' { /** * Additional attributes of a cell metadata. */ - custom?: { [key: string]: any }; + custom?: { [key: string]: any; }; } export interface CellDisplayOutput { @@ -1179,7 +1151,7 @@ declare module 'vscode' { /** * Additional attributes of a cell metadata. */ - custom?: { [key: string]: any }; + custom?: { [key: string]: any; }; } export interface NotebookCell { @@ -1229,7 +1201,7 @@ declare module 'vscode' { /** * Additional attributes of the document metadata. */ - custom?: { [key: string]: any }; + custom?: { [key: string]: any; }; /** * The document's current run state @@ -1241,6 +1213,11 @@ declare module 'vscode' { * When false, insecure outputs like HTML, JavaScript, SVG will not be rendered. */ trusted?: boolean; + + /** + * Languages the document supports + */ + languages?: string[]; } export interface NotebookDocumentContentOptions { @@ -1286,7 +1263,7 @@ declare module 'vscode' { locationAt(positionOrRange: Position | Range): Location; positionAt(location: Location): Position; - contains(uri: Uri): boolean + contains(uri: Uri): boolean; } export interface WorkspaceEdit { @@ -1320,11 +1297,17 @@ declare module 'vscode' { * The range will always be revealed in the center of the viewport. */ InCenter = 1, + /** * If the range is outside the viewport, it will be revealed in the center of the viewport. * Otherwise, it will be revealed with as little scrolling as possible. */ InCenterIfOutsideViewport = 2, + + /** + * The range will always be revealed at the top of the viewport. + */ + AtTop = 3 } export interface NotebookEditor { @@ -1589,11 +1572,17 @@ declare module 'vscode' { * Content providers should always use [file system providers](#FileSystemProvider) to * resolve the raw content for `uri` as the resouce is not necessarily a file on disk. */ - openNotebook(uri: Uri, openContext: NotebookDocumentOpenContext): NotebookData | Promise; - resolveNotebook(document: NotebookDocument, webview: NotebookCommunication): Promise; - saveNotebook(document: NotebookDocument, cancellation: CancellationToken): Promise; - saveNotebookAs(targetResource: Uri, document: NotebookDocument, cancellation: CancellationToken): Promise; - backupNotebook(document: NotebookDocument, context: NotebookDocumentBackupContext, cancellation: CancellationToken): Promise; + // eslint-disable-next-line vscode-dts-provider-naming + openNotebook(uri: Uri, openContext: NotebookDocumentOpenContext): NotebookData | Thenable; + // eslint-disable-next-line vscode-dts-provider-naming + // eslint-disable-next-line vscode-dts-cancellation + resolveNotebook(document: NotebookDocument, webview: NotebookCommunication): Thenable; + // eslint-disable-next-line vscode-dts-provider-naming + saveNotebook(document: NotebookDocument, cancellation: CancellationToken): Thenable; + // eslint-disable-next-line vscode-dts-provider-naming + saveNotebookAs(targetResource: Uri, document: NotebookDocument, cancellation: CancellationToken): Thenable; + // eslint-disable-next-line vscode-dts-provider-naming + backupNotebook(document: NotebookDocument, context: NotebookDocumentBackupContext, cancellation: CancellationToken): Thenable; } export interface NotebookKernel { @@ -1609,7 +1598,7 @@ declare module 'vscode' { cancelAllCellsExecution(document: NotebookDocument): void; } - export type NotebookFilenamePattern = GlobPattern | { include: GlobPattern; exclude: GlobPattern }; + export type NotebookFilenamePattern = GlobPattern | { include: GlobPattern; exclude: GlobPattern; }; export interface NotebookDocumentFilter { viewType?: string | string[]; @@ -1692,7 +1681,7 @@ declare module 'vscode' { ): Disposable; export function createNotebookEditorDecorationType(options: NotebookDecorationRenderOptions): NotebookEditorDecorationType; - export function openNotebookDocument(uri: Uri, viewType?: string): Promise; + export function openNotebookDocument(uri: Uri, viewType?: string): Thenable; export const onDidOpenNotebookDocument: Event; export const onDidCloseNotebookDocument: Event; export const onDidSaveNotebookDocument: Event; @@ -1715,7 +1704,7 @@ declare module 'vscode' { */ export function createConcatTextDocument(notebook: NotebookDocument, selector?: DocumentSelector): NotebookConcatTextDocument; - export const onDidChangeActiveNotebookKernel: Event<{ document: NotebookDocument, kernel: NotebookKernel | undefined }>; + export const onDidChangeActiveNotebookKernel: Event<{ document: NotebookDocument, kernel: NotebookKernel | undefined; }>; /** * Creates a notebook cell status bar [item](#NotebookCellStatusBarItem). @@ -1736,7 +1725,7 @@ declare module 'vscode' { export const onDidChangeActiveNotebookEditor: Event; export const onDidChangeNotebookEditorSelection: Event; export const onDidChangeNotebookEditorVisibleRanges: Event; - export function showNotebookDocument(document: NotebookDocument, options?: NotebookDocumentShowOptions): Promise; + export function showNotebookDocument(document: NotebookDocument, options?: NotebookDocumentShowOptions): Thenable; } //#endregion @@ -1947,11 +1936,88 @@ declare module 'vscode' { } export namespace languages { - export function getTokenInformationAtPosition(document: TextDocument, position: Position): Promise; + export function getTokenInformationAtPosition(document: TextDocument, position: Position): Thenable; } //#endregion + //#region https://github.com/microsoft/vscode/issues/16221 + + export namespace languages { + /** + * Register a inline hints provider. + * + * Multiple providers can be registered for a language. In that case providers are asked in + * parallel and the results are merged. A failing provider (rejected promise or exception) will + * not cause a failure of the whole operation. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider An inline hints provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerInlineHintsProvider(selector: DocumentSelector, provider: InlineHintsProvider): Disposable; + } + + /** + * Inline hint information. + */ + export class InlineHint { + /** + * The text of the hint. + */ + text: string; + /** + * The range of the hint. + */ + range: Range; + /** + * Tooltip when hover on the hint. + */ + description?: string | MarkdownString; + /** + * Whitespace before the hint. + */ + whitespaceBefore?: boolean; + /** + * Whitespace after the hint. + */ + whitespaceAfter?: boolean; + + /** + * Creates a new inline hint information object. + * + * @param text The text of the hint. + * @param range The range of the hint. + * @param hoverMessage Tooltip when hover on the hint. + * @param whitespaceBefore Whitespace before the hint. + * @param whitespaceAfter TWhitespace after the hint. + */ + constructor(text: string, range: Range, description?: string | MarkdownString, whitespaceBefore?: boolean, whitespaceAfter?: boolean); + } + + /** + * The inline hints provider interface defines the contract between extensions and + * the inline hints feature. + */ + export interface InlineHintsProvider { + + /** + * An optional event to signal that inline hints have changed. + * @see [EventEmitter](#EventEmitter) + */ + onDidChangeInlineHints?: Event; + + /** + * @param model The document in which the command was invoked. + * @param range The range for which line hints should be computed. + * @param token A cancellation token. + * + * @return A list of arguments labels or a thenable that resolves to such. + */ + provideInlineHints(model: TextDocument, range: Range, token: CancellationToken): ProviderResult; + } + //#endregion + //#region https://github.com/microsoft/vscode/issues/104436 export enum ExtensionRuntime { @@ -1971,7 +2037,6 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/102091 export interface TextDocument { @@ -2004,7 +2069,7 @@ declare module 'vscode' { * Runs tests with the given options. If no options are given, then * all tests are run. Returns the resulting test run. */ - export function runTests(options: TestRunOptions): Thenable; + export function runTests(options: TestRunOptions, cancellationToken?: CancellationToken): Thenable; /** * Returns an observer that retrieves tests in the given workspace folder. @@ -2015,13 +2080,31 @@ declare module 'vscode' { * Returns an observer that retrieves tests in the given text document. */ export function createDocumentTestObserver(document: TextDocument): TestObserver; + + /** + * The last or selected test run. Cleared when a new test run starts. + */ + export const testResults: TestResults | undefined; + + /** + * Event that fires when the testResults are updated. + */ + export const onDidChangeTestResults: Event; + } + + export interface TestResults { + /** + * The results from the latest test run. The array contains a snapshot of + * all tests involved in the run at the moment when it completed. + */ + readonly tests: ReadonlyArray | undefined; } export interface TestObserver { /** * List of tests returned by test provider for files in the workspace. */ - readonly tests: ReadonlyArray; + readonly tests: ReadonlyArray; /** * An event that fires when an existing test in the collection changes, or @@ -2051,23 +2134,23 @@ declare module 'vscode' { /** * List of all tests that are newly added. */ - readonly added: ReadonlyArray; + readonly added: ReadonlyArray; /** * List of existing tests that have updated. */ - readonly updated: ReadonlyArray; + readonly updated: ReadonlyArray; /** * List of existing tests that have been removed. */ - readonly removed: ReadonlyArray; + readonly removed: ReadonlyArray; /** * Highest node in the test tree under which changes were made. This can * be easily plugged into events like the TreeDataProvider update event. */ - readonly commonChangeAncestor: TestItem | null; + readonly commonChangeAncestor: RequiredTestItem | null; } /** @@ -2094,13 +2177,11 @@ declare module 'vscode' { readonly onDidChangeTest: Event; /** - * An event that should be fired when all tests that are currently defined - * have been discovered. The provider should continue to watch for changes - * and fire `onDidChangeTest` until the hierarchy is disposed. - * - * @todo can this be covered by existing progress apis? Or return a promise + * Promise that should be resolved when all tests that are initially + * defined have been discovered. The provider should continue to watch for + * changes and fire `onDidChangeTest` until the hierarchy is disposed. */ - readonly onDidDiscoverInitialTests: Event; + readonly discoveredInitialTests?: Thenable; /** * Dispose will be called when there are no longer observers interested @@ -2129,20 +2210,23 @@ declare module 'vscode' { * It's guaranteed that this method will not be called again while * there is a previous undisposed watcher for the given workspace folder. */ - createWorkspaceTestHierarchy?(workspace: WorkspaceFolder): TestHierarchy; + // eslint-disable-next-line vscode-dts-provider-naming + createWorkspaceTestHierarchy?(workspace: WorkspaceFolder): TestHierarchy | undefined; /** * Requests that tests be provided for the given document. This will * be called when tests need to be enumerated for a single open file, * for instance by code lens UI. */ - createDocumentTestHierarchy?(document: TextDocument): TestHierarchy; + // eslint-disable-next-line vscode-dts-provider-naming + createDocumentTestHierarchy?(document: TextDocument): TestHierarchy | undefined; /** * Starts a test run. This should cause {@link onDidChangeTest} to * fire with update test states during the run. * @todo this will eventually need to be able to return a summary report, coverage for example. */ + // eslint-disable-next-line vscode-dts-provider-naming runTests?(options: TestRunOptions, cancellationToken: CancellationToken): ProviderResult; } @@ -2172,6 +2256,16 @@ declare module 'vscode' { */ label: string; + /** + * Optional unique identifier for the TestItem. This is used to correlate + * test results and tests in the document with those in the workspace + * (test explorer). This must not change for the lifetime of a test item. + * + * If the ID is not provided, it defaults to the concatenation of the + * item's label and its parent's ID, if any. + */ + readonly id?: string; + /** * Optional description that appears next to the label. */ @@ -2208,19 +2302,30 @@ declare module 'vscode' { state: TestState; } + /** + * A {@link TestItem} with its defaults filled in. + */ + export type RequiredTestItem = { + [K in keyof Required]: K extends 'children' + ? RequiredTestItem[] + : (K extends 'description' | 'location' ? TestItem[K] : Required[K]) + }; + export enum TestRunState { // Initial state Unset = 0, + // Test will be run, but is not currently running. + Queued = 1, // Test is currently running - Running = 1, + Running = 2, // Test run has passed - Passed = 2, + Passed = 3, // Test run has failed (on an assertion) - Failed = 3, + Failed = 4, // Test run has been skipped - Skipped = 4, + Skipped = 5, // Test run failed for some other reason (compilation error, timeout, etc) - Errored = 5 + Errored = 6 } /** @@ -2294,27 +2399,180 @@ declare module 'vscode' { */ location?: Location; } + //#endregion - //#region Statusbar Item Background Color (https://github.com/microsoft/vscode/issues/110214) + //#region Opener service (https://github.com/microsoft/vscode/issues/109277) /** - * A status bar item is a status bar contribution that can - * show text and icons and run a command on click. + * Details if an `ExternalUriOpener` can open a uri. + * + * The priority is also used to rank multiple openers against each other and determine + * if an opener should be selected automatically or if the user should be prompted to + * select an opener. + * + * VS Code will try to use the best available opener, as sorted by `ExternalUriOpenerPriority`. + * If there are multiple potential "best" openers for a URI, then the user will be prompted + * to select an opener. */ - export interface StatusBarItem { + export enum ExternalUriOpenerPriority { + /** + * The opener is disabled and will never be shown to users. + * + * Note that the opener can still be used if the user specifically + * configures it in their settings. + */ + None = 0, /** - * The background color for this entry. - * - * Note: only `new ThemeColor('statusBarItem.errorBackground')` is - * supported for now. More background colors may be supported in the - * future. - * - * Note: when a background color is set, the statusbar may override - * the `color` choice to ensure the entry is readable in all themes. + * The opener can open the uri but will not cause a prompt on its own + * since VS Code always contributes a built-in `Default` opener. */ - backgroundColor: ThemeColor | undefined; + Option = 1, + + /** + * The opener can open the uri. + * + * VS Code's built-in opener has `Default` priority. This means that any additional `Default` + * openers will cause the user to be prompted to select from a list of all potential openers. + */ + Default = 2, + + /** + * The opener can open the uri and should be automatically selected over any + * default openers, include the built-in one from VS Code. + * + * A preferred opener will be automatically selected if no other preferred openers + * are available. If multiple preferred openers are available, then the user + * is shown a prompt with all potential openers (not just preferred openers). + */ + Preferred = 3, + } + + /** + * Handles opening uris to external resources, such as http(s) links. + * + * Extensions can implement an `ExternalUriOpener` to open `http` links to a webserver + * inside of VS Code instead of having the link be opened by the web browser. + * + * Currently openers may only be registered for `http` and `https` uris. + */ + export interface ExternalUriOpener { + + /** + * Check if the opener can open a uri. + * + * @param uri The uri being opened. This is the uri that the user clicked on. It has + * not yet gone through port forwarding. + * @param token Cancellation token indicating that the result is no longer needed. + * + * @return Priority indicating if the opener can open the external uri. + */ + canOpenExternalUri(uri: Uri, token: CancellationToken): ExternalUriOpenerPriority | Thenable; + + /** + * Open a uri. + * + * This is invoked when: + * + * - The user clicks a link which does not have an assigned opener. In this case, first `canOpenExternalUri` + * is called and if the user selects this opener, then `openExternalUri` is called. + * - The user sets the default opener for a link in their settings and then visits a link. + * + * @param resolvedUri The uri to open. This uri may have been transformed by port forwarding, so it + * may not match the original uri passed to `canOpenExternalUri`. Use `ctx.originalUri` to check the + * original uri. + * @param ctx Additional information about the uri being opened. + * @param token Cancellation token indicating that opening has been canceled. + * + * @return Thenable indicating that the opening has completed. + */ + openExternalUri(resolvedUri: Uri, ctx: OpenExternalUriContext, token: CancellationToken): Thenable | void; + } + + /** + * Additional information about the uri being opened. + */ + interface OpenExternalUriContext { + /** + * The uri that triggered the open. + * + * This is the original uri that the user clicked on or that was passed to `openExternal.` + * Due to port forwarding, this may not match the `resolvedUri` passed to `openExternalUri`. + */ + readonly sourceUri: Uri; + } + + /** + * Additional metadata about a registered `ExternalUriOpener`. + */ + interface ExternalUriOpenerMetadata { + + /** + * List of uri schemes the opener is triggered for. + * + * Currently only `http` and `https` are supported. + */ + readonly schemes: readonly string[] + + /** + * Text displayed to the user that explains what the opener does. + * + * For example, 'Open in browser preview' + */ + readonly label: string; + } + + namespace window { + /** + * Register a new `ExternalUriOpener`. + * + * When a uri is about to be opened, an `onUriOpen:SCHEME` activation event is fired. + * + * @param id Unique id of the opener, such as `myExtension.browserPreview`. This is used in settings + * and commands to identify the opener. + * @param opener Opener to register. + * @param metadata Additional information about the opener. + * + * @returns Disposable that unregisters the opener. + */ + export function registerExternalUriOpener(id: string, opener: ExternalUriOpener, metadata: ExternalUriOpenerMetadata): Disposable; + } + + interface OpenExternalOptions { + /** + * Allows using openers contributed by extensions through `registerExternalUriOpener` + * when opening the resource. + * + * If `true`, VS Code will check if any contributed openers can handle the + * uri, and fallback to the default opener behavior. + * + * If it is string, this specifies the id of the `ExternalUriOpener` + * that should be used if it is available. Use `'default'` to force VS Code's + * standard external opener to be used. + */ + readonly allowContributedOpeners?: boolean | string; + } + + namespace env { + export function openExternal(target: Uri, options?: OpenExternalOptions): Thenable; + } + + //endregionn + + //#region https://github.com/Microsoft/vscode/issues/15178 + + // TODO@API must be a class + export interface OpenEditorInfo { + name: string; + resource: Uri; + } + + export namespace window { + export const openEditors: ReadonlyArray; + + // todo@API proper event type + export const onDidChangeOpenEditors: Event; } //#endregion diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index 3b4c8a66c..1e3169fd5 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -17,6 +17,7 @@ import { LanguageConfigurationFileHandler } from 'vs/workbench/contrib/codeEdito // --- mainThread participants import './mainThreadBulkEdits'; import './mainThreadCodeInsets'; +import './mainThreadCLICommands'; import './mainThreadClipboard'; import './mainThreadCommands'; import './mainThreadConfiguration'; @@ -30,6 +31,7 @@ import './mainThreadDocuments'; import './mainThreadDocumentsAndEditors'; import './mainThreadEditor'; import './mainThreadEditors'; +import './mainThreadEditorTabs'; import './mainThreadErrors'; import './mainThreadExtensionService'; import './mainThreadFileSystem'; @@ -54,6 +56,7 @@ import './mainThreadTheming'; import './mainThreadTreeViews'; import './mainThreadDownloadService'; import './mainThreadUrls'; +import './mainThreadUriOpeners'; import './mainThreadWindow'; import './mainThreadWebviewManager'; import './mainThreadWorkspace'; @@ -65,6 +68,7 @@ import './mainThreadTunnelService'; import './mainThreadAuthentication'; import './mainThreadTimeline'; import './mainThreadTesting'; +import './mainThreadSecretState'; import 'vs/workbench/api/common/apiCommands'; export class ExtensionPoints implements IWorkbenchContribution { diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index c39650404..98bb98d8c 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -18,9 +18,6 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA import { fromNow } from 'vs/base/common/date'; import { ActivationKind, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { isWeb } from 'vs/base/common/platform'; -import { IEncryptionService } from 'vs/workbench/services/encryption/common/encryptionService'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials'; const VSO_ALLOWED_EXTENSIONS = ['github.vscode-pull-request-github', 'github.vscode-pull-request-github-insiders', 'vscode.git', 'ms-vsonline.vsonline', 'vscode.github-browser', 'ms-vscode.github-browser', 'github.codespaces']; @@ -225,10 +222,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu @INotificationService private readonly notificationService: INotificationService, @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, @IQuickInputService private readonly quickInputService: IQuickInputService, - @IExtensionService private readonly extensionService: IExtensionService, - @ICredentialsService private readonly credentialsService: ICredentialsService, - @IEncryptionService private readonly encryptionService: IEncryptionService, - @IProductService private readonly productService: IProductService + @IExtensionService private readonly extensionService: IExtensionService ) { super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostAuthentication); @@ -250,10 +244,6 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu this._register(this.authenticationService.onDidChangeDeclaredProviders(e => { this._proxy.$setProviders(e); })); - - this._register(this.credentialsService.onDidChangePassword(_ => { - this._proxy.$onDidChangePassword(); - })); } $getProviderIds(): Promise { @@ -416,7 +406,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu const remoteConnection = this.remoteAgentService.getConnection(); const isVSO = remoteConnection !== null - ? remoteConnection.remoteAuthority.startsWith('vsonline') + ? remoteConnection.remoteAuthority.startsWith('vsonline') || remoteConnection.remoteAuthority.startsWith('codespaces') : isWeb; if (isVSO && VSO_ALLOWED_EXTENSIONS.includes(extensionId)) { @@ -466,46 +456,4 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu this.storageService.store(`${extensionName}-${providerId}`, sessionId, StorageScope.GLOBAL, StorageTarget.MACHINE); addAccountUsage(this.storageService, providerId, accountName, extensionId, extensionName); } - - private getFullKey(extensionId: string): string { - return `${this.productService.urlProtocol}${extensionId}`; - } - - async $getPassword(extensionId: string, key: string): Promise { - const fullKey = this.getFullKey(extensionId); - const password = await this.credentialsService.getPassword(fullKey, key); - const decrypted = password && await this.encryptionService.decrypt(password); - - if (decrypted) { - try { - const value = JSON.parse(decrypted); - if (value.extensionId === extensionId) { - return value.content; - } - } catch (_) { - throw new Error('Cannot get password'); - } - } - - return undefined; - } - - async $setPassword(extensionId: string, key: string, value: string): Promise { - const fullKey = this.getFullKey(extensionId); - const toEncrypt = JSON.stringify({ - extensionId, - content: value - }); - const encrypted = await this.encryptionService.encrypt(toEncrypt); - return this.credentialsService.setPassword(fullKey, key, encrypted); - } - - async $deletePassword(extensionId: string, key: string): Promise { - try { - const fullKey = this.getFullKey(extensionId); - await this.credentialsService.deletePassword(fullKey, key); - } catch (_) { - throw new Error('Cannot delete password'); - } - } } diff --git a/src/vs/workbench/api/browser/mainThreadBulkEdits.ts b/src/vs/workbench/api/browser/mainThreadBulkEdits.ts index 70fb045d7..8583555b8 100644 --- a/src/vs/workbench/api/browser/mainThreadBulkEdits.ts +++ b/src/vs/workbench/api/browser/mainThreadBulkEdits.ts @@ -3,29 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IBulkEditService, ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; -import { IExtHostContext, IWorkspaceEditDto, WorkspaceEditType, MainThreadBulkEditsShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; -import { revive } from 'vs/base/common/marshalling'; -import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits'; -import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; - -function reviveWorkspaceEditDto2(data: IWorkspaceEditDto | undefined): ResourceEdit[] { - if (!data?.edits) { - return []; - } - - const result: ResourceEdit[] = []; - for (let edit of revive(data).edits) { - if (edit._type === WorkspaceEditType.File) { - result.push(new ResourceFileEdit(edit.oldUri, edit.newUri, edit.options, edit.metadata)); - } else if (edit._type === WorkspaceEditType.Text) { - result.push(new ResourceTextEdit(edit.resource, edit.edit, edit.modelVersionId, edit.metadata)); - } else if (edit._type === WorkspaceEditType.Cell) { - result.push(new ResourceNotebookCellEdit(edit.resource, edit.edit, edit.notebookVersionId, edit.metadata)); - } - } - return result; -} +import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; +import { IExtHostContext, IWorkspaceEditDto, MainThreadBulkEditsShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { reviveWorkspaceEditDto2 } from 'vs/workbench/api/browser/mainThreadEditors'; @extHostNamedCustomer(MainContext.MainThreadBulkEdits) export class MainThreadBulkEdits implements MainThreadBulkEditsShape { @@ -39,12 +19,6 @@ export class MainThreadBulkEdits implements MainThreadBulkEditsShape { $tryApplyWorkspaceEdit(dto: IWorkspaceEditDto, undoRedoGroupId?: number): Promise { const edits = reviveWorkspaceEditDto2(dto); - return this._bulkEditService.apply(edits, { - // having a undoRedoGroupId means that this is a nested workspace edit, - // e.g one from a onWill-handler and for now we need to forcefully suppress - // refactor previewing, see: https://github.com/microsoft/vscode/issues/111873#issuecomment-738739852 - undoRedoGroupId, - suppressPreview: typeof undoRedoGroupId === 'number' ? true : undefined - }).then(() => true, _err => false); + return this._bulkEditService.apply(edits, { undoRedoGroupId }).then(() => true, _err => false); } } diff --git a/src/vs/workbench/api/browser/mainThreadCLICommands.ts b/src/vs/workbench/api/browser/mainThreadCLICommands.ts new file mode 100644 index 000000000..a02cdf6ea --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadCLICommands.ts @@ -0,0 +1,108 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Schemas } from 'vs/base/common/network'; +import { isString } from 'vs/base/common/types'; +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 { CLIOutput, IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagementCLIService'; +import { getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { canExecuteOnWorkspace } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { IExtensionManifest } from 'vs/workbench/workbench.web.api'; + + +// this class contains the commands that the CLI server is reying on + +CommandsRegistry.registerCommand('_remoteCLI.openExternal', function (accessor: ServicesAccessor, uri: UriComponents, options: { allowTunneling?: boolean }) { + // TODO: discuss martin, ben where to put this + const openerService = accessor.get(IOpenerService); + openerService.open(URI.revive(uri), { openExternal: true, allowTunneling: options?.allowTunneling === true }); +}); + +interface ManageExtensionsArgs { + list?: { showVersions?: boolean, category?: string; }; + install?: (string | URI)[]; + uninstall?: string[]; + force?: boolean; +} + +CommandsRegistry.registerCommand('_remoteCLI.manageExtensions', async function (accessor: ServicesAccessor, args: ManageExtensionsArgs) { + + const instantiationService = accessor.get(IInstantiationService); + const extensionManagementServerService = accessor.get(IExtensionManagementServerService); + const remoteExtensionManagementService = extensionManagementServerService.remoteExtensionManagementServer?.extensionManagementService; + if (!remoteExtensionManagementService) { + return; + } + + const cliService = instantiationService.createChild(new ServiceCollection([IExtensionManagementService, remoteExtensionManagementService])).createInstance(RemoteExtensionCLIManagementService); + + const lines: string[] = []; + const output = { log: lines.push.bind(lines), error: lines.push.bind(lines) }; + + if (args.list) { + await cliService.listExtensions(!!args.list.showVersions, args.list.category, output); + } else { + const revive = (inputs: (string | UriComponents)[]) => inputs.map(input => isString(input) ? input : URI.revive(input)); + if (Array.isArray(args.install) && args.install.length) { + try { + await cliService.installExtensions(revive(args.install), [], true, !!args.force, output); + } catch (e) { + lines.push(e.message); + } + } + if (Array.isArray(args.uninstall) && args.uninstall.length) { + try { + await cliService.uninstallExtensions(revive(args.uninstall), !!args.force, output); + } catch (e) { + lines.push(e.message); + } + } + } + return lines.join('\n'); +}); + +class RemoteExtensionCLIManagementService extends ExtensionManagementCLIService { + + private _location: string | undefined; + + constructor( + @IExtensionManagementService extensionManagementService: IExtensionManagementService, + @IProductService private readonly productService: IProductService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, + @ILocalizationsService localizationsService: ILocalizationsService, + @ILabelService labelService: ILabelService, + @IWorkbenchEnvironmentService envService: IWorkbenchEnvironmentService + ) { + super(extensionManagementService, extensionGalleryService, localizationsService); + + const remoteAuthority = envService.remoteAuthority; + this._location = remoteAuthority ? labelService.getHostLabel(Schemas.vscodeRemote, remoteAuthority) : undefined; + } + + protected get location(): string | undefined { + return this._location; + } + + protected validateExtensionKind(manifest: IExtensionManifest, output: CLIOutput): boolean { + if (!canExecuteOnWorkspace(manifest, this.productService, this.configurationService)) { + output.log(localize('cannot be installed', "Cannot install '{0}' because this extension has defined that it cannot run on the remote server.", getExtensionId(manifest.publisher, manifest.name))); + return false; + } + return true; + } +} diff --git a/src/vs/workbench/api/browser/mainThreadConsole.ts b/src/vs/workbench/api/browser/mainThreadConsole.ts index 4a244875f..d678f21bc 100644 --- a/src/vs/workbench/api/browser/mainThreadConsole.ts +++ b/src/vs/workbench/api/browser/mainThreadConsole.ts @@ -10,22 +10,18 @@ import { IRemoteConsoleLog, log } from 'vs/base/common/console'; import { logRemoteEntry } from 'vs/workbench/services/extensions/common/remoteConsoleUtil'; import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions'; import { ILogService } from 'vs/platform/log/common/log'; -import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; @extHostNamedCustomer(MainContext.MainThreadConsole) export class MainThreadConsole implements MainThreadConsoleShape { - private readonly _isExtensionDevHost: boolean; private readonly _isExtensionDevTestFromCli: boolean; constructor( - extHostContext: IExtHostContext, + _extHostContext: IExtHostContext, @IEnvironmentService private readonly _environmentService: IEnvironmentService, @ILogService private readonly _logService: ILogService, - @IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService, ) { const devOpts = parseExtensionDevOptions(this._environmentService); - this._isExtensionDevHost = devOpts.isExtensionDevHost; this._isExtensionDevTestFromCli = devOpts.isExtensionDevTestFromCli; } @@ -43,10 +39,5 @@ export class MainThreadConsole implements MainThreadConsoleShape { if (this._isExtensionDevTestFromCli) { logRemoteEntry(this._logService, entry); } - - // Broadcast to other windows if we are in development mode - else if (this._environmentService.debugExtensionHost.debugId && (!this._environmentService.isBuilt || this._isExtensionDevHost)) { - this._extensionHostDebugService.logToSession(this._environmentService.debugExtensionHost.debugId, entry); - } } } diff --git a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts index bcf31a1a7..e5f1d3f57 100644 --- a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts @@ -3,15 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { multibyteAwareBtoa } from 'vs/base/browser/dom'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore, IDisposable, IReference } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, dispose, IDisposable, IReference } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { basename } from 'vs/base/common/path'; import { isEqual, isEqualOrParent, toLocalResource } from 'vs/base/common/resources'; -import { multibyteAwareBtoa } from 'vs/base/browser/dom'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as modes from 'vs/editor/common/modes'; import { localize } from 'vs/nls'; @@ -95,10 +95,7 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc dispose() { super.dispose(); - for (const disposable of this._editorProviders.values()) { - disposable.dispose(); - } - + dispose(this._editorProviders.values()); this._editorProviders.clear(); } diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index f1906f16a..cb4c39856 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -327,7 +327,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb return { id: sessionID, type: session.configuration.type, - name: session.configuration.name, + name: session.name, folderUri: session.root ? session.root.uri : undefined, configuration: session.configuration }; diff --git a/src/vs/workbench/api/browser/mainThreadEditorTabs.ts b/src/vs/workbench/api/browser/mainThreadEditorTabs.ts new file mode 100644 index 000000000..2e6aaffa5 --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadEditorTabs.ts @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { ExtHostContext, IExtHostEditorTabsShape, IExtHostContext, MainContext, IEditorTabDto } from 'vs/workbench/api/common/extHost.protocol'; +import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { Verbosity } from 'vs/workbench/common/editor'; +import { GroupChangeKind, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; + +export interface ITabInfo { + name: string; + resource: URI; +} + +@extHostNamedCustomer(MainContext.MainThreadEditorTabs) +export class MainThreadEditorTabs { + + private static _GroupEventFilter = new Set([GroupChangeKind.EDITOR_CLOSE, GroupChangeKind.EDITOR_OPEN]); + + private readonly _dispoables = new DisposableStore(); + private readonly _groups = new Map(); + private readonly _proxy: IExtHostEditorTabsShape; + + constructor( + extHostContext: IExtHostContext, + @IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService, + ) { + + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostEditorTabs); + + this._editorGroupsService.groups.forEach(this._subscribeToGroup, this); + this._dispoables.add(_editorGroupsService.onDidAddGroup(this._subscribeToGroup, this)); + this._dispoables.add(_editorGroupsService.onDidRemoveGroup(e => { + const subscription = this._groups.get(e); + if (subscription) { + subscription.dispose(); + this._groups.delete(e); + this._pushEditorTabs(); + } + })); + this._pushEditorTabs(); + } + + dispose(): void { + dispose(this._groups.values()); + this._dispoables.dispose(); + } + + private _subscribeToGroup(group: IEditorGroup) { + this._groups.get(group)?.dispose(); + const listener = group.onDidGroupChange(e => { + if (MainThreadEditorTabs._GroupEventFilter.has(e.kind)) { + this._pushEditorTabs(); + } + }); + this._groups.set(group, listener); + } + + private _pushEditorTabs(): void { + const tabs: IEditorTabDto[] = []; + for (const group of this._editorGroupsService.groups) { + for (const editor of group.editors) { + if (editor.isDisposed() || !editor.resource) { + continue; + } + tabs.push({ + group: group.id, + name: editor.getTitle(Verbosity.SHORT) ?? '', + resource: editor.resource + }); + } + } + + this._proxy.$acceptEditorTabs(tabs); + } +} diff --git a/src/vs/workbench/api/browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts index 1723f6328..0f52d9af1 100644 --- a/src/vs/workbench/api/browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -27,7 +27,7 @@ import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/wo import { revive } from 'vs/base/common/marshalling'; import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits'; -function reviveWorkspaceEditDto2(data: IWorkspaceEditDto | undefined): ResourceEdit[] { +export function reviveWorkspaceEditDto2(data: IWorkspaceEditDto | undefined): ResourceEdit[] { if (!data?.edits) { return []; } diff --git a/src/vs/workbench/api/browser/mainThreadExtensionService.ts b/src/vs/workbench/api/browser/mainThreadExtensionService.ts index 5d0132e29..11cfcc3db 100644 --- a/src/vs/workbench/api/browser/mainThreadExtensionService.ts +++ b/src/vs/workbench/api/browser/mainThreadExtensionService.ts @@ -7,7 +7,7 @@ import { SerializedError } from 'vs/base/common/errors'; import Severity from 'vs/base/common/severity'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { IExtHostContext, MainContext, MainThreadExtensionServiceShape } from 'vs/workbench/api/common/extHost.protocol'; -import { IExtensionService, ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionService, ExtensionActivationError, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { localize } from 'vs/nls'; @@ -19,29 +19,23 @@ import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/com import { CancellationToken } from 'vs/base/common/cancellation'; import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; +import { ITimerService } from 'vs/workbench/services/timer/browser/timerService'; @extHostNamedCustomer(MainContext.MainThreadExtensionService) export class MainThreadExtensionService implements MainThreadExtensionServiceShape { - private readonly _extensionService: IExtensionService; - private readonly _notificationService: INotificationService; - private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService; - private readonly _hostService: IHostService; - private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService; + private readonly _extensionHostKind: ExtensionHostKind; constructor( extHostContext: IExtHostContext, - @IExtensionService extensionService: IExtensionService, - @INotificationService notificationService: INotificationService, - @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, - @IHostService hostService: IHostService, - @IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService + @IExtensionService private readonly _extensionService: IExtensionService, + @INotificationService private readonly _notificationService: INotificationService, + @IExtensionsWorkbenchService private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService, + @IHostService private readonly _hostService: IHostService, + @IWorkbenchExtensionEnablementService private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService, + @ITimerService private readonly _timerService: ITimerService, ) { - this._extensionService = extensionService; - this._notificationService = notificationService; - this._extensionsWorkbenchService = extensionsWorkbenchService; - this._hostService = hostService; - this._extensionEnablementService = extensionEnablementService; + this._extensionHostKind = extHostContext.extensionHostKind; } public dispose(): void { @@ -131,4 +125,14 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha async $onExtensionHostExit(code: number): Promise { this._extensionService._onExtensionHostExit(code); } + + async $setPerformanceMarks(marks: PerformanceMark[]): Promise { + if (this._extensionHostKind === ExtensionHostKind.LocalProcess) { + this._timerService.setPerformanceMarks('localExtHost', marks); + } else if (this._extensionHostKind === ExtensionHostKind.LocalWebWorker) { + this._timerService.setPerformanceMarks('workerExtHost', marks); + } else { + this._timerService.setPerformanceMarks('remoteExtHost', marks); + } + } } diff --git a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts index 81905c6b1..c03943332 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts @@ -4,23 +4,43 @@ *--------------------------------------------------------------------------------------------*/ import { DisposableStore } from 'vs/base/common/lifecycle'; -import { FileChangeType, IFileService } from 'vs/platform/files/common/files'; +import { FileChangeType, FileOperation, IFileService } from 'vs/platform/files/common/files'; import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { ExtHostContext, FileSystemEvents, IExtHostContext } from '../common/extHost.protocol'; import { localize } from 'vs/nls'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { IWorkingCopyFileOperationParticipant, IWorkingCopyFileService, SourceTargetPair, IFileOperationUndoRedoInfo } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { reviveWorkspaceEditDto2 } from 'vs/workbench/api/browser/mainThreadEditors'; +import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { raceCancellation } from 'vs/base/common/async'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import Severity from 'vs/base/common/severity'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @extHostCustomer export class MainThreadFileSystemEventService { + static readonly MementoKeyAdditionalEdits = `file.particpants.additionalEdits`; + private readonly _listener = new DisposableStore(); constructor( extHostContext: IExtHostContext, @IFileService fileService: IFileService, - @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService + @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService, + @IBulkEditService bulkEditService: IBulkEditService, + @IProgressService progressService: IProgressService, + @IDialogService dialogService: IDialogService, + @IStorageService storageService: IStorageService, + @ILogService logService: ILogService, + @IEnvironmentService envService: IEnvironmentService ) { const proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemEventService); @@ -53,14 +73,124 @@ export class MainThreadFileSystemEventService { })); - // BEFORE file operation - this._listener.add(workingCopyFileService.addFileOperationParticipant({ - participate: async (files, operation, undoRedoGroupId, isUndoing, _progress, timeout, token) => { - if (!isUndoing) { - return proxy.$onWillRunFileOperation(operation, files, undoRedoGroupId, timeout, token); + const fileOperationParticipant = new class implements IWorkingCopyFileOperationParticipant { + async participate(files: SourceTargetPair[], operation: FileOperation, undoInfo: IFileOperationUndoRedoInfo | undefined, timeout: number, token: CancellationToken) { + if (undoInfo?.isUndoing) { + return; + } + + const cts = new CancellationTokenSource(token); + const timer = setTimeout(() => cts.cancel(), timeout); + + const data = await progressService.withProgress({ + location: ProgressLocation.Notification, + title: this._progressLabel(operation), + cancellable: true, + delay: Math.min(timeout / 2, 3000) + }, () => { + // race extension host event delivery against timeout AND user-cancel + const onWillEvent = proxy.$onWillRunFileOperation(operation, files, timeout, token); + return raceCancellation(onWillEvent, cts.token); + }, () => { + // user-cancel + cts.cancel(); + + }).finally(() => { + cts.dispose(); + clearTimeout(timer); + }); + + if (!data) { + // cancelled or no reply + return; + } + + const needsConfirmation = data.edit.edits.some(edit => edit.metadata?.needsConfirmation); + let showPreview = storageService.getBoolean(MainThreadFileSystemEventService.MementoKeyAdditionalEdits, StorageScope.GLOBAL); + + if (envService.extensionTestsLocationURI) { + // don't show dialog in tests + showPreview = false; + } + + if (showPreview === undefined) { + // show a user facing message + + let message: string; + if (data.extensionNames.length === 1) { + if (operation === FileOperation.CREATE) { + message = localize('ask.1.create', "Extension '{0}' wants to make refactoring changes with this file creation", data.extensionNames[0]); + } else if (operation === FileOperation.COPY) { + message = localize('ask.1.copy', "Extension '{0}' wants to make refactoring changes with this file copy", data.extensionNames[0]); + } else if (operation === FileOperation.MOVE) { + message = localize('ask.1.move', "Extension '{0}' wants to make refactoring changes with this file move", data.extensionNames[0]); + } else /* if (operation === FileOperation.DELETE) */ { + message = localize('ask.1.delete', "Extension '{0}' wants to make refactoring changes with this file deletion", data.extensionNames[0]); + } + } else { + if (operation === FileOperation.CREATE) { + message = localize('ask.N.create', "{0} extensions want to make refactoring changes with this file creation", data.extensionNames.length); + } else if (operation === FileOperation.COPY) { + message = localize('ask.N.copy', "{0} extensions want to make refactoring changes with this file copy", data.extensionNames.length); + } else if (operation === FileOperation.MOVE) { + message = localize('ask.N.move', "{0} extensions want to make refactoring changes with this file move", data.extensionNames.length); + } else /* if (operation === FileOperation.DELETE) */ { + message = localize('ask.N.delete', "{0} extensions want to make refactoring changes with this file deletion", data.extensionNames.length); + } + } + + if (needsConfirmation) { + // edit which needs confirmation -> always show dialog + const answer = await dialogService.show(Severity.Info, message, [localize('preview', "Show Preview"), localize('cancel', "Skip Changes")], { cancelId: 1 }); + showPreview = true; + if (answer.choice === 1) { + // no changes wanted + return; + } + } else { + // choice + const answer = await dialogService.show(Severity.Info, message, + [localize('ok', "OK"), localize('preview', "Show Preview"), localize('cancel', "Skip Changes")], + { + cancelId: 2, + checkbox: { label: localize('again', "Don't ask again") } + } + ); + if (answer.choice === 2) { + // no changes wanted, don't persist cancel option + return; + } + showPreview = answer.choice === 1; + if (answer.checkboxChecked /* && answer.choice !== 2 */) { + storageService.store(MainThreadFileSystemEventService.MementoKeyAdditionalEdits, showPreview, StorageScope.GLOBAL, StorageTarget.USER); + } + } + } + + logService.info('[onWill-handler] applying additional workspace edit from extensions', data.extensionNames); + + await bulkEditService.apply( + reviveWorkspaceEditDto2(data.edit), + { undoRedoGroupId: undoInfo?.undoRedoGroupId, showPreview } + ); + } + + private _progressLabel(operation: FileOperation): string { + switch (operation) { + case FileOperation.CREATE: + return localize('msg-create', "Running 'File Create' participants..."); + case FileOperation.MOVE: + return localize('msg-rename', "Running 'File Rename' participants..."); + case FileOperation.COPY: + return localize('msg-copy', "Running 'File Copy' participants..."); + case FileOperation.DELETE: + return localize('msg-delete', "Running 'File Delete' participants..."); } } - })); + }; + + // BEFORE file operation + this._listener.add(workingCopyFileService.addFileOperationParticipant(fileOperationParticipant)); // AFTER file operation this._listener.add(workingCopyFileService.onDidRunWorkingCopyFileOperation(e => proxy.$onDidRunFileOperation(e.operation, e.files))); @@ -71,6 +201,19 @@ export class MainThreadFileSystemEventService { } } +registerAction2(class ResetMemento extends Action2 { + constructor() { + super({ + id: 'files.participants.resetChoice', + title: localize('label', "Reset choice for 'File operation needs preview'"), + f1: true + }); + } + run(accessor: ServicesAccessor) { + accessor.get(IStorageService).remove(MainThreadFileSystemEventService.MementoKeyAdditionalEdits, StorageScope.GLOBAL); + } +}); + Registry.as(Extensions.Configuration).registerConfiguration({ id: 'files', diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index ddea8e0e9..391cc7add 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -21,7 +21,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { mixin } from 'vs/base/common/objects'; -import { decodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokensDto'; +import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape { @@ -497,6 +497,32 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha })); } + // --- inline hints + + $registerInlineHintsProvider(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void { + const provider = { + provideInlineHints: async (model: ITextModel, range: EditorRange, token: CancellationToken): Promise => { + const result = await this._proxy.$provideInlineHints(handle, model.uri, range, token); + return result?.hints; + } + }; + + if (typeof eventHandle === 'number') { + const emitter = new Emitter(); + this._registrations.set(eventHandle, emitter); + provider.onDidChangeInlineHints = emitter.event; + } + + this._registrations.set(handle, modes.InlineHintsProviderRegistry.register(selector, provider)); + } + + $emitInlineHintsEvent(eventHandle: number, event?: any): void { + const obj = this._registrations.get(eventHandle); + if (obj instanceof Emitter) { + obj.fire(event); + } + } + // --- links $registerDocumentLinkProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean): void { @@ -662,7 +688,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha return { beforeText: MainThreadLanguageFeatures._reviveRegExp(onEnterRule.beforeText), afterText: onEnterRule.afterText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.afterText) : undefined, - oneLineAboveText: onEnterRule.oneLineAboveText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.oneLineAboveText) : undefined, + previousLineText: onEnterRule.previousLineText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.previousLineText) : undefined, action: onEnterRule.action }; } diff --git a/src/vs/workbench/api/browser/mainThreadMessageService.ts b/src/vs/workbench/api/browser/mainThreadMessageService.ts index c956da47b..b7470ac8f 100644 --- a/src/vs/workbench/api/browser/mainThreadMessageService.ts +++ b/src/vs/workbench/api/browser/mainThreadMessageService.ts @@ -33,7 +33,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape { $showMessage(severity: Severity, message: string, options: MainThreadMessageOptions, commands: { title: string; isCloseAffordance: boolean; handle: number; }[]): Promise { if (options.modal) { - return this._showModalMessage(severity, message, commands); + return this._showModalMessage(severity, message, commands, options.useCustom); } else { return this._showMessage(severity, message, commands, options.extension); } @@ -97,7 +97,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape { }); } - private async _showModalMessage(severity: Severity, message: string, commands: { title: string; isCloseAffordance: boolean; handle: number; }[]): Promise { + private async _showModalMessage(severity: Severity, message: string, commands: { title: string; isCloseAffordance: boolean; handle: number; }[], useCustom?: boolean): Promise { let cancelId: number | undefined = undefined; const buttons = commands.map((command, index) => { @@ -118,7 +118,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape { cancelId = buttons.length - 1; } - const { choice } = await this._dialogService.show(severity, message, buttons, { cancelId }); + const { choice } = await this._dialogService.show(severity, message, buttons, { cancelId, useCustom }); return choice === commands.length ? undefined : commands[choice].handle; } } diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index cbf34422d..0b4b7f3f5 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -16,8 +16,8 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { EditorActivation, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { viewColumnToEditorGroup } from 'vs/workbench/common/editor'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -129,12 +129,12 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IAccessibilityService private readonly accessibilityService: IAccessibilityService, - @IQuickInputService private readonly quickInputService: IQuickInputService, @ILogService private readonly logService: ILogService, @INotebookCellStatusBarService private readonly cellStatusBarService: INotebookCellStatusBarService, @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, @INotebookEditorModelResolverService private readonly _notebookModelResolverService: INotebookEditorModelResolverService, @IUriIdentityService private readonly _uriIdentityService: IUriIdentityService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook); @@ -646,14 +646,13 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo switch (revealType) { case NotebookEditorRevealType.Default: - notebookEditor.revealInView(cell); - break; + return notebookEditor.revealCellRangeInView(range); case NotebookEditorRevealType.InCenter: - notebookEditor.revealInCenter(cell); - break; + return notebookEditor.revealInCenter(cell); case NotebookEditorRevealType.InCenterIfOutsideViewport: - notebookEditor.revealInCenterIfOutsideViewport(cell); - break; + return notebookEditor.revealInCenterIfOutsideViewport(cell); + case NotebookEditorRevealType.AtTop: + return notebookEditor.revealInViewAtTop(cell); default: break; } @@ -733,7 +732,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo const input = this.editorService.createEditorInput({ resource: URI.revive(resource), options: editorOptions }); // TODO: handle options.selection - const editorPane = await openEditorWith(input, viewType, options, group, this.editorService, this.configurationService, this.quickInputService); + const editorPane = await this._instantiationService.invokeFunction(openEditorWith, input, viewType, options, group); const notebookEditor = (editorPane as unknown as { isNotebookEditor?: boolean })?.isNotebookEditor ? (editorPane!.getControl() as INotebookEditor) : undefined; if (notebookEditor) { diff --git a/src/vs/workbench/api/browser/mainThreadSecretState.ts b/src/vs/workbench/api/browser/mainThreadSecretState.ts new file mode 100644 index 000000000..4067acedb --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadSecretState.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 { Disposable } from 'vs/base/common/lifecycle'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials'; +import { IEncryptionService } from 'vs/workbench/services/encryption/common/encryptionService'; +import { ExtHostContext, ExtHostSecretStateShape, IExtHostContext, MainContext, MainThreadSecretStateShape } from '../common/extHost.protocol'; + +@extHostNamedCustomer(MainContext.MainThreadSecretState) +export class MainThreadSecretState extends Disposable implements MainThreadSecretStateShape { + private readonly _proxy: ExtHostSecretStateShape; + + constructor( + extHostContext: IExtHostContext, + @ICredentialsService private readonly credentialsService: ICredentialsService, + @IEncryptionService private readonly encryptionService: IEncryptionService, + @IProductService private readonly productService: IProductService + ) { + super(); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostSecretState); + + this._register(this.credentialsService.onDidChangePassword(e => { + const extensionId = e.service.substring(this.productService.urlProtocol.length); + this._proxy.$onDidChangePassword({ extensionId, key: e.account }); + })); + } + + private getFullKey(extensionId: string): string { + return `${this.productService.urlProtocol}${extensionId}`; + } + + async $getPassword(extensionId: string, key: string): Promise { + const fullKey = this.getFullKey(extensionId); + const password = await this.credentialsService.getPassword(fullKey, key); + const decrypted = password && await this.encryptionService.decrypt(password); + + if (decrypted) { + try { + const value = JSON.parse(decrypted); + if (value.extensionId === extensionId) { + return value.content; + } + } catch (_) { + throw new Error('Cannot get password'); + } + } + + return undefined; + } + + async $setPassword(extensionId: string, key: string, value: string): Promise { + const fullKey = this.getFullKey(extensionId); + const toEncrypt = JSON.stringify({ + extensionId, + content: value + }); + const encrypted = await this.encryptionService.encrypt(toEncrypt); + return this.credentialsService.setPassword(fullKey, key, encrypted); + } + + async $deletePassword(extensionId: string, key: string): Promise { + try { + const fullKey = this.getFullKey(extensionId); + await this.credentialsService.deletePassword(fullKey, key); + } catch (_) { + throw new Error('Cannot delete password'); + } + } +} diff --git a/src/vs/workbench/api/browser/mainThreadTask.ts b/src/vs/workbench/api/browser/mainThreadTask.ts index 2f4cb073c..3cc31f086 100644 --- a/src/vs/workbench/api/browser/mainThreadTask.ts +++ b/src/vs/workbench/api/browser/mainThreadTask.ts @@ -567,13 +567,19 @@ export class MainThreadTask implements MainThreadTaskShape { if (!task) { reject(new Error('Task not found')); } else { - this._taskService.run(task).then(undefined, reason => { - // eat the error, it has already been surfaced to the user and we don't care about it here - }); const result: TaskExecutionDTO = { id: value.id, task: TaskDTO.from(task) }; + this._taskService.run(task).then(summary => { + // Ensure that the task execution gets cleaned up if the exit code is undefined + // This can happen when the task has dependent tasks and one of them failed + if ((summary?.exitCode === undefined) || (summary.exitCode !== 0)) { + this._proxy.$OnDidEndTask(result); + } + }, reason => { + // eat the error, it has already been surfaced to the user and we don't care about it here + }); resolve(result); } }, (_error) => { diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 8e54cb8f4..8dad46c6a 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { IShellLaunchConfig, ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY, IAvailableShellsRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest } from 'vs/workbench/contrib/terminal/common/terminal'; -import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, IShellLaunchConfigDto, TerminalLaunchConfig, ITerminalDimensionsDto } from 'vs/workbench/api/common/extHost.protocol'; +import { IShellLaunchConfig, ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, ITerminalDimensions, IAvailableShellsRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest, ITerminalConfiguration, TERMINAL_CONFIG_SECTION } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, IShellLaunchConfigDto, TerminalLaunchConfig, ITerminalDimensionsDto, TerminalIdentifier } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { URI } from 'vs/base/common/uri'; import { StopWatch } from 'vs/base/common/stopwatch'; @@ -16,11 +16,18 @@ import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/termi import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared'; import { ILogService } from 'vs/platform/log/common/log'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @extHostNamedCustomer(MainContext.MainThreadTerminalService) export class MainThreadTerminalService implements MainThreadTerminalServiceShape { private _proxy: ExtHostTerminalServiceShape; + /** + * Stores a map from a temporary terminal id (a UUID generated on the extension host side) + * to a numeric terminal id (an id generated on the renderer side) + * This comes in play only when dealing with terminals created on the extension host side + */ + private _extHostTerminalIds = new Map(); private _remoteAuthority: string | null; private readonly _toDispose = new DisposableStore(); private readonly _terminalProcessProxies = new Map(); @@ -40,6 +47,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService, + @IConfigurationService private readonly _configurationService: IConfigurationService, @ILogService private readonly _logService: ILogService, ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService); @@ -47,13 +55,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape // ITerminalService listeners this._toDispose.add(_terminalService.onInstanceCreated((instance) => { - // Delay this message so the TerminalInstance constructor has a chance to finish and - // return the ID normally to the extension host. The ID that is passed here will be - // used to register non-extension API terminals in the extension host. - setTimeout(() => { - this._onTerminalOpened(instance); - this._onInstanceDimensionsChanged(instance); - }, EXT_HOST_CREATION_DELAY); + this._onTerminalOpened(instance); + this._onInstanceDimensionsChanged(instance); })); this._toDispose.add(_terminalService.onInstanceDisposed(instance => this._onTerminalDisposed(instance))); @@ -100,7 +103,22 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape // when the extension host process goes down ? } - public $createTerminal(launchConfig: TerminalLaunchConfig): Promise<{ id: number, name: string }> { + private _getTerminalId(id: TerminalIdentifier): number | undefined { + if (typeof id === 'number') { + return id; + } + return this._extHostTerminalIds.get(id); + } + + private _getTerminalInstance(id: TerminalIdentifier): ITerminalInstance | undefined { + const rendererId = this._getTerminalId(id); + if (typeof rendererId === 'number') { + return this._terminalService.getInstanceFromId(rendererId); + } + return undefined; + } + + public async $createTerminal(extHostTerminalId: string, launchConfig: TerminalLaunchConfig): Promise { const shellLaunchConfig: IShellLaunchConfig = { name: launchConfig.name, executable: launchConfig.shellPath, @@ -112,39 +130,38 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape strictEnv: launchConfig.strictEnv, hideFromUser: launchConfig.hideFromUser, isExtensionTerminal: launchConfig.isExtensionTerminal, + extHostTerminalId: extHostTerminalId, isFeatureTerminal: launchConfig.isFeatureTerminal }; const terminal = this._terminalService.createTerminal(shellLaunchConfig); - return Promise.resolve({ - id: terminal.id, - name: terminal.title - }); + this._extHostTerminalIds.set(extHostTerminalId, terminal.id); } - public $show(terminalId: number, preserveFocus: boolean): void { - const terminalInstance = this._terminalService.getInstanceFromId(terminalId); + public $show(id: TerminalIdentifier, preserveFocus: boolean): void { + const terminalInstance = this._getTerminalInstance(id); if (terminalInstance) { this._terminalService.setActiveInstance(terminalInstance); this._terminalService.showPanel(!preserveFocus); } } - public $hide(terminalId: number): void { + public $hide(id: TerminalIdentifier): void { + const rendererId = this._getTerminalId(id); const instance = this._terminalService.getActiveInstance(); - if (instance && instance.id === terminalId) { + if (instance && instance.id === rendererId) { this._terminalService.hidePanel(); } } - public $dispose(terminalId: number): void { - const terminalInstance = this._terminalService.getInstanceFromId(terminalId); + public $dispose(id: TerminalIdentifier): void { + const terminalInstance = this._getTerminalInstance(id); if (terminalInstance) { terminalInstance.dispose(); } } - public $sendText(terminalId: number, text: string, addNewLine: boolean): void { - const terminalInstance = this._terminalService.getInstanceFromId(terminalId); + public $sendText(id: TerminalIdentifier, text: string, addNewLine: boolean): void { + const terminalInstance = this._getTerminalInstance(id); if (terminalInstance) { terminalInstance.sendText(text, addNewLine); } @@ -204,6 +221,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } private _onTerminalOpened(terminalInstance: ITerminalInstance): void { + const extHostTerminalId = terminalInstance.shellLaunchConfig.extHostTerminalId; const shellLaunchConfigDto: IShellLaunchConfigDto = { name: terminalInstance.shellLaunchConfig.name, executable: terminalInstance.shellLaunchConfig.executable, @@ -212,13 +230,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape env: terminalInstance.shellLaunchConfig.env, hideFromUser: terminalInstance.shellLaunchConfig.hideFromUser }; - if (terminalInstance.title) { - this._proxy.$acceptTerminalOpened(terminalInstance.id, terminalInstance.title, shellLaunchConfigDto); - } else { - terminalInstance.waitForTitle().then(title => { - this._proxy.$acceptTerminalOpened(terminalInstance.id, title, shellLaunchConfigDto); - }); - } + this._proxy.$acceptTerminalOpened(terminalInstance.id, extHostTerminalId, terminalInstance.title, shellLaunchConfigDto); } private _onTerminalProcessIdReady(terminalInstance: ITerminalInstance): void { @@ -249,7 +261,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape executable: request.shellLaunchConfig.executable, args: request.shellLaunchConfig.args, cwd: request.shellLaunchConfig.cwd, - env: request.shellLaunchConfig.env + env: request.shellLaunchConfig.env, + flowControl: this._configurationService.getValue(TERMINAL_CONFIG_SECTION).flowControl }; this._logService.trace('Spawning ext host process', { terminalId: proxy.terminalId, shellLaunchConfigDto, request }); @@ -260,8 +273,9 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape request.cols, request.rows, request.isWorkspaceShellAllowed - ).then(request.callback); + ).then(request.callback, request.callback); + proxy.onAcknowledgeDataEvent(charCount => this._proxy.$acceptProcessAckDataEvent(proxy.terminalId, charCount)); proxy.onInput(data => this._proxy.$acceptProcessInput(proxy.terminalId, data)); proxy.onResize(dimensions => this._proxy.$acceptProcessResize(proxy.terminalId, dimensions.cols, dimensions.rows)); proxy.onShutdown(immediate => this._proxy.$acceptProcessShutdown(proxy.terminalId, immediate)); @@ -294,38 +308,59 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } public $sendProcessTitle(terminalId: number, title: string): void { - this._getTerminalProcess(terminalId).emitTitle(title); + const terminalProcess = this._terminalProcessProxies.get(terminalId); + if (terminalProcess) { + terminalProcess.emitTitle(title); + } } public $sendProcessData(terminalId: number, data: string): void { - this._getTerminalProcess(terminalId).emitData(data); + const terminalProcess = this._terminalProcessProxies.get(terminalId); + if (terminalProcess) { + terminalProcess.emitData(data); + } } public $sendProcessReady(terminalId: number, pid: number, cwd: string): void { - this._getTerminalProcess(terminalId).emitReady(pid, cwd); + const terminalProcess = this._terminalProcessProxies.get(terminalId); + if (terminalProcess) { + terminalProcess.emitReady(pid, cwd); + } } public $sendProcessExit(terminalId: number, exitCode: number | undefined): void { - this._getTerminalProcess(terminalId).emitExit(exitCode); - this._terminalProcessProxies.delete(terminalId); + const terminalProcess = this._terminalProcessProxies.get(terminalId); + if (terminalProcess) { + terminalProcess.emitExit(exitCode); + this._terminalProcessProxies.delete(terminalId); + } } public $sendOverrideDimensions(terminalId: number, dimensions: ITerminalDimensions | undefined): void { - this._getTerminalProcess(terminalId).emitOverrideDimensions(dimensions); + const terminalProcess = this._terminalProcessProxies.get(terminalId); + if (terminalProcess) { + terminalProcess.emitOverrideDimensions(dimensions); + } } public $sendProcessInitialCwd(terminalId: number, initialCwd: string): void { - this._getTerminalProcess(terminalId).emitInitialCwd(initialCwd); + const terminalProcess = this._terminalProcessProxies.get(terminalId); + if (terminalProcess) { + terminalProcess.emitInitialCwd(initialCwd); + } } public $sendProcessCwd(terminalId: number, cwd: string): void { - this._getTerminalProcess(terminalId).emitCwd(cwd); + const terminalProcess = this._terminalProcessProxies.get(terminalId); + if (terminalProcess) { + terminalProcess.emitCwd(cwd); + } } public $sendResolvedLaunchConfig(terminalId: number, shellLaunchConfig: IShellLaunchConfig): void { const instance = this._terminalService.getInstanceFromId(terminalId); if (instance) { - this._getTerminalProcess(terminalId).emitResolvedShellLaunchConfig(shellLaunchConfig); + this._getTerminalProcess(terminalId)?.emitResolvedShellLaunchConfig(shellLaunchConfig); } } @@ -338,7 +373,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape sw.stop(); sum += sw.elapsed(); } - this._getTerminalProcess(terminalId).emitLatency(sum / COUNT); + this._getTerminalProcess(terminalId)?.emitLatency(sum / COUNT); } private _isPrimaryExtHost(): boolean { @@ -363,10 +398,11 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } } - private _getTerminalProcess(terminalId: number): ITerminalProcessExtHostProxy { + private _getTerminalProcess(terminalId: number): ITerminalProcessExtHostProxy | undefined { const terminal = this._terminalProcessProxies.get(terminalId); if (!terminal) { - throw new Error(`Unknown terminal: ${terminalId}`); + this._logService.error(`Unknown terminal: ${terminalId}`); + return undefined; } return terminal; } diff --git a/src/vs/workbench/api/browser/mainThreadTesting.ts b/src/vs/workbench/api/browser/mainThreadTesting.ts index 52185f8dd..370f08df9 100644 --- a/src/vs/workbench/api/browser/mainThreadTesting.ts +++ b/src/vs/workbench/api/browser/mainThreadTesting.ts @@ -3,12 +3,31 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { getTestSubscriptionKey, RunTestsRequest, RunTestsResult, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; -import { ITestService } from 'vs/workbench/contrib/testing/common/testService'; -import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { ExtHostContext, ExtHostTestingResource, ExtHostTestingShape, IExtHostContext, MainContext, MainThreadTestingShape } from '../common/extHost.protocol'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; +import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { getTestSubscriptionKey, RunTestsRequest, RunTestsResult, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; +import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; +import { ITestService } from 'vs/workbench/contrib/testing/common/testService'; +import { ExtHostContext, ExtHostTestingResource, ExtHostTestingShape, IExtHostContext, MainContext, MainThreadTestingShape } from '../common/extHost.protocol'; + +const reviveDiff = (diff: TestsDiff) => { + for (const entry of diff) { + if (entry[0] === TestDiffOpType.Add || entry[0] === TestDiffOpType.Update) { + const item = entry[1]; + if (item.item.location) { + item.item.location.uri = URI.revive(item.item.location.uri); + } + + for (const message of item.item.state.messages) { + if (message.location) { + message.location.uri = URI.revive(message.location.uri); + } + } + } + } +}; @extHostNamedCustomer(MainContext.MainThreadTesting) export class MainThreadTesting extends Disposable implements MainThreadTestingShape { @@ -18,11 +37,28 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh constructor( extHostContext: IExtHostContext, @ITestService private readonly testService: ITestService, + @ITestResultService resultService: ITestResultService, ) { super(); this.proxy = extHostContext.getProxy(ExtHostContext.ExtHostTesting); this._register(this.testService.onShouldSubscribe(args => this.proxy.$subscribeToTests(args.resource, args.uri))); this._register(this.testService.onShouldUnsubscribe(args => this.proxy.$unsubscribeFromTests(args.resource, args.uri))); + + const testCompleteListener = this._register(new MutableDisposable()); + this._register(resultService.onNewTestResult(results => { + testCompleteListener.value = results.onComplete(() => this.proxy.$publishTestResults({ tests: results.tests })); + })); + + testService.updateRootProviderCount(1); + + const lastCompleted = resultService.results.find(r => !r.isComplete); + if (lastCompleted) { + this.proxy.$publishTestResults({ tests: lastCompleted.tests }); + } + + for (const { resource, uri } of this.testService.subscriptions) { + this.proxy.$subscribeToTests(resource, uri); + } } /** @@ -30,7 +66,8 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh */ public $registerTestProvider(id: string) { this.testService.registerTestController(id, { - runTests: req => this.proxy.$runTestsForProvider(req), + runTests: (req, token) => this.proxy.$runTestsForProvider(req, token), + lookupTest: test => this.proxy.$lookupTest(test), }); } @@ -64,14 +101,19 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh * @inheritdoc */ public $publishDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void { + reviveDiff(diff); this.testService.publishDiff(resource, URI.revive(uri), diff); } - public $runTests(req: RunTestsRequest): Promise { - return this.testService.runTests(req); + public $runTests(req: RunTestsRequest, token: CancellationToken): Promise { + return this.testService.runTests(req, token); } public dispose() { - // no-op + this.testService.updateRootProviderCount(-1); + for (const subscription of this.testSubscriptions.values()) { + subscription.dispose(); + } + this.testSubscriptions.clear(); } } diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index 8e32acd7f..cfe9d842e 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -222,8 +222,8 @@ class TreeViewDataProvider implements ITreeViewDataProvider { const hasResolve = await this.hasResolve; if (elements) { for (const element of elements) { - const resolvable = new ResolvableTreeItem(element, hasResolve ? () => { - return this._proxy.$resolve(this.treeViewId, element.handle); + const resolvable = new ResolvableTreeItem(element, hasResolve ? (token) => { + return this._proxy.$resolve(this.treeViewId, element.handle, token); } : undefined); this.itemsMap.set(element.handle, resolvable); result.push(resolvable); diff --git a/src/vs/workbench/api/browser/mainThreadTunnelService.ts b/src/vs/workbench/api/browser/mainThreadTunnelService.ts index ac9b83562..82425aadb 100644 --- a/src/vs/workbench/api/browser/mainThreadTunnelService.ts +++ b/src/vs/workbench/api/browser/mainThreadTunnelService.ts @@ -3,22 +3,31 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostContext, ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { IRemoteExplorerService, makeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService'; -import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel'; +import { CandidatePort, IRemoteExplorerService, makeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, RemoteTunnel, isPortPrivileged } from 'vs/platform/remote/common/tunnel'; import { Disposable } from 'vs/base/common/lifecycle'; import type { TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { PORT_AUTO_FORWARD_SETTING } from 'vs/workbench/contrib/remote/browser/tunnelView'; +import { ILogService } from 'vs/platform/log/common/log'; @extHostNamedCustomer(MainContext.MainThreadTunnelService) export class MainThreadTunnelService extends Disposable implements MainThreadTunnelServiceShape { private readonly _proxy: ExtHostTunnelServiceShape; + private elevateionRetry: boolean = false; constructor( extHostContext: IExtHostContext, @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService, - @ITunnelService private readonly tunnelService: ITunnelService + @ITunnelService private readonly tunnelService: ITunnelService, + @INotificationService private readonly notificationService: INotificationService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @ILogService private readonly logService: ILogService ) { super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTunnelService); @@ -26,14 +35,50 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun this._register(tunnelService.onTunnelClosed(() => this._proxy.$onDidTunnelsChange())); } + async $setCandidateFinder(): Promise { + if (this.remoteExplorerService.portsFeaturesEnabled) { + this._proxy.$registerCandidateFinder(this.configurationService.getValue(PORT_AUTO_FORWARD_SETTING)); + } else { + this._register(this.remoteExplorerService.onEnabledPortsFeatures(() => this._proxy.$registerCandidateFinder(this.configurationService.getValue(PORT_AUTO_FORWARD_SETTING)))); + } + this._register(this.configurationService.onDidChangeConfiguration(async (e) => { + if (e.affectsConfiguration(PORT_AUTO_FORWARD_SETTING)) { + return this._proxy.$registerCandidateFinder((this.configurationService.getValue(PORT_AUTO_FORWARD_SETTING))); + } + })); + } + async $openTunnel(tunnelOptions: TunnelOptions, source: string): Promise { - const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remoteAddress, tunnelOptions.localAddressPort, tunnelOptions.label, source); + const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remoteAddress, tunnelOptions.localAddressPort, tunnelOptions.label, source, false); if (tunnel) { + if (!this.elevateionRetry + && (tunnelOptions.localAddressPort !== undefined) + && (tunnel.tunnelLocalPort !== undefined) + && isPortPrivileged(tunnelOptions.localAddressPort) + && (tunnel.tunnelLocalPort !== tunnelOptions.localAddressPort) + && this.tunnelService.canElevate) { + + this.elevationPrompt(tunnelOptions, tunnel, source); + } return TunnelDto.fromServiceTunnel(tunnel); } return undefined; } + private async elevationPrompt(tunnelOptions: TunnelOptions, tunnel: RemoteTunnel, source: string) { + return this.notificationService.prompt(Severity.Info, + nls.localize('remote.tunnel.openTunnel', "The extension {0} has forwarded port {1}. You'll need to run as superuser to use port {2} locally.", source, tunnelOptions.remoteAddress.port, tunnelOptions.localAddressPort), + [{ + label: nls.localize('remote.tunnelsView.elevationButton', "Use Port {0} as Sudo...", tunnel.tunnelRemotePort), + run: async () => { + this.elevateionRetry = true; + await this.remoteExplorerService.close({ host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }); + await this.remoteExplorerService.forward(tunnelOptions.remoteAddress, tunnelOptions.localAddressPort, tunnelOptions.label, source, true); + this.elevateionRetry = false; + } + }]); + } + async $closeTunnel(remote: { host: string, port: number }): Promise { return this.remoteExplorerService.close(remote); } @@ -47,27 +92,29 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun }); } - async $onFoundNewCandidates(candidates: { host: string, port: number, detail: string }[]): Promise { + async $onFoundNewCandidates(candidates: CandidatePort[]): Promise { this.remoteExplorerService.onFoundNewCandidates(candidates); } - async $tunnelServiceReady(): Promise { - return this.remoteExplorerService.restore(); - } - - async $setTunnelProvider(): Promise { + async $setTunnelProvider(features: TunnelProviderFeatures): Promise { const tunnelProvider: ITunnelProvider = { forwardPort: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => { const forward = this._proxy.$forwardPort(tunnelOptions, tunnelCreationOptions); if (forward) { return forward.then(tunnel => { + this.logService.trace(`MainThreadTunnelService: New tunnel established by tunnel provider: ${tunnel?.remoteAddress.host}:${tunnel?.remoteAddress.port}`); + if (!tunnel) { + return undefined; + } return { tunnelRemotePort: tunnel.remoteAddress.port, tunnelRemoteHost: tunnel.remoteAddress.host, localAddress: typeof tunnel.localAddress === 'string' ? tunnel.localAddress : makeAddress(tunnel.localAddress.host, tunnel.localAddress.port), tunnelLocalPort: typeof tunnel.localAddress !== 'string' ? tunnel.localAddress.port : undefined, - dispose: (silent?: boolean) => { - this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port }, silent); + public: tunnel.public, + dispose: async (silent?: boolean) => { + this.logService.trace(`MainThreadTunnelService: Closing tunnel from tunnel provider: ${tunnel?.remoteAddress.host}:${tunnel?.remoteAddress.port}`); + return this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port }, silent); } }; }); @@ -75,9 +122,16 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun return undefined; } }; - this.tunnelService.setTunnelProvider(tunnelProvider); + this.tunnelService.setTunnelProvider(tunnelProvider, features); } + async $setCandidateFilter(): Promise { + this.remoteExplorerService.setCandidateFilter((candidates: CandidatePort[]): Promise => { + return this._proxy.$applyCandidateFilter(candidates); + }); + } + + dispose(): void { } diff --git a/src/vs/workbench/api/browser/mainThreadUriOpeners.ts b/src/vs/workbench/api/browser/mainThreadUriOpeners.ts new file mode 100644 index 000000000..9ac5d76c1 --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadUriOpeners.ts @@ -0,0 +1,132 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Action } from 'vs/base/common/actions'; +import { isPromiseCanceledError } from 'vs/base/common/errors'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { ExtHostContext, ExtHostUriOpenersShape, IExtHostContext, MainContext, MainThreadUriOpenersShape } from 'vs/workbench/api/common/extHost.protocol'; +import { defaultExternalUriOpenerId } from 'vs/workbench/contrib/externalUriOpener/common/configuration'; +import { ContributedExternalUriOpenersStore } from 'vs/workbench/contrib/externalUriOpener/common/contributedOpeners'; +import { IExternalOpenerProvider, IExternalUriOpener, IExternalUriOpenerService } from 'vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { extHostNamedCustomer } from '../common/extHostCustomers'; + +interface RegisteredOpenerMetadata { + readonly schemes: ReadonlySet; + readonly extensionId: ExtensionIdentifier; + readonly label: string; +} + +@extHostNamedCustomer(MainContext.MainThreadUriOpeners) +export class MainThreadUriOpeners extends Disposable implements MainThreadUriOpenersShape, IExternalOpenerProvider { + + private readonly proxy: ExtHostUriOpenersShape; + private readonly _registeredOpeners = new Map(); + private readonly _contributedExternalUriOpenersStore: ContributedExternalUriOpenersStore; + + constructor( + context: IExtHostContext, + @IStorageService storageService: IStorageService, + @IExternalUriOpenerService externalUriOpenerService: IExternalUriOpenerService, + @IExtensionService private readonly extensionService: IExtensionService, + @IOpenerService private readonly openerService: IOpenerService, + @INotificationService private readonly notificationService: INotificationService, + ) { + super(); + this.proxy = context.getProxy(ExtHostContext.ExtHostUriOpeners); + + this._register(externalUriOpenerService.registerExternalOpenerProvider(this)); + + this._contributedExternalUriOpenersStore = this._register(new ContributedExternalUriOpenersStore(storageService, extensionService)); + } + + public async *getOpeners(targetUri: URI): AsyncIterable { + + // Currently we only allow openers for http and https urls + if (targetUri.scheme !== Schemas.http && targetUri.scheme !== Schemas.https) { + return; + } + + await this.extensionService.activateByEvent(`onUriOpen:${targetUri.scheme}`); + + for (const [id, openerMetadata] of this._registeredOpeners) { + if (openerMetadata.schemes.has(targetUri.scheme)) { + yield this.createOpener(id, openerMetadata); + } + } + } + + private createOpener(id: string, metadata: RegisteredOpenerMetadata): IExternalUriOpener { + return { + id: id, + label: metadata.label, + canOpen: (uri, token) => { + return this.proxy.$canOpenUri(id, uri, token); + }, + openExternalUri: async (uri, ctx, token) => { + try { + await this.proxy.$openUri(id, { resolvedUri: uri, sourceUri: ctx.sourceUri }, token); + } catch (e) { + if (!isPromiseCanceledError(e)) { + const openDefaultAction = new Action('default', localize('openerFailedUseDefault', "Open using default opener"), undefined, undefined, () => { + return this.openerService.open(uri, { + allowTunneling: false, + allowContributedOpeners: defaultExternalUriOpenerId, + }); + }); + openDefaultAction.tooltip = uri.toString(); + + this.notificationService.notify({ + severity: Severity.Error, + message: localize('openerFailedMessage', 'Could not open uri with \'{0}\': {1}', id, e.toString()), + actions: { + primary: [ + openDefaultAction + ] + } + }); + } + } + return true; + }, + }; + } + + async $registerUriOpener( + id: string, + schemes: readonly string[], + extensionId: ExtensionIdentifier, + label: string, + ): Promise { + if (this._registeredOpeners.has(id)) { + throw new Error(`Opener with id '${id}' already registered`); + } + + this._registeredOpeners.set(id, { + schemes: new Set(schemes), + label, + extensionId, + }); + + this._contributedExternalUriOpenersStore.add(id, extensionId.value); + } + + async $unregisterUriOpener(id: string): Promise { + this._registeredOpeners.delete(id); + this._contributedExternalUriOpenersStore.delete(id); + } + + dispose(): void { + super.dispose(); + this._registeredOpeners.clear(); + } +} diff --git a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts index 6428e6f48..7aa690772 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts @@ -129,6 +129,9 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc dispose(this._editorProviders.values()); this._editorProviders.clear(); + + dispose(this._revivers.values()); + this._revivers.clear(); } public get webviewInputs(): Iterable { return this._webviewInputs; } @@ -181,7 +184,6 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc webview.setName(value); } - public $setIconPath(handle: extHostProtocol.WebviewHandle, value: { light: UriComponents, dark: UriComponents; } | undefined): void { const webview = this.getWebviewInput(handle); webview.iconPath = reviveWebviewIcon(value); @@ -199,8 +201,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc } } - public $registerSerializer(viewType: string) - : void { + public $registerSerializer(viewType: string): void { if (this._revivers.has(viewType)) { throw new Error(`Reviver for ${viewType} already registered`); } @@ -216,7 +217,6 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc return; } - const handle = webviewInput.id; this.addWebviewInput(handle, webviewInput); diff --git a/src/vs/workbench/api/browser/mainThreadWebviews.ts b/src/vs/workbench/api/browser/mainThreadWebviews.ts index cc5b05c29..684096227 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviews.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviews.ts @@ -78,7 +78,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma private onDidClickLink(handle: extHostProtocol.WebviewHandle, link: string): void { const webview = this.getWebview(handle); if (this.isSupportedLink(webview, URI.parse(link))) { - this._openerService.open(link, { fromUserGesture: true }); + this._openerService.open(link, { fromUserGesture: true, allowContributedOpeners: true }); } } diff --git a/src/vs/workbench/api/browser/mainThreadWindow.ts b/src/vs/workbench/api/browser/mainThreadWindow.ts index 2a431111e..7f10aa334 100644 --- a/src/vs/workbench/api/browser/mainThreadWindow.ts +++ b/src/vs/workbench/api/browser/mainThreadWindow.ts @@ -52,7 +52,11 @@ export class MainThreadWindow implements MainThreadWindowShape { // called with URI or transformed -> use uri target = uri; } - return this.openerService.open(target, { openExternal: true, allowTunneling: options.allowTunneling }); + return this.openerService.open(target, { + openExternal: true, + allowTunneling: options.allowTunneling, + allowContributedOpeners: options.allowContributedOpeners, + }); } async $asExternalUri(uriComponents: UriComponents, options: IOpenUriOptions): Promise { diff --git a/src/vs/workbench/api/browser/mainThreadWorkspace.ts b/src/vs/workbench/api/browser/mainThreadWorkspace.ts index 252fa8e42..946c7bd3d 100644 --- a/src/vs/workbench/api/browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/browser/mainThreadWorkspace.ts @@ -6,25 +6,25 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { isNative } from 'vs/base/common/platform'; +import { withNullAsUndefined } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; -import { isNative } from 'vs/base/common/platform'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +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 { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'vs/workbench/services/search/common/search'; -import { IWorkspaceContextService, WorkbenchState, IWorkspace, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IRequestService } from 'vs/platform/request/common/request'; +import { IWorkspace, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { checkGlobFileExists } from 'vs/workbench/api/common/shared/workspaceContains'; import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'vs/workbench/services/search/common/search'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; -import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, MainContext, MainThreadWorkspaceShape, IWorkspaceData, ITextSearchComplete } from '../common/extHost.protocol'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { withNullAsUndefined } from 'vs/base/common/types'; -import { IFileService } from 'vs/platform/files/common/files'; -import { IRequestService } from 'vs/platform/request/common/request'; -import { checkGlobFileExists } from 'vs/workbench/api/common/shared/workspaceContains'; +import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, ITextSearchComplete, IWorkspaceData, MainContext, MainThreadWorkspaceShape } from '../common/extHost.protocol'; @extHostNamedCustomer(MainContext.MainThreadWorkspace) export class MainThreadWorkspace implements MainThreadWorkspaceShape { @@ -139,7 +139,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { } const query = this._queryBuilder.file( - includeFolder ? [toWorkspaceFolder(includeFolder)] : workspace.folders, + includeFolder ? [includeFolder] : workspace.folders, { maxResults: withNullAsUndefined(maxResults), disregardExcludeSettings: (excludePatternOrDisregardExcludes === false) || undefined, diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 6be78f4ec..84b95cfc7 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -3,35 +3,32 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { coalesce } from 'vs/base/common/arrays'; import { forEach } from 'vs/base/common/collections'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as resources from 'vs/base/common/resources'; -import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation, testViewIcon } from 'vs/workbench/common/views'; -import { CustomTreeView, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { coalesce, } from 'vs/base/common/arrays'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { VIEWLET_ID as EXPLORER } from 'vs/workbench/contrib/files/common/files'; -import { VIEWLET_ID as SCM } from 'vs/workbench/contrib/scm/common/scm'; -import { VIEWLET_ID as DEBUG } from 'vs/workbench/contrib/debug/common/debug'; -import { VIEWLET_ID as REMOTE } from 'vs/workbench/contrib/remote/browser/remoteExplorer'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; -import { ViewletRegistry, Extensions as ViewletExtensions, ShowViewletAction } from 'vs/workbench/browser/viewlet'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions, CATEGORIES } from 'vs/workbench/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { localize } from 'vs/nls'; +import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { WebviewViewPane } from 'vs/workbench/contrib/webviewView/browser/webviewViewPane'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { Registry } from 'vs/platform/registry/common/platform'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { CustomTreeView, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { Extensions as ViewletExtensions, ShowViewletAction2, ViewletRegistry } from 'vs/workbench/browser/viewlet'; +import { CATEGORIES } from 'vs/workbench/common/actions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions as ViewContainerExtensions, ITreeViewDescriptor, IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; +import { VIEWLET_ID as DEBUG } from 'vs/workbench/contrib/debug/common/debug'; +import { VIEWLET_ID as EXPLORER } from 'vs/workbench/contrib/files/common/files'; +import { VIEWLET_ID as REMOTE } from 'vs/workbench/contrib/remote/browser/remoteExplorer'; +import { VIEWLET_ID as SCM } from 'vs/workbench/contrib/scm/common/scm'; +import { WebviewViewPane } from 'vs/workbench/contrib/webviewView/browser/webviewViewPane'; +import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; export interface IUserFriendlyViewsContainerDescriptor { id: string; @@ -110,7 +107,7 @@ const viewDescriptor: IJSONSchema = { defaultSnippets: [{ body: { id: '${1:id}', name: '${2:name}' } }], properties: { type: { - markdownDescription: localize('vscode.extension.contributes.view.type', "Type of the the view. This can either be `tree` for a tree view based view or `webview` for a webview based view. The default is `tree`."), + markdownDescription: localize('vscode.extension.contributes.view.type', "Type of the view. This can either be `tree` for a tree view based view or `webview` for a webview based view. The default is `tree`."), type: 'string', enum: [ 'tree', @@ -255,7 +252,8 @@ const viewsExtensionPoint: IExtensionPoint = ExtensionsR jsonSchema: viewsContribution }); -const TEST_VIEW_CONTAINER_ORDER = 6; +const CUSTOM_VIEWS_START_ORDER = 7; + class ViewsExtensionHandler implements IWorkbenchContribution { private viewContainersRegistry: IViewContainersRegistry; @@ -271,7 +269,6 @@ class ViewsExtensionHandler implements IWorkbenchContribution { } private handleAndRegisterCustomViewContainers() { - this.registerTestViewContainer(); viewsContainersExtensionPoint.setHandler((extensions, { added, removed }) => { if (removed.length) { this.removeCustomViewContainers(removed); @@ -284,7 +281,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { private addCustomViewContainers(extensionPoints: readonly IExtensionPointUser[], existingViewContainers: ViewContainer[]): void { const viewContainersRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); - let activityBarOrder = TEST_VIEW_CONTAINER_ORDER + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Sidebar).length + 1; + let activityBarOrder = CUSTOM_VIEWS_START_ORDER + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Sidebar).length; let panelOrder = 5 + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Panel).length + 1; for (let { value, collector, description } of extensionPoints) { forEach(value, entry => { @@ -318,13 +315,6 @@ class ViewsExtensionHandler implements IWorkbenchContribution { } } - private registerTestViewContainer(): void { - const title = localize('test', "Test"); - const icon = testViewIcon; - - this.registerCustomViewContainer(TEST_VIEW_CONTAINER_ID, title, icon, TEST_VIEW_CONTAINER_ORDER, undefined, ViewContainerLocation.Sidebar); - } - private isValidViewsContainer(viewsContainersDescriptors: IUserFriendlyViewsContainerDescriptor[], collector: ExtensionMessageCollector): boolean { if (!Array.isArray(viewsContainersDescriptors)) { collector.error(localize('viewcontainer requirearray', "views containers must be an array")); @@ -395,22 +385,15 @@ class ViewsExtensionHandler implements IWorkbenchContribution { }, location); // Register Action to Open Viewlet - class OpenCustomViewletAction extends ShowViewletAction { - constructor( - id: string, label: string, - @IViewletService viewletService: IViewletService, - @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService - ) { - super(id, label, id, viewletService, editorGroupService, layoutService); + registerAction2(class OpenCustomViewletAction extends ShowViewletAction2 { + constructor() { + super({ id, f1: true, title: localize('showViewlet', "Show {0}", title), category: CATEGORIES.View.value }); } - } - const registry = Registry.as(ActionExtensions.WorkbenchActions); - registry.registerWorkbenchAction( - SyncActionDescriptor.create(OpenCustomViewletAction, id, localize('showViewlet', "Show {0}", title)), - `View: Show ${title}`, - CATEGORIES.View.value - ); + + protected viewletId() { + return id; + } + }); } return viewContainer; @@ -501,7 +484,8 @@ class ViewsExtensionHandler implements IWorkbenchContribution { originalContainerId: entry.key, group: item.group, remoteAuthority: item.remoteName || (item).remoteAuthority, // TODO@roblou - delete after remote extensions are updated - hideByDefault: initialVisibility === InitialVisibility.Hidden + hideByDefault: initialVisibility === InitialVisibility.Hidden, + workspace: viewContainer?.id === REMOTE ? true : undefined }; diff --git a/src/vs/workbench/api/common/apiCommands.ts b/src/vs/workbench/api/common/apiCommands.ts index 7670549c6..d4a7ea407 100644 --- a/src/vs/workbench/api/common/apiCommands.ts +++ b/src/vs/workbench/api/common/apiCommands.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI, UriComponents } from 'vs/base/common/uri'; +import { URI } from 'vs/base/common/uri'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { CommandsRegistry, ICommandService, ICommandHandler } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -12,7 +12,6 @@ import { IWorkspacesService, IRecent } from 'vs/platform/workspaces/common/works import { ILogService } from 'vs/platform/log/common/log'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IViewDescriptorService, IViewsService, ViewVisibilityState } from 'vs/workbench/common/views'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; // ----------------------------------------------------------------- // The following commands are registered on both sides separately. @@ -128,12 +127,6 @@ CommandsRegistry.registerCommand('_extensionTests.setLogLevel', function (access } }); -CommandsRegistry.registerCommand('_workbench.openExternal', function (accessor: ServicesAccessor, uri: UriComponents, options: { allowTunneling?: boolean }) { - // TODO: discuss martin, ben where to put this - const openerService = accessor.get(IOpenerService); - openerService.open(URI.revive(uri), options); -}); - CommandsRegistry.registerCommand('_extensionTests.getLogLevel', function (accessor: ServicesAccessor) { const logService = accessor.get(ILogService); diff --git a/src/vs/workbench/api/common/exHostSecretState.ts b/src/vs/workbench/api/common/exHostSecretState.ts new file mode 100644 index 000000000..b06f12450 --- /dev/null +++ b/src/vs/workbench/api/common/exHostSecretState.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 { ExtHostSecretStateShape, MainContext, MainThreadSecretStateShape } from 'vs/workbench/api/common/extHost.protocol'; +import { Emitter } from 'vs/base/common/event'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export class ExtHostSecretState implements ExtHostSecretStateShape { + private _proxy: MainThreadSecretStateShape; + private _onDidChangePassword = new Emitter<{ extensionId: string, key: string }>(); + readonly onDidChangePassword = this._onDidChangePassword.event; + + constructor(mainContext: IExtHostRpcService) { + this._proxy = mainContext.getProxy(MainContext.MainThreadSecretState); + } + + async $onDidChangePassword(e: { extensionId: string, key: string }): Promise { + this._onDidChangePassword.fire(e); + } + + get(extensionId: string, key: string): Promise { + return this._proxy.$getPassword(extensionId, key); + } + + store(extensionId: string, key: string, value: string): Promise { + return this._proxy.$setPassword(extensionId, key, value); + } + + delete(extensionId: string, key: string): Promise { + return this._proxy.$deletePassword(extensionId, key); + } +} + +export interface IExtHostSecretState extends ExtHostSecretState { } +export const IExtHostSecretState = createDecorator('IExtHostSecretState'); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index fa880fcfc..2da02381a 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -82,6 +82,9 @@ import { ExtHostWebviewPanels } from 'vs/workbench/api/common/extHostWebviewPane import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits'; import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; import { ExtHostTesting } from 'vs/workbench/api/common/extHostTesting'; +import { ExtHostUriOpeners } from 'vs/workbench/api/common/extHostUriOpener'; +import { IExtHostSecretState } from 'vs/workbench/api/common/exHostSecretState'; +import { ExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs'; export interface IExtensionApiFactory { (extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode; @@ -107,6 +110,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostTunnelService = accessor.get(IExtHostTunnelService); const extHostApiDeprecation = accessor.get(IExtHostApiDeprecationService); const extHostWindow = accessor.get(IExtHostWindow); + const extHostSecretState = accessor.get(IExtHostSecretState); // register addressable instances rpcProtocol.set(ExtHostContext.ExtHostFileSystemInfo, extHostFileSystemInfo); @@ -117,6 +121,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I rpcProtocol.set(ExtHostContext.ExtHostStorage, extHostStorage); rpcProtocol.set(ExtHostContext.ExtHostTunnelService, extHostTunnelService); rpcProtocol.set(ExtHostContext.ExtHostWindow, extHostWindow); + rpcProtocol.set(ExtHostContext.ExtHostSecretState, extHostSecretState); // automatically create and register addressable instances const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, accessor.get(IExtHostDecorations)); @@ -129,6 +134,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostOutputService = rpcProtocol.set(ExtHostContext.ExtHostOutputService, accessor.get(IExtHostOutputService)); // manually create and register addressable instances + const extHostEditorTabs = rpcProtocol.set(ExtHostContext.ExtHostEditorTabs, new ExtHostEditorTabs()); const extHostUrls = rpcProtocol.set(ExtHostContext.ExtHostUrls, new ExtHostUrls(rpcProtocol)); const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors)); const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService)); @@ -154,6 +160,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I 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, extHostDocumentsAndEditors, extHostWorkspace)); + const extHostUriOpeners = rpcProtocol.set(ExtHostContext.ExtHostUriOpeners, new ExtHostUriOpeners(rpcProtocol)); // Check that no named customers are missing const expected: ProxyIdentifier[] = values(ExtHostContext); @@ -209,22 +216,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I get onDidChangeSessions(): Event { return extHostAuthentication.onDidChangeSessions; }, - registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable { + registerAuthenticationProvider(id: string, label: string, provider: vscode.AuthenticationProvider, options?: vscode.AuthenticationProviderOptions): vscode.Disposable { checkProposedApiEnabled(extension); - return extHostAuthentication.registerAuthenticationProvider(provider); + return extHostAuthentication.registerAuthenticationProvider(id, label, provider, options); }, get onDidChangeAuthenticationProviders(): Event { checkProposedApiEnabled(extension); return extHostAuthentication.onDidChangeAuthenticationProviders; }, - getProviderIds(): Thenable> { - checkProposedApiEnabled(extension); - return extHostAuthentication.getProviderIds(); - }, - get providerIds(): string[] { - checkProposedApiEnabled(extension); - return extHostAuthentication.providerIds; - }, get providers(): ReadonlyArray { checkProposedApiEnabled(extension); return extHostAuthentication.providers; @@ -232,22 +231,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I logout(providerId: string, sessionId: string): Thenable { checkProposedApiEnabled(extension); return extHostAuthentication.logout(providerId, sessionId); - }, - getPassword(key: string): Thenable { - checkProposedApiEnabled(extension); - return extHostAuthentication.getPassword(extension, key); - }, - setPassword(key: string, value: string): Thenable { - checkProposedApiEnabled(extension); - return extHostAuthentication.setPassword(extension, key, value); - }, - deletePassword(key: string): Thenable { - checkProposedApiEnabled(extension); - return extHostAuthentication.deletePassword(extension, key); - }, - get onDidChangePassword(): Event { - checkProposedApiEnabled(extension); - return extHostAuthentication.onDidChangePassword; } }; @@ -311,8 +294,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I get shell() { return extHostTerminalService.getDefaultShell(false, configProvider); }, - openExternal(uri: URI) { - return extHostWindow.openUri(uri, { allowTunneling: !!initData.remote.authority }); + openExternal(uri: URI, options?: { allowContributedOpeners?: boolean | string; }) { + return extHostWindow.openUri(uri, { + allowTunneling: !!initData.remote.authority, + allowContributedOpeners: options?.allowContributedOpeners, + }); }, asExternalUri(uri: URI) { if (uri.scheme === initData.environment.appUriScheme) { @@ -354,6 +340,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostTesting.runTests(provider); }, + get onDidChangeTestResults() { + checkProposedApiEnabled(extension); + return extHostTesting.onLastResultsChanged; + }, + get testResults() { + checkProposedApiEnabled(extension); + return extHostTesting.lastResults; + }, }; // namespace: extensions @@ -480,6 +474,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I getTokenInformationAtPosition(doc: vscode.TextDocument, pos: vscode.Position) { checkProposedApiEnabled(extension); return extHostLanguages.tokenAtPosition(doc, pos); + }, + registerInlineHintsProvider(selector: vscode.DocumentSelector, provider: vscode.InlineHintsProvider): vscode.Disposable { + checkProposedApiEnabled(extension); + return extHostLanguageFeatures.registerInlineHintsProvider(extension, selector, provider); } }; @@ -588,10 +586,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I id = extension.identifier.value; name = nls.localize('extensionLabel', "{0} (Extension)", extension.displayName || extension.name); alignment = alignmentOrOptions; - priority = priority; } - return extHostStatusBar.createStatusBarEntry(id, name, alignment, priority, accessibilityInformation, extension); + return extHostStatusBar.createStatusBarEntry(id, name, alignment, priority, accessibilityInformation); }, setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable): vscode.Disposable { return extHostStatusBar.setStatusBarMessage(text, timeoutOrThenable); @@ -691,6 +688,18 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I showNotebookDocument(document, options?) { checkProposedApiEnabled(extension); return extHostNotebook.showNotebookDocument(document, options); + }, + registerExternalUriOpener(id: string, opener: vscode.ExternalUriOpener, metadata: vscode.ExternalUriOpenerMetadata) { + checkProposedApiEnabled(extension); + return extHostUriOpeners.registerExternalUriOpener(extension.identifier, id, opener, metadata); + }, + get openEditors() { + checkProposedApiEnabled(extension); + return extHostEditorTabs.tabs; + }, + get onDidChangeOpenEditors() { + checkProposedApiEnabled(extension); + return extHostEditorTabs.onDidChangeTabs; } }; @@ -1108,6 +1117,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I CallHierarchyIncomingCall: extHostTypes.CallHierarchyIncomingCall, CallHierarchyItem: extHostTypes.CallHierarchyItem, CallHierarchyOutgoingCall: extHostTypes.CallHierarchyOutgoingCall, + CancellationError: errors.CancellationError, CancellationTokenSource: CancellationTokenSource, CodeAction: extHostTypes.CodeAction, CodeActionKind: extHostTypes.CodeActionKind, @@ -1148,6 +1158,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I EventEmitter: Emitter, ExtensionKind: extHostTypes.ExtensionKind, ExtensionMode: extHostTypes.ExtensionMode, + ExternalUriOpenerPriority: extHostTypes.ExternalUriOpenerPriority, FileChangeType: extHostTypes.FileChangeType, FileDecoration: extHostTypes.FileDecoration, FileSystemError: extHostTypes.FileSystemError, @@ -1157,6 +1168,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I FunctionBreakpoint: extHostTypes.FunctionBreakpoint, Hover: extHostTypes.Hover, IndentAction: languageConfiguration.IndentAction, + InlineHint: extHostTypes.InlineHint, Location: extHostTypes.Location, MarkdownString: extHostTypes.MarkdownString, OverviewRulerLane: OverviewRulerLane, diff --git a/src/vs/workbench/api/common/extHost.common.services.ts b/src/vs/workbench/api/common/extHost.common.services.ts index 6c1a02f44..b1fe830b2 100644 --- a/src/vs/workbench/api/common/extHost.common.services.ts +++ b/src/vs/workbench/api/common/extHost.common.services.ts @@ -21,6 +21,7 @@ import { IExtHostApiDeprecationService, ExtHostApiDeprecationService, } from 'vs import { IExtHostWindow, ExtHostWindow } from 'vs/workbench/api/common/extHostWindow'; import { IExtHostConsumerFileSystem, ExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer'; import { IExtHostFileSystemInfo, ExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; +import { IExtHostSecretState, ExtHostSecretState } from 'vs/workbench/api/common/exHostSecretState'; registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths); registerSingleton(IExtHostApiDeprecationService, ExtHostApiDeprecationService); @@ -39,3 +40,4 @@ registerSingleton(IExtHostTerminalService, WorkerExtHostTerminalService); registerSingleton(IExtHostTunnelService, ExtHostTunnelService); registerSingleton(IExtHostWindow, ExtHostWindow); registerSingleton(IExtHostWorkspace, ExtHostWorkspace); +registerSingleton(IExtHostSecretState, ExtHostSecretState); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b97a962f6..be7a14f88 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as performance from 'vs/base/common/performance'; import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IRemoteConsoleLog } from 'vs/base/common/console'; @@ -41,13 +42,13 @@ import { IRevealOptions, ITreeItem } from 'vs/workbench/common/views'; import { IAdapterDescriptor, IConfig, IDebugSessionReplMode } from 'vs/workbench/contrib/debug/common/debug'; import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder'; import { ITerminalDimensions, IShellLaunchConfig, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal'; -import { ActivationKind, ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions'; +import { ActivationKind, ExtensionActivationError, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions'; import { createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId, IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import * as search from 'vs/workbench/services/search/common/search'; import { EditorGroupColumn, SaveReason } from 'vs/workbench/common/editor'; import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; -import { TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel'; +import { TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline'; import { revive } from 'vs/base/common/marshalling'; import { IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, INotebookKernelInfoDto2, TransientMetadata, INotebookCellStatusBarEntry, ICellRange, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -57,7 +58,8 @@ import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/api/common/extHostTypes'; import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility'; import { IExtensionIdWithVersion } from 'vs/platform/userDataSync/common/extensionsStorageSync'; -import { RunTestForProviderRequest, RunTestsRequest, RunTestsResult, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; +import { InternalTestItem, InternalTestResults, RunTestForProviderRequest, RunTestsRequest, RunTestsResult, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; +import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; @@ -108,7 +110,8 @@ export interface IConfigurationInitData extends IConfigurationData { } export interface IExtHostContext extends IRPCProtocol { - remoteAuthority: string | null; + readonly remoteAuthority: string | null; + readonly extensionHostKind: ExtensionHostKind; } export interface IMainContext extends IRPCProtocol { @@ -174,7 +177,9 @@ export interface MainThreadAuthenticationShape extends IDisposable { $getSessions(providerId: string): Promise>; $login(providerId: string, scopes: string[]): Promise; $logout(providerId: string, sessionId: string): Promise; +} +export interface MainThreadSecretStateShape extends IDisposable { $getPassword(extensionId: string, key: string): Promise; $setPassword(extensionId: string, key: string, value: string): Promise; $deletePassword(extensionId: string, key: string): Promise; @@ -330,7 +335,7 @@ export interface IIndentationRuleDto { export interface IOnEnterRuleDto { beforeText: IRegExpDto; afterText?: IRegExpDto; - oneLineAboveText?: IRegExpDto; + previousLineText?: IRegExpDto; action: EnterAction; } export interface ILanguageConfigurationDto { @@ -397,6 +402,8 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerDocumentRangeSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend): void; $registerSuggestSupport(handle: number, selector: IDocumentFilterDto[], triggerCharacters: string[], supportsResolveDetails: boolean, displayName: string): void; $registerSignatureHelpProvider(handle: number, selector: IDocumentFilterDto[], metadata: ISignatureHelpProviderMetadataDto): void; + $registerInlineHintsProvider(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void; + $emitInlineHintsEvent(eventHandle: number, event?: any): void; $registerDocumentLinkProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean): void; $registerDocumentColorProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerFoldingRangeProvider(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void; @@ -415,6 +422,7 @@ export interface MainThreadLanguagesShape extends IDisposable { export interface MainThreadMessageOptions { extension?: IExtensionDescription; modal?: boolean; + useCustom?: boolean; } export interface MainThreadMessageServiceShape extends IDisposable { @@ -438,6 +446,16 @@ export interface MainThreadProgressShape extends IDisposable { $progressEnd(handle: number): void; } +/** + * A terminal that is created on the extension host side is temporarily assigned + * a UUID by the extension host that created it. Once the renderer side has assigned + * a real numeric id, the numeric id will be used. + * + * All other terminals (that are not created on the extension host side) always + * use the numeric id. + */ +export type TerminalIdentifier = number | string; + export interface TerminalLaunchConfig { name?: string; shellPath?: string; @@ -452,11 +470,11 @@ export interface TerminalLaunchConfig { } export interface MainThreadTerminalServiceShape extends IDisposable { - $createTerminal(config: TerminalLaunchConfig): Promise<{ id: number, name: string; }>; - $dispose(terminalId: number): void; - $hide(terminalId: number): void; - $sendText(terminalId: number, text: string, addNewLine: boolean): void; - $show(terminalId: number, preserveFocus: boolean): void; + $createTerminal(extHostTerminalId: string, config: TerminalLaunchConfig): Promise; + $dispose(id: TerminalIdentifier): void; + $hide(id: TerminalIdentifier): void; + $sendText(id: TerminalIdentifier, text: string, addNewLine: boolean): void; + $show(id: TerminalIdentifier, preserveFocus: boolean): void; $startSendingDataEvents(): void; $stopSendingDataEvents(): void; $startLinkProvider(): void; @@ -594,6 +612,24 @@ export interface ExtHostEditorInsetsShape { $onDidReceiveMessage(handle: number, message: any): void; } +//#region --- open editors model + +export interface MainThreadEditorTabsShape extends IDisposable { + // manage tabs: move, close, rearrange etc +} + +export interface IEditorTabDto { + group: number; + name: string; + resource: UriComponents +} + +export interface IExtHostEditorTabsShape { + $acceptEditorTabs(tabs: IEditorTabDto[]): void; +} + +//#endregion + export type WebviewHandle = string; export interface WebviewPanelShowOptions { @@ -739,6 +775,7 @@ export enum NotebookEditorRevealType { Default = 0, InCenter = 1, InCenterIfOutsideViewport = 2, + AtTop = 3 } export interface INotebookDocumentShowOptions { @@ -786,6 +823,16 @@ export interface ExtHostUrlsShape { $handleExternalUri(handle: number, uri: UriComponents): Promise; } +export interface MainThreadUriOpenersShape extends IDisposable { + $registerUriOpener(id: string, schemes: readonly string[], extensionId: ExtensionIdentifier, label: string): Promise; + $unregisterUriOpener(id: string): Promise; +} + +export interface ExtHostUriOpenersShape { + $canOpenUri(id: string, uri: UriComponents, token: CancellationToken): Promise; + $openUri(id: string, context: { resolvedUri: UriComponents, sourceUri: UriComponents }, token: CancellationToken): Promise; +} + export interface ITextSearchComplete { limitHit?: boolean; } @@ -853,6 +900,7 @@ export interface MainThreadExtensionServiceShape extends IDisposable { $onExtensionActivationError(extensionId: ExtensionIdentifier, error: ExtensionActivationError): Promise; $onExtensionRuntimeError(extensionId: ExtensionIdentifier, error: SerializedError): void; $onExtensionHostExit(code: number): Promise; + $setPerformanceMarks(marks: performance.PerformanceMark[]): Promise; } export interface SCMProviderFeatures { @@ -946,6 +994,7 @@ export interface MainThreadDebugServiceShape extends IDisposable { export interface IOpenUriOptions { readonly allowTunneling?: boolean; + readonly allowContributedOpeners?: boolean | string; } export interface MainThreadWindowShape extends IDisposable { @@ -958,8 +1007,9 @@ export interface MainThreadTunnelServiceShape extends IDisposable { $openTunnel(tunnelOptions: TunnelOptions, source: string | undefined): Promise; $closeTunnel(remote: { host: string, port: number }): Promise; $getTunnels(): Promise; - $setTunnelProvider(): Promise; - $tunnelServiceReady(): Promise; + $setTunnelProvider(features: TunnelProviderFeatures): Promise; + $setCandidateFinder(): Promise; + $setCandidateFilter(): Promise; $onFoundNewCandidates(candidates: { host: string, port: number, detail: string }[]): Promise; } @@ -1052,7 +1102,7 @@ export interface ExtHostTreeViewsShape { $setSelection(treeViewId: string, treeItemHandles: string[]): void; $setVisible(treeViewId: string, visible: boolean): void; $hasResolve(treeViewId: string): Promise; - $resolve(treeViewId: string, treeItemHandle: string): Promise; + $resolve(treeViewId: string, treeItemHandle: string, token: CancellationToken): Promise; } export interface ExtHostWorkspaceShape { @@ -1094,7 +1144,10 @@ export interface ExtHostAuthenticationShape { $onDidChangeAuthenticationSessions(id: string, label: string, event: modes.AuthenticationSessionsChangeEvent): Promise; $onDidChangeAuthenticationProviders(added: modes.AuthenticationProviderInformation[], removed: modes.AuthenticationProviderInformation[]): Promise; $setProviders(providers: modes.AuthenticationProviderInformation[]): Promise; - $onDidChangePassword(): Promise; +} + +export interface ExtHostSecretStateShape { + $onDidChangePassword(e: { extensionId: string, key: string }): Promise; } export interface ExtHostSearchShape { @@ -1145,9 +1198,14 @@ export interface SourceTargetPair { target: UriComponents; } +export interface IWillRunFileOperationParticipation { + edit: IWorkspaceEditDto; + extensionNames: string[] +} + export interface ExtHostFileSystemEventServiceShape { $onFileEvent(events: FileSystemEvents): void; - $onWillRunFileOperation(operation: files.FileOperation, files: SourceTargetPair[], undoRedoGroupId: number | undefined, timeout: number, token: CancellationToken): Promise; + $onWillRunFileOperation(operation: files.FileOperation, files: SourceTargetPair[], timeout: number, token: CancellationToken): Promise; $onDidRunFileOperation(operation: files.FileOperation, files: SourceTargetPair[]): void; } @@ -1252,6 +1310,18 @@ export interface ISignatureHelpContextDto { readonly activeSignatureHelp?: ISignatureHelpDto; } +export interface IInlineHintDto { + text: string; + range: IRange; + hoverMessage?: string; + whitespaceBefore?: boolean; + whitespaceAfter?: boolean; +} + +export interface IInlineHintsDto { + hints: IInlineHintDto[] +} + export interface ILocationDto { uri: UriComponents; range: IRange; @@ -1441,6 +1511,7 @@ export interface ExtHostLanguageFeaturesShape { $releaseCompletionItems(handle: number, id: number): void; $provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: modes.SignatureHelpContext, token: CancellationToken): Promise; $releaseSignatureHelp(handle: number, id: number): void; + $provideInlineHints(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise $provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise; $resolveDocumentLink(handle: number, id: ChainedCacheId, token: CancellationToken): Promise; $releaseDocumentLinks(handle: number, id: number): void; @@ -1473,6 +1544,7 @@ export interface IShellLaunchConfigDto { cwd?: string | UriComponents; env?: { [key: string]: string | null; }; hideFromUser?: boolean; + flowControl?: boolean; } export interface IShellDefinitionDto { @@ -1503,7 +1575,7 @@ export interface ITerminalDimensionsDto { export interface ExtHostTerminalServiceShape { $acceptTerminalClosed(id: number, exitCode: number | undefined): void; - $acceptTerminalOpened(id: number, name: string, shellLaunchConfig: IShellLaunchConfigDto): void; + $acceptTerminalOpened(id: number, extHostTerminalId: string | undefined, name: string, shellLaunchConfig: IShellLaunchConfigDto): void; $acceptActiveTerminalChanged(id: number | null): void; $acceptTerminalProcessId(id: number, processId: number): void; $acceptTerminalProcessData(id: number, data: string): void; @@ -1512,6 +1584,7 @@ export interface ExtHostTerminalServiceShape { $acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): void; $spawnExtHostProcess(id: number, shellLaunchConfig: IShellLaunchConfigDto, activeWorkspaceRootUri: UriComponents | undefined, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise; $startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise; + $acceptProcessAckDataEvent(id: number, charCount: number): void; $acceptProcessInput(id: number, data: string): void; $acceptProcessResize(id: number, cols: number, rows: number): void; $acceptProcessShutdown(id: number, immediate: boolean): void; @@ -1725,7 +1798,7 @@ export interface ExtHostNotebookShape { $saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise; $backup(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise; $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void; - $acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelId: string | undefined }): void; + $acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelFriendlyId: string | undefined }): void; $onDidReceiveMessage(editorId: string, rendererId: string | undefined, message: unknown): void; $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEventDto, isDirty: boolean): void; $acceptModelSaved(uriComponents: UriComponents): void; @@ -1749,9 +1822,11 @@ export interface MainThreadThemingShape extends IDisposable { } export interface ExtHostTunnelServiceShape { - $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise | undefined; + $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise; $closeTunnel(remote: { host: string, port: number }, silent?: boolean): Promise; $onDidTunnelsChange(): Promise; + $registerCandidateFinder(enable: boolean): Promise; + $applyCandidateFilter(candidates: CandidatePort[]): Promise; } export interface ExtHostTimelineShape { @@ -1764,11 +1839,12 @@ export const enum ExtHostTestingResource { } export interface ExtHostTestingShape { - $runTestsForProvider(req: RunTestForProviderRequest): Promise; + $runTestsForProvider(req: RunTestForProviderRequest, token: CancellationToken): Promise; $subscribeToTests(resource: ExtHostTestingResource, uri: UriComponents): void; $unsubscribeFromTests(resource: ExtHostTestingResource, uri: UriComponents): void; - + $lookupTest(test: TestIdWithProvider): Promise; $acceptDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void; + $publishTestResults(results: InternalTestResults): void; } export interface MainThreadTestingShape { @@ -1777,7 +1853,7 @@ export interface MainThreadTestingShape { $subscribeToDiffs(resource: ExtHostTestingResource, uri: UriComponents): void; $unsubscribeFromDiffs(resource: ExtHostTestingResource, uri: UriComponents): void; $publishDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void; - $runTests(req: RunTestsRequest): Promise; + $runTests(req: RunTestsRequest, token: CancellationToken): Promise; } // --- proxy identifiers @@ -1798,6 +1874,7 @@ export const MainContext = { MainThreadDocumentContentProviders: createMainId('MainThreadDocumentContentProviders'), MainThreadTextEditors: createMainId('MainThreadTextEditors'), MainThreadEditorInsets: createMainId('MainThreadEditorInsets'), + MainThreadEditorTabs: createMainId('MainThreadEditorTabs'), MainThreadErrors: createMainId('MainThreadErrors'), MainThreadTreeViews: createMainId('MainThreadTreeViews'), MainThreadDownloadService: createMainId('MainThreadDownloadService'), @@ -1810,6 +1887,7 @@ export const MainContext = { MainThreadProgress: createMainId('MainThreadProgress'), MainThreadQuickOpen: createMainId('MainThreadQuickOpen'), MainThreadStatusBar: createMainId('MainThreadStatusBar'), + MainThreadSecretState: createMainId('MainThreadSecretState'), MainThreadStorage: createMainId('MainThreadStorage'), MainThreadTelemetry: createMainId('MainThreadTelemetry'), MainThreadTerminalService: createMainId('MainThreadTerminalService'), @@ -1818,6 +1896,7 @@ export const MainContext = { MainThreadWebviewViews: createMainId('MainThreadWebviewViews'), MainThreadCustomEditors: createMainId('MainThreadCustomEditors'), MainThreadUrls: createMainId('MainThreadUrls'), + MainThreadUriOpeners: createMainId('MainThreadUriOpeners'), MainThreadWorkspace: createMainId('MainThreadWorkspace'), MainThreadFileSystem: createMainId('MainThreadFileSystem'), MainThreadExtensionService: createMainId('MainThreadExtensionService'), @@ -1863,10 +1942,13 @@ export const ExtHostContext = { ExtHostCustomEditors: createExtId('ExtHostCustomEditors'), ExtHostWebviewViews: createExtId('ExtHostWebviewViews'), ExtHostEditorInsets: createExtId('ExtHostEditorInsets'), + ExtHostEditorTabs: createExtId('ExtHostEditorTabs'), ExtHostProgress: createMainId('ExtHostProgress'), ExtHostComments: createMainId('ExtHostComments'), + ExtHostSecretState: createMainId('ExtHostSecretState'), ExtHostStorage: createMainId('ExtHostStorage'), ExtHostUrls: createExtId('ExtHostUrls'), + ExtHostUriOpeners: createExtId('ExtHostUriOpeners'), ExtHostOutputService: createMainId('ExtHostOutputService'), ExtHosLabelService: createMainId('ExtHostLabelService'), ExtHostNotebook: createMainId('ExtHostNotebook'), diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 08b2543a4..93f77a957 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -20,6 +20,8 @@ import { IRange } from 'vs/editor/common/core/range'; import { IPosition } from 'vs/editor/common/core/position'; import { TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto'; //#region --- NEW world @@ -176,6 +178,57 @@ const newCommands: ApiCommand[] = [ [ApiCommandArgument.Uri, ApiCommandArgument.Number.with('linkResolveCount', 'Number of links that should be resolved, only when links are unresolved.').optional()], new ApiCommandResult('A promise that resolves to an array of DocumentLink-instances.', value => value.map(typeConverters.DocumentLink.to)) ), + // --- semantic tokens + new ApiCommand( + 'vscode.provideDocumentSemanticTokensLegend', '_provideDocumentSemanticTokensLegend', 'Provide semantic tokens legend for a document', + [ApiCommandArgument.Uri], + new ApiCommandResult('A promise that resolves to SemanticTokensLegend.', value => { + if (!value) { + return undefined; + } + return new types.SemanticTokensLegend(value.tokenTypes, value.tokenModifiers); + }) + ), + new ApiCommand( + 'vscode.provideDocumentSemanticTokens', '_provideDocumentSemanticTokens', 'Provide semantic tokens for a document', + [ApiCommandArgument.Uri], + new ApiCommandResult('A promise that resolves to SemanticTokens.', value => { + if (!value) { + return undefined; + } + const semanticTokensDto = decodeSemanticTokensDto(value); + if (semanticTokensDto.type !== 'full') { + // only accepting full semantic tokens from provideDocumentSemanticTokens + return undefined; + } + return new types.SemanticTokens(semanticTokensDto.data, undefined); + }) + ), + new ApiCommand( + 'vscode.provideDocumentRangeSemanticTokensLegend', '_provideDocumentRangeSemanticTokensLegend', 'Provide semantic tokens legend for a document range', + [ApiCommandArgument.Uri], + new ApiCommandResult('A promise that resolves to SemanticTokensLegend.', value => { + if (!value) { + return undefined; + } + return new types.SemanticTokensLegend(value.tokenTypes, value.tokenModifiers); + }) + ), + new ApiCommand( + 'vscode.provideDocumentRangeSemanticTokens', '_provideDocumentRangeSemanticTokens', 'Provide semantic tokens for a document range', + [ApiCommandArgument.Uri, ApiCommandArgument.Range], + new ApiCommandResult('A promise that resolves to SemanticTokens.', value => { + if (!value) { + return undefined; + } + const semanticTokensDto = decodeSemanticTokensDto(value); + if (semanticTokensDto.type !== 'full') { + // only accepting full semantic tokens from provideDocumentRangeSemanticTokens + return undefined; + } + return new types.SemanticTokens(semanticTokensDto.data, undefined); + }) + ), // --- completions new ApiCommand( 'vscode.executeCompletionItemProvider', '_executeCompletionItemProvider', 'Execute completion item provider.', @@ -271,13 +324,21 @@ const newCommands: ApiCommand[] = [ return []; }) ), + // --- inline hints + new ApiCommand( + 'vscode.executeInlineHintProvider', '_executeInlineHintProvider', 'Execute inline hints provider', + [ApiCommandArgument.Uri, ApiCommandArgument.Range], + new ApiCommandResult('A promise that resolves to an array of InlineHint objects', result => { + return result.map(typeConverters.InlineHint.to); + }) + ), // --- notebooks new ApiCommand( 'vscode.resolveNotebookContentProviders', '_resolveNotebookContentProvider', 'Resolve Notebook Content Providers', [ - new ApiCommandArgument('viewType', '', v => typeof v === 'string', v => v), - new ApiCommandArgument('displayName', '', v => typeof v === 'string', v => v), - new ApiCommandArgument('options', '', v => typeof v === 'object', v => v), + // new ApiCommandArgument('viewType', '', v => typeof v === 'string', v => v), + // new ApiCommandArgument('displayName', '', v => typeof v === 'string', v => v), + // new ApiCommandArgument('options', '', v => typeof v === 'object', v => v), ], new ApiCommandResult<{ viewType: string; diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index 631999062..7376e9c66 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -15,11 +15,15 @@ interface GetSessionsRequest { result: Promise; } +interface ProviderWithMetadata { + label: string; + provider: vscode.AuthenticationProvider; + options: vscode.AuthenticationProviderOptions; +} + export class ExtHostAuthentication implements ExtHostAuthenticationShape { private _proxy: MainThreadAuthenticationShape; - private _authenticationProviders: Map = new Map(); - - private _providerIds: string[] = []; + private _authenticationProviders: Map = new Map(); private _providers: vscode.AuthenticationProviderInformation[] = []; @@ -29,9 +33,6 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { private _onDidChangeSessions = new Emitter(); readonly onDidChangeSessions: Event = this._onDidChangeSessions.event; - private _onDidChangePassword = new Emitter(); - readonly onDidChangePassword: Event = this._onDidChangePassword.event; - private _inFlightRequests = new Map(); constructor(mainContext: IMainContext) { @@ -43,14 +44,6 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { return Promise.resolve(); } - getProviderIds(): Promise> { - return this._proxy.$getProviderIds(); - } - - get providerIds(): string[] { - return this._providerIds; - } - get providers(): ReadonlyArray { return Object.freeze(this._providers.slice()); } @@ -90,128 +83,120 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { private async _getSession(requestingExtension: IExtensionDescription, extensionId: string, providerId: string, scopes: string[], options: vscode.AuthenticationGetSessionOptions = {}): Promise { await this._proxy.$ensureProvider(providerId); - const provider = this._authenticationProviders.get(providerId); + const providerData = this._authenticationProviders.get(providerId); const extensionName = requestingExtension.displayName || requestingExtension.name; - if (!provider) { + if (!providerData) { return this._proxy.$getSession(providerId, scopes, extensionId, extensionName, options); } const orderedScopes = scopes.sort().join(' '); - const sessions = (await provider.getSessions()).filter(session => session.scopes.slice().sort().join(' ') === orderedScopes); + const sessions = (await providerData.provider.getSessions()).filter(session => session.scopes.slice().sort().join(' ') === orderedScopes); let session: vscode.AuthenticationSession | undefined = undefined; if (sessions.length) { - if (!provider.supportsMultipleAccounts) { + if (!providerData.options.supportsMultipleAccounts) { session = sessions[0]; - const allowed = await this._proxy.$getSessionsPrompt(providerId, session.account.label, provider.label, extensionId, extensionName); + const allowed = await this._proxy.$getSessionsPrompt(providerId, session.account.label, providerData.label, extensionId, extensionName); if (!allowed) { throw new Error('User did not consent to login.'); } } else { // On renderer side, confirm consent, ask user to choose between accounts if multiple sessions are valid - const selected = await this._proxy.$selectSession(providerId, provider.label, extensionId, extensionName, sessions, scopes, !!options.clearSessionPreference); + const selected = await this._proxy.$selectSession(providerId, providerData.label, extensionId, extensionName, sessions, scopes, !!options.clearSessionPreference); session = sessions.find(session => session.id === selected.id); } } else { if (options.createIfNone) { - const isAllowed = await this._proxy.$loginPrompt(provider.label, extensionName); + const isAllowed = await this._proxy.$loginPrompt(providerData.label, extensionName); if (!isAllowed) { throw new Error('User did not consent to login.'); } - session = await provider.login(scopes); + session = await providerData.provider.login(scopes); await this._proxy.$setTrustedExtensionAndAccountPreference(providerId, session.account.label, extensionId, extensionName, session.id); } else { await this._proxy.$requestNewSession(providerId, scopes, extensionId, extensionName); } } - return session; } async logout(providerId: string, sessionId: string): Promise { - const provider = this._authenticationProviders.get(providerId); - if (!provider) { + const providerData = this._authenticationProviders.get(providerId); + if (!providerData) { return this._proxy.$logout(providerId, sessionId); } - return provider.logout(sessionId); + return providerData.provider.logout(sessionId); } - registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable { - if (this._authenticationProviders.get(provider.id)) { - throw new Error(`An authentication provider with id '${provider.id}' is already registered.`); + registerAuthenticationProvider(id: string, label: string, provider: vscode.AuthenticationProvider, options?: vscode.AuthenticationProviderOptions): vscode.Disposable { + if (this._authenticationProviders.get(id)) { + throw new Error(`An authentication provider with id '${id}' is already registered.`); } - this._authenticationProviders.set(provider.id, provider); - if (!this._providerIds.includes(provider.id)) { - this._providerIds.push(provider.id); - } + this._authenticationProviders.set(id, { label, provider, options: options ?? { supportsMultipleAccounts: false } }); - if (!this._providers.find(p => p.id === provider.id)) { + if (!this._providers.find(p => p.id === id)) { this._providers.push({ - id: provider.id, - label: provider.label + id: id, + label: label }); } const listener = provider.onDidChangeSessions(e => { - this._proxy.$sendDidChangeSessions(provider.id, e); + this._proxy.$sendDidChangeSessions(id, e); }); - this._proxy.$registerAuthenticationProvider(provider.id, provider.label, provider.supportsMultipleAccounts); + this._proxy.$registerAuthenticationProvider(id, label, options?.supportsMultipleAccounts ?? false); return new Disposable(() => { listener.dispose(); - this._authenticationProviders.delete(provider.id); - const index = this._providerIds.findIndex(id => id === provider.id); - if (index > -1) { - this._providerIds.splice(index); - } + this._authenticationProviders.delete(id); - const i = this._providers.findIndex(p => p.id === provider.id); + const i = this._providers.findIndex(p => p.id === id); if (i > -1) { this._providers.splice(i); } - this._proxy.$unregisterAuthenticationProvider(provider.id); + this._proxy.$unregisterAuthenticationProvider(id); }); } $login(providerId: string, scopes: string[]): Promise { - const authProvider = this._authenticationProviders.get(providerId); - if (authProvider) { - return Promise.resolve(authProvider.login(scopes)); + const providerData = this._authenticationProviders.get(providerId); + if (providerData) { + return Promise.resolve(providerData.provider.login(scopes)); } throw new Error(`Unable to find authentication provider with handle: ${providerId}`); } $logout(providerId: string, sessionId: string): Promise { - const authProvider = this._authenticationProviders.get(providerId); - if (authProvider) { - return Promise.resolve(authProvider.logout(sessionId)); + const providerData = this._authenticationProviders.get(providerId); + if (providerData) { + return Promise.resolve(providerData.provider.logout(sessionId)); } throw new Error(`Unable to find authentication provider with handle: ${providerId}`); } $getSessions(providerId: string): Promise> { - const authProvider = this._authenticationProviders.get(providerId); - if (authProvider) { - return Promise.resolve(authProvider.getSessions()); + const providerData = this._authenticationProviders.get(providerId); + if (providerData) { + return Promise.resolve(providerData.provider.getSessions()); } throw new Error(`Unable to find authentication provider with handle: ${providerId}`); } async $getSessionAccessToken(providerId: string, sessionId: string): Promise { - const authProvider = this._authenticationProviders.get(providerId); - if (authProvider) { - const sessions = await authProvider.getSessions(); + const providerData = this._authenticationProviders.get(providerId); + if (providerData) { + const sessions = await providerData.provider.getSessions(); const session = sessions.find(session => session.id === sessionId); if (session) { return session.accessToken; @@ -245,23 +230,4 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { this._onDidChangeAuthenticationProviders.fire({ added, removed }); return Promise.resolve(); } - - async $onDidChangePassword(): Promise { - this._onDidChangePassword.fire(); - } - - getPassword(requestingExtension: IExtensionDescription, key: string): Promise { - const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier); - return this._proxy.$getPassword(extensionId, key); - } - - setPassword(requestingExtension: IExtensionDescription, key: string, value: string): Promise { - const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier); - return this._proxy.$setPassword(extensionId, key, value); - } - - deletePassword(requestingExtension: IExtensionDescription, key: string): Promise { - const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier); - return this._proxy.$deletePassword(extensionId, key); - } } diff --git a/src/vs/workbench/api/common/extHostCommands.ts b/src/vs/workbench/api/common/extHostCommands.ts index 649612e38..c33737246 100644 --- a/src/vs/workbench/api/common/extHostCommands.ts +++ b/src/vs/workbench/api/common/extHostCommands.ts @@ -103,7 +103,7 @@ export class ExtHostCommands implements ExtHostCommandsShape { const internalArgs = apiCommand.args.map((arg, i) => { if (!arg.validate(apiArgs[i])) { - throw new Error(`Invalid argument '${arg.name}' when running '${apiCommand.id}', receieved: ${apiArgs[i]}`); + throw new Error(`Invalid argument '${arg.name}' when running '${apiCommand.id}', received: ${apiArgs[i]}`); } return arg.convert(apiArgs[i]); }); @@ -194,7 +194,7 @@ export class ExtHostCommands implements ExtHostCommandsShape { } } - private _executeContributedCommand(id: string, args: any[]): Promise { + private async _executeContributedCommand(id: string, args: any[]): Promise { const command = this._commands.get(id); if (!command) { throw new Error('Unknown command'); @@ -205,17 +205,16 @@ export class ExtHostCommands implements ExtHostCommandsShape { try { validateConstraint(args[i], description.args[i].constraint); } catch (err) { - return Promise.reject(new Error(`Running the contributed command: '${id}' failed. Illegal argument '${description.args[i].name}' - ${description.args[i].description}`)); + throw new Error(`Running the contributed command: '${id}' failed. Illegal argument '${description.args[i].name}' - ${description.args[i].description}`); } } } try { - const result = callback.apply(thisArg, args); - return Promise.resolve(result); + return await callback.apply(thisArg, args); } catch (err) { this._logService.error(err, id); - return Promise.reject(new Error(`Running the contributed command: '${id}' failed.`)); + throw new Error(`Running the contributed command: '${id}' failed.`); } } diff --git a/src/vs/workbench/api/common/extHostCustomEditors.ts b/src/vs/workbench/api/common/extHostCustomEditors.ts index becd0887e..a4359d91a 100644 --- a/src/vs/workbench/api/common/extHostCustomEditors.ts +++ b/src/vs/workbench/api/common/extHostCustomEditors.ts @@ -13,6 +13,7 @@ import * as modes from 'vs/editor/common/modes'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; +import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { ExtHostWebviews, toExtensionData } from 'vs/workbench/api/common/extHostWebview'; import { ExtHostWebviewPanels } from 'vs/workbench/api/common/extHostWebviewPanels'; import { EditorGroupColumn } from 'vs/workbench/common/editor'; @@ -178,7 +179,7 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor options: { webviewOptions?: vscode.WebviewPanelOptions, supportsMultipleEditorsPerDocument?: boolean }, ): vscode.Disposable { const disposables = new DisposableStore(); - if ('resolveCustomTextEditor' in provider) { + if (isCustomTextEditorProvider(provider)) { disposables.add(this._editorProviders.addTextProvider(viewType, extension, provider)); this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options.webviewOptions || {}, { supportsMove: !!provider.moveCustomTextEditor, @@ -208,7 +209,6 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor })); } - async $createCustomDocument(resource: UriComponents, viewType: string, backupId: string | undefined, cancellation: CancellationToken) { const entry = this._editorProviders.get(viewType); if (!entry) { @@ -261,8 +261,10 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor throw new Error(`No provider found for '${viewType}'`); } + const viewColumn = typeConverters.ViewColumn.to(position); + const webview = this._extHostWebview.createNewWebview(handle, options, entry.extension); - const panel = this._extHostWebviewPanels.createNewWebviewPanel(handle, viewType, title, position, options, webview); + const panel = this._extHostWebviewPanels.createNewWebviewPanel(handle, viewType, title, viewColumn, options, webview); const revivedResource = URI.revive(resource); @@ -350,7 +352,6 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor return backup.id; } - private getCustomDocumentEntry(viewType: string, resource: UriComponents): CustomDocumentStoreEntry { const entry = this._documents.get(viewType, URI.revive(resource)); if (!entry) { @@ -375,6 +376,9 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor } } +function isCustomTextEditorProvider(provider: vscode.CustomReadonlyEditorProvider | vscode.CustomTextEditorProvider): provider is vscode.CustomTextEditorProvider { + return typeof (provider as vscode.CustomTextEditorProvider).resolveCustomTextEditor === 'function'; +} function isEditEvent(e: vscode.CustomDocumentContentChangeEvent | vscode.CustomDocumentEditEvent): e is vscode.CustomDocumentEditEvent { return typeof (e as vscode.CustomDocumentEditEvent).undo === 'function' diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index 4026a1d2b..d639431a1 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -152,7 +152,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E const source = src; - if (typeof source.sourceReference === 'number') { + if (typeof source.sourceReference === 'number' && source.sourceReference > 0) { // src can be retrieved via DAP's "source" request let debug = `debug:${encodeURIComponent(source.path || '')}`; diff --git a/src/vs/workbench/api/common/extHostEditorTabs.ts b/src/vs/workbench/api/common/extHostEditorTabs.ts new file mode 100644 index 000000000..2d0f7b4c6 --- /dev/null +++ b/src/vs/workbench/api/common/extHostEditorTabs.ts @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type * as vscode from 'vscode'; +import { IEditorTabDto, IExtHostEditorTabsShape } from 'vs/workbench/api/common/extHost.protocol'; +import { URI } from 'vs/base/common/uri'; +import { Emitter, Event } from 'vs/base/common/event'; + + +export interface IEditorTab { + name: string; + group: number; + resource: vscode.Uri +} + +export class ExtHostEditorTabs implements IExtHostEditorTabsShape { + + private readonly _onDidChangeTabs = new Emitter(); + readonly onDidChangeTabs: Event = this._onDidChangeTabs.event; + + private _tabs: IEditorTab[] = []; + + get tabs(): readonly IEditorTab[] { + return this._tabs; + } + + $acceptEditorTabs(tabs: IEditorTabDto[]): void { + this._tabs = tabs.map(dto => { + return { + name: dto.name, + group: dto.group, + resource: URI.revive(dto.resource) + }; + }); + this._onDidChangeTabs.fire(); + } +} diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 328b93272..2274a4f12 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -5,6 +5,7 @@ import * as nls from 'vs/nls'; import * as path from 'vs/base/common/path'; +import * as performance from 'vs/base/common/performance'; import { originalFSPath, joinPath } from 'vs/base/common/resources'; import { Barrier, timeout } from 'vs/base/common/async'; import { dispose, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; @@ -35,6 +36,8 @@ import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelServ import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; import { Emitter, Event } from 'vs/base/common/event'; import { IExtensionActivationHost, checkActivateWorkspaceContainsExtension } from 'vs/workbench/api/common/shared/workspaceContains'; +import { ExtHostSecretState, IExtHostSecretState } from 'vs/workbench/api/common/exHostSecretState'; +import { ExtensionSecrets } from 'vs/workbench/api/common/extHostSecrets'; interface ITestRunner { /** Old test runner API, as exported from `vscode/lib/testrunner` */ @@ -50,7 +53,7 @@ export const IHostUtils = createDecorator('IHostUtils'); export interface IHostUtils { readonly _serviceBrand: undefined; - exit(code?: number): void; + exit(code: number): void; exists(path: string): Promise; realpath(path: string): Promise; } @@ -94,6 +97,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme private readonly _readyToRunExtensions: Barrier; protected readonly _registry: ExtensionDescriptionRegistry; private readonly _storage: ExtHostStorage; + private readonly _secretState: ExtHostSecretState; private readonly _storagePath: IExtensionStoragePaths; private readonly _activator: ExtensionsActivator; private _extensionPathIndex: Promise> | null; @@ -115,7 +119,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme @IExtHostInitDataService initData: IExtHostInitDataService, @IExtensionStoragePaths storagePath: IExtensionStoragePaths, @IExtHostTunnelService extHostTunnelService: IExtHostTunnelService, - @IExtHostTerminalService extHostTerminalService: IExtHostTerminalService + @IExtHostTerminalService extHostTerminalService: IExtHostTerminalService, ) { super(); this._hostUtils = hostUtils; @@ -138,10 +142,12 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme this._readyToRunExtensions = new Barrier(); this._registry = new ExtensionDescriptionRegistry(this._initData.extensions); this._storage = new ExtHostStorage(this._extHostContext); + this._secretState = new ExtHostSecretState(this._extHostContext); this._storagePath = storagePath; this._instaService = instaService.createChild(new ServiceCollection( - [IExtHostStorage, this._storage] + [IExtHostStorage, this._storage], + [IExtHostSecretState, this._secretState] )); const hostExtensions = new Set(); @@ -184,6 +190,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme this._almostReadyToRunExtensions.open(); await this._extHostWorkspace.waitForInitializeCall(); + performance.mark('code/extHost/ready'); this._readyToStartExtensionHost.open(); if (this._initData.autoStart) { @@ -362,10 +369,14 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup); return Promise.all([ - this._loadCommonJSModule(joinPath(extensionDescription.extensionLocation, entryPoint), activationTimesBuilder), + this._loadCommonJSModule(extensionDescription.identifier, joinPath(extensionDescription.extensionLocation, entryPoint), activationTimesBuilder), this._loadExtensionContext(extensionDescription) ]).then(values => { + performance.mark(`code/extHost/willActivateExtension/${extensionDescription.identifier.value}`); return AbstractExtHostExtensionService._callActivate(this._logService, extensionDescription.identifier, values[0], values[1], activationTimesBuilder); + }).then((activatedExtension) => { + performance.mark(`code/extHost/didActivateExtension/${extensionDescription.identifier.value}`); + return activatedExtension; }); } @@ -373,6 +384,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme 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 extensionMode = extensionDescription.isUnderDevelopment ? (this._initData.environment.extensionTestsLocationURI ? ExtensionMode.Test : ExtensionMode.Development) : ExtensionMode.Production; @@ -388,6 +400,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme return Object.freeze({ globalState, workspaceState, + secrets, subscriptions: [], get extensionUri() { return extensionDescription.extensionLocation; }, get extensionPath() { return extensionDescription.extensionLocation.fsPath; }, @@ -456,6 +469,9 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme } private _activateAllStartupFinished(): void { + // startup is considered finished + this._mainThreadExtensionsProxy.$setPerformanceMarks(performance.getMarks()); + for (const desc of this._registry.getAllExtensionDescriptions()) { if (desc.activationEvents) { for (const activationEvent of desc.activationEvents) { @@ -541,7 +557,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme let testRunner: ITestRunner | INewTestRunner | undefined; let requireError: Error | undefined; try { - testRunner = await this._loadCommonJSModule(URI.file(extensionTestsPath), new ExtensionActivationTimesBuilder(false)); + testRunner = await this._loadCommonJSModule(null, URI.file(extensionTestsPath), new ExtensionActivationTimesBuilder(false)); } catch (error) { requireError = error; } @@ -586,11 +602,17 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme } private _testRunnerExit(code: number): void { + this._logService.info(`extension host terminating: test runner requested exit with code ${code}`); + this._logService.flush(); + // wait at most 5000ms for the renderer to confirm our exit request and for the renderer socket to drain // (this is to ensure all outstanding messages reach the renderer) const exitPromise = this._mainThreadExtensionsProxy.$onExtensionHostExit(code); const drainPromise = this._extHostContext.drain(); Promise.race([Promise.all([exitPromise, drainPromise]), timeout(5000)]).then(() => { + this._logService.info(`exiting with code ${code}`); + this._logService.flush(); + this._hostUtils.exit(code); }); } @@ -644,7 +666,9 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme } try { + performance.mark(`code/extHost/willResolveAuthority/${authorityPrefix}`); const result = await resolver.resolve(remoteAuthority, { resolveAttempt }); + performance.mark(`code/extHost/didResolveAuthorityOK/${authorityPrefix}`); this._disposables.add(await this._extHostTunnelService.setTunnelExtensionFunctions(resolver)); // Split merged API result into separate authority/options @@ -667,6 +691,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme } }; } catch (err) { + performance.mark(`code/extHost/didResolveAuthorityError/${authorityPrefix}`); if (err instanceof RemoteAuthorityResolverError) { return { type: 'error', @@ -754,7 +779,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme protected abstract _beforeAlmostReadyToRunExtensions(): Promise; protected abstract _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined; - protected abstract _loadCommonJSModule(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise; + protected abstract _loadCommonJSModule(extensionId: ExtensionIdentifier | null, module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise; public abstract $setRemoteEnvironment(env: { [key: string]: string | null }): Promise; } diff --git a/src/vs/workbench/api/common/extHostFileSystemEventService.ts b/src/vs/workbench/api/common/extHostFileSystemEventService.ts index 0503a131b..6801cf0b3 100644 --- a/src/vs/workbench/api/common/extHostFileSystemEventService.ts +++ b/src/vs/workbench/api/common/extHostFileSystemEventService.ts @@ -8,7 +8,7 @@ import { IRelativePattern, parse } from 'vs/base/common/glob'; import { URI } from 'vs/base/common/uri'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import type * as vscode from 'vscode'; -import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, SourceTargetPair, IWorkspaceEditDto, MainThreadBulkEditsShape } from './extHost.protocol'; +import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, SourceTargetPair, IWorkspaceEditDto, IWillRunFileOperationParticipation } from './extHost.protocol'; import * as typeConverter from './extHostTypeConverters'; import { Disposable, WorkspaceEdit } from './extHostTypes'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -122,8 +122,7 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ constructor( mainContext: IMainContext, private readonly _logService: ILogService, - private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors, - private readonly _mainThreadBulkEdits: MainThreadBulkEditsShape = mainContext.getProxy(MainContext.MainThreadBulkEdits) + private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors ) { // } @@ -178,24 +177,21 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ }; } - async $onWillRunFileOperation(operation: FileOperation, files: SourceTargetPair[], undoRedoGroupId: number | undefined, timeout: number, token: CancellationToken): Promise { + async $onWillRunFileOperation(operation: FileOperation, files: SourceTargetPair[], timeout: number, token: CancellationToken): Promise { switch (operation) { case FileOperation.MOVE: - await this._fireWillEvent(this._onWillRenameFile, { files: files.map(f => ({ oldUri: URI.revive(f.source!), newUri: URI.revive(f.target) })) }, undoRedoGroupId, timeout, token); - break; + return await this._fireWillEvent(this._onWillRenameFile, { files: files.map(f => ({ oldUri: URI.revive(f.source!), newUri: URI.revive(f.target) })) }, timeout, token); case FileOperation.DELETE: - await this._fireWillEvent(this._onWillDeleteFile, { files: files.map(f => URI.revive(f.target)) }, undoRedoGroupId, timeout, token); - break; + return await this._fireWillEvent(this._onWillDeleteFile, { files: files.map(f => URI.revive(f.target)) }, timeout, token); case FileOperation.CREATE: - await this._fireWillEvent(this._onWillCreateFile, { files: files.map(f => URI.revive(f.target)) }, undoRedoGroupId, timeout, token); - break; - default: - //ignore, dont send + return await this._fireWillEvent(this._onWillCreateFile, { files: files.map(f => URI.revive(f.target)) }, timeout, token); } + return undefined; } - private async _fireWillEvent(emitter: AsyncEmitter, data: Omit, undoRedoGroupId: number | undefined, timeout: number, token: CancellationToken): Promise { + private async _fireWillEvent(emitter: AsyncEmitter, data: Omit, timeout: number, token: CancellationToken): Promise { + const extensionNames = new Set(); const edits: WorkspaceEdit[] = []; await emitter.fireAsync(data, token, async (thenable, listener) => { @@ -204,25 +200,28 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ const result = await Promise.resolve(thenable); if (result instanceof WorkspaceEdit) { edits.push(result); + extensionNames.add((>listener).extension.displayName ?? (>listener).extension.identifier.value); } if (Date.now() - now > timeout) { - this._logService.warn('SLOW file-participant', (>listener).extension?.identifier); + this._logService.warn('SLOW file-participant', (>listener).extension.identifier); } }); if (token.isCancellationRequested) { - return; + return undefined; } - if (edits.length > 0) { - // concat all WorkspaceEdits collected via waitUntil-call and apply them in one go. - const dto: IWorkspaceEditDto = { edits: [] }; - for (let edit of edits) { - let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors); - dto.edits = dto.edits.concat(edits); - } - return this._mainThreadBulkEdits.$tryApplyWorkspaceEdit(dto, undoRedoGroupId); + if (edits.length === 0) { + return undefined; } + + // concat all WorkspaceEdits collected via waitUntil-call and send them over to the renderer + const dto: IWorkspaceEditDto = { edits: [] }; + for (let edit of edits) { + let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors); + dto.edits = dto.edits.concat(edits); + } + return { edit: dto, extensionNames: Array.from(extensionNames) }; } } diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 49cde88e1..34ca10650 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -27,11 +27,12 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio import { IURITransformer } from 'vs/base/common/uriIpc'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { VSBuffer } from 'vs/base/common/buffer'; -import { encodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokensDto'; +import { encodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto'; import { IdGenerator } from 'vs/base/common/idGenerator'; import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; import { Cache } from './cache'; import { StopWatch } from 'vs/base/common/stopwatch'; +import { CancellationError } from 'vs/base/common/errors'; // --- adapter @@ -1062,6 +1063,20 @@ class SignatureHelpAdapter { } } +class InlineHintsAdapter { + constructor( + private readonly _documents: ExtHostDocuments, + private readonly _provider: vscode.InlineHintsProvider, + ) { } + + provideInlineHints(resource: URI, range: IRange, token: CancellationToken): Promise { + const doc = this._documents.getDocument(resource); + return asPromise(() => this._provider.provideInlineHints(doc, typeConvert.Range.to(range), token)).then(value => { + return value ? { hints: value.map(typeConvert.InlineHint.from) } : undefined; + }); + } +} + class LinkProviderAdapter { private _cache = new Cache('DocumentLink'); @@ -1320,7 +1335,7 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov | SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter | TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter | SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter - | LinkedEditingRangeAdapter; + | LinkedEditingRangeAdapter | InlineHintsAdapter; class AdapterData { constructor( @@ -1403,7 +1418,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return ExtHostLanguageFeatures._handlePool++; } - private _withAdapter(handle: number, ctor: { new(...args: any[]): A; }, callback: (adapter: A, extension: IExtensionDescription | undefined) => Promise, fallbackValue: R): Promise { + private _withAdapter(handle: number, ctor: { new(...args: any[]): A; }, callback: (adapter: A, extension: IExtensionDescription | undefined) => Promise, fallbackValue: R, allowCancellationError: boolean = false): Promise { const data = this._adapter.get(handle); if (!data) { return Promise.resolve(fallbackValue); @@ -1421,8 +1436,11 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF Promise.resolve(p).then( () => this._logService.trace(`[${extension.identifier.value}] provider DONE after ${Date.now() - t1}ms`), err => { - this._logService.error(`[${extension.identifier.value}] provider FAILED`); - this._logService.error(err); + const isExpectedError = allowCancellationError && (err instanceof CancellationError); + if (!isExpectedError) { + this._logService.error(`[${extension.identifier.value}] provider FAILED`); + this._logService.error(err); + } } ); } @@ -1711,7 +1729,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF } $provideDocumentSemanticTokens(handle: number, resource: UriComponents, previousResultId: number, token: CancellationToken): Promise { - return this._withAdapter(handle, DocumentSemanticTokensAdapter, adapter => adapter.provideDocumentSemanticTokens(URI.revive(resource), previousResultId, token), null); + return this._withAdapter(handle, DocumentSemanticTokensAdapter, adapter => adapter.provideDocumentSemanticTokens(URI.revive(resource), previousResultId, token), null, true); } $releaseDocumentSemanticTokens(handle: number, semanticColoringResultId: number): void { @@ -1725,7 +1743,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF } $provideDocumentRangeSemanticTokens(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise { - return this._withAdapter(handle, DocumentRangeSemanticTokensAdapter, adapter => adapter.provideDocumentRangeSemanticTokens(URI.revive(resource), range, token), null); + return this._withAdapter(handle, DocumentRangeSemanticTokensAdapter, adapter => adapter.provideDocumentRangeSemanticTokens(URI.revive(resource), range, token), null, true); } //#endregion @@ -1770,6 +1788,27 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF this._withAdapter(handle, SignatureHelpAdapter, adapter => adapter.releaseSignatureHelp(id), undefined); } + // --- inline hints + + registerInlineHintsProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.InlineHintsProvider): vscode.Disposable { + + const eventHandle = typeof provider.onDidChangeInlineHints === 'function' ? this._nextHandle() : undefined; + const handle = this._addNewAdapter(new InlineHintsAdapter(this._documents, provider), extension); + + this._proxy.$registerInlineHintsProvider(handle, this._transformDocumentSelector(selector), eventHandle); + let result = this._createDisposable(handle); + + if (eventHandle !== undefined) { + const subscription = provider.onDidChangeInlineHints!(_ => this._proxy.$emitInlineHintsEvent(eventHandle)); + result = Disposable.from(result, subscription); + } + return result; + } + + $provideInlineHints(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise { + return this._withAdapter(handle, InlineHintsAdapter, adapter => adapter.provideInlineHints(URI.revive(resource), range, token), undefined); + } + // --- links registerDocumentLinkProvider(extension: IExtensionDescription | undefined, selector: vscode.DocumentSelector, provider: vscode.DocumentLinkProvider): vscode.Disposable { @@ -1882,7 +1921,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return { beforeText: ExtHostLanguageFeatures._serializeRegExp(onEnterRule.beforeText), afterText: onEnterRule.afterText ? ExtHostLanguageFeatures._serializeRegExp(onEnterRule.afterText) : undefined, - oneLineAboveText: onEnterRule.oneLineAboveText ? ExtHostLanguageFeatures._serializeRegExp(onEnterRule.oneLineAboveText) : undefined, + previousLineText: onEnterRule.previousLineText ? ExtHostLanguageFeatures._serializeRegExp(onEnterRule.previousLineText) : undefined, action: onEnterRule.action }; } diff --git a/src/vs/workbench/api/common/extHostMessageService.ts b/src/vs/workbench/api/common/extHostMessageService.ts index ff75e2f33..847a27a2b 100644 --- a/src/vs/workbench/api/common/extHostMessageService.ts +++ b/src/vs/workbench/api/common/extHostMessageService.ts @@ -8,6 +8,7 @@ import type * as vscode from 'vscode'; import { MainContext, MainThreadMessageServiceShape, MainThreadMessageOptions, IMainContext } from './extHost.protocol'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; +import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; function isMessageItem(item: any): item is vscode.MessageItem { return item && item.title; @@ -37,9 +38,14 @@ export class ExtHostMessageService { items = [optionsOrFirstItem, ...rest]; } else { options.modal = optionsOrFirstItem && optionsOrFirstItem.modal; + options.useCustom = optionsOrFirstItem && optionsOrFirstItem.useCustom; items = rest; } + if (options.useCustom) { + checkProposedApiEnabled(extension); + } + const commands: { title: string; isCloseAffordance: boolean; handle: number; }[] = []; for (let handle = 0; handle < items.length; handle++) { diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index bb136d1c0..298548f29 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -78,8 +78,8 @@ export interface ExtHostNotebookOutputRenderingHandler { } export class ExtHostNotebookKernelProviderAdapter extends Disposable { - private _kernelToId = new Map(); - private _idToKernel = new Map(); + private _kernelToFriendlyId = new Map(); + private _friendlyIdToKernel = new Map(); constructor( private readonly _proxy: MainThreadNotebookShape, private readonly _handle: number, @@ -101,24 +101,25 @@ export class ExtHostNotebookKernelProviderAdapter extends Disposable { const newMap = new Map(); let kernel_unique_pool = 0; - const kernelIdCache = new Set(); + const kernelFriendlyIdCache = new Set(); const transformedData: INotebookKernelInfoDto2[] = data.map(kernel => { - let id = this._kernelToId.get(kernel); - if (id === undefined) { - if (kernel.id && kernelIdCache.has(kernel.id)) { - id = `${this._extension.identifier.value}_${kernel.id}_${kernel_unique_pool++}`; + let friendlyId = this._kernelToFriendlyId.get(kernel); + if (friendlyId === undefined) { + if (kernel.id && kernelFriendlyIdCache.has(kernel.id)) { + friendlyId = `${this._extension.identifier.value}_${kernel.id}_${kernel_unique_pool++}`; } else { - id = `${this._extension.identifier.value}_${kernel.id || UUID.generateUuid()}`; + friendlyId = `${this._extension.identifier.value}_${kernel.id || UUID.generateUuid()}`; } - this._kernelToId.set(kernel, id); + this._kernelToFriendlyId.set(kernel, friendlyId); } - newMap.set(kernel, id); + newMap.set(kernel, friendlyId); return { - id, + id: kernel.id, + friendlyId: friendlyId, label: kernel.label, extension: this._extension.identifier, extensionLocation: this._extension.extensionLocation, @@ -129,22 +130,22 @@ export class ExtHostNotebookKernelProviderAdapter extends Disposable { }; }); - this._kernelToId = newMap; + this._kernelToFriendlyId = newMap; - this._idToKernel.clear(); - this._kernelToId.forEach((value, key) => { - this._idToKernel.set(value, key); + this._friendlyIdToKernel.clear(); + this._kernelToFriendlyId.forEach((value, key) => { + this._friendlyIdToKernel.set(value, key); }); return transformedData; } - getKernel(kernelId: string) { - return this._idToKernel.get(kernelId); + getKernelByFriendlyId(kernelId: string) { + return this._friendlyIdToKernel.get(kernelId); } async resolveNotebook(kernelId: string, document: ExtHostNotebookDocument, webview: vscode.NotebookCommunication, token: CancellationToken) { - const kernel = this._idToKernel.get(kernelId); + const kernel = this._friendlyIdToKernel.get(kernelId); if (kernel && this._provider.resolveKernel) { return this._provider.resolveKernel(kernel, document.notebookDocument, webview, token); @@ -152,7 +153,7 @@ export class ExtHostNotebookKernelProviderAdapter extends Disposable { } async executeNotebook(kernelId: string, document: ExtHostNotebookDocument, cell: ExtHostCell | undefined) { - const kernel = this._idToKernel.get(kernelId); + const kernel = this._friendlyIdToKernel.get(kernelId); if (!kernel) { return; @@ -166,7 +167,7 @@ export class ExtHostNotebookKernelProviderAdapter extends Disposable { } async cancelNotebook(kernelId: string, document: ExtHostNotebookDocument, cell: ExtHostCell | undefined) { - const kernel = this._idToKernel.get(kernelId); + const kernel = this._friendlyIdToKernel.get(kernelId); if (!kernel) { return; @@ -580,10 +581,10 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN this._outputDisplayOrder = displayOrder; } - $acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelId: string | undefined; }) { + $acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelFriendlyId: string | undefined; }) { if (event.providerHandle !== undefined) { this._withAdapter(event.providerHandle, event.uri, async (adapter, document) => { - const kernel = event.kernelId ? adapter.getKernel(event.kernelId) : undefined; + const kernel = event.kernelFriendlyId ? adapter.getKernelByFriendlyId(event.kernelFriendlyId) : undefined; this._editors.forEach(editor => { if (editor.editor.notebookData === document) { editor.editor._acceptKernel(kernel); diff --git a/src/vs/workbench/api/common/extHostNotebookDocument.ts b/src/vs/workbench/api/common/extHostNotebookDocument.ts index e67332a40..85f9083ba 100644 --- a/src/vs/workbench/api/common/extHostNotebookDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookDocument.ts @@ -113,14 +113,17 @@ export class ExtHostCell extends Disposable { get cell(): vscode.NotebookCell { if (!this._cell) { const that = this; - const document = this._extHostDocument.getDocument(this.uri)!.document; + const data = this._extHostDocument.getDocument(this.uri); + if (!data) { + throw new Error(`MISSING extHostDocument for notebook cell: ${this.uri}`); + } this._cell = Object.freeze({ get index() { return that._notebook.getCellIndex(that); }, notebook: that._notebook.notebookDocument, uri: that.uri, cellKind: this._cellData.cellKind, - document, - get language() { return document.languageId; }, + document: data.document, + get language() { return data!.document.languageId; }, get outputs() { return that._outputs; }, set outputs(value) { that._updateOutputs(value); }, get metadata() { return that._metadata; }, diff --git a/src/vs/workbench/api/common/extHostRequireInterceptor.ts b/src/vs/workbench/api/common/extHostRequireInterceptor.ts index fd9ae5f2d..f59d41ce8 100644 --- a/src/vs/workbench/api/common/extHostRequireInterceptor.ts +++ b/src/vs/workbench/api/common/extHostRequireInterceptor.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as performance from 'vs/base/common/performance'; import { TernarySearchTree } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; import { MainThreadTelemetryShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; @@ -52,7 +53,9 @@ export abstract class RequireInterceptor { this._installInterceptor(); + performance.mark('code/extHost/willWaitForConfig'); const configProvider = await this._extHostConfiguration.getConfigProvider(); + performance.mark('code/extHost/didWaitForConfig'); const extensionPaths = await this._extHostExtensionService.getExtensionPathIndex(); this.register(new VSCodeNodeModuleFactory(this._apiFactory, extensionPaths, this._extensionRegistry, configProvider, this._logService)); diff --git a/src/vs/workbench/api/common/extHostSecrets.ts b/src/vs/workbench/api/common/extHostSecrets.ts new file mode 100644 index 000000000..6d973d31e --- /dev/null +++ b/src/vs/workbench/api/common/extHostSecrets.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 type * as vscode from 'vscode'; + +import { ExtHostSecretState } from 'vs/workbench/api/common/exHostSecretState'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { Emitter, Event } from 'vs/base/common/event'; + +export class ExtensionSecrets implements vscode.SecretStorage { + + protected readonly _id: string; + readonly #secretState: ExtHostSecretState; + + private _onDidChange = new Emitter(); + readonly onDidChange: Event = this._onDidChange.event; + + + 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 }); + } + }); + } + + get(key: string): Promise { + return this.#secretState.get(this._id, key); + } + + store(key: string, value: string): Promise { + return this.#secretState.store(this._id, key, value); + } + + delete(key: string): Promise { + return this.#secretState.delete(this._id, key); + } +} diff --git a/src/vs/workbench/api/common/extHostStatusBar.ts b/src/vs/workbench/api/common/extHostStatusBar.ts index a89e9fe30..877d56e79 100644 --- a/src/vs/workbench/api/common/extHostStatusBar.ts +++ b/src/vs/workbench/api/common/extHostStatusBar.ts @@ -10,8 +10,6 @@ import { MainContext, MainThreadStatusBarShape, IMainContext, ICommandDto } from import { localize } from 'vs/nls'; import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; export class ExtHostStatusBarEntry implements vscode.StatusBarItem { private static ID_GEN = 0; @@ -48,9 +46,8 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { private _proxy: MainThreadStatusBarShape; private _commands: CommandsConverter; private _accessibilityInformation?: vscode.AccessibilityInformation; - private _extension?: IExtensionDescription; - constructor(proxy: MainThreadStatusBarShape, commands: CommandsConverter, id: string, name: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number, accessibilityInformation?: vscode.AccessibilityInformation, extension?: IExtensionDescription) { + constructor(proxy: MainThreadStatusBarShape, commands: CommandsConverter, id: string, name: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number, accessibilityInformation?: vscode.AccessibilityInformation) { this._id = ExtHostStatusBarEntry.ID_GEN++; this._proxy = proxy; this._commands = commands; @@ -59,7 +56,6 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { this._alignment = alignment; this._priority = priority; this._accessibilityInformation = accessibilityInformation; - this._extension = extension; } public get id(): number { @@ -87,10 +83,6 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { } public get backgroundColor(): ThemeColor | undefined { - if (this._extension) { - checkProposedApiEnabled(this._extension); - } - return this._backgroundColor; } @@ -118,10 +110,6 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { } public set backgroundColor(color: ThemeColor | undefined) { - if (this._extension) { - checkProposedApiEnabled(this._extension); - } - if (color && !ExtHostStatusBarEntry.ALLOWED_BACKGROUND_COLORS.has(color.id)) { color = undefined; } @@ -248,8 +236,8 @@ export class ExtHostStatusBar { this._statusMessage = new StatusBarMessage(this); } - createStatusBarEntry(id: string, name: string, alignment?: ExtHostStatusBarAlignment, priority?: number, accessibilityInformation?: vscode.AccessibilityInformation, extension?: IExtensionDescription): vscode.StatusBarItem { - return new ExtHostStatusBarEntry(this._proxy, this._commands, id, name, alignment, priority, accessibilityInformation, extension); + createStatusBarEntry(id: string, name: string, alignment?: ExtHostStatusBarAlignment, priority?: number, accessibilityInformation?: vscode.AccessibilityInformation): vscode.StatusBarItem { + return new ExtHostStatusBarEntry(this._proxy, this._commands, id, name, alignment, priority, accessibilityInformation); } setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable): Disposable { diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 1cccd958f..f684dbb85 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -5,12 +5,11 @@ import type * as vscode from 'vscode'; import { Event, Emitter } from 'vs/base/common/event'; -import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto, ITerminalDimensionsDto, ITerminalLinkDto } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto, ITerminalDimensionsDto, ITerminalLinkDto, TerminalIdentifier } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { ITerminalChildProcess, EXT_HOST_CREATION_DELAY, ITerminalLaunchError, ITerminalDimensionsOverride } from 'vs/workbench/contrib/terminal/common/terminal'; -import { timeout } from 'vs/base/common/async'; +import { ITerminalChildProcess, ITerminalLaunchError, ITerminalDimensionsOverride } from 'vs/workbench/contrib/terminal/common/terminal'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering'; import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; @@ -21,6 +20,7 @@ import { localize } from 'vs/nls'; import { NotSupportedError } from 'vs/base/common/errors'; import { serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { generateUuid } from 'vs/base/common/uuid'; export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, IDisposable { @@ -47,63 +47,8 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, ID export const IExtHostTerminalService = createDecorator('IExtHostTerminalService'); -export class BaseExtHostTerminal { - public _id: number | undefined; - protected _idPromise: Promise; - private _idPromiseComplete: ((value: number) => any) | undefined; +export class ExtHostTerminal implements vscode.Terminal { private _disposed: boolean = false; - private _queuedRequests: ApiRequest[] = []; - - constructor( - protected _proxy: MainThreadTerminalServiceShape, - id?: number - ) { - this._idPromise = new Promise(c => { - if (id !== undefined) { - this._id = id; - c(id); - } else { - this._idPromiseComplete = c; - } - }); - } - - public dispose(): void { - if (!this._disposed) { - this._disposed = true; - this._queueApiRequest(this._proxy.$dispose, []); - } - } - - protected _checkDisposed() { - if (this._disposed) { - throw new Error('Terminal has already been disposed'); - } - } - - protected _queueApiRequest(callback: (...args: any[]) => void, args: any[]): void { - const request: ApiRequest = new ApiRequest(callback, args); - if (!this._id) { - this._queuedRequests.push(request); - return; - } - request.run(this._proxy, this._id); - } - - public _runQueuedRequests(id: number): void { - this._id = id; - if (this._idPromiseComplete) { - this._idPromiseComplete(id); - this._idPromiseComplete = undefined; - } - this._queuedRequests.forEach((r) => { - r.run(this._proxy, id); - }); - this._queuedRequests.length = 0; - } -} - -export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Terminal { private _pidPromise: Promise; private _cols: number | undefined; private _pidPromiseComplete: ((value: number | undefined) => any) | undefined; @@ -113,12 +58,11 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi public isOpen: boolean = false; constructor( - proxy: MainThreadTerminalServiceShape, + private _proxy: MainThreadTerminalServiceShape, + public _id: TerminalIdentifier, private readonly _creationOptions: vscode.TerminalOptions | vscode.ExtensionTerminalOptions, private _name?: string, - id?: number ) { - super(proxy, id); this._creationOptions = Object.freeze(this._creationOptions); this._pidPromise = new Promise(c => this._pidPromiseComplete = c); } @@ -133,16 +77,35 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi hideFromUser?: boolean, isFeatureTerminal?: boolean ): Promise { - const result = await this._proxy.$createTerminal({ name: this._name, shellPath, shellArgs, cwd, env, waitOnExit, strictEnv, hideFromUser, isFeatureTerminal }); - this._name = result.name; - this._runQueuedRequests(result.id); + if (typeof this._id !== 'string') { + throw new Error('Terminal has already been created'); + } + await this._proxy.$createTerminal(this._id, { name: this._name, shellPath, shellArgs, cwd, env, waitOnExit, strictEnv, hideFromUser, isFeatureTerminal }); } public async createExtensionTerminal(): Promise { - const result = await this._proxy.$createTerminal({ name: this._name, isExtensionTerminal: true }); - this._name = result.name; - this._runQueuedRequests(result.id); - return result.id; + if (typeof this._id !== 'string') { + throw new Error('Terminal has already been created'); + } + await this._proxy.$createTerminal(this._id, { name: this._name, isExtensionTerminal: true }); + // At this point, the id has been set via `$acceptTerminalOpened` + if (typeof this._id === 'string') { + throw new Error('Terminal creation failed'); + } + return this._id; + } + + public dispose(): void { + if (!this._disposed) { + this._disposed = true; + this._proxy.$dispose(this._id); + } + } + + private _checkDisposed() { + if (this._disposed) { + throw new Error('Terminal has already been disposed'); + } } public get name(): string { @@ -194,17 +157,17 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi public sendText(text: string, addNewLine: boolean = true): void { this._checkDisposed(); - this._queueApiRequest(this._proxy.$sendText, [text, addNewLine]); + this._proxy.$sendText(this._id, text, addNewLine); } public show(preserveFocus: boolean): void { this._checkDisposed(); - this._queueApiRequest(this._proxy.$show, [preserveFocus]); + this._proxy.$show(this._id, preserveFocus); } public hide(): void { this._checkDisposed(); - this._queueApiRequest(this._proxy.$hide, []); + this._proxy.$hide(this._id); } public _setProcessId(processId: number | undefined): void { @@ -223,20 +186,6 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi } } -class ApiRequest { - private _callback: (...args: any[]) => void; - private _args: any[]; - - constructor(callback: (...args: any[]) => void, args: any[]) { - this._callback = callback; - this._args = args; - } - - public run(proxy: MainThreadTerminalServiceShape, id: number) { - this._callback.apply(proxy, [id].concat(this._args)); - } -} - export class ExtHostPseudoterminal implements ITerminalChildProcess { private readonly _onProcessData = new Emitter(); public readonly onProcessData: Event = this._onProcessData.event; @@ -271,6 +220,11 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess { } } + acknowledgeDataEvent(charCount: number): void { + // No-op, flow control is not supported in extension owned terminals. If this is ever + // implemented it will need new pause and resume VS Code APIs. + } + getInitialCwd(): Promise { return Promise.resolve(''); } @@ -296,6 +250,11 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess { } this._pty.open(initialDimensions ? initialDimensions : undefined); + + if (this._pty.setDimensions && initialDimensions) { + this._pty.setDimensions(initialDimensions); + } + this._onProcessReady.fire({ pid: -1, cwd: '' }); } } @@ -370,7 +329,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I public abstract $acceptWorkspacePermissionsChanged(isAllowed: boolean): void; public createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal { - const terminal = new ExtHostTerminal(this._proxy, options, options.name); + const terminal = new ExtHostTerminal(this._proxy, generateUuid(), options, options.name); const p = new ExtHostPseudoterminal(options.pty); terminal.createExtensionTerminal().then(id => { const disposable = this._setupExtHostProcessListeners(id, p); @@ -381,7 +340,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I } public attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): void { - const terminal = this._getTerminalByIdEventually(id); + const terminal = this._getTerminalById(id); if (!terminal) { throw new Error(`Cannot resolve terminal with id ${id} for virtual process`); } @@ -399,7 +358,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I } return; } - const terminal = await this._getTerminalByIdEventually(id); + const terminal = this._getTerminalById(id); if (terminal) { this._activeTerminal = terminal; if (original !== this._activeTerminal) { @@ -409,14 +368,14 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I } public async $acceptTerminalProcessData(id: number, data: string): Promise { - const terminal = await this._getTerminalByIdEventually(id); + const terminal = this._getTerminalById(id); if (terminal) { this._onDidWriteTerminalData.fire({ terminal, data }); } } public async $acceptTerminalDimensions(id: number, cols: number, rows: number): Promise { - const terminal = await this._getTerminalByIdEventually(id); + const terminal = this._getTerminalById(id); if (terminal) { if (terminal.setDimensions(cols, rows)) { this._onDidChangeTerminalDimensions.fire({ @@ -428,23 +387,19 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I } public async $acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): Promise { - await this._getTerminalByIdEventually(id); - // Extension pty terminal only - when virtual process resize fires it means that the // terminal's maximum dimensions changed this._terminalProcesses.get(id)?.resize(cols, rows); } public async $acceptTerminalTitleChange(id: number, name: string): Promise { - await this._getTerminalByIdEventually(id); - const extHostTerminal = this._getTerminalObjectById(this.terminals, id); - if (extHostTerminal) { - extHostTerminal.name = name; + const terminal = this._getTerminalById(id); + if (terminal) { + terminal.name = name; } } public async $acceptTerminalClosed(id: number, exitCode: number | undefined): Promise { - await this._getTerminalByIdEventually(id); const index = this._getTerminalObjectIndexById(this.terminals, id); if (index !== null) { const terminal = this._terminals.splice(index, 1)[0]; @@ -453,13 +408,17 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I } } - public $acceptTerminalOpened(id: number, name: string, shellLaunchConfigDto: IShellLaunchConfigDto): void { - const index = this._getTerminalObjectIndexById(this._terminals, id); - if (index !== null) { - // The terminal has already been created (via createTerminal*), only fire the event - this._onDidOpenTerminal.fire(this.terminals[index]); - this.terminals[index].isOpen = true; - return; + public $acceptTerminalOpened(id: number, extHostTerminalId: string | undefined, name: string, shellLaunchConfigDto: IShellLaunchConfigDto): void { + if (extHostTerminalId) { + // Resolve with the renderer generated id + const index = this._getTerminalObjectIndexById(this._terminals, extHostTerminalId); + if (index !== null) { + // The terminal has already been created (via createTerminal*), only fire the event + this.terminals[index]._id = id; + this._onDidOpenTerminal.fire(this.terminals[index]); + this.terminals[index].isOpen = true; + return; + } } const creationOptions: vscode.TerminalOptions = { @@ -470,14 +429,14 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I env: shellLaunchConfigDto.env, hideFromUser: shellLaunchConfigDto.hideFromUser }; - const terminal = new ExtHostTerminal(this._proxy, creationOptions, name, id); + const terminal = new ExtHostTerminal(this._proxy, id, creationOptions, name); this._terminals.push(terminal); this._onDidOpenTerminal.fire(terminal); terminal.isOpen = true; } public async $acceptTerminalProcessId(id: number, processId: number): Promise { - const terminal = await this._getTerminalByIdEventually(id); + const terminal = this._getTerminalById(id); if (terminal) { terminal._setProcessId(processId); } @@ -486,7 +445,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I public async $startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise { // Make sure the ExtHostTerminal exists so onDidOpenTerminal has fired before we call // Pseudoterminal.start - const terminal = await this._getTerminalByIdEventually(id); + const terminal = this._getTerminalById(id); if (!terminal) { return { message: localize('launchFail.idMissingOnExtHost', "Could not find the terminal with id {0} on the extension host", id) }; } @@ -539,6 +498,10 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I return disposables; } + public $acceptProcessAckDataEvent(id: number, charCount: number): void { + this._terminalProcesses.get(id)?.acknowledgeDataEvent(charCount); + } + public $acceptProcessInput(id: number, data: string): void { this._terminalProcesses.get(id)?.input(data); } @@ -670,32 +633,6 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I this._proxy.$sendProcessExit(id, exitCode); } - // TODO: This could be improved by using a single promise and resolve it when the terminal is ready - private _getTerminalByIdEventually(id: number, retries: number = 5): Promise { - if (!this._getTerminalPromises[id]) { - this._getTerminalPromises[id] = this._createGetTerminalPromise(id, retries); - } - return this._getTerminalPromises[id]; - } - - private _createGetTerminalPromise(id: number, retries: number = 5): Promise { - return new Promise(c => { - if (retries === 0) { - c(undefined); - return; - } - - const terminal = this._getTerminalById(id); - if (terminal) { - c(terminal); - } else { - // This should only be needed immediately after createTerminalRenderer is called as - // the ExtHostTerminal has not yet been iniitalized - timeout(EXT_HOST_CREATION_DELAY * 2).then(() => c(this._createGetTerminalPromise(id, retries - 1))); - } - }); - } - private _getTerminalById(id: number): ExtHostTerminal | null { return this._getTerminalObjectById(this._terminals, id); } @@ -705,7 +642,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I return index !== null ? array[index] : null; } - private _getTerminalObjectIndexById(array: T[], id: number): number | null { + private _getTerminalObjectIndexById(array: T[], id: TerminalIdentifier): number | null { let index: number | null = null; array.some((item, i) => { const thisId = item._id; diff --git a/src/vs/workbench/api/common/extHostTesting.ts b/src/vs/workbench/api/common/extHostTesting.ts index 02fc32cbf..fb3084566 100644 --- a/src/vs/workbench/api/common/extHostTesting.ts +++ b/src/vs/workbench/api/common/extHostTesting.ts @@ -6,7 +6,6 @@ import { mapFind } from 'vs/base/common/arrays'; import { disposableTimeout } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { throttle } from 'vs/base/common/decorators'; import { Emitter } from 'vs/base/common/event'; import { once } from 'vs/base/common/functional'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; @@ -14,25 +13,35 @@ import { isDefined } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { ExtHostTestingResource, ExtHostTestingShape, MainContext, MainThreadTestingShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { TestItem } from 'vs/workbench/api/common/extHostTypeConverters'; -import { Disposable, RequiredTestItem } from 'vs/workbench/api/common/extHostTypes'; +import { Disposable } from 'vs/workbench/api/common/extHostTypes'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; -import { AbstractIncrementalTestCollection, EMPTY_TEST_RESULT, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, RunTestForProviderRequest, RunTestsResult, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; +import { OwnedTestCollection, SingleUseTestCollection } from 'vs/workbench/contrib/testing/common/ownedTestCollection'; +import { AbstractIncrementalTestCollection, EMPTY_TEST_RESULT, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, InternalTestItemWithChildren, InternalTestResults, RunTestForProviderRequest, RunTestsResult, TestDiffOpType, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; import type * as vscode from 'vscode'; const getTestSubscriptionKey = (resource: ExtHostTestingResource, uri: URI) => `${resource}:${uri.toString()}`; export class ExtHostTesting implements ExtHostTestingShape { + private readonly resultsChangedEmitter = new Emitter(); private readonly providers = new Map(); private readonly proxy: MainThreadTestingShape; private readonly ownedTests = new OwnedTestCollection(); - private readonly testSubscriptions = new Map(); + private readonly testSubscriptions = new Map void; + }>(); private workspaceObservers: WorkspaceFolderTestObserverFactory; private textDocumentObservers: TextDocumentTestObserverFactory; + public onLastResultsChanged = this.resultsChangedEmitter.event; + public lastResults?: vscode.TestResults; + constructor(@IExtHostRpcService rpc: IExtHostRpcService, @IExtHostDocumentsAndEditors private readonly documents: IExtHostDocumentsAndEditors, @IExtHostWorkspace private readonly workspace: IExtHostWorkspace) { this.proxy = rpc.getProxy(MainContext.MainThreadTesting); this.workspaceObservers = new WorkspaceFolderTestObserverFactory(this.proxy); @@ -47,6 +56,14 @@ export class ExtHostTesting implements ExtHostTestingShape { this.providers.set(providerId, provider); this.proxy.$registerTestProvider(providerId); + // give the ext a moment to register things rather than synchronously invoking within activate() + const toSubscribe = [...this.testSubscriptions.keys()]; + setTimeout(() => { + for (const subscription of toSubscribe) { + this.testSubscriptions.get(subscription)?.subscribeFn(providerId, provider); + } + }, 0); + return new Disposable(() => { this.providers.delete(providerId); this.proxy.$unregisterTestProvider(providerId); @@ -70,7 +87,7 @@ export class ExtHostTesting implements ExtHostTestingShape { /** * Implements vscode.test.runTests */ - public async runTests(req: vscode.TestRunOptions) { + public async runTests(req: vscode.TestRunOptions, token = CancellationToken.None) { await this.proxy.$runTests({ tests: req.tests // Find workspace items first, then owned tests, then document tests. @@ -82,14 +99,27 @@ export class ExtHostTesting implements ExtHostTestingShape { .filter(isDefined) .map(item => ({ providerId: item.providerId, testId: item.id })), debug: req.debug - }); + }, token); + } + + + /** + * Updates test results shown to extensions. + * @override + */ + public $publishTestResults(results: InternalTestResults): void { + const convert = (item: InternalTestItemWithChildren): vscode.RequiredTestItem => + ({ ...TestItem.toShallow(item.item), children: item.children.map(convert) }); + + this.lastResults = { tests: results.tests.map(convert) }; + this.resultsChangedEmitter.fire(); } /** * Handles a request to read tests for a file, or workspace. * @override */ - public $subscribeToTests(resource: ExtHostTestingResource, uriComponents: UriComponents) { + public async $subscribeToTests(resource: ExtHostTestingResource, uriComponents: UriComponents) { const uri = URI.revive(uriComponents); const subscriptionKey = getTestSubscriptionKey(resource, uri); if (this.testSubscriptions.has(subscriptionKey)) { @@ -98,12 +128,29 @@ export class ExtHostTesting implements ExtHostTestingShape { let method: undefined | ((p: vscode.TestProvider) => vscode.TestHierarchy | undefined); if (resource === ExtHostTestingResource.TextDocument) { - const document = this.documents.getDocument(uri); + let document = this.documents.getDocument(uri); + + // we can ask to subscribe to tests before the documents are populated in + // the extension host. Try to wait. + if (!document) { + const store = new DisposableStore(); + document = await new Promise(resolve => { + store.add(disposableTimeout(() => resolve(undefined), 5000)); + store.add(this.documents.onDidAddDocuments(e => { + const data = e.find(data => data.document.uri.toString() === uri.toString()); + if (data) { resolve(data); } + })); + }).finally(() => store.dispose()); + } + if (document) { - method = p => p.createDocumentTestHierarchy?.(document.document); + const folder = await this.workspace.getWorkspaceFolder2(uri, false); + method = p => p.createDocumentTestHierarchy + ? p.createDocumentTestHierarchy(document!.document) + : this.createDefaultDocumentTestHierarchy(p, document!.document, folder); } } else { - const folder = this.workspace.getWorkspaceFolder(uri, false); + const folder = await this.workspace.getWorkspaceFolder2(uri, false); if (folder) { method = p => p.createWorkspaceTestHierarchy?.(folder); } @@ -113,24 +160,34 @@ export class ExtHostTesting implements ExtHostTestingShape { return; } - const disposable = new DisposableStore(); - const collection = disposable.add(this.ownedTests.createForHierarchy(diff => this.proxy.$publishDiff(resource, uriComponents, diff))); - for (const [id, provider] of this.providers) { + const subscribeFn = (id: string, provider: vscode.TestProvider) => { try { - const hierarchy = method(provider); + const hierarchy = method!(provider); if (!hierarchy) { - continue; + return; } + collection.pushDiff([TestDiffOpType.DeltaDiscoverComplete, 1]); disposable.add(hierarchy); collection.addRoot(hierarchy.root, id); + Promise.resolve(hierarchy.discoveredInitialTests).then(() => collection.pushDiff([TestDiffOpType.DeltaDiscoverComplete, -1])); hierarchy.onDidChangeTest(e => collection.onItemChange(e, id)); } catch (e) { console.error(e); } + }; + + const disposable = new DisposableStore(); + const collection = disposable.add(this.ownedTests.createForHierarchy(diff => this.proxy.$publishDiff(resource, uriComponents, diff))); + for (const [id, provider] of this.providers) { + subscribeFn(id, provider); } - this.testSubscriptions.set(subscriptionKey, { store: disposable, collection }); + // note: we don't increment the root count initially -- this is done by the + // main thread, incrementing once per extension host. We just push the + // diff to signal that roots have been discovered. + collection.pushDiff([TestDiffOpType.DeltaRootsComplete, -1]); + this.testSubscriptions.set(subscriptionKey, { store: disposable, collection, subscribeFn }); } /** @@ -162,217 +219,193 @@ export class ExtHostTesting implements ExtHostTestingShape { * providers to be run. * @override */ - public async $runTestsForProvider(req: RunTestForProviderRequest): Promise { + public async $runTestsForProvider(req: RunTestForProviderRequest, cancellation: CancellationToken): Promise { const provider = this.providers.get(req.providerId); if (!provider || !provider.runTests) { return EMPTY_TEST_RESULT; } - const tests = req.ids.map(id => this.ownedTests.getTestById(id)?.actual).filter(isDefined); + const tests = req.ids.map(id => this.ownedTests.getTestById(id)?.actual) + .filter(isDefined) + // Only send the actual TestItem's to the user to run. + .map(t => t instanceof TestItemFilteredWrapper ? t.actual : t); if (!tests.length) { return EMPTY_TEST_RESULT; } - await provider.runTests({ tests, debug: req.debug }, CancellationToken.None); - return EMPTY_TEST_RESULT; - } -} - -const keyMap: { [K in keyof Omit]: null } = { - label: null, - location: null, - state: null, - debuggable: null, - description: null, - runnable: null -}; - -const simpleProps = Object.keys(keyMap) as ReadonlyArray; - -const itemEqualityComparator = (a: vscode.TestItem) => { - const values: unknown[] = []; - for (const prop of simpleProps) { - values.push(a[prop]); - } - - return (b: vscode.TestItem) => { - for (let i = 0; i < simpleProps.length; i++) { - if (values[i] !== b[simpleProps[i]]) { - return false; + try { + await provider.runTests({ tests, debug: req.debug }, cancellation); + for (const { collection } of this.testSubscriptions.values()) { + collection.flushDiff(); // ensure all states are updated } + + return EMPTY_TEST_RESULT; + } catch (e) { + console.error(e); // so it appears to attached debuggers + throw e; + } + } + + public $lookupTest(req: TestIdWithProvider): Promise { + const owned = this.ownedTests.getTestById(req.testId); + if (!owned) { + return Promise.resolve(undefined); } - return true; - }; -}; - -/** - * @private - */ -export interface OwnedCollectionTestItem extends InternalTestItem { - actual: vscode.TestItem; - previousChildren: Set; - previousEquals: (v: vscode.TestItem) => boolean; -} - -/** - * @private - */ -export class OwnedTestCollection { - protected readonly testIdToInternal = new Map(); - - /** - * Gets test information by ID, if it was defined and still exists in this - * extension host. - */ - public getTestById(id: string) { - return this.testIdToInternal.get(id); + const { actual, previousChildren, previousEquals, ...item } = owned; + return Promise.resolve(item); } - /** - * Creates a new test collection for a specific hierarchy for a workspace - * or document observation. - */ - public createForHierarchy(publishDiff: (diff: TestsDiff) => void = () => undefined) { - return new SingleUseTestCollection(this.testIdToInternal, publishDiff); - } -} - -/** - * Maintains tests created and registered for a single set of hierarchies - * for a workspace or document. - * @private - */ -export class SingleUseTestCollection implements IDisposable { - protected readonly testItemToInternal = new Map(); - protected diff: TestsDiff = []; - private disposed = false; - - constructor(private readonly testIdToInternal: Map, private readonly publishDiff: (diff: TestsDiff) => void) { } - - /** - * Adds a new root node to the collection. - */ - public addRoot(item: vscode.TestItem, providerId: string) { - this.addItem(item, providerId, null); - this.throttleSendDiff(); - } - - /** - * Gets test information by its reference, if it was defined and still exists - * in this extension host. - */ - public getTestByReference(item: vscode.TestItem) { - return this.testItemToInternal.get(item); - } - - /** - * Should be called when an item change is fired on the test provider. - */ - public onItemChange(item: vscode.TestItem, providerId: string) { - const existing = this.testItemToInternal.get(item); - if (!existing) { - if (!this.disposed) { - console.warn(`Received a TestProvider.onDidChangeTest for a test that wasn't seen before as a child.`); - } + private createDefaultDocumentTestHierarchy(provider: vscode.TestProvider, document: vscode.TextDocument, folder: vscode.WorkspaceFolder | undefined): vscode.TestHierarchy | undefined { + if (!folder) { return; } - this.addItem(item, providerId, existing.parent); - this.throttleSendDiff(); - } - - /** - * Gets a diff of all changes that have been made, and clears the diff queue. - */ - public collectDiff() { - const diff = this.diff; - this.diff = []; - return diff; - } - - public dispose() { - for (const item of this.testItemToInternal.values()) { - this.testIdToInternal.delete(item.id); + const workspaceHierarchy = provider.createWorkspaceTestHierarchy?.(folder); + if (!workspaceHierarchy) { + return; } - this.testIdToInternal.clear(); - this.diff = []; - this.disposed = true; - } + const onDidChangeTest = new Emitter(); + workspaceHierarchy.onDidChangeTest(node => { + const wrapper = TestItemFilteredWrapper.getWrapperForTestItem(node, document); + const previouslySeen = wrapper.hasNodeMatchingFilter; - protected getId(): string { - return generateUuid(); - } + if (previouslySeen) { + // reset cache and get whether you can currently see the TestItem. + wrapper.reset(); + const currentlySeen = wrapper.hasNodeMatchingFilter; - private addItem(actual: vscode.TestItem, providerId: string, parent: string | null) { - let internal = this.testItemToInternal.get(actual); - if (!internal) { - internal = { - actual, - id: this.getId(), - parent, - item: TestItem.from(actual), - providerId, - previousChildren: new Set(), - previousEquals: itemEqualityComparator(actual), - }; + if (currentlySeen) { + onDidChangeTest.fire(wrapper); + return; + } - this.testItemToInternal.set(actual, internal); - this.testIdToInternal.set(internal.id, internal); - this.diff.push([TestDiffOpType.Add, { id: internal.id, parent, providerId, item: internal.item }]); - } else if (!internal.previousEquals(actual)) { - internal.item = TestItem.from(actual); - internal.previousEquals = itemEqualityComparator(actual); - this.diff.push([TestDiffOpType.Update, { id: internal.id, parent, providerId, item: internal.item }]); - } - - // If there are children, track which ones are deleted - // and recursively and/update them. - if (actual.children) { - const deletedChildren = internal.previousChildren; - const currentChildren = new Set(); - for (const child of actual.children) { - const c = this.addItem(child, providerId, internal.id); - deletedChildren.delete(c.id); - currentChildren.add(c.id); + // Fire the event to say that the current visible parent has changed. + onDidChangeTest.fire(wrapper.visibleParent); + return; } - for (const child of deletedChildren) { - this.removeItembyId(child); + const previousParent = wrapper.visibleParent; + wrapper.reset(); + const currentlySeen = wrapper.hasNodeMatchingFilter; + + // It wasn't previously seen and isn't currently seen so + // nothing has actually changed. + if (!currentlySeen) { + return; } - internal.previousChildren = currentChildren; - } + // The test is now visible so we need to refresh the cache + // of the previous visible parent and fire that it has changed. + previousParent.reset(); + onDidChangeTest.fire(previousParent); + }); + return { + root: TestItemFilteredWrapper.getWrapperForTestItem(workspaceHierarchy.root, document), + dispose: () => { + onDidChangeTest.dispose(); + TestItemFilteredWrapper.removeFilter(document); + }, + onDidChangeTest: onDidChangeTest.event + }; + } +} - return internal; +/* + * A class which wraps a vscode.TestItem that provides the ability to filter a TestItem's children + * to only the children that are located in a certain vscode.Uri. + */ +export class TestItemFilteredWrapper implements vscode.TestItem { + private static wrapperMap = new WeakMap>(); + public static removeFilter(document: vscode.TextDocument): void { + this.wrapperMap.delete(document); } - private removeItembyId(id: string) { - this.diff.push([TestDiffOpType.Remove, id]); - - const queue = [this.testIdToInternal.get(id)]; - while (queue.length) { - const item = queue.pop(); - if (!item) { - continue; - } - - this.testIdToInternal.delete(item.id); - this.testItemToInternal.delete(item.actual); - for (const child of item.previousChildren) { - queue.push(this.testIdToInternal.get(child)); - } + // Wraps the TestItem specified in a TestItemFilteredWrapper and pulls from a cache if it already exists. + public static getWrapperForTestItem(item: vscode.TestItem, filterDocument: vscode.TextDocument, parent?: TestItemFilteredWrapper): TestItemFilteredWrapper { + let innerMap = this.wrapperMap.get(filterDocument); + if (innerMap?.has(item)) { + return innerMap.get(item)!; } + + if (!innerMap) { + innerMap = new WeakMap(); + this.wrapperMap.set(filterDocument, innerMap); + + } + + const w = new TestItemFilteredWrapper(item, filterDocument, parent); + innerMap.set(item, w); + return w; } - @throttle(200) - protected throttleSendDiff() { - const diff = this.collectDiff(); - if (diff.length) { - this.publishDiff(diff); + public get label() { + return this.actual.label; + } + + public get debuggable() { + return this.actual.debuggable; + } + + public get description() { + return this.actual.description; + } + + public get location() { + return this.actual.location; + } + + public get runnable() { + return this.actual.runnable; + } + + public get state() { + return this.actual.state; + } + + public get children() { + // We only want children that match the filter. + return this.getWrappedChildren().filter(child => child.hasNodeMatchingFilter); + } + + public get visibleParent(): TestItemFilteredWrapper { + return this.hasNodeMatchingFilter ? this : this.parent!.visibleParent; + } + + private matchesFilter: boolean | undefined; + + // Determines if the TestItem matches the filter. This would be true if: + // 1. We don't have a parent (because the root is the workspace root node) + // 2. The URI of the current node matches the filter URI + // 3. Some child of the current node matches the filter URI + public get hasNodeMatchingFilter(): boolean { + if (this.matchesFilter === undefined) { + this.matchesFilter = !this.parent + || this.actual.location?.uri.toString() === this.filterDocument.uri.toString() + || this.getWrappedChildren().some(child => child.hasNodeMatchingFilter); } + + return this.matchesFilter; + } + + // Reset the cache of whether or not you can see a node from a particular node + // up to it's visible parent. + public reset(): void { + if (this !== this.visibleParent) { + this.parent?.reset(); + } + this.matchesFilter = undefined; + } + + + private constructor(public readonly actual: vscode.TestItem, private filterDocument: vscode.TextDocument, private readonly parent?: TestItemFilteredWrapper) { + this.getWrappedChildren(); + } + + private getWrappedChildren() { + return this.actual.children?.map(t => TestItemFilteredWrapper.getWrapperForTestItem(t, this.filterDocument, this)) || []; } } @@ -382,7 +415,7 @@ export class SingleUseTestCollection implements IDisposable { interface MirroredCollectionTestItem extends IncrementalTestCollectionItem { revived: vscode.TestItem; depth: number; - wrapped?: vscode.TestItem; + wrapped?: vscode.RequiredTestItem; } class MirroredChangeCollector extends IncrementalChangeCollector { @@ -411,7 +444,7 @@ class MirroredChangeCollector extends IncrementalChangeCollector): vscode.TestItem[] { - let output: vscode.TestItem[] = []; + public getAllAsTestItem(itemIds: Iterable): vscode.RequiredTestItem[] { + let output: vscode.RequiredTestItem[] = []; for (const itemId of itemIds) { const item = this.items.get(itemId); if (item) { @@ -577,7 +610,7 @@ export class MirroredTestCollection extends AbstractIncrementalTestCollection { const MirroredItemId = Symbol('MirroredItemId'); -class ExtHostTestItem implements vscode.TestItem, RequiredTestItem { +class TestItemFromMirror implements vscode.RequiredTestItem { readonly #internal: MirroredCollectionTestItem; readonly #collection: MirroredTestCollection; + public get id() { return this.#internal.revived.id!; } public get label() { return this.#internal.revived.label; } public get description() { return this.#internal.revived.description; } public get state() { return this.#internal.revived.state; } @@ -627,14 +661,18 @@ class ExtHostTestItem implements vscode.TestItem, RequiredTestItem { } public toJSON() { - const serialized: RequiredTestItem = { + const serialized: vscode.RequiredTestItem & TestIdWithProvider = { + id: this.id, label: this.label, description: this.description, state: this.state, location: this.location, runnable: this.runnable, debuggable: this.debuggable, - children: this.children.map(c => (c as ExtHostTestItem).toJSON()), + children: this.children.map(c => (c as TestItemFromMirror).toJSON()), + + providerId: this.#internal.providerId, + testId: this.#internal.id, }; return serialized; @@ -655,6 +693,7 @@ abstract class AbstractTestObserverFactory { const resourceKey = resourceUri.toString(); const resource = this.resources.get(resourceKey) ?? this.createObserverData(resourceUri); + resource.pendingDeletion?.dispose(); resource.observers++; return { @@ -778,16 +817,9 @@ class TextDocumentTestObserverFactory extends AbstractTestObserverFactory { const uriString = resourceUri.toString(); this.diffListeners.set(uriString, onDiff); - const disposeListener = this.documents.onDidRemoveDocuments(evt => { - if (evt.some(delta => delta.document.uri.toString() === uriString)) { - this.unlisten(resourceUri); - } - }); - this.proxy.$subscribeToDiffs(ExtHostTestingResource.TextDocument, resourceUri); return new Disposable(() => { this.proxy.$unsubscribeFromDiffs(ExtHostTestingResource.TextDocument, resourceUri); - disposeListener.dispose(); this.diffListeners.delete(uriString); }); } diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index 63d307018..ca1914d2e 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -21,6 +21,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import { MarkdownString } from 'vs/workbench/api/common/extHostTypeConverters'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Command } from 'vs/editor/common/modes'; type TreeItemHandle = string; @@ -132,12 +133,12 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { return treeView.hasResolve; } - $resolve(treeViewId: string, treeItemHandle: string): Promise { + $resolve(treeViewId: string, treeItemHandle: string, token: vscode.CancellationToken): Promise { const treeView = this.treeViews.get(treeViewId); if (!treeView) { throw new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId)); } - return treeView.resolveTreeItem(treeItemHandle); + return treeView.resolveTreeItem(treeItemHandle, token); } $setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void { @@ -184,6 +185,7 @@ interface TreeNode extends IDisposable { extensionItem: vscode.TreeItem; parent: TreeNode | Root; children?: TreeNode[]; + disposableStore: DisposableStore; } class ExtHostTreeView extends Disposable { @@ -370,7 +372,7 @@ class ExtHostTreeView extends Disposable { return !!this.dataProvider.resolveTreeItem; } - async resolveTreeItem(treeItemHandle: string): Promise { + async resolveTreeItem(treeItemHandle: string, token: vscode.CancellationToken): Promise { if (!this.dataProvider.resolveTreeItem) { return; } @@ -378,9 +380,10 @@ class ExtHostTreeView extends Disposable { if (element) { const node = this.nodes.get(element); if (node) { - const resolve = await this.dataProvider.resolveTreeItem(node.extensionItem, element) ?? node.extensionItem; - // Resolvable elements. Currently only tooltip. + const resolve = await this.dataProvider.resolveTreeItem(node.extensionItem, element, token) ?? node.extensionItem; + // Resolvable elements. Currently only tooltip and command. node.item.tooltip = this.getTooltip(resolve.tooltip); + node.item.command = this.getCommand(node.disposableStore, resolve.command); return node.item; } } @@ -573,8 +576,12 @@ class ExtHostTreeView extends Disposable { return tooltip; } + private getCommand(disposable: DisposableStore, command?: vscode.Command): Command | undefined { + return command ? this.commands.toInternal(command, disposable) : undefined; + } + private createTreeNode(element: T, extensionTreeItem: vscode.TreeItem, parent: TreeNode | Root): TreeNode { - const disposable = new DisposableStore(); + const disposableStore = new DisposableStore(); const handle = this.createHandle(element, extensionTreeItem, parent); const icon = this.getLightIconPath(extensionTreeItem); const item: ITreeItem = { @@ -584,7 +591,7 @@ class ExtHostTreeView extends Disposable { description: extensionTreeItem.description, resourceUri: extensionTreeItem.resourceUri, tooltip: this.getTooltip(extensionTreeItem.tooltip), - command: extensionTreeItem.command ? this.commands.toInternal(extensionTreeItem.command, disposable) : undefined, + command: this.getCommand(disposableStore, extensionTreeItem.command), contextValue: extensionTreeItem.contextValue, icon, iconDark: this.getDarkIconPath(extensionTreeItem) || icon, @@ -598,7 +605,8 @@ class ExtHostTreeView extends Disposable { extensionItem: extensionTreeItem, parent, children: undefined, - dispose(): void { disposable.dispose(); } + disposableStore, + dispose(): void { disposableStore.dispose(); } }; } diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index 29a50ae27..721cbaba4 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtHostTunnelServiceShape, MainContext, MainThreadTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import * as vscode from 'vscode'; import { RemoteTunnel, TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel'; @@ -11,18 +11,27 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService'; export interface TunnelDto { remoteAddress: { port: number, host: string }; localAddress: { port: number, host: string } | string; + public: boolean; } export namespace TunnelDto { export function fromApiTunnel(tunnel: vscode.Tunnel): TunnelDto { - return { remoteAddress: tunnel.remoteAddress, localAddress: tunnel.localAddress }; + return { remoteAddress: tunnel.remoteAddress, localAddress: tunnel.localAddress, public: !!tunnel.public }; } export function fromServiceTunnel(tunnel: RemoteTunnel): TunnelDto { - return { remoteAddress: { host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }, localAddress: tunnel.localAddress }; + return { + remoteAddress: { + host: tunnel.tunnelRemoteHost, + port: tunnel.tunnelRemotePort + }, + localAddress: tunnel.localAddress, + public: tunnel.public + }; } } @@ -44,12 +53,13 @@ export const IExtHostTunnelService = createDecorator('IEx export class ExtHostTunnelService implements IExtHostTunnelService { declare readonly _serviceBrand: undefined; onDidChangeTunnels: vscode.Event = (new Emitter()).event; - private readonly _proxy: MainThreadTunnelServiceShape; constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, ) { - this._proxy = extHostRpc.getProxy(MainContext.MainThreadTunnelService); + } + async $applyCandidateFilter(candidates: CandidatePort[]): Promise { + return candidates; } async openTunnel(extension: IExtensionDescription, forward: TunnelOptions): Promise { @@ -59,10 +69,10 @@ export class ExtHostTunnelService implements IExtHostTunnelService { return []; } async setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise { - await this._proxy.$tunnelServiceReady(); return { dispose: () => { } }; } - $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise | undefined { return undefined; } + async $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise { return undefined; } async $closeTunnel(remote: { host: string, port: number }): Promise { } async $onDidTunnelsChange(): Promise { } + async $registerCandidateFinder(): Promise { } } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index d80dfae4b..d1512e59b 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -1015,6 +1015,29 @@ export namespace SignatureHelp { } } +export namespace InlineHint { + + export function from(hint: vscode.InlineHint): modes.InlineHint { + return { + text: hint.text, + range: Range.from(hint.range), + description: hint.description && MarkdownString.fromStrict(hint.description), + whitespaceBefore: hint.whitespaceBefore, + whitespaceAfter: hint.whitespaceAfter + }; + } + + export function to(hint: modes.InlineHint): vscode.InlineHint { + return new types.InlineHint( + hint.text, + Range.to(hint.range), + htmlContent.isMarkdownString(hint.description) ? MarkdownString.to(hint.description) : hint.description, + hint.whitespaceBefore, + hint.whitespaceAfter + ); + } +} + export namespace DocumentLink { export function from(link: vscode.DocumentLink): modes.ILink { @@ -1389,19 +1412,21 @@ export namespace TestState { export namespace TestItem { - export function from(item: vscode.TestItem): ITestItem { + export function from(item: vscode.TestItem, parentExtId?: string): ITestItem { return { + extId: item.id ?? (parentExtId ? `${parentExtId}\0${item.label}` : item.label), label: item.label, location: item.location ? location.from(item.location) : undefined, - debuggable: item.debuggable, + debuggable: item.debuggable ?? false, description: item.description, - runnable: item.runnable, + runnable: item.runnable ?? true, state: TestState.from(item.state), }; } - export function to(item: ITestItem): vscode.TestItem { + export function toShallow(item: ITestItem): Omit { return { + id: item.extId, label: item.label, location: item.location && location.to({ range: item.location.range, diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 069c56583..5d1bb34cb 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files'; import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { addIdToOutput, CellEditType, ICellEditOperation, IDisplayOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { addIdToOutput, CellEditType, ICellEditOperation, IDisplayOutput, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import type * as vscode from 'vscode'; function es5ClassCompat(target: Function): any { @@ -638,7 +638,7 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { // --- notebook replaceNotebookMetadata(uri: URI, value: vscode.NotebookDocumentMetadata, metadata?: vscode.WorkspaceEditEntryMetadata): void { - this._edits.push({ _type: FileEditType.Cell, metadata, uri, notebookMetadata: value }); + this._edits.push({ _type: FileEditType.Cell, metadata, uri, edit: { editType: CellEditType.DocumentMetadata, metadata: { ...notebookDocumentMetadataDefaults, ...value } }, notebookMetadata: value }); } replaceNotebookCells(uri: URI, start: number, end: number, cells: vscode.NotebookCellData[], metadata?: vscode.WorkspaceEditEntryMetadata): void { @@ -1373,6 +1373,23 @@ export enum SignatureHelpTriggerKind { ContentChange = 3, } +@es5ClassCompat +export class InlineHint { + text: string; + range: Range; + description?: string | vscode.MarkdownString; + whitespaceBefore?: boolean; + whitespaceAfter?: boolean; + + constructor(text: string, range: Range, description?: string | vscode.MarkdownString, whitespaceBefore?: boolean, whitespaceAfter?: boolean) { + this.text = text; + this.range = range; + this.description = description; + this.whitespaceBefore = whitespaceBefore; + this.whitespaceAfter = whitespaceAfter; + } +} + export enum CompletionTriggerKind { Invoke = 0, TriggerCharacter = 1, @@ -2298,9 +2315,6 @@ export class FunctionBreakpoint extends Breakpoint { constructor(functionName: string, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string) { super(enabled, condition, hitCondition, logMessage); - if (!functionName) { - throw illegalArgument('functionName'); - } this.functionName = functionName; } } @@ -2858,7 +2872,8 @@ export enum NotebookCellStatusBarAlignment { export enum NotebookEditorRevealType { Default = 0, InCenter = 1, - InCenterIfOutsideViewport = 2 + InCenterIfOutsideViewport = 2, + AtTop = 3 } @@ -2924,11 +2939,12 @@ export class LinkedEditingRanges { //#region Testing export enum TestRunState { Unset = 0, - Running = 1, - Passed = 2, - Failed = 3, - Skipped = 4, - Errored = 5 + Queued = 1, + Running = 2, + Passed = 3, + Failed = 4, + Skipped = 5, + Errored = 6 } export enum TestMessageSeverity { @@ -2963,15 +2979,15 @@ export class TestState { } } -type AllowedUndefined = 'description' | 'location'; - -/** - * Test item without any optional properties. Only some properties are - * permitted to be undefined, but they must still exist. - */ -export type RequiredTestItem = { - [K in keyof Required]: K extends AllowedUndefined ? vscode.TestItem[K] : Required[K] -}; +export type RequiredTestItem = vscode.RequiredTestItem; +export type TestItem = vscode.TestItem; //#endregion + +export enum ExternalUriOpenerPriority { + None = 0, + Option = 1, + Default = 2, + Preferred = 3, +} diff --git a/src/vs/workbench/api/common/extHostUriOpener.ts b/src/vs/workbench/api/common/extHostUriOpener.ts new file mode 100644 index 000000000..81bbc6f4d --- /dev/null +++ b/src/vs/workbench/api/common/extHostUriOpener.ts @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { toDisposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import * as modes from 'vs/editor/common/modes'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import type * as vscode from 'vscode'; +import { ExtHostUriOpenersShape, IMainContext, MainContext, MainThreadUriOpenersShape } from './extHost.protocol'; + + +export class ExtHostUriOpeners implements ExtHostUriOpenersShape { + + private static readonly supportedSchemes = new Set([Schemas.http, Schemas.https]); + + private readonly _proxy: MainThreadUriOpenersShape; + + private readonly _openers = new Map(); + + constructor( + mainContext: IMainContext, + ) { + this._proxy = mainContext.getProxy(MainContext.MainThreadUriOpeners); + } + + registerExternalUriOpener( + extensionId: ExtensionIdentifier, + id: string, + opener: vscode.ExternalUriOpener, + metadata: vscode.ExternalUriOpenerMetadata, + ): vscode.Disposable { + if (this._openers.has(id)) { + throw new Error(`Opener with id '${id}' already registered`); + } + + const invalidScheme = metadata.schemes.find(scheme => !ExtHostUriOpeners.supportedSchemes.has(scheme)); + if (invalidScheme) { + throw new Error(`Scheme '${invalidScheme}' is not supported. Only http and https are currently supported.`); + } + + this._openers.set(id, opener); + this._proxy.$registerUriOpener(id, metadata.schemes, extensionId, metadata.label); + + return toDisposable(() => { + this._openers.delete(id); + this._proxy.$unregisterUriOpener(id); + }); + } + + async $canOpenUri(id: string, uriComponents: UriComponents, token: CancellationToken): Promise { + const opener = this._openers.get(id); + if (!opener) { + throw new Error(`Unknown opener with id: ${id}`); + } + + const uri = URI.revive(uriComponents); + return opener.canOpenExternalUri(uri, token); + } + + async $openUri(id: string, context: { resolvedUri: UriComponents, sourceUri: UriComponents }, token: CancellationToken): Promise { + const opener = this._openers.get(id); + if (!opener) { + throw new Error(`Unknown opener id: '${id}'`); + } + + return opener.openExternalUri(URI.revive(context.resolvedUri), { + sourceUri: URI.revive(context.sourceUri) + }, token); + } +} diff --git a/src/vs/workbench/api/common/extHostWebviewPanels.ts b/src/vs/workbench/api/common/extHostWebviewPanels.ts index 797702b88..dfdd948be 100644 --- a/src/vs/workbench/api/common/extHostWebviewPanels.ts +++ b/src/vs/workbench/api/common/extHostWebviewPanels.ts @@ -287,8 +287,8 @@ export class ExtHostWebviewPanels implements extHostProtocol.ExtHostWebviewPanel await serializer.deserializeWebviewPanel(revivedPanel, state); } - public createNewWebviewPanel(webviewHandle: string, viewType: string, title: string, position: number, options: modes.IWebviewOptions & modes.IWebviewPanelOptions, webview: ExtHostWebview) { - const panel = new ExtHostWebviewPanel(webviewHandle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview); + public createNewWebviewPanel(webviewHandle: string, viewType: string, title: string, position: vscode.ViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions, webview: ExtHostWebview) { + const panel = new ExtHostWebviewPanel(webviewHandle, this._proxy, viewType, title, position, options, webview); this._webviewPanels.set(webviewHandle, panel); return panel; } diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 3e0de07e3..c03c6be54 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -10,7 +10,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { TernarySearchTree } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; import { Counter } from 'vs/base/common/numbers'; -import { basename, basenameOrAuthority, dirname, isEqual, relativePath } from 'vs/base/common/resources'; +import { basename, basenameOrAuthority, dirname, ExtUri, relativePath } from 'vs/base/common/resources'; import { compare } from 'vs/base/common/strings'; import { withUndefinedAsNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; @@ -37,27 +37,27 @@ export interface IExtHostWorkspaceProvider { resolveProxy(url: string): Promise; } -function isFolderEqual(folderA: URI, folderB: URI): boolean { - return isEqual(folderA, folderB); +function isFolderEqual(folderA: URI, folderB: URI, extHostFileSystemInfo: IExtHostFileSystemInfo): boolean { + return new ExtUri(uri => ignorePathCasing(uri, extHostFileSystemInfo)).isEqual(folderA, folderB); } -function compareWorkspaceFolderByUri(a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder): number { - return isFolderEqual(a.uri, b.uri) ? 0 : compare(a.uri.toString(), b.uri.toString()); +function compareWorkspaceFolderByUri(a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder, extHostFileSystemInfo: IExtHostFileSystemInfo): number { + return isFolderEqual(a.uri, b.uri, extHostFileSystemInfo) ? 0 : compare(a.uri.toString(), b.uri.toString()); } -function compareWorkspaceFolderByUriAndNameAndIndex(a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder): number { +function compareWorkspaceFolderByUriAndNameAndIndex(a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder, extHostFileSystemInfo: IExtHostFileSystemInfo): number { if (a.index !== b.index) { return a.index < b.index ? -1 : 1; } - return isFolderEqual(a.uri, b.uri) ? compare(a.name, b.name) : compare(a.uri.toString(), b.uri.toString()); + return isFolderEqual(a.uri, b.uri, extHostFileSystemInfo) ? compare(a.name, b.name) : compare(a.uri.toString(), b.uri.toString()); } -function delta(oldFolders: vscode.WorkspaceFolder[], newFolders: vscode.WorkspaceFolder[], compare: (a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder) => number): { removed: vscode.WorkspaceFolder[], added: vscode.WorkspaceFolder[] } { - const oldSortedFolders = oldFolders.slice(0).sort(compare); - const newSortedFolders = newFolders.slice(0).sort(compare); +function delta(oldFolders: vscode.WorkspaceFolder[], newFolders: vscode.WorkspaceFolder[], compare: (a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder, extHostFileSystemInfo: IExtHostFileSystemInfo) => number, extHostFileSystemInfo: IExtHostFileSystemInfo): { removed: vscode.WorkspaceFolder[], added: vscode.WorkspaceFolder[] } { + const oldSortedFolders = oldFolders.slice(0).sort((a, b) => compare(a, b, extHostFileSystemInfo)); + const newSortedFolders = newFolders.slice(0).sort((a, b) => compare(a, b, extHostFileSystemInfo)); - return arrayDelta(oldSortedFolders, newSortedFolders, compare); + return arrayDelta(oldSortedFolders, newSortedFolders, (a, b) => compare(a, b, extHostFileSystemInfo)); } function ignorePathCasing(uri: URI, extHostFileSystemInfo: IExtHostFileSystemInfo): boolean { @@ -87,7 +87,7 @@ class ExtHostWorkspaceImpl extends Workspace { if (previousConfirmedWorkspace) { folders.forEach((folderData, index) => { const folderUri = URI.revive(folderData.uri); - const existingFolder = ExtHostWorkspaceImpl._findFolder(previousUnconfirmedWorkspace || previousConfirmedWorkspace, folderUri); + const existingFolder = ExtHostWorkspaceImpl._findFolder(previousUnconfirmedWorkspace || previousConfirmedWorkspace, folderUri, extHostFileSystemInfo); if (existingFolder) { existingFolder.name = folderData.name; @@ -106,15 +106,15 @@ class ExtHostWorkspaceImpl extends Workspace { newWorkspaceFolders.sort((f1, f2) => f1.index < f2.index ? -1 : 1); const workspace = new ExtHostWorkspaceImpl(id, name, newWorkspaceFolders, configuration ? URI.revive(configuration) : null, !!isUntitled, uri => ignorePathCasing(uri, extHostFileSystemInfo)); - const { added, removed } = delta(oldWorkspace ? oldWorkspace.workspaceFolders : [], workspace.workspaceFolders, compareWorkspaceFolderByUri); + const { added, removed } = delta(oldWorkspace ? oldWorkspace.workspaceFolders : [], workspace.workspaceFolders, compareWorkspaceFolderByUri, extHostFileSystemInfo); return { workspace, added, removed }; } - private static _findFolder(workspace: ExtHostWorkspaceImpl, folderUriToFind: URI): MutableWorkspaceFolder | undefined { + private static _findFolder(workspace: ExtHostWorkspaceImpl, folderUriToFind: URI, extHostFileSystemInfo: IExtHostFileSystemInfo): MutableWorkspaceFolder | undefined { for (let i = 0; i < workspace.folders.length; i++) { const folder = workspace.workspaceFolders[i]; - if (isFolderEqual(folder.uri, folderUriToFind)) { + if (isFolderEqual(folder.uri, folderUriToFind, extHostFileSystemInfo)) { return folder; } } @@ -254,7 +254,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac const validatedDistinctWorkspaceFoldersToAdd: { uri: vscode.Uri, name?: string }[] = []; if (Array.isArray(workspaceFoldersToAdd)) { workspaceFoldersToAdd.forEach(folderToAdd => { - if (URI.isUri(folderToAdd.uri) && !validatedDistinctWorkspaceFoldersToAdd.some(f => isFolderEqual(f.uri, folderToAdd.uri))) { + if (URI.isUri(folderToAdd.uri) && !validatedDistinctWorkspaceFoldersToAdd.some(f => isFolderEqual(f.uri, folderToAdd.uri, this._extHostFileSystemInfo))) { validatedDistinctWorkspaceFoldersToAdd.push({ uri: folderToAdd.uri, name: folderToAdd.name || basenameOrAuthority(folderToAdd.uri) }); } }); @@ -283,13 +283,13 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac for (let i = 0; i < newWorkspaceFolders.length; i++) { const folder = newWorkspaceFolders[i]; - if (newWorkspaceFolders.some((otherFolder, index) => index !== i && isFolderEqual(folder.uri, otherFolder.uri))) { + if (newWorkspaceFolders.some((otherFolder, index) => index !== i && isFolderEqual(folder.uri, otherFolder.uri, this._extHostFileSystemInfo))) { return false; // cannot add the same folder multiple times } } newWorkspaceFolders.forEach((f, index) => f.index = index); // fix index - const { added, removed } = delta(currentWorkspaceFolders, newWorkspaceFolders, compareWorkspaceFolderByUriAndNameAndIndex); + const { added, removed } = delta(currentWorkspaceFolders, newWorkspaceFolders, compareWorkspaceFolderByUriAndNameAndIndex, this._extHostFileSystemInfo); if (added.length === 0 && removed.length === 0) { return false; // nothing actually changed } diff --git a/src/vs/workbench/api/common/shared/workspaceContains.ts b/src/vs/workbench/api/common/shared/workspaceContains.ts index 629c9994a..74a283dce 100644 --- a/src/vs/workbench/api/common/shared/workspaceContains.ts +++ b/src/vs/workbench/api/common/shared/workspaceContains.ts @@ -119,8 +119,7 @@ export function checkGlobFileExists( const queryBuilder = instantiationService.createInstance(QueryBuilder); const query = queryBuilder.file(folders.map(folder => toWorkspaceFolder(URI.revive(folder))), { _reason: 'checkExists', - includePattern: includes.join(', '), - expandPatterns: true, + includePattern: includes, exists: true }); diff --git a/src/vs/workbench/api/node/extHostCLIServer.ts b/src/vs/workbench/api/node/extHostCLIServer.ts index bb5ec1cd7..b3424a779 100644 --- a/src/vs/workbench/api/node/extHostCLIServer.ts +++ b/src/vs/workbench/api/node/extHostCLIServer.ts @@ -39,7 +39,15 @@ export interface RunCommandPipeArgs { args: any[]; } -export type PipeCommand = OpenCommandPipeArgs | StatusPipeArgs | RunCommandPipeArgs | OpenExternalCommandPipeArgs; +export interface ExtensionManagementPipeArgs { + type: 'extensionManagement'; + list?: { showVersions?: boolean, category?: string; }; + install?: string[]; + uninstall?: string[]; + force?: boolean; +} + +export type PipeCommand = OpenCommandPipeArgs | StatusPipeArgs | RunCommandPipeArgs | OpenExternalCommandPipeArgs | ExtensionManagementPipeArgs; export interface ICommandsExecuter { executeCommand(id: string, ...args: any[]): Promise; @@ -95,6 +103,10 @@ export class CLIServerBase { this.runCommand(data, res) .catch(this.logService.error); break; + case 'extensionManagement': + this.manageExtensions(data, res) + .catch(this.logService.error); + break; default: res.writeHead(404); res.write(`Unknown message type: ${data.type}`, err => { @@ -143,14 +155,34 @@ export class CLIServerBase { res.end(); } - private openExternal(data: OpenExternalCommandPipeArgs, res: http.ServerResponse) { + private async openExternal(data: OpenExternalCommandPipeArgs, res: http.ServerResponse) { for (const uri of data.uris) { - this._commands.executeCommand('_workbench.openExternal', URI.parse(uri), { allowTunneling: true }); + await this._commands.executeCommand('_remoteCLI.openExternal', URI.parse(uri), { allowTunneling: true }); } res.writeHead(200); res.end(); } + private async manageExtensions(data: ExtensionManagementPipeArgs, res: http.ServerResponse) { + console.log('server: manageExtensions'); + try { + const toExtOrVSIX = (inputs: string[] | undefined) => inputs?.map(input => /\.vsix$/i.test(input) ? URI.parse(input) : input); + const commandArgs = { + list: data.list, + install: toExtOrVSIX(data.install), + uninstall: toExtOrVSIX(data.uninstall), + force: data.force + }; + const output = await this._commands.executeCommand('_remoteCLI.manageExtensions', commandArgs, { allowTunneling: true }); + res.writeHead(200); + res.write(output); + } catch (e) { + res.writeHead(500); + res.write(String(e)); + } + res.end(); + } + private async getStatus(data: StatusPipeArgs, res: http.ServerResponse) { try { const status = await this._commands.executeCommand('_issues.getSystemStatus'); diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index ff76f109b..992c63777 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -104,7 +104,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { cwdForPrepareCommand = args.cwd; } - terminal.show(); + terminal.show(true); const shellProcessId = await terminal.processId; diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 3d92a6991..d9377b68c 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as performance from 'vs/base/common/performance'; import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHost.api.impl'; import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor'; import { MainContext } from 'vs/workbench/api/common/extHost.protocol'; @@ -13,7 +14,7 @@ import { ExtHostDownloadService } from 'vs/workbench/api/node/extHostDownloadSer import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes'; class NodeModuleRequireInterceptor extends RequireInterceptor { @@ -62,10 +63,12 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { // Module loading tricks const interceptor = this._instaService.createInstance(NodeModuleRequireInterceptor, extensionApiFactory, this._registry); await interceptor.install(); + performance.mark('code/extHost/didInitAPI'); // Do this when extension service exists, but extensions are not being activated yet. const configProvider = await this._extHostConfiguration.getConfigProvider(); await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._logService, this._mainThreadTelemetryProxy, this._initData); + performance.mark('code/extHost/didInitProxyResolver'); // Use IPC messages to forward console-calls, note that the console is // already patched to use`process.send()` @@ -84,7 +87,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { return extensionDescription.main; } - protected _loadCommonJSModule(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { + protected _loadCommonJSModule(extensionId: ExtensionIdentifier | null, module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { if (module.scheme !== Schemas.file) { throw new Error(`Cannot load URI: '${module}', must be of file-scheme`); } @@ -93,10 +96,16 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { this._logService.info(`ExtensionService#loadCommonJSModule ${module.toString(true)}`); this._logService.flush(); try { + if (extensionId) { + performance.mark(`code/extHost/willLoadExtensionCode/${extensionId.value}`); + } r = require.__$__nodeRequire(module.fsPath); } catch (e) { return Promise.reject(e); } finally { + if (extensionId) { + performance.mark(`code/extHost/didLoadExtensionCode/${extensionId.value}`); + } activationTimesBuilder.codeLoadingStop(); } return Promise.resolve(r); diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index 9d5951b84..260ace16b 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -50,11 +50,12 @@ export class ExtHostTask extends ExtHostTaskBase { } public async executeTask(extension: IExtensionDescription, task: vscode.Task): Promise { - if (!task.execution) { + const tTask = (task as types.Task); + + if (!task.execution && (tTask._id === undefined)) { throw new Error('Tasks to execute must include an execution'); } - const tTask = (task as types.Task); // We have a preserved ID. So the task didn't change. if (tTask._id !== undefined) { // Always get the task execution first to prevent timing issues when retrieving it later @@ -121,7 +122,7 @@ export class ExtHostTask extends ExtHostTaskBase { private async getVariableResolver(workspaceFolders: vscode.WorkspaceFolder[]): Promise { if (this._variableResolver === undefined) { const configProvider = await this._configurationService.getConfigProvider(); - this._variableResolver = new ExtHostVariableResolverService(workspaceFolders, this._editorService, configProvider, process.env as IProcessEnvironment); + this._variableResolver = new ExtHostVariableResolverService(workspaceFolders, this._editorService, configProvider, process.env as IProcessEnvironment, this.workspaceService); } return this._variableResolver; } diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 01a34ff3f..adad271b3 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -17,13 +17,15 @@ import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/ext import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostDebugService'; import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { getSystemShell, detectAvailableShells } from 'vs/workbench/contrib/terminal/node/terminal'; +import { detectAvailableShells } from 'vs/workbench/contrib/terminal/node/terminal'; import { getMainProcessParentEnv } from 'vs/workbench/contrib/terminal/node/terminalEnvironment'; import { BaseExtHostTerminalService, ExtHostTerminal } from 'vs/workbench/api/common/extHostTerminalService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { getSystemShell, getSystemShellSync } from 'vs/base/node/shell'; +import { generateUuid } from 'vs/base/common/uuid'; export class ExtHostTerminalService extends BaseExtHostTerminalService { @@ -32,6 +34,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { // TODO: Pull this from main side private _isWorkspaceShellAllowed: boolean = false; + private _defaultShell: string | undefined; constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, @@ -42,20 +45,26 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { @IExtHostInitDataService private _extHostInitDataService: IExtHostInitDataService ) { super(true, extHostRpc); + + // Getting the SystemShell is an async operation, however, the ExtHost terminal service is mostly synchronous + // and the API `vscode.env.shell` is also synchronous. The default shell _should_ be set when extensions are + // starting up but if not, we run getSystemShellSync below which gets a sane default. + getSystemShell(platform.platform).then(s => this._defaultShell = s); + this._updateLastActiveWorkspace(); this._updateVariableResolver(); this._registerListeners(); } public createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal { - const terminal = new ExtHostTerminal(this._proxy, { name, shellPath, shellArgs }, name); + const terminal = new ExtHostTerminal(this._proxy, generateUuid(), { name, shellPath, shellArgs }, name); this._terminals.push(terminal); terminal.create(shellPath, shellArgs); return terminal; } public createTerminalFromOptions(options: vscode.TerminalOptions, isFeatureTerminal?: boolean): vscode.Terminal { - const terminal = new ExtHostTerminal(this._proxy, options, options.name); + const terminal = new ExtHostTerminal(this._proxy, generateUuid(), options, options.name); this._terminals.push(terminal); terminal.create( withNullAsUndefined(options.shellPath), @@ -76,10 +85,11 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { .inspect(key.substr(key.lastIndexOf('.') + 1)); return this._apiInspectConfigToPlain(setting); }; + return terminalEnvironment.getDefaultShell( fetchSetting, this._isWorkspaceShellAllowed, - getSystemShell(platform.platform), + this._defaultShell ?? getSystemShellSync(platform.platform), process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'), process.env.windir, terminalEnvironment.createVariableResolver(this._lastActiveWorkspace, this._variableResolver), @@ -139,7 +149,8 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { executable: shellLaunchConfigDto.executable, args: shellLaunchConfigDto.args, cwd: typeof shellLaunchConfigDto.cwd === 'string' ? shellLaunchConfigDto.cwd : URI.revive(shellLaunchConfigDto.cwd), - env: shellLaunchConfigDto.env + env: shellLaunchConfigDto.env, + flowControl: shellLaunchConfigDto.flowControl }; // Merge in shell and args from settings diff --git a/src/vs/workbench/api/node/extHostTunnelService.ts b/src/vs/workbench/api/node/extHostTunnelService.ts index 922451d10..b7a058768 100644 --- a/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -12,12 +12,16 @@ import { URI } from 'vs/base/common/uri'; import { exec } from 'child_process'; import * as resources from 'vs/base/common/resources'; import * as fs from 'fs'; +import * as pfs from 'vs/base/node/pfs'; import { isLinux } from 'vs/base/common/platform'; import { IExtHostTunnelService, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; -import { asPromise } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; import { TunnelOptions, TunnelCreationOptions } from 'vs/platform/remote/common/tunnel'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { promisify } from 'util'; +import { MovingAverage } from 'vs/base/common/numbers'; +import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { ILogService } from 'vs/platform/log/common/log'; class ExtensionTunnel implements vscode.Tunnel { private _onDispose: Emitter = new Emitter(); @@ -26,14 +30,106 @@ class ExtensionTunnel implements vscode.Tunnel { constructor( public readonly remoteAddress: { port: number, host: string }, public readonly localAddress: { port: number, host: string } | string, - private readonly _dispose: () => void) { } + private readonly _dispose: () => Promise) { } - dispose(): void { + dispose(): Promise { this._onDispose.fire(); - this._dispose(); + return this._dispose(); } } +export function getSockets(stdout: string): { pid: number, socket: number }[] { + const lines = stdout.trim().split('\n'); + const mapped: { pid: number, socket: number }[] = []; + lines.forEach(line => { + const match = /\/proc\/(\d+)\/fd\/\d+ -> socket:\[(\d+)\]/.exec(line)!; + if (match && match.length >= 3) { + mapped.push({ + pid: parseInt(match[1], 10), + socket: parseInt(match[2], 10) + }); + } + }); + return mapped; +} + +export function loadListeningPorts(...stdouts: string[]): { socket: number, ip: string, port: number }[] { + const table = ([] as Record[]).concat(...stdouts.map(loadConnectionTable)); + return [ + ...new Map( + table.filter(row => row.st === '0A') + .map(row => { + const address = row.local_address.split(':'); + return { + socket: parseInt(row.inode, 10), + ip: parseIpAddress(address[0]), + port: parseInt(address[1], 16) + }; + }).map(port => [port.ip + ':' + port.port, port]) + ).values() + ]; +} + +export function parseIpAddress(hex: string): string { + let result = ''; + if (hex.length === 8) { + for (let i = hex.length - 2; i >= 0; i -= 2) { + result += parseInt(hex.substr(i, 2), 16); + if (i !== 0) { + result += '.'; + } + } + } else { + for (let i = hex.length - 4; i >= 0; i -= 4) { + result += parseInt(hex.substr(i, 4), 16).toString(16); + if (i !== 0) { + result += ':'; + } + } + } + return result; +} + +export function loadConnectionTable(stdout: string): Record[] { + const lines = stdout.trim().split('\n'); + const names = lines.shift()!.trim().split(/\s+/) + .filter(name => name !== 'rx_queue' && name !== 'tm->when'); + const table = lines.map(line => line.trim().split(/\s+/).reduce((obj, value, i) => { + obj[names[i] || i] = value; + return obj; + }, {} as Record)); + return table; +} + +function knownExcludeCmdline(command: string): boolean { + return !!command.match(/.*\.vscode-server-[a-zA-Z]+\/bin.*/) + || (command.indexOf('out/vs/server/main.js') !== -1) + || (command.indexOf('_productName=VSCode') !== -1); +} + +export async function findPorts(tcp: string, tcp6: string, procSockets: string, processes: { pid: number, cwd: string, cmd: string }[]): Promise { + const connections: { socket: number, ip: string, port: number }[] = loadListeningPorts(tcp, tcp6); + const sockets = getSockets(procSockets); + + const socketMap = sockets.reduce((m, socket) => { + m[socket.socket] = socket; + return m; + }, {} as Record); + const processMap = processes.reduce((m, process) => { + m[process.pid] = process; + return m; + }, {} as Record); + + const ports: CandidatePort[] = []; + connections.filter((connection => socketMap[connection.socket])).forEach(({ socket, ip, port }) => { + const command = processMap[socketMap[socket].pid].cmd; + if (!knownExcludeCmdline(command)) { + ports.push({ host: ip, port, detail: processMap[socketMap[socket].pid].cmd, pid: socketMap[socket].pid }); + } + }); + return ports; +} + export class ExtHostTunnelService extends Disposable implements IExtHostTunnelService { readonly _serviceBrand: undefined; private readonly _proxy: MainThreadTunnelServiceShape; @@ -42,15 +138,17 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe private _extensionTunnels: Map> = new Map(); private _onDidChangeTunnels: Emitter = new Emitter(); onDidChangeTunnels: vscode.Event = this._onDidChangeTunnels.event; + private _candidateFindingEnabled: boolean = false; constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, - @IExtHostInitDataService initData: IExtHostInitDataService + @IExtHostInitDataService initData: IExtHostInitDataService, + @ILogService private readonly logService: ILogService ) { super(); this._proxy = extHostRpc.getProxy(MainContext.MainThreadTunnelService); - if (initData.remote.isRemote && initData.remote.authority) { - this.registerCandidateFinder(); + if (isLinux && initData.remote.isRemote && initData.remote.authority) { + this._proxy.$setCandidateFinder(); } } @@ -70,18 +168,30 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe return this._proxy.$getTunnels(); } - registerCandidateFinder(): void { - // Every two seconds, scan to see if the candidate ports have changed; - if (isLinux) { - let oldPorts: { host: string, port: number, detail: string }[] | undefined = undefined; - setInterval(async () => { - const newPorts = await this.findCandidatePorts(); - if (!oldPorts || (JSON.stringify(oldPorts) !== JSON.stringify(newPorts))) { - oldPorts = newPorts; - this._proxy.$onFoundNewCandidates(oldPorts.filter(async (candidate) => await this._showCandidatePort(candidate.host, candidate.port, candidate.detail))); - return; - } - }, 2000); + private calculateDelay(movingAverage: number) { + // Some local testing indicated that the moving average might be between 50-100 ms. + return Math.max(movingAverage * 20, 2000); + } + + async $registerCandidateFinder(enable: boolean): Promise { + if (enable && this._candidateFindingEnabled) { + // already enabled + return; + } + this._candidateFindingEnabled = enable; + // Regularly scan to see if the candidate ports have changed. + let movingAverage = new MovingAverage(); + let oldPorts: { host: string, port: number, detail: string }[] | undefined = undefined; + while (this._candidateFindingEnabled) { + const startTime = new Date().getTime(); + const newPorts = await this.findCandidatePorts(); + const timeTaken = new Date().getTime() - startTime; + movingAverage.update(timeTaken); + if (!oldPorts || (JSON.stringify(oldPorts) !== JSON.stringify(newPorts))) { + oldPorts = newPorts; + await this._proxy.$onFoundNewCandidates(oldPorts); + } + await (new Promise(resolve => setTimeout(() => resolve(), this.calculateDelay(movingAverage.value)))); } } @@ -89,15 +199,18 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe if (provider) { if (provider.showCandidatePort) { this._showCandidatePort = provider.showCandidatePort; + await this._proxy.$setCandidateFilter(); } if (provider.tunnelFactory) { this._forwardPortProvider = provider.tunnelFactory; - await this._proxy.$setTunnelProvider(); + await this._proxy.$setTunnelProvider(provider.tunnelFeatures ?? { + elevation: false, + public: false + }); } } else { this._forwardPortProvider = undefined; } - await this._proxy.$tunnelServiceReady(); return toDisposable(() => { this._forwardPortProvider = undefined; }); @@ -110,7 +223,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe if (silent) { hostMap.get(remote.port)!.disposeListener.dispose(); } - hostMap.get(remote.port)!.tunnel.dispose(); + await hostMap.get(remote.port)!.tunnel.dispose(); hostMap.delete(remote.port); } } @@ -120,31 +233,42 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe this._onDidChangeTunnels.fire(); } - $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise | undefined { + async $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise { if (this._forwardPortProvider) { - const providedPort = this._forwardPortProvider(tunnelOptions, tunnelCreationOptions); - if (providedPort !== undefined) { - return asPromise(() => providedPort).then(tunnel => { + try { + this.logService.trace('$forwardPort: Getting tunnel from provider.'); + const providedPort = this._forwardPortProvider(tunnelOptions, tunnelCreationOptions); + this.logService.trace('$forwardPort: Got tunnel promise from provider.'); + if (providedPort !== undefined) { + const tunnel = await providedPort; + this.logService.trace('$forwardPort: Successfully awaited tunnel from provider.'); if (!this._extensionTunnels.has(tunnelOptions.remoteAddress.host)) { this._extensionTunnels.set(tunnelOptions.remoteAddress.host, new Map()); } const disposeListener = this._register(tunnel.onDidDispose(() => this._proxy.$closeTunnel(tunnel.remoteAddress))); this._extensionTunnels.get(tunnelOptions.remoteAddress.host)!.set(tunnelOptions.remoteAddress.port, { tunnel, disposeListener }); - return Promise.resolve(TunnelDto.fromApiTunnel(tunnel)); - }); + return TunnelDto.fromApiTunnel(tunnel); + } else { + this.logService.trace('$forwardPort: Tunnel is undefined'); + } + } catch (e) { + this.logService.trace('$forwardPort: tunnel provider error'); } } return undefined; } + async $applyCandidateFilter(candidates: CandidatePort[]): Promise { + const filter = await Promise.all(candidates.map(candidate => this._showCandidatePort(candidate.host, candidate.port, candidate.detail))); + return candidates.filter((candidate, index) => filter[index]); + } - async findCandidatePorts(): Promise<{ host: string, port: number, detail: string }[]> { - const ports: { host: string, port: number, detail: string }[] = []; + async findCandidatePorts(): Promise { let tcp: string = ''; let tcp6: string = ''; try { - tcp = fs.readFileSync('/proc/net/tcp', 'utf8'); - tcp6 = fs.readFileSync('/proc/net/tcp6', 'utf8'); + tcp = await pfs.readFile('/proc/net/tcp', 'utf8'); + tcp6 = await pfs.readFile('/proc/net/tcp6', 'utf8'); } catch (e) { // File reading error. No additional handling needed. } @@ -154,105 +278,24 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe }); })); - const procChildren = fs.readdirSync('/proc'); - const processes: { pid: number, cwd: string, cmd: string }[] = []; + const procChildren = await pfs.readdir('/proc'); + const processes: { + pid: number, cwd: string, cmd: string + }[] = []; for (let childName of procChildren) { try { const pid: number = Number(childName); const childUri = resources.joinPath(URI.file('/proc'), childName); - const childStat = fs.statSync(childUri.fsPath); + const childStat = await pfs.stat(childUri.fsPath); if (childStat.isDirectory() && !isNaN(pid)) { - const cwd = fs.readlinkSync(resources.joinPath(childUri, 'cwd').fsPath); - const cmd = fs.readFileSync(resources.joinPath(childUri, 'cmdline').fsPath, 'utf8'); + const cwd = await promisify(fs.readlink)(resources.joinPath(childUri, 'cwd').fsPath); + const cmd = await pfs.readFile(resources.joinPath(childUri, 'cmdline').fsPath, 'utf8'); processes.push({ pid, cwd, cmd }); } } catch (e) { // } } - - const connections: { socket: number, ip: string, port: number }[] = this.loadListeningPorts(tcp, tcp6); - const sockets = this.getSockets(procSockets); - - const socketMap = sockets.reduce((m, socket) => { - m[socket.socket] = socket; - return m; - }, {} as Record); - const processMap = processes.reduce((m, process) => { - m[process.pid] = process; - return m; - }, {} as Record); - - connections.filter((connection => socketMap[connection.socket])).forEach(({ socket, ip, port }) => { - const command = processMap[socketMap[socket].pid].cmd; - if (!command.match(/.*\.vscode-server-[a-zA-Z]+\/bin.*/) && (command.indexOf('out/vs/server/main.js') === -1)) { - ports.push({ host: ip, port, detail: processMap[socketMap[socket].pid].cmd }); - } - }); - - return ports; - } - - private getSockets(stdout: string): { pid: number, socket: number }[] { - const lines = stdout.trim().split('\n'); - const mapped: { pid: number, socket: number }[] = []; - lines.forEach(line => { - const match = /\/proc\/(\d+)\/fd\/\d+ -> socket:\[(\d+)\]/.exec(line)!; - if (match && match.length >= 3) { - mapped.push({ - pid: parseInt(match[1], 10), - socket: parseInt(match[2], 10) - }); - } - }); - return mapped; - } - - private loadListeningPorts(...stdouts: string[]): { socket: number, ip: string, port: number }[] { - const table = ([] as Record[]).concat(...stdouts.map(this.loadConnectionTable)); - return [ - ...new Map( - table.filter(row => row.st === '0A') - .map(row => { - const address = row.local_address.split(':'); - return { - socket: parseInt(row.inode, 10), - ip: this.parseIpAddress(address[0]), - port: parseInt(address[1], 16) - }; - }).map(port => [port.ip + ':' + port.port, port]) - ).values() - ]; - } - - private parseIpAddress(hex: string): string { - let result = ''; - if (hex.length === 8) { - for (let i = hex.length - 2; i >= 0; i -= 2) { - result += parseInt(hex.substr(i, 2), 16); - if (i !== 0) { - result += '.'; - } - } - } else { - for (let i = hex.length - 4; i >= 0; i -= 4) { - result += parseInt(hex.substr(i, 4), 16).toString(16); - if (i !== 0) { - result += ':'; - } - } - } - return result; - } - - private loadConnectionTable(stdout: string): Record[] { - const lines = stdout.trim().split('\n'); - const names = lines.shift()!.trim().split(/\s+/) - .filter(name => name !== 'rx_queue' && name !== 'tm->when'); - const table = lines.map(line => line.trim().split(/\s+/).reduce((obj, value, i) => { - obj[names[i] || i] = value; - return obj; - }, {} as Record)); - return table; + return findPorts(tcp, tcp6, procSockets, processes); } } diff --git a/src/vs/workbench/api/worker/extHostExtensionService.ts b/src/vs/workbench/api/worker/extHostExtensionService.ts index 021af6e0f..6ebbb92e4 100644 --- a/src/vs/workbench/api/worker/extHostExtensionService.ts +++ b/src/vs/workbench/api/worker/extHostExtensionService.ts @@ -8,10 +8,38 @@ import { ExtensionActivationTimesBuilder } from 'vs/workbench/api/common/extHost import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { URI } from 'vs/base/common/uri'; import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes'; import { timeout } from 'vs/base/common/async'; +namespace TrustedFunction { + + // workaround a chrome issue not allowing to create new functions + // see https://github.com/w3c/webappsec-trusted-types/wiki/Trusted-Types-for-function-constructor + const ttpTrustedFunction = self.trustedTypes?.createPolicy('TrustedFunctionWorkaround', { + createScript: (_, ...args: string[]) => { + args.forEach((arg) => { + if (!self.trustedTypes?.isScript(arg)) { + throw new Error('TrustedScripts only, please'); + } + }); + // NOTE: This is insecure without parsing the arguments and body, + // Malicious inputs can escape the function body and execute immediately! + const fnArgs = args.slice(0, -1).join(','); + const fnBody = args.pop()!.toString(); + const body = `(function anonymous(${fnArgs}) {\n${fnBody}\n})`; + return body; + } + }); + + export function create(...args: string[]): Function { + if (!ttpTrustedFunction) { + return new Function(...args); + } + return self.eval(ttpTrustedFunction.createScript('', ...args) as unknown as string); + } +} + class WorkerRequireInterceptor extends RequireInterceptor { _installInterceptor() { } @@ -35,6 +63,8 @@ class WorkerRequireInterceptor extends RequireInterceptor { export class ExtHostExtensionService extends AbstractExtHostExtensionService { readonly extensionRuntime = ExtensionRuntime.Webworker; + private static _ttpExtensionScripts = self.trustedTypes?.createPolicy('ExtensionScripts', { createScript: source => source }); + private _fakeModules?: WorkerRequireInterceptor; protected async _beforeAlmostReadyToRunExtensions(): Promise { @@ -42,6 +72,8 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { const apiFactory = this._instaService.invokeFunction(createApiFactoryAndRegisterActors); this._fakeModules = this._instaService.createInstance(WorkerRequireInterceptor, apiFactory, this._registry); await this._fakeModules.install(); + performance.mark('code/extHost/didInitAPI'); + await this._waitForDebuggerAttachment(); } @@ -49,10 +81,16 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { return extensionDescription.browser; } - protected async _loadCommonJSModule(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { + protected async _loadCommonJSModule(extensionId: ExtensionIdentifier | null, module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { module = module.with({ path: ensureSuffix(module.path, '.js') }); + if (extensionId) { + performance.mark(`code/extHost/willFetchExtensionCode/${extensionId.value}`); + } const response = await fetch(module.toString(true)); + if (extensionId) { + performance.mark(`code/extHost/didFetchExtensionCode/${extensionId.value}`); + } if (response.status !== 200) { throw new Error(response.statusText); @@ -63,7 +101,25 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { // Here we append #vscode-extension to serve as a marker, such that source maps // can be adjusted for the extra wrapping function. const sourceURL = `${module.toString(true)}#vscode-extension`; - const initFn = new Function('module', 'exports', 'require', `${source}\n//# sourceURL=${sourceURL}`); + const fullSource = `${source}\n//# sourceURL=${sourceURL}`; + let initFn: Function; + try { + initFn = TrustedFunction.create( + ExtHostExtensionService._ttpExtensionScripts?.createScript('module') as unknown as string ?? 'module', + ExtHostExtensionService._ttpExtensionScripts?.createScript('exports') as unknown as string ?? 'exports', + ExtHostExtensionService._ttpExtensionScripts?.createScript('require') as unknown as string ?? 'require', + ExtHostExtensionService._ttpExtensionScripts?.createScript(fullSource) as unknown as string ?? fullSource + ); + } catch (err) { + if (extensionId) { + console.error(`Loading code for extension ${extensionId.value} failed: ${err.message}`); + } else { + console.error(`Loading code failed: ${err.message}`); + } + console.error(`${module.toString(true)}${typeof err.line === 'number' ? ` line ${err.line}` : ''}${typeof err.column === 'number' ? ` column ${err.column}` : ''}`); + console.error(err); + throw err; + } // define commonjs globals: `module`, `exports`, and `require` const _exports = {}; @@ -78,9 +134,15 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { try { activationTimesBuilder.codeLoadingStart(); + if (extensionId) { + performance.mark(`code/extHost/willLoadExtensionCode/${extensionId.value}`); + } initFn(_module, _exports, _require); return (_module.exports !== _exports ? _module.exports : _exports); } finally { + if (extensionId) { + performance.mark(`code/extHost/didLoadExtensionCode/${extensionId.value}`); + } activationTimesBuilder.codeLoadingStop(); } } diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index cdd2cdfc3..3515af949 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -8,25 +8,21 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Action } from 'vs/base/common/actions'; import { SyncActionDescriptor, MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions, CATEGORIES } from 'vs/workbench/common/actions'; -import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; -import { IEditorGroupsService, GroupOrientation } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { getMenuBarVisibility } from 'vs/platform/windows/common/windows'; import { isWindows, isLinux, isWeb } from 'vs/base/common/platform'; import { IsMacNativeContext } from 'vs/platform/contextkey/common/contextkeys'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { InEditorZenModeContext, IsCenteredLayoutContext, EditorAreaVisibleContext } from 'vs/workbench/common/editor'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { SideBarVisibleContext } from 'vs/workbench/common/viewlet'; -import { IViewDescriptorService, IViewsService, FocusedViewContext, ViewContainerLocation, IViewDescriptor } from 'vs/workbench/common/views'; +import { IViewDescriptorService, IViewsService, FocusedViewContext, ViewContainerLocation, IViewDescriptor, ViewContainerLocationToString } from 'vs/workbench/common/views'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { Codicon } from 'vs/base/common/codicons'; const registry = Registry.as(WorkbenchExtensions.WorkbenchActions); @@ -125,54 +121,6 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { order: 3 }); -// --- Toggle Editor Layout - -export class ToggleEditorLayoutAction extends Action { - - static readonly ID = 'workbench.action.toggleEditorGroupLayout'; - static readonly LABEL = nls.localize('flipLayout', "Toggle Vertical/Horizontal Editor Layout"); - - private readonly toDispose = this._register(new DisposableStore()); - - constructor( - id: string, - label: string, - @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService - ) { - super(id, label); - - this.class = Codicon.editorLayout.classNames; - this.updateEnablement(); - - this.registerListeners(); - } - - private registerListeners(): void { - this.toDispose.add(this.editorGroupService.onDidAddGroup(() => this.updateEnablement())); - this.toDispose.add(this.editorGroupService.onDidRemoveGroup(() => this.updateEnablement())); - } - - private updateEnablement(): void { - this.enabled = this.editorGroupService.count > 1; - } - - async run(): Promise { - const newOrientation = (this.editorGroupService.orientation === GroupOrientation.VERTICAL) ? GroupOrientation.HORIZONTAL : GroupOrientation.VERTICAL; - this.editorGroupService.setGroupOrientation(newOrientation); - } -} - -registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleEditorLayoutAction, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_0 } }), 'View: Toggle Vertical/Horizontal Editor Layout', CATEGORIES.View.value); - -MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { - group: 'z_flip', - command: { - id: ToggleEditorLayoutAction.ID, - title: nls.localize({ key: 'miToggleEditorLayout', comment: ['&& denotes a mnemonic'] }, "Flip &&Layout") - }, - order: 1 -}); - // --- Toggle Sidebar Position export class ToggleSidebarPositionAction extends Action { @@ -203,7 +151,64 @@ export class ToggleSidebarPositionAction extends Action { } } -registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleSidebarPositionAction), 'View: Toggle Side Bar Position', CATEGORIES.View.value); +registerAction2(class extends Action2 { + constructor() { + super({ + id: ToggleSidebarPositionAction.ID, + title: { value: nls.localize('toggleSidebarPosition', "Toggle Side Bar Position"), original: 'Toggle Side Bar Position' }, + category: CATEGORIES.View, + f1: true + }); + } + run(accessor: ServicesAccessor) { + accessor.get(IInstantiationService).createInstance(ToggleSidebarPositionAction, ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.LABEL).run(); + } +}); +MenuRegistry.appendMenuItems([{ + id: MenuId.ViewContainerTitleContext, + item: { + group: '3_workbench_layout_move', + command: { + id: ToggleSidebarPositionAction.ID, + title: nls.localize('move sidebar right', "Move Side Bar Right") + }, + when: ContextKeyExpr.and(ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))), + order: 1 + } +}, { + id: MenuId.ViewTitleContext, + item: { + group: '3_workbench_layout_move', + command: { + id: ToggleSidebarPositionAction.ID, + title: nls.localize('move sidebar right', "Move Side Bar Right") + }, + when: ContextKeyExpr.and(ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))), + order: 1 + } +}, { + id: MenuId.ViewContainerTitleContext, + item: { + group: '3_workbench_layout_move', + command: { + id: ToggleSidebarPositionAction.ID, + title: nls.localize('move sidebar left', "Move Side Bar Left") + }, + when: ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))), + order: 1 + } +}, { + id: MenuId.ViewTitleContext, + item: { + group: '3_workbench_layout_move', + command: { + id: ToggleSidebarPositionAction.ID, + title: nls.localize('move sidebar left', "Move Side Bar Left") + }, + when: ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))), + order: 1 + } +}]); MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '3_workbench_layout_move', @@ -256,27 +261,6 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { order: 5 }); -export class ToggleSidebarVisibilityAction extends Action { - - static readonly ID = 'workbench.action.toggleSidebarVisibility'; - static readonly LABEL = nls.localize('toggleSidebar', "Toggle Side Bar Visibility"); - - constructor( - id: string, - label: string, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService - ) { - super(id, label); - } - - async run(): Promise { - const hideSidebar = this.layoutService.isVisible(Parts.SIDEBAR_PART); - this.layoutService.setSideBarHidden(hideSidebar); - } -} - -registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleSidebarVisibilityAction, { primary: KeyMod.CtrlCmd | KeyCode.KEY_B }), 'View: Toggle Side Bar Visibility', CATEGORIES.View.value); - MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { group: '2_appearance', title: nls.localize({ key: 'miAppearance', comment: ['&& denotes a mnemonic'] }, "&&Appearance"), @@ -284,10 +268,53 @@ MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { order: 1 }); +export const TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID = 'workbench.action.toggleSidebarVisibility'; +registerAction2(class extends Action2 { + constructor() { + super({ + id: TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID, + title: { value: nls.localize('toggleSidebar', "Toggle Side Bar Visibility"), original: 'Toggle Side Bar Visibility' }, + category: CATEGORIES.View, + f1: true, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyCode.KEY_B + } + }); + } + run(accessor: ServicesAccessor) { + const layoutService = accessor.get(IWorkbenchLayoutService); + layoutService.setSideBarHidden(layoutService.isVisible(Parts.SIDEBAR_PART)); + } +}); +MenuRegistry.appendMenuItems([{ + id: MenuId.ViewContainerTitleContext, + item: { + group: '3_workbench_layout_move', + command: { + id: TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID, + title: nls.localize('compositePart.hideSideBarLabel', "Hide Side Bar"), + }, + when: ContextKeyExpr.and(SideBarVisibleContext, ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))), + order: 2 + } +}, { + id: MenuId.ViewTitleContext, + item: { + group: '3_workbench_layout_move', + command: { + id: TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID, + title: nls.localize('compositePart.hideSideBarLabel', "Hide Side Bar"), + }, + when: ContextKeyExpr.and(SideBarVisibleContext, ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))), + order: 2 + } +}]); + MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '2_workbench_layout', command: { - id: ToggleSidebarVisibilityAction.ID, + id: TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID, title: nls.localize({ key: 'miShowSidebar', comment: ['&& denotes a mnemonic'] }, "Show &&Side Bar"), toggled: SideBarVisibleContext }, @@ -413,32 +440,16 @@ export class ToggleMenuBarAction extends Action { static readonly ID = 'workbench.action.toggleMenuBar'; static readonly LABEL = nls.localize('toggleMenuBar', "Toggle Menu Bar"); - private static readonly menuBarVisibilityKey = 'window.menuBarVisibility'; - constructor( id: string, label: string, - @IConfigurationService private readonly configurationService: IConfigurationService + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { super(id, label); } - run(): Promise { - let currentVisibilityValue = getMenuBarVisibility(this.configurationService); - if (typeof currentVisibilityValue !== 'string') { - currentVisibilityValue = 'default'; - } - - let newVisibilityValue: string; - if (currentVisibilityValue === 'visible' || currentVisibilityValue === 'default') { - newVisibilityValue = 'toggle'; - } else if (currentVisibilityValue === 'compact') { - newVisibilityValue = 'hidden'; - } else { - newVisibilityValue = (isWeb && currentVisibilityValue === 'hidden') ? 'compact' : 'default'; - } - - return this.configurationService.updateValue(ToggleMenuBarAction.menuBarVisibilityKey, newVisibilityValue, ConfigurationTarget.USER); + async run(): Promise { + this.layoutService.toggleMenuBar(); } } @@ -479,19 +490,18 @@ export class ResetViewLocationsAction extends Action { registry.registerWorkbenchAction(SyncActionDescriptor.from(ResetViewLocationsAction), 'View: Reset View Locations', CATEGORIES.View.value); // --- Toggle View with Command -export abstract class ToggleViewAction extends Action { +export class ToggleViewAction extends Action { constructor( id: string, label: string, private readonly viewId: string, - protected viewsService: IViewsService, - protected viewDescriptorService: IViewDescriptorService, - protected contextKeyService: IContextKeyService, - private layoutService: IWorkbenchLayoutService, - cssClass?: string + @IViewsService protected viewsService: IViewsService, + @IViewDescriptorService protected viewDescriptorService: IViewDescriptorService, + @IContextKeyService protected contextKeyService: IContextKeyService, + @IWorkbenchLayoutService private layoutService: IWorkbenchLayoutService, ) { - super(id, label, cssClass); + super(id, label); } async run(): Promise { diff --git a/src/vs/workbench/browser/actions/navigationActions.ts b/src/vs/workbench/browser/actions/navigationActions.ts index 5833c3291..20accba76 100644 --- a/src/vs/workbench/browser/actions/navigationActions.ts +++ b/src/vs/workbench/browser/actions/navigationActions.ts @@ -17,7 +17,6 @@ import { IWorkbenchActionRegistry, Extensions, CATEGORIES } from 'vs/workbench/c import { Direction } from 'vs/base/browser/ui/grid/grid'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { isAncestor } from 'vs/base/browser/dom'; abstract class BaseNavigationAction extends Action { @@ -215,7 +214,8 @@ function findVisibleNeighbour(layoutService: IWorkbenchLayoutService, part: Part } function focusNextOrPreviousPart(layoutService: IWorkbenchLayoutService, editorService: IEditorService, next: boolean): void { - const currentlyFocusedPart = isActiveElementInNotebookEditor(editorService) ? Parts.EDITOR_PART : layoutService.hasFocus(Parts.EDITOR_PART) ? Parts.EDITOR_PART : layoutService.hasFocus(Parts.ACTIVITYBAR_PART) ? Parts.ACTIVITYBAR_PART : + const editorFocused = editorService.activeEditorPane?.hasFocus(); + const currentlyFocusedPart = editorFocused ? Parts.EDITOR_PART : layoutService.hasFocus(Parts.ACTIVITYBAR_PART) ? Parts.ACTIVITYBAR_PART : layoutService.hasFocus(Parts.STATUSBAR_PART) ? Parts.STATUSBAR_PART : layoutService.hasFocus(Parts.SIDEBAR_PART) ? Parts.SIDEBAR_PART : layoutService.hasFocus(Parts.PANEL_PART) ? Parts.PANEL_PART : undefined; let partToFocus = Parts.EDITOR_PART; if (currentlyFocusedPart) { @@ -225,17 +225,6 @@ function focusNextOrPreviousPart(layoutService: IWorkbenchLayoutService, editorS layoutService.focusPart(partToFocus); } -function isActiveElementInNotebookEditor(editorService: IEditorService): boolean { - const activeEditorPane = editorService.activeEditorPane as unknown as { isNotebookEditor?: boolean } | undefined; - if (activeEditorPane?.isNotebookEditor) { - const control = editorService.activeEditorPane?.getControl() as { getDomNode(): HTMLElement; getOverflowContainerDomNode(): HTMLElement; }; - const activeElement = document.activeElement; - return isAncestor(activeElement, control.getDomNode()) || isAncestor(activeElement, control.getOverflowContainerDomNode()); - } - - return false; -} - export class FocusNextPart extends Action { static readonly ID = 'workbench.action.focusNextPart'; static readonly LABEL = nls.localize('focusNextPart', "Focus Next Part"); diff --git a/src/vs/workbench/browser/actions/textInputActions.ts b/src/vs/workbench/browser/actions/textInputActions.ts index 8b63f052b..606099d23 100644 --- a/src/vs/workbench/browser/actions/textInputActions.ts +++ b/src/vs/workbench/browser/actions/textInputActions.ts @@ -76,23 +76,26 @@ export class TextInputActionsProvider extends Disposable implements IWorkbenchCo // Context menu support in input/textarea this.layoutService.container.addEventListener('contextmenu', e => this.onContextMenu(e)); - } private onContextMenu(e: MouseEvent): void { - if (e.target instanceof HTMLElement) { - const target = e.target; - if (target.nodeName && (target.nodeName.toLowerCase() === 'input' || target.nodeName.toLowerCase() === 'textarea')) { - EventHelper.stop(e, true); - - this.contextMenuService.showContextMenu({ - getAnchor: () => e, - getActions: () => this.textInputActions, - getActionsContext: () => target, - onHide: () => target.focus() // fixes https://github.com/microsoft/vscode/issues/52948 - }); - } + if (e.defaultPrevented) { + return; // make sure to not show these actions by accident if component indicated to prevent } + + const target = e.target; + if (!(target instanceof HTMLElement) || (target.nodeName.toLowerCase() !== 'input' && target.nodeName.toLowerCase() !== 'textarea')) { + return; // only for inputs or textareas + } + + EventHelper.stop(e, true); + + this.contextMenuService.showContextMenu({ + getAnchor: () => e, + getActions: () => this.textInputActions, + getActionsContext: () => target, + onHide: () => target.focus() // fixes https://github.com/microsoft/vscode/issues/52948 + }); } } diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index e5fdcdbca..926527871 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -33,7 +33,7 @@ import { ResourceMap } from 'vs/base/common/map'; import { Codicon } from 'vs/base/common/codicons'; import { isHTMLElement } from 'vs/base/browser/dom'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export const inRecentFilesPickerContextKey = 'inRecentFilesPicker'; @@ -49,12 +49,17 @@ abstract class BaseOpenRecentAction extends Action { tooltip: nls.localize('remove', "Remove from Recently Opened") }; - private readonly dirtyRecentlyOpened: IQuickInputButton = { + private readonly dirtyRecentlyOpenedFolder: IQuickInputButton = { iconClass: 'dirty-workspace ' + Codicon.closeDirty.classNames, - tooltip: nls.localize('dirtyRecentlyOpened', "Workspace With Dirty Files"), + tooltip: nls.localize('dirtyRecentlyOpenedFolder', "Folder With Unsaved Files"), alwaysVisible: true }; + private readonly dirtyRecentlyOpenedWorkspace: IQuickInputButton = { + ...this.dirtyRecentlyOpenedFolder, + tooltip: nls.localize('dirtyRecentlyOpenedWorkspace', "Workspace With Unsaved Files"), + }; + constructor( id: string, label: string, @@ -77,7 +82,9 @@ abstract class BaseOpenRecentAction extends Action { const recentlyOpened = await this.workspacesService.getRecentlyOpened(); const dirtyWorkspacesAndFolders = await this.workspacesService.getDirtyWorkspaces(); - // Identify all folders and workspaces with dirty files + let hasWorkspaces = false; + + // Identify all folders and workspaces with unsaved files const dirtyFolders = new ResourceMap(); const dirtyWorkspaces = new ResourceMap(); for (const dirtyWorkspace of dirtyWorkspacesAndFolders) { @@ -85,6 +92,7 @@ abstract class BaseOpenRecentAction extends Action { dirtyFolders.set(dirtyWorkspace, true); } else { dirtyWorkspaces.set(dirtyWorkspace.configPath, dirtyWorkspace); + hasWorkspaces = true; } } @@ -96,6 +104,7 @@ abstract class BaseOpenRecentAction extends Action { recentFolders.set(recent.folderUri, true); } else { recentWorkspaces.set(recent.workspace.configPath, recent.workspace); + hasWorkspaces = true; } } @@ -124,7 +133,7 @@ abstract class BaseOpenRecentAction extends Action { let keyMods: IKeyMods | undefined; - const workspaceSeparator: IQuickPickSeparator = { type: 'separator', label: nls.localize('workspaces', "workspaces") }; + const workspaceSeparator: IQuickPickSeparator = { type: 'separator', label: hasWorkspaces ? nls.localize('workspacesAndFolders', "folders & workspaces") : nls.localize('folders', "folders") }; const fileSeparator: IQuickPickSeparator = { type: 'separator', label: nls.localize('files', "files") }; const picks = [workspaceSeparator, ...workspacePicks, fileSeparator, ...filePicks]; @@ -143,13 +152,14 @@ abstract class BaseOpenRecentAction extends Action { context.removeItem(); } - // Dirty Workspace - else if (context.button === this.dirtyRecentlyOpened) { + // Dirty Folder/Workspace + else if (context.button === this.dirtyRecentlyOpenedFolder || context.button === this.dirtyRecentlyOpenedWorkspace) { + const isDirtyWorkspace = context.button === this.dirtyRecentlyOpenedWorkspace; const result = await this.dialogService.confirm({ type: 'question', - title: nls.localize('dirtyWorkspace', "Workspace with Dirty Files"), - message: nls.localize('dirtyWorkspaceConfirm', "Do you want to open the workspace to review the dirty files?"), - detail: nls.localize('dirtyWorkspaceConfirmDetail', "Workspaces with dirty files cannot be removed until all dirty files have been saved or reverted.") + title: isDirtyWorkspace ? nls.localize('dirtyWorkspace', "Workspace with Unsaved Files") : nls.localize('dirtyFolder', "Folder with Unsaved Files"), + message: isDirtyWorkspace ? nls.localize('dirtyWorkspaceConfirm', "Do you want to open the workspace to review the unsaved files?") : nls.localize('dirtyFolderConfirm', "Do you want to open the folder to review the unsaved files?"), + detail: isDirtyWorkspace ? nls.localize('dirtyWorkspaceConfirmDetail', "Workspaces with unsaved files cannot be removed until all unsaved files have been saved or reverted.") : nls.localize('dirtyFolderConfirmDetail', "Folders with unsaved files cannot be removed until all unsaved files have been saved or reverted.") }); if (result.confirmed) { @@ -170,6 +180,7 @@ abstract class BaseOpenRecentAction extends Action { let iconClasses: string[]; let fullLabel: string | undefined; let resource: URI | undefined; + let isWorkspace = false; // Folder if (isRecentFolder(recent)) { @@ -185,6 +196,7 @@ abstract class BaseOpenRecentAction extends Action { iconClasses = getIconClasses(this.modelService, this.modeService, resource, FileKind.ROOT_FOLDER); openable = { workspaceUri: resource }; fullLabel = recent.label || this.labelService.getWorkspaceLabel(recent.workspace, { verbose: true }); + isWorkspace = true; } // File @@ -200,9 +212,9 @@ abstract class BaseOpenRecentAction extends Action { return { iconClasses, label: name, - ariaLabel: isDirty ? nls.localize('recentDirtyAriaLabel', "{0}, dirty workspace", name) : name, + ariaLabel: isDirty ? isWorkspace ? nls.localize('recentDirtyWorkspaceAriaLabel', "{0}, workspace with unsaved changes", name) : nls.localize('recentDirtyFolderAriaLabel', "{0}, folder with unsaved changes", name) : name, description: parentPath, - buttons: isDirty ? [this.dirtyRecentlyOpened] : [this.removeFromRecentlyOpened], + buttons: isDirty ? [isWorkspace ? this.dirtyRecentlyOpenedWorkspace : this.dirtyRecentlyOpenedFolder] : [this.removeFromRecentlyOpened], openable, resource }; @@ -405,7 +417,7 @@ CommandsRegistry.registerCommand('workbench.action.toggleConfirmBeforeClose', ac const configurationService = accessor.get(IConfigurationService); const setting = configurationService.inspect<'always' | 'keyboardOnly' | 'never'>('window.confirmBeforeClose').userValue; - return configurationService.updateValue('window.confirmBeforeClose', setting === 'never' ? 'keyboardOnly' : 'never', ConfigurationTarget.USER); + return configurationService.updateValue('window.confirmBeforeClose', setting === 'never' ? 'keyboardOnly' : 'never'); }); // --- Menu Registration diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index b73573760..ea67ce4c5 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -114,7 +114,7 @@ export class CloseWorkspaceAction extends Action { async run(): Promise { if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { - this.notificationService.info(nls.localize('noWorkspaceOpened', "There is currently no workspace opened in this instance to close.")); + this.notificationService.info(nls.localize('noWorkspaceOrFolderOpened', "There is currently no workspace or folder opened in this instance to close.")); return; } @@ -225,7 +225,7 @@ export class SaveWorkspaceAsAction extends Action { export class DuplicateWorkspaceInNewWindowAction extends Action { static readonly ID = 'workbench.action.duplicateWorkspaceInNewWindow'; - static readonly LABEL = nls.localize('duplicateWorkspaceInNewWindow', "Duplicate Workspace in New Window"); + static readonly LABEL = nls.localize('duplicateWorkspaceInNewWindow', "Duplicate As Workspace in New Window"); constructor( id: string, @@ -259,7 +259,7 @@ registry.registerWorkbenchAction(SyncActionDescriptor.from(AddRootFolderAction), registry.registerWorkbenchAction(SyncActionDescriptor.from(GlobalRemoveRootFolderAction), 'Workspaces: Remove Folder from Workspace...', workspacesCategory); registry.registerWorkbenchAction(SyncActionDescriptor.from(CloseWorkspaceAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'Workspaces: Close Workspace', workspacesCategory, EmptyWorkspaceSupportContext); registry.registerWorkbenchAction(SyncActionDescriptor.from(SaveWorkspaceAsAction), 'Workspaces: Save Workspace As...', workspacesCategory, EmptyWorkspaceSupportContext); -registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateWorkspaceInNewWindowAction), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateWorkspaceInNewWindowAction), 'Workspaces: Duplicate As Workspace in New Window', workspacesCategory); // --- Menu Registration diff --git a/src/vs/workbench/browser/parts/editor/editorWidgets.ts b/src/vs/workbench/browser/codeeditor.ts similarity index 60% rename from src/vs/workbench/browser/parts/editor/editorWidgets.ts rename to src/vs/workbench/browser/codeeditor.ts index 02601bc97..7bcac2ca0 100644 --- a/src/vs/workbench/browser/parts/editor/editorWidgets.ts +++ b/src/vs/workbench/browser/codeeditor.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Widget } from 'vs/base/browser/ui/widget'; -import { IOverlayWidget, ICodeEditor, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; +import { IOverlayWidget, ICodeEditor, IOverlayWidgetPosition, OverlayWidgetPositionPreference, isCodeEditor, isCompositeEditor } from 'vs/editor/browser/editorBrowser'; import { Emitter } from 'vs/base/common/event'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -15,11 +15,121 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; -import { Disposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { isEqual } from 'vs/base/common/resources'; import { IFileService } from 'vs/platform/files/common/files'; +import { URI } from 'vs/base/common/uri'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IRange } from 'vs/editor/common/core/range'; +import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; +import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; +import { TrackedRangeStickiness, IModelDecorationsChangeAccessor } from 'vs/editor/common/model'; + +export interface IRangeHighlightDecoration { + resource: URI; + range: IRange; + isWholeLine?: boolean; +} + +export class RangeHighlightDecorations extends Disposable { + + private readonly _onHighlightRemoved = this._register(new Emitter()); + readonly onHighlightRemoved = this._onHighlightRemoved.event; + + private rangeHighlightDecorationId: string | null = null; + private editor: ICodeEditor | null = null; + private readonly editorDisposables = this._register(new DisposableStore()); + + constructor(@IEditorService private readonly editorService: IEditorService) { + super(); + } + + removeHighlightRange() { + if (this.editor && this.editor.getModel() && this.rangeHighlightDecorationId) { + this.editor.deltaDecorations([this.rangeHighlightDecorationId], []); + this._onHighlightRemoved.fire(); + } + + this.rangeHighlightDecorationId = null; + } + + highlightRange(range: IRangeHighlightDecoration, editor?: any) { + editor = editor ?? this.getEditor(range); + if (isCodeEditor(editor)) { + this.doHighlightRange(editor, range); + } else if (isCompositeEditor(editor) && isCodeEditor(editor.activeCodeEditor)) { + this.doHighlightRange(editor.activeCodeEditor, range); + } + } + + private doHighlightRange(editor: ICodeEditor, selectionRange: IRangeHighlightDecoration) { + this.removeHighlightRange(); + + editor.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => { + this.rangeHighlightDecorationId = changeAccessor.addDecoration(selectionRange.range, this.createRangeHighlightDecoration(selectionRange.isWholeLine)); + }); + + this.setEditor(editor); + } + + private getEditor(resourceRange: IRangeHighlightDecoration): ICodeEditor | undefined { + const activeEditor = this.editorService.activeEditor; + const resource = activeEditor && activeEditor.resource; + if (resource && isEqual(resource, resourceRange.resource)) { + return this.editorService.activeTextEditorControl as ICodeEditor; + } + + return undefined; + } + + private setEditor(editor: ICodeEditor) { + if (this.editor !== editor) { + this.editorDisposables.clear(); + this.editor = editor; + this.editorDisposables.add(this.editor.onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => { + if ( + e.reason === CursorChangeReason.NotSet + || e.reason === CursorChangeReason.Explicit + || e.reason === CursorChangeReason.Undo + || e.reason === CursorChangeReason.Redo + ) { + this.removeHighlightRange(); + } + })); + this.editorDisposables.add(this.editor.onDidChangeModel(() => { this.removeHighlightRange(); })); + this.editorDisposables.add(this.editor.onDidDispose(() => { + this.removeHighlightRange(); + this.editor = null; + })); + } + } + + private static readonly _WHOLE_LINE_RANGE_HIGHLIGHT = ModelDecorationOptions.register({ + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + className: 'rangeHighlight', + isWholeLine: true + }); + + private static readonly _RANGE_HIGHLIGHT = ModelDecorationOptions.register({ + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + className: 'rangeHighlight' + }); + + private createRangeHighlightDecoration(isWholeLine: boolean = true): ModelDecorationOptions { + return (isWholeLine ? RangeHighlightDecorations._WHOLE_LINE_RANGE_HIGHLIGHT : RangeHighlightDecorations._RANGE_HIGHLIGHT); + } + + dispose() { + super.dispose(); + + if (this.editor && this.editor.getModel()) { + this.removeHighlightRange(); + this.editor = null; + } + } +} export class FloatingClickWidget extends Widget implements IOverlayWidget { diff --git a/src/vs/workbench/browser/composite.ts b/src/vs/workbench/browser/composite.ts index 16f7cf463..c0087e858 100644 --- a/src/vs/workbench/browser/composite.ts +++ b/src/vs/workbench/browser/composite.ts @@ -56,15 +56,26 @@ export abstract class Composite extends Component implements IComposite { return this._onDidBlur.event; } + private _hasFocus = false; + hasFocus(): boolean { + return this._hasFocus; + } + private registerFocusTrackEvents(): { onDidFocus: Emitter, onDidBlur: Emitter } { const container = assertIsDefined(this.getContainer()); const focusTracker = this._register(trackFocus(container)); const onDidFocus = this._onDidFocus = this._register(new Emitter()); - this._register(focusTracker.onDidFocus(() => onDidFocus.fire())); + this._register(focusTracker.onDidFocus(() => { + this._hasFocus = true; + onDidFocus.fire(); + })); const onDidBlur = this._onDidBlur = this._register(new Emitter()); - this._register(focusTracker.onDidBlur(() => onDidBlur.fire())); + this._register(focusTracker.onDidBlur(() => { + this._hasFocus = false; + onDidBlur.fire(); + })); return { onDidFocus, onDidBlur }; } @@ -234,7 +245,6 @@ export abstract class CompositeDescriptor { readonly cssClass?: string, readonly order?: number, readonly requestedIndex?: number, - readonly keybindingId?: string, ) { } instantiate(instantiationService: IInstantiationService): T { diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index 10e7b53ef..833ec2215 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -17,7 +17,7 @@ import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/ import { SideBarVisibleContext } from 'vs/workbench/common/viewlet'; import { IWorkbenchLayoutService, Parts, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { PanelPositionContext } from 'vs/workbench/common/panel'; +import { PanelMaximizedContext, PanelPositionContext, PanelVisibleContext } from 'vs/workbench/common/panel'; import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { isNative } from 'vs/base/common/platform'; @@ -64,6 +64,8 @@ export class WorkbenchContextKeysHandler extends Disposable { private sideBarVisibleContext: IContextKey; private editorAreaVisibleContext: IContextKey; private panelPositionContext: IContextKey; + private panelVisibleContext: IContextKey; + private panelMaximizedContext: IContextKey; constructor( @IContextKeyService private readonly contextKeyService: IContextKeyService, @@ -146,9 +148,13 @@ export class WorkbenchContextKeysHandler extends Disposable { // Sidebar this.sideBarVisibleContext = SideBarVisibleContext.bindTo(this.contextKeyService); - // Panel Position + // Panel this.panelPositionContext = PanelPositionContext.bindTo(this.contextKeyService); this.panelPositionContext.set(positionToString(this.layoutService.getPanelPosition())); + this.panelVisibleContext = PanelVisibleContext.bindTo(this.contextKeyService); + this.panelVisibleContext.set(this.layoutService.isVisible(Parts.PANEL_PART)); + this.panelMaximizedContext = PanelMaximizedContext.bindTo(this.contextKeyService); + this.panelMaximizedContext.set(this.layoutService.isPanelMaximized()); this.registerListeners(); } @@ -182,7 +188,11 @@ export class WorkbenchContextKeysHandler extends Disposable { this._register(this.viewletService.onDidViewletClose(() => this.updateSideBarContextKeys())); this._register(this.viewletService.onDidViewletOpen(() => this.updateSideBarContextKeys())); - this._register(this.layoutService.onPartVisibilityChange(() => this.editorAreaVisibleContext.set(this.layoutService.isVisible(Parts.EDITOR_PART)))); + this._register(this.layoutService.onPartVisibilityChange(() => { + this.editorAreaVisibleContext.set(this.layoutService.isVisible(Parts.EDITOR_PART)); + this.panelVisibleContext.set(this.layoutService.isVisible(Parts.PANEL_PART)); + this.panelMaximizedContext.set(this.layoutService.isPanelMaximized()); + })); this._register(this.workingCopyService.onDidChangeDirty(workingCopy => this.dirtyWorkingCopiesContext.set(workingCopy.isDirty() || this.workingCopyService.hasDirty))); } diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index baab13434..962b92b73 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -737,7 +737,7 @@ export class CompositeDragAndDropObserver extends Disposable { if (callbacks.onDragEnd) { this._onDragEnd.event(e => { callbacks.onDragEnd!(e); - }); + }, this, disposableStore); } return this._register(disposableStore); } diff --git a/src/vs/workbench/browser/editor.ts b/src/vs/workbench/browser/editor.ts index 5df3e69fd..fd865714e 100644 --- a/src/vs/workbench/browser/editor.ts +++ b/src/vs/workbench/browser/editor.ts @@ -12,7 +12,6 @@ import { insert } from 'vs/base/common/arrays'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; export interface IEditorDescriptor { - getId(): string; getName(): string; diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 5984484d2..a4729a6bf 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -100,6 +100,10 @@ export const DEFAULT_LABELS_CONTAINER: IResourceLabelsContainer = { }; export class ResourceLabels extends Disposable { + + private _onDidChangeDecorations = this._register(new Emitter()); + readonly onDidChangeDecorations = this._onDidChangeDecorations.event; + private widgets: ResourceLabelWidget[] = []; private labels: IResourceLabel[] = []; @@ -148,7 +152,18 @@ export class ResourceLabels extends Disposable { })); // notify when file decoration changes - this._register(this.decorationsService.onDidChangeDecorations(e => this.widgets.forEach(widget => widget.notifyFileDecorationsChanges(e)))); + this._register(this.decorationsService.onDidChangeDecorations(e => { + let notifyDidChangeDecorations = false; + this.widgets.forEach(widget => { + if (widget.notifyFileDecorationsChanges(e)) { + notifyDidChangeDecorations = true; + } + }); + + if (notifyDidChangeDecorations) { + this._onDidChangeDecorations.fire(); + } + })); // notify when theme changes this._register(this.themeService.onDidColorThemeChange(() => this.widgets.forEach(widget => widget.notifyThemeChange()))); @@ -311,19 +326,21 @@ class ResourceLabelWidget extends IconLabel { } } - notifyFileDecorationsChanges(e: IResourceDecorationChangeEvent): void { + notifyFileDecorationsChanges(e: IResourceDecorationChangeEvent): boolean { if (!this.options) { - return; + return false; } const resource = toResource(this.label); if (!resource) { - return; + return false; } if (this.options.fileDecorations && e.affectsResource(resource)) { - this.render(false); + return this.render(false); } + + return false; } notifyExtensionsRegistered(): void { @@ -465,7 +482,7 @@ class ResourceLabelWidget extends IconLabel { this.setLabel(''); } - private render(clearIconCache: boolean): void { + private render(clearIconCache: boolean): boolean { if (this.isHidden) { if (!this.needsRedraw) { this.needsRedraw = clearIconCache ? Redraw.Full : Redraw.Basic; @@ -475,7 +492,7 @@ class ResourceLabelWidget extends IconLabel { this.needsRedraw = Redraw.Full; } - return; + return false; } if (this.label) { @@ -492,7 +509,7 @@ class ResourceLabelWidget extends IconLabel { } if (!this.label) { - return; + return false; } this.renderDisposables.clear(); @@ -558,6 +575,8 @@ class ResourceLabelWidget extends IconLabel { this.setLabel(label || '', this.label.description, iconLabelOptions); this._onDidRender.fire(); + + return true; } dispose(): void { diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 460594ee0..1fe5aa621 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -78,7 +78,9 @@ enum Storage { GRID_LAYOUT = 'workbench.grid.layout', GRID_WIDTH = 'workbench.grid.width', - GRID_HEIGHT = 'workbench.grid.height' + GRID_HEIGHT = 'workbench.grid.height', + + MENU_VISIBILITY = 'window.menuBarVisibility' } enum Classes { @@ -321,9 +323,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (this.state.fullscreen && (this.state.menuBar.visibility === 'toggle' || this.state.menuBar.visibility === 'default')) { // Propagate to grid this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); - - this.layout(); } + + // Move layout call to any time the menubar + // is toggled to update consumers of offset + // see issue #115267 + this.layout(); } } @@ -622,7 +627,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return this._openedDefaultEditors; } - private getInitialFilesToOpen(): { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[] } | undefined { + private getInitialFilesToOpen(): { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[]; } | undefined { const defaultLayout = this.environmentService.options?.defaultLayout; if (defaultLayout?.editors?.length && this.storageService.isNew(StorageScope.WORKSPACE)) { this._openedDefaultEditors = true; @@ -652,7 +657,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Restore editors restorePromises.push((async () => { - mark('willRestoreEditors'); + mark('code/willRestoreEditors'); // first ensure the editor part is restored await this.editorGroupService.whenRestored; @@ -669,17 +674,17 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi await this.editorService.openEditors(editors); } - mark('didRestoreEditors'); + mark('code/didRestoreEditors'); })()); // Restore default views const restoreDefaultViewsPromise = (async () => { if (this.state.views.defaults?.length) { - mark('willOpenDefaultViews'); + mark('code/willOpenDefaultViews'); - let locationsRestored: { id: string; order: number }[] = []; + let locationsRestored: { id: string; order: number; }[] = []; - const tryOpenView = (view: { id: string; order: number }): boolean => { + const tryOpenView = (view: { id: string; order: number; }): boolean => { const location = this.viewDescriptorService.getViewLocationById(view.id); if (location !== null) { const container = this.viewDescriptorService.getViewContainerByViewId(view.id); @@ -733,7 +738,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.state.panel.panelToRestore = locationsRestored[ViewContainerLocation.Panel].id; } - mark('didOpenDefaultViews'); + mark('code/didOpenDefaultViews'); } })(); restorePromises.push(restoreDefaultViewsPromise); @@ -748,14 +753,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return; } - mark('willRestoreViewlet'); + mark('code/willRestoreViewlet'); const viewlet = await this.viewletService.openViewlet(this.state.sideBar.viewletToRestore); if (!viewlet) { await this.viewletService.openViewlet(this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id); // fallback to default viewlet as needed } - mark('didRestoreViewlet'); + mark('code/didRestoreViewlet'); })()); // Restore Panel @@ -768,14 +773,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return; } - mark('willRestorePanel'); + mark('code/willRestorePanel'); const panel = await this.panelService.openPanel(this.state.panel.panelToRestore!); if (!panel) { await this.panelService.openPanel(Registry.as(PanelExtensions.Panels).getDefaultPanelId()); // fallback to default panel as needed } - mark('didRestorePanel'); + mark('code/didRestorePanel'); })()); // Restore Zen Mode @@ -1128,7 +1133,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi [Parts.STATUSBAR_PART]: this.statusBarPartView }; - const fromJSON = ({ type }: { type: Parts }) => viewMap[type]; + const fromJSON = ({ type }: { type: Parts; }) => viewMap[type]; const workbenchGrid = SerializableGrid.deserialize( this.createGridDescriptor(), { fromJSON }, @@ -1410,7 +1415,29 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // If panel part becomes visible, show last active panel or default panel else if (!hidden && !this.panelService.getActivePanel()) { - const panelToOpen = this.panelService.getLastActivePanelId(); + let panelToOpen: string | undefined = this.panelService.getLastActivePanelId(); + const hasViews = (id: string): boolean => { + const viewContainer = this.viewDescriptorService.getViewContainerById(id); + if (!viewContainer) { + return false; + } + + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); + if (!viewContainerModel) { + return false; + } + + return viewContainerModel.activeViewDescriptors.length >= 1; + }; + + // verify that the panel we try to open has views before we default to it + // otherwise fall back to any view that has views still refs #111463 + if (!panelToOpen || !hasViews(panelToOpen)) { + panelToOpen = this.viewDescriptorService + .getViewContainersByLocation(ViewContainerLocation.Panel) + .find(viewContainer => hasViews(viewContainer.id))?.id; + } + if (panelToOpen) { const focus = !skipLayout; this.panelService.openPanel(panelToOpen, focus); @@ -1529,6 +1556,24 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return this.state.menuBar.visibility; } + toggleMenuBar(): void { + let currentVisibilityValue = getMenuBarVisibility(this.configurationService); + if (typeof currentVisibilityValue !== 'string') { + currentVisibilityValue = 'default'; + } + + let newVisibilityValue: string; + if (currentVisibilityValue === 'visible' || currentVisibilityValue === 'default') { + newVisibilityValue = 'toggle'; + } else if (currentVisibilityValue === 'compact') { + newVisibilityValue = 'hidden'; + } else { + newVisibilityValue = (isWeb && currentVisibilityValue === 'hidden') ? 'compact' : 'default'; + } + + this.configurationService.updateValue(Storage.MENU_VISIBILITY, newVisibilityValue); + } + getPanelPosition(): Position { return this.state.panel.position; } diff --git a/src/vs/workbench/browser/menuActions.ts b/src/vs/workbench/browser/menuActions.ts new file mode 100644 index 000000000..7aecec66e --- /dev/null +++ b/src/vs/workbench/browser/menuActions.ts @@ -0,0 +1,99 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IAction } from 'vs/base/common/actions'; +import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Emitter, Event } from 'vs/base/common/event'; +import { MenuId, IMenuService, IMenu, SubmenuItemAction, IMenuActionOptions } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; + +class MenuActions extends Disposable { + + private readonly menu: IMenu; + + private _primaryActions: IAction[] = []; + get primaryActions() { return this._primaryActions; } + + private _secondaryActions: IAction[] = []; + get secondaryActions() { return this._secondaryActions; } + + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange = this._onDidChange.event; + + private disposables = this._register(new DisposableStore()); + + constructor( + menuId: MenuId, + private readonly options: IMenuActionOptions | undefined, + private readonly menuService: IMenuService, + private readonly contextKeyService: IContextKeyService + ) { + super(); + this.menu = this._register(menuService.createMenu(menuId, contextKeyService)); + this._register(this.menu.onDidChange(() => this.updateActions())); + this.updateActions(); + } + + private updateActions(): void { + this.disposables.clear(); + this._primaryActions = []; + this._secondaryActions = []; + this.disposables.add(createAndFillInActionBarActions(this.menu, this.options, { primary: this._primaryActions, secondary: this._secondaryActions })); + this.disposables.add(this.updateSubmenus([...this._primaryActions, ...this._secondaryActions], {})); + this._onDidChange.fire(); + } + + private updateSubmenus(actions: readonly IAction[], submenus: { [id: number]: IMenu }): IDisposable { + const disposables = new DisposableStore(); + for (const action of actions) { + if (action instanceof SubmenuItemAction && !submenus[action.item.submenu.id]) { + const menu = submenus[action.item.submenu.id] = disposables.add(this.menuService.createMenu(action.item.submenu, this.contextKeyService)); + disposables.add(menu.onDidChange(() => this.updateActions())); + disposables.add(this.updateSubmenus(action.actions, submenus)); + } + } + return disposables; + } +} + +export class CompositeMenuActions extends Disposable { + + private readonly menuActions: MenuActions; + private readonly contextMenuActionsDisposable = this._register(new MutableDisposable()); + + private _onDidChange = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; + + constructor( + menuId: MenuId, + private readonly contextMenuId: MenuId | undefined, + private readonly options: IMenuActionOptions | undefined, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IMenuService private readonly menuService: IMenuService, + ) { + super(); + this.menuActions = this._register(new MenuActions(menuId, this.options, menuService, contextKeyService)); + this._register(this.menuActions.onDidChange(() => this._onDidChange.fire())); + } + + getPrimaryActions(): IAction[] { + return this.menuActions.primaryActions; + } + + getSecondaryActions(): IAction[] { + return this.menuActions.secondaryActions; + } + + getContextMenuActions(): IAction[] { + const actions: IAction[] = []; + if (this.contextMenuId) { + const menu = this.menuService.createMenu(this.contextMenuId, this.contextKeyService); + this.contextMenuActionsDisposable.value = createAndFillInActionBarActions(menu, this.options, { primary: [], secondary: actions }); + menu.dispose(); + } + return actions; + } +} diff --git a/src/vs/workbench/browser/panecomposite.ts b/src/vs/workbench/browser/panecomposite.ts index 5279b52e2..4a8a1144f 100644 --- a/src/vs/workbench/browser/panecomposite.ts +++ b/src/vs/workbench/browser/panecomposite.ts @@ -16,13 +16,9 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { ViewPaneContainer } from './parts/views/viewPaneContainer'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { IAction, IActionViewItem, Separator } from 'vs/base/common/actions'; -import { ViewContainerMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions'; -import { MenuId } from 'vs/platform/actions/common/actions'; export class PaneComposite extends Composite implements IPaneComposite { - private menuActions: ViewContainerMenuActions; - constructor( id: string, protected readonly viewPaneContainer: ViewPaneContainer, @@ -35,8 +31,6 @@ export class PaneComposite extends Composite implements IPaneComposite { @IWorkspaceContextService protected contextService: IWorkspaceContextService ) { super(id, telemetryService, themeService, storageService); - - this.menuActions = this._register(this.instantiationService.createInstance(ViewContainerMenuActions, this.getId(), MenuId.ViewContainerTitleContext)); this._register(this.viewPaneContainer.onTitleAreaUpdate(() => this.updateTitleArea())); } @@ -71,22 +65,36 @@ export class PaneComposite extends Composite implements IPaneComposite { getContextMenuActions(): ReadonlyArray { const result = []; - result.push(...this.menuActions.getContextMenuActions()); + result.push(...this.viewPaneContainer.getContextMenuActions2()); - if (result.length) { + const otherActions = this.viewPaneContainer.getContextMenuActions(); + + if (otherActions.length) { result.push(new Separator()); + result.push(...otherActions); } - result.push(...this.viewPaneContainer.getContextMenuActions()); return result; } getActions(): ReadonlyArray { - return this.viewPaneContainer.getActions(); + const result = []; + result.push(...this.viewPaneContainer.getActions2()); + result.push(...this.viewPaneContainer.getActions()); + return result; } getSecondaryActions(): ReadonlyArray { - return this.viewPaneContainer.getSecondaryActions(); + const menuActions = this.viewPaneContainer.getSecondaryActions2(); + const viewPaneContainerActions = this.viewPaneContainer.getSecondaryActions(); + if (menuActions.length && viewPaneContainerActions.length) { + return [ + ...menuActions, + new Separator(), + ...viewPaneContainerActions + ]; + } + return menuActions.length ? menuActions : viewPaneContainerActions; } getActionViewItem(action: IAction): IActionViewItem | undefined { diff --git a/src/vs/workbench/browser/panel.ts b/src/vs/workbench/browser/panel.ts index b2b2a18b4..0d04a6a1c 100644 --- a/src/vs/workbench/browser/panel.ts +++ b/src/vs/workbench/browser/panel.ts @@ -6,23 +6,74 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IPanel } from 'vs/workbench/common/panel'; import { CompositeDescriptor, CompositeRegistry } from 'vs/workbench/browser/composite'; -import { IConstructorSignature0, BrandedService } from 'vs/platform/instantiation/common/instantiation'; +import { IConstructorSignature0, BrandedService, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { assertIsDefined } from 'vs/base/common/types'; import { PaneComposite } from 'vs/workbench/browser/panecomposite'; +import { IAction, Separator } from 'vs/base/common/actions'; +import { CompositeMenuActions } from 'vs/workbench/browser/menuActions'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +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 { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -export abstract class Panel extends PaneComposite implements IPanel { } +export abstract class Panel extends PaneComposite implements IPanel { + + private readonly panelActions: CompositeMenuActions; + + constructor(id: string, + viewPaneContainer: ViewPaneContainer, + @ITelemetryService telemetryService: ITelemetryService, + @IStorageService storageService: IStorageService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + ) { + super(id, viewPaneContainer, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + this.panelActions = this._register(this.instantiationService.createInstance(CompositeMenuActions, MenuId.PanelTitle, undefined, undefined)); + this._register(this.panelActions.onDidChange(() => this.updateTitleArea())); + } + + getActions(): ReadonlyArray { + return [...super.getActions(), ...this.panelActions.getPrimaryActions()]; + } + + getSecondaryActions(): ReadonlyArray { + return this.mergeSecondaryActions(super.getSecondaryActions(), this.panelActions.getSecondaryActions()); + } + + getContextMenuActions(): ReadonlyArray { + return this.mergeSecondaryActions(super.getContextMenuActions(), this.panelActions.getContextMenuActions()); + } + + private mergeSecondaryActions(actions: ReadonlyArray, panelActions: IAction[]): ReadonlyArray { + if (panelActions.length && actions.length) { + return [ + ...actions, + new Separator(), + ...panelActions, + ]; + } + return panelActions.length ? panelActions : actions; + } +} /** * A panel descriptor is a leightweight descriptor of a panel in the workbench. */ export class PanelDescriptor extends CompositeDescriptor { - static create(ctor: { new(...services: Services): Panel }, id: string, name: string, cssClass?: string, order?: number, requestedIndex?: number, _commandId?: string): PanelDescriptor { - return new PanelDescriptor(ctor as IConstructorSignature0, id, name, cssClass, order, requestedIndex, _commandId); + static create(ctor: { new(...services: Services): Panel }, id: string, name: string, cssClass?: string, order?: number, requestedIndex?: number): PanelDescriptor { + return new PanelDescriptor(ctor as IConstructorSignature0, id, name, cssClass, order, requestedIndex); } - private constructor(ctor: IConstructorSignature0, id: string, name: string, cssClass?: string, order?: number, requestedIndex?: number, _commandId?: string) { - super(ctor, id, name, cssClass, order, requestedIndex, _commandId); + private constructor(ctor: IConstructorSignature0, id: string, name: string, cssClass?: string, order?: number, requestedIndex?: number) { + super(ctor, id, name, cssClass, order, requestedIndex); } } diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index dfab3bc5c..8e78cbccc 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -162,7 +162,7 @@ class PartLayout { if (this.options && this.options.hasTitle) { titleSize = new Dimension(width, Math.min(height, PartLayout.TITLE_HEIGHT)); } else { - titleSize = new Dimension(0, 0); + titleSize = Dimension.None; } let contentWidth = width; diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index 49bbae451..485e48516 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -4,18 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/activityaction'; -import * as nls from 'vs/nls'; -import * as DOM from 'vs/base/browser/dom'; +import { localize } from 'vs/nls'; +import { EventType, addDisposableListener, EventHelper } from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; -import { Action, IAction, Separator, SubmenuAction } from 'vs/base/common/actions'; +import { Action, IAction, Separator, SubmenuAction, toAction } from 'vs/base/common/actions'; import { KeyCode } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IMenuService, MenuId, IMenu, registerAction2, Action2, IAction2Options } from 'vs/platform/actions/common/actions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; -import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ActivityAction, ActivityActionViewItem, ICompositeBar, ICompositeBarColors, ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions'; import { CATEGORIES } from 'vs/workbench/common/actions'; import { IActivity } from 'vs/workbench/common/activity'; @@ -29,12 +29,12 @@ import { isMacintosh, isWeb } from 'vs/base/common/platform'; import { getCurrentAuthenticationSessionInfo, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; import { AuthenticationSession } from 'vs/editor/common/modes'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IProductService } from 'vs/platform/product/common/productService'; import { AnchorAlignment, AnchorAxisAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { getTitleBarStyle } from 'vs/platform/windows/common/windows'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; export class ViewContainerActivityAction extends ActivityAction { @@ -97,10 +97,10 @@ export class ViewContainerActivityAction extends ActivityAction { private logAction(action: string) { type ActivityBarActionClassification = { - viewletId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - action: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + viewletId: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; }; + action: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; }; }; - this.telemetryService.publicLog2<{ viewletId: String, action: String }, ActivityBarActionClassification>('activityBarAction', { viewletId: this.activity.id, action }); + this.telemetryService.publicLog2<{ viewletId: String, action: String; }, ActivityBarActionClassification>('activityBarAction', { viewletId: this.activity.id, action }); } } @@ -109,6 +109,7 @@ class MenuActivityActionViewItem extends ActivityActionViewItem { constructor( private readonly menuId: MenuId, action: ActivityAction, + private contextMenuActionsProvider: () => IAction[], colors: (theme: IColorTheme) => ICompositeBarColors, @IThemeService themeService: IThemeService, @IMenuService protected readonly menuService: IMenuService, @@ -126,30 +127,35 @@ class MenuActivityActionViewItem extends ActivityActionViewItem { // Context menus are triggered on mouse down so that an item can be picked // and executed with releasing the mouse over it - this._register(DOM.addDisposableListener(this.container, DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => { - DOM.EventHelper.stop(e, true); + this._register(addDisposableListener(this.container, EventType.MOUSE_DOWN, (e: MouseEvent) => { + EventHelper.stop(e, true); this.showContextMenu(e); })); - this._register(DOM.addDisposableListener(this.container, DOM.EventType.KEY_UP, (e: KeyboardEvent) => { + this._register(addDisposableListener(this.container, EventType.KEY_UP, (e: KeyboardEvent) => { let event = new StandardKeyboardEvent(e); if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { - DOM.EventHelper.stop(e, true); + EventHelper.stop(e, true); this.showContextMenu(); } })); - this._register(DOM.addDisposableListener(this.container, TouchEventType.Tap, (e: GestureEvent) => { - DOM.EventHelper.stop(e, true); + this._register(addDisposableListener(this.container, TouchEventType.Tap, (e: GestureEvent) => { + EventHelper.stop(e, true); this.showContextMenu(); })); } - protected async showContextMenu(e?: MouseEvent): Promise { + private async showContextMenu(e?: MouseEvent): Promise { const disposables = new DisposableStore(); - const menu = disposables.add(this.menuService.createMenu(this.menuId, this.contextKeyService)); - const actions = await this.resolveActions(menu, disposables); + let actions: IAction[]; + if (e?.button !== 2) { + const menu = disposables.add(this.menuService.createMenu(this.menuId, this.contextKeyService)); + actions = await this.resolveMainMenuActions(menu, disposables); + } else { + actions = await this.resolveContextMenuActions(disposables); + } const isUsingCustomMenu = isWeb || (getTitleBarStyle(this.configurationService) !== 'native' && !isMacintosh); // see #40262 const position = this.configurationService.getValue('workbench.sideBar.location'); @@ -163,13 +169,17 @@ class MenuActivityActionViewItem extends ActivityActionViewItem { }); } - protected async resolveActions(menu: IMenu, disposables: DisposableStore): Promise { + protected async resolveMainMenuActions(menu: IMenu, disposables: DisposableStore): Promise { const actions: IAction[] = []; disposables.add(createAndFillInActionBarActions(menu, undefined, { primary: [], secondary: actions })); return actions; } + + protected async resolveContextMenuActions(disposables: DisposableStore): Promise { + return this.contextMenuActionsProvider(); + } } export class HomeActivityActionViewItem extends MenuActivityActionViewItem { @@ -179,6 +189,7 @@ export class HomeActivityActionViewItem extends MenuActivityActionViewItem { constructor( private readonly goHomeHref: string, action: ActivityAction, + contextMenuActionsProvider: () => IAction[], colors: (theme: IColorTheme) => ICompositeBarColors, @IThemeService themeService: IThemeService, @IMenuService menuService: IMenuService, @@ -188,27 +199,32 @@ export class HomeActivityActionViewItem extends MenuActivityActionViewItem { @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IStorageService private readonly storageService: IStorageService ) { - super(MenuId.MenubarHomeMenu, action, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService); + super(MenuId.MenubarHomeMenu, action, contextMenuActionsProvider, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService); } - protected async resolveActions(homeMenu: IMenu, disposables: DisposableStore): Promise { + protected async resolveMainMenuActions(homeMenu: IMenu, disposables: DisposableStore): Promise { const actions = []; // Go Home - actions.push(disposables.add(new Action('goHome', nls.localize('goHome', "Go Home"), undefined, true, async () => window.location.href = this.goHomeHref))); - actions.push(disposables.add(new Separator())); + actions.push(toAction({ id: 'goHome', label: localize('goHome', "Go Home"), run: () => window.location.href = this.goHomeHref })); // Contributed - const contributedActions = await super.resolveActions(homeMenu, disposables); - actions.push(...contributedActions); - - // Hide - if (contributedActions.length > 0) { + const contributedActions = await super.resolveMainMenuActions(homeMenu, disposables); + if (contributedActions.length) { actions.push(disposables.add(new Separator())); + actions.push(...contributedActions); } - actions.push(disposables.add(new Action('hide', nls.localize('hide', "Hide"), undefined, true, async () => { - this.storageService.store(HomeActivityActionViewItem.HOME_BAR_VISIBILITY_PREFERENCE, false, StorageScope.GLOBAL, StorageTarget.USER); - }))); + + return actions; + } + + protected async resolveContextMenuActions(disposables: DisposableStore): Promise { + const actions = await super.resolveContextMenuActions(disposables); + + actions.unshift(...[ + toAction({ id: 'hideHomeButton', label: localize('hideHomeButton', "Hide Home Button"), run: () => this.storageService.store(HomeActivityActionViewItem.HOME_BAR_VISIBILITY_PREFERENCE, false, StorageScope.GLOBAL, StorageTarget.USER) }), + new Separator() + ]); return actions; } @@ -220,6 +236,7 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem { constructor( action: ActivityAction, + contextMenuActionsProvider: () => IAction[], colors: (theme: IColorTheme) => ICompositeBarColors, @IThemeService themeService: IThemeService, @IContextMenuService contextMenuService: IContextMenuService, @@ -227,15 +244,15 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem { @IContextKeyService contextKeyService: IContextKeyService, @IAuthenticationService private readonly authenticationService: IAuthenticationService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @IStorageService private readonly storageService: IStorageService, @IProductService private readonly productService: IProductService, @IConfigurationService configurationService: IConfigurationService, + @IStorageService private readonly storageService: IStorageService ) { - super(MenuId.AccountsContext, action, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService); + super(MenuId.AccountsContext, action, contextMenuActionsProvider, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService); } - protected async resolveActions(accountsMenu: IMenu, disposables: DisposableStore): Promise { - await super.resolveActions(accountsMenu, disposables); + protected async resolveMainMenuActions(accountsMenu: IMenu, disposables: DisposableStore): Promise { + await super.resolveMainMenuActions(accountsMenu, disposables); const otherCommands = accountsMenu.getActions(); const providers = this.authenticationService.getProviderIds(); @@ -243,7 +260,7 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem { try { const sessions = await this.authenticationService.getSessions(providerId); - const groupedSessions: { [label: string]: AuthenticationSession[] } = {}; + const groupedSessions: { [label: string]: AuthenticationSession[]; } = {}; sessions.forEach(session => { if (groupedSessions[session.account.label]) { groupedSessions[session.account.label].push(session); @@ -266,11 +283,11 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem { if (sessionInfo.sessions) { Object.keys(sessionInfo.sessions).forEach(accountName => { - const manageExtensionsAction = disposables.add(new Action(`configureSessions${accountName}`, nls.localize('manageTrustedExtensions', "Manage Trusted Extensions"), '', true, () => { + const manageExtensionsAction = disposables.add(new Action(`configureSessions${accountName}`, localize('manageTrustedExtensions', "Manage Trusted Extensions"), '', true, () => { return this.authenticationService.manageTrustedExtensionsForAccount(sessionInfo.providerId, accountName); })); - const signOutAction = disposables.add(new Action('signOut', nls.localize('signOut', "Sign Out"), '', true, () => { + const signOutAction = disposables.add(new Action('signOut', localize('signOut', "Sign Out"), '', true, () => { return this.authenticationService.signOutOfAccount(sessionInfo.providerId, accountName); })); @@ -285,7 +302,7 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem { menus.push(providerSubMenu); }); } else { - const providerUnavailableAction = disposables.add(new Action('providerUnavailable', nls.localize('authProviderUnavailable', '{0} is currently unavailable', providerDisplayName))); + const providerUnavailableAction = disposables.add(new Action('providerUnavailable', localize('authProviderUnavailable', '{0} is currently unavailable', providerDisplayName))); menus.push(providerUnavailableAction); } }); @@ -302,22 +319,26 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem { } }); - if (menus.length) { - menus.push(disposables.add(new Separator())); - } - - menus.push(disposables.add(new Action('hide', nls.localize('hide', "Hide"), undefined, true, async () => { - this.storageService.store(AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, false, StorageScope.GLOBAL, StorageTarget.USER); - }))); - return menus; } + + protected async resolveContextMenuActions(disposables: DisposableStore): Promise { + const actions = await super.resolveContextMenuActions(disposables); + + actions.unshift(...[ + toAction({ id: 'hideAccounts', label: localize('hideAccounts', "Hide Accounts"), run: () => this.storageService.store(AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, false, StorageScope.GLOBAL, StorageTarget.USER) }), + new Separator() + ]); + + return actions; + } } export class GlobalActivityActionViewItem extends MenuActivityActionViewItem { constructor( action: ActivityAction, + contextMenuActionsProvider: () => IAction[], colors: (theme: IColorTheme) => ICompositeBarColors, @IThemeService themeService: IThemeService, @IMenuService menuService: IMenuService, @@ -326,7 +347,7 @@ export class GlobalActivityActionViewItem extends MenuActivityActionViewItem { @IConfigurationService configurationService: IConfigurationService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService ) { - super(MenuId.GlobalActivity, action, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService); + super(MenuId.GlobalActivity, action, contextMenuActionsProvider, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService); } } @@ -379,7 +400,7 @@ registerAction2( constructor() { super({ id: 'workbench.action.previousSideBarView', - title: { value: nls.localize('previousSideBarView', "Previous Side Bar View"), original: 'Previous Side Bar View' }, + title: { value: localize('previousSideBarView', "Previous Side Bar View"), original: 'Previous Side Bar View' }, category: CATEGORIES.View, f1: true }, -1); @@ -392,7 +413,7 @@ registerAction2( constructor() { super({ id: 'workbench.action.nextSideBarView', - title: { value: nls.localize('nextSideBarView', "Next Side Bar View"), original: 'Next Side Bar View' }, + title: { value: localize('nextSideBarView', "Next Side Bar View"), original: 'Next Side Bar View' }, category: CATEGORIES.View, f1: true }, 1); @@ -400,7 +421,7 @@ registerAction2( } ); -registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme, collector) => { const activityBarBackgroundColor = theme.getColor(ACTIVITY_BAR_BACKGROUND); if (activityBarBackgroundColor) { collector.addRule(` diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 39a3ca76e..4e78ba227 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/activitybarpart'; -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { GLOBAL_ACTIVITY_ID, IActivity, ACCOUNTS_ACTIVITY_ID } from 'vs/workbench/common/activity'; import { Part } from 'vs/workbench/browser/part'; @@ -13,7 +13,7 @@ import { IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activ import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; -import { ToggleActivityBarVisibilityAction, ToggleMenuBarAction, ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions'; +import { ToggleActivityBarVisibilityAction, ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions'; import { IThemeService, IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_ACTIVE_BACKGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BORDER } from 'vs/workbench/common/theme'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; @@ -23,33 +23,34 @@ import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity } from 'vs/workbench/browser/parts/compositeBarActions'; -import { IViewDescriptorService, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewContainerModel, ViewContainerLocation, IViewsService } from 'vs/workbench/common/views'; +import { IViewDescriptorService, ViewContainer, IViewContainerModel, ViewContainerLocation, IViewsService } from 'vs/workbench/common/views'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { assertIsDefined } from 'vs/base/common/types'; +import { assertIsDefined, isString } from 'vs/base/common/types'; import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { Schemas } from 'vs/base/common/network'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { getMenuBarVisibility } from 'vs/platform/windows/common/windows'; -import { isWeb } from 'vs/base/common/platform'; +import { isNative, isWeb } from 'vs/base/common/platform'; import { Before2D } from 'vs/workbench/browser/dnd'; import { Codicon, iconRegistry } from 'vs/base/common/codicons'; -import { Action, Separator } from 'vs/base/common/actions'; +import { IAction, Separator, toAction } from 'vs/base/common/actions'; import { Event } from 'vs/base/common/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { CATEGORIES } from 'vs/workbench/common/actions'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; +import { StringSHA1 } from 'vs/base/common/hash'; interface IPlaceholderViewContainer { readonly id: string; readonly name?: string; readonly iconUrl?: UriComponents; readonly themeIcon?: ThemeIcon; - readonly views?: { when?: string }[]; + readonly isBuiltin?: boolean; + readonly views?: { when?: string; }[]; } interface IPinnedViewContainer { @@ -66,12 +67,10 @@ interface ICachedViewContainer { readonly pinned: boolean; readonly order?: number; visible: boolean; - views?: { when?: string }[]; + isBuiltin?: boolean; + views?: { when?: string; }[]; } -const settingsViewBarIcon = registerIcon('settings-view-bar-icon', Codicon.settingsGear, nls.localize('settingsViewBarIcon', 'Settings icon in the view bar.')); -const accountsViewBarIcon = registerIcon('accounts-view-bar-icon', Codicon.account, nls.localize('accountsViewBarIcon', 'Accounts icon in the view bar.')); - export class ActivitybarPart extends Part implements IActivityBarService { declare readonly _serviceBrand: undefined; @@ -81,6 +80,9 @@ export class ActivitybarPart extends Part implements IActivityBarService { private static readonly ACTION_HEIGHT = 48; private static readonly ACCOUNTS_ACTION_INDEX = 0; + private static readonly GEAR_ICON = registerIcon('settings-view-bar-icon', Codicon.settingsGear, localize('settingsViewBarIcon', "Settings icon in the view bar.")); + private static readonly ACCOUNTS_ICON = registerIcon('accounts-view-bar-icon', Codicon.account, localize('accountsViewBarIcon', "Accounts icon in the view bar.")); + //#region IView readonly minimumWidth: number = 48; @@ -110,12 +112,13 @@ export class ActivitybarPart extends Part implements IActivityBarService { private readonly accountsActivity: ICompositeActivity[] = []; - private readonly compositeActions = new Map(); + private readonly compositeActions = new Map(); private readonly viewContainerDisposables = new Map(); private readonly keyboardNavigationDisposables = this._register(new DisposableStore()); private readonly location = ViewContainerLocation.Sidebar; + private hasExtensionsRegistered: boolean = false; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -132,14 +135,8 @@ export class ActivitybarPart extends Part implements IActivityBarService { super(Parts.ACTIVITYBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); for (const cachedViewContainer of this.cachedViewContainers) { - if ( - environmentService.remoteAuthority || // In remote window, hide activity bar entries until registered - this.shouldBeHidden(cachedViewContainer.id, cachedViewContainer) - ) { - cachedViewContainer.visible = false; - } + cachedViewContainer.visible = !this.shouldBeHidden(cachedViewContainer.id, cachedViewContainer); } - this.compositeBar = this.createCompositeBar(); this.onDidRegisterViewContainers(this.getViewContainers()); @@ -161,53 +158,66 @@ export class ActivitybarPart extends Part implements IActivityBarService { icon: true, orientation: ActionsOrientation.VERTICAL, preventLoopNavigation: true, - openComposite: (compositeId: string) => this.viewsService.openViewContainer(compositeId, true), - getActivityAction: (compositeId: string) => this.getCompositeActions(compositeId).activityAction, - getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction, - getOnCompositeClickAction: (compositeId: string) => new Action(compositeId, '', '', true, () => this.viewsService.isViewContainerVisible(compositeId) ? Promise.resolve(this.viewsService.closeViewContainer(compositeId)) : this.viewsService.openViewContainer(compositeId)), - getContextMenuActions: () => { - const actions = []; + openComposite: compositeId => this.viewsService.openViewContainer(compositeId, true), + getActivityAction: compositeId => this.getCompositeActions(compositeId).activityAction, + getCompositePinnedAction: compositeId => this.getCompositeActions(compositeId).pinnedAction, + getOnCompositeClickAction: compositeId => toAction({ id: compositeId, label: '', run: async () => this.viewsService.isViewContainerVisible(compositeId) ? this.viewsService.closeViewContainer(compositeId) : this.viewsService.openViewContainer(compositeId) }), + fillExtraContextMenuActions: actions => { // Home + const topActions: IAction[] = []; if (this.homeBarContainer) { - actions.push(new Action( - 'toggleHomeBarAction', - this.homeBarVisibilityPreference ? nls.localize('hideHomeBar', "Hide Home Button") : nls.localize('showHomeBar', "Show Home Button"), - undefined, - true, - async () => { this.homeBarVisibilityPreference = !this.homeBarVisibilityPreference; } - )); + topActions.push({ + id: 'toggleHomeBarAction', + label: localize('homeButton', "Home Button"), + class: undefined, + tooltip: localize('homeButton', "Home Button"), + checked: this.homeBarVisibilityPreference, + enabled: true, + run: async () => this.homeBarVisibilityPreference = !this.homeBarVisibilityPreference, + dispose: () => { } + }); } // Menu const menuBarVisibility = getMenuBarVisibility(this.configurationService); if (menuBarVisibility === 'compact' || (menuBarVisibility === 'hidden' && isWeb)) { - actions.push(this.instantiationService.createInstance(ToggleMenuBarAction, ToggleMenuBarAction.ID, menuBarVisibility === 'compact' ? nls.localize('hideMenu', "Hide Menu") : nls.localize('showMenu', "Show Menu"))); + topActions.push({ + id: 'toggleMenuVisibility', + label: localize('menu', "Menu"), + class: undefined, + tooltip: localize('menu', "Menu"), + checked: menuBarVisibility === 'compact', + enabled: true, + run: async () => this.layoutService.toggleMenuBar(), + dispose: () => { } + }); + } + + if (topActions.length) { + actions.unshift(...topActions, new Separator()); } // Accounts - actions.push(new Action( - 'toggleAccountsVisibility', - this.accountsVisibilityPreference ? nls.localize('hideAccounts', "Hide Accounts") : nls.localize('showAccounts', "Show Accounts"), - undefined, - true, - async () => { this.accountsVisibilityPreference = !this.accountsVisibilityPreference; } - )); + actions.push(new Separator()); + actions.push({ + id: 'toggleAccountsVisibility', + label: localize('accounts', "Accounts"), + class: undefined, + tooltip: localize('accounts', "Accounts"), + checked: this.accountsVisibilityPreference, + enabled: true, + run: async () => this.accountsVisibilityPreference = !this.accountsVisibilityPreference, + dispose: () => { } + }); + actions.push(new Separator()); // Toggle Sidebar actions.push(this.instantiationService.createInstance(ToggleSidebarPositionAction, ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.getLabel(this.layoutService))); // Toggle Activity Bar - actions.push(new Action( - ToggleActivityBarVisibilityAction.ID, - nls.localize('hideActivitBar', "Hide Activity Bar"), - undefined, - true, - async () => { this.instantiationService.invokeFunction(accessor => new ToggleActivityBarVisibilityAction().run(accessor)); } - )); - - return actions; + actions.push(toAction({ id: ToggleActivityBarVisibilityAction.ID, label: localize('hideActivitBar', "Hide Activity Bar"), run: async () => this.instantiationService.invokeFunction(accessor => new ToggleActivityBarVisibilityAction().run(accessor)) })); }, getContextMenuActionsForComposite: compositeId => this.getContextMenuActionsForComposite(compositeId), getDefaultCompositeId: () => this.viewDescriptorService.getDefaultViewContainer(this.location)!.id, @@ -223,24 +233,20 @@ export class ActivitybarPart extends Part implements IActivityBarService { })); } - private getContextMenuActionsForComposite(compositeId: string): Action[] { - const actions = []; + private getContextMenuActionsForComposite(compositeId: string): IAction[] { + const actions: IAction[] = []; const viewContainer = this.viewDescriptorService.getViewContainerById(compositeId)!; const defaultLocation = this.viewDescriptorService.getDefaultViewContainerLocation(viewContainer)!; if (defaultLocation !== this.viewDescriptorService.getViewContainerLocation(viewContainer)) { - actions.push(new Action('resetLocationAction', nls.localize('resetLocation', "Reset Location"), undefined, true, async () => { - this.viewDescriptorService.moveViewContainerToLocation(viewContainer, defaultLocation); - })); + actions.push(toAction({ id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => this.viewDescriptorService.moveViewContainerToLocation(viewContainer, defaultLocation) })); } else { const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); if (viewContainerModel.allViewDescriptors.length === 1) { const viewToReset = viewContainerModel.allViewDescriptors[0]; const defaultContainer = this.viewDescriptorService.getDefaultContainerById(viewToReset.id)!; if (defaultContainer !== viewContainer) { - actions.push(new Action('resetLocationAction', nls.localize('resetLocation', "Reset Location"), undefined, true, async () => { - this.viewDescriptorService.moveViewsToContainer([viewToReset], defaultContainer); - })); + actions.push(toAction({ id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => this.viewDescriptorService.moveViewsToContainer([viewToReset], defaultContainer) })); } } } @@ -278,7 +284,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { })); } - private onDidChangeViewContainers(added: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation }>, removed: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation }>) { + private onDidChangeViewContainers(added: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation; }>, removed: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation; }>) { removed.filter(({ location }) => location === ViewContainerLocation.Sidebar).forEach(({ container }) => this.onDidDeregisterViewContainer(container)); this.onDidRegisterViewContainers(added.filter(({ location }) => location === ViewContainerLocation.Sidebar).map(({ container }) => container)); } @@ -310,7 +316,22 @@ export class ActivitybarPart extends Part implements IActivityBarService { } private onDidRegisterExtensions(): void { - this.removeNotExistingComposites(); + this.hasExtensionsRegistered = true; + + // show/hide/remove composites + for (const { id } of this.cachedViewContainers) { + const viewContainer = this.getViewContainer(id); + if (viewContainer) { + this.showOrHideViewContainer(viewContainer); + } else { + if (this.viewDescriptorService.isViewContainerRemovedPermanently(id)) { + this.removeComposite(id); + } else { + this.hideComposite(id); + } + } + } + this.saveCachedViewContainers(); } @@ -322,7 +343,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.compositeBar.addComposite(viewContainer); this.compositeBar.activateComposite(viewContainer.id); - if (viewContainer.hideIfEmpty) { + if (this.shouldBeHidden(viewContainer)) { const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); if (viewContainerModel.activeViewDescriptors.length === 0) { // Update the composite bar by hiding @@ -460,7 +481,8 @@ export class ActivitybarPart extends Part implements IActivityBarService { // Home action bar const homeIndicator = this.environmentService.options?.homeIndicator; - if (homeIndicator) { + // TODO @sbatten remove the fake setting and associated code + if (homeIndicator && this.configurationService.getValue('window.showHomeIndicator')) { let codicon = iconRegistry.get(homeIndicator.icon); if (!codicon) { codicon = Codicon.code; @@ -556,14 +578,14 @@ export class ActivitybarPart extends Part implements IActivityBarService { private createHomeBar(href: string, icon: Codicon): void { this.homeBarContainer = document.createElement('div'); - this.homeBarContainer.setAttribute('aria-label', nls.localize('homeIndicator', "Home")); + this.homeBarContainer.setAttribute('aria-label', localize('homeIndicator', "Home")); this.homeBarContainer.setAttribute('role', 'toolbar'); this.homeBarContainer.classList.add('home-bar'); this.homeBar = this._register(new ActionBar(this.homeBarContainer, { - actionViewItemProvider: action => this.instantiationService.createInstance(HomeActivityActionViewItem, href, action as ActivityAction, (theme: IColorTheme) => this.getActivitybarItemColors(theme)), + actionViewItemProvider: action => this.instantiationService.createInstance(HomeActivityActionViewItem, href, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme)), orientation: ActionsOrientation.VERTICAL, - ariaLabel: nls.localize('home', "Home"), + ariaLabel: localize('home', "Home"), animated: false, preventLoopNavigation: true, ignoreOrientationForPreviousAndNextKey: true @@ -575,7 +597,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.homeBar.push(this._register(new ActivityAction({ id: 'workbench.actions.home', - name: nls.localize('home', "Home"), + name: localize('home', "Home"), cssClass: icon.classNames }))); @@ -587,17 +609,17 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.globalActivityActionBar = this._register(new ActionBar(container, { actionViewItemProvider: action => { if (action.id === 'workbench.actions.manage') { - return this.instantiationService.createInstance(GlobalActivityActionViewItem, action as ActivityAction, (theme: IColorTheme) => this.getActivitybarItemColors(theme)); + return this.instantiationService.createInstance(GlobalActivityActionViewItem, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme)); } if (action.id === 'workbench.actions.accounts') { - return this.instantiationService.createInstance(AccountsActivityActionViewItem, action as ActivityAction, (theme: IColorTheme) => this.getActivitybarItemColors(theme)); + return this.instantiationService.createInstance(AccountsActivityActionViewItem, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme)); } throw new Error(`No view item for action '${action.id}'`); }, orientation: ActionsOrientation.VERTICAL, - ariaLabel: nls.localize('manage', "Manage"), + ariaLabel: localize('manage', "Manage"), animated: false, preventLoopNavigation: true, ignoreOrientationForPreviousAndNextKey: true @@ -605,15 +627,15 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.globalActivityAction = this._register(new ActivityAction({ id: 'workbench.actions.manage', - name: nls.localize('manage', "Manage"), - cssClass: ThemeIcon.asClassName(settingsViewBarIcon) + name: localize('manage', "Manage"), + cssClass: ThemeIcon.asClassName(ActivitybarPart.GEAR_ICON) })); if (this.accountsVisibilityPreference) { this.accountsActivityAction = this._register(new ActivityAction({ id: 'workbench.actions.accounts', - name: nls.localize('accounts', "Accounts"), - cssClass: ThemeIcon.asClassName(accountsViewBarIcon) + name: localize('accounts', "Accounts"), + cssClass: ThemeIcon.asClassName(ActivitybarPart.ACCOUNTS_ICON) })); this.globalActivityActionBar.push(this.accountsActivityAction, { index: ActivitybarPart.ACCOUNTS_ACTION_INDEX }); @@ -630,7 +652,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { } else { this.accountsActivityAction = this._register(new ActivityAction({ id: 'workbench.actions.accounts', - name: nls.localize('accounts', "Accounts"), + name: localize('accounts', "Accounts"), cssClass: Codicon.account.classNames })); this.globalActivityActionBar.push(this.accountsActivityAction, { index: ActivitybarPart.ACCOUNTS_ACTION_INDEX }); @@ -640,7 +662,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.updateGlobalActivity(ACCOUNTS_ACTIVITY_ID); } - private getCompositeActions(compositeId: string): { activityAction: ViewContainerActivityAction, pinnedAction: ToggleCompositePinnedAction } { + private getCompositeActions(compositeId: string): { activityAction: ViewContainerActivityAction, pinnedAction: ToggleCompositePinnedAction; } { let compositeActions = this.compositeActions.get(compositeId); if (!compositeActions) { const viewContainer = this.getViewContainer(compositeId); @@ -666,32 +688,27 @@ export class ActivitybarPart extends Part implements IActivityBarService { private onDidRegisterViewContainers(viewContainers: ReadonlyArray): void { for (const viewContainer of viewContainers) { + this.compositeBar.addComposite(viewContainer); + + // Pin it by default if it is new const cachedViewContainer = this.cachedViewContainers.filter(({ id }) => id === viewContainer.id)[0]; - const visibleViewContainer = this.viewsService.getVisibleViewContainer(this.location); - const isActive = visibleViewContainer?.id === viewContainer.id; - - if (isActive || !this.shouldBeHidden(viewContainer.id, cachedViewContainer)) { - this.compositeBar.addComposite(viewContainer); - - // Pin it by default if it is new - if (!cachedViewContainer) { - this.compositeBar.pin(viewContainer.id); - } - - if (isActive) { - this.compositeBar.activateComposite(viewContainer.id); - } + if (!cachedViewContainer) { + this.compositeBar.pin(viewContainer.id); + } + + // Active + const visibleViewContainer = this.viewsService.getVisibleViewContainer(this.location); + if (visibleViewContainer?.id === viewContainer.id) { + this.compositeBar.activateComposite(viewContainer.id); } - } - for (const viewContainer of viewContainers) { const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); this.updateActivity(viewContainer, viewContainerModel); - this.onDidChangeActiveViews(viewContainer, viewContainerModel); + this.showOrHideViewContainer(viewContainer); const disposables = new DisposableStore(); disposables.add(viewContainerModel.onDidChangeContainerInfo(() => this.updateActivity(viewContainer, viewContainerModel))); - disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.onDidChangeActiveViews(viewContainer, viewContainerModel))); + disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.showOrHideViewContainer(viewContainer))); this.viewContainerDisposables.set(viewContainer.id, disposables); } @@ -728,12 +745,15 @@ export class ActivitybarPart extends Part implements IActivityBarService { let iconUrl: URI | undefined = undefined; if (URI.isUri(icon)) { iconUrl = icon; - cssClass = `activity-${id.replace(/\./g, '-')}`; + const cssUrl = asCSSUrl(icon); + const hash = new StringSHA1(); + hash.update(cssUrl); + cssClass = `activity-${id.replace(/\./g, '-')}-${hash.digest()}`; const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${cssClass}`; createCSSRule(iconClass, ` - mask: ${asCSSUrl(icon)} no-repeat 50% 50%; + mask: ${cssUrl} no-repeat 50% 50%; mask-size: 24px; - -webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%; + -webkit-mask: ${cssUrl} no-repeat 50% 50%; -webkit-mask-size: 24px; `); } else if (ThemeIcon.isThemeIcon(icon)) { @@ -743,36 +763,43 @@ export class ActivitybarPart extends Part implements IActivityBarService { return { id, name, cssClass, iconUrl, keybindingId }; } - private onDidChangeActiveViews(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void { - if (viewContainerModel.activeViewDescriptors.length) { - this.compositeBar.addComposite(viewContainer); - } else if (viewContainer.hideIfEmpty) { + private showOrHideViewContainer(viewContainer: ViewContainer): void { + if (this.shouldBeHidden(viewContainer)) { this.hideComposite(viewContainer.id); + } else { + this.compositeBar.addComposite(viewContainer); } } - private shouldBeHidden(viewContainerId: string, cachedViewContainer?: ICachedViewContainer): boolean { - const viewContainer = this.getViewContainer(viewContainerId); - if (!viewContainer || !viewContainer.hideIfEmpty) { - return false; - } + private shouldBeHidden(viewContainerOrId: string | ViewContainer, cachedViewContainer?: ICachedViewContainer): boolean { + const viewContainer = isString(viewContainerOrId) ? this.getViewContainer(viewContainerOrId) : viewContainerOrId; + const viewContainerId = isString(viewContainerOrId) ? viewContainerOrId : viewContainerOrId.id; - return cachedViewContainer?.views && cachedViewContainer.views.length - ? cachedViewContainer.views.every(({ when }) => !!when && !this.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(when))) - : viewContainerId === TEST_VIEW_CONTAINER_ID /* Hide Test view container for the first time or it had no views registered before */; - } - - private removeNotExistingComposites(): void { - const viewContainers = this.getViewContainers(); - for (const { id } of this.cachedViewContainers) { - if (viewContainers.every(viewContainer => viewContainer.id !== id)) { - if (this.viewDescriptorService.isViewContainerRemovedPermanently(id)) { - this.removeComposite(id); - } else { - this.hideComposite(id); + if (viewContainer) { + if (viewContainer.hideIfEmpty) { + if (this.viewDescriptorService.getViewContainerModel(viewContainer).activeViewDescriptors.length > 0) { + return false; } + } else { + return false; } } + + // Check cache only if extensions are not yet registered and current window is not native (desktop) remote connection window + if (!this.hasExtensionsRegistered && !(this.environmentService.remoteAuthority && isNative)) { + cachedViewContainer = cachedViewContainer || this.cachedViewContainers.find(({ id }) => id === viewContainerId); + + // Show builtin ViewContainer if not registered yet + if (!viewContainer && cachedViewContainer?.isBuiltin) { + return false; + } + + if (cachedViewContainer?.views?.length) { + return cachedViewContainer.views.every(({ when }) => !!when && !this.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(when))); + } + } + + return true; } private hideComposite(compositeId: string): void { @@ -864,7 +891,6 @@ export class ActivitybarPart extends Part implements IActivityBarService { private getViewContainer(id: string): ViewContainer | undefined { const viewContainer = this.viewDescriptorService.getViewContainerById(id); - return viewContainer && this.viewDescriptorService.getViewContainerLocation(viewContainer) === this.location ? viewContainer : undefined; } @@ -918,22 +944,22 @@ export class ActivitybarPart extends Part implements IActivityBarService { const viewContainer = this.getViewContainer(compositeItem.id); if (viewContainer) { const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); - const views: { when: string | undefined }[] = []; + const views: { when: string | undefined; }[] = []; for (const { when } of viewContainerModel.allViewDescriptors) { views.push({ when: when ? when.serialize() : undefined }); } - const cacheIcon = URI.isUri(viewContainerModel.icon) ? viewContainerModel.icon.scheme === Schemas.file : true; state.push({ id: compositeItem.id, name: viewContainerModel.title, - icon: cacheIcon ? viewContainerModel.icon : undefined, + icon: URI.isUri(viewContainerModel.icon) && this.environmentService.remoteAuthority && isNative ? undefined : viewContainerModel.icon, /* Donot cache uri icons in desktop with remote connection */ views, pinned: compositeItem.pinned, order: compositeItem.order, - visible: compositeItem.visible + visible: compositeItem.visible, + isBuiltin: !viewContainer.extensionId }); } else { - state.push({ id: compositeItem.id, pinned: compositeItem.pinned, order: compositeItem.order, visible: false }); + state.push({ id: compositeItem.id, pinned: compositeItem.pinned, order: compositeItem.order, visible: false, isBuiltin: false }); } } @@ -948,9 +974,10 @@ export class ActivitybarPart extends Part implements IActivityBarService { const cachedViewContainer = this._cachedViewContainers.filter(cached => cached.id === placeholderViewContainer.id)[0]; if (cachedViewContainer) { cachedViewContainer.name = placeholderViewContainer.name; - cachedViewContainer.icon = placeholderViewContainer.themeIcon ? ThemeIcon.revive(placeholderViewContainer.themeIcon) : + cachedViewContainer.icon = placeholderViewContainer.themeIcon ? placeholderViewContainer.themeIcon : placeholderViewContainer.iconUrl ? URI.revive(placeholderViewContainer.iconUrl) : undefined; cachedViewContainer.views = placeholderViewContainer.views; + cachedViewContainer.isBuiltin = placeholderViewContainer.isBuiltin; } } } @@ -966,11 +993,12 @@ export class ActivitybarPart extends Part implements IActivityBarService { order }))); - this.setPlaceholderViewContainers(cachedViewContainers.map(({ id, icon, name, views }) => ({ + this.setPlaceholderViewContainers(cachedViewContainers.map(({ id, icon, name, views, isBuiltin }) => ({ id, iconUrl: URI.isUri(icon) ? icon : undefined, themeIcon: ThemeIcon.isThemeIcon(icon) ? icon : undefined, name, + isBuiltin, views }))); } @@ -1067,7 +1095,7 @@ class FocusActivityBarAction extends Action2 { constructor() { super({ id: 'workbench.action.focusActivityBar', - title: { value: nls.localize('focusActivityBar', "Focus Activity Bar"), original: 'Focus Activity Bar' }, + title: { value: localize('focusActivityBar', "Focus Activity Bar"), original: 'Focus Activity Bar' }, category: CATEGORIES.View, f1: true }); diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 06a031a56..70d629fb2 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { Action, IAction, Separator } from 'vs/base/common/actions'; +import { IAction, toAction } from 'vs/base/common/actions'; import { illegalArgument } from 'vs/base/common/errors'; import * as arrays from 'vs/base/common/arrays'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -149,10 +149,10 @@ export interface ICompositeBarOptions { readonly preventLoopNavigation?: boolean; getActivityAction: (compositeId: string) => ActivityAction; - getCompositePinnedAction: (compositeId: string) => Action; - getOnCompositeClickAction: (compositeId: string) => Action; - getContextMenuActions: () => Action[]; - getContextMenuActionsForComposite: (compositeId: string) => Action[]; + getCompositePinnedAction: (compositeId: string) => IAction; + getOnCompositeClickAction: (compositeId: string) => IAction; + fillExtraContextMenuActions: (actions: IAction[]) => void; + getContextMenuActionsForComposite: (compositeId: string) => IAction[]; openComposite: (compositeId: string) => Promise; getDefaultCompositeId: () => string; hidePart: () => void; @@ -208,15 +208,15 @@ export class CompositeBar extends Widget implements ICompositeBar { create(parent: HTMLElement): HTMLElement { const actionBarDiv = parent.appendChild($('.composite-bar')); this.compositeSwitcherBar = this._register(new ActionBar(actionBarDiv, { - actionViewItemProvider: (action: IAction) => { + actionViewItemProvider: action => { if (action instanceof CompositeOverflowActivityAction) { return this.compositeOverflowActionViewItem; } const item = this.model.findItem(action.id); return item && this.instantiationService.createInstance( CompositeActionViewItem, action as ActivityAction, item.pinnedAction, - (compositeId: string) => this.options.getContextMenuActionsForComposite(compositeId), - () => this.getContextMenuActions() as Action[], + compositeId => this.options.getContextMenuActionsForComposite(compositeId), + () => this.getContextMenuActions(), this.options.colors, this.options.icon, this.options.dndHandler, @@ -319,7 +319,7 @@ export class CompositeBar extends Widget implements ICompositeBar { this.updateCompositeSwitcher(); } - addComposite({ id, name, order, requestedIndex }: { id: string; name: string, order?: number, requestedIndex?: number }): void { + addComposite({ id, name, order, requestedIndex }: { id: string; name: string, order?: number, requestedIndex?: number; }): void { // Add to the model if (this.model.add(id, name, order, requestedIndex)) { this.computeSizes([this.model.findItem(id)]); @@ -596,7 +596,7 @@ export class CompositeBar extends Widget implements ICompositeBar { this.compositeOverflowAction, () => this.getOverflowingComposites(), () => this.model.activeItem ? this.model.activeItem.id : undefined, - (compositeId: string) => { + compositeId => { const item = this.model.findItem(compositeId); return item?.activity[0]?.badge; }, @@ -610,7 +610,7 @@ export class CompositeBar extends Widget implements ICompositeBar { this._onDidChange.fire(); } - private getOverflowingComposites(): { id: string, name?: string }[] { + private getOverflowingComposites(): { id: string, name?: string; }[] { let overflowingIds = this.model.visibleItems.filter(item => item.pinned).map(item => item.id); // Show the active composite even if it is not pinned @@ -631,9 +631,9 @@ export class CompositeBar extends Widget implements ICompositeBar { }); } - private getContextMenuActions(): IAction[] { + getContextMenuActions(): IAction[] { const actions: IAction[] = this.model.visibleItems - .map(({ id, name, activityAction }) => ({ + .map(({ id, name, activityAction }) => (toAction({ id, label: this.getAction(id).label || name || id, checked: this.isPinned(id), @@ -645,19 +645,17 @@ export class CompositeBar extends Widget implements ICompositeBar { this.pin(id, true); } } - })); - const otherActions = this.options.getContextMenuActions(); - if (otherActions.length) { - actions.push(new Separator()); - actions.push(...otherActions); - } + }))); + + this.options.fillExtraContextMenuActions(actions); + return actions; } } interface ICompositeBarModelItem extends ICompositeBarItem { activityAction: ActivityAction; - pinnedAction: Action; + pinnedAction: IAction; activity: ICompositeActivity[]; } diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index be07132e5..779b309c5 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { Action, Separator } from 'vs/base/common/actions'; +import { Action, IAction, Separator } from 'vs/base/common/actions'; import * as dom from 'vs/base/browser/dom'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { dispose, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; @@ -374,14 +374,14 @@ export class CompositeOverflowActivityAction extends ActivityAction { } export class CompositeOverflowActivityActionViewItem extends ActivityActionViewItem { - private actions: Action[] = []; + private actions: IAction[] = []; constructor( action: ActivityAction, private getOverflowingComposites: () => { id: string, name?: string }[], private getActiveCompositeId: () => string | undefined, private getBadge: (compositeId: string) => IBadge, - private getCompositeOpenAction: (compositeId: string) => Action, + private getCompositeOpenAction: (compositeId: string) => IAction, colors: (theme: IColorTheme) => ICompositeBarColors, @IContextMenuService private readonly contextMenuService: IContextMenuService, @IThemeService themeService: IThemeService @@ -404,7 +404,7 @@ export class CompositeOverflowActivityActionViewItem extends ActivityActionViewI }); } - private getActions(): Action[] { + private getActions(): IAction[] { return this.getOverflowingComposites().map(composite => { const action = this.getCompositeOpenAction(composite.id); action.checked = this.getActiveCompositeId() === action.id; @@ -457,9 +457,9 @@ export class CompositeActionViewItem extends ActivityActionViewItem { constructor( private compositeActivityAction: ActivityAction, - private toggleCompositePinnedAction: Action, - private compositeContextMenuActionsProvider: (compositeId: string) => ReadonlyArray, - private contextMenuActionsProvider: () => ReadonlyArray, + private toggleCompositePinnedAction: IAction, + private compositeContextMenuActionsProvider: (compositeId: string) => IAction[], + private contextMenuActionsProvider: () => IAction[], colors: (theme: IColorTheme) => ICompositeBarColors, icon: boolean, private dndHandler: ICompositeDragAndDrop, @@ -500,9 +500,14 @@ export class CompositeActionViewItem extends ActivityActionViewItem { return this.compositeActivity; } - private getActivtyName(): string { + private getActivtyName(skipKeybinding = false): string { + let name = this.compositeActivityAction.activity.name; + if (skipKeybinding) { + return name; + } + const keybinding = this.compositeActivityAction.activity.keybindingId ? this.keybindingService.lookupKeybinding(this.compositeActivityAction.activity.keybindingId) : null; - return keybinding ? nls.localize('titleKeybinding', "{0} ({1})", this.compositeActivityAction.activity.name, keybinding.getLabel()) : this.compositeActivityAction.activity.name; + return keybinding ? nls.localize('titleKeybinding', "{0} ({1})", name, keybinding.getLabel()) : name; } render(container: HTMLElement): void { @@ -601,7 +606,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem { } private showContextMenu(container: HTMLElement): void { - const actions: Action[] = [this.toggleCompositePinnedAction]; + const actions: IAction[] = [this.toggleCompositePinnedAction]; const compositeContextMenuActions = this.compositeContextMenuActionsProvider(this.activity.id); if (compositeContextMenuActions.length) { @@ -615,10 +620,10 @@ export class CompositeActionViewItem extends ActivityActionViewItem { const isPinned = this.compositeBar.isPinned(this.activity.id); if (isPinned) { - this.toggleCompositePinnedAction.label = nls.localize('hide', "Hide"); + this.toggleCompositePinnedAction.label = nls.localize('hide', "Hide '{0}'", this.getActivtyName(true)); this.toggleCompositePinnedAction.checked = false; } else { - this.toggleCompositePinnedAction.label = nls.localize('keep', "Keep"); + this.toggleCompositePinnedAction.label = nls.localize('keep', "Keep '{0}'", this.getActivtyName(true)); } const otherActions = this.contextMenuActionsProvider(); diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 1350ddfe5..13fe045e5 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -327,10 +327,6 @@ export abstract class CompositePart extends Part { const primaryActions: IAction[] = composite?.getActions().slice(0) || []; const secondaryActions: IAction[] = composite?.getSecondaryActions().slice(0) || []; - // From Part - primaryActions.push(...this.getActions()); - secondaryActions.push(...this.getSecondaryActions()); - // Update context const toolBar = assertIsDefined(this.toolBar); toolBar.context = this.actionsContextProvider(); @@ -471,14 +467,6 @@ export abstract class CompositePart extends Part { return compositeItem ? compositeItem.progress : undefined; } - protected getActions(): ReadonlyArray { - return []; - } - - protected getSecondaryActions(): ReadonlyArray { - return []; - } - protected getTitleAreaDropDownAnchorAlignment(): AnchorAlignment { return AnchorAlignment.RIGHT; } diff --git a/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts b/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts index b5fc1d1fa..836303dbf 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts @@ -19,9 +19,9 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { Disposable } from 'vs/base/common/lifecycle'; export class DialogHandlerContribution extends Disposable implements IWorkbenchContribution { - private impl: IDialogHandler; + private readonly model: IDialogsModel; + private readonly impl: IDialogHandler; - private model: IDialogsModel; private currentDialog: IDialogViewItem | undefined; constructor( diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 541ea9b2c..026581b29 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -6,19 +6,13 @@ import * as dom from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { BreadcrumbsItem, BreadcrumbsWidget, IBreadcrumbsItemEvent } from 'vs/base/browser/ui/breadcrumbs/breadcrumbsWidget'; -import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { tail } from 'vs/base/common/arrays'; import { timeout } from 'vs/base/common/async'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { combinedDisposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { extUri } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/breadcrumbscontrol'; -import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; -import { Range } from 'vs/editor/common/core/range'; -import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; -import { SymbolKinds } from 'vs/editor/common/modes'; -import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { localize } from 'vs/nls'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -28,35 +22,92 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IListService, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; +import { IListService, WorkbenchDataTree, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { ColorIdentifier, ColorFunction } from 'vs/platform/theme/common/colorRegistry'; import { attachBreadcrumbsStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ResourceLabel } from 'vs/workbench/browser/labels'; import { BreadcrumbsConfig, IBreadcrumbsService } from 'vs/workbench/browser/parts/editor/breadcrumbs'; -import { BreadcrumbElement, EditorBreadcrumbsModel, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel'; -import { BreadcrumbsPicker, createBreadcrumbsPicker } from 'vs/workbench/browser/parts/editor/breadcrumbsPicker'; +import { BreadcrumbsModel, FileElement, OutlineElement2 } from 'vs/workbench/browser/parts/editor/breadcrumbsModel'; +import { BreadcrumbsFilePicker, BreadcrumbsOutlinePicker, BreadcrumbsPicker } from 'vs/workbench/browser/parts/editor/breadcrumbsPicker'; import { IEditorPartOptions, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { ACTIVE_GROUP, ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { onDidChangeZoomLevel } from 'vs/base/browser/browser'; -import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; import { ILabelService } from 'vs/platform/label/common/label'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; -import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; import { CATEGORIES } from 'vs/workbench/common/actions'; +import { ITreeNode } from 'vs/base/browser/ui/tree/tree'; +import { IOutline } from 'vs/workbench/services/outline/browser/outline'; -class Item extends BreadcrumbsItem { +class OutlineItem extends BreadcrumbsItem { private readonly _disposables = new DisposableStore(); constructor( - readonly element: BreadcrumbElement, + readonly model: BreadcrumbsModel, + readonly element: OutlineElement2, + readonly options: IBreadcrumbsControlOptions + ) { + super(); + } + + dispose(): void { + this._disposables.dispose(); + } + + equals(other: BreadcrumbsItem): boolean { + if (!(other instanceof OutlineItem)) { + return false; + } + return this.element === other.element && + this.options.showFileIcons === other.options.showFileIcons && + this.options.showSymbolIcons === other.options.showSymbolIcons; + } + + render(container: HTMLElement): void { + const { element, outline } = this.element; + + if (element === outline) { + const element = dom.$('span', undefined, '…'); + container.appendChild(element); + return; + } + + const templateId = outline.config.delegate.getTemplateId(element); + const renderer = outline.config.renderers.find(renderer => renderer.templateId === templateId); + if (!renderer) { + container.innerText = '<>'; + return; + } + + const template = renderer.renderTemplate(container); + renderer.renderElement(>{ + element, + children: [], + depth: 0, + visibleChildrenCount: 0, + visibleChildIndex: 0, + collapsible: false, + collapsed: false, + visible: true, + filterData: undefined + }, 0, template, undefined); + + this._disposables.add(toDisposable(() => { renderer.disposeTemplate(template); })); + } + +} + +class FileItem extends BreadcrumbsItem { + + private readonly _disposables = new DisposableStore(); + + constructor( + readonly model: BreadcrumbsModel, + readonly element: FileElement, readonly options: IBreadcrumbsControlOptions, @IInstantiationService private readonly _instantiationService: IInstantiationService ) { @@ -68,59 +119,26 @@ class Item extends BreadcrumbsItem { } equals(other: BreadcrumbsItem): boolean { - if (!(other instanceof Item)) { + if (!(other instanceof FileItem)) { return false; } - if (this.element instanceof FileElement && other.element instanceof FileElement) { - return (extUri.isEqual(this.element.uri, other.element.uri) && - this.options.showFileIcons === other.options.showFileIcons && - this.options.showSymbolIcons === other.options.showSymbolIcons); - } - if (this.element instanceof TreeElement && other.element instanceof TreeElement) { - return this.element.id === other.element.id; - } - return false; + return (extUri.isEqual(this.element.uri, other.element.uri) && + this.options.showFileIcons === other.options.showFileIcons && + this.options.showSymbolIcons === other.options.showSymbolIcons); + } render(container: HTMLElement): void { - if (this.element instanceof FileElement) { - // file/folder - let label = this._instantiationService.createInstance(ResourceLabel, container, {}); - label.element.setFile(this.element.uri, { - hidePath: true, - hideIcon: this.element.kind === FileKind.FOLDER || !this.options.showFileIcons, - fileKind: this.element.kind, - fileDecorations: { colors: this.options.showDecorationColors, badges: false }, - }); - container.classList.add(FileKind[this.element.kind].toLowerCase()); - this._disposables.add(label); - - } else if (this.element instanceof OutlineModel) { - // has outline element but not in one - let label = document.createElement('div'); - label.innerText = '\u2026'; - label.className = 'hint-more'; - container.appendChild(label); - - } else if (this.element instanceof OutlineGroup) { - // provider - let label = new IconLabel(container); - label.setLabel(this.element.label); - this._disposables.add(label); - - } else if (this.element instanceof OutlineElement) { - // symbol - if (this.options.showSymbolIcons) { - let icon = document.createElement('div'); - icon.className = SymbolKinds.toCssClassName(this.element.symbol.kind); - container.appendChild(icon); - container.classList.add('shows-symbol-icon'); - } - let label = new IconLabel(container); - let title = this.element.symbol.name.replace(/\r|\n|\r\n/g, '\u23CE'); - label.setLabel(title); - this._disposables.add(label); - } + // file/folder + let label = this._instantiationService.createInstance(ResourceLabel, container, {}); + label.element.setFile(this.element.uri, { + hidePath: true, + hideIcon: this.element.kind === FileKind.FOLDER || !this.options.showFileIcons, + fileKind: this.element.kind, + fileDecorations: { colors: this.options.showDecorationColors, badges: false }, + }); + container.classList.add(FileKind[this.element.kind].toLowerCase()); + this._disposables.add(label); } } @@ -170,26 +188,23 @@ export class BreadcrumbsControl { private readonly _editorGroup: IEditorGroupView, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IContextViewService private readonly _contextViewService: IContextViewService, - @IEditorService private readonly _editorService: IEditorService, - @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, - @IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IThemeService private readonly _themeService: IThemeService, @IQuickInputService private readonly _quickInputService: IQuickInputService, - @IConfigurationService private readonly _configurationService: IConfigurationService, - @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, @IFileService private readonly _fileService: IFileService, @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IEditorService private readonly _editorService: IEditorService, @ILabelService private readonly _labelService: ILabelService, + @IConfigurationService configurationService: IConfigurationService, @IBreadcrumbsService breadcrumbsService: IBreadcrumbsService, ) { this.domNode = document.createElement('div'); this.domNode.classList.add('breadcrumbs-control'); dom.append(container, this.domNode); - this._cfUseQuickPick = BreadcrumbsConfig.UseQuickPick.bindTo(_configurationService); - this._cfShowIcons = BreadcrumbsConfig.Icons.bindTo(_configurationService); - this._cfTitleScrollbarSizing = BreadcrumbsConfig.TitleScrollbarSizing.bindTo(_configurationService); + this._cfUseQuickPick = BreadcrumbsConfig.UseQuickPick.bindTo(configurationService); + this._cfShowIcons = BreadcrumbsConfig.Icons.bindTo(configurationService); + this._cfTitleScrollbarSizing = BreadcrumbsConfig.TitleScrollbarSizing.bindTo(configurationService); const sizing = this._cfTitleScrollbarSizing.getValue() ?? 'default'; this._widget = new BreadcrumbsWidget(this.domNode, BreadcrumbsControl.SCROLLBAR_SIZES[sizing]); @@ -256,14 +271,11 @@ export class BreadcrumbsControl { this._ckBreadcrumbsVisible.set(true); this._ckBreadcrumbsPossible.set(true); - const editor = this._getActiveCodeEditor(); - const model = new EditorBreadcrumbsModel( + const model = this._instantiationService.createInstance(BreadcrumbsModel, fileInfoUri ?? uri, - uri, editor, - this._configurationService, - this._textResourceConfigurationService, - this._workspaceService + this._editorGroup.activeEditorPane ); + this.domNode.classList.toggle('relative-path', model.isRelative()); this.domNode.classList.toggle('backslash-path', this._labelService.getSeparator(uri.scheme, uri.authority) === '\\'); @@ -274,7 +286,7 @@ export class BreadcrumbsControl { showFileIcons: this._options.showFileIcons && showIcons, showSymbolIcons: this._options.showSymbolIcons && showIcons }; - const items = model.getElements().map(element => new Item(element, options, this._instantiationService)); + const items = model.getElements().map(element => element instanceof FileElement ? new FileItem(model, element, options, this._instantiationService) : new OutlineItem(model, element, options)); this._widget.setItems(items); this._widget.reveal(items[items.length - 1]); }; @@ -298,7 +310,7 @@ export class BreadcrumbsControl { this._breadcrumbsDisposables.add({ dispose: () => { if (this._breadcrumbsPickerShowing) { - this._contextViewService.hideContextView(this); + this._contextViewService.hideContextView({ source: this }); } } }); @@ -306,20 +318,6 @@ export class BreadcrumbsControl { return true; } - private _getActiveCodeEditor(): ICodeEditor | undefined { - if (!this._editorGroup.activeEditorPane) { - return undefined; - } - let control = this._editorGroup.activeEditorPane.getControl(); - let editor: ICodeEditor | undefined; - if (isCodeEditor(control)) { - editor = control as ICodeEditor; - } else if (isDiffEditor(control)) { - editor = control.getModifiedEditor(); - } - return editor; - } - private _onFocusEvent(event: IBreadcrumbsItemEvent): void { if (event.item && this._breadcrumbsPickerShowing) { this._breadcrumbsPickerIgnoreOnceItem = undefined; @@ -339,13 +337,12 @@ export class BreadcrumbsControl { return; } - const { element } = event.item as Item; + const { element } = event.item as FileItem | OutlineItem; this._editorGroup.focus(); - type BreadcrumbSelectClassification = { - type: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - }; - this._telemetryService.publicLog2<{ type: string }, BreadcrumbSelectClassification>('breadcrumbs/select', { type: element instanceof TreeElement ? 'symbol' : 'file' }); + type BreadcrumbSelect = { type: string }; + type BreadcrumbSelectClassification = { type: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; }; + this._telemetryService.publicLog2('breadcrumbs/select', { type: event.item instanceof OutlineItem ? 'symbol' : 'file' }); const group = this._getEditorGroup(event.payload); if (group !== undefined) { @@ -360,64 +357,31 @@ export class BreadcrumbsControl { // using quick pick this._widget.setFocused(undefined); this._widget.setSelection(undefined); - this._quickInputService.quickAccess.show(element instanceof TreeElement ? '@' : ''); + this._quickInputService.quickAccess.show(element instanceof OutlineElement2 ? '@' : ''); return; } // show picker let picker: BreadcrumbsPicker; let pickerAnchor: { x: number; y: number }; - let editor = this._getActiveCodeEditor(); - let editorDecorations: string[] = []; - let editorViewState: ICodeEditorViewState | undefined; + + interface IHideData { didPick?: boolean, source?: BreadcrumbsControl } this._contextViewService.showContextView({ render: (parent: HTMLElement) => { - picker = createBreadcrumbsPicker(this._instantiationService, parent, element); - let selectListener = picker.onDidPickElement(data => { - if (data.target) { - editorViewState = undefined; - } - this._contextViewService.hideContextView(this); + if (event.item instanceof FileItem) { + picker = this._instantiationService.createInstance(BreadcrumbsFilePicker, parent, event.item.model.resource); + } else if (event.item instanceof OutlineItem) { + picker = this._instantiationService.createInstance(BreadcrumbsOutlinePicker, parent, event.item.model.resource); + } - const group = (picker.useAltAsMultipleSelectionModifier && (data.browserEvent as MouseEvent).metaKey) || (!picker.useAltAsMultipleSelectionModifier && (data.browserEvent as MouseEvent).altKey) - ? SIDE_GROUP - : ACTIVE_GROUP; - - this._revealInEditor(event, data.target, group, (data.browserEvent as MouseEvent).button === 1); - /* __GDPR__ - "breadcrumbs/open" : { - "type": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this._telemetryService.publicLog('breadcrumbs/open', { type: !data ? 'nothing' : data.target instanceof TreeElement ? 'symbol' : 'file' }); - }); - let focusListener = picker.onDidFocusElement(data => { - if (!editor || !(data.target instanceof OutlineElement)) { - return; - } - if (!editorViewState) { - editorViewState = withNullAsUndefined(editor.saveViewState()); - } - const { symbol } = data.target; - editor.revealRangeInCenterIfOutsideViewport(symbol.range, ScrollType.Smooth); - editorDecorations = editor.deltaDecorations(editorDecorations, [{ - range: symbol.range, - options: { - className: 'rangeHighlight', - isWholeLine: true - } - }]); - }); - - let zoomListener = onDidChangeZoomLevel(() => { - this._contextViewService.hideContextView(this); - }); + let selectListener = picker.onWillPickElement(() => this._contextViewService.hideContextView({ source: this, didPick: true })); + let zoomListener = onDidChangeZoomLevel(() => this._contextViewService.hideContextView({ source: this })); let focusTracker = dom.trackFocus(parent); let blurListener = focusTracker.onDidBlur(() => { this._breadcrumbsPickerIgnoreOnceItem = this._widget.isDOMFocused() ? event.item : undefined; - this._contextViewService.hideContextView(this); + this._contextViewService.hideContextView({ source: this }); }); this._breadcrumbsPickerShowing = true; @@ -426,7 +390,6 @@ export class BreadcrumbsControl { return combinedDisposable( picker, selectListener, - focusListener, zoomListener, focusTracker, blurListener @@ -465,19 +428,17 @@ export class BreadcrumbsControl { } return pickerAnchor; }, - onHide: (data) => { - if (editor) { - editor.deltaDecorations(editorDecorations, []); - if (editorViewState) { - editor.restoreViewState(editorViewState); - } + onHide: (data?: IHideData) => { + if (!data?.didPick) { + picker.restoreViewState(); } this._breadcrumbsPickerShowing = false; this._updateCkBreadcrumbsActive(); - if (data === this) { + if (data?.source === this) { this._widget.setFocused(undefined); this._widget.setSelection(undefined); } + picker.dispose(); } }); } @@ -487,11 +448,11 @@ export class BreadcrumbsControl { this._ckBreadcrumbsActive.set(value); } - private _revealInEditor(event: IBreadcrumbsItemEvent, element: BreadcrumbElement, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined, pinned: boolean = false): void { + private async _revealInEditor(event: IBreadcrumbsItemEvent, element: FileElement | OutlineElement2, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined, pinned: boolean = false): Promise { + if (element instanceof FileElement) { if (element.kind === FileKind.FILE) { - // open file in any editor - this._editorService.openEditor({ resource: element.uri, options: { pinned } }, group); + await this._editorService.openEditor({ resource: element.uri, options: { pinned } }, group); } else { // show next picker let items = this._widget.getItems(); @@ -499,20 +460,8 @@ export class BreadcrumbsControl { this._widget.setFocused(items[idx + 1]); this._widget.setSelection(items[idx + 1], BreadcrumbsControl.Payload_Pick); } - - } else if (element instanceof OutlineElement) { - // open symbol in code editor - const model = OutlineModel.get(element); - if (model) { - this._codeEditorService.openCodeEditor({ - resource: model.uri, - options: { - selection: Range.collapseToStart(element.symbol.selectionRange), - selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport, - pinned - } - }, withUndefinedAsNull(this._getActiveCodeEditor()), group === SIDE_GROUP); - } + } else { + element.outline.reveal(element, { pinned }, group === SIDE_GROUP); } } @@ -744,29 +693,29 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler(accessor) { const editors = accessor.get(IEditorService); const lists = accessor.get(IListService); - const element = lists.lastFocusedList ? lists.lastFocusedList.getFocus()[0] : undefined; - if (element instanceof OutlineElement) { - const outlineElement = OutlineModel.get(element); - if (!outlineElement) { - return undefined; - } - // open symbol in editor - return editors.openEditor({ - resource: outlineElement.uri, - options: { selection: Range.collapseToStart(element.symbol.selectionRange), pinned: true } - }, SIDE_GROUP); + const tree = lists.lastFocusedList; + if (!(tree instanceof WorkbenchDataTree)) { + return; + } - } else if (element && URI.isUri(element.resource)) { - // open file in editor + const element = tree.getFocus()[0]; + + if (URI.isUri((element)?.resource)) { + // IFileStat: open file in editor return editors.openEditor({ - resource: element.resource, + resource: (element).resource, options: { pinned: true } }, SIDE_GROUP); + } - } else { - // ignore - return undefined; + // IOutline: check if this the outline and iff so reveal element + const input = tree.getInput(); + if (input && typeof (>input).outlineKind === 'string') { + return (>input).reveal(element, { + pinned: true, + preserveFocus: false + }, true); } } }); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts index 01ff3916e..4364969d2 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts @@ -3,27 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { equals } from 'vs/base/common/arrays'; -import { TimeoutTimer } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { isEqual, dirname } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IPosition } from 'vs/editor/common/core/position'; -import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes'; -import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { Schemas } from 'vs/base/common/network'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs'; import { FileKind } from 'vs/platform/files/common/files'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { OutlineFilter } from 'vs/editor/contrib/documentSymbols/outlineTree'; -import { ITextModel } from 'vs/editor/common/model'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; +import { IOutline, IOutlineService, OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; +import { IEditorPane } from 'vs/workbench/common/editor'; export class FileElement { constructor( @@ -32,11 +25,16 @@ export class FileElement { ) { } } -export type BreadcrumbElement = FileElement | OutlineModel | OutlineGroup | OutlineElement; - type FileInfo = { path: FileElement[], folder?: IWorkspaceFolder }; -export class EditorBreadcrumbsModel { +export class OutlineElement2 { + constructor( + readonly element: IOutline | any, + readonly outline: IOutline + ) { } +} + +export class BreadcrumbsModel { private readonly _disposables = new DisposableStore(); private readonly _fileInfo: FileInfo; @@ -45,28 +43,31 @@ export class EditorBreadcrumbsModel { private readonly _cfgFilePath: BreadcrumbsConfig<'on' | 'off' | 'last'>; private readonly _cfgSymbolPath: BreadcrumbsConfig<'on' | 'off' | 'last'>; - private _outlineElements: Array = []; - private _outlineDisposables = new DisposableStore(); + private readonly _currentOutline = new MutableDisposable>(); + private readonly _outlineDisposables = new DisposableStore(); private readonly _onDidUpdate = new Emitter(); readonly onDidUpdate: Event = this._onDidUpdate.event; constructor( - fileInfoUri: URI, - private readonly _uri: URI, - private readonly _editor: ICodeEditor | undefined, - @IConfigurationService private readonly _configurationService: IConfigurationService, - @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, - @IWorkspaceContextService workspaceService: IWorkspaceContextService, + readonly resource: URI, + editor: IEditorPane | undefined, + @IConfigurationService configurationService: IConfigurationService, + @IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService, + @IOutlineService private readonly _outlineService: IOutlineService, ) { - this._cfgEnabled = BreadcrumbsConfig.IsEnabled.bindTo(_configurationService); - this._cfgFilePath = BreadcrumbsConfig.FilePath.bindTo(_configurationService); - this._cfgSymbolPath = BreadcrumbsConfig.SymbolPath.bindTo(_configurationService); + this._cfgEnabled = BreadcrumbsConfig.IsEnabled.bindTo(configurationService); + this._cfgFilePath = BreadcrumbsConfig.FilePath.bindTo(configurationService); + this._cfgSymbolPath = BreadcrumbsConfig.SymbolPath.bindTo(configurationService); this._disposables.add(this._cfgFilePath.onDidChange(_ => this._onDidUpdate.fire(this))); this._disposables.add(this._cfgSymbolPath.onDidChange(_ => this._onDidUpdate.fire(this))); - this._fileInfo = EditorBreadcrumbsModel._initFilePathInfo(fileInfoUri, workspaceService); - this._bindToEditor(); + this._fileInfo = this._initFilePathInfo(resource); + + if (editor) { + this._bindToEditor(editor); + this._disposables.add(_outlineService.onDidChange(() => this._bindToEditor(editor))); + } this._onDidUpdate.fire(this); } @@ -74,6 +75,7 @@ export class EditorBreadcrumbsModel { this._cfgEnabled.dispose(); this._cfgFilePath.dispose(); this._cfgSymbolPath.dispose(); + this._currentOutline.dispose(); this._outlineDisposables.dispose(); this._disposables.dispose(); this._onDidUpdate.dispose(); @@ -83,8 +85,8 @@ export class EditorBreadcrumbsModel { return Boolean(this._fileInfo.folder); } - getElements(): ReadonlyArray { - let result: BreadcrumbElement[] = []; + getElements(): ReadonlyArray { + let result: (FileElement | OutlineElement2)[] = []; // file path elements if (this._cfgFilePath.getValue() === 'on') { @@ -93,17 +95,27 @@ export class EditorBreadcrumbsModel { result = result.concat(this._fileInfo.path.slice(-1)); } - // symbol path elements - if (this._cfgSymbolPath.getValue() === 'on') { - result = result.concat(this._outlineElements); - } else if (this._cfgSymbolPath.getValue() === 'last' && this._outlineElements.length > 0) { - result = result.concat(this._outlineElements.slice(-1)); + if (this._cfgSymbolPath.getValue() === 'off') { + return result; + } + + if (!this._currentOutline.value) { + return result; + } + + const breadcrumbsElements = this._currentOutline.value.config.breadcrumbsDataSource.getBreadcrumbElements(); + for (let i = this._cfgSymbolPath.getValue() === 'last' && breadcrumbsElements.length > 0 ? breadcrumbsElements.length - 1 : 0; i < breadcrumbsElements.length; i++) { + result.push(new OutlineElement2(breadcrumbsElements[i], this._currentOutline.value)); + } + + if (breadcrumbsElements.length === 0 && !this._currentOutline.value.isEmpty) { + result.push(new OutlineElement2(this._currentOutline.value, this._currentOutline.value)); } return result; } - private static _initFilePathInfo(uri: URI, workspaceService: IWorkspaceContextService): FileInfo { + private _initFilePathInfo(uri: URI): FileInfo { if (uri.scheme === Schemas.untitled) { return { @@ -113,7 +125,7 @@ export class EditorBreadcrumbsModel { } let info: FileInfo = { - folder: withNullAsUndefined(workspaceService.getWorkspaceFolder(uri)), + folder: withNullAsUndefined(this._workspaceService.getWorkspaceFolder(uri)), path: [] }; @@ -130,181 +142,33 @@ export class EditorBreadcrumbsModel { } } - if (info.folder && workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE) { + if (info.folder && this._workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE) { info.path.unshift(new FileElement(info.folder.uri, FileKind.ROOT_FOLDER)); } return info; } - private _bindToEditor(): void { - if (!this._editor) { - return; - } - // update as language, model, providers changes - this._disposables.add(DocumentSymbolProviderRegistry.onDidChange(_ => this._updateOutline())); - this._disposables.add(this._editor.onDidChangeModel(_ => this._updateOutline())); - this._disposables.add(this._editor.onDidChangeModelLanguage(_ => this._updateOutline())); - - // update when config changes (re-render) - this._disposables.add(this._configurationService.onDidChangeConfiguration(e => { - if (!this._cfgEnabled.getValue()) { - // breadcrumbs might be disabled (also via a setting/config) and that is - // something we must check before proceeding. - return; - } - if (e.affectsConfiguration('breadcrumbs')) { - this._updateOutline(true); - return; - } - if (this._editor && this._editor.getModel()) { - const editorModel = this._editor.getModel() as ITextModel; - const languageName = editorModel.getLanguageIdentifier().language; - - // Checking for changes in the current language override config. - // We can't be more specific than this because the ConfigurationChangeEvent(e) only includes the first part of the root path - if (e.affectsConfiguration(`[${languageName}]`)) { - this._updateOutline(true); - } - } - })); - - - // update soon'ish as model content change - const updateSoon = new TimeoutTimer(); - this._disposables.add(updateSoon); - this._disposables.add(this._editor.onDidChangeModelContent(_ => { - const timeout = OutlineModel.getRequestDelay(this._editor!.getModel()); - updateSoon.cancelAndSet(() => this._updateOutline(true), timeout); - })); - this._updateOutline(); - - // stop when editor dies - this._disposables.add(this._editor.onDidDispose(() => this._outlineDisposables.clear())); - } - - private _updateOutline(didChangeContent?: boolean): void { - + private _bindToEditor(editor: IEditorPane): void { + const newCts = new CancellationTokenSource(); + this._currentOutline.clear(); this._outlineDisposables.clear(); - if (!didChangeContent) { - this._updateOutlineElements([]); - } + this._outlineDisposables.add(toDisposable(() => newCts.dispose(true))); - const editor = this._editor!; - - const buffer = editor.getModel(); - if (!buffer || !DocumentSymbolProviderRegistry.has(buffer) || !isEqual(buffer.uri, this._uri)) { - return; - } - - const source = new CancellationTokenSource(); - const versionIdThen = buffer.getVersionId(); - const timeout = new TimeoutTimer(); - - this._outlineDisposables.add({ - dispose: () => { - source.dispose(true); - timeout.dispose(); + this._outlineService.createOutline(editor, OutlineTarget.Breadcrumbs, newCts.token).then(outline => { + if (newCts.token.isCancellationRequested) { + // cancelled: dispose new outline and reset + outline?.dispose(); + outline = undefined; } - }); - - OutlineModel.create(buffer, source.token).then(model => { - if (source.token.isCancellationRequested) { - // cancelled -> do nothing - return; + this._currentOutline.value = outline; + this._onDidUpdate.fire(this); + if (outline) { + this._outlineDisposables.add(outline.onDidChange(() => this._onDidUpdate.fire(this))); } - if (TreeElement.empty(model)) { - // empty -> no outline elements - this._updateOutlineElements([]); - } else { - // copy the model - model = model.adopt(); - - this._updateOutlineElements(this._getOutlineElements(model, editor.getPosition())); - this._outlineDisposables.add(editor.onDidChangeCursorPosition(_ => { - timeout.cancelAndSet(() => { - if (!buffer.isDisposed() && versionIdThen === buffer.getVersionId() && editor.getModel()) { - this._updateOutlineElements(this._getOutlineElements(model, editor.getPosition())); - } - }, 150); - })); - } }).catch(err => { - this._updateOutlineElements([]); + this._onDidUpdate.fire(this); onUnexpectedError(err); }); } - - private _getOutlineElements(model: OutlineModel, position: IPosition | null): Array { - if (!model || !position) { - return []; - } - let item: OutlineGroup | OutlineElement | undefined = model.getItemEnclosingPosition(position); - if (!item) { - return this._getOutlineElementsRoot(model); - } - let chain: Array = []; - while (item) { - chain.push(item); - let parent: any = item.parent; - if (parent instanceof OutlineModel) { - break; - } - if (parent instanceof OutlineGroup && parent.parent && parent.parent.children.size === 1) { - break; - } - item = parent; - } - let result: Array = []; - for (let i = chain.length - 1; i >= 0; i--) { - let element = chain[i]; - if (this._isFiltered(element)) { - break; - } - result.push(element); - } - if (result.length === 0) { - return this._getOutlineElementsRoot(model); - } - return result; - } - - private _getOutlineElementsRoot(model: OutlineModel): (OutlineModel | OutlineGroup | OutlineElement)[] { - for (const child of model.children.values()) { - if (!this._isFiltered(child)) { - return [model]; - } - } - return []; - } - - private _isFiltered(element: TreeElement): boolean { - if (element instanceof OutlineElement) { - const key = `breadcrumbs.${OutlineFilter.kindToConfigName[element.symbol.kind]}`; - let uri: URI | undefined; - if (this._editor && this._editor.getModel()) { - const model = this._editor.getModel() as ITextModel; - uri = model.uri; - } - return !this._textResourceConfigurationService.getValue(uri, key); - } - return false; - } - - private _updateOutlineElements(elements: Array): void { - if (!equals(elements, this._outlineElements, EditorBreadcrumbsModel._outlineElementEquals)) { - this._outlineElements = elements; - this._onDidUpdate.fire(this); - } - } - - private static _outlineElementEquals(a: OutlineModel | OutlineGroup | OutlineElement, b: OutlineModel | OutlineGroup | OutlineElement): boolean { - if (a === b) { - return true; - } else if (!a || !b) { - return false; - } else { - return a.id === b.id; - } - } } diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 001398ec0..223380e6c 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -8,34 +8,30 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import * as glob from 'vs/base/common/glob'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore, MutableDisposable, Disposable } from 'vs/base/common/lifecycle'; import { posix } from 'vs/base/common/path'; import { basename, dirname, isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/breadcrumbscontrol'; -import { OutlineElement, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; -import { IConfigurationService, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { WorkbenchDataTree, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { breadcrumbsPickerBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; -import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { isWorkspace, isWorkspaceFolder, IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ResourceLabels, IResourceLabel, DEFAULT_LABELS_CONTAINER } from 'vs/workbench/browser/labels'; import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs'; -import { BreadcrumbElement, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel'; +import { OutlineElement2, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel'; import { IAsyncDataSource, ITreeRenderer, ITreeNode, ITreeFilter, TreeVisibility, ITreeSorter } from 'vs/base/browser/ui/tree/tree'; -import { OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItemComparator, OutlineIdentityProvider, OutlineNavigationLabelProvider, OutlineDataSource, OutlineSortOrder, OutlineFilter, OutlineAccessibilityProvider } from 'vs/editor/contrib/documentSymbols/outlineTree'; import { IIdentityProvider, IListVirtualDelegate, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list'; import { IFileIconTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; -import { IModeService } from 'vs/editor/common/services/modeService'; import { localize } from 'vs/nls'; - -export function createBreadcrumbsPicker(instantiationService: IInstantiationService, parent: HTMLElement, element: BreadcrumbElement): BreadcrumbsPicker { - return element instanceof FileElement - ? instantiationService.createInstance(BreadcrumbsFilePicker, parent) - : instantiationService.createInstance(BreadcrumbsOutlinePicker, parent); -} +import { IOutline, IOutlineComparator } from 'vs/workbench/services/outline/browser/outline'; +import { IEditorOptions } from 'vs/platform/editor/common/editor'; +import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; interface ILayoutInfo { maxHeight: number; @@ -62,17 +58,18 @@ export abstract class BreadcrumbsPicker { protected _fakeEvent = new UIEvent('fakeEvent'); protected _layoutInfo!: ILayoutInfo; - private readonly _onDidPickElement = new Emitter(); - readonly onDidPickElement: Event = this._onDidPickElement.event; + protected readonly _onWillPickElement = new Emitter(); + readonly onWillPickElement: Event = this._onWillPickElement.event; - private readonly _onDidFocusElement = new Emitter(); - readonly onDidFocusElement: Event = this._onDidFocusElement.event; + private readonly _previewDispoables = new MutableDisposable(); constructor( parent: HTMLElement, + protected resource: URI, @IInstantiationService protected readonly _instantiationService: IInstantiationService, @IThemeService protected readonly _themeService: IThemeService, @IConfigurationService protected readonly _configurationService: IConfigurationService, + @ITelemetryService private readonly _telemetryService: ITelemetryService, ) { this._domNode = document.createElement('div'); this._domNode.className = 'monaco-breadcrumbs-picker show-file-icons'; @@ -81,12 +78,13 @@ export abstract class BreadcrumbsPicker { dispose(): void { this._disposables.dispose(); - this._onDidPickElement.dispose(); - this._onDidFocusElement.dispose(); - this._tree.dispose(); + this._previewDispoables.dispose(); + this._onWillPickElement.dispose(); + this._domNode.remove(); + setTimeout(() => this._tree.dispose(), 0); // tree cannot be disposed while being opened... } - show(input: any, maxHeight: number, width: number, arrowSize: number, arrowOffset: number): void { + async show(input: any, maxHeight: number, width: number, arrowSize: number, arrowOffset: number): Promise { const theme = this._themeService.getColorTheme(); const color = theme.getColor(breadcrumbsPickerBackground); @@ -103,33 +101,33 @@ export abstract class BreadcrumbsPicker { this._domNode.appendChild(this._treeContainer); this._layoutInfo = { maxHeight, width, arrowSize, arrowOffset, inputHeight: 0 }; - this._tree = this._createTree(this._treeContainer); + this._tree = this._createTree(this._treeContainer, input); - this._disposables.add(this._tree.onDidChangeSelection(e => { - if (e.browserEvent !== this._fakeEvent) { - const target = this._getTargetFromEvent(e.elements[0]); - if (target) { - setTimeout(_ => {// need to debounce here because this disposes the tree and the tree doesn't like to be disposed on click - this._onDidPickElement.fire({ target, browserEvent: e.browserEvent || new UIEvent('fake') }); - }, 0); - } + this._disposables.add(this._tree.onDidOpen(async e => { + const { element, editorOptions, sideBySide } = e; + const didReveal = await this._revealElement(element, { ...editorOptions, preserveFocus: false }, sideBySide); + if (!didReveal) { + return; } + // send telemetry + interface OpenEvent { type: string } + interface OpenEventGDPR { type: { classification: 'SystemMetaData', purpose: 'FeatureInsight' } } + this._telemetryService.publicLog2('breadcrumbs/open', { type: element instanceof OutlineElement2 ? 'symbol' : 'file' }); })); this._disposables.add(this._tree.onDidChangeFocus(e => { - const target = this._getTargetFromEvent(e.elements[0]); - if (target) { - this._onDidFocusElement.fire({ target, browserEvent: e.browserEvent || new UIEvent('fake') }); - } + this._previewDispoables.value = this._previewElement(e.elements[0]); })); this._disposables.add(this._tree.onDidChangeContentHeight(() => { this._layout(); })); this._domNode.focus(); - - this._setInput(input).then(() => { + try { + await this._setInput(input); this._layout(); - }).catch(onUnexpectedError); + } catch (err) { + onUnexpectedError(err); + } } protected _layout(): void { @@ -146,16 +144,15 @@ export abstract class BreadcrumbsPicker { this._treeContainer.style.height = `${treeHeight}px`; this._treeContainer.style.width = `${this._layoutInfo.width}px`; this._tree.layout(treeHeight, this._layoutInfo.width); - } - get useAltAsMultipleSelectionModifier() { - return this._tree.useAltAsMultipleSelectionModifier; - } + restoreViewState(): void { } + + protected abstract _setInput(element: FileElement | OutlineElement2): Promise; + protected abstract _createTree(container: HTMLElement, input: any): Tree; + protected abstract _previewElement(element: any): IDisposable; + protected abstract _revealElement(element: any, options: IEditorOptions, sideBySide: boolean): Promise; - protected abstract _setInput(element: BreadcrumbElement): Promise; - protected abstract _createTree(container: HTMLElement): Tree; - protected abstract _getTargetFromEvent(element: any): any | undefined; } //#region - Files @@ -173,9 +170,9 @@ class FileIdentityProvider implements IIdentityProvider { - private readonly _parents = new WeakMap(); - constructor( @IFileService private readonly _fileService: IFileService, ) { } hasChildren(element: IWorkspace | URI | IWorkspaceFolder | IFileStat): boolean { return URI.isUri(element) - || IWorkspace.isIWorkspace(element) - || IWorkspaceFolder.isIWorkspaceFolder(element) + || isWorkspace(element) + || isWorkspaceFolder(element) || element.isDirectory; } - getChildren(element: IWorkspace | URI | IWorkspaceFolder | IFileStat): Promise<(IWorkspaceFolder | IFileStat)[]> { - - if (IWorkspace.isIWorkspace(element)) { - return Promise.resolve(element.folders).then(folders => { - for (let child of folders) { - this._parents.set(element, child); - } - return folders; - }); + async getChildren(element: IWorkspace | URI | IWorkspaceFolder | IFileStat): Promise<(IWorkspaceFolder | IFileStat)[]> { + if (isWorkspace(element)) { + return element.folders; } let uri: URI; - if (IWorkspaceFolder.isIWorkspaceFolder(element)) { + if (isWorkspaceFolder(element)) { uri = element.uri; } else if (URI.isUri(element)) { uri = element; } else { uri = element.resource; } - return this._fileService.resolve(uri).then(stat => { - for (const child of stat.children || []) { - this._parents.set(stat, child); - } - return stat.children || []; - }); + const stat = await this._fileService.resolve(uri); + return stat.children ?? []; } } @@ -245,7 +230,7 @@ class FileRenderer implements ITreeRenderer { } filter(element: IWorkspaceFolder | IFileStat, _parentVisibility: TreeVisibility): boolean { - if (IWorkspaceFolder.isIWorkspaceFolder(element)) { + if (isWorkspaceFolder(element)) { // not a file return true; } @@ -345,7 +330,7 @@ class FileFilter implements ITreeFilter { export class FileSorter implements ITreeSorter { compare(a: IFileStat | IWorkspaceFolder, b: IFileStat | IWorkspaceFolder): number { - if (IWorkspaceFolder.isIWorkspaceFolder(a) && IWorkspaceFolder.isIWorkspaceFolder(b)) { + if (isWorkspaceFolder(a) && isWorkspaceFolder(b)) { return a.index - b.index; } if ((a as IFileStat).isDirectory === (b as IFileStat).isDirectory) { @@ -363,12 +348,15 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { constructor( parent: HTMLElement, + protected resource: URI, @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @IConfigurationService configService: IConfigurationService, @IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService, + @IEditorService private readonly _editorService: IEditorService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(parent, instantiationService, themeService, configService); + super(parent, resource, instantiationService, themeService, configService, telemetryService); } _createTree(container: HTMLElement) { @@ -406,7 +394,7 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { }); } - _setInput(element: BreadcrumbElement): Promise { + async _setInput(element: FileElement | OutlineElement2): Promise { const { uri, kind } = (element as FileElement); let input: IWorkspace | URI; if (kind === FileKind.ROOT_FOLDER) { @@ -416,115 +404,120 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { } const tree = this._tree as WorkbenchAsyncDataTree; - return tree.setInput(input).then(() => { - let focusElement: IWorkspaceFolder | IFileStat | undefined; - for (const { element } of tree.getNode().children) { - if (IWorkspaceFolder.isIWorkspaceFolder(element) && isEqual(element.uri, uri)) { - focusElement = element; - break; - } else if (isEqual((element as IFileStat).resource, uri)) { - focusElement = element as IFileStat; - break; - } + await tree.setInput(input); + let focusElement: IWorkspaceFolder | IFileStat | undefined; + for (const { element } of tree.getNode().children) { + if (isWorkspaceFolder(element) && isEqual(element.uri, uri)) { + focusElement = element; + break; + } else if (isEqual((element as IFileStat).resource, uri)) { + focusElement = element as IFileStat; + break; } - if (focusElement) { - tree.reveal(focusElement, 0.5); - tree.setFocus([focusElement], this._fakeEvent); - } - tree.domFocus(); - }); + } + if (focusElement) { + tree.reveal(focusElement, 0.5); + tree.setFocus([focusElement], this._fakeEvent); + } + tree.domFocus(); } - protected _getTargetFromEvent(element: any): any | undefined { - if (element && !IWorkspaceFolder.isIWorkspaceFolder(element) && !(element as IFileStat).isDirectory) { - return new FileElement((element as IFileStat).resource, FileKind.FILE); + protected _previewElement(_element: any): IDisposable { + return Disposable.None; + } + + async _revealElement(element: IFileStat | IWorkspaceFolder, options: IEditorOptions, sideBySide: boolean): Promise { + let resource: URI | undefined; + if (isWorkspaceFolder(element)) { + resource = element.uri; + } else if (!element.isDirectory) { + resource = element.resource; } + if (resource) { + this._onWillPickElement.fire(); + await this._editorService.openEditor({ resource, options }, sideBySide ? SIDE_GROUP : undefined); + return true; + } + return false; } } //#endregion -//#region - Symbols +//#region - Outline + +class OutlineTreeSorter implements ITreeSorter { + + private _order: 'name' | 'type' | 'position'; + + constructor( + private comparator: IOutlineComparator, + uri: URI | undefined, + @ITextResourceConfigurationService configService: ITextResourceConfigurationService, + ) { + this._order = configService.getValue(uri, 'breadcrumbs.symbolSortOrder'); + } + + compare(a: E, b: E): number { + if (this._order === 'name') { + return this.comparator.compareByName(a, b); + } else if (this._order === 'type') { + return this.comparator.compareByType(a, b); + } else { + return this.comparator.compareByPosition(a, b); + } + } +} export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { - protected readonly _symbolSortOrder: BreadcrumbsConfig<'position' | 'name' | 'type'>; - protected _outlineComparator: OutlineItemComparator; + protected _createTree(container: HTMLElement, input: OutlineElement2) { - constructor( - parent: HTMLElement, - @IInstantiationService instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IConfigurationService configurationService: IConfigurationService, - @IModeService private readonly _modeService: IModeService, - ) { - super(parent, instantiationService, themeService, configurationService); - this._symbolSortOrder = BreadcrumbsConfig.SymbolSortOrder.bindTo(this._configurationService); - this._outlineComparator = new OutlineItemComparator(); - } + const { config } = input.outline; - protected _createTree(container: HTMLElement) { - return >this._instantiationService.createInstance( + return , any, FuzzyScore>>this._instantiationService.createInstance( WorkbenchDataTree, 'BreadcrumbsOutlinePicker', container, - new OutlineVirtualDelegate(), - [new OutlineGroupRenderer(), this._instantiationService.createInstance(OutlineElementRenderer)], - new OutlineDataSource(), + config.delegate, + config.renderers, + config.treeDataSource, { + ...config.options, + sorter: this._instantiationService.createInstance(OutlineTreeSorter, config.comparator, undefined), collapseByDefault: true, expandOnlyOnTwistieClick: true, multipleSelectionSupport: false, - sorter: this._outlineComparator, - identityProvider: new OutlineIdentityProvider(), - keyboardNavigationLabelProvider: new OutlineNavigationLabelProvider(), - accessibilityProvider: new OutlineAccessibilityProvider(localize('breadcrumbs', "Breadcrumbs")), - filter: this._instantiationService.createInstance(OutlineFilter, 'breadcrumbs') } ); } - dispose(): void { - this._symbolSortOrder.dispose(); - super.dispose(); - } + protected _setInput(input: OutlineElement2): Promise { - protected _setInput(input: BreadcrumbElement): Promise { - const element = input as TreeElement; - const model = OutlineModel.get(element)!; - const tree = this._tree as WorkbenchDataTree; + const viewState = input.outline.captureViewState(); + this.restoreViewState = () => { viewState.dispose(); }; - const overrideConfiguration = { - resource: model.uri, - overrideIdentifier: this._modeService.getModeIdByFilepathOrFirstLine(model.uri) - }; - this._outlineComparator.type = this._getOutlineItemCompareType(overrideConfiguration); + const tree = this._tree as WorkbenchDataTree, any, FuzzyScore>; - tree.setInput(model); - if (element !== model) { - tree.reveal(element, 0.5); - tree.setFocus([element], this._fakeEvent); + tree.setInput(input.outline); + if (input.element !== input.outline) { + tree.reveal(input.element, 0.5); + tree.setFocus([input.element], this._fakeEvent); } tree.domFocus(); return Promise.resolve(); } - protected _getTargetFromEvent(element: any): any | undefined { - if (element instanceof OutlineElement) { - return element; - } + protected _previewElement(element: any): IDisposable { + const outline: IOutline = this._tree.getInput(); + return outline.preview(element); } - private _getOutlineItemCompareType(overrideConfiguration?: IConfigurationOverrides): OutlineSortOrder { - switch (this._symbolSortOrder.getValue(overrideConfiguration)) { - case 'name': - return OutlineSortOrder.ByName; - case 'type': - return OutlineSortOrder.ByKind; - case 'position': - default: - return OutlineSortOrder.ByPosition; - } + async _revealElement(element: any, options: IEditorOptions, sideBySide: boolean): Promise { + this._onWillPickElement.fire(); + const outline: IOutline = this._tree.getInput(); + await outline.reveal(element, options, sideBySide); + return true; } } diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index fdb0a57ac..5fdcc208c 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -33,7 +33,7 @@ import { JoinAllGroupsAction, FocusLeftGroup, FocusAboveGroup, FocusRightGroup, FocusBelowGroup, EditorLayoutSingleAction, EditorLayoutTwoColumnsAction, EditorLayoutThreeColumnsAction, EditorLayoutTwoByTwoGridAction, EditorLayoutTwoRowsAction, EditorLayoutThreeRowsAction, EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoRowsRightAction, NewEditorGroupLeftAction, NewEditorGroupRightAction, NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction, ToggleGroupSizesAction, ShowAllEditorsByMostRecentlyUsedAction, - QuickAccessPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction, QuickAccessLeastRecentlyUsedEditorAction, QuickAccessLeastRecentlyUsedEditorInGroupAction, ReopenResourcesAction, ToggleEditorTypeAction + QuickAccessPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction, QuickAccessLeastRecentlyUsedEditorAction, QuickAccessLeastRecentlyUsedEditorInGroupAction, ReopenResourcesAction, ToggleEditorTypeAction, DuplicateGroupDownAction, DuplicateGroupLeftAction, DuplicateGroupRightAction, DuplicateGroupUpAction } from 'vs/workbench/browser/parts/editor/editorActions'; import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -42,7 +42,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { isMacintosh } from 'vs/base/common/platform'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { OpenWorkspaceButtonContribution } from 'vs/workbench/browser/parts/editor/editorWidgets'; +import { OpenWorkspaceButtonContribution } from 'vs/workbench/browser/codeeditor'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { toLocalResource } from 'vs/base/common/resources'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; @@ -351,6 +351,10 @@ registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveGroupLeftAction, registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveGroupRightAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.RightArrow) }), 'View: Move Editor Group Right', CATEGORIES.View.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveGroupUpAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.UpArrow) }), 'View: Move Editor Group Up', CATEGORIES.View.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveGroupDownAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.DownArrow) }), 'View: Move Editor Group Down', CATEGORIES.View.value); +registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateGroupLeftAction), 'View: Duplicate Editor Group Left', CATEGORIES.View.value); +registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateGroupRightAction), 'View: Duplicate Editor Group Right', CATEGORIES.View.value); +registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateGroupUpAction), 'View: Duplicate Editor Group Up', CATEGORIES.View.value); +registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateGroupDownAction), 'View: Duplicate Editor Group Down', CATEGORIES.View.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveEditorToPreviousGroupAction, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow } }), 'View: Move Editor into Previous Group', CATEGORIES.View.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveEditorToNextGroupAction, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow } }), 'View: Move Editor into Next Group', CATEGORIES.View.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveEditorToFirstGroupAction, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_1, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_1 } }), 'View: Move Editor into First Group', CATEGORIES.View.value); diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 8b18d4e90..7e901d0ae 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -23,6 +23,7 @@ import { AllEditorsByMostRecentlyUsedQuickAccess, ActiveGroupEditorsByMostRecent import { Codicon } from 'vs/base/common/codicons'; import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { openEditorWith, getAllAvailableEditors } from 'vs/workbench/services/editor/common/editorOpenWith'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class ExecuteCommandAction extends Action { @@ -746,12 +747,13 @@ export class CloseEditorInAllGroupsAction extends Action { } } -export class BaseMoveGroupAction extends Action { +class BaseMoveCopyGroupAction extends Action { constructor( id: string, label: string, private direction: GroupDirection, + private isMove: boolean, private editorGroupService: IEditorGroupsService ) { super(id, label); @@ -766,9 +768,18 @@ export class BaseMoveGroupAction extends Action { } if (sourceGroup) { - const targetGroup = this.findTargetGroup(sourceGroup); - if (targetGroup) { - this.editorGroupService.moveGroup(sourceGroup, targetGroup, this.direction); + let resultGroup: IEditorGroup | undefined = undefined; + if (this.isMove) { + const targetGroup = this.findTargetGroup(sourceGroup); + if (targetGroup) { + resultGroup = this.editorGroupService.moveGroup(sourceGroup, targetGroup, this.direction); + } + } else { + resultGroup = this.editorGroupService.copyGroup(sourceGroup, sourceGroup, this.direction); + } + + if (resultGroup) { + this.editorGroupService.activateGroup(resultGroup); } } } @@ -801,6 +812,18 @@ export class BaseMoveGroupAction extends Action { } } +class BaseMoveGroupAction extends BaseMoveCopyGroupAction { + + constructor( + id: string, + label: string, + direction: GroupDirection, + editorGroupService: IEditorGroupsService + ) { + super(id, label, direction, true, editorGroupService); + } +} + export class MoveGroupLeftAction extends BaseMoveGroupAction { static readonly ID = 'workbench.action.moveActiveEditorGroupLeft'; @@ -857,6 +880,74 @@ export class MoveGroupDownAction extends BaseMoveGroupAction { } } +class BaseDuplicateGroupAction extends BaseMoveCopyGroupAction { + + constructor( + id: string, + label: string, + direction: GroupDirection, + editorGroupService: IEditorGroupsService + ) { + super(id, label, direction, false, editorGroupService); + } +} + +export class DuplicateGroupLeftAction extends BaseDuplicateGroupAction { + + static readonly ID = 'workbench.action.duplicateActiveEditorGroupLeft'; + static readonly LABEL = nls.localize('duplicateActiveGroupLeft', "Duplicate Editor Group Left"); + + constructor( + id: string, + label: string, + @IEditorGroupsService editorGroupService: IEditorGroupsService + ) { + super(id, label, GroupDirection.LEFT, editorGroupService); + } +} + +export class DuplicateGroupRightAction extends BaseDuplicateGroupAction { + + static readonly ID = 'workbench.action.duplicateActiveEditorGroupRight'; + static readonly LABEL = nls.localize('duplicateActiveGroupRight', "Duplicate Editor Group Right"); + + constructor( + id: string, + label: string, + @IEditorGroupsService editorGroupService: IEditorGroupsService + ) { + super(id, label, GroupDirection.RIGHT, editorGroupService); + } +} + +export class DuplicateGroupUpAction extends BaseDuplicateGroupAction { + + static readonly ID = 'workbench.action.duplicateActiveEditorGroupUp'; + static readonly LABEL = nls.localize('duplicateActiveGroupUp', "Duplicate Editor Group Up"); + + constructor( + id: string, + label: string, + @IEditorGroupsService editorGroupService: IEditorGroupsService + ) { + super(id, label, GroupDirection.UP, editorGroupService); + } +} + +export class DuplicateGroupDownAction extends BaseDuplicateGroupAction { + + static readonly ID = 'workbench.action.duplicateActiveEditorGroupDown'; + static readonly LABEL = nls.localize('duplicateActiveGroupDown', "Duplicate Editor Group Down"); + + constructor( + id: string, + label: string, + @IEditorGroupsService editorGroupService: IEditorGroupsService + ) { + super(id, label, GroupDirection.DOWN, editorGroupService); + } +} + export class MinimizeOtherGroupsAction extends Action { static readonly ID = 'workbench.action.minimizeOtherEditors'; @@ -1803,9 +1894,8 @@ export class ReopenResourcesAction extends Action { constructor( id: string, label: string, - @IQuickInputService private readonly quickInputService: IQuickInputService, @IEditorService private readonly editorService: IEditorService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(id, label); } @@ -1823,7 +1913,7 @@ export class ReopenResourcesAction extends Action { const options = activeEditorPane.options; const group = activeEditorPane.group; - await openEditorWith(activeInput, undefined, options, group, this.editorService, this.configurationService, this.quickInputService); + await this.instantiationService.invokeFunction(openEditorWith, activeInput, undefined, options, group); } } diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index dcece5343..f9fd2d571 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -23,7 +23,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { openEditorWith } from 'vs/workbench/services/editor/common/editorOpenWith'; @@ -484,7 +483,6 @@ function registerOpenEditorAPICommands(): void { const editorService = accessor.get(IEditorService); const editorGroupsService = accessor.get(IEditorGroupsService); const configurationService = accessor.get(IConfigurationService); - const quickInputService = accessor.get(IQuickInputService); const [columnArg, optionsArg] = columnAndOptions ?? []; let group: IEditorGroup | undefined = undefined; @@ -504,7 +502,7 @@ function registerOpenEditorAPICommands(): void { const textOptions: ITextEditorOptions = optionsArg ? { ...optionsArg, override: false } : { override: false }; const input = editorService.createEditorInput({ resource: URI.revive(resource) }); - return openEditorWith(input, id, textOptions, group, editorService, configurationService, quickInputService); + return openEditorWith(accessor, input, id, textOptions, group); }); } @@ -902,24 +900,10 @@ function registerOtherEditorCommands(): void { id: TOGGLE_KEEP_EDITORS_COMMAND_ID, handler: accessor => { const configurationService = accessor.get(IConfigurationService); - const notificationService = accessor.get(INotificationService); - const openerService = accessor.get(IOpenerService); - // Update setting const currentSetting = configurationService.getValue('workbench.editor.enablePreview'); const newSetting = currentSetting === true ? false : true; configurationService.updateValue('workbench.editor.enablePreview', newSetting); - - // Inform user - notificationService.prompt( - Severity.Info, - newSetting ? - nls.localize('enablePreview', "Preview editors have been enabled in settings.") : - nls.localize('disablePreview', "Preview editors have been disabled in settings."), - [{ - label: nls.localize('learnMode', "Learn More"), run: () => openerService.open('https://go.microsoft.com/fwlink/?linkid=2147473') - }] - ); } }); diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 50f571563..d3d2258f5 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -283,7 +283,8 @@ class DropOverlay extends Themable { // Open in target group const options = getActiveTextEditorOptions(sourceGroup, draggedEditor.editor, EditorOptions.create({ pinned: true, // always pin dropped editor - sticky: sourceGroup.isSticky(draggedEditor.editor) // preserve sticky state + sticky: sourceGroup.isSticky(draggedEditor.editor), // preserve sticky state + override: false, // Use `draggedEditor.editor` as is. If it is already a custom editor, it will stay so. })); const copyEditor = this.isCopyOperation(event, draggedEditor); targetGroup.openEditor(draggedEditor.editor, options, copyEditor ? OpenEditorContext.COPY_EDITOR : OpenEditorContext.MOVE_EDITOR); diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index a212456dd..277f029c3 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -322,8 +322,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private createContainerContextMenu(): void { const menu = this._register(this.menuService.createMenu(MenuId.EmptyEditorGroupContext, this.contextKeyService)); - this._register(addDisposableListener(this.element, EventType.CONTEXT_MENU, event => this.onShowContainerContextMenu(menu, event))); - this._register(addDisposableListener(this.element, TouchEventType.Contextmenu, event => this.onShowContainerContextMenu(menu))); + this._register(addDisposableListener(this.element, EventType.CONTEXT_MENU, e => this.onShowContainerContextMenu(menu, e))); + this._register(addDisposableListener(this.element, TouchEventType.Contextmenu, () => this.onShowContainerContextMenu(menu))); } private onShowContainerContextMenu(menu: IMenu, e?: MouseEvent): void { @@ -1704,13 +1704,15 @@ export class EditorGroupView extends Themable implements IEditorGroupView { layout(width: number, height: number): void { this.dimension = new Dimension(width, height); - // Ensure editor container gets height as CSS depending on the preferred height of the title control - const titleHeight = this.titleDimensions.height; - const editorHeight = Math.max(0, height - titleHeight); - this.editorContainer.style.height = `${editorHeight}px`; + // Layout the title area first to receive the size it occupies + const titleAreaSize = this.titleAreaControl.layout({ + container: this.dimension, + available: new Dimension(width, height - this.editorControl.minimumHeight) + }); - // Forward to controls - this.titleAreaControl.layout(new Dimension(width, titleHeight)); + // Pass the container width and remaining height to the editor layout + const editorHeight = Math.max(0, height - titleAreaSize.height); + this.editorContainer.style.height = `${editorHeight}px`; this.editorControl.layout(new Dimension(width, editorHeight)); } @@ -1769,7 +1771,7 @@ export interface EditorReplacement { readonly options?: EditorOptions; } -registerThemingParticipant((theme, collector, environment) => { +registerThemingParticipant((theme, collector) => { // Letterpress const letterpress = `./media/letterpress${theme.type === 'dark' ? '-dark' : theme.type === 'hc' ? '-hc' : ''}.svg`; diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 1d85f0ad5..3d289709b 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/editorstatus'; -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { format, compare, splitLines } from 'vs/base/common/strings'; import { extname, basename, isEqual } from 'vs/base/common/resources'; @@ -283,14 +283,15 @@ class State { } } -const nlsSingleSelectionRange = nls.localize('singleSelectionRange', "Ln {0}, Col {1} ({2} selected)"); -const nlsSingleSelection = nls.localize('singleSelection', "Ln {0}, Col {1}"); -const nlsMultiSelectionRange = nls.localize('multiSelectionRange', "{0} selections ({1} characters selected)"); -const nlsMultiSelection = nls.localize('multiSelection', "{0} selections"); -const nlsEOLLF = nls.localize('endOfLineLineFeed', "LF"); -const nlsEOLCRLF = nls.localize('endOfLineCarriageReturnLineFeed', "CRLF"); +const nlsSingleSelectionRange = localize('singleSelectionRange', "Ln {0}, Col {1} ({2} selected)"); +const nlsSingleSelection = localize('singleSelection', "Ln {0}, Col {1}"); +const nlsMultiSelectionRange = localize('multiSelectionRange', "{0} selections ({1} characters selected)"); +const nlsMultiSelection = localize('multiSelection', "{0} selections"); +const nlsEOLLF = localize('endOfLineLineFeed', "LF"); +const nlsEOLCRLF = localize('endOfLineCarriageReturnLineFeed', "CRLF"); export class EditorStatus extends Disposable implements IWorkbenchContribution { + private readonly tabFocusModeElement = this._register(new MutableDisposable()); private readonly columnSelectionModeElement = this._register(new MutableDisposable()); private readonly screenRedearModeElement = this._register(new MutableDisposable()); @@ -342,14 +343,14 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { if (!this.screenReaderNotification) { this.screenReaderNotification = this.notificationService.prompt( Severity.Info, - nls.localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate VS Code? (word wrap is disabled when using a screen reader)"), + localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate VS Code? (word wrap is disabled when using a screen reader)"), [{ - label: nls.localize('screenReaderDetectedExplanation.answerYes', "Yes"), + label: localize('screenReaderDetectedExplanation.answerYes', "Yes"), run: () => { this.configurationService.updateValue('editor.accessibilitySupport', 'on'); } }, { - label: nls.localize('screenReaderDetectedExplanation.answerNo', "No"), + label: localize('screenReaderDetectedExplanation.answerNo', "No"), run: () => { this.configurationService.updateValue('editor.accessibilitySupport', 'off'); } @@ -364,11 +365,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { private async showIndentationPicker(): Promise { const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl); if (!activeTextEditorControl) { - return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + return this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]); } if (this.editorService.activeEditor?.isReadonly()) { - return this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]); + return this.quickInputService.pick([{ label: localize('noWritableCodeEditor', "The active code editor is read-only.") }]); } const picks: QuickPickInput[] = [ @@ -390,25 +391,25 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { }; }); - picks.splice(3, 0, { type: 'separator', label: nls.localize('indentConvert', "convert file") }); - picks.unshift({ type: 'separator', label: nls.localize('indentView', "change view") }); + picks.splice(3, 0, { type: 'separator', label: localize('indentConvert', "convert file") }); + picks.unshift({ type: 'separator', label: localize('indentView', "change view") }); - const action = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true }); + const action = await this.quickInputService.pick(picks, { placeHolder: localize('pickAction', "Select Action"), matchOnDetail: true }); return action?.run(); } private updateTabFocusModeElement(visible: boolean): void { if (visible) { if (!this.tabFocusModeElement.value) { - const text = nls.localize('tabFocusModeEnabled', "Tab Moves Focus"); + const text = localize('tabFocusModeEnabled', "Tab Moves Focus"); this.tabFocusModeElement.value = this.statusbarService.addEntry({ text, ariaLabel: text, - tooltip: nls.localize('disableTabMode', "Disable Accessibility Mode"), + tooltip: localize('disableTabMode', "Disable Accessibility Mode"), command: 'editor.action.toggleTabFocusMode', backgroundColor: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_BACKGROUND), color: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_FOREGROUND) - }, 'status.editor.tabFocusMode', nls.localize('status.editor.tabFocusMode', "Accessibility Mode"), StatusbarAlignment.RIGHT, 100.7); + }, 'status.editor.tabFocusMode', localize('status.editor.tabFocusMode', "Accessibility Mode"), StatusbarAlignment.RIGHT, 100.7); } } else { this.tabFocusModeElement.clear(); @@ -418,15 +419,15 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { private updateColumnSelectionModeElement(visible: boolean): void { if (visible) { if (!this.columnSelectionModeElement.value) { - const text = nls.localize('columnSelectionModeEnabled', "Column Selection"); + const text = localize('columnSelectionModeEnabled', "Column Selection"); this.columnSelectionModeElement.value = this.statusbarService.addEntry({ text, ariaLabel: text, - tooltip: nls.localize('disableColumnSelectionMode', "Disable Column Selection Mode"), + tooltip: localize('disableColumnSelectionMode', "Disable Column Selection Mode"), command: 'editor.action.toggleColumnSelection', backgroundColor: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_BACKGROUND), color: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_FOREGROUND) - }, 'status.editor.columnSelectionMode', nls.localize('status.editor.columnSelectionMode', "Column Selection Mode"), StatusbarAlignment.RIGHT, 100.8); + }, 'status.editor.columnSelectionMode', localize('status.editor.columnSelectionMode', "Column Selection Mode"), StatusbarAlignment.RIGHT, 100.8); } } else { this.columnSelectionModeElement.clear(); @@ -436,14 +437,14 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { private updateScreenReaderModeElement(visible: boolean): void { if (visible) { if (!this.screenRedearModeElement.value) { - const text = nls.localize('screenReaderDetected', "Screen Reader Optimized"); + const text = localize('screenReaderDetected', "Screen Reader Optimized"); this.screenRedearModeElement.value = this.statusbarService.addEntry({ text, ariaLabel: text, command: 'showEditorScreenReaderNotification', backgroundColor: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_BACKGROUND), color: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_FOREGROUND) - }, 'status.editor.screenReaderMode', nls.localize('status.editor.screenReaderMode', "Screen Reader Mode"), StatusbarAlignment.RIGHT, 100.6); + }, 'status.editor.screenReaderMode', localize('status.editor.screenReaderMode', "Screen Reader Mode"), StatusbarAlignment.RIGHT, 100.6); } } else { this.screenRedearModeElement.clear(); @@ -459,11 +460,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { const props: IStatusbarEntry = { text, ariaLabel: text, - tooltip: nls.localize('gotoLine', "Go to Line/Column"), + tooltip: localize('gotoLine', "Go to Line/Column"), command: 'workbench.action.gotoLine' }; - this.updateElement(this.selectionElement, props, 'status.editor.selection', nls.localize('status.editor.selection', "Editor Selection"), StatusbarAlignment.RIGHT, 100.5); + this.updateElement(this.selectionElement, props, 'status.editor.selection', localize('status.editor.selection', "Editor Selection"), StatusbarAlignment.RIGHT, 100.5); } private updateIndentationElement(text: string | undefined): void { @@ -475,11 +476,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { const props: IStatusbarEntry = { text, ariaLabel: text, - tooltip: nls.localize('selectIndentation', "Select Indentation"), + tooltip: localize('selectIndentation', "Select Indentation"), command: 'changeEditorIndentation' }; - this.updateElement(this.indentationElement, props, 'status.editor.indentation', nls.localize('status.editor.indentation', "Editor Indentation"), StatusbarAlignment.RIGHT, 100.4); + this.updateElement(this.indentationElement, props, 'status.editor.indentation', localize('status.editor.indentation', "Editor Indentation"), StatusbarAlignment.RIGHT, 100.4); } private updateEncodingElement(text: string | undefined): void { @@ -491,11 +492,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { const props: IStatusbarEntry = { text, ariaLabel: text, - tooltip: nls.localize('selectEncoding', "Select Encoding"), + tooltip: localize('selectEncoding', "Select Encoding"), command: 'workbench.action.editor.changeEncoding' }; - this.updateElement(this.encodingElement, props, 'status.editor.encoding', nls.localize('status.editor.encoding', "Editor Encoding"), StatusbarAlignment.RIGHT, 100.3); + this.updateElement(this.encodingElement, props, 'status.editor.encoding', localize('status.editor.encoding', "Editor Encoding"), StatusbarAlignment.RIGHT, 100.3); } private updateEOLElement(text: string | undefined): void { @@ -507,11 +508,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { const props: IStatusbarEntry = { text, ariaLabel: text, - tooltip: nls.localize('selectEOL', "Select End of Line Sequence"), + tooltip: localize('selectEOL', "Select End of Line Sequence"), command: 'workbench.action.editor.changeEOL' }; - this.updateElement(this.eolElement, props, 'status.editor.eol', nls.localize('status.editor.eol', "Editor End of Line"), StatusbarAlignment.RIGHT, 100.2); + this.updateElement(this.eolElement, props, 'status.editor.eol', localize('status.editor.eol', "Editor End of Line"), StatusbarAlignment.RIGHT, 100.2); } private updateModeElement(text: string | undefined): void { @@ -523,11 +524,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { const props: IStatusbarEntry = { text, ariaLabel: text, - tooltip: nls.localize('selectLanguageMode', "Select Language Mode"), + tooltip: localize('selectLanguageMode', "Select Language Mode"), command: 'workbench.action.editor.changeLanguageMode' }; - this.updateElement(this.modeElement, props, 'status.editor.mode', nls.localize('status.editor.mode', "Editor Language"), StatusbarAlignment.RIGHT, 100.1); + this.updateElement(this.modeElement, props, 'status.editor.mode', localize('status.editor.mode', "Editor Language"), StatusbarAlignment.RIGHT, 100.1); } private updateMetadataElement(text: string | undefined): void { @@ -539,10 +540,10 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { const props: IStatusbarEntry = { text, ariaLabel: text, - tooltip: nls.localize('fileInfo', "File Information") + tooltip: localize('fileInfo', "File Information") }; - this.updateElement(this.metadataElement, props, 'status.editor.info', nls.localize('status.editor.info', "File Information"), StatusbarAlignment.RIGHT, 100); + this.updateElement(this.metadataElement, props, 'status.editor.info', localize('status.editor.info', "File Information"), StatusbarAlignment.RIGHT, 100); } private updateElement(element: MutableDisposable, props: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority: number) { @@ -730,8 +731,8 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { const modelOpts = model.getOptions(); update.indentation = ( modelOpts.insertSpaces - ? nls.localize('spacesSize', "Spaces: {0}", modelOpts.indentSize) - : nls.localize({ key: 'tabSize', comment: ['Tab corresponds to the tab key'] }, "Tab Size: {0}", modelOpts.tabSize) + ? localize('spacesSize', "Spaces: {0}", modelOpts.indentSize) + : localize({ key: 'tabSize', comment: ['Tab corresponds to the tab key'] }, "Tab Size: {0}", modelOpts.tabSize) ); } } @@ -766,7 +767,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { if (editorWidget) { const screenReaderDetected = this.accessibilityService.isScreenReaderOptimized(); if (screenReaderDetected) { - const screenReaderConfiguration = this.configurationService.getValue('editor').accessibilitySupport; + const screenReaderConfiguration = this.configurationService.getValue('editor')?.accessibilitySupport; if (screenReaderConfiguration === 'auto') { if (!this.promptedScreenReader) { this.promptedScreenReader = true; @@ -921,7 +922,7 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable { const line = splitLines(this.currentMarker.message)[0]; const text = `${this.getType(this.currentMarker)} ${line}`; if (!this.statusBarEntryAccessor.value) { - this.statusBarEntryAccessor.value = this.statusbarService.addEntry({ text: '', ariaLabel: '' }, 'statusbar.currentProblem', nls.localize('currentProblem', "Current Problem"), StatusbarAlignment.LEFT); + this.statusBarEntryAccessor.value = this.statusbarService.addEntry({ text: '', ariaLabel: '' }, 'statusbar.currentProblem', localize('currentProblem', "Current Problem"), StatusbarAlignment.LEFT); } this.statusBarEntryAccessor.value.update({ text, ariaLabel: text }); } else { @@ -934,9 +935,11 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable { if (!currentMarker) { return true; } + if (!previousMarker) { return true; } + return IMarkerData.makeKey(previousMarker) !== IMarkerData.makeKey(currentMarker); } @@ -946,6 +949,7 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable { case MarkerSeverity.Warning: return '$(warning)'; case MarkerSeverity.Info: return '$(info)'; } + return ''; } @@ -953,17 +957,21 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable { if (!this.configurationService.getValue('problems.showCurrentInStatus')) { return null; } + if (!this.editor) { return null; } + const model = this.editor.getModel(); if (!model) { return null; } + const position = this.editor.getPosition(); if (!position) { return null; } + return this.markers.find(marker => Range.containsPosition(marker, position)) || null; } @@ -971,13 +979,16 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable { if (!this.editor) { return; } + const model = this.editor.getModel(); if (!model) { return; } + if (model && !changedResources.some(r => isEqual(model.uri, r))) { return; } + this.updateMarkers(); } @@ -985,10 +996,12 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable { if (!this.editor) { return; } + const model = this.editor.getModel(); if (!model) { return; } + if (model) { this.markers = this.markerService.read({ resource: model.uri, @@ -998,6 +1011,7 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable { } else { this.markers = []; } + this.updateStatus(); } } @@ -1007,9 +1021,11 @@ function compareMarker(a: IMarker, b: IMarker): number { if (res === 0) { res = MarkerSeverity.compare(a.severity, b.severity); } + if (res === 0) { res = Range.compareRangesUsingStarts(a, b); } + return res; } @@ -1022,7 +1038,7 @@ export class ShowLanguageExtensionsAction extends Action { @ICommandService private readonly commandService: ICommandService, @IExtensionGalleryService galleryService: IExtensionGalleryService ) { - super(ShowLanguageExtensionsAction.ID, nls.localize('showLanguageExtensions', "Search Marketplace Extensions for '{0}'...", fileExtension)); + super(ShowLanguageExtensionsAction.ID, localize('showLanguageExtensions', "Search Marketplace Extensions for '{0}'...", fileExtension)); this.enabled = galleryService.isEnabled(); } @@ -1035,7 +1051,7 @@ export class ShowLanguageExtensionsAction extends Action { export class ChangeModeAction extends Action { static readonly ID = 'workbench.action.editor.changeLanguageMode'; - static readonly LABEL = nls.localize('changeMode', "Change Language Mode"); + static readonly LABEL = localize('changeMode', "Change Language Mode"); constructor( actionId: string, @@ -1054,14 +1070,14 @@ export class ChangeModeAction extends Action { async run(): Promise { const activeEditorPane = this.editorService.activeEditorPane as unknown as { isNotebookEditor?: boolean } | undefined; - if (activeEditorPane?.isNotebookEditor) { + if (activeEditorPane?.isNotebookEditor) { // TODO@rebornix TODO@jrieken debt: https://github.com/microsoft/vscode/issues/114554 // it's inside notebook editor return this.commandService.executeCommand('notebook.cell.changeLanguage'); } const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl); if (!activeTextEditorControl) { - await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]); return; } @@ -1085,22 +1101,24 @@ export class ChangeModeAction extends Action { const languages = this.modeService.getRegisteredLanguageNames(); const picks: QuickPickInput[] = languages.sort().map((lang, index) => { const modeId = this.modeService.getModeIdForLanguageName(lang.toLowerCase()) || 'unknown'; + const extensions = this.modeService.getExtensions(lang).join(' '); let description: string; if (currentLanguageId === lang) { - description = nls.localize('languageDescription', "({0}) - Configured Language", modeId); + description = localize('languageDescription', "({0}) - Configured Language", modeId); } else { - description = nls.localize('languageDescriptionConfigured', "({0})", modeId); + description = localize('languageDescriptionConfigured', "({0})", modeId); } return { label: lang, + meta: extensions, iconClasses: getIconClassesForModeId(modeId), description }; }); if (hasLanguageSupport) { - picks.unshift({ type: 'separator', label: nls.localize('languagesPicks', "languages (identifier)") }); + picks.unshift({ type: 'separator', label: localize('languagesPicks', "languages (identifier)") }); } // Offer action to configure via settings @@ -1115,22 +1133,22 @@ export class ChangeModeAction extends Action { picks.unshift(galleryAction); } - configureModeSettings = { label: nls.localize('configureModeSettings', "Configure '{0}' language based settings...", currentLanguageId) }; + configureModeSettings = { label: localize('configureModeSettings', "Configure '{0}' language based settings...", currentLanguageId) }; picks.unshift(configureModeSettings); - configureModeAssociations = { label: nls.localize('configureAssociationsExt', "Configure File Association for '{0}'...", ext) }; + configureModeAssociations = { label: localize('configureAssociationsExt', "Configure File Association for '{0}'...", ext) }; picks.unshift(configureModeAssociations); } // Offer to "Auto Detect" const autoDetectMode: IQuickPickItem = { - label: nls.localize('autoDetect', "Auto Detect") + label: localize('autoDetect', "Auto Detect") }; if (hasLanguageSupport) { picks.unshift(autoDetectMode); } - const pick = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }); + const pick = await this.quickInputService.pick(picks, { placeHolder: localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }); if (!pick) { return; } @@ -1178,6 +1196,8 @@ export class ChangeModeAction extends Action { modeSupport.setMode(languageSelection.languageIdentifier.language); } } + + activeTextEditorControl.focus(); } } @@ -1194,12 +1214,12 @@ export class ChangeModeAction extends Action { id, label: lang, iconClasses: getIconClassesForModeId(id), - description: (id === currentAssociation) ? nls.localize('currentAssociation', "Current Association") : undefined + description: (id === currentAssociation) ? localize('currentAssociation', "Current Association") : undefined }; }); setTimeout(async () => { - const language = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguageToConfigure', "Select Language Mode to Associate with '{0}'", extension || base) }); + const language = await this.quickInputService.pick(picks, { placeHolder: localize('pickLanguageToConfigure', "Select Language Mode to Associate with '{0}'", extension || base) }); if (language) { const fileAssociationsConfig = this.configurationService.inspect<{}>(FILES_ASSOCIATIONS_CONFIG); @@ -1233,7 +1253,7 @@ export interface IChangeEOLEntry extends IQuickPickItem { export class ChangeEOLAction extends Action { static readonly ID = 'workbench.action.editor.changeEOL'; - static readonly LABEL = nls.localize('changeEndOfLine', "Change End of Line Sequence"); + static readonly LABEL = localize('changeEndOfLine', "Change End of Line Sequence"); constructor( actionId: string, @@ -1247,12 +1267,12 @@ export class ChangeEOLAction extends Action { async run(): Promise { const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl); if (!activeTextEditorControl) { - await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]); return; } if (this.editorService.activeEditor?.isReadonly()) { - await this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]); + await this.quickInputService.pick([{ label: localize('noWritableCodeEditor', "The active code editor is read-only.") }]); return; } @@ -1265,7 +1285,7 @@ export class ChangeEOLAction extends Action { const selectedIndex = (textModel?.getEOL() === '\n') ? 0 : 1; - const eol = await this.quickInputService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), activeItem: EOLOptions[selectedIndex] }); + const eol = await this.quickInputService.pick(EOLOptions, { placeHolder: localize('pickEndOfLine', "Select End of Line Sequence"), activeItem: EOLOptions[selectedIndex] }); if (eol) { const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorControl); if (activeCodeEditor?.hasModel() && !this.editorService.activeEditor?.isReadonly()) { @@ -1275,13 +1295,15 @@ export class ChangeEOLAction extends Action { textModel.pushStackElement(); } } + + activeTextEditorControl.focus(); } } export class ChangeEncodingAction extends Action { static readonly ID = 'workbench.action.editor.changeEncoding'; - static readonly LABEL = nls.localize('changeEncoding', "Change File Encoding"); + static readonly LABEL = localize('changeEncoding', "Change File Encoding"); constructor( actionId: string, @@ -1296,25 +1318,26 @@ export class ChangeEncodingAction extends Action { } async run(): Promise { - if (!getCodeEditor(this.editorService.activeTextEditorControl)) { - await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl); + if (!activeTextEditorControl) { + await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]); return; } const activeEditorPane = this.editorService.activeEditorPane; if (!activeEditorPane) { - await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]); return; } const encodingSupport: IEncodingSupport | null = toEditorWithEncodingSupport(activeEditorPane.input); if (!encodingSupport) { - await this.quickInputService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]); + await this.quickInputService.pick([{ label: localize('noFileEditor', "No file active at this time") }]); return; } - const saveWithEncodingPick: IQuickPickItem = { label: nls.localize('saveWithEncoding', "Save with Encoding") }; - const reopenWithEncodingPick: IQuickPickItem = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding") }; + const saveWithEncodingPick: IQuickPickItem = { label: localize('saveWithEncoding', "Save with Encoding") }; + const reopenWithEncodingPick: IQuickPickItem = { label: localize('reopenWithEncoding', "Reopen with Encoding") }; if (!Language.isDefaultVariant()) { const saveWithEncodingAlias = 'Save with Encoding'; @@ -1334,7 +1357,7 @@ export class ChangeEncodingAction extends Action { } else if (activeEditorPane.input.isReadonly()) { action = reopenWithEncodingPick; } else { - action = await this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true }); + action = await this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: localize('pickAction', "Select Action"), matchOnDetail: true }); } if (!action) { @@ -1394,11 +1417,11 @@ export class ChangeEncodingAction extends Action { // If we have a guessed encoding, show it first unless it matches the configured encoding if (guessedEncoding && configuredEncoding !== guessedEncoding && SUPPORTED_ENCODINGS[guessedEncoding]) { picks.unshift({ type: 'separator' }); - picks.unshift({ id: guessedEncoding, label: SUPPORTED_ENCODINGS[guessedEncoding].labelLong, description: nls.localize('guessedEncoding', "Guessed from content") }); + picks.unshift({ id: guessedEncoding, label: SUPPORTED_ENCODINGS[guessedEncoding].labelLong, description: localize('guessedEncoding', "Guessed from content") }); } const encoding = await this.quickInputService.pick(picks, { - placeHolder: isReopenWithEncoding ? nls.localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : nls.localize('pickEncodingForSave', "Select File Encoding to Save with"), + placeHolder: isReopenWithEncoding ? localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : localize('pickEncodingForSave', "Select File Encoding to Save with"), activeItem: items[typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : -1] }); @@ -1414,5 +1437,7 @@ export class ChangeEncodingAction extends Action { if (typeof encoding.id !== 'undefined' && activeEncodingSupport && activeEncodingSupport.getEncoding() !== encoding.id) { activeEncodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding } + + activeTextEditorControl.focus(); } } diff --git a/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css index 77a7fa03e..366f6dbe9 100644 --- a/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css @@ -14,7 +14,7 @@ flex: auto; } -.monaco-workbench .part.editor > .content .editor-group-container > .title .title-label { +.monaco-workbench .part.editor > .content .editor-group-container > .title > .label-container > .title-label { line-height: 35px; overflow: hidden; text-overflow: ellipsis; @@ -22,16 +22,16 @@ padding-left: 20px; } -.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .no-tabs.title-label { - flex: none; -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title .monaco-icon-label::before { - height: 35px; /* tweak the icon size of the editor labels when icons are enabled */ +.monaco-workbench .part.editor > .content .editor-group-container > .title > .label-container > .title-label > .monaco-icon-label-container { + flex: none; /* helps to show decorations right next to the label and not at the end */ } /* Breadcrumbs */ +.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .no-tabs.title-label { + flex: none; +} + .monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control { flex: 1 50%; overflow: hidden; @@ -62,13 +62,11 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item.root_folder + .monaco-breadcrumb-item::before, .monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control.relative-path .monaco-breadcrumb-item:nth-child(2)::before, .monaco-workbench.windows .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:nth-child(2)::before { - /* workspace folder, item following workspace folder, or relative path -> hide first seperator */ - display: none; + display: none; /* workspace folder, item following workspace folder, or relative path -> hide first seperator */ } .monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item.root_folder::after { - /* use dot separator for workspace folder */ - content: '\00a0•\00a0'; + content: '\00a0•\00a0'; /* use dot separator for workspace folder */ padding: 0; } @@ -80,13 +78,18 @@ padding: 0 1px; } -.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item .codicon:last-child, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child .codicon:last-child { - display: none; /* hides chevrons when no tabs visible and when last items */ +.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item .codicon:last-child { + display: none; /* hides chevrons when no tabs visible */ } -/* Title Actions */ -.monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions { +.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-icon-label::before { + height: 18px; + padding-right: 2px; +} + +/* Editor Actions Toolbar (via title actions) */ + +.monaco-workbench .part.editor > .content .editor-group-container > .title > .title-actions { display: flex; flex: initial; opacity: 0.5; @@ -94,6 +97,6 @@ height: 35px; } -.monaco-workbench .part.editor > .content .editor-group-container.active > .title .title-actions { +.monaco-workbench .part.editor > .content .editor-group-container.active > .title > .title-actions { opacity: 1; } diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index a9158b796..4dd7e77d5 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -3,17 +3,45 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +/* + ################################### z-index explainer ################################### + + Tabs have various levels of z-index depending on state, typically: + - scrollbar should be above all + - sticky (compact, shrink) tabs need to be above non-sticky tabs for scroll under effect + including non-sticky tabs-top borders, otherwise these borders would not scroll under + (https://github.com/microsoft/vscode/issues/111641) + - bottom-border needs to be above tabs bottom border to win but also support sticky tabs + (https://github.com/microsoft/vscode/issues/99084) <- this currently cannot be done and + is still broken. putting sticky-tabs above tabs bottom border would not render this + border at all for sticky tabs. + + On top of that there is 2 borders with a z-index for a general border below tabs + - tabs bottom border + - editor title bottom border (when breadcrumbs are disabled, this border will appear + same as tabs bottom border) + + The following tabls shows the current stacking order: + + [z-index] [kind] + 7 scrollbar + 6 active-tab border-bottom + 5 tabs, title border bottom + 4 sticky-tab + 2 active/dirty-tab border top + 0 tab + + ########################################################################################## +*/ + /* Title Container */ -.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container { +.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container { display: flex; + position: relative; /* position tabs border bottom or editor actions (when tabs wrap) relative to this container */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.tabs-border-bottom { - position: relative; -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.tabs-border-bottom::after { +.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.tabs-border-bottom::after { content: ''; position: absolute; bottom: 0; @@ -25,12 +53,12 @@ height: 1px; } -.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container > .monaco-scrollable-element { +.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container > .monaco-scrollable-element { flex: 1; } -.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container > .monaco-scrollable-element .scrollbar { - z-index: 3; /* on top of tabs */ +.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container > .monaco-scrollable-element .scrollbar { + z-index: 7; cursor: default; } @@ -46,6 +74,13 @@ overflow: scroll !important; } +.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container { + + /* Enable wrapping via flex layout and dynamic height */ + height: auto; + flex-wrap: wrap; +} + .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container::-webkit-scrollbar { display: none; /* Chrome + Safari: hide scrollbar */ } @@ -62,6 +97,10 @@ padding-left: 10px; } +.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab:last-child { + margin-right: var(--last-tab-margin-right); /* when tabs wrap, we need a margin away from the absolute positioned editor actions */ +} + .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.tab-actions-right, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.tab-actions-off:not(.sticky-compact) { padding-left: 5px; /* reduce padding when we show icons and are in shrinking mode and tab actions is not left (unless sticky-compact) */ @@ -74,6 +113,10 @@ flex-shrink: 0; } +.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab.sizing-fit { + flex-grow: 1; /* grow the tabs to fill each row for a more homogeneous look when tabs wrap */ +} + .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink { min-width: 80px; flex-basis: 0; /* all tabs are even */ @@ -89,7 +132,7 @@ /** Sticky compact/shrink tabs do not scroll in case of overflow and are always above unsticky tabs which scroll under */ position: sticky; - z-index: 1; + z-index: 4; /** Sticky compact/shrink tabs are even and never grow */ flex-basis: 0; @@ -118,9 +161,7 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-shrink.sticky-compact, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-fit.sticky-shrink, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-shrink.sticky-shrink { - - /** Disable sticky positions for sticky compact/shrink tabs if the available space is too little */ - position: static; + position: static; /** disable sticky positions for sticky compact/shrink tabs if the available space is too little */ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-left .action-label { @@ -132,7 +173,7 @@ content: ''; display: flex; flex: 0; - width: 5px; /* Reserve space to hide tab fade when close button is left or off (fixes https://github.com/microsoft/vscode/issues/45728) */ + width: 5px; /* reserve space to hide tab fade when close button is left or off (fixes https://github.com/microsoft/vscode/issues/45728) */ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.tab-actions-left { @@ -167,24 +208,26 @@ display: block; position: absolute; left: 0; - z-index: 6; /* over possible title border */ pointer-events: none; width: 100%; } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container { + z-index: 2; top: 0; height: 1px; background-color: var(--tab-border-top-color); } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container { + z-index: 6; bottom: 0; height: 1px; background-color: var(--tab-border-bottom-color); } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty-border-top > .tab-border-top-container { + z-index: 2; top: 0; height: 2px; background-color: var(--tab-dirty-border-top-color); @@ -203,13 +246,16 @@ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink > .tab-label::after { - content: ''; + content: ''; /* enables a linear gradient to overlay the end of the label when tabs overflow */ position: absolute; right: 0; - height: 100%; width: 5px; opacity: 1; padding: 0; + /* the rules below ensure that the gradient does not impact top/bottom borders (https://github.com/microsoft/vscode/issues/115129) */ + top: 1px; + bottom: 1px; + height: calc(100% - 2px); } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink:focus > .tab-label::after { @@ -243,7 +289,7 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-right.sizing-shrink > .tab-actions { flex: 0; - overflow: hidden; /* let the tab actions be pushed out of view when sizing is set to shrink to make more room... */ + overflow: hidden; /* let the tab actions be pushed out of view when sizing is set to shrink to make more room */ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty.tab-actions-right.sizing-shrink > .tab-actions, @@ -301,7 +347,7 @@ margin-right: 0.5em; } -/* No Tab Actions */ +/* Tab Actions: Off */ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off { padding-right: 10px; /* give a little bit more room if tab actions is off */ @@ -324,15 +370,6 @@ pointer-events: none; /* don't allow tab actions to be clicked when running without tab actions */ } -/* Editor Actions */ - -.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions { - cursor: default; - flex: initial; - padding: 0 8px 0 4px; - height: 35px; -} - /* Breadcrumbs */ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control { @@ -350,11 +387,17 @@ height: 22px; /* tweak the icon size of the editor labels when icons are enabled */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .outline-element-icon { + padding-right: 3px; + height: 22px; /* tweak the icon size of the editor labels when icons are enabled */ + line-height: 22px; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item { max-width: 80%; } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item::before { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item::before { width: 16px; height: 22px; display: flex; @@ -362,6 +405,27 @@ justify-content: center; } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child { padding-right: 8px; } + +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child .codicon:last-child { + display: none; /* hides chevrons when last item */ +} + +/* Editor Actions Toolbar */ + +.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions { + cursor: default; + flex: initial; + padding: 0 8px 0 4px; + height: 35px; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .editor-actions { + + /* When tabs are wrapped, position the editor actions at the end of the very last row */ + position: absolute; + bottom: 0; + right: 0; +} diff --git a/src/vs/workbench/browser/parts/editor/media/titlecontrol.css b/src/vs/workbench/browser/parts/editor/media/titlecontrol.css index 59ab0d4af..420f4031f 100644 --- a/src/vs/workbench/browser/parts/editor/media/titlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/titlecontrol.css @@ -26,6 +26,15 @@ cursor: pointer; } +.monaco-workbench .part.editor > .content .editor-group-container > .title .monaco-icon-label::before { + height: 35px; /* tweak the icon size of the editor labels when icons are enabled */ +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .monaco-icon-label::after, +.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs .monaco-icon-label::after { + padding-right: 0; /* by default the icon label has a padding right and this isn't wanted when not showing tabs and not showing breadcrumbs */ +} + /* Title Actions */ .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label:not(span), @@ -59,13 +68,12 @@ opacity: 0.4; } -/* Drag Cursor */ +/* Drag and Drop */ + .monaco-workbench .part.editor > .content .editor-group-container > .title { cursor: grab; } -/* Drag and Drop Feedback */ - .monaco-editor-group-drag-image { display: inline-block; padding: 1px 7px; diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts index 66a7caec1..c779550c1 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/notabstitlecontrol'; import { EditorResourceAccessor, Verbosity, IEditorInput, IEditorPartOptions, SideBySideEditor } from 'vs/workbench/common/editor'; -import { TitleControl, IToolbarActions } from 'vs/workbench/browser/parts/editor/titleControl'; +import { TitleControl, IToolbarActions, ITitleControlDimensions } from 'vs/workbench/browser/parts/editor/titleControl'; import { ResourceLabel, IResourceLabel } from 'vs/workbench/browser/labels'; import { TAB_ACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND } from 'vs/workbench/common/theme'; import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch'; @@ -15,6 +15,7 @@ import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/edito import { Color } from 'vs/base/common/color'; import { withNullAsUndefined, assertIsDefined, assertAllDefined } from 'vs/base/common/types'; import { IEditorGroupTitleDimensions } from 'vs/workbench/browser/parts/editor/editor'; +import { equals } from 'vs/base/common/objects'; interface IRenderedEditorLabel { editor?: IEditorInput; @@ -50,7 +51,7 @@ export class NoTabsTitleControl extends TitleControl { // Breadcrumbs this.createBreadcrumbsControl(labelContainer, { showFileIcons: false, showSymbolIcons: true, showDecorationColors: false, breadcrumbsBackground: () => Color.transparent }); titleContainer.classList.toggle('breadcrumbs', Boolean(this.breadcrumbsControl)); - this._register({ dispose: () => titleContainer.classList.remove('breadcrumbs') }); // import to remove because the container is a shared dom node + this._register({ dispose: () => titleContainer.classList.remove('breadcrumbs') }); // important to remove because the container is a shared dom node // Right Actions Container const actionsContainer = document.createElement('div'); @@ -67,16 +68,16 @@ export class NoTabsTitleControl extends TitleControl { this.enableGroupDragging(titleContainer); // Pin on double click - this._register(addDisposableListener(titleContainer, EventType.DBLCLICK, (e: MouseEvent) => this.onTitleDoubleClick(e))); + this._register(addDisposableListener(titleContainer, EventType.DBLCLICK, e => this.onTitleDoubleClick(e))); // Detect mouse click - this._register(addDisposableListener(titleContainer, EventType.AUXCLICK, (e: MouseEvent) => this.onTitleAuxClick(e))); + this._register(addDisposableListener(titleContainer, EventType.AUXCLICK, e => this.onTitleAuxClick(e))); // Detect touch this._register(addDisposableListener(titleContainer, TouchEventType.Tap, (e: GestureEvent) => this.onTitleTap(e))); // Context Menu - this._register(addDisposableListener(titleContainer, EventType.CONTEXT_MENU, (e: Event) => { + this._register(addDisposableListener(titleContainer, EventType.CONTEXT_MENU, e => { if (this.group.activeEditor) { this.onContextMenu(this.group.activeEditor, e, titleContainer); } @@ -188,7 +189,7 @@ export class NoTabsTitleControl extends TitleControl { } updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void { - if (oldOptions.labelFormat !== newOptions.labelFormat) { + if (oldOptions.labelFormat !== newOptions.labelFormat || !equals(oldOptions.decorations, newOptions.decorations)) { this.redraw(); } } @@ -236,6 +237,7 @@ export class NoTabsTitleControl extends TitleControl { private redraw(): void { const editor = withNullAsUndefined(this.group.activeEditor); + const options = this.accessor.partOptions; const isEditorPinned = editor ? this.group.isPinned(editor) : false; const isGroupActive = this.accessor.activeGroup === this.group; @@ -291,14 +293,18 @@ export class NoTabsTitleControl extends TitleControl { { title, italic: !isEditorPinned, - extraClasses: ['no-tabs', 'title-label'] + extraClasses: ['no-tabs', 'title-label'], + fileDecorations: { + colors: Boolean(options.decorations?.colors), + badges: Boolean(options.decorations?.badges) + }, } ); if (isGroupActive) { - editorLabel.element.style.color = this.getColor(TAB_ACTIVE_FOREGROUND) || ''; + titleContainer.style.color = this.getColor(TAB_ACTIVE_FOREGROUND) || ''; } else { - editorLabel.element.style.color = this.getColor(TAB_UNFOCUSED_ACTIVE_FOREGROUND) || ''; + titleContainer.style.color = this.getColor(TAB_UNFOCUSED_ACTIVE_FOREGROUND) || ''; } // Update Editor Actions Toolbar @@ -333,9 +339,11 @@ export class NoTabsTitleControl extends TitleControl { }; } - layout(dimension: Dimension): void { + layout(dimensions: ITitleControlDimensions): Dimension { if (this.breadcrumbsControl) { this.breadcrumbsControl.layout(undefined); } + + return new Dimension(dimensions.container.width, this.getDimensions().height); } } diff --git a/src/vs/workbench/browser/parts/editor/rangeDecorations.ts b/src/vs/workbench/browser/parts/editor/rangeDecorations.ts deleted file mode 100644 index 222d0fd5e..000000000 --- a/src/vs/workbench/browser/parts/editor/rangeDecorations.ts +++ /dev/null @@ -1,121 +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 { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import { Emitter } from 'vs/base/common/event'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IRange } from 'vs/editor/common/core/range'; -import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { ICodeEditor, isCodeEditor, isCompositeEditor } from 'vs/editor/browser/editorBrowser'; -import { TrackedRangeStickiness, IModelDecorationsChangeAccessor } from 'vs/editor/common/model'; -import { isEqual } from 'vs/base/common/resources'; - -export interface IRangeHighlightDecoration { - resource: URI; - range: IRange; - isWholeLine?: boolean; -} - -export class RangeHighlightDecorations extends Disposable { - - private rangeHighlightDecorationId: string | null = null; - private editor: ICodeEditor | null = null; - private readonly editorDisposables = this._register(new DisposableStore()); - - private readonly _onHighlightRemoved: Emitter = this._register(new Emitter()); - readonly onHighlightRemoved = this._onHighlightRemoved.event; - - constructor( - @IEditorService private readonly editorService: IEditorService - ) { - super(); - } - - removeHighlightRange() { - if (this.editor && this.editor.getModel() && this.rangeHighlightDecorationId) { - this.editor.deltaDecorations([this.rangeHighlightDecorationId], []); - this._onHighlightRemoved.fire(); - } - - this.rangeHighlightDecorationId = null; - } - - highlightRange(range: IRangeHighlightDecoration, editor?: any) { - editor = editor ?? this.getEditor(range); - if (isCodeEditor(editor)) { - this.doHighlightRange(editor, range); - } else if (isCompositeEditor(editor) && isCodeEditor(editor.activeCodeEditor)) { - this.doHighlightRange(editor.activeCodeEditor, range); - } - } - - private doHighlightRange(editor: ICodeEditor, selectionRange: IRangeHighlightDecoration) { - this.removeHighlightRange(); - - editor.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => { - this.rangeHighlightDecorationId = changeAccessor.addDecoration(selectionRange.range, this.createRangeHighlightDecoration(selectionRange.isWholeLine)); - }); - - this.setEditor(editor); - } - - private getEditor(resourceRange: IRangeHighlightDecoration): ICodeEditor | undefined { - const activeEditor = this.editorService.activeEditor; - const resource = activeEditor && activeEditor.resource; - if (resource && isEqual(resource, resourceRange.resource)) { - return this.editorService.activeTextEditorControl as ICodeEditor; - } - - return undefined; - } - - private setEditor(editor: ICodeEditor) { - if (this.editor !== editor) { - this.editorDisposables.clear(); - this.editor = editor; - this.editorDisposables.add(this.editor.onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => { - if ( - e.reason === CursorChangeReason.NotSet - || e.reason === CursorChangeReason.Explicit - || e.reason === CursorChangeReason.Undo - || e.reason === CursorChangeReason.Redo - ) { - this.removeHighlightRange(); - } - })); - this.editorDisposables.add(this.editor.onDidChangeModel(() => { this.removeHighlightRange(); })); - this.editorDisposables.add(this.editor.onDidDispose(() => { - this.removeHighlightRange(); - this.editor = null; - })); - } - } - - private static readonly _WHOLE_LINE_RANGE_HIGHLIGHT = ModelDecorationOptions.register({ - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - className: 'rangeHighlight', - isWholeLine: true - }); - - private static readonly _RANGE_HIGHLIGHT = ModelDecorationOptions.register({ - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - className: 'rangeHighlight' - }); - - private createRangeHighlightDecoration(isWholeLine: boolean = true): ModelDecorationOptions { - return (isWholeLine ? RangeHighlightDecorations._WHOLE_LINE_RANGE_HIGHLIGHT : RangeHighlightDecorations._RANGE_HIGHLIGHT); - } - - dispose() { - super.dispose(); - - if (this.editor && this.editor.getModel()) { - this.removeHighlightRange(); - this.editor = null; - } - } -} diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index bcd835f4c..e6adbec28 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -18,19 +18,18 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IMenuService } from 'vs/platform/actions/common/actions'; -import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; +import { ITitleControlDimensions, TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IDisposable, dispose, DisposableStore, combinedDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { getOrSet } from 'vs/base/common/map'; -import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BACKGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_BACKGROUND, TAB_HOVER_FOREGROUND, TAB_UNFOCUSED_HOVER_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BORDER, TAB_LAST_PINNED_BORDER } from 'vs/workbench/common/theme'; import { activeContrastBorder, contrastBorder, editorBackground, breadcrumbsBackground } from 'vs/platform/theme/common/colorRegistry'; import { ResourcesDropHandler, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd'; import { Color } from 'vs/base/common/color'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { MergeGroupMode, IMergeGroupOptions, GroupsArrangement, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { addDisposableListener, EventType, EventHelper, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode } from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; @@ -48,6 +47,7 @@ import { IPath, win32, posix } from 'vs/base/common/path'; import { insert } from 'vs/base/common/arrays'; import { ColorScheme } from 'vs/platform/theme/common/theme'; import { isSafari } from 'vs/base/browser/browser'; +import { equals } from 'vs/base/common/objects'; interface IEditorInputLabel { name?: string; @@ -73,6 +73,9 @@ export class TabsTitleControl extends TitleControl { private static readonly TAB_HEIGHT = 35; + private static readonly MOUSE_WHEEL_EVENT_THRESHOLD = 150; + private static readonly MOUSE_WHEEL_DISTANCE_THRESHOLD = 1.5; + private titleContainer: HTMLElement | undefined; private tabsAndActionsContainer: HTMLElement | undefined; private tabsContainer: HTMLElement | undefined; @@ -87,12 +90,18 @@ export class TabsTitleControl extends TitleControl { private tabActionBars: ActionBar[] = []; private tabDisposables: IDisposable[] = []; - private dimension: Dimension | undefined; + private dimensions: ITitleControlDimensions & { used?: Dimension } = { + container: Dimension.None, + available: Dimension.None + }; + private readonly layoutScheduled = this._register(new MutableDisposable()); private blockRevealActiveTab: boolean | undefined; private path: IPath = isWindows ? win32 : posix; + private lastMouseWheelEventTime = 0; + constructor( parent: HTMLElement, accessor: IEditorGroupsAccessor, @@ -106,19 +115,21 @@ export class TabsTitleControl extends TitleControl { @IMenuService menuService: IMenuService, @IQuickInputService quickInputService: IQuickInputService, @IThemeService themeService: IThemeService, - @IExtensionService extensionService: IExtensionService, @IConfigurationService configurationService: IConfigurationService, @IFileService fileService: IFileService, @IEditorService private readonly editorService: EditorServiceImpl, @IPathService private readonly pathService: IPathService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService ) { - super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickInputService, themeService, extensionService, configurationService, fileService); + super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickInputService, themeService, configurationService, fileService); // Resolve the correct path library for the OS we are on // If we are connected to remote, this accounts for the // remote OS. (async () => this.path = await this.pathService.path)(); + + // React to decorations changing for our resource labels + this._register(this.tabResourceLabels.onDidChangeDecorations(() => this.doHandleDecorationsChange())); } protected create(parent: HTMLElement): void { @@ -151,7 +162,7 @@ export class TabsTitleControl extends TitleControl { // Editor Actions Toolbar this.createEditorActionsToolBar(this.editorToolbarContainer); - // Breadcrumbs (are on a separate row below tabs and actions) + // Breadcrumbs const breadcrumbsContainer = document.createElement('div'); breadcrumbsContainer.classList.add('tabs-breadcrumbs'); this.titleContainer.appendChild(breadcrumbsContainer); @@ -242,7 +253,7 @@ export class TabsTitleControl extends TitleControl { }); // Prevent auto-scrolling (https://github.com/microsoft/vscode/issues/16690) - this._register(addDisposableListener(tabsContainer, EventType.MOUSE_DOWN, (e: MouseEvent) => { + this._register(addDisposableListener(tabsContainer, EventType.MOUSE_DOWN, e => { if (e.button === 1) { e.preventDefault(); } @@ -337,8 +348,27 @@ export class TabsTitleControl extends TitleControl { } } - // Figure out scrolling direction - const nextEditor = this.group.getEditorByIndex(this.group.getIndexOfEditor(activeEditor) + (e.deltaX < 0 || e.deltaY < 0 /* scrolling up */ ? -1 : 1)); + // Ignore event if the last one happened too recently (https://github.com/microsoft/vscode/issues/96409) + // The restriction is relaxed according to the absolute value of `deltaX` and `deltaY` + // to support discrete (mouse wheel) and contiguous scrolling (touchpad) equally well + const now = Date.now(); + if (now - this.lastMouseWheelEventTime < TabsTitleControl.MOUSE_WHEEL_EVENT_THRESHOLD - 2 * (Math.abs(e.deltaX) + Math.abs(e.deltaY))) { + return; + } + + this.lastMouseWheelEventTime = now; + + // Figure out scrolling direction but ignore it if too subtle + let tabSwitchDirection: number; + if (e.deltaX + e.deltaY < - TabsTitleControl.MOUSE_WHEEL_DISTANCE_THRESHOLD) { + tabSwitchDirection = -1; + } else if (e.deltaX + e.deltaY > TabsTitleControl.MOUSE_WHEEL_DISTANCE_THRESHOLD) { + tabSwitchDirection = 1; + } else { + return; + } + + const nextEditor = this.group.getEditorByIndex(this.group.getIndexOfEditor(activeEditor) + tabSwitchDirection); if (!nextEditor) { return; } @@ -351,12 +381,19 @@ export class TabsTitleControl extends TitleControl { })); } + private doHandleDecorationsChange(): void { + + // A change to decorations potentially has an impact on the size of tabs + // so we need to trigger a layout in that case to adjust things + this.layout(this.dimensions); + } + protected updateEditorActionsToolbar(): void { super.updateEditorActionsToolbar(); // Changing the actions in the toolbar can have an impact on the size of the // tab container, so we need to layout the tabs to make sure the active is visible - this.layout(this.dimension); + this.layout(this.dimensions); } openEditor(editor: IEditorInput): void { @@ -439,7 +476,7 @@ export class TabsTitleControl extends TitleControl { }); // Moving an editor requires a layout to keep the active editor visible - this.layout(this.dimension); + this.layout(this.dimensions); } pinEditor(editor: IEditorInput): void { @@ -466,7 +503,7 @@ export class TabsTitleControl extends TitleControl { }); // A change to the sticky state requires a layout to keep the active editor visible - this.layout(this.dimension); + this.layout(this.dimensions); } setActive(isGroupActive: boolean): void { @@ -478,7 +515,7 @@ export class TabsTitleControl extends TitleControl { // Activity has an impact on the toolbar, so we need to update and layout this.updateEditorActionsToolbar(); - this.layout(this.dimension); + this.layout(this.dimensions); } private updateEditorLabelAggregator = this._register(new RunOnceScheduler(() => this.updateEditorLabels(), 0)); @@ -504,7 +541,7 @@ export class TabsTitleControl extends TitleControl { }); // A change to a label requires a layout to keep the active editor visible - this.layout(this.dimension); + this.layout(this.dimensions); } updateEditorDirty(editor: IEditorInput): void { @@ -531,7 +568,9 @@ export class TabsTitleControl extends TitleControl { oldOptions.pinnedTabSizing !== newOptions.pinnedTabSizing || oldOptions.showIcons !== newOptions.showIcons || oldOptions.hasIcons !== newOptions.hasIcons || - oldOptions.highlightModifiedTabs !== newOptions.highlightModifiedTabs + oldOptions.highlightModifiedTabs !== newOptions.highlightModifiedTabs || + oldOptions.wrapTabs !== newOptions.wrapTabs || + !equals(oldOptions.decorations, newOptions.decorations) ) { this.redraw(); } @@ -648,7 +687,7 @@ export class TabsTitleControl extends TitleControl { }; // Open on Click / Touch - disposables.add(addDisposableListener(tab, EventType.MOUSE_DOWN, (e: MouseEvent) => handleClickOrTouch(e))); + disposables.add(addDisposableListener(tab, EventType.MOUSE_DOWN, e => handleClickOrTouch(e))); disposables.add(addDisposableListener(tab, TouchEventType.Tap, (e: GestureEvent) => handleClickOrTouch(e))); // Touch Scroll Support @@ -657,14 +696,14 @@ export class TabsTitleControl extends TitleControl { })); // Prevent flicker of focus outline on tab until editor got focus - disposables.add(addDisposableListener(tab, EventType.MOUSE_UP, (e: MouseEvent) => { + disposables.add(addDisposableListener(tab, EventType.MOUSE_UP, e => { EventHelper.stop(e); tab.blur(); })); // Close on mouse middle click - disposables.add(addDisposableListener(tab, EventType.AUXCLICK, (e: MouseEvent) => { + disposables.add(addDisposableListener(tab, EventType.AUXCLICK, e => { if (e.button === 1 /* Middle Button*/) { EventHelper.stop(e, true /* for https://github.com/microsoft/vscode/issues/56715 */); @@ -674,7 +713,7 @@ export class TabsTitleControl extends TitleControl { })); // Context menu on Shift+F10 - disposables.add(addDisposableListener(tab, EventType.KEY_DOWN, (e: KeyboardEvent) => { + disposables.add(addDisposableListener(tab, EventType.KEY_DOWN, e => { const event = new StandardKeyboardEvent(e); if (event.shiftKey && event.keyCode === KeyCode.F10) { showContextMenu(e); @@ -687,7 +726,7 @@ export class TabsTitleControl extends TitleControl { })); // Keyboard accessibility - disposables.add(addDisposableListener(tab, EventType.KEY_UP, (e: KeyboardEvent) => { + disposables.add(addDisposableListener(tab, EventType.KEY_UP, e => { const event = new StandardKeyboardEvent(e); let handled = false; @@ -750,7 +789,7 @@ export class TabsTitleControl extends TitleControl { }); // Context menu - disposables.add(addDisposableListener(tab, EventType.CONTEXT_MENU, (e: Event) => { + disposables.add(addDisposableListener(tab, EventType.CONTEXT_MENU, e => { EventHelper.stop(e, true); const input = this.group.getEditorByIndex(index); @@ -760,7 +799,7 @@ export class TabsTitleControl extends TitleControl { }, true /* use capture to fix https://github.com/microsoft/vscode/issues/19145 */)); // Drag support - disposables.add(addDisposableListener(tab, EventType.DRAG_START, (e: DragEvent) => { + disposables.add(addDisposableListener(tab, EventType.DRAG_START, e => { const editor = this.group.getEditorByIndex(index); if (!editor) { return; @@ -1022,7 +1061,7 @@ export class TabsTitleControl extends TitleControl { this.updateEditorActionsToolbar(); // Ensure the active tab is always revealed - this.layout(this.dimension); + this.layout(this.dimensions); } private redrawTab(editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar): void { @@ -1092,12 +1131,14 @@ export class TabsTitleControl extends TitleControl { // or their first character of the name otherwise let name: string | undefined; let forceLabel = false; + let forceDisableBadgeDecorations = false; let description: string; if (options.pinnedTabSizing === 'compact' && this.group.isSticky(index)) { const isShowingIcons = options.showIcons && options.hasIcons; name = isShowingIcons ? '' : tabLabel.name?.charAt(0).toUpperCase(); description = ''; forceLabel = true; + forceDisableBadgeDecorations = true; // not enough space when sticky tabs are compact } else { name = tabLabel.name; description = tabLabel.description || ''; @@ -1116,7 +1157,16 @@ export class TabsTitleControl extends TitleControl { // Label tabLabelWidget.setResource( { name, description, resource: EditorResourceAccessor.getOriginalUri(editor, { supportSideBySide: SideBySideEditor.BOTH }) }, - { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor), forceLabel } + { + title, + extraClasses: ['tab-label'], + italic: !this.group.isPinned(editor), + forceLabel, + fileDecorations: { + colors: Boolean(options.decorations?.colors), + badges: forceDisableBadgeDecorations ? false : Boolean(options.decorations?.badges) + } + } ); // Tests helper @@ -1234,62 +1284,179 @@ export class TabsTitleControl extends TitleControl { } getDimensions(): IEditorGroupTitleDimensions { - let height = TabsTitleControl.TAB_HEIGHT; - if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) { - height += BreadcrumbsControl.HEIGHT; + let height: number; + + // Wrap: we need to ask `offsetHeight` to get + // the real height of the title area with wrapping. + if (this.accessor.partOptions.wrapTabs && this.tabsAndActionsContainer?.classList.contains('wrapping')) { + height = this.tabsAndActionsContainer.offsetHeight; + } else { + height = TabsTitleControl.TAB_HEIGHT; } - return { - height, - offset: TabsTitleControl.TAB_HEIGHT - }; + const offset = height; + + // Account for breadcrumbs if visible + if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) { + height += BreadcrumbsControl.HEIGHT; // Account for breadcrumbs if visible + } + + return { height, offset }; } - layout(dimension: Dimension | undefined): void { - this.dimension = dimension; + layout(dimensions: ITitleControlDimensions): Dimension { - const activeTabAndIndex = this.group.activeEditor ? this.getTabAndIndex(this.group.activeEditor) : undefined; - if (!activeTabAndIndex || !this.dimension) { - return; - } + // Remember dimensions that we get + Object.assign(this.dimensions, dimensions); // The layout of tabs can be an expensive operation because we access DOM properties // that can result in the browser doing a full page layout to validate them. To buffer // this a little bit we try at least to schedule this work on the next animation frame. if (!this.layoutScheduled.value) { this.layoutScheduled.value = scheduleAtNextAnimationFrame(() => { - const dimension = assertIsDefined(this.dimension); - this.doLayout(dimension); + this.doLayout(this.dimensions); this.layoutScheduled.clear(); }); } + + // First time layout: compute the dimensions and store it + if (!this.dimensions.used) { + this.dimensions.used = new Dimension(dimensions.container.width, this.getDimensions().height); + } + + return this.dimensions.used; } - private doLayout(dimension: Dimension): void { + private doLayout(dimensions: ITitleControlDimensions): void { + + // Only layout if we have valid tab index and dimensions const activeTabAndIndex = this.group.activeEditor ? this.getTabAndIndex(this.group.activeEditor) : undefined; - if (!activeTabAndIndex) { - return; // nothing to do if not editor opened + if (activeTabAndIndex && dimensions.container !== Dimension.None && dimensions.available !== Dimension.None) { + + // Breadcrumbs + this.doLayoutBreadcrumbs(dimensions); + + // Tabs + const [activeTab, activeIndex] = activeTabAndIndex; + this.doLayoutTabs(activeTab, activeIndex, dimensions); } - // Breadcrumbs - this.doLayoutBreadcrumbs(dimension); + // Remember the dimensions used in the control so that we can + // return it fast from the `layout` call without having to + // compute it over and over again + const oldDimension = this.dimensions.used; + const newDimension = this.dimensions.used = new Dimension(dimensions.container.width, this.getDimensions().height); - // Tabs - const [activeTab, activeIndex] = activeTabAndIndex; - this.doLayoutTabs(activeTab, activeIndex); + // In case the height of the title control changed from before + // (currently only possible if wrapping changed on/off), we need + // to signal this to the outside via a `relayout` call so that + // e.g. the editor control can be adjusted accordingly. + if (oldDimension && oldDimension.height !== newDimension.height) { + this.group.relayout(); + } } - private doLayoutBreadcrumbs(dimension: Dimension): void { + private doLayoutBreadcrumbs(dimensions: ITitleControlDimensions): void { if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) { - const tabsScrollbar = assertIsDefined(this.tabsScrollbar); - - this.breadcrumbsControl.layout(new Dimension(dimension.width, BreadcrumbsControl.HEIGHT)); - tabsScrollbar.getDomNode().style.height = `${dimension.height - BreadcrumbsControl.HEIGHT}px`; + this.breadcrumbsControl.layout(new Dimension(dimensions.container.width, BreadcrumbsControl.HEIGHT)); } } - private doLayoutTabs(activeTab: HTMLElement, activeIndex: number): void { + private doLayoutTabs(activeTab: HTMLElement, activeIndex: number, dimensions: ITitleControlDimensions): void { + + // Always first layout tabs with wrapping support even if wrapping + // is disabled. The result indicates if tabs wrap and if not, we + // need to proceed with the layout without wrapping because even + // if wrapping is enabled in settings, there are cases where + // wrapping is disabled (e.g. due to space constraints) + const tabsWrapMultiLine = this.doLayoutTabsWrapping(dimensions); + if (!tabsWrapMultiLine) { + this.doLayoutTabsNonWrapping(activeTab, activeIndex); + } + } + + private doLayoutTabsWrapping(dimensions: ITitleControlDimensions): boolean { + const [tabsAndActionsContainer, tabsContainer, editorToolbarContainer, tabsScrollbar] = assertAllDefined(this.tabsAndActionsContainer, this.tabsContainer, this.editorToolbarContainer, this.tabsScrollbar); + + // Handle wrapping tabs according to setting: + // - enabled: only add class if tabs wrap and don't exceed available dimensions + // - disabled: remove class and margin-right variable + + const didTabsWrapMultiLine = tabsAndActionsContainer.classList.contains('wrapping'); + let tabsWrapMultiLine = didTabsWrapMultiLine; + + function updateTabsWrapping(enabled: boolean): void { + tabsWrapMultiLine = enabled; + + // Toggle the `wrapped` class to enable wrapping + tabsAndActionsContainer.classList.toggle('wrapping', tabsWrapMultiLine); + + // Update `last-tab-margin-right` CSS variable to account for the absolute + // positioned editor actions container when tabs wrap. The margin needs to + // be the width of the editor actions container to avoid screen cheese. + tabsContainer.style.setProperty('--last-tab-margin-right', tabsWrapMultiLine ? `${editorToolbarContainer.offsetWidth}px` : '0'); + } + + // Setting enabled: selectively enable wrapping if possible + if (this.accessor.partOptions.wrapTabs) { + const visibleTabsWidth = tabsContainer.offsetWidth; + const allTabsWidth = tabsContainer.scrollWidth; + const lastTabFitsWrapped = () => { + const lastTab = this.getLastTab(); + if (!lastTab) { + return true; // no tab always fits + } + + return lastTab.offsetWidth <= (dimensions.available.width - editorToolbarContainer.offsetWidth); + }; + + // If tabs wrap or should start to wrap (when width exceeds visible width) + // we must trigger `updateWrapping` to set the `last-tab-margin-right` + // accordingly based on the number of actions. The margin is important to + // properly position the last tab apart from the actions + // + // We already check here if the last tab would fit when wrapped given the + // editor toolbar will also show right next to it. This ensures we are not + // enabling wrapping only to disable it again in the code below (this fixes + // flickering issue https://github.com/microsoft/vscode/issues/115050) + if (tabsWrapMultiLine || (allTabsWidth > visibleTabsWidth && lastTabFitsWrapped())) { + updateTabsWrapping(true); + } + + // Tabs wrap multiline: remove wrapping under certain size constraint conditions + if (tabsWrapMultiLine) { + if ( + (tabsContainer.offsetHeight > dimensions.available.height) || // if height exceeds available height + (allTabsWidth === visibleTabsWidth && tabsContainer.offsetHeight === TabsTitleControl.TAB_HEIGHT) || // if wrapping is not needed anymore + (!lastTabFitsWrapped()) // if last tab does not fit anymore + ) { + updateTabsWrapping(false); + } + } + } + + // Setting disabled: remove CSS traces only if tabs did wrap + else if (didTabsWrapMultiLine) { + updateTabsWrapping(false); + } + + // If we transitioned from non-wrapping to wrapping, we need + // to update the scrollbar to have an equal `width` and + // `scrollWidth`. Otherwise a scrollbar would appear which is + // never desired when wrapping. + if (tabsWrapMultiLine && !didTabsWrapMultiLine) { + const visibleTabsWidth = tabsContainer.offsetWidth; + tabsScrollbar.setScrollDimensions({ + width: visibleTabsWidth, + scrollWidth: visibleTabsWidth + }); + } + + return tabsWrapMultiLine; + } + + private doLayoutTabsNonWrapping(activeTab: HTMLElement, activeIndex: number): void { const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar); // @@ -1308,7 +1475,7 @@ export class TabsTitleControl extends TitleControl { // [-- Sticky Tabs Width --] // - const visibleTabsContainerWidth = tabsContainer.offsetWidth; + const visibleTabsWidth = tabsContainer.offsetWidth; const allTabsWidth = tabsContainer.scrollWidth; // Compute width of sticky tabs depending on pinned tab sizing @@ -1331,17 +1498,17 @@ export class TabsTitleControl extends TitleControl { } // Figure out if active tab is positioned static which has an - // impact on wether to reveal the tab or not later + // impact on whether to reveal the tab or not later let activeTabPositionStatic = this.accessor.partOptions.pinnedTabSizing !== 'normal' && this.group.isSticky(activeIndex); // Special case: we have sticky tabs but the available space for showing tabs // is little enough that we need to disable sticky tabs sticky positioning // so that tabs can be scrolled at naturally. - let availableTabsContainerWidth = visibleTabsContainerWidth - stickyTabsWidth; + let availableTabsContainerWidth = visibleTabsWidth - stickyTabsWidth; if (this.group.stickyCount > 0 && availableTabsContainerWidth < TabsTitleControl.TAB_WIDTH.fit) { tabsContainer.classList.add('disable-sticky-tabs'); - availableTabsContainerWidth = visibleTabsContainerWidth; + availableTabsContainerWidth = visibleTabsWidth; stickyTabsWidth = 0; activeTabPositionStatic = false; } else { @@ -1358,7 +1525,7 @@ export class TabsTitleControl extends TitleControl { // Update scrollbar tabsScrollbar.setScrollDimensions({ - width: visibleTabsContainerWidth, + width: visibleTabsWidth, scrollWidth: allTabsWidth }); @@ -1426,15 +1593,28 @@ export class TabsTitleControl extends TitleControl { private getTabAndIndex(editor: IEditorInput): [HTMLElement, number /* index */] | undefined { const editorIndex = this.group.getIndexOfEditor(editor); - if (editorIndex >= 0) { - const tabsContainer = assertIsDefined(this.tabsContainer); - - return [tabsContainer.children[editorIndex] as HTMLElement, editorIndex]; + const tab = this.getTabAtIndex(editorIndex); + if (tab) { + return [tab, editorIndex]; } return undefined; } + private getTabAtIndex(editorIndex: number): HTMLElement | undefined { + if (editorIndex >= 0) { + const tabsContainer = assertIsDefined(this.tabsContainer); + + return tabsContainer.children[editorIndex] as HTMLElement | undefined; + } + + return undefined; + } + + private getLastTab(): HTMLElement | undefined { + return this.getTabAtIndex(this.group.count - 1); + } + private blockRevealActiveTabOnce(): void { // When closing tabs through the tab close button or gesture, the user @@ -1527,16 +1707,28 @@ export class TabsTitleControl extends TitleControl { } } -registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme, collector) => { // Add border between tabs and breadcrumbs in high contrast mode. if (theme.type === ColorScheme.HIGH_CONTRAST) { const borderColor = (theme.getColor(TAB_BORDER) || theme.getColor(contrastBorder)); + if (borderColor) { + collector.addRule(` + .monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container { + border-bottom: 1px solid ${borderColor}; + } + `); + } + } + + // Add bottom border to tabs when wrapping + const borderColor = theme.getColor(TAB_BORDER); + if (borderColor) { collector.addRule(` - .monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container { - border-bottom: 1px solid ${borderColor}; - } - `); + .monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab { + border-bottom: 1px solid ${borderColor}; + } + `); } // Styling with Outline color (e.g. high contrast theme) diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 4557a2c48..7ae3677e1 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import * as objects from 'vs/base/common/objects'; -import { isFunction, isObject, isArray, assertIsDefined } from 'vs/base/common/types'; +import { isFunction, isObject, isArray, assertIsDefined, withUndefinedAsNull } from 'vs/base/common/types'; import { IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditorOptions, IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor'; @@ -103,7 +103,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan } createEditorControl(parent: HTMLElement, configuration: ICodeEditorOptions): IDiffEditor { - return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration); + return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration, {}); } async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { @@ -132,8 +132,8 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan // Set Editor Model const diffEditor = assertIsDefined(this.getControl()); - const resolvedDiffEditorModel = resolvedModel; - diffEditor.setModel(resolvedDiffEditorModel.textDiffEditorModel); + const resolvedDiffEditorModel = resolvedModel as TextDiffEditorModel; + diffEditor.setModel(withUndefinedAsNull(resolvedDiffEditorModel.textDiffEditorModel)); // Apply Options from TextOptions let optionsGotApplied = false; diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index 9cae4d214..7ec16141d 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -233,7 +233,6 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa return undefined; } - protected saveTextEditorViewState(resource: URI, cleanUpOnDispose?: IEditorInput): void { const editorViewState = this.retrieveTextEditorViewState(resource); if (!editorViewState || !this.group) { diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index 8358dfcba..6b7e1e5e9 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -9,14 +9,13 @@ import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { ActionsOrientation, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; -import { IAction, IRunEvent, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, IActionViewItem } from 'vs/base/common/actions'; -import * as arrays from 'vs/base/common/arrays'; +import { IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, IActionViewItem } from 'vs/base/common/actions'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { localize } from 'vs/nls'; -import { createAndFillInActionBarActions, createAndFillInContextMenuActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { ExecuteCommandAction, IMenu, IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { createActionViewItem, createAndFillInActionBarActions, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -26,7 +25,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { listActiveSelectionBackground, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry'; -import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService'; import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillResourceDataTransfers, LocalSelectionTransfer } from 'vs/workbench/browser/dnd'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs'; @@ -34,7 +33,6 @@ import { BreadcrumbsControl, IBreadcrumbsControlOptions } from 'vs/workbench/bro import { IEditorGroupsAccessor, IEditorGroupTitleDimensions, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, EditorResourceAccessor, IEditorPartOptions, SideBySideEditor, ActiveEditorPinnedContext, ActiveEditorStickyContext } from 'vs/workbench/common/editor'; import { ResourceContextKey } from 'vs/workbench/common/resources'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IFileService } from 'vs/platform/files/common/files'; import { withNullAsUndefined, withUndefinedAsNull, assertIsDefined } from 'vs/base/common/types'; @@ -46,6 +44,20 @@ export interface IToolbarActions { secondary: IAction[]; } +export interface ITitleControlDimensions { + + /** + * The size of the parent container the title control is layed out in. + */ + container: Dimension; + + /** + * The maximum size the title control is allowed to consume based on + * other controls that are positioned inside the container. + */ + available: Dimension; +} + export abstract class TitleControl extends Themable { protected readonly groupTransfer = LocalSelectionTransfer.getInstance(); @@ -53,9 +65,6 @@ export abstract class TitleControl extends Themable { protected breadcrumbsControl: BreadcrumbsControl | undefined = undefined; - private currentPrimaryEditorActionIds: string[] = []; - private currentSecondaryEditorActionIds: string[] = []; - private editorActionsToolbar: ToolBar | undefined; private resourceContext: ResourceContextKey; @@ -79,7 +88,6 @@ export abstract class TitleControl extends Themable { @IMenuService private readonly menuService: IMenuService, @IQuickInputService protected quickInputService: IQuickInputService, @IThemeService themeService: IThemeService, - @IExtensionService private readonly extensionService: IExtensionService, @IConfigurationService protected configurationService: IConfigurationService, @IFileService private readonly fileService: IFileService ) { @@ -92,13 +100,6 @@ export abstract class TitleControl extends Themable { this.contextMenu = this._register(this.menuService.createMenu(MenuId.EditorTitleContext, this.contextKeyService)); this.create(parent); - this.registerListeners(); - } - - protected registerListeners(): void { - - // Update actions toolbar when extension register that may contribute them - this._register(this.extensionService.onDidRegisterExtensions(() => this.updateEditorActionsToolbar())); } protected abstract create(parent: HTMLElement): void; @@ -147,7 +148,7 @@ export abstract class TitleControl extends Themable { this.editorActionsToolbar.context = context; // Action Run Handling - this._register(this.editorActionsToolbar.actionRunner.onDidRun((e: IRunEvent) => { + this._register(this.editorActionsToolbar.actionRunner.onDidRun(e => { // Notify for Error this.notificationService.error(e.error); @@ -172,35 +173,14 @@ export abstract class TitleControl extends Themable { } // Check extensions - if (action instanceof MenuItemAction) { - return this.instantiationService.createInstance(MenuEntryActionViewItem, action); - } else if (action instanceof SubmenuItemAction) { - return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action); - } - - return undefined; + return createActionViewItem(this.instantiationService, action); } protected updateEditorActionsToolbar(): void { - - // Update Editor Actions Toolbar const { primaryEditorActions, secondaryEditorActions } = this.prepareEditorActions(this.getEditorActions()); - // Only update if something actually has changed - const primaryEditorActionIds = primaryEditorActions.map(a => a.id); - const secondaryEditorActionIds = secondaryEditorActions.map(a => a.id); - if ( - !arrays.equals(primaryEditorActionIds, this.currentPrimaryEditorActionIds) || - !arrays.equals(secondaryEditorActionIds, this.currentSecondaryEditorActionIds) || - primaryEditorActions.some(action => action instanceof ExecuteCommandAction) || // execute command actions can have the same ID but different arguments - secondaryEditorActions.some(action => action instanceof ExecuteCommandAction) // see also https://github.com/microsoft/vscode/issues/16298 - ) { - const editorActionsToolbar = assertIsDefined(this.editorActionsToolbar); - editorActionsToolbar.setActions(primaryEditorActions, secondaryEditorActions); - - this.currentPrimaryEditorActionIds = primaryEditorActionIds; - this.currentSecondaryEditorActionIds = secondaryEditorActionIds; - } + const editorActionsToolbar = assertIsDefined(this.editorActionsToolbar); + editorActionsToolbar.setActions(primaryEditorActions, secondaryEditorActions); } protected prepareEditorActions(editorActions: IToolbarActions): { primaryEditorActions: IAction[]; secondaryEditorActions: IAction[]; } { @@ -251,18 +231,13 @@ export abstract class TitleControl extends Themable { } protected clearEditorActionsToolbar(): void { - if (this.editorActionsToolbar) { - this.editorActionsToolbar.setActions([], []); - } - - this.currentPrimaryEditorActionIds = []; - this.currentSecondaryEditorActionIds = []; + this.editorActionsToolbar?.setActions([], []); } protected enableGroupDragging(element: HTMLElement): void { // Drag start - this._register(addDisposableListener(element, EventType.DRAG_START, (e: DragEvent) => { + this._register(addDisposableListener(element, EventType.DRAG_START, e => { if (e.target !== element) { return; // only if originating from tabs container } @@ -354,7 +329,7 @@ export abstract class TitleControl extends Themable { getAnchor: () => anchor, getActions: () => actions, getActionsContext: () => ({ groupId: this.group.id, editorIndex: this.group.getIndexOfEditor(editor) }), - getKeyBinding: (action) => this.getKeybinding(action), + getKeyBinding: action => this.getKeybinding(action), onHide: () => { // restore previous contexts @@ -407,7 +382,7 @@ export abstract class TitleControl extends Themable { abstract updateStyles(): void; - abstract layout(dimension: Dimension): void; + abstract layout(dimensions: ITitleControlDimensions): Dimension; abstract getDimensions(): IEditorGroupTitleDimensions; @@ -419,7 +394,7 @@ export abstract class TitleControl extends Themable { } } -registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme, collector) => { // Drag Feedback const dragImageBackground = theme.getColor(listActiveSelectionBackground); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index d4074bbeb..8113a026b 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/notificationsCenter'; import 'vs/css!./media/notificationsActions'; import { NOTIFICATIONS_BORDER, NOTIFICATIONS_CENTER_HEADER_FOREGROUND, NOTIFICATIONS_CENTER_HEADER_BACKGROUND, NOTIFICATIONS_CENTER_BORDER } from 'vs/workbench/common/theme'; -import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, Themable } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService'; import { INotificationsModel, INotificationChangeEvent, NotificationChangeType, NotificationViewItemContentChangeKind } from 'vs/workbench/common/notifications'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { Emitter } from 'vs/base/common/event'; @@ -313,7 +313,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente } } -registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme, collector) => { const notificationBorderColor = theme.getColor(NOTIFICATIONS_BORDER); if (notificationBorderColor) { collector.addRule(`.monaco-workbench > .notifications-center .notifications-list-container .monaco-list-row[data-last-element="false"] > .notification-list-item { border-bottom: 1px solid ${notificationBorderColor}; }`); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsList.ts b/src/vs/workbench/browser/parts/notifications/notificationsList.ts index 7cbfb84f2..b1e8112ea 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsList.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsList.ts @@ -10,7 +10,7 @@ import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IListOptions } from 'vs/base/browser/ui/list/listWidget'; import { NOTIFICATIONS_LINKS, NOTIFICATIONS_BACKGROUND, NOTIFICATIONS_FOREGROUND, NOTIFICATIONS_ERROR_ICON_FOREGROUND, NOTIFICATIONS_WARNING_ICON_FOREGROUND, NOTIFICATIONS_INFO_ICON_FOREGROUND } from 'vs/workbench/common/theme'; -import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, Themable } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService'; import { contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; import { INotificationViewItem } from 'vs/workbench/common/notifications'; import { NotificationsListDelegate, NotificationRenderer } from 'vs/workbench/browser/parts/notifications/notificationsViewer'; @@ -278,7 +278,7 @@ export class NotificationsList extends Themable { } } -registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme, collector) => { const linkColor = theme.getColor(NOTIFICATIONS_LINKS); if (linkColor) { collector.addRule(`.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-message a { color: ${linkColor}; }`); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index 51272ab8a..2c8b0f465 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -470,7 +470,9 @@ export class NotificationTemplateRenderer extends Disposable { : buttonToolbar.addButton(buttonOptions)); button.label = action.label; this.inputDisposables.add(button.onDidClick(e => { - EventHelper.stop(e, true); + if (e) { + EventHelper.stop(e, true); + } actionRunner.run(action); })); diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 74619ecec..372e78f8a 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -5,45 +5,26 @@ import 'vs/css!./media/panelpart'; import * as nls from 'vs/nls'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Action } from 'vs/base/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions, CATEGORIES } from 'vs/workbench/common/actions'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IWorkbenchLayoutService, Parts, Position, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { ActivityAction, ToggleCompositePinnedAction, ICompositeBar } from 'vs/workbench/browser/parts/compositeBarActions'; import { IActivity } from 'vs/workbench/common/activity'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { ActivePanelContext, PanelPositionContext } from 'vs/workbench/common/panel'; -import { ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; +import { ActivePanelContext, PanelMaximizedContext, PanelPositionContext, PanelVisibleContext } from 'vs/workbench/common/panel'; +import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { ViewContainerLocationToString, ViewContainerLocation } from 'vs/workbench/common/views'; const maximizeIcon = registerIcon('panel-maximize', Codicon.chevronUp, nls.localize('maximizeIcon', 'Icon to maximize a panel.')); const restoreIcon = registerIcon('panel-restore', Codicon.chevronDown, nls.localize('restoreIcon', 'Icon to restore a panel.')); const closeIcon = registerIcon('panel-close', Codicon.close, nls.localize('closeIcon', 'Icon to close a panel.')); -export class ClosePanelAction extends Action { - - static readonly ID = 'workbench.action.closePanel'; - static readonly LABEL = nls.localize('closePanel', "Close Panel"); - - constructor( - id: string, - name: string, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService - ) { - super(id, name, ThemeIcon.asClassName(closeIcon)); - } - - async run(): Promise { - this.layoutService.setPanelHidden(true); - } -} - export class TogglePanelAction extends Action { static readonly ID = 'workbench.action.togglePanel'; @@ -91,46 +72,6 @@ class FocusPanelAction extends Action { } } - -export class ToggleMaximizedPanelAction extends Action { - - static readonly ID = 'workbench.action.toggleMaximizedPanel'; - static readonly LABEL = nls.localize('toggleMaximizedPanel', "Toggle Maximized Panel"); - - private static readonly MAXIMIZE_LABEL = nls.localize('maximizePanel', "Maximize Panel Size"); - private static readonly RESTORE_LABEL = nls.localize('minimizePanel', "Restore Panel Size"); - - private readonly toDispose = this._register(new DisposableStore()); - - constructor( - id: string, - label: string, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IEditorGroupsService editorGroupsService: IEditorGroupsService - ) { - super(id, label, layoutService.isPanelMaximized() ? ThemeIcon.asClassName(restoreIcon) : ThemeIcon.asClassName(maximizeIcon)); - - this.toDispose.add(editorGroupsService.onDidLayout(() => { - const maximized = this.layoutService.isPanelMaximized(); - this.class = maximized ? ThemeIcon.asClassName(restoreIcon) : ThemeIcon.asClassName(maximizeIcon); - this.label = maximized ? ToggleMaximizedPanelAction.RESTORE_LABEL : ToggleMaximizedPanelAction.MAXIMIZE_LABEL; - })); - } - - async run(): Promise { - if (!this.layoutService.isVisible(Parts.PANEL_PART)) { - this.layoutService.setPanelHidden(false); - // If the panel is not already maximized, maximize it - if (!this.layoutService.isPanelMaximized()) { - this.layoutService.toggleMaximizedPanel(); - } - } - else { - this.layoutService.toggleMaximizedPanel(); - } - } -} - const PositionPanelActionId = { LEFT: 'workbench.action.positionPanelLeft', RIGHT: 'workbench.action.positionPanelRight', @@ -218,7 +159,6 @@ export class PlaceHolderToggleCompositePinnedAction extends ToggleCompositePinne } } - export class SwitchPanelViewAction extends Action { constructor( @@ -287,35 +227,117 @@ export class NextPanelViewAction extends SwitchPanelViewAction { const actionRegistry = Registry.as(WorkbenchExtensions.WorkbenchActions); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(TogglePanelAction, { primary: KeyMod.CtrlCmd | KeyCode.KEY_J }), 'View: Toggle Panel', CATEGORIES.View.value); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(FocusPanelAction), 'View: Focus into Panel', CATEGORIES.View.value); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleMaximizedPanelAction), 'View: Toggle Maximized Panel', CATEGORIES.View.value); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ClosePanelAction), 'View: Close Panel', CATEGORIES.View.value); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(PreviousPanelViewAction), 'View: Previous Panel View', CATEGORIES.View.value); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(NextPanelViewAction), 'View: Next Panel View', CATEGORIES.View.value); -MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '2_workbench_layout', - command: { - id: TogglePanelAction.ID, - title: nls.localize({ key: 'miShowPanel', comment: ['&& denotes a mnemonic'] }, "Show &&Panel"), - toggled: ActivePanelContext - }, - order: 5 +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.toggleMaximizedPanel', + title: { value: nls.localize('toggleMaximizedPanel', "Toggle Maximized Panel"), original: 'Toggle Maximized Panel' }, + tooltip: nls.localize('maximizePanel', "Maximize Panel Size"), + category: CATEGORIES.View, + f1: true, + icon: maximizeIcon, + toggled: { condition: PanelMaximizedContext, icon: restoreIcon, tooltip: nls.localize('minimizePanel', "Restore Panel Size") }, + menu: [{ + id: MenuId.PanelTitle, + group: 'navigation', + order: 1 + }] + }); + } + run(accessor: ServicesAccessor) { + const layoutService = accessor.get(IWorkbenchLayoutService); + if (!layoutService.isVisible(Parts.PANEL_PART)) { + layoutService.setPanelHidden(false); + // If the panel is not already maximized, maximize it + if (!layoutService.isPanelMaximized()) { + layoutService.toggleMaximizedPanel(); + } + } + else { + layoutService.toggleMaximizedPanel(); + } + } }); +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.closePanel', + title: { value: nls.localize('closePanel', "Close Panel"), original: 'Close Panel' }, + category: CATEGORIES.View, + icon: closeIcon, + menu: [{ + id: MenuId.CommandPalette, + when: PanelVisibleContext, + }, { + id: MenuId.PanelTitle, + group: 'navigation', + order: 2 + }] + }); + } + run(accessor: ServicesAccessor) { + accessor.get(IWorkbenchLayoutService).setPanelHidden(true); + } +}); + +MenuRegistry.appendMenuItems([ + { + id: MenuId.MenubarAppearanceMenu, + item: { + group: '2_workbench_layout', + command: { + id: TogglePanelAction.ID, + title: nls.localize({ key: 'miShowPanel', comment: ['&& denotes a mnemonic'] }, "Show &&Panel"), + toggled: ActivePanelContext + }, + order: 5 + } + }, { + id: MenuId.ViewTitleContext, + item: { + group: '3_workbench_layout_move', + command: { + id: TogglePanelAction.ID, + title: { value: nls.localize('hidePanel', "Hide Panel"), original: 'Hide Panel' }, + }, + when: ContextKeyExpr.and(PanelVisibleContext, ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Panel))), + order: 2 + } + } +]); + function registerPositionPanelActionById(config: PanelActionConfig) { const { id, label, alias, when } = config; // register the workbench action actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SetPanelPositionAction, id, label), alias, CATEGORIES.View.value, when); // register as a menu item - MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '3_workbench_layout_move', - command: { - id, - title: label - }, - when, - order: 5 - }); + MenuRegistry.appendMenuItems([{ + id: MenuId.MenubarAppearanceMenu, + item: { + group: '3_workbench_layout_move', + command: { + id, + title: label + }, + when, + order: 5 + } + }, { + id: MenuId.ViewTitleContext, + item: { + group: '3_workbench_layout_move', + command: { + id: id, + title: label, + }, + when: ContextKeyExpr.and(when, ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Panel))), + order: 1 + } + }]); } // register each position panel action diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 332a9ab4c..b4727ebc0 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/panelpart'; -import { IAction, Action } from 'vs/base/common/actions'; +import { localize } from 'vs/nls'; +import { IAction, Separator, toAction } from 'vs/base/common/actions'; import { Event } from 'vs/base/common/event'; import { Registry } from 'vs/platform/registry/common/platform'; import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -18,8 +19,8 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ClosePanelAction, PanelActivityAction, ToggleMaximizedPanelAction, TogglePanelAction, PlaceHolderPanelActivityAction, PlaceHolderToggleCompositePinnedAction, PositionPanelActionConfigs, SetPanelPositionAction } from 'vs/workbench/browser/parts/panel/panelActions'; -import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { PanelActivityAction, TogglePanelAction, PlaceHolderPanelActivityAction, PlaceHolderToggleCompositePinnedAction, PositionPanelActionConfigs, SetPanelPositionAction } from 'vs/workbench/browser/parts/panel/panelActions'; +import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_INPUT_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, PANEL_DRAG_AND_DROP_BORDER } from 'vs/workbench/common/theme'; import { activeContrastBorder, focusBorder, contrastBorder, editorBackground, badgeBackground, badgeForeground } from 'vs/platform/theme/common/colorRegistry'; import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbench/browser/parts/compositeBar'; @@ -27,15 +28,12 @@ import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/composit import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { Dimension, trackFocus, EventHelper } from 'vs/base/browser/dom'; -import { localize } from 'vs/nls'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ViewContainer, IViewDescriptorService, IViewContainerModel, ViewContainerLocation } from 'vs/workbench/common/views'; -import { MenuId } from 'vs/platform/actions/common/actions'; -import { ViewMenuActions, ViewContainerMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { Before2D, CompositeDragAndDropObserver, ICompositeDragAndDrop, toggleDropEffect } from 'vs/workbench/browser/dnd'; import { IActivity } from 'vs/workbench/common/activity'; @@ -46,7 +44,7 @@ interface ICachedPanel { pinned: boolean; order?: number; visible: boolean; - views?: { when?: string }[]; + views?: { when?: string; }[]; } interface IPlaceholderViewContainer { @@ -148,24 +146,27 @@ export class PanelPart extends CompositePart implements IPanelService { this.compositeBar = this._register(this.instantiationService.createInstance(CompositeBar, this.getCachedPanels(), { icon: false, orientation: ActionsOrientation.HORIZONTAL, - openComposite: (compositeId: string) => this.openPanel(compositeId, true).then(panel => panel || null), - getActivityAction: (compositeId: string) => this.getCompositeActions(compositeId).activityAction, - getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction, - getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(PanelActivityAction, assertIsDefined(this.getPanel(compositeId))), - getContextMenuActions: () => [ - ...PositionPanelActionConfigs - // show the contextual menu item if it is not in that position - .filter(({ when }) => contextKeyService.contextMatchesRules(when)) - .map(({ id, label }) => this.instantiationService.createInstance(SetPanelPositionAction, id, label)), - this.instantiationService.createInstance(TogglePanelAction, TogglePanelAction.ID, localize('hidePanel', "Hide Panel")) - ] as Action[], - getContextMenuActionsForComposite: (compositeId: string) => this.getContextMenuActionsForComposite(compositeId) as Action[], + openComposite: compositeId => this.openPanel(compositeId, true).then(panel => panel || null), + getActivityAction: compositeId => this.getCompositeActions(compositeId).activityAction, + getCompositePinnedAction: compositeId => this.getCompositeActions(compositeId).pinnedAction, + getOnCompositeClickAction: compositeId => this.instantiationService.createInstance(PanelActivityAction, assertIsDefined(this.getPanel(compositeId))), + fillExtraContextMenuActions: actions => { + actions.push(...[ + new Separator(), + ...PositionPanelActionConfigs + // show the contextual menu item if it is not in that position + .filter(({ when }) => contextKeyService.contextMatchesRules(when)) + .map(({ id, label }) => this.instantiationService.createInstance(SetPanelPositionAction, id, label)), + this.instantiationService.createInstance(TogglePanelAction, TogglePanelAction.ID, localize('hidePanel', "Hide Panel")) + ]); + }, + getContextMenuActionsForComposite: compositeId => this.getContextMenuActionsForComposite(compositeId), getDefaultCompositeId: () => this.panelRegistry.getDefaultPanelId(), hidePart: () => this.layoutService.setPanelHidden(true), dndHandler: this.dndHandler, compositeSize: 0, overflowActionSize: 44, - colors: (theme: IColorTheme) => ({ + colors: theme => ({ activeBackgroundColor: theme.getColor(PANEL_BACKGROUND), // Background color for overflow action inactiveBackgroundColor: theme.getColor(PANEL_BACKGROUND), // Background color for overflow action activeBorderBottomColor: theme.getColor(PANEL_ACTIVE_TITLE_BORDER), @@ -184,20 +185,21 @@ export class PanelPart extends CompositePart implements IPanelService { this.onDidRegisterPanels([...this.getPanels()]); } - private getContextMenuActionsForComposite(compositeId: string): readonly IAction[] { + private getContextMenuActionsForComposite(compositeId: string): IAction[] { const result: IAction[] = []; - const container = this.getViewContainer(compositeId); - if (container) { - const viewContainerModel = this.viewDescriptorService.getViewContainerModel(container); + const viewContainer = this.viewDescriptorService.getViewContainerById(compositeId)!; + const defaultLocation = this.viewDescriptorService.getDefaultViewContainerLocation(viewContainer)!; + if (defaultLocation !== this.viewDescriptorService.getViewContainerLocation(viewContainer)) { + result.push(toAction({ id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => this.viewDescriptorService.moveViewContainerToLocation(viewContainer, defaultLocation) })); + } else { + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); if (viewContainerModel.allViewDescriptors.length === 1) { - const viewMenuActions = this.instantiationService.createInstance(ViewMenuActions, viewContainerModel.allViewDescriptors[0].id, MenuId.ViewTitle, MenuId.ViewTitleContext); - result.push(...viewMenuActions.getContextMenuActions()); - viewMenuActions.dispose(); + const viewToReset = viewContainerModel.allViewDescriptors[0]; + const defaultContainer = this.viewDescriptorService.getDefaultContainerById(viewToReset.id)!; + if (defaultContainer !== viewContainer) { + result.push(toAction({ id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => this.viewDescriptorService.moveViewsToContainer([viewToReset], defaultContainer) })); + } } - - const viewContainerMenuActions = this.instantiationService.createInstance(ViewContainerMenuActions, container.id, MenuId.ViewContainerTitleContext); - result.push(...viewContainerMenuActions.getContextMenuActions()); - viewContainerMenuActions.dispose(); } return result; } @@ -534,13 +536,6 @@ export class PanelPart extends CompositePart implements IPanelService { .sort((p1, p2) => pinnedCompositeIds.indexOf(p1.id) - pinnedCompositeIds.indexOf(p2.id)); } - protected getActions(): ReadonlyArray { - return [ - this.instantiationService.createInstance(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, ToggleMaximizedPanelAction.LABEL), - this.instantiationService.createInstance(ClosePanelAction, ClosePanelAction.ID, ClosePanelAction.LABEL) - ]; - } - getActivePanel(): IPanel | undefined { return this.getActiveComposite(); } @@ -803,7 +798,7 @@ export class PanelPart extends CompositePart implements IPanelService { } } -registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme, collector) => { // Panel Background: since panels can host editors, we apply a background rule if the panel background // color is different from the editor background color. This is a bit of a hack though. The better way diff --git a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css index c5953e535..c581f528b 100644 --- a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css +++ b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css @@ -3,11 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* Removed to allow progress bar positioning to escape */ -/* .monaco-workbench .sidebar > .content { - overflow: hidden; -} */ - .monaco-workbench.nosidebar > .part.sidebar { display: none !important; visibility: hidden !important; diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index f6504de93..671a05822 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -6,7 +6,6 @@ import 'vs/css!./media/sidebarpart'; import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Action } from 'vs/base/common/actions'; import { CompositePart } from 'vs/workbench/browser/parts/compositePart'; import { Viewlet, ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; @@ -288,8 +287,8 @@ export class SidebarPart extends CompositePart implements IViewletServi const anchor: { x: number, y: number } = { x: event.posx, y: event.posy }; this.contextMenuService.showContextMenu({ getAnchor: () => anchor, - getActions: () => contextMenuActions, - getActionViewItem: action => this.actionViewItemProvider(action as Action), + getActions: () => contextMenuActions.slice(), + getActionViewItem: action => this.actionViewItemProvider(action), actionRunner: activeViewlet.getActionRunner() }); } diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 759039699..aa9d9f05f 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/statusbarpart'; import * as nls from 'vs/nls'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { dispose, IDisposable, Disposable, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { CodiconLabel } from 'vs/base/browser/ui/codicons/codiconLabel'; +import { SimpleIconLabel } from 'vs/base/browser/ui/iconLabel/simpleIconLabel'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { Part } from 'vs/workbench/browser/part'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -15,13 +15,12 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntry, IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/common/statusbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Action, IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, Separator } from 'vs/base/common/actions'; -import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, ThemeColor } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, ThemeColor } from 'vs/platform/theme/common/themeService'; import { STATUS_BAR_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_ITEM_HOVER_BACKGROUND, STATUS_BAR_ITEM_ACTIVE_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_FOREGROUND, STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND, STATUS_BAR_BORDER, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER } from 'vs/workbench/common/theme'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { isThemeColor } from 'vs/editor/common/editorCommon'; -import { Color } from 'vs/base/common/color'; -import { EventHelper, createStyleSheet, addDisposableListener, EventType, hide, show, isAncestor, appendChildren } from 'vs/base/browser/dom'; +import { EventHelper, createStyleSheet, addDisposableListener, EventType, hide, show, isAncestor, append } from 'vs/base/browser/dom'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage'; import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -38,7 +37,8 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ColorScheme } from 'vs/platform/theme/common/theme'; -import { renderCodicon, renderCodicons } from 'vs/base/browser/codicons'; +import { renderIcon, renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { syncing } from 'vs/platform/theme/common/iconRegistry'; interface IPendingStatusbarEntry { id: string; @@ -610,7 +610,7 @@ export class StatusbarPart extends Part implements IStatusbarService { } private getContextMenuActions(event: StandardMouseEvent): IAction[] { - const actions: Action[] = []; + const actions: IAction[] = []; // Provide an action to hide the status bar at last actions.push(this.instantiationService.createInstance(ToggleStatusbarVisibilityAction, ToggleStatusbarVisibilityAction.ID, nls.localize('hideStatusBar', "Hide Status Bar"))); @@ -704,9 +704,9 @@ export class StatusbarPart extends Part implements IStatusbarService { } } -class StatusBarCodiconLabel extends CodiconLabel { +class StatusBarCodiconLabel extends SimpleIconLabel { - private readonly progressCodicon = renderCodicon('sync', 'spin'); + private readonly progressCodicon = renderIcon(syncing); private currentText = ''; private currentShowProgress = false; @@ -749,7 +749,7 @@ class StatusBarCodiconLabel extends CodiconLabel { } // Append new elements - appendChildren(this.container, ...renderCodicons(textContent)); + append(this.container, ...renderLabelWithIcons(textContent)); } // No Progress: no special handling @@ -868,12 +868,8 @@ class StatusbarEntryItem extends Disposable { // Update: Background if (!this.entry || entry.backgroundColor !== this.entry.backgroundColor) { - if (entry.backgroundColor) { - this.applyColor(this.container, entry.backgroundColor, true); - this.container.classList.add('has-background-color'); - } else { - this.container.classList.remove('has-background-color'); - } + this.container.classList.toggle('has-background-color', !!entry.backgroundColor); + this.applyColor(this.container, entry.backgroundColor, true); } // Remember for next round @@ -893,7 +889,7 @@ class StatusbarEntryItem extends Disposable { } private applyColor(container: HTMLElement, color: string | ThemeColor | undefined, isBackground?: boolean): void { - let colorResult: string | null = null; + let colorResult: string | undefined = undefined; if (isBackground) { this.backgroundListener.clear(); @@ -903,15 +899,15 @@ class StatusbarEntryItem extends Disposable { if (color) { if (isThemeColor(color)) { - colorResult = (this.themeService.getColorTheme().getColor(color.id) || Color.transparent).toString(); + colorResult = this.themeService.getColorTheme().getColor(color.id)?.toString(); const listener = this.themeService.onDidColorThemeChange(theme => { - const colorValue = (theme.getColor(color.id) || Color.transparent).toString(); + const colorValue = theme.getColor(color.id)?.toString(); if (isBackground) { - container.style.backgroundColor = colorValue; + container.style.backgroundColor = colorValue ?? ''; } else { - container.style.color = colorValue; + container.style.color = colorValue ?? ''; } }); @@ -926,9 +922,9 @@ class StatusbarEntryItem extends Disposable { } if (isBackground) { - container.style.backgroundColor = colorResult || ''; + container.style.backgroundColor = colorResult ?? ''; } else { - container.style.color = colorResult || ''; + container.style.color = colorResult ?? ''; } } @@ -942,7 +938,7 @@ class StatusbarEntryItem extends Disposable { } } -registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme, collector) => { if (theme.type !== ColorScheme.HIGH_CONTRAST) { const statusBarItemHoverBackground = theme.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND); if (statusBarItemHoverBackground) { diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index a4c34e450..7ac774cb7 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -85,11 +85,34 @@ height: 100%; position: relative; z-index: 3000; + flex-shrink: 0; +} + +.monaco-workbench .part.titlebar > .window-appicon:not(.codicon) { background-image: url('../../../media/code-icon.svg'); background-repeat: no-repeat; background-position: center center; background-size: 16px; - flex-shrink: 0; +} + +.monaco-workbench .part.titlebar .window-appicon > .home-bar-icon-badge { + position: absolute; + right: 9px; + bottom: 6px; + width: 8px; + height: 8px; + z-index: 1; /* on top of home indicator */ + background-image: url('../../../media/code-icon.svg'); + background-repeat: no-repeat; + background-position: center center; + background-size: 8px; + pointer-events: none; + border-top: 1px solid transparent; + border-left: 1px solid transparent; +} + +.monaco-workbench .part.titlebar > .window-appicon.codicon { + line-height: 30px; } .monaco-workbench.fullscreen .part.titlebar > .window-appicon { diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 99fcf2b6e..886c2ff68 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { IMenuService, MenuId, IMenu, SubmenuItemAction, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; -import { registerThemingParticipant, IColorTheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService'; +import { IMenuService, MenuId, IMenu, SubmenuItemAction, registerAction2, Action2, MenuRegistry, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService'; import { MenuBarVisibility, getTitleBarStyle, IWindowOpenable, getMenuBarVisibility } from 'vs/platform/windows/common/windows'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IAction, Action, SubmenuAction, Separator } from 'vs/base/common/actions'; import * as DOM from 'vs/base/browser/dom'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -37,6 +37,7 @@ import { BrowserFeatures } from 'vs/base/browser/canIUse'; import { KeyCode } from 'vs/base/common/keyCodes'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; +import { ICommandService } from 'vs/platform/commands/common/commands'; export abstract class MenubarControl extends Disposable { @@ -91,7 +92,8 @@ export abstract class MenubarControl extends Disposable { protected readonly preferencesService: IPreferencesService, protected readonly environmentService: IWorkbenchEnvironmentService, protected readonly accessibilityService: IAccessibilityService, - protected readonly hostService: IHostService + protected readonly hostService: IHostService, + protected readonly commandService: ICommandService ) { super(); @@ -308,23 +310,10 @@ export class CustomMenubarControl extends MenubarControl { @IAccessibilityService accessibilityService: IAccessibilityService, @IThemeService private readonly themeService: IThemeService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IHostService protected readonly hostService: IHostService + @IHostService protected readonly hostService: IHostService, + @ICommandService commandService: ICommandService ) { - super( - menuService, - workspacesService, - contextKeyService, - keybindingService, - configurationService, - labelService, - updateService, - storageService, - notificationService, - preferencesService, - environmentService, - accessibilityService, - hostService - ); + super(menuService, workspacesService, contextKeyService, keybindingService, configurationService, labelService, updateService, storageService, notificationService, preferencesService, environmentService, accessibilityService, hostService, commandService); this._onVisibilityChange = this._register(new Emitter()); this._onFocusStateChange = this._register(new Emitter()); @@ -337,7 +326,17 @@ export class CustomMenubarControl extends MenubarControl { this.registerActions(); - registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { + // Register web menu actions to the file menu when its in the title + this.getWebNavigationMenuItemActions().forEach(actionItem => { + this._register(MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + command: actionItem.item, + title: actionItem.item.title, + group: 'z_Web', + when: ContextKeyExpr.and(IsWebContext, ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'compact')) + })); + }); + + registerThemingParticipant((theme, collector) => { const menubarActiveWindowFgColor = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND); if (menubarActiveWindowFgColor) { collector.addRule(` @@ -356,7 +355,6 @@ export class CustomMenubarControl extends MenubarControl { color: ${activityBarInactiveFgColor}; } `); - } const activityBarFgColor = theme.getColor(ACTIVITY_BAR_FOREGROUND); @@ -383,7 +381,6 @@ export class CustomMenubarControl extends MenubarControl { `); } - const menubarSelectedFgColor = theme.getColor(MENUBAR_SELECTION_FOREGROUND); if (menubarSelectedFgColor) { collector.addRule(` @@ -608,6 +605,12 @@ export class CustomMenubarControl extends MenubarControl { for (let action of actions) { this.insertActionsBefore(action, target); + + // use mnemonicTitle whenever possible + const title = typeof action.item.title === 'string' + ? action.item.title + : action.item.title.mnemonicTitle ?? action.item.title.value; + if (action instanceof SubmenuItemAction) { let submenu = this.menus[action.item.submenu.id]; if (!submenu) { @@ -625,10 +628,12 @@ export class CustomMenubarControl extends MenubarControl { const submenuActions: SubmenuAction[] = []; updateActions(submenu, submenuActions, topLevelTitle); - target.push(new SubmenuAction(action.id, mnemonicMenuLabel(action.label), submenuActions)); + target.push(new SubmenuAction(action.id, mnemonicMenuLabel(title), submenuActions)); } else { - action.label = mnemonicMenuLabel(this.calculateActionLabel(action)); - target.push(action); + const newAction = new Action(action.id, mnemonicMenuLabel(title), action.class, action.enabled, () => this.commandService.executeCommand(action.id)); + newAction.tooltip = action.tooltip; + newAction.checked = action.checked; + target.push(newAction); } } @@ -667,6 +672,27 @@ export class CustomMenubarControl extends MenubarControl { } } + private getWebNavigationMenuItemActions(): MenuItemAction[] { + if (!isWeb) { + return []; // only for web + } + + const webNavigationActions = []; + const webNavigationMenu = this.menuService.createMenu(MenuId.MenubarHomeMenu, this.contextKeyService); + for (const groups of webNavigationMenu.getActions()) { + const [, actions] = groups; + for (const action of actions) { + if (action instanceof MenuItemAction) { + webNavigationActions.push(action); + } + } + } + + webNavigationMenu.dispose(); + + return webNavigationActions; + } + private getMenuBarOptions(): IMenuBarOptions { return { enableMnemonics: this.currentEnableMenuBarMnemonics, @@ -674,7 +700,35 @@ export class CustomMenubarControl extends MenubarControl { visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics, - compactMode: this.currentCompactMenuMode + compactMode: this.currentCompactMenuMode, + getCompactMenuActions: () => { + if (!isWeb) { + return []; // only for web + } + + const webNavigationActions: IAction[] = []; + const href = this.environmentService.options?.homeIndicator?.href; + if (href) { + webNavigationActions.push(new Action('goHome', nls.localize('goHome', "Go Home"), undefined, true, + async (event?: MouseEvent) => { + if ((!isMacintosh && event?.ctrlKey) || (isMacintosh && event?.metaKey)) { + window.open(href, '_blank'); + } else { + window.location.href = href; + } + })); + } + + const otherActions = this.getWebNavigationMenuItemActions().map(action => { + const title = typeof action.item.title === 'string' + ? action.item.title + : action.item.title.mnemonicTitle ?? action.item.title.value; + return new Action(action.id, mnemonicMenuLabel(title), action.class, action.enabled, () => this.commandService.executeCommand(action.id)); + }); + + webNavigationActions.push(...otherActions); + return webNavigationActions; + } }; } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 4783cb522..52a1f2f45 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/titlebarpart'; +import { localize } from 'vs/nls'; import { dirname, basename } from 'vs/base/common/resources'; import { Part } from 'vs/workbench/browser/part'; import { ITitleService, ITitleProperties } from 'vs/workbench/services/title/common/titleService'; @@ -15,17 +16,16 @@ import { IAction } from 'vs/base/common/actions'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; -import * as nls from 'vs/nls'; import { EditorResourceAccessor, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER, WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { Color } from 'vs/base/common/color'; import { trim } from 'vs/base/common/strings'; -import { EventType, EventHelper, Dimension, isAncestor, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { EventType, EventHelper, Dimension, isAncestor, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame, prepend } from 'vs/base/browser/dom'; import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { template } from 'vs/base/common/labels'; @@ -41,12 +41,13 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IProductService } from 'vs/platform/product/common/productService'; import { Schemas } from 'vs/base/common/network'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { Codicon, iconRegistry } from 'vs/base/common/codicons'; export class TitlebarPart extends Part implements ITitleService { - private static readonly NLS_UNSUPPORTED = nls.localize('patchedWindowTitle', "[Unsupported]"); - private static readonly NLS_USER_IS_ADMIN = isWindows ? nls.localize('userIsAdmin', "[Administrator]") : nls.localize('userIsSudo', "[Superuser]"); - private static readonly NLS_EXTENSION_HOST = nls.localize('devExtensionWindowTitlePrefix', "[Extension Development Host]"); + private static readonly NLS_UNSUPPORTED = localize('patchedWindowTitle', "[Unsupported]"); + private static readonly NLS_USER_IS_ADMIN = isWindows ? localize('userIsAdmin', "[Administrator]") : localize('userIsSudo', "[Superuser]"); + private static readonly NLS_EXTENSION_HOST = localize('devExtensionWindowTitlePrefix', "[Extension Development Host]"); private static readonly TITLE_DIRTY = '\u25cf '; //#region IView @@ -65,6 +66,8 @@ export class TitlebarPart extends Part implements ITitleService { protected title!: HTMLElement; protected customMenubar: CustomMenubarControl | undefined; + protected appIcon: HTMLElement | undefined; + private appIconBadge: HTMLElement | undefined; protected menubar?: HTMLElement; protected lastLayoutDimensions: Dimension | undefined; private titleBarStyle: 'native' | 'custom'; @@ -86,7 +89,7 @@ export class TitlebarPart extends Part implements ITitleService { @IEditorService private readonly editorService: IEditorService, @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService protected readonly instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @ILabelService private readonly labelService: ILabelService, @IStorageService storageService: IStorageService, @@ -341,6 +344,28 @@ export class TitlebarPart extends Part implements ITitleService { createContentArea(parent: HTMLElement): HTMLElement { this.element = parent; + // App Icon (Native Windows/Linux and Web) + if (!isMacintosh || isWeb) { + this.appIcon = prepend(this.element, $('a.window-appicon')); + + // Web-only home indicator and menu + if (isWeb) { + const homeIndicator = this.environmentService.options?.homeIndicator; + if (homeIndicator) { + let codicon = iconRegistry.get(homeIndicator.icon); + if (!codicon) { + codicon = Codicon.code; + } + + this.appIcon.setAttribute('href', homeIndicator.href); + this.appIcon.classList.add(...codicon.classNamesArray); + this.appIconBadge = document.createElement('div'); + this.appIconBadge.classList.add('home-bar-icon-badge'); + this.appIcon.appendChild(this.appIconBadge); + } + } + } + // Menubar: install a custom menu bar depending on configuration // and when not in activity bar if (this.titleBarStyle !== 'native' @@ -407,6 +432,11 @@ export class TitlebarPart extends Part implements ITitleService { return color.isOpaque() ? color : color.makeOpaque(WORKBENCH_BACKGROUND(theme)); }) || ''; this.element.style.backgroundColor = titleBackground; + + if (this.appIconBadge) { + this.appIconBadge.style.backgroundColor = titleBackground; + } + if (titleBackground && Color.fromHex(titleBackground).isLighter()) { this.element.classList.add('light'); } else { @@ -441,7 +471,7 @@ export class TitlebarPart extends Part implements ITitleService { protected adjustTitleMarginToCenter(): void { if (this.customMenubar && this.menubar) { - const leftMarker = this.menubar.clientWidth + 10; + const leftMarker = (this.appIcon ? this.appIcon.clientWidth : 0) + this.menubar.clientWidth + 10; const rightMarker = this.element.clientWidth - 10; // Not enough space to center the titlebar within window, @@ -497,7 +527,7 @@ export class TitlebarPart extends Part implements ITitleService { } } -registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme, collector) => { const titlebarActiveFg = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND); if (titlebarActiveFg) { collector.addRule(` diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index dbd8ffce1..73c7e5e45 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -8,19 +8,19 @@ import { toDisposable, IDisposable, Disposable, DisposableStore } from 'vs/base/ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { MenuId, IMenuService, MenuItemAction, registerAction2, Action2, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { MenuId, IMenuService, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { IContextKeyService, ContextKeyExpr, ContextKeyEqualsExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ITreeView, ITreeViewDescriptor, IViewsRegistry, Extensions, IViewDescriptorService, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeItemLabel, ViewContainer, ViewContainerLocation, ResolvableTreeItem } from 'vs/workbench/common/views'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IThemeService, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane'; import { Registry } from 'vs/platform/registry/common/platform'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Event, Emitter } from 'vs/base/common/event'; import { IAction, ActionRunner, IActionViewItemProvider } from 'vs/base/common/actions'; -import { MenuEntryActionViewItem, createAndFillInContextMenuActions, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { createAndFillInContextMenuActions, createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -52,6 +52,8 @@ import { IIconLabelMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabel import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { Codicon } from 'vs/base/common/codicons'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Command } from 'vs/editor/common/modes'; export class TreeViewPane extends ViewPane { @@ -453,15 +455,7 @@ export class TreeView extends Disposable implements ITreeView { } private createTree() { - const actionViewItemProvider = (action: IAction) => { - if (action instanceof MenuItemAction) { - return this.instantiationService.createInstance(MenuEntryActionViewItem, action); - } else if (action instanceof SubmenuItemAction) { - return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action); - } - - return undefined; - }; + const actionViewItemProvider = createActionViewItem.bind(undefined, this.instantiationService); const treeMenus = this._register(this.instantiationService.createInstance(TreeMenus, this.id)); this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this)); const dataSource = this.instantiationService.createInstance(TreeDataSource, this, (task: Promise) => this.progressService.withProgress({ location: this.id }, () => task)); @@ -534,12 +528,13 @@ export class TreeView extends Disposable implements ITreeView { })); this.tree.setInput(this.root).then(() => this.updateContentAreas()); - this._register(this.tree.onDidOpen(e => { + this._register(this.tree.onDidOpen(async (e) => { if (!e.browserEvent) { return; } const selection = this.tree!.getSelection(); - const command = selection.length === 1 ? selection[0].command : undefined; + const command = await this.resolveCommand(selection.length === 1 ? selection[0] : undefined); + if (command) { let args = command.arguments || []; if (command.id === API_OPEN_EDITOR_COMMAND_ID || command.id === API_OPEN_DIFF_EDITOR_COMMAND_ID) { @@ -554,6 +549,17 @@ export class TreeView extends Disposable implements ITreeView { } + private async resolveCommand(element: ITreeItem | undefined): Promise { + let command = element?.command; + if (element && !command) { + if ((element instanceof ResolvableTreeItem) && element.hasResolve) { + await element.resolve(new CancellationTokenSource().token); + command = element.command; + } + } + return command; + } + private onContextMenu(treeMenus: TreeMenus, treeEvent: ITreeContextMenuEvent, actionRunner: MultipleSelectionActionRunner): void { this.hoverService.hideHover(); const node: ITreeItem | null = treeEvent.element; @@ -776,10 +782,17 @@ class TreeDataSource implements IAsyncDataSource { } async getChildren(element: ITreeItem): Promise { + let result: ITreeItem[] = []; if (this.treeView.dataProvider) { - return this.withProgress(this.treeView.dataProvider.getChildren(element)); + try { + result = await this.withProgress(this.treeView.dataProvider.getChildren(element)); + } catch (e) { + if (!(e.message).startsWith('Bad progress location:')) { + throw e; + } + } } - return []; + return result; } } @@ -840,6 +853,9 @@ class TreeRenderer extends Disposable implements ITreeRenderer { return this.hoverService.showHover(options); + }, + hideHover: () => { + return this.hoverService.hideHover(); } }; } @@ -868,7 +884,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer => { + markdown: (token: CancellationToken): Promise => { return new Promise(async (resolve) => { - await node.resolve(); + await node.resolve(token); resolve(node.tooltip); }); }, - markdownNotSupportedFallback: resource ? undefined : '' // Passing undefined as the fallback for a resource falls back to the old native hover + markdownNotSupportedFallback: resource ? undefined : label ?? '' // Passing undefined as the fallback for a resource falls back to the old native hover }; } @@ -928,7 +944,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer()); - readonly onDidChangeTitle: Event = this._onDidChangeTitle.event; - - constructor( - viewId: string, - menuId: MenuId, - contextMenuId: MenuId, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IMenuService private readonly menuService: IMenuService, - ) { - super(); - - const scopedContextKeyService = this._register(this.contextKeyService.createScoped()); - scopedContextKeyService.createKey('view', viewId); - - const menu = this._register(this.menuService.createMenu(menuId, scopedContextKeyService)); - const updateActions = () => { - this.primaryActions = []; - this.secondaryActions = []; - this.titleActionsDisposable.value = createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, { primary: this.primaryActions, secondary: this.secondaryActions }); - this._onDidChangeTitle.fire(); - }; - this._register(menu.onDidChange(updateActions)); - updateActions(); - - const contextMenu = this._register(this.menuService.createMenu(contextMenuId, scopedContextKeyService)); - const updateContextMenuActions = () => { - this.contextMenuActions = []; - this.titleActionsDisposable.value = createAndFillInActionBarActions(contextMenu, { shouldForwardArgs: true }, { primary: [], secondary: this.contextMenuActions }); - }; - this._register(contextMenu.onDidChange(updateContextMenuActions)); - updateContextMenuActions(); - - this._register(toDisposable(() => { - this.primaryActions = []; - this.secondaryActions = []; - this.contextMenuActions = []; - })); - } - - getPrimaryActions(): IAction[] { - return this.primaryActions; - } - - getSecondaryActions(): IAction[] { - return this.secondaryActions; - } - - getContextMenuActions(): IAction[] { - return this.contextMenuActions; - } -} - -export class ViewContainerMenuActions extends Disposable { - - private readonly titleActionsDisposable = this._register(new MutableDisposable()); - private contextMenuActions: IAction[] = []; - - constructor( - containerId: string, - contextMenuId: MenuId, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IMenuService private readonly menuService: IMenuService, - ) { - super(); - - const scopedContextKeyService = this._register(this.contextKeyService.createScoped()); - scopedContextKeyService.createKey('container', containerId); - - const contextMenu = this._register(this.menuService.createMenu(contextMenuId, scopedContextKeyService)); - const updateContextMenuActions = () => { - this.contextMenuActions = []; - this.titleActionsDisposable.value = createAndFillInActionBarActions(contextMenu, { shouldForwardArgs: true }, { primary: [], secondary: this.contextMenuActions }); - }; - this._register(contextMenu.onDidChange(updateContextMenuActions)); - updateContextMenuActions(); - - this._register(toDisposable(() => { - this.contextMenuActions = []; - })); - } - - getContextMenuActions(): IAction[] { - return this.contextMenuActions; - } -} diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts new file mode 100644 index 000000000..27f5af3cf --- /dev/null +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -0,0 +1,642 @@ +/*--------------------------------------------------------------------------------------------- + * 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/paneviewlet'; +import * as nls from 'vs/nls'; +import { Event, Emitter } from 'vs/base/common/event'; +import { foreground } from 'vs/platform/theme/common/colorRegistry'; +import { attachButtonStyler, attachLinkStyler, attachProgressBarStyler } from 'vs/platform/theme/common/styler'; +import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { after, append, $, trackFocus, EventType, addDisposableListener, createCSSRule, asCSSUrl } from 'vs/base/browser/dom'; +import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IAction, IActionViewItem } from 'vs/base/common/actions'; +import { ActionsOrientation, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IPaneOptions, Pane, IPaneStyles } from 'vs/base/browser/ui/splitview/paneview'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewDescriptorService, ViewContainerLocation, IViewsRegistry, IViewContentDescriptor, defaultViewIcon, IViewsService, ViewContainerLocationToString } from 'vs/workbench/common/views'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { assertIsDefined } from 'vs/base/common/types'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { MenuId, Action2, IAction2Options, IMenuService } from 'vs/platform/actions/common/actions'; +import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { parseLinkedText } from 'vs/base/common/linkedText'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { Button } from 'vs/base/browser/ui/button/button'; +import { Link } from 'vs/platform/opener/browser/link'; +import { Orientation } from 'vs/base/browser/ui/sash/sash'; +import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; +import { CompositeProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; +import { IProgressIndicator } from 'vs/platform/progress/common/progress'; +import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; +import { ScrollbarVisibility } from 'vs/base/common/scrollable'; +import { URI } from 'vs/base/common/uri'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; +import { Codicon } from 'vs/base/common/codicons'; +import { CompositeMenuActions } from 'vs/workbench/browser/menuActions'; + +export interface IViewPaneOptions extends IPaneOptions { + id: string; + showActionsAlways?: boolean; + titleMenuId?: MenuId; +} + +type WelcomeActionClassification = { + viewId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + uri: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; +}; + +const viewPaneContainerExpandedIcon = registerIcon('view-pane-container-expanded', Codicon.chevronDown, nls.localize('viewPaneContainerExpandedIcon', 'Icon for an expanded view pane container.')); +const viewPaneContainerCollapsedIcon = registerIcon('view-pane-container-collapsed', Codicon.chevronRight, nls.localize('viewPaneContainerCollapsedIcon', 'Icon for a collapsed view pane container.')); + +const viewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); + +interface IItem { + readonly descriptor: IViewContentDescriptor; + visible: boolean; +} + +class ViewWelcomeController { + + private _onDidChange = new Emitter(); + readonly onDidChange = this._onDidChange.event; + + private defaultItem: IItem | undefined; + private items: IItem[] = []; + get contents(): IViewContentDescriptor[] { + const visibleItems = this.items.filter(v => v.visible); + + if (visibleItems.length === 0 && this.defaultItem) { + return [this.defaultItem.descriptor]; + } + + return visibleItems.map(v => v.descriptor); + } + + private contextKeyService: IContextKeyService; + private disposables = new DisposableStore(); + + constructor( + private id: string, + @IContextKeyService contextKeyService: IContextKeyService, + ) { + this.contextKeyService = contextKeyService.createScoped(); + this.disposables.add(this.contextKeyService); + + contextKeyService.onDidChangeContext(this.onDidChangeContext, this, this.disposables); + Event.filter(viewsRegistry.onDidChangeViewWelcomeContent, id => id === this.id)(this.onDidChangeViewWelcomeContent, this, this.disposables); + this.onDidChangeViewWelcomeContent(); + } + + private onDidChangeViewWelcomeContent(): void { + const descriptors = viewsRegistry.getViewWelcomeContent(this.id); + + this.items = []; + + for (const descriptor of descriptors) { + if (descriptor.when === 'default') { + this.defaultItem = { descriptor, visible: true }; + } else { + const visible = descriptor.when ? this.contextKeyService.contextMatchesRules(descriptor.when) : true; + this.items.push({ descriptor, visible }); + } + } + + this._onDidChange.fire(); + } + + private onDidChangeContext(): void { + let didChange = false; + + for (const item of this.items) { + if (!item.descriptor.when || item.descriptor.when === 'default') { + continue; + } + + const visible = this.contextKeyService.contextMatchesRules(item.descriptor.when); + + if (item.visible === visible) { + continue; + } + + item.visible = visible; + didChange = true; + } + + if (didChange) { + this._onDidChange.fire(); + } + } + + dispose(): void { + this.disposables.dispose(); + } +} + +class ViewMenuActions extends CompositeMenuActions { + constructor( + viewId: string, + menuId: MenuId, + contextMenuId: MenuId, + @IContextKeyService contextKeyService: IContextKeyService, + @IMenuService menuService: IMenuService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + ) { + const scopedContextKeyService = contextKeyService.createScoped(); + scopedContextKeyService.createKey('view', viewId); + const viewLocationKey = scopedContextKeyService.createKey('viewLocation', ViewContainerLocationToString(viewDescriptorService.getViewLocationById(viewId)!)); + super(menuId, contextMenuId, { shouldForwardArgs: true }, scopedContextKeyService, menuService); + this._register(scopedContextKeyService); + this._register(Event.filter(viewDescriptorService.onDidChangeLocation, e => e.views.some(view => view.id === viewId))(() => viewLocationKey.set(ViewContainerLocationToString(viewDescriptorService.getViewLocationById(viewId)!)))); + } + +} + +export abstract class ViewPane extends Pane implements IView { + + private static readonly AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions'; + + private _onDidFocus = this._register(new Emitter()); + readonly onDidFocus: Event = this._onDidFocus.event; + + private _onDidBlur = this._register(new Emitter()); + readonly onDidBlur: Event = this._onDidBlur.event; + + private _onDidChangeBodyVisibility = this._register(new Emitter()); + readonly onDidChangeBodyVisibility: Event = this._onDidChangeBodyVisibility.event; + + protected _onDidChangeTitleArea = this._register(new Emitter()); + readonly onDidChangeTitleArea: Event = this._onDidChangeTitleArea.event; + + protected _onDidChangeViewWelcomeState = this._register(new Emitter()); + readonly onDidChangeViewWelcomeState: Event = this._onDidChangeViewWelcomeState.event; + + private focusedViewContextKey: IContextKey; + + private _isVisible: boolean = false; + readonly id: string; + + private _title: string; + public get title(): string { + return this._title; + } + + private _titleDescription: string | undefined; + public get titleDescription(): string | undefined { + return this._titleDescription; + } + + private readonly menuActions: ViewMenuActions; + private progressBar!: ProgressBar; + private progressIndicator!: IProgressIndicator; + + private toolbar?: ToolBar; + private readonly showActionsAlways: boolean = false; + private headerContainer?: HTMLElement; + private titleContainer?: HTMLElement; + private titleDescriptionContainer?: HTMLElement; + private iconContainer?: HTMLElement; + protected twistiesContainer?: HTMLElement; + + private bodyContainer!: HTMLElement; + private viewWelcomeContainer!: HTMLElement; + private viewWelcomeDisposable: IDisposable = Disposable.None; + private viewWelcomeController: ViewWelcomeController; + + constructor( + options: IViewPaneOptions, + @IKeybindingService protected keybindingService: IKeybindingService, + @IContextMenuService protected contextMenuService: IContextMenuService, + @IConfigurationService protected readonly configurationService: IConfigurationService, + @IContextKeyService protected contextKeyService: IContextKeyService, + @IViewDescriptorService protected viewDescriptorService: IViewDescriptorService, + @IInstantiationService protected instantiationService: IInstantiationService, + @IOpenerService protected openerService: IOpenerService, + @IThemeService protected themeService: IThemeService, + @ITelemetryService protected telemetryService: ITelemetryService, + ) { + super({ ...options, ...{ orientation: viewDescriptorService.getViewLocationById(options.id) === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL } }); + + this.id = options.id; + this._title = options.title; + this._titleDescription = options.titleDescription; + this.showActionsAlways = !!options.showActionsAlways; + this.focusedViewContextKey = FocusedViewContext.bindTo(contextKeyService); + + this.menuActions = this._register(instantiationService.createInstance(ViewMenuActions, this.id, options.titleMenuId || MenuId.ViewTitle, MenuId.ViewTitleContext)); + this._register(this.menuActions.onDidChange(() => this.updateActions())); + + this.viewWelcomeController = new ViewWelcomeController(this.id, contextKeyService); + } + + get headerVisible(): boolean { + return super.headerVisible; + } + + set headerVisible(visible: boolean) { + super.headerVisible = visible; + this.element.classList.toggle('merged-header', !visible); + } + + setVisible(visible: boolean): void { + if (this._isVisible !== visible) { + this._isVisible = visible; + + if (this.isExpanded()) { + this._onDidChangeBodyVisibility.fire(visible); + } + } + } + + isVisible(): boolean { + return this._isVisible; + } + + isBodyVisible(): boolean { + return this._isVisible && this.isExpanded(); + } + + setExpanded(expanded: boolean): boolean { + const changed = super.setExpanded(expanded); + if (changed) { + this._onDidChangeBodyVisibility.fire(expanded); + } + if (this.twistiesContainer) { + this.twistiesContainer.classList.remove(...ThemeIcon.asClassNameArray(this.getTwistyIcon(!expanded))); + this.twistiesContainer.classList.add(...ThemeIcon.asClassNameArray(this.getTwistyIcon(expanded))); + } + return changed; + } + + render(): void { + super.render(); + + const focusTracker = trackFocus(this.element); + this._register(focusTracker); + this._register(focusTracker.onDidFocus(() => { + this.focusedViewContextKey.set(this.id); + this._onDidFocus.fire(); + })); + this._register(focusTracker.onDidBlur(() => { + if (this.focusedViewContextKey.get() === this.id) { + this.focusedViewContextKey.reset(); + } + + this._onDidBlur.fire(); + })); + } + + protected renderHeader(container: HTMLElement): void { + this.headerContainer = container; + + this.twistiesContainer = append(container, $(ThemeIcon.asCSSSelector(this.getTwistyIcon(this.isExpanded())))); + + this.renderHeaderTitle(container, this.title); + + const actions = append(container, $('.actions')); + actions.classList.toggle('show', this.showActionsAlways); + this.toolbar = new ToolBar(actions, this.contextMenuService, { + orientation: ActionsOrientation.HORIZONTAL, + actionViewItemProvider: action => this.getActionViewItem(action), + ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.title), + getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), + renderDropdownAsChildElement: true + }); + + this._register(this.toolbar); + this.setActions(); + + this._register(addDisposableListener(actions, EventType.CLICK, e => e.preventDefault())); + + this._register(this.viewDescriptorService.getViewContainerModel(this.viewDescriptorService.getViewContainerByViewId(this.id)!)!.onDidChangeContainerInfo(({ title }) => { + this.updateTitle(this.title); + })); + + const onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewPane.AlwaysShowActionsConfig)); + this._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this)); + this.updateActionsVisibility(); + } + + protected getTwistyIcon(expanded: boolean): ThemeIcon { + return expanded ? viewPaneContainerExpandedIcon : viewPaneContainerCollapsedIcon; + } + + style(styles: IPaneStyles): void { + super.style(styles); + + const icon = this.getIcon(); + if (this.iconContainer) { + const fgColor = styles.headerForeground || this.themeService.getColorTheme().getColor(foreground); + if (URI.isUri(icon)) { + // Apply background color to activity bar item provided with iconUrls + this.iconContainer.style.backgroundColor = fgColor ? fgColor.toString() : ''; + this.iconContainer.style.color = ''; + } else { + // Apply foreground color to activity bar items provided with codicons + this.iconContainer.style.color = fgColor ? fgColor.toString() : ''; + this.iconContainer.style.backgroundColor = ''; + } + } + } + + private getIcon(): ThemeIcon | URI { + return this.viewDescriptorService.getViewDescriptorById(this.id)?.containerIcon || defaultViewIcon; + } + + protected renderHeaderTitle(container: HTMLElement, title: string): void { + this.iconContainer = append(container, $('.icon', undefined)); + const icon = this.getIcon(); + + let cssClass: string | undefined = undefined; + if (URI.isUri(icon)) { + cssClass = `view-${this.id.replace(/[\.\:]/g, '-')}`; + const iconClass = `.pane-header .icon.${cssClass}`; + + createCSSRule(iconClass, ` + mask: ${asCSSUrl(icon)} no-repeat 50% 50%; + mask-size: 24px; + -webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%; + -webkit-mask-size: 16px; + `); + } else if (ThemeIcon.isThemeIcon(icon)) { + cssClass = ThemeIcon.asClassName(icon); + } + + if (cssClass) { + this.iconContainer.classList.add(...cssClass.split(' ')); + } + + const calculatedTitle = this.calculateTitle(title); + this.titleContainer = append(container, $('h3.title', { title: calculatedTitle }, calculatedTitle)); + + if (this._titleDescription) { + this.setTitleDescription(this._titleDescription); + } + + this.iconContainer.title = calculatedTitle; + this.iconContainer.setAttribute('aria-label', calculatedTitle); + } + + protected updateTitle(title: string): void { + const calculatedTitle = this.calculateTitle(title); + if (this.titleContainer) { + this.titleContainer.textContent = calculatedTitle; + this.titleContainer.setAttribute('title', calculatedTitle); + } + + if (this.iconContainer) { + this.iconContainer.title = calculatedTitle; + this.iconContainer.setAttribute('aria-label', calculatedTitle); + } + + this._title = title; + this._onDidChangeTitleArea.fire(); + } + + private setTitleDescription(description: string | undefined) { + if (this.titleDescriptionContainer) { + this.titleDescriptionContainer.textContent = description ?? ''; + this.titleDescriptionContainer.setAttribute('title', description ?? ''); + } + else if (description && this.titleContainer) { + this.titleDescriptionContainer = after(this.titleContainer, $('span.description', { title: description }, description)); + } + } + + protected updateTitleDescription(description?: string | undefined): void { + this.setTitleDescription(description); + + this._titleDescription = description; + this._onDidChangeTitleArea.fire(); + } + + private calculateTitle(title: string): string { + const viewContainer = this.viewDescriptorService.getViewContainerByViewId(this.id)!; + const model = this.viewDescriptorService.getViewContainerModel(viewContainer); + const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(this.id); + const isDefault = this.viewDescriptorService.getDefaultContainerById(this.id) === viewContainer; + + if (!isDefault && viewDescriptor?.containerTitle && model.title !== viewDescriptor.containerTitle) { + return `${viewDescriptor.containerTitle}: ${title}`; + } + + return title; + } + + private scrollableElement!: DomScrollableElement; + + protected renderBody(container: HTMLElement): void { + this.bodyContainer = container; + + const viewWelcomeContainer = append(container, $('.welcome-view')); + this.viewWelcomeContainer = $('.welcome-view-content', { tabIndex: 0 }); + this.scrollableElement = this._register(new DomScrollableElement(this.viewWelcomeContainer, { + alwaysConsumeMouseWheel: true, + horizontal: ScrollbarVisibility.Hidden, + vertical: ScrollbarVisibility.Visible, + })); + + append(viewWelcomeContainer, this.scrollableElement.getDomNode()); + + const onViewWelcomeChange = Event.any(this.viewWelcomeController.onDidChange, this.onDidChangeViewWelcomeState); + this._register(onViewWelcomeChange(this.updateViewWelcome, this)); + this.updateViewWelcome(); + } + + protected layoutBody(height: number, width: number): void { + this.viewWelcomeContainer.style.height = `${height}px`; + this.viewWelcomeContainer.style.width = `${width}px`; + this.scrollableElement.scanDomNode(); + } + + getProgressIndicator() { + if (this.progressBar === undefined) { + // Progress bar + this.progressBar = this._register(new ProgressBar(this.element)); + this._register(attachProgressBarStyler(this.progressBar, this.themeService)); + this.progressBar.hide(); + } + + if (this.progressIndicator === undefined) { + this.progressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), this.id, this.isBodyVisible()); + } + return this.progressIndicator; + } + + protected getProgressLocation(): string { + return this.viewDescriptorService.getViewContainerByViewId(this.id)!.id; + } + + protected getBackgroundColor(): string { + return this.viewDescriptorService.getViewLocationById(this.id) === ViewContainerLocation.Panel ? PANEL_BACKGROUND : SIDE_BAR_BACKGROUND; + } + + focus(): void { + if (this.shouldShowWelcome()) { + this.viewWelcomeContainer.focus(); + } else if (this.element) { + this.element.focus(); + this._onDidFocus.fire(); + } + } + + private setActions(): void { + if (this.toolbar) { + this.toolbar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions())); + this.toolbar.context = this.getActionsContext(); + } + } + + private updateActionsVisibility(): void { + if (!this.headerContainer) { + return; + } + const shouldAlwaysShowActions = this.configurationService.getValue('workbench.view.alwaysShowHeaderActions'); + this.headerContainer.classList.toggle('actions-always-visible', shouldAlwaysShowActions); + } + + protected updateActions(): void { + this.setActions(); + this._onDidChangeTitleArea.fire(); + } + + getActions(): IAction[] { + return this.menuActions.getPrimaryActions(); + } + + getSecondaryActions(): IAction[] { + return this.menuActions.getSecondaryActions(); + } + + getContextMenuActions(): IAction[] { + return this.menuActions.getContextMenuActions(); + } + + getActionViewItem(action: IAction): IActionViewItem | undefined { + return createActionViewItem(this.instantiationService, action); + } + + getActionsContext(): unknown { + return undefined; + } + + getOptimalWidth(): number { + return 0; + } + + saveState(): void { + // Subclasses to implement for saving state + } + + private updateViewWelcome(): void { + this.viewWelcomeDisposable.dispose(); + + if (!this.shouldShowWelcome()) { + this.bodyContainer.classList.remove('welcome'); + this.viewWelcomeContainer.innerText = ''; + this.scrollableElement.scanDomNode(); + return; + } + + const contents = this.viewWelcomeController.contents; + + if (contents.length === 0) { + this.bodyContainer.classList.remove('welcome'); + this.viewWelcomeContainer.innerText = ''; + this.scrollableElement.scanDomNode(); + return; + } + + const disposables = new DisposableStore(); + this.bodyContainer.classList.add('welcome'); + this.viewWelcomeContainer.innerText = ''; + + for (const { content, precondition } of contents) { + const lines = content.split('\n'); + + for (let line of lines) { + line = line.trim(); + + if (!line) { + continue; + } + + const linkedText = parseLinkedText(line); + + if (linkedText.nodes.length === 1 && typeof linkedText.nodes[0] !== 'string') { + const node = linkedText.nodes[0]; + const button = new Button(this.viewWelcomeContainer, { title: node.title, supportIcons: true }); + button.label = node.label; + button.onDidClick(_ => { + this.telemetryService.publicLog2<{ viewId: string, uri: string }, WelcomeActionClassification>('views.welcomeAction', { viewId: this.id, uri: node.href }); + this.openerService.open(node.href); + }, null, disposables); + disposables.add(button); + disposables.add(attachButtonStyler(button, this.themeService)); + + if (precondition) { + const updateEnablement = () => button.enabled = this.contextKeyService.contextMatchesRules(precondition); + updateEnablement(); + + const keys = new Set(); + precondition.keys().forEach(key => keys.add(key)); + const onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys)); + onDidChangeContext(updateEnablement, null, disposables); + } + } else { + const p = append(this.viewWelcomeContainer, $('p')); + + for (const node of linkedText.nodes) { + if (typeof node === 'string') { + append(p, document.createTextNode(node)); + } else { + const link = this.instantiationService.createInstance(Link, node); + append(p, link.el); + disposables.add(link); + disposables.add(attachLinkStyler(link, this.themeService)); + + if (precondition && node.href.startsWith('command:')) { + const updateEnablement = () => link.style({ disabled: !this.contextKeyService.contextMatchesRules(precondition) }); + updateEnablement(); + + const keys = new Set(); + precondition.keys().forEach(key => keys.add(key)); + const onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys)); + onDidChangeContext(updateEnablement, null, disposables); + } + } + } + } + } + } + + this.scrollableElement.scanDomNode(); + this.viewWelcomeDisposable = disposables; + } + + shouldShowWelcome(): boolean { + return false; + } +} + +export abstract class ViewAction extends Action2 { + constructor(readonly desc: Readonly & { viewId: string }) { + super(desc); + } + + run(accessor: ServicesAccessor, ...args: any[]) { + const view = accessor.get(IViewsService).getActiveViewWithId(this.desc.viewId); + if (view) { + return this.runInView(accessor, view, ...args); + } + } + + abstract runInView(accessor: ServicesAccessor, view: T, ...args: any[]): any; +} diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 410c67244..e71e07d07 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -6,52 +6,45 @@ import 'vs/css!./media/paneviewlet'; import * as nls from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; -import { ColorIdentifier, activeContrastBorder, foreground } from 'vs/platform/theme/common/colorRegistry'; -import { attachStyler, IColorMapping, attachButtonStyler, attachLinkStyler, attachProgressBarStyler } from 'vs/platform/theme/common/styler'; -import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER, PANEL_BACKGROUND, SIDE_BAR_BACKGROUND, PANEL_SECTION_HEADER_FOREGROUND, PANEL_SECTION_HEADER_BACKGROUND, PANEL_SECTION_HEADER_BORDER, PANEL_SECTION_DRAG_AND_DROP_BACKGROUND, PANEL_SECTION_BORDER } from 'vs/workbench/common/theme'; -import { after, append, $, trackFocus, EventType, isAncestor, Dimension, addDisposableListener, createCSSRule, asCSSUrl } from 'vs/base/browser/dom'; -import { IDisposable, combinedDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IAction, Separator, IActionViewItem } from 'vs/base/common/actions'; -import { ActionsOrientation, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ColorIdentifier, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; +import { attachStyler, IColorMapping } from 'vs/platform/theme/common/styler'; +import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER, PANEL_SECTION_HEADER_FOREGROUND, PANEL_SECTION_HEADER_BACKGROUND, PANEL_SECTION_HEADER_BORDER, PANEL_SECTION_DRAG_AND_DROP_BACKGROUND, PANEL_SECTION_BORDER } from 'vs/workbench/common/theme'; +import { EventType, Dimension, addDisposableListener, isAncestor } from 'vs/base/browser/dom'; +import { IDisposable, combinedDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { IAction, IActionViewItem, Separator } from 'vs/base/common/actions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IThemeService, Themable, ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { PaneView, IPaneViewOptions, IPaneOptions, Pane, IPaneStyles } from 'vs/base/browser/ui/splitview/paneview'; +import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; +import { PaneView, IPaneViewOptions } from 'vs/base/browser/ui/splitview/paneview'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewPaneContainer, IViewsRegistry, IViewContentDescriptor, IAddedViewDescriptorRef, IViewDescriptorRef, IViewContainerModel, defaultViewIcon } from 'vs/workbench/common/views'; +import { IView, FocusedViewContext, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewPaneContainer, IAddedViewDescriptorRef, IViewDescriptorRef, IViewContainerModel, IViewsService, ViewContainerLocationToString } from 'vs/workbench/common/views'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyEqualsExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { assertIsDefined } from 'vs/base/common/types'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { Component } from 'vs/workbench/common/component'; -import { MenuId, MenuItemAction, registerAction2, Action2, IAction2Options, SubmenuItemAction } from 'vs/platform/actions/common/actions'; -import { MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions'; -import { parseLinkedText } from 'vs/base/common/linkedText'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { Button } from 'vs/base/browser/ui/button/button'; -import { Link } from 'vs/platform/opener/browser/link'; +import { registerAction2, Action2, IAction2Options, IMenuService, MenuId, MenuRegistry, ISubmenuItem, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { CompositeDragAndDropObserver, DragAndDropObserver, toggleDropEffect } from 'vs/workbench/browser/dnd'; import { Orientation } from 'vs/base/browser/ui/sash/sash'; -import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; -import { CompositeProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; -import { IProgressIndicator } from 'vs/platform/progress/common/progress'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; -import { ScrollbarVisibility } from 'vs/base/common/scrollable'; -import { URI } from 'vs/base/common/uri'; import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; -import { Codicon } from 'vs/base/common/codicons'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; +import { CompositeMenuActions } from 'vs/workbench/browser/menuActions'; +import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; + +export const ViewsSubMenu = new MenuId('Views'); +MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, { + submenu: ViewsSubMenu, + title: nls.localize('views', "Views"), + order: 1, + when: ContextKeyEqualsExpr.create('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar)), +}); export interface IPaneColors extends IColorMapping { dropBackground?: ColorIdentifier; @@ -61,566 +54,6 @@ export interface IPaneColors extends IColorMapping { leftBorder?: ColorIdentifier; } -export interface IViewPaneOptions extends IPaneOptions { - id: string; - showActionsAlways?: boolean; - titleMenuId?: MenuId; -} - -type WelcomeActionClassification = { - viewId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - uri: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; -}; - -const viewPaneContainerExpandedIcon = registerIcon('view-pane-container-expanded', Codicon.chevronDown, nls.localize('viewPaneContainerExpandedIcon', 'Icon for an expanded view pane container.')); -const viewPaneContainerCollapsedIcon = registerIcon('view-pane-container-collapsed', Codicon.chevronRight, nls.localize('viewPaneContainerCollapsedIcon', 'Icon for a collapsed view pane container.')); - -const viewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); - -interface IItem { - readonly descriptor: IViewContentDescriptor; - visible: boolean; -} - -class ViewWelcomeController { - - private _onDidChange = new Emitter(); - readonly onDidChange = this._onDidChange.event; - - private defaultItem: IItem | undefined; - private items: IItem[] = []; - get contents(): IViewContentDescriptor[] { - const visibleItems = this.items.filter(v => v.visible); - - if (visibleItems.length === 0 && this.defaultItem) { - return [this.defaultItem.descriptor]; - } - - return visibleItems.map(v => v.descriptor); - } - - private contextKeyService: IContextKeyService; - private disposables = new DisposableStore(); - - constructor( - private id: string, - @IContextKeyService contextKeyService: IContextKeyService, - ) { - this.contextKeyService = contextKeyService.createScoped(); - this.disposables.add(this.contextKeyService); - - contextKeyService.onDidChangeContext(this.onDidChangeContext, this, this.disposables); - Event.filter(viewsRegistry.onDidChangeViewWelcomeContent, id => id === this.id)(this.onDidChangeViewWelcomeContent, this, this.disposables); - this.onDidChangeViewWelcomeContent(); - } - - private onDidChangeViewWelcomeContent(): void { - const descriptors = viewsRegistry.getViewWelcomeContent(this.id); - - this.items = []; - - for (const descriptor of descriptors) { - if (descriptor.when === 'default') { - this.defaultItem = { descriptor, visible: true }; - } else { - const visible = descriptor.when ? this.contextKeyService.contextMatchesRules(descriptor.when) : true; - this.items.push({ descriptor, visible }); - } - } - - this._onDidChange.fire(); - } - - private onDidChangeContext(): void { - let didChange = false; - - for (const item of this.items) { - if (!item.descriptor.when || item.descriptor.when === 'default') { - continue; - } - - const visible = this.contextKeyService.contextMatchesRules(item.descriptor.when); - - if (item.visible === visible) { - continue; - } - - item.visible = visible; - didChange = true; - } - - if (didChange) { - this._onDidChange.fire(); - } - } - - dispose(): void { - this.disposables.dispose(); - } -} - -export abstract class ViewPane extends Pane implements IView { - - private static readonly AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions'; - - private _onDidFocus = this._register(new Emitter()); - readonly onDidFocus: Event = this._onDidFocus.event; - - private _onDidBlur = this._register(new Emitter()); - readonly onDidBlur: Event = this._onDidBlur.event; - - private _onDidChangeBodyVisibility = this._register(new Emitter()); - readonly onDidChangeBodyVisibility: Event = this._onDidChangeBodyVisibility.event; - - protected _onDidChangeTitleArea = this._register(new Emitter()); - readonly onDidChangeTitleArea: Event = this._onDidChangeTitleArea.event; - - protected _onDidChangeViewWelcomeState = this._register(new Emitter()); - readonly onDidChangeViewWelcomeState: Event = this._onDidChangeViewWelcomeState.event; - - private focusedViewContextKey: IContextKey; - - private _isVisible: boolean = false; - readonly id: string; - - private _title: string; - public get title(): string { - return this._title; - } - - private _titleDescription: string | undefined; - public get titleDescription(): string | undefined { - return this._titleDescription; - } - - private readonly menuActions: ViewMenuActions; - private progressBar!: ProgressBar; - private progressIndicator!: IProgressIndicator; - - private toolbar?: ToolBar; - private readonly showActionsAlways: boolean = false; - private headerContainer?: HTMLElement; - private titleContainer?: HTMLElement; - private titleDescriptionContainer?: HTMLElement; - private iconContainer?: HTMLElement; - protected twistiesContainer?: HTMLElement; - - private bodyContainer!: HTMLElement; - private viewWelcomeContainer!: HTMLElement; - private viewWelcomeDisposable: IDisposable = Disposable.None; - private viewWelcomeController: ViewWelcomeController; - - constructor( - options: IViewPaneOptions, - @IKeybindingService protected keybindingService: IKeybindingService, - @IContextMenuService protected contextMenuService: IContextMenuService, - @IConfigurationService protected readonly configurationService: IConfigurationService, - @IContextKeyService protected contextKeyService: IContextKeyService, - @IViewDescriptorService protected viewDescriptorService: IViewDescriptorService, - @IInstantiationService protected instantiationService: IInstantiationService, - @IOpenerService protected openerService: IOpenerService, - @IThemeService protected themeService: IThemeService, - @ITelemetryService protected telemetryService: ITelemetryService, - ) { - super({ ...options, ...{ orientation: viewDescriptorService.getViewLocationById(options.id) === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL } }); - - this.id = options.id; - this._title = options.title; - this._titleDescription = options.titleDescription; - this.showActionsAlways = !!options.showActionsAlways; - this.focusedViewContextKey = FocusedViewContext.bindTo(contextKeyService); - - this.menuActions = this._register(instantiationService.createInstance(ViewMenuActions, this.id, options.titleMenuId || MenuId.ViewTitle, MenuId.ViewTitleContext)); - this._register(this.menuActions.onDidChangeTitle(() => this.updateActions())); - - this.viewWelcomeController = new ViewWelcomeController(this.id, contextKeyService); - } - - get headerVisible(): boolean { - return super.headerVisible; - } - - set headerVisible(visible: boolean) { - super.headerVisible = visible; - this.element.classList.toggle('merged-header', !visible); - } - - setVisible(visible: boolean): void { - if (this._isVisible !== visible) { - this._isVisible = visible; - - if (this.isExpanded()) { - this._onDidChangeBodyVisibility.fire(visible); - } - } - } - - isVisible(): boolean { - return this._isVisible; - } - - isBodyVisible(): boolean { - return this._isVisible && this.isExpanded(); - } - - setExpanded(expanded: boolean): boolean { - const changed = super.setExpanded(expanded); - if (changed) { - this._onDidChangeBodyVisibility.fire(expanded); - } - if (this.twistiesContainer) { - this.twistiesContainer.classList.remove(...ThemeIcon.asClassNameArray(this.getTwistyIcon(!expanded))); - this.twistiesContainer.classList.add(...ThemeIcon.asClassNameArray(this.getTwistyIcon(expanded))); - } - return changed; - } - - render(): void { - super.render(); - - const focusTracker = trackFocus(this.element); - this._register(focusTracker); - this._register(focusTracker.onDidFocus(() => { - this.focusedViewContextKey.set(this.id); - this._onDidFocus.fire(); - })); - this._register(focusTracker.onDidBlur(() => { - if (this.focusedViewContextKey.get() === this.id) { - this.focusedViewContextKey.reset(); - } - - this._onDidBlur.fire(); - })); - } - - protected renderHeader(container: HTMLElement): void { - this.headerContainer = container; - - this.twistiesContainer = append(container, $(ThemeIcon.asCSSSelector(this.getTwistyIcon(this.isExpanded())))); - - this.renderHeaderTitle(container, this.title); - - const actions = append(container, $('.actions')); - actions.classList.toggle('show', this.showActionsAlways); - this.toolbar = new ToolBar(actions, this.contextMenuService, { - orientation: ActionsOrientation.HORIZONTAL, - actionViewItemProvider: action => this.getActionViewItem(action), - ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.title), - getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), - renderDropdownAsChildElement: true - }); - - this._register(this.toolbar); - this.setActions(); - - this._register(addDisposableListener(actions, EventType.CLICK, e => e.preventDefault())); - - this._register(this.viewDescriptorService.getViewContainerModel(this.viewDescriptorService.getViewContainerByViewId(this.id)!)!.onDidChangeContainerInfo(({ title }) => { - this.updateTitle(this.title); - })); - - const onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewPane.AlwaysShowActionsConfig)); - this._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this)); - this.updateActionsVisibility(); - } - - protected getTwistyIcon(expanded: boolean): ThemeIcon { - return expanded ? viewPaneContainerExpandedIcon : viewPaneContainerCollapsedIcon; - } - - style(styles: IPaneStyles): void { - super.style(styles); - - const icon = this.getIcon(); - if (this.iconContainer) { - const fgColor = styles.headerForeground || this.themeService.getColorTheme().getColor(foreground); - if (URI.isUri(icon)) { - // Apply background color to activity bar item provided with iconUrls - this.iconContainer.style.backgroundColor = fgColor ? fgColor.toString() : ''; - this.iconContainer.style.color = ''; - } else { - // Apply foreground color to activity bar items provided with codicons - this.iconContainer.style.color = fgColor ? fgColor.toString() : ''; - this.iconContainer.style.backgroundColor = ''; - } - } - } - - private getIcon(): ThemeIcon | URI { - return this.viewDescriptorService.getViewDescriptorById(this.id)?.containerIcon || defaultViewIcon; - } - - protected renderHeaderTitle(container: HTMLElement, title: string): void { - this.iconContainer = append(container, $('.icon', undefined)); - const icon = this.getIcon(); - - let cssClass: string | undefined = undefined; - if (URI.isUri(icon)) { - cssClass = `view-${this.id.replace(/[\.\:]/g, '-')}`; - const iconClass = `.pane-header .icon.${cssClass}`; - - createCSSRule(iconClass, ` - mask: ${asCSSUrl(icon)} no-repeat 50% 50%; - mask-size: 24px; - -webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%; - -webkit-mask-size: 16px; - `); - } else if (ThemeIcon.isThemeIcon(icon)) { - cssClass = ThemeIcon.asClassName(icon); - } - - if (cssClass) { - this.iconContainer.classList.add(...cssClass.split(' ')); - } - - const calculatedTitle = this.calculateTitle(title); - this.titleContainer = append(container, $('h3.title', { title: calculatedTitle }, calculatedTitle)); - - if (this._titleDescription) { - this.setTitleDescription(this._titleDescription); - } - - this.iconContainer.title = calculatedTitle; - this.iconContainer.setAttribute('aria-label', calculatedTitle); - } - - protected updateTitle(title: string): void { - const calculatedTitle = this.calculateTitle(title); - if (this.titleContainer) { - this.titleContainer.textContent = calculatedTitle; - this.titleContainer.setAttribute('title', calculatedTitle); - } - - if (this.iconContainer) { - this.iconContainer.title = calculatedTitle; - this.iconContainer.setAttribute('aria-label', calculatedTitle); - } - - this._title = title; - this._onDidChangeTitleArea.fire(); - } - - private setTitleDescription(description: string | undefined) { - if (this.titleDescriptionContainer) { - this.titleDescriptionContainer.textContent = description ?? ''; - this.titleDescriptionContainer.setAttribute('title', description ?? ''); - } - else if (description && this.titleContainer) { - this.titleDescriptionContainer = after(this.titleContainer, $('span.description', { title: description }, description)); - } - } - - protected updateTitleDescription(description?: string | undefined): void { - this.setTitleDescription(description); - - this._titleDescription = description; - this._onDidChangeTitleArea.fire(); - } - - private calculateTitle(title: string): string { - const viewContainer = this.viewDescriptorService.getViewContainerByViewId(this.id)!; - const model = this.viewDescriptorService.getViewContainerModel(viewContainer); - const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(this.id); - const isDefault = this.viewDescriptorService.getDefaultContainerById(this.id) === viewContainer; - - if (!isDefault && viewDescriptor?.containerTitle && model.title !== viewDescriptor.containerTitle) { - return `${viewDescriptor.containerTitle}: ${title}`; - } - - return title; - } - - private scrollableElement!: DomScrollableElement; - - protected renderBody(container: HTMLElement): void { - this.bodyContainer = container; - - const viewWelcomeContainer = append(container, $('.welcome-view')); - this.viewWelcomeContainer = $('.welcome-view-content', { tabIndex: 0 }); - this.scrollableElement = this._register(new DomScrollableElement(this.viewWelcomeContainer, { - alwaysConsumeMouseWheel: true, - horizontal: ScrollbarVisibility.Hidden, - vertical: ScrollbarVisibility.Visible, - })); - - append(viewWelcomeContainer, this.scrollableElement.getDomNode()); - - const onViewWelcomeChange = Event.any(this.viewWelcomeController.onDidChange, this.onDidChangeViewWelcomeState); - this._register(onViewWelcomeChange(this.updateViewWelcome, this)); - this.updateViewWelcome(); - } - - protected layoutBody(height: number, width: number): void { - this.viewWelcomeContainer.style.height = `${height}px`; - this.viewWelcomeContainer.style.width = `${width}px`; - this.scrollableElement.scanDomNode(); - } - - getProgressIndicator() { - if (this.progressBar === undefined) { - // Progress bar - this.progressBar = this._register(new ProgressBar(this.element)); - this._register(attachProgressBarStyler(this.progressBar, this.themeService)); - this.progressBar.hide(); - } - - if (this.progressIndicator === undefined) { - this.progressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), this.id, this.isBodyVisible()); - } - return this.progressIndicator; - } - - protected getProgressLocation(): string { - return this.viewDescriptorService.getViewContainerByViewId(this.id)!.id; - } - - protected getBackgroundColor(): string { - return this.viewDescriptorService.getViewLocationById(this.id) === ViewContainerLocation.Panel ? PANEL_BACKGROUND : SIDE_BAR_BACKGROUND; - } - - focus(): void { - if (this.shouldShowWelcome()) { - this.viewWelcomeContainer.focus(); - } else if (this.element) { - this.element.focus(); - this._onDidFocus.fire(); - } - } - - private setActions(): void { - if (this.toolbar) { - this.toolbar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions())); - this.toolbar.context = this.getActionsContext(); - } - } - - private updateActionsVisibility(): void { - if (!this.headerContainer) { - return; - } - const shouldAlwaysShowActions = this.configurationService.getValue('workbench.view.alwaysShowHeaderActions'); - this.headerContainer.classList.toggle('actions-always-visible', shouldAlwaysShowActions); - } - - protected updateActions(): void { - this.setActions(); - this._onDidChangeTitleArea.fire(); - } - - getActions(): IAction[] { - return this.menuActions.getPrimaryActions(); - } - - getSecondaryActions(): IAction[] { - return this.menuActions.getSecondaryActions(); - } - - getContextMenuActions(): IAction[] { - return this.menuActions.getContextMenuActions(); - } - - getActionViewItem(action: IAction): IActionViewItem | undefined { - if (action instanceof MenuItemAction) { - return this.instantiationService.createInstance(MenuEntryActionViewItem, action); - } else if (action instanceof SubmenuItemAction) { - return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action); - } - return undefined; - } - - getActionsContext(): unknown { - return undefined; - } - - getOptimalWidth(): number { - return 0; - } - - saveState(): void { - // Subclasses to implement for saving state - } - - private updateViewWelcome(): void { - this.viewWelcomeDisposable.dispose(); - - if (!this.shouldShowWelcome()) { - this.bodyContainer.classList.remove('welcome'); - this.viewWelcomeContainer.innerText = ''; - this.scrollableElement.scanDomNode(); - return; - } - - const contents = this.viewWelcomeController.contents; - - if (contents.length === 0) { - this.bodyContainer.classList.remove('welcome'); - this.viewWelcomeContainer.innerText = ''; - this.scrollableElement.scanDomNode(); - return; - } - - const disposables = new DisposableStore(); - this.bodyContainer.classList.add('welcome'); - this.viewWelcomeContainer.innerText = ''; - - for (const { content, precondition } of contents) { - const lines = content.split('\n'); - - for (let line of lines) { - line = line.trim(); - - if (!line) { - continue; - } - - const linkedText = parseLinkedText(line); - - if (linkedText.nodes.length === 1 && typeof linkedText.nodes[0] !== 'string') { - const node = linkedText.nodes[0]; - const button = new Button(this.viewWelcomeContainer, { title: node.title, supportCodicons: true }); - button.label = node.label; - button.onDidClick(_ => { - this.telemetryService.publicLog2<{ viewId: string, uri: string }, WelcomeActionClassification>('views.welcomeAction', { viewId: this.id, uri: node.href }); - this.openerService.open(node.href); - }, null, disposables); - disposables.add(button); - disposables.add(attachButtonStyler(button, this.themeService)); - - if (precondition) { - const updateEnablement = () => button.enabled = this.contextKeyService.contextMatchesRules(precondition); - updateEnablement(); - - const keys = new Set(); - precondition.keys().forEach(key => keys.add(key)); - const onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys)); - onDidChangeContext(updateEnablement, null, disposables); - } - } else { - const p = append(this.viewWelcomeContainer, $('p')); - - for (const node of linkedText.nodes) { - if (typeof node === 'string') { - append(p, document.createTextNode(node)); - } else { - const link = this.instantiationService.createInstance(Link, node); - append(p, link.el); - disposables.add(link); - disposables.add(attachLinkStyler(link, this.themeService)); - } - } - } - } - } - - this.scrollableElement.scanDomNode(); - this.viewWelcomeDisposable = disposables; - } - - shouldShowWelcome(): boolean { - return false; - } -} - export interface IViewPaneContainerOptions extends IPaneViewOptions { mergeViewWithContainerWhenSingleView: boolean; } @@ -860,6 +293,22 @@ class ViewPaneDropOverlay extends Themable { } } +class ViewContainerMenuActions extends CompositeMenuActions { + constructor( + viewContainer: ViewContainer, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IContextKeyService contextKeyService: IContextKeyService, + @IMenuService menuService: IMenuService, + ) { + const scopedContextKeyService = contextKeyService.createScoped(); + scopedContextKeyService.createKey('viewContainer', viewContainer.id); + const viewContainerLocationKey = scopedContextKeyService.createKey('viewContainerLocation', ViewContainerLocationToString(viewDescriptorService.getViewContainerLocation(viewContainer)!)); + super(MenuId.ViewContainerTitle, MenuId.ViewContainerTitleContext, { shouldForwardArgs: true }, scopedContextKeyService, menuService); + this._register(scopedContextKeyService); + this._register(Event.filter(viewDescriptorService.onDidChangeContainerLocation, e => e.viewContainer === viewContainer)(() => viewContainerLocationKey.set(ViewContainerLocationToString(viewDescriptorService.getViewContainerLocation(viewContainer)!)))); + } +} + export class ViewPaneContainer extends Component implements IViewPaneContainer { readonly viewContainer: ViewContainer; @@ -910,6 +359,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { return this.paneItems.length; } + private readonly menuActions: ViewContainerMenuActions; + constructor( id: string, private options: IViewPaneContainerOptions, @@ -922,7 +373,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { @IThemeService protected themeService: IThemeService, @IStorageService protected storageService: IStorageService, @IWorkspaceContextService protected contextService: IWorkspaceContextService, - @IViewDescriptorService protected viewDescriptorService: IViewDescriptorService + @IViewDescriptorService protected viewDescriptorService: IViewDescriptorService, ) { super(id, themeService, storageService); @@ -938,6 +389,9 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { this.visibleViewsCountFromCache = this.storageService.getNumber(this.visibleViewsStorageId, StorageScope.WORKSPACE, undefined); this._register(toDisposable(() => this.viewDisposables = dispose(this.viewDisposables))); this.viewContainerModel = this.viewDescriptorService.getViewContainerModel(container); + + this.menuActions = this._register(instantiationService.createInstance(ViewContainerMenuActions, container)); + this._register(this.menuActions.onDidChange(() => this.updateTitleArea())); } create(parent: HTMLElement): void { @@ -1110,57 +564,62 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { let anchor: { x: number, y: number; } = { x: event.posx, y: event.posy }; this.contextMenuService.showContextMenu({ getAnchor: () => anchor, - getActions: () => this.getContextMenuActions() + getActions: () => [...this.getContextMenuActions2()] }); } + getContextMenuActions2(): ReadonlyArray { + return this.menuActions.getContextMenuActions(); + } + getContextMenuActions(viewDescriptor?: IViewDescriptor): IAction[] { - const result: IAction[] = []; + return []; + } - let showHide = true; - if (!viewDescriptor && this.isViewMergedWithContainer()) { - viewDescriptor = this.viewDescriptorService.getViewDescriptorById(this.panes[0].id) || undefined; - showHide = false; + getActions2(): IAction[] { + const result = []; + result.push(...this.menuActions.getPrimaryActions()); + if (this.isViewMergedWithContainer()) { + result.push(...this.paneItems[0].pane.getActions()); } - - if (viewDescriptor) { - if (showHide) { - result.push({ - id: `${viewDescriptor.id}.removeView`, - label: nls.localize('hideView', "Hide"), - enabled: viewDescriptor.canToggleVisibility, - run: () => this.toggleViewVisibility(viewDescriptor!.id) - }); - } - const view = this.getView(viewDescriptor.id); - if (view) { - result.push(...view.getContextMenuActions()); - } - } - - const viewToggleActions = this.getViewsVisibilityActions(); - if (result.length && viewToggleActions.length) { - result.push(new Separator()); - } - - result.push(...viewToggleActions); - return result; } getActions(): IAction[] { - if (this.isViewMergedWithContainer()) { - return this.paneItems[0].pane.getActions(); - } - return []; } - getSecondaryActions(): IAction[] { - if (this.isViewMergedWithContainer()) { - return this.paneItems[0].pane.getSecondaryActions(); + getSecondaryActions2(): IAction[] { + const viewPaneActions = this.isViewMergedWithContainer() ? this.paneItems[0].pane.getSecondaryActions() : []; + let menuActions = this.menuActions.getSecondaryActions(); + + const viewsSubmenuActionIndex = menuActions.findIndex(action => action instanceof SubmenuItemAction && action.item.submenu === ViewsSubMenu); + if (viewsSubmenuActionIndex !== -1) { + const viewsSubmenuAction = menuActions[viewsSubmenuActionIndex]; + if (viewsSubmenuAction.actions.some(({ enabled }) => enabled)) { + if (menuActions.length === 1 && viewPaneActions.length === 0) { + menuActions = viewsSubmenuAction.actions.slice(); + } else if (viewsSubmenuActionIndex !== 0) { + menuActions = [viewsSubmenuAction, ...menuActions.slice(0, viewsSubmenuActionIndex), ...menuActions.slice(viewsSubmenuActionIndex + 1)]; + } + } else { + // Remove views submenu if none of the actions are enabled + menuActions.splice(viewsSubmenuActionIndex, 1); + } } + if (menuActions.length && viewPaneActions.length) { + return [ + ...menuActions, + new Separator(), + ...viewPaneActions + ]; + } + + return menuActions.length ? menuActions : viewPaneActions; + } + + getSecondaryActions(): IAction[] { return []; } @@ -1168,22 +627,11 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { return undefined; } - getViewsVisibilityActions(): IAction[] { - return this.viewContainerModel.activeViewDescriptors.map(viewDescriptor => ({ - id: `${viewDescriptor.id}.toggleVisibility`, - label: viewDescriptor.name, - checked: this.viewContainerModel.isVisible(viewDescriptor.id), - enabled: viewDescriptor.canToggleVisibility && (!this.viewContainerModel.isVisible(viewDescriptor.id) || this.viewContainerModel.visibleViewDescriptors.length > 1), - run: () => this.toggleViewVisibility(viewDescriptor.id) - })); - } - getActionViewItem(action: IAction): IActionViewItem | undefined { if (this.isViewMergedWithContainer()) { return this.paneItems[0].pane.getActionViewItem(action); } - - return undefined; + return createActionViewItem(this.instantiationService, action); } focus(): void { @@ -1321,11 +769,11 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { this.storageService.store(this.visibleViewsStorageId, this.length, StorageScope.WORKSPACE, StorageTarget.USER); } - private onContextMenu(event: StandardMouseEvent, viewDescriptor: IViewDescriptor): void { + private onContextMenu(event: StandardMouseEvent, viewPane: ViewPane): void { event.stopPropagation(); event.preventDefault(); - const actions: IAction[] = this.getContextMenuActions(viewDescriptor); + const actions: IAction[] = viewPane.getContextMenuActions(); let anchor: { x: number, y: number } = { x: event.posx, y: event.posy }; this.contextMenuService.showContextMenu({ @@ -1364,7 +812,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { const contextMenuDisposable = addDisposableListener(pane.draggableElement, 'contextmenu', e => { e.stopPropagation(); e.preventDefault(); - this.onContextMenu(new StandardMouseEvent(e), viewDescriptor); + this.onContextMenu(new StandardMouseEvent(e), pane); }); const collapseDisposable = Event.latch(Event.map(pane.onDidChange, () => !pane.isExpanded()))(collapsed => { @@ -1401,7 +849,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } } - protected toggleViewVisibility(viewId: string): void { + toggleViewVisibility(viewId: string): void { // Check if view is active if (this.viewContainerModel.activeViewDescriptors.some(viewDescriptor => viewDescriptor.id === viewId)) { const visible = !this.viewContainerModel.isVisible(viewId); @@ -1641,7 +1089,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } } - private isViewMergedWithContainer(): boolean { + isViewMergedWithContainer(): boolean { if (!(this.options.mergeViewWithContainerWhenSingleView && this.paneItems.length === 1)) { return false; } @@ -1665,6 +1113,21 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } } +export abstract class ViewPaneContainerAction extends Action2 { + constructor(readonly desc: Readonly & { viewPaneContainerId: string }) { + super(desc); + } + + run(accessor: ServicesAccessor, ...args: any[]) { + const viewPaneContainer = accessor.get(IViewsService).getActiveViewPaneContainerWithId(this.desc.viewPaneContainerId); + if (viewPaneContainer) { + return this.runInViewPaneContainer(accessor, viewPaneContainer, ...args); + } + } + + abstract runInViewPaneContainer(accessor: ServicesAccessor, viewPaneContainer: T, ...args: any[]): any; +} + class MoveViewPosition extends Action2 { constructor(desc: Readonly, private readonly offset: number) { super(desc); diff --git a/src/vs/workbench/browser/parts/views/viewsService.ts b/src/vs/workbench/browser/parts/views/viewsService.ts index 687fbdb88..3f47ba106 100644 --- a/src/vs/workbench/browser/parts/views/viewsService.ts +++ b/src/vs/workbench/browser/parts/views/viewsService.ts @@ -196,7 +196,9 @@ export class ViewsService extends Disposable implements IViewsService { ContextKeyExpr.equals('view', viewDescriptor.id), ContextKeyExpr.equals(`${viewDescriptor.id}.defaultViewLocation`, false) ) - ) + ), + group: '1_hide', + order: 2 }], }); } @@ -392,12 +394,29 @@ export class ViewsService extends Disposable implements IViewsService { getViewProgressIndicator(viewId: string): IProgressIndicator | undefined { const viewContainer = this.viewDescriptorService.getViewContainerByViewId(viewId); - if (viewContainer === null) { + if (!viewContainer) { return undefined; } - const view = this.viewPaneContainers.get(viewContainer.id)?.viewPaneContainer?.getView(viewId); - return view?.getProgressIndicator(); + const viewPaneContainer = this.viewPaneContainers.get(viewContainer.id)?.viewPaneContainer; + if (!viewPaneContainer) { + return undefined; + } + + const view = viewPaneContainer.getView(viewId); + if (!view) { + return undefined; + } + + if (viewPaneContainer.isViewMergedWithContainer()) { + return this.getViewContainerProgressIndicator(viewContainer); + } + + return view.getProgressIndicator(); + } + + private getViewContainerProgressIndicator(viewContainer: ViewContainer): IProgressIndicator | undefined { + return this.viewDescriptorService.getViewContainerLocation(viewContainer) === ViewContainerLocation.Sidebar ? this.viewletService.getProgressIndicator(viewContainer.id) : this.panelService.getProgressIndicator(viewContainer.id); } private registerViewletOrPanel(viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation): void { @@ -451,7 +470,6 @@ export class ViewsService extends Disposable implements IViewsService { undefined, viewContainer.order, viewContainer.requestedIndex, - viewContainer.focusCommand?.id, )); } diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 47b8f0515..d6a6f07e6 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAction } from 'vs/base/common/actions'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IViewDescriptor, IViewDescriptorService, IAddedViewDescriptorRef } from 'vs/workbench/common/views'; @@ -12,7 +11,8 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { ViewPaneContainer, ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane'; import { Event } from 'vs/base/common/event'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -81,18 +81,6 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { this.getViewsForTarget(newFilterValue).forEach(item => this.viewContainerModel.setVisible(item.id, true)); } - getContextMenuActions(): IAction[] { - const result: IAction[] = Array.from(this.constantViewDescriptors.values()).map(viewDescriptor => ({ - id: `${viewDescriptor.id}.toggleVisibility`, - label: viewDescriptor.name, - checked: this.viewContainerModel.isVisible(viewDescriptor.id), - enabled: viewDescriptor.canToggleVisibility, - run: () => this.toggleViewVisibility(viewDescriptor.id) - })); - - return result; - } - private getViewsForTarget(target: string[]): IViewDescriptor[] { const views: IViewDescriptor[] = []; for (let i = 0; i < target.length; i++) { @@ -140,7 +128,4 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { abstract getTitle(): string; - getViewsVisibilityActions(): IAction[] { - return []; - } } diff --git a/src/vs/workbench/browser/style.ts b/src/vs/workbench/browser/style.ts index 0517f059b..4f1ecbcd0 100644 --- a/src/vs/workbench/browser/style.ts +++ b/src/vs/workbench/browser/style.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/style'; -import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { iconForeground, foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; import { WORKBENCH_BACKGROUND, TITLE_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; import { isWeb, isIOS, isMacintosh, isWindows } from 'vs/base/common/platform'; @@ -13,7 +13,7 @@ import { createMetaElement } from 'vs/base/browser/dom'; import { isSafari, isStandalone } from 'vs/base/browser/browser'; import { ColorScheme } from 'vs/platform/theme/common/theme'; -registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme, collector) => { // Foreground const windowForeground = theme.getColor(foreground); diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index 01ef1665c..79ec52099 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -3,23 +3,19 @@ * 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 { Registry } from 'vs/platform/registry/common/platform'; -import { Action, IAction, Separator, SubmenuAction } from 'vs/base/common/actions'; +import { Action } from 'vs/base/common/actions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { CompositeDescriptor, CompositeRegistry } from 'vs/workbench/browser/composite'; -import { IConstructorSignature0, IInstantiationService, BrandedService } from 'vs/platform/instantiation/common/instantiation'; -import { ToggleSidebarVisibilityAction, ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions'; +import { IConstructorSignature0, IInstantiationService, BrandedService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { URI } from 'vs/base/common/uri'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; -import { AbstractTree } from 'vs/base/browser/ui/tree/abstractTree'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -28,6 +24,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { PaneComposite } from 'vs/workbench/browser/panecomposite'; import { Event } from 'vs/base/common/event'; import { FilterViewPaneContainer } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { Action2 } from 'vs/platform/actions/common/actions'; export abstract class Viewlet extends PaneComposite implements IViewlet { @@ -52,40 +49,6 @@ export abstract class Viewlet extends PaneComposite implements IViewlet { })); } } - - getContextMenuActions(): IAction[] { - const parentActions = [...super.getContextMenuActions()]; - if (parentActions.length) { - parentActions.push(new Separator()); - } - - const toggleSidebarPositionAction = new ToggleSidebarPositionAction(ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.getLabel(this.layoutService), this.layoutService, this.configurationService); - return [...parentActions, toggleSidebarPositionAction, - { - id: ToggleSidebarVisibilityAction.ID, - label: nls.localize('compositePart.hideSideBarLabel', "Hide Side Bar"), - enabled: true, - run: () => this.layoutService.setSideBarHidden(true) - }]; - } - - getSecondaryActions(): IAction[] { - const viewVisibilityActions = this.viewPaneContainer.getViewsVisibilityActions(); - const secondaryActions = this.viewPaneContainer.getSecondaryActions(); - if (viewVisibilityActions.length <= 1 || viewVisibilityActions.every(({ enabled }) => !enabled)) { - return secondaryActions; - } - - if (secondaryActions.length === 0) { - return viewVisibilityActions; - } - - return [ - new SubmenuAction('workbench.views', nls.localize('views', "Views"), viewVisibilityActions), - new Separator(), - ...secondaryActions - ]; - } } /** @@ -115,7 +78,7 @@ export class ViewletDescriptor extends CompositeDescriptor { requestedIndex?: number, readonly iconUrl?: URI ) { - super(ctor, id, name, cssClass, order, requestedIndex, id); + super(ctor, id, name, cssClass, order, requestedIndex); } } @@ -152,7 +115,6 @@ export class ViewletRegistry extends CompositeRegistry { getViewlets(): ViewletDescriptor[] { return this.getComposites() as ViewletDescriptor[]; } - } Registry.add(Extensions.Viewlets, new ViewletRegistry()); @@ -176,7 +138,7 @@ export class ShowViewletAction extends Action { async run(): Promise { // Pass focus to viewlet if not open or focused - if (this.otherViewletShowing() || !this.sidebarHasFocus()) { + if (otherViewletShowing(this.viewletService, this.viewletId) || !sidebarHasFocus(this.viewletService, this.layoutService)) { await this.viewletService.openViewlet(this.viewletId, true); return; } @@ -184,28 +146,42 @@ export class ShowViewletAction extends Action { // Otherwise pass focus to editor group this.editorGroupService.activeGroup.focus(); } +} - private otherViewletShowing(): boolean { - const activeViewlet = this.viewletService.getActiveViewlet(); +/** + * A reusable action to show a viewlet with a specific id. + */ +export abstract class ShowViewletAction2 extends Action2 { + /** + * Gets the viewlet ID to show. + */ + protected abstract viewletId(): string; - return !activeViewlet || activeViewlet.getId() !== this.viewletId; - } + public async run(accessor: ServicesAccessor): Promise { + const viewletService = accessor.get(IViewletService); + const editorGroupService = accessor.get(IEditorGroupsService); + const layoutService = accessor.get(IWorkbenchLayoutService); - private sidebarHasFocus(): boolean { - const activeViewlet = this.viewletService.getActiveViewlet(); - const activeElement = document.activeElement; - const sidebarPart = this.layoutService.getContainer(Parts.SIDEBAR_PART); + // Pass focus to viewlet if not open or focused + if (otherViewletShowing(viewletService, this.viewletId()) || !sidebarHasFocus(viewletService, layoutService)) { + await viewletService.openViewlet(this.viewletId(), true); + return; + } - return !!(activeViewlet && activeElement && sidebarPart && DOM.isAncestor(activeElement, sidebarPart)); + // Otherwise pass focus to editor group + editorGroupService.activeGroup.focus(); } } -export class CollapseAction extends Action { - // We need a tree getter because the action is sometimes instantiated too early - constructor(treeGetter: () => AsyncDataTree | AbstractTree, enabled: boolean, clazz?: string) { - super('workbench.action.collapse', nls.localize('collapse', "Collapse All"), clazz, enabled, async () => { - const tree = treeGetter(); - tree.collapseAll(); - }); - } -} +const otherViewletShowing = (viewletService: IViewletService, viewletId: string): boolean => { + const activeViewlet = viewletService.getActiveViewlet(); + return !activeViewlet || activeViewlet.getId() !== viewletId; +}; + +const sidebarHasFocus = (viewletService: IViewletService, layoutService: IWorkbenchLayoutService): boolean => { + const activeViewlet = viewletService.getActiveViewlet(); + const activeElement = document.activeElement; + const sidebarPart = layoutService.getContainer(Parts.SIDEBAR_PART); + + return !!(activeViewlet && activeElement && sidebarPart && DOM.isAncestor(activeElement, sidebarPart)); +}; diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index e7d260b00..78bfe33cb 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { mark } from 'vs/base/common/performance'; -import { hash } from 'vs/base/common/hash'; -import { domContentLoaded, addDisposableListener, EventType, EventHelper, detectFullscreen, addDisposableThrottledListener } from 'vs/base/browser/dom'; +import { domContentLoaded, detectFullscreen, getCookieValue } from 'vs/base/browser/dom'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ILogService, ConsoleLogService, MultiplexLogService, getLogLevel } from 'vs/platform/log/common/log'; import { ConsoleLogInAutomationService } from 'vs/platform/log/browser/log'; @@ -24,10 +23,9 @@ import { IFileService, IFileSystemProvider } from 'vs/platform/files/common/file import { FileService } from 'vs/platform/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { onUnexpectedError } from 'vs/base/common/errors'; import { setFullscreen } from 'vs/base/browser/browser'; -import { isIOS, isMacintosh } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; @@ -37,12 +35,11 @@ import { SignService } from 'vs/platform/sign/browser/signService'; import type { IWorkbenchConstructionOptions, IWorkspace, IWorkbench } from 'vs/workbench/workbench.web.api'; import { BrowserStorageService } from 'vs/platform/storage/browser/storageService'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { registerWindowDriver } from 'vs/platform/driver/browser/driver'; import { BufferLogService } from 'vs/platform/log/common/bufferLog'; import { FileLogService } from 'vs/platform/log/common/fileLogService'; import { toLocalISOString } from 'vs/base/common/date'; import { isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows'; -import { getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces'; +import { getSingleFolderWorkspaceIdentifier, getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces'; import { coalesce } from 'vs/base/common/arrays'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -61,6 +58,8 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService'; +import { BrowserWindow } from 'vs/workbench/browser/window'; +import { ITimerService } from 'vs/workbench/services/timer/browser/timerService'; class BrowserMain extends Disposable { @@ -83,35 +82,40 @@ class BrowserMain extends Disposable { const services = await this.initServices(); await domContentLoaded(); - mark('willStartWorkbench'); + mark('code/willStartWorkbench'); // Create Workbench - const workbench = new Workbench( - this.domElement, - services.serviceCollection, - services.logService - ); + const workbench = new Workbench(this.domElement, services.serviceCollection, services.logService); // Listeners this.registerListeners(workbench, services.storageService, services.logService); - // Driver - if (this.configuration.driver) { - (async () => this._register(await registerWindowDriver()))(); - } - // Startup const instantiationService = workbench.startup(); + // Window + this._register(instantiationService.createInstance(BrowserWindow)); + + // Logging + services.logService.trace('workbench configuration', JSON.stringify(this.configuration)); + // Return API Facade return instantiationService.invokeFunction(accessor => { const commandService = accessor.get(ICommandService); const lifecycleService = accessor.get(ILifecycleService); + const timerService = accessor.get(ITimerService); return { commands: { executeCommand: (command, ...args) => commandService.executeCommand(command, ...args) }, + env: { + async retrievePerformanceMarks() { + await timerService.whenReady(); + + return timerService.getPerformanceMarks(); + } + }, shutdown: () => lifecycleService.shutdown() }; }); @@ -119,46 +123,17 @@ class BrowserMain extends Disposable { private registerListeners(workbench: Workbench, storageService: BrowserStorageService, logService: ILogService): void { - // Layout - const viewport = isIOS && window.visualViewport ? window.visualViewport /** Visual viewport */ : window /** Layout viewport */; - this._register(addDisposableListener(viewport, EventType.RESIZE, () => { - logService.trace(`web.main#${isIOS && window.visualViewport ? 'visualViewport' : 'window'}Resize`); - workbench.layout(); - })); - - // Prevent the back/forward gestures in macOS - this._register(addDisposableListener(this.domElement, EventType.WHEEL, e => e.preventDefault(), { passive: false })); - - // Prevent native context menus in web - this._register(addDisposableListener(this.domElement, EventType.CONTEXT_MENU, e => EventHelper.stop(e, true))); - - // Prevent default navigation on drop - this._register(addDisposableListener(this.domElement, EventType.DROP, e => EventHelper.stop(e, true))); - // Workbench Lifecycle this._register(workbench.onBeforeShutdown(event => { if (storageService.hasPendingUpdate) { - logService.warn('Unload veto: pending storage update'); - event.veto(true); // prevent data loss from pending storage update + event.veto(true, 'veto.pendingStorageUpdate'); // prevent data loss from pending storage update } })); - this._register(workbench.onWillShutdown(() => { - storageService.close(); - })); + this._register(workbench.onWillShutdown(() => storageService.close())); this._register(workbench.onShutdown(() => this.dispose())); - - // Fullscreen (Browser) - [EventType.FULLSCREEN_CHANGE, EventType.WK_FULLSCREEN_CHANGE].forEach(event => { - this._register(addDisposableListener(document, event, () => setFullscreen(!!detectFullscreen()))); - }); - - // Fullscreen (Native) - this._register(addDisposableThrottledListener(viewport, EventType.RESIZE, () => { - setFullscreen(!!detectFullscreen()); - }, undefined, isMacintosh ? 2000 /* adjust for macOS animation */ : 800 /* can be throttled */)); } - private async initServices(): Promise<{ serviceCollection: ServiceCollection, configurationService: IConfigurationService, logService: ILogService, storageService: BrowserStorageService }> { + private async initServices(): Promise<{ serviceCollection: ServiceCollection, configurationService: IWorkbenchConfigurationService, logService: ILogService, storageService: BrowserStorageService }> { const serviceCollection = new ServiceCollection(); // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -166,7 +141,7 @@ class BrowserMain extends Disposable { // CONTRIBUTE IT VIA WORKBENCH.WEB.MAIN.TS AND registerSingleton(). // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - const payload = await this.resolveWorkspaceInitializationPayload(); + const payload = this.resolveWorkspaceInitializationPayload(); // Product const productService: IProductService = { _serviceBrand: undefined, ...product, ...this.configuration.productConfiguration }; @@ -181,9 +156,8 @@ class BrowserMain extends Disposable { const logService = new BufferLogService(getLogLevel(environmentService)); serviceCollection.set(ILogService, logService); - const connectionToken = environmentService.options.connectionToken || this.getCookieValue('vscode-tkn'); - // Remote + const connectionToken = environmentService.options.connectionToken || getCookieValue('vscode-tkn'); const remoteAuthorityResolverService = new RemoteAuthorityResolverService(connectionToken, this.configuration.resourceUriProvider); serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService); @@ -212,7 +186,7 @@ class BrowserMain extends Disposable { serviceCollection.set(IWorkspaceContextService, service); // Configuration - serviceCollection.set(IConfigurationService, service); + serviceCollection.set(IWorkbenchConfigurationService, service); return service; }), @@ -239,14 +213,16 @@ class BrowserMain extends Disposable { serviceCollection.set(IUserDataInitializationService, userDataInitializationService); if (await userDataInitializationService.requiresInitialization()) { - mark('willInitRequiredUserData'); + mark('code/willInitRequiredUserData'); + // Initialize required resources - settings & global state await userDataInitializationService.initializeRequiredResources(); // Important: Reload only local user configuration after initializing // Reloading complete configuraiton blocks workbench until remote configuration is loaded. await configurationService.reloadLocalUserConfiguration(); - mark('didInitRequiredUserData'); + + mark('code/didInitRequiredUserData'); } return { serviceCollection, configurationService, logService, storageService }; @@ -261,8 +237,9 @@ class BrowserMain extends Disposable { try { indexedDBLogProvider = await indexedDB.createFileSystemProvider(logsPath.scheme, INDEXEDDB_LOGS_OBJECT_STORE); } catch (error) { - console.error(error); + onUnexpectedError(error); } + if (indexedDBLogProvider) { fileService.registerProvider(logsPath.scheme, indexedDBLogProvider); } else { @@ -279,6 +256,7 @@ class BrowserMain extends Disposable { const connection = remoteAgentService.getConnection(); if (connection) { + // Remote file system const remoteFileSystemProvider = this._register(new RemoteFileSystemProvider(remoteAgentService)); fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider); @@ -289,8 +267,9 @@ class BrowserMain extends Disposable { try { indexedDBUserDataProvider = await indexedDB.createFileSystemProvider(Schemas.userData, INDEXEDDB_USERDATA_OBJECT_STORE); } catch (error) { - console.error(error); + onUnexpectedError(error); } + fileService.registerProvider(Schemas.userData, indexedDBUserDataProvider || new InMemoryFileSystemProvider()); if (indexedDBUserDataProvider) { registerAction2(class ResetUserDataAction extends Action2 { @@ -304,21 +283,22 @@ class BrowserMain extends Disposable { } }); } + async run(accessor: ServicesAccessor): Promise { const dialogService = accessor.get(IDialogService); const hostService = accessor.get(IHostService); const result = await dialogService.confirm({ message: localize('reset user data message', "Would you like to reset your data (settings, keybindings, extensions, snippets and UI State) and reload?") }); + if (result.confirmed) { - await indexedDBUserDataProvider!.reset(); + await indexedDBUserDataProvider?.reset(); } + hostService.reload(); } }); } - - fileService.registerProvider(Schemas.userData, indexedDBUserDataProvider || new InMemoryFileSystemProvider()); } private async createStorageService(payload: IWorkspaceInitializationPayload, environmentService: IWorkbenchEnvironmentService, fileService: IFileService, logService: ILogService): Promise { @@ -351,7 +331,7 @@ class BrowserMain extends Disposable { } } - private async resolveWorkspaceInitializationPayload(): Promise { + private resolveWorkspaceInitializationPayload(): IWorkspaceInitializationPayload { let workspace: IWorkspace | undefined = undefined; if (this.configuration.workspaceProvider) { workspace = this.configuration.workspaceProvider.workspace; @@ -364,18 +344,11 @@ class BrowserMain extends Disposable { // Single-folder workspace if (workspace && isFolderToOpen(workspace)) { - const id = hash(workspace.folderUri.toString()).toString(16); - return { id, folder: workspace.folderUri }; + return getSingleFolderWorkspaceIdentifier(workspace.folderUri); } return { id: 'empty-window' }; } - - 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; - } } export function main(domElement: HTMLElement, options: IWorkbenchConstructionOptions): Promise { diff --git a/src/vs/workbench/browser/window.ts b/src/vs/workbench/browser/window.ts new file mode 100644 index 000000000..337771b2d --- /dev/null +++ b/src/vs/workbench/browser/window.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 { setFullscreen } from 'vs/base/browser/browser'; +import { addDisposableListener, addDisposableThrottledListener, detectFullscreen, EventHelper, EventType, windowOpenNoOpener } from 'vs/base/browser/dom'; +import { domEvent } from 'vs/base/browser/event'; +import { timeout } from 'vs/base/common/async'; +import { Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; +import { isIOS, isMacintosh } from 'vs/base/common/platform'; +import Severity from 'vs/base/common/severity'; +import { localize } from 'vs/nls'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { registerWindowDriver } from 'vs/platform/driver/browser/driver'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { BrowserLifecycleService } from 'vs/workbench/services/lifecycle/browser/lifecycleService'; +import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; + +export class BrowserWindow extends Disposable { + + constructor( + @IOpenerService private readonly openerService: IOpenerService, + @ILifecycleService private readonly lifecycleService: BrowserLifecycleService, + @IDialogService private readonly dialogService: IDialogService, + @IHostService private readonly hostService: IHostService, + @ILabelService private readonly labelService: ILabelService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @ILogService private readonly logService: ILogService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService + ) { + super(); + + this.registerListeners(); + this.create(); + } + + private registerListeners(): void { + + // Lifecycle + this._register(this.lifecycleService.onWillShutdown(() => this.onWillShutdown())); + + // Layout + const viewport = isIOS && window.visualViewport ? window.visualViewport /** Visual viewport */ : window /** Layout viewport */; + this._register(addDisposableListener(viewport, EventType.RESIZE, () => this.onWindowResize())); + + // Prevent the back/forward gestures in macOS + this._register(addDisposableListener(this.layoutService.getWorkbenchContainer(), EventType.WHEEL, e => e.preventDefault(), { passive: false })); + + // Prevent native context menus in web + this._register(addDisposableListener(this.layoutService.getWorkbenchContainer(), EventType.CONTEXT_MENU, e => EventHelper.stop(e, true))); + + // Prevent default navigation on drop + this._register(addDisposableListener(this.layoutService.getWorkbenchContainer(), EventType.DROP, e => EventHelper.stop(e, true))); + + // Fullscreen (Browser) + [EventType.FULLSCREEN_CHANGE, EventType.WK_FULLSCREEN_CHANGE].forEach(event => { + this._register(addDisposableListener(document, event, () => setFullscreen(!!detectFullscreen()))); + }); + + // Fullscreen (Native) + this._register(addDisposableThrottledListener(viewport, EventType.RESIZE, () => { + setFullscreen(!!detectFullscreen()); + }, undefined, isMacintosh ? 2000 /* adjust for macOS animation */ : 800 /* can be throttled */)); + } + + private onWindowResize(): void { + this.logService.trace(`web.main#${isIOS && window.visualViewport ? 'visualViewport' : 'window'}Resize`); + + this.layoutService.layout(); + } + + private onWillShutdown(): void { + + // Try to detect some user interaction with the workbench + // when shutdown has happened to not show the dialog e.g. + // when navigation takes a longer time. + Event.toPromise(Event.any( + Event.once(domEvent(document.body, EventType.KEY_DOWN, true)), + Event.once(domEvent(document.body, EventType.MOUSE_DOWN, true)) + )).then(async () => { + + // Delay the dialog in case the user interacted + // with the page before it transitioned away + await timeout(3000); + + // This should normally not happen, but if for some reason + // the workbench was shutdown while the page is still there, + // inform the user that only a reload can bring back a working + // state. + const res = await this.dialogService.show( + Severity.Error, + localize('shutdownError', "An unexpected error occurred that requires a reload of this page."), + [ + localize('reload', "Reload") + ], + { + detail: localize('shutdownErrorDetail', "The workbench was unexpectedly disposed while running.") + } + ); + + if (res.choice === 0) { + this.hostService.reload(); + } + }); + } + + private create(): void { + + // Driver + if (this.environmentService.options?.driver) { + (async () => this._register(await registerWindowDriver()))(); + } + + // Handle open calls + this.setupOpenHandlers(); + + // Label formatting + this.registerLabelFormatters(); + } + + private setupOpenHandlers(): void { + + // We need to ignore the `beforeunload` event while + // we handle external links to open specifically for + // the case of application protocols that e.g. invoke + // vscode itself. We do not want to open these links + // in a new window because that would leave a blank + // window to the user, but using `window.location.href` + // will trigger the `beforeunload`. + this.openerService.setDefaultExternalOpener({ + openExternal: async (href: string) => { + if (matchesScheme(href, Schemas.http) || matchesScheme(href, Schemas.https)) { + windowOpenNoOpener(href); + } else { + this.lifecycleService.withExpectedUnload(() => window.location.href = href); + } + + return true; + } + }); + } + + private registerLabelFormatters() { + this.labelService.registerFormatter({ + scheme: Schemas.userData, + priority: true, + formatting: { + label: '${scheme}:${path}', + separator: '/', + } + }); + } +} diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index fdc99470b..03297afe7 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -33,14 +33,29 @@ import { isStandalone } from 'vs/base/browser/browser'; 'description': nls.localize('showEditorTabs', "Controls whether opened editors should show in tabs or not."), 'default': true }, + 'workbench.editor.wrapTabs': { + 'type': 'boolean', + 'markdownDescription': nls.localize('wrapTabs', "Controls whether tabs should be wrapped over multiple lines when exceeding available space or whether a scrollbar should appear instead. This value is ignored when `#workbench.editor.showTabs#` is disabled."), + 'default': false + }, 'workbench.editor.scrollToSwitchTabs': { 'type': 'boolean', - 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls whether scrolling over tabs will open them or not. By default tabs will only reveal upon scrolling, but not open. You can press and hold the Shift-key while scrolling to change this behaviour for that duration. This value is ignored when `#workbench.editor.showTabs#` is `false`."), + 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls whether scrolling over tabs will open them or not. By default tabs will only reveal upon scrolling, but not open. You can press and hold the Shift-key while scrolling to change this behaviour for that duration. This value is ignored when `#workbench.editor.showTabs#` is disabled."), 'default': false }, 'workbench.editor.highlightModifiedTabs': { 'type': 'boolean', - 'markdownDescription': nls.localize('highlightModifiedTabs', "Controls whether a top border is drawn on modified (dirty) editor tabs or not. This value is ignored when `#workbench.editor.showTabs#` is `false`."), + 'markdownDescription': nls.localize('highlightModifiedTabs', "Controls whether a top border is drawn on modified (dirty) editor tabs or not. This value is ignored when `#workbench.editor.showTabs#` is disabled."), + 'default': false + }, + 'workbench.editor.decorations.badges': { + 'type': 'boolean', + 'markdownDescription': nls.localize('decorations.badges', "Controls whether editor file decorations should use badges."), + 'default': false + }, + 'workbench.editor.decorations.colors': { + 'type': 'boolean', + 'markdownDescription': nls.localize('decorations.colors', "Controls whether editor file decorations should use colors."), 'default': false }, 'workbench.editor.labelFormat': { @@ -75,7 +90,7 @@ import { isStandalone } from 'vs/base/browser/browser'; 'type': 'string', 'enum': ['left', 'right', 'off'], 'default': 'right', - 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorTabCloseButton' }, "Controls the position of the editor's tabs close buttons, or disables them when set to 'off'. This value is ignored when `#workbench.editor.showTabs#` is `false`.") + 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorTabCloseButton' }, "Controls the position of the editor's tabs close buttons, or disables them when set to 'off'. This value is ignored when `#workbench.editor.showTabs#` is disabled.") }, 'workbench.editor.tabSizing': { 'type': 'string', @@ -85,7 +100,7 @@ import { isStandalone } from 'vs/base/browser/browser'; nls.localize('workbench.editor.tabSizing.fit', "Always keep tabs large enough to show the full editor label."), nls.localize('workbench.editor.tabSizing.shrink', "Allow tabs to get smaller when the available space is not enough to show all tabs at once.") ], - 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'tabSizing' }, "Controls the sizing of editor tabs. This value is ignored when `#workbench.editor.showTabs#` is `false`.") + 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'tabSizing' }, "Controls the sizing of editor tabs. This value is ignored when `#workbench.editor.showTabs#` is disabled.") }, 'workbench.editor.pinnedTabSizing': { 'type': 'string', @@ -96,7 +111,7 @@ import { isStandalone } from 'vs/base/browser/browser'; nls.localize('workbench.editor.pinnedTabSizing.compact', "A pinned tab will show in a compact form with only icon or first letter of the editor name."), nls.localize('workbench.editor.pinnedTabSizing.shrink', "A pinned tab shrinks to a compact fixed size showing parts of the editor name.") ], - 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'pinnedTabSizing' }, "Controls the sizing of pinned editor tabs. Pinned tabs are sorted to the beginning of all opened tabs and typically do not close until unpinned. This value is ignored when `#workbench.editor.showTabs#` is `false`.") + 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'pinnedTabSizing' }, "Controls the sizing of pinned editor tabs. Pinned tabs are sorted to the beginning of all opened tabs and typically do not close until unpinned. This value is ignored when `#workbench.editor.showTabs#` is disabled.") }, 'workbench.editor.splitSizing': { 'type': 'string', @@ -130,7 +145,12 @@ import { isStandalone } from 'vs/base/browser/browser'; }, 'workbench.editor.enablePreviewFromQuickOpen': { 'type': 'boolean', - 'description': nls.localize('enablePreviewFromQuickOpen', "Controls whether editors opened from Quick Open show as preview. Preview editors do not keep open and are reused until explicitly set to be kept open (e.g. via double click or editing)."), + 'markdownDescription': nls.localize('enablePreviewFromQuickOpen', "Controls whether editors opened from Quick Open show as preview. Preview editors do not keep open and are reused until explicitly set to be kept open (e.g. via double click or editing). This value is ignored when `#workbench.editor.enablePreview#` is disabled."), + 'default': false + }, + 'workbench.editor.enablePreviewFromCodeNavigation': { + 'type': 'boolean', + 'markdownDescription': nls.localize('enablePreviewFromCodeNavigation', "Controls whether editors remain in preview when a code navigation is started from them. Preview editors do not keep open and are reused until explicitly set to be kept open (e.g. via double click or editing). This value is ignored when `#workbench.editor.enablePreview#` is disabled."), 'default': false }, 'workbench.editor.closeOnFileDelete': { @@ -315,8 +335,8 @@ import { isStandalone } from 'vs/base/browser/browser'; nls.localize('activeFolderLong', "`\${activeFolderLong}`: the full path of the folder the file is contained in (e.g. /Users/Development/myFolder/myFileFolder)."), nls.localize('folderName', "`\${folderName}`: name of the workspace folder the file is contained in (e.g. myFolder)."), nls.localize('folderPath', "`\${folderPath}`: file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder)."), - nls.localize('rootName', "`\${rootName}`: name of the workspace (e.g. myFolder or myWorkspace)."), - nls.localize('rootPath', "`\${rootPath}`: file path of the workspace (e.g. /Users/Development/myWorkspace)."), + nls.localize('rootName', "`\${rootName}`: name of the opened workspace or folder (e.g. myFolder or myWorkspace)."), + nls.localize('rootPath', "`\${rootPath}`: file path of the opened workspace or folder (e.g. /Users/Development/myWorkspace)."), nls.localize('appName', "`\${appName}`: e.g. VS Code."), nls.localize('remoteName', "`\${remoteName}`: e.g. SSH"), nls.localize('dirty', "`\${dirty}`: a dirty indicator if the active editor is dirty."), @@ -353,7 +373,7 @@ import { isStandalone } from 'vs/base/browser/browser'; 'window.menuBarVisibility': { 'type': 'string', 'enum': ['default', 'visible', 'toggle', 'hidden', 'compact'], - 'enumDescriptions': [ + 'markdownEnumDescriptions': [ nls.localize('window.menuBarVisibility.default', "Menu is only hidden in full screen mode."), nls.localize('window.menuBarVisibility.visible', "Menu is always visible even in full screen mode."), nls.localize('window.menuBarVisibility.toggle', "Menu is hidden but can be displayed via Alt key."), @@ -463,7 +483,7 @@ import { isStandalone } from 'vs/base/browser/browser'; }, 'zenMode.restore': { 'type': 'boolean', - 'default': false, + 'default': true, 'description': nls.localize('zenMode.restore', "Controls whether a window should restore to zen mode if it was exited in zen mode.") }, 'zenMode.silentNotifications': { diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 2172c0944..aedb2a6f6 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -8,7 +8,7 @@ import 'vs/workbench/browser/style'; import { localize } from 'vs/nls'; import { Emitter, setGlobalLeakWarningThreshold } from 'vs/base/common/event'; import { runWhenIdle } from 'vs/base/common/async'; -import { getZoomLevel, isFirefox, isSafari, isChrome } from 'vs/base/browser/browser'; +import { getZoomLevel, isFirefox, isSafari, isChrome, getPixelRatio } from 'vs/base/browser/browser'; import { mark } from 'vs/base/common/performance'; import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -177,10 +177,17 @@ export class Workbench extends Layout { // Layout Service serviceCollection.set(IWorkbenchLayoutService, this); - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // NOTE: DO NOT ADD ANY OTHER SERVICE INTO THE COLLECTION HERE. - // CONTRIBUTE IT VIA WORKBENCH.DESKTOP.MAIN.TS AND registerSingleton(). - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // + // NOTE: Please do NOT register services here. Use `registerSingleton()` + // from `workbench.common.main.ts` if the service is shared between + // native and web or `workbench.sandbox.main.ts` if the service + // is native only. + // + // DO NOT add services to `workbench.desktop.main.ts`, always add + // to `workbench.sandbox.main.ts` to support our Electron sandbox + // + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // All Contributed Services const contributedServices = getSingletonServiceDescriptors(); @@ -284,7 +291,7 @@ export class Workbench extends Layout { } } - readFontInfo(BareFontInfo.createFromRawSettings(configurationService.getValue('editor'), getZoomLevel())); + readFontInfo(BareFontInfo.createFromRawSettings(configurationService.getValue('editor'), getZoomLevel(), getPixelRatio())); } private storeFontInfo(storageService: IStorageService): void { @@ -412,10 +419,10 @@ export class Workbench extends Layout { }, 2500); // Telemetry: startup metrics - mark('didStartWorkbench'); + mark('code/didStartWorkbench'); // Perf reporting (devtools) - performance.measure('perf: workbench create & restore', 'didLoadWorkbenchMain', 'didStartWorkbench'); + performance.measure('perf: workbench create & restore', 'code/didLoadWorkbenchMain', 'code/didStartWorkbench'); } } } diff --git a/src/vs/workbench/common/actions.ts b/src/vs/workbench/common/actions.ts index de82ce88e..dfe61debb 100644 --- a/src/vs/workbench/common/actions.ts +++ b/src/vs/workbench/common/actions.ts @@ -128,5 +128,6 @@ Registry.add(Extensions.WorkbenchActions, new class implements IWorkbenchActionR export const CATEGORIES = { View: { value: localize('view', "View"), original: 'View' }, Help: { value: localize('help', "Help"), original: 'Help' }, + Preferences: { value: localize('preferences', "Preferences"), original: 'Preferences' }, Developer: { value: localize({ key: 'developer', comment: ['A developer on Code itself or someone diagnosing issues in Code'] }, "Developer"), original: 'Developer' } }; diff --git a/src/vs/workbench/common/composite.ts b/src/vs/workbench/common/composite.ts index 8b48a7f4d..d27f59b36 100644 --- a/src/vs/workbench/common/composite.ts +++ b/src/vs/workbench/common/composite.ts @@ -18,6 +18,11 @@ export interface IComposite { */ readonly onDidBlur: Event; + /** + * Returns true if the composite has focus. + */ + hasFocus(): boolean; + /** * Returns the unique identifier of this composite. */ diff --git a/src/vs/workbench/common/dialogs.ts b/src/vs/workbench/common/dialogs.ts index 5de8eac68..5e0561583 100644 --- a/src/vs/workbench/common/dialogs.ts +++ b/src/vs/workbench/common/dialogs.ts @@ -18,6 +18,7 @@ export interface IDialogHandle { } export interface IDialogsModel { + readonly onDidShowDialog: Event; readonly dialogs: IDialogViewItem[]; @@ -26,6 +27,7 @@ export interface IDialogsModel { } export class DialogsModel extends Disposable implements IDialogsModel { + readonly dialogs: IDialogViewItem[] = []; private readonly _onDidShowDialog = this._register(new Emitter()); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index f104149d8..883328209 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -74,7 +74,7 @@ export interface IEditorPane extends IComposite { /** * The assigned options of the editor. */ - readonly options: EditorOptions | undefined; + readonly options: IEditorOptions | undefined; /** * The assigned group this editor is showing in. @@ -1215,7 +1215,7 @@ export interface IEditorOpenContext { * An editor is new for a group if it was not part of the group before and * otherwise was already opened in the group and just became the active editor. * - * This hint can e.g. be used to decide wether to restore view state or not. + * This hint can e.g. be used to decide whether to restore view state or not. */ newInGroup?: boolean; } @@ -1257,14 +1257,15 @@ export interface IEditorCloseEvent extends IEditorIdentifier { export type GroupIdentifier = number; export interface IWorkbenchEditorConfiguration { - workbench: { - editor: IEditorPartConfiguration, - iconTheme: string; + workbench?: { + editor?: IEditorPartConfiguration, + iconTheme?: string; }; } interface IEditorPartConfiguration { showTabs?: boolean; + wrapTabs?: boolean; scrollToSwitchTabs?: boolean; highlightModifiedTabs?: boolean; tabCloseButton?: 'left' | 'right' | 'off'; @@ -1275,6 +1276,7 @@ interface IEditorPartConfiguration { showIcons?: boolean; enablePreview?: boolean; enablePreviewFromQuickOpen?: boolean; + enablePreviewFromCodeNavigation?: boolean; closeOnFileDelete?: boolean; openPositioning?: 'left' | 'right' | 'first' | 'last'; openSideBySideDirection?: 'right' | 'down'; @@ -1290,6 +1292,10 @@ interface IEditorPartConfiguration { value?: number; perEditorGroup?: boolean; }; + decorations?: { + badges?: boolean; + colors?: boolean; + } } export interface IEditorPartOptions extends IEditorPartConfiguration { @@ -1328,7 +1334,9 @@ class EditorResourceAccessorImpl { /** * The original URI of an editor is the URI that was used originally to open * the editor and should be used whenever the URI is presented to the user, - * e.g. as a label. + * e.g. as a label together with utility methods such as `ResourceLabel` or + * `ILabelService` that can turn this original URI into the best form for + * presenting. * * In contrast, the canonical URI (#getCanonicalUri) may be different and should * be used whenever the URI is used to e.g. compare with other editors or when diff --git a/src/vs/workbench/common/editor/diffEditorInput.ts b/src/vs/workbench/common/editor/diffEditorInput.ts index 38051db4d..1b0eed959 100644 --- a/src/vs/workbench/common/editor/diffEditorInput.ts +++ b/src/vs/workbench/common/editor/diffEditorInput.ts @@ -13,6 +13,7 @@ import { dirname } from 'vs/base/common/resources'; import { ILabelService } from 'vs/platform/label/common/label'; import { IFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; +import { withNullAsUndefined } from 'vs/base/common/types'; /** * The base editor input for the diff editor. It is made up of two editor inputs, the original version @@ -110,21 +111,18 @@ export class DiffEditorInput extends SideBySideEditorInput { private async createModel(): Promise { // Join resolve call over two inputs and build diff editor model - const models = await Promise.all([ + const [originalEditorModel, modifiedEditorModel] = await Promise.all([ this.originalInput.resolve(), this.modifiedInput.resolve() ]); - const originalEditorModel = models[0]; - const modifiedEditorModel = models[1]; - // If both are text models, return textdiffeditor model if (modifiedEditorModel instanceof BaseTextEditorModel && originalEditorModel instanceof BaseTextEditorModel) { return new TextDiffEditorModel(originalEditorModel, modifiedEditorModel); } // Otherwise return normal diff model - return new DiffEditorModel(originalEditorModel, modifiedEditorModel); + return new DiffEditorModel(withNullAsUndefined(originalEditorModel), withNullAsUndefined(modifiedEditorModel)); } matches(otherInput: unknown): boolean { diff --git a/src/vs/workbench/common/editor/diffEditorModel.ts b/src/vs/workbench/common/editor/diffEditorModel.ts index dfa51d62d..5550bf997 100644 --- a/src/vs/workbench/common/editor/diffEditorModel.ts +++ b/src/vs/workbench/common/editor/diffEditorModel.ts @@ -12,13 +12,13 @@ import { IEditorModel } from 'vs/platform/editor/common/editor'; */ export class DiffEditorModel extends EditorModel { - protected readonly _originalModel: IEditorModel | null; - get originalModel(): IEditorModel | null { return this._originalModel; } + protected readonly _originalModel: IEditorModel | undefined; + get originalModel(): IEditorModel | undefined { return this._originalModel; } - protected readonly _modifiedModel: IEditorModel | null; - get modifiedModel(): IEditorModel | null { return this._modifiedModel; } + protected readonly _modifiedModel: IEditorModel | undefined; + get modifiedModel(): IEditorModel | undefined { return this._modifiedModel; } - constructor(originalModel: IEditorModel | null, modifiedModel: IEditorModel | null) { + constructor(originalModel: IEditorModel | undefined, modifiedModel: IEditorModel | undefined) { super(); this._originalModel = originalModel; @@ -28,7 +28,7 @@ export class DiffEditorModel extends EditorModel { async load(): Promise { await Promise.all([ this._originalModel?.load(), - this._modifiedModel?.load(), + this._modifiedModel?.load() ]); return this; diff --git a/src/vs/workbench/common/editor/editorGroup.ts b/src/vs/workbench/common/editor/editorGroup.ts index 8d7410ac5..d1580899a 100644 --- a/src/vs/workbench/common/editor/editorGroup.ts +++ b/src/vs/workbench/common/editor/editorGroup.ts @@ -724,12 +724,19 @@ export class EditorGroup extends Disposable { clone(): EditorGroup { const group = this.instantiationService.createInstance(EditorGroup, undefined); + + // Copy over group properties group.editors = this.editors.slice(0); group.mru = this.mru.slice(0); group.preview = this.preview; group.active = this.active; group.sticky = this.sticky; + // Ensure to register listeners for each editor + for (const editor of group.editors) { + group.registerEditorListeners(editor); + } + return group; } diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index a72d26526..0167182c2 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -97,7 +97,7 @@ export class ResourceEditorInput extends AbstractTextResourceEditorInput impleme ref.dispose(); this.modelReference = undefined; - throw new Error(`Unexpected model for ResourcEditorInput: ${this.resource}`); + throw new Error(`Unexpected model for ResourceEditorInput: ${this.resource}`); } this.cachedModel = model; diff --git a/src/vs/workbench/common/editor/resourceEditorModel.ts b/src/vs/workbench/common/editor/resourceEditorModel.ts index e8e58c3f9..56f7aefe2 100644 --- a/src/vs/workbench/common/editor/resourceEditorModel.ts +++ b/src/vs/workbench/common/editor/resourceEditorModel.ts @@ -23,7 +23,7 @@ export class ResourceEditorModel extends BaseTextEditorModel { dispose(): void { - // TODO@Joao: force this class to dispose the underlying model + // force this class to dispose the underlying model if (this.textEditorModelHandle) { this.modelService.destroyModel(this.textEditorModelHandle); } diff --git a/src/vs/workbench/common/editor/textDiffEditorModel.ts b/src/vs/workbench/common/editor/textDiffEditorModel.ts index fcb97b428..98528b2e5 100644 --- a/src/vs/workbench/common/editor/textDiffEditorModel.ts +++ b/src/vs/workbench/common/editor/textDiffEditorModel.ts @@ -14,14 +14,14 @@ import { DiffEditorModel } from 'vs/workbench/common/editor/diffEditorModel'; */ export class TextDiffEditorModel extends DiffEditorModel { - protected readonly _originalModel: BaseTextEditorModel | null; - get originalModel(): BaseTextEditorModel | null { return this._originalModel; } + protected readonly _originalModel: BaseTextEditorModel | undefined; + get originalModel(): BaseTextEditorModel | undefined { return this._originalModel; } - protected readonly _modifiedModel: BaseTextEditorModel | null; - get modifiedModel(): BaseTextEditorModel | null { return this._modifiedModel; } + protected readonly _modifiedModel: BaseTextEditorModel | undefined; + get modifiedModel(): BaseTextEditorModel | undefined { return this._modifiedModel; } - private _textDiffEditorModel: IDiffEditorModel | null = null; - get textDiffEditorModel(): IDiffEditorModel | null { return this._textDiffEditorModel; } + private _textDiffEditorModel: IDiffEditorModel | undefined = undefined; + get textDiffEditorModel(): IDiffEditorModel | undefined { return this._textDiffEditorModel; } constructor(originalModel: BaseTextEditorModel, modifiedModel: BaseTextEditorModel) { super(originalModel, modifiedModel); @@ -73,7 +73,7 @@ export class TextDiffEditorModel extends DiffEditorModel { // inside. We never created the two models (original and modified) so we can not dispose // them without sideeffects. Rather rely on the models getting disposed when their related // inputs get disposed from the diffEditorInput. - this._textDiffEditorModel = null; + this._textDiffEditorModel = undefined; super.dispose(); } diff --git a/src/vs/workbench/common/editor/textEditorModel.ts b/src/vs/workbench/common/editor/textEditorModel.ts index f730d008e..3225ea5a9 100644 --- a/src/vs/workbench/common/editor/textEditorModel.ts +++ b/src/vs/workbench/common/editor/textEditorModel.ts @@ -18,7 +18,7 @@ import { withUndefinedAsNull } from 'vs/base/common/types'; */ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel, IModeSupport { - protected textEditorModelHandle: URI | null = null; + protected textEditorModelHandle: URI | undefined = undefined; private createdEditorModel: boolean | undefined; @@ -52,7 +52,7 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel private registerModelDisposeListener(model: ITextModel): void { this.modelDisposeListener.value = model.onWillDispose(() => { - this.textEditorModelHandle = null; // make sure we do not dispose code editor model again + this.textEditorModelHandle = undefined; // make sure we do not dispose code editor model again this.dispose(); }); } @@ -178,7 +178,7 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel this.modelService.destroyModel(this.textEditorModelHandle); } - this.textEditorModelHandle = null; + this.textEditorModelHandle = undefined; this.createdEditorModel = false; super.dispose(); diff --git a/src/vs/workbench/common/editor/textResourceEditorInput.ts b/src/vs/workbench/common/editor/textResourceEditorInput.ts index e55b55679..b738dca4c 100644 --- a/src/vs/workbench/common/editor/textResourceEditorInput.ts +++ b/src/vs/workbench/common/editor/textResourceEditorInput.ts @@ -199,14 +199,14 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput implem } // Normal save - return this.doSave(group, options, false); + return this.doSave(options, false); } saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { - return this.doSave(group, options, true); + return this.doSave(options, true); } - private async doSave(group: GroupIdentifier, options: ISaveOptions | undefined, saveAs: boolean): Promise { + private async doSave(options: ISaveOptions | undefined, saveAs: boolean): Promise { // Save / Save As let target: URI | undefined; @@ -220,8 +220,13 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput implem return undefined; // save cancelled } - // If the target is a different resource, return with a new editor input - if (!isEqual(target, this.preferredResource)) { + // If this save operation results in a new editor, either + // because it was saved to disk (e.g. from untitled) or + // through an explicit "Save As", make sure to replace it. + if ( + target.scheme !== this.resource.scheme || + (saveAs && !isEqual(target, this.preferredResource)) + ) { return this.editorService.createEditorInput({ resource: target }); } diff --git a/src/vs/workbench/common/panel.ts b/src/vs/workbench/common/panel.ts index 7b836be95..555e85dcf 100644 --- a/src/vs/workbench/common/panel.ts +++ b/src/vs/workbench/common/panel.ts @@ -9,5 +9,7 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export const ActivePanelContext = new RawContextKey('activePanel', ''); export const PanelFocusContext = new RawContextKey('panelFocus', false); export const PanelPositionContext = new RawContextKey('panelPosition', 'bottom'); +export const PanelVisibleContext = new RawContextKey('panelVisible', false); +export const PanelMaximizedContext = new RawContextKey('panelMaximized', false); export interface IPanel extends IComposite { } diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 53f29efbd..317cfd2fc 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -339,7 +339,7 @@ export const STATUS_BAR_FOREGROUND = registerColor('statusBar.foreground', { dark: '#FFFFFF', light: '#FFFFFF', hc: '#FFFFFF' -}, nls.localize('statusBarForeground', "Status bar foreground color when a workspace is opened. The status bar is shown in the bottom of the window.")); +}, nls.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, @@ -351,7 +351,7 @@ export const STATUS_BAR_BACKGROUND = registerColor('statusBar.background', { dark: '#007ACC', light: '#007ACC', hc: null -}, nls.localize('statusBarBackground', "Status bar background color when a workspace is opened. The status bar is shown in the bottom of the window.")); +}, nls.localize('statusBarBackground', "Status bar background 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_BACKGROUND = registerColor('statusBar.noFolderBackground', { dark: '#68217A', diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 0744cecf6..7bbafb802 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -27,9 +27,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { mixin } from 'vs/base/common/objects'; import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; - -export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test'; -export const testViewIcon = registerIcon('test-view-icon', Codicon.beaker, localize('testViewIcon', 'View icon of the test view.')); +import { CancellationToken } from 'vs/base/common/cancellation'; export const defaultViewIcon = registerIcon('default-view-icon', Codicon.window, localize('defaultViewIcon', 'Default view icon.')); @@ -38,11 +36,18 @@ export namespace Extensions { export const ViewsRegistry = 'workbench.registry.view'; } -export enum ViewContainerLocation { +export const enum ViewContainerLocation { Sidebar, Panel } +export function ViewContainerLocationToString(viewContainerLocation: ViewContainerLocation) { + switch (viewContainerLocation) { + case ViewContainerLocation.Sidebar: return 'sidebar'; + case ViewContainerLocation.Panel: return 'panel'; + } +} + export interface IViewContainerDescriptor { readonly id: string; @@ -256,6 +261,8 @@ export interface IAddedViewDescriptorState { export interface IViewContainerModel { + readonly viewContainer: ViewContainer; + readonly title: string; readonly icon: ThemeIcon | URI | undefined; readonly onDidChangeContainerInfo: Event<{ title?: boolean, icon?: boolean }>; @@ -505,7 +512,7 @@ export interface IViewsService { * View Contexts */ export const FocusedViewContext = new RawContextKey('focusedView', ''); -export function getVisbileViewContextKey(viewId: string): string { return `${viewId}.visible`; } +export function getVisbileViewContextKey(viewId: string): string { return `view.${viewId}.visible`; } export const IViewDescriptorService = createDecorator('viewDescriptorService'); @@ -684,21 +691,24 @@ export class ResolvableTreeItem implements ITreeItem { command?: Command; children?: ITreeItem[]; accessibilityInformation?: IAccessibilityInformation; - resolve: () => Promise; + resolve: (token: CancellationToken) => Promise; private resolved: boolean = false; private _hasResolve: boolean = false; - constructor(treeItem: ITreeItem, resolve?: (() => Promise)) { + constructor(treeItem: ITreeItem, resolve?: ((token: CancellationToken) => Promise)) { mixin(this, treeItem); this._hasResolve = !!resolve; - this.resolve = async () => { + this.resolve = async (token: CancellationToken) => { if (resolve && !this.resolved) { - const resolvedItem = await resolve(); + const resolvedItem = await resolve(token); if (resolvedItem) { - // Resolvable elements. Currently only tooltip. - this.tooltip = resolvedItem.tooltip; + // Resolvable elements. Currently tooltip and command. + this.tooltip = this.tooltip ?? resolvedItem.tooltip; + this.command = this.command ?? resolvedItem.command; } } - this.resolved = true; + if (!token.isCancellationRequested) { + this.resolved = true; + } }; } get hasResolve(): boolean { @@ -756,5 +766,6 @@ export interface IViewPaneContainer { getActionViewItem(action: IAction): IActionViewItem | undefined; getActionsContext(): unknown; getView(viewId: string): IView | undefined; + toggleViewVisibility(viewId: string): void; saveState(): void; } diff --git a/src/vs/workbench/contrib/backup/common/backupTracker.ts b/src/vs/workbench/contrib/backup/common/backupTracker.ts index d8f35fc9d..f9767e534 100644 --- a/src/vs/workbench/contrib/backup/common/backupTracker.ts +++ b/src/vs/workbench/contrib/backup/common/backupTracker.ts @@ -18,7 +18,7 @@ export abstract class BackupTracker extends Disposable { private readonly mapWorkingCopyToContentVersion = new Map(); // A map of scheduled pending backups for working copies - private readonly pendingBackups = new Map(); + protected readonly pendingBackups = new Map(); constructor( protected readonly backupFileService: IBackupFileService, @@ -43,7 +43,7 @@ export abstract class BackupTracker extends Disposable { this._register(this.workingCopyService.onDidChangeContent(workingCopy => this.onDidChangeContent(workingCopy))); // Lifecycle (handled in subclasses) - this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdown(event.reason))); + this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdown(event.reason), 'veto.backups')); } private onDidRegister(workingCopy: IWorkingCopy): void { diff --git a/src/vs/workbench/contrib/backup/electron-sandbox/backupTracker.ts b/src/vs/workbench/contrib/backup/electron-sandbox/backupTracker.ts index d2c4f92fb..c4a4e33a5 100644 --- a/src/vs/workbench/contrib/backup/electron-sandbox/backupTracker.ts +++ b/src/vs/workbench/contrib/backup/electron-sandbox/backupTracker.ts @@ -9,7 +9,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { ILifecycleService, LifecyclePhase, ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { ConfirmResult, IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { ConfirmResult, IFileDialogService, IDialogService, getFileNamesMessage } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { isMacintosh } from 'vs/base/common/platform'; @@ -20,7 +20,9 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { SaveReason } from 'vs/workbench/common/editor'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { raceCancellation } from 'vs/base/common/async'; export class NativeBackupTracker extends BackupTracker implements IWorkbenchContribution { @@ -47,7 +49,8 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont @INativeHostService private readonly nativeHostService: INativeHostService, @ILogService logService: ILogService, @IEditorService private readonly editorService: IEditorService, - @IEnvironmentService private readonly environmentService: IEnvironmentService + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IProgressService private readonly progressService: IProgressService ) { super(backupFileService, workingCopyService, logService, lifecycleService); } @@ -121,7 +124,7 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont // we ran a backup but received an error that we show to the user if (backupError) { - this.showErrorDialog(localize('backupTrackerBackupFailed', "One or more dirty editors could not be saved to the back up location."), backupError); + this.showErrorDialog(localize('backupTrackerBackupFailed', "The following dirty editors could not be saved to the back up location."), workingCopies, backupError); return true; // veto (the backup failed) } @@ -131,14 +134,21 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont try { return await this.confirmBeforeShutdown(workingCopies.filter(workingCopy => !backups.includes(workingCopy))); } catch (error) { - this.showErrorDialog(localize('backupTrackerConfirmFailed', "One or more dirty editors could not be saved or reverted."), error); + this.showErrorDialog(localize('backupTrackerConfirmFailed', "The following dirty editors could not be saved or reverted."), workingCopies, error); return true; // veto (save or revert failed) } } - private showErrorDialog(msg: string, error?: Error): void { - this.dialogService.show(Severity.Error, msg, [localize('ok', 'OK')], { detail: localize('backupErrorDetails', "Try saving or reverting the dirty editors first and then try again.") }); + private showErrorDialog(msg: string, workingCopies: readonly IWorkingCopy[], error?: Error): void { + const dirtyEditors = workingCopies.filter(workingCopy => workingCopy.isDirty()); + + const advice = localize('backupErrorDetails', "Try saving or reverting the dirty editors first and then try again."); + const detail = dirtyEditors.length + ? getFileNamesMessage(dirtyEditors.map(x => x.name)) + '\n' + advice + : advice; + + this.dialogService.show(Severity.Error, msg, [localize('ok', 'OK')], { detail }); this.logService.error(error ? `[backup tracker] ${msg}: ${error}` : `[backup tracker] ${msg}`); } @@ -234,9 +244,9 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont return true; // veto (user canceled) } - private async doSaveAllBeforeShutdown(workingCopies: IWorkingCopy[], reason: SaveReason): Promise; - private async doSaveAllBeforeShutdown(includeUntitled: boolean, reason: SaveReason): Promise; - private async doSaveAllBeforeShutdown(arg1: IWorkingCopy[] | boolean, reason: SaveReason): Promise { + private doSaveAllBeforeShutdown(workingCopies: IWorkingCopy[], reason: SaveReason): Promise; + private doSaveAllBeforeShutdown(includeUntitled: boolean, reason: SaveReason): Promise; + private doSaveAllBeforeShutdown(arg1: IWorkingCopy[] | boolean, reason: SaveReason): Promise { const workingCopies = Array.isArray(arg1) ? arg1 : this.workingCopyService.dirtyWorkingCopies.filter(workingCopy => { if (arg1 === false && (workingCopy.capabilities & WorkingCopyCapabilities.Untitled)) { return false; // skip untitled unless explicitly included @@ -245,21 +255,34 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont return true; }); - // Skip save participants on shutdown for performance reasons - const saveOptions = { skipSaveParticipants: true, reason }; + const cts = new CancellationTokenSource(); + return this.progressService.withProgress({ + location: ProgressLocation.Notification, + cancellable: true, // for https://github.com/microsoft/vscode/issues/112278 + delay: 800, // delay notification so that it only appears when saving takes a long time + title: localize('saveBeforeShutdown', "Waiting for dirty editors to save...") + }, () => { + const saveAllPromise = (async () => { - // First save through the editor service if we save all to benefit - // from some extras like switching to untitled dirty editors before saving. - let result: boolean | undefined = undefined; - if (typeof arg1 === 'boolean' || workingCopies.length === this.workingCopyService.dirtyCount) { - result = await this.editorService.saveAll({ includeUntitled: typeof arg1 === 'boolean' ? arg1 : true, ...saveOptions }); - } + // Skip save participants on shutdown for performance reasons + const saveOptions = { skipSaveParticipants: true, reason }; - // If we still have dirty working copies, save those directly - // unless the save was not successful (e.g. cancelled) - if (result !== false) { - await Promise.all(workingCopies.map(workingCopy => workingCopy.isDirty() ? workingCopy.save(saveOptions) : true)); - } + // First save through the editor service if we save all to benefit + // from some extras like switching to untitled dirty editors before saving. + let result: boolean | undefined = undefined; + if (typeof arg1 === 'boolean' || workingCopies.length === this.workingCopyService.dirtyCount) { + result = await this.editorService.saveAll({ includeUntitled: typeof arg1 === 'boolean' ? arg1 : true, ...saveOptions }); + } + + // If we still have dirty working copies, save those directly + // unless the save was not successful (e.g. cancelled) + if (result !== false) { + await Promise.all(workingCopies.map(workingCopy => workingCopy.isDirty() ? workingCopy.save(saveOptions) : true)); + } + })(); + + return raceCancellation(saveAllPromise, cts.token); + }, () => cts.dispose(true)); } private async doRevertAllBeforeShutdown(workingCopies: IWorkingCopy[]): Promise { diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts b/src/vs/workbench/contrib/backup/test/browser/backupRestorer.test.ts similarity index 56% rename from src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts rename to src/vs/workbench/contrib/backup/test/browser/backupRestorer.test.ts index dfe8325aa..1835bff59 100644 --- a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts +++ b/src/vs/workbench/contrib/backup/test/browser/backupRestorer.test.ts @@ -4,46 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as platform from 'vs/base/common/platform'; -import * as os from 'os'; -import * as path from 'vs/base/common/path'; -import * as pfs from 'vs/base/node/pfs'; +import { isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; -import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { DefaultEndOfLine } from 'vs/editor/common/model'; -import { hashPath } from 'vs/workbench/services/backup/electron-browser/backupFileService'; -import { NativeBackupTracker } from 'vs/workbench/contrib/backup/electron-sandbox/backupTracker'; -import { workbenchInstantiationService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; -import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { EditorInput } from 'vs/workbench/common/editor'; -import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; -import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; -import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textFileEditor'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { NodeTestBackupFileService } from 'vs/workbench/services/backup/test/electron-browser/backupFileService.test'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; -import { TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; +import { InMemoryTestBackupFileService, TestServiceAccessor, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { BackupRestorer } from 'vs/workbench/contrib/backup/common/backupRestorer'; - -const userdataDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backuprestorer'); -const backupHome = path.join(userdataDir, 'Backups'); -const workspacesJsonPath = path.join(backupHome, 'workspaces.json'); - -const workspaceResource = URI.file(platform.isWindows ? 'c:\\workspace' : '/workspace'); -const workspaceBackupPath = path.join(backupHome, hashPath(workspaceResource)); -const fooFile = URI.file(platform.isWindows ? 'c:\\Foo' : '/Foo'); -const barFile = URI.file(platform.isWindows ? 'c:\\Bar' : '/Bar'); -const untitledFile1 = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' }); -const untitledFile2 = URI.from({ scheme: Schemas.untitled, path: 'Untitled-2' }); +import { BrowserBackupTracker } from 'vs/workbench/contrib/backup/browser/backupTracker'; class TestBackupRestorer extends BackupRestorer { async doRestoreBackups(): Promise { @@ -54,38 +28,13 @@ class TestBackupRestorer extends BackupRestorer { suite('BackupRestorer', () => { let accessor: TestServiceAccessor; - let disposables: IDisposable[] = []; - - setup(async () => { - disposables.push(Registry.as(EditorExtensions.Editors).registerEditor( - EditorDescriptor.create( - TextFileEditor, - TextFileEditor.ID, - 'Text File Editor' - ), - [new SyncDescriptor(FileEditorInput)] - )); - - // Delete any existing backups completely and then re-create it. - await pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); - await pfs.mkdirp(backupHome); - - return pfs.writeFile(workspacesJsonPath, ''); - }); - - teardown(async () => { - dispose(disposables); - disposables = []; - - (accessor.textFileService.files).dispose(); - - return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); - }); + const fooFile = URI.file(isWindows ? 'c:\\Foo' : '/Foo'); + const barFile = URI.file(isWindows ? 'c:\\Bar' : '/Bar'); + const untitledFile1 = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' }); + const untitledFile2 = URI.from({ scheme: Schemas.untitled, path: 'Untitled-2' }); test('Restore backups', async function () { - this.timeout(20000); - - const backupFileService = new NodeTestBackupFileService(workspaceBackupPath); + const backupFileService = new InMemoryTestBackupFileService(); const instantiationService = workbenchInstantiationService(); instantiationService.stub(IBackupFileService, backupFileService); @@ -102,18 +51,18 @@ suite('BackupRestorer', () => { await part.whenRestored; - const tracker = instantiationService.createInstance(NativeBackupTracker); + const tracker = instantiationService.createInstance(BrowserBackupTracker); const restorer = instantiationService.createInstance(TestBackupRestorer); // Backup 2 normal files and 2 untitled file - await backupFileService.backup(untitledFile1, createTextBufferFactory('untitled-1').create(DefaultEndOfLine.LF).createSnapshot(false)); - await backupFileService.backup(untitledFile2, createTextBufferFactory('untitled-2').create(DefaultEndOfLine.LF).createSnapshot(false)); - await backupFileService.backup(fooFile, createTextBufferFactory('fooFile').create(DefaultEndOfLine.LF).createSnapshot(false)); - await backupFileService.backup(barFile, createTextBufferFactory('barFile').create(DefaultEndOfLine.LF).createSnapshot(false)); + await backupFileService.backup(untitledFile1, createTextBufferFactory('untitled-1').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); + await backupFileService.backup(untitledFile2, createTextBufferFactory('untitled-2').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); + await backupFileService.backup(fooFile, createTextBufferFactory('fooFile').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); + await backupFileService.backup(barFile, createTextBufferFactory('barFile').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); // Verify backups restored and opened as dirty await restorer.doRestoreBackups(); - assert.equal(editorService.count, 4); + assert.strictEqual(editorService.count, 4); assert.ok(editorService.editors.every(editor => editor.isDirty())); let counter = 0; @@ -152,7 +101,7 @@ suite('BackupRestorer', () => { } } - assert.equal(counter, 4); + assert.strictEqual(counter, 4); part.dispose(); tracker.dispose(); diff --git a/src/vs/workbench/contrib/backup/test/browser/backupTracker.test.ts b/src/vs/workbench/contrib/backup/test/browser/backupTracker.test.ts new file mode 100644 index 000000000..a75a319c7 --- /dev/null +++ b/src/vs/workbench/contrib/backup/test/browser/backupTracker.test.ts @@ -0,0 +1,155 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { URI } from 'vs/base/common/uri'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; +import { IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { toResource } from 'vs/base/test/common/utils'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IWorkingCopyBackup, IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; +import { InMemoryTestBackupFileService, TestServiceAccessor, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestWorkingCopy } from 'vs/workbench/test/common/workbenchTestServices'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { timeout } from 'vs/base/common/async'; +import { BrowserBackupTracker } from 'vs/workbench/contrib/backup/browser/backupTracker'; + +class TestBackupTracker extends BrowserBackupTracker { + + constructor( + @IBackupFileService backupFileService: IBackupFileService, + @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, + @IWorkingCopyService workingCopyService: IWorkingCopyService, + @ILifecycleService lifecycleService: ILifecycleService, + @ILogService logService: ILogService, + ) { + super(backupFileService, filesConfigurationService, workingCopyService, lifecycleService, logService); + } + + protected getBackupScheduleDelay(): number { + return 10; // Reduce timeout for tests + } +} + +suite('BackupTracker (browser)', function () { + let accessor: TestServiceAccessor; + + async function createTracker(): Promise<{ accessor: TestServiceAccessor, part: EditorPart, tracker: BackupTracker, backupFileService: InMemoryTestBackupFileService, instantiationService: IInstantiationService, cleanup: () => void }> { + const backupFileService = new InMemoryTestBackupFileService(); + const instantiationService = workbenchInstantiationService(); + instantiationService.stub(IBackupFileService, backupFileService); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + instantiationService.stub(IEditorGroupsService, part); + + const editorService: EditorService = instantiationService.createInstance(EditorService); + instantiationService.stub(IEditorService, editorService); + + accessor = instantiationService.createInstance(TestServiceAccessor); + + await part.whenRestored; + + const tracker = instantiationService.createInstance(TestBackupTracker); + + const cleanup = () => { + part.dispose(); + tracker.dispose(); + }; + + return { accessor, part, tracker, backupFileService, instantiationService, cleanup }; + } + + async function untitledBackupTest(untitled: IUntitledTextResourceEditorInput = {}): Promise { + const { accessor, cleanup, backupFileService } = await createTracker(); + + const untitledEditor = (await accessor.editorService.openEditor(untitled))?.input as UntitledTextEditorInput; + + const untitledModel = await untitledEditor.resolve(); + + if (!untitled?.contents) { + untitledModel.textEditorModel.setValue('Super Good'); + } + + await backupFileService.joinBackupResource(); + + assert.strictEqual(backupFileService.hasBackupSync(untitledEditor.resource), true); + + untitledModel.dispose(); + + await backupFileService.joinDiscardBackup(); + + assert.strictEqual(backupFileService.hasBackupSync(untitledEditor.resource), false); + + cleanup(); + } + + test('Track backups (untitled)', function () { + return untitledBackupTest(); + }); + + test('Track backups (untitled with initial contents)', function () { + return untitledBackupTest({ contents: 'Foo Bar' }); + }); + + test('Track backups (custom)', async function () { + const { accessor, cleanup, backupFileService } = await createTracker(); + + class TestBackupWorkingCopy extends TestWorkingCopy { + + backupDelay = 0; + + constructor(resource: URI) { + super(resource); + + accessor.workingCopyService.registerWorkingCopy(this); + } + + async backup(token: CancellationToken): Promise { + await timeout(this.backupDelay); + + return {}; + } + } + + const resource = toResource.call(this, '/path/custom.txt'); + const customWorkingCopy = new TestBackupWorkingCopy(resource); + + // Normal + customWorkingCopy.setDirty(true); + await backupFileService.joinBackupResource(); + assert.strictEqual(backupFileService.hasBackupSync(resource), true); + + customWorkingCopy.setDirty(false); + customWorkingCopy.setDirty(true); + await backupFileService.joinBackupResource(); + assert.strictEqual(backupFileService.hasBackupSync(resource), true); + + customWorkingCopy.setDirty(false); + await backupFileService.joinDiscardBackup(); + assert.strictEqual(backupFileService.hasBackupSync(resource), false); + + // Cancellation + customWorkingCopy.setDirty(true); + await timeout(0); + customWorkingCopy.setDirty(false); + await backupFileService.joinDiscardBackup(); + assert.strictEqual(backupFileService.hasBackupSync(resource), false); + + customWorkingCopy.dispose(); + await cleanup(); + }); +}); diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts index efb89fafb..010507834 100644 --- a/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts @@ -9,7 +9,7 @@ import * as os from 'os'; import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; -import { getRandomTestPath } from 'vs/base/test/node/testUtils'; +import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; import { hashPath } from 'vs/workbench/services/backup/electron-browser/backupFileService'; import { NativeBackupTracker } from 'vs/workbench/contrib/backup/electron-sandbox/backupTracker'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; @@ -18,7 +18,7 @@ import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; import { Registry } from 'vs/platform/registry/common/platform'; -import { EditorInput, IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor'; +import { EditorInput } from 'vs/workbench/common/editor'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; @@ -28,7 +28,7 @@ import { NodeTestBackupFileService } from 'vs/workbench/services/backup/test/ele import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { toResource } from 'vs/base/test/common/utils'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; -import { IWorkingCopyBackup, IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { ILogService } from 'vs/platform/log/common/log'; import { HotExitConfiguration } from 'vs/platform/files/common/files'; import { ShutdownReason, ILifecycleService, BeforeShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -38,24 +38,14 @@ import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker'; import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestFilesConfigurationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { TestWorkingCopy } from 'vs/workbench/test/common/workbenchTestServices'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { timeout } from 'vs/base/common/async'; import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; - -const userdataDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backuprestorer'); -const backupHome = path.join(userdataDir, 'Backups'); -const workspacesJsonPath = path.join(backupHome, 'workspaces.json'); - -const workspaceResource = URI.file(platform.isWindows ? 'c:\\workspace' : '/workspace'); -const workspaceBackupPath = path.join(backupHome, hashPath(workspaceResource)); +import { IProgressService } from 'vs/platform/progress/common/progress'; class TestBackupTracker extends NativeBackupTracker { @@ -70,14 +60,22 @@ class TestBackupTracker extends NativeBackupTracker { @INativeHostService nativeHostService: INativeHostService, @ILogService logService: ILogService, @IEditorService editorService: IEditorService, - @IEnvironmentService environmentService: IEnvironmentService + @IEnvironmentService environmentService: IEnvironmentService, + @IProgressService progressService: IProgressService ) { - super(backupFileService, filesConfigurationService, workingCopyService, lifecycleService, fileDialogService, dialogService, contextService, nativeHostService, logService, editorService, environmentService); + super(backupFileService, filesConfigurationService, workingCopyService, lifecycleService, fileDialogService, dialogService, contextService, nativeHostService, logService, editorService, environmentService, progressService); } protected getBackupScheduleDelay(): number { return 10; // Reduce timeout for tests } + + dispose() { + super.dispose(); + for (const [_, disposable] of this.pendingBackups) { + disposable.dispose(); + } + } } class BeforeShutdownEventImpl implements BeforeShutdownEvent { @@ -90,11 +88,22 @@ class BeforeShutdownEventImpl implements BeforeShutdownEvent { } } -suite('BackupTracker', () => { +flakySuite('BackupTracker (native)', function () { + let testDir: string; + let backupHome: string; + let workspaceBackupPath: string; + let accessor: TestServiceAccessor; let disposables: IDisposable[] = []; setup(async () => { + testDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backuprestorer'); + backupHome = path.join(testDir, 'Backups'); + const workspacesJsonPath = path.join(backupHome, 'workspaces.json'); + + const workspaceResource = URI.file(platform.isWindows ? 'c:\\workspace' : '/workspace'); + workspaceBackupPath = path.join(backupHome, hashPath(workspaceResource)); + const instantiationService = workbenchInstantiationService(); accessor = instantiationService.createInstance(TestServiceAccessor); @@ -107,8 +116,6 @@ suite('BackupTracker', () => { [new SyncDescriptor(FileEditorInput)] )); - // Delete any existing backups completely and then re-create it. - await pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); await pfs.mkdirp(backupHome); await pfs.mkdirp(workspaceBackupPath); @@ -121,11 +128,11 @@ suite('BackupTracker', () => { (accessor.textFileService.files).dispose(); - return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); + return pfs.rimraf(testDir); }); - async function createTracker(autoSaveEnabled = false): Promise<[TestServiceAccessor, EditorPart, BackupTracker, IInstantiationService]> { - const backupFileService = new NodeTestBackupFileService(workspaceBackupPath); + async function createTracker(autoSaveEnabled = false): Promise<{ accessor: TestServiceAccessor, part: EditorPart, tracker: BackupTracker, instantiationService: IInstantiationService, cleanup: () => Promise }> { + const backupFileService = new NodeTestBackupFileService(testDir, workspaceBackupPath); const instantiationService = workbenchInstantiationService(); instantiationService.stub(IBackupFileService, backupFileService); @@ -155,50 +162,19 @@ suite('BackupTracker', () => { const tracker = instantiationService.createInstance(TestBackupTracker); - return [accessor, part, tracker, instantiationService]; + const cleanup = async () => { + // File changes could also schedule some backup operations so we need to wait for them before finishing the test + await accessor.backupFileService.waitForAllBackups(); + + part.dispose(); + tracker.dispose(); + }; + + return { accessor, part, tracker, instantiationService, cleanup }; } - async function untitledBackupTest(untitled: IUntitledTextResourceEditorInput = {}): Promise { - const [accessor, part, tracker] = await createTracker(); - - const untitledEditor = (await accessor.editorService.openEditor(untitled))?.input as UntitledTextEditorInput; - - const untitledModel = await untitledEditor.resolve(); - - if (!untitled?.contents) { - untitledModel.textEditorModel.setValue('Super Good'); - } - - await accessor.backupFileService.joinBackupResource(); - - assert.equal(accessor.backupFileService.hasBackupSync(untitledEditor.resource), true); - - untitledModel.dispose(); - - await accessor.backupFileService.joinDiscardBackup(); - - assert.equal(accessor.backupFileService.hasBackupSync(untitledEditor.resource), false); - - part.dispose(); - tracker.dispose(); - } - - test('Track backups (untitled)', function () { - this.timeout(20000); - - return untitledBackupTest(); - }); - - test('Track backups (untitled with initial contents)', function () { - this.timeout(20000); - - return untitledBackupTest({ contents: 'Foo Bar' }); - }); - test('Track backups (file)', async function () { - this.timeout(20000); - - const [accessor, part, tracker] = await createTracker(); + const { accessor, cleanup } = await createTracker(); const resource = toResource.call(this, '/path/index.txt'); await accessor.editorService.openEditor({ resource, options: { pinned: true } }); @@ -208,69 +184,19 @@ suite('BackupTracker', () => { await accessor.backupFileService.joinBackupResource(); - assert.equal(accessor.backupFileService.hasBackupSync(resource), true); + assert.strictEqual(accessor.backupFileService.hasBackupSync(resource), true); fileModel?.dispose(); await accessor.backupFileService.joinDiscardBackup(); - assert.equal(accessor.backupFileService.hasBackupSync(resource), false); + assert.strictEqual(accessor.backupFileService.hasBackupSync(resource), false); - part.dispose(); - tracker.dispose(); - }); - - test('Track backups (custom)', async function () { - const [accessor, part, tracker] = await createTracker(); - - class TestBackupWorkingCopy extends TestWorkingCopy { - - backupDelay = 0; - - constructor(resource: URI) { - super(resource); - - accessor.workingCopyService.registerWorkingCopy(this); - } - - async backup(token: CancellationToken): Promise { - await timeout(this.backupDelay); - - return {}; - } - } - - const resource = toResource.call(this, '/path/custom.txt'); - const customWorkingCopy = new TestBackupWorkingCopy(resource); - - // Normal - customWorkingCopy.setDirty(true); - await accessor.backupFileService.joinBackupResource(); - assert.equal(accessor.backupFileService.hasBackupSync(resource), true); - - customWorkingCopy.setDirty(false); - customWorkingCopy.setDirty(true); - await accessor.backupFileService.joinBackupResource(); - assert.equal(accessor.backupFileService.hasBackupSync(resource), true); - - customWorkingCopy.setDirty(false); - await accessor.backupFileService.joinDiscardBackup(); - assert.equal(accessor.backupFileService.hasBackupSync(resource), false); - - // Cancellation - customWorkingCopy.setDirty(true); - await timeout(0); - customWorkingCopy.setDirty(false); - await accessor.backupFileService.joinDiscardBackup(); - assert.equal(accessor.backupFileService.hasBackupSync(resource), false); - - customWorkingCopy.dispose(); - part.dispose(); - tracker.dispose(); + await cleanup(); }); test('onWillShutdown - no veto if no dirty files', async function () { - const [accessor, part, tracker] = await createTracker(); + const { accessor, cleanup } = await createTracker(); const resource = toResource.call(this, '/path/index.txt'); await accessor.editorService.openEditor({ resource, options: { pinned: true } }); @@ -281,12 +207,11 @@ suite('BackupTracker', () => { const veto = await event.value; assert.ok(!veto); - part.dispose(); - tracker.dispose(); + await cleanup(); }); test('onWillShutdown - veto if user cancels (hot.exit: off)', async function () { - const [accessor, part, tracker] = await createTracker(); + const { accessor, cleanup } = await createTracker(); const resource = toResource.call(this, '/path/index.txt'); await accessor.editorService.openEditor({ resource, options: { pinned: true } }); @@ -298,7 +223,7 @@ suite('BackupTracker', () => { await model?.load(); model?.textEditorModel?.setValue('foo'); - assert.equal(accessor.workingCopyService.dirtyCount, 1); + assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); const event = new BeforeShutdownEventImpl(); accessor.lifecycleService.fireWillShutdown(event); @@ -306,12 +231,11 @@ suite('BackupTracker', () => { const veto = await event.value; assert.ok(veto); - part.dispose(); - tracker.dispose(); + await cleanup(); }); test('onWillShutdown - no veto if auto save is on', async function () { - const [accessor, part, tracker] = await createTracker(true /* auto save enabled */); + const { accessor, cleanup } = await createTracker(true /* auto save enabled */); const resource = toResource.call(this, '/path/index.txt'); await accessor.editorService.openEditor({ resource, options: { pinned: true } }); @@ -320,7 +244,7 @@ suite('BackupTracker', () => { await model?.load(); model?.textEditorModel?.setValue('foo'); - assert.equal(accessor.workingCopyService.dirtyCount, 1); + assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); const event = new BeforeShutdownEventImpl(); accessor.lifecycleService.fireWillShutdown(event); @@ -328,14 +252,13 @@ suite('BackupTracker', () => { const veto = await event.value; assert.ok(!veto); - assert.equal(accessor.workingCopyService.dirtyCount, 0); + assert.strictEqual(accessor.workingCopyService.dirtyCount, 0); - part.dispose(); - tracker.dispose(); + await cleanup(); }); test('onWillShutdown - no veto and backups cleaned up if user does not want to save (hot.exit: off)', async function () { - const [accessor, part, tracker] = await createTracker(); + const { accessor, cleanup } = await createTracker(); const resource = toResource.call(this, '/path/index.txt'); await accessor.editorService.openEditor({ resource, options: { pinned: true } }); @@ -347,7 +270,7 @@ suite('BackupTracker', () => { await model?.load(); model?.textEditorModel?.setValue('foo'); - assert.equal(accessor.workingCopyService.dirtyCount, 1); + assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); const event = new BeforeShutdownEventImpl(); accessor.lifecycleService.fireWillShutdown(event); @@ -355,12 +278,11 @@ suite('BackupTracker', () => { assert.ok(!veto); assert.ok(accessor.backupFileService.discardedBackups.length > 0); - part.dispose(); - tracker.dispose(); + await cleanup(); }); test('onWillShutdown - save (hot.exit: off)', async function () { - const [accessor, part, tracker] = await createTracker(); + const { accessor, cleanup } = await createTracker(); const resource = toResource.call(this, '/path/index.txt'); await accessor.editorService.openEditor({ resource, options: { pinned: true } }); @@ -372,7 +294,7 @@ suite('BackupTracker', () => { await model?.load(); model?.textEditorModel?.setValue('foo'); - assert.equal(accessor.workingCopyService.dirtyCount, 1); + assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); const event = new BeforeShutdownEventImpl(); accessor.lifecycleService.fireWillShutdown(event); @@ -380,8 +302,7 @@ suite('BackupTracker', () => { assert.ok(!veto); assert.ok(!model?.isDirty()); - part.dispose(); - tracker.dispose(); + await cleanup(); }); suite('Hot Exit', () => { @@ -488,7 +409,7 @@ suite('BackupTracker', () => { }); async function hotExitTest(this: any, setting: string, shutdownReason: ShutdownReason, multipleWindows: boolean, workspace: boolean, shouldVeto: boolean): Promise { - const [accessor, part, tracker] = await createTracker(); + const { accessor, cleanup } = await createTracker(); const resource = toResource.call(this, '/path/index.txt'); await accessor.editorService.openEditor({ resource, options: { pinned: true } }); @@ -513,18 +434,17 @@ suite('BackupTracker', () => { await model?.load(); model?.textEditorModel?.setValue('foo'); - assert.equal(accessor.workingCopyService.dirtyCount, 1); + assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); const event = new BeforeShutdownEventImpl(); event.reason = shutdownReason; accessor.lifecycleService.fireWillShutdown(event); const veto = await event.value; - assert.equal(accessor.backupFileService.discardedBackups.length, 0); // When hot exit is set, backups should never be cleaned since the confirm result is cancel - assert.equal(veto, shouldVeto); + assert.strictEqual(accessor.backupFileService.discardedBackups.length, 0); // When hot exit is set, backups should never be cleaned since the confirm result is cancel + assert.strictEqual(veto, shouldVeto); - part.dispose(); - tracker.dispose(); + await cleanup(); } }); }); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts index ffc54667e..889c25a91 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts @@ -19,6 +19,8 @@ import { BulkCellEdits, ResourceNotebookCellEdit } from 'vs/workbench/contrib/bu import { UndoRedoGroup, UndoRedoSource } from 'vs/platform/undoRedo/common/undoRedo'; import { LinkedList } from 'vs/base/common/linkedList'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; class BulkEdit { @@ -30,6 +32,7 @@ class BulkEdit { private readonly _edits: ResourceEdit[], private readonly _undoRedoGroup: UndoRedoGroup, private readonly _undoRedoSource: UndoRedoSource | undefined, + private readonly _confirmBeforeUndo: boolean, @IInstantiationService private readonly _instaService: IInstantiationService, @ILogService private readonly _logService: ILogService, ) { @@ -76,7 +79,7 @@ class BulkEdit { } const group = this._edits.slice(index, index + range); if (group[0] instanceof ResourceFileEdit) { - await this._performFileEdits(group, this._undoRedoGroup, this._undoRedoSource, progress); + await this._performFileEdits(group, this._undoRedoGroup, this._undoRedoSource, this._confirmBeforeUndo, progress); } else if (group[0] instanceof ResourceTextEdit) { await this._performTextEdits(group, this._undoRedoGroup, this._undoRedoSource, progress); } else if (group[0] instanceof ResourceNotebookCellEdit) { @@ -88,9 +91,9 @@ class BulkEdit { } } - private async _performFileEdits(edits: ResourceFileEdit[], undoRedoGroup: UndoRedoGroup, undoRedoSource: UndoRedoSource | undefined, progress: IProgress) { + private async _performFileEdits(edits: ResourceFileEdit[], undoRedoGroup: UndoRedoGroup, undoRedoSource: UndoRedoSource | undefined, confirmBeforeUndo: boolean, progress: IProgress) { this._logService.debug('_performFileEdits', JSON.stringify(edits)); - const model = this._instaService.createInstance(BulkFileEdits, this._label || localize('workspaceEdit', "Workspace Edit"), undoRedoGroup, undoRedoSource, progress, this._token, edits); + const model = this._instaService.createInstance(BulkFileEdits, this._label || localize('workspaceEdit', "Workspace Edit"), undoRedoGroup, undoRedoSource, confirmBeforeUndo, progress, this._token, edits); await model.apply(); } @@ -118,6 +121,8 @@ export class BulkEditService implements IBulkEditService { @IInstantiationService private readonly _instaService: IInstantiationService, @ILogService private readonly _logService: ILogService, @IEditorService private readonly _editorService: IEditorService, + @ILifecycleService private readonly _lifecycleService: ILifecycleService, + @IDialogService private readonly _dialogService: IDialogService ) { } setPreviewHandler(handler: IBulkEditPreviewHandler): IDisposable { @@ -139,7 +144,7 @@ export class BulkEditService implements IBulkEditService { return { ariaSummary: localize('nothing', "Made no edits") }; } - if (this._previewHandler && !options?.suppressPreview && (options?.showPreview || edits.some(value => value.metadata?.needsConfirmation))) { + if (this._previewHandler && (options?.showPreview || edits.some(value => value.metadata?.needsConfirmation))) { edits = await this._previewHandler(edits, options); } @@ -175,18 +180,22 @@ export class BulkEditService implements IBulkEditService { undoRedoGroupRemove = this._activeUndoRedoGroups.push(undoRedoGroup); } + const label = options?.quotableLabel || options?.label; const bulkEdit = this._instaService.createInstance( BulkEdit, - options?.quotableLabel || options?.label, + label, codeEditor, options?.progress ?? Progress.None, options?.token ?? CancellationToken.None, edits, undoRedoGroup, - options?.undoRedoSource + options?.undoRedoSource, + !!options?.confirmBeforeUndo ); + let listener: IDisposable | undefined; try { + listener = this._lifecycleService.onBeforeShutdown(e => e.veto(this.shouldVeto(label), 'veto.blukEditService')); await bulkEdit.perform(); return { ariaSummary: bulkEdit.ariaMessage() }; } catch (err) { @@ -195,9 +204,20 @@ export class BulkEditService implements IBulkEditService { this._logService.error(err); throw err; } finally { + listener?.dispose(); undoRedoGroupRemove(); } } + + private async shouldVeto(label: string | undefined): Promise { + label = label || localize('fileOperation', "File operation"); + const result = await this._dialogService.confirm({ + message: localize('areYouSureQuiteBulkEdit', "Are you sure you want to quit? '{0}' is in progress.", label), + primaryButton: localize('quit', "Quit") + }); + + return !result.confirmed; + } } registerSingleton(IBulkEditService, BulkEditService, true); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts index f4ac30ca0..e15f322a5 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts @@ -8,20 +8,16 @@ import { WorkspaceFileEditOptions } from 'vs/editor/common/modes'; import { IFileService, FileSystemProviderCapabilities, IFileContent } from 'vs/platform/files/common/files'; import { IProgress } from 'vs/platform/progress/common/progress'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { IWorkingCopyFileService, IFileOperationUndoRedoInfo, IMoveOperation, ICopyOperation, IDeleteOperation, ICreateOperation, ICreateFileOperation } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IWorkspaceUndoRedoElement, UndoRedoElementType, IUndoRedoService, UndoRedoGroup, UndoRedoSource } from 'vs/platform/undoRedo/common/undoRedo'; import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { VSBuffer } from 'vs/base/common/buffer'; import { ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService'; -import * as resources from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; - -interface IFileOperationUndoRedoInfo { - undoRedoGroupId?: number; - isUndoing?: boolean; -} +import { flatten, tail } from 'vs/base/common/arrays'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; interface IFileOperation { uris: URI[]; @@ -36,115 +32,189 @@ class Noop implements IFileOperation { } } -class RenameOperation implements IFileOperation { - +class RenameEdit { + readonly type = 'rename'; constructor( readonly newUri: URI, readonly oldUri: URI, - readonly options: WorkspaceFileEditOptions, - readonly undoRedoInfo: IFileOperationUndoRedoInfo, + readonly options: WorkspaceFileEditOptions + ) { } +} + +class RenameOperation implements IFileOperation { + + constructor( + private readonly _edits: RenameEdit[], + private readonly _undoRedoInfo: IFileOperationUndoRedoInfo, @IWorkingCopyFileService private readonly _workingCopyFileService: IWorkingCopyFileService, @IFileService private readonly _fileService: IFileService, ) { } get uris() { - return [this.newUri, this.oldUri]; + return flatten(this._edits.map(edit => [edit.newUri, edit.oldUri])); } async perform(token: CancellationToken): Promise { - // rename - if (this.options.overwrite === undefined && this.options.ignoreIfExists && await this._fileService.exists(this.newUri)) { - return new Noop(); // not overwriting, but ignoring, and the target file exists + + const moves: IMoveOperation[] = []; + const undoes: RenameEdit[] = []; + for (const edit of this._edits) { + // check: not overwriting, but ignoring, and the target file exists + const skip = edit.options.overwrite === undefined && edit.options.ignoreIfExists && await this._fileService.exists(edit.newUri); + if (!skip) { + moves.push({ + file: { source: edit.oldUri, target: edit.newUri }, + overwrite: edit.options.overwrite + }); + + // reverse edit + undoes.push(new RenameEdit(edit.oldUri, edit.newUri, edit.options)); + } } - await this._workingCopyFileService.move([{ source: this.oldUri, target: this.newUri }], { overwrite: this.options.overwrite, ...this.undoRedoInfo }, token); - return new RenameOperation(this.oldUri, this.newUri, this.options, { isUndoing: true }, this._workingCopyFileService, this._fileService); + if (moves.length === 0) { + return new Noop(); + } + + await this._workingCopyFileService.move(moves, this._undoRedoInfo, token); + return new RenameOperation(undoes, { isUndoing: true }, this._workingCopyFileService, this._fileService); } toString(): string { - const oldBasename = resources.basename(this.oldUri); - const newBasename = resources.basename(this.newUri); - if (oldBasename !== newBasename) { - return `(rename ${oldBasename} to ${newBasename})`; - } - return `(rename ${this.oldUri} to ${this.newUri})`; + return `(rename ${this._edits.map(edit => `${edit.oldUri} to ${edit.newUri}`).join(', ')})`; } } +class CopyEdit { + readonly type = 'copy'; + constructor( + readonly newUri: URI, + readonly oldUri: URI, + readonly options: WorkspaceFileEditOptions + ) { } +} + class CopyOperation implements IFileOperation { constructor( - readonly newUri: URI, - readonly oldUri: URI, - readonly options: WorkspaceFileEditOptions, - readonly undoRedoInfo: IFileOperationUndoRedoInfo, + private readonly _edits: CopyEdit[], + private readonly _undoRedoInfo: IFileOperationUndoRedoInfo, @IWorkingCopyFileService private readonly _workingCopyFileService: IWorkingCopyFileService, @IFileService private readonly _fileService: IFileService, @IInstantiationService private readonly _instaService: IInstantiationService ) { } get uris() { - return [this.newUri, this.oldUri]; + return flatten(this._edits.map(edit => [edit.newUri, edit.oldUri])); } async perform(token: CancellationToken): Promise { - // copy - if (this.options.overwrite === undefined && this.options.ignoreIfExists && await this._fileService.exists(this.newUri)) { - return new Noop(); // not overwriting, but ignoring, and the target file exists + + // (1) create copy operations, remove noops + const copies: ICopyOperation[] = []; + for (const edit of this._edits) { + //check: not overwriting, but ignoring, and the target file exists + const skip = edit.options.overwrite === undefined && edit.options.ignoreIfExists && await this._fileService.exists(edit.newUri); + if (!skip) { + copies.push({ file: { source: edit.oldUri, target: edit.newUri }, overwrite: edit.options.overwrite }); + } } - const stat = await this._workingCopyFileService.copy([{ source: this.oldUri, target: this.newUri }], { overwrite: this.options.overwrite, ...this.undoRedoInfo }, token); - const folder = this.options.folder || (stat.length === 1 && stat[0].isDirectory); - return this._instaService.createInstance(DeleteOperation, this.newUri, { recursive: true, folder, ...this.options }, { isUndoing: true }, false); + if (copies.length === 0) { + return new Noop(); + } + + // (2) perform the actual copy and use the return stats to build undo edits + const stats = await this._workingCopyFileService.copy(copies, this._undoRedoInfo, token); + const undoes: DeleteEdit[] = []; + + for (let i = 0; i < stats.length; i++) { + const stat = stats[i]; + const edit = this._edits[i]; + undoes.push(new DeleteEdit(stat.resource, { recursive: true, folder: this._edits[i].options.folder || stat.isDirectory, ...edit.options }, false)); + } + + return this._instaService.createInstance(DeleteOperation, undoes, { isUndoing: true }); } toString(): string { - return `(copy ${this.oldUri} to ${this.newUri})`; + return `(copy ${this._edits.map(edit => `${edit.oldUri} to ${edit.newUri}`).join(', ')})`; } } +class CreateEdit { + readonly type = 'create'; + constructor( + readonly newUri: URI, + readonly options: WorkspaceFileEditOptions, + readonly contents: VSBuffer | undefined, + ) { } +} + class CreateOperation implements IFileOperation { constructor( - readonly newUri: URI, - readonly options: WorkspaceFileEditOptions, - readonly undoRedoInfo: IFileOperationUndoRedoInfo, - readonly contents: VSBuffer | undefined, + private readonly _edits: CreateEdit[], + private readonly _undoRedoInfo: IFileOperationUndoRedoInfo, @IFileService private readonly _fileService: IFileService, @IWorkingCopyFileService private readonly _workingCopyFileService: IWorkingCopyFileService, @IInstantiationService private readonly _instaService: IInstantiationService, + @ITextFileService private readonly _textFileService: ITextFileService ) { } get uris() { - return [this.newUri]; + return this._edits.map(edit => edit.newUri); } async perform(token: CancellationToken): Promise { - // create file - if (this.options.overwrite === undefined && this.options.ignoreIfExists && await this._fileService.exists(this.newUri)) { - return new Noop(); // not overwriting, but ignoring, and the target file exists + + const folderCreates: ICreateOperation[] = []; + const fileCreates: ICreateFileOperation[] = []; + const undoes: DeleteEdit[] = []; + + for (const edit of this._edits) { + if (edit.options.overwrite === undefined && edit.options.ignoreIfExists && await this._fileService.exists(edit.newUri)) { + continue; // not overwriting, but ignoring, and the target file exists + } + if (edit.options.folder) { + folderCreates.push({ resource: edit.newUri }); + } else { + // If the contents are part of the edit they include the encoding, thus use them. Otherwise get the encoding for a new empty file. + const encodedReadable = typeof edit.contents !== 'undefined' ? edit.contents : await this._textFileService.getEncodedReadable(edit.newUri); + fileCreates.push({ resource: edit.newUri, contents: encodedReadable, overwrite: edit.options.overwrite }); + } + undoes.push(new DeleteEdit(edit.newUri, edit.options, !edit.options.folder && !edit.contents)); } - if (this.options.folder) { - await this._workingCopyFileService.createFolder(this.newUri, { ...this.undoRedoInfo }, token); - } else { - await this._workingCopyFileService.create(this.newUri, this.contents, { overwrite: this.options.overwrite, ...this.undoRedoInfo }, token); + + if (folderCreates.length === 0 && fileCreates.length === 0) { + return new Noop(); } - return this._instaService.createInstance(DeleteOperation, this.newUri, this.options, { isUndoing: true }, !this.options.folder && !this.contents); + + await this._workingCopyFileService.createFolder(folderCreates, this._undoRedoInfo, token); + await this._workingCopyFileService.create(fileCreates, this._undoRedoInfo, token); + + return this._instaService.createInstance(DeleteOperation, undoes, { isUndoing: true }); } toString(): string { - return this.options.folder ? `create ${resources.basename(this.newUri)} folder` - : `(create ${resources.basename(this.newUri)} with ${this.contents?.byteLength || 0} bytes)`; + return `(create ${this._edits.map(edit => edit.options.folder ? `folder ${edit.newUri}` : `file ${edit.newUri} with ${edit.contents?.byteLength || 0} bytes`).join(', ')})`; } } +class DeleteEdit { + readonly type = 'delete'; + constructor( + readonly oldUri: URI, + readonly options: WorkspaceFileEditOptions, + readonly undoesCreate: boolean, + ) { } +} + class DeleteOperation implements IFileOperation { constructor( - readonly oldUri: URI, - readonly options: WorkspaceFileEditOptions, - readonly undoRedoInfo: IFileOperationUndoRedoInfo, - private readonly _undoesCreateOperation: boolean, + private _edits: DeleteEdit[], + private readonly _undoRedoInfo: IFileOperationUndoRedoInfo, @IWorkingCopyFileService private readonly _workingCopyFileService: IWorkingCopyFileService, @IFileService private readonly _fileService: IFileService, @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -153,38 +223,58 @@ class DeleteOperation implements IFileOperation { ) { } get uris() { - return [this.oldUri]; + return this._edits.map(edit => edit.oldUri); } async perform(token: CancellationToken): Promise { // delete file - if (!await this._fileService.exists(this.oldUri)) { - if (!this.options.ignoreIfNotExists) { - throw new Error(`${this.oldUri} does not exist and can not be deleted`); - } - return new Noop(); - } - let fileContent: IFileContent | undefined; - if (!this._undoesCreateOperation && !this.options.folder) { - try { - fileContent = await this._fileService.readFile(this.oldUri); - } catch (err) { - this._logService.critical(err); + const deletes: IDeleteOperation[] = []; + const undoes: CreateEdit[] = []; + + for (const edit of this._edits) { + if (!await this._fileService.exists(edit.oldUri)) { + if (!edit.options.ignoreIfNotExists) { + throw new Error(`${edit.oldUri} does not exist and can not be deleted`); + } + continue; + } + + deletes.push({ + resource: edit.oldUri, + recursive: edit.options.recursive, + useTrash: !edit.options.skipTrashBin && this._fileService.hasCapability(edit.oldUri, FileSystemProviderCapabilities.Trash) && this._configurationService.getValue('files.enableTrash') + }); + + + // read file contents for undo operation. when a file is too large it won't be restored + let fileContent: IFileContent | undefined; + if (!edit.undoesCreate && !edit.options.folder) { + try { + fileContent = await this._fileService.readFile(edit.oldUri); + } catch (err) { + this._logService.critical(err); + } + } + if (!(typeof edit.options.maxSize === 'number' && fileContent && (fileContent?.size > edit.options.maxSize))) { + undoes.push(new CreateEdit(edit.oldUri, edit.options, fileContent?.value)); } } - const useTrash = !this.options.skipTrashBin && this._fileService.hasCapability(this.oldUri, FileSystemProviderCapabilities.Trash) && this._configurationService.getValue('files.enableTrash'); - await this._workingCopyFileService.delete([this.oldUri], { useTrash, recursive: this.options.recursive, ...this.undoRedoInfo }, token); - - if (typeof this.options.maxSize === 'number' && fileContent && (fileContent?.size > this.options.maxSize)) { + if (deletes.length === 0) { return new Noop(); } - return this._instaService.createInstance(CreateOperation, this.oldUri, this.options, { isUndoing: true }, fileContent?.value); + + await this._workingCopyFileService.delete(deletes, this._undoRedoInfo, token); + + if (undoes.length === 0) { + return new Noop(); + } + return this._instaService.createInstance(CreateOperation, undoes, { isUndoing: true }); } toString(): string { - return `(delete ${resources.basename(this.oldUri)})`; + return `(delete ${this._edits.map(edit => edit.oldUri).join(', ')})`; } } @@ -196,7 +286,8 @@ class FileUndoRedoElement implements IWorkspaceUndoRedoElement { constructor( readonly label: string, - readonly operations: IFileOperation[] + readonly operations: IFileOperation[], + readonly confirmBeforeUndo: boolean ) { this.resources = ([]).concat(...operations.map(op => op.uris)); } @@ -217,7 +308,7 @@ class FileUndoRedoElement implements IWorkspaceUndoRedoElement { } } - public toString(): string { + toString(): string { return this.operations.map(op => String(op)).join(', '); } } @@ -228,6 +319,7 @@ export class BulkFileEdits { private readonly _label: string, private readonly _undoRedoGroup: UndoRedoGroup, private readonly _undoRedoSource: UndoRedoSource | undefined, + private readonly _confirmBeforeUndo: boolean, private readonly _progress: IProgress, private readonly _token: CancellationToken, private readonly _edits: ResourceFileEdit[], @@ -238,34 +330,66 @@ export class BulkFileEdits { async apply(): Promise { const undoOperations: IFileOperation[] = []; const undoRedoInfo = { undoRedoGroupId: this._undoRedoGroup.id }; + + const edits: Array = []; for (const edit of this._edits) { + if (edit.newResource && edit.oldResource && !edit.options?.copy) { + edits.push(new RenameEdit(edit.newResource, edit.oldResource, edit.options ?? {})); + } else if (edit.newResource && edit.oldResource && edit.options?.copy) { + edits.push(new CopyEdit(edit.newResource, edit.oldResource, edit.options ?? {})); + } else if (!edit.newResource && edit.oldResource) { + edits.push(new DeleteEdit(edit.oldResource, edit.options ?? {}, false)); + } else if (edit.newResource && !edit.oldResource) { + edits.push(new CreateEdit(edit.newResource, edit.options ?? {}, undefined)); + } + } + + if (edits.length === 0) { + return; + } + + const groups: Array[] = []; + groups[0] = [edits[0]]; + + for (let i = 1; i < edits.length; i++) { + const edit = edits[i]; + const lastGroup = tail(groups); + if (lastGroup[0].type === edit.type) { + lastGroup.push(edit); + } else { + groups.push([edit]); + } + } + + for (let group of groups) { if (this._token.isCancellationRequested) { break; } - const options = edit.options || {}; let op: IFileOperation | undefined; - if (edit.newResource && edit.oldResource && !options.copy) { - // rename - op = this._instaService.createInstance(RenameOperation, edit.newResource, edit.oldResource, options, undoRedoInfo); - } else if (edit.newResource && edit.oldResource && options.copy) { - op = this._instaService.createInstance(CopyOperation, edit.newResource, edit.oldResource, options, undoRedoInfo); - } else if (!edit.newResource && edit.oldResource) { - // delete file - op = this._instaService.createInstance(DeleteOperation, edit.oldResource, options, undoRedoInfo, false); - } else if (edit.newResource && !edit.oldResource) { - // create file - op = this._instaService.createInstance(CreateOperation, edit.newResource, options, undoRedoInfo, undefined); + switch (group[0].type) { + case 'rename': + op = this._instaService.createInstance(RenameOperation, group, undoRedoInfo); + break; + case 'copy': + op = this._instaService.createInstance(CopyOperation, group, undoRedoInfo); + break; + case 'delete': + op = this._instaService.createInstance(DeleteOperation, group, undoRedoInfo); + break; + case 'create': + op = this._instaService.createInstance(CreateOperation, group, undoRedoInfo); + break; } + if (op) { const undoOp = await op.perform(this._token); undoOperations.push(undoOp); } - this._progress.report(undefined); } - this._undoRedoService.pushElement(new FileUndoRedoElement(this._label, undoOperations), this._undoRedoGroup, this._undoRedoSource); + this._undoRedoService.pushElement(new FileUndoRedoElement(this._label, undoOperations, this._confirmBeforeUndo), this._undoRedoGroup, this._undoRedoSource); } } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index ab0172eec..3b35a67b7 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -17,7 +17,7 @@ import { BulkEditPreviewProvider, BulkFileOperations, BulkFileOperationType } fr import { ILabelService } from 'vs/platform/label/common/label'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { URI } from 'vs/base/common/uri'; -import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +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 { IConfigurationService } from 'vs/platform/configuration/common/configuration'; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts index e9dddb2a2..7d1348b25 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts @@ -409,14 +409,14 @@ export class CategoryElementRenderer implements ITreeRenderer('accessibilityHelpWidgetVisible', false); -class AccessibilityHelpController extends Disposable implements IEditorContribution { +export class AccessibilityHelpController extends Disposable implements IEditorContribution { public static readonly ID = 'editor.contrib.accessibilityHelpController'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts index c3ede411a..0f7a8480b 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -8,10 +8,10 @@ import { IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { registerDiffEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IDiffEditorContribution } from 'vs/editor/common/editorCommon'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets'; +import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; import { IDiffComputationResult } from 'vs/editor/common/services/editorWorkerService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; const enum WidgetState { @@ -48,7 +48,7 @@ class DiffEditorHelperContribution extends Disposable implements IDiffEditorCont Severity.Warning, nls.localize('hintTimeout', "The diff algorithm was stopped early (after {0} ms.)", this._diffEditor.maxComputationTime), [{ - label: nls.localize('removeTimeout', "Remove limit"), + label: nls.localize('removeTimeout', "Remove Limit"), run: () => { this._configurationService.updateValue('diffEditor.maxComputationTime', 0); } @@ -94,7 +94,7 @@ class DiffEditorHelperContribution extends Disposable implements IDiffEditorCont private _onDidClickHelperWidget(): void { if (this._state === WidgetState.HintWhitespace) { - this._configurationService.updateValue('diffEditor.ignoreTrimWhitespace', false, ConfigurationTarget.USER); + this._configurationService.updateValue('diffEditor.ignoreTrimWhitespace', false); } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts b/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts index 69ee09504..657af6f1b 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts @@ -9,7 +9,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as types from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { LanguageIdentifier } from 'vs/editor/common/modes'; -import { CharacterPair, CommentRule, FoldingRules, IAutoClosingPair, IAutoClosingPairConditional, IndentationRule, LanguageConfiguration } from 'vs/editor/common/modes/languageConfiguration'; +import { CharacterPair, CommentRule, EnterAction, FoldingRules, IAutoClosingPair, IAutoClosingPairConditional, IndentAction, IndentationRule, LanguageConfiguration, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { IModeService } from 'vs/editor/common/services/modeService'; import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; @@ -31,6 +31,19 @@ interface IIndentationRules { unIndentedLinePattern?: string | IRegExp; } +interface IEnterAction { + indent: 'none' | 'indent' | 'indentOutdent' | 'outdent'; + appendText?: string; + removeText?: number; +} + +interface IOnEnterRule { + beforeText: string | IRegExp; + afterText?: string | IRegExp; + previousLineText?: string | IRegExp; + action: IEnterAction; +} + interface ILanguageConfiguration { comments?: CommentRule; brackets?: CharacterPair[]; @@ -40,6 +53,7 @@ interface ILanguageConfiguration { indentationRules?: IIndentationRules; folding?: FoldingRules; autoCloseBefore?: string; + onEnterRules?: IOnEnterRule[]; } function isStringArr(something: string[] | null): something is string[] { @@ -93,7 +107,7 @@ export class LanguageConfigurationFileHandler { } this._done[languageIdentifier.id] = true; - let configurationFiles = this._modeService.getConfigurationFiles(languageIdentifier.language); + const configurationFiles = this._modeService.getConfigurationFiles(languageIdentifier.language); configurationFiles.forEach((configFileLocation) => this._handleConfigFile(languageIdentifier, configFileLocation)); } @@ -254,18 +268,82 @@ export class LanguageConfigurationFileHandler { return result; } - // private _mapCharacterPairs(pairs: Array): IAutoClosingPairConditional[] { - // return pairs.map(pair => { - // if (Array.isArray(pair)) { - // return { open: pair[0], close: pair[1] }; - // } - // return pair; - // }); - // } + private _extractValidOnEnterRules(languageIdentifier: LanguageIdentifier, configuration: ILanguageConfiguration): OnEnterRule[] | null { + const source = configuration.onEnterRules; + if (typeof source === 'undefined') { + return null; + } + if (!Array.isArray(source)) { + console.warn(`[${languageIdentifier.language}]: language configuration: expected \`onEnterRules\` to be an array.`); + return null; + } + + let result: OnEnterRule[] | null = null; + for (let i = 0, len = source.length; i < len; i++) { + const onEnterRule = source[i]; + if (!types.isObject(onEnterRule)) { + console.warn(`[${languageIdentifier.language}]: language configuration: expected \`onEnterRules[${i}]\` to be an object.`); + continue; + } + if (!types.isObject(onEnterRule.action)) { + console.warn(`[${languageIdentifier.language}]: language configuration: expected \`onEnterRules[${i}].action\` to be an object.`); + continue; + } + let indentAction: IndentAction; + if (onEnterRule.action.indent === 'none') { + indentAction = IndentAction.None; + } else if (onEnterRule.action.indent === 'indent') { + indentAction = IndentAction.Indent; + } else if (onEnterRule.action.indent === 'indentOutdent') { + indentAction = IndentAction.IndentOutdent; + } else if (onEnterRule.action.indent === 'outdent') { + indentAction = IndentAction.Outdent; + } else { + console.warn(`[${languageIdentifier.language}]: language configuration: expected \`onEnterRules[${i}].action.indent\` to be 'none', 'indent', 'indentOutdent' or 'outdent'.`); + continue; + } + const action: EnterAction = { indentAction }; + if (onEnterRule.action.appendText) { + if (typeof onEnterRule.action.appendText === 'string') { + action.appendText = onEnterRule.action.appendText; + } else { + console.warn(`[${languageIdentifier.language}]: language configuration: expected \`onEnterRules[${i}].action.appendText\` to be undefined or a string.`); + } + } + if (onEnterRule.action.removeText) { + if (typeof onEnterRule.action.removeText === 'number') { + action.removeText = onEnterRule.action.removeText; + } else { + console.warn(`[${languageIdentifier.language}]: language configuration: expected \`onEnterRules[${i}].action.removeText\` to be undefined or a number.`); + } + } + const beforeText = this._parseRegex(languageIdentifier, `onEnterRules[${i}].beforeText`, onEnterRule.beforeText); + if (!beforeText) { + continue; + } + const resultingOnEnterRule: OnEnterRule = { beforeText, action }; + if (onEnterRule.afterText) { + const afterText = this._parseRegex(languageIdentifier, `onEnterRules[${i}].afterText`, onEnterRule.afterText); + if (afterText) { + resultingOnEnterRule.afterText = afterText; + } + } + if (onEnterRule.previousLineText) { + const previousLineText = this._parseRegex(languageIdentifier, `onEnterRules[${i}].previousLineText`, onEnterRule.previousLineText); + if (previousLineText) { + resultingOnEnterRule.previousLineText = previousLineText; + } + } + result = result || []; + result.push(resultingOnEnterRule); + } + + return result; + } private _handleConfig(languageIdentifier: LanguageIdentifier, configuration: ILanguageConfiguration): void { - let richEditConfig: LanguageConfiguration = {}; + const richEditConfig: LanguageConfiguration = {}; const comments = this._extractValidCommentRule(languageIdentifier, configuration); if (comments) { @@ -293,25 +371,21 @@ export class LanguageConfigurationFileHandler { } if (configuration.wordPattern) { - try { - let wordPattern = this._parseRegex(configuration.wordPattern); - if (wordPattern) { - richEditConfig.wordPattern = wordPattern; - } - } catch (error) { - // Malformed regexes are ignored + const wordPattern = this._parseRegex(languageIdentifier, `wordPattern`, configuration.wordPattern); + if (wordPattern) { + richEditConfig.wordPattern = wordPattern; } } if (configuration.indentationRules) { - let indentationRules = this._mapIndentationRules(configuration.indentationRules); + const indentationRules = this._mapIndentationRules(languageIdentifier, configuration.indentationRules); if (indentationRules) { richEditConfig.indentationRules = indentationRules; } } if (configuration.folding) { - let markers = configuration.folding.markers; + const markers = configuration.folding.markers; richEditConfig.folding = { offSide: configuration.folding.offSide, @@ -319,44 +393,66 @@ export class LanguageConfigurationFileHandler { }; } + const onEnterRules = this._extractValidOnEnterRules(languageIdentifier, configuration); + if (onEnterRules) { + richEditConfig.onEnterRules = onEnterRules; + } + LanguageConfigurationRegistry.register(languageIdentifier, richEditConfig); } - private _parseRegex(value: string | IRegExp) { + private _parseRegex(languageIdentifier: LanguageIdentifier, confPath: string, value: string | IRegExp) { if (typeof value === 'string') { - return new RegExp(value, ''); - } else if (typeof value === 'object') { - return new RegExp(value.pattern, value.flags); + try { + return new RegExp(value, ''); + } catch (err) { + console.warn(`[${languageIdentifier.language}]: Invalid regular expression in \`${confPath}\`: `, err); + return null; + } } - + if (types.isObject(value)) { + if (typeof value.pattern !== 'string') { + console.warn(`[${languageIdentifier.language}]: language configuration: expected \`${confPath}.pattern\` to be a string.`); + return null; + } + if (typeof value.flags !== 'undefined' && typeof value.flags !== 'string') { + console.warn(`[${languageIdentifier.language}]: language configuration: expected \`${confPath}.flags\` to be a string.`); + return null; + } + try { + return new RegExp(value.pattern, value.flags); + } catch (err) { + console.warn(`[${languageIdentifier.language}]: Invalid regular expression in \`${confPath}\`: `, err); + return null; + } + } + console.warn(`[${languageIdentifier.language}]: language configuration: expected \`${confPath}\` to be a string or an object.`); return null; } - private _mapIndentationRules(indentationRules: IIndentationRules): IndentationRule | null { - try { - let increaseIndentPattern = this._parseRegex(indentationRules.increaseIndentPattern); - let decreaseIndentPattern = this._parseRegex(indentationRules.decreaseIndentPattern); - - if (increaseIndentPattern && decreaseIndentPattern) { - let result: IndentationRule = { - increaseIndentPattern: increaseIndentPattern, - decreaseIndentPattern: decreaseIndentPattern - }; - - if (indentationRules.indentNextLinePattern) { - result.indentNextLinePattern = this._parseRegex(indentationRules.indentNextLinePattern); - } - if (indentationRules.unIndentedLinePattern) { - result.unIndentedLinePattern = this._parseRegex(indentationRules.unIndentedLinePattern); - } - - return result; - } - } catch (error) { - // Malformed regexes are ignored + private _mapIndentationRules(languageIdentifier: LanguageIdentifier, indentationRules: IIndentationRules): IndentationRule | null { + const increaseIndentPattern = this._parseRegex(languageIdentifier, `indentationRules.increaseIndentPattern`, indentationRules.increaseIndentPattern); + if (!increaseIndentPattern) { + return null; + } + const decreaseIndentPattern = this._parseRegex(languageIdentifier, `indentationRules.decreaseIndentPattern`, indentationRules.decreaseIndentPattern); + if (!decreaseIndentPattern) { + return null; } - return null; + const result: IndentationRule = { + increaseIndentPattern: increaseIndentPattern, + decreaseIndentPattern: decreaseIndentPattern + }; + + if (indentationRules.indentNextLinePattern) { + result.indentNextLinePattern = this._parseRegex(languageIdentifier, `indentationRules.indentNextLinePattern`, indentationRules.indentNextLinePattern); + } + if (indentationRules.unIndentedLinePattern) { + result.unIndentedLinePattern = this._parseRegex(languageIdentifier, `indentationRules.unIndentedLinePattern`, indentationRules.unIndentedLinePattern); + } + + return result; } } @@ -601,6 +697,101 @@ const schema: IJSONSchema = { } } } + }, + onEnterRules: { + type: 'array', + description: nls.localize('schema.onEnterRules', 'The language\'s rules to be evaluated when pressing Enter.'), + items: { + type: 'object', + description: nls.localize('schema.onEnterRules', 'The language\'s rules to be evaluated when pressing Enter.'), + required: ['beforeText', 'action'], + properties: { + beforeText: { + type: ['string', 'object'], + description: nls.localize('schema.onEnterRules.beforeText', 'This rule will only execute if the text before the cursor matches this regular expression.'), + properties: { + pattern: { + type: 'string', + description: nls.localize('schema.onEnterRules.beforeText.pattern', 'The RegExp pattern for beforeText.'), + default: '', + }, + flags: { + type: 'string', + description: nls.localize('schema.onEnterRules.beforeText.flags', 'The RegExp flags for beforeText.'), + default: '', + pattern: '^([gimuy]+)$', + patternErrorMessage: nls.localize('schema.onEnterRules.beforeText.errorMessage', 'Must match the pattern `/^([gimuy]+)$/`.') + } + } + }, + afterText: { + type: ['string', 'object'], + description: nls.localize('schema.onEnterRules.afterText', 'This rule will only execute if the text after the cursor matches this regular expression.'), + properties: { + pattern: { + type: 'string', + description: nls.localize('schema.onEnterRules.afterText.pattern', 'The RegExp pattern for afterText.'), + default: '', + }, + flags: { + type: 'string', + description: nls.localize('schema.onEnterRules.afterText.flags', 'The RegExp flags for afterText.'), + default: '', + pattern: '^([gimuy]+)$', + patternErrorMessage: nls.localize('schema.onEnterRules.afterText.errorMessage', 'Must match the pattern `/^([gimuy]+)$/`.') + } + } + }, + previousLineText: { + type: ['string', 'object'], + description: nls.localize('schema.onEnterRules.previousLineText', 'This rule will only execute if the text above the line matches this regular expression.'), + properties: { + pattern: { + type: 'string', + description: nls.localize('schema.onEnterRules.previousLineText.pattern', 'The RegExp pattern for previousLineText.'), + default: '', + }, + flags: { + type: 'string', + description: nls.localize('schema.onEnterRules.previousLineText.flags', 'The RegExp flags for previousLineText.'), + default: '', + pattern: '^([gimuy]+)$', + patternErrorMessage: nls.localize('schema.onEnterRules.previousLineText.errorMessage', 'Must match the pattern `/^([gimuy]+)$/`.') + } + } + }, + action: { + type: ['string', 'object'], + description: nls.localize('schema.onEnterRules.action', 'The action to execute.'), + required: ['indent'], + default: { 'indent': 'indent' }, + properties: { + indent: { + type: 'string', + description: nls.localize('schema.onEnterRules.action.indent', "Describe what to do with the indentation"), + default: 'indent', + enum: ['none', 'indent', 'indentOutdent', 'outdent'], + markdownEnumDescriptions: [ + nls.localize('schema.onEnterRules.action.indent.none', "Insert new line and copy the previous line's indentation."), + nls.localize('schema.onEnterRules.action.indent.indent', "Insert new line and indent once (relative to the previous line's indentation)."), + nls.localize('schema.onEnterRules.action.indent.indentOutdent', "Insert two new lines:\n - the first one indented which will hold the cursor\n - the second one at the same indentation level"), + nls.localize('schema.onEnterRules.action.indent.outdent', "Insert new line and outdent once (relative to the previous line's indentation).") + ] + }, + appendText: { + type: 'string', + description: nls.localize('schema.onEnterRules.action.appendText', 'Describes text to be appended after the new line and after the indentation.'), + default: '', + }, + removeText: { + type: 'number', + description: nls.localize('schema.onEnterRules.action.removeText', 'Describes the number of characters to remove from the new line\'s indentation.'), + default: 0, + } + } + } + } + } } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts b/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts index f7a7ea98d..0d8174166 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts @@ -46,7 +46,7 @@ export class LargeFileOptimizationsWarner extends Disposable implements IEditorC this._notificationService.prompt(Severity.Info, message, [ { - label: nls.localize('removeOptimizations', "Forcefully enable features"), + label: nls.localize('removeOptimizations', "Forcefully Enable Features"), run: () => { this._configurationService.updateValue(`editor.largeFileOptimizations`, false).then(() => { this._notificationService.info(nls.localize('reopenFilePrompt', "Please reopen file in order for this setting to take effect.")); diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts new file mode 100644 index 000000000..bdb42efac --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts @@ -0,0 +1,429 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IBreadcrumbsDataSource, IOutline, IOutlineCreator, IOutlineListConfig, IOutlineService, OutlineChangeEvent, OutlineConfigKeys, OutlineTarget, } from 'vs/workbench/services/outline/browser/outline'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IEditorPane } from 'vs/workbench/common/editor'; +import { DocumentSymbolComparator, DocumentSymbolAccessibilityProvider, DocumentSymbolRenderer, DocumentSymbolFilter, DocumentSymbolGroupRenderer, DocumentSymbolIdentityProvider, DocumentSymbolNavigationLabelProvider, DocumentSymbolVirtualDelegate } from 'vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree'; +import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { OutlineGroup, OutlineElement, OutlineModel, TreeElement, IOutlineMarker } from 'vs/editor/contrib/documentSymbols/outlineModel'; +import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { raceCancellation, TimeoutTimer, timeout, Barrier } from 'vs/base/common/async'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { URI } from 'vs/base/common/uri'; +import { ITextModel } from 'vs/editor/common/model'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IPosition } from 'vs/editor/common/core/position'; +import { ScrollType } from 'vs/editor/common/editorCommon'; +import { Range } from 'vs/editor/common/core/range'; +import { IEditorOptions, TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; +import { IDataSource } from 'vs/base/browser/ui/tree/tree'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { localize } from 'vs/nls'; +import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; +import { MarkerSeverity } from 'vs/platform/markers/common/markers'; +import { isEqual } from 'vs/base/common/resources'; + +type DocumentSymbolItem = OutlineGroup | OutlineElement; + +class DocumentSymbolBreadcrumbsSource implements IBreadcrumbsDataSource{ + + private _breadcrumbs: (OutlineGroup | OutlineElement)[] = []; + + constructor( + private readonly _editor: ICodeEditor, + @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, + ) { } + + getBreadcrumbElements(): readonly DocumentSymbolItem[] { + return this._breadcrumbs; + } + + clear(): void { + this._breadcrumbs = []; + } + + update(model: OutlineModel, position: IPosition): void { + const newElements = this._computeBreadcrumbs(model, position); + this._breadcrumbs = newElements; + } + + private _computeBreadcrumbs(model: OutlineModel, position: IPosition): Array { + let item: OutlineGroup | OutlineElement | undefined = model.getItemEnclosingPosition(position); + if (!item) { + return []; + } + let chain: Array = []; + while (item) { + chain.push(item); + let parent: any = item.parent; + if (parent instanceof OutlineModel) { + break; + } + if (parent instanceof OutlineGroup && parent.parent && parent.parent.children.size === 1) { + break; + } + item = parent; + } + let result: Array = []; + for (let i = chain.length - 1; i >= 0; i--) { + let element = chain[i]; + if (this._isFiltered(element)) { + break; + } + result.push(element); + } + if (result.length === 0) { + return []; + } + return result; + } + + private _isFiltered(element: TreeElement): boolean { + if (!(element instanceof OutlineElement)) { + return false; + } + const key = `breadcrumbs.${DocumentSymbolFilter.kindToConfigName[element.symbol.kind]}`; + let uri: URI | undefined; + if (this._editor && this._editor.getModel()) { + const model = this._editor.getModel() as ITextModel; + uri = model.uri; + } + return !this._textResourceConfigurationService.getValue(uri, key); + } +} + +class DocumentSymbolsOutline implements IOutline { + + private readonly _disposables = new DisposableStore(); + private readonly _onDidChange = new Emitter(); + + readonly onDidChange: Event = this._onDidChange.event; + + private _outlineModel?: OutlineModel; + private _outlineDisposables = new DisposableStore(); + + private readonly _breadcrumbsDataSource: DocumentSymbolBreadcrumbsSource; + + readonly config: IOutlineListConfig; + + readonly outlineKind = 'documentSymbols'; + + get activeElement(): DocumentSymbolItem | undefined { + const posistion = this._editor.getPosition(); + if (!posistion || !this._outlineModel) { + return undefined; + } else { + return this._outlineModel.getItemEnclosingPosition(posistion); + } + } + + constructor( + private readonly _editor: ICodeEditor, + target: OutlineTarget, + firstLoadBarrier: Barrier, + @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IMarkerDecorationsService private readonly _markerDecorationsService: IMarkerDecorationsService, + @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, + @IInstantiationService instantiationService: IInstantiationService, + ) { + + this._breadcrumbsDataSource = new DocumentSymbolBreadcrumbsSource(_editor, textResourceConfigurationService); + const delegate = new DocumentSymbolVirtualDelegate(); + const renderers = [new DocumentSymbolGroupRenderer(), instantiationService.createInstance(DocumentSymbolRenderer, true)]; + const treeDataSource: IDataSource = { + getChildren: (parent) => { + if (parent instanceof OutlineElement || parent instanceof OutlineGroup) { + return parent.children.values(); + } + if (parent === this && this._outlineModel) { + return this._outlineModel.children.values(); + } + return []; + } + }; + const comparator = new DocumentSymbolComparator(); + const options = { + collapseByDefault: target === OutlineTarget.Breadcrumbs, + expandOnlyOnTwistieClick: true, + multipleSelectionSupport: false, + identityProvider: new DocumentSymbolIdentityProvider(), + keyboardNavigationLabelProvider: new DocumentSymbolNavigationLabelProvider(), + accessibilityProvider: new DocumentSymbolAccessibilityProvider(localize('document', "Document Symbols")), + filter: target === OutlineTarget.OutlinePane + ? instantiationService.createInstance(DocumentSymbolFilter, 'outline') + : target === OutlineTarget.Breadcrumbs + ? instantiationService.createInstance(DocumentSymbolFilter, 'breadcrumbs') + : undefined + }; + + this.config = { + breadcrumbsDataSource: this._breadcrumbsDataSource, + delegate, + renderers, + treeDataSource, + comparator, + options, + quickPickDataSource: { getQuickPickElements: () => { throw new Error('not implemented'); } } + }; + + + // update as language, model, providers changes + this._disposables.add(DocumentSymbolProviderRegistry.onDidChange(_ => this._createOutline())); + this._disposables.add(this._editor.onDidChangeModel(_ => this._createOutline())); + this._disposables.add(this._editor.onDidChangeModelLanguage(_ => this._createOutline())); + + // update soon'ish as model content change + const updateSoon = new TimeoutTimer(); + this._disposables.add(updateSoon); + this._disposables.add(this._editor.onDidChangeModelContent(event => { + const timeout = OutlineModel.getRequestDelay(this._editor!.getModel()); + updateSoon.cancelAndSet(() => this._createOutline(event), timeout); + })); + + // stop when editor dies + this._disposables.add(this._editor.onDidDispose(() => this._outlineDisposables.clear())); + + // initial load + this._createOutline().finally(() => firstLoadBarrier.open()); + } + + dispose(): void { + this._disposables.dispose(); + this._outlineDisposables.dispose(); + } + + get isEmpty(): boolean { + return !this._outlineModel || TreeElement.empty(this._outlineModel); + } + + async reveal(entry: DocumentSymbolItem, options: IEditorOptions, sideBySide: boolean): Promise { + const model = OutlineModel.get(entry); + if (!model || !(entry instanceof OutlineElement)) { + return; + } + await this._codeEditorService.openCodeEditor({ + resource: model.uri, + options: { + ...options, + selection: Range.collapseToStart(entry.symbol.selectionRange), + selectionRevealType: TextEditorSelectionRevealType.NearTopIfOutsideViewport, + } + }, this._editor, sideBySide); + } + + preview(entry: DocumentSymbolItem): IDisposable { + if (!(entry instanceof OutlineElement)) { + return Disposable.None; + } + + const { symbol } = entry; + this._editor.revealRangeInCenterIfOutsideViewport(symbol.range, ScrollType.Smooth); + const ids = this._editor.deltaDecorations([], [{ + range: symbol.range, + options: { + className: 'rangeHighlight', + isWholeLine: true + } + }]); + return toDisposable(() => this._editor.deltaDecorations(ids, [])); + } + + captureViewState(): IDisposable { + const viewState = this._editor.saveViewState(); + return toDisposable(() => { + if (viewState) { + this._editor.restoreViewState(viewState); + } + }); + } + + private async _createOutline(contentChangeEvent?: IModelContentChangedEvent): Promise { + + this._outlineDisposables.clear(); + if (!contentChangeEvent) { + this._setOutlineModel(undefined); + } + + if (!this._editor.hasModel()) { + return; + } + const buffer = this._editor.getModel(); + if (!DocumentSymbolProviderRegistry.has(buffer)) { + return; + } + + const cts = new CancellationTokenSource(); + const versionIdThen = buffer.getVersionId(); + const timeoutTimer = new TimeoutTimer(); + + this._outlineDisposables.add(timeoutTimer); + this._outlineDisposables.add(toDisposable(() => cts.dispose(true))); + + try { + let model = await OutlineModel.create(buffer, cts.token); + if (cts.token.isCancellationRequested) { + // cancelled -> do nothing + return; + } + + if (TreeElement.empty(model) || !this._editor.hasModel()) { + // empty -> no outline elements + this._setOutlineModel(model); + return; + } + + // heuristic: when the symbols-to-lines ratio changes by 50% between edits + // wait a little (and hope that the next change isn't as drastic). + if (contentChangeEvent && this._outlineModel && buffer.getLineCount() >= 25) { + const newSize = TreeElement.size(model); + const newLength = buffer.getValueLength(); + const newRatio = newSize / newLength; + const oldSize = TreeElement.size(this._outlineModel); + const oldLength = newLength - contentChangeEvent.changes.reduce((prev, value) => prev + value.rangeLength, 0); + const oldRatio = oldSize / oldLength; + if (newRatio <= oldRatio * 0.5 || newRatio >= oldRatio * 1.5) { + // wait for a better state and ignore current model when more + // typing has happened + const value = await raceCancellation(timeout(2000).then(() => true), cts.token, false); + if (!value) { + return; + } + } + } + + // copy the model + model = model.adopt(); + + // feature: show markers with outline element + this._applyMarkersToOutline(model); + this._outlineDisposables.add(this._markerDecorationsService.onDidChangeMarker(textModel => { + if (isEqual(model.uri, textModel.uri)) { + this._applyMarkersToOutline(model); + this._onDidChange.fire({}); + } + })); + this._outlineDisposables.add(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(OutlineConfigKeys.problemsEnabled)) { + if (this._configurationService.getValue(OutlineConfigKeys.problemsEnabled)) { + this._applyMarkersToOutline(model); + } else { + model.updateMarker([]); + } + this._onDidChange.fire({}); + } + if (e.affectsConfiguration('outline')) { + // outline filtering, problems on/off + this._onDidChange.fire({}); + } + if (e.affectsConfiguration('breadcrumbs') && this._editor.hasModel()) { + // breadcrumbs filtering + this._breadcrumbsDataSource.update(model, this._editor.getPosition()); + this._onDidChange.fire({}); + } + })); + + // feature: toggle icons + this._outlineDisposables.add(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(OutlineConfigKeys.icons)) { + this._onDidChange.fire({}); + } + if (e.affectsConfiguration('outline')) { + this._onDidChange.fire({}); + } + })); + + // feature: update active when cursor changes + this._outlineDisposables.add(this._editor.onDidChangeCursorPosition(_ => { + timeoutTimer.cancelAndSet(() => { + if (!buffer.isDisposed() && versionIdThen === buffer.getVersionId() && this._editor.hasModel()) { + this._breadcrumbsDataSource.update(model, this._editor.getPosition()); + this._onDidChange.fire({ affectOnlyActiveElement: true }); + } + }, 150); + })); + + // update properties, send event + this._setOutlineModel(model); + + } catch (err) { + this._setOutlineModel(undefined); + onUnexpectedError(err); + } + } + + private _applyMarkersToOutline(model: OutlineModel | undefined): void { + if (!model || !this._configurationService.getValue(OutlineConfigKeys.problemsEnabled)) { + return; + } + const markers: IOutlineMarker[] = []; + for (const [range, marker] of this._markerDecorationsService.getLiveMarkers(model.uri)) { + if (marker.severity === MarkerSeverity.Error || marker.severity === MarkerSeverity.Warning) { + markers.push({ ...range, severity: marker.severity }); + } + } + model.updateMarker(markers); + } + + private _setOutlineModel(model: OutlineModel | undefined) { + const position = this._editor.getPosition(); + if (!position || !model) { + this._outlineModel = undefined; + this._breadcrumbsDataSource.clear(); + } else { + if (!this._outlineModel?.merge(model)) { + this._outlineModel = model; + } + this._breadcrumbsDataSource.update(model, position); + } + this._onDidChange.fire({}); + } +} + +class DocumentSymbolsOutlineCreator implements IOutlineCreator { + + readonly dispose: () => void; + + constructor( + @IOutlineService outlineService: IOutlineService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + ) { + const reg = outlineService.registerOutlineCreator(this); + this.dispose = () => reg.dispose(); + } + + matches(candidate: IEditorPane): candidate is IEditorPane { + const ctrl = candidate.getControl(); + return isCodeEditor(ctrl) || isDiffEditor(ctrl); + } + + async createOutline(pane: IEditorPane, target: OutlineTarget, _token: CancellationToken): Promise | undefined> { + const control = pane.getControl(); + let editor: ICodeEditor | undefined; + if (isCodeEditor(control)) { + editor = control; + } else if (isDiffEditor(control)) { + editor = control.getModifiedEditor(); + } + if (!editor) { + return undefined; + } + const firstLoadBarrier = new Barrier(); + const result = this._instantiationService.createInstance(DocumentSymbolsOutline, editor, target, firstLoadBarrier); + await firstLoadBarrier.wait(); + return result; + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DocumentSymbolsOutlineCreator, LifecyclePhase.Eventually); diff --git a/src/vs/editor/contrib/documentSymbols/media/outlineTree.css b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.css similarity index 77% rename from src/vs/editor/contrib/documentSymbols/media/outlineTree.css rename to src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.css index ccdfe57a4..107c29992 100644 --- a/src/vs/editor/contrib/documentSymbols/media/outlineTree.css +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.css @@ -20,11 +20,7 @@ color: var(--outline-element-color); } -.monaco-list .outline-element .monaco-icon-label-container .monaco-highlighted-label, -.monaco-list .outline-element .monaco-icon-label-container .label-description { - white-space: nowrap; -} - +.monaco-breadcrumbs .outline-element .outline-element-decoration, .monaco-list .outline-element .outline-element-decoration { opacity: 0.75; font-size: 90%; @@ -35,10 +31,17 @@ color: var(--outline-element-color); } +/* when showing in breadcrumbs than hide a few things, like markers or descriptions */ +.monaco-breadcrumbs .outline-element .monaco-icon-label-container .monaco-icon-description-container, +.monaco-breadcrumbs .outline-element .outline-element-decoration { + display: none; +} + .monaco-list .outline-element .outline-element-decoration.bubble { font-family: codicon; font-size: 14px; opacity: 0.4; + padding-right: 8px; } .monaco-list .outline-element .outline-element-icon { diff --git a/src/vs/editor/contrib/documentSymbols/outlineTree.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts similarity index 82% rename from src/vs/editor/contrib/documentSymbols/outlineTree.ts rename to src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts index c0ef55263..64b463a09 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineTree.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts @@ -3,35 +3,32 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./documentSymbolsTree'; import * as dom from 'vs/base/browser/dom'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { IDataSource, ITreeNode, ITreeRenderer, ITreeSorter, ITreeFilter } from 'vs/base/browser/ui/tree/tree'; +import { ITreeNode, ITreeRenderer, ITreeFilter } from 'vs/base/browser/ui/tree/tree'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; -import 'vs/css!./media/outlineTree'; -import 'vs/css!./media/symbol-icons'; import { Range } from 'vs/editor/common/core/range'; import { SymbolKind, SymbolKinds, SymbolTag } from 'vs/editor/common/modes'; import { OutlineElement, OutlineGroup, OutlineModel } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { localize } from 'vs/nls'; -import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; +import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { OutlineConfigKeys } from 'vs/editor/contrib/documentSymbols/outline'; import { MarkerSeverity } from 'vs/platform/markers/common/markers'; import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { registerColor, listErrorForeground, listWarningForeground, foreground } from 'vs/platform/theme/common/colorRegistry'; import { IdleValue } from 'vs/base/common/async'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; -import { URI } from 'vs/base/common/uri'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; -import { Iterable } from 'vs/base/common/iterator'; import { Codicon } from 'vs/base/common/codicons'; +import { IOutlineComparator, OutlineConfigKeys } from 'vs/workbench/services/outline/browser/outline'; -export type OutlineItem = OutlineGroup | OutlineElement; +export type DocumentSymbolItem = OutlineGroup | OutlineElement; -export class OutlineNavigationLabelProvider implements IKeyboardNavigationLabelProvider { +export class DocumentSymbolNavigationLabelProvider implements IKeyboardNavigationLabelProvider { - getKeyboardNavigationLabel(element: OutlineItem): { toString(): string; } { + getKeyboardNavigationLabel(element: DocumentSymbolItem): { toString(): string; } { if (element instanceof OutlineGroup) { return element.label; } else { @@ -40,15 +37,14 @@ export class OutlineNavigationLabelProvider implements IKeyboardNavigationLabelP } } -export class OutlineAccessibilityProvider implements IListAccessibilityProvider { +export class DocumentSymbolAccessibilityProvider implements IListAccessibilityProvider { - constructor(private readonly ariaLabel: string) { } + constructor(private readonly _ariaLabel: string) { } getWidgetAriaLabel(): string { - return this.ariaLabel; + return this._ariaLabel; } - - getAriaLabel(element: OutlineItem): string | null { + getAriaLabel(element: DocumentSymbolItem): string | null { if (element instanceof OutlineGroup) { return element.label; } else { @@ -57,22 +53,22 @@ export class OutlineAccessibilityProvider implements IListAccessibilityProvider< } } -export class OutlineIdentityProvider implements IIdentityProvider { - getId(element: OutlineItem): { toString(): string; } { +export class DocumentSymbolIdentityProvider implements IIdentityProvider { + getId(element: DocumentSymbolItem): { toString(): string; } { return element.id; } } -export class OutlineGroupTemplate { - static readonly id = 'OutlineGroupTemplate'; +class DocumentSymbolGroupTemplate { + static readonly id = 'DocumentSymbolGroupTemplate'; constructor( readonly labelContainer: HTMLElement, readonly label: HighlightedLabel, ) { } } -export class OutlineElementTemplate { - static readonly id = 'OutlineElementTemplate'; +class DocumentSymbolTemplate { + static readonly id = 'DocumentSymbolTemplate'; constructor( readonly container: HTMLElement, readonly iconLabel: IconLabel, @@ -81,70 +77,66 @@ export class OutlineElementTemplate { ) { } } -export class OutlineVirtualDelegate implements IListVirtualDelegate { +export class DocumentSymbolVirtualDelegate implements IListVirtualDelegate { - getHeight(_element: OutlineItem): number { + getHeight(_element: DocumentSymbolItem): number { return 22; } - getTemplateId(element: OutlineItem): string { - if (element instanceof OutlineGroup) { - return OutlineGroupTemplate.id; - } else { - return OutlineElementTemplate.id; - } + getTemplateId(element: DocumentSymbolItem): string { + return element instanceof OutlineGroup + ? DocumentSymbolGroupTemplate.id + : DocumentSymbolTemplate.id; } } -export class OutlineGroupRenderer implements ITreeRenderer { +export class DocumentSymbolGroupRenderer implements ITreeRenderer { - readonly templateId: string = OutlineGroupTemplate.id; + readonly templateId: string = DocumentSymbolGroupTemplate.id; - renderTemplate(container: HTMLElement): OutlineGroupTemplate { + renderTemplate(container: HTMLElement): DocumentSymbolGroupTemplate { const labelContainer = dom.$('.outline-element-label'); container.classList.add('outline-element'); dom.append(container, labelContainer); - return new OutlineGroupTemplate(labelContainer, new HighlightedLabel(labelContainer, true)); + return new DocumentSymbolGroupTemplate(labelContainer, new HighlightedLabel(labelContainer, true)); } - renderElement(node: ITreeNode, index: number, template: OutlineGroupTemplate): void { - template.label.set( - node.element.label, - createMatches(node.filterData) - ); + renderElement(node: ITreeNode, _index: number, template: DocumentSymbolGroupTemplate): void { + template.label.set(node.element.label, createMatches(node.filterData)); } - disposeTemplate(_template: OutlineGroupTemplate): void { + disposeTemplate(_template: DocumentSymbolGroupTemplate): void { // nothing } } -export class OutlineElementRenderer implements ITreeRenderer { +export class DocumentSymbolRenderer implements ITreeRenderer { - readonly templateId: string = OutlineElementTemplate.id; + readonly templateId: string = DocumentSymbolTemplate.id; constructor( + private _renderMarker: boolean, @IConfigurationService private readonly _configurationService: IConfigurationService, @IThemeService private readonly _themeService: IThemeService, ) { } - renderTemplate(container: HTMLElement): OutlineElementTemplate { + renderTemplate(container: HTMLElement): DocumentSymbolTemplate { container.classList.add('outline-element'); const iconLabel = new IconLabel(container, { supportHighlights: true }); const iconClass = dom.$('.outline-element-icon'); const decoration = dom.$('.outline-element-decoration'); container.prepend(iconClass); container.appendChild(decoration); - return new OutlineElementTemplate(container, iconLabel, iconClass, decoration); + return new DocumentSymbolTemplate(container, iconLabel, iconClass, decoration); } - renderElement(node: ITreeNode, index: number, template: OutlineElementTemplate): void { + renderElement(node: ITreeNode, _index: number, template: DocumentSymbolTemplate): void { const { element } = node; - const options = { + const options: IIconLabelValueOptions = { matches: createMatches(node.filterData), labelEscapeNewLines: true, - extraClasses: [], - title: localize('title.template', "{0} ({1})", element.symbol.name, OutlineElementRenderer._symbolKindNames[element.symbol.kind]) + extraClasses: ['nowrap'], + title: localize('title.template', "{0} ({1})", element.symbol.name, DocumentSymbolRenderer._symbolKindNames[element.symbol.kind]) }; if (this._configurationService.getValue(OutlineConfigKeys.icons)) { // add styles for the icons @@ -152,14 +144,17 @@ export class OutlineElementRenderer implements ITreeRenderer= 0) { - options.extraClasses.push(`deprecated`); + options.extraClasses!.push(`deprecated`); options.matches = []; } template.iconLabel.setLabel(element.symbol.name, element.symbol.detail, options); - this._renderMarkerInfo(element, template); + + if (this._renderMarker) { + this._renderMarkerInfo(element, template); + } } - private _renderMarkerInfo(element: OutlineElement, template: OutlineElementTemplate): void { + private _renderMarkerInfo(element: OutlineElement, template: DocumentSymbolTemplate): void { if (!element.marker) { dom.hide(template.decoration); @@ -227,47 +222,12 @@ export class OutlineElementRenderer implements ITreeRenderer { - - static readonly configNameToKind = Object.freeze({ - ['showFiles']: SymbolKind.File, - ['showModules']: SymbolKind.Module, - ['showNamespaces']: SymbolKind.Namespace, - ['showPackages']: SymbolKind.Package, - ['showClasses']: SymbolKind.Class, - ['showMethods']: SymbolKind.Method, - ['showProperties']: SymbolKind.Property, - ['showFields']: SymbolKind.Field, - ['showConstructors']: SymbolKind.Constructor, - ['showEnums']: SymbolKind.Enum, - ['showInterfaces']: SymbolKind.Interface, - ['showFunctions']: SymbolKind.Function, - ['showVariables']: SymbolKind.Variable, - ['showConstants']: SymbolKind.Constant, - ['showStrings']: SymbolKind.String, - ['showNumbers']: SymbolKind.Number, - ['showBooleans']: SymbolKind.Boolean, - ['showArrays']: SymbolKind.Array, - ['showObjects']: SymbolKind.Object, - ['showKeys']: SymbolKind.Key, - ['showNull']: SymbolKind.Null, - ['showEnumMembers']: SymbolKind.EnumMember, - ['showStructs']: SymbolKind.Struct, - ['showEvents']: SymbolKind.Event, - ['showOperators']: SymbolKind.Operator, - ['showTypeParameters']: SymbolKind.TypeParameter, - }); +export class DocumentSymbolFilter implements ITreeFilter { static readonly kindToConfigName = Object.freeze({ [SymbolKind.File]: 'showFiles', @@ -299,60 +259,48 @@ export class OutlineFilter implements ITreeFilter { }); constructor( - private readonly _prefix: string, + private readonly _prefix: 'breadcrumbs' | 'outline', @ITextResourceConfigurationService private readonly _textResourceConfigService: ITextResourceConfigurationService, ) { } - filter(element: OutlineItem): boolean { + filter(element: DocumentSymbolItem): boolean { const outline = OutlineModel.get(element); - let uri: URI | undefined; - - if (outline) { - uri = outline.uri; - } - if (!(element instanceof OutlineElement)) { return true; } - - const configName = OutlineFilter.kindToConfigName[element.symbol.kind]; + const configName = DocumentSymbolFilter.kindToConfigName[element.symbol.kind]; const configKey = `${this._prefix}.${configName}`; - return this._textResourceConfigService.getValue(uri, configKey); + return this._textResourceConfigService.getValue(outline?.uri, configKey); } } -export class OutlineItemComparator implements ITreeSorter { +export class DocumentSymbolComparator implements IOutlineComparator { private readonly _collator = new IdleValue(() => new Intl.Collator(undefined, { numeric: true })); - constructor( - public type: OutlineSortOrder = OutlineSortOrder.ByPosition - ) { } - - compare(a: OutlineItem, b: OutlineItem): number { + compareByPosition(a: DocumentSymbolItem, b: DocumentSymbolItem): number { if (a instanceof OutlineGroup && b instanceof OutlineGroup) { return a.order - b.order; - } else if (a instanceof OutlineElement && b instanceof OutlineElement) { - if (this.type === OutlineSortOrder.ByKind) { - return a.symbol.kind - b.symbol.kind || this._collator.value.compare(a.symbol.name, b.symbol.name); - } else if (this.type === OutlineSortOrder.ByName) { - return this._collator.value.compare(a.symbol.name, b.symbol.name) || Range.compareRangesUsingStarts(a.symbol.range, b.symbol.range); - } else if (this.type === OutlineSortOrder.ByPosition) { - return Range.compareRangesUsingStarts(a.symbol.range, b.symbol.range) || this._collator.value.compare(a.symbol.name, b.symbol.name); - } + return Range.compareRangesUsingStarts(a.symbol.range, b.symbol.range) || this._collator.value.compare(a.symbol.name, b.symbol.name); } return 0; } -} - -export class OutlineDataSource implements IDataSource { - - getChildren(element: undefined | OutlineModel | OutlineGroup | OutlineElement) { - if (!element) { - return Iterable.empty(); + compareByType(a: DocumentSymbolItem, b: DocumentSymbolItem): number { + if (a instanceof OutlineGroup && b instanceof OutlineGroup) { + return a.order - b.order; + } else if (a instanceof OutlineElement && b instanceof OutlineElement) { + return a.symbol.kind - b.symbol.kind || this._collator.value.compare(a.symbol.name, b.symbol.name); } - return element.children.values(); + return 0; + } + compareByName(a: DocumentSymbolItem, b: DocumentSymbolItem): number { + if (a instanceof OutlineGroup && b instanceof OutlineGroup) { + return a.order - b.order; + } else if (a instanceof OutlineElement && b instanceof OutlineElement) { + return this._collator.value.compare(a.symbol.name, b.symbol.name) || Range.compareRangesUsingStarts(a.symbol.range, b.symbol.range); + } + return 0; } } @@ -721,5 +669,4 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = if (symbolIconVariableColor) { collector.addRule(`${Codicon.symbolVariable.cssSelector} { color: ${symbolIconVariableColor}; }`); } - }); diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts index eb4566740..99417655a 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts @@ -30,10 +30,10 @@ export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProv } private get configuration() { - const editorConfig = this.configurationService.getValue().workbench.editor; + const editorConfig = this.configurationService.getValue().workbench?.editor; return { - openEditorPinned: !editorConfig.enablePreviewFromQuickOpen, + openEditorPinned: !editorConfig?.enablePreviewFromQuickOpen || !editorConfig?.enablePreview }; } @@ -44,12 +44,12 @@ export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProv protected gotoLocation(context: IQuickAccessTextEditorContext, options: { range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean, preserveFocus?: boolean }): void { // Check for sideBySide use - if ((options.keyMods.ctrlCmd || options.forceSideBySide) && this.editorService.activeEditor) { + if ((options.keyMods.alt || (this.configuration.openEditorPinned && options.keyMods.ctrlCmd) || options.forceSideBySide) && this.editorService.activeEditor) { context.restoreViewState?.(); // since we open to the side, restore view state in this editor this.editorService.openEditor(this.editorService.activeEditor, { selection: options.range, - pinned: options.keyMods.alt || this.configuration.openEditorPinned, + pinned: options.keyMods.ctrlCmd || this.configuration.openEditorPinned, preserveFocus: options.preserveFocus }, SIDE_GROUP); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts index bb7642fe7..4f76c5547 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts @@ -12,9 +12,9 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IQuickAccessRegistry, Extensions as QuickaccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; import { AbstractGotoSymbolQuickAccessProvider, IGotoSymbolQuickPickItem } from 'vs/editor/contrib/quickAccess/gotoSymbolQuickAccess'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWorkbenchEditorConfiguration, IEditorPane } from 'vs/workbench/common/editor'; +import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; import { ITextModel } from 'vs/editor/common/model'; -import { DisposableStore, IDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, toDisposable, Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { timeout } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { registerAction2, Action2 } from 'vs/platform/actions/common/actions'; @@ -23,10 +23,11 @@ import { prepareQuery } from 'vs/base/common/fuzzyScorer'; import { SymbolKind } from 'vs/editor/common/modes'; import { fuzzyScore, createMatches } from 'vs/base/common/filters'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IQuickAccessTextEditorContext } from 'vs/editor/contrib/quickAccess/editorNavigationQuickAccess'; +import { IOutlineService, OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; +import { isCompositeEditor } from 'vs/editor/browser/editorBrowser'; export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider { @@ -34,7 +35,8 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess constructor( @IEditorService private readonly editorService: IEditorService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IOutlineService private readonly outlineService: IOutlineService, ) { super({ openSideBySideDirection: () => this.configuration.openSideBySideDirection @@ -43,36 +45,37 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess //#region DocumentSymbols (text editor required) - protected provideWithTextEditor(context: IQuickAccessTextEditorContext, picker: IQuickPick, token: CancellationToken): IDisposable { - if (this.canPickFromTableOfContents()) { - return this.doGetTableOfContentsPicks(picker); - } - - return super.provideWithTextEditor(context, picker, token); - } - private get configuration() { - const editorConfig = this.configurationService.getValue().workbench.editor; + const editorConfig = this.configurationService.getValue().workbench?.editor; return { - openEditorPinned: !editorConfig.enablePreviewFromQuickOpen, - openSideBySideDirection: editorConfig.openSideBySideDirection + openEditorPinned: !editorConfig?.enablePreviewFromQuickOpen || !editorConfig?.enablePreview, + openSideBySideDirection: editorConfig?.openSideBySideDirection }; } protected get activeTextEditorControl() { + // TODO@bpasero this distinction should go away by adopting `IOutlineService` + // for all editors (either text based ones or not). Currently text based + // editors are not yet using the new outline service infrastructure but the + // "classical" document symbols approach. + + if (isCompositeEditor(this.editorService.activeEditorPane?.getControl())) { + return undefined; + } + return this.editorService.activeTextEditorControl; } protected gotoLocation(context: IQuickAccessTextEditorContext, options: { range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean, preserveFocus?: boolean }): void { // Check for sideBySide use - if ((options.keyMods.ctrlCmd || options.forceSideBySide) && this.editorService.activeEditor) { + if ((options.keyMods.alt || (this.configuration.openEditorPinned && options.keyMods.ctrlCmd) || options.forceSideBySide) && this.editorService.activeEditor) { context.restoreViewState?.(); // since we open to the side, restore view state in this editor this.editorService.openEditor(this.editorService.activeEditor, { selection: options.range, - pinned: options.keyMods.alt || this.configuration.openEditorPinned, + pinned: options.keyMods.ctrlCmd || this.configuration.openEditorPinned, preserveFocus: options.preserveFocus }, SIDE_GROUP); } @@ -104,7 +107,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess return []; } - return this.doGetSymbolPicks(this.getDocumentSymbols(model, true, token), prepareQuery(filter), options, token); + return this.doGetSymbolPicks(this.getDocumentSymbols(model, token), prepareQuery(filter), options, token); } addDecorations(editor: IEditor, range: IRange): void { @@ -118,22 +121,21 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess //#endregion protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { - if (this.canPickFromTableOfContents()) { - return this.doGetTableOfContentsPicks(picker); + if (this.canPickWithOutlineService()) { + return this.doGetOutlinePicks(picker); } return super.provideWithoutTextEditor(picker); } - private canPickFromTableOfContents(): boolean { - return this.editorService.activeEditorPane ? TableOfContentsProviderRegistry.has(this.editorService.activeEditorPane.getId()) : false; + private canPickWithOutlineService(): boolean { + return this.editorService.activeEditorPane ? this.outlineService.canCreateOutline(this.editorService.activeEditorPane) : false; } - private doGetTableOfContentsPicks(picker: IQuickPick): IDisposable { + private doGetOutlinePicks(picker: IQuickPick): IDisposable { const pane = this.editorService.activeEditorPane; if (!pane) { return Disposable.None; } - const provider = TableOfContentsProviderRegistry.get(pane.getId())!; const cts = new CancellationTokenSource(); const disposables = new DisposableStore(); @@ -141,30 +143,45 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess picker.busy = true; - provider.provideTableOfContents(pane, { disposables }, cts.token).then(entries => { + this.outlineService.createOutline(pane, OutlineTarget.QuickPick, cts.token).then(outline => { - picker.busy = false; - - if (cts.token.isCancellationRequested || !entries || entries.length === 0) { + if (!outline) { return; } + if (cts.token.isCancellationRequested) { + outline.dispose(); + return; + } + + disposables.add(outline); + + const viewState = outline.captureViewState(); + disposables.add(toDisposable(() => { + if (picker.selectedItems.length === 0) { + viewState.dispose(); + } + })); + + const entries = Array.from(outline.config.quickPickDataSource.getQuickPickElements()); const items: IGotoSymbolQuickPickItem[] = entries.map((entry, idx) => { return { kind: SymbolKind.File, index: idx, score: 0, - label: entry.icon ? `$(${entry.icon.id}) ${entry.label}` : entry.label, - ariaLabel: entry.detail ? `${entry.label}, ${entry.detail}` : entry.label, - detail: entry.detail, + label: entry.label, description: entry.description, + ariaLabel: entry.ariaLabel, + iconClasses: entry.iconClasses }; }); disposables.add(picker.onDidAccept(() => { picker.hide(); const [entry] = picker.selectedItems; - entries[entry.index]?.pick(); + if (entry && entries[entry.index]) { + outline.reveal(entries[entry.index].element, {}, false); + } })); const updatePickerItems = () => { @@ -194,16 +211,23 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess updatePickerItems(); disposables.add(picker.onDidChangeValue(updatePickerItems)); + const previewDisposable = new MutableDisposable(); + disposables.add(previewDisposable); + disposables.add(picker.onDidChangeActive(() => { const [entry] = picker.activeItems; - if (entry) { - entries[entry.index]?.preview(); + if (entry && entries[entry.index]) { + previewDisposable.value = outline.preview(entries[entry.index].element); + } else { + previewDisposable.clear(); } })); }).catch(err => { onUnexpectedError(err); picker.hide(); + }).finally(() => { + picker.busy = false; }); return disposables; @@ -243,45 +267,3 @@ registerAction2(class GotoSymbolAction extends Action2 { accessor.get(IQuickInputService).quickAccess.show(GotoSymbolQuickAccessProvider.PREFIX); } }); - -//#region toc definition and logic - -export interface ITableOfContentsEntry { - icon?: ThemeIcon; - label: string; - detail?: string; - description?: string; - pick(): any; - preview(): any; -} - -export interface ITableOfContentsProvider { - - provideTableOfContents(editor: T, context: { disposables: DisposableStore }, token: CancellationToken): Promise; -} - -class ProviderRegistry { - - private readonly _provider = new Map(); - - register(type: string, provider: ITableOfContentsProvider): IDisposable { - this._provider.set(type, provider); - return toDisposable(() => { - if (this._provider.get(type) === provider) { - this._provider.delete(type); - } - }); - } - - get(type: string): ITableOfContentsProvider | undefined { - return this._provider.get(type); - } - - has(type: string): boolean { - return this._provider.has(type); - } -} - -export const TableOfContentsProviderRegistry = new ProviderRegistry(); - -//#endregion diff --git a/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts b/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts index f7e084d03..4e693565f 100644 --- a/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts +++ b/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts @@ -42,25 +42,25 @@ suite('Save Participants', function () { let lineContent = ''; model.textEditorModel.setValue(lineContent); await participant.participate(model, { reason: SaveReason.EXPLICIT }); - assert.equal(snapshotToString(model.createSnapshot()!), lineContent); + assert.strictEqual(snapshotToString(model.createSnapshot()!), lineContent); // No new line if last line already empty lineContent = `Hello New Line${model.textEditorModel.getEOL()}`; model.textEditorModel.setValue(lineContent); await participant.participate(model, { reason: SaveReason.EXPLICIT }); - assert.equal(snapshotToString(model.createSnapshot()!), lineContent); + assert.strictEqual(snapshotToString(model.createSnapshot()!), lineContent); // New empty line added (single line) lineContent = 'Hello New Line'; model.textEditorModel.setValue(lineContent); await participant.participate(model, { reason: SaveReason.EXPLICIT }); - assert.equal(snapshotToString(model.createSnapshot()!), `${lineContent}${model.textEditorModel.getEOL()}`); + assert.strictEqual(snapshotToString(model.createSnapshot()!), `${lineContent}${model.textEditorModel.getEOL()}`); // New empty line added (multi line) lineContent = `Hello New Line${model.textEditorModel.getEOL()}Hello New Line${model.textEditorModel.getEOL()}Hello New Line`; model.textEditorModel.setValue(lineContent); await participant.participate(model, { reason: SaveReason.EXPLICIT }); - assert.equal(snapshotToString(model.createSnapshot()!), `${lineContent}${model.textEditorModel.getEOL()}`); + assert.strictEqual(snapshotToString(model.createSnapshot()!), `${lineContent}${model.textEditorModel.getEOL()}`); }); test('trim final new lines', async function () { @@ -77,25 +77,25 @@ suite('Save Participants', function () { let lineContent = `${textContent}`; model.textEditorModel.setValue(lineContent); await participant.participate(model, { reason: SaveReason.EXPLICIT }); - assert.equal(snapshotToString(model.createSnapshot()!), lineContent); + assert.strictEqual(snapshotToString(model.createSnapshot()!), lineContent); // No new line removal if last line is single new line lineContent = `${textContent}${eol}`; model.textEditorModel.setValue(lineContent); await participant.participate(model, { reason: SaveReason.EXPLICIT }); - assert.equal(snapshotToString(model.createSnapshot()!), lineContent); + assert.strictEqual(snapshotToString(model.createSnapshot()!), lineContent); // Remove new line (single line with two new lines) lineContent = `${textContent}${eol}${eol}`; model.textEditorModel.setValue(lineContent); await participant.participate(model, { reason: SaveReason.EXPLICIT }); - assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}${eol}`); + assert.strictEqual(snapshotToString(model.createSnapshot()!), `${textContent}${eol}`); // Remove new lines (multiple lines with multiple new lines) lineContent = `${textContent}${eol}${textContent}${eol}${eol}${eol}`; model.textEditorModel.setValue(lineContent); await participant.participate(model, { reason: SaveReason.EXPLICIT }); - assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}${eol}${textContent}${eol}`); + assert.strictEqual(snapshotToString(model.createSnapshot()!), `${textContent}${eol}${textContent}${eol}`); }); test('trim final new lines bug#39750', async function () { @@ -117,12 +117,12 @@ suite('Save Participants', function () { // undo await model.textEditorModel.undo(); - assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}`); + assert.strictEqual(snapshotToString(model.createSnapshot()!), `${textContent}`); // trim final new lines should not mess the undo stack await participant.participate(model, { reason: SaveReason.EXPLICIT }); await model.textEditorModel.redo(); - assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}.`); + assert.strictEqual(snapshotToString(model.createSnapshot()!), `${textContent}.`); }); test('trim final new lines bug#46075', async function () { @@ -143,13 +143,13 @@ suite('Save Participants', function () { } // confirm trimming - assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}${eol}`); + assert.strictEqual(snapshotToString(model.createSnapshot()!), `${textContent}${eol}`); // undo should go back to previous content immediately await model.textEditorModel.undo(); - assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}${eol}${eol}`); + assert.strictEqual(snapshotToString(model.createSnapshot()!), `${textContent}${eol}${eol}`); await model.textEditorModel.redo(); - assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}${eol}`); + assert.strictEqual(snapshotToString(model.createSnapshot()!), `${textContent}${eol}`); }); test('trim whitespace', async function () { @@ -169,6 +169,6 @@ suite('Save Participants', function () { } // confirm trimming - assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}`); + assert.strictEqual(snapshotToString(model.createSnapshot()!), `${textContent}`); }); }); diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index ffab7e07c..1d4e0e380 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -25,7 +25,7 @@ import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; import { peekViewBorder } from 'vs/editor/contrib/peekView/peekView'; import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; import * as nls from 'vs/nls'; -import { MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenu, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -45,7 +45,6 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor'; -import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { PANEL_BORDER } from 'vs/workbench/common/theme'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { Codicon } from 'vs/base/common/codicons'; @@ -239,15 +238,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget const actionsContainer = dom.append(this._headElement, dom.$('.review-actions')); this._actionbarWidget = new ActionBar(actionsContainer, { - actionViewItemProvider: (action: IAction) => { - if (action instanceof MenuItemAction) { - return this.instantiationService.createInstance(MenuEntryActionViewItem, action); - } else if (action instanceof SubmenuItemAction) { - return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action); - } else { - return new ActionViewItem({}, action, { label: false, icon: true }); - } - } + actionViewItemProvider: createActionViewItem.bind(undefined, this.instantiationService) }); this._disposables.add(this._actionbarWidget); diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 9ddb42f76..0249effb3 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -142,6 +142,7 @@ export class CommentNodeRenderer implements IListRenderer } templateData.commentText.appendChild(renderedComment); + templateData.commentText.title = renderedComment.textContent ?? ''; } disposeTemplate(templateData: ICommentThreadTemplateData): void { diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts index 5ed011a2c..ec5544fd8 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -6,11 +6,9 @@ import 'vs/css!./media/panel'; import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; -import { basename, isEqual } from 'vs/base/common/resources'; -import { IAction, Action } from 'vs/base/common/actions'; -import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults'; +import { basename } from 'vs/base/common/resources'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { CommentNode, CommentsModel, ResourceWithCommentThreads, ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel'; import { CommentController } from 'vs/workbench/contrib/comments/browser/commentsEditorContribution'; @@ -20,14 +18,19 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { textLinkForeground, textLinkActiveForeground, focusBorder, textPreformatForeground } from 'vs/platform/theme/common/colorRegistry'; import { ResourceLabels } from 'vs/workbench/browser/labels'; import { CommentsList, COMMENTS_VIEW_ID, COMMENTS_VIEW_TITLE } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; -import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane, IViewPaneOptions, ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyAndExpr, ContextKeyEqualsExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { Codicon } from 'vs/base/common/codicons'; + +const CONTEXT_KEY_HAS_COMMENTS = new RawContextKey('commentsView.hasComments', false); export class CommentsPanel extends ViewPane { private treeLabels!: ResourceLabels; @@ -35,7 +38,7 @@ export class CommentsPanel extends ViewPane { private treeContainer!: HTMLElement; private messageBoxContainer!: HTMLElement; private commentsModel!: CommentsModel; - private collapseAllAction?: IAction; + private readonly hasCommentsContextKey: IContextKey; readonly onDidChangeVisibility = this.onDidChangeBodyVisibility; @@ -52,8 +55,10 @@ export class CommentsPanel extends ViewPane { @IThemeService themeService: IThemeService, @ICommentService private readonly commentService: ICommentService, @ITelemetryService telemetryService: ITelemetryService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + this.hasCommentsContextKey = CONTEXT_KEY_HAS_COMMENTS.bindTo(contextKeyService); } public renderBody(container: HTMLElement): void { @@ -129,13 +134,14 @@ export class CommentsPanel extends ViewPane { await this.tree.setInput(this.commentsModel); } - public getActions(): IAction[] { - if (!this.collapseAllAction) { - this.collapseAllAction = new Action('vs.tree.collapse', nls.localize('collapseAll', "Collapse All"), 'collapse-all', true, () => this.tree ? new CollapseAllAction(this.tree, true).run() : Promise.resolve()); - this._register(this.collapseAllAction); + public collapseAll() { + if (this.tree) { + this.tree.collapseAll(); + this.tree.setSelection([]); + this.tree.setFocus([]); + this.tree.domFocus(); + this.tree.focusFirst(); } - - return [this.collapseAllAction]; } public layoutBody(height: number, width: number): void { @@ -206,7 +212,7 @@ export class CommentsPanel extends ViewPane { const activeEditor = this.editorService.activeEditor; let currentActiveResource = activeEditor ? activeEditor.resource : undefined; - if (currentActiveResource && isEqual(currentActiveResource, element.resource)) { + if (this.uriIdentityService.extUri.isEqual(element.resource, currentActiveResource)) { const threadToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].threadId : element.threadId; const commentToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].comment.uniqueIdInThread : element.comment.uniqueIdInThread; const control = this.editorService.activeTextEditorControl; @@ -243,9 +249,7 @@ export class CommentsPanel extends ViewPane { private async refresh(): Promise { if (this.isVisible()) { - if (this.collapseAllAction) { - this.collapseAllAction.enabled = this.commentsModel.hasCommentThreads(); - } + this.hasCommentsContextKey.set(this.commentsModel.hasCommentThreads()); this.treeContainer.classList.toggle('hidden', !this.commentsModel.hasCommentThreads()); this.renderMessage(); @@ -281,3 +285,23 @@ CommandsRegistry.registerCommand({ viewsService.openView(COMMENTS_VIEW_ID, true); } }); + +registerAction2(class Collapse extends ViewAction { + constructor() { + super({ + viewId: COMMENTS_VIEW_ID, + id: 'comments.collapse', + title: nls.localize('collapseAll', "Collapse All"), + f1: false, + icon: Codicon.collapseAll, + menu: { + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyAndExpr.create([ContextKeyEqualsExpr.create('view', COMMENTS_VIEW_ID), CONTEXT_KEY_HAS_COMMENTS]) + } + }); + } + runInView(_accessor: ServicesAccessor, view: CommentsPanel) { + view.collapseAll(); + } +}); diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index c57aa6dd3..66de7e83c 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { coalesce, distinct } from 'vs/base/common/arrays'; +import { coalesce, distinct, firstOrDefault } from 'vs/base/common/arrays'; import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; import { Lazy } from 'vs/base/common/lazy'; @@ -28,7 +28,7 @@ import { EditorInput, EditorOptions, Extensions as EditorInputExtensions, GroupI import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { CONTEXT_CUSTOM_EDITORS, CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CustomEditorCapabilities, CustomEditorInfo, CustomEditorInfoCollection, CustomEditorPriority, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { CustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditorModelManager'; -import { IWebviewService, webviewHasOwnEditFunctionsContext } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CustomEditorAssociation, CustomEditorsAssociations, customEditorsAssociationsSettingId } from 'vs/workbench/services/editor/common/editorOpenWith'; import { ICustomEditorInfo, ICustomEditorViewTypesHandler, IEditorService, IOpenEditorOverride, IOpenEditorOverrideEntry } from 'vs/workbench/services/editor/common/editorService'; @@ -45,7 +45,6 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ private readonly _customEditorContextKey: IContextKey; private readonly _focusedCustomEditorIsEditable: IContextKey; - private readonly _webviewHasOwnEditFunctions: IContextKey; private readonly _onDidChangeViewTypes = new Emitter(); onDidChangeViewTypes: Event = this._onDidChangeViewTypes.event; @@ -66,7 +65,6 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ this._customEditorContextKey = CONTEXT_CUSTOM_EDITORS.bindTo(contextKeyService); this._focusedCustomEditorIsEditable = CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE.bindTo(contextKeyService); - this._webviewHasOwnEditFunctions = webviewHasOwnEditFunctionsContext.bindTo(contextKeyService); this._contributedEditors = this._register(new ContributedCustomEditors(storageService)); this._register(this._contributedEditors.onChange(() => { @@ -156,13 +154,8 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ ...this.getAllCustomEditors(resource).allEditors, ]); - let currentlyOpenedEditorType: undefined | string; - for (const editor of group ? group.editors : []) { - if (editor.resource && isEqual(editor.resource, resource)) { - currentlyOpenedEditorType = editor instanceof CustomEditorInput ? editor.viewType : defaultCustomEditor.id; - break; - } - } + const existingEditorForResource = group && firstOrDefault(this.editorService.findEditors(resource, group)); + const currentlyOpenedEditorType: undefined | string = existingEditorForResource instanceof CustomEditorInput ? existingEditorForResource.viewType : defaultCustomEditor.id; const resourceExt = extname(resource); @@ -278,9 +271,8 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ } // Try to replace existing editors for resource - const existingEditors = targetGroup.editors.filter(editor => editor.resource && isEqual(editor.resource, resource)); - if (existingEditors.length) { - const existing = existingEditors[0]; + const existing = firstOrDefault(this.editorService.findEditors(resource, targetGroup)); + if (existing) { if (!input.matches(existing)) { await this.editorService.replaceEditors([{ editor: existing, @@ -317,7 +309,6 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ if (!resource) { this._customEditorContextKey.reset(); this._focusedCustomEditorIsEditable.reset(); - this._webviewHasOwnEditFunctions.reset(); return; } @@ -325,7 +316,6 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ this._customEditorContextKey.set(possibleEditors.map(x => x.id).join(',')); this._focusedCustomEditorIsEditable.set(activeEditorPane?.input instanceof CustomEditorInput); - this._webviewHasOwnEditFunctions.set(possibleEditors.length > 0); } private async handleMovedFileInOpenedFileEditors(oldResource: URI, newResource: URI): Promise { @@ -457,7 +447,7 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo return this.onEditorOpening(editor, options, group); }, getEditorOverrides: (resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): IOpenEditorOverrideEntry[] => { - const currentEditor = group?.editors.find(editor => isEqual(editor.resource, resource)); + const currentEditor = group && firstOrDefault(this.editorService.findEditors(resource, group)); const toOverride = (entry: CustomEditorInfo): IOpenEditorOverrideEntry => { return { @@ -546,7 +536,7 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo return; } - const existingEditorForResource = group.editors.find(editor => isEqual(resource, editor.resource)); + const existingEditorForResource = firstOrDefault(this.editorService.findEditors(resource, group)); if (existingEditorForResource) { if (editor === existingEditorForResource) { return; diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 966dc83e5..65d81d074 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -15,7 +15,6 @@ import { IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness, import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { RemoveBreakpointAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, BreakpointWidgetContext, IBreakpointEditorContribution, IBreakpointUpdateData, IDebugConfiguration, State, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -173,6 +172,24 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi this.setDecorationsScheduler.schedule(); } + /** + * Returns context menu actions at the line number if breakpoints can be + * set. This is used by the {@link TestingDecorations} to allow breakpoint + * setting on lines where breakpoint "run" actions are present. + */ + public getContextMenuActionsAtPosition(lineNumber: number, model: ITextModel) { + if (!this.debugService.getAdapterManager().hasDebuggers()) { + return []; + } + + if (!this.debugService.canSetBreakpointsIn(model)) { + return []; + } + + const breakpoints = this.debugService.getModel().getBreakpoints({ lineNumber, uri: model.uri }); + return this.getContextMenuActions(breakpoints, model.uri, lineNumber); + } + private registerListeners(): void { this.toDispose.push(this.editor.onMouseDown(async (e: IEditorMouseEvent) => { if (!this.debugService.getAdapterManager().hasDebuggers()) { @@ -301,7 +318,9 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi const actions: IAction[] = []; if (breakpoints.length === 1) { const breakpointType = breakpoints[0].logMessage ? nls.localize('logPoint', "Logpoint") : nls.localize('breakpoint', "Breakpoint"); - actions.push(new RemoveBreakpointAction(RemoveBreakpointAction.ID, nls.localize('removeBreakpoint', "Remove {0}", breakpointType), this.debugService)); + actions.push(new Action('debug.removeBreakpoint', nls.localize('removeBreakpoint', "Remove {0}", breakpointType), undefined, true, async () => { + await this.debugService.removeBreakpoints(breakpoints[0].getId()); + })); actions.push(new Action( 'workbench.debug.action.editBreakpointAction', nls.localize('editBreakpoint', "Edit {0}...", breakpointType), @@ -375,7 +394,8 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi const decorations = this.editor.getLineDecorations(line); if (decorations) { for (const { options } of decorations) { - if (options.glyphMarginClassName && options.glyphMarginClassName.indexOf('codicon-') === -1) { + const clz = options.glyphMarginClassName; + if (clz && (!clz.includes('codicon-') || clz.includes('codicon-testing-'))) { return false; } } @@ -454,7 +474,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi // Candidate decoration has a breakpoint attached when a breakpoint is already at that location and we did not yet set a decoration there // In practice this happens for the first breakpoint that was set on a line // We could have also rendered this first decoration as part of desiredBreakpointDecorations however at that moment we have no location information - const icon = candidate.breakpoint ? getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), candidate.breakpoint, this.labelService).icon : icons.debugBreakpointDisabled; + const icon = candidate.breakpoint ? getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), candidate.breakpoint, this.labelService).icon : icons.breakpoint.disabled; const contextMenuActions = () => this.getContextMenuActions(candidate.breakpoint ? [candidate.breakpoint] : [], activeCodeEditor.getModel().uri, candidate.range.startLineNumber, candidate.range.startColumn); const inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, ThemeIcon.asClassName(icon), candidate.breakpoint, this.debugService, this.contextMenuService, contextMenuActions); @@ -645,15 +665,11 @@ registerThemingParticipant((theme, collector) => { const debugIconBreakpointColor = theme.getColor(debugIconBreakpointForeground); if (debugIconBreakpointColor) { collector.addRule(` - .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpoint)}, - .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpointConditional)}, - .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpointLog)}, - .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpointFunction)}, - .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpointData)}, + ${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.debugBreakpoint)}${ThemeIcon.asCSSSelector(icons.debugStackframeFocused)}::after, - .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpoint)}${ThemeIcon.asCSSSelector(icons.debugStackframe)}::after { + .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; } `); @@ -662,7 +678,7 @@ registerThemingParticipant((theme, collector) => { const debugIconBreakpointDisabledColor = theme.getColor(debugIconBreakpointDisabledForeground); if (debugIconBreakpointDisabledColor) { collector.addRule(` - .monaco-workbench .codicon[class*='-disabled'] { + ${icons.allBreakpoints.map(b => `.monaco-workbench ${ThemeIcon.asCSSSelector(b.disabled)}`).join(',\n ')} { color: ${debugIconBreakpointDisabledColor} !important; } `); @@ -671,7 +687,7 @@ registerThemingParticipant((theme, collector) => { const debugIconBreakpointUnverifiedColor = theme.getColor(debugIconBreakpointUnverifiedForeground); if (debugIconBreakpointUnverifiedColor) { collector.addRule(` - .monaco-workbench .codicon[class*='-unverified'] { + ${icons.allBreakpoints.map(b => `.monaco-workbench ${ThemeIcon.asCSSSelector(b.unverified)}`).join(',\n ')} { color: ${debugIconBreakpointUnverifiedColor}; } `); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 362d05447..cc8b39633 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -221,6 +221,9 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi this.input = scopedInstatiationService.createInstance(CodeEditorWidget, container, options, codeEditorWidgetOptions); CONTEXT_IN_BREAKPOINT_WIDGET.bindTo(scopedContextKeyService).set(true); const model = this.modelService.createModel('', null, uri.parse(`${DEBUG_SCHEME}:${this.editor.getId()}:breakpointinput`), true); + if (this.editor.hasModel()) { + model.setMode(this.editor.getModel().getLanguageIdentifier()); + } this.input.setModel(model); this.toDispose.push(model); const setDecorations = () => { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 7fa72c43f..4fb3fead1 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -3,13 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import * as resources from 'vs/base/common/resources'; import * as dom from 'vs/base/browser/dom'; -import { IAction, Action, Separator } from 'vs/base/common/actions'; -import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINTS_FOCUSED, State, DEBUG_SCHEME, IFunctionBreakpoint, IExceptionBreakpoint, IEnablement, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, IDebugModel, IDataBreakpoint } from 'vs/workbench/contrib/debug/common/debug'; +import { IAction } from 'vs/base/common/actions'; +import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINTS_FOCUSED, State, DEBUG_SCHEME, IFunctionBreakpoint, IExceptionBreakpoint, IEnablement, IDebugModel, IDataBreakpoint, BREAKPOINTS_VIEW_ID, CONTEXT_BREAKPOINT_ITEM_TYPE, CONTEXT_BREAKPOINT_SUPPORTS_CONDITION, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_IN_DEBUG_MODE, IBaseBreakpoint, IBreakpointEditorContribution, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINT_INPUT_FOCUSED } from 'vs/workbench/contrib/debug/common/debug'; import { ExceptionBreakpoint, FunctionBreakpoint, Breakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; -import { AddFunctionBreakpointAction, ToggleBreakpointsActivatedAction, RemoveAllBreakpointsAction, RemoveBreakpointAction, EnableAllBreakpointsAction, DisableAllBreakpointsAction, ReapplyBreakpointsAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -24,12 +22,11 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { WorkbenchList, ListResourceNavigator } from 'vs/platform/list/browser/listService'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; -import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane, ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; import { ILabelService } from 'vs/platform/label/common/label'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, ContextKeyEqualsExpr, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Gesture } from 'vs/base/browser/touch'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; @@ -38,6 +35,13 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Orientation } from 'vs/base/browser/ui/splitview/splitview'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; +import { registerAction2, Action2, MenuId, IMenu, IMenuService } from 'vs/platform/actions/common/actions'; +import { localize } from 'vs/nls'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { createAndFillInContextMenuActions, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Codicon } from 'vs/base/common/codicons'; const $ = dom.$; @@ -57,11 +61,21 @@ export function getExpandedBodySize(model: IDebugModel, countLimit: number): num } type BreakpointItem = IBreakpoint | IFunctionBreakpoint | IDataBreakpoint | IExceptionBreakpoint; +interface InputBoxData { + breakpoint: IFunctionBreakpoint | IExceptionBreakpoint; + type: 'condition' | 'hitCount' | 'name'; +} + export class BreakpointsView extends ViewPane { private list!: WorkbenchList; private needsRefresh = false; private ignoreLayout = false; + private menu: IMenu; + private breakpointItemType: IContextKey; + private breakpointSupportsCondition: IContextKey; + private _inputBoxData: InputBoxData | undefined; + breakpointInputFocused: IContextKey; constructor( options: IViewletViewOptions, @@ -78,26 +92,32 @@ export class BreakpointsView extends ViewPane { @IOpenerService openerService: IOpenerService, @ITelemetryService telemetryService: ITelemetryService, @ILabelService private readonly labelService: ILabelService, + @IMenuService menuService: IMenuService ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + this.menu = menuService.createMenu(MenuId.DebugBreakpointsContext, contextKeyService); + this._register(this.menu); + this.breakpointItemType = CONTEXT_BREAKPOINT_ITEM_TYPE.bindTo(contextKeyService); + this.breakpointSupportsCondition = CONTEXT_BREAKPOINT_SUPPORTS_CONDITION.bindTo(contextKeyService); + this.breakpointInputFocused = CONTEXT_BREAKPOINT_INPUT_FOCUSED.bindTo(contextKeyService); this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange())); } - public renderBody(container: HTMLElement): void { + renderBody(container: HTMLElement): void { super.renderBody(container); this.element.classList.add('debug-pane'); container.classList.add('debug-breakpoints'); - const delegate = new BreakpointsDelegate(this.debugService); + const delegate = new BreakpointsDelegate(this); this.list = >this.instantiationService.createInstance(WorkbenchList, 'Breakpoints', container, delegate, [ - this.instantiationService.createInstance(BreakpointsRenderer), - new ExceptionBreakpointsRenderer(this.debugService), - new ExceptionBreakpointInputRenderer(this.debugService, this.contextViewService, this.themeService), - this.instantiationService.createInstance(FunctionBreakpointsRenderer), + this.instantiationService.createInstance(BreakpointsRenderer, this.menu, this.breakpointSupportsCondition), + new ExceptionBreakpointsRenderer(this.menu, this.breakpointSupportsCondition, this.debugService), + new ExceptionBreakpointInputRenderer(this, this.debugService, this.contextViewService, this.themeService), + this.instantiationService.createInstance(FunctionBreakpointsRenderer, this.menu, this.breakpointSupportsCondition), this.instantiationService.createInstance(DataBreakpointsRenderer), - new FunctionBreakpointInputRenderer(this.debugService, this.contextViewService, this.themeService, this.labelService) + new FunctionBreakpointInputRenderer(this, this.debugService, this.contextViewService, this.themeService, this.labelService) ], { identityProvider: { getId: (element: IEnablement) => element.getId() }, multipleSelectionSupport: false, @@ -135,10 +155,9 @@ export class BreakpointsView extends ViewPane { if (e.element instanceof Breakpoint) { openBreakpointSource(e.element, e.sideBySide, e.editorOptions.preserveFocus || false, e.editorOptions.pinned || !e.editorOptions.preserveFocus, this.debugService, this.editorService); } - if (e.browserEvent instanceof MouseEvent && e.browserEvent.detail === 2 && e.element instanceof FunctionBreakpoint && e.element !== this.debugService.getViewModel().getSelectedBreakpoint()) { + if (e.browserEvent instanceof MouseEvent && e.browserEvent.detail === 2 && e.element instanceof FunctionBreakpoint && e.element !== this.inputBoxData?.breakpoint) { // double click - this.debugService.getViewModel().setSelectedBreakpoint(e.element); - this.onBreakpointsChange(); + this.renderInputBox({ breakpoint: e.element, type: 'name' }); } })); @@ -156,13 +175,23 @@ export class BreakpointsView extends ViewPane { })); } - public focus(): void { + focus(): void { super.focus(); if (this.list) { this.list.domFocus(); } } + renderInputBox(data: InputBoxData | undefined): void { + this._inputBoxData = data; + this.onBreakpointsChange(); + this._inputBoxData = undefined; + } + + get inputBoxData(): InputBoxData | undefined { + return this._inputBoxData; + } + protected layoutBody(height: number, width: number): void { if (this.ignoreLayout) { return; @@ -181,71 +210,25 @@ export class BreakpointsView extends ViewPane { } private onListContextMenu(e: IListContextMenuEvent): void { - if (!e.element) { - return; - } - - const actions: IAction[] = []; const element = e.element; + const type = element instanceof Breakpoint ? 'breakpoint' : element instanceof ExceptionBreakpoint ? 'exceptionBreakpoint' : + element instanceof FunctionBreakpoint ? 'functionBreakpoint' : element instanceof DataBreakpoint ? 'dataBreakpoint' : undefined; + this.breakpointItemType.set(type); + const session = this.debugService.getViewModel().focusedSession; + const conditionSupported = element instanceof ExceptionBreakpoint ? element.supportsCondition : (!session || !!session.capabilities.supportsConditionalBreakpoints); + this.breakpointSupportsCondition.set(conditionSupported); - if (element instanceof ExceptionBreakpoint) { - if (element.supportsCondition) { - actions.push(new Action('workbench.action.debug.editExceptionBreakpointCondition', nls.localize('editCondition', "Edit Condition"), '', true, async () => { - this.debugService.getViewModel().setSelectedBreakpoint(element); - this.onBreakpointsChange(); - })); - } - } else { - const breakpointType = element instanceof Breakpoint && element.logMessage ? nls.localize('Logpoint', "Logpoint") : nls.localize('Breakpoint', "Breakpoint"); - if (element instanceof Breakpoint || element instanceof FunctionBreakpoint) { - actions.push(new Action('workbench.action.debug.openEditorAndEditBreakpoint', nls.localize('editBreakpoint', "Edit {0}...", breakpointType), '', true, async () => { - if (element instanceof Breakpoint) { - const editor = await openBreakpointSource(element, false, false, true, this.debugService, this.editorService); - if (editor) { - const codeEditor = editor.getControl(); - if (isCodeEditor(codeEditor)) { - codeEditor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(element.lineNumber, element.column); - } - } - } else { - this.debugService.getViewModel().setSelectedBreakpoint(element); - this.onBreakpointsChange(); - } - })); - actions.push(new Separator()); - } - - - actions.push(new RemoveBreakpointAction(RemoveBreakpointAction.ID, nls.localize('removeBreakpoint', "Remove {0}", breakpointType), this.debugService)); - - if (this.debugService.getModel().getBreakpoints().length + this.debugService.getModel().getFunctionBreakpoints().length >= 1) { - actions.push(new RemoveAllBreakpointsAction(RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL, this.debugService, this.keybindingService)); - actions.push(new Separator()); - - actions.push(new EnableAllBreakpointsAction(EnableAllBreakpointsAction.ID, EnableAllBreakpointsAction.LABEL, this.debugService, this.keybindingService)); - actions.push(new DisableAllBreakpointsAction(DisableAllBreakpointsAction.ID, DisableAllBreakpointsAction.LABEL, this.debugService, this.keybindingService)); - } - - actions.push(new Separator()); - actions.push(new ReapplyBreakpointsAction(ReapplyBreakpointsAction.ID, ReapplyBreakpointsAction.LABEL, this.debugService, this.keybindingService)); - } + const secondary: IAction[] = []; + const actionsDisposable = createAndFillInContextMenuActions(this.menu, { arg: e.element, shouldForwardArgs: false }, { primary: [], secondary }, g => /^inline/.test(g)); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, - getActions: () => actions, + getActions: () => secondary, getActionsContext: () => element, - onHide: () => dispose(actions) + onHide: () => dispose(actionsDisposable) }); } - public getActions(): IAction[] { - return [ - new AddFunctionBreakpointAction(AddFunctionBreakpointAction.ID, AddFunctionBreakpointAction.LABEL, this.debugService, this.keybindingService), - new ToggleBreakpointsActivatedAction(ToggleBreakpointsActivatedAction.ID, ToggleBreakpointsActivatedAction.ACTIVATE_LABEL, this.debugService, this.keybindingService), - new RemoveAllBreakpointsAction(RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL, this.debugService, this.keybindingService) - ]; - } - private updateSize(): void { const containerModel = this.viewDescriptorService.getViewContainerModel(this.viewDescriptorService.getViewContainerByViewId(this.id)!)!; @@ -282,7 +265,7 @@ export class BreakpointsView extends ViewPane { class BreakpointsDelegate implements IListVirtualDelegate { - constructor(private debugService: IDebugService) { + constructor(private view: BreakpointsView) { // noop } @@ -295,16 +278,16 @@ class BreakpointsDelegate implements IListVirtualDelegate { return BreakpointsRenderer.ID; } if (element instanceof FunctionBreakpoint) { - const selected = this.debugService.getViewModel().getSelectedBreakpoint(); - if (!element.name || (selected && selected.getId() === element.getId())) { + const inputBoxBreakpoint = this.view.inputBoxData?.breakpoint; + if (!element.name || (inputBoxBreakpoint && inputBoxBreakpoint.getId() === element.getId())) { return FunctionBreakpointInputRenderer.ID; } return FunctionBreakpointsRenderer.ID; } if (element instanceof ExceptionBreakpoint) { - const selected = this.debugService.getViewModel().getSelectedBreakpoint(); - if (selected && selected.getId() === element.getId()) { + const inputBoxBreakpoint = this.view.inputBoxData?.breakpoint; + if (inputBoxBreakpoint && inputBoxBreakpoint.getId() === element.getId()) { return ExceptionBreakpointInputRenderer.ID; } return ExceptionBreakpointsRenderer.ID; @@ -322,7 +305,9 @@ interface IBaseBreakpointTemplateData { name: HTMLElement; checkbox: HTMLInputElement; context: BreakpointItem; + actionBar: ActionBar; toDispose: IDisposable[]; + elementDisposable: IDisposable[]; } interface IBaseBreakpointWithIconTemplateData extends IBaseBreakpointTemplateData { @@ -338,26 +323,31 @@ interface IExceptionBreakpointTemplateData extends IBaseBreakpointTemplateData { condition: HTMLElement; } +interface IFunctionBreakpointTemplateData extends IBaseBreakpointWithIconTemplateData { + condition: HTMLElement; +} + interface IFunctionBreakpointInputTemplateData { inputBox: InputBox; checkbox: HTMLInputElement; icon: HTMLElement; breakpoint: IFunctionBreakpoint; - reactedOnEvent: boolean; toDispose: IDisposable[]; + type: 'hitCount' | 'condition' | 'name'; } interface IExceptionBreakpointInputTemplateData { inputBox: InputBox; checkbox: HTMLInputElement; breakpoint: IExceptionBreakpoint; - reactedOnEvent: boolean; toDispose: IDisposable[]; } class BreakpointsRenderer implements IListRenderer { constructor( + private menu: IMenu, + private breakpointSupportsCondition: IContextKey, @IDebugService private readonly debugService: IDebugService, @ILabelService private readonly labelService: ILabelService ) { @@ -377,6 +367,7 @@ class BreakpointsRenderer implements IListRenderer { this.debugService.enableOrDisableBreakpoints(!data.context.enabled, data.context); })); @@ -387,6 +378,8 @@ class BreakpointsRenderer implements IListRenderer /^inline/.test(g))); + data.actionBar.clear(); + data.actionBar.push(primary, { icon: true, label: false }); + } + + disposeElement(_element: IBreakpoint, _index: number, templateData: IBreakpointTemplateData): void { + dispose(templateData.elementDisposable); } disposeTemplate(templateData: IBreakpointTemplateData): void { @@ -423,6 +427,8 @@ class BreakpointsRenderer implements IListRenderer { constructor( + private menu: IMenu, + private breakpointSupportsCondition: IContextKey, private debugService: IDebugService ) { // noop @@ -440,6 +446,7 @@ class ExceptionBreakpointsRenderer implements IListRenderer { this.debugService.enableOrDisableBreakpoints(!data.context.enabled, data.context); })); @@ -450,6 +457,8 @@ class ExceptionBreakpointsRenderer implements IListRenderer /^inline/.test(g))); + data.actionBar.clear(); + data.actionBar.push(primary, { icon: true, label: false }); + } + + disposeElement(_element: IExceptionBreakpoint, _index: number, templateData: IExceptionBreakpointTemplateData): void { + dispose(templateData.elementDisposable); } disposeTemplate(templateData: IExceptionBreakpointTemplateData): void { @@ -467,9 +486,11 @@ class ExceptionBreakpointsRenderer implements IListRenderer { +class FunctionBreakpointsRenderer implements IListRenderer { constructor( + private menu: IMenu, + private breakpointSupportsCondition: IContextKey, @IDebugService private readonly debugService: IDebugService, @ILabelService private readonly labelService: ILabelService ) { @@ -482,13 +503,14 @@ class FunctionBreakpointsRenderer implements IListRenderer { this.debugService.enableOrDisableBreakpoints(!data.context.enabled, data.context); })); @@ -497,11 +519,15 @@ class FunctionBreakpointsRenderer implements IListRenderer /^inline/.test(g))); + data.actionBar.clear(); + data.actionBar.push(primary, { icon: true, label: false }); } - disposeTemplate(templateData: IBaseBreakpointWithIconTemplateData): void { + disposeElement(_element: IFunctionBreakpoint, _index: number, templateData: IFunctionBreakpointTemplateData): void { + dispose(templateData.elementDisposable); + } + + disposeTemplate(templateData: IFunctionBreakpointTemplateData): void { dispose(templateData.toDispose); } } @@ -570,7 +611,7 @@ class DataBreakpointsRenderer implements IListRenderer { constructor( + private view: BreakpointsView, private debugService: IDebugService, private contextViewService: IContextViewService, private themeService: IThemeService, private labelService: ILabelService - ) { - // noop - } + ) { } static readonly ID = 'functionbreakpointinput'; @@ -605,22 +645,33 @@ class FunctionBreakpointInputRenderer implements IListRenderer { - if (!template.reactedOnEvent) { - template.reactedOnEvent = true; - this.debugService.getViewModel().setSelectedBreakpoint(undefined); - if (inputBox.value && (renamed || template.breakpoint.name)) { - this.debugService.renameFunctionBreakpoint(template.breakpoint.getId(), renamed ? inputBox.value : template.breakpoint.name); + const wrapUp = (success: boolean) => { + this.view.breakpointInputFocused.set(false); + const id = template.breakpoint.getId(); + + if (success) { + if (template.type === 'name') { + this.debugService.updateFunctionBreakpoint(id, { name: inputBox.value }); + } + if (template.type === 'condition') { + this.debugService.updateFunctionBreakpoint(id, { condition: inputBox.value }); + } + if (template.type === 'hitCount') { + this.debugService.updateFunctionBreakpoint(id, { hitCondition: inputBox.value }); + } + } else { + if (template.type === 'name' && !template.breakpoint.name) { + this.debugService.removeFunctionBreakpoints(id); } else { - this.debugService.removeFunctionBreakpoints(template.breakpoint.getId()); + this.view.renderInputBox(undefined); } } }; @@ -650,7 +701,7 @@ class FunctionBreakpointInputRenderer implements IListRenderer { data.inputBox.focus(); data.inputBox.select(); @@ -672,6 +738,7 @@ class FunctionBreakpointInputRenderer implements IListRenderer { constructor( + private view: BreakpointsView, private debugService: IDebugService, private contextViewService: IContextViewService, private themeService: IThemeService @@ -693,24 +760,22 @@ class ExceptionBreakpointInputRenderer implements IListRenderer { - if (!template.reactedOnEvent) { - template.reactedOnEvent = true; - this.debugService.getViewModel().setSelectedBreakpoint(undefined); - let newCondition = template.breakpoint.condition; - if (success) { - newCondition = inputBox.value !== '' ? inputBox.value : undefined; - } - this.debugService.setExceptionBreakpointCondition(template.breakpoint, newCondition); + this.view.breakpointInputFocused.set(false); + let newCondition = template.breakpoint.condition; + if (success) { + newCondition = inputBox.value !== '' ? inputBox.value : undefined; } + this.debugService.setExceptionBreakpointCondition(template.breakpoint, newCondition); }; toDispose.push(dom.addStandardDisposableListener(inputBox.inputElement, 'keydown', (e: IKeyboardEvent) => { @@ -736,7 +801,6 @@ class ExceptionBreakpointInputRenderer implements IListRenderer { + const debugService = accessor.get(IDebugService); + if (breakpoint instanceof Breakpoint) { + await debugService.removeBreakpoints(breakpoint.getId()); + } else if (breakpoint instanceof FunctionBreakpoint) { + await debugService.removeFunctionBreakpoints(breakpoint.getId()); + } else if (breakpoint instanceof DataBreakpoint) { + await debugService.removeDataBreakpoints(breakpoint.getId()); + } + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.debug.viewlet.action.removeAllBreakpoints', + title: { + original: 'Remove All Breakpoints', + value: localize('removeAllBreakpoints', "Remove All Breakpoints"), + mnemonicTitle: localize({ key: 'miRemoveAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Remove &&All Breakpoints") + }, + f1: true, + icon: icons.breakpointsRemoveAll, + menu: [{ + id: MenuId.ViewTitle, + group: 'navigation', + order: 30, + when: ContextKeyEqualsExpr.create('view', BREAKPOINTS_VIEW_ID) + }, { + id: MenuId.DebugBreakpointsContext, + group: '3_modification', + order: 20, + when: ContextKeyExpr.and(CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAKPOINT_ITEM_TYPE.notEqualsTo('exceptionBreakpoint')) + }, { + id: MenuId.MenubarDebugMenu, + group: '5_breakpoints', + order: 3, + when: CONTEXT_DEBUGGERS_AVAILABLE + }] + }); + } + + run(accessor: ServicesAccessor): void { + const debugService = accessor.get(IDebugService); + debugService.removeBreakpoints(); + debugService.removeFunctionBreakpoints(); + debugService.removeDataBreakpoints(); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.debug.viewlet.action.enableAllBreakpoints', + title: { + original: '', + value: localize('enableAllBreakpoints', "Enable All Breakpoints"), + mnemonicTitle: localize({ key: 'miEnableAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "&&Enable All Breakpoints"), + }, + f1: true, + precondition: CONTEXT_DEBUGGERS_AVAILABLE, + menu: [{ + id: MenuId.DebugBreakpointsContext, + group: 'z_commands', + order: 10, + when: ContextKeyExpr.and(CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAKPOINT_ITEM_TYPE.notEqualsTo('exceptionBreakpoint')) + }, { + id: MenuId.MenubarDebugMenu, + group: '5_breakpoints', + order: 1, + when: CONTEXT_DEBUGGERS_AVAILABLE + }] + }); + } + + async run(accessor: ServicesAccessor): Promise { + const debugService = accessor.get(IDebugService); + await debugService.enableOrDisableBreakpoints(true); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.debug.viewlet.action.disableAllBreakpoints', + title: { + original: 'Disable All Breakpoints', + value: localize('disableAllBreakpoints', "Disable All Breakpoints"), + mnemonicTitle: localize({ key: 'miDisableAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Disable A&&ll Breakpoints") + }, + f1: true, + precondition: CONTEXT_DEBUGGERS_AVAILABLE, + menu: [{ + id: MenuId.DebugBreakpointsContext, + group: 'z_commands', + order: 20, + when: ContextKeyExpr.and(CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAKPOINT_ITEM_TYPE.notEqualsTo('exceptionBreakpoint')) + }, { + id: MenuId.MenubarDebugMenu, + group: '5_breakpoints', + order: 2, + + when: CONTEXT_DEBUGGERS_AVAILABLE + }] + }); + } + + async run(accessor: ServicesAccessor): Promise { + const debugService = accessor.get(IDebugService); + await debugService.enableOrDisableBreakpoints(false); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.debug.viewlet.action.reapplyBreakpointsAction', + title: localize('reapplyAllBreakpoints', "Reapply All Breakpoints"), + f1: true, + precondition: CONTEXT_IN_DEBUG_MODE, + menu: [{ + id: MenuId.DebugBreakpointsContext, + group: 'z_commands', + order: 30, + when: ContextKeyExpr.and(CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAKPOINT_ITEM_TYPE.notEqualsTo('exceptionBreakpoint')) + }] + }); + } + + async run(accessor: ServicesAccessor): Promise { + const debugService = accessor.get(IDebugService); + await debugService.setBreakpointsActivated(true); + } +}); + +registerAction2(class extends ViewAction { + constructor() { + super({ + id: 'debug.editBreakpoint', + viewId: BREAKPOINTS_VIEW_ID, + title: localize('editCondition', "Edit Condition..."), + icon: Codicon.edit, + precondition: CONTEXT_BREAKPOINT_SUPPORTS_CONDITION, + menu: [{ + id: MenuId.DebugBreakpointsContext, + group: 'navigation', + order: 10 + }, { + id: MenuId.DebugBreakpointsContext, + group: 'inline', + order: 10 + }] + }); + } + + async runInView(accessor: ServicesAccessor, view: BreakpointsView, breakpoint: ExceptionBreakpoint | Breakpoint | FunctionBreakpoint): Promise { + const debugService = accessor.get(IDebugService); + const editorService = accessor.get(IEditorService); + if (breakpoint instanceof Breakpoint) { + const editor = await openBreakpointSource(breakpoint, false, false, true, debugService, editorService); + if (editor) { + const codeEditor = editor.getControl(); + if (isCodeEditor(codeEditor)) { + codeEditor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(breakpoint.lineNumber, breakpoint.column); + } + } + } else { + view.renderInputBox({ breakpoint, type: 'condition' }); + } + } +}); + + +registerAction2(class extends ViewAction { + constructor() { + super({ + id: 'debug.editFunctionBreakpoint', + viewId: BREAKPOINTS_VIEW_ID, + title: localize('editBreakpoint', "Edit Function Breakpoint..."), + menu: [{ + id: MenuId.DebugBreakpointsContext, + group: '1_breakpoints', + order: 10, + when: CONTEXT_BREAKPOINT_ITEM_TYPE.isEqualTo('functionBreakpoint') + }] + }); + } + + runInView(_accessor: ServicesAccessor, view: BreakpointsView, breakpoint: IFunctionBreakpoint) { + view.renderInputBox({ breakpoint, type: 'name' }); + } +}); + +registerAction2(class extends ViewAction { + constructor() { + super({ + id: 'debug.editFunctionBreakpointHitCount', + viewId: BREAKPOINTS_VIEW_ID, + title: localize('editHitCount', "Edit Hit Count..."), + precondition: CONTEXT_BREAKPOINT_SUPPORTS_CONDITION, + menu: [{ + id: MenuId.DebugBreakpointsContext, + group: 'navigation', + order: 20, + when: CONTEXT_BREAKPOINT_ITEM_TYPE.isEqualTo('functionBreakpoint') + }] + }); + } + + runInView(_accessor: ServicesAccessor, view: BreakpointsView, breakpoint: IFunctionBreakpoint) { + view.renderInputBox({ breakpoint, type: 'hitCount' }); + } +}); diff --git a/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts index 9fd5d78b0..fa7901ea3 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts @@ -127,17 +127,21 @@ export class CallStackEditorContribution implements IEditorContribution { const isSessionFocused = s === focusedStackFrame?.thread.session; s.getAllThreads().forEach(t => { if (t.stopped) { - let candidateStackFrame = t === focusedStackFrame?.thread ? focusedStackFrame : undefined; - if (!candidateStackFrame) { - const callStack = t.getCallStack(); - if (callStack.length) { - candidateStackFrame = callStack[0]; + const callStack = t.getCallStack(); + const stackFrames: IStackFrame[] = []; + if (callStack.length > 0) { + // Always decorate top stack frame, and decorate focused stack frame if it is not the top stack frame + if (focusedStackFrame && !focusedStackFrame.equals(callStack[0])) { + stackFrames.push(focusedStackFrame); } + stackFrames.push(callStack[0]); } - if (candidateStackFrame && this.uriIdentityService.extUri.isEqual(candidateStackFrame.source.uri, this.editor.getModel()?.uri)) { - decorations.push(...createDecorationsForStackFrame(candidateStackFrame, this.topStackFrameRange, isSessionFocused)); - } + stackFrames.forEach(candidateStackFrame => { + if (candidateStackFrame && this.uriIdentityService.extUri.isEqual(candidateStackFrame.source.uri, this.editor.getModel()?.uri)) { + decorations.push(...createDecorationsForStackFrame(candidateStackFrame, this.topStackFrameRange, isSessionFocused)); + } + }); } }); }); diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 70851479d..1fc41463c 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -3,22 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import { RunOnceScheduler } from 'vs/base/common/async'; import * as dom from 'vs/base/browser/dom'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { IDebugService, State, IStackFrame, IDebugSession, IThread, CONTEXT_CALLSTACK_ITEM_TYPE, IDebugModel } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, State, IStackFrame, IDebugSession, IThread, CONTEXT_CALLSTACK_ITEM_TYPE, IDebugModel, CALLSTACK_VIEW_ID, CONTEXT_DEBUG_STATE, getStateLabel } from 'vs/workbench/contrib/debug/common/debug'; import { Thread, StackFrame, ThreadAndSessionIds } from 'vs/workbench/contrib/debug/common/debugModel'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { MenuId, IMenu, IMenuService, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { MenuId, IMenu, IMenuService, MenuItemAction, SubmenuItemAction, registerAction2 } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IAction, Action } from 'vs/base/common/actions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IContextKey, IContextKeyService, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ViewPane, ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; import { ILabelService } from 'vs/platform/label/common/label'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { createAndFillInContextMenuActions, createAndFillInActionBarActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -33,7 +32,6 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { isSessionAttach } from 'vs/workbench/contrib/debug/common/debugUtils'; import { STOP_ID, STOP_LABEL, DISCONNECT_ID, DISCONNECT_LABEL, RESTART_SESSION_ID, RESTART_LABEL, STEP_OVER_ID, STEP_OVER_LABEL, STEP_INTO_LABEL, STEP_INTO_ID, STEP_OUT_LABEL, STEP_OUT_ID, PAUSE_ID, PAUSE_LABEL, CONTINUE_ID, CONTINUE_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { CollapseAction } from 'vs/workbench/browser/viewlet'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; @@ -47,6 +45,8 @@ import { ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree' import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; +import { localize } from 'vs/nls'; +import { Codicon } from 'vs/base/common/codicons'; const $ = dom.$; @@ -162,7 +162,7 @@ export class CallStackView extends ViewPane { this.stateMessageLabel.classList.toggle('exception', thread.stoppedDetails.reason === 'exception'); this.stateMessage.hidden = false; } else if (sessions.length === 1 && sessions[0].state === State.Running) { - this.stateMessageLabel.textContent = nls.localize({ key: 'running', comment: ['indicates state'] }, "Running"); + this.stateMessageLabel.textContent = localize({ key: 'running', comment: ['indicates state'] }, "Running"); this.stateMessageLabel.title = sessions[0].getLabel(); this.stateMessageLabel.classList.remove('exception'); this.stateMessage.hidden = false; @@ -205,14 +205,6 @@ export class CallStackView extends ViewPane { this.stateMessageLabel = dom.append(this.stateMessage, $('span.label')); } - getActions(): IAction[] { - if (this.stateMessage.hidden) { - return [new CollapseAction(() => this.tree, true, 'explorer-action ' + ThemeIcon.asClassName(icons.debugCollapseAll))]; - } - - return []; - } - renderBody(container: HTMLElement): void { super.renderBody(container); this.element.classList.add('debug-pane'); @@ -220,11 +212,11 @@ export class CallStackView extends ViewPane { const treeContainer = renderViewTree(container); this.dataSource = new CallStackDataSource(this.debugService); - const sessionsRenderer = this.instantiationService.createInstance(SessionsRenderer, this.menu); + const sessionsRenderer = this.instantiationService.createInstance(SessionsRenderer, this.menu, this.callStackItemType); this.tree = >this.instantiationService.createInstance(WorkbenchCompressibleAsyncDataTree, 'CallStackView', treeContainer, new CallStackDelegate(), new CallStackCompressionDelegate(this.debugService), [ sessionsRenderer, - new ThreadsRenderer(this.instantiationService), - this.instantiationService.createInstance(StackFramesRenderer), + new ThreadsRenderer(this.callStackItemType, this.instantiationService), + this.instantiationService.createInstance(StackFramesRenderer, this.callStackItemType), new ErrorsRenderer(), new LoadAllRenderer(this.themeService), new ShowMoreRenderer(this.themeService) @@ -259,7 +251,7 @@ export class CallStackView extends ViewPane { return LoadAllRenderer.LABEL; } - return nls.localize('showMoreStackFrames2', "Show More Stack Frames"); + return localize('showMoreStackFrames2', "Show More Stack Frames"); }, getCompressedNodeKeyboardNavigationLabel: (e: CallStackItem[]) => { const firstItem = e[0]; @@ -378,6 +370,10 @@ export class CallStackView extends ViewPane { this.tree.domFocus(); } + collapseAll(): void { + this.tree.collapseAll(); + } + private async updateTreeSelection(): Promise { if (!this.tree || !this.tree.getInput()) { // Tree not initialized yet @@ -493,6 +489,7 @@ class SessionsRenderer implements ICompressibleTreeRenderer, @IInstantiationService private readonly instantiationService: IInstantiationService ) { } @@ -532,7 +529,7 @@ class SessionsRenderer implements ICompressibleTreeRenderer t.stopped); @@ -542,6 +539,7 @@ class SessionsRenderer implements ICompressibleTreeRenderer /^inline/.test(g))); data.actionBar.clear(); @@ -557,7 +555,7 @@ class SessionsRenderer implements ICompressibleTreeRenderer { static readonly ID = 'thread'; - constructor(private readonly instantiationService: IInstantiationService) { } + constructor( + private callStackItemType: IContextKey, + private readonly instantiationService: IInstantiationService + ) { } get templateId(): string { return ThreadsRenderer.ID; @@ -591,11 +592,12 @@ class ThreadsRenderer implements ICompressibleTreeRenderer, index: number, data: IThreadTemplateData): void { const thread = element.element; - data.thread.title = nls.localize('thread', "Thread"); + data.thread.title = localize('thread', "Thread"); data.label.set(thread.name, createMatches(element.filterData)); data.stateLabel.textContent = thread.stateLabel; data.actionBar.clear(); + this.callStackItemType.set('thread'); const actions = getActions(this.instantiationService, thread); data.actionBar.push(actions, { icon: true, label: false }); } @@ -613,8 +615,9 @@ class StackFramesRenderer implements ICompressibleTreeRenderer, @ILabelService private readonly labelService: ILabelService, - @INotificationService private readonly notificationService: INotificationService + @INotificationService private readonly notificationService: INotificationService, ) { } get templateId(): string { @@ -659,8 +662,9 @@ class StackFramesRenderer implements ICompressibleTreeRenderer { + const action = new Action('debug.callStack.restartFrame', localize('restartFrame', "Restart Frame"), ThemeIcon.asClassName(icons.debugRestartFrame), true, async () => { try { await stackFrame.restart(); } catch (e) { @@ -710,7 +714,7 @@ class ErrorsRenderer implements ICompressibleTreeRenderer { static readonly ID = 'loadAll'; - static readonly LABEL = nls.localize('loadAllStackFrames', "Load All Stack Frames"); + static readonly LABEL = localize('loadAllStackFrames', "Load All Stack Frames"); constructor(private readonly themeService: IThemeService) { } @@ -766,9 +770,9 @@ class ShowMoreRenderer implements ICompressibleTreeRenderer, index: number, data: ILabelTemplateData): void { const stackFrames = element.element; if (stackFrames.every(sf => !!(sf.source && sf.source.origin && sf.source.origin === stackFrames[0].source.origin))) { - data.label.textContent = nls.localize('showMoreAndOrigin', "Show {0} More: {1}", stackFrames.length, stackFrames[0].source.origin); + data.label.textContent = localize('showMoreAndOrigin', "Show {0} More: {1}", stackFrames.length, stackFrames[0].source.origin); } else { - data.label.textContent = nls.localize('showMoreStackFrames', "Show {0} More Stack Frames", stackFrames.length); + data.label.textContent = localize('showMoreStackFrames', "Show {0} More Stack Frames", stackFrames.length); } } @@ -930,26 +934,26 @@ class CallStackDataSource implements IAsyncDataSource { getWidgetAriaLabel(): string { - return nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'callStackAriaLabel' }, "Debug Call Stack"); + return localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'callStackAriaLabel' }, "Debug Call Stack"); } getAriaLabel(element: CallStackItem): string { if (element instanceof Thread) { - return nls.localize({ key: 'threadAriaLabel', comment: ['Placeholders stand for the thread name and the thread state.For example "Thread 1" and "Stopped'] }, "Thread {0} {1}", element.name, element.stateLabel); + return localize({ key: 'threadAriaLabel', comment: ['Placeholders stand for the thread name and the thread state.For example "Thread 1" and "Stopped'] }, "Thread {0} {1}", element.name, element.stateLabel); } if (element instanceof StackFrame) { - return nls.localize('stackFrameAriaLabel', "Stack Frame {0}, line {1}, {2}", element.name, element.range.startLineNumber, getSpecificSourceName(element)); + return localize('stackFrameAriaLabel', "Stack Frame {0}, line {1}, {2}", element.name, element.range.startLineNumber, getSpecificSourceName(element)); } if (isDebugSession(element)) { const thread = element.getAllThreads().find(t => t.stopped); - const state = thread ? thread.stateLabel : nls.localize({ key: 'running', comment: ['indicates state'] }, "Running"); - return nls.localize({ key: 'sessionLabel', comment: ['Placeholders stand for the session name and the session state. For example "Launch Program" and "Running"'] }, "Session {0} {1}", element.getLabel(), state); + const state = thread ? thread.stateLabel : localize({ key: 'running', comment: ['indicates state'] }, "Running"); + return localize({ key: 'sessionLabel', comment: ['Placeholders stand for the session name and the session state. For example "Launch Program" and "Running"'] }, "Session {0} {1}", element.getLabel(), state); } if (typeof element === 'string') { return element; } if (element instanceof Array) { - return nls.localize('showMoreStackFrames', "Show {0} More Stack Frames", element.length); + return localize('showMoreStackFrames', "Show {0} More Stack Frames", element.length); } // element instanceof ThreadAndSessionIds @@ -1121,3 +1125,26 @@ class CallStackCompressionDelegate implements ITreeCompressionDelegate { + constructor() { + super({ + id: 'callStack.collapse', + viewId: CALLSTACK_VIEW_ID, + title: localize('collapse', "Collapse All"), + f1: false, + icon: Codicon.collapseAll, + precondition: CONTEXT_DEBUG_STATE.isEqualTo(getStateLabel(State.Stopped)), + menu: { + id: MenuId.ViewTitle, + order: 10, + group: 'navigation', + when: ContextKeyEqualsExpr.create('view', CALLSTACK_VIEW_ID) + } + }); + } + + runInView(_accessor: ServicesAccessor, view: CallStackView) { + view.collapseAll(); + } +}); diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 9438ac11c..a2f0090e4 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -17,12 +17,11 @@ import { CallStackView } from 'vs/workbench/contrib/debug/browser/callStackView' import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IDebugService, VIEWLET_ID, DEBUG_PANEL_ID, CONTEXT_IN_DEBUG_MODE, INTERNAL_CONSOLE_OPTIONS_SCHEMA, - CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_DEBUG_UX, BREAKPOINT_EDITOR_CONTRIBUTION_ID, REPL_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, EDITOR_CONTRIBUTION_ID, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, + CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_DEBUG_UX, BREAKPOINT_EDITOR_CONTRIBUTION_ID, REPL_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, EDITOR_CONTRIBUTION_ID, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, getStateLabel, State, CONTEXT_WATCH_ITEM_TYPE, } from 'vs/workbench/contrib/debug/common/debug'; -import { StartAction, AddFunctionBreakpointAction, ConfigureAction, DisableAllBreakpointsAction, EnableAllBreakpointsAction, RemoveAllBreakpointsAction, RunAction, ReapplyBreakpointsAction, SelectAndStartAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { DebugToolBar } from 'vs/workbench/contrib/debug/browser/debugToolBar'; import { DebugService } from 'vs/workbench/contrib/debug/browser/debugService'; -import { registerCommands, ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_ID, REVERSE_CONTINUE_ID, STEP_BACK_ID, RESTART_SESSION_ID, TERMINATE_THREAD_ID, STEP_OVER_ID, STEP_INTO_ID, STEP_OUT_ID, PAUSE_ID, DISCONNECT_ID, STOP_ID, RESTART_FRAME_ID, CONTINUE_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, RESTART_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, STEP_OUT_LABEL, PAUSE_LABEL, DISCONNECT_LABEL, STOP_LABEL, CONTINUE_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands'; +import { registerCommands, ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_ID, RESTART_SESSION_ID, TERMINATE_THREAD_ID, STEP_OVER_ID, STEP_INTO_ID, STEP_OUT_ID, PAUSE_ID, DISCONNECT_ID, STOP_ID, RESTART_FRAME_ID, CONTINUE_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, RESTART_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, STEP_OUT_LABEL, PAUSE_LABEL, DISCONNECT_LABEL, STOP_LABEL, CONTINUE_LABEL, DEBUG_START_LABEL, DEBUG_START_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_RUN_COMMAND_ID, EDIT_EXPRESSION_COMMAND_ID, REMOVE_EXPRESSION_COMMAND_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { StatusBarColorProvider } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider'; import { IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views'; import { isMacintosh, isWeb } from 'vs/base/common/platform'; @@ -32,14 +31,13 @@ import { DebugStatusContribution } from 'vs/workbench/contrib/debug/browser/debu import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { LoadedScriptsView } from 'vs/workbench/contrib/debug/browser/loadedScriptsView'; -import { ADD_LOG_POINT_ID, TOGGLE_CONDITIONAL_BREAKPOINT_ID, TOGGLE_BREAKPOINT_ID, RunToCursorAction, registerEditorActions } from 'vs/workbench/contrib/debug/browser/debugEditorActions'; -import { WatchExpressionsView } from 'vs/workbench/contrib/debug/browser/watchExpressionsView'; +import { RunToCursorAction, registerEditorActions } from 'vs/workbench/contrib/debug/browser/debugEditorActions'; +import { WatchExpressionsView, ADD_WATCH_LABEL, REMOVE_WATCH_EXPRESSIONS_COMMAND_ID, REMOVE_WATCH_EXPRESSIONS_LABEL, ADD_WATCH_ID } from 'vs/workbench/contrib/debug/browser/watchExpressionsView'; import { VariablesView, SET_VARIABLE_ID, COPY_VALUE_ID, BREAK_WHEN_VALUE_CHANGES_ID, COPY_EVALUATE_PATH_ID, ADD_TO_WATCH_ID } from 'vs/workbench/contrib/debug/browser/variablesView'; -import { ClearReplAction, Repl } from 'vs/workbench/contrib/debug/browser/repl'; +import { Repl } from 'vs/workbench/contrib/debug/browser/repl'; import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider'; import { WelcomeView } from 'vs/workbench/contrib/debug/browser/welcomeView'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { DebugViewPaneContainer, OpenDebugConsoleAction, OpenDebugViewletAction } from 'vs/workbench/contrib/debug/browser/debugViewlet'; +import { DebugViewPaneContainer, OpenDebugViewletAction, OPEN_REPL_COMMAND_ID } from 'vs/workbench/contrib/debug/browser/debugViewlet'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { CallStackEditorContribution } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution'; import { BreakpointEditorContribution } from 'vs/workbench/contrib/debug/browser/breakpointEditorContribution'; @@ -56,7 +54,6 @@ import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; const registry = Registry.as(WorkbenchActionRegistryExtensions.WorkbenchActions); const debugCategory = nls.localize('debugCategory', "Debug"); -const runCategroy = nls.localize('runCategory', "Run"); registerWorkbenchContributions(); registerColors(); registerCommandsAndActions(); @@ -64,8 +61,6 @@ registerDebugMenu(); registerEditorActions(); registerCommands(); registerDebugPanel(); -registry.registerWorkbenchAction(SyncActionDescriptor.from(StartAction, { primary: KeyCode.F5 }, CONTEXT_IN_DEBUG_MODE.toNegated()), 'Debug: Start Debugging', debugCategory, CONTEXT_DEBUGGERS_AVAILABLE); -registry.registerWorkbenchAction(SyncActionDescriptor.from(RunAction, { primary: KeyMod.CtrlCmd | KeyCode.F5, mac: { primary: KeyMod.WinCtrl | KeyCode.F5 } }), 'Run: Start Without Debugging', runCategroy, CONTEXT_DEBUGGERS_AVAILABLE); registerSingleton(IDebugService, DebugService, true); registerDebugView(); @@ -102,18 +97,10 @@ function regsiterEditorContributions(): void { function registerCommandsAndActions(): void { - registry.registerWorkbenchAction(SyncActionDescriptor.from(ConfigureAction), 'Debug: Open launch.json', debugCategory, CONTEXT_DEBUGGERS_AVAILABLE); - registry.registerWorkbenchAction(SyncActionDescriptor.from(AddFunctionBreakpointAction), 'Debug: Add Function Breakpoint', debugCategory, CONTEXT_DEBUGGERS_AVAILABLE); - registry.registerWorkbenchAction(SyncActionDescriptor.from(ReapplyBreakpointsAction), 'Debug: Reapply All Breakpoints', debugCategory, CONTEXT_DEBUGGERS_AVAILABLE); - registry.registerWorkbenchAction(SyncActionDescriptor.from(RemoveAllBreakpointsAction), 'Debug: Remove All Breakpoints', debugCategory, CONTEXT_DEBUGGERS_AVAILABLE); - registry.registerWorkbenchAction(SyncActionDescriptor.from(EnableAllBreakpointsAction), 'Debug: Enable All Breakpoints', debugCategory, CONTEXT_DEBUGGERS_AVAILABLE); - registry.registerWorkbenchAction(SyncActionDescriptor.from(DisableAllBreakpointsAction), 'Debug: Disable All Breakpoints', debugCategory, CONTEXT_DEBUGGERS_AVAILABLE); - registry.registerWorkbenchAction(SyncActionDescriptor.from(SelectAndStartAction), 'Debug: Select and Start Debugging', debugCategory, CONTEXT_DEBUGGERS_AVAILABLE); - registry.registerWorkbenchAction(SyncActionDescriptor.from(ClearReplAction), 'Debug: Clear Console', debugCategory, CONTEXT_DEBUGGERS_AVAILABLE); - const registerDebugCommandPaletteItem = (id: string, title: string, when?: ContextKeyExpression, precondition?: ContextKeyExpression) => { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { when: ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, when), + group: debugCategory, command: { id, title: `Debug: ${title}`, @@ -136,33 +123,8 @@ function registerCommandsAndActions(): void { registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, nls.localize('SetNextStatement', "Set Next Statement"), CONTEXT_JUMP_TO_CURSOR_SUPPORTED); registerDebugCommandPaletteItem(RunToCursorAction.ID, RunToCursorAction.LABEL, ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'))); registerDebugCommandPaletteItem(TOGGLE_INLINE_BREAKPOINT_ID, nls.localize('inlineBreakpoint', "Inline Breakpoint")); - - // Debug toolbar - - const registerDebugToolBarItem = (id: string, title: string, order: number, icon: { light?: URI, dark?: URI } | ThemeIcon, when?: ContextKeyExpression, precondition?: ContextKeyExpression) => { - MenuRegistry.appendMenuItem(MenuId.DebugToolBar, { - group: 'navigation', - when, - order, - command: { - id, - title, - icon, - precondition - } - }); - }; - - registerDebugToolBarItem(CONTINUE_ID, CONTINUE_LABEL, 10, icons.debugContinue, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); - registerDebugToolBarItem(PAUSE_ID, PAUSE_LABEL, 10, icons.debugPause, CONTEXT_DEBUG_STATE.notEqualsTo('stopped'), CONTEXT_DEBUG_STATE.isEqualTo('running')); - registerDebugToolBarItem(STOP_ID, STOP_LABEL, 70, icons.debugStop, CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated()); - registerDebugToolBarItem(DISCONNECT_ID, DISCONNECT_LABEL, 70, icons.debugDisconnect, CONTEXT_FOCUSED_SESSION_IS_ATTACH); - registerDebugToolBarItem(STEP_OVER_ID, STEP_OVER_LABEL, 20, icons.debugStepOver, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); - registerDebugToolBarItem(STEP_INTO_ID, STEP_INTO_LABEL, 30, icons.debugStepInto, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); - registerDebugToolBarItem(STEP_OUT_ID, STEP_OUT_LABEL, 40, icons.debugStepOut, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); - registerDebugToolBarItem(RESTART_SESSION_ID, RESTART_LABEL, 60, icons.debugRestart); - registerDebugToolBarItem(STEP_BACK_ID, nls.localize('stepBackDebug', "Step Back"), 50, icons.debugStepBack, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); - registerDebugToolBarItem(REVERSE_CONTINUE_ID, nls.localize('reverseContinue', "Reverse"), 60, icons.debugReverseContinue, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); + registerDebugCommandPaletteItem(DEBUG_START_COMMAND_ID, DEBUG_START_LABEL, ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing)))); + registerDebugCommandPaletteItem(DEBUG_RUN_COMMAND_ID, DEBUG_RUN_LABEL, ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing)))); // Debug callstack context menu const registerDebugViewMenuItem = (menuId: MenuId, id: string, title: string, order: number, when?: ContextKeyExpression, precondition?: ContextKeyExpression, group = 'navigation') => { @@ -194,6 +156,12 @@ function registerCommandsAndActions(): void { registerDebugViewMenuItem(MenuId.DebugVariablesContext, ADD_TO_WATCH_ID, nls.localize('addToWatchExpressions', "Add to Watch"), 100, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, undefined, 'z_commands'); registerDebugViewMenuItem(MenuId.DebugVariablesContext, BREAK_WHEN_VALUE_CHANGES_ID, nls.localize('breakWhenValueChanges', "Break When Value Changes"), 200, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, undefined, 'z_commands'); + registerDebugViewMenuItem(MenuId.DebugWatchContext, ADD_WATCH_ID, ADD_WATCH_LABEL, 10, undefined, undefined, '3_modification'); + registerDebugViewMenuItem(MenuId.DebugWatchContext, EDIT_EXPRESSION_COMMAND_ID, nls.localize('editWatchExpression', "Edit Expression"), 20, CONTEXT_WATCH_ITEM_TYPE.isEqualTo('expression'), undefined, '3_modification'); + registerDebugViewMenuItem(MenuId.DebugWatchContext, COPY_VALUE_ID, nls.localize('copyValue', "Copy Value"), 30, ContextKeyExpr.or(CONTEXT_WATCH_ITEM_TYPE.isEqualTo('expression'), CONTEXT_WATCH_ITEM_TYPE.isEqualTo('variable')), CONTEXT_IN_DEBUG_MODE, '3_modification'); + registerDebugViewMenuItem(MenuId.DebugWatchContext, REMOVE_EXPRESSION_COMMAND_ID, nls.localize('removeWatchExpression', "Remove Expression"), 10, CONTEXT_WATCH_ITEM_TYPE.isEqualTo('expression'), undefined, 'z_commands'); + registerDebugViewMenuItem(MenuId.DebugWatchContext, REMOVE_WATCH_EXPRESSIONS_COMMAND_ID, REMOVE_WATCH_EXPRESSIONS_LABEL, 20, undefined, undefined, 'z_commands'); + // Touch Bar if (isMacintosh) { @@ -210,8 +178,8 @@ function registerCommandsAndActions(): void { }); }; - registerTouchBarEntry(StartAction.ID, StartAction.LABEL, 0, CONTEXT_IN_DEBUG_MODE.toNegated(), FileAccess.asFileUri('vs/workbench/contrib/debug/browser/media/continue-tb.png', require)); - registerTouchBarEntry(RunAction.ID, RunAction.LABEL, 1, CONTEXT_IN_DEBUG_MODE.toNegated(), FileAccess.asFileUri('vs/workbench/contrib/debug/browser/media/continue-without-debugging-tb.png', require)); + registerTouchBarEntry(DEBUG_START_COMMAND_ID, DEBUG_START_LABEL, 0, CONTEXT_IN_DEBUG_MODE.toNegated(), FileAccess.asFileUri('vs/workbench/contrib/debug/browser/media/continue-tb.png', require)); + registerTouchBarEntry(DEBUG_RUN_COMMAND_ID, DEBUG_RUN_LABEL, 1, CONTEXT_IN_DEBUG_MODE.toNegated(), FileAccess.asFileUri('vs/workbench/contrib/debug/browser/media/continue-without-debugging-tb.png', require)); registerTouchBarEntry(CONTINUE_ID, CONTINUE_LABEL, 0, CONTEXT_DEBUG_STATE.isEqualTo('stopped'), FileAccess.asFileUri('vs/workbench/contrib/debug/browser/media/continue-tb.png', require)); registerTouchBarEntry(PAUSE_ID, PAUSE_LABEL, 1, ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.notEquals('debugState', 'stopped')), FileAccess.asFileUri('vs/workbench/contrib/debug/browser/media/pause-tb.png', require)); registerTouchBarEntry(STEP_OVER_ID, STEP_OVER_LABEL, 2, CONTEXT_IN_DEBUG_MODE, FileAccess.asFileUri('vs/workbench/contrib/debug/browser/media/stepover-tb.png', require)); @@ -234,21 +202,12 @@ function registerDebugMenu(): void { order: 4 }); - MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '4_panels', - command: { - id: OpenDebugConsoleAction.ID, - title: nls.localize({ key: 'miToggleDebugConsole', comment: ['&& denotes a mnemonic'] }, "De&&bug Console") - }, - order: 2 - }); - // Debug menu MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { group: '1_debug', command: { - id: StartAction.ID, + id: DEBUG_START_COMMAND_ID, title: nls.localize({ key: 'miStartDebugging', comment: ['&& denotes a mnemonic'] }, "&&Start Debugging") }, order: 1, @@ -258,7 +217,7 @@ function registerDebugMenu(): void { MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { group: '1_debug', command: { - id: RunAction.ID, + id: DEBUG_RUN_COMMAND_ID, title: nls.localize({ key: 'miRun', comment: ['&& denotes a mnemonic'] }, "Run &&Without Debugging") }, order: 2, @@ -288,15 +247,6 @@ function registerDebugMenu(): void { }); // Configuration - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '2_configuration', - command: { - id: ConfigureAction.ID, - title: nls.localize({ key: 'miOpenConfigurations', comment: ['&& denotes a mnemonic'] }, "Open &&Configurations") - }, - order: 1, - when: CONTEXT_DEBUGGERS_AVAILABLE - }); MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { group: '2_configuration', @@ -354,25 +304,6 @@ function registerDebugMenu(): void { }); // New Breakpoints - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '4_new_breakpoint', - command: { - id: TOGGLE_BREAKPOINT_ID, - title: nls.localize({ key: 'miToggleBreakpoint', comment: ['&& denotes a mnemonic'] }, "Toggle &&Breakpoint") - }, - order: 1, - when: CONTEXT_DEBUGGERS_AVAILABLE - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarNewBreakpointMenu, { - group: '1_breakpoints', - command: { - id: TOGGLE_CONDITIONAL_BREAKPOINT_ID, - title: nls.localize({ key: 'miConditionalBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&Conditional Breakpoint...") - }, - order: 1, - when: CONTEXT_DEBUGGERS_AVAILABLE - }); MenuRegistry.appendMenuItem(MenuId.MenubarNewBreakpointMenu, { group: '1_breakpoints', @@ -384,26 +315,6 @@ function registerDebugMenu(): void { when: CONTEXT_DEBUGGERS_AVAILABLE }); - MenuRegistry.appendMenuItem(MenuId.MenubarNewBreakpointMenu, { - group: '1_breakpoints', - command: { - id: AddFunctionBreakpointAction.ID, - title: nls.localize({ key: 'miFunctionBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&Function Breakpoint...") - }, - order: 3, - when: CONTEXT_DEBUGGERS_AVAILABLE - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarNewBreakpointMenu, { - group: '1_breakpoints', - command: { - id: ADD_LOG_POINT_ID, - title: nls.localize({ key: 'miLogPoint', comment: ['&& denotes a mnemonic'] }, "&&Logpoint...") - }, - order: 4, - when: CONTEXT_DEBUGGERS_AVAILABLE - }); - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { group: '4_new_breakpoint', title: nls.localize({ key: 'miNewBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&New Breakpoint"), @@ -412,36 +323,7 @@ function registerDebugMenu(): void { when: CONTEXT_DEBUGGERS_AVAILABLE }); - // Modify Breakpoints - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '5_breakpoints', - command: { - id: EnableAllBreakpointsAction.ID, - title: nls.localize({ key: 'miEnableAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "&&Enable All Breakpoints") - }, - order: 1, - when: CONTEXT_DEBUGGERS_AVAILABLE - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '5_breakpoints', - command: { - id: DisableAllBreakpointsAction.ID, - title: nls.localize({ key: 'miDisableAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Disable A&&ll Breakpoints") - }, - order: 2, - when: CONTEXT_DEBUGGERS_AVAILABLE - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '5_breakpoints', - command: { - id: RemoveAllBreakpointsAction.ID, - title: nls.localize({ key: 'miRemoveAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Remove &&All Breakpoints") - }, - order: 3, - when: CONTEXT_DEBUGGERS_AVAILABLE - }); + // Breakpoint actions are registered from breakpointsView.ts // Install Debuggers MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { @@ -463,7 +345,7 @@ function registerDebugPanel(): void { icon: icons.debugConsoleViewIcon, ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [DEBUG_PANEL_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), storageId: DEBUG_PANEL_ID, - focusCommand: { id: OpenDebugConsoleAction.ID }, + focusCommand: { id: OPEN_REPL_COMMAND_ID }, order: 2, hideIfEmpty: true }, ViewContainerLocation.Panel); @@ -477,8 +359,6 @@ function registerDebugPanel(): void { when: CONTEXT_DEBUGGERS_AVAILABLE, ctorDescriptor: new SyncDescriptor(Repl), }], VIEW_CONTAINER); - - registry.registerWorkbenchAction(SyncActionDescriptor.from(OpenDebugConsoleAction, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Y }), 'View: Debug Console', CATEGORIES.View.value, CONTEXT_DEBUGGERS_AVAILABLE); } diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index 3ab907976..b600fc994 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -11,7 +11,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { SelectBox, ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IDebugService, IDebugSession, IDebugConfiguration, IConfig, ILaunch } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, IDebugSession, IDebugConfiguration, IConfig, ILaunch, State } from 'vs/workbench/contrib/debug/common/debug'; import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; import { selectBorder, selectBackground } from 'vs/platform/theme/common/colorRegistry'; @@ -76,7 +76,9 @@ export class StartDebugActionViewItem implements IActionViewItem { this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.CLICK, () => { this.start.blur(); - this.actionRunner.run(this.action, this.context); + if (this.debugService.state !== State.Initializing) { + this.actionRunner.run(this.action, this.context); + } })); this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.MOUSE_DOWN, (e: MouseEvent) => { @@ -93,7 +95,7 @@ export class StartDebugActionViewItem implements IActionViewItem { this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { const event = new StandardKeyboardEvent(e); - if (event.equals(KeyCode.Enter)) { + if (event.equals(KeyCode.Enter) && this.debugService.state !== State.Initializing) { this.actionRunner.run(this.action, this.context); } if (event.equals(KeyCode.RightArrow)) { diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts deleted file mode 100644 index 3c9472104..000000000 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ /dev/null @@ -1,421 +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 { Action } from 'vs/base/common/actions'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IDebugService, State, IEnablement, IBreakpoint, IDebugSession, ILaunch } from 'vs/workbench/contrib/debug/common/debug'; -import { Variable, Breakpoint, FunctionBreakpoint, Expression } from 'vs/workbench/contrib/debug/common/debugModel'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { deepClone } from 'vs/base/common/objects'; -import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; - -export abstract class AbstractDebugAction extends Action { - - constructor( - id: string, label: string, cssClass: string, - @IDebugService protected debugService: IDebugService, - @IKeybindingService protected keybindingService: IKeybindingService, - ) { - super(id, label, cssClass, false); - this._register(this.debugService.onDidChangeState(state => this.updateEnablement(state))); - - this.updateLabel(label); - this.updateEnablement(); - } - - run(_: any): Promise { - throw new Error('implement me'); - } - - get tooltip(): string { - const keybinding = this.keybindingService.lookupKeybinding(this.id); - const keybindingLabel = keybinding && keybinding.getLabel(); - - return keybindingLabel ? `${this.label} (${keybindingLabel})` : this.label; - } - - protected updateLabel(newLabel: string): void { - this.label = newLabel; - } - - protected updateEnablement(state = this.debugService.state): void { - this.enabled = this.isEnabled(state); - } - - protected isEnabled(_: State): boolean { - return true; - } -} - -export class ConfigureAction extends AbstractDebugAction { - static readonly ID = 'workbench.action.debug.configure'; - static readonly LABEL = nls.localize('openLaunchJson', "Open {0}", 'launch.json'); - - constructor(id: string, label: string, - @IDebugService debugService: IDebugService, - @IKeybindingService keybindingService: IKeybindingService, - @INotificationService private readonly notificationService: INotificationService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IQuickInputService private readonly quickInputService: IQuickInputService - ) { - super(id, label, 'debug-action ' + ThemeIcon.asClassName(icons.debugConfigure), debugService, keybindingService); - this._register(debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateClass())); - this.updateClass(); - } - - get tooltip(): string { - if (this.debugService.getConfigurationManager().selectedConfiguration.name) { - return ConfigureAction.LABEL; - } - - return nls.localize('launchJsonNeedsConfigurtion', "Configure or Fix 'launch.json'"); - } - - private updateClass(): void { - const configurationManager = this.debugService.getConfigurationManager(); - this.class = configurationManager.selectedConfiguration.name ? 'debug-action' + ThemeIcon.asClassName(icons.debugConfigure) : 'debug-action ' + ThemeIcon.asClassName(icons.debugConfigure) + ' notification'; - } - - async run(): Promise { - if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY || this.contextService.getWorkspace().folders.length === 0) { - this.notificationService.info(nls.localize('noFolderDebugConfig', "Please first open a folder in order to do advanced debug configuration.")); - return; - } - - const configurationManager = this.debugService.getConfigurationManager(); - let launch: ILaunch | undefined; - if (configurationManager.selectedConfiguration.name) { - launch = configurationManager.selectedConfiguration.launch; - } else { - const launches = configurationManager.getLaunches().filter(l => !l.hidden); - if (launches.length === 1) { - launch = launches[0]; - } else { - const picks = launches.map(l => ({ label: l.name, launch: l })); - const picked = await this.quickInputService.pick<{ label: string, launch: ILaunch }>(picks, { - activeItem: picks[0], - placeHolder: nls.localize({ key: 'selectWorkspaceFolder', comment: ['User picks a workspace folder or a workspace configuration file here. Workspace configuration files can contain settings and thus a launch.json configuration can be written into one.'] }, "Select a workspace folder to create a launch.json file in or add it to the workspace config file") - }); - if (picked) { - launch = picked.launch; - } - } - } - - if (launch) { - return launch.openConfigFile(false); - } - } -} - -export class StartAction extends AbstractDebugAction { - static ID = 'workbench.action.debug.start'; - static LABEL = nls.localize('startDebug', "Start Debugging"); - - constructor(id: string, label: string, - @IDebugService debugService: IDebugService, - @IKeybindingService keybindingService: IKeybindingService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - ) { - super(id, label, 'debug-action start', debugService, keybindingService); - - this._register(this.debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateEnablement())); - this._register(this.debugService.onDidNewSession(() => this.updateEnablement())); - this._register(this.debugService.onDidEndSession(() => this.updateEnablement())); - this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateEnablement())); - } - - async run(): Promise { - let { launch, name, getConfig } = this.debugService.getConfigurationManager().selectedConfiguration; - const config = await getConfig(); - const clonedConfig = deepClone(config); - return this.debugService.startDebugging(launch, clonedConfig || name, { noDebug: this.isNoDebug() }); - } - - protected isNoDebug(): boolean { - return false; - } - - static isEnabled(debugService: IDebugService) { - const sessions = debugService.getModel().getSessions(); - - if (debugService.state === State.Initializing) { - return false; - } - let { name, launch } = debugService.getConfigurationManager().selectedConfiguration; - let nameToStart = name; - - if (sessions.some(s => s.configuration.name === nameToStart && s.root === launch?.workspace)) { - // There is already a debug session running and we do not have any launch configuration selected - return false; - } - - return true; - } - - // Disabled if the launch drop down shows the launch config that is already running. - protected isEnabled(): boolean { - return StartAction.isEnabled(this.debugService); - } -} - -export class RunAction extends StartAction { - static readonly ID = 'workbench.action.debug.run'; - static LABEL = nls.localize('startWithoutDebugging', "Start Without Debugging"); - - protected isNoDebug(): boolean { - return true; - } -} - -export class SelectAndStartAction extends AbstractDebugAction { - static readonly ID = 'workbench.action.debug.selectandstart'; - static readonly LABEL = nls.localize('selectAndStartDebugging', "Select and Start Debugging"); - - constructor(id: string, label: string, - @IDebugService debugService: IDebugService, - @IKeybindingService keybindingService: IKeybindingService, - @IQuickInputService private readonly quickInputService: IQuickInputService - ) { - super(id, label, '', debugService, keybindingService); - } - - async run(): Promise { - this.quickInputService.quickAccess.show('debug '); - } -} - -export class RemoveBreakpointAction extends Action { - static readonly ID = 'workbench.debug.viewlet.action.removeBreakpoint'; - static readonly LABEL = nls.localize('removeBreakpoint', "Remove Breakpoint"); - - constructor(id: string, label: string, @IDebugService private readonly debugService: IDebugService) { - super(id, label, 'debug-action remove'); - } - - run(breakpoint: IBreakpoint): Promise { - return breakpoint instanceof Breakpoint ? this.debugService.removeBreakpoints(breakpoint.getId()) - : breakpoint instanceof FunctionBreakpoint ? this.debugService.removeFunctionBreakpoints(breakpoint.getId()) : this.debugService.removeDataBreakpoints(breakpoint.getId()); - } -} - -export class RemoveAllBreakpointsAction extends AbstractDebugAction { - static readonly ID = 'workbench.debug.viewlet.action.removeAllBreakpoints'; - static readonly LABEL = nls.localize('removeAllBreakpoints', "Remove All Breakpoints"); - - constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action ' + ThemeIcon.asClassName(icons.breakpointsRemoveAll), debugService, keybindingService); - this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); - } - - run(): Promise { - return Promise.all([this.debugService.removeBreakpoints(), this.debugService.removeFunctionBreakpoints(), this.debugService.removeDataBreakpoints()]); - } - - protected isEnabled(_: State): boolean { - const model = this.debugService.getModel(); - return (model.getBreakpoints().length > 0 || model.getFunctionBreakpoints().length > 0 || model.getDataBreakpoints().length > 0); - } -} - -export class EnableAllBreakpointsAction extends AbstractDebugAction { - static readonly ID = 'workbench.debug.viewlet.action.enableAllBreakpoints'; - static readonly LABEL = nls.localize('enableAllBreakpoints', "Enable All Breakpoints"); - - constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action enable-all-breakpoints', debugService, keybindingService); - this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); - } - - run(): Promise { - return this.debugService.enableOrDisableBreakpoints(true); - } - - protected isEnabled(_: State): boolean { - const model = this.debugService.getModel(); - return (>model.getBreakpoints()).concat(model.getFunctionBreakpoints()).concat(model.getExceptionBreakpoints()).some(bp => !bp.enabled); - } -} - -export class DisableAllBreakpointsAction extends AbstractDebugAction { - static readonly ID = 'workbench.debug.viewlet.action.disableAllBreakpoints'; - static readonly LABEL = nls.localize('disableAllBreakpoints', "Disable All Breakpoints"); - - constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action disable-all-breakpoints', debugService, keybindingService); - this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); - } - - run(): Promise { - return this.debugService.enableOrDisableBreakpoints(false); - } - - protected isEnabled(_: State): boolean { - const model = this.debugService.getModel(); - return (>model.getBreakpoints()).concat(model.getFunctionBreakpoints()).concat(model.getExceptionBreakpoints()).some(bp => bp.enabled); - } -} - -export class ToggleBreakpointsActivatedAction extends AbstractDebugAction { - static readonly ID = 'workbench.debug.viewlet.action.toggleBreakpointsActivatedAction'; - static readonly ACTIVATE_LABEL = nls.localize('activateBreakpoints', "Activate Breakpoints"); - static readonly DEACTIVATE_LABEL = nls.localize('deactivateBreakpoints', "Deactivate Breakpoints"); - - constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action ' + ThemeIcon.asClassName(icons.breakpointsActivate), debugService, keybindingService); - this.updateLabel(this.debugService.getModel().areBreakpointsActivated() ? ToggleBreakpointsActivatedAction.DEACTIVATE_LABEL : ToggleBreakpointsActivatedAction.ACTIVATE_LABEL); - - this._register(this.debugService.getModel().onDidChangeBreakpoints(() => { - this.updateLabel(this.debugService.getModel().areBreakpointsActivated() ? ToggleBreakpointsActivatedAction.DEACTIVATE_LABEL : ToggleBreakpointsActivatedAction.ACTIVATE_LABEL); - this.updateEnablement(); - })); - } - - run(): Promise { - return this.debugService.setBreakpointsActivated(!this.debugService.getModel().areBreakpointsActivated()); - } - - protected isEnabled(_: State): boolean { - return !!(this.debugService.getModel().getFunctionBreakpoints().length || this.debugService.getModel().getBreakpoints().length || this.debugService.getModel().getDataBreakpoints().length); - } -} - -export class ReapplyBreakpointsAction extends AbstractDebugAction { - static readonly ID = 'workbench.debug.viewlet.action.reapplyBreakpointsAction'; - static readonly LABEL = nls.localize('reapplyAllBreakpoints', "Reapply All Breakpoints"); - - constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, '', debugService, keybindingService); - this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); - } - - run(): Promise { - return this.debugService.setBreakpointsActivated(true); - } - - protected isEnabled(state: State): boolean { - const model = this.debugService.getModel(); - return (state === State.Running || state === State.Stopped) && - ((model.getFunctionBreakpoints().length + model.getBreakpoints().length + model.getExceptionBreakpoints().length + model.getDataBreakpoints().length) > 0); - } -} - -export class AddFunctionBreakpointAction extends AbstractDebugAction { - static readonly ID = 'workbench.debug.viewlet.action.addFunctionBreakpointAction'; - static readonly LABEL = nls.localize('addFunctionBreakpoint', "Add Function Breakpoint"); - - constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action ' + ThemeIcon.asClassName(icons.watchExpressionsAddFuncBreakpoint), debugService, keybindingService); - this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); - } - - async run(): Promise { - this.debugService.addFunctionBreakpoint(); - } - - protected isEnabled(_: State): boolean { - return !this.debugService.getViewModel().getSelectedBreakpoint() - && this.debugService.getModel().getFunctionBreakpoints().every(fbp => !!fbp.name); - } -} - -export class AddWatchExpressionAction extends AbstractDebugAction { - static readonly ID = 'workbench.debug.viewlet.action.addWatchExpression'; - static readonly LABEL = nls.localize('addWatchExpression', "Add Expression"); - - constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action ' + ThemeIcon.asClassName(icons.watchExpressionsAdd), debugService, keybindingService); - this._register(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement())); - this._register(this.debugService.getViewModel().onDidSelectExpression(() => this.updateEnablement())); - } - - async run(): Promise { - this.debugService.addWatchExpression(); - } - - protected isEnabled(_: State): boolean { - const focusedExpression = this.debugService.getViewModel().getSelectedExpression(); - return this.debugService.getModel().getWatchExpressions().every(we => !!we.name && we !== focusedExpression); - } -} - -export class RemoveAllWatchExpressionsAction extends AbstractDebugAction { - static readonly ID = 'workbench.debug.viewlet.action.removeAllWatchExpressions'; - static readonly LABEL = nls.localize('removeAllWatchExpressions', "Remove All Expressions"); - - constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action ' + ThemeIcon.asClassName(icons.watchExpressionsRemoveAll), debugService, keybindingService); - this._register(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement())); - } - - async run(): Promise { - this.debugService.removeWatchExpressions(); - } - - protected isEnabled(_: State): boolean { - return this.debugService.getModel().getWatchExpressions().length > 0; - } -} - -export class FocusSessionAction extends AbstractDebugAction { - static readonly ID = 'workbench.action.debug.focusProcess'; - static readonly LABEL = nls.localize('focusSession', "Focus Session"); - - constructor(id: string, label: string, - @IDebugService debugService: IDebugService, - @IKeybindingService keybindingService: IKeybindingService, - @IEditorService private readonly editorService: IEditorService - ) { - super(id, label, '', debugService, keybindingService); - } - - async run(session: IDebugSession): Promise { - await this.debugService.focusStackFrame(undefined, undefined, session, true); - const stackFrame = this.debugService.getViewModel().focusedStackFrame; - if (stackFrame) { - await stackFrame.openInEditor(this.editorService, true); - } - } -} - -export class CopyValueAction extends Action { - static readonly ID = 'workbench.debug.viewlet.action.copyValue'; - static readonly LABEL = nls.localize('copyValue', "Copy Value"); - - constructor( - id: string, label: string, private value: Variable | Expression, private context: string, - @IDebugService private readonly debugService: IDebugService, - @IClipboardService private readonly clipboardService: IClipboardService - ) { - super(id, label); - this._enabled = (this.value instanceof Expression) || (this.value instanceof Variable && !!this.value.evaluateName); - } - - async run(): Promise { - const stackFrame = this.debugService.getViewModel().focusedStackFrame; - const session = this.debugService.getViewModel().focusedSession; - if (!stackFrame || !session) { - return; - } - - const context = session.capabilities.supportsClipboardContext ? 'clipboard' : this.context; - const toEvaluate = this.value instanceof Variable ? (this.value.evaluateName || this.value.value) : this.value.name; - - try { - const evaluation = await session.evaluate(toEvaluate, stackFrame.frameId, context); - if (evaluation) { - this.clipboardService.writeText(evaluation.body.result); - } - } catch (e) { - this.clipboardService.writeText(typeof this.value === 'string' ? this.value : this.value.value); - } - } -} diff --git a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts index 249f1e69d..bf1fc62fe 100644 --- a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts @@ -54,11 +54,6 @@ export class AdapterManager implements IAdapterManager { if (!rawAdapter.type || (typeof rawAdapter.type !== 'string')) { added.collector.error(nls.localize('debugNoType', "Debugger 'type' can not be omitted and must be of type 'string'.")); } - if (rawAdapter.enableBreakpointsFor && rawAdapter.enableBreakpointsFor.languageIds) { - rawAdapter.enableBreakpointsFor.languageIds.forEach(modeId => { - this.breakpointModeIdsSet.add(modeId); - }); - } if (rawAdapter.type !== '*') { const existing = this.getDebugger(rawAdapter.type); diff --git a/src/vs/workbench/contrib/debug/browser/debugColors.ts b/src/vs/workbench/contrib/debug/browser/debugColors.ts index df54038d0..476f8571e 100644 --- a/src/vs/workbench/contrib/debug/browser/debugColors.ts +++ b/src/vs/workbench/contrib/debug/browser/debugColors.ts @@ -4,8 +4,28 @@ *--------------------------------------------------------------------------------------------*/ import { registerColor, foreground, editorInfoForeground, editorWarningForeground, errorForeground, badgeBackground, badgeForeground, listDeemphasizedForeground, contrastBorder, inputBorder } from 'vs/platform/theme/common/colorRegistry'; -import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; +import { localize } from 'vs/nls'; +import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; + +export const debugToolBarBackground = registerColor('debugToolBar.background', { + dark: '#333333', + light: '#F3F3F3', + hc: '#000000' +}, localize('debugToolBarBackground', "Debug toolbar background color.")); + +export const debugToolBarBorder = registerColor('debugToolBar.border', { + dark: null, + light: null, + hc: null +}, localize('debugToolBarBorder', "Debug toolbar border color.")); + +export const debugIconStartForeground = registerColor('debugIcon.startForeground', { + dark: '#89D185', + light: '#388A34', + hc: '#89D185' +}, localize('debugIcon.startForeground', "Debug toolbar icon for start debugging.")); export function registerColors() { @@ -28,6 +48,62 @@ export function registerColors() { const debugConsoleSourceForeground = registerColor('debugConsole.sourceForeground', { dark: foreground, light: foreground, hc: foreground }, 'Foreground color for source filenames in debug REPL console.'); const debugConsoleInputIconForeground = registerColor('debugConsoleInputIcon.foreground', { dark: foreground, light: foreground, hc: foreground }, 'Foreground color for debug console input marker icon.'); + + + const debugIconPauseForeground = registerColor('debugIcon.pauseForeground', { + dark: '#75BEFF', + light: '#007ACC', + hc: '#75BEFF' + }, localize('debugIcon.pauseForeground', "Debug toolbar icon for pause.")); + + const debugIconStopForeground = registerColor('debugIcon.stopForeground', { + dark: '#F48771', + light: '#A1260D', + hc: '#F48771' + }, localize('debugIcon.stopForeground', "Debug toolbar icon for stop.")); + + const debugIconDisconnectForeground = registerColor('debugIcon.disconnectForeground', { + dark: '#F48771', + light: '#A1260D', + hc: '#F48771' + }, localize('debugIcon.disconnectForeground', "Debug toolbar icon for disconnect.")); + + const debugIconRestartForeground = registerColor('debugIcon.restartForeground', { + dark: '#89D185', + light: '#388A34', + hc: '#89D185' + }, localize('debugIcon.restartForeground', "Debug toolbar icon for restart.")); + + const debugIconStepOverForeground = registerColor('debugIcon.stepOverForeground', { + dark: '#75BEFF', + light: '#007ACC', + hc: '#75BEFF' + }, localize('debugIcon.stepOverForeground', "Debug toolbar icon for step over.")); + + const debugIconStepIntoForeground = registerColor('debugIcon.stepIntoForeground', { + dark: '#75BEFF', + light: '#007ACC', + hc: '#75BEFF' + }, localize('debugIcon.stepIntoForeground', "Debug toolbar icon for step into.")); + + const debugIconStepOutForeground = registerColor('debugIcon.stepOutForeground', { + dark: '#75BEFF', + light: '#007ACC', + hc: '#75BEFF' + }, localize('debugIcon.stepOutForeground', "Debug toolbar icon for step over.")); + + const debugIconContinueForeground = registerColor('debugIcon.continueForeground', { + dark: '#75BEFF', + light: '#007ACC', + hc: '#75BEFF' + }, localize('debugIcon.continueForeground', "Debug toolbar icon for continue.")); + + const debugIconStepBackForeground = registerColor('debugIcon.stepBackForeground', { + dark: '#75BEFF', + light: '#007ACC', + hc: '#75BEFF' + }, localize('debugIcon.stepBackForeground', "Debug toolbar icon for step back.")); + registerThemingParticipant((theme, collector) => { // All these colours provide a default value so they will never be undefined, hence the `!` const badgeBackgroundColor = theme.getColor(badgeBackground)!; @@ -186,5 +262,55 @@ export function registerColors() { } `); } + + const debugIconStartColor = theme.getColor(debugIconStartForeground); + if (debugIconStartColor) { + collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStart)} { color: ${debugIconStartColor} !important; }`); + } + + const debugIconPauseColor = theme.getColor(debugIconPauseForeground); + if (debugIconPauseColor) { + collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugPause)} { color: ${debugIconPauseColor} !important; }`); + } + + const debugIconStopColor = theme.getColor(debugIconStopForeground); + if (debugIconStopColor) { + collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStop)} { color: ${debugIconStopColor} !important; }`); + } + + const debugIconDisconnectColor = theme.getColor(debugIconDisconnectForeground); + if (debugIconDisconnectColor) { + collector.addRule(`.monaco-workbench .debug-view-content ${ThemeIcon.asCSSSelector(icons.debugDisconnect)}, .monaco-workbench .debug-toolbar ${ThemeIcon.asCSSSelector(icons.debugDisconnect)} { color: ${debugIconDisconnectColor} !important; }`); + } + + const debugIconRestartColor = theme.getColor(debugIconRestartForeground); + if (debugIconRestartColor) { + collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugRestart)}, .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugRestartFrame)} { color: ${debugIconRestartColor} !important; }`); + } + + const debugIconStepOverColor = theme.getColor(debugIconStepOverForeground); + if (debugIconStepOverColor) { + collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStepOver)} { color: ${debugIconStepOverColor} !important; }`); + } + + const debugIconStepIntoColor = theme.getColor(debugIconStepIntoForeground); + if (debugIconStepIntoColor) { + collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStepInto)} { color: ${debugIconStepIntoColor} !important; }`); + } + + const debugIconStepOutColor = theme.getColor(debugIconStepOutForeground); + if (debugIconStepOutColor) { + collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStepOut)} { color: ${debugIconStepOutColor} !important; }`); + } + + const debugIconContinueColor = theme.getColor(debugIconContinueForeground); + if (debugIconContinueColor) { + collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugContinue)}, .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugReverseContinue)} { color: ${debugIconContinueColor} !important; }`); + } + + const debugIconStepBackColor = theme.getColor(debugIconStepBackForeground); + if (debugIconStepBackColor) { + collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStepBack)} { color: ${debugIconStepBackColor} !important; }`); + } }); } diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 47aaedfb9..3b353cd61 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -9,7 +9,7 @@ import { List } from 'vs/base/browser/ui/list/listWidget'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IListService } from 'vs/platform/list/browser/listService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_EXPRESSION_SELECTED, CONTEXT_BREAKPOINT_SELECTED, IConfig, IStackFrame, IThread, IDebugSession, CONTEXT_DEBUG_STATE, IDebugConfiguration, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, REPL_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_EXPRESSION_SELECTED, IConfig, IStackFrame, IThread, IDebugSession, CONTEXT_DEBUG_STATE, IDebugConfiguration, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, REPL_VIEW_ID, CONTEXT_DEBUGGERS_AVAILABLE, State, getStateLabel, CONTEXT_BREAKPOINT_INPUT_FOCUSED } from 'vs/workbench/contrib/debug/common/debug'; import { Expression, Variable, Breakpoint, FunctionBreakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { IExtensionsViewPaneContainer, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -23,12 +23,13 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { PanelFocusContext } from 'vs/workbench/common/panel'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IViewsService } from 'vs/workbench/common/views'; +import { deepClone } from 'vs/base/common/objects'; export const ADD_CONFIGURATION_ID = 'debug.addConfiguration'; export const TOGGLE_INLINE_BREAKPOINT_ID = 'editor.debug.action.toggleInlineBreakpoint'; @@ -47,6 +48,13 @@ export const RESTART_FRAME_ID = 'workbench.action.debug.restartFrame'; export const CONTINUE_ID = 'workbench.action.debug.continue'; export const FOCUS_REPL_ID = 'workbench.debug.action.focusRepl'; export const JUMP_TO_CURSOR_ID = 'debug.jumpToCursor'; +export const FOCUS_SESSION_ID = 'workbench.action.debug.focusProcess'; +export const SELECT_AND_START_ID = 'workbench.action.debug.selectandstart'; +export const DEBUG_CONFIGURE_COMMAND_ID = 'workbench.action.debug.configure'; +export const DEBUG_START_COMMAND_ID = 'workbench.action.debug.start'; +export const DEBUG_RUN_COMMAND_ID = 'workbench.action.debug.run'; +export const EDIT_EXPRESSION_COMMAND_ID = 'debug.renameWatchExpression'; +export const REMOVE_EXPRESSION_COMMAND_ID = 'debug.removeWatchExpression'; export const RESTART_LABEL = nls.localize('restartDebug', "Restart"); export const STEP_OVER_LABEL = nls.localize('stepOverDebug', "Step Over"); @@ -56,6 +64,11 @@ export const PAUSE_LABEL = nls.localize('pauseDebug', "Pause"); export const DISCONNECT_LABEL = nls.localize('disconnect', "Disconnect"); export const STOP_LABEL = nls.localize('stop', "Stop"); export const CONTINUE_LABEL = nls.localize('continueDebug', "Continue"); +export const FOCUS_SESSION_LABEL = nls.localize('focusSession', "Focus Session"); +export const SELECT_AND_START_LABEL = nls.localize('selectAndStartDebugging', "Select and Start Debugging"); +export const DEBUG_CONFIGURE_LABEL = nls.localize('openLaunchJson', "Open {0}", 'launch.json'); +export const DEBUG_START_LABEL = nls.localize('startDebug', "Start Debugging"); +export const DEBUG_RUN_LABEL = nls.localize('startWithoutDebugging', "Start Without Debugging"); interface CallStackContext { sessionId: string; @@ -322,7 +335,7 @@ export function registerCommands(): void { KeybindingsRegistry.registerCommandAndKeybindingRule({ id: CONTINUE_ID, - weight: KeybindingWeight.WorkbenchContrib, + weight: KeybindingWeight.WorkbenchContrib + 10, // Use a stronger weight to get priority over start debugging F5 shortcut primary: KeyCode.F5, when: CONTEXT_IN_DEBUG_MODE, handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { @@ -346,6 +359,53 @@ export function registerCommands(): void { } }); + CommandsRegistry.registerCommand({ + id: FOCUS_SESSION_ID, + handler: async (accessor: ServicesAccessor, session: IDebugSession) => { + const debugService = accessor.get(IDebugService); + const editorService = accessor.get(IEditorService); + await debugService.focusStackFrame(undefined, undefined, session, true); + const stackFrame = debugService.getViewModel().focusedStackFrame; + if (stackFrame) { + await stackFrame.openInEditor(editorService, true); + } + } + }); + + CommandsRegistry.registerCommand({ + id: SELECT_AND_START_ID, + handler: async (accessor: ServicesAccessor) => { + const quickInputService = accessor.get(IQuickInputService); + quickInputService.quickAccess.show('debug '); + } + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: DEBUG_START_COMMAND_ID, + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyCode.F5, + when: ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing))), + handler: async (accessor: ServicesAccessor, debugStartOptions?: { noDebug: boolean }) => { + const debugService = accessor.get(IDebugService); + let { launch, name, getConfig } = debugService.getConfigurationManager().selectedConfiguration; + const config = await getConfig(); + const clonedConfig = deepClone(config); + await debugService.startDebugging(launch, clonedConfig || name, { noDebug: debugStartOptions && debugStartOptions.noDebug }); + } + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: DEBUG_RUN_COMMAND_ID, + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyCode.F5, + mac: { primary: KeyMod.WinCtrl | KeyCode.F5 }, + when: ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing))), + handler: async (accessor: ServicesAccessor) => { + const commandService = accessor.get(ICommandService); + await commandService.executeCommand(DEBUG_START_COMMAND_ID, { noDebug: true }); + } + }); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'debug.toggleBreakpoint', weight: KeybindingWeight.WorkbenchContrib + 5, @@ -389,22 +449,27 @@ export function registerCommands(): void { }); KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'debug.renameWatchExpression', + id: EDIT_EXPRESSION_COMMAND_ID, weight: KeybindingWeight.WorkbenchContrib + 5, when: CONTEXT_WATCH_EXPRESSIONS_FOCUSED, primary: KeyCode.F2, mac: { primary: KeyCode.Enter }, - handler: (accessor) => { - const listService = accessor.get(IListService); + handler: (accessor: ServicesAccessor, expression: Expression | unknown) => { const debugService = accessor.get(IDebugService); - const focused = listService.lastFocusedList; - - if (focused) { - const elements = focused.getFocus(); - if (Array.isArray(elements) && elements[0] instanceof Expression) { - debugService.getViewModel().setSelectedExpression(elements[0]); + if (!(expression instanceof Expression)) { + const listService = accessor.get(IListService); + const focused = listService.lastFocusedList; + if (focused) { + const elements = focused.getFocus(); + if (Array.isArray(elements) && elements[0] instanceof Expression) { + expression = elements[0]; + } } } + + if (expression instanceof Expression) { + debugService.getViewModel().setSelectedExpression(expression); + } } }); @@ -429,16 +494,21 @@ export function registerCommands(): void { }); KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'debug.removeWatchExpression', + id: REMOVE_EXPRESSION_COMMAND_ID, weight: KeybindingWeight.WorkbenchContrib, when: ContextKeyExpr.and(CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_EXPRESSION_SELECTED.toNegated()), primary: KeyCode.Delete, mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace }, - handler: (accessor) => { - const listService = accessor.get(IListService); + handler: (accessor: ServicesAccessor, expression: Expression | unknown) => { const debugService = accessor.get(IDebugService); - const focused = listService.lastFocusedList; + if (expression instanceof Expression) { + debugService.removeWatchExpressions(expression.getId()); + return; + } + + const listService = accessor.get(IListService); + const focused = listService.lastFocusedList; if (focused) { let elements = focused.getFocus(); if (Array.isArray(elements) && elements[0] instanceof Expression) { @@ -455,7 +525,7 @@ export function registerCommands(): void { KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'debug.removeBreakpoint', weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_BREAKPOINT_SELECTED.toNegated()), + when: ContextKeyExpr.and(CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_BREAKPOINT_INPUT_FOCUSED.toNegated()), primary: KeyCode.Delete, mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace }, handler: (accessor) => { diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index d8ba1d554..bb92347d7 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { Range } from 'vs/editor/common/core/range'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { ServicesAccessor, registerEditorAction, EditorAction, IActionOptions } from 'vs/editor/browser/editorExtensions'; +import { registerEditorAction, EditorAction, IActionOptions, EditorAction2 } from 'vs/editor/browser/editorExtensions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IDebugService, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE, State, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, IBreakpoint, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, REPL_VIEW_ID, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, WATCH_VIEW_ID, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_EXCEPTION_WIDGET_VISIBLE } from 'vs/workbench/contrib/debug/common/debug'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -24,24 +24,35 @@ import { Position } from 'vs/editor/common/core/position'; import { URI } from 'vs/base/common/uri'; import { IDisposable } from 'vs/base/common/lifecycle'; import { raceTimeout } from 'vs/base/common/async'; +import { registerAction2, MenuId } from 'vs/platform/actions/common/actions'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -export const TOGGLE_BREAKPOINT_ID = 'editor.debug.action.toggleBreakpoint'; -class ToggleBreakpointAction extends EditorAction { +class ToggleBreakpointAction extends EditorAction2 { constructor() { super({ - id: TOGGLE_BREAKPOINT_ID, - label: nls.localize('toggleBreakpointAction', "Debug: Toggle Breakpoint"), - alias: 'Debug: Toggle Breakpoint', + id: 'editor.debug.action.toggleBreakpoint', + title: { + value: nls.localize('toggleBreakpointAction', "Debug: Toggle Breakpoint"), + original: 'Toggle Breakpoint', + mnemonicTitle: nls.localize({ key: 'miToggleBreakpoint', comment: ['&& denotes a mnemonic'] }, "Toggle &&Breakpoint") + }, + f1: true, precondition: CONTEXT_DEBUGGERS_AVAILABLE, - kbOpts: { - kbExpr: EditorContextKeys.editorTextFocus, + keybinding: { + when: EditorContextKeys.editorTextFocus, primary: KeyCode.F9, weight: KeybindingWeight.EditorContrib + }, + menu: { + when: CONTEXT_DEBUGGERS_AVAILABLE, + id: MenuId.MenubarDebugMenu, + group: '4_new_breakpoint', + order: 1 } }); } - async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]): Promise { if (editor.hasModel()) { const debugService = accessor.get(IDebugService); const modelUri = editor.getModel().uri; @@ -49,33 +60,39 @@ class ToggleBreakpointAction extends EditorAction { // Does not account for multi line selections, Set to remove multiple cursor on the same line const lineNumbers = [...new Set(editor.getSelections().map(s => s.getPosition().lineNumber))]; - return Promise.all(lineNumbers.map(line => { + await Promise.all(lineNumbers.map(async line => { const bps = debugService.getModel().getBreakpoints({ lineNumber: line, uri: modelUri }); if (bps.length) { - return Promise.all(bps.map(bp => debugService.removeBreakpoints(bp.getId()))); + await Promise.all(bps.map(bp => debugService.removeBreakpoints(bp.getId()))); } else if (canSet) { - return (debugService.addBreakpoints(modelUri, [{ lineNumber: line }])); - } else { - return []; + await debugService.addBreakpoints(modelUri, [{ lineNumber: line }]); } })); } } } -export const TOGGLE_CONDITIONAL_BREAKPOINT_ID = 'editor.debug.action.conditionalBreakpoint'; -class ConditionalBreakpointAction extends EditorAction { - +class ConditionalBreakpointAction extends EditorAction2 { constructor() { super({ - id: TOGGLE_CONDITIONAL_BREAKPOINT_ID, - label: nls.localize('conditionalBreakpointEditorAction', "Debug: Add Conditional Breakpoint..."), - alias: 'Debug: Add Conditional Breakpoint...', - precondition: CONTEXT_DEBUGGERS_AVAILABLE + id: 'editor.debug.action.conditionalBreakpoint', + title: { + value: nls.localize('conditionalBreakpointEditorAction', "Debug: Add Conditional Breakpoint..."), + original: 'Debug: Add Conditional Breakpoint...', + mnemonicTitle: nls.localize({ key: 'miConditionalBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&Conditional Breakpoint...") + }, + f1: true, + precondition: CONTEXT_DEBUGGERS_AVAILABLE, + menu: { + id: MenuId.MenubarNewBreakpointMenu, + group: '1_breakpoints', + order: 1, + when: CONTEXT_DEBUGGERS_AVAILABLE + } }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]): void { const debugService = accessor.get(IDebugService); const position = editor.getPosition(); @@ -85,19 +102,28 @@ class ConditionalBreakpointAction extends EditorAction { } } -export const ADD_LOG_POINT_ID = 'editor.debug.action.addLogPoint'; -class LogPointAction extends EditorAction { +class LogPointAction extends EditorAction2 { constructor() { super({ - id: ADD_LOG_POINT_ID, - label: nls.localize('logPointEditorAction', "Debug: Add Logpoint..."), - alias: 'Debug: Add Logpoint...', - precondition: CONTEXT_DEBUGGERS_AVAILABLE + id: 'editor.debug.action.addLogPoint', + title: { + value: nls.localize('logPointEditorAction', "Debug: Add Logpoint..."), + original: 'Debug: Add Logpoint...', + mnemonicTitle: nls.localize({ key: 'miLogPoint', comment: ['&& denotes a mnemonic'] }, "&&Logpoint...") + }, + precondition: CONTEXT_DEBUGGERS_AVAILABLE, + f1: true, + menu: { + id: MenuId.MenubarNewBreakpointMenu, + group: '1_breakpoints', + order: 4, + when: CONTEXT_DEBUGGERS_AVAILABLE + } }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]): void { const debugService = accessor.get(IDebugService); const position = editor.getPosition(); @@ -476,9 +502,9 @@ class CloseExceptionWidgetAction extends EditorAction { } export function registerEditorActions(): void { - registerEditorAction(ToggleBreakpointAction); - registerEditorAction(ConditionalBreakpointAction); - registerEditorAction(LogPointAction); + registerAction2(ToggleBreakpointAction); + registerAction2(ConditionalBreakpointAction); + registerAction2(LogPointAction); registerEditorAction(RunToCursorAction); registerEditorAction(StepIntoTargetsAction); registerEditorAction(SelectionToReplAction); diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index dbd53aa5b..f3fa36a2c 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -23,7 +23,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ICommandService } from 'vs/platform/commands/common/commands'; import { IDebugEditorContribution, IDebugService, State, IStackFrame, IDebugConfiguration, IExpression, IExceptionInfo, IDebugSession, CONTEXT_EXCEPTION_WIDGET_VISIBLE } from 'vs/workbench/contrib/debug/common/debug'; import { ExceptionWidget } from 'vs/workbench/contrib/debug/browser/exceptionWidget'; -import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets'; +import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; import { Position } from 'vs/editor/common/core/position'; import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; import { memoize, createMemoizer } from 'vs/base/common/decorators'; @@ -339,7 +339,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { if (this.hoverRange) { this.showHover(this.hoverRange, false); } - }, hoverOption.delay); + }, hoverOption.delay * 2); this.toDispose.push(scheduler); return scheduler; @@ -432,7 +432,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { this.closeExceptionWidget(); } else if (sameUri) { const exceptionInfo = await focusedSf.thread.exceptionInfo; - if (exceptionInfo && exceptionSf.range.startLineNumber && exceptionSf.range.startColumn) { + if (exceptionInfo) { this.showExceptionWidget(exceptionInfo, this.debugService.getViewModel().focusedSession, exceptionSf.range.startLineNumber, exceptionSf.range.startColumn); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index d828f5bf4..3bbddfa95 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -105,7 +105,7 @@ export class DebugHoverWidget implements IContentWidget { this.treeContainer = dom.append(this.complexValueContainer, $('.debug-hover-tree')); this.treeContainer.setAttribute('role', 'tree'); const tip = dom.append(this.complexValueContainer, $('.tip')); - tip.textContent = nls.localize('quickTip', 'Hold {0} key to switch to editor language hover', isMacintosh ? 'Option' : 'Alt'); + tip.textContent = nls.localize({ key: 'quickTip', comment: ['"switch to editor language hover" means to show the programming language hover widget instead of the debug hover'] }, 'Hold {0} key to switch to editor language hover', isMacintosh ? 'Option' : 'Alt'); const dataSource = new DebugHoverDataSource(); this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'DebugHover', this.treeContainer, new DebugHoverDelegate(), [this.instantiationService.createInstance(VariablesRenderer)], diff --git a/src/vs/workbench/contrib/debug/browser/debugIcons.ts b/src/vs/workbench/contrib/debug/browser/debugIcons.ts index b2203e381..b031cd582 100644 --- a/src/vs/workbench/contrib/debug/browser/debugIcons.ts +++ b/src/vs/workbench/contrib/debug/browser/debugIcons.ts @@ -15,25 +15,37 @@ export const callStackViewIcon = registerIcon('callstack-view-icon', Codicon.deb export const breakpointsViewIcon = registerIcon('breakpoints-view-icon', Codicon.debugAlt, localize('breakpointsViewIcon', 'View icon of the breakpoints view.')); export const loadedScriptsViewIcon = registerIcon('loaded-scripts-view-icon', Codicon.debugAlt, localize('loadedScriptsViewIcon', 'View icon of the loaded scripts view.')); -export const debugBreakpoint = registerIcon('debug-breakpoint', Codicon.debugBreakpoint, localize('debugBreakpoint', 'Icon for breakpoints.')); -export const debugBreakpointDisabled = registerIcon('debug-breakpoint-disabled', Codicon.debugBreakpointDisabled, localize('debugBreakpointDisabled', 'Icon for disabled breakpoints.')); -export const debugBreakpointUnverified = registerIcon('debug-breakpoint-unverified', Codicon.debugBreakpointUnverified, localize('debugBreakpointUnverified', 'Icon for unverified breakpoints.')); -export const debugBreakpointHint = registerIcon('debug-hint', Codicon.debugHint, localize('debugBreakpointHint', 'Icon for breakpoint hints shown on hover in editor glyph margin.')); -export const debugBreakpointFunction = registerIcon('debug-breakpoint-function', Codicon.debugBreakpointFunction, localize('debugBreakpointFunction', 'Icon for function breakpoints.')); -export const debugBreakpointFunctionUnverified = registerIcon('debug-breakpoint-function-unverified', Codicon.debugBreakpointFunctionUnverified, localize('debugBreakpointFunctionUnverified', 'Icon for unverified function breakpoints.')); -export const debugBreakpointFunctionDisabled = registerIcon('debug-breakpoint-function-disabled', Codicon.debugBreakpointFunctionDisabled, localize('debugBreakpointFunctionDisabled', 'Icon for disabled function breakpoints.')); +export const breakpoint = { + regular: registerIcon('debug-breakpoint', Codicon.debugBreakpoint, localize('debugBreakpoint', 'Icon for breakpoints.')), + disabled: registerIcon('debug-breakpoint-disabled', Codicon.debugBreakpointDisabled, localize('debugBreakpointDisabled', 'Icon for disabled breakpoints.')), + unverified: registerIcon('debug-breakpoint-unverified', Codicon.debugBreakpointUnverified, localize('debugBreakpointUnverified', 'Icon for unverified breakpoints.')) +}; +export const functionBreakpoint = { + regular: registerIcon('debug-breakpoint-function', Codicon.debugBreakpointFunction, localize('debugBreakpointFunction', 'Icon for function breakpoints.')), + disabled: registerIcon('debug-breakpoint-function-disabled', Codicon.debugBreakpointFunctionDisabled, localize('debugBreakpointFunctionDisabled', 'Icon for disabled function breakpoints.')), + unverified: registerIcon('debug-breakpoint-function-unverified', Codicon.debugBreakpointFunctionUnverified, localize('debugBreakpointFunctionUnverified', 'Icon for unverified function breakpoints.')) +}; +export const conditionalBreakpoint = { + regular: registerIcon('debug-breakpoint-conditional', Codicon.debugBreakpointConditional, localize('debugBreakpointConditional', 'Icon for conditional breakpoints.')), + disabled: registerIcon('debug-breakpoint-conditional-disabled', Codicon.debugBreakpointConditionalDisabled, localize('debugBreakpointConditionalDisabled', 'Icon for disabled conditional breakpoints.')), + unverified: registerIcon('debug-breakpoint-conditional-unverified', Codicon.debugBreakpointConditionalUnverified, localize('debugBreakpointConditionalUnverified', 'Icon for unverified conditional breakpoints.')) +}; +export const dataBreakpoint = { + regular: registerIcon('debug-breakpoint-data', Codicon.debugBreakpointData, localize('debugBreakpointData', 'Icon for data breakpoints.')), + disabled: registerIcon('debug-breakpoint-data-disabled', Codicon.debugBreakpointDataDisabled, localize('debugBreakpointDataDisabled', 'Icon for disabled data breakpoints.')), + unverified: registerIcon('debug-breakpoint-data-unverified', Codicon.debugBreakpointDataUnverified, localize('debugBreakpointDataUnverified', 'Icon for unverified data breakpoints.')), +}; +export const logBreakpoint = { + regular: registerIcon('debug-breakpoint-log', Codicon.debugBreakpointLog, localize('debugBreakpointLog', 'Icon for log breakpoints.')), + disabled: registerIcon('debug-breakpoint-log-disabled', Codicon.debugBreakpointLogDisabled, localize('debugBreakpointLogDisabled', 'Icon for disabled log breakpoint.')), + unverified: registerIcon('debug-breakpoint-log-unverified', Codicon.debugBreakpointLogUnverified, localize('debugBreakpointLogUnverified', 'Icon for unverified log breakpoints.')), +}; +export const debugBreakpointHint = registerIcon('debug-hint', Codicon.debugHint, localize('debugBreakpointHint', 'Icon for breakpoint hints shown on hover in editor glyph margin.')); export const debugBreakpointUnsupported = registerIcon('debug-breakpoint-unsupported', Codicon.debugBreakpointUnsupported, localize('debugBreakpointUnsupported', 'Icon for unsupported breakpoints.')); -export const debugBreakpointConditionalUnverified = registerIcon('debug-breakpoint-conditional-unverified', Codicon.debugBreakpointConditionalUnverified, localize('debugBreakpointConditionalUnverified', 'Icon for unverified conditional breakpoints.')); -export const debugBreakpointConditional = registerIcon('debug-breakpoint-conditional', Codicon.debugBreakpointConditional, localize('debugBreakpointConditional', 'Icon for conditional breakpoints.')); -export const debugBreakpointConditionalDisabled = registerIcon('debug-breakpoint-conditional-disabled', Codicon.debugBreakpointConditionalDisabled, localize('debugBreakpointConditionalDisabled', 'Icon for disabled conditional breakpoints.')); -export const debugBreakpointDataUnverified = registerIcon('debug-breakpoint-data-unverified', Codicon.debugBreakpointDataUnverified, localize('debugBreakpointDataUnverified', 'Icon for unverified data breakpoints.')); -export const debugBreakpointData = registerIcon('debug-breakpoint-data', Codicon.debugBreakpointData, localize('debugBreakpointData', 'Icon for data breakpoints.')); -export const debugBreakpointDataDisabled = registerIcon('debug-breakpoint-data-disabled', Codicon.debugBreakpointDataDisabled, localize('debugBreakpointDataDisabled', 'Icon for disabled data breakpoints.')); -export const debugBreakpointLogUnverified = registerIcon('debug-breakpoint-log-unverified', Codicon.debugBreakpointLogUnverified, localize('debugBreakpointLogUnverified', 'Icon for unverified log breakpoints.')); -export const debugBreakpointLog = registerIcon('debug-breakpoint-log', Codicon.debugBreakpointLog, localize('debugBreakpointLog', 'Icon for log breakpoints.')); -export const debugBreakpointLogDisabled = registerIcon('debug-breakpoint-log-disabled', Codicon.debugBreakpointLogDisabled, localize('debugBreakpointLogDisabled', 'Icon for disabled log breakpoint.')); +export const allBreakpoints = [breakpoint, functionBreakpoint, conditionalBreakpoint, dataBreakpoint, logBreakpoint]; + export const debugStackframe = registerIcon('debug-stackframe', Codicon.debugStackframe, localize('debugStackframe', 'Icon for a stackframe shown in the editor glyph margin.')); export const debugStackframeFocused = registerIcon('debug-stackframe-focused', Codicon.debugStackframeFocused, localize('debugStackframeFocused', 'Icon for a focused stackframe shown in the editor glyph margin.')); diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts index 2b6f530c4..3c920d136 100644 --- a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts +++ b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts @@ -115,7 +115,8 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { const pick = await provider.pick(); if (pick) { - await configManager.selectConfiguration(pick.launch, pick.config.name, pick.config, { type: pick.config.type }); + // Use the type of the provider, not of the config since config sometimes have subtypes (for example "node-terminal") + await configManager.selectConfiguration(pick.launch, pick.config.name, pick.config, { type: provider.type }); this.debugService.startDebugging(pick.launch, pick.config); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index e05a471f5..7631d5406 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -17,7 +17,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files'; import { DebugModel, FunctionBreakpoint, Breakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { ViewModel } from 'vs/workbench/contrib/debug/common/debugViewModel'; -import * as debugactions from 'vs/workbench/contrib/debug/browser/debugActions'; import { ConfigurationManager } from 'vs/workbench/contrib/debug/browser/debugConfigurationManager'; import { VIEWLET_ID as EXPLORER_VIEWLET_ID } from 'vs/workbench/contrib/files/common/files'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -25,7 +24,6 @@ import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/bro import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { parse, getFirstFrame } from 'vs/base/common/console'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IAction, Action } from 'vs/base/common/actions'; @@ -48,9 +46,9 @@ import { DebugTelemetry } from 'vs/workbench/contrib/debug/common/debugTelemetry import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { AdapterManager } from 'vs/workbench/contrib/debug/browser/debugAdapterManager'; import { ITextModel } from 'vs/editor/common/model'; +import { DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands'; export class DebugService implements IDebugService { declare readonly _serviceBrand: undefined; @@ -96,8 +94,7 @@ export class DebugService implements IDebugService { @IExtensionHostDebugService private readonly extensionHostDebugService: IExtensionHostDebugService, @IActivityService private readonly activityService: IActivityService, @ICommandService private readonly commandService: ICommandService, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService + @IQuickInputService private readonly quickInputService: IQuickInputService ) { this.toDispose = []; @@ -149,16 +146,6 @@ export class DebugService implements IDebugService { session.disconnect(); } })); - this.toDispose.push(this.extensionHostDebugService.onLogToSession(event => { - const session = this.model.getSession(event.sessionId, true); - if (session) { - // extension logged output -> show it in REPL - const sev = event.log.severity === 'warn' ? severity.Warning : event.log.severity === 'error' ? severity.Error : severity.Info; - const { args, stack } = parse(event.log); - const frame = !!stack ? getFirstFrame(stack) : undefined; - session.logToRepl(sev, args, frame); - } - })); this.toDispose.push(this.viewModel.onDidFocusStackFrame(() => { this.onStateChange(); @@ -290,6 +277,11 @@ export class DebugService implements IDebugService { await this.extensionService.activateByEvent('onDebug'); if (!options?.parentSession) { await this.editorService.saveAll(); + const activeEditor = this.editorService.activeEditorPane; + if (activeEditor) { + // Make sure to save the active editor in case it is in untitled file it wont be saved as part of saveAll #111850 + await this.editorService.save({ editor: activeEditor.input, groupId: activeEditor.group.id }); + } } await this.configurationService.reloadConfiguration(launch ? launch.workspace : undefined); await this.extensionService.whenInstalledExtensionsRegistered(); @@ -302,15 +294,6 @@ export class DebugService implements IDebugService { if (typeof configOrName === 'string' && launch) { config = launch.getConfiguration(configOrName); compound = launch.getCompound(configOrName); - - const sessions = this.model.getSessions(); - const alreadyRunningMessage = nls.localize('configurationAlreadyRunning', "There is already a debug configuration \"{0}\" running.", configOrName); - if (sessions.some(s => (s.configuration.name === configOrName && s.root === launch.workspace) && (!launch || !launch.workspace || !s.root || this.uriIdentityService.extUri.isEqual(s.root.uri, launch.workspace.uri)))) { - throw new Error(alreadyRunningMessage); - } - if (compound && compound.configurations && sessions.some(p => compound!.configurations.indexOf(p.configuration.name) !== -1)) { - throw new Error(alreadyRunningMessage); - } } else if (typeof configOrName !== 'string') { config = configOrName; } @@ -784,7 +767,7 @@ export class DebugService implements IDebugService { } private async showError(message: string, errorActions: ReadonlyArray = []): Promise { - const configureAction = this.instantiationService.createInstance(debugactions.ConfigureAction, debugactions.ConfigureAction.ID, debugactions.ConfigureAction.LABEL); + const configureAction = new Action(DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL, undefined, true, () => this.commandService.executeCommand(DEBUG_CONFIGURE_COMMAND_ID)); const actions = [...errorActions, configureAction]; const { choice } = await this.dialogService.show(severity.Error, message, actions.map(a => a.label).concat(nls.localize('cancel', "Cancel")), { cancelId: actions.length }); if (choice < actions.length) { @@ -917,12 +900,11 @@ export class DebugService implements IDebugService { } addFunctionBreakpoint(name?: string, id?: string): void { - const newFunctionBreakpoint = this.model.addFunctionBreakpoint(name || '', id); - this.viewModel.setSelectedBreakpoint(newFunctionBreakpoint); + this.model.addFunctionBreakpoint(name || '', id); } - async renameFunctionBreakpoint(id: string, newFunctionName: string): Promise { - this.model.renameFunctionBreakpoint(id, newFunctionName); + async updateFunctionBreakpoint(id: string, update: { name?: string, hitCondition?: string, condition?: string }): Promise { + this.model.updateFunctionBreakpoint(id, update); this.debugStorage.storeBreakpoints(this.model); await this.sendFunctionBreakpoints(); } @@ -946,6 +928,11 @@ export class DebugService implements IDebugService { await this.sendDataBreakpoints(); } + setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void { + this.model.setExceptionBreakpoints(data); + this.debugStorage.storeBreakpoints(this.model); + } + async setExceptionBreakpointCondition(exceptionBreakpoint: IExceptionBreakpoint, condition: string | undefined): Promise { this.model.setExceptionBreakpointCondition(exceptionBreakpoint, condition); this.debugStorage.storeBreakpoints(this.model); diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 9b8c75afa..ec549fd04 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -64,7 +64,7 @@ export class DebugSession implements IDebugSession { private readonly _onDidChangeREPLElements = new Emitter(); - private name: string | undefined; + private _name: string | undefined; private readonly _onDidChangeName = new Emitter(); constructor( @@ -146,15 +146,18 @@ export class DebugSession implements IDebugSession { getLabel(): string { const includeRoot = this.workspaceContextService.getWorkspace().folders.length > 1; - const name = this.name || this.configuration.name; - return includeRoot && this.root ? `${name} (${resources.basenameOrAuthority(this.root.uri)})` : name; + return includeRoot && this.root ? `${this.name} (${resources.basenameOrAuthority(this.root.uri)})` : this.name; } setName(name: string): void { - this.name = name; + this._name = name; this._onDidChangeName.fire(name); } + get name(): string { + return this._name || this.configuration.name; + } + get state(): State { if (!this.initialized) { return State.Initializing; @@ -253,7 +256,7 @@ export class DebugSession implements IDebugSession { this.initialized = true; this._onDidChangeState.fire(); - this.model.setExceptionBreakpoints((this.raw && this.raw.capabilities.exceptionBreakpointFilters) || []); + this.debugService.setExceptionBreakpoints((this.raw && this.raw.capabilities.exceptionBreakpointFilters) || []); } catch (err) { this.initialized = true; this._onDidChangeState.fire(); @@ -1015,8 +1018,8 @@ export class DebugSession implements IDebugSession { this._onDidProgressEnd.fire(event); })); this.rawListeners.push(this.raw.onDidInvalidated(async event => { - if (!(event.body.areas && event.body.areas.length === 1 && event.body.areas[0] === 'variables')) { - // If invalidated event only requires to update variables, do that, otherwise refatch threads https://github.com/microsoft/vscode/issues/106745 + if (!(event.body.areas && event.body.areas.length === 1 && (event.body.areas[0] === 'variables' || event.body.areas[0] === 'watch'))) { + // If invalidated event only requires to update variables or watch, do that, otherwise refatch threads https://github.com/microsoft/vscode/issues/106745 this.cancelAllRequests(); this.model.clearThreads(this.getId(), true); await this.fetchThreads(this.stoppedDetails); diff --git a/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts b/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts index 689328eb3..cac07a988 100644 --- a/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts +++ b/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts @@ -169,12 +169,18 @@ export class DebugTaskRunner { return taskPromise.then(withUndefinedAsNull); }); - return new Promise((c, e) => { + return new Promise(async (c, e) => { + const waitForInput = new Promise(resolve => once(e => (e.kind === TaskEventKind.AcquiredInput) && e.taskId === task._id, this.taskService.onDidStateChange)(() => { + resolve(); + })); + promise.then(result => { taskStarted = true; c(result); }, error => e(error)); + await waitForInput; + setTimeout(() => { if (!taskStarted) { const errorMessage = typeof taskId === 'string' diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 8f62ac8d9..cfb91f7a3 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -8,28 +8,30 @@ import * as errors from 'vs/base/common/errors'; import * as browser from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import * as arrays from 'vs/base/common/arrays'; +import { localize } from 'vs/nls'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { IAction, IRunEvent, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, Separator } from 'vs/base/common/actions'; +import { IAction, IRunEvent, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IDebugConfiguration, IDebugService, State } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugConfiguration, IDebugService, State, CONTEXT_DEBUG_STATE, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_MULTI_SESSION_DEBUG, VIEWLET_ID } from 'vs/workbench/contrib/debug/common/debug'; import { FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems'; -import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { registerThemingParticipant, IThemeService, Themable, ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { registerColor, contrastBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; -import { localize } from 'vs/nls'; +import { IThemeService, Themable, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { contrastBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { createAndFillInActionBarActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IMenu, IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { FocusSessionAction } from 'vs/workbench/contrib/debug/browser/debugActions'; +import { createActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenu, IMenuService, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { IContextKeyService, ContextKeyExpression, ContextKeyExpr, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; +import { debugToolBarBackground, debugToolBarBorder } from 'vs/workbench/contrib/debug/browser/debugColors'; +import { URI } from 'vs/base/common/uri'; +import { CONTINUE_LABEL, CONTINUE_ID, PAUSE_ID, STOP_ID, DISCONNECT_ID, STEP_OVER_ID, STEP_INTO_ID, RESTART_SESSION_ID, STEP_OUT_ID, STEP_BACK_ID, REVERSE_CONTINUE_ID, RESTART_LABEL, STEP_OUT_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, DISCONNECT_LABEL, STOP_LABEL, PAUSE_LABEL, FOCUS_SESSION_ID, FOCUS_SESSION_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands'; const DEBUG_TOOLBAR_POSITION_KEY = 'debug.actionswidgetposition'; const DEBUG_TOOLBAR_Y_KEY = 'debug.actionswidgety'; @@ -75,15 +77,10 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { this.actionBar = this._register(new ActionBar(actionBarContainer, { orientation: ActionsOrientation.HORIZONTAL, actionViewItemProvider: (action: IAction) => { - if (action.id === FocusSessionAction.ID) { + if (action.id === FOCUS_SESSION_ID) { return this.instantiationService.createInstance(FocusSessionActionViewItem, action, undefined); - } else if (action instanceof MenuItemAction) { - return this.instantiationService.createInstance(MenuEntryActionViewItem, action); - } else if (action instanceof SubmenuItemAction) { - return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action); } - - return undefined; + return createActionViewItem(this.instantiationService, action); } })); @@ -94,7 +91,8 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { return this.hide(); } - const { actions, disposable } = DebugToolBar.getActions(this.debugToolBarMenu, this.debugService, this.instantiationService); + const actions: IAction[] = []; + const disposable = createAndFillInActionBarActions(this.debugToolBarMenu, { shouldForwardArgs: true }, actions, () => false); if (!arrays.equals(actions, this.activeActions, (first, second) => first.id === second.id && first.enabled === second.enabled)) { this.actionBar.clear(); this.actionBar.push(actions, { icon: true, label: false }); @@ -115,9 +113,11 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { private registerListeners(): void { this._register(this.debugService.onDidChangeState(() => this.updateScheduler.schedule())); - this._register(this.debugService.getViewModel().onDidFocusSession(() => this.updateScheduler.schedule())); - this._register(this.debugService.onDidNewSession(() => this.updateScheduler.schedule())); - this._register(this.configurationService.onDidChangeConfiguration(e => this.onDidConfigurationChange(e))); + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('debug.toolBarLocation')) { + this.updateScheduler.schedule(); + } + })); this._register(this.debugToolBarMenu.onDidChange(() => this.updateScheduler.schedule())); this._register(this.actionBar.actionRunner.onDidRun((e: IRunEvent) => { // check for error @@ -225,12 +225,6 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { } } - private onDidConfigurationChange(event: IConfigurationChangeEvent): void { - if (event.affectsConfiguration('debug.hideActionBar') || event.affectsConfiguration('debug.toolBarLocation')) { - this.updateScheduler.schedule(); - } - } - private show(): void { if (this.isVisible) { this.setCoordinates(); @@ -251,19 +245,6 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { dom.hide(this.$el); } - static getActions(menu: IMenu, debugService: IDebugService, instantiationService: IInstantiationService): { actions: IAction[], disposable: IDisposable } { - const actions: IAction[] = []; - const disposable = createAndFillInActionBarActions(menu, undefined, actions, () => false); - if (debugService.getViewModel().isMultiSessionView()) { - actions.push(instantiationService.createInstance(FocusSessionAction, FocusSessionAction.ID, FocusSessionAction.LABEL)); - } - - return { - actions: actions.filter(a => !(a instanceof Separator)), // do not render separators for now - disposable - }; - } - dispose(): void { super.dispose(); @@ -276,127 +257,43 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { } } -export const debugToolBarBackground = registerColor('debugToolBar.background', { - dark: '#333333', - light: '#F3F3F3', - hc: '#000000' -}, localize('debugToolBarBackground', "Debug toolbar background color.")); +// Debug toolbar -export const debugToolBarBorder = registerColor('debugToolBar.border', { - dark: null, - light: null, - hc: null -}, localize('debugToolBarBorder', "Debug toolbar border color.")); +const registerDebugToolBarItem = (id: string, title: string, order: number, icon?: { light?: URI, dark?: URI } | ThemeIcon, when?: ContextKeyExpression, precondition?: ContextKeyExpression) => { + MenuRegistry.appendMenuItem(MenuId.DebugToolBar, { + group: 'navigation', + when, + order, + command: { + id, + title, + icon, + precondition + } + }); -export const debugIconStartForeground = registerColor('debugIcon.startForeground', { - dark: '#89D185', - light: '#388A34', - hc: '#89D185' -}, localize('debugIcon.startForeground', "Debug toolbar icon for start debugging.")); + // Register actions in debug viewlet when toolbar is docked + MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, { + group: 'navigation', + when: ContextKeyExpr.and(when, ContextKeyEqualsExpr.create('viewContainer', VIEWLET_ID), CONTEXT_DEBUG_STATE.notEqualsTo('inactive'), ContextKeyExpr.equals('config.debug.toolBarLocation', 'docked')), + order, + command: { + id, + title, + icon, + precondition + } + }); +}; -export const debugIconPauseForeground = registerColor('debugIcon.pauseForeground', { - dark: '#75BEFF', - light: '#007ACC', - hc: '#75BEFF' -}, localize('debugIcon.pauseForeground', "Debug toolbar icon for pause.")); - -export const debugIconStopForeground = registerColor('debugIcon.stopForeground', { - dark: '#F48771', - light: '#A1260D', - hc: '#F48771' -}, localize('debugIcon.stopForeground', "Debug toolbar icon for stop.")); - -export const debugIconDisconnectForeground = registerColor('debugIcon.disconnectForeground', { - dark: '#F48771', - light: '#A1260D', - hc: '#F48771' -}, localize('debugIcon.disconnectForeground', "Debug toolbar icon for disconnect.")); - -export const debugIconRestartForeground = registerColor('debugIcon.restartForeground', { - dark: '#89D185', - light: '#388A34', - hc: '#89D185' -}, localize('debugIcon.restartForeground', "Debug toolbar icon for restart.")); - -export const debugIconStepOverForeground = registerColor('debugIcon.stepOverForeground', { - dark: '#75BEFF', - light: '#007ACC', - hc: '#75BEFF' -}, localize('debugIcon.stepOverForeground', "Debug toolbar icon for step over.")); - -export const debugIconStepIntoForeground = registerColor('debugIcon.stepIntoForeground', { - dark: '#75BEFF', - light: '#007ACC', - hc: '#75BEFF' -}, localize('debugIcon.stepIntoForeground', "Debug toolbar icon for step into.")); - -export const debugIconStepOutForeground = registerColor('debugIcon.stepOutForeground', { - dark: '#75BEFF', - light: '#007ACC', - hc: '#75BEFF' -}, localize('debugIcon.stepOutForeground', "Debug toolbar icon for step over.")); - -export const debugIconContinueForeground = registerColor('debugIcon.continueForeground', { - dark: '#75BEFF', - light: '#007ACC', - hc: '#75BEFF' -}, localize('debugIcon.continueForeground', "Debug toolbar icon for continue.")); - -export const debugIconStepBackForeground = registerColor('debugIcon.stepBackForeground', { - dark: '#75BEFF', - light: '#007ACC', - hc: '#75BEFF' -}, localize('debugIcon.stepBackForeground', "Debug toolbar icon for step back.")); - -registerThemingParticipant((theme, collector) => { - - const debugIconStartColor = theme.getColor(debugIconStartForeground); - if (debugIconStartColor) { - collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStart)} { color: ${debugIconStartColor} !important; }`); - } - - const debugIconPauseColor = theme.getColor(debugIconPauseForeground); - if (debugIconPauseColor) { - collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugPause)} { color: ${debugIconPauseColor} !important; }`); - } - - const debugIconStopColor = theme.getColor(debugIconStopForeground); - if (debugIconStopColor) { - collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStop)} { color: ${debugIconStopColor} !important; }`); - } - - const debugIconDisconnectColor = theme.getColor(debugIconDisconnectForeground); - if (debugIconDisconnectColor) { - collector.addRule(`.monaco-workbench .debug-view-content ${ThemeIcon.asCSSSelector(icons.debugDisconnect)}, .monaco-workbench .debug-toolbar ${ThemeIcon.asCSSSelector(icons.debugDisconnect)} { color: ${debugIconDisconnectColor} !important; }`); - } - - const debugIconRestartColor = theme.getColor(debugIconRestartForeground); - if (debugIconRestartColor) { - collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugRestart)}, .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugRestartFrame)} { color: ${debugIconRestartColor} !important; }`); - } - - const debugIconStepOverColor = theme.getColor(debugIconStepOverForeground); - if (debugIconStepOverColor) { - collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStepOver)} { color: ${debugIconStepOverColor} !important; }`); - } - - const debugIconStepIntoColor = theme.getColor(debugIconStepIntoForeground); - if (debugIconStepIntoColor) { - collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStepInto)} { color: ${debugIconStepIntoColor} !important; }`); - } - - const debugIconStepOutColor = theme.getColor(debugIconStepOutForeground); - if (debugIconStepOutColor) { - collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStepOut)} { color: ${debugIconStepOutColor} !important; }`); - } - - const debugIconContinueColor = theme.getColor(debugIconContinueForeground); - if (debugIconContinueColor) { - collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugContinue)}, .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugReverseContinue)} { color: ${debugIconContinueColor} !important; }`); - } - - const debugIconStepBackColor = theme.getColor(debugIconStepBackForeground); - if (debugIconStepBackColor) { - collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStepBack)} { color: ${debugIconStepBackColor} !important; }`); - } -}); +registerDebugToolBarItem(CONTINUE_ID, CONTINUE_LABEL, 10, icons.debugContinue, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolBarItem(PAUSE_ID, PAUSE_LABEL, 10, icons.debugPause, CONTEXT_DEBUG_STATE.notEqualsTo('stopped'), CONTEXT_DEBUG_STATE.isEqualTo('running')); +registerDebugToolBarItem(STOP_ID, STOP_LABEL, 70, icons.debugStop, CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated()); +registerDebugToolBarItem(DISCONNECT_ID, DISCONNECT_LABEL, 70, icons.debugDisconnect, CONTEXT_FOCUSED_SESSION_IS_ATTACH); +registerDebugToolBarItem(STEP_OVER_ID, STEP_OVER_LABEL, 20, icons.debugStepOver, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolBarItem(STEP_INTO_ID, STEP_INTO_LABEL, 30, icons.debugStepInto, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolBarItem(STEP_OUT_ID, STEP_OUT_LABEL, 40, icons.debugStepOut, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolBarItem(RESTART_SESSION_ID, RESTART_LABEL, 60, icons.debugRestart); +registerDebugToolBarItem(STEP_BACK_ID, localize('stepBackDebug', "Step Back"), 50, icons.debugStepBack, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolBarItem(REVERSE_CONTINUE_ID, localize('reverseContinue', "Reverse"), 60, icons.debugReverseContinue, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolBarItem(FOCUS_SESSION_ID, FOCUS_SESSION_LABEL, 100, undefined, CONTEXT_MULTI_SESSION_DEBUG); diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index 91187dd3a..d0749ded5 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -6,34 +6,36 @@ import 'vs/css!./media/debugViewlet'; import * as nls from 'vs/nls'; import { IAction, IActionViewItem } from 'vs/base/common/actions'; -import { IDebugService, VIEWLET_ID, State, BREAKPOINTS_VIEW_ID, IDebugConfiguration, CONTEXT_DEBUG_UX, CONTEXT_DEBUG_UX_KEY, REPL_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; -import { StartAction, ConfigureAction, SelectAndStartAction, FocusSessionAction } from 'vs/workbench/contrib/debug/browser/debugActions'; +import { IDebugService, VIEWLET_ID, State, BREAKPOINTS_VIEW_ID, CONTEXT_DEBUG_UX, CONTEXT_DEBUG_UX_KEY, REPL_VIEW_ID, CONTEXT_DEBUG_STATE, ILaunch, getStateLabel, CONTEXT_DEBUGGERS_AVAILABLE } from 'vs/workbench/contrib/debug/common/debug'; import { StartDebugActionViewItem, FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IProgressService } from 'vs/platform/progress/common/progress'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { memoize } from 'vs/base/common/decorators'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { DebugToolBar } from 'vs/workbench/contrib/debug/browser/debugToolBar'; -import { ViewPane, ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { IMenu, MenuId, IMenuService, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { ViewPaneContainer, ViewsSubMenu } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; +import { MenuId, registerAction2, Action2, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { IContextKeyService, ContextKeyEqualsExpr, ContextKeyExpr, ContextKeyDefinedExpr } from 'vs/platform/contextkey/common/contextkey'; +import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; import { WelcomeView } from 'vs/workbench/contrib/debug/browser/welcomeView'; -import { ToggleViewAction } from 'vs/workbench/browser/actions/layoutActions'; -import { RunOnceScheduler } from 'vs/base/common/async'; import { ShowViewletAction } from 'vs/workbench/browser/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { debugConsole } from 'vs/workbench/contrib/debug/browser/debugIcons'; +import { debugConfigure, debugConsole } from 'vs/workbench/contrib/debug/browser/debugIcons'; +import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { ToggleViewAction } from 'vs/workbench/browser/actions/layoutActions'; +import { FOCUS_SESSION_ID, SELECT_AND_START_ID, DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL, DEBUG_START_LABEL, DEBUG_START_COMMAND_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; export class DebugViewPaneContainer extends ViewPaneContainer { @@ -41,9 +43,6 @@ export class DebugViewPaneContainer extends ViewPaneContainer { private progressResolve: (() => void) | undefined; private breakpointView: ViewPane | undefined; private paneListeners = new Map(); - private debugToolBarMenu: IMenu | undefined; - private disposeOnTitleUpdate: IDisposable | undefined; - private updateToolBarScheduler: RunOnceScheduler; constructor( @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @@ -58,22 +57,13 @@ export class DebugViewPaneContainer extends ViewPaneContainer { @IExtensionService extensionService: IExtensionService, @IConfigurationService configurationService: IConfigurationService, @IContextViewService private readonly contextViewService: IContextViewService, - @IMenuService private readonly menuService: IMenuService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); - this.updateToolBarScheduler = this._register(new RunOnceScheduler(() => { - if (this.configurationService.getValue('debug').toolBarLocation === 'docked') { - this.updateTitleArea(); - } - }, 20)); - // When there are potential updates to the docked debug toolbar we need to update it this._register(this.debugService.onDidChangeState(state => this.onDebugServiceStateChange(state))); - this._register(this.debugService.onDidNewSession(() => this.updateToolBarScheduler.schedule())); - this._register(this.debugService.getViewModel().onDidFocusSession(() => this.updateToolBarScheduler.schedule())); this._register(this.contextKeyService.onDidChangeContext(e => { if (e.affectsSome(new Set([CONTEXT_DEBUG_UX_KEY]))) { @@ -104,83 +94,15 @@ export class DebugViewPaneContainer extends ViewPaneContainer { } } - @memoize - private get startAction(): StartAction { - return this._register(this.instantiationService.createInstance(StartAction, StartAction.ID, StartAction.LABEL)); - } - - @memoize - private get configureAction(): ConfigureAction { - return this._register(this.instantiationService.createInstance(ConfigureAction, ConfigureAction.ID, ConfigureAction.LABEL)); - } - - @memoize - private get toggleReplAction(): OpenDebugConsoleAction { - return this._register(this.instantiationService.createInstance(OpenDebugConsoleAction, OpenDebugConsoleAction.ID, OpenDebugConsoleAction.LABEL)); - } - - @memoize - private get selectAndStartAction(): SelectAndStartAction { - return this._register(this.instantiationService.createInstance(SelectAndStartAction, SelectAndStartAction.ID, nls.localize('startAdditionalSession', "Start Additional Session"))); - } - - getActions(): IAction[] { - if (CONTEXT_DEBUG_UX.getValue(this.contextKeyService) === 'simple') { - return []; - } - - if (!this.showInitialDebugActions) { - - if (!this.debugToolBarMenu) { - this.debugToolBarMenu = this.menuService.createMenu(MenuId.DebugToolBar, this.contextKeyService); - this._register(this.debugToolBarMenu); - this._register(this.debugToolBarMenu.onDidChange(() => this.updateToolBarScheduler.schedule())); - } - - const { actions, disposable } = DebugToolBar.getActions(this.debugToolBarMenu, this.debugService, this.instantiationService); - if (this.disposeOnTitleUpdate) { - dispose(this.disposeOnTitleUpdate); - } - this.disposeOnTitleUpdate = disposable; - - return actions; - } - - if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { - return [this.toggleReplAction]; - } - - return [this.startAction, this.configureAction, this.toggleReplAction]; - } - - get showInitialDebugActions(): boolean { - const state = this.debugService.state; - return state === State.Inactive || this.configurationService.getValue('debug').toolBarLocation !== 'docked'; - } - - getSecondaryActions(): IAction[] { - if (this.showInitialDebugActions) { - return []; - } - - return [this.selectAndStartAction, this.configureAction, this.toggleReplAction]; - } - getActionViewItem(action: IAction): IActionViewItem | undefined { - if (action.id === StartAction.ID) { + if (action.id === DEBUG_START_COMMAND_ID) { this.startDebugActionViewItem = this.instantiationService.createInstance(StartDebugActionViewItem, null, action); return this.startDebugActionViewItem; } - if (action.id === FocusSessionAction.ID) { + if (action.id === FOCUS_SESSION_ID) { return new FocusSessionActionViewItem(action, undefined, this.debugService, this.themeService, this.contextViewService, this.configurationService); } - if (action instanceof MenuItemAction) { - return this.instantiationService.createInstance(MenuEntryActionViewItem, action); - } else if (action instanceof SubmenuItemAction) { - return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action); - } - - return undefined; + return createActionViewItem(this.instantiationService, action); } focusView(id: string): void { @@ -201,8 +123,6 @@ export class DebugViewPaneContainer extends ViewPaneContainer { return new Promise(resolve => this.progressResolve = resolve); }); } - - this.updateToolBarScheduler.schedule(); } addPanes(panes: { pane: ViewPane, size: number, index?: number }[]): void { @@ -236,22 +156,6 @@ export class DebugViewPaneContainer extends ViewPaneContainer { } } -export class OpenDebugConsoleAction extends ToggleViewAction { - public static readonly ID = 'workbench.debug.action.toggleRepl'; - public static readonly LABEL = nls.localize('toggleDebugPanel', "Debug Console"); - - constructor( - id: string, - label: string, - @IViewsService viewsService: IViewsService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IContextKeyService contextKeyService: IContextKeyService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService - ) { - super(id, label, REPL_VIEW_ID, viewsService, viewDescriptorService, contextKeyService, layoutService, ThemeIcon.asClassName(debugConsole)); - } -} - export class OpenDebugViewletAction extends ShowViewletAction { public static readonly ID = VIEWLET_ID; public static readonly LABEL = nls.localize('toggleDebugViewlet', "Show Run and Debug"); @@ -266,3 +170,139 @@ export class OpenDebugViewletAction extends ShowViewletAction { super(id, label, VIEWLET_ID, viewletService, editorGroupService, layoutService); } } + +MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, { + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('viewContainer', VIEWLET_ID), CONTEXT_DEBUG_UX.notEqualsTo('simple'), WorkbenchStateContext.notEqualsTo('empty'), + ContextKeyExpr.or(CONTEXT_DEBUG_STATE.isEqualTo('inactive'), ContextKeyExpr.notEquals('config.debug.toolBarLocation', 'docked'))), + order: 10, + group: 'navigation', + command: { + precondition: CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing)), + id: DEBUG_START_COMMAND_ID, + title: DEBUG_START_LABEL + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: DEBUG_CONFIGURE_COMMAND_ID, + title: { + value: DEBUG_CONFIGURE_LABEL, + original: DEBUG_CONFIGURE_LABEL, + mnemonicTitle: nls.localize({ key: 'miOpenConfigurations', comment: ['&& denotes a mnemonic'] }, "Open &&Configurations") + }, + f1: true, + icon: debugConfigure, + precondition: CONTEXT_DEBUG_UX.notEqualsTo('simple'), + menu: [{ + id: MenuId.ViewContainerTitle, + group: 'navigation', + order: 20, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('viewContainer', VIEWLET_ID), CONTEXT_DEBUG_UX.notEqualsTo('simple'), WorkbenchStateContext.notEqualsTo('empty'), + ContextKeyExpr.or(CONTEXT_DEBUG_STATE.isEqualTo('inactive'), ContextKeyExpr.notEquals('config.debug.toolBarLocation', 'docked'))) + }, { + id: MenuId.ViewContainerTitle, + order: 20, + // Show in debug viewlet secondary actions when debugging and debug toolbar is docked + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('viewContainer', VIEWLET_ID), CONTEXT_DEBUG_STATE.notEqualsTo('inactive'), ContextKeyExpr.equals('config.debug.toolBarLocation', 'docked')) + }, { + id: MenuId.MenubarDebugMenu, + group: '2_configuration', + order: 1, + when: CONTEXT_DEBUGGERS_AVAILABLE + }] + }); + } + + async run(accessor: ServicesAccessor): Promise { + const debugService = accessor.get(IDebugService); + const quickInputService = accessor.get(IQuickInputService); + const configurationManager = debugService.getConfigurationManager(); + let launch: ILaunch | undefined; + if (configurationManager.selectedConfiguration.name) { + launch = configurationManager.selectedConfiguration.launch; + } else { + const launches = configurationManager.getLaunches().filter(l => !l.hidden); + if (launches.length === 1) { + launch = launches[0]; + } else { + const picks = launches.map(l => ({ label: l.name, launch: l })); + const picked = await quickInputService.pick<{ label: string, launch: ILaunch }>(picks, { + activeItem: picks[0], + placeHolder: nls.localize({ key: 'selectWorkspaceFolder', comment: ['User picks a workspace folder or a workspace configuration file here. Workspace configuration files can contain settings and thus a launch.json configuration can be written into one.'] }, "Select a workspace folder to create a launch.json file in or add it to the workspace config file") + }); + if (picked) { + launch = picked.launch; + } + } + } + + if (launch) { + await launch.openConfigFile(false); + } + } +}); + +export const OPEN_REPL_COMMAND_ID = 'workbench.debug.action.toggleRepl'; +registerAction2(class extends Action2 { + constructor() { + super({ + id: OPEN_REPL_COMMAND_ID, + title: { + value: nls.localize('debugPanel', "Debug Console"), + original: 'Debug Console', + mnemonicTitle: nls.localize({ key: 'miToggleDebugConsole', comment: ['&& denotes a mnemonic'] }, "De&&bug Console") + }, + f1: true, + keybinding: { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Y, + weight: KeybindingWeight.WorkbenchContrib + }, + icon: debugConsole, + menu: [{ + id: MenuId.MenubarViewMenu, + group: '4_panels', + order: 2 + }] + }); + } + + async run(accessor: ServicesAccessor): Promise { + return accessor.get(IInstantiationService).createInstance(ToggleViewAction, OPEN_REPL_COMMAND_ID, 'Debug Console', REPL_VIEW_ID).run(); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'debug.toggleReplIgnoreFocus', + title: nls.localize('debugPanel', "Debug Console"), + toggled: ContextKeyDefinedExpr.create(`view.${REPL_VIEW_ID}.visible`), + menu: [{ + id: ViewsSubMenu, + group: '3_toggleRepl', + order: 30, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('viewContainer', VIEWLET_ID)) + }] + }); + } + + async run(accessor: ServicesAccessor): Promise { + const viewsService = accessor.get(IViewsService); + if (viewsService.isViewVisible(REPL_VIEW_ID)) { + viewsService.closeView(REPL_VIEW_ID); + } else { + await viewsService.openView(REPL_VIEW_ID); + } + } +}); + +MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, { + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('viewContainer', VIEWLET_ID), CONTEXT_DEBUG_STATE.notEqualsTo('inactive'), ContextKeyExpr.equals('config.debug.toolBarLocation', 'docked')), + order: 10, + command: { + id: SELECT_AND_START_ID, + title: nls.localize('startAdditionalSession', "Start Additional Session"), + } +}); diff --git a/src/vs/workbench/contrib/debug/browser/linkDetector.ts b/src/vs/workbench/contrib/debug/browser/linkDetector.ts index e927b779b..14ea88967 100644 --- a/src/vs/workbench/contrib/debug/browser/linkDetector.ts +++ b/src/vs/workbench/contrib/debug/browser/linkDetector.ts @@ -3,16 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Schemas } from 'vs/base/common/network'; import * as osPath from 'vs/base/common/path'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; -import * as nls from 'vs/nls'; import { IFileService } from 'vs/platform/files/common/files'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyCode } from 'vs/base/common/keyCodes'; const CONTROL_CODES = '\\u0000-\\u0020\\u007f-\\u009f'; const WEB_LINK_REGEX = new RegExp('(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\\/\\/|data:|www\\.)[^\\s' + CONTROL_CODES + '"]{2,}[^\\s' + CONTROL_CODES + '"\')}\\],:;.!?]', 'ug'); @@ -97,8 +99,28 @@ export class LinkDetector { private createWebLink(url: string): Node { const link = this.createLink(url); - const uri = URI.parse(url); - this.decorateLink(link, () => this.openerService.open(uri, { allowTunneling: !!this.environmentService.remoteAuthority })); + + this.decorateLink(link, async () => { + const uri = URI.parse(url); + + if (uri.scheme === Schemas.file) { + // Just using fsPath here is unsafe: https://github.com/microsoft/vscode/issues/109076 + const fsPath = uri.fsPath; + const path = await this.pathService.path; + const fileUrl = osPath.normalize(((path.sep === osPath.posix.sep) && platform.isWindows) ? fsPath.replace(/\\/g, osPath.posix.sep) : fsPath); + + const resolvedLink = await this.fileService.resolve(URI.parse(fileUrl)); + if (!resolvedLink) { + return; + } + + await this.editorService.openEditor({ resource: resolvedLink.resource, options: { pinned: true } }); + return; + } + + this.openerService.open(url, { allowTunneling: !!this.environmentService.remoteAuthority }); + }); + return link; } @@ -108,14 +130,14 @@ export class LinkDetector { return document.createTextNode(text); } + const options = { selection: { startLineNumber: lineNumber, startColumn: columnNumber } }; if (path[0] === '.') { if (!workspaceFolder) { return document.createTextNode(text); } const uri = workspaceFolder.toResource(path); - const options = { selection: { startLineNumber: lineNumber, startColumn: columnNumber } }; const link = this.createLink(text); - this.decorateLink(link, () => this.editorService.openEditor({ resource: uri, options })); + this.decorateLink(link, (preserveFocus: boolean) => this.editorService.openEditor({ resource: uri, options: { ...options, preserveFocus } })); return link; } @@ -127,13 +149,13 @@ export class LinkDetector { } const link = this.createLink(text); + link.tabIndex = 0; const uri = URI.file(osPath.normalize(path)); this.fileService.resolve(uri).then(stat => { if (stat.isDirectory) { return; } - const options = { selection: { startLineNumber: lineNumber, startColumn: columnNumber } }; - this.decorateLink(link, () => this.editorService.openEditor({ resource: uri, options })); + this.decorateLink(link, (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 }); @@ -146,9 +168,8 @@ export class LinkDetector { return link; } - private decorateLink(link: HTMLElement, onclick: () => void) { + private decorateLink(link: HTMLElement, onClick: (preserveFocus: boolean) => void) { link.classList.add('link'); - link.title = platform.isMacintosh ? nls.localize('fileLinkMac', "Cmd + click to follow link") : nls.localize('fileLink', "Ctrl + click to follow link"); link.onmousemove = (event) => { link.classList.toggle('pointer', platform.isMacintosh ? event.metaKey : event.ctrlKey); }; link.onmouseleave = () => link.classList.remove('pointer'); link.onclick = (event) => { @@ -156,12 +177,17 @@ export class LinkDetector { if (!selection || selection.type === 'Range') { return; // do not navigate when user is selecting } - if (!(platform.isMacintosh ? event.metaKey : event.ctrlKey)) { - return; - } event.preventDefault(); event.stopImmediatePropagation(); - onclick(); + onClick(false); + }; + link.onkeydown = e => { + const event = new StandardKeyboardEvent(e); + if (event.keyCode === KeyCode.Enter || event.keyCode === KeyCode.Space) { + event.preventDefault(); + event.stopPropagation(); + onClick(event.keyCode === KeyCode.Space); + } }; } diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index 198da9108..6ebf580c2 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { normalize, isAbsolute, posix } from 'vs/base/common/path'; -import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index 47cafc6ae..5487c7f7e 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -144,22 +144,22 @@ display: none; } -.debug-pane .debug-call-stack .monaco-list-row .monaco-action-bar { +.debug-pane .monaco-list-row .monaco-action-bar { display: none; flex-shrink: 0; margin-right: 1px; } -.debug-pane .debug-call-stack .monaco-list-row:hover .monaco-action-bar { +.debug-pane .monaco-list-row:hover .monaco-action-bar { display: initial; } -.debug-pane .debug-call-stack .session .codicon { +.debug-pane .session .codicon { line-height: 22px; margin-right: 2px; } -.monaco-workbench .debug-pane .debug-call-stack .monaco-action-bar .action-item > .action-label { +.monaco-workbench .debug-pane .monaco-action-bar .action-item > .action-label { width: 16px; height: 100%; line-height: 22px; @@ -259,11 +259,11 @@ font-family: var(--monaco-monospace-font); } -.debug-pane .monaco-inputbox > .wrapper { +.debug-pane .monaco-inputbox > .ibwrapper { height: 19px; } -.debug-pane .monaco-inputbox > .wrapper > .input { +.debug-pane .monaco-inputbox > .ibwrapper > .input { padding: 0px; color: initial; } @@ -319,7 +319,7 @@ } .debug-pane .debug-breakpoints .breakpoint > .file-path, -.debug-pane .debug-breakpoints .breakpoint.exception > .condition { +.debug-pane .debug-breakpoints .breakpoint > .condition { opacity: 0.7; margin-left: 0.9em; flex: 1; diff --git a/src/vs/workbench/contrib/debug/browser/media/repl.css b/src/vs/workbench/contrib/debug/browser/media/repl.css index 04de3a219..d48c7b775 100644 --- a/src/vs/workbench/contrib/debug/browser/media/repl.css +++ b/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -17,6 +17,10 @@ white-space: pre; } +.monaco-workbench .repl .repl-tree .monaco-tl-contents .expression { + font-family: var(--vscode-repl-font-family); +} + .monaco-workbench .repl .repl-tree.word-wrap .monaco-tl-contents { /* Wrap words but also do not trim whitespace #6275 */ word-wrap: break-word; diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index a70036599..19e9bc3b6 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -154,6 +154,10 @@ export class RawDebugSession implements IDisposable { case 'invalidated': this._onDidInvalidated.fire(event as DebugProtocol.InvalidatedEvent); break; + case 'process': + break; + case 'module': + break; default: this._onDidCustomEvent.fire(event); break; diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 74467fa80..1afbbaf12 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/repl'; import { URI as uri } from 'vs/base/common/uri'; -import { IAction, IActionViewItem, Action, Separator } from 'vs/base/common/actions'; +import { IAction, IActionViewItem } from 'vs/base/common/actions'; import * as dom from 'vs/base/browser/dom'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -14,11 +14,11 @@ import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; import { ITextModel } from 'vs/editor/common/model'; import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; -import { registerEditorAction, ServicesAccessor, EditorAction } from 'vs/editor/browser/editorExtensions'; +import { registerEditorAction, EditorAction } from 'vs/editor/browser/editorExtensions'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IContextKeyService, IContextKey, ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -26,7 +26,7 @@ import { memoize } from 'vs/base/common/decorators'; import { dispose, IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { IDebugService, DEBUG_SCHEME, CONTEXT_IN_DEBUG_REPL, IDebugSession, State, IReplElement, IDebugConfiguration, REPL_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, DEBUG_SCHEME, CONTEXT_IN_DEBUG_REPL, IDebugSession, State, IReplElement, IDebugConfiguration, REPL_VIEW_ID, CONTEXT_MULTI_SESSION_REPL, CONTEXT_DEBUG_STATE, getStateLabel } from 'vs/workbench/contrib/debug/common/debug'; import { HistoryNavigator } from 'vs/base/common/history'; import { IHistoryNavigationWidget } from 'vs/base/browser/history'; import { createAndBindHistoryNavigationWidgetScopedContextKeyService } from 'vs/platform/browser/contextScopedHistoryWidget'; @@ -50,7 +50,7 @@ import { FuzzyScore } from 'vs/base/common/filters'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ReplDelegate, ReplVariablesRenderer, ReplSimpleElementsRenderer, ReplEvaluationInputsRenderer, ReplEvaluationResultsRenderer, ReplRawObjectsRenderer, ReplDataSource, ReplAccessibilityProvider, ReplGroupRenderer } from 'vs/workbench/contrib/debug/browser/replViewer'; import { localize } from 'vs/nls'; -import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane, IViewPaneOptions, ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IViewsService, IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -60,6 +60,8 @@ import { EDITOR_FONT_DEFAULTS, EditorOption } from 'vs/editor/common/config/edit import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor'; import { ReplFilter, ReplFilterState, ReplFilterActionViewItem } from 'vs/workbench/contrib/debug/browser/replFilter'; import { debugConsoleClearAll, debugConsoleEvaluationPrompt } from 'vs/workbench/contrib/debug/browser/debugIcons'; +import { registerAction2, MenuId, Action2, IMenuService, IMenu } from 'vs/platform/actions/common/actions'; +import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; const $ = dom.$; @@ -73,17 +75,19 @@ function revealLastElement(tree: WorkbenchAsyncDataTree) { } const sessionsToIgnore = new Set(); +const identityProvider = { getId: (element: IReplElement) => element.getId() }; export class Repl extends ViewPane implements IHistoryNavigationWidget { declare readonly _serviceBrand: undefined; - private static readonly REFRESH_DELAY = 100; // delay in ms to refresh the repl for new elements to show + private static readonly REFRESH_DELAY = 50; // delay in ms to refresh the repl for new elements to show private static readonly URI = uri.parse(`${DEBUG_SCHEME}:replinput`); private history: HistoryNavigator; private tree!: WorkbenchAsyncDataTree; private replDelegate!: ReplDelegate; private container!: HTMLElement; + private treeContainer!: HTMLElement; private replInput!: CodeEditorWidget; private replInputContainer!: HTMLElement; private dimension!: dom.Dimension; @@ -98,6 +102,8 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { private filter: ReplFilter; private filterState: ReplFilterState; private filterActionViewItem: ReplFilterActionViewItem | undefined; + private multiSessionRepl: IContextKey; + private menu: IMenu; constructor( options: IViewPaneOptions, @@ -112,17 +118,21 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, @ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService, - @IClipboardService private readonly clipboardService: IClipboardService, @IEditorService private readonly editorService: IEditorService, @IKeybindingService keybindingService: IKeybindingService, @IOpenerService openerService: IOpenerService, @ITelemetryService telemetryService: ITelemetryService, + @IMenuService menuService: IMenuService ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + 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.filter = new ReplFilter(); this.filterState = new ReplFilterState(this); + this.multiSessionRepl = CONTEXT_MULTI_SESSION_REPL.bindTo(contextKeyService); + this.multiSessionRepl.set(this.isMultiSessionView); codeEditorService.registerDecorationType(DECORATION_KEY, {}); this.registerListeners(); @@ -207,7 +217,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { if (!input || input.state === State.Inactive) { await this.selectSession(newSession); } - this.updateActions(); + this.multiSessionRepl.set(this.isMultiSessionView); })); this._register(this.themeService.onDidColorThemeChange(() => { this.refreshReplElements(false); @@ -224,10 +234,16 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { this.replInput.setModel(this.model); this.updateInputDecoration(); this.refreshReplElements(true); + this.layoutBody(this.dimension.height, this.dimension.width); } })); this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('debug.console.lineHeight') || e.affectsConfiguration('debug.console.fontSize') || e.affectsConfiguration('debug.console.fontFamily')) { + if (e.affectsConfiguration('debug.console.wordWrap')) { + this.tree.dispose(); + this.treeContainer.innerText = ''; + dom.clearNode(this.treeContainer); + this.createReplTree(); + } else if (e.affectsConfiguration('debug.console.lineHeight') || e.affectsConfiguration('debug.console.fontSize') || e.affectsConfiguration('debug.console.fontFamily')) { this.onDidStyleChange(); } })); @@ -305,7 +321,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { if (this.styleElement) { const debugConsole = this.configurationService.getValue('debug').console; const fontSize = debugConsole.fontSize; - const fontFamily = debugConsole.fontFamily === 'default' ? 'var(--monaco-monospace-font)' : `'${debugConsole.fontFamily}'`; + const fontFamily = debugConsole.fontFamily === 'default' ? 'var(--monaco-monospace-font)' : `${debugConsole.fontFamily}`; const lineHeight = debugConsole.lineHeight ? `${debugConsole.lineHeight}px` : '1.4em'; const backgroundColor = this.themeService.getColorTheme().getColor(this.getBackgroundColor()); @@ -321,7 +337,6 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { this.styleElement.textContent = ` .repl .repl-tree .expression { font-size: ${fontSize}px; - font-family: ${fontFamily}; } .repl .repl-tree .expression { @@ -340,6 +355,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { background-color: ${backgroundColor}; } `; + this.container.style.setProperty(`--vscode-repl-font-family`, fontFamily); this.tree.rerender(); @@ -397,7 +413,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { // Ignore inactive sessions which got cleared - so they are not shown any more sessionsToIgnore.add(session); await this.selectSession(); - this.updateActions(); + this.multiSessionRepl.set(this.isMultiSessionView); } } this.replInput.focus(); @@ -455,14 +471,22 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { this.replInput.layout({ width: width - 30, height: replInputHeight }); } + collapseAll(): void { + this.tree.collapseAll(); + } + + getReplInput(): CodeEditorWidget { + return this.replInput; + } + focus(): void { setTimeout(() => this.replInput.focus(), 0); } getActionViewItem(action: IAction): IActionViewItem | undefined { - if (action.id === SelectReplAction.ID) { + if (action.id === selectReplCommandId) { const session = (this.tree ? this.tree.getInput() : undefined) ?? this.debugService.getViewModel().focusedSession; - return this.instantiationService.createInstance(SelectReplActionViewItem, this.selectReplAction, session); + return this.instantiationService.createInstance(SelectReplActionViewItem, action, session); } else if (action.id === FILTER_ACTION_ID) { const filterHistory = JSON.parse(this.storageService.get(FILTER_HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')) as string[]; this.filterActionViewItem = this.instantiationService.createInstance(ReplFilterActionViewItem, action, @@ -473,29 +497,11 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { return super.getActionViewItem(action); } - getActions(): IAction[] { - const result: IAction[] = []; - result.push(new Action(FILTER_ACTION_ID)); - if (this.debugService.getModel().getSessions(true).filter(s => s.hasSeparateRepl() && !sessionsToIgnore.has(s)).length > 1) { - result.push(this.selectReplAction); - } - result.push(this.clearReplAction); - - result.forEach(a => this._register(a)); - - return result; + private get isMultiSessionView(): boolean { + return this.debugService.getModel().getSessions(true).filter(s => s.hasSeparateRepl() && !sessionsToIgnore.has(s)).length > 1; } // --- Cached locals - @memoize - private get selectReplAction(): SelectReplAction { - return this.instantiationService.createInstance(SelectReplAction, SelectReplAction.ID, SelectReplAction.LABEL); - } - - @memoize - private get clearReplAction(): ClearReplAction { - return this.instantiationService.createInstance(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL); - } @memoize private get refreshScheduler(): RunOnceScheduler { @@ -506,7 +512,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { } const lastElementVisible = this.tree.scrollTop + this.tree.renderHeight >= this.tree.scrollHeight; - await this.tree.updateChildren(); + await this.tree.updateChildren(undefined, true, false, { diffIdentityProvider: identityProvider }); const session = this.tree.getInput(); if (session) { @@ -541,25 +547,27 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { protected renderBody(parent: HTMLElement): void { super.renderBody(parent); - this.container = dom.append(parent, $('.repl')); - const treeContainer = dom.append(this.container, $(`.repl-tree.${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`)); + this.treeContainer = dom.append(this.container, $(`.repl-tree.${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`)); this.createReplInput(this.container); + this.createReplTree(); + } + private createReplTree(): void { this.replDelegate = new ReplDelegate(this.configurationService); const wordWrap = this.configurationService.getValue('debug').console.wordWrap; - treeContainer.classList.toggle('word-wrap', wordWrap); + this.treeContainer.classList.toggle('word-wrap', wordWrap); const linkDetector = this.instantiationService.createInstance(LinkDetector); this.tree = >this.instantiationService.createInstance( WorkbenchAsyncDataTree, 'DebugRepl', - treeContainer, + this.treeContainer, this.replDelegate, [ this.instantiationService.createInstance(ReplVariablesRenderer, linkDetector), this.instantiationService.createInstance(ReplSimpleElementsRenderer, linkDetector), new ReplEvaluationInputsRenderer(), - new ReplGroupRenderer(), + this.instantiationService.createInstance(ReplGroupRenderer, linkDetector), new ReplEvaluationResultsRenderer(linkDetector), new ReplRawObjectsRenderer(linkDetector), ], @@ -568,7 +576,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { { filter: this.filter, accessibilityProvider: new ReplAccessibilityProvider(), - identityProvider: { getId: (element: IReplElement) => element.getId() }, + identityProvider, mouseSupport: false, keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IReplElement) => e }, horizontalScrolling: !wordWrap, @@ -598,7 +606,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { this.replInputContainer = dom.append(container, $('.repl-input-wrapper')); dom.append(this.replInputContainer, $('.repl-input-chevron' + ThemeIcon.asCSSSelector(debugConsoleEvaluationPrompt))); - const { scopedContextKeyService, historyNavigationEnablement } = createAndBindHistoryNavigationWidgetScopedContextKeyService(this.contextKeyService, { target: this.replInputContainer, historyNavigator: this }); + const { scopedContextKeyService, historyNavigationEnablement } = createAndBindHistoryNavigationWidgetScopedContextKeyService(this.contextKeyService, { target: container, historyNavigator: this }); this.historyNavigationEnablement = historyNavigationEnablement; this._register(scopedContextKeyService); CONTEXT_IN_DEBUG_REPL.bindTo(scopedContextKeyService).set(true); @@ -629,44 +637,12 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { private onContextMenu(e: ITreeContextMenuEvent): void { const actions: IAction[] = []; - actions.push(new Action('debug.replCopy', localize('copy', "Copy"), undefined, true, async () => { - const nativeSelection = window.getSelection(); - if (nativeSelection) { - await this.clipboardService.writeText(nativeSelection.toString()); - } - return Promise.resolve(); - })); - actions.push(new Action('workbench.debug.action.copyAll', localize('copyAll', "Copy All"), undefined, true, async () => { - await this.clipboardService.writeText(this.getVisibleContent()); - return Promise.resolve(); - })); - actions.push(new Action('debug.replPaste', localize('paste', "Paste"), undefined, this.debugService.state !== State.Inactive, async () => { - const clipboardText = await this.clipboardService.readText(); - if (clipboardText) { - this.replInput.setValue(this.replInput.getValue().concat(clipboardText)); - this.replInput.focus(); - const model = this.replInput.getModel(); - const lineNumber = model ? model.getLineCount() : 0; - const column = model?.getLineMaxColumn(lineNumber); - if (typeof lineNumber === 'number' && typeof column === 'number') { - this.replInput.setPosition({ lineNumber, column }); - } - } - })); - actions.push(new Separator()); - actions.push(new Action('debug.collapseRepl', localize('collapse', "Collapse All"), undefined, true, () => { - this.tree.collapseAll(); - this.replInput.focus(); - return Promise.resolve(); - })); - actions.push(new Separator()); - actions.push(this.clearReplAction); - + const actionsDisposable = createAndFillInContextMenuActions(this.menu, { arg: e.element, shouldForwardArgs: false }, actions); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => actions, getActionsContext: () => e.element, - onHide: () => dispose(actions) + onHide: () => dispose(actionsDisposable) }); } @@ -825,48 +801,183 @@ class SelectReplActionViewItem extends FocusSessionActionViewItem { } } -class SelectReplAction extends Action { - - static readonly ID = 'workbench.action.debug.selectRepl'; - static readonly LABEL = localize('selectRepl', "Select Debug Console"); - - constructor(id: string, label: string, - @IDebugService private readonly debugService: IDebugService, - @IViewsService private readonly viewsService: IViewsService - ) { - super(id, label); - } - - async run(session: IDebugSession): Promise { - // If session is already the focused session we need to manualy update the tree since view model will not send a focused change event - if (session && session.state !== State.Inactive && session !== this.debugService.getViewModel().focusedSession) { - await this.debugService.focusStackFrame(undefined, undefined, session, true); - } else { - const repl = getReplView(this.viewsService); - if (repl) { - await repl.selectSession(session); - } - } - } -} - -export class ClearReplAction extends Action { - static readonly ID = 'workbench.debug.panel.action.clearReplAction'; - static readonly LABEL = localize('clearRepl', "Clear Console"); - - constructor(id: string, label: string, - @IViewsService private readonly viewsService: IViewsService - ) { - super(id, label, 'debug-action ' + ThemeIcon.asClassName(debugConsoleClearAll)); - } - - async run(): Promise { - const view = await this.viewsService.openView(REPL_VIEW_ID) as Repl; - await view.clearRepl(); - aria.status(localize('debugConsoleCleared', "Debug console was cleared")); - } -} - function getReplView(viewsService: IViewsService): Repl | undefined { return viewsService.getActiveViewWithId(REPL_VIEW_ID) as Repl ?? undefined; } + +registerAction2(class extends Action2 { + constructor() { + super({ + id: FILTER_ACTION_ID, + title: localize('filter', "Filter"), + f1: true, + menu: { + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyEqualsExpr.create('view', REPL_VIEW_ID), + order: 10 + } + }); + } + + run(_accessor: ServicesAccessor) { + // noop this action is just a placeholder for the filter action view item + } +}); + +const selectReplCommandId = 'workbench.action.debug.selectRepl'; +registerAction2(class extends ViewAction { + constructor() { + super({ + id: selectReplCommandId, + viewId: REPL_VIEW_ID, + title: localize('selectRepl', "Select Debug Console"), + f1: true, + icon: debugConsoleClearAll, + menu: { + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', REPL_VIEW_ID), CONTEXT_MULTI_SESSION_REPL), + order: 20 + } + }); + } + + async runInView(accessor: ServicesAccessor, view: Repl, session: IDebugSession | undefined) { + const debugService = accessor.get(IDebugService); + // If session is already the focused session we need to manualy update the tree since view model will not send a focused change event + if (session && session.state !== State.Inactive && session !== debugService.getViewModel().focusedSession) { + if (session.state !== State.Stopped) { + // Focus child session instead if it is stopped #112595 + const stopppedChildSession = debugService.getModel().getSessions().find(s => s.parentSession === session); + if (stopppedChildSession) { + session = stopppedChildSession; + } + } + await debugService.focusStackFrame(undefined, undefined, session, true); + } else { + await view.selectSession(session); + } + } +}); + +registerAction2(class extends ViewAction { + constructor() { + super({ + id: 'workbench.debug.panel.action.clearReplAction', + viewId: REPL_VIEW_ID, + title: localize('clearRepl', "Clear Console"), + f1: true, + icon: debugConsoleClearAll, + menu: [{ + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyEqualsExpr.create('view', REPL_VIEW_ID), + order: 30 + }, { + id: MenuId.DebugConsoleContext, + group: 'z_commands', + order: 20 + }] + }); + } + + runInView(_accessor: ServicesAccessor, view: Repl): void { + view.clearRepl(); + aria.status(localize('debugConsoleCleared', "Debug console was cleared")); + } +}); + +registerAction2(class extends ViewAction { + constructor() { + super({ + id: 'debug.collapseRepl', + title: localize('collapse', "Collapse All"), + viewId: REPL_VIEW_ID, + menu: { + id: MenuId.DebugConsoleContext, + group: 'z_commands', + order: 10 + } + }); + } + + runInView(_accessor: ServicesAccessor, view: Repl): void { + view.collapseAll(); + view.focus(); + } +}); + +registerAction2(class extends ViewAction { + constructor() { + super({ + id: 'debug.replPaste', + title: localize('paste', "Paste"), + viewId: REPL_VIEW_ID, + precondition: CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Inactive)), + menu: { + id: MenuId.DebugConsoleContext, + group: '2_cutcopypaste', + order: 30 + } + }); + } + + async runInView(accessor: ServicesAccessor, view: Repl): Promise { + const clipboardService = accessor.get(IClipboardService); + const clipboardText = await clipboardService.readText(); + if (clipboardText) { + const replInput = view.getReplInput(); + replInput.setValue(replInput.getValue().concat(clipboardText)); + view.focus(); + const model = replInput.getModel(); + const lineNumber = model ? model.getLineCount() : 0; + const column = model?.getLineMaxColumn(lineNumber); + if (typeof lineNumber === 'number' && typeof column === 'number') { + replInput.setPosition({ lineNumber, column }); + } + } + } +}); + +registerAction2(class extends ViewAction { + constructor() { + super({ + id: 'workbench.debug.action.copyAll', + title: localize('copyAll', "Copy All"), + viewId: REPL_VIEW_ID, + menu: { + id: MenuId.DebugConsoleContext, + group: '2_cutcopypaste', + order: 20 + } + }); + } + + async runInView(accessor: ServicesAccessor, view: Repl): Promise { + const clipboardService = accessor.get(IClipboardService); + await clipboardService.writeText(view.getVisibleContent()); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'debug.replCopy', + title: localize('copy', "Copy"), + menu: { + id: MenuId.DebugConsoleContext, + group: '2_cutcopypaste', + order: 10 + } + }); + } + + async run(accessor: ServicesAccessor): Promise { + const clipboardService = accessor.get(IClipboardService); + const nativeSelection = window.getSelection(); + if (nativeSelection) { + await clipboardService.writeText(nativeSelection.toString()); + } + } +}); diff --git a/src/vs/workbench/contrib/debug/browser/replViewer.ts b/src/vs/workbench/contrib/debug/browser/replViewer.ts index 0d432417f..937976c63 100644 --- a/src/vs/workbench/contrib/debug/browser/replViewer.ts +++ b/src/vs/workbench/contrib/debug/browser/replViewer.ts @@ -34,7 +34,7 @@ interface IReplEvaluationInputTemplateData { } interface IReplGroupTemplateData { - label: HighlightedLabel; + label: HTMLElement; } interface IReplEvaluationResultTemplateData { @@ -87,22 +87,28 @@ export class ReplEvaluationInputsRenderer implements ITreeRenderer { static readonly ID = 'replGroup'; + constructor( + private readonly linkDetector: LinkDetector, + @IThemeService private readonly themeService: IThemeService + ) { } + get templateId(): string { return ReplGroupRenderer.ID; } - renderTemplate(container: HTMLElement): IReplEvaluationInputTemplateData { - const input = dom.append(container, $('.expression')); - const label = new HighlightedLabel(input, false); + renderTemplate(container: HTMLElement): IReplGroupTemplateData { + const label = dom.append(container, $('.expression')); return { label }; } renderElement(element: ITreeNode, _index: number, templateData: IReplGroupTemplateData): void { const replGroup = element.element; - templateData.label.set(replGroup.name, createMatches(element.filterData)); + dom.clearNode(templateData.label); + const result = handleANSIOutput(replGroup.name, this.linkDetector, this.themeService, undefined); + templateData.label.appendChild(result); } - disposeTemplate(_templateData: IReplEvaluationInputTemplateData): void { + disposeTemplate(_templateData: IReplGroupTemplateData): void { // noop } } @@ -300,15 +306,15 @@ export class ReplDelegate extends CachedListVirtualDelegate { protected estimateHeight(element: IReplElement, ignoreValueLength = false): number { const config = this.configurationService.getValue('debug'); - const rowHeight = Math.ceil(1.4 * config.console.fontSize); + const rowHeight = Math.ceil(1.3 * config.console.fontSize); const countNumberOfLines = (str: string) => Math.max(1, (str && str.match(/\r\n|\n/g) || []).length); const hasValue = (e: any): e is { value: string } => typeof e.value === 'string'; // Calculate a rough overestimation for the height - // For every 30 characters increase the number of lines needed - if (hasValue(element)) { + // For every 70 characters increase the number of lines needed beyond the first + if (hasValue(element) && !(element instanceof Variable)) { let value = element.value; - let valueRows = countNumberOfLines(value) + (ignoreValueLength ? 0 : Math.floor(value.length / 30)); + let valueRows = countNumberOfLines(value) + (ignoreValueLength ? 0 : Math.floor(value.length / 70)); return valueRows * rowHeight; } @@ -338,6 +344,10 @@ export class ReplDelegate extends CachedListVirtualDelegate { } hasDynamicHeight(element: IReplElement): boolean { + if (element instanceof Variable) { + // Variables should always be in one line #111843 + return false; + } // Empty elements should not have dynamic height since they will be invisible return element.toString().length > 0; } @@ -383,7 +393,8 @@ export class ReplAccessibilityProvider implements IListAccessibilityProvider 1 ? localize('occurred', ", occured {0} times", element.count) : ''); + return element.value + (element instanceof SimpleReplElement && element.count > 1 ? localize({ key: 'occurred', comment: ['Front will the value of the debug console element. Placeholder will be replaced by a number which represents occurrance count.'] }, + ", occured {0} times", element.count) : ''); } if (element instanceof RawObjectReplElement) { return localize('replRawObjectAriaLabel', "Debug console variable {0}, value {1}", element.name, element.value); diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 7c8acdc41..665894a0b 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -3,20 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import { RunOnceScheduler } from 'vs/base/common/async'; import * as dom from 'vs/base/browser/dom'; -import { CollapseAction } from 'vs/workbench/browser/viewlet'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { IDebugService, IExpression, IScope, CONTEXT_VARIABLES_FOCUSED, IStackFrame, CONTEXT_DEBUG_PROTOCOL_VARIABLE_MENU_CONTEXT, IDataBreakpointInfoResponse, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT } from 'vs/workbench/contrib/debug/common/debug'; -import { Variable, Scope, ErrorScope, StackFrame } from 'vs/workbench/contrib/debug/common/debugModel'; +import { IDebugService, IExpression, IScope, CONTEXT_VARIABLES_FOCUSED, IStackFrame, CONTEXT_DEBUG_PROTOCOL_VARIABLE_MENU_CONTEXT, IDataBreakpointInfoResponse, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, VARIABLES_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { Variable, Scope, ErrorScope, StackFrame, Expression } from 'vs/workbench/contrib/debug/common/debugModel'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { renderViewTree, renderVariable, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IAction } from 'vs/base/common/actions'; -import { CopyValueAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane, ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, ITreeMouseEvent, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; @@ -26,17 +23,19 @@ import { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, IContextKey, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey'; import { dispose } from 'vs/base/common/lifecycle'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { withUndefinedAsNull } from 'vs/base/common/types'; -import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions'; +import { IMenuService, IMenu, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { debugCollapseAll } from 'vs/workbench/contrib/debug/browser/debugIcons'; +import { localize } from 'vs/nls'; +import { Codicon } from 'vs/base/common/codicons'; +import { coalesce } from 'vs/base/common/arrays'; const $ = dom.$; let forgetScopes = true; @@ -179,10 +178,6 @@ export class VariablesView extends ViewPane { })); } - getActions(): IAction[] { - return [new CollapseAction(() => this.tree, true, 'explorer-action ' + ThemeIcon.asClassName(debugCollapseAll))]; - } - layoutBody(width: number, height: number): void { super.layoutBody(height, width); this.tree.layout(width, height); @@ -192,6 +187,10 @@ export class VariablesView extends ViewPane { this.tree.domFocus(); } + collapseAll(): void { + this.tree.collapseAll(); + } + private onMouseDblClick(e: ITreeMouseEvent): void { const session = this.debugService.getViewModel().focusedSession; if (session && e.element instanceof Variable && session.capabilities.supportsSetVariable) { @@ -345,7 +344,7 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { const variable = expression; return { initialValue: expression.value, - ariaLabel: nls.localize('variableValueAriaLabel', "Type new variable value"), + ariaLabel: localize('variableValueAriaLabel', "Type new variable value"), validationOptions: { validation: () => variable.errorMessage ? ({ content: variable.errorMessage }) : null }, @@ -368,15 +367,15 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { class VariablesAccessibilityProvider implements IListAccessibilityProvider { getWidgetAriaLabel(): string { - return nls.localize('variablesAriaTreeLabel', "Debug Variables"); + return localize('variablesAriaTreeLabel', "Debug Variables"); } getAriaLabel(element: IExpression | IScope): string | null { if (element instanceof Scope) { - return nls.localize('variableScopeAriaLabel', "Scope {0}", element.name); + return localize('variableScopeAriaLabel', "Scope {0}", element.name); } if (element instanceof Variable) { - return nls.localize({ key: 'variableAriaLabel', comment: ['Placeholders are variable name and variable value respectivly. They should not be translated.'] }, "{0}, value {1}", element.name, element.value); + return localize({ key: 'variableAriaLabel', comment: ['Placeholders are variable name and variable value respectivly. They should not be translated.'] }, "{0}, value {1}", element.name, element.value); } return null; @@ -392,14 +391,40 @@ CommandsRegistry.registerCommand({ } }); -export const COPY_VALUE_ID = 'debug.copyValue'; +export const COPY_VALUE_ID = 'workbench.debug.viewlet.action.copyValue'; CommandsRegistry.registerCommand({ id: COPY_VALUE_ID, - handler: async (accessor: ServicesAccessor) => { - const instantiationService = accessor.get(IInstantiationService); - if (variableInternalContext) { - const action = instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variableInternalContext, 'variables'); - await action.run(); + handler: async (accessor: ServicesAccessor, arg: Variable | Expression | unknown, ctx?: (Variable | Expression)[]) => { + const debugService = accessor.get(IDebugService); + const clipboardService = accessor.get(IClipboardService); + let elementContext = ''; + let elements: (Variable | Expression)[]; + if (arg instanceof Variable || arg instanceof Expression) { + elementContext = 'watch'; + elements = ctx ? ctx : []; + } else { + elementContext = 'variables'; + elements = variableInternalContext ? [variableInternalContext] : []; + } + + const stackFrame = debugService.getViewModel().focusedStackFrame; + const session = debugService.getViewModel().focusedSession; + if (!stackFrame || !session || elements.length === 0) { + return; + } + + const evalContext = session.capabilities.supportsClipboardContext ? 'clipboard' : elementContext; + const toEvaluate = elements.map(element => element instanceof Variable ? (element.evaluateName || element.value) : element.name); + + try { + const evaluations = await Promise.all(toEvaluate.map(expr => session.evaluate(expr, stackFrame.frameId, evalContext))); + const result = coalesce(evaluations).map(evaluation => evaluation.body.result); + if (result.length) { + clipboardService.writeText(result.join('\n')); + } + } catch (e) { + const result = elements.map(element => element.value); + clipboardService.writeText(result.join('\n')); } } }); @@ -433,3 +458,23 @@ CommandsRegistry.registerCommand({ } }); +registerAction2(class extends ViewAction { + constructor() { + super({ + id: 'variables.collapse', + viewId: VARIABLES_VIEW_ID, + title: localize('collapse', "Collapse All"), + f1: false, + icon: Codicon.collapseAll, + menu: { + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyEqualsExpr.create('view', VARIABLES_VIEW_ID) + } + }); + } + + runInView(_accessor: ServicesAccessor, view: VariablesView) { + view.collapseAll(); + } +}); diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 2e18414c9..23ee7babf 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -3,20 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { CollapseAction } from 'vs/workbench/browser/viewlet'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { IDebugService, IExpression, CONTEXT_WATCH_EXPRESSIONS_FOCUSED } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, IExpression, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, WATCH_VIEW_ID, CONTEXT_WATCH_EXPRESSIONS_EXIST, CONTEXT_WATCH_ITEM_TYPE } from 'vs/workbench/contrib/debug/common/debug'; import { Expression, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; -import { AddWatchExpressionAction, RemoveAllWatchExpressionsAction, CopyValueAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IAction, Action, Separator } from 'vs/base/common/actions'; +import { IAction } from 'vs/base/common/actions'; import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane, ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; @@ -26,13 +23,17 @@ import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { FuzzyScore } from 'vs/base/common/filters'; import { IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, ContextKeyEqualsExpr, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { dispose } from 'vs/base/common/lifecycle'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { debugCollapseAll } from 'vs/workbench/contrib/debug/browser/debugIcons'; +import { watchExpressionsRemoveAll, watchExpressionsAdd } from 'vs/workbench/contrib/debug/browser/debugIcons'; +import { registerAction2, MenuId, Action2, IMenuService, IMenu } from 'vs/platform/actions/common/actions'; +import { localize } from 'vs/nls'; +import { Codicon } from 'vs/base/common/codicons'; +import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; let ignoreViewUpdates = false; @@ -43,6 +44,9 @@ export class WatchExpressionsView extends ViewPane { private watchExpressionsUpdatedScheduler: RunOnceScheduler; private needsRefresh = false; private tree!: WorkbenchAsyncDataTree; + private watchExpressionsExist: IContextKey; + private watchItemType: IContextKey; + private menu: IMenu; constructor( options: IViewletViewOptions, @@ -56,13 +60,19 @@ export class WatchExpressionsView extends ViewPane { @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, + @IMenuService menuService: IMenuService ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + this.menu = menuService.createMenu(MenuId.DebugWatchContext, contextKeyService); + this._register(this.menu); this.watchExpressionsUpdatedScheduler = new RunOnceScheduler(() => { this.needsRefresh = false; this.tree.updateChildren(); }, 50); + this.watchExpressionsExist = CONTEXT_WATCH_EXPRESSIONS_EXIST.bindTo(contextKeyService); + this.watchExpressionsExist.set(this.debugService.getModel().getWatchExpressions().length > 0); + this.watchItemType = CONTEXT_WATCH_ITEM_TYPE.bindTo(contextKeyService); } renderBody(container: HTMLElement): void { @@ -98,6 +108,7 @@ export class WatchExpressionsView extends ViewPane { this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); this._register(this.tree.onMouseDblClick(e => this.onMouseDblClick(e))); this._register(this.debugService.getModel().onDidChangeWatchExpressions(async we => { + this.watchExpressionsExist.set(this.debugService.getModel().getWatchExpressions().length > 0); if (!this.isBodyVisible()) { this.needsRefresh = true; } else { @@ -141,7 +152,10 @@ export class WatchExpressionsView extends ViewPane { this.tree.updateOptions({ horizontalScrolling: false }); } - this.tree.rerender(e); + if (e.name) { + // Only rerender if the input is already done since otherwise the tree is not yet aware of the new element + this.tree.rerender(e); + } } else if (!e && horizontalScrolling !== undefined) { this.tree.updateOptions({ horizontalScrolling: horizontalScrolling }); horizontalScrolling = undefined; @@ -158,12 +172,8 @@ export class WatchExpressionsView extends ViewPane { this.tree.domFocus(); } - getActions(): IAction[] { - return [ - new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService), - new CollapseAction(() => this.tree, true, 'explorer-action ' + ThemeIcon.asClassName(debugCollapseAll)), - new RemoveAllWatchExpressionsAction(RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL, this.debugService, this.keybindingService) - ]; + collapseAll(): void { + this.tree.collapseAll(); } private onMouseDblClick(e: ITreeMouseEvent): void { @@ -184,42 +194,17 @@ export class WatchExpressionsView extends ViewPane { private onContextMenu(e: ITreeContextMenuEvent): void { const element = e.element; - const anchor = e.anchor; - if (!anchor) { - return; - } + const selection = this.tree.getSelection(); + + this.watchItemType.set(element instanceof Expression ? 'expression' : element instanceof Variable ? 'variable' : undefined); const actions: IAction[] = []; - - if (element instanceof Expression) { - const expression = element; - actions.push(new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService)); - actions.push(new Action('debug.editWatchExpression', nls.localize('editWatchExpression', "Edit Expression"), undefined, true, () => { - this.debugService.getViewModel().setSelectedExpression(expression); - return Promise.resolve(); - })); - actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, expression, 'watch')); - actions.push(new Separator()); - - actions.push(new Action('debug.removeWatchExpression', nls.localize('removeWatchExpression', "Remove Expression"), undefined, true, () => { - this.debugService.removeWatchExpressions(expression.getId()); - return Promise.resolve(); - })); - actions.push(new RemoveAllWatchExpressionsAction(RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL, this.debugService, this.keybindingService)); - } else { - actions.push(new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService)); - if (element instanceof Variable) { - const variable = element as Variable; - actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable, 'watch')); - actions.push(new Separator()); - } - actions.push(new RemoveAllWatchExpressionsAction(RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL, this.debugService, this.keybindingService)); - } + const actionsDisposable = createAndFillInContextMenuActions(this.menu, { arg: element, shouldForwardArgs: true }, actions); this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, + getAnchor: () => e.anchor, getActions: () => actions, - getActionsContext: () => element, - onHide: () => dispose(actions) + getActionsContext: () => element && selection.includes(element) ? selection : element ? [element] : [], + onHide: () => dispose(actionsDisposable) }); } } @@ -287,8 +272,8 @@ export class WatchExpressionsRenderer extends AbstractExpressionsRenderer { protected getInputBoxOptions(expression: IExpression): IInputBoxOptions { return { initialValue: expression.name ? expression.name : '', - ariaLabel: nls.localize('watchExpressionInputAriaLabel', "Type watch expression"), - placeholder: nls.localize('watchExpressionPlaceholder', "Expression to watch"), + ariaLabel: localize('watchExpressionInputAriaLabel', "Type watch expression"), + placeholder: localize('watchExpressionPlaceholder', "Expression to watch"), onFinish: (value: string, success: boolean) => { if (success && value) { this.debugService.renameWatchExpression(expression.getId(), value); @@ -306,16 +291,16 @@ export class WatchExpressionsRenderer extends AbstractExpressionsRenderer { class WatchExpressionsAccessibilityProvider implements IListAccessibilityProvider { getWidgetAriaLabel(): string { - return nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'watchAriaTreeLabel' }, "Debug Watch Expressions"); + return localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'watchAriaTreeLabel' }, "Debug Watch Expressions"); } getAriaLabel(element: IExpression): string { if (element instanceof Expression) { - return nls.localize('watchExpressionAriaLabel', "{0}, value {1}", (element).name, (element).value); + return localize('watchExpressionAriaLabel', "{0}, value {1}", (element).name, (element).value); } // Variable - return nls.localize('watchVariableAriaLabel', "{0}, value {1}", (element).name, (element).value); + return localize('watchVariableAriaLabel', "{0}, value {1}", (element).name, (element).value); } } @@ -359,3 +344,75 @@ class WatchExpressionsDragAndDrop implements ITreeDragAndDrop { this.debugService.moveWatchExpression(draggedElement.getId(), position); } } + +registerAction2(class Collapse extends ViewAction { + constructor() { + super({ + id: 'watch.collapse', + viewId: WATCH_VIEW_ID, + title: localize('collapse', "Collapse All"), + f1: false, + icon: Codicon.collapseAll, + precondition: CONTEXT_WATCH_EXPRESSIONS_EXIST, + menu: { + id: MenuId.ViewTitle, + order: 30, + group: 'navigation', + when: ContextKeyEqualsExpr.create('view', WATCH_VIEW_ID) + } + }); + } + + runInView(_accessor: ServicesAccessor, view: WatchExpressionsView) { + view.collapseAll(); + } +}); + +export const ADD_WATCH_ID = 'workbench.debug.viewlet.action.addWatchExpression'; // Use old and long id for backwards compatibility +export const ADD_WATCH_LABEL = localize('addWatchExpression', "Add Expression"); + +registerAction2(class AddWatchExpressionAction extends Action2 { + constructor() { + super({ + id: ADD_WATCH_ID, + title: ADD_WATCH_LABEL, + f1: false, + icon: watchExpressionsAdd, + menu: { + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyEqualsExpr.create('view', WATCH_VIEW_ID) + } + }); + } + + run(accessor: ServicesAccessor): void { + const debugService = accessor.get(IDebugService); + debugService.addWatchExpression(); + } +}); + +export const REMOVE_WATCH_EXPRESSIONS_COMMAND_ID = 'workbench.debug.viewlet.action.removeAllWatchExpressions'; +export const REMOVE_WATCH_EXPRESSIONS_LABEL = localize('removeAllWatchExpressions', "Remove All Expressions"); +registerAction2(class RemoveAllWatchExpressionsAction extends Action2 { + constructor() { + super({ + id: REMOVE_WATCH_EXPRESSIONS_COMMAND_ID, // Use old and long id for backwards compatibility + title: REMOVE_WATCH_EXPRESSIONS_LABEL, + f1: false, + icon: watchExpressionsRemoveAll, + precondition: CONTEXT_WATCH_EXPRESSIONS_EXIST, + menu: { + id: MenuId.ViewTitle, + order: 20, + group: 'navigation', + when: ContextKeyEqualsExpr.create('view', WATCH_VIEW_ID) + } + }); + } + + run(accessor: ServicesAccessor): void { + const debugService = accessor.get(IDebugService); + debugService.removeWatchExpressions(); + } +}); diff --git a/src/vs/workbench/contrib/debug/browser/welcomeView.ts b/src/vs/workbench/contrib/debug/browser/welcomeView.ts index c88b28e8b..e33671e73 100644 --- a/src/vs/workbench/contrib/debug/browser/welcomeView.ts +++ b/src/vs/workbench/contrib/debug/browser/welcomeView.ts @@ -10,10 +10,9 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService, RawContextKey, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { localize } from 'vs/nls'; -import { StartAction, ConfigureAction, SelectAndStartAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { IDebugService, CONTEXT_DEBUGGERS_AVAILABLE } from 'vs/workbench/contrib/debug/common/debug'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IViewDescriptorService, IViewsRegistry, Extensions, ViewContentGroups } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -25,6 +24,7 @@ import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { SELECT_AND_START_ID, DEBUG_CONFIGURE_COMMAND_ID, DEBUG_START_COMMAND_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; const debugStartLanguageKey = 'debugStartLanguage'; const CONTEXT_DEBUG_START_LANGUAGE = new RawContextKey(debugStartLanguageKey, undefined); @@ -96,7 +96,7 @@ export class WelcomeView extends ViewPane { })); setContextKey(); - const debugKeybinding = this.keybindingService.lookupKeybinding(StartAction.ID); + const debugKeybinding = this.keybindingService.lookupKeybinding(DEBUG_START_COMMAND_ID); debugKeybindingLabel = debugKeybinding ? ` (${debugKeybinding.getLabel()})` : ''; } @@ -116,14 +116,14 @@ viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { let debugKeybindingLabel = ''; viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { content: localize({ key: 'runAndDebugAction', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, - "[Run and Debug{0}](command:{1})", debugKeybindingLabel, StartAction.ID), + "[Run and Debug{0}](command:{1})", debugKeybindingLabel, DEBUG_START_COMMAND_ID), when: CONTEXT_DEBUGGERS_AVAILABLE, group: ViewContentGroups.Debug }); viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { content: localize({ key: 'detectThenRunAndDebug', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, - "[Show](command:{0}) all automatic debug configurations.", SelectAndStartAction.ID), + "[Show](command:{0}) all automatic debug configurations.", SELECT_AND_START_ID), when: CONTEXT_DEBUGGERS_AVAILABLE, group: ViewContentGroups.Debug, order: 10 @@ -131,7 +131,7 @@ viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { content: localize({ key: 'customizeRunAndDebug', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, - "To customize Run and Debug [create a launch.json file](command:{0}).", ConfigureAction.ID), + "To customize Run and Debug [create a launch.json file](command:{0}).", DEBUG_CONFIGURE_COMMAND_ID), when: ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, WorkbenchStateContext.notEqualsTo('empty')), group: ViewContentGroups.Debug }); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 8f1715543..c46778c61 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -25,6 +25,7 @@ import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configur import { CancellationToken } from 'vs/base/common/cancellation'; import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/api/common/extHostTypes'; import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot'; +import { IAction } from 'vs/base/common/actions'; export const VIEWLET_ID = 'workbench.view.debug'; @@ -47,10 +48,14 @@ export const CONTEXT_BREAKPOINT_WIDGET_VISIBLE = new RawContextKey('bre export const CONTEXT_IN_BREAKPOINT_WIDGET = new RawContextKey('inBreakpointWidget', false); export const CONTEXT_BREAKPOINTS_FOCUSED = new RawContextKey('breakpointsFocused', true); export const CONTEXT_WATCH_EXPRESSIONS_FOCUSED = new RawContextKey('watchExpressionsFocused', true); +export const CONTEXT_WATCH_EXPRESSIONS_EXIST = new RawContextKey('watchExpressionsExist', false); export const CONTEXT_VARIABLES_FOCUSED = new RawContextKey('variablesFocused', true); export const CONTEXT_EXPRESSION_SELECTED = new RawContextKey('expressionSelected', false); -export const CONTEXT_BREAKPOINT_SELECTED = new RawContextKey('breakpointSelected', false); +export const CONTEXT_BREAKPOINT_INPUT_FOCUSED = new RawContextKey('breakpointInputFocused', false); export const CONTEXT_CALLSTACK_ITEM_TYPE = new RawContextKey('callStackItemType', undefined); +export const CONTEXT_WATCH_ITEM_TYPE = new RawContextKey('watchItemType', undefined); +export const CONTEXT_BREAKPOINT_ITEM_TYPE = new RawContextKey('breakpointItemType', undefined); +export const CONTEXT_BREAKPOINT_SUPPORTS_CONDITION = new RawContextKey('breakpointSupportsCondition', false); export const CONTEXT_LOADED_SCRIPTS_SUPPORTED = new RawContextKey('loadedScriptsSupported', false); export const CONTEXT_LOADED_SCRIPTS_ITEM_TYPE = new RawContextKey('loadedScriptsItemType', undefined); export const CONTEXT_FOCUSED_SESSION_IS_ATTACH = new RawContextKey('focusedSessionIsAttach', false); @@ -65,6 +70,8 @@ export const CONTEXT_SET_VARIABLE_SUPPORTED = new RawContextKey('debugS export const CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED = new RawContextKey('breakWhenValueChangesSupported', false); export const CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT = new RawContextKey('variableEvaluateNamePresent', false); export const CONTEXT_EXCEPTION_WIDGET_VISIBLE = new RawContextKey('exceptionWidgetVisible', false); +export const CONTEXT_MULTI_SESSION_REPL = new RawContextKey('multiSessionRepl', false); +export const CONTEXT_MULTI_SESSION_DEBUG = new RawContextKey('multiSessionDebug', false); export const EDITOR_CONTRIBUTION_ID = 'editor.contrib.debug'; export const BREAKPOINT_EDITOR_CONTRIBUTION_ID = 'editor.contrib.breakpoint'; @@ -183,6 +190,7 @@ export interface IDebugSession extends ITreeElement { readonly subId: string | undefined; readonly compact: boolean; readonly compoundRoot: DebugCompoundRoot | undefined; + readonly name: string; setSubId(subId: string | undefined): void; @@ -437,9 +445,7 @@ export interface IViewModel extends ITreeElement { readonly focusedStackFrame: IStackFrame | undefined; getSelectedExpression(): IExpression | undefined; - getSelectedBreakpoint(): IFunctionBreakpoint | IExceptionBreakpoint | undefined; setSelectedExpression(expression: IExpression | undefined): void; - setSelectedBreakpoint(functionBreakpoint: IFunctionBreakpoint | IExceptionBreakpoint | undefined): void; updateViews(): void; isMultiSessionView(): boolean; @@ -629,7 +635,6 @@ export interface IDebuggerContribution extends IPlatformSpecificAdapterContribut // supported languages languages?: string[]; - enableBreakpointsFor?: { languageIds?: string[] }; // debug configuration support configurationAttributes?: any; @@ -845,10 +850,10 @@ export interface IDebugService { addFunctionBreakpoint(name?: string, id?: string): void; /** - * Renames an already existing function breakpoint. + * Updates an already existing function breakpoint. * Notifies debug adapter of breakpoint changes. */ - renameFunctionBreakpoint(id: string, newFunctionName: string): Promise; + updateFunctionBreakpoint(id: string, update: { name?: string, hitCondition?: string, condition?: string }): Promise; /** * Removes all function breakpoints. If id is passed only removes the function breakpoint with the passed id. @@ -869,6 +874,8 @@ export interface IDebugService { setExceptionBreakpointCondition(breakpoint: IExceptionBreakpoint, condition: string | undefined): Promise; + setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void; + /** * Sends all breakpoints to the passed session. * If session is not passed, sends all breakpoints to each session. @@ -947,6 +954,7 @@ export interface IDebugEditorContribution extends editorCommon.IEditorContributi export interface IBreakpointEditorContribution extends editorCommon.IEditorContribution { showBreakpointWidget(lineNumber: number, column: number | undefined, context?: BreakpointWidgetContext): void; closeBreakpointWidget(): void; + getContextMenuActionsAtPosition(lineNumber: number, model: EditorIModel): IAction[]; } // temporary debug helper service diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index b98b3b897..68184d87c 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -423,7 +423,9 @@ export class Thread implements IThread { } getTopStackFrame(): IStackFrame | undefined { - return this.getCallStack().find(sf => !!(sf && sf.source && sf.source.available && sf.source.presentationHint !== 'deemphasize')); + const callStack = this.getCallStack(); + const firstAvailableStackFrame = callStack.find(sf => !!(sf && sf.source && sf.source.available && sf.source.presentationHint !== 'deemphasize')); + return firstAvailableStackFrame || (callStack.length > 0 ? callStack[0] : undefined); } get stateLabel(): string { @@ -452,6 +454,9 @@ export class Thread implements IThread { this.callStack.splice(start, this.callStack.length - start); } this.callStack = this.callStack.concat(callStack || []); + if (typeof this.stoppedDetails?.totalFrames === 'number' && this.stoppedDetails.totalFrames === this.callStack.length) { + this.reachedEndOfCallStack = true; + } } } @@ -946,6 +951,11 @@ export class DebugModel implements IDebugModel { return true; }); + let i = 1; + while (this.sessions.some(s => s.getLabel() === session.getLabel())) { + session.setName(`${session.configuration.name} ${++i}`); + } + let index = -1; if (session.parentSession) { // Make sure that child sessions are placed after the parent session @@ -1236,10 +1246,18 @@ export class DebugModel implements IDebugModel { return newFunctionBreakpoint; } - renameFunctionBreakpoint(id: string, name: string): void { + updateFunctionBreakpoint(id: string, update: { name?: string, hitCondition?: string, condition?: string }): void { const functionBreakpoint = this.functionBreakpoints.find(fbp => fbp.getId() === id); if (functionBreakpoint) { - functionBreakpoint.name = name; + if (typeof update.name === 'string') { + functionBreakpoint.name = update.name; + } + if (typeof update.condition === 'string') { + functionBreakpoint.condition = update.condition; + } + if (typeof update.hitCondition === 'string') { + functionBreakpoint.hitCondition = update.hitCondition; + } this._onDidChangeBreakpoints.fire({ changed: [functionBreakpoint], sessionOnly: false }); } } diff --git a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts index f11bab4fb..dad39a583 100644 --- a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts +++ b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts @@ -740,7 +740,7 @@ declare module DebugProtocol { /** Reference to the Variable container if the data breakpoint is requested for a child of the container. */ variablesReference?: number; /** The name of the Variable's child to obtain data breakpoint information for. - If variableReference isn’t provided, this can be an expression. + If variablesReference isn’t provided, this can be an expression. */ name: string; } @@ -1012,7 +1012,7 @@ declare module DebugProtocol { /** StackTrace request; value of command field is 'stackTrace'. The request returns a stacktrace from the current execution state of a given thread. - A client can request all stack frames by omitting the startFrame and levels arguments. For performance conscious clients stack frames can be retrieved in a piecemeal way with the startFrame and levels arguments. The response of the stackTrace request may contain a totalFrames property that hints at the total number of frames in the stack. If a client needs this total number upfront, it can issue a request for a single (first) frame and depending on the value of totalFrames decide how to proceed. In any case a client should be prepared to receive less frames than requested, which is an indication that the end of the stack has been reached. + A client can request all stack frames by omitting the startFrame and levels arguments. For performance conscious clients and if the debug adapter's 'supportsDelayedStackTraceLoading' capability is true, stack frames can be retrieved in a piecemeal way with the startFrame and levels arguments. The response of the stackTrace request may contain a totalFrames property that hints at the total number of frames in the stack. If a client needs this total number upfront, it can issue a request for a single (first) frame and depending on the value of totalFrames decide how to proceed. In any case a client should be prepared to receive less frames than requested, which is an indication that the end of the stack has been reached. */ export interface StackTraceRequest extends Request { // command: 'stackTrace'; @@ -1591,7 +1591,7 @@ declare module DebugProtocol { supportsExceptionInfoRequest?: boolean; /** The debug adapter supports the 'terminateDebuggee' attribute on the 'disconnect' request. */ supportTerminateDebuggee?: boolean; - /** The debug adapter supports the delayed loading of parts of the stack, which requires that both the 'startFrame' and 'levels' arguments and the 'totalFrames' result of the 'StackTrace' request are supported. */ + /** The debug adapter supports the delayed loading of parts of the stack, which requires that both the 'startFrame' and 'levels' arguments and an optional 'totalFrames' result of the 'StackTrace' request are supported. */ supportsDelayedStackTraceLoading?: boolean; /** The debug adapter supports the 'loadedSources' request. */ supportsLoadedSourcesRequest?: boolean; @@ -1871,7 +1871,7 @@ declare module DebugProtocol { 'mostDerivedClass': Indicates that the object is the most derived class. 'virtual': Indicates that the object is virtual, that means it is a synthetic object introducedby the adapter for rendering purposes, e.g. an index range for large arrays. - 'dataBreakpoint': Indicates that a data breakpoint is registered for the object. + 'dataBreakpoint': Deprecated: Indicates that a data breakpoint is registered for the object. The 'hasDataBreakpoint' attribute should generally be used instead. etc. */ kind?: 'property' | 'method' | 'class' | 'data' | 'event' | 'baseClass' | 'innerClass' | 'interface' | 'mostDerivedClass' | 'virtual' | 'dataBreakpoint' | string; @@ -1884,9 +1884,10 @@ declare module DebugProtocol { 'hasObjectId': Indicates that the object can have an Object ID created for it. 'canHaveObjectId': Indicates that the object has an Object ID associated with it. 'hasSideEffects': Indicates that the evaluation had side effects. + 'hasDataBreakpoint': Indicates that the object has its value tracked by a data breakpoint. etc. */ - attributes?: ('static' | 'constant' | 'readOnly' | 'rawString' | 'hasObjectId' | 'canHaveObjectId' | 'hasSideEffects' | string)[]; + attributes?: ('static' | 'constant' | 'readOnly' | 'rawString' | 'hasObjectId' | 'canHaveObjectId' | 'hasSideEffects' | 'hasDataBreakpoint' | string)[]; /** Visibility of variable. Before introducing additional values, try to use the listed values. Values: 'public', 'private', 'protected', 'internal', 'final', etc. */ diff --git a/src/vs/workbench/contrib/debug/common/debugViewModel.ts b/src/vs/workbench/contrib/debug/common/debugViewModel.ts index e7541cd89..a17e61185 100644 --- a/src/vs/workbench/contrib/debug/common/debugViewModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugViewModel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { CONTEXT_EXPRESSION_SELECTED, IViewModel, IStackFrame, IDebugSession, IThread, IExpression, IFunctionBreakpoint, CONTEXT_BREAKPOINT_SELECTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, IExceptionBreakpoint } from 'vs/workbench/contrib/debug/common/debug'; +import { CONTEXT_EXPRESSION_SELECTED, IViewModel, IStackFrame, IDebugSession, IThread, IExpression, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_MULTI_SESSION_DEBUG } from 'vs/workbench/contrib/debug/common/debug'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { isSessionAttach } from 'vs/workbench/contrib/debug/common/debugUtils'; @@ -16,14 +16,11 @@ export class ViewModel implements IViewModel { private _focusedSession: IDebugSession | undefined; private _focusedThread: IThread | undefined; private selectedExpression: IExpression | undefined; - private selectedBreakpoint: IFunctionBreakpoint | IExceptionBreakpoint | undefined; private readonly _onDidFocusSession = new Emitter(); private readonly _onDidFocusStackFrame = new Emitter<{ stackFrame: IStackFrame | undefined, explicit: boolean }>(); private readonly _onDidSelectExpression = new Emitter(); private readonly _onWillUpdateViews = new Emitter(); - private multiSessionView: boolean; private expressionSelectedContextKey!: IContextKey; - private breakpointSelectedContextKey!: IContextKey; private loadedScriptsSupportedContextKey!: IContextKey; private stepBackSupportedContextKey!: IContextKey; private focusedSessionIsAttach!: IContextKey; @@ -31,12 +28,11 @@ export class ViewModel implements IViewModel { private stepIntoTargetsSupported!: IContextKey; private jumpToCursorSupported!: IContextKey; private setVariableSupported!: IContextKey; + private multiSessionDebug!: IContextKey; constructor(private contextKeyService: IContextKeyService) { - this.multiSessionView = false; contextKeyService.bufferChangeEvents(() => { this.expressionSelectedContextKey = CONTEXT_EXPRESSION_SELECTED.bindTo(contextKeyService); - this.breakpointSelectedContextKey = CONTEXT_BREAKPOINT_SELECTED.bindTo(contextKeyService); this.loadedScriptsSupportedContextKey = CONTEXT_LOADED_SCRIPTS_SUPPORTED.bindTo(contextKeyService); this.stepBackSupportedContextKey = CONTEXT_STEP_BACK_SUPPORTED.bindTo(contextKeyService); this.focusedSessionIsAttach = CONTEXT_FOCUSED_SESSION_IS_ATTACH.bindTo(contextKeyService); @@ -44,6 +40,7 @@ export class ViewModel implements IViewModel { this.stepIntoTargetsSupported = CONTEXT_STEP_INTO_TARGETS_SUPPORTED.bindTo(contextKeyService); this.jumpToCursorSupported = CONTEXT_JUMP_TO_CURSOR_SUPPORTED.bindTo(contextKeyService); this.setVariableSupported = CONTEXT_SET_VARIABLE_SUPPORTED.bindTo(contextKeyService); + this.multiSessionDebug = CONTEXT_MULTI_SESSION_DEBUG.bindTo(contextKeyService); }); } @@ -112,10 +109,6 @@ export class ViewModel implements IViewModel { return this._onDidSelectExpression.event; } - getSelectedBreakpoint(): IFunctionBreakpoint | IExceptionBreakpoint | undefined { - return this.selectedBreakpoint; - } - updateViews(): void { this._onWillUpdateViews.fire(); } @@ -124,16 +117,11 @@ export class ViewModel implements IViewModel { return this._onWillUpdateViews.event; } - setSelectedBreakpoint(breakpoint: IFunctionBreakpoint | IExceptionBreakpoint | undefined): void { - this.selectedBreakpoint = breakpoint; - this.breakpointSelectedContextKey.set(!!breakpoint); - } - isMultiSessionView(): boolean { - return this.multiSessionView; + return !!this.multiSessionDebug.get(); } setMultiSessionView(isMultiSessionView: boolean): void { - this.multiSessionView = isMultiSessionView; + this.multiSessionDebug.set(isMultiSessionView); } } diff --git a/src/vs/workbench/contrib/debug/common/replModel.ts b/src/vs/workbench/contrib/debug/common/replModel.ts index f10608a01..c46e6b9ca 100644 --- a/src/vs/workbench/contrib/debug/common/replModel.ts +++ b/src/vs/workbench/contrib/debug/common/replModel.ts @@ -15,6 +15,7 @@ import { Emitter, Event } from 'vs/base/common/event'; const MAX_REPL_LENGTH = 10000; let topReplElementCounter = 0; +const getUniqueId = () => `topReplElement:${topReplElementCounter++}`; export class SimpleReplElement implements IReplElement { @@ -30,8 +31,12 @@ export class SimpleReplElement implements IReplElement { ) { } toString(): string { + let valueRespectCount = this.value; + for (let i = 1; i < this.count; i++) { + valueRespectCount += (valueRespectCount.endsWith('\n') ? '' : '\n') + this.value; + } const sourceStr = this.sourceData ? ` ${this.sourceData.source.name}` : ''; - return this.value + sourceStr; + return valueRespectCount + sourceStr; } getId(): string { @@ -226,13 +231,14 @@ export class ReplModel { return; } if (!previousElement.value.endsWith('\n') && !previousElement.value.endsWith('\r\n') && previousElement.count === 1) { - previousElement.value += data; + this.replElements[this.replElements.length - 1] = new SimpleReplElement( + session, getUniqueId(), previousElement.value + data, sev, source); this._onDidChangeElements.fire(); return; } } - const element = new SimpleReplElement(session, `topReplElement:${topReplElementCounter++}`, data, sev, source); + const element = new SimpleReplElement(session, getUniqueId(), data, sev, source); this.addReplElement(element); } else { // TODO@Isidor hack, we should introduce a new type which is an output that can fetch children like an expression @@ -307,7 +313,7 @@ export class ReplModel { } // show object - this.appendToRepl(session, new RawObjectReplElement(`topReplElement:${topReplElementCounter++}`, (a).prototype, a, undefined, nls.localize('snapshotObj', "Only primitive values are shown for this object.")), sev, source); + this.appendToRepl(session, new RawObjectReplElement(getUniqueId(), (a).prototype, a, undefined, nls.localize('snapshotObj', "Only primitive values are shown for this object.")), sev, source); } // string: watch out for % replacement directive diff --git a/src/vs/workbench/contrib/debug/node/debugHelperService.ts b/src/vs/workbench/contrib/debug/node/debugHelperService.ts index a79291240..05be0f7d8 100644 --- a/src/vs/workbench/contrib/debug/node/debugHelperService.ts +++ b/src/vs/workbench/contrib/debug/node/debugHelperService.ts @@ -5,7 +5,7 @@ import { IDebugHelperService } from 'vs/workbench/contrib/debug/common/debug'; import { Client as TelemetryClient } from 'vs/base/parts/ipc/node/ipc.cp'; -import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc'; +import { TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc'; import { FileAccess } from 'vs/base/common/network'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -31,8 +31,8 @@ export class NodeDebugHelperService implements IDebugHelperService { args: args, env: { ELECTRON_RUN_AS_NODE: 1, - PIPE_LOGGING: 'true', - AMD_ENTRYPOINT: 'vs/workbench/contrib/debug/node/telemetryApp' + VSCODE_PIPE_LOGGING: 'true', + VSCODE_AMD_ENTRYPOINT: 'vs/workbench/contrib/debug/node/telemetryApp' } } ); diff --git a/src/vs/workbench/contrib/debug/node/telemetryApp.ts b/src/vs/workbench/contrib/debug/node/telemetryApp.ts index 52507e4fd..85597832c 100644 --- a/src/vs/workbench/contrib/debug/node/telemetryApp.ts +++ b/src/vs/workbench/contrib/debug/node/telemetryApp.ts @@ -5,7 +5,7 @@ import { Server } from 'vs/base/parts/ipc/node/ipc.cp'; import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; -import { TelemetryAppenderChannel } from 'vs/platform/telemetry/node/telemetryIpc'; +import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc'; const appender = new AppInsightsAppender(process.argv[2], JSON.parse(process.argv[3]), process.argv[4]); process.once('exit', () => appender.flush()); diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index 45a8fc654..03c4fc8f6 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -9,7 +9,7 @@ import { WindowsExternalTerminalService, MacExternalTerminalService, LinuxExtern import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IExternalTerminalService } from 'vs/workbench/contrib/externalTerminal/common/externalTerminal'; import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; -import { extractDriveLetter } from 'vs/base/common/labels'; +import { getDriveLetter } from 'vs/base/common/extpath'; let externalTerminalService: IExternalTerminalService | undefined = undefined; @@ -112,7 +112,7 @@ export function prepareCommand(shell: string, args: string[], cwd?: string, env? }; if (cwd) { - const driveLetter = extractDriveLetter(cwd); + const driveLetter = getDriveLetter(cwd); if (driveLetter) { command += `${driveLetter}:; `; } @@ -145,7 +145,7 @@ export function prepareCommand(shell: string, args: string[], cwd?: string, env? }; if (cwd) { - const driveLetter = extractDriveLetter(cwd); + const driveLetter = getDriveLetter(cwd); if (driveLetter) { command += `${driveLetter}: && `; } diff --git a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts index 1a2bed974..fec15f39f 100644 --- a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts @@ -153,8 +153,8 @@ suite('Debug - Breakpoints', () => { test('function breakpoints', () => { model.addFunctionBreakpoint('foo', '1'); model.addFunctionBreakpoint('bar', '2'); - model.renameFunctionBreakpoint('1', 'fooUpdated'); - model.renameFunctionBreakpoint('2', 'barUpdated'); + model.updateFunctionBreakpoint('1', { name: 'fooUpdated' }); + model.updateFunctionBreakpoint('2', { name: 'barUpdated' }); const functionBps = model.getFunctionBreakpoints(); assert.equal(functionBps[0].name, 'fooUpdated'); diff --git a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts index ca7c0db50..dd6943746 100644 --- a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts @@ -20,6 +20,14 @@ import { generateUuid } from 'vs/base/common/uuid'; import { debugStackframe, debugStackframeFocused } from 'vs/workbench/contrib/debug/browser/debugIcons'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +const mockWorkspaceContextService = { + getWorkspace: () => { + return { + folders: [] + }; + } +} as any; + export function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession { return new DebugSession(generateUuid(), { resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, { getViewModel(): any { @@ -29,7 +37,7 @@ export function createMockSession(model: DebugModel, name = 'mockSession', optio } }; } - } as IDebugService, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!, mockUriIdentityService); + } as IDebugService, undefined!, undefined!, undefined!, undefined!, mockWorkspaceContextService, undefined!, undefined!, NullOpenerService, undefined!, undefined!, mockUriIdentityService); } function createTwoStackFrames(session: DebugSession): { firstStackFrame: StackFrame, secondStackFrame: StackFrame } { @@ -91,7 +99,7 @@ suite('Debug - CallStack', () => { assert.equal(model.getSessions(true).length, 1); }); - test('threads multiple wtih allThreadsStopped', () => { + test('threads multiple wtih allThreadsStopped', async () => { const threadId1 = 1; const threadName1 = 'firstThread'; const threadId2 = 2; @@ -145,19 +153,16 @@ suite('Debug - CallStack', () => { // after calling getCallStack, the callstack becomes available // and results in a request for the callstack in the debug adapter - thread1.fetchCallStack().then(() => { - assert.notEqual(thread1.getCallStack().length, 0); - }); + await thread1.fetchCallStack(); + assert.notEqual(thread1.getCallStack().length, 0); - thread2.fetchCallStack().then(() => { - assert.notEqual(thread2.getCallStack().length, 0); - }); + await thread2.fetchCallStack(); + assert.notEqual(thread2.getCallStack().length, 0); // calling multiple times getCallStack doesn't result in multiple calls // to the debug adapter - thread1.fetchCallStack().then(() => { - return thread2.fetchCallStack(); - }); + await thread1.fetchCallStack(); + await thread2.fetchCallStack(); // clearing the callstack results in the callstack not being available thread1.clearCallStack(); @@ -174,7 +179,7 @@ suite('Debug - CallStack', () => { assert.equal(session.getAllThreads().length, 0); }); - test('threads mutltiple without allThreadsStopped', () => { + test('threads mutltiple without allThreadsStopped', async () => { const sessionStub = sinon.spy(rawSession, 'stackTrace'); const stoppedThreadId = 1; @@ -230,19 +235,17 @@ suite('Debug - CallStack', () => { // after calling getCallStack, the callstack becomes available // and results in a request for the callstack in the debug adapter - stoppedThread.fetchCallStack().then(() => { - assert.notEqual(stoppedThread.getCallStack().length, 0); - assert.equal(runningThread.getCallStack().length, 0); - assert.equal(sessionStub.callCount, 1); - }); + await stoppedThread.fetchCallStack(); + assert.notEqual(stoppedThread.getCallStack().length, 0); + assert.equal(runningThread.getCallStack().length, 0); + assert.equal(sessionStub.callCount, 1); // calling getCallStack on the running thread returns empty array // and does not return in a request for the callstack in the debug // adapter - runningThread.fetchCallStack().then(() => { - assert.equal(runningThread.getCallStack().length, 0); - assert.equal(sessionStub.callCount, 1); - }); + await runningThread.fetchCallStack(); + assert.equal(runningThread.getCallStack().length, 0); + assert.equal(sessionStub.callCount, 1); // clearing the callstack results in the callstack not being available stoppedThread.clearCallStack(); @@ -374,7 +377,7 @@ suite('Debug - CallStack', () => { get state(): State { return State.Stopped; } - }(generateUuid(), { resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!, mockUriIdentityService); + }(generateUuid(), { resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, mockWorkspaceContextService, undefined!, undefined!, NullOpenerService, undefined!, undefined!, mockUriIdentityService); const runningSession = createMockSession(model); model.addSession(runningSession); diff --git a/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts similarity index 100% rename from src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts rename to src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts diff --git a/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts b/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts index b22d2044a..552642346 100644 --- a/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts @@ -64,7 +64,7 @@ suite('Debug - Link Detector', () => { test('singleLineLink', () => { const input = isWindows ? 'C:\\foo\\bar.js:12:34' : '/Users/foo/bar.js:12:34'; - const expectedOutput = isWindows ? 'C:\\foo\\bar.js:12:34<\/a><\/span>' : '/Users/foo/bar.js:12:34<\/a><\/span>'; + const expectedOutput = isWindows ? 'C:\\foo\\bar.js:12:34<\/a><\/span>' : '/Users/foo/bar.js:12:34<\/a><\/span>'; const output = linkDetector.linkify(input); assert.equal(1, output.children.length); @@ -87,7 +87,7 @@ suite('Debug - Link Detector', () => { test('relativeLinkWithWorkspace', () => { const input = '\./foo/bar.js'; - const expectedOutput = /^\.\/foo\/bar\.js<\/a><\/span>$/; + const expectedOutput = /^\.\/foo\/bar\.js<\/a><\/span>$/; const output = linkDetector.linkify(input, false, new WorkspaceFolder({ uri: URI.file('/path/to/workspace'), name: 'ws', index: 0 })); assert.equal('SPAN', output.tagName); @@ -96,7 +96,7 @@ suite('Debug - Link Detector', () => { test('singleLineLinkAndText', function () { const input = isWindows ? 'The link: C:/foo/bar.js:12:34' : 'The link: /Users/foo/bar.js:12:34'; - const expectedOutput = /^The link: .*\/foo\/bar.js:12:34<\/a><\/span>$/; + const expectedOutput = /^The link: .*\/foo\/bar.js:12:34<\/a><\/span>$/; const output = linkDetector.linkify(input); assert.equal(1, output.children.length); @@ -110,7 +110,7 @@ suite('Debug - Link Detector', () => { test('singleLineMultipleLinks', () => { const input = isWindows ? 'Here is a link C:/foo/bar.js:12:34 and here is another D:/boo/far.js:56:78' : 'Here is a link /Users/foo/bar.js:12:34 and here is another /Users/boo/far.js:56:78'; - const expectedOutput = /^Here is a link .*\/foo\/bar.js:12:34<\/a> and here is another .*\/boo\/far.js:56:78<\/a><\/span>$/; + const expectedOutput = /^Here is a link .*\/foo\/bar.js:12:34<\/a> and here is another .*\/boo\/far.js:56:78<\/a><\/span>$/; const output = linkDetector.linkify(input); assert.equal(2, output.children.length); @@ -152,7 +152,7 @@ suite('Debug - Link Detector', () => { test('multilineWithLinks', () => { const input = isWindows ? 'I have a link for you\nHere it is: C:/foo/bar.js:12:34\nCool, huh?' : 'I have a link for you\nHere it is: /Users/foo/bar.js:12:34\nCool, huh?'; - const expectedOutput = /^I have a link for you\n<\/span>Here it is: .*\/foo\/bar.js:12:34<\/a>\n<\/span>Cool, huh\?<\/span><\/span>$/; + const expectedOutput = /^I have a link for you\n<\/span>Here it is: .*\/foo\/bar.js:12:34<\/a>\n<\/span>Cool, huh\?<\/span><\/span>$/; const output = linkDetector.linkify(input, true); assert.equal(3, output.children.length); diff --git a/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts b/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts index 96a346ab9..8983f96e3 100644 --- a/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts @@ -24,29 +24,29 @@ export const mockUriIdentityService = new UriIdentityService(fileService); export class MockDebugService implements IDebugService { - public _serviceBrand: undefined; + _serviceBrand: undefined; - public get state(): State { + get state(): State { throw new Error('not implemented'); } - public get onWillNewSession(): Event { + get onWillNewSession(): Event { throw new Error('not implemented'); } - public get onDidNewSession(): Event { + get onDidNewSession(): Event { throw new Error('not implemented'); } - public get onDidEndSession(): Event { + get onDidEndSession(): Event { throw new Error('not implemented'); } - public get onDidChangeState(): Event { + get onDidChangeState(): Event { throw new Error('not implemented'); } - public getConfigurationManager(): IConfigurationManager { + getConfigurationManager(): IConfigurationManager { throw new Error('not implemented'); } @@ -58,7 +58,7 @@ export class MockDebugService implements IDebugService { throw new Error('Method not implemented.'); } - public focusStackFrame(focusedStackFrame: IStackFrame): Promise { + focusStackFrame(focusedStackFrame: IStackFrame): Promise { throw new Error('not implemented'); } @@ -66,23 +66,23 @@ export class MockDebugService implements IDebugService { throw new Error('not implemented'); } - public addBreakpoints(uri: uri, rawBreakpoints: IBreakpointData[]): Promise { + addBreakpoints(uri: uri, rawBreakpoints: IBreakpointData[]): Promise { throw new Error('not implemented'); } - public updateBreakpoints(uri: uri, data: Map, sendOnResourceSaved: boolean): Promise { + updateBreakpoints(uri: uri, data: Map, sendOnResourceSaved: boolean): Promise { throw new Error('not implemented'); } - public enableOrDisableBreakpoints(enabled: boolean): Promise { + enableOrDisableBreakpoints(enabled: boolean): Promise { throw new Error('not implemented'); } - public setBreakpointsActivated(): Promise { + setBreakpointsActivated(): Promise { throw new Error('not implemented'); } - public removeBreakpoints(): Promise { + removeBreakpoints(): Promise { throw new Error('not implemented'); } @@ -90,15 +90,19 @@ export class MockDebugService implements IDebugService { throw new Error('Method not implemented.'); } - public addFunctionBreakpoint(): void { } + setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void { + throw new Error('Method not implemented.'); + } - public moveWatchExpression(id: string, position: number): void { } + addFunctionBreakpoint(): void { } - public renameFunctionBreakpoint(id: string, newFunctionName: string): Promise { + moveWatchExpression(id: string, position: number): void { } + + updateFunctionBreakpoint(id: string, update: { name?: string, hitCondition?: string, condition?: string }): Promise { throw new Error('not implemented'); } - public removeFunctionBreakpoints(id?: string): Promise { + removeFunctionBreakpoints(id?: string): Promise { throw new Error('not implemented'); } @@ -109,47 +113,47 @@ export class MockDebugService implements IDebugService { throw new Error('Method not implemented.'); } - public addReplExpression(name: string): Promise { + addReplExpression(name: string): Promise { throw new Error('not implemented'); } - public removeReplExpressions(): void { } + removeReplExpressions(): void { } - public addWatchExpression(name?: string): Promise { + addWatchExpression(name?: string): Promise { throw new Error('not implemented'); } - public renameWatchExpression(id: string, newName: string): Promise { + renameWatchExpression(id: string, newName: string): Promise { throw new Error('not implemented'); } - public removeWatchExpressions(id?: string): void { } + removeWatchExpressions(id?: string): void { } - public startDebugging(launch: ILaunch, configOrName?: IConfig | string, options?: IDebugSessionOptions): Promise { + startDebugging(launch: ILaunch, configOrName?: IConfig | string, options?: IDebugSessionOptions): Promise { return Promise.resolve(true); } - public restartSession(): Promise { + restartSession(): Promise { throw new Error('not implemented'); } - public stopSession(): Promise { + stopSession(): Promise { throw new Error('not implemented'); } - public getModel(): IDebugModel { + getModel(): IDebugModel { throw new Error('not implemented'); } - public getViewModel(): IViewModel { + getViewModel(): IViewModel { throw new Error('not implemented'); } - public logToRepl(session: IDebugSession, value: string): void { } + logToRepl(session: IDebugSession, value: string): void { } - public sourceIsNotAvailable(uri: uri): void { } + sourceIsNotAvailable(uri: uri): void { } - public tryToAutoFocusStackFrame(thread: IThread): Promise { + tryToAutoFocusStackFrame(thread: IThread): Promise { throw new Error('not implemented'); } } @@ -227,6 +231,10 @@ export class MockSession implements IDebugSession { return 'mockname'; } + get name(): string { + return 'mockname'; + } + setName(name: string): void { throw new Error('not implemented'); } @@ -387,14 +395,14 @@ export class MockRawSession { disconnected = false; sessionLengthInSeconds: number = 0; - public readyForBreakpoints = true; - public emittedStopped = true; + readyForBreakpoints = true; + emittedStopped = true; - public getLengthInSeconds(): number { + getLengthInSeconds(): number { return 100; } - public stackTrace(args: DebugProtocol.StackTraceArguments): Promise { + stackTrace(args: DebugProtocol.StackTraceArguments): Promise { return Promise.resolve({ seq: 1, type: 'response', @@ -412,19 +420,19 @@ export class MockRawSession { }); } - public exceptionInfo(args: DebugProtocol.ExceptionInfoArguments): Promise { + exceptionInfo(args: DebugProtocol.ExceptionInfoArguments): Promise { throw new Error('not implemented'); } - public launchOrAttach(args: IConfig): Promise { + launchOrAttach(args: IConfig): Promise { throw new Error('not implemented'); } - public scopes(args: DebugProtocol.ScopesArguments): Promise { + scopes(args: DebugProtocol.ScopesArguments): Promise { throw new Error('not implemented'); } - public variables(args: DebugProtocol.VariablesArguments): Promise { + variables(args: DebugProtocol.VariablesArguments): Promise { throw new Error('not implemented'); } @@ -432,87 +440,87 @@ export class MockRawSession { return Promise.resolve(null!); } - public custom(request: string, args: any): Promise { + custom(request: string, args: any): Promise { throw new Error('not implemented'); } - public terminate(restart = false): Promise { + terminate(restart = false): Promise { throw new Error('not implemented'); } - public disconnect(restart?: boolean): Promise { + disconnect(restart?: boolean): Promise { throw new Error('not implemented'); } - public threads(): Promise { + threads(): Promise { throw new Error('not implemented'); } - public stepIn(args: DebugProtocol.StepInArguments): Promise { + stepIn(args: DebugProtocol.StepInArguments): Promise { throw new Error('not implemented'); } - public stepOut(args: DebugProtocol.StepOutArguments): Promise { + stepOut(args: DebugProtocol.StepOutArguments): Promise { throw new Error('not implemented'); } - public stepBack(args: DebugProtocol.StepBackArguments): Promise { + stepBack(args: DebugProtocol.StepBackArguments): Promise { throw new Error('not implemented'); } - public continue(args: DebugProtocol.ContinueArguments): Promise { + continue(args: DebugProtocol.ContinueArguments): Promise { throw new Error('not implemented'); } - public reverseContinue(args: DebugProtocol.ReverseContinueArguments): Promise { + reverseContinue(args: DebugProtocol.ReverseContinueArguments): Promise { throw new Error('not implemented'); } - public pause(args: DebugProtocol.PauseArguments): Promise { + pause(args: DebugProtocol.PauseArguments): Promise { throw new Error('not implemented'); } - public terminateThreads(args: DebugProtocol.TerminateThreadsArguments): Promise { + terminateThreads(args: DebugProtocol.TerminateThreadsArguments): Promise { throw new Error('not implemented'); } - public setVariable(args: DebugProtocol.SetVariableArguments): Promise { + setVariable(args: DebugProtocol.SetVariableArguments): Promise { throw new Error('not implemented'); } - public restartFrame(args: DebugProtocol.RestartFrameArguments): Promise { + restartFrame(args: DebugProtocol.RestartFrameArguments): Promise { throw new Error('not implemented'); } - public completions(args: DebugProtocol.CompletionsArguments): Promise { + completions(args: DebugProtocol.CompletionsArguments): Promise { throw new Error('not implemented'); } - public next(args: DebugProtocol.NextArguments): Promise { + next(args: DebugProtocol.NextArguments): Promise { throw new Error('not implemented'); } - public source(args: DebugProtocol.SourceArguments): Promise { + source(args: DebugProtocol.SourceArguments): Promise { throw new Error('not implemented'); } - public loadedSources(args: DebugProtocol.LoadedSourcesArguments): Promise { + loadedSources(args: DebugProtocol.LoadedSourcesArguments): Promise { throw new Error('not implemented'); } - public setBreakpoints(args: DebugProtocol.SetBreakpointsArguments): Promise { + setBreakpoints(args: DebugProtocol.SetBreakpointsArguments): Promise { throw new Error('not implemented'); } - public setFunctionBreakpoints(args: DebugProtocol.SetFunctionBreakpointsArguments): Promise { + setFunctionBreakpoints(args: DebugProtocol.SetFunctionBreakpointsArguments): Promise { throw new Error('not implemented'); } - public setExceptionBreakpoints(args: DebugProtocol.SetExceptionBreakpointsArguments): Promise { + setExceptionBreakpoints(args: DebugProtocol.SetExceptionBreakpointsArguments): Promise { throw new Error('not implemented'); } - public readonly onDidStop: Event = null!; + readonly onDidStop: Event = null!; } export class MockDebugAdapter extends AbstractDebugAdapter { diff --git a/src/vs/workbench/contrib/debug/test/browser/repl.test.ts b/src/vs/workbench/contrib/debug/test/browser/repl.test.ts index 288fa962a..e8826837f 100644 --- a/src/vs/workbench/contrib/debug/test/browser/repl.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/repl.test.ts @@ -74,11 +74,11 @@ suite('Debug - REPL', () => { repl.appendToRepl(session, 'third line', severity.Info); elements = repl.getReplElements(); assert.equal(elements.length, 3); - assert.equal(elements[0], 'first line\n'); + assert.equal(elements[0].value, 'first line\n'); assert.equal(elements[0].count, 3); - assert.equal(elements[1], 'second line'); + assert.equal(elements[1].value, 'second line'); assert.equal(elements[1].count, 2); - assert.equal(elements[2], 'third line'); + assert.equal(elements[2].value, 'third line'); assert.equal(elements[2].count, 1); }); @@ -93,11 +93,13 @@ suite('Debug - REPL', () => { repl.appendToRepl(session, 'third line', severity.Info); const elements = repl.getReplElements(); assert.equal(elements.length, 3); - assert.equal(elements[0], 'first line\n'); + assert.equal(elements[0].value, 'first line\n'); + assert.equal(elements[0].toString(), 'first line\nfirst line\nfirst line\n'); assert.equal(elements[0].count, 3); - assert.equal(elements[1], 'second line'); + assert.equal(elements[1].value, 'second line'); + assert.equal(elements[1].toString(), 'second line\nsecond line'); assert.equal(elements[1].count, 2); - assert.equal(elements[2], 'third line'); + assert.equal(elements[2].value, 'third line'); assert.equal(elements[2].count, 1); }); diff --git a/src/vs/workbench/contrib/debug/test/node/debugger.test.ts b/src/vs/workbench/contrib/debug/test/node/debugger.test.ts index 68a34b4b1..fd61d857b 100644 --- a/src/vs/workbench/contrib/debug/test/node/debugger.test.ts +++ b/src/vs/workbench/contrib/debug/test/node/debugger.test.ts @@ -11,7 +11,7 @@ import { Debugger } from 'vs/workbench/contrib/debug/common/debugger'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { URI } from 'vs/base/common/uri'; import { ExecutableDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter'; -import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/modelService.test'; +import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/testTextResourcePropertiesService'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; diff --git a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts index 96c54f0a8..feae80004 100644 --- a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts @@ -27,7 +27,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ILabelService } from 'vs/platform/label/common/label'; -import { renderCodicons } from 'vs/base/browser/codicons'; +import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { Schemas } from 'vs/base/common/network'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -297,21 +297,28 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { const activationId = activationTimes.activationReason.extensionId.value; const activationEvent = activationTimes.activationReason.activationEvent; if (activationEvent === '*') { - title = nls.localize('starActivation', "Activated by {0} on start-up", activationId); + title = nls.localize({ + key: 'starActivation', + comment: [ + '{0} will be an extension identifier' + ] + }, "Activated by {0} on start-up", activationId); } else if (/^workspaceContains:/.test(activationEvent)) { let fileNameOrGlob = activationEvent.substr('workspaceContains:'.length); if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0) { title = nls.localize({ key: 'workspaceContainsGlobActivation', comment: [ - '{0} will be a glob pattern' + '{0} will be a glob pattern', + '{1} will be an extension identifier' ] - }, "Activated by {1} because a file matching {1} exists in your workspace", fileNameOrGlob, activationId); + }, "Activated by {1} because a file matching {0} exists in your workspace", fileNameOrGlob, activationId); } else { title = nls.localize({ key: 'workspaceContainsFileActivation', comment: [ - '{0} will be a file name' + '{0} will be a file name', + '{1} will be an extension identifier' ] }, "Activated by {1} because file {0} exists in your workspace", fileNameOrGlob, activationId); } @@ -320,7 +327,8 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { title = nls.localize({ key: 'workspaceContainsTimeout', comment: [ - '{0} will be a glob pattern' + '{0} will be a glob pattern', + '{1} will be an extension identifier' ] }, "Activated by {1} because searching for {0} took too long", glob, activationId); } else if (activationEvent === 'onStartupFinished') { @@ -337,7 +345,8 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { title = nls.localize({ key: 'workspaceGenericActivation', comment: [ - 'The {0} placeholder will be an activation event, like e.g. \'language:typescript\', \'debug\', etc.' + '{0} will be an activation event, like e.g. \'language:typescript\', \'debug\', etc.', + '{1} will be an extension identifier' ] }, "Activated by {1} on {0}", activationEvent, activationId); } @@ -346,28 +355,28 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { clearNode(data.msgContainer); if (this._getUnresponsiveProfile(element.description.identifier)) { - const el = $('span', undefined, ...renderCodicons(` $(alert) Unresponsive`)); + const el = $('span', undefined, ...renderLabelWithIcons(` $(alert) Unresponsive`)); el.title = nls.localize('unresponsive.title', "Extension has caused the extension host to freeze."); data.msgContainer.appendChild(el); } if (isNonEmptyArray(element.status.runtimeErrors)) { - const el = $('span', undefined, ...renderCodicons(`$(bug) ${nls.localize('errors', "{0} uncaught errors", element.status.runtimeErrors.length)}`)); + const el = $('span', undefined, ...renderLabelWithIcons(`$(bug) ${nls.localize('errors', "{0} uncaught errors", element.status.runtimeErrors.length)}`)); data.msgContainer.appendChild(el); } if (element.status.messages && element.status.messages.length > 0) { - const el = $('span', undefined, ...renderCodicons(`$(alert) ${element.status.messages[0].message}`)); + const el = $('span', undefined, ...renderLabelWithIcons(`$(alert) ${element.status.messages[0].message}`)); data.msgContainer.appendChild(el); } if (element.description.extensionLocation.scheme === Schemas.vscodeRemote) { - const el = $('span', undefined, ...renderCodicons(`$(remote) ${element.description.extensionLocation.authority}`)); + const el = $('span', undefined, ...renderLabelWithIcons(`$(remote) ${element.description.extensionLocation.authority}`)); data.msgContainer.appendChild(el); const hostLabel = this._labelService.getHostLabel(Schemas.vscodeRemote, this._environmentService.remoteAuthority); if (hostLabel) { - reset(el, ...renderCodicons(`$(remote) ${hostLabel}`)); + reset(el, ...renderLabelWithIcons(`$(remote) ${hostLabel}`)); } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 7a58b96da..e7f0a15b3 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -65,22 +65,14 @@ import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/to import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { insane } from 'vs/base/common/insane/insane'; function removeEmbeddedSVGs(documentContent: string): string { - const newDocument = new DOMParser().parseFromString(documentContent, 'text/html'); - - // remove all inline svgs - const allSVGs = newDocument.documentElement.querySelectorAll('svg'); - if (allSVGs) { - for (let i = 0; i < allSVGs.length; i++) { - const svg = allSVGs[i]; - if (svg.parentNode) { - svg.parentNode.removeChild(allSVGs[i]); - } + return insane(documentContent, { + filter(token: { tag: string, attrs: { readonly [key: string]: string } }): boolean { + return token.tag !== 'svg'; } - } - - return newDocument.documentElement.outerHTML; + }); } class NavBar extends Disposable { @@ -169,6 +161,11 @@ interface IExtensionEditorTemplate { header: HTMLElement; } +const enum WebviewIndex { + Readme, + Changelog +} + export class ExtensionEditor extends EditorPane { static readonly ID: string = 'workbench.editor.extension'; @@ -179,6 +176,12 @@ export class ExtensionEditor extends EditorPane { private extensionChangelog: Cache | null; private extensionManifest: Cache | null; + // Some action bar items use a webview whose vertical scroll position we track in this map + private initialScrollProgress: Map = new Map(); + + // Spot when an ExtensionEditor instance gets reused for a different extension, in which case the vertical scroll positions must be zeroed + private currentIdentifier: string = ''; + private layoutParticipants: ILayoutParticipant[] = []; private readonly contentDisposables = this._register(new DisposableStore()); private readonly transientDisposables = this._register(new DisposableStore()); @@ -336,6 +339,11 @@ export class ExtensionEditor extends EditorPane { this.editorLoadComplete = false; const extension = input.extension; + if (this.currentIdentifier !== extension.identifier.id) { + this.initialScrollProgress.clear(); + this.currentIdentifier = extension.identifier.id; + } + this.transientDisposables.clear(); this.extensionReadme = new Cache(() => createCancelablePromise(token => extension.getReadme(token))); @@ -563,7 +571,7 @@ export class ExtensionEditor extends EditorPane { return Promise.resolve(null); } - private async openMarkdown(cacheResult: CacheResult, noContentCopy: string, template: IExtensionEditorTemplate, token: CancellationToken): Promise { + private async openMarkdown(cacheResult: CacheResult, noContentCopy: string, template: IExtensionEditorTemplate, webviewIndex: WebviewIndex, token: CancellationToken): Promise { try { const body = await this.renderMarkdown(cacheResult, template); if (token.isCancellationRequested) { @@ -572,15 +580,22 @@ export class ExtensionEditor extends EditorPane { const webview = this.contentDisposables.add(this.webviewService.createWebviewOverlay('extensionEditor', { enableFindWidget: true, + tryRestoreScrollPosition: true, }, {}, undefined)); + webview.initialScrollProgress = this.initialScrollProgress.get(webviewIndex) || 0; + webview.claim(this, this.scopedContextKeyService); setParentFlowTo(webview.container, template.content); webview.layoutWebviewOverElement(template.content); webview.html = body; + webview.claim(this, undefined); this.contentDisposables.add(webview.onDidFocus(() => this.fireOnDidFocus())); + + this.contentDisposables.add(webview.onDidScroll(() => this.initialScrollProgress.set(webviewIndex, webview.initialScrollProgress))); + const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout: () => { webview.layoutWebviewOverElement(template.content); @@ -622,8 +637,8 @@ export class ExtensionEditor extends EditorPane { private async renderMarkdown(cacheResult: CacheResult, template: IExtensionEditorTemplate) { const contents = await this.loadContents(() => cacheResult, template); const content = await renderMarkdownDocument(contents, this.extensionService, this.modeService); - const documentContent = await this.renderBody(content); - return removeEmbeddedSVGs(documentContent); + const sanitizedContent = removeEmbeddedSVGs(content); + return await this.renderBody(sanitizedContent); } private async renderBody(body: string): Promise { @@ -709,9 +724,16 @@ export class ExtensionEditor extends EditorPane { } code { + font-family: "SF Mono", Monaco, Menlo, Consolas, "Ubuntu Mono", "Liberation Mono", "DejaVu Sans Mono", "Courier New", monospace; + font-size: 14px; + line-height: 19px; + } + + pre code { font-family: var(--vscode-editor-font-family); font-weight: var(--vscode-editor-font-weight); font-size: var(--vscode-editor-font-size); + line-height: 1.5; } code > div { @@ -823,7 +845,7 @@ export class ExtensionEditor extends EditorPane { if (manifest && manifest.extensionPack && manifest.extensionPack.length) { return this.openExtensionPackReadme(manifest, template, token); } - return this.openMarkdown(this.extensionReadme!.get(), localize('noReadme', "No README available."), template, token); + return this.openMarkdown(this.extensionReadme!.get(), localize('noReadme', "No README available."), template, WebviewIndex.Readme, token); } private async openExtensionPackReadme(manifest: IExtensionManifest, template: IExtensionEditorTemplate, token: CancellationToken): Promise { @@ -855,14 +877,14 @@ export class ExtensionEditor extends EditorPane { await Promise.all([ this.renderExtensionPack(manifest, extensionPackContent, token), - this.openMarkdown(this.extensionReadme!.get(), localize('noReadme', "No README available."), { ...template, ...{ content: readmeContent } }, token), + this.openMarkdown(this.extensionReadme!.get(), localize('noReadme', "No README available."), { ...template, ...{ content: readmeContent } }, WebviewIndex.Readme, token), ]); return { focus: () => extensionPackContent.focus() }; } private openChangelog(template: IExtensionEditorTemplate, token: CancellationToken): Promise { - return this.openMarkdown(this.extensionChangelog!.get(), localize('noChangelog', "No Changelog available."), template, token); + return this.openMarkdown(this.extensionChangelog!.get(), localize('noChangelog', "No Changelog available."), template, WebviewIndex.Changelog, token); } private openContributions(template: IExtensionEditorTemplate, token: CancellationToken): Promise { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts index ab902f413..d48a5cde2 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts @@ -13,7 +13,7 @@ import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { IExtensionRecommendationNotificationService, RecommendationsNotificationResult, RecommendationSource } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; +import { IExtensionRecommendationNotificationService, RecommendationsNotificationResult, RecommendationSource, RecommendationSourceToString } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation'; import { INotificationHandle, INotificationService, IPromptChoice, IPromptChoiceWithMenu, Severity } from 'vs/platform/notification/common/notification'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; @@ -21,6 +21,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUserDataAutoSyncEnablementService, IUserDataSyncResourceEnablementService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { SearchExtensionsAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IExtension, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService'; import { EnablementState, IWorkbenchExtensioManagementService, IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IExtensionIgnoredRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; @@ -28,6 +29,7 @@ import { IExtensionIgnoredRecommendationsService } from 'vs/workbench/services/e type ExtensionRecommendationsNotificationClassification = { userReaction: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; extensionId?: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; + source: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; }; type ExtensionWorkspaceRecommendationsNotificationClassification = { @@ -135,6 +137,7 @@ export class ExtensionRecommendationNotificationService implements IExtensionRec @IExtensionIgnoredRecommendationsService private readonly extensionIgnoredRecommendationsService: IExtensionIgnoredRecommendationsService, @IUserDataAutoSyncEnablementService private readonly userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService, @IUserDataSyncResourceEnablementService private readonly userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService, + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, @optional(ITASExperimentService) tasExperimentService: ITASExperimentService, ) { this.tasExperimentService = tasExperimentService; @@ -153,13 +156,13 @@ export class ExtensionRecommendationNotificationService implements IExtensionRec } return this.promptRecommendationsNotification(extensionIds, message, searchValue, source, { - onDidInstallRecommendedExtensions: (extensions: IExtension[]) => extensions.forEach(extension => this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'install', extensionId: extension.identifier.id })), - onDidShowRecommendedExtensions: (extensions: IExtension[]) => extensions.forEach(extension => this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'show', extensionId: extension.identifier.id })), - onDidCancelRecommendedExtensions: (extensions: IExtension[]) => extensions.forEach(extension => this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'cancelled', extensionId: extension.identifier.id })), + onDidInstallRecommendedExtensions: (extensions: IExtension[]) => extensions.forEach(extension => this.telemetryService.publicLog2<{ userReaction: string, extensionId: string, source: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'install', extensionId: extension.identifier.id, source: RecommendationSourceToString(source) })), + onDidShowRecommendedExtensions: (extensions: IExtension[]) => extensions.forEach(extension => this.telemetryService.publicLog2<{ userReaction: string, extensionId: string, source: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'show', extensionId: extension.identifier.id, source: RecommendationSourceToString(source) })), + onDidCancelRecommendedExtensions: (extensions: IExtension[]) => extensions.forEach(extension => this.telemetryService.publicLog2<{ userReaction: string, extensionId: string, source: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'cancelled', extensionId: extension.identifier.id, source: RecommendationSourceToString(source) })), onDidNeverShowRecommendedExtensionsAgain: (extensions: IExtension[]) => { for (const extension of extensions) { this.addToImportantRecommendationsIgnore(extension.identifier.id); - this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'neverShowAgain', extensionId: extension.identifier.id }); + this.telemetryService.publicLog2<{ userReaction: string, extensionId: string, source: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'neverShowAgain', extensionId: extension.identifier.id, source: RecommendationSourceToString(source) }); } this.notificationService.prompt( Severity.Info, @@ -207,6 +210,11 @@ export class ExtensionRecommendationNotificationService implements IExtensionRec return RecommendationsNotificationResult.Ignored; } + // Do not show exe based recommendations in remote window + if (source === RecommendationSource.EXE && this.workbenchEnvironmentService.remoteAuthority) { + return RecommendationsNotificationResult.IncompatibleWindow; + } + // Ignore exe recommendation if the window // => has shown an exe based recommendation already // => or has shown any two recommendations already diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 839c3ffbe..4cb6024b2 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -6,20 +6,16 @@ import { localize } from 'vs/nls'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; -import { MenuRegistry, MenuId, registerAction2, Action2, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { MenuRegistry, MenuId, registerAction2, Action2, SyncActionDescriptor, ISubmenuItem, IMenuItem, IAction2Options } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ExtensionsLabel, ExtensionsLocalizedLabel, ExtensionsChannelId, IExtensionManagementService, IExtensionGalleryService, PreferencesLocalizedLabel } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { ExtensionsLabel, ExtensionsLocalizedLabel, ExtensionsChannelId, IExtensionManagementService, IExtensionGalleryService, PreferencesLocalizedLabel, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { EnablementState, IExtensionManagementServerService, IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID } from 'vs/workbench/contrib/extensions/common/extensions'; -import { - OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction, - ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowDisabledExtensionsAction, ShowBuiltInExtensionsAction, UpdateAllAction, - EnableAllAction, EnableAllWorkspaceAction, DisableAllAction, DisableAllWorkspaceAction, CheckForUpdatesAction, ShowLanguageExtensionsAction, EnableAutoUpdateAction, DisableAutoUpdateAction, InstallVSIXAction, ReinstallAction, InstallSpecificVersionOfExtensionAction, ClearExtensionsSearchResultsAction, ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, RefreshExtensionsAction -} from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, DefaultViewsContext, ExtensionsSortByContext, WORKSPACE_RECOMMENDATIONS_VIEW_ID, IWorkspaceRecommendedExtensionsView, AutoUpdateConfigurationKey, HasOutdatedExtensionsContext, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID } from 'vs/workbench/contrib/extensions/common/extensions'; +import { ReinstallAction, InstallSpecificVersionOfExtensionAction, ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, PromptExtensionInstallFailureAction, SearchExtensionsAction } 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 } from 'vs/workbench/contrib/extensions/browser/extensionsViewlet'; @@ -37,11 +33,10 @@ import { ExtensionActivationProgress } from 'vs/workbench/contrib/extensions/bro import { onUnexpectedError } from 'vs/base/common/errors'; import { ExtensionDependencyChecker } from 'vs/workbench/contrib/extensions/browser/extensionsDependencyChecker'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { RemoteExtensionsInstaller } from 'vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller'; -import { IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views'; +import { IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions, IViewsService } from 'vs/workbench/common/views'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -import { ContextKeyAndExpr, ContextKeyExpr, ContextKeyOrExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyAndExpr, ContextKeyDefinedExpr, ContextKeyEqualsExpr, ContextKeyExpr, ContextKeyOrExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; import { InstallExtensionQuickAccessProvider, ManageExtensionsQuickAccessProvider } from 'vs/workbench/contrib/extensions/browser/extensionsQuickAccess'; @@ -65,7 +60,15 @@ import { IAction } from 'vs/base/common/actions'; import { IWorkpsaceExtensionsConfigService } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig'; import { Schemas } from 'vs/base/common/network'; import { ShowRuntimeExtensionsAction } from 'vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor'; -import { extensionsViewIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; +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 } from 'vs/base/common/lifecycle'; +import { isArray } from 'vs/base/common/types'; +import { ShowViewletAction } from 'vs/workbench/browser/viewlet'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { mnemonicButtonLabel } from 'vs/base/common/labels'; +import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); @@ -83,16 +86,6 @@ Registry.as(Extensions.Quickaccess).registerQuickAccessPro helpEntries: [{ description: localize('manageExtensionsHelp', "Manage Extensions"), needsEditor: false }] }); -// Explorer -MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { - group: 'extensions', - command: { - id: INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, - title: localize('installVSIX', "Install Extension VSIX"), - }, - when: ResourceContextKey.Extension.isEqualTo('.vsix') -}); - // Editor Registry.as(EditorExtensions.Editors).registerEditor( EditorDescriptor.create( @@ -226,36 +219,6 @@ CommandsRegistry.registerCommand({ } }); -CommandsRegistry.registerCommand({ - id: INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, - handler: 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 Promise.all(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, - { sticky: true } - ); - } - }); - } -}); - CommandsRegistry.registerCommand({ id: 'workbench.extensions.uninstallExtension', description: { @@ -316,57 +279,6 @@ CommandsRegistry.registerCommand({ } }); -// File menu registration - -MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { - group: '2_keybindings', - command: { - id: ShowRecommendedKeymapExtensionsAction.ID, - title: localize({ key: 'miOpenKeymapExtensions', comment: ['&& denotes a mnemonic'] }, "&&Keymaps") - }, - order: 2 -}); - -MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - group: '2_keybindings', - command: { - id: ShowRecommendedKeymapExtensionsAction.ID, - title: localize('miOpenKeymapExtensions2', "Keymaps") - }, - order: 2 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { - group: '1_settings', - command: { - id: VIEWLET_ID, - title: localize({ key: 'miPreferencesExtensions', comment: ['&& denotes a mnemonic'] }, "&&Extensions") - }, - order: 3 -}); - -// View menu - -MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '3_views', - command: { - id: VIEWLET_ID, - title: localize({ key: 'miViewExtensions', comment: ['&& denotes a mnemonic'] }, "E&&xtensions") - }, - order: 5 -}); - -// Global Activity Menu - -MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - group: '2_configuration', - command: { - id: VIEWLET_ID, - title: localize('showExtensions', "Extensions") - }, - order: 3 -}); - function overrideActionForActiveExtensionEditorWebview(command: MultiCommand | undefined, f: (webview: Webview) => void) { command?.addImplementation(105, (accessor) => { const editorService = accessor.get(IEditorService); @@ -399,13 +311,25 @@ async function runAction(action: IAction): Promise { } } -class ExtensionsContributions implements IWorkbenchContribution { +interface IExtensionActionOptions extends IAction2Options { + menuTitles?: { [id: number]: string }; + run(accessor: ServicesAccessor, ...args: any[]): Promise; +} + +class ExtensionsContributions extends Disposable implements IWorkbenchContribution { constructor( @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, @IContextKeyService contextKeyService: IContextKeyService, + @IViewletService private readonly viewletService: IViewletService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @INotificationService private readonly notificationService: INotificationService, + @ICommandService private readonly commandService: ICommandService, ) { + super(); const hasGalleryContext = CONTEXT_HAS_GALLERY.bindTo(contextKeyService); if (extensionGalleryService.isEnabled()) { hasGalleryContext.set(true); @@ -447,437 +371,667 @@ class ExtensionsContributions implements IWorkbenchContribution { // Global actions private registerGlobalActions(): void { - registerAction2(class extends Action2 { - constructor() { - super({ - id: OpenExtensionsViewletAction.ID, - title: { value: OpenExtensionsViewletAction.LABEL, original: 'Show Extensions' }, - category: CATEGORIES.View, - menu: { - id: MenuId.CommandPalette, - }, - keybinding: { - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_X, - weight: KeybindingWeight.WorkbenchContrib + this.registerExtensionAction({ + id: VIEWLET_ID, + title: { value: localize('toggleExtensionsViewlet', "Show Extensions"), original: 'Show Extensions' }, + category: CATEGORIES.View, + menu: [{ + id: MenuId.CommandPalette, + }, { + id: MenuId.MenubarPreferencesMenu, + group: '1_settings', + order: 3 + }, { + id: MenuId.MenubarViewMenu, + group: '3_views', + order: 5 + }, { + id: MenuId.GlobalActivity, + group: '2_configuration', + order: 3 + }], + keybinding: { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_X, + weight: KeybindingWeight.WorkbenchContrib + }, + menuTitles: { + [MenuId.MenubarPreferencesMenu.id]: localize({ key: 'miPreferencesExtensions', comment: ['&& denotes a mnemonic'] }, "&&Extensions"), + [MenuId.MenubarViewMenu.id]: localize({ key: 'miViewExtensions', comment: ['&& denotes a mnemonic'] }, "E&&xtensions"), + [MenuId.GlobalActivity.id]: localize('showExtensions', "Extensions"), + }, + run: () => runAction(this.instantiationService.createInstance(ShowViewletAction, VIEWLET_ID, 'Show Extensions', VIEWLET_ID)) + }); + + this.registerExtensionAction({ + id: 'workbench.extensions.action.installExtensions', + title: { value: localize('installExtensions', "Install Extensions"), original: 'Install Extensions' }, + category: ExtensionsLocalizedLabel, + menu: { + id: MenuId.CommandPalette, + when: ContextKeyAndExpr.create([CONTEXT_HAS_GALLERY, ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER])]) + }, + run: () => runAction(this.instantiationService.createInstance(ShowViewletAction, VIEWLET_ID, 'Install Extensions', VIEWLET_ID)) + }); + + this.registerExtensionAction({ + id: 'workbench.extensions.action.showRecommendedKeymapExtensions', + title: { value: localize('showRecommendedKeymapExtensionsShort', "Keymaps"), original: 'Keymaps' }, + category: PreferencesLocalizedLabel, + menu: [{ + id: MenuId.CommandPalette, + when: CONTEXT_HAS_GALLERY + }, { + id: MenuId.MenubarPreferencesMenu, + group: '2_keybindings', + order: 2 + }, { + id: MenuId.GlobalActivity, + group: '2_keybindings', + order: 2 + }], + keybinding: { + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_M), + weight: KeybindingWeight.WorkbenchContrib + }, + menuTitles: { + [MenuId.MenubarPreferencesMenu.id]: localize({ key: 'miOpenKeymapExtensions', comment: ['&& denotes a mnemonic'] }, "&&Keymaps"), + [MenuId.GlobalActivity.id]: localize('miOpenKeymapExtensions2', "Keymaps") + }, + run: () => runAction(this.instantiationService.createInstance(SearchExtensionsAction, '@recommended:keymaps ')) + }); + + this.registerExtensionAction({ + id: 'workbench.extensions.action.showLanguageExtensions', + title: { value: localize('showLanguageExtensionsShort', "Language Extensions"), original: 'Language Extensions' }, + category: PreferencesLocalizedLabel, + menu: { + id: MenuId.CommandPalette, + when: CONTEXT_HAS_GALLERY + }, + run: () => runAction(this.instantiationService.createInstance(SearchExtensionsAction, '@category:"programming languages" @sort:installs ')) + }); + + this.registerExtensionAction({ + id: 'workbench.extensions.action.checkForUpdates', + title: { value: localize('checkForUpdates', "Check for Extension Updates"), original: 'Check for Extension Updates' }, + category: ExtensionsLocalizedLabel, + menu: [{ + id: MenuId.CommandPalette, + when: ContextKeyAndExpr.create([CONTEXT_HAS_GALLERY, ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER])]) + }, { + id: MenuId.ViewContainerTitle, + when: ContextKeyEqualsExpr.create('viewContainer', VIEWLET_ID), + group: '1_updates', + order: 1 + }], + run: async () => { + await this.extensionsWorkbenchService.checkForUpdates(); + const outdated = this.extensionsWorkbenchService.outdated; + if (!outdated.length) { + this.notificationService.info(localize('noUpdatesAvailable', "All extensions are up to date.")); + return; + } + + let msgAvailableExtensions = outdated.length === 1 ? localize('singleUpdateAvailable', "An extension update is available.") : localize('updatesAvailable', "{0} extension updates are available.", outdated.length); + + const disabledExtensionsCount = outdated.filter(ext => ext.local && !this.extensionEnablementService.isEnabled(ext.local)).length; + if (disabledExtensionsCount) { + if (outdated.length === 1) { + msgAvailableExtensions = localize('singleDisabledUpdateAvailable', "An update to an extension which is disabled is available."); + } else if (disabledExtensionsCount === 1) { + msgAvailableExtensions = localize('updatesAvailableOneDisabled', "{0} extension updates are available. One of them is for a disabled extension.", outdated.length); + } else if (disabledExtensionsCount === outdated.length) { + msgAvailableExtensions = localize('updatesAvailableAllDisabled', "{0} extension updates are available. All of them are for disabled extensions.", outdated.length); + } else { + msgAvailableExtensions = localize('updatesAvailableIncludingDisabled', "{0} extension updates are available. {1} of them are for disabled extensions.", outdated.length, disabledExtensionsCount); } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(OpenExtensionsViewletAction, OpenExtensionsViewletAction.ID, OpenExtensionsViewletAction.LABEL)); + } + + this.viewletService.openViewlet(VIEWLET_ID, true); + this.notificationService.info(msgAvailableExtensions); } }); - registerAction2(class extends Action2 { - constructor() { - super({ - id: InstallExtensionsAction.ID, - title: { value: InstallExtensionsAction.LABEL, original: 'Install Extensions' }, - category: ExtensionsLocalizedLabel, - menu: { - id: MenuId.CommandPalette, - when: ContextKeyAndExpr.create([CONTEXT_HAS_GALLERY, ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER])]) + this.registerExtensionAction({ + id: 'workbench.extensions.action.disableAutoUpdate', + title: { value: localize('disableAutoUpdate', "Disable Auto Updating Extensions"), original: 'Disable Auto Updating Extensions' }, + category: ExtensionsLocalizedLabel, + menu: [{ + id: MenuId.CommandPalette, + }, { + id: MenuId.ViewContainerTitle, + when: ContextKeyAndExpr.create([ContextKeyEqualsExpr.create('viewContainer', VIEWLET_ID), ContextKeyDefinedExpr.create(`config.${AutoUpdateConfigurationKey}`)]), + group: '1_updates', + order: 2 + }], + run: (accessor: ServicesAccessor) => accessor.get(IConfigurationService).updateValue(AutoUpdateConfigurationKey, false) + }); + + this.registerExtensionAction({ + id: 'workbench.extensions.action.updateAllExtensions', + title: { value: localize('updateAll', "Update All Extensions"), original: 'Update All Extensions' }, + category: ExtensionsLocalizedLabel, + precondition: HasOutdatedExtensionsContext, + menu: [{ + id: MenuId.CommandPalette, + when: ContextKeyAndExpr.create([CONTEXT_HAS_GALLERY, ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER])]) + }, { + id: MenuId.ViewContainerTitle, + when: ContextKeyAndExpr.create([ContextKeyEqualsExpr.create('viewContainer', VIEWLET_ID), ContextKeyDefinedExpr.create(`config.${AutoUpdateConfigurationKey}`).negate()]), + group: '1_updates', + order: 2 + }], + run: () => { + return Promise.all(this.extensionsWorkbenchService.outdated.map(async extension => { + try { + await this.extensionsWorkbenchService.install(extension); + } catch (err) { + runAction(this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, extension.latestVersion, InstallOperation.Update, err)); } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(InstallExtensionsAction, InstallExtensionsAction.ID, InstallExtensionsAction.LABEL)); + })); } }); - registerAction2(class extends Action2 { - constructor() { - super({ - id: ShowOutdatedExtensionsAction.ID, - title: { value: ShowOutdatedExtensionsAction.LABEL, original: 'Show Outdated Extensions' }, - category: ExtensionsLocalizedLabel, - menu: { - id: MenuId.CommandPalette, - when: ContextKeyAndExpr.create([CONTEXT_HAS_GALLERY, ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER])]) - } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, ShowOutdatedExtensionsAction.LABEL)); + this.registerExtensionAction({ + id: 'workbench.extensions.action.enableAutoUpdate', + title: { value: localize('enableAutoUpdate', "Enable Auto Updating Extensions"), original: 'Enable Auto Updating Extensions' }, + category: ExtensionsLocalizedLabel, + menu: [{ + id: MenuId.CommandPalette, + }, { + id: MenuId.ViewContainerTitle, + when: ContextKeyAndExpr.create([ContextKeyEqualsExpr.create('viewContainer', VIEWLET_ID), ContextKeyDefinedExpr.create(`config.${AutoUpdateConfigurationKey}`).negate()]), + group: '1_updates', + order: 3 + }], + run: (accessor: ServicesAccessor) => accessor.get(IConfigurationService).updateValue(AutoUpdateConfigurationKey, true) + }); + + this.registerExtensionAction({ + id: 'workbench.extensions.action.enableAll', + title: { value: localize('enableAll', "Enable All Extensions"), original: 'Enable All Extensions' }, + category: ExtensionsLocalizedLabel, + menu: [{ + id: MenuId.CommandPalette, + when: ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER]) + }, { + id: MenuId.ViewContainerTitle, + when: ContextKeyEqualsExpr.create('viewContainer', VIEWLET_ID), + group: '2_enablement', + order: 1 + }], + run: async () => { + const extensionsToEnable = this.extensionsWorkbenchService.local.filter(e => !!e.local && this.extensionEnablementService.canChangeEnablement(e.local) && !this.extensionEnablementService.isEnabled(e.local)); + if (extensionsToEnable.length) { + await this.extensionsWorkbenchService.setEnablement(extensionsToEnable, EnablementState.EnabledGlobally); + } } }); - registerAction2(class extends Action2 { - constructor() { - super({ - id: ShowRecommendedExtensionsAction.ID, - title: { value: ShowRecommendedExtensionsAction.LABEL, original: 'Show Recommended Extensions' }, - category: ExtensionsLocalizedLabel, - menu: { - id: MenuId.CommandPalette, - when: CONTEXT_HAS_GALLERY - } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL)); + this.registerExtensionAction({ + id: 'workbench.extensions.action.enableAllWorkspace', + title: { value: localize('enableAllWorkspace', "Enable All Extensions for this Workspace"), original: 'Enable All Extensions for this Workspace' }, + category: ExtensionsLocalizedLabel, + menu: { + id: MenuId.CommandPalette, + when: ContextKeyAndExpr.create([WorkbenchStateContext.notEqualsTo('empty'), ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER])]) + }, + run: async () => { + const extensionsToEnable = this.extensionsWorkbenchService.local.filter(e => !!e.local && this.extensionEnablementService.canChangeEnablement(e.local) && !this.extensionEnablementService.isEnabled(e.local)); + if (extensionsToEnable.length) { + await this.extensionsWorkbenchService.setEnablement(extensionsToEnable, EnablementState.EnabledWorkspace); + } } }); - registerAction2(class extends Action2 { - constructor() { - super({ - id: ShowRecommendedKeymapExtensionsAction.ID, - title: { value: ShowRecommendedKeymapExtensionsAction.LABEL, original: 'Keymaps' }, - category: PreferencesLocalizedLabel, - menu: { - id: MenuId.CommandPalette, - when: CONTEXT_HAS_GALLERY - }, - keybinding: { - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_M), - weight: KeybindingWeight.WorkbenchContrib - } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(ShowRecommendedKeymapExtensionsAction, ShowRecommendedKeymapExtensionsAction.ID, ShowRecommendedKeymapExtensionsAction.LABEL)); + this.registerExtensionAction({ + id: 'workbench.extensions.action.disableAll', + title: { value: localize('disableAll', "Disable All Installed Extensions"), original: 'Disable All Installed Extensions' }, + category: ExtensionsLocalizedLabel, + menu: [{ + id: MenuId.CommandPalette, + when: ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER]) + }, { + id: MenuId.ViewContainerTitle, + when: ContextKeyEqualsExpr.create('viewContainer', VIEWLET_ID), + group: '2_enablement', + order: 2 + }], + run: async () => { + const extensionsToDisable = this.extensionsWorkbenchService.local.filter(e => !e.isBuiltin && !!e.local && this.extensionEnablementService.isEnabled(e.local) && this.extensionEnablementService.canChangeEnablement(e.local)); + if (extensionsToDisable.length) { + await this.extensionsWorkbenchService.setEnablement(extensionsToDisable, EnablementState.DisabledGlobally); + } } }); - registerAction2(class extends Action2 { - constructor() { - super({ - id: ShowLanguageExtensionsAction.ID, - title: { value: ShowLanguageExtensionsAction.LABEL, original: 'Language Extensions' }, - category: PreferencesLocalizedLabel, - menu: { - id: MenuId.CommandPalette, - when: CONTEXT_HAS_GALLERY - } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(ShowLanguageExtensionsAction, ShowLanguageExtensionsAction.ID, ShowLanguageExtensionsAction.LABEL)); + this.registerExtensionAction({ + id: 'workbench.extensions.action.disableAllWorkspace', + title: { value: localize('disableAllWorkspace', "Disable All Installed Extensions for this Workspace"), original: 'Disable All Installed Extensions for this Workspace' }, + category: ExtensionsLocalizedLabel, + menu: { + id: MenuId.CommandPalette, + when: ContextKeyAndExpr.create([WorkbenchStateContext.notEqualsTo('empty'), ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER])]) + }, + run: async () => { + const extensionsToDisable = this.extensionsWorkbenchService.local.filter(e => !e.isBuiltin && !!e.local && this.extensionEnablementService.isEnabled(e.local) && this.extensionEnablementService.canChangeEnablement(e.local)); + if (extensionsToDisable.length) { + await this.extensionsWorkbenchService.setEnablement(extensionsToDisable, EnablementState.DisabledWorkspace); + } } }); - registerAction2(class extends Action2 { - constructor() { - super({ - id: ShowPopularExtensionsAction.ID, - title: { value: ShowPopularExtensionsAction.LABEL, original: 'Show Popular Extensions' }, - category: ExtensionsLocalizedLabel, - menu: { - id: MenuId.CommandPalette, - when: CONTEXT_HAS_GALLERY - } + this.registerExtensionAction({ + id: SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, + title: { value: localize('InstallFromVSIX', "Install from VSIX..."), original: 'Install from VSIX...' }, + category: ExtensionsLocalizedLabel, + menu: [{ + id: MenuId.CommandPalette, + when: ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER]) + }, { + id: MenuId.ViewContainerTitle, + when: ContextKeyAndExpr.create([ContextKeyEqualsExpr.create('viewContainer', VIEWLET_ID), ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER])]), + group: '3_install', + order: 1 + }], + run: async (accessor: ServicesAccessor) => { + const fileDialogService = accessor.get(IFileDialogService); + const commandService = accessor.get(ICommandService); + const vsixPaths = await fileDialogService.showOpenDialog({ + title: localize('installFromVSIX', "Install from VSIX"), + filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }], + canSelectFiles: true, + canSelectMany: true, + openLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install")) }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, ShowPopularExtensionsAction.LABEL)); + if (vsixPaths) { + await commandService.executeCommand(INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, vsixPaths); + } } }); - registerAction2(class extends Action2 { - constructor() { - super({ - id: ShowEnabledExtensionsAction.ID, - title: { value: ShowEnabledExtensionsAction.LABEL, original: 'Show Enabled Extensions' }, - category: ExtensionsLocalizedLabel, - menu: { - id: MenuId.CommandPalette, - when: ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER]) - } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(ShowEnabledExtensionsAction, ShowEnabledExtensionsAction.ID, ShowEnabledExtensionsAction.LABEL)); + this.registerExtensionAction({ + id: INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, + title: localize('installVSIX', "Install Extension VSIX"), + menu: [{ + id: MenuId.ExplorerContext, + group: 'extensions', + when: ContextKeyAndExpr.create([ResourceContextKey.Extension.isEqualTo('.vsix'), ContextKeyOrExpr.create([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 Promise.all(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, + { sticky: true } + ); + } + }); } }); - registerAction2(class extends Action2 { - constructor() { - super({ - id: ShowInstalledExtensionsAction.ID, - title: { value: ShowInstalledExtensionsAction.LABEL, original: 'Show Installed Extensions' }, - category: ExtensionsLocalizedLabel, - menu: { - id: MenuId.CommandPalette, - when: ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER]) - } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL)); + const extensionsFilterSubMenu = new MenuId('extensionsFilterSubMenu'); + MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, { + submenu: extensionsFilterSubMenu, + title: localize('filterExtensions', "Filter Extensions..."), + when: ContextKeyEqualsExpr.create('viewContainer', VIEWLET_ID), + group: 'navigation', + order: 1, + icon: filterIcon, + }); + + const showFeaturedExtensionsId = 'extensions.filter.featured'; + this.registerExtensionAction({ + id: showFeaturedExtensionsId, + title: { value: localize('showFeaturedExtensions', "Show Featured Extensions"), original: 'Show Featured Extensions' }, + category: ExtensionsLocalizedLabel, + menu: [{ + id: MenuId.CommandPalette, + when: CONTEXT_HAS_GALLERY + }, { + id: extensionsFilterSubMenu, + when: CONTEXT_HAS_GALLERY, + group: '1_predefined', + order: 1, + }], + menuTitles: { + [extensionsFilterSubMenu.id]: localize('featured filter', "Featured") + }, + run: () => runAction(this.instantiationService.createInstance(SearchExtensionsAction, '@featured ')) + }); + + this.registerExtensionAction({ + id: 'workbench.extensions.action.showPopularExtensions', + title: { value: localize('showPopularExtensions', "Show Popular Extensions"), original: 'Show Popular Extensions' }, + category: ExtensionsLocalizedLabel, + menu: [{ + id: MenuId.CommandPalette, + when: CONTEXT_HAS_GALLERY + }, { + id: extensionsFilterSubMenu, + when: CONTEXT_HAS_GALLERY, + group: '1_predefined', + order: 2, + }], + menuTitles: { + [extensionsFilterSubMenu.id]: localize('most popular filter', "Most Popular") + }, + run: () => runAction(this.instantiationService.createInstance(SearchExtensionsAction, '@popular ')) + }); + + this.registerExtensionAction({ + id: 'workbench.extensions.action.showRecommendedExtensions', + title: { value: localize('showRecommendedExtensions', "Show Recommended Extensions"), original: 'Show Recommended Extensions' }, + category: ExtensionsLocalizedLabel, + menu: [{ + id: MenuId.CommandPalette, + when: CONTEXT_HAS_GALLERY + }, { + id: extensionsFilterSubMenu, + when: CONTEXT_HAS_GALLERY, + group: '1_predefined', + order: 2, + }], + menuTitles: { + [extensionsFilterSubMenu.id]: localize('most popular recommended', "Recommended") + }, + run: () => runAction(this.instantiationService.createInstance(SearchExtensionsAction, '@recommended ')) + }); + + this.registerExtensionAction({ + id: 'workbench.extensions.action.recentlyPublishedExtensions', + title: { value: localize('recentlyPublishedExtensions', "Show Recently Published Extensions"), original: 'Show Recently Published Extensions' }, + category: ExtensionsLocalizedLabel, + menu: [{ + id: MenuId.CommandPalette, + when: CONTEXT_HAS_GALLERY + }, { + id: extensionsFilterSubMenu, + when: CONTEXT_HAS_GALLERY, + group: '1_predefined', + order: 2, + }], + menuTitles: { + [extensionsFilterSubMenu.id]: localize('recently published filter', "Recently Published") + }, + run: () => runAction(this.instantiationService.createInstance(SearchExtensionsAction, '@sort:publishedDate ')) + }); + + const extensionsCategoryFilterSubMenu = new MenuId('extensionsCategoryFilterSubMenu'); + MenuRegistry.appendMenuItem(extensionsFilterSubMenu, { + submenu: extensionsCategoryFilterSubMenu, + title: localize('filter by category', "Category"), + when: CONTEXT_HAS_GALLERY, + group: '2_categories', + order: 1, + }); + + EXTENSION_CATEGORIES.map((category, index) => { + this.registerExtensionAction({ + id: `extensions.actions.searchByCategory.${category}`, + title: category, + menu: [{ + id: extensionsCategoryFilterSubMenu, + when: CONTEXT_HAS_GALLERY, + order: index, + }], + run: () => runAction(this.instantiationService.createInstance(SearchExtensionsAction, `@category:"${category.toLowerCase()}"`)) + }); + }); + + this.registerExtensionAction({ + id: 'workbench.extensions.action.listBuiltInExtensions', + title: { value: localize('showBuiltInExtensions', "Show Built-in Extensions"), original: 'Show Built-in Extensions' }, + category: ExtensionsLocalizedLabel, + menu: [{ + id: MenuId.CommandPalette, + when: ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER]) + }, { + id: extensionsFilterSubMenu, + group: '3_installed', + order: 1, + }], + menuTitles: { + [extensionsFilterSubMenu.id]: localize('builtin filter', "Built-in") + }, + run: () => runAction(this.instantiationService.createInstance(SearchExtensionsAction, '@builtin ')) + }); + + this.registerExtensionAction({ + id: 'workbench.extensions.action.showInstalledExtensions', + title: { value: localize('showInstalledExtensions', "Show Installed Extensions"), original: 'Show Installed Extensions' }, + category: ExtensionsLocalizedLabel, + menu: [{ + id: MenuId.CommandPalette, + when: ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER]) + }, { + id: extensionsFilterSubMenu, + group: '3_installed', + order: 2, + }], + menuTitles: { + [extensionsFilterSubMenu.id]: localize('installed filter', "Installed") + }, + run: () => runAction(this.instantiationService.createInstance(SearchExtensionsAction, '@installed ')) + }); + + this.registerExtensionAction({ + id: 'workbench.extensions.action.showEnabledExtensions', + title: { value: localize('showEnabledExtensions', "Show Enabled Extensions"), original: 'Show Enabled Extensions' }, + category: ExtensionsLocalizedLabel, + menu: [{ + id: MenuId.CommandPalette, + when: ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER]) + }, { + id: extensionsFilterSubMenu, + group: '3_installed', + order: 3, + }], + menuTitles: { + [extensionsFilterSubMenu.id]: localize('enabled filter', "Enabled") + }, + run: () => runAction(this.instantiationService.createInstance(SearchExtensionsAction, '@enabled ')) + }); + + this.registerExtensionAction({ + id: 'workbench.extensions.action.showDisabledExtensions', + title: { value: localize('showDisabledExtensions', "Show Disabled Extensions"), original: 'Show Disabled Extensions' }, + category: ExtensionsLocalizedLabel, + + menu: [{ + id: MenuId.CommandPalette, + when: ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER]) + }, { + id: extensionsFilterSubMenu, + group: '3_installed', + order: 4, + }], + menuTitles: { + [extensionsFilterSubMenu.id]: localize('disabled filter', "Disabled") + }, + run: () => runAction(this.instantiationService.createInstance(SearchExtensionsAction, '@disabled ')) + }); + + this.registerExtensionAction({ + id: 'workbench.extensions.action.listOutdatedExtensions', + title: { value: localize('showOutdatedExtensions', "Show Outdated Extensions"), original: 'Show Outdated Extensions' }, + category: ExtensionsLocalizedLabel, + menu: [{ + id: MenuId.CommandPalette, + when: ContextKeyAndExpr.create([CONTEXT_HAS_GALLERY, ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER])]) + }, { + id: extensionsFilterSubMenu, + group: '3_installed', + order: 5, + }], + menuTitles: { + [extensionsFilterSubMenu.id]: localize('outdated filter', "Outdated") + }, + run: () => runAction(this.instantiationService.createInstance(SearchExtensionsAction, '@outdated ')) + }); + + const extensionsSortSubMenu = new MenuId('extensionsSortSubMenu'); + MenuRegistry.appendMenuItem(extensionsFilterSubMenu, { + submenu: extensionsSortSubMenu, + title: localize('sorty by', "Sort By"), + when: CONTEXT_HAS_GALLERY, + group: '4_sort', + order: 1, + }); + + [ + { id: 'installs', title: localize('sort by installs', "Install Count") }, + { id: 'rating', title: localize('sort by rating', "Rating") }, + { id: 'name', title: localize('sort by name', "Name") }, + { id: 'publishedDate', title: localize('sort by date', "Published Date") }, + ].map(({ id, title }, index) => { + this.registerExtensionAction({ + id: `extensions.sort.${id}`, + title, + precondition: DefaultViewsContext.toNegated(), + menu: [{ + id: extensionsSortSubMenu, + when: CONTEXT_HAS_GALLERY, + order: index, + }], + toggled: ExtensionsSortByContext.isEqualTo(id), + run: async () => { + const viewlet = await this.viewletService.openViewlet(VIEWLET_ID, true); + const extensionsViewPaneContainer = viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer; + const currentQuery = Query.parse(extensionsViewPaneContainer.searchValue || ''); + extensionsViewPaneContainer.search(new Query(currentQuery.value, id, currentQuery.groupBy).toString()); + extensionsViewPaneContainer.focus(); + } + }); + }); + + this.registerExtensionAction({ + id: 'workbench.extensions.action.clearExtensionsSearchResults', + title: { value: localize('clearExtensionsSearchResults', "Clear Extensions Search Results"), original: 'Clear Extensions Search Results' }, + category: ExtensionsLocalizedLabel, + icon: clearSearchResultsIcon, + f1: true, + precondition: DefaultViewsContext.toNegated(), + menu: { + id: MenuId.ViewContainerTitle, + when: ContextKeyEqualsExpr.create('viewContainer', VIEWLET_ID), + group: 'navigation', + order: 3, + }, + run: async (accessor: ServicesAccessor) => { + const viewPaneContainer = accessor.get(IViewsService).getActiveViewPaneContainerWithId(VIEWLET_ID); + if (viewPaneContainer) { + const extensionsViewPaneContainer = viewPaneContainer as IExtensionsViewPaneContainer; + extensionsViewPaneContainer.search(''); + extensionsViewPaneContainer.focus(); + } } }); - registerAction2(class extends Action2 { - constructor() { - super({ - id: ShowDisabledExtensionsAction.ID, - title: { value: ShowDisabledExtensionsAction.LABEL, original: 'Show Disabled Extensions' }, - category: ExtensionsLocalizedLabel, - menu: { - id: MenuId.CommandPalette, - when: ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER]) - } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, ShowDisabledExtensionsAction.LABEL)); + this.registerExtensionAction({ + id: 'workbench.extensions.action.refreshExtension', + title: { value: localize('refreshExtension', "Refresh"), original: 'Refresh' }, + category: ExtensionsLocalizedLabel, + icon: refreshIcon, + f1: true, + menu: { + id: MenuId.ViewContainerTitle, + when: ContextKeyEqualsExpr.create('viewContainer', VIEWLET_ID), + group: 'navigation', + order: 2 + }, + run: async (accessor: ServicesAccessor) => { + const viewPaneContainer = accessor.get(IViewsService).getActiveViewPaneContainerWithId(VIEWLET_ID); + if (viewPaneContainer) { + await (viewPaneContainer as IExtensionsViewPaneContainer).refresh(); + } } }); - registerAction2(class extends Action2 { - constructor() { - super({ - id: ShowBuiltInExtensionsAction.ID, - title: { value: ShowBuiltInExtensionsAction.LABEL, original: 'Show Built-in Extensions' }, - category: ExtensionsLocalizedLabel, - menu: { - id: MenuId.CommandPalette, - when: ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER]) - } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, ShowBuiltInExtensionsAction.LABEL)); + this.registerExtensionAction({ + id: 'workbench.extensions.action.installWorkspaceRecommendedExtensions', + title: localize('installWorkspaceRecommendedExtensions', "Install Workspace Recommended Extensions"), + icon: installWorkspaceRecommendedIcon, + menu: { + id: MenuId.ViewTitle, + when: ContextKeyEqualsExpr.create('view', WORKSPACE_RECOMMENDATIONS_VIEW_ID), + group: 'navigation', + order: 1 + }, + run: async (accessor: ServicesAccessor) => { + const view = accessor.get(IViewsService).getActiveViewWithId(WORKSPACE_RECOMMENDATIONS_VIEW_ID) as IWorkspaceRecommendedExtensionsView; + return view.installWorkspaceRecommendations(); } }); - registerAction2(class extends Action2 { - constructor() { - super({ - id: UpdateAllAction.ID, - title: { value: UpdateAllAction.LABEL, original: 'Update All Extensions' }, - category: ExtensionsLocalizedLabel, - menu: { - id: MenuId.CommandPalette, - when: ContextKeyAndExpr.create([CONTEXT_HAS_GALLERY, ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER])]) - } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL, false)); - } + this.registerExtensionAction({ + id: ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, + title: ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL, + icon: configureRecommendedIcon, + menu: [{ + id: MenuId.CommandPalette, + when: WorkbenchStateContext.notEqualsTo('empty'), + }, { + id: MenuId.ViewTitle, + when: ContextKeyEqualsExpr.create('view', WORKSPACE_RECOMMENDATIONS_VIEW_ID), + group: 'navigation', + order: 2 + }], + run: () => runAction(this.instantiationService.createInstance(ConfigureWorkspaceFolderRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL)) }); - registerAction2(class extends Action2 { - constructor() { - super({ - id: InstallVSIXAction.ID, - title: { value: InstallVSIXAction.LABEL, original: 'Install from VSIX...' }, - category: ExtensionsLocalizedLabel, - menu: { - id: MenuId.CommandPalette, - when: ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER]) - } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL)); - } + this.registerExtensionAction({ + id: InstallSpecificVersionOfExtensionAction.ID, + title: { value: InstallSpecificVersionOfExtensionAction.LABEL, original: 'Install Specific Version of Extension...' }, + category: ExtensionsLocalizedLabel, + menu: { + id: MenuId.CommandPalette, + when: ContextKeyAndExpr.create([CONTEXT_HAS_GALLERY, ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER])]) + }, + run: () => runAction(this.instantiationService.createInstance(InstallSpecificVersionOfExtensionAction, InstallSpecificVersionOfExtensionAction.ID, InstallSpecificVersionOfExtensionAction.LABEL)) }); - registerAction2(class extends Action2 { - constructor() { - super({ - id: DisableAllAction.ID, - title: { value: DisableAllAction.LABEL, original: 'Disable All Installed Extensions' }, - category: ExtensionsLocalizedLabel, - menu: { - id: MenuId.CommandPalette, - when: ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER]) - } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL, false)); - } - }); - - registerAction2(class extends Action2 { - constructor() { - super({ - id: DisableAllWorkspaceAction.ID, - title: { value: DisableAllWorkspaceAction.LABEL, original: 'Disable All Installed Extensions for this Workspace' }, - category: ExtensionsLocalizedLabel, - menu: { - id: MenuId.CommandPalette, - when: ContextKeyAndExpr.create([WorkbenchStateContext.notEqualsTo('empty'), ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER])]) - } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(DisableAllWorkspaceAction, DisableAllWorkspaceAction.ID, DisableAllWorkspaceAction.LABEL, false)); - } - }); - - registerAction2(class extends Action2 { - constructor() { - super({ - id: EnableAllAction.ID, - title: { value: EnableAllAction.LABEL, original: 'Enable All Extensions' }, - category: ExtensionsLocalizedLabel, - menu: { - id: MenuId.CommandPalette, - when: ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER]) - } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL, false)); - } - }); - - registerAction2(class extends Action2 { - constructor() { - super({ - id: EnableAllWorkspaceAction.ID, - title: { value: EnableAllWorkspaceAction.LABEL, original: 'Enable All Extensions for this Workspace' }, - category: ExtensionsLocalizedLabel, - menu: { - id: MenuId.CommandPalette, - when: ContextKeyAndExpr.create([WorkbenchStateContext.notEqualsTo('empty'), ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER])]) - } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(EnableAllWorkspaceAction, EnableAllWorkspaceAction.ID, EnableAllWorkspaceAction.LABEL, false)); - } - }); - - registerAction2(class extends Action2 { - constructor() { - super({ - id: CheckForUpdatesAction.ID, - title: { value: CheckForUpdatesAction.LABEL, original: 'Check for Extension Updates' }, - category: ExtensionsLocalizedLabel, - menu: { - id: MenuId.CommandPalette, - when: ContextKeyAndExpr.create([CONTEXT_HAS_GALLERY, ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER])]) - } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL)); - } - }); - - registerAction2(class extends Action2 { - constructor() { - super({ - id: ClearExtensionsSearchResultsAction.ID, - title: { value: ClearExtensionsSearchResultsAction.LABEL, original: 'Clear Extensions Search Results' }, - category: ExtensionsLocalizedLabel, - menu: { - id: MenuId.CommandPalette, - } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(ClearExtensionsSearchResultsAction, ClearExtensionsSearchResultsAction.ID, ClearExtensionsSearchResultsAction.LABEL)); - } - }); - - registerAction2(class extends Action2 { - constructor() { - super({ - id: RefreshExtensionsAction.ID, - title: { value: RefreshExtensionsAction.LABEL, original: 'Refresh' }, - category: ExtensionsLocalizedLabel, - menu: { - id: MenuId.CommandPalette, - } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(RefreshExtensionsAction, RefreshExtensionsAction.ID, RefreshExtensionsAction.LABEL)); - } - }); - - registerAction2(class extends Action2 { - constructor() { - super({ - id: EnableAutoUpdateAction.ID, - title: { value: EnableAutoUpdateAction.LABEL, original: 'Enable Auto Updating Extensions' }, - category: ExtensionsLocalizedLabel, - menu: { - id: MenuId.CommandPalette, - } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL)); - } - }); - - registerAction2(class extends Action2 { - constructor() { - super({ - id: DisableAutoUpdateAction.ID, - title: { value: DisableAutoUpdateAction.LABEL, original: 'Disable Auto Updating Extensions' }, - category: ExtensionsLocalizedLabel, - menu: { - id: MenuId.CommandPalette, - } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL)); - } - }); - - registerAction2(class extends Action2 { - constructor() { - super({ - id: InstallSpecificVersionOfExtensionAction.ID, - title: { value: InstallSpecificVersionOfExtensionAction.LABEL, original: 'Install Specific Version of Extension...' }, - category: ExtensionsLocalizedLabel, - menu: { - id: MenuId.CommandPalette, - when: ContextKeyAndExpr.create([CONTEXT_HAS_GALLERY, ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER])]) - } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(InstallSpecificVersionOfExtensionAction, InstallSpecificVersionOfExtensionAction.ID, InstallSpecificVersionOfExtensionAction.LABEL)); - } - }); - - registerAction2(class extends Action2 { - constructor() { - super({ - id: ReinstallAction.ID, - title: { value: ReinstallAction.LABEL, original: 'Reinstall Extension...' }, - category: CATEGORIES.Developer, - menu: { - id: MenuId.CommandPalette, - when: ContextKeyAndExpr.create([CONTEXT_HAS_GALLERY, ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER])]) - } - }); - } - run(accessor: ServicesAccessor) { - return runAction(accessor.get(IInstantiationService).createInstance(ReinstallAction, ReinstallAction.ID, ReinstallAction.LABEL)); - } + this.registerExtensionAction({ + id: ReinstallAction.ID, + title: { value: ReinstallAction.LABEL, original: 'Reinstall Extension...' }, + category: CATEGORIES.Developer, + menu: { + id: MenuId.CommandPalette, + when: ContextKeyAndExpr.create([CONTEXT_HAS_GALLERY, ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER])]) + }, + run: () => runAction(this.instantiationService.createInstance(ReinstallAction, ReinstallAction.ID, ReinstallAction.LABEL)) }); } // Extension Context Menu private registerContextMenuActions(): void { - registerAction2(class extends Action2 { - - constructor() { - super({ - id: 'workbench.extensions.action.copyExtension', - title: { value: localize('workbench.extensions.action.copyExtension', "Copy"), original: 'Copy' }, - menu: { - id: MenuId.ExtensionContext, - group: '1_copy' - } - }); - } - - async run(accessor: ServicesAccessor, extensionId: string) { - const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); - let extension = extensionWorkbenchService.local.filter(e => areSameExtensions(e.identifier, { id: extensionId }))[0] - || (await extensionWorkbenchService.queryGallery({ names: [extensionId], pageSize: 1 }, CancellationToken.None)).firstPage[0]; + this.registerExtensionAction({ + id: 'workbench.extensions.action.copyExtension', + title: { value: localize('workbench.extensions.action.copyExtension', "Copy"), original: 'Copy' }, + menu: { + id: MenuId.ExtensionContext, + group: '1_copy' + }, + run: async (accessor: ServicesAccessor, extensionId: string) => { + const clipboardService = accessor.get(IClipboardService); + let extension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, { id: extensionId }))[0] + || (await this.extensionsWorkbenchService.queryGallery({ names: [extensionId], pageSize: 1 }, CancellationToken.None)).firstPage[0]; if (extension) { const name = localize('extensionInfoName', 'Name: {0}', extension.displayName); const id = localize('extensionInfoId', 'Id: {0}', extensionId); @@ -886,165 +1040,104 @@ class ExtensionsContributions implements IWorkbenchContribution { const publisher = localize('extensionInfoPublisher', 'Publisher: {0}', extension.publisherDisplayName); const link = extension.url ? localize('extensionInfoVSMarketplaceLink', 'VS Marketplace Link: {0}', `${extension.url}`) : null; const clipboardStr = `${name}\n${id}\n${description}\n${verision}\n${publisher}${link ? '\n' + link : ''}`; - await accessor.get(IClipboardService).writeText(clipboardStr); + await clipboardService.writeText(clipboardStr); } } }); - registerAction2(class extends Action2 { - - constructor() { - super({ - id: 'workbench.extensions.action.copyExtensionId', - title: { value: localize('workbench.extensions.action.copyExtensionId', "Copy Extension Id"), original: 'Copy Extension Id' }, - menu: { - id: MenuId.ExtensionContext, - group: '1_copy' - } - }); - } - - async run(accessor: ServicesAccessor, id: string) { - await accessor.get(IClipboardService).writeText(id); - } + this.registerExtensionAction({ + id: 'workbench.extensions.action.copyExtensionId', + title: { value: localize('workbench.extensions.action.copyExtensionId', "Copy Extension Id"), original: 'Copy Extension Id' }, + menu: { + id: MenuId.ExtensionContext, + group: '1_copy' + }, + run: async (accessor: ServicesAccessor, id: string) => accessor.get(IClipboardService).writeText(id) }); - registerAction2(class extends Action2 { - - constructor() { - super({ - id: 'workbench.extensions.action.configure', - title: { value: localize('workbench.extensions.action.configure', "Extension Settings"), original: 'Extension Settings' }, - menu: { - id: MenuId.ExtensionContext, - group: '2_configure', - when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.has('extensionHasConfiguration')) - } - }); - } - - async run(accessor: ServicesAccessor, id: string) { - await accessor.get(IPreferencesService).openSettings(false, `@ext:${id}`); - } + this.registerExtensionAction({ + id: 'workbench.extensions.action.configure', + title: { value: localize('workbench.extensions.action.configure', "Extension Settings"), original: 'Extension Settings' }, + menu: { + id: MenuId.ExtensionContext, + group: '2_configure', + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.has('extensionHasConfiguration')) + }, + run: async (accessor: ServicesAccessor, id: string) => accessor.get(IPreferencesService).openSettings(false, `@ext:${id}`) }); - registerAction2(class extends Action2 { - - constructor() { - super({ - id: TOGGLE_IGNORE_EXTENSION_ACTION_ID, - title: { value: localize('workbench.extensions.action.toggleIgnoreExtension', "Sync This Extension"), original: `Sync This Extension` }, - menu: { - id: MenuId.ExtensionContext, - group: '2_configure', - when: ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, ContextKeyExpr.has('inExtensionEditor').negate()) - }, - }); - } - - async run(accessor: ServicesAccessor, id: string) { - const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); - const extension = extensionsWorkbenchService.local.find(e => areSameExtensions({ id }, e.identifier)); + this.registerExtensionAction({ + id: TOGGLE_IGNORE_EXTENSION_ACTION_ID, + title: { value: localize('workbench.extensions.action.toggleIgnoreExtension', "Sync This Extension"), original: `Sync This Extension` }, + menu: { + id: MenuId.ExtensionContext, + group: '2_configure', + when: ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, ContextKeyExpr.has('inExtensionEditor').negate()) + }, + run: async (accessor: ServicesAccessor, id: string) => { + const extension = this.extensionsWorkbenchService.local.find(e => areSameExtensions({ id }, e.identifier)); if (extension) { - return extensionsWorkbenchService.toggleExtensionIgnoredToSync(extension); + return this.extensionsWorkbenchService.toggleExtensionIgnoredToSync(extension); } } }); - registerAction2(class extends Action2 { - - constructor() { - super({ - id: 'workbench.extensions.action.ignoreRecommendation', - title: { value: localize('workbench.extensions.action.ignoreRecommendation', "Ignore Recommendation"), original: `Ignore Recommendation` }, - menu: { - id: MenuId.ExtensionContext, - group: '3_recommendations', - when: ContextKeyExpr.has('isExtensionRecommended'), - order: 1 - }, - }); - } - - async run(accessor: ServicesAccessor, id: string): Promise { - accessor.get(IExtensionIgnoredRecommendationsService).toggleGlobalIgnoredRecommendation(id, true); - } + this.registerExtensionAction({ + id: 'workbench.extensions.action.ignoreRecommendation', + title: { value: localize('workbench.extensions.action.ignoreRecommendation', "Ignore Recommendation"), original: `Ignore Recommendation` }, + menu: { + id: MenuId.ExtensionContext, + group: '3_recommendations', + when: ContextKeyExpr.has('isExtensionRecommended'), + order: 1 + }, + run: async (accessor: ServicesAccessor, id: string) => accessor.get(IExtensionIgnoredRecommendationsService).toggleGlobalIgnoredRecommendation(id, true) }); - registerAction2(class extends Action2 { - - constructor() { - super({ - id: 'workbench.extensions.action.undoIgnoredRecommendation', - title: { value: localize('workbench.extensions.action.undoIgnoredRecommendation', "Undo Ignored Recommendation"), original: `Undo Ignored Recommendation` }, - menu: { - id: MenuId.ExtensionContext, - group: '3_recommendations', - when: ContextKeyExpr.has('isUserIgnoredRecommendation'), - order: 1 - }, - }); - } - - async run(accessor: ServicesAccessor, id: string): Promise { - accessor.get(IExtensionIgnoredRecommendationsService).toggleGlobalIgnoredRecommendation(id, false); - } + this.registerExtensionAction({ + id: 'workbench.extensions.action.undoIgnoredRecommendation', + title: { value: localize('workbench.extensions.action.undoIgnoredRecommendation', "Undo Ignored Recommendation"), original: `Undo Ignored Recommendation` }, + menu: { + id: MenuId.ExtensionContext, + group: '3_recommendations', + when: ContextKeyExpr.has('isUserIgnoredRecommendation'), + order: 1 + }, + run: async (accessor: ServicesAccessor, id: string) => accessor.get(IExtensionIgnoredRecommendationsService).toggleGlobalIgnoredRecommendation(id, false) }); - registerAction2(class extends Action2 { - - constructor() { - super({ - id: 'workbench.extensions.action.addExtensionToWorkspaceRecommendations', - title: { value: localize('workbench.extensions.action.addExtensionToWorkspaceRecommendations', "Add to Workspace Recommendations"), original: `Add to Workspace Recommendations` }, - menu: { - id: MenuId.ExtensionContext, - group: '3_recommendations', - when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('empty'), ContextKeyExpr.has('isBuiltinExtension').negate(), ContextKeyExpr.has('isExtensionWorkspaceRecommended').negate(), ContextKeyExpr.has('isUserIgnoredRecommendation').negate()), - order: 2 - }, - }); - } - - run(accessor: ServicesAccessor, id: string): Promise { - return accessor.get(IWorkpsaceExtensionsConfigService).toggleRecommendation(id); - } + this.registerExtensionAction({ + id: 'workbench.extensions.action.addExtensionToWorkspaceRecommendations', + title: { value: localize('workbench.extensions.action.addExtensionToWorkspaceRecommendations', "Add to Workspace Recommendations"), original: `Add to Workspace Recommendations` }, + menu: { + id: MenuId.ExtensionContext, + group: '3_recommendations', + when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('empty'), ContextKeyExpr.has('isBuiltinExtension').negate(), ContextKeyExpr.has('isExtensionWorkspaceRecommended').negate(), ContextKeyExpr.has('isUserIgnoredRecommendation').negate()), + order: 2 + }, + run: (accessor: ServicesAccessor, id: string) => accessor.get(IWorkpsaceExtensionsConfigService).toggleRecommendation(id) }); - registerAction2(class extends Action2 { - - constructor() { - super({ - id: 'workbench.extensions.action.removeExtensionFromWorkspaceRecommendations', - title: { value: localize('workbench.extensions.action.removeExtensionFromWorkspaceRecommendations', "Remove from Workspace Recommendations"), original: `Remove from Workspace Recommendations` }, - menu: { - id: MenuId.ExtensionContext, - group: '3_recommendations', - when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('empty'), ContextKeyExpr.has('isBuiltinExtension').negate(), ContextKeyExpr.has('isExtensionWorkspaceRecommended')), - order: 2 - }, - }); - } - - run(accessor: ServicesAccessor, id: string): Promise { - return accessor.get(IWorkpsaceExtensionsConfigService).toggleRecommendation(id); - } + this.registerExtensionAction({ + id: 'workbench.extensions.action.removeExtensionFromWorkspaceRecommendations', + title: { value: localize('workbench.extensions.action.removeExtensionFromWorkspaceRecommendations', "Remove from Workspace Recommendations"), original: `Remove from Workspace Recommendations` }, + menu: { + id: MenuId.ExtensionContext, + group: '3_recommendations', + when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('empty'), ContextKeyExpr.has('isBuiltinExtension').negate(), ContextKeyExpr.has('isExtensionWorkspaceRecommended')), + order: 2 + }, + run: (accessor: ServicesAccessor, id: string) => accessor.get(IWorkpsaceExtensionsConfigService).toggleRecommendation(id) }); - registerAction2(class extends Action2 { - - constructor() { - super({ - id: 'workbench.extensions.action.addToWorkspaceRecommendations', - title: { value: localize('workbench.extensions.action.addToWorkspaceRecommendations', "Add Extension to Workspace Recommendations"), original: `Add Extension to Workspace Recommendations` }, - category: localize('extensions', "Extensions"), - menu: { - id: MenuId.CommandPalette, - when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace'), ContextKeyExpr.equals('resourceScheme', Schemas.extension)), - }, - }); - } - + this.registerExtensionAction({ + id: 'workbench.extensions.action.addToWorkspaceRecommendations', + title: { value: localize('workbench.extensions.action.addToWorkspaceRecommendations', "Add Extension to Workspace Recommendations"), original: `Add Extension to Workspace Recommendations` }, + category: localize('extensions', "Extensions"), + menu: { + id: MenuId.CommandPalette, + when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace'), ContextKeyExpr.equals('resourceScheme', Schemas.extension)), + }, async run(accessor: ServicesAccessor): Promise { const editorService = accessor.get(IEditorService); const workpsaceExtensionsConfigService = accessor.get(IWorkpsaceExtensionsConfigService); @@ -1060,39 +1153,25 @@ class ExtensionsContributions implements IWorkbenchContribution { } }); - registerAction2(class extends Action2 { - - constructor() { - super({ - id: 'workbench.extensions.action.addToWorkspaceFolderRecommendations', - title: { value: localize('workbench.extensions.action.addToWorkspaceFolderRecommendations', "Add Extension to Workspace Folder Recommendations"), original: `Add Extension to Workspace Folder Recommendations` }, - category: localize('extensions', "Extensions"), - menu: { - id: MenuId.CommandPalette, - when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('folder'), ContextKeyExpr.equals('resourceScheme', Schemas.extension)), - }, - }); - } - - async run(accessor: ServicesAccessor): Promise { - return accessor.get(ICommandService).executeCommand('workbench.extensions.action.addToWorkspaceRecommendations'); - } + this.registerExtensionAction({ + id: 'workbench.extensions.action.addToWorkspaceFolderRecommendations', + title: { value: localize('workbench.extensions.action.addToWorkspaceFolderRecommendations', "Add Extension to Workspace Folder Recommendations"), original: `Add Extension to Workspace Folder Recommendations` }, + category: localize('extensions', "Extensions"), + menu: { + id: MenuId.CommandPalette, + when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('folder'), ContextKeyExpr.equals('resourceScheme', Schemas.extension)), + }, + run: () => this.commandService.executeCommand('workbench.extensions.action.addToWorkspaceRecommendations') }); - registerAction2(class extends Action2 { - - constructor() { - super({ - id: 'workbench.extensions.action.addToWorkspaceIgnoredRecommendations', - title: { value: localize('workbench.extensions.action.addToWorkspaceIgnoredRecommendations', "Add Extension to Workspace Ignored Recommendations"), original: `Add Extension to Workspace Ignored Recommendations` }, - category: localize('extensions', "Extensions"), - menu: { - id: MenuId.CommandPalette, - when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace'), ContextKeyExpr.equals('resourceScheme', Schemas.extension)), - }, - }); - } - + this.registerExtensionAction({ + id: 'workbench.extensions.action.addToWorkspaceIgnoredRecommendations', + title: { value: localize('workbench.extensions.action.addToWorkspaceIgnoredRecommendations', "Add Extension to Workspace Ignored Recommendations"), original: `Add Extension to Workspace Ignored Recommendations` }, + category: localize('extensions', "Extensions"), + menu: { + id: MenuId.CommandPalette, + when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace'), ContextKeyExpr.equals('resourceScheme', Schemas.extension)), + }, async run(accessor: ServicesAccessor): Promise { const editorService = accessor.get(IEditorService); const workpsaceExtensionsConfigService = accessor.get(IWorkpsaceExtensionsConfigService); @@ -1108,63 +1187,65 @@ class ExtensionsContributions implements IWorkbenchContribution { } }); - registerAction2(class extends Action2 { - - constructor() { - super({ - id: 'workbench.extensions.action.addToWorkspaceFolderIgnoredRecommendations', - title: { value: localize('workbench.extensions.action.addToWorkspaceFolderIgnoredRecommendations', "Add Extension to Workspace Folder Ignored Recommendations"), original: `Add Extension to Workspace Folder Ignored Recommendations` }, - category: localize('extensions', "Extensions"), - menu: { - id: MenuId.CommandPalette, - when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('folder'), ContextKeyExpr.equals('resourceScheme', Schemas.extension)), - }, - }); - } - - run(accessor: ServicesAccessor): Promise { - return accessor.get(ICommandService).executeCommand('workbench.extensions.action.addToWorkspaceIgnoredRecommendations'); - } + this.registerExtensionAction({ + id: 'workbench.extensions.action.addToWorkspaceFolderIgnoredRecommendations', + title: { value: localize('workbench.extensions.action.addToWorkspaceFolderIgnoredRecommendations', "Add Extension to Workspace Folder Ignored Recommendations"), original: `Add Extension to Workspace Folder Ignored Recommendations` }, + category: localize('extensions', "Extensions"), + menu: { + id: MenuId.CommandPalette, + when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('folder'), ContextKeyExpr.equals('resourceScheme', Schemas.extension)), + }, + run: () => this.commandService.executeCommand('workbench.extensions.action.addToWorkspaceIgnoredRecommendations') }); - registerAction2(class extends Action2 { - - constructor() { - super({ - id: ConfigureWorkspaceRecommendedExtensionsAction.ID, - title: { value: ConfigureWorkspaceRecommendedExtensionsAction.LABEL, original: 'Configure Recommended Extensions (Workspace)' }, - category: localize('extensions', "Extensions"), - menu: { - id: MenuId.CommandPalette, - when: WorkbenchStateContext.isEqualTo('workspace'), - }, - }); - } - - run(accessor: ServicesAccessor): Promise { - return accessor.get(IInstantiationService).createInstance(ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceRecommendedExtensionsAction.ID, ConfigureWorkspaceRecommendedExtensionsAction.LABEL).run(); - } + this.registerExtensionAction({ + id: ConfigureWorkspaceRecommendedExtensionsAction.ID, + title: { value: ConfigureWorkspaceRecommendedExtensionsAction.LABEL, original: 'Configure Recommended Extensions (Workspace)' }, + category: localize('extensions', "Extensions"), + menu: { + id: MenuId.CommandPalette, + when: WorkbenchStateContext.isEqualTo('workspace'), + }, + run: () => runAction(this.instantiationService.createInstance(ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceRecommendedExtensionsAction.ID, ConfigureWorkspaceRecommendedExtensionsAction.LABEL)) }); - registerAction2(class extends Action2 { - - constructor() { - super({ - id: ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, - title: { value: ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL, original: 'Configure Recommended Extensions (Workspace Folder)' }, - category: localize('extensions', "Extensions"), - menu: { - id: MenuId.CommandPalette, - when: WorkbenchStateContext.notEqualsTo('empty'), - }, - }); - } - - run(accessor: ServicesAccessor): Promise { - return accessor.get(IInstantiationService).createInstance(ConfigureWorkspaceFolderRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL).run(); - } - }); } + + private registerExtensionAction(extensionActionOptions: IExtensionActionOptions): IDisposable { + const menus = extensionActionOptions.menu ? isArray(extensionActionOptions.menu) ? extensionActionOptions.menu : [extensionActionOptions.menu] : []; + let menusWithOutTitles: ({ id: MenuId } & Omit)[] = []; + const menusWithTitles: { id: MenuId, item: IMenuItem }[] = []; + if (extensionActionOptions.menuTitles) { + for (let index = 0; index < menus.length; index++) { + const menu = menus[index]; + const menuTitle = extensionActionOptions.menuTitles[menu.id.id]; + if (menuTitle) { + menusWithTitles.push({ id: menu.id, item: { ...menu, command: { id: extensionActionOptions.id, title: menuTitle } } }); + } else { + menusWithOutTitles.push(menu); + } + } + } else { + menusWithOutTitles = menus; + } + const disposables = new DisposableStore(); + disposables.add(registerAction2(class extends Action2 { + constructor() { + super({ + ...extensionActionOptions, + menu: menusWithOutTitles + }); + } + run(accessor: ServicesAccessor, ...args: any[]): Promise { + return extensionActionOptions.run(accessor, ...args); + } + })); + if (menusWithTitles.length) { + disposables.add(MenuRegistry.appendMenuItems(menusWithTitles)); + } + return disposables; + } + } const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); @@ -1175,7 +1256,6 @@ workbenchRegistry.registerWorkbenchContribution(KeymapExtensions, LifecyclePhase workbenchRegistry.registerWorkbenchContribution(ExtensionsViewletViewsContribution, LifecyclePhase.Starting); workbenchRegistry.registerWorkbenchContribution(ExtensionActivationProgress, LifecyclePhase.Eventually); workbenchRegistry.registerWorkbenchContribution(ExtensionDependencyChecker, LifecyclePhase.Eventually); -workbenchRegistry.registerWorkbenchContribution(RemoteExtensionsInstaller, LifecyclePhase.Eventually); // Running Extensions const actionRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 36dbf7c75..f12e706e8 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -12,7 +12,7 @@ import { Event } from 'vs/base/common/event'; import * as json from 'vs/base/common/json'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { dispose } from 'vs/base/common/lifecycle'; -import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, AutoUpdateConfigurationKey, IExtensionContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID } 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 } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; import { IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension, INSTALL_ERROR_NOT_SUPPORTED, InstallOptions, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensioManagementService, IWebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; @@ -20,9 +20,7 @@ import { ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService, import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { ShowViewletAction } from 'vs/workbench/browser/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; import { IFileService, IFileContent } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IHostService } from 'vs/workbench/services/host/browser/host'; @@ -40,12 +38,9 @@ import { MenuId, IMenuService } from 'vs/platform/actions/common/actions'; import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IQuickPickItem, IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { coalesce } from 'vs/base/common/arrays'; import { IWorkbenchThemeService, IWorkbenchTheme, IWorkbenchColorTheme, IWorkbenchFileIconTheme, IWorkbenchProductIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; @@ -53,9 +48,8 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { prefersExecuteOnUI, prefersExecuteOnWorkspace, canExecuteOnUI, canExecuteOnWorkspace, prefersExecuteOnWeb } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { IViewsService } from 'vs/workbench/common/views'; import { IActionViewItemOptions, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { EXTENSIONS_CONFIG, IExtensionsConfigContent } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig'; import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors'; @@ -64,7 +58,7 @@ import { ActionWithDropdownActionViewItem, IActionWithDropdownActionViewItemOpti import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; import { ILogService } from 'vs/platform/log/common/log'; import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; -import { clearSearchResultsIcon, infoIcon, manageExtensionIcon, refreshIcon, syncEnabledIcon, syncIgnoredIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; +import { infoIcon, manageExtensionIcon, syncEnabledIcon, syncIgnoredIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; function getRelativeDateLabel(date: Date): string { const delta = new Date().getTime() - date.getTime(); @@ -100,17 +94,18 @@ function getRelativeDateLabel(date: Date): string { return ''; } -class PromptExtensionInstallFailureAction extends Action { +export class PromptExtensionInstallFailureAction extends Action { constructor( private readonly extension: IExtension, + private readonly version: string, private readonly installOperation: InstallOperation, private readonly error: Error, @IProductService private readonly productService: IProductService, @IOpenerService private readonly openerService: IOpenerService, @INotificationService private readonly notificationService: INotificationService, @IDialogService private readonly dialogService: IDialogService, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @ICommandService private readonly commandService: ICommandService, @ILogService private readonly logService: ILogService, ) { super('extension.promptExtensionInstallFailure'); @@ -134,17 +129,13 @@ class PromptExtensionInstallFailureAction extends Action { if (this.extension.gallery && this.productService.extensionsGallery) { promptChoices.push({ label: localize('download', "Try Downloading Manually..."), - run: () => this.openerService.open(URI.parse(`${this.productService.extensionsGallery!.serviceUrl}/publishers/${this.extension.publisher}/vsextensions/${this.extension.name}/${this.extension.version}/vspackage`)).then(() => { + run: () => this.openerService.open(URI.parse(`${this.productService.extensionsGallery!.serviceUrl}/publishers/${this.extension.publisher}/vsextensions/${this.extension.name}/${this.version}/vspackage`)).then(() => { this.notificationService.prompt( Severity.Info, localize('install vsix', 'Once downloaded, please manually install the downloaded VSIX of \'{0}\'.', this.extension.identifier.id), [{ - label: InstallVSIXAction.LABEL, - run: () => { - const action = this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL); - action.run(); - action.dispose(); - } + label: localize('installVSIX', "Install from VSIX..."), + run: () => this.commandService.executeCommand(SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID) }] ); }) @@ -287,7 +278,7 @@ export abstract class AbstractInstallAction extends ExtensionAction { try { return await this.extensionsWorkbenchService.install(extension, this.getInstallOptions()); } catch (error) { - await this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, InstallOperation.Install, error).run(); + await this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, extension.latestVersion, InstallOperation.Install, error).run(); return undefined; } } @@ -487,7 +478,7 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { || !this.extension.local || this.extension.state !== ExtensionState.Installed || this.extension.type !== ExtensionType.User - || this.extension.enablementState === EnablementState.DisabledByEnvironemt + || this.extension.enablementState === EnablementState.DisabledByEnvironment ) { return false; } @@ -715,7 +706,7 @@ export class UpdateAction extends ExtensionAction { await this.extensionsWorkbenchService.install(extension); alert(localize('updateExtensionComplete', "Updating extension {0} to version {1} completed.", extension.displayName, extension.latestVersion)); } catch (err) { - this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, InstallOperation.Update, err).run(); + this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, extension.latestVersion, InstallOperation.Update, err).run(); } } @@ -775,7 +766,8 @@ export class ExtensionActionWithDropdownActionViewItem extends ActionWithDropdow updateClass(): void { super.updateClass(); - if (this.dropdownMenuActionViewItem && this.dropdownMenuActionViewItem.element) { + 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); } } @@ -1063,7 +1055,7 @@ export class InstallAnotherVersionAction extends ExtensionAction { await this.extensionsWorkbenchService.installVersion(this.extension!, pick.id); } } catch (error) { - this.instantiationService.createInstance(PromptExtensionInstallFailureAction, this.extension!, InstallOperation.Install, error).run(); + this.instantiationService.createInstance(PromptExtensionInstallFailureAction, this.extension!, pick.latest ? this.extension!.latestVersion : pick.id, InstallOperation.Install, error).run(); } } return null; @@ -1246,140 +1238,6 @@ export class DisableDropDownAction extends ActionWithDropDownAction { } -export class CheckForUpdatesAction extends Action { - - static readonly ID = 'workbench.extensions.action.checkForUpdates'; - static readonly LABEL = localize('checkForUpdates', "Check for Extension Updates"); - - constructor( - id = CheckForUpdatesAction.ID, - label = CheckForUpdatesAction.LABEL, - @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, - @IViewletService private readonly viewletService: IViewletService, - @INotificationService private readonly notificationService: INotificationService - ) { - super(id, label, '', true); - } - - private checkUpdatesAndNotify(): void { - const outdated = this.extensionsWorkbenchService.outdated; - if (!outdated.length) { - this.notificationService.info(localize('noUpdatesAvailable', "All extensions are up to date.")); - return; - } - - let msgAvailableExtensions = outdated.length === 1 ? localize('singleUpdateAvailable', "An extension update is available.") : localize('updatesAvailable', "{0} extension updates are available.", outdated.length); - - const disabledExtensionsCount = outdated.filter(ext => ext.local && !this.extensionEnablementService.isEnabled(ext.local)).length; - if (disabledExtensionsCount) { - if (outdated.length === 1) { - msgAvailableExtensions = localize('singleDisabledUpdateAvailable', "An update to an extension which is disabled is available."); - } else if (disabledExtensionsCount === 1) { - msgAvailableExtensions = localize('updatesAvailableOneDisabled', "{0} extension updates are available. One of them is for a disabled extension.", outdated.length); - } else if (disabledExtensionsCount === outdated.length) { - msgAvailableExtensions = localize('updatesAvailableAllDisabled', "{0} extension updates are available. All of them are for disabled extensions.", outdated.length); - } else { - msgAvailableExtensions = localize('updatesAvailableIncludingDisabled', "{0} extension updates are available. {1} of them are for disabled extensions.", outdated.length, disabledExtensionsCount); - } - } - - this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => viewlet.search('')); - - this.notificationService.info(msgAvailableExtensions); - } - - run(): Promise { - return this.extensionsWorkbenchService.checkForUpdates().then(() => this.checkUpdatesAndNotify()); - } -} - -export class ToggleAutoUpdateAction extends Action { - - constructor( - id: string, - label: string, - private autoUpdateValue: boolean, - @IConfigurationService private readonly configurationService: IConfigurationService - ) { - super(id, label, '', true); - this.updateEnablement(); - configurationService.onDidChangeConfiguration(() => this.updateEnablement()); - } - - private updateEnablement(): void { - this.enabled = this.configurationService.getValue(AutoUpdateConfigurationKey) !== this.autoUpdateValue; - } - - run(): Promise { - return this.configurationService.updateValue(AutoUpdateConfigurationKey, this.autoUpdateValue); - } -} - -export class EnableAutoUpdateAction extends ToggleAutoUpdateAction { - - static readonly ID = 'workbench.extensions.action.enableAutoUpdate'; - static readonly LABEL = localize('enableAutoUpdate', "Enable Auto Updating Extensions"); - - constructor( - id = EnableAutoUpdateAction.ID, - label = EnableAutoUpdateAction.LABEL, - @IConfigurationService configurationService: IConfigurationService - ) { - super(id, label, true, configurationService); - } -} - -export class DisableAutoUpdateAction extends ToggleAutoUpdateAction { - - static readonly ID = 'workbench.extensions.action.disableAutoUpdate'; - static readonly LABEL = localize('disableAutoUpdate', "Disable Auto Updating Extensions"); - - constructor( - id = EnableAutoUpdateAction.ID, - label = EnableAutoUpdateAction.LABEL, - @IConfigurationService configurationService: IConfigurationService - ) { - super(id, label, false, configurationService); - } -} - -export class UpdateAllAction extends Action { - - static readonly ID = 'workbench.extensions.action.updateAllExtensions'; - static readonly LABEL = localize('updateAll', "Update All Extensions"); - - constructor( - id: string, label: string, isPrimary: boolean, - @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - ) { - super(id, label, '', false); - - if (isPrimary) { - this._register(this.extensionsWorkbenchService.onChange(() => this._onDidChange.fire({ enabled: this.enabled }))); - } - } - - get enabled(): boolean { - return this.extensionsWorkbenchService.outdated.length > 0; - } - - run(): Promise { - return Promise.all(this.extensionsWorkbenchService.outdated.map(e => this.install(e))); - } - - private async install(extension: IExtension): Promise { - try { - await this.extensionsWorkbenchService.install(extension); - } catch (err) { - this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, InstallOperation.Update, err).run(); - } - } -} - export class ReloadAction extends ExtensionAction { private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} reload`; @@ -1722,296 +1580,6 @@ export class SetProductIconThemeAction extends ExtensionAction { } } -export class OpenExtensionsViewletAction extends ShowViewletAction { - - static ID = VIEWLET_ID; - static LABEL = localize('toggleExtensionsViewlet', "Show Extensions"); - - constructor( - id: string, - label: string, - @IViewletService viewletService: IViewletService, - @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService - ) { - super(id, label, VIEWLET_ID, viewletService, editorGroupService, layoutService); - } -} - -export class InstallExtensionsAction extends OpenExtensionsViewletAction { - static ID = 'workbench.extensions.action.installExtensions'; - static LABEL = localize('installExtensions', "Install Extensions"); -} - -export class ShowEnabledExtensionsAction extends Action { - - static readonly ID = 'workbench.extensions.action.showEnabledExtensions'; - static readonly LABEL = localize('showEnabledExtensions', "Show Enabled Extensions"); - - constructor( - id: string, - label: string, - @IViewletService private readonly viewletService: IViewletService - ) { - super(id, label, undefined, true); - } - - run(): Promise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => { - viewlet.search('@enabled '); - viewlet.focus(); - }); - } -} - -export class ShowInstalledExtensionsAction extends Action { - - static readonly ID = 'workbench.extensions.action.showInstalledExtensions'; - static readonly LABEL = localize('showInstalledExtensions', "Show Installed Extensions"); - - constructor( - id: string, - label: string, - @IViewletService private readonly viewletService: IViewletService - ) { - super(id, label, undefined, true); - } - - run(): Promise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => { - viewlet.search('@installed '); - viewlet.focus(); - }); - } -} - -export class ShowDisabledExtensionsAction extends Action { - - static readonly ID = 'workbench.extensions.action.showDisabledExtensions'; - static readonly LABEL = localize('showDisabledExtensions', "Show Disabled Extensions"); - - constructor( - id: string, - label: string, - @IViewletService private readonly viewletService: IViewletService - ) { - super(id, label, 'null', true); - } - - run(): Promise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => { - viewlet.search('@disabled '); - viewlet.focus(); - }); - } -} - -export class ClearExtensionsSearchResultsAction extends Action { - - static readonly ID = 'workbench.extensions.action.clearExtensionsSearchResults'; - static readonly LABEL = localize('clearExtensionsSearchResults', "Clear Extensions Search Results"); - - constructor( - id: string, - label: string, - @IViewsService private readonly viewsService: IViewsService - ) { - super(id, label, ThemeIcon.asClassName(clearSearchResultsIcon), true); - } - - async run(): Promise { - const viewPaneContainer = this.viewsService.getActiveViewPaneContainerWithId(VIEWLET_ID); - if (viewPaneContainer) { - const extensionsViewPaneContainer = viewPaneContainer as IExtensionsViewPaneContainer; - extensionsViewPaneContainer.search(''); - extensionsViewPaneContainer.focus(); - } - } -} - -export class ClearExtensionsInputAction extends ClearExtensionsSearchResultsAction { - - constructor( - id: string, - label: string, - onSearchChange: Event, - getValue: () => string, - @IViewsService viewsService: IViewsService - ) { - super(id, label, viewsService); - this.onSearchChange(getValue()); - this._register(onSearchChange(this.onSearchChange, this)); - } - - private onSearchChange(value: string): void { - this.enabled = !!value; - } -} - -export class RefreshExtensionsAction extends Action { - - static readonly ID = 'workbench.extensions.action.refreshExtension'; - static readonly LABEL = localize('refreshExtension', "Refresh"); - - constructor( - id: string, - label: string, - @IViewsService private readonly viewsService: IViewsService - ) { - super(id, label, ThemeIcon.asClassName(refreshIcon), true); - } - - async run(): Promise { - const viewPaneContainer = this.viewsService.getActiveViewPaneContainerWithId(VIEWLET_ID); - if (viewPaneContainer) { - const extensionsViewPaneContainer = viewPaneContainer as IExtensionsViewPaneContainer; - return extensionsViewPaneContainer.refresh(); - } - } -} - -export class ShowBuiltInExtensionsAction extends Action { - - static readonly ID = 'workbench.extensions.action.listBuiltInExtensions'; - static readonly LABEL = localize('showBuiltInExtensions', "Show Built-in Extensions"); - - constructor( - id: string, - label: string, - @IViewletService private readonly viewletService: IViewletService - ) { - super(id, label, undefined, true); - } - - run(): Promise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => { - viewlet.search('@builtin '); - viewlet.focus(); - }); - } -} - -export class ShowOutdatedExtensionsAction extends Action { - - static readonly ID = 'workbench.extensions.action.listOutdatedExtensions'; - static readonly LABEL = localize('showOutdatedExtensions', "Show Outdated Extensions"); - - constructor( - id: string, - label: string, - @IViewletService private readonly viewletService: IViewletService - ) { - super(id, label, undefined, true); - } - - run(): Promise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => { - viewlet.search('@outdated '); - viewlet.focus(); - }); - } -} - -export class ShowPopularExtensionsAction extends Action { - - static readonly ID = 'workbench.extensions.action.showPopularExtensions'; - static readonly LABEL = localize('showPopularExtensions', "Show Popular Extensions"); - - constructor( - id: string, - label: string, - @IViewletService private readonly viewletService: IViewletService - ) { - super(id, label, undefined, true); - } - - run(): Promise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => { - viewlet.search('@popular '); - viewlet.focus(); - }); - } -} - -export class PredefinedExtensionFilterAction extends Action { - - constructor( - id: string, - label: string, - private readonly filter: string, - @IViewletService private readonly viewletService: IViewletService - ) { - super(id, label, undefined, true); - } - - run(): Promise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => { - viewlet.search(`${this.filter} `); - viewlet.focus(); - }); - } -} - -export class RecentlyPublishedExtensionsAction extends Action { - - static readonly ID = 'workbench.extensions.action.recentlyPublishedExtensions'; - static readonly LABEL = localize('recentlyPublishedExtensions', "Recently Published Extensions"); - - constructor( - id: string, - label: string, - @IViewletService private readonly viewletService: IViewletService - ) { - super(id, label, undefined, true); - } - - run(): Promise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => { - viewlet.search('@sort:publishedDate '); - viewlet.focus(); - }); - } -} - -export class ShowRecommendedExtensionsAction extends Action { - - static readonly ID = 'workbench.extensions.action.showRecommendedExtensions'; - static readonly LABEL = localize('showRecommendedExtensions', "Show Recommended Extensions"); - - constructor( - id: string, - label: string, - @IViewletService private readonly viewletService: IViewletService - ) { - super(id, label, undefined, true); - } - - run(): Promise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => { - viewlet.search('@recommended '); - viewlet.focus(); - }); - } -} - export class ShowRecommendedExtensionAction extends Action { static readonly ID = 'workbench.extensions.action.showRecommendedExtension'; @@ -2075,7 +1643,7 @@ export class InstallRecommendedExtensionAction extends Action { try { await this.extensionWorkbenchService.install(extension); } catch (err) { - this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, InstallOperation.Install, err).run(); + this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, extension.latestVersion, InstallOperation.Install, err).run(); } } } @@ -2127,68 +1695,6 @@ export class UndoIgnoreExtensionRecommendationAction extends Action { } } -export class ShowRecommendedKeymapExtensionsAction extends Action { - - static readonly ID = 'workbench.extensions.action.showRecommendedKeymapExtensions'; - static readonly LABEL = localize('showRecommendedKeymapExtensionsShort', "Keymaps"); - - constructor( - id: string, - label: string, - @IViewletService private readonly viewletService: IViewletService - ) { - super(id, label, undefined, true); - } - - run(): Promise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => { - viewlet.search('@recommended:keymaps '); - viewlet.focus(); - }); - } -} - -export class ShowLanguageExtensionsAction extends Action { - - static readonly ID = 'workbench.extensions.action.showLanguageExtensions'; - static readonly LABEL = localize('showLanguageExtensionsShort', "Language Extensions"); - - constructor( - id: string, - label: string, - @IViewletService private readonly viewletService: IViewletService - ) { - super(id, label, undefined, true); - } - - run(): Promise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => { - viewlet.search('@category:"programming languages" @sort:installs '); - viewlet.focus(); - }); - } -} - -export class SearchCategoryAction extends Action { - - constructor( - id: string, - label: string, - private readonly category: string, - @IViewletService private readonly viewletService: IViewletService - ) { - super(id, label, undefined, true); - } - - run(): Promise { - return new SearchExtensionsAction(`@category:"${this.category.toLowerCase()}"`, this.viewletService).run(); - } -} - export class SearchExtensionsAction extends Action { constructor( @@ -2205,46 +1711,6 @@ export class SearchExtensionsAction extends Action { } } -export class ChangeSortAction extends Action { - - private query: Query; - - constructor( - id: string, - label: string, - onSearchChange: Event, - private sortBy: string, - @IViewletService private readonly viewletService: IViewletService - ) { - super(id, label, undefined, true); - - if (sortBy === undefined) { - throw new Error('bad arguments'); - } - - this.query = Query.parse(''); - this.enabled = false; - this.checked = false; - this._register(onSearchChange(this.onSearchChange, this)); - } - - private onSearchChange(value: string): void { - const query = Query.parse(value); - this.query = new Query(query.value, this.sortBy || query.sortBy, query.groupBy); - this.enabled = !!value && this.query.isValid(); - this.checked = this.enabled && this.query.equals(query); - } - - run(): Promise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => { - viewlet.search(this.query.toString()); - viewlet.focus(); - }); - } -} - export abstract class AbstractConfigureRecommendedExtensionsAction extends Action { constructor( @@ -2383,12 +1849,6 @@ export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends Abstrac @ICommandService private readonly commandService: ICommandService ) { super(id, label, contextService, fileService, textFileService, editorService, jsonEditingService, textModelResolverService); - this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.update(), this)); - this.update(); - } - - private update(): void { - this.enabled = this.contextService.getWorkspace().folders.length > 0; } public run(): Promise { @@ -2735,156 +2195,6 @@ export class SystemDisabledWarningAction extends ExtensionAction { } } -export class DisableAllAction extends Action { - - static readonly ID = 'workbench.extensions.action.disableAll'; - static readonly LABEL = localize('disableAll', "Disable All Installed Extensions"); - - constructor( - id: string, label: string, isPrimary: boolean, - @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService - ) { - super(id, label); - if (isPrimary) { - this._register(this.extensionsWorkbenchService.onChange(() => this._onDidChange.fire({ enabled: this.enabled }))); - } - } - - private getExtensionsToDisable(): IExtension[] { - return this.extensionsWorkbenchService.local.filter(e => !e.isBuiltin && !!e.local && this.extensionEnablementService.isEnabled(e.local) && this.extensionEnablementService.canChangeEnablement(e.local)); - } - - get enabled(): boolean { - return this.getExtensionsToDisable().length > 0; - } - - run(): Promise { - return this.extensionsWorkbenchService.setEnablement(this.getExtensionsToDisable(), EnablementState.DisabledGlobally); - } -} - -export class DisableAllWorkspaceAction extends Action { - - static readonly ID = 'workbench.extensions.action.disableAllWorkspace'; - static readonly LABEL = localize('disableAllWorkspace', "Disable All Installed Extensions for this Workspace"); - - constructor( - id: string, label: string, isPrimary: boolean, - @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService - ) { - super(id, label); - if (isPrimary) { - this._register(Event.any(this.workspaceContextService.onDidChangeWorkbenchState, this.extensionsWorkbenchService.onChange)(() => this._onDidChange.fire({ enabled: this.enabled }))); - } - } - - private getExtensionsToDisable(): IExtension[] { - return this.extensionsWorkbenchService.local.filter(e => !e.isBuiltin && !!e.local && this.extensionEnablementService.isEnabled(e.local) && this.extensionEnablementService.canChangeEnablement(e.local)); - } - - get enabled(): boolean { - return this.getExtensionsToDisable().length > 0; - } - - run(): Promise { - return this.extensionsWorkbenchService.setEnablement(this.getExtensionsToDisable(), EnablementState.DisabledWorkspace); - } -} - -export class EnableAllAction extends Action { - - static readonly ID = 'workbench.extensions.action.enableAll'; - static readonly LABEL = localize('enableAll', "Enable All Extensions"); - - constructor( - id: string, label: string, isPrimary: boolean, - @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService - ) { - super(id, label); - if (isPrimary) { - this._register(this.extensionsWorkbenchService.onChange(() => this._onDidChange.fire({ enabled: this.enabled }))); - } - } - - private getExtensionsToEnable(): IExtension[] { - return this.extensionsWorkbenchService.local.filter(e => !!e.local && this.extensionEnablementService.canChangeEnablement(e.local) && !this.extensionEnablementService.isEnabled(e.local)); - } - - get enabled(): boolean { - return this.getExtensionsToEnable().length > 0; - } - - run(): Promise { - return this.extensionsWorkbenchService.setEnablement(this.getExtensionsToEnable(), EnablementState.EnabledGlobally); - } -} - -export class EnableAllWorkspaceAction extends Action { - - static readonly ID = 'workbench.extensions.action.enableAllWorkspace'; - static readonly LABEL = localize('enableAllWorkspace', "Enable All Extensions for this Workspace"); - - constructor( - id: string, label: string, isPrimary: boolean, - @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService - ) { - super(id, label); - if (isPrimary) { - this._register(Event.any(this.workspaceContextService.onDidChangeWorkbenchState, this.extensionsWorkbenchService.onChange)(() => this._onDidChange.fire({ enabled: this.enabled }))); - } - } - - private getExtensionsToEnable(): IExtension[] { - return this.extensionsWorkbenchService.local.filter(e => !!e.local && this.extensionEnablementService.canChangeEnablement(e.local) && !this.extensionEnablementService.isEnabled(e.local)); - } - - get enabled(): boolean { - return this.getExtensionsToEnable().length > 0; - } - - run(): Promise { - return this.extensionsWorkbenchService.setEnablement(this.getExtensionsToEnable(), EnablementState.EnabledWorkspace); - } -} - -export class InstallVSIXAction extends Action { - - static readonly ID = 'workbench.extensions.action.installVSIX'; - static readonly LABEL = localize('installVSIX', "Install from VSIX..."); - - constructor( - id = InstallVSIXAction.ID, - label = InstallVSIXAction.LABEL, - @IFileDialogService private readonly fileDialogService: IFileDialogService, - @ICommandService private readonly commandService: ICommandService - ) { - super(id, label, 'extension-action install-vsix', true); - } - - async run(): Promise { - const vsixPaths = await this.fileDialogService.showOpenDialog({ - title: localize('installFromVSIX', "Install from VSIX"), - filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }], - canSelectFiles: true, - canSelectMany: true, - openLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install")) - }); - - if (!vsixPaths) { - return; - } - - // Install extension(s), display notification(s), display @installed extensions - await this.commandService.executeCommand(INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, vsixPaths); - } -} - export class ReinstallAction extends Action { static readonly ID = 'workbench.extensions.action.reinstall'; @@ -2929,7 +2239,7 @@ export class ReinstallAction extends Action { } private reinstallExtension(extension: IExtension): Promise { - return this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL).run() + return this.instantiationService.createInstance(SearchExtensionsAction, '@installed ').run() .then(() => { return this.extensionsWorkbenchService.reinstall(extension) .then(extension => { @@ -3015,7 +2325,7 @@ export class InstallSpecificVersionOfExtensionAction extends Action { } private install(extension: IExtension, version: string): Promise { - return this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL).run() + return this.instantiationService.createInstance(SearchExtensionsAction, '@installed ').run() .then(() => { return this.extensionsWorkbenchService.installVersion(extension, version) .then(extension => { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index afe9ebc8a..9858def3b 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -9,22 +9,17 @@ import { timeout, Delayer } from 'vs/base/common/async'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { Event, Emitter } from 'vs/base/common/event'; -import { IAction, Action, Separator, SubmenuAction } from 'vs/base/common/actions'; +import { Event } from 'vs/base/common/event'; +import { Action } from 'vs/base/common/actions'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { append, $, Dimension, hide, show } from 'vs/base/browser/dom'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, AutoUpdateConfigurationKey, CloseExtensionDetailsOnViewChangeKey, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID } from '../common/extensions'; -import { - ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, CheckForUpdatesAction, DisableAllAction, EnableAllAction, - EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction, SearchCategoryAction, - RecentlyPublishedExtensionsAction, ShowInstalledExtensionsAction, ShowOutdatedExtensionsAction, ShowDisabledExtensionsAction, - ShowEnabledExtensionsAction, PredefinedExtensionFilterAction, RefreshExtensionsAction -} from 'vs/workbench/contrib/extensions/browser/extensionsActions'; -import { IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, CloseExtensionDetailsOnViewChangeKey, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, DefaultViewsContext, ExtensionsSortByContext, WORKSPACE_RECOMMENDATIONS_VIEW_ID } 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'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInFeatureExtensionsView, BuiltInThemesExtensionsView, BuiltInProgrammingLanguageExtensionsView, ServerInstalledExtensionsView, DefaultRecommendedExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; @@ -32,24 +27,25 @@ import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import Severity from 'vs/base/common/severity'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; -import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewDescriptorService, IAddedViewDescriptorRef } from 'vs/workbench/common/views'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { ViewPane, ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; import { SuggestEnabledInput, attachSuggestEnabledInputBoxStyler } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; -import { ExtensionType, EXTENSION_CATEGORIES } from 'vs/platform/extensions/common/extensions'; +import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; import { ILabelService } from 'vs/platform/label/common/label'; import { MementoObject } from 'vs/workbench/common/memento'; @@ -62,10 +58,9 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { isWeb } from 'vs/base/common/platform'; -import { memoize } from 'vs/base/common/decorators'; -import { filterIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; +import { installLocalInRemoteIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; +import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; -const DefaultViewsContext = new RawContextKey('defaultExtensionViews', true); const SearchMarketplaceExtensionsContext = new RawContextKey('searchMarketplaceExtensions', false); const SearchIntalledExtensionsContext = new RawContextKey('searchInstalledExtensions', false); const SearchOutdatedExtensionsContext = new RawContextKey('searchOutdatedExtensions', false); @@ -155,7 +150,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio get name() { return getInstalledViewName(); }, weight: 100, order: 1, - when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.not('hasInstalledExtensions')), + when: ContextKeyExpr.and(DefaultViewsContext, ContextKeyExpr.not('hasInstalledExtensions')), /* Empty installed extensions view shall have fixed height */ ctorDescriptor: new SyncDescriptor(ServerInstalledExtensionsView, [{ server, fixedHeight: true, onDidChangeTitle }]), /* Empty installed extensions views shall not be allowed to hidden */ @@ -168,11 +163,49 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio get name() { return getInstalledViewName(); }, weight: 100, order: 1, - when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), isWebServer ? ContextKeyExpr.has('hasInstalledWebExtensions') : ContextKeyExpr.has('hasInstalledExtensions')), + when: ContextKeyExpr.and(DefaultViewsContext, isWebServer ? ContextKeyExpr.has('hasInstalledWebExtensions') : ContextKeyExpr.has('hasInstalledExtensions')), ctorDescriptor: new SyncDescriptor(ServerInstalledExtensionsView, [{ server, onDidChangeTitle }]), /* Installed extensions views shall not be allowed to hidden when there are more than one server */ canToggleVisibility: servers.length === 1 }); + + if (server === this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManagementServerService.localExtensionManagementServer) { + registerAction2(class InstallLocalExtensionsInRemoteAction2 extends Action2 { + constructor() { + super({ + id: 'workbench.extensions.installLocalExtensions', + get title() { return localize('select and install local extensions', "Install Local Extensions in '{0}'...", server.label); }, + category: localize({ key: 'remote', comment: ['Remote as in remote machine'] }, "Remote"), + icon: installLocalInRemoteIcon, + f1: true, + menu: { + id: MenuId.ViewTitle, + when: ContextKeyEqualsExpr.create('view', id), + group: 'navigation', + } + }); + } + run(accessor: ServicesAccessor): Promise { + return accessor.get(IInstantiationService).createInstance(InstallLocalExtensionsInRemoteAction).run(); + } + }); + } + } + + if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { + registerAction2(class InstallRemoteExtensionsInLocalAction2 extends Action2 { + constructor() { + super({ + id: 'workbench.extensions.actions.installLocalExtensionsInRemote', + title: { value: localize('install remote in local', "Install Remote Extensions Locally..."), original: 'Install Remote Extensions Locally...' }, + category: localize({ key: 'remote', comment: ['Remote as in remote machine'] }, "Remote"), + f1: true + }); + } + run(accessor: ServicesAccessor): Promise { + return accessor.get(IInstantiationService).createInstance(InstallRemoteExtensionsInLocalAction, 'workbench.extensions.actions.installLocalExtensionsInRemote').run(); + } + }); } /* @@ -184,7 +217,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio id: 'workbench.views.extensions.popular', name: localize('popularExtensions', "Popular"), ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]), - when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.not('hasInstalledExtensions')), + when: ContextKeyExpr.and(DefaultViewsContext, ContextKeyExpr.not('hasInstalledExtensions')), weight: 60, order: 2, canToggleVisibility: false @@ -199,7 +232,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio id: 'extensions.recommendedList', name: localize('recommendedExtensions', "Recommended"), ctorDescriptor: new SyncDescriptor(DefaultRecommendedExtensionsView, [{}]), - when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.not('config.extensions.showRecommendationsOnlyOnDemand')), + when: ContextKeyExpr.and(DefaultViewsContext, ContextKeyExpr.not('config.extensions.showRecommendationsOnlyOnDemand')), weight: 40, order: 3, canToggleVisibility: true @@ -215,7 +248,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio id: 'workbench.views.extensions.enabled', name: localize('enabledExtensions', "Enabled"), ctorDescriptor: new SyncDescriptor(EnabledExtensionsView, [{}]), - when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions')), + when: ContextKeyExpr.and(DefaultViewsContext, ContextKeyExpr.has('hasInstalledExtensions')), hideByDefault: true, weight: 40, order: 4, @@ -230,7 +263,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio id: 'workbench.views.extensions.disabled', name: localize('disabledExtensions', "Disabled"), ctorDescriptor: new SyncDescriptor(DisabledExtensionsView, [{}]), - when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions')), + when: ContextKeyExpr.and(DefaultViewsContext, ContextKeyExpr.has('hasInstalledExtensions')), hideByDefault: true, weight: 10, order: 5, @@ -312,7 +345,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio const viewDescriptors: IViewDescriptor[] = []; viewDescriptors.push({ - id: 'workbench.views.extensions.workspaceRecommendations', + id: WORKSPACE_RECOMMENDATIONS_VIEW_ID, name: localize('workspaceRecommendedExtensions', "Workspace Recommendations"), ctorDescriptor: new SyncDescriptor(WorkspaceRecommendedExtensionsView, [{}]), when: ContextKeyExpr.and(ContextKeyExpr.has('recommendedExtensions'), WorkbenchStateContext.notEqualsTo('empty')), @@ -361,9 +394,8 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IExtensionsViewPaneContainer { - private readonly _onSearchChange: Emitter = this._register(new Emitter()); - private readonly onSearchChange: Event = this._onSearchChange.event; private defaultViewsContextKey: IContextKey; + private sortByContextKey: IContextKey; private searchMarketplaceExtensionsContextKey: IContextKey; private searchInstalledExtensionsContextKey: IContextKey; private searchOutdatedExtensionsContextKey: IContextKey; @@ -379,8 +411,6 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE private root: HTMLElement | undefined; private searchBox: SuggestEnabledInput | undefined; private readonly searchViewletState: MementoObject; - private readonly sortActions: ChangeSortAction[]; - private secondaryActions: IAction[] | undefined = undefined; constructor( @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @@ -390,7 +420,6 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, - @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @INotificationService private readonly notificationService: INotificationService, @IViewletService private readonly viewletService: IViewletService, @IThemeService themeService: IThemeService, @@ -408,6 +437,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE this.searchDelayer = new Delayer(500); this.defaultViewsContextKey = DefaultViewsContext.bindTo(contextKeyService); + this.sortByContextKey = ExtensionsSortByContext.bindTo(contextKeyService); this.searchMarketplaceExtensionsContextKey = SearchMarketplaceExtensionsContext.bindTo(contextKeyService); this.searchInstalledExtensionsContextKey = SearchIntalledExtensionsContext.bindTo(contextKeyService); this.searchOutdatedExtensionsContextKey = SearchOutdatedExtensionsContext.bindTo(contextKeyService); @@ -421,13 +451,6 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE this._register(this.viewletService.onDidViewletOpen(this.onViewletOpen, this)); this.searchViewletState = this.getMemento(StorageScope.WORKSPACE, StorageTarget.USER); - this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(AutoUpdateConfigurationKey)) { - this.secondaryActions = undefined; - this.updateTitleArea(); - } - }, this)); - if (extensionManagementServerService.webExtensionManagementServer) { this._register(extensionsWorkbenchService.onChange(() => { // show installed web extensions view only when it is not visible @@ -437,13 +460,10 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE } })); } + } - this.sortActions = [ - this._register(this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.install', localize('sort by installs', "Install Count"), this.onSearchChange, 'installs')), - this._register(this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.rating', localize('sort by rating', "Rating"), this.onSearchChange, 'rating')), - this._register(this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.name', localize('sort by name', "Name"), this.onSearchChange, 'name')), - this._register(this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.publishedDate', localize('sort by date', "Published Date"), this.onSearchChange, 'publishedDate')), - ]; + get searchValue(): string | undefined { + return this.searchBox?.getValue(); } create(parent: HTMLElement): void { @@ -478,8 +498,8 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE this._register(attachSuggestEnabledInputBoxStyler(this.searchBox, this.themeService)); this._register(this.searchBox.onInputDidChange(() => { + this.sortByContextKey.set(Query.parse(this.searchBox!.getValue() || '').sortBy); this.triggerSearch(); - this._onSearchChange.fire(this.searchBox!.getValue()); }, this)); this._register(this.searchBox.onShouldFocusResults(() => this.focusListView(), this)); @@ -556,59 +576,6 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE return 400; } - @memoize - getActions(): IAction[] { - // Local extensions filters - let filterActions: IAction[] = [ - this._register(this.instantiationService.createInstance(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, localize('builtin filter', "Built-in"))), - this._register(this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, localize('installed filter', "Installed"))), - this._register(this.instantiationService.createInstance(ShowEnabledExtensionsAction, ShowEnabledExtensionsAction.ID, localize('enabled filter', "Enabled"))), - this._register(this.instantiationService.createInstance(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, localize('disabled filter', "Disabled"))), - this._register(this.instantiationService.createInstance(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, localize('outdated filter', "Outdated"))), - ]; - - if (this.extensionGalleryService.isEnabled()) { - filterActions = [ - this._register(this.instantiationService.createInstance(PredefinedExtensionFilterAction, 'extensions.filter.featured', localize('featured filter', "Featured"), '@featured')), - this._register(this.instantiationService.createInstance(PredefinedExtensionFilterAction, 'extensions.filter.popular', localize('most popular filter', "Most Popular"), '@popular')), - this._register(this.instantiationService.createInstance(PredefinedExtensionFilterAction, 'extensions.filter.recommended', localize('most popular recommended', "Recommended"), '@recommended')), - this._register(this.instantiationService.createInstance(RecentlyPublishedExtensionsAction, RecentlyPublishedExtensionsAction.ID, localize('recently published filter', "Recently Published"))), - this._register(new Separator()), - this._register(new SubmenuAction('workbench.extensions.action.filterExtensionsByCategory', localize('filter by category', "Category"), EXTENSION_CATEGORIES.map(category => this.instantiationService.createInstance(SearchCategoryAction, `extensions.actions.searchByCategory.${category}`, category, category)))), - this._register(new Separator()), - ...filterActions, - this._register(new Separator()), - this._register(new SubmenuAction('workbench.extensions.action.sortBy', localize('sorty by', "Sort By"), this.sortActions)), - ]; - } - - return [ - this._register(new SubmenuAction('workbench.extensions.action.filterExtensions', localize('filterExtensions', "Filter Extensions..."), filterActions, ThemeIcon.asClassName(filterIcon))), - this._register(this.instantiationService.createInstance(RefreshExtensionsAction, RefreshExtensionsAction.ID, RefreshExtensionsAction.LABEL)), - this._register(this.instantiationService.createInstance(ClearExtensionsInputAction, ClearExtensionsInputAction.ID, ClearExtensionsInputAction.LABEL, this.onSearchChange, () => this.searchBox!.getValue() || '')), - ]; - } - - getSecondaryActions(): IAction[] { - if (!this.secondaryActions) { - this.secondaryActions = []; - this.secondaryActions.push(this.instantiationService.createInstance(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL)); - if (this.configurationService.getValue(AutoUpdateConfigurationKey)) { - this.secondaryActions.push(this.instantiationService.createInstance(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL)); - } else { - this.secondaryActions.push(this.instantiationService.createInstance(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL, false), this.instantiationService.createInstance(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL)); - } - - this.secondaryActions.push(new Separator()); - this.secondaryActions.push(this.instantiationService.createInstance(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL, false)); - this.secondaryActions.push(this.instantiationService.createInstance(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL, false)); - - this.secondaryActions.push(new Separator()); - this.secondaryActions.push(this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL)); - } - return this.secondaryActions; - } - search(value: string): void { if (this.searchBox && this.searchBox.getValue() !== value) { this.searchBox.setValue(value); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 932a76b68..be8f1ac3c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -17,19 +17,19 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { append, $ } from 'vs/base/browser/dom'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Delegate, Renderer, IExtensionsViewState, EXTENSION_LIST_ELEMENT_HEIGHT } from 'vs/workbench/contrib/extensions/browser/extensionsList'; -import { ExtensionState, IExtension, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { ExtensionState, IExtension, IExtensionsWorkbenchService, IWorkspaceRecommendedExtensionsView } from 'vs/workbench/contrib/extensions/common/extensions'; import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; import { IExtensionService, toExtension } from 'vs/workbench/services/extensions/common/extensions'; -import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; -import { ConfigureWorkspaceFolderRecommendedExtensionsAction, ManageExtensionAction, InstallLocalExtensionsInRemoteAction, getContextMenuActions, ExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { ManageExtensionAction, getContextMenuActions, ExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { WorkbenchPagedList, ListResourceNavigator } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { coalesce, distinct, flatten } from 'vs/base/common/arrays'; import { IExperimentService, IExperiment, ExperimentActionType } from 'vs/workbench/contrib/experiments/common/experimentService'; @@ -49,7 +49,6 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { configureRecommendedIcon, installLocalInRemoteIcon, installWorkspaceRecommendedIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; // Extensions that are automatically classified as Programming Language extensions, but should be Feature extensions const FORCE_FEATURE_EXTENSIONS = ['vscode.git', 'vscode.search-result']; @@ -134,8 +133,12 @@ export class ExtensionsListView extends ViewPane { if (this.options.onDidChangeTitle) { this._register(this.options.onDidChangeTitle(title => this.updateTitle(title))); } + + this.registerActions(); } + protected registerActions(): void { } + protected renderHeader(container: HTMLElement): void { container.classList.add('extension-view-header'); super.renderHeader(container); @@ -263,32 +266,27 @@ export class ExtensionsListView extends ViewPane { const runningExtensions = await this.extensionService.getExtensions(); const manageExtensionAction = this.instantiationService.createInstance(ManageExtensionAction); manageExtensionAction.extension = e.element; + let groups: IAction[][] = []; if (manageExtensionAction.enabled) { - const groups = await manageExtensionAction.getActionGroups(runningExtensions); - let actions: IAction[] = []; - for (const menuActions of groups) { - actions = [...actions, ...menuActions, new Separator()]; - } - this.contextMenuService.showContextMenu({ - getAnchor: () => e.anchor, - getActions: () => actions.slice(0, actions.length - 1) - }); + groups = await manageExtensionAction.getActionGroups(runningExtensions); + } else if (e.element) { - const groups = getContextMenuActions(e.element, false, this.instantiationService); + groups = getContextMenuActions(e.element, false, this.instantiationService); groups.forEach(group => group.forEach(extensionAction => { if (extensionAction instanceof ExtensionAction) { extensionAction.extension = e.element!; } })); - let actions: IAction[] = []; - for (const menuActions of groups) { - actions = [...actions, ...menuActions, new Separator()]; - } - this.contextMenuService.showContextMenu({ - getAnchor: () => e.anchor, - getActions: () => actions - }); } + let actions: IAction[] = []; + for (const menuActions of groups) { + actions = [...actions, ...menuActions, new Separator()]; + } + actions.pop(); + this.contextMenuService.showContextMenu({ + getAnchor: () => e.anchor, + getActions: () => actions + }); } } @@ -693,8 +691,14 @@ export class ExtensionsListView extends ViewPane { } // All recommendations - if (/@recommended:all/i.test(query.value) || ExtensionsListView.isSearchRecommendedExtensionsQuery(query.value)) { - return this.getAllRecommendationsModel(query, options, token); + if (/@recommended:all/i.test(query.value)) { + return this.getAllRecommendationsModel(options, token); + } + + // Search recommendations + if (ExtensionsListView.isSearchRecommendedExtensionsQuery(query.value) || + (ExtensionsListView.isRecommendedExtensionsQuery(query.value) && options.sortBy !== undefined)) { + return this.searchRecommendations(query, options, token); } // Other recommendations @@ -730,10 +734,8 @@ export class ExtensionsListView extends ViewPane { } private async getWorkspaceRecommendationsModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise> { - const value = query.value.replace(/@recommended:workspace/g, '').trim().toLowerCase(); const recommendations = await this.getWorkspaceRecommendations(); - const installableRecommendations = (await this.getInstallableRecommendations(recommendations, { ...options, source: 'recommendations-workspace' }, token)) - .filter(extension => extension.identifier.id.toLowerCase().indexOf(value) > -1); + const installableRecommendations = (await this.getInstallableRecommendations(recommendations, { ...options, source: 'recommendations-workspace' }, token)); this.telemetryService.publicLog2<{ count: number }, WorkspaceRecommendationsClassification>('extensionWorkspaceRecommendations:open', { count: installableRecommendations.length }); const result: IExtension[] = coalesce(recommendations.map(id => installableRecommendations.find(i => areSameExtensions(i.identifier, { id })))); return new PagedModel(result); @@ -755,14 +757,19 @@ export class ExtensionsListView extends ViewPane { } private async getOtherRecommendationsModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise> { - const value = query.value.replace(/@recommended/g, '').trim().toLowerCase(); + const otherRecommendations = await this.getOtherRecommendations(); + const installableRecommendations = await this.getInstallableRecommendations(otherRecommendations, { ...options, source: 'recommendations-other', sortBy: undefined }, token); + const result = coalesce(otherRecommendations.map(id => installableRecommendations.find(i => areSameExtensions(i.identifier, { id })))); + return new PagedModel(result); + } + private async getOtherRecommendations(): Promise { const local = (await this.extensionsWorkbenchService.queryLocal(this.options.server)) .map(e => e.identifier.id.toLowerCase()); const workspaceRecommendations = (await this.getWorkspaceRecommendations()) .map(extensionId => extensionId.toLowerCase()); - const otherRecommendations = distinct( + return distinct( flatten(await Promise.all([ // Order is important this.extensionRecommendationsService.getImportantRecommendations(), @@ -770,16 +777,10 @@ export class ExtensionsListView extends ViewPane { this.extensionRecommendationsService.getOtherRecommendations() ])).filter(extensionId => !local.includes(extensionId.toLowerCase()) && !workspaceRecommendations.includes(extensionId.toLowerCase()) ), extensionId => extensionId.toLowerCase()); - - const installableRecommendations = (await this.getInstallableRecommendations(otherRecommendations, { ...options, source: 'recommendations-other', sortBy: undefined }, token)) - .filter(extension => extension.identifier.id.toLowerCase().indexOf(value) > -1); - - const result: IExtension[] = coalesce(otherRecommendations.map(id => installableRecommendations.find(i => areSameExtensions(i.identifier, { id })))); - return new PagedModel(result); } // Get All types of recommendations, trimmed to show a max of 8 at any given time - private async getAllRecommendationsModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise> { + private async getAllRecommendationsModel(options: IQueryOptions, token: CancellationToken): Promise> { const local = (await this.extensionsWorkbenchService.queryLocal(this.options.server)).map(e => e.identifier.id.toLowerCase()); const allRecommendations = distinct( @@ -797,6 +798,15 @@ export class ExtensionsListView extends ViewPane { return new PagedModel(result.slice(0, 8)); } + private async searchRecommendations(query: Query, options: IQueryOptions, token: CancellationToken): Promise> { + const value = query.value.replace(/@recommended/g, '').trim().toLowerCase(); + const recommendations = distinct([...await this.getWorkspaceRecommendations(), ...await this.getOtherRecommendations()]); + const installableRecommendations = (await this.getInstallableRecommendations(recommendations, { ...options, source: 'recommendations', sortBy: undefined }, token)) + .filter(extension => extension.identifier.id.toLowerCase().indexOf(value) > -1); + const result = coalesce(recommendations.map(id => installableRecommendations.find(i => areSameExtensions(i.identifier, { id })))); + return new PagedModel(this.sortExtensions(result, options)); + } + // Sorts the firstPage of the pager in the same order as given array of extension ids private sortFirstPage(pager: IPager, ids: string[]) { ids = ids.map(x => x.toLowerCase()); @@ -951,7 +961,7 @@ export class ExtensionsListView extends ViewPane { } static isSearchRecommendedExtensionsQuery(query: string): boolean { - return /@recommended/i.test(query) && !ExtensionsListView.isRecommendedExtensionsQuery(query); + return /@recommended\s.+/i.test(query); } static isWorkspaceRecommendedExtensionsQuery(query: string): boolean { @@ -989,14 +999,6 @@ export class ServerInstalledExtensionsView extends ExtensionsListView { return super.show(query.trim()); } - getActions(): IAction[] { - if (this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManagementServerService.localExtensionManagementServer === this.options.server) { - const installLocalExtensionsInRemoteAction = this._register(this.instantiationService.createInstance(InstallLocalExtensionsInRemoteAction)); - installLocalExtensionsInRemoteAction.class = ThemeIcon.asClassName(installLocalInRemoteIcon); - return [installLocalExtensionsInRemoteAction]; - } - return []; - } } export class EnabledExtensionsView extends ExtensionsListView { @@ -1074,9 +1076,8 @@ export class RecommendedExtensionsView extends ExtensionsListView { } } -export class WorkspaceRecommendedExtensionsView extends ExtensionsListView { +export class WorkspaceRecommendedExtensionsView extends ExtensionsListView implements IWorkspaceRecommendedExtensionsView { private readonly recommendedExtensionsQuery = '@recommended:workspace'; - private installAllAction: Action | undefined; renderBody(container: HTMLElement): void { super.renderBody(container); @@ -1085,31 +1086,13 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView { this._register(this.contextService.onDidChangeWorkbenchState(() => this.show(this.recommendedExtensionsQuery))); } - getActions(): IAction[] { - if (!this.installAllAction) { - this.installAllAction = this._register(new Action('workbench.extensions.action.installWorkspaceRecommendedExtensions', localize('installWorkspaceRecommendedExtensions', "Install Workspace Recommended Extensions"), ThemeIcon.asClassName(installWorkspaceRecommendedIcon), false, () => this.installWorkspaceRecommendations())); - } - - const configureWorkspaceFolderAction = this._register(this.instantiationService.createInstance(ConfigureWorkspaceFolderRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL)); - configureWorkspaceFolderAction.class = ThemeIcon.asClassName(configureRecommendedIcon); - return [this.installAllAction, configureWorkspaceFolderAction]; - } - async show(query: string): Promise> { let shouldShowEmptyView = query && query.trim() !== '@recommended' && query.trim() !== '@recommended:workspace'; let model = await (shouldShowEmptyView ? this.showEmptyModel() : super.show(this.recommendedExtensionsQuery)); this.setExpanded(model.length > 0); - await this.setRecommendationsToInstall(); return model; } - private async setRecommendationsToInstall(): Promise { - const installableRecommendations = await this.getInstallableWorkspaceRecommendations(); - if (this.installAllAction) { - this.installAllAction.enabled = installableRecommendations.length > 0; - } - } - private async getInstallableWorkspaceRecommendations() { const installed = (await this.extensionsWorkbenchService.queryLocal()) .filter(l => l.enablementState !== EnablementState.DisabledByExtensionKind); // Filter extensions disabled by kind @@ -1118,9 +1101,16 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView { return this.getInstallableRecommendations(recommendations, { source: 'install-all-workspace-recommendations' }, CancellationToken.None); } - private async installWorkspaceRecommendations(): Promise { + async installWorkspaceRecommendations(): Promise { const installableRecommendations = await this.getInstallableWorkspaceRecommendations(); - await this.extensionManagementService.installExtensions(installableRecommendations.map(i => i.gallery!)); + if (installableRecommendations.length) { + await this.extensionManagementService.installExtensions(installableRecommendations.map(i => i.gallery!)); + } else { + this.notificationService.notify({ + severity: Severity.Info, + message: localize('no local extensions', "There are no extensions to install.") + }); + } } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 3566ca415..2f8ac0a8f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -22,7 +22,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti 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 } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, HasOutdatedExtensionsContext } 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 } from 'vs/workbench/contrib/extensions/common/extensionsInput'; @@ -40,6 +40,7 @@ import { getExtensionKind } from 'vs/workbench/services/extensions/common/extens import { FileAccess } from 'vs/base/common/network'; import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; import { IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; interface IExtensionStateProvider { (extension: Extension): T; @@ -492,6 +493,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension private static readonly SyncPeriod = 1000 * 60 * 60 * 12; // 12 hours declare readonly _serviceBrand: undefined; + private hasOutdatedExtensionsContextKey: IContextKey; + private readonly localExtensions: Extensions | null = null; private readonly remoteExtensions: Extensions | null = null; private readonly webExtensions: Extensions | null = null; @@ -520,9 +523,11 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension @IModeService private readonly modeService: IModeService, @IIgnoredExtensionsManagementService private readonly extensionsSyncManagementService: IIgnoredExtensionsManagementService, @IUserDataAutoSyncService private readonly userDataAutoSyncService: IUserDataAutoSyncService, - @IProductService private readonly productService: IProductService + @IProductService private readonly productService: IProductService, + @IContextKeyService contextKeyService: IContextKeyService ) { super(); + this.hasOutdatedExtensionsContextKey = HasOutdatedExtensionsContext.bindTo(contextKeyService); if (extensionManagementServerService.localExtensionManagementServer) { this.localExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.localExtensionManagementServer, ext => this.getExtensionState(ext))); this._register(this.localExtensions.onChange(e => this._onChange.fire(e ? e.extension : undefined))); @@ -559,7 +564,10 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension this.eventuallySyncWithGallery(true); }); - this._register(this.onChange(() => this.updateActivity())); + this._register(this.onChange(() => { + this.updateContexts(); + this.updateActivity(); + })); } get local(): IExtension[] { @@ -1189,13 +1197,21 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return changed; } + private updateContexts(extension?: Extension): void { + if (extension && extension.outdated) { + this.hasOutdatedExtensionsContextKey.set(true); + } else { + this.hasOutdatedExtensionsContextKey.set(this.outdated.length > 0); + } + } + private _activityCallBack: ((value: void) => void) | null = null; private updateActivity(): void { if ((this.localExtensions && this.localExtensions.local.some(e => e.state === ExtensionState.Installing || e.state === ExtensionState.Uninstalling)) || (this.remoteExtensions && this.remoteExtensions.local.some(e => e.state === ExtensionState.Installing || e.state === ExtensionState.Uninstalling)) || (this.webExtensions && this.webExtensions.local.some(e => e.state === ExtensionState.Installing || e.state === ExtensionState.Uninstalling))) { if (!this._activityCallBack) { - this.progressService.withProgress({ location: ProgressLocation.Extensions }, () => new Promise(c => this._activityCallBack = c)); + this.progressService.withProgress({ location: ProgressLocation.Extensions }, () => new Promise(resolve => this._activityCallBack = resolve)); } } else { if (this._activityCallBack) { diff --git a/src/vs/workbench/contrib/extensions/browser/media/extension.css b/src/vs/workbench/contrib/extensions/browser/media/extension.css index 0502ce085..8749cbe23 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extension.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extension.css @@ -179,3 +179,7 @@ .extension-list-item > .details > .footer > .monaco-action-bar > .actions-container .extension-action.label { max-width: 150px; } + +.extension-list-item .footer .monaco-action-bar .action-item.action-dropdown-item.empty > .action-label { + margin-right: 4px; +} diff --git a/src/vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller.ts b/src/vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller.ts deleted file mode 100644 index 3a8caf10a..000000000 --- a/src/vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller.ts +++ /dev/null @@ -1,59 +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 { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { MenuRegistry, MenuId, Action2, registerAction2 } from 'vs/platform/actions/common/actions'; -import { localize } from 'vs/nls'; -import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { InstallLocalExtensionsInRemoteAction, InstallRemoteExtensionsInLocalAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; -import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; - -export class RemoteExtensionsInstaller extends Disposable implements IWorkbenchContribution { - - constructor( - @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, - @ILabelService labelService: ILabelService, - @IInstantiationService instantiationService: IInstantiationService - ) { - super(); - if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { - const installLocalExtensionsInRemoteAction = instantiationService.createInstance(InstallLocalExtensionsInRemoteAction); - CommandsRegistry.registerCommand('workbench.extensions.installLocalExtensions', () => installLocalExtensionsInRemoteAction.run()); - let disposable = Disposable.None; - const appendMenuItem = () => { - disposable.dispose(); - disposable = MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: 'workbench.extensions.installLocalExtensions', - category: localize({ key: 'remote', comment: ['Remote as in remote machine'] }, "Remote"), - title: installLocalExtensionsInRemoteAction.label - } - }); - }; - appendMenuItem(); - this._register(labelService.onDidChangeFormatters(e => appendMenuItem())); - this._register(toDisposable(() => disposable.dispose())); - - this._register(registerAction2(class InstallRemoteExtensionsInLocalAction2 extends Action2 { - constructor() { - super({ - id: 'workbench.extensions.actions.installLocalExtensionsInRemote', - title: { value: localize('install remote in local', "Install Remote Extensions Locally..."), original: 'Install Remote Extensions Locally...' }, - category: localize({ key: 'remote', comment: ['Remote as in remote machine'] }, "Remote"), - f1: true - }); - } - run(accessor: ServicesAccessor): Promise { - return accessor.get(IInstantiationService).createInstance(InstallRemoteExtensionsInLocalAction, 'workbench.extensions.actions.installLocalExtensionsInRemote').run(); - } - })); - } - } - -} diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 3cfe2796c..127ae556d 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -13,15 +13,21 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; -import { IViewPaneContainer } from 'vs/workbench/common/views'; +import { IView, IViewPaneContainer } from 'vs/workbench/common/views'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export const VIEWLET_ID = 'workbench.view.extensions'; export interface IExtensionsViewPaneContainer extends IViewPaneContainer { + readonly searchValue: string | undefined; search(text: string): void; refresh(): Promise; } +export interface IWorkspaceRecommendedExtensionsView extends IView { + installWorkspaceRecommendations(): Promise; +} + export const enum ExtensionState { Installing, Installed, @@ -145,5 +151,12 @@ export class ExtensionContainers extends Disposable { } } +export const WORKSPACE_RECOMMENDATIONS_VIEW_ID = 'workbench.views.extensions.workspaceRecommendations'; export const TOGGLE_IGNORE_EXTENSION_ACTION_ID = 'workbench.extensions.action.toggleIgnoreExtension'; +export const SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID = 'workbench.extensions.action.installVSIX'; export const INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID = 'workbench.extensions.command.installFromVSIX'; + +// Context Keys +export const DefaultViewsContext = new RawContextKey('defaultExtensionViews', true); +export const ExtensionsSortByContext = new RawContextKey('extensionsSortByValue', ''); +export const HasOutdatedExtensionsContext = new RawContextKey('hasOutdatedExtensions', false); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts index a472654f0..edf6d1001 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts @@ -21,7 +21,6 @@ import { ExtensionHostProfileService } from 'vs/workbench/contrib/extensions/ele import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/runtimeExtensionsInput'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionsAutoProfiler } from 'vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { OpenExtensionsFolderAction } from 'vs/workbench/contrib/extensions/electron-sandbox/extensionsActions'; import { ExtensionsLabel } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; @@ -62,11 +61,10 @@ const actionRegistry = Registry.as(WorkbenchActionExte class ExtensionsContributions implements IWorkbenchContribution { constructor( - @INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @IExtensionRecommendationNotificationService extensionRecommendationNotificationService: IExtensionRecommendationNotificationService, @ISharedProcessService sharedProcessService: ISharedProcessService, ) { - sharedProcessService.registerChannel('IExtensionRecommendationNotificationService', new ExtensionRecommendationNotificationServiceChannel(extensionRecommendationNotificationService)); + sharedProcessService.registerChannel('extensionRecommendationNotification', new ExtensionRecommendationNotificationServiceChannel(extensionRecommendationNotificationService)); const openExtensionsFolderActionDescriptor = SyncActionDescriptor.from(OpenExtensionsFolderAction); actionRegistry.registerWorkbenchAction(openExtensionsFolderActionDescriptor, 'Extensions: Open Extensions Folder', ExtensionsLabel); } diff --git a/src/vs/workbench/contrib/extensions/electron-browser/reportExtensionIssueAction.ts b/src/vs/workbench/contrib/extensions/electron-browser/reportExtensionIssueAction.ts index faf606a75..762b07c45 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/reportExtensionIssueAction.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/reportExtensionIssueAction.ts @@ -14,6 +14,8 @@ import { ExtensionType, IExtensionDescription } from 'vs/platform/extensions/com import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; +const builtinExtensionIssueUrl = 'https://github.com/microsoft/vscode'; + export class ReportExtensionIssueAction extends Action { private static readonly _id = 'workbench.extensions.action.reportExtensionIssue'; @@ -34,7 +36,8 @@ export class ReportExtensionIssueAction extends Action { @INativeHostService private readonly nativeHostService: INativeHostService ) { super(ReportExtensionIssueAction._id, ReportExtensionIssueAction._label, 'extension-action report-issue'); - this.enabled = !!extension.description.repository && !!extension.description.repository.url; + + this.enabled = extension.description.isBuiltin || (!!extension.description.repository && !!extension.description.repository.url); } async run(): Promise { @@ -51,6 +54,9 @@ export class ReportExtensionIssueAction extends Action { unresponsiveProfile?: IExtensionHostProfile }): Promise { let baseUrl = extension.marketplaceInfo && extension.marketplaceInfo.type === ExtensionType.User && extension.description.repository ? extension.description.repository.url : undefined; + if (!baseUrl && extension.description.isBuiltin) { + baseUrl = builtinExtensionIssueUrl; + } if (!!baseUrl) { baseUrl = `${baseUrl.indexOf('.git') !== -1 ? baseUrl.substr(0, baseUrl.length - 4) : baseUrl}/issues/new/`; } else { diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts index ed8a92a98..c380c4c74 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts @@ -5,11 +5,7 @@ import * as sinon from 'sinon'; import * as assert from 'assert'; -import * as path from 'vs/base/common/path'; -import * as fs from 'fs'; -import * as os from 'os'; import * as uuid from 'vs/base/common/uuid'; -import { mkdirp, rimraf, RimRafMode } from 'vs/base/node/pfs'; import { IExtensionGalleryService, IGalleryExtensionAssets, IGalleryExtension, IExtensionManagementService, DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, IExtensionTipsService @@ -47,8 +43,6 @@ import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService, ILogService } from 'vs/platform/log/common/log'; -import { Schemas } from 'vs/base/common/network'; -import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { IFileService } from 'vs/platform/files/common/files'; import { IProductService } from 'vs/platform/product/common/productService'; import { ExtensionTipsService } from 'vs/platform/extensionManagement/electron-sandbox/extensionTipsService'; @@ -62,6 +56,11 @@ import { IExtensionIgnoredRecommendationsService } from 'vs/workbench/services/e import { ExtensionIgnoredRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionIgnoredRecommendationsService'; import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; import { ExtensionRecommendationNotificationService } from 'vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; +import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; +import { joinPath } from 'vs/base/common/resources'; +import { VSBuffer } from 'vs/base/common/buffer'; const mockExtensionGallery: IGalleryExtension[] = [ aGalleryExtension('MockExtension1', { @@ -181,7 +180,6 @@ suite('ExtensionRecommendationsService Test', () => { let instantiationService: TestInstantiationService; let testConfigurationService: TestConfigurationService; let testObject: ExtensionRecommendationsService; - let parentResource: string; let installEvent: Emitter, didInstallEvent: Emitter, uninstallEvent: Emitter, @@ -203,6 +201,7 @@ suite('ExtensionRecommendationsService Test', () => { testConfigurationService = new TestConfigurationService(); instantiationService.stub(IConfigurationService, testConfigurationService); instantiationService.stub(INotificationService, new TestNotificationService()); + instantiationService.stub(IContextKeyService, new MockContextKeyService()); instantiationService.stub(IExtensionManagementService, >{ onInstallExtension: installEvent.event, onDidInstallExtension: didInstallEvent.event, @@ -278,34 +277,30 @@ suite('ExtensionRecommendationsService Test', () => { }); }); - teardown(done => { - (testObject).dispose(); - if (parentResource) { - rimraf(parentResource, RimRafMode.MOVE).then(done, done); - } else { - done(); - } - }); + teardown(() => (testObject).dispose()); function setUpFolderWorkspace(folderName: string, recommendedExtensions: string[], ignoredRecommendations: string[] = []): Promise { - const id = uuid.generateUuid(); - parentResource = path.join(os.tmpdir(), 'vsctests', id); - return setUpFolder(folderName, parentResource, recommendedExtensions, ignoredRecommendations); + return setUpFolder(folderName, recommendedExtensions, ignoredRecommendations); } - async function setUpFolder(folderName: string, parentDir: string, recommendedExtensions: string[], ignoredRecommendations: string[] = []): Promise { - const folderDir = path.join(parentDir, folderName); - const workspaceSettingsDir = path.join(folderDir, '.vscode'); - await mkdirp(workspaceSettingsDir, 493); - const configPath = path.join(workspaceSettingsDir, 'extensions.json'); - fs.writeFileSync(configPath, JSON.stringify({ + async function setUpFolder(folderName: string, recommendedExtensions: string[], ignoredRecommendations: string[] = []): Promise { + const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); + const logService = new NullLogService(); + const fileService = new FileService(logService); + const fileSystemProvider = new InMemoryFileSystemProvider(); + fileService.registerProvider(ROOT.scheme, fileSystemProvider); + + const folderDir = joinPath(ROOT, folderName); + const workspaceSettingsDir = joinPath(folderDir, '.vscode'); + await fileService.createFolder(workspaceSettingsDir); + const configPath = joinPath(workspaceSettingsDir, 'extensions.json'); + await fileService.writeFile(configPath, VSBuffer.fromString(JSON.stringify({ 'recommendations': recommendedExtensions, 'unwantedRecommendations': ignoredRecommendations, - }, null, '\t')); + }, null, '\t'))); + + const myWorkspace = testWorkspace(folderDir); - const myWorkspace = testWorkspace(URI.from({ scheme: 'file', path: folderDir })); - const fileService = new FileService(new NullLogService()); - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); instantiationService.stub(IFileService, fileService); workspaceService = new TestContextService(myWorkspace); instantiationService.stub(IWorkspaceContextService, workspaceService); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index 40cf65cc2..bcef66bbd 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -33,7 +33,7 @@ import { NativeURLService } from 'vs/platform/url/common/urlService'; import { URI } from 'vs/base/common/uri'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; +import { RemoteAgentService } from 'vs/workbench/services/remote/electron-sandbox/remoteAgentServiceImpl'; import { ExtensionIdentifier, IExtensionContributions, ExtensionType, IExtensionDescription, IExtension } from 'vs/platform/extensions/common/extensions'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -53,6 +53,8 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { UserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataAutoSyncService'; import { IUserDataAutoSyncEnablementService, IUserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSyncResourceEnablementService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; let instantiationService: TestInstantiationService; let installEvent: Emitter, @@ -77,6 +79,7 @@ async function setupTest() { instantiationService.stub(IConfigurationService, new TestConfigurationService()); instantiationService.stub(IProgressService, ProgressService); instantiationService.stub(IProductService, {}); + instantiationService.stub(IContextKeyService, new MockContextKeyService()); instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); instantiationService.stub(ISharedProcessService, TestSharedProcessService); @@ -885,83 +888,6 @@ suite('ExtensionsActions', () => { }); }); - test('Test UpdateAllAction when no installed extensions', () => { - const testObject: ExtensionsActions.UpdateAllAction = instantiationService.createInstance(ExtensionsActions.UpdateAllAction, 'id', 'label', true); - - assert.ok(!testObject.enabled); - }); - - test('Test UpdateAllAction when installed extensions are not outdated', () => { - const testObject: ExtensionsActions.UpdateAllAction = instantiationService.createInstance(ExtensionsActions.UpdateAllAction, 'id', 'label', true); - instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a'), aLocalExtension('b')]); - return instantiationService.get(IExtensionsWorkbenchService).queryLocal() - .then(extensions => assert.ok(!testObject.enabled)); - }); - - test('Test UpdateAllAction when some installed extensions are outdated', () => { - const testObject: ExtensionsActions.UpdateAllAction = instantiationService.createInstance(ExtensionsActions.UpdateAllAction, 'id', 'label', true); - const local = [aLocalExtension('a', { version: '1.0.1' }), aLocalExtension('b', { version: '1.0.1' }), aLocalExtension('c', { version: '1.0.1' })]; - const workbenchService = instantiationService.get(IExtensionsWorkbenchService); - instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', local); - return workbenchService.queryLocal() - .then(async () => { - instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: local[0].identifier, version: '1.0.2' }), aGalleryExtension('b', { identifier: local[1].identifier, version: '1.0.2' }), aGalleryExtension('c', local[2].manifest))); - assert.ok(!testObject.enabled); - return new Promise(c => { - testObject.onDidChange(() => { - if (testObject.enabled) { - c(); - } - }); - workbenchService.queryGallery(CancellationToken.None); - }); - }); - }); - - test('Test UpdateAllAction when some installed extensions are outdated and some outdated are being installed', () => { - const testObject: ExtensionsActions.UpdateAllAction = instantiationService.createInstance(ExtensionsActions.UpdateAllAction, 'id', 'label', true); - const local = [aLocalExtension('a', { version: '1.0.1' }), aLocalExtension('b', { version: '1.0.1' }), aLocalExtension('c', { version: '1.0.1' })]; - const gallery = [aGalleryExtension('a', { identifier: local[0].identifier, version: '1.0.2' }), aGalleryExtension('b', { identifier: local[1].identifier, version: '1.0.2' }), aGalleryExtension('c', local[2].manifest)]; - const workbenchService = instantiationService.get(IExtensionsWorkbenchService); - instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', local); - return workbenchService.queryLocal() - .then(async () => { - instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...gallery)); - assert.ok(!testObject.enabled); - return new Promise(c => { - installEvent.fire({ identifier: local[0].identifier, gallery: gallery[0] }); - testObject.onDidChange(() => { - if (testObject.enabled) { - c(); - } - }); - workbenchService.queryGallery(CancellationToken.None); - }); - }); - }); - - test('Test UpdateAllAction when some installed extensions are outdated and all outdated are being installed', () => { - const testObject: ExtensionsActions.UpdateAllAction = instantiationService.createInstance(ExtensionsActions.UpdateAllAction, 'id', 'label', true); - const local = [aLocalExtension('a', { version: '1.0.1' }), aLocalExtension('b', { version: '1.0.1' }), aLocalExtension('c', { version: '1.0.1' })]; - const gallery = [aGalleryExtension('a', { identifier: local[0].identifier, version: '1.0.2' }), aGalleryExtension('b', { identifier: local[1].identifier, version: '1.0.2' }), aGalleryExtension('c', local[2].manifest)]; - const workbenchService = instantiationService.get(IExtensionsWorkbenchService); - instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', local); - return workbenchService.queryLocal() - .then(() => { - instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...gallery)); - return workbenchService.queryGallery(CancellationToken.None) - .then(() => { - installEvent.fire({ identifier: local[0].identifier, gallery: gallery[0] }); - installEvent.fire({ identifier: local[1].identifier, gallery: gallery[1] }); - assert.ok(!testObject.enabled); - }); - }); - }); - - test(`RecommendToFolderAction`, () => { - // TODO: Implement test - }); - }); suite('ReloadAction', () => { diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index cde013f45..b1e00bb80 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -35,7 +35,7 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ import { SinonStub } from 'sinon'; import { IExperimentService, ExperimentState, ExperimentActionType, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; +import { RemoteAgentService } from 'vs/workbench/services/remote/electron-sandbox/remoteAgentServiceImpl'; import { ExtensionType, IExtension, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -165,7 +165,8 @@ suite('ExtensionsListView Tests', () => { instantiationService.stub(IViewDescriptorService, { getViewLocationById(): ViewContainerLocation { return ViewContainerLocation.Sidebar; - } + }, + onDidChangeLocation: Event.None }); instantiationService.stub(IExtensionService, >{ diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index f4f5d8ee5..76a502062 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -36,7 +36,7 @@ import { URI } from 'vs/base/common/uri'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionType, IExtension, ExtensionKind } from 'vs/platform/extensions/common/extensions'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; +import { RemoteAgentService } from 'vs/workbench/services/remote/electron-sandbox/remoteAgentServiceImpl'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -46,6 +46,8 @@ import { IExperimentService } from 'vs/workbench/contrib/experiments/common/expe import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test'; import { ExtensionTipsService } from 'vs/platform/extensionManagement/electron-sandbox/extensionTipsService'; import { Schemas } from 'vs/base/common/network'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; suite('ExtensionsWorkbenchServiceTest', () => { @@ -72,6 +74,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); instantiationService.stub(IURLService, NativeURLService); instantiationService.stub(ISharedProcessService, TestSharedProcessService); + instantiationService.stub(IContextKeyService, new MockContextKeyService()); instantiationService.stub(IWorkspaceContextService, new TestContextService()); instantiationService.stub(IConfigurationService, >{ diff --git a/src/vs/workbench/contrib/externalUriOpener/common/configuration.ts b/src/vs/workbench/contrib/externalUriOpener/common/configuration.ts new file mode 100644 index 000000000..83bd5a398 --- /dev/null +++ b/src/vs/workbench/contrib/externalUriOpener/common/configuration.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 { IConfigurationNode, IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; +import * as nls from 'vs/nls'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { Registry } from 'vs/platform/registry/common/platform'; + +export const defaultExternalUriOpenerId = 'default'; + +export const externalUriOpenersSettingId = 'workbench.externalUriOpeners'; + +export interface ExternalUriOpenersConfiguration { + readonly [uriGlob: string]: string; +} + +const externalUriOpenerIdSchemaAddition: IJSONSchema = { + type: 'string', + enum: [] +}; + +const exampleUriPatterns = ` +- \`https://microsoft.com\`: Matches this specific domain using https +- \`https://microsoft.com:8080\`: Matches this specific domain on this port using https +- \`https://microsoft.com:*\`: Matches this specific domain on any port using https +- \`https://microsoft.com/foo\`: Matches \`https://microsoft.com/foo\` and \`https://microsoft.com/foo/bar\`, but not \`https://microsoft.com/foobar\` or \`https://microsoft.com/bar\` +- \`https://*.microsoft.com\`: Match all domains ending in \`microsoft.com\` using https +- \`microsoft.com\`: Match this specific domain using either http or https +- \`*.microsoft.com\`: Match all domains ending in \`microsoft.com\` using either http or https +- \`http://192.168.0.1\`: Matches this specific IP using http +- \`http://192.168.0.*\`: Matches all IP's with this prefix using http +- \`*\`: Match all domains using either http or https`; + +export const externalUriOpenersConfigurationNode: IConfigurationNode = { + ...workbenchConfigurationNodeBase, + properties: { + [externalUriOpenersSettingId]: { + type: 'object', + markdownDescription: nls.localize('externalUriOpeners', "Configure the opener to use for external uris (i.e. http, https)."), + defaultSnippets: [{ + body: { + 'example.com': '$1' + } + }], + additionalProperties: { + anyOf: [ + { + type: 'string', + markdownDescription: nls.localize('externalUriOpeners.uri', "Map uri pattern to an opener id.\nExample patterns: \n{0}", exampleUriPatterns), + }, + { + type: 'string', + markdownDescription: nls.localize('externalUriOpeners.uri', "Map uri pattern to an opener id.\nExample patterns: \n{0}", exampleUriPatterns), + enum: [defaultExternalUriOpenerId], + enumDescriptions: [nls.localize('externalUriOpeners.defaultId', "Open using VS Code's standard opener.")], + }, + externalUriOpenerIdSchemaAddition + ] + } + } + } +}; + +export function updateContributedOpeners(enumValues: string[], enumDescriptions: string[]): void { + externalUriOpenerIdSchemaAddition.enum = enumValues; + externalUriOpenerIdSchemaAddition.enumDescriptions = enumDescriptions; + + Registry.as(Extensions.Configuration) + .notifyConfigurationSchemaUpdated(externalUriOpenersConfigurationNode); +} diff --git a/src/vs/workbench/contrib/externalUriOpener/common/contributedOpeners.ts b/src/vs/workbench/contrib/externalUriOpener/common/contributedOpeners.ts new file mode 100644 index 000000000..457d5df30 --- /dev/null +++ b/src/vs/workbench/contrib/externalUriOpener/common/contributedOpeners.ts @@ -0,0 +1,86 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { Memento } from 'vs/workbench/common/memento'; +import { updateContributedOpeners } from 'vs/workbench/contrib/externalUriOpener/common/configuration'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; + +interface RegisteredExternalOpener { + readonly extensionId: string; +} + +interface OpenersMemento { + [id: string]: RegisteredExternalOpener; +} + +/** + */ +export class ContributedExternalUriOpenersStore extends Disposable { + + private static readonly STORAGE_ID = 'externalUriOpeners'; + + private readonly _openers = new Map(); + private readonly _memento: Memento; + private _mementoObject: OpenersMemento; + + constructor( + @IStorageService storageService: IStorageService, + @IExtensionService private readonly _extensionService: IExtensionService + ) { + super(); + + this._memento = new Memento(ContributedExternalUriOpenersStore.STORAGE_ID, storageService); + this._mementoObject = this._memento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); + for (const id of Object.keys(this._mementoObject || {})) { + this.add(id, this._mementoObject[id].extensionId); + } + + this.invalidateOpenersForUninstalledExtension(); + + this._register(this._extensionService.onDidChangeExtensions(() => this.invalidateOpenersForUninstalledExtension())); + } + + public add(id: string, extensionId: string): void { + this._openers.set(id, { extensionId }); + + this._mementoObject[id] = { extensionId }; + this._memento.saveMemento(); + + this.updateSchema(); + } + + public delete(id: string): void { + this._openers.delete(id); + + delete this._mementoObject[id]; + this._memento.saveMemento(); + + this.updateSchema(); + } + + private async invalidateOpenersForUninstalledExtension() { + const registeredExtensions = await this._extensionService.getExtensions(); + for (const [id, entry] of this._openers) { + const isExtensionRegistered = registeredExtensions.some(r => r.identifier.value === entry.extensionId); + if (!isExtensionRegistered) { + this.delete(id); + } + } + } + + private updateSchema() { + const ids: string[] = []; + const descriptions: string[] = []; + + for (const [id, entry] of this._openers) { + ids.push(id); + descriptions.push(entry.extensionId); + } + + updateContributedOpeners(ids, descriptions); + } +} diff --git a/src/vs/workbench/contrib/externalUriOpener/common/externalUriOpener.contribution.ts b/src/vs/workbench/contrib/externalUriOpener/common/externalUriOpener.contribution.ts new file mode 100644 index 000000000..34f349d2a --- /dev/null +++ b/src/vs/workbench/contrib/externalUriOpener/common/externalUriOpener.contribution.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { externalUriOpenersConfigurationNode } from 'vs/workbench/contrib/externalUriOpener/common/configuration'; +import { ExternalUriOpenerService, IExternalUriOpenerService } from 'vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService'; + +registerSingleton(IExternalUriOpenerService, ExternalUriOpenerService); + +Registry.as(ConfigurationExtensions.Configuration) + .registerConfiguration(externalUriOpenersConfigurationNode); diff --git a/src/vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService.ts b/src/vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService.ts new file mode 100644 index 000000000..069ad5e48 --- /dev/null +++ b/src/vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService.ts @@ -0,0 +1,216 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { firstOrDefault } from 'vs/base/common/arrays'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Iterable } from 'vs/base/common/iterator'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { LinkedList } from 'vs/base/common/linkedList'; +import { isWeb } from 'vs/base/common/platform'; +import { URI } from 'vs/base/common/uri'; +import * as modes from 'vs/editor/common/modes'; +import * as nls from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IExternalOpener, IOpenerService } from 'vs/platform/opener/common/opener'; +import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { defaultExternalUriOpenerId, ExternalUriOpenersConfiguration, externalUriOpenersSettingId } from 'vs/workbench/contrib/externalUriOpener/common/configuration'; +import { testUrlMatchesGlob } from 'vs/workbench/contrib/url/common/urlGlob'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; + + +export const IExternalUriOpenerService = createDecorator('externalUriOpenerService'); + + +export interface IExternalOpenerProvider { + getOpeners(targetUri: URI): AsyncIterable; +} + +export interface IExternalUriOpener { + readonly id: string; + readonly label: string; + + canOpen(uri: URI, token: CancellationToken): Promise; + openExternalUri(uri: URI, ctx: { sourceUri: URI }, token: CancellationToken): Promise; +} + +export interface IExternalUriOpenerService { + readonly _serviceBrand: undefined + + /** + * Registers a provider for external resources openers. + */ + registerExternalOpenerProvider(provider: IExternalOpenerProvider): IDisposable; +} + +export class ExternalUriOpenerService extends Disposable implements IExternalUriOpenerService, IExternalOpener { + + public readonly _serviceBrand: undefined; + + private readonly _providers = new LinkedList(); + + constructor( + @IOpenerService openerService: IOpenerService, + @IStorageService storageService: IStorageService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @ILogService private readonly logService: ILogService, + @IPreferencesService private readonly preferencesService: IPreferencesService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + ) { + super(); + this._register(openerService.registerExternalOpener(this)); + } + + registerExternalOpenerProvider(provider: IExternalOpenerProvider): IDisposable { + const remove = this._providers.push(provider); + return { dispose: remove }; + } + + async openExternal(href: string, ctx: { sourceUri: URI, preferredOpenerId?: string }, token: CancellationToken): Promise { + + const targetUri = typeof href === 'string' ? URI.parse(href) : href; + + const allOpeners = await this.getAllOpenersForUri(targetUri); + + if (allOpeners.size === 0) { + return false; + } + + // First see if we have a preferredOpener + if (ctx.preferredOpenerId) { + if (ctx.preferredOpenerId === defaultExternalUriOpenerId) { + return false; + } + + const preferredOpener = allOpeners.get(ctx.preferredOpenerId); + if (preferredOpener) { + // Skip the `canOpen` check here since the opener was specifically requested. + return preferredOpener.openExternalUri(targetUri, ctx, token); + } + } + + // Check to see if we have a configured opener + const configuredOpener = this.getConfiguredOpenerForUri(allOpeners, targetUri); + if (configuredOpener) { + // Skip the `canOpen` check here since the opener was specifically requested. + return configuredOpener === defaultExternalUriOpenerId ? false : configuredOpener.openExternalUri(targetUri, ctx, token); + } + + // Then check to see if there is a valid opener + const validOpeners: Array<{ opener: IExternalUriOpener, priority: modes.ExternalUriOpenerPriority }> = []; + await Promise.all(Array.from(allOpeners.values()).map(async opener => { + let priority: modes.ExternalUriOpenerPriority; + try { + priority = await opener.canOpen(targetUri, token); + } catch (e) { + this.logService.error(e); + return; + } + + switch (priority) { + case modes.ExternalUriOpenerPriority.Option: + case modes.ExternalUriOpenerPriority.Default: + case modes.ExternalUriOpenerPriority.Preferred: + validOpeners.push({ opener, priority }); + break; + } + })); + + if (validOpeners.length === 0) { + return false; + } + + // See if we have a preferred opener first + const preferred = firstOrDefault(validOpeners.filter(x => x.priority === modes.ExternalUriOpenerPriority.Preferred)); + if (preferred) { + return preferred.opener.openExternalUri(targetUri, ctx, token); + } + + // See if we only have optional openers, use the default opener + if (validOpeners.every(x => x.priority === modes.ExternalUriOpenerPriority.Option)) { + return false; + } + + // Otherwise prompt + return this.showOpenerPrompt(validOpeners.map(x => x.opener), targetUri, ctx, token); + } + + private async getAllOpenersForUri(targetUri: URI): Promise> { + const allOpeners = new Map(); + await Promise.all(Iterable.map(this._providers, async (provider) => { + for await (const opener of provider.getOpeners(targetUri)) { + allOpeners.set(opener.id, opener); + } + })); + return allOpeners; + } + + private getConfiguredOpenerForUri(openers: Map, targetUri: URI): IExternalUriOpener | 'default' | undefined { + const config = this.configurationService.getValue(externalUriOpenersSettingId) || {}; + for (const [uriGlob, id] of Object.entries(config)) { + if (testUrlMatchesGlob(targetUri.toString(), uriGlob)) { + if (id === defaultExternalUriOpenerId) { + return 'default'; + } + + const entry = openers.get(id); + if (entry) { + return entry; + } + } + } + return undefined; + } + + private async showOpenerPrompt( + openers: ReadonlyArray, + targetUri: URI, + ctx: { sourceUri: URI }, + token: CancellationToken + ): Promise { + type PickItem = IQuickPickItem & { opener?: IExternalUriOpener | 'configureDefault' }; + + const items: Array = openers.map((opener): PickItem => { + return { + label: opener.label, + opener: opener + }; + }); + items.push( + { + label: isWeb + ? nls.localize('selectOpenerDefaultLabel.web', 'Open in new browser window') + : nls.localize('selectOpenerDefaultLabel', 'Open in default browser'), + opener: undefined + }, + { type: 'separator' }, + { + label: nls.localize('selectOpenerConfigureTitle', "Configure default opener..."), + opener: 'configureDefault' + }); + + const picked = await this.quickInputService.pick(items, { + placeHolder: nls.localize('selectOpenerPlaceHolder', "How would you like to open: {0}", targetUri.toString()) + }); + + if (!picked) { + // Still cancel the default opener here since we prompted the user + return true; + } + + if (typeof picked.opener === 'undefined') { + return false; // Fallback to default opener + } else if (picked.opener === 'configureDefault') { + await this.preferencesService.openGlobalSettings(true, { + revealSetting: { key: externalUriOpenersSettingId, edit: true } + }); + return true; + } else { + return picked.opener.openExternalUri(targetUri, ctx, token); + } + } +} diff --git a/src/vs/workbench/contrib/externalUriOpener/test/common/externalUriOpenerService.test.ts b/src/vs/workbench/contrib/externalUriOpener/test/common/externalUriOpenerService.test.ts new file mode 100644 index 000000000..557424f74 --- /dev/null +++ b/src/vs/workbench/contrib/externalUriOpener/test/common/externalUriOpenerService.test.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 * as assert from 'assert'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { ExternalUriOpenerPriority } from 'vs/editor/common/modes'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IPickOptions, IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; +import { ExternalUriOpenerService, IExternalOpenerProvider, IExternalUriOpener } from 'vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService'; + + +class MockQuickInputService implements Partial{ + + constructor( + private readonly pickIndex: number + ) { } + + public pick(picks: Promise[]> | QuickPickInput[], options?: IPickOptions & { canPickMany: true }, token?: CancellationToken): Promise; + public pick(picks: Promise[]> | QuickPickInput[], options?: IPickOptions & { canPickMany: false }, token?: CancellationToken): Promise; + public async pick(picks: Promise[]> | QuickPickInput[], options?: Omit, 'canPickMany'>, token?: CancellationToken): Promise { + const resolvedPicks = await picks; + const item = resolvedPicks[this.pickIndex]; + if (item.type === 'separator') { + return undefined; + } + return item; + } + +} + +suite('ExternalUriOpenerService', () => { + + let instantiationService: TestInstantiationService; + + setup(() => { + instantiationService = new TestInstantiationService(); + + instantiationService.stub(IConfigurationService, new TestConfigurationService()); + instantiationService.stub(IOpenerService, { + registerExternalOpener: () => { return Disposable.None; } + }); + }); + + test('Should not open if there are no openers', async () => { + const externalUriOpenerService: ExternalUriOpenerService = instantiationService.createInstance(ExternalUriOpenerService); + + externalUriOpenerService.registerExternalOpenerProvider(new class implements IExternalOpenerProvider { + async *getOpeners(_targetUri: URI): AsyncGenerator { + // noop + } + }); + + const uri = URI.parse('http://contoso.com'); + const didOpen = await externalUriOpenerService.openExternal(uri.toString(), { sourceUri: uri }, CancellationToken.None); + assert.strictEqual(didOpen, false); + }); + + test('Should prompt if there is at least one enabled opener', async () => { + instantiationService.stub(IQuickInputService, new MockQuickInputService(0)); + + const externalUriOpenerService: ExternalUriOpenerService = instantiationService.createInstance(ExternalUriOpenerService); + + let openedWithEnabled = false; + externalUriOpenerService.registerExternalOpenerProvider(new class implements IExternalOpenerProvider { + async *getOpeners(_targetUri: URI): AsyncGenerator { + yield { + id: 'disabled-id', + label: 'disabled', + canOpen: async () => ExternalUriOpenerPriority.None, + openExternalUri: async () => true, + }; + yield { + id: 'enabled-id', + label: 'enabled', + canOpen: async () => ExternalUriOpenerPriority.Default, + openExternalUri: async () => { + openedWithEnabled = true; + return true; + } + }; + } + }); + + const uri = URI.parse('http://contoso.com'); + const didOpen = await externalUriOpenerService.openExternal(uri.toString(), { sourceUri: uri }, CancellationToken.None); + assert.strictEqual(didOpen, true); + assert.strictEqual(openedWithEnabled, true); + }); + + test('Should automatically pick single preferred opener without prompt', async () => { + const externalUriOpenerService: ExternalUriOpenerService = instantiationService.createInstance(ExternalUriOpenerService); + + let openedWithPreferred = false; + externalUriOpenerService.registerExternalOpenerProvider(new class implements IExternalOpenerProvider { + async *getOpeners(_targetUri: URI): AsyncGenerator { + yield { + id: 'other-id', + label: 'other', + canOpen: async () => ExternalUriOpenerPriority.Default, + openExternalUri: async () => { + return true; + } + }; + yield { + id: 'preferred-id', + label: 'preferred', + canOpen: async () => ExternalUriOpenerPriority.Preferred, + openExternalUri: async () => { + openedWithPreferred = true; + return true; + } + }; + } + }); + + const uri = URI.parse('http://contoso.com'); + const didOpen = await externalUriOpenerService.openExternal(uri.toString(), { sourceUri: uri }, CancellationToken.None); + assert.strictEqual(didOpen, true); + assert.strictEqual(openedWithPreferred, true); + }); +}); diff --git a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts index 7b93fdb20..1cc203a01 100644 --- a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts @@ -10,13 +10,11 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { BINARY_FILE_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { openEditorWith } from 'vs/workbench/services/editor/common/editorOpenWith'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; /** * An implementation of editor for binary files that cannot be displayed. @@ -29,9 +27,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IOpenerService private readonly openerService: IOpenerService, - @IEditorService private readonly editorService: IEditorService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IQuickInputService private readonly quickInputService: IQuickInputService, + @IInstantiationService private readonly instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, ) { @@ -55,7 +51,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { input.setForceOpenAsText(); // If more editors are installed that can handle this input, show a picker - await openEditorWith(input, undefined, options, this.group, this.editorService, this.configurationService, this.quickInputService); + await this.instantiationService.invokeFunction(openEditorWith, input, undefined, options, this.group); } } diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index ec837dc48..cd7fb061e 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { isFunction, assertIsDefined } from 'vs/base/common/types'; import { isValidBasename } from 'vs/base/common/extpath'; import { basename } from 'vs/base/common/resources'; -import { Action } from 'vs/base/common/actions'; +import { toAction } from 'vs/base/common/actions'; import { VIEWLET_ID, TEXT_FILE_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; import { ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; @@ -97,7 +97,7 @@ export class TextFileEditor extends BaseTextEditor { } getTitle(): string { - return this.input ? this.input.getName() : nls.localize('textFileEditor', "Text File Editor"); + return this.input ? this.input.getName() : localize('textFileEditor', "Text File Editor"); } get input(): FileEditorInput | undefined { @@ -169,22 +169,24 @@ export class TextFileEditor extends BaseTextEditor { if ((error).fileOperationResult === FileOperationResult.FILE_IS_DIRECTORY) { this.openAsFolder(input); - throw new Error(nls.localize('openFolderError', "File is a directory")); + throw new Error(localize('openFolderError', "File is a directory")); } // Offer to create a file from the error if we have a file not found and the name is valid if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND && isValidBasename(basename(input.preferredResource))) { throw createErrorWithActions(toErrorMessage(error), { actions: [ - new Action('workbench.files.action.createMissingFile', nls.localize('createFile', "Create File"), undefined, true, async () => { - await this.textFileService.create(input.preferredResource); + toAction({ + id: 'workbench.files.action.createMissingFile', label: localize('createFile', "Create File"), run: async () => { + await this.textFileService.create([{ resource: input.preferredResource }]); - return this.editorService.openEditor({ - resource: input.preferredResource, - options: { - pinned: true // new file gets pinned by default - } - }); + return this.editorService.openEditor({ + resource: input.preferredResource, + options: { + pinned: true // new file gets pinned by default + } + }); + } }) ] }); diff --git a/src/vs/workbench/contrib/files/browser/explorerService.ts b/src/vs/workbench/contrib/files/browser/explorerService.ts index a1a537578..fa078a16d 100644 --- a/src/vs/workbench/contrib/files/browser/explorerService.ts +++ b/src/vs/workbench/contrib/files/browser/explorerService.ts @@ -10,7 +10,7 @@ import { IFilesConfiguration, SortOrder } from 'vs/workbench/contrib/files/commo import { ExplorerItem, ExplorerModel } from 'vs/workbench/contrib/files/common/explorerModel'; import { URI } from 'vs/base/common/uri'; import { FileOperationEvent, FileOperation, IFileService, FileChangesEvent, FileChangeType, IResolveFileOptions } from 'vs/platform/files/common/files'; -import { dirname } from 'vs/base/common/resources'; +import { dirname, basename } from 'vs/base/common/resources'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -19,7 +19,7 @@ import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/ur import { IBulkEditService, ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService'; import { UndoRedoSource } from 'vs/platform/undoRedo/common/undoRedo'; import { IExplorerView, IExplorerService } from 'vs/workbench/contrib/files/browser/files'; -import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IProgressService, ProgressLocation, IProgressNotificationOptions, IProgressCompositeOptions } from 'vs/platform/progress/common/progress'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -28,7 +28,7 @@ export const UNDO_REDO_SOURCE = new UndoRedoSource(); export class ExplorerService implements IExplorerService { declare readonly _serviceBrand: undefined; - private static readonly EXPLORER_FILE_CHANGES_REACT_DELAY = 1000; // delay in ms to react to file changes to give our internal events a chance to react first + private static readonly EXPLORER_FILE_CHANGES_REACT_DELAY = 500; // delay in ms to react to file changes to give our internal events a chance to react first private readonly disposables = new DisposableStore(); private editable: { stat: ExplorerItem, data: IEditableData } | undefined; @@ -60,17 +60,32 @@ export class ExplorerService implements IExplorerService { this.fileChangeEvents = []; // Filter to the ones we care - const types = [FileChangeType.ADDED, FileChangeType.DELETED]; + const types = [FileChangeType.DELETED]; if (this._sortOrder === SortOrder.Modified) { types.push(FileChangeType.UPDATED); } let shouldRefresh = false; + // For DELETED and UPDATED events go through the explorer model and check if any of the items got affected this.roots.forEach(r => { if (this.view && !shouldRefresh) { shouldRefresh = doesFileEventAffect(r, this.view, events, types); } }); + // For ADDED events we need to go through all the events and check if the explorer is already aware of some of them + // Or if they affect not yet resolved parts of the explorer. If that is the case we will not refresh. + events.forEach(e => { + if (!shouldRefresh) { + const added = e.getAdded(); + if (added.some(a => { + const parent = this.model.findClosest(dirname(a.resource)); + // Parent of the added resource is resolved and the explorer model is not aware of the added resource - we need to refresh + return parent && !parent.getChild(basename(a.resource)); + })) { + shouldRefresh = true; + } + } + }); if (shouldRefresh) { await this.refresh(false); @@ -95,7 +110,7 @@ export class ExplorerService implements IExplorerService { }); if (affected) { if (this.view) { - await this.view.refresh(true); + await this.view.setTreeInput(); } } })); @@ -125,19 +140,20 @@ export class ExplorerService implements IExplorerService { return this.view.getContext(respectMultiSelection); } - async applyBulkEdit(edit: ResourceFileEdit[], options: { undoLabel: string, progressLabel: string }): Promise { + async applyBulkEdit(edit: ResourceFileEdit[], options: { undoLabel: string, progressLabel: string, confirmBeforeUndo?: boolean, progressLocation?: ProgressLocation.Explorer | ProgressLocation.Window }): Promise { const cancellationTokenSource = new CancellationTokenSource(); - const promise = this.progressService.withProgress({ - location: ProgressLocation.Window, - delay: 500, + const promise = this.progressService.withProgress({ + location: options.progressLocation || ProgressLocation.Window, title: options.progressLabel, - cancellable: edit.length > 1 // Only allow cancellation when there is more than one edit. Since cancelling will not actually stop the current edit that is in progress. + cancellable: edit.length > 1, // Only allow cancellation when there is more than one edit. Since cancelling will not actually stop the current edit that is in progress. + delay: 500, }, async progress => { await this.bulkEditService.apply(edit, { undoRedoSource: UNDO_REDO_SOURCE, label: options.undoLabel, progress, - token: cancellationTokenSource.token + token: cancellationTokenSource.token, + confirmBeforeUndo: options.confirmBeforeUndo }); }, () => cancellationTokenSource.cancel()); await this.progressService.withProgress({ location: ProgressLocation.Explorer, delay: 500 }, () => promise); @@ -345,11 +361,11 @@ export class ExplorerService implements IExplorerService { } function doesFileEventAffect(item: ExplorerItem, view: IExplorerView, events: FileChangesEvent[], types: FileChangeType[]): boolean { - if (events.some(e => e.affects(item.resource, ...types))) { - return true; - } for (let [_name, child] of item.children) { if (view.isItemVisible(child)) { + if (events.some(e => e.contains(child.resource, ...types))) { + return true; + } if (child.isDirectory && child.isDirectoryResolved) { if (doesFileEventAffect(child, view, events, types)) { return true; diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 9745ec52d..4753cc33d 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -28,7 +28,8 @@ import { DelegatingEditorService } from 'vs/workbench/services/editor/browser/ed import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorPane } from 'vs/workbench/common/editor'; -import { ViewPane, ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { KeyChord, KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; @@ -41,6 +42,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; const explorerViewIcon = registerIcon('explorer-view-icon', Codicon.files, localize('explorerViewIcon', 'View icon of the explorer view.')); +const openEditorsViewIcon = registerIcon('open-editors-view-icon', Codicon.book, localize('openEditorsIcon', 'View icon of the open editors view.')); export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution { @@ -111,12 +113,13 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor id: OpenEditorsView.ID, name: OpenEditorsView.NAME, ctorDescriptor: new SyncDescriptor(OpenEditorsView), - containerIcon: explorerViewIcon, + containerIcon: openEditorsViewIcon, order: 0, when: OpenEditorsVisibleContext, canToggleVisibility: true, canMoveView: true, - collapsed: true, + collapsed: false, + hideByDefault: true, focusCommand: { id: 'workbench.files.action.focusOpenEditorsView', keybindings: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_E) } @@ -207,7 +210,7 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { let delay = 0; const config = this.configurationService.getValue(); - const delayEditorOpeningInOpenedEditors = !!config.workbench.editor.enablePreview; // No need to delay if preview is disabled + const delayEditorOpeningInOpenedEditors = !!config.workbench?.editor?.enablePreview; // No need to delay if preview is disabled const activeGroup = this.editorGroupService.activeGroup; if (delayEditorOpeningInOpenedEditors && group === activeGroup && !activeGroup.previewEditor) { diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 80deb8269..c9b2dcd2a 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -5,12 +5,12 @@ import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ToggleAutoSaveAction, FocusFilesExplorer, GlobalCompareResourcesAction, SaveAllAction, ShowActiveFileInExplorer, CollapseExplorerView, RefreshExplorerView, CompareWithClipboardAction, NEW_FILE_COMMAND_ID, NEW_FILE_LABEL, NEW_FOLDER_COMMAND_ID, NEW_FOLDER_LABEL, TRIGGER_RENAME_LABEL, MOVE_FILE_TO_TRASH_LABEL, COPY_FILE_LABEL, PASTE_FILE_LABEL, FileCopiedContext, renameHandler, moveFileToTrashHandler, copyFileHandler, pasteFileHandler, deleteFileHandler, cutFileHandler, DOWNLOAD_COMMAND_ID, openFilePreserveFocusHandler, DOWNLOAD_LABEL, ShowOpenedFileInNewWindow } from 'vs/workbench/contrib/files/browser/fileActions'; +import { ToggleAutoSaveAction, FocusFilesExplorer, GlobalCompareResourcesAction, ShowActiveFileInExplorer, CompareWithClipboardAction, NEW_FILE_COMMAND_ID, NEW_FILE_LABEL, NEW_FOLDER_COMMAND_ID, NEW_FOLDER_LABEL, TRIGGER_RENAME_LABEL, MOVE_FILE_TO_TRASH_LABEL, COPY_FILE_LABEL, PASTE_FILE_LABEL, FileCopiedContext, renameHandler, moveFileToTrashHandler, copyFileHandler, pasteFileHandler, deleteFileHandler, cutFileHandler, DOWNLOAD_COMMAND_ID, openFilePreserveFocusHandler, DOWNLOAD_LABEL, ShowOpenedFileInNewWindow } from 'vs/workbench/contrib/files/browser/fileActions'; import { revertLocalChangesCommand, acceptLocalChangesCommand, CONFLICT_RESOLUTION_CONTEXT } from 'vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler'; import { SyncActionDescriptor, MenuId, MenuRegistry, ILocalizedString } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; -import { openWindowCommand, 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, newWindowCommand, OpenEditorsReadonlyEditorContext, OPEN_WITH_EXPLORER_COMMAND_ID, NEW_UNTITLED_FILE_COMMAND_ID, NEW_UNTITLED_FILE_LABEL } from 'vs/workbench/contrib/files/browser/fileCommands'; +import { openWindowCommand, 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, newWindowCommand, 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/fileCommands'; 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'; @@ -36,12 +36,9 @@ import { Codicon } from 'vs/base/common/codicons'; const category = { value: nls.localize('filesCategory', "File"), original: 'File' }; const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(SyncActionDescriptor.from(SaveAllAction, { primary: undefined, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_S }, win: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_S) } }), 'File: Save All', category.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(GlobalCompareResourcesAction), 'File: Compare Active File With...', category.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(FocusFilesExplorer), 'File: Focus on Files Explorer', category.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(ShowActiveFileInExplorer), 'File: Reveal Active File in Side Bar', category.value); -registry.registerWorkbenchAction(SyncActionDescriptor.from(CollapseExplorerView), 'File: Collapse Folders in Explorer', category.value); -registry.registerWorkbenchAction(SyncActionDescriptor.from(RefreshExplorerView), 'File: Refresh Explorer', category.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(CompareWithClipboardAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_C) }), 'File: Compare Active File with Clipboard', category.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleAutoSaveAction), 'File: Toggle Auto Save', category.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(ShowOpenedFileInNewWindow, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_O) }), 'File: Open Active File in New Window', category.value, EmptyWorkspaceSupportContext); @@ -612,7 +609,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: '4_save', command: { - id: SaveAllAction.ID, + id: SAVE_ALL_COMMAND_ID, title: nls.localize({ key: 'miSaveAll', comment: ['&& denotes a mnemonic'] }, "Save A&&ll"), precondition: DirtyWorkingCopiesContext }, diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 46aacd31f..69d3c400c 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -15,13 +15,12 @@ import { DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/com import { VIEWLET_ID, IFilesConfiguration, VIEW_ID } from 'vs/workbench/contrib/files/common/files'; import { ByteSize, IFileService, IFileStatWithMetadata } from 'vs/platform/files/common/files'; import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; -import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { IQuickInputService, ItemActivation } from 'vs/platform/quickinput/common/quickInput'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ITextModel } from 'vs/editor/common/model'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { REVEAL_IN_EXPLORER_COMMAND_ID, SAVE_ALL_COMMAND_ID, SAVE_ALL_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, NEW_UNTITLED_FILE_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileCommands'; +import { REVEAL_IN_EXPLORER_COMMAND_ID, SAVE_ALL_IN_GROUP_COMMAND_ID, NEW_UNTITLED_FILE_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileCommands'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -83,40 +82,6 @@ async function refreshIfSeparator(value: string, explorerService: IExplorerServi } } -/* New File */ -export class NewFileAction extends Action { - static readonly ID = 'workbench.files.action.createFileFromExplorer'; - static readonly LABEL = nls.localize('createNewFile', "New File"); - - constructor( - @ICommandService private commandService: ICommandService - ) { - super('explorer.newFile', NEW_FILE_LABEL); - this.class = 'explorer-action ' + Codicon.newFile.classNames; - } - - run(): Promise { - return this.commandService.executeCommand(NEW_FILE_COMMAND_ID); - } -} - -/* New Folder */ -export class NewFolderAction extends Action { - static readonly ID = 'workbench.files.action.createFolderFromExplorer'; - static readonly LABEL = nls.localize('createNewFolder', "New Folder"); - - constructor( - @ICommandService private commandService: ICommandService - ) { - super('explorer.newFolder', NEW_FOLDER_LABEL); - this.class = 'explorer-action ' + Codicon.newFolder.classNames; - } - - run(): Promise { - return this.commandService.executeCommand(NEW_FOLDER_COMMAND_ID); - } -} - async function deleteFiles(explorerService: IExplorerService, workingCopyFileService: IWorkingCopyFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise { let primaryButton: string; if (useTrash) { @@ -168,6 +133,9 @@ async function deleteFiles(explorerService: IExplorerService, workingCopyFileSer } let confirmation: IConfirmationResult; + // We do not support undo of folders, so in that case the delete action is irreversible + const deleteDetail = distinctElements.some(e => e.isDirectory) ? nls.localize('irreversible', "This action is irreversible!") : + distinctElements.length > 1 ? nls.localize('restorePlural', "You can restore these files using the Undo command") : nls.localize('restore', "You can restore this file using the Undo command"); // Check if we need to ask for confirmation at all if (skipConfirm || (useTrash && configurationService.getValue(CONFIRM_DELETE_SETTING_KEY) === false)) { @@ -199,7 +167,7 @@ async function deleteFiles(explorerService: IExplorerService, workingCopyFileSer else { let { message, detail } = getDeleteMessage(distinctElements); detail += detail ? '\n' : ''; - detail += nls.localize('irreversible', "This action is irreversible!"); + detail += deleteDetail; confirmation = await dialogService.confirm({ message, detail, @@ -222,8 +190,8 @@ async function deleteFiles(explorerService: IExplorerService, workingCopyFileSer try { const resourceFileEdits = distinctElements.map(e => new ResourceFileEdit(e.resource, undefined, { recursive: true, folder: e.isDirectory, skipTrashBin: !useTrash, maxSize: MAX_UNDO_FILE_SIZE })); const options = { - undoLabel: distinctElements.length > 1 ? nls.localize('deleteBulkEdit', "Delete {0} files", distinctElements.length) : nls.localize('deleteFileBulkEdit', "Delete {0}", distinctElements[0].name), - progressLabel: distinctElements.length > 1 ? nls.localize('deletingBulkEdit', "Deleting {0} files", distinctElements.length) : nls.localize('deletingFileBulkEdit', "Deleting {0}", distinctElements[0].name), + undoLabel: distinctElements.length > 1 ? nls.localize({ key: 'deleteBulkEdit', comment: ['Placeholder will be replaced by the number of files deleted'] }, "Delete {0} files", distinctElements.length) : nls.localize({ key: 'deleteFileBulkEdit', comment: ['Placeholder will be replaced by the name of the file deleted'] }, "Delete {0}", distinctElements[0].name), + progressLabel: distinctElements.length > 1 ? nls.localize({ key: 'deletingBulkEdit', comment: ['Placeholder will be replaced by the number of files deleted'] }, "Deleting {0} files", distinctElements.length) : nls.localize({ key: 'deletingFileBulkEdit', comment: ['Placeholder will be replaced by the name of the file deleted'] }, "Deleting {0}", distinctElements[0].name), }; await explorerService.applyBulkEdit(resourceFileEdits, options); } catch (error) { @@ -234,7 +202,7 @@ async function deleteFiles(explorerService: IExplorerService, workingCopyFileSer let primaryButton: string; if (useTrash) { errorMessage = isWindows ? nls.localize('binFailed', "Failed to delete using the Recycle Bin. Do you want to permanently delete instead?") : nls.localize('trashFailed', "Failed to delete using the Trash. Do you want to permanently delete instead?"); - detailMessage = nls.localize('irreversible', "This action is irreversible!"); + detailMessage = deleteDetail; primaryButton = nls.localize({ key: 'deletePermanentlyButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Delete Permanently"); } else { errorMessage = toErrorMessage(error, false); @@ -411,6 +379,32 @@ export function incrementFileName(name: string, isFolder: boolean, incrementalNa return `${name.substr(0, lastIndexOfDot)}.1${name.substr(lastIndexOfDot)}`; } + // 123 => 124 + let noNameNoExtensionRegex = RegExp('(\\d+)$'); + if (!isFolder && lastIndexOfDot === -1 && name.match(noNameNoExtensionRegex)) { + return name.replace(noNameNoExtensionRegex, (match, g1?) => { + let number = parseInt(g1); + return number < maxNumber + ? String(number + 1).padStart(g1.length, '0') + : `${g1}.1`; + }); + } + + // file => file1 + // file1 => file2 + let noExtensionRegex = RegExp('(.*)(\d*)$'); + if (!isFolder && lastIndexOfDot === -1 && name.match(noExtensionRegex)) { + return name.replace(noExtensionRegex, (match, g1?, g2?) => { + let number = parseInt(g2); + if (isNaN(number)) { + number = 0; + } + return number < maxNumber + ? g1 + String(number + 1).padStart(g2.length, '0') + : `${g1}${g2}.1`; + }); + } + // folder.1=>folder.2 if (isFolder && name.match(/(\d+)$/)) { return name.replace(/(\d+)$/, (match, ...groups) => { @@ -560,20 +554,6 @@ export abstract class BaseSaveAllAction extends Action { } } -export class SaveAllAction extends BaseSaveAllAction { - - static readonly ID = 'workbench.action.files.saveAll'; - static readonly LABEL = SAVE_ALL_LABEL; - - get class(): string { - return 'explorer-action ' + Codicon.saveAll.classNames; - } - - protected doRun(): Promise { - return this.commandService.executeCommand(SAVE_ALL_COMMAND_ID); - } -} - export class SaveAllInGroupAction extends BaseSaveAllAction { static readonly ID = 'workbench.files.action.saveAllInGroup'; @@ -645,48 +625,6 @@ export class ShowActiveFileInExplorer extends Action { } } -export class CollapseExplorerView extends Action { - - static readonly ID = 'workbench.files.action.collapseExplorerFolders'; - static readonly LABEL = nls.localize('collapseExplorerFolders', "Collapse Folders in Explorer"); - - constructor(id: string, - label: string, - @IViewletService private readonly viewletService: IViewletService, - @IExplorerService readonly explorerService: IExplorerService - ) { - super(id, label, 'explorer-action ' + Codicon.collapseAll.classNames); - } - - async run(): Promise { - const explorerViewlet = (await this.viewletService.openViewlet(VIEWLET_ID))?.getViewPaneContainer() as ExplorerViewPaneContainer; - const explorerView = explorerViewlet.getExplorerView(); - if (explorerView) { - explorerView.collapseAll(); - } - } -} - -export class RefreshExplorerView extends Action { - - static readonly ID = 'workbench.files.action.refreshFilesExplorer'; - static readonly LABEL = nls.localize('refreshExplorer', "Refresh Explorer"); - - - constructor( - id: string, label: string, - @IViewletService private readonly viewletService: IViewletService, - @IExplorerService private readonly explorerService: IExplorerService - ) { - super(id, label, 'explorer-action ' + Codicon.refresh.classNames); - } - - async run(): Promise { - await this.viewletService.openViewlet(VIEWLET_ID); - await this.explorerService.refresh(); - } -} - export class ShowOpenedFileInNewWindow extends Action { static readonly ID = 'workbench.action.files.showOpenedFileInNewWindow'; @@ -915,7 +853,8 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole const resourceToCreate = resources.joinPath(folder.resource, value); await explorerService.applyBulkEdit([new ResourceFileEdit(undefined, resourceToCreate, { folder: isFolder })], { undoLabel: nls.localize('createBulkEdit', "Create {0}", value), - progressLabel: nls.localize('creatingBulkEdit', "Creating {0}", value) + progressLabel: nls.localize('creatingBulkEdit', "Creating {0}", value), + confirmBeforeUndo: true }); await refreshIfSeparator(value, explorerService); @@ -1242,7 +1181,7 @@ const downloadFileHandler = async (accessor: ServicesAccessor) => { const destination = await fileDialogService.showSaveDialog({ availableFileSystems: [Schemas.file], saveLabel: mnemonicButtonLabel(nls.localize('downloadButton', "Download")), - title: explorerItem.isDirectory ? nls.localize('downloadFolder', "Download Folder") : nls.localize('downloadFile', "Download File"), + title: nls.localize('chooseWhereToDownload', "Choose Where to Download"), defaultUri }); @@ -1250,6 +1189,7 @@ const downloadFileHandler = async (accessor: ServicesAccessor) => { await explorerService.applyBulkEdit([new ResourceFileEdit(explorerItem.resource, destination, { overwrite: true, copy: true })], { undoLabel: nls.localize('downloadBulkEdit', "Download {0}", explorerItem.name), progressLabel: nls.localize('downloadingBulkEdit', "Downloading {0}", explorerItem.name), + progressLocation: ProgressLocation.Explorer }); } else { cts.cancel(); // User canceled a download. In case there were multiple files selected we should cancel the remainder of the prompts #86100 @@ -1305,15 +1245,19 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { if (pasteShouldMove) { const resourceFileEdits = sourceTargetPairs.map(pair => new ResourceFileEdit(pair.source, pair.target)); const options = { - progressLabel: sourceTargetPairs.length > 1 ? nls.localize('movingBulkEdit', "Moving {0} files", sourceTargetPairs.length) : nls.localize('movingFileBulkEdit', "Moving {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target)), - undoLabel: sourceTargetPairs.length > 1 ? nls.localize('moveBulkEdit', "Move {0} files", sourceTargetPairs.length) : nls.localize('moveFileBulkEdit', "Move {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target)) + progressLabel: sourceTargetPairs.length > 1 ? nls.localize({ key: 'movingBulkEdit', comment: ['Placeholder will be replaced by the number of files being moved'] }, "Moving {0} files", sourceTargetPairs.length) + : nls.localize({ key: 'movingFileBulkEdit', comment: ['Placeholder will be replaced by the name of the file moved.'] }, "Moving {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target)), + undoLabel: sourceTargetPairs.length > 1 ? nls.localize({ key: 'moveBulkEdit', comment: ['Placeholder will be replaced by the number of files being moved'] }, "Move {0} files", sourceTargetPairs.length) + : nls.localize({ key: 'moveFileBulkEdit', comment: ['Placeholder will be replaced by the name of the file moved.'] }, "Move {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target)) }; await explorerService.applyBulkEdit(resourceFileEdits, options); } else { const resourceFileEdits = sourceTargetPairs.map(pair => new ResourceFileEdit(pair.source, pair.target, { copy: true })); const options = { - progressLabel: sourceTargetPairs.length > 1 ? nls.localize('copyingBulkEdit', "Copying {0} files", sourceTargetPairs.length) : nls.localize('copyingFileBulkEdit', "Copying {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target)), - undoLabel: sourceTargetPairs.length > 1 ? nls.localize('copyBulkEdit', "Copy {0} files", sourceTargetPairs.length) : nls.localize('copyFileBulkEdit', "Copy {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target)) + progressLabel: sourceTargetPairs.length > 1 ? nls.localize({ key: 'copyingBulkEdit', comment: ['Placeholder will be replaced by the number of files being copied'] }, "Copying {0} files", sourceTargetPairs.length) + : nls.localize({ key: 'copyingFileBulkEdit', comment: ['Placeholder will be replaced by the name of the file copied.'] }, "Copying {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target)), + undoLabel: sourceTargetPairs.length > 1 ? nls.localize({ key: 'copyBulkEdit', comment: ['Placeholder will be replaced by the number of files being copied'] }, "Copy {0} files", sourceTargetPairs.length) + : nls.localize({ key: 'copyFileBulkEdit', comment: ['Placeholder will be replaced by the name of the file copied.'] }, "Copy {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target)) }; await explorerService.applyBulkEdit(resourceFileEdits, options); } diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 8e1badc5f..ef6eb4dd9 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -40,8 +40,6 @@ import { coalesce } from 'vs/base/common/arrays'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { openEditorWith } from 'vs/workbench/services/editor/common/editorOpenWith'; import { isPromiseCanceledError } from 'vs/base/common/errors'; @@ -356,13 +354,11 @@ CommandsRegistry.registerCommand({ handler: async (accessor, resource: URI | object) => { const editorService = accessor.get(IEditorService); const editorGroupsService = accessor.get(IEditorGroupsService); - const configurationService = accessor.get(IConfigurationService); - const quickInputService = accessor.get(IQuickInputService); const uri = getResourceForCommand(resource, accessor.get(IListService), accessor.get(IEditorService)); if (uri) { const input = editorService.createEditorInput({ resource: uri }); - openEditorWith(input, undefined, undefined, editorGroupsService.activeGroup, editorService, configurationService, quickInputService); + openEditorWith(accessor, input, undefined, undefined, editorGroupsService.activeGroup); } } }); @@ -482,7 +478,12 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -CommandsRegistry.registerCommand({ +KeybindingsRegistry.registerCommandAndKeybindingRule({ + when: undefined, + weight: KeybindingWeight.WorkbenchContrib, + primary: undefined, + mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_S }, + win: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_S) }, id: SAVE_ALL_COMMAND_ID, handler: (accessor) => { return saveDirtyEditorsOfGroups(accessor, accessor.get(IEditorGroupsService).getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE), { reason: SaveReason.EXPLICIT }); @@ -664,12 +665,10 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ if (typeof args?.viewType === 'string') { const editorGroupsService = accessor.get(IEditorGroupsService); - const configurationService = accessor.get(IConfigurationService); - const quickInputService = accessor.get(IQuickInputService); const textInput = editorService.createEditorInput({ options: { pinned: true } }); const group = editorGroupsService.activeGroup; - await openEditorWith(textInput, args.viewType, { pinned: true }, group, editorService, configurationService, quickInputService); + await openEditorWith(accessor, textInput, args.viewType, { pinned: true }, group); } else { await editorService.openEditor({ options: { pinned: true } }); // untitled are always pinned } diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 7ea77d336..f4b223bf4 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -198,8 +198,8 @@ const hotExitConfiguration: IConfigurationPropertySchema = platform.isNative ? 'default': HotExitConfiguration.ON_EXIT, 'markdownEnumDescriptions': [ nls.localize('hotExit.off', 'Disable hot exit. A prompt will show when attempting to close a window with dirty files.'), - nls.localize('hotExit.onExit', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit` command is triggered (command palette, keybinding, menu). All windows without folders opened will be restored upon next launch. A list of workspaces with unsaved files can be accessed via `File > Open Recent > More...`'), - nls.localize('hotExit.onExitAndWindowClose', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit` command is triggered (command palette, keybinding, menu), and also for any window with a folder opened regardless of whether it\'s the last window. All windows without folders opened will be restored upon next launch. A list of workspaces with unsaved files can be accessed via `File > Open Recent > More...`') + nls.localize('hotExit.onExit', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit` command is triggered (command palette, keybinding, menu). All windows without folders opened will be restored upon next launch. A list of previously opened windows with unsaved files can be accessed via `File > Open Recent > More...`'), + nls.localize('hotExit.onExitAndWindowClose', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit` command is triggered (command palette, keybinding, menu), and also for any window with a folder opened regardless of whether it\'s the last window. All windows without folders opened will be restored upon next launch. A list of previously opened windows with unsaved files can be accessed via `File > Open Recent > More...`') ], 'description': nls.localize('hotExit', "Controls whether unsaved files are remembered between sessions, allowing the save prompt when exiting the editor to be skipped.", HotExitConfiguration.ON_EXIT, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) } : { @@ -386,7 +386,7 @@ configurationRegistry.registerConfiguration({ nls.localize({ key: 'everything', comment: ['This is the description of an option'] }, "Format the whole file."), nls.localize({ key: 'modification', comment: ['This is the description of an option'] }, "Format modifications (requires source control)."), ], - 'markdownDescription': nls.localize('formatOnSaveMode', "Controls if format on save formats the whole file or only modifications. Only applies when `#editor.formatOnSave#` is `true`."), + 'markdownDescription': nls.localize('formatOnSaveMode', "Controls if format on save formats the whole file or only modifications. Only applies when `#editor.formatOnSave#` is enabled."), 'scope': ConfigurationScope.LANGUAGE_OVERRIDABLE, }, } @@ -492,7 +492,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { UndoCommand.addImplementation(110, (accessor: ServicesAccessor) => { const undoRedoService = accessor.get(IUndoRedoService); const explorerService = accessor.get(IExplorerService); - if (explorerService.hasViewFocus()) { + if (explorerService.hasViewFocus() && undoRedoService.canUndo(UNDO_REDO_SOURCE)) { undoRedoService.undo(UNDO_REDO_SOURCE); return true; } @@ -503,7 +503,7 @@ UndoCommand.addImplementation(110, (accessor: ServicesAccessor) => { RedoCommand.addImplementation(110, (accessor: ServicesAccessor) => { const undoRedoService = accessor.get(IUndoRedoService); const explorerService = accessor.get(IExplorerService); - if (explorerService.hasViewFocus()) { + if (explorerService.hasViewFocus() && undoRedoService.canRedo(UNDO_REDO_SOURCE)) { undoRedoService.redo(UNDO_REDO_SOURCE); return true; } diff --git a/src/vs/workbench/contrib/files/browser/files.ts b/src/vs/workbench/contrib/files/browser/files.ts index 95e137cb3..bd0880343 100644 --- a/src/vs/workbench/contrib/files/browser/files.ts +++ b/src/vs/workbench/contrib/files/browser/files.ts @@ -16,6 +16,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IEditableData } from 'vs/workbench/common/views'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService'; +import { ProgressLocation } from 'vs/platform/progress/common/progress'; export interface IExplorerService { @@ -34,7 +35,7 @@ export interface IExplorerService { refresh(): Promise; setToCopy(stats: ExplorerItem[], cut: boolean): Promise; isCut(stat: ExplorerItem): boolean; - applyBulkEdit(edit: ResourceFileEdit[], options: { undoLabel: string, progressLabel: string }): Promise; + applyBulkEdit(edit: ResourceFileEdit[], options: { undoLabel: string, progressLabel: string, confirmBeforeUndo?: boolean, progressLocation?: ProgressLocation.Explorer | ProgressLocation.Window }): Promise; /** * Selects and reveal the file element provided by the given resource if its found in the explorer. diff --git a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css index 8d645ccaa..29bad5473 100644 --- a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css @@ -89,7 +89,7 @@ height: 22px; } -.monaco-workbench .explorer-viewlet .explorer-item .monaco-inputbox > .wrapper > .input { +.monaco-workbench .explorer-viewlet .explorer-item .monaco-inputbox > .ibwrapper > .input { padding: 0; height: 20px; } diff --git a/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts index d649859fc..7e70f008f 100644 --- a/src/vs/workbench/contrib/files/browser/views/emptyView.ts +++ b/src/vs/workbench/contrib/files/browser/views/emptyView.ts @@ -11,7 +11,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { ResourcesDropHandler, DragAndDropObserver } from 'vs/workbench/browser/dnd'; import { listDropBackground } from 'vs/platform/theme/common/colorRegistry'; import { ILabelService } from 'vs/platform/label/common/label'; diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index a9309b467..59ca8405e 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -8,30 +8,30 @@ import { URI } from 'vs/base/common/uri'; import * as perf from 'vs/base/common/performance'; import { IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { memoize } from 'vs/base/common/decorators'; -import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, ExplorerResourceAvailableEditorIdsContext } from 'vs/workbench/contrib/files/common/files'; -import { NewFolderAction, NewFileAction, FileCopiedContext, RefreshExplorerView, CollapseExplorerView } from 'vs/workbench/contrib/files/browser/fileActions'; +import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, ExplorerResourceAvailableEditorIdsContext, VIEW_ID, VIEWLET_ID } from 'vs/workbench/contrib/files/common/files'; +import { FileCopiedContext, NEW_FILE_COMMAND_ID, NEW_FOLDER_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileActions'; import * as DOM from 'vs/base/browser/dom'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ExplorerDecorationsProvider } from 'vs/workbench/contrib/files/browser/views/explorerDecorationsProvider'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, IContextKey, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { IDecorationsService } from 'vs/workbench/services/decorations/browser/decorations'; import { WorkbenchCompressibleAsyncDataTree } from 'vs/platform/list/browser/listService'; import { DelayedDragHandler } from 'vs/base/browser/dnd'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IViewPaneOptions, ViewPane, ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; import { ILabelService } from 'vs/platform/label/common/label'; import { ExplorerDelegate, ExplorerDataSource, FilesRenderer, ICompressedNavigationController, FilesFilter, FileSorter, FileDragAndDrop, ExplorerCompressionDelegate, isCompressedFolderName } from 'vs/workbench/contrib/files/browser/views/explorerViewer'; import { IThemeService, IFileIconTheme } from 'vs/platform/theme/common/themeService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ITreeContextMenuEvent, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; -import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId, IMenu, Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; @@ -52,6 +52,9 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { IExplorerService } from 'vs/workbench/contrib/files/browser/files'; +import { Codicon } from 'vs/base/common/codicons'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; interface IExplorerViewColors extends IColorMapping { listDropBackground?: ColorValue | undefined; @@ -75,6 +78,16 @@ function hasExpandedRootChild(tree: WorkbenchCompressibleAsyncDataTree { + if (stat instanceof NewExplorerItem) { + return `new:${stat.resource}`; + } + + return stat.resource; + } +}; + export function getContext(focus: ExplorerItem[], selection: ExplorerItem[], respectMultiSelection: boolean, compressedNavigationControllerProvider: { getCompressedNavigationController(stat: ExplorerItem): ICompressedNavigationController | undefined }): ExplorerItem[] { @@ -146,7 +159,6 @@ export class ExplorerView extends ViewPane { private shouldRefresh = true; private dragHandler!: DelayedDragHandler; private autoReveal: boolean | 'focusNoScroll' = false; - private actions: IAction[] | undefined; private decorationsProvider: ExplorerDecorationsProvider | undefined; constructor( @@ -284,19 +296,6 @@ export class ExplorerView extends ViewPane { })); } - getActions(): IAction[] { - if (!this.actions) { - this.actions = [ - this.instantiationService.createInstance(NewFileAction), - this.instantiationService.createInstance(NewFolderAction), - this.instantiationService.createInstance(RefreshExplorerView, RefreshExplorerView.ID, RefreshExplorerView.LABEL), - this.instantiationService.createInstance(CollapseExplorerView, CollapseExplorerView.ID, CollapseExplorerView.LABEL) - ]; - this.actions.forEach(a => this._register(a)); - } - return this.actions; - } - focus(): void { this.tree.domFocus(); @@ -381,15 +380,7 @@ export class ExplorerView extends ViewPane { this.instantiationService.createInstance(ExplorerDataSource), { compressionEnabled: isCompressionEnabled(), accessibilityProvider: this.renderer, - identityProvider: { - getId: (stat: ExplorerItem) => { - if (stat instanceof NewExplorerItem) { - return `new:${stat.resource}`; - } - - return stat.resource; - } - }, + identityProvider, keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (stat: ExplorerItem) => { if (this.explorerService.isEditable(stat)) { @@ -593,7 +584,9 @@ export class ExplorerView extends ViewPane { } const toRefresh = item || this.tree.getInput(); - return this.tree.updateChildren(toRefresh, recursive); + return this.tree.updateChildren(toRefresh, recursive, false, { + diffIdentityProvider: identityProvider + }); } focusNeighbourIfItemFocused(item: ExplorerItem): void { @@ -635,7 +628,7 @@ export class ExplorerView extends ViewPane { const initialInputSetup = !this.tree.getInput(); if (initialInputSetup) { - perf.mark('willResolveExplorer'); + perf.mark('code/willResolveExplorer'); } const roots = this.explorerService.roots; let input: ExplorerItem | ExplorerItem[] = roots[0]; @@ -675,7 +668,7 @@ export class ExplorerView extends ViewPane { } } if (initialInputSetup) { - perf.mark('didResolveExplorer'); + perf.mark('code/didResolveExplorer'); } }); @@ -856,3 +849,93 @@ function createFileIconThemableTreeContainerScope(container: HTMLElement, themeS onDidChangeFileIconTheme(themeService.getFileIconTheme()); return themeService.onDidFileIconThemeChange(onDidChangeFileIconTheme); } + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.files.action.createFileFromExplorer', + title: nls.localize('createNewFile', "New File"), + f1: false, + icon: Codicon.newFile, + menu: { + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyEqualsExpr.create('view', VIEW_ID), + order: 10 + } + }); + } + + run(accessor: ServicesAccessor): void { + const commandService = accessor.get(ICommandService); + commandService.executeCommand(NEW_FILE_COMMAND_ID); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.files.action.createFolderFromExplorer', + title: nls.localize('createNewFolder', "New Folder"), + f1: false, + icon: Codicon.newFolder, + menu: { + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyEqualsExpr.create('view', VIEW_ID), + order: 20 + } + }); + } + + run(accessor: ServicesAccessor): void { + const commandService = accessor.get(ICommandService); + commandService.executeCommand(NEW_FOLDER_COMMAND_ID); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.files.action.refreshFilesExplorer', + title: nls.localize('refreshExplorer', "Refresh Explorer"), + f1: true, + icon: Codicon.refresh, + menu: { + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyEqualsExpr.create('view', VIEW_ID), + order: 30 + } + }); + } + + async run(accessor: ServicesAccessor): Promise { + const viewletService = accessor.get(IViewletService); + const explorerService = accessor.get(IExplorerService); + await viewletService.openViewlet(VIEWLET_ID); + await explorerService.refresh(); + } +}); + +registerAction2(class extends ViewAction { + constructor() { + super({ + id: 'workbench.files.action.collapseExplorerFolders', + title: nls.localize('collapseExplorerFolders', "Collapse Folders in Explorer"), + viewId: VIEW_ID, + f1: true, + icon: Codicon.collapseAll, + menu: { + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyEqualsExpr.create('view', VIEW_ID), + order: 40 + } + }); + } + + runInView(_accessor: ServicesAccessor, view: ExplorerView): void { + view.collapseAll(); + } +}); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 9e8330b3d..6bf781d2e 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -451,7 +451,7 @@ export class FilesRenderer implements ICompressibleTreeRenderer { if (e.equals(KeyCode.Enter)) { - if (inputBox.validate()) { + if (!inputBox.validate()) { done(true, true); } } else if (e.equals(KeyCode.Escape)) { @@ -626,7 +626,7 @@ export class FilesFilter implements ITreeFilter { stat.isExcluded = true; return false; } - if (this.explorerService.getEditableData(stat) || stat.isRoot) { + if (this.explorerService.getEditableData(stat)) { return true; // always visible } diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 3535108c3..db0b429cf 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -9,16 +9,15 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { IAction, ActionRunner, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import * as dom from 'vs/base/browser/dom'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorGroupsService, IEditorGroup, GroupChangeKind, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IEditorGroupsService, IEditorGroup, GroupChangeKind, GroupsOrder, GroupOrientation } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IEditorInput, Verbosity, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; -import { SaveAllAction, SaveAllInGroupAction, CloseGroupAction } from 'vs/workbench/contrib/files/browser/fileActions'; +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 { ToggleEditorLayoutAction } from 'vs/workbench/browser/actions/layoutActions'; -import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, IContextKey, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey'; import { attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; @@ -30,11 +29,11 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; -import { OpenEditorsDirtyEditorContext, OpenEditorsGroupContext, OpenEditorsReadonlyEditorContext } from 'vs/workbench/contrib/files/browser/fileCommands'; +import { IMenuService, MenuId, IMenu, Action2, registerAction2, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { OpenEditorsDirtyEditorContext, OpenEditorsGroupContext, OpenEditorsReadonlyEditorContext, SAVE_ALL_LABEL, SAVE_ALL_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileCommands'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { ResourcesDropHandler, fillResourceDataTransfers, CodeDataTransfers, containsDragType } from 'vs/workbench/browser/dnd'; -import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IDragAndDropData, DataTransfers } from 'vs/base/browser/dnd'; import { memoize } from 'vs/base/common/decorators'; @@ -49,6 +48,10 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { Orientation } from 'vs/base/browser/ui/splitview/splitview'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { compareFileNamesDefault } from 'vs/base/common/comparers'; +import { Codicon } from 'vs/base/common/codicons'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { ICommandService } from 'vs/platform/commands/common/commands'; const $ = dom.$; @@ -92,14 +95,26 @@ export class OpenEditorsView extends ViewPane { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.structuralRefreshDelay = 0; + let labelChangeListeners: IDisposable[] = []; this.listRefreshScheduler = new RunOnceScheduler(() => { + labelChangeListeners = dispose(labelChangeListeners); const previousLength = this.list.length; - this.list.splice(0, this.list.length, this.getElements()); + const elements = this.getElements(); + this.list.splice(0, this.list.length, elements); this.focusActiveEditor(); if (previousLength !== this.list.length) { this.updateSize(); } this.needsRefresh = false; + + if (this.sortOrder === 'alphabetical') { + // We need to resort the list if the editor label changed + elements.forEach(e => { + if (e instanceof OpenEditor) { + labelChangeListeners.push(e.editor.onDidChangeLabel(() => this.listRefreshScheduler.schedule())); + } + }); + } }, this.structuralRefreshDelay); this.sortOrder = configurationService.getValue('explorer.openEditors.sortOrder'); @@ -151,6 +166,7 @@ export class OpenEditorsView extends ViewPane { case GroupChangeKind.EDITOR_STICKY: case GroupChangeKind.EDITOR_PIN: { this.list.splice(index, 1, [new OpenEditor(e.editor!, group)]); + this.focusActiveEditor(); break; } case GroupChangeKind.EDITOR_OPEN: @@ -294,14 +310,6 @@ export class OpenEditorsView extends ViewPane { })); } - getActions(): IAction[] { - return [ - this.instantiationService.createInstance(ToggleEditorLayoutAction, ToggleEditorLayoutAction.ID, ToggleEditorLayoutAction.LABEL), - this.instantiationService.createInstance(SaveAllAction, SaveAllAction.ID, SaveAllAction.LABEL), - this.instantiationService.createInstance(CloseAllEditorsAction, CloseAllEditorsAction.ID, CloseAllEditorsAction.LABEL) - ]; - } - focus(): void { super.focus(); this.list.domFocus(); @@ -705,3 +713,86 @@ class OpenEditorsAccessibilityProvider implements IListAccessibilityProvider { + const editorGroupService = accessor.get(IEditorGroupsService); + const newOrientation = (editorGroupService.orientation === GroupOrientation.VERTICAL) ? GroupOrientation.HORIZONTAL : GroupOrientation.VERTICAL; + editorGroupService.setGroupOrientation(newOrientation); + } +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { + group: 'z_flip', + command: { + id: toggleEditorGroupLayoutId, + title: nls.localize({ key: 'miToggleEditorLayout', comment: ['&& denotes a mnemonic'] }, "Flip &&Layout") + }, + order: 1 +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.files.saveAll', + title: SAVE_ALL_LABEL, + f1: true, + icon: Codicon.saveAll, + menu: { + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyEqualsExpr.create('view', OpenEditorsView.ID), + order: 20 + } + }); + } + + async run(accessor: ServicesAccessor): Promise { + const commandService = accessor.get(ICommandService); + await commandService.executeCommand(SAVE_ALL_COMMAND_ID); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'openEditors.closeAll', + title: CloseAllEditorsAction.LABEL, + f1: false, + icon: Codicon.closeAll, + menu: { + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyEqualsExpr.create('view', OpenEditorsView.ID), + order: 30 + } + }); + } + + async run(accessor: ServicesAccessor): Promise { + const instantiationService = accessor.get(IInstantiationService); + const closeAll = instantiationService.createInstance(CloseAllEditorsAction, CloseAllEditorsAction.ID, CloseAllEditorsAction.LABEL); + await closeAll.run(); + } +}); diff --git a/src/vs/workbench/contrib/files/common/workspaceWatcher.ts b/src/vs/workbench/contrib/files/common/workspaceWatcher.ts index 04bfd96da..1534bdc86 100644 --- a/src/vs/workbench/contrib/files/common/workspaceWatcher.ts +++ b/src/vs/workbench/contrib/files/common/workspaceWatcher.ts @@ -89,7 +89,7 @@ export class WorkspaceWatcher extends Disposable { if (msg.indexOf('ENOSPC') >= 0) { this.notificationService.prompt( Severity.Warning, - localize('enospcError', "Unable to watch for file changes in this large workspace. Please follow the instructions link to resolve this issue."), + localize('enospcError', "Unable to watch for file changes in this large workspace folder. Please follow the instructions link to resolve this issue."), [{ label: localize('learnMore', "Instructions"), run: () => this.openerService.open(URI.parse('https://go.microsoft.com/fwlink/?linkid=867693')) diff --git a/src/vs/workbench/contrib/files/electron-sandbox/textFileEditor.ts b/src/vs/workbench/contrib/files/electron-sandbox/textFileEditor.ts index d310e9872..145efa742 100644 --- a/src/vs/workbench/contrib/files/electron-sandbox/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/electron-sandbox/textFileEditor.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textFileEditor'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { EditorOptions } from 'vs/workbench/common/editor'; import { FileOperationError, FileOperationResult, IFileService, MIN_MAX_MEMORY_SIZE_MB, FALLBACK_MAX_MEMORY_SIZE_MB } from 'vs/platform/files/common/files'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; -import { Action } from 'vs/base/common/actions'; +import { toAction } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -56,18 +56,22 @@ export class NativeTextFileEditor extends TextFileEditor { if ((error).fileOperationResult === FileOperationResult.FILE_EXCEEDS_MEMORY_LIMIT) { const memoryLimit = Math.max(MIN_MAX_MEMORY_SIZE_MB, +this.textResourceConfigurationService.getValue(undefined, 'files.maxMemoryForLargeFilesMB') || FALLBACK_MAX_MEMORY_SIZE_MB); - throw createErrorWithActions(nls.localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart and allow it to use more memory"), { + throw createErrorWithActions(localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart and allow it to use more memory"), { actions: [ - new Action('workbench.window.action.relaunchWithIncreasedMemoryLimit', nls.localize('relaunchWithIncreasedMemoryLimit', "Restart with {0} MB", memoryLimit), undefined, true, () => { - return this.nativeHostService.relaunch({ - addArgs: [ - `--max-memory=${memoryLimit}` - ] - }); + toAction({ + id: 'workbench.window.action.relaunchWithIncreasedMemoryLimit', label: localize('relaunchWithIncreasedMemoryLimit', "Restart with {0} MB", memoryLimit), run: () => { + return this.nativeHostService.relaunch({ + addArgs: [ + `--max-memory=${memoryLimit}` + ] + }); + } + }), + toAction({ + id: 'workbench.window.action.configureMemoryLimit', label: localize('configureMemoryLimit', 'Configure Memory Limit'), run: () => { + return this.preferencesService.openGlobalSettings(undefined, { query: 'files.maxMemoryForLargeFilesMB' }); + } }), - new Action('workbench.window.action.configureMemoryLimit', nls.localize('configureMemoryLimit', 'Configure Memory Limit'), undefined, true, () => { - return this.preferencesService.openGlobalSettings(undefined, { query: 'files.maxMemoryForLargeFilesMB' }); - }) ] }); } diff --git a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts index 644cdf743..821be9888 100644 --- a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts @@ -119,8 +119,6 @@ suite('EditorAutoSave', () => { }); function awaitModelSaved(model: ITextFileEditorModel): Promise { - return new Promise(c => { - Event.once(model.onDidChangeDirty)(c); - }); + return Event.toPromise(Event.once(model.onDidChangeDirty)); } }); diff --git a/src/vs/workbench/contrib/files/test/browser/fileActions.test.ts b/src/vs/workbench/contrib/files/test/browser/fileActions.test.ts index bf0642cde..bfa18a3b7 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileActions.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileActions.test.ts @@ -258,7 +258,7 @@ suite('Files - Increment file name smart', () => { assert.strictEqual(result, '2-test.js'); }); - test('Increment file name with prefix version with `-` as separator', function () { + test('Increment file name with prefix version with `_` as separator', function () { const name = '1_test.js'; const result = incrementFileName(name, false, 'smart'); assert.strictEqual(result, '2_test.js'); @@ -270,6 +270,36 @@ suite('Files - Increment file name smart', () => { assert.strictEqual(result, '9007199254740992.test.1.js'); }); + test('Increment file name with just version and no extension', function () { + const name = '001004'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, '001005'); + }); + + test('Increment file name with just version and no extension, too big number', function () { + const name = '9007199254740992'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, '9007199254740992.1'); + }); + + test('Increment file name with no extension and no version', function () { + const name = 'file'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, 'file1'); + }); + + test('Increment file name with no extension', function () { + const name = 'file1'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, 'file2'); + }); + + test('Increment file name with no extension, too big number', function () { + const name = 'file9007199254740992'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, 'file9007199254740992.1'); + }); + test('Increment folder name with prefix version', function () { const name = '1.test'; const result = incrementFileName(name, true, 'smart'); diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts index 0df2ed5d5..bb2a00dda 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -102,28 +102,28 @@ suite('Files - FileEditorInput', () => { const preferredResource = toResource.call(this, '/foo/bar/UPDATEFILE.js'); const inputWithoutPreferredResource = createFileInput(resource); - assert.equal(inputWithoutPreferredResource.resource.toString(), resource.toString()); - assert.equal(inputWithoutPreferredResource.preferredResource.toString(), resource.toString()); + assert.strictEqual(inputWithoutPreferredResource.resource.toString(), resource.toString()); + assert.strictEqual(inputWithoutPreferredResource.preferredResource.toString(), resource.toString()); const inputWithPreferredResource = createFileInput(resource, preferredResource); - assert.equal(inputWithPreferredResource.resource.toString(), resource.toString()); - assert.equal(inputWithPreferredResource.preferredResource.toString(), preferredResource.toString()); + assert.strictEqual(inputWithPreferredResource.resource.toString(), resource.toString()); + assert.strictEqual(inputWithPreferredResource.preferredResource.toString(), preferredResource.toString()); let didChangeLabel = false; const listener = inputWithPreferredResource.onDidChangeLabel(e => { didChangeLabel = true; }); - assert.equal(inputWithPreferredResource.getName(), 'UPDATEFILE.js'); + assert.strictEqual(inputWithPreferredResource.getName(), 'UPDATEFILE.js'); const otherPreferredResource = toResource.call(this, '/FOO/BAR/updateFILE.js'); inputWithPreferredResource.setPreferredResource(otherPreferredResource); - assert.equal(inputWithPreferredResource.resource.toString(), resource.toString()); - assert.equal(inputWithPreferredResource.preferredResource.toString(), otherPreferredResource.toString()); - assert.equal(inputWithPreferredResource.getName(), 'updateFILE.js'); - assert.equal(didChangeLabel, true); + assert.strictEqual(inputWithPreferredResource.resource.toString(), resource.toString()); + assert.strictEqual(inputWithPreferredResource.preferredResource.toString(), otherPreferredResource.toString()); + assert.strictEqual(inputWithPreferredResource.getName(), 'updateFILE.js'); + assert.strictEqual(didChangeLabel, true); listener.dispose(); }); @@ -135,20 +135,20 @@ suite('Files - FileEditorInput', () => { }); const input = createFileInput(toResource.call(this, '/foo/bar/file.js'), undefined, mode); - assert.equal(input.getPreferredMode(), mode); + assert.strictEqual(input.getPreferredMode(), mode); const model = await input.resolve() as TextFileEditorModel; - assert.equal(model.textEditorModel!.getModeId(), mode); + assert.strictEqual(model.textEditorModel!.getModeId(), mode); input.setMode('text'); - assert.equal(input.getPreferredMode(), 'text'); - assert.equal(model.textEditorModel!.getModeId(), PLAINTEXT_MODE_ID); + assert.strictEqual(input.getPreferredMode(), 'text'); + assert.strictEqual(model.textEditorModel!.getModeId(), PLAINTEXT_MODE_ID); const input2 = createFileInput(toResource.call(this, '/foo/bar/file.js')); input2.setPreferredMode(mode); const model2 = await input2.resolve() as TextFileEditorModel; - assert.equal(model2.textEditorModel!.getModeId(), mode); + assert.strictEqual(model2.textEditorModel!.getModeId(), mode); }); test('matches', function () { @@ -169,10 +169,10 @@ suite('Files - FileEditorInput', () => { const input = createFileInput(toResource.call(this, '/foo/bar/updatefile.js')); input.setEncoding('utf16', EncodingMode.Encode); - assert.equal(input.getEncoding(), 'utf16'); + assert.strictEqual(input.getEncoding(), 'utf16'); const resolved = await input.resolve() as TextFileEditorModel; - assert.equal(input.getEncoding(), resolved.getEncoding()); + assert.strictEqual(input.getEncoding(), resolved.getEncoding()); resolved.dispose(); }); @@ -237,7 +237,7 @@ suite('Files - FileEditorInput', () => { const model = await accessor.textFileService.files.resolve(input.resource); model.textEditorModel?.setValue('hello world'); - assert.equal(listenerCount, 1); + assert.strictEqual(listenerCount, 1); assert.ok(input.isDirty()); input.dispose(); @@ -269,7 +269,7 @@ suite('Files - FileEditorInput', () => { assert.fail('File Editor Input Factory missing'); } - assert.equal(factory.canSerialize(input), true); + assert.strictEqual(factory.canSerialize(input), true); const inputSerialized = factory.serialize(input); if (!inputSerialized) { @@ -277,7 +277,7 @@ suite('Files - FileEditorInput', () => { } const inputDeserialized = factory.deserialize(instantiationService, inputSerialized); - assert.equal(input.matches(inputDeserialized), true); + assert.strictEqual(input.matches(inputDeserialized), true); const preferredResource = toResource.call(this, '/foo/bar/UPDATEfile.js'); const inputWithPreferredResource = createFileInput(toResource.call(this, '/foo/bar/updatefile.js'), preferredResource); @@ -288,8 +288,8 @@ suite('Files - FileEditorInput', () => { } const inputWithPreferredResourceDeserialized = factory.deserialize(instantiationService, inputWithPreferredResourceSerialized) as FileEditorInput; - assert.equal(inputWithPreferredResource.resource.toString(), inputWithPreferredResourceDeserialized.resource.toString()); - assert.equal(inputWithPreferredResource.preferredResource.toString(), inputWithPreferredResourceDeserialized.preferredResource.toString()); + assert.strictEqual(inputWithPreferredResource.resource.toString(), inputWithPreferredResourceDeserialized.resource.toString()); + assert.strictEqual(inputWithPreferredResource.preferredResource.toString(), inputWithPreferredResourceDeserialized.preferredResource.toString()); }); test('preferred name/description', async function () { @@ -302,16 +302,16 @@ suite('Files - FileEditorInput', () => { didChangeLabelCounter++; }); - assert.equal(customFileInput.getName(), 'My Name'); - assert.equal(customFileInput.getDescription(), 'My Description'); + assert.strictEqual(customFileInput.getName(), 'My Name'); + assert.strictEqual(customFileInput.getDescription(), 'My Description'); customFileInput.setPreferredName('My Name 2'); customFileInput.setPreferredDescription('My Description 2'); - assert.equal(customFileInput.getName(), 'My Name 2'); - assert.equal(customFileInput.getDescription(), 'My Description 2'); + assert.strictEqual(customFileInput.getName(), 'My Name 2'); + assert.strictEqual(customFileInput.getDescription(), 'My Description 2'); - assert.equal(didChangeLabelCounter, 2); + assert.strictEqual(didChangeLabelCounter, 2); customFileInput.dispose(); @@ -332,7 +332,7 @@ suite('Files - FileEditorInput', () => { assert.notEqual(fileInput.getName(), 'My Name 2'); assert.notEqual(fileInput.getDescription(), 'My Description 2'); - assert.equal(didChangeLabelCounter, 0); + assert.strictEqual(didChangeLabelCounter, 0); fileInput.dispose(); }); diff --git a/src/vs/workbench/contrib/files/test/browser/fileOnDiskProvider.test.ts b/src/vs/workbench/contrib/files/test/browser/fileOnDiskProvider.test.ts index 70c2fa9e2..00b36384a 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileOnDiskProvider.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileOnDiskProvider.test.ts @@ -27,8 +27,8 @@ suite('Files - FileOnDiskContentProvider', () => { const content = await provider.provideTextContent(uri.with({ scheme: 'conflictResolution', query: JSON.stringify({ scheme: uri.scheme }) })); assert.ok(content); - assert.equal(snapshotToString(content!.createSnapshot()), 'Hello Html'); - assert.equal(accessor.fileService.getLastReadFileUri().scheme, uri.scheme); - assert.equal(accessor.fileService.getLastReadFileUri().path, uri.path); + assert.strictEqual(snapshotToString(content!.createSnapshot()), 'Hello Html'); + assert.strictEqual(accessor.fileService.getLastReadFileUri().scheme, uri.scheme); + assert.strictEqual(accessor.fileService.getLastReadFileUri().path, uri.path); }); }); diff --git a/src/vs/workbench/contrib/files/test/browser/textFileEditor.test.ts b/src/vs/workbench/contrib/files/test/browser/textFileEditor.test.ts index 9b346315b..33342c8fa 100644 --- a/src/vs/workbench/contrib/files/test/browser/textFileEditor.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/textFileEditor.test.ts @@ -27,6 +27,7 @@ import { IFilesConfigurationService } from 'vs/workbench/services/filesConfigura import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; +import { raceTimeout } from 'vs/base/common/async'; suite('Files - TextFileEditor', () => { @@ -73,7 +74,7 @@ suite('Files - TextFileEditor', () => { const accessor = instantiationService.createInstance(TestServiceAccessor); - await part.whenRestored; + await raceTimeout(part.whenRestored, 2000, () => assert.fail('textFileEditor.test.ts: Unexpected long time to wait for part to restore (#112649)')); return [part, accessor, instantiationService, editorService]; } @@ -86,7 +87,7 @@ suite('Files - TextFileEditor', () => { return viewStateTest(this, false); }); - async function viewStateTest(context: Mocha.ITestCallbackContext, restoreViewState: boolean): Promise { + async function viewStateTest(context: Mocha.Context, restoreViewState: boolean): Promise { const [part, accessor] = await createPart(restoreViewState); let editor = await accessor.editorService.openEditor(accessor.editorService.createEditorInput({ resource: toResource.call(context, '/path/index.txt'), forceFile: true })); diff --git a/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts b/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts index a869de372..2ce57b49a 100644 --- a/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts @@ -94,7 +94,7 @@ suite('Files - TextFileEditorTracker', () => { const model = await accessor.textFileService.files.resolve(resource) as IResolvedTextFileEditorModel; model.textEditorModel.setValue('Super Good'); - assert.equal(snapshotToString(model.createSnapshot()!), 'Super Good'); + assert.strictEqual(snapshotToString(model.createSnapshot()!), 'Super Good'); await model.save(); @@ -103,7 +103,7 @@ suite('Files - TextFileEditorTracker', () => { await timeout(0); // due to event updating model async - assert.equal(snapshotToString(model.createSnapshot()!), 'Hello Html'); + assert.strictEqual(snapshotToString(model.createSnapshot()!), 'Hello Html'); tracker.dispose(); (accessor.textFileService.files).dispose(); @@ -162,9 +162,7 @@ suite('Files - TextFileEditorTracker', () => { }); function awaitEditorOpening(editorService: IEditorService): Promise { - return new Promise(c => { - Event.once(editorService.onDidActiveEditorChange)(c); - }); + return Event.toPromise(Event.once(editorService.onDidActiveEditorChange)); } test('non-dirty files reload on window focus', async function () { diff --git a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts index 05c0d93f9..14a4c09a3 100644 --- a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts +++ b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts @@ -30,6 +30,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { editorConfigurationBaseNode } from 'vs/editor/common/config/commonEditorConfig'; import { mergeSort } from 'vs/base/common/arrays'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; type FormattingEditProvider = DocumentFormattingEditProvider | DocumentRangeFormattingEditProvider; @@ -45,6 +46,7 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution { @IWorkbenchExtensionEnablementService private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService, @IConfigurationService private readonly _configService: IConfigurationService, @INotificationService private readonly _notificationService: INotificationService, + @IDialogService private readonly _dialogService: IDialogService, @IQuickInputService private readonly _quickInputService: IQuickInputService, @IModeService private readonly _modeService: IModeService, @ILabelService private readonly _labelService: ILabelService, @@ -119,25 +121,32 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution { } const langName = this._modeService.getLanguageName(document.getModeId()) || document.getModeId(); - const silent = mode === FormattingMode.Silent; const message = !defaultFormatterId ? nls.localize('config.needed', "There are multiple formatters for '{0}' files. Select a default formatter to continue.", DefaultFormatter._maybeQuotes(langName)) : nls.localize('config.bad', "Extension '{0}' is configured as formatter but not available. Select a different default formatter to continue.", defaultFormatterId); - return new Promise((resolve, reject) => { + if (mode !== FormattingMode.Silent) { + // running from a user action -> show modal dialog so that users configure + // a default formatter + const result = await this._dialogService.confirm({ + message, + primaryButton: nls.localize('do.config', "Configure..."), + secondaryButton: nls.localize('cancel', "Cancel") + }); + if (result.confirmed) { + return this._pickAndPersistDefaultFormatter(formatter, document); + } + + } else { + // no user action -> show a silent notification and proceed this._notificationService.prompt( Severity.Info, message, - [{ label: nls.localize('do.config', "Configure..."), run: () => this._pickAndPersistDefaultFormatter(formatter, document).then(resolve, reject) }], - { silent, onCancel: () => resolve(undefined) } + [{ label: nls.localize('do.config', "Configure..."), run: () => this._pickAndPersistDefaultFormatter(formatter, document) }], + { silent: true } ); - - if (silent) { - // don't wait when formatting happens without interaction - // but pick some formatter... - resolve(undefined); - } - }); + } + return undefined; } private async _pickAndPersistDefaultFormatter(formatter: T[], document: ITextModel): Promise { diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issueService.ts b/src/vs/workbench/contrib/issue/electron-sandbox/issueService.ts index f7beaf0f5..94beb1646 100644 --- a/src/vs/workbench/contrib/issue/electron-sandbox/issueService.ts +++ b/src/vs/workbench/contrib/issue/electron-sandbox/issueService.ts @@ -6,7 +6,7 @@ import { IssueReporterStyles, IssueReporterData, ProcessExplorerData, IssueReporterExtensionData } from 'vs/platform/issue/common/issue'; import { IIssueService } from 'vs/platform/issue/electron-sandbox/issue'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; -import { textLinkForeground, inputBackground, inputBorder, inputForeground, buttonBackground, buttonHoverBackground, buttonForeground, inputValidationErrorBorder, foreground, inputActiveOptionBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, editorBackground, editorForeground, listHoverBackground, listHoverForeground, listHighlightForeground, textLinkActiveForeground, inputValidationErrorBackground, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry'; +import { textLinkForeground, inputBackground, inputBorder, inputForeground, buttonBackground, buttonHoverBackground, buttonForeground, inputValidationErrorBorder, foreground, inputActiveOptionBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, editorBackground, editorForeground, listHoverBackground, listHoverForeground, textLinkActiveForeground, inputValidationErrorBackground, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; @@ -17,6 +17,7 @@ import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { IProductService } from 'vs/platform/product/common/productService'; import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService'; +import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; export class WorkbenchIssueService implements IWorkbenchIssueService { declare readonly _serviceBrand: undefined; @@ -28,7 +29,8 @@ export class WorkbenchIssueService implements IWorkbenchIssueService { @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @INativeWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService, @IProductService private readonly productService: IProductService, - @ITASExperimentService private readonly experimentService: ITASExperimentService + @ITASExperimentService private readonly experimentService: ITASExperimentService, + @IAuthenticationService private readonly authenticationService: IAuthenticationService ) { } async openReporter(dataOverrides: Partial = {}): Promise { @@ -52,12 +54,15 @@ export class WorkbenchIssueService implements IWorkbenchIssueService { }; }); const experiments = await this.experimentService.getCurrentExperiments(); + const githubSessions = await this.authenticationService.getSessions('github'); + const potentialSessions = githubSessions.filter(session => session.scopes.includes('repo')); const theme = this.themeService.getColorTheme(); const issueReporterData: IssueReporterData = Object.assign({ styles: getIssueReporterStyles(theme), zoomLevel: getZoomLevel(), enabledExtensions: extensionData, - experiments: experiments?.join('\n') + experiments: experiments?.join('\n'), + githubAccessToken: potentialSessions[0]?.accessToken }, dataOverrides); return this.issueService.openReporter(issueReporterData); } @@ -71,8 +76,7 @@ export class WorkbenchIssueService implements IWorkbenchIssueService { backgroundColor: getColor(theme, editorBackground), color: getColor(theme, editorForeground), hoverBackground: getColor(theme, listHoverBackground), - hoverForeground: getColor(theme, listHoverForeground), - highlightForeground: getColor(theme, listHighlightForeground), + hoverForeground: getColor(theme, listHoverForeground) }, platform: process.platform, applicationName: this.productService.applicationName diff --git a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts index f8b4c1582..a2dd32715 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts @@ -60,7 +60,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo updateAndRestart ? localize('updateLocale', "Would you like to change VS Code's UI language to {0} and restart?", e.local.manifest.contributes.localizations[0].languageName || e.local.manifest.contributes.localizations[0].languageId) : localize('activateLanguagePack', "In order to use VS Code in {0}, VS Code needs to restart.", e.local.manifest.contributes.localizations[0].languageName || e.local.manifest.contributes.localizations[0].languageId), [{ - label: updateAndRestart ? localize('yes', "Yes") : localize('restart now', "Restart Now"), + label: updateAndRestart ? localize('changeAndRestart', "Change Language and Restart") : localize('restart', "Restart"), run: () => { const updatePromise = updateAndRestart ? this.jsonEditingService.write(this.environmentService.argvResource, [{ path: ['locale'], value: locale }], true) : Promise.resolve(undefined); updatePromise.then(() => this.hostService.restart(), e => this.notificationService.error(e)); diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index 208fea76b..d65e91027 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -4,35 +4,33 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/workbench/contrib/markers/browser/markersFileDecorations'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions, CATEGORIES } from 'vs/workbench/common/actions'; +import { CATEGORIES } from 'vs/workbench/common/actions'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { localize } from 'vs/nls'; import { Marker, RelatedInformation } from 'vs/workbench/contrib/markers/browser/markersModel'; import { MarkersView } from 'vs/workbench/contrib/markers/browser/markersView'; -import { MenuId, MenuRegistry, SyncActionDescriptor, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; +import { MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ShowProblemsPanelAction } from 'vs/workbench/contrib/markers/browser/markersViewActions'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; import Messages from 'vs/workbench/contrib/markers/browser/messages'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { ActivityUpdater } from 'vs/workbench/contrib/markers/browser/markers'; +import { ActivityUpdater, IMarkersView } from 'vs/workbench/contrib/markers/browser/markers'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { Disposable } from 'vs/base/common/lifecycle'; import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; import { IMarkerService, MarkerStatistics } from 'vs/platform/markers/common/markers'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry, IViewsService, getVisbileViewContextKey, FocusedViewContext, IViewDescriptorService } from 'vs/workbench/common/views'; +import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry, IViewsService, getVisbileViewContextKey, FocusedViewContext } from 'vs/workbench/common/views'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import type { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ToggleViewAction } from 'vs/workbench/browser/actions/layoutActions'; import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; +import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; KeybindingsRegistry.registerCommandAndKeybindingRule({ id: Constants.MARKER_OPEN_ACTION_ID, @@ -107,24 +105,10 @@ Registry.as(Extensions.Configuration).registerConfigurat } }); -class ToggleMarkersPanelAction extends ToggleViewAction { - - public static readonly ID = 'workbench.actions.view.problems'; - public static readonly LABEL = Messages.MARKERS_PANEL_TOGGLE_LABEL; - - constructor(id: string, label: string, - @IViewsService viewsService: IViewsService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IContextKeyService contextKeyService: IContextKeyService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService - ) { - super(id, label, Constants.MARKERS_VIEW_ID, viewsService, viewDescriptorService, contextKeyService, layoutService); - } -} - const markersViewIcon = registerIcon('markers-view-icon', Codicon.warning, localize('markersViewIcon', 'View icon of the markers view.')); // markers view container +const TOGGLE_MARKERS_VIEW_ACTION_ID = 'workbench.actions.view.problems'; const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: Constants.MARKERS_CONTAINER_ID, name: Messages.MARKERS_PANEL_TITLE_PROBLEMS, @@ -134,7 +118,8 @@ const VIEW_CONTAINER: ViewContainer = Registry.as(ViewC ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [Constants.MARKERS_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), storageId: Constants.MARKERS_VIEW_STORAGE_ID, focusCommand: { - id: ToggleMarkersPanelAction.ID, keybindings: { + id: TOGGLE_MARKERS_VIEW_ACTION_ID, + keybindings: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_M } } @@ -154,12 +139,47 @@ const workbenchRegistry = Registry.as(Workbench workbenchRegistry.registerWorkbenchContribution(ActivityUpdater, LifecyclePhase.Restored); // actions -const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleMarkersPanelAction, { - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_M -}), 'View: Toggle Problems (Errors, Warnings, Infos)', CATEGORIES.View.value); -registry.registerWorkbenchAction(SyncActionDescriptor.from(ShowProblemsPanelAction), 'View: Focus Problems (Errors, Warnings, Infos)', CATEGORIES.View.value); registerAction2(class extends Action2 { + constructor() { + super({ + id: TOGGLE_MARKERS_VIEW_ACTION_ID, + title: { value: Messages.MARKERS_PANEL_TOGGLE_LABEL, original: 'Toggle Problems (Errors, Warnings, Infos)' }, + category: CATEGORIES.View.value, + f1: true, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_M + } + }); + } + async run(accessor: ServicesAccessor): Promise { + return accessor.get(IInstantiationService).createInstance(ToggleViewAction, TOGGLE_MARKERS_VIEW_ACTION_ID, 'Toggle Problems (Errors, Warnings, Infos)', Constants.MARKERS_VIEW_ID).run(); + } +}); +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '4_panels', + command: { + id: TOGGLE_MARKERS_VIEW_ACTION_ID, + title: localize({ key: 'miMarker', comment: ['&& denotes a mnemonic'] }, "&&Problems") + }, + order: 4 +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.problems.focus', + title: { value: Messages.MARKERS_PANEL_SHOW_LABEL, original: 'Focus Problems (Errors, Warnings, Infos)' }, + category: CATEGORIES.View.value, + f1: true, + }); + } + async run(accessor: ServicesAccessor): Promise { + accessor.get(IViewsService).openView(Constants.MARKERS_VIEW_ID, true); + } +}); + +registerAction2(class extends ViewAction { constructor() { super({ id: Constants.MARKER_COPY_ACTION_ID, @@ -174,13 +194,19 @@ registerAction2(class extends Action2 { primary: KeyMod.CtrlCmd | KeyCode.KEY_C, when: Constants.MarkerFocusContextKey }, + viewId: Constants.MARKERS_VIEW_ID }); } - async run(accessor: ServicesAccessor) { - await copyMarker(accessor.get(IViewsService), accessor.get(IClipboardService)); + async runInView(serviceAccessor: ServicesAccessor, markersView: IMarkersView): Promise { + const clipboardService = serviceAccessor.get(IClipboardService); + const element = markersView.getFocusElement(); + if (element instanceof Marker) { + await clipboardService.writeText(`${element}`); + } } }); -registerAction2(class extends Action2 { + +registerAction2(class extends ViewAction { constructor() { super({ id: Constants.MARKER_COPY_MESSAGE_ACTION_ID, @@ -190,13 +216,19 @@ registerAction2(class extends Action2 { when: Constants.MarkerFocusContextKey, group: 'navigation' }, + viewId: Constants.MARKERS_VIEW_ID }); } - async run(accessor: ServicesAccessor) { - await copyMessage(accessor.get(IViewsService), accessor.get(IClipboardService)); + async runInView(serviceAccessor: ServicesAccessor, markersView: IMarkersView): Promise { + const clipboardService = serviceAccessor.get(IClipboardService); + const element = markersView.getFocusElement(); + if (element instanceof Marker) { + await clipboardService.writeText(element.marker.message); + } } }); -registerAction2(class extends Action2 { + +registerAction2(class extends ViewAction { constructor() { super({ id: Constants.RELATED_INFORMATION_COPY_MESSAGE_ACTION_ID, @@ -205,14 +237,20 @@ registerAction2(class extends Action2 { id: MenuId.ProblemsPanelContext, when: Constants.RelatedInformationFocusContextKey, group: 'navigation' - } + }, + viewId: Constants.MARKERS_VIEW_ID }); } - async run(accessor: ServicesAccessor) { - await copyRelatedInformationMessage(accessor.get(IViewsService), accessor.get(IClipboardService)); + async runInView(serviceAccessor: ServicesAccessor, markersView: IMarkersView): Promise { + const clipboardService = serviceAccessor.get(IClipboardService); + const element = markersView.getFocusElement(); + if (element instanceof RelatedInformation) { + await clipboardService.writeText(element.raw.message); + } } }); -registerAction2(class extends Action2 { + +registerAction2(class extends ViewAction { constructor() { super({ id: Constants.FOCUS_PROBLEMS_FROM_FILTER, @@ -221,14 +259,16 @@ registerAction2(class extends Action2 { when: Constants.MarkerViewFilterFocusContextKey, weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.DownArrow - } + }, + viewId: Constants.MARKERS_VIEW_ID }); } - run(accessor: ServicesAccessor) { - focusProblemsView(accessor.get(IViewsService)); + async runInView(serviceAccessor: ServicesAccessor, markersView: IMarkersView): Promise { + markersView.focus(); } }); -registerAction2(class extends Action2 { + +registerAction2(class extends ViewAction { constructor() { super({ id: Constants.MARKERS_VIEW_FOCUS_FILTER, @@ -237,14 +277,16 @@ registerAction2(class extends Action2 { when: FocusedViewContext.isEqualTo(Constants.MARKERS_VIEW_ID), weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.KEY_F - } + }, + viewId: Constants.MARKERS_VIEW_ID }); } - run(accessor: ServicesAccessor) { - focusProblemsFilter(accessor.get(IViewsService)); + async runInView(serviceAccessor: ServicesAccessor, markersView: IMarkersView): Promise { + markersView.focusFilter(); } }); -registerAction2(class extends Action2 { + +registerAction2(class extends ViewAction { constructor() { super({ id: Constants.MARKERS_VIEW_SHOW_MULTILINE_MESSAGE, @@ -253,17 +295,16 @@ registerAction2(class extends Action2 { menu: { id: MenuId.CommandPalette, when: ContextKeyExpr.has(getVisbileViewContextKey(Constants.MARKERS_VIEW_ID)) - } + }, + viewId: Constants.MARKERS_VIEW_ID }); } - run(accessor: ServicesAccessor) { - const markersView = accessor.get(IViewsService).getActiveViewWithId(Constants.MARKERS_VIEW_ID)!; - if (markersView) { - markersView.markersViewModel.multiline = true; - } + async runInView(serviceAccessor: ServicesAccessor, markersView: IMarkersView): Promise { + markersView.setMultiline(true); } }); -registerAction2(class extends Action2 { + +registerAction2(class extends ViewAction { constructor() { super({ id: Constants.MARKERS_VIEW_SHOW_SINGLELINE_MESSAGE, @@ -272,17 +313,16 @@ registerAction2(class extends Action2 { menu: { id: MenuId.CommandPalette, when: ContextKeyExpr.has(getVisbileViewContextKey(Constants.MARKERS_VIEW_ID)) - } + }, + viewId: Constants.MARKERS_VIEW_ID }); } - run(accessor: ServicesAccessor) { - const markersView = accessor.get(IViewsService).getActiveViewWithId(Constants.MARKERS_VIEW_ID); - if (markersView) { - markersView.markersViewModel.multiline = false; - } + async runInView(serviceAccessor: ServicesAccessor, markersView: IMarkersView): Promise { + markersView.setMultiline(false); } }); -registerAction2(class extends Action2 { + +registerAction2(class extends ViewAction { constructor() { super({ id: Constants.MARKERS_VIEW_CLEAR_FILTER_TEXT, @@ -291,76 +331,65 @@ registerAction2(class extends Action2 { keybinding: { when: Constants.MarkerViewFilterFocusContextKey, weight: KeybindingWeight.WorkbenchContrib, - } + }, + viewId: Constants.MARKERS_VIEW_ID }); } - run(accessor: ServicesAccessor) { - const markersView = accessor.get(IViewsService).getActiveViewWithId(Constants.MARKERS_VIEW_ID); - if (markersView) { - markersView.clearFilterText(); - } + async runInView(serviceAccessor: ServicesAccessor, markersView: IMarkersView): Promise { + markersView.clearFilterText(); } }); -async function copyMarker(viewsService: IViewsService, clipboardService: IClipboardService) { - const markersView = viewsService.getActiveViewWithId(Constants.MARKERS_VIEW_ID); - if (markersView) { - const element = markersView.getFocusElement(); - if (element instanceof Marker) { - await clipboardService.writeText(`${element}`); - } +registerAction2(class extends ViewAction { + constructor() { + super({ + id: `workbench.actions.treeView.${Constants.MARKERS_VIEW_ID}.collapseAll`, + title: localize('collapseAll', "Collapse All"), + menu: { + id: MenuId.ViewTitle, + when: ContextKeyEqualsExpr.create('view', Constants.MARKERS_VIEW_ID), + group: 'navigation', + order: 2, + }, + icon: Codicon.collapseAll, + viewId: Constants.MARKERS_VIEW_ID + }); } -} - -async function copyMessage(viewsService: IViewsService, clipboardService: IClipboardService) { - const markersView = viewsService.getActiveViewWithId(Constants.MARKERS_VIEW_ID); - if (markersView) { - const element = markersView.getFocusElement(); - if (element instanceof Marker) { - await clipboardService.writeText(element.marker.message); - } + async runInView(serviceAccessor: ServicesAccessor, view: IMarkersView): Promise { + return view.collapseAll(); } -} - -async function copyRelatedInformationMessage(viewsService: IViewsService, clipboardService: IClipboardService) { - const markersView = viewsService.getActiveViewWithId(Constants.MARKERS_VIEW_ID); - if (markersView) { - const element = markersView.getFocusElement(); - if (element instanceof RelatedInformation) { - await clipboardService.writeText(element.raw.message); - } - } -} - -function focusProblemsView(viewsService: IViewsService) { - const markersView = viewsService.getActiveViewWithId(Constants.MARKERS_VIEW_ID); - if (markersView) { - markersView.focus(); - } -} - -function focusProblemsFilter(viewsService: IViewsService): void { - const markersView = viewsService.getActiveViewWithId(Constants.MARKERS_VIEW_ID); - if (markersView) { - markersView.focusFilter(); - } -} - -MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '4_panels', - command: { - id: ToggleMarkersPanelAction.ID, - title: localize({ key: 'miMarker', comment: ['&& denotes a mnemonic'] }, "&&Problems") - }, - order: 4 }); -CommandsRegistry.registerCommand(Constants.TOGGLE_MARKERS_VIEW_ACTION_ID, async (accessor) => { - const viewsService = accessor.get(IViewsService); - if (viewsService.isViewVisible(Constants.MARKERS_VIEW_ID)) { - viewsService.closeView(Constants.MARKERS_VIEW_ID); - } else { - viewsService.openView(Constants.MARKERS_VIEW_ID, true); +registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.treeView.${Constants.MARKERS_VIEW_ID}.filter`, + title: localize('filter', "Filter"), + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', Constants.MARKERS_VIEW_ID), Constants.MarkersViewSmallLayoutContextKey.negate()), + group: 'navigation', + order: 1, + }, + }); + } + async run(): Promise { } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: Constants.TOGGLE_MARKERS_VIEW_ACTION_ID, + title: Messages.MARKERS_PANEL_TOGGLE_LABEL, + }); + } + async run(accessor: ServicesAccessor): Promise { + const viewsService = accessor.get(IViewsService); + if (viewsService.isViewVisible(Constants.MARKERS_VIEW_ID)) { + viewsService.closeView(Constants.MARKERS_VIEW_ID); + } else { + viewsService.openView(Constants.MARKERS_VIEW_ID, true); + } } }); diff --git a/src/vs/workbench/contrib/markers/browser/markers.ts b/src/vs/workbench/contrib/markers/browser/markers.ts index 196235465..10592aba3 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.ts @@ -9,6 +9,26 @@ import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/co import { localize } from 'vs/nls'; import Constants from './constants'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { MarkersFilters } from 'vs/workbench/contrib/markers/browser/markersViewActions'; +import { Event } from 'vs/base/common/event'; +import { IView } from 'vs/workbench/common/views'; +import { MarkerElement } from 'vs/workbench/contrib/markers/browser/markersModel'; + +export interface IMarkersView extends IView { + + readonly onDidFocusFilter: Event; + readonly onDidClearFilterText: Event; + readonly filters: MarkersFilters; + readonly onDidChangeFilterStats: Event<{ total: number, filtered: number }>; + focusFilter(): void; + clearFilterText(): void; + getFilterStats(): { total: number, filtered: number }; + + getFocusElement(): MarkerElement | undefined; + + collapseAll(): void; + setMultiline(multiline: boolean): void; +} export class ActivityUpdater extends Disposable implements IWorkbenchContribution { diff --git a/src/vs/workbench/contrib/markers/browser/markersModel.ts b/src/vs/workbench/contrib/markers/browser/markersModel.ts index a12e1658e..794791b34 100644 --- a/src/vs/workbench/contrib/markers/browser/markersModel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersModel.ts @@ -14,6 +14,7 @@ import { Hasher } from 'vs/base/common/hash'; import { withUndefinedAsNull } from 'vs/base/common/types'; import { splitLines } from 'vs/base/common/strings'; +export type MarkerElement = ResourceMarkers | Marker | RelatedInformation; export function compareMarkersByUri(a: IMarker, b: IMarker) { return extUri.compare(a.resource, b.resource); diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 6a344c8a0..653b012f4 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -10,7 +10,7 @@ import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IMarker, MarkerSeverity } from 'vs/platform/markers/common/markers'; -import { ResourceMarkers, Marker, RelatedInformation } from 'vs/workbench/contrib/markers/browser/markersModel'; +import { ResourceMarkers, Marker, RelatedInformation, MarkerElement } from 'vs/workbench/contrib/markers/browser/markersModel'; import Messages from 'vs/workbench/contrib/markers/browser/messages'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; @@ -56,8 +56,6 @@ import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; -export type TreeElement = ResourceMarkers | Marker | RelatedInformation; - interface IResourceMarkersTemplateData { resourceLabel: IResourceLabel; count: CountBadge; @@ -74,7 +72,7 @@ interface IRelatedInformationTemplateData { description: HighlightedLabel; } -export class MarkersTreeAccessibilityProvider implements IListAccessibilityProvider { +export class MarkersTreeAccessibilityProvider implements IListAccessibilityProvider { constructor(@ILabelService private readonly labelService: ILabelService) { } @@ -82,7 +80,7 @@ export class MarkersTreeAccessibilityProvider implements IListAccessibilityProvi return localize('problemsView', "Problems View"); } - public getAriaLabel(element: TreeElement): string | null { + public getAriaLabel(element: MarkerElement): string | null { if (element instanceof ResourceMarkers) { const path = this.labelService.getUriLabel(element.resource, { relative: true }) || element.resource.fsPath; return Messages.MARKERS_TREE_ARIA_LABEL_RESOURCE(element.markers.length, element.name, paths.dirname(path)); @@ -103,13 +101,13 @@ const enum TemplateId { RelatedInformation = 'ri' } -export class VirtualDelegate implements IListVirtualDelegate { +export class VirtualDelegate implements IListVirtualDelegate { static LINE_HEIGHT: number = 22; constructor(private readonly markersViewState: MarkersViewModel) { } - getHeight(element: TreeElement): number { + getHeight(element: MarkerElement): number { if (element instanceof Marker) { const viewModel = this.markersViewState.getViewModel(element); const noOfLines = !viewModel || viewModel.multiline ? element.lines.length : 1; @@ -118,7 +116,7 @@ export class VirtualDelegate implements IListVirtualDelegate { return VirtualDelegate.LINE_HEIGHT; } - getTemplateId(element: TreeElement): string { + getTemplateId(element: MarkerElement): string { if (element instanceof ResourceMarkers) { return TemplateId.ResourceMarkers; } else if (element instanceof Marker) { @@ -512,11 +510,11 @@ export class RelatedInformationRenderer implements ITreeRenderer { +export class Filter implements ITreeFilter { constructor(public options: FilterOptions) { } - filter(element: TreeElement, parentVisibility: TreeVisibility): TreeFilterResult { + filter(element: MarkerElement, parentVisibility: TreeVisibility): TreeFilterResult { if (element instanceof ResourceMarkers) { return this.filterResourceMarkers(element); } else if (element instanceof Marker) { @@ -852,23 +850,23 @@ export class MarkersViewModel extends Disposable { } -export class ResourceDragAndDrop implements ITreeDragAndDrop { +export class ResourceDragAndDrop implements ITreeDragAndDrop { constructor( private instantiationService: IInstantiationService ) { } - onDragOver(data: IDragAndDropData, targetElement: TreeElement, targetIndex: number, originalEvent: DragEvent): boolean | ITreeDragOverReaction { + onDragOver(data: IDragAndDropData, targetElement: MarkerElement, targetIndex: number, originalEvent: DragEvent): boolean | ITreeDragOverReaction { return false; } - getDragURI(element: TreeElement): string | null { + getDragURI(element: MarkerElement): string | null { if (element instanceof ResourceMarkers) { return element.resource.toString(); } return null; } - getDragLabel?(elements: TreeElement[]): string | undefined { + getDragLabel?(elements: MarkerElement[]): string | undefined { if (elements.length > 1) { return String(elements.length); } @@ -877,7 +875,7 @@ export class ResourceDragAndDrop implements ITreeDragAndDrop { } onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void { - const elements = (data as ElementsDragAndDropData).elements; + const elements = (data as ElementsDragAndDropData).elements; const resources: URI[] = elements .filter(e => e instanceof ResourceMarkers) .map(resourceMarker => (resourceMarker as ResourceMarkers).resource); @@ -888,7 +886,7 @@ export class ResourceDragAndDrop implements ITreeDragAndDrop { } } - drop(data: IDragAndDropData, targetElement: TreeElement, targetIndex: number, originalEvent: DragEvent): void { + drop(data: IDragAndDropData, targetElement: MarkerElement, targetIndex: number, originalEvent: DragEvent): void { } } diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index 8836c8371..b2a7ce0f6 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -11,17 +11,17 @@ import { IAction, IActionViewItem, Action, Separator } from 'vs/base/common/acti import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; -import { Marker, ResourceMarkers, RelatedInformation, MarkerChangesEvent, MarkersModel, compareMarkersByUri } from 'vs/workbench/contrib/markers/browser/markersModel'; +import { Marker, ResourceMarkers, RelatedInformation, MarkerChangesEvent, MarkersModel, compareMarkersByUri, MarkerElement } from 'vs/workbench/contrib/markers/browser/markersModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { MarkersFilterActionViewItem, MarkersFilters, IMarkersFiltersChangeEvent, IMarkerFilterController } from 'vs/workbench/contrib/markers/browser/markersViewActions'; +import { MarkersFilterActionViewItem, MarkersFilters, IMarkersFiltersChangeEvent } from 'vs/workbench/contrib/markers/browser/markersViewActions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import Messages from 'vs/workbench/contrib/markers/browser/messages'; -import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations'; +import { RangeHighlightDecorations } from 'vs/workbench/browser/codeeditor'; import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { localize } from 'vs/nls'; -import { IContextKey, IContextKeyService, ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Iterable } from 'vs/base/common/iterator'; import { ITreeElement, ITreeNode, ITreeContextMenuEvent, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { Relay, Event, Emitter } from 'vs/base/common/event'; @@ -30,10 +30,10 @@ import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilte import { IExpression } from 'vs/base/common/glob'; import { deepClone } from 'vs/base/common/objects'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { FilterData, Filter, VirtualDelegate, ResourceMarkersRenderer, MarkerRenderer, RelatedInformationRenderer, TreeElement, MarkersTreeAccessibilityProvider, MarkersViewModel, ResourceDragAndDrop } from 'vs/workbench/contrib/markers/browser/markersTreeViewer'; +import { FilterData, Filter, VirtualDelegate, ResourceMarkersRenderer, MarkerRenderer, RelatedInformationRenderer, MarkersTreeAccessibilityProvider, MarkersViewModel, ResourceDragAndDrop } from 'vs/workbench/contrib/markers/browser/markersTreeViewer'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IMenuService, MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { domEvent } from 'vs/base/browser/event'; @@ -45,7 +45,7 @@ import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { KeyCode } from 'vs/base/common/keyCodes'; import { editorLightBulbForeground, editorLightBulbAutoFixForeground } from 'vs/platform/theme/common/colorRegistry'; -import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { Codicon } from 'vs/base/common/codicons'; @@ -55,8 +55,9 @@ import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifec import { groupBy } from 'vs/base/common/arrays'; import { ResourceMap } from 'vs/base/common/map'; import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; +import { IMarkersView } from 'vs/workbench/contrib/markers/browser/markers'; -function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterable> { +function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterable> { return Iterable.map(resourceMarkers.markers, m => { const relatedInformationIt = Iterable.from(m.relatedInformation); const children = Iterable.map(relatedInformationIt, r => ({ element: r })); @@ -65,7 +66,7 @@ function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterab }); } -export class MarkersView extends ViewPane implements IMarkerFilterController { +export class MarkersView extends ViewPane implements IMarkersView { private lastSelectedRelativeTop: number = 0; private currentActiveResource: URI | null = null; @@ -88,7 +89,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { private cachedFilterStats: { total: number; filtered: number; } | undefined = undefined; private currentResourceGotAddedToMarkersData: boolean = false; - readonly markersViewModel: MarkersViewModel; + private readonly markersViewModel: MarkersViewModel; private readonly smallLayoutContextKey: IContextKey; private get smallLayout(): boolean { return !!this.smallLayoutContextKey.get(); } private set smallLayout(smallLayout: boolean) { this.smallLayoutContextKey.set(smallLayout); } @@ -132,8 +133,6 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { this.filter = new Filter(FilterOptions.EMPTY(uriIdentityService)); this.rangeHighlightDecorations = this._register(this.instantiationService.createInstance(RangeHighlightDecorations)); - // actions - this.regiserActions(); this.filters = this._register(new MarkersFilters({ filterText: this.panelState['filter'] || '', filterHistory: this.panelState['filterHistory'] || [], @@ -208,43 +207,6 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { this._onDidClearFilterText.fire(); } - private regiserActions(): void { - const that = this; - this._register(registerAction2(class extends Action2 { - constructor() { - super({ - id: `workbench.actions.treeView.${that.id}.collapseAll`, - title: localize('collapseAll', "Collapse All"), - menu: { - id: MenuId.ViewTitle, - when: ContextKeyEqualsExpr.create('view', that.id), - group: 'navigation', - order: Number.MAX_SAFE_INTEGER, - }, - icon: Codicon.collapseAll - }); - } - async run(): Promise { - return that.collapseAll(); - } - })); - this._register(registerAction2(class extends Action2 { - constructor() { - super({ - id: `workbench.actions.treeView.${that.id}.filter`, - title: localize('filter', "Filter"), - menu: { - id: MenuId.ViewTitle, - when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', that.id), Constants.MarkersViewSmallLayoutContextKey.negate()), - group: 'navigation', - order: 1, - }, - }); - } - async run(): Promise { } - })); - } - public showQuickFixes(marker: Marker): void { const viewModel = this.markersViewModel.getViewModel(marker); if (viewModel) { @@ -423,7 +385,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { const accessibilityProvider = this.instantiationService.createInstance(MarkersTreeAccessibilityProvider); const identityProvider = { - getId(element: TreeElement) { + getId(element: MarkerElement) { return element.id; } }; @@ -438,7 +400,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { accessibilityProvider, identityProvider, dnd: new ResourceDragAndDrop(this.instantiationService), - expandOnlyOnTwistieClick: (e: TreeElement) => e instanceof Marker && e.relatedInformation.length > 0, + expandOnlyOnTwistieClick: (e: MarkerElement) => e instanceof Marker && e.relatedInformation.length > 0, overrideStyles: { listBackground: this.getBackgroundColor() }, @@ -501,7 +463,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { this._register(this.tree.onDidChangeSelection(() => this.onSelected())); } - private collapseAll(): void { + collapseAll(): void { if (this.tree) { this.tree.collapseAll(); this.tree.setSelection([]); @@ -511,6 +473,10 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } } + setMultiline(multiline: boolean): void { + this.markersViewModel.multiline = multiline; + } + private onDidChangeMarkersViewVisibility(visible: boolean): void { this.onVisibleDisposables.clear(); if (visible) { @@ -790,7 +756,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { this.rangeHighlightDecorations.highlightRange(selection); } - private onContextMenu(e: ITreeContextMenuEvent): void { + private onContextMenu(e: ITreeContextMenuEvent): void { const element = e.element; if (!element) { return; @@ -817,7 +783,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { }); } - private getMenuActions(element: TreeElement): IAction[] { + private getMenuActions(element: MarkerElement): IAction[] { const result: IAction[] = []; if (element instanceof Marker) { @@ -845,8 +811,8 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { return result; } - public getFocusElement() { - return this.tree ? this.tree.getFocus()[0] : undefined; + public getFocusElement(): MarkerElement | undefined { + return this.tree?.getFocus()[0] || undefined; } public getActionViewItem(action: IAction): IActionViewItem | undefined { @@ -924,14 +890,14 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } -class MarkersTree extends WorkbenchObjectTree { +class MarkersTree extends WorkbenchObjectTree { constructor( user: string, readonly container: HTMLElement, - delegate: IListVirtualDelegate, - renderers: ITreeRenderer[], - options: IWorkbenchObjectTreeOptions, + delegate: IListVirtualDelegate, + renderers: ITreeRenderer[], + options: IWorkbenchObjectTreeOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, diff --git a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts index 973e9e697..7dacfc301 100644 --- a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts @@ -24,27 +24,11 @@ import { Marker } from 'vs/workbench/contrib/markers/browser/markersModel'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; -import { IViewsService } from 'vs/workbench/common/views'; import { Codicon } from 'vs/base/common/codicons'; import { BaseActionViewItem, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; - -export class ShowProblemsPanelAction extends Action { - - public static readonly ID = 'workbench.action.problems.focus'; - public static readonly LABEL = Messages.MARKERS_PANEL_SHOW_LABEL; - - constructor(id: string, label: string, - @IViewsService private readonly viewsService: IViewsService - ) { - super(id, label); - } - - public run(): Promise { - return this.viewsService.openView(Constants.MARKERS_VIEW_ID, true); - } -} +import { IMarkersView } from 'vs/workbench/contrib/markers/browser/markers'; export interface IMarkersFiltersChangeEvent { filterText?: boolean; @@ -164,14 +148,6 @@ export class MarkersFilters extends Disposable { } } -export interface IMarkerFilterController { - readonly onDidFocusFilter: Event; - readonly onDidClearFilterText: Event; - readonly filters: MarkersFilters; - readonly onDidChangeFilterStats: Event<{ total: number, filtered: number }>; - getFilterStats(): { total: number, filtered: number }; -} - class FiltersDropdownMenuActionViewItem extends DropdownMenuActionViewItem { constructor( @@ -271,7 +247,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { constructor( action: IAction, - private filterController: IMarkerFilterController, + private markersView: IMarkersView, @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextViewService private readonly contextViewService: IContextViewService, @IThemeService private readonly themeService: IThemeService, @@ -281,11 +257,11 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { this.focusContextKey = Constants.MarkerViewFilterFocusContextKey.bindTo(contextKeyService); this.delayedFilterUpdate = new Delayer(400); this._register(toDisposable(() => this.delayedFilterUpdate.cancel())); - this._register(filterController.onDidFocusFilter(() => this.focus())); - this._register(filterController.onDidClearFilterText(() => this.clearFilterText())); + this._register(markersView.onDidFocusFilter(() => this.focus())); + this._register(markersView.onDidClearFilterText(() => this.clearFilterText())); this.filtersAction = new Action('markersFiltersAction', Messages.MARKERS_PANEL_ACTION_TOOLTIP_MORE_FILTERS, 'markers-filters ' + ThemeIcon.asClassName(filterIcon)); this.filtersAction.checked = this.hasFiltersChanged(); - this._register(filterController.filters.onDidChange(e => this.onDidFiltersChange(e))); + this._register(markersView.filters.onDidChange(e => this.onDidFiltersChange(e))); } render(container: HTMLElement): void { @@ -321,21 +297,21 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { } private hasFiltersChanged(): boolean { - return !this.filterController.filters.showErrors || !this.filterController.filters.showWarnings || !this.filterController.filters.showInfos || this.filterController.filters.excludedFiles || this.filterController.filters.activeFile; + return !this.markersView.filters.showErrors || !this.markersView.filters.showWarnings || !this.markersView.filters.showInfos || this.markersView.filters.excludedFiles || this.markersView.filters.activeFile; } private createInput(container: HTMLElement): void { this.filterInputBox = this._register(this.instantiationService.createInstance(ContextScopedHistoryInputBox, container, this.contextViewService, { placeholder: Messages.MARKERS_PANEL_FILTER_PLACEHOLDER, ariaLabel: Messages.MARKERS_PANEL_FILTER_ARIA_LABEL, - history: this.filterController.filters.filterHistory + history: this.markersView.filters.filterHistory })); this._register(attachInputBoxStyler(this.filterInputBox, this.themeService)); - this.filterInputBox.value = this.filterController.filters.filterText; + this.filterInputBox.value = this.markersView.filters.filterText; this._register(this.filterInputBox.onDidChange(filter => this.delayedFilterUpdate.trigger(() => this.onDidInputChange(this.filterInputBox!)))); - this._register(this.filterController.filters.onDidChange((event: IMarkersFiltersChangeEvent) => { + this._register(this.markersView.filters.onDidChange((event: IMarkersFiltersChangeEvent) => { if (event.filterText) { - this.filterInputBox!.value = this.filterController.filters.filterText; + this.filterInputBox!.value = this.markersView.filters.filterText; } })); this._register(DOM.addStandardDisposableListener(this.filterInputBox.inputElement, DOM.EventType.KEY_DOWN, (e: any) => this.onInputKeyDown(e, this.filterInputBox!))); @@ -373,14 +349,14 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { filterBadge.style.color = foreground; })); this.updateBadge(); - this._register(this.filterController.onDidChangeFilterStats(() => this.updateBadge())); + this._register(this.markersView.onDidChangeFilterStats(() => this.updateBadge())); } private createFilters(container: HTMLElement): void { const actionbar = this._register(new ActionBar(container, { actionViewItemProvider: action => { if (action.id === this.filtersAction.id) { - return this.instantiationService.createInstance(FiltersDropdownMenuActionViewItem, action, this.filterController.filters, this.actionRunner); + return this.instantiationService.createInstance(FiltersDropdownMenuActionViewItem, action, this.markersView.filters, this.actionRunner); } return undefined; } @@ -390,13 +366,13 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { private onDidInputChange(inputbox: HistoryInputBox) { inputbox.addToHistory(); - this.filterController.filters.filterText = inputbox.value; - this.filterController.filters.filterHistory = inputbox.getHistory(); + this.markersView.filters.filterText = inputbox.value; + this.markersView.filters.filterHistory = inputbox.getHistory(); } private updateBadge(): void { if (this.filterBadge) { - const { total, filtered } = this.filterController.getFilterStats(); + const { total, filtered } = this.markersView.getFilterStats(); this.filterBadge.classList.toggle('hidden', total === filtered || total === 0); this.filterBadge.textContent = localize('showing filtered problems', "Showing {0} of {1}", filtered, total); this.adjustInputBox(); @@ -441,9 +417,9 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { } protected get class(): string { - if (this.filterController.filters.layout.width > 600) { + if (this.markersView.filters.layout.width > 600) { return 'markers-panel-action-filter grow'; - } else if (this.filterController.filters.layout.width < 400) { + } else if (this.markersView.filters.layout.width < 400) { return 'markers-panel-action-filter small'; } else { return 'markers-panel-action-filter'; diff --git a/src/vs/workbench/contrib/markers/browser/messages.ts b/src/vs/workbench/contrib/markers/browser/messages.ts index 5d7101256..d263f7e10 100644 --- a/src/vs/workbench/contrib/markers/browser/messages.ts +++ b/src/vs/workbench/contrib/markers/browser/messages.ts @@ -19,8 +19,8 @@ export default class Messages { public static MARKERS_PANEL_TITLE_PROBLEMS: string = nls.localize('markers.panel.title.problems', "Problems"); - public static MARKERS_PANEL_NO_PROBLEMS_BUILT: string = nls.localize('markers.panel.no.problems.build', "No problems have been detected in the workspace so far."); - public static MARKERS_PANEL_NO_PROBLEMS_ACTIVE_FILE_BUILT: string = nls.localize('markers.panel.no.problems.activeFile.build', "No problems have been detected in the current file so far."); + public static MARKERS_PANEL_NO_PROBLEMS_BUILT: string = nls.localize('markers.panel.no.problems.build', "No problems have been detected in the workspace."); + public static MARKERS_PANEL_NO_PROBLEMS_ACTIVE_FILE_BUILT: string = nls.localize('markers.panel.no.problems.activeFile.build', "No problems have been detected in the current file."); public static MARKERS_PANEL_NO_PROBLEMS_FILTERS: string = nls.localize('markers.panel.no.problems.filters', "No results found with provided filter criteria."); public static MARKERS_PANEL_ACTION_TOOLTIP_MORE_FILTERS: string = nls.localize('markers.panel.action.moreFilters', "More Filters..."); diff --git a/src/vs/workbench/contrib/notebook/browser/constants.ts b/src/vs/workbench/contrib/notebook/browser/constants.ts index 3232b0474..ab61c29ec 100644 --- a/src/vs/workbench/contrib/notebook/browser/constants.ts +++ b/src/vs/workbench/contrib/notebook/browser/constants.ts @@ -13,8 +13,8 @@ export const CELL_RUN_GUTTER = 28; export const CODE_CELL_LEFT_MARGIN = 32; export const EDITOR_TOOLBAR_HEIGHT = 0; -export const BOTTOM_CELL_TOOLBAR_GAP = 18; -export const BOTTOM_CELL_TOOLBAR_HEIGHT = 50; +export const BOTTOM_CELL_TOOLBAR_GAP = 16; +export const BOTTOM_CELL_TOOLBAR_HEIGHT = 24; export const CELL_STATUSBAR_HEIGHT = 22; // Margin above editor diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index acf32d1a9..9fb714811 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -23,11 +23,17 @@ import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/ import { CATEGORIES } from 'vs/workbench/common/actions'; import { BaseCellRenderTemplate, CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, EXPAND_CELL_CONTENT_COMMAND_ID, IActiveNotebookEditor, ICellViewModel, INotebookEditor, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { CellEditType, CellKind, ICellEditOperation, ICellRange, isDocumentExcludePattern, NotebookCellMetadata, NotebookCellRunState, NOTEBOOK_EDITOR_CURSOR_BEGIN_END, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellKind, ICellEditOperation, ICellRange, INotebookDocumentFilter, isDocumentExcludePattern, NotebookCellMetadata, NotebookCellRunState, NOTEBOOK_EDITOR_CURSOR_BEGIN_END, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; +import { EditorsOrder } from 'vs/workbench/common/editor'; +import { INotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidgetService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; // Notebook Commands const EXECUTE_NOTEBOOK_COMMAND_ID = 'notebook.execute'; @@ -137,14 +143,17 @@ abstract class NotebookAction extends Action2 { super(desc); } - async run(accessor: ServicesAccessor, context?: INotebookActionContext): Promise { + async run(accessor: ServicesAccessor, context?: any): Promise { if (!this.isNotebookActionContext(context)) { - context = this.getActiveEditorContext(accessor); + context = this.getEditorContextFromArgsOrActive(accessor, context); if (!context) { return; } } + const telemetryService = accessor.get(ITelemetryService); + telemetryService.publicLog2('workbenchActionExecuted', { id: this.desc.id, from: 'ui' }); + this.runWithContext(accessor, context); } @@ -154,7 +163,7 @@ abstract class NotebookAction extends Action2 { return !!context && !!(context as INotebookActionContext).notebookEditor; } - protected getActiveEditorContext(accessor: ServicesAccessor): INotebookActionContext | undefined { + protected getEditorContextFromArgsOrActive(accessor: ServicesAccessor, context?: any): INotebookActionContext | undefined { const editorService = accessor.get(IEditorService); const editor = getActiveNotebookEditor(editorService); @@ -179,22 +188,22 @@ abstract class NotebookCellAction extends Notebo return !!context && !!(context as INotebookCellActionContext).notebookEditor && !!(context as INotebookCellActionContext).cell; } - protected getCellContextFromArgs(accessor: ServicesAccessor, context?: T): INotebookCellActionContext | undefined { + protected getCellContextFromArgs(accessor: ServicesAccessor, context?: T, ...additionalArgs: any[]): INotebookCellActionContext | undefined { return undefined; } - async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise { + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext, ...additionalArgs: any[]): Promise { if (this.isCellActionContext(context)) { return this.runWithContext(accessor, context); } - const contextFromArgs = this.getCellContextFromArgs(accessor, context); + const contextFromArgs = this.getCellContextFromArgs(accessor, context, ...additionalArgs); if (contextFromArgs) { return this.runWithContext(accessor, contextFromArgs); } - const activeEditorContext = this.getActiveEditorContext(accessor); + const activeEditorContext = this.getEditorContextFromArgsOrActive(accessor); if (this.isCellActionContext(activeEditorContext)) { return this.runWithContext(accessor, activeEditorContext); } @@ -203,6 +212,22 @@ abstract class NotebookCellAction extends Notebo abstract runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise; } +export function getWidgetFromUri(accessor: ServicesAccessor, uri: URI) { + const editorService = accessor.get(IEditorService); + const notebookWidgetService = accessor.get(INotebookEditorWidgetService); + const editorId = editorService.getEditors(EditorsOrder.SEQUENTIAL).find(editorId => editorId.editor instanceof NotebookEditorInput && editorId.editor.resource?.toString() === uri.toString()); + + if (editorId) { + const widget = notebookWidgetService.widgets.find(widget => widget.textModel?.viewType === (editorId.editor as NotebookEditorInput).viewType && widget.uri?.toString() === editorId.editor.resource!.toString()); + + if (widget && widget.hasModel()) { + return widget; + } + } + + return undefined; +} + registerAction2(class extends NotebookCellAction { constructor() { super({ @@ -234,6 +259,11 @@ registerAction2(class extends NotebookCellAction { } } } + }, + { + name: 'uri', + description: 'The document uri', + constraint: URI } ] }, @@ -241,12 +271,28 @@ registerAction2(class extends NotebookCellAction { }); } - getCellContextFromArgs(accessor: ServicesAccessor, context?: ICellRange): INotebookCellActionContext | undefined { + getCellContextFromArgs(accessor: ServicesAccessor, context?: ICellRange, ...additionalArgs: any[]): INotebookCellActionContext | undefined { if (!context || typeof context.start !== 'number' || typeof context.end !== 'number' || context.start >= context.end) { return; } - const activeEditorContext = this.getActiveEditorContext(accessor); + if (additionalArgs.length && additionalArgs[0]) { + const uri = URI.revive(additionalArgs[0]); + + if (uri) { + const widget = getWidgetFromUri(accessor, uri); + if (widget) { + const cells = widget.viewModel.viewCells; + + return { + notebookEditor: widget, + cell: cells[context.start] + }; + } + } + } + + const activeEditorContext = this.getEditorContextFromArgsOrActive(accessor); if (!activeEditorContext || !activeEditorContext.notebookEditor.viewModel || context.start >= activeEditorContext.notebookEditor.viewModel.viewCells.length) { return; @@ -273,7 +319,7 @@ registerAction2(class extends NotebookCellAction { title: localize('notebookActions.cancel', "Stop Cell Execution"), icon: icons.stopIcon, description: { - description: localize('notebookActions.execute', "Execute Cell"), + description: localize('notebookActions.cancel', "Stop Cell Execution"), args: [ { name: 'range', @@ -290,18 +336,39 @@ registerAction2(class extends NotebookCellAction { } } } + }, + { + name: 'uri', + description: 'The document uri', + constraint: URI } ] }, }); } - getCellContextFromArgs(accessor: ServicesAccessor, context?: ICellRange): INotebookCellActionContext | undefined { + getCellContextFromArgs(accessor: ServicesAccessor, context?: ICellRange, ...additionalArgs: any[]): INotebookCellActionContext | undefined { if (!context || typeof context.start !== 'number' || typeof context.end !== 'number' || context.start >= context.end) { return; } - const activeEditorContext = this.getActiveEditorContext(accessor); + if (additionalArgs.length && additionalArgs[0]) { + const uri = URI.revive(additionalArgs[0]); + + if (uri) { + const widget = getWidgetFromUri(accessor, uri); + if (widget) { + const cells = widget.viewModel.viewCells; + + return { + notebookEditor: widget, + cell: cells[context.start] + }; + } + } + } + + const activeEditorContext = this.getEditorContextFromArgsOrActive(accessor); if (!activeEditorContext || !activeEditorContext.notebookEditor.viewModel || context.start >= activeEditorContext.notebookEditor.viewModel.viewCells.length) { return; @@ -455,19 +522,62 @@ registerAction2(class extends NotebookAction { super({ id: EXECUTE_NOTEBOOK_COMMAND_ID, title: localize('notebookActions.executeNotebook', "Execute Notebook"), + description: { + description: localize('notebookActions.executeNotebook', "Execute Notebook"), + args: [ + { + name: 'uri', + description: 'The document uri', + constraint: URI + } + ] + }, }); } + getEditorContextFromArgsOrActive(accessor: ServicesAccessor, context?: UriComponents): INotebookActionContext | undefined { + if (context) { + const uri = URI.revive(context); + + if (uri) { + const widget = getWidgetFromUri(accessor, uri); + + if (widget) { + return { + notebookEditor: widget, + }; + } + } + } + + const editorService = accessor.get(IEditorService); + const editor = getActiveNotebookEditor(editorService); + if (!editor) { + return; + } + + if (!editor.hasModel()) { + return; + } + + const activeCell = editor.getActiveCell(); + return { + cell: activeCell, + notebookEditor: editor + }; + } + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { renderAllMarkdownCells(context); + const editorService = accessor.get(IEditorService); + const editor = editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).find( + editor => editor.editor instanceof NotebookEditorInput && editor.editor.viewType === context.notebookEditor.viewModel.viewType && editor.editor.resource.toString() === context.notebookEditor.viewModel.uri.toString()); const editorGroupService = accessor.get(IEditorGroupsService); - const group = editorGroupService.activeGroup; - if (group) { - if (group.activeEditor) { - group.pinEditor(group.activeEditor); - } + if (editor) { + const group = editorGroupService.getGroup(editor.groupId); + group?.pinEditor(editor.editor); } return context.notebookEditor.executeNotebook(); @@ -487,9 +597,51 @@ registerAction2(class extends NotebookAction { super({ id: CANCEL_NOTEBOOK_COMMAND_ID, title: localize('notebookActions.cancelNotebook', "Cancel Notebook Execution"), + description: { + description: localize('notebookActions.cancelNotebook', "Cancel Notebook Execution"), + args: [ + { + name: 'uri', + description: 'The document uri', + constraint: URI + } + ] + }, }); } + getEditorContextFromArgsOrActive(accessor: ServicesAccessor, context?: UriComponents): INotebookActionContext | undefined { + if (context) { + const uri = URI.revive(context); + + if (uri) { + const widget = getWidgetFromUri(accessor, uri); + + if (widget) { + return { + notebookEditor: widget, + }; + } + } + } + + const editorService = accessor.get(IEditorService); + const editor = getActiveNotebookEditor(editorService); + if (!editor) { + return; + } + + if (!editor.hasModel()) { + return; + } + + const activeCell = editor.getActiveCell(); + return { + cell: activeCell, + notebookEditor: editor + }; + } + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { return context.notebookEditor.cancelNotebookExecution(); } @@ -580,7 +732,7 @@ registerAction2(class extends NotebookCellAction { export function getActiveNotebookEditor(editorService: IEditorService): INotebookEditor | undefined { // TODO@roblourens can `isNotebookEditor` be on INotebookEditor to avoid a circular dependency? - const activeEditorPane = editorService.activeEditorPane as unknown as { isNotebookEditor?: boolean } | undefined; + const activeEditorPane = editorService.activeEditorPane as unknown as { isNotebookEditor?: boolean; } | undefined; return activeEditorPane?.isNotebookEditor ? (editorService.activeEditorPane?.getControl() as INotebookEditor) : undefined; } @@ -709,7 +861,7 @@ registerAction2(class extends NotebookAction { } async run(accessor: ServicesAccessor): Promise { - const context = this.getActiveEditorContext(accessor); + const context = this.getEditorContextFromArgsOrActive(accessor); if (context) { this.runWithContext(accessor, context); } @@ -734,7 +886,7 @@ registerAction2(class extends NotebookAction { } async run(accessor: ServicesAccessor): Promise { - const context = this.getActiveEditorContext(accessor); + const context = this.getEditorContextFromArgsOrActive(accessor); if (context) { this.runWithContext(accessor, context); } @@ -1891,8 +2043,8 @@ CommandsRegistry.registerCommand('notebook.trust', (accessor, args) => { CommandsRegistry.registerCommand('_resolveNotebookContentProvider', (accessor, args): { viewType: string; displayName: string; - options: { transientOutputs: boolean; transientMetadata: TransientMetadata }; - filenamePattern: (string | glob.IRelativePattern | { include: string | glob.IRelativePattern, exclude: string | glob.IRelativePattern })[] + options: { transientOutputs: boolean; transientMetadata: TransientMetadata; }; + filenamePattern: (string | glob.IRelativePattern | { include: string | glob.IRelativePattern, exclude: string | glob.IRelativePattern; })[]; }[] => { const notebookService = accessor.get(INotebookService); const contentProviders = notebookService.getContributedNotebookProviders(); @@ -1914,7 +2066,7 @@ CommandsRegistry.registerCommand('_resolveNotebookContentProvider', (accessor, a } return null; - }).filter(pattern => pattern !== null) as (string | glob.IRelativePattern | { include: string | glob.IRelativePattern, exclude: string | glob.IRelativePattern })[]; + }).filter(pattern => pattern !== null) as (string | glob.IRelativePattern | { include: string | glob.IRelativePattern, exclude: string | glob.IRelativePattern; })[]; return { viewType: provider.id, @@ -1924,3 +2076,44 @@ CommandsRegistry.registerCommand('_resolveNotebookContentProvider', (accessor, a }; }); }); + +CommandsRegistry.registerCommand('_resolveNotebookKernelProviders', async (accessor, args): Promise<{ + extensionId: string; + description?: string; + selector: INotebookDocumentFilter; +}[]> => { + const notebookService = accessor.get(INotebookService); + const providers = await notebookService.getContributedNotebookKernelProviders(); + return providers.map(provider => ({ + extensionId: provider.providerExtensionId, + description: provider.providerDescription, + selector: provider.selector + })); +}); + +CommandsRegistry.registerCommand('_resolveNotebookKernels', async (accessor, args: { + viewType: string; + uri: UriComponents; +}): Promise<{ + id?: string; + label: string; + description?: string; + detail?: string; + isPreferred?: boolean; + preloads?: URI[]; +}[]> => { + const notebookService = accessor.get(INotebookService); + const uri = URI.revive(args.uri as UriComponents); + const source = new CancellationTokenSource(); + const kernels = await notebookService.getContributedNotebookKernels(args.viewType, uri, source.token); + source.dispose(); + + return kernels.map(provider => ({ + id: provider.friendlyId, + label: provider.label, + description: provider.description, + detail: provider.detail, + isPreferred: provider.isPreferred, + preloads: provider.preloads?.map(preload => URI.revive(preload)) || [] + })); +}); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts index 2c2c46b89..c7f0f53cd 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/notebookFind'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED, INotebookEditor, CellFindMatch, CellEditState, INotebookEditorContribution, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED, INotebookEditor, CellFindMatch, CellEditState, INotebookEditorContribution, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_OPEN } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { FindDecorations } from 'vs/editor/contrib/find/findDecorations'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IModelDeltaDecoration } from 'vs/editor/common/model'; @@ -385,7 +385,7 @@ registerAction2(class extends Action2 { id: 'notebook.find', title: { value: localize('notebookActions.findInNotebook', "Find in Notebook"), original: 'Find in Notebook' }, keybinding: { - when: NOTEBOOK_EDITOR_FOCUSED, + when: ContextKeyExpr.or(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_OPEN), primary: KeyCode.KEY_F | KeyMod.CtrlCmd, weight: KeybindingWeight.WorkbenchContrib } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.css b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.css new file mode 100644 index 000000000..6e71e660f --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.css @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-list .notebook-outline-element { + display: flex; + flex: 1; + flex-flow: row nowrap; + align-items: center; +} + +.monaco-list .notebook-outline-element > .element-icon.file-icon { + height: 100%; +} + +.monaco-breadcrumbs > .notebook-outline-element > .element-icon.file-icon { + height: 18px; +} +.monaco-list .notebook-outline-element .monaco-highlighted-label { + color: var(--outline-element-color); +} + +.monaco-breadcrumbs .notebook-outline-element .element-decoration, +.monaco-list .notebook-outline-element > .element-decoration { + opacity: 0.75; + font-size: 90%; + font-weight: 600; + padding: 0 12px 0 5px; + margin-left: auto; + text-align: center; + color: var(--outline-element-color); +} + +.monaco-list .notebook-outline-element > .element-decoration.bubble { + font-family: codicon; + font-size: 14px; + opacity: 0.4; + padding-right: 8px; +} + +.monaco-breadcrumbs .notebook-outline-element .element-decoration { + /* Don't show markers inline with breadcrumbs */ + display: none; +} diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts new file mode 100644 index 000000000..f6c2dd201 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts @@ -0,0 +1,607 @@ +/*--------------------------------------------------------------------------------------------- + * 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!./notebookOutline'; +import { Codicon } from 'vs/base/common/codicons'; +import { Emitter, Event } from 'vs/base/common/event'; +import { combinedDisposable, IDisposable, Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IOutline, IOutlineComparator, IOutlineCreator, IOutlineListConfig, IOutlineService, IQuickPickDataSource, IQuickPickOutlineElement, OutlineChangeEvent, OutlineConfigKeys, OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IEditorPane } from 'vs/workbench/common/editor'; +import { IKeyboardNavigationLabelProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { IDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; +import { createMatches, FuzzyScore } from 'vs/base/common/filters'; +import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { IEditorOptions } from 'vs/platform/editor/common/editor'; +import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { getIconClassesForModeId } from 'vs/editor/common/services/getIconClasses'; +import { IWorkbenchDataTreeOptions } from 'vs/platform/list/browser/listService'; +import { localize } from 'vs/nls'; +import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; +import { listErrorForeground, listWarningForeground } from 'vs/platform/theme/common/colorRegistry'; +import { isEqual } from 'vs/base/common/resources'; +import { IdleValue } from 'vs/base/common/async'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import * as marked from 'vs/base/common/marked/marked'; +import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; + +export interface IOutlineMarkerInfo { + readonly count: number; + readonly topSev: MarkerSeverity; +} + +export class OutlineEntry { + + private _children: OutlineEntry[] = []; + private _parent: OutlineEntry | undefined; + private _markerInfo: IOutlineMarkerInfo | undefined; + + constructor( + readonly index: number, + readonly level: number, + readonly cell: ICellViewModel, + readonly label: string, + readonly icon: ThemeIcon + ) { } + + addChild(entry: OutlineEntry) { + this._children.push(entry); + entry._parent = this; + } + + get parent(): OutlineEntry | undefined { + return this._parent; + } + + get children(): Iterable { + return this._children; + } + + get markerInfo(): IOutlineMarkerInfo | undefined { + return this._markerInfo; + } + + updateMarkers(markerService: IMarkerService): void { + if (this.cell.cellKind === CellKind.Code) { + // a code cell can have marker + const marker = markerService.read({ resource: this.cell.uri, severities: MarkerSeverity.Error | MarkerSeverity.Warning }); + if (marker.length === 0) { + this._markerInfo = undefined; + } else { + const topSev = marker.find(a => a.severity === MarkerSeverity.Error)?.severity ?? MarkerSeverity.Warning; + this._markerInfo = { topSev, count: marker.length }; + } + } else { + // a markdown cell can inherit markers from its children + let topChild: MarkerSeverity | undefined; + for (let child of this.children) { + child.updateMarkers(markerService); + if (child.markerInfo) { + topChild = !topChild ? child.markerInfo.topSev : Math.max(child.markerInfo.topSev, topChild); + } + } + this._markerInfo = topChild && { topSev: topChild, count: 0 }; + } + } + + clearMarkers(): void { + this._markerInfo = undefined; + for (let child of this.children) { + child.clearMarkers(); + } + } + + find(cell: ICellViewModel, parents: OutlineEntry[]): OutlineEntry | undefined { + if (cell.id === this.cell.id) { + return this; + } + parents.push(this); + for (let child of this.children) { + const result = child.find(cell, parents); + if (result) { + return result; + } + } + parents.pop(); + return undefined; + } + + asFlatList(bucket: OutlineEntry[]): void { + bucket.push(this); + for (let child of this.children) { + child.asFlatList(bucket); + } + } +} + +class NotebookOutlineTemplate { + + static readonly templateId = 'NotebookOutlineRenderer'; + + constructor( + readonly container: HTMLElement, + readonly iconClass: HTMLElement, + readonly iconLabel: IconLabel, + readonly decoration: HTMLElement + ) { } +} + +class NotebookOutlineRenderer implements ITreeRenderer { + + templateId: string = NotebookOutlineTemplate.templateId; + + constructor( + @IThemeService private readonly _themeService: IThemeService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + ) { } + + renderTemplate(container: HTMLElement): NotebookOutlineTemplate { + container.classList.add('notebook-outline-element', 'show-file-icons'); + const iconClass = document.createElement('div'); + container.append(iconClass); + const iconLabel = new IconLabel(container, { supportHighlights: true }); + const decoration = document.createElement('div'); + decoration.className = 'element-decoration'; + container.append(decoration); + return new NotebookOutlineTemplate(container, iconClass, iconLabel, decoration); + } + + renderElement(node: ITreeNode, _index: number, template: NotebookOutlineTemplate, _height: number | undefined): void { + const options: IIconLabelValueOptions = { + matches: createMatches(node.filterData), + extraClasses: [] + }; + + if (node.element.cell.cellKind === CellKind.Code && this._themeService.getFileIconTheme().hasFileIcons) { + template.iconClass.className = ''; + options.extraClasses?.push(...getIconClassesForModeId(node.element.cell.language ?? '')); + } else { + template.iconClass.className = 'element-icon ' + ThemeIcon.asClassNameArray(node.element.icon).join(' '); + } + + template.iconLabel.setLabel(node.element.label, undefined, options); + + const { markerInfo } = node.element; + + template.container.style.removeProperty('--outline-element-color'); + template.decoration.innerText = ''; + if (markerInfo) { + const useBadges = this._configurationService.getValue(OutlineConfigKeys.problemsBadges); + if (!useBadges) { + template.decoration.classList.remove('bubble'); + template.decoration.innerText = ''; + } else if (markerInfo.count === 0) { + template.decoration.classList.add('bubble'); + template.decoration.innerText = '\uea71'; + } else { + template.decoration.classList.remove('bubble'); + template.decoration.innerText = markerInfo.count > 9 ? '9+' : String(markerInfo.count); + } + const color = this._themeService.getColorTheme().getColor(markerInfo.topSev === MarkerSeverity.Error ? listErrorForeground : listWarningForeground); + const useColors = this._configurationService.getValue(OutlineConfigKeys.problemsColors); + if (!useColors) { + template.container.style.removeProperty('--outline-element-color'); + template.decoration.style.setProperty('--outline-element-color', color?.toString() ?? 'inherit'); + } else { + template.container.style.setProperty('--outline-element-color', color?.toString() ?? 'inherit'); + } + } + } + + disposeTemplate(templateData: NotebookOutlineTemplate): void { + templateData.iconLabel.dispose(); + } +} + +class NotebookOutlineAccessibility implements IListAccessibilityProvider { + getAriaLabel(element: OutlineEntry): string | null { + return element.label; + } + getWidgetAriaLabel(): string { + return ''; + } +} + +class NotebookNavigationLabelProvider implements IKeyboardNavigationLabelProvider { + getKeyboardNavigationLabel(element: OutlineEntry): { toString(): string | undefined; } | { toString(): string | undefined; }[] | undefined { + return element.label; + } +} + +class NotebookOutlineVirtualDelegate implements IListVirtualDelegate { + + getHeight(_element: OutlineEntry): number { + return 22; + } + + getTemplateId(_element: OutlineEntry): string { + return NotebookOutlineTemplate.templateId; + } +} + +class NotebookQuickPickProvider implements IQuickPickDataSource { + + constructor( + private _getEntries: () => OutlineEntry[], + @IThemeService private readonly _themeService: IThemeService + ) { } + + getQuickPickElements(): Iterable> { + const bucket: OutlineEntry[] = []; + for (let entry of this._getEntries()) { + entry.asFlatList(bucket); + } + const result: IQuickPickOutlineElement[] = []; + const { hasFileIcons } = this._themeService.getFileIconTheme(); + for (let element of bucket) { + // todo@jrieken it is fishy that codicons cannot be used with iconClasses + // but file icons can... + result.push({ + element, + label: hasFileIcons ? element.label : `$(${element.icon.id}) ${element.label}`, + ariaLabel: element.label, + iconClasses: hasFileIcons ? getIconClassesForModeId(element.cell.language ?? '') : undefined, + }); + } + return result; + } +} + +class NotebookComparator implements IOutlineComparator { + + private readonly _collator = new IdleValue(() => new Intl.Collator(undefined, { numeric: true })); + + compareByPosition(a: OutlineEntry, b: OutlineEntry): number { + return a.index - b.index; + } + compareByType(a: OutlineEntry, b: OutlineEntry): number { + return a.cell.cellKind - b.cell.cellKind || this._collator.value.compare(a.label, b.label); + } + compareByName(a: OutlineEntry, b: OutlineEntry): number { + return this._collator.value.compare(a.label, b.label); + } +} + +class NotebookCellOutline implements IOutline { + + private readonly _dispoables = new DisposableStore(); + + private readonly _onDidChange = new Emitter(); + + readonly onDidChange: Event = this._onDidChange.event; + + private _entries: OutlineEntry[] = []; + private _activeEntry?: OutlineEntry; + private readonly _entriesDisposables = new DisposableStore(); + + readonly config: IOutlineListConfig; + readonly outlineKind = 'notebookCells'; + + get activeElement(): OutlineEntry | undefined { + return this._activeEntry; + } + + constructor( + private readonly _editor: NotebookEditor, + private readonly _target: OutlineTarget, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IEditorService private readonly _editorService: IEditorService, + @IMarkerService private readonly _markerService: IMarkerService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + ) { + const selectionListener = new MutableDisposable(); + this._dispoables.add(selectionListener); + const installSelectionListener = () => { + if (!_editor.viewModel) { + selectionListener.clear(); + } else { + selectionListener.value = combinedDisposable( + _editor.viewModel.onDidChangeSelection(() => this._recomputeActive()), + _editor.viewModel.onDidChangeViewCells(() => this._recomputeState()) + ); + } + }; + + this._dispoables.add(_editor.onDidChangeModel(() => { + this._recomputeState(); + installSelectionListener(); + })); + + this._dispoables.add(_configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('notebook.outline.showCodeCells')) { + this._recomputeState(); + } + })); + + this._dispoables.add(themeService.onDidFileIconThemeChange(() => { + this._onDidChange.fire({}); + })); + + this._recomputeState(); + installSelectionListener(); + + const options: IWorkbenchDataTreeOptions = { + collapseByDefault: _target === OutlineTarget.Breadcrumbs, + expandOnlyOnTwistieClick: true, + multipleSelectionSupport: false, + accessibilityProvider: new NotebookOutlineAccessibility(), + identityProvider: { getId: element => element.cell.id }, + keyboardNavigationLabelProvider: new NotebookNavigationLabelProvider() + }; + + const treeDataSource: IDataSource = { getChildren: parent => parent instanceof NotebookCellOutline ? this._entries : parent.children }; + const delegate = new NotebookOutlineVirtualDelegate(); + const renderers = [instantiationService.createInstance(NotebookOutlineRenderer)]; + const comparator = new NotebookComparator(); + + this.config = { + breadcrumbsDataSource: { + getBreadcrumbElements: () => { + let result: OutlineEntry[] = []; + let candidate = this._activeEntry; + while (candidate) { + result.unshift(candidate); + candidate = candidate.parent; + } + return result; + } + }, + quickPickDataSource: instantiationService.createInstance(NotebookQuickPickProvider, () => this._entries), + treeDataSource, + delegate, + renderers, + comparator, + options + }; + } + + dispose(): void { + this._dispoables.dispose(); + this._entriesDisposables.dispose(); + } + + private _recomputeState(): void { + this._entriesDisposables.clear(); + this._activeEntry = undefined; + this._entries.length = 0; + + const { viewModel } = this._editor; + if (!viewModel) { + return; + } + + let includeCodeCells = true; + if (this._target === OutlineTarget.OutlinePane) { + includeCodeCells = this._configurationService.getValue('notebook.outline.showCodeCells'); + } else if (this._target === OutlineTarget.Breadcrumbs) { + includeCodeCells = this._configurationService.getValue('notebook.breadcrumbs.showCodeCells'); + } + + const [selected] = viewModel.selectionHandles; + const entries: OutlineEntry[] = []; + + for (let i = 0; i < viewModel.viewCells.length; i++) { + const cell = viewModel.viewCells[i]; + const isMarkdown = cell.cellKind === CellKind.Markdown; + if (!isMarkdown && !includeCodeCells) { + continue; + } + + // anaslse cell text but cap it 10000 characters + let content = cell.getText().substr(0, 10_000); + let level = 7; + + if (isMarkdown) { + // MD cell: "render" as plain text, find highest header + for (const token of marked.lexer(content, { gfm: true })) { + if (token.type === 'heading') { + level = Math.min(level, token.depth); + } + } + content = renderMarkdownAsPlaintext({ value: content }); + } + + // find first none empty line or use default text + const lineMatch = content.match(/^.*\w+.*\w*$/m); + let preview: string; + if (!lineMatch) { + preview = localize('empty', "empty cell"); + } else { + preview = lineMatch[0].trim(); + if (preview.length >= 64) { + preview = preview.slice(0, 64) + '…'; + } + } + + const entry = new OutlineEntry(i, level, cell, preview, isMarkdown ? Codicon.markdown : Codicon.code); + entries.push(entry); + if (cell.handle === selected) { + this._activeEntry = entry; + } + + // send an event whenever any of the cells change + this._entriesDisposables.add(cell.model.onDidChangeContent(() => { + this._recomputeState(); + this._onDidChange.fire({}); + })); + } + + // build a tree from the list of entries + if (entries.length > 0) { + let result: OutlineEntry[] = [entries[0]]; + let parentStack: OutlineEntry[] = [entries[0]]; + + for (let i = 1; i < entries.length; i++) { + let entry = entries[i]; + + while (true) { + const len = parentStack.length; + if (len === 0) { + // root node + result.push(entry); + parentStack.push(entry); + break; + + } else { + let 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 = () => { + const doUpdateMarker = (clear: boolean) => { + for (let entry of this._entries) { + if (clear) { + entry.clearMarkers(); + } else { + entry.updateMarkers(this._markerService); + } + } + }; + if (this._configurationService.getValue(OutlineConfigKeys.problemsEnabled)) { + markerServiceListener.value = this._markerService.onMarkerChanged(e => { + if (e.some(uri => viewModel.viewCells.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(OutlineConfigKeys.problemsEnabled)) { + updateMarkerUpdater(); + this._onDidChange.fire({}); + } + })); + + this._onDidChange.fire({}); + } + + private _recomputeActive(): void { + let newActive: OutlineEntry | undefined; + const { viewModel } = this._editor; + + if (viewModel) { + const [selected] = viewModel.selectionHandles; + const cell = viewModel.getCellByHandle(selected); + if (cell) { + for (let entry of this._entries) { + newActive = entry.find(cell, []); + if (newActive) { + break; + } + } + } + } + if (newActive !== this._activeEntry) { + this._activeEntry = newActive; + this._onDidChange.fire({ affectOnlyActiveElement: true }); + } + } + + get isEmpty(): boolean { + return this._entries.length === 0; + } + + async reveal(entry: OutlineEntry, options: IEditorOptions, sideBySide: boolean): Promise { + await this._editorService.openEditor({ + resource: entry.cell.uri, + options, + }, sideBySide ? SIDE_GROUP : undefined); + } + + preview(entry: OutlineEntry): IDisposable { + const widget = this._editor.getControl(); + if (!widget) { + return Disposable.None; + } + widget.revealInCenterIfOutsideViewport(entry.cell); + const ids = widget.deltaCellDecorations([], [{ + handle: entry.cell.handle, + options: { className: 'nb-symbolHighlight', outputClassName: 'nb-symbolHighlight' } + }]); + return toDisposable(() => { widget.deltaCellDecorations(ids, []); }); + + } + + captureViewState(): IDisposable { + const widget = this._editor.getControl(); + let viewState = widget?.getEditorViewState(); + return toDisposable(() => { + if (viewState) { + widget?.restoreListViewState(viewState); + } + }); + } +} + +class NotebookOutlineCreator implements IOutlineCreator { + + readonly dispose: () => void; + + constructor( + @IOutlineService outlineService: IOutlineService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + ) { + const reg = outlineService.registerOutlineCreator(this); + this.dispose = () => reg.dispose(); + } + + matches(candidate: IEditorPane): candidate is NotebookEditor { + return candidate.getId() === NotebookEditor.ID; + } + + async createOutline(editor: NotebookEditor, target: OutlineTarget): Promise | undefined> { + return this._instantiationService.createInstance(NotebookCellOutline, editor, target); + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(NotebookOutlineCreator, LifecyclePhase.Eventually); + + +Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ + id: 'notebook', + order: 100, + type: 'object', + 'properties': { + 'notebook.outline.showCodeCells': { + type: 'boolean', + default: false, + markdownDescription: localize('outline.showCodeCells', "When enabled notebook outline shows code cells.") + }, + 'notebook.breadcrumbs.showCodeCells': { + type: 'boolean', + default: true, + markdownDescription: localize('breadcrumbs.showCodeCells', "When enabled notebook breadcrumbs contain code cells.") + }, + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts b/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts index 2f32225cb..d3bc0b2ab 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts @@ -8,7 +8,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; -import { INotebookActionContext, NOTEBOOK_ACTIONS_CATEGORY, getActiveNotebookEditor } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; +import { NOTEBOOK_ACTIONS_CATEGORY, getActiveNotebookEditor } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; import { INotebookEditor, NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; @@ -33,11 +33,33 @@ registerAction2(class extends Action2 { title: { value: nls.localize('notebookActions.selectKernel', "Select Notebook Kernel"), original: 'Select Notebook Kernel' }, precondition: NOTEBOOK_IS_ACTIVE_EDITOR, icon: selectKernelIcon, - f1: true + f1: true, + description: { + description: nls.localize('notebookActions.selectKernel.args', "Notebook Kernel Args"), + args: [ + { + name: 'kernelInfo', + description: 'The kernel info', + schema: { + 'type': 'object', + 'required': ['id', 'extension'], + 'properties': { + 'id': { + 'type': 'string' + }, + 'extension': { + 'type': 'string' + } + } + } + } + ] + }, + }); } - async run(accessor: ServicesAccessor, context?: INotebookActionContext): Promise { + async run(accessor: ServicesAccessor, context?: { id: string, extension: string }): Promise { const editorService = accessor.get(IEditorService); const quickInputService = accessor.get(IQuickInputService); const configurationService = accessor.get(IConfigurationService); @@ -52,20 +74,38 @@ registerAction2(class extends Action2 { const picker = quickInputService.createQuickPick<(IQuickPickItem & { run(): void; kernelProviderId?: string })>(); picker.placeholder = nls.localize('notebook.runCell.selectKernel', "Select a notebook kernel to run this notebook"); picker.matchOnDetail = true; - picker.show(); + + + if (context && context.id) { + } else { + picker.show(); + } + picker.busy = true; const tokenSource = new CancellationTokenSource(); - const availableKernels2 = await editor.beginComputeContributedKernels(); - const picks: QuickPickInput[] = [...availableKernels2].map((a) => { + const availableKernels = await editor.beginComputeContributedKernels(); + + const selectedKernel = availableKernels.length ? availableKernels.find( + kernel => kernel.id && context?.id && kernel.id === context?.id && kernel.extension.value === context?.extension + ) : undefined; + + if (selectedKernel) { + editor.activeKernel = selectedKernel!; + return selectedKernel!.resolve(editor.uri!, editor.getId(), tokenSource.token); + } else { + picker.show(); + } + + const picks: QuickPickInput[] = [...availableKernels].map((a) => { return { - id: a.id, + id: a.friendlyId, label: a.label, - picked: a.id === activeKernel?.id, + picked: a.friendlyId === activeKernel?.friendlyId, description: a.description ? a.description - : a.extension.value + (a.id === activeKernel?.id + : a.extension.value + (a.friendlyId === activeKernel?.friendlyId ? nls.localize('currentActiveKernel', " (Currently Active)") : ''), detail: a.detail, diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/toc/tocProvider.ts b/src/vs/workbench/contrib/notebook/browser/contrib/toc/tocProvider.ts deleted file mode 100644 index 48599557f..000000000 --- a/src/vs/workbench/contrib/notebook/browser/contrib/toc/tocProvider.ts +++ /dev/null @@ -1,72 +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 { TableOfContentsProviderRegistry, ITableOfContentsProvider, ITableOfContentsEntry } from 'vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess'; -import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; -import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { Codicon } from 'vs/base/common/codicons'; -import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; - -TableOfContentsProviderRegistry.register(NotebookEditor.ID, new class implements ITableOfContentsProvider { - async provideTableOfContents(editor: NotebookEditor, context: { disposables: DisposableStore }) { - if (!editor.viewModel) { - return undefined; - } - // return an entry per markdown header - const notebookWidget = editor.getControl(); - if (!notebookWidget) { - return undefined; - } - - // restore initial view state when no item was picked - let didPickOne = false; - const viewState = notebookWidget.getEditorViewState(); - context.disposables.add(toDisposable(() => { - if (!didPickOne) { - notebookWidget.restoreListViewState(viewState); - } - })); - - let lastDecorationId: string[] = []; - const result: ITableOfContentsEntry[] = []; - for (const cell of editor.viewModel.viewCells) { - const content = cell.getText(); - const regexp = cell.cellKind === CellKind.Markdown - ? /^[ \t]*(\#+)(.+)$/gm // md: header - : /^.*\w+.*\w*$/m; // code: none empty line - - const matches = content.match(regexp); - if (matches && matches.length) { - for (let j = 0; j < matches.length; j++) { - result.push({ - icon: cell.cellKind === CellKind.Markdown ? Codicon.markdown : Codicon.code, - label: matches[j].replace(/^[ \t]*(\#+)/, ''), - pick() { - didPickOne = true; - notebookWidget.revealInCenterIfOutsideViewport(cell); - notebookWidget.selectElement(cell); - notebookWidget.focusNotebookCell(cell, cell.cellKind === CellKind.Markdown ? 'container' : 'editor'); - lastDecorationId = notebookWidget.deltaCellDecorations(lastDecorationId, []); - }, - preview() { - notebookWidget.revealInCenterIfOutsideViewport(cell); - notebookWidget.selectElement(cell); - lastDecorationId = notebookWidget.deltaCellDecorations(lastDecorationId, [{ - handle: cell.handle, - options: { className: 'nb-symbolHighlight', outputClassName: 'nb-symbolHighlight' } - }]); - } - }); - } - } - } - - context.disposables.add(toDisposable(() => { - notebookWidget.deltaCellDecorations(lastDecorationId, []); - })); - - return result; - } -}); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/cellComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/cellComponents.ts deleted file mode 100644 index f526080b2..000000000 --- a/src/vs/workbench/contrib/notebook/browser/diff/cellComponents.ts +++ /dev/null @@ -1,1091 +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 { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { CellDiffViewModel, PropertyFoldingState } from 'vs/workbench/contrib/notebook/browser/diff/celllDiffViewModel'; -import { CellDiffRenderTemplate, CellDiffViewModelLayoutChangeEvent, DIFF_CELL_MARGIN, INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/common'; -import { EDITOR_BOTTOM_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { format } from 'vs/base/common/jsonFormatter'; -import { applyEdits } from 'vs/base/common/jsonEdit'; -import { CellEditType, CellUri, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { hash } from 'vs/base/common/hash'; -import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IMenu, IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IAction } from 'vs/base/common/actions'; -import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellActionView'; -import { getEditorTopPadding } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { collapsedIcon, expandedIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { renderCodicons } from 'vs/base/browser/codicons'; - -const fixedEditorOptions: IEditorOptions = { - padding: { - top: 12, - bottom: 12 - }, - scrollBeyondLastLine: false, - scrollbar: { - verticalScrollbarSize: 14, - horizontal: 'auto', - useShadows: true, - verticalHasArrows: false, - horizontalHasArrows: false, - alwaysConsumeMouseWheel: false - }, - renderLineHighlightOnlyWhenFocus: true, - overviewRulerLanes: 0, - selectOnLineNumbers: false, - wordWrap: 'off', - lineNumbers: 'off', - lineDecorationsWidth: 0, - glyphMargin: false, - fixedOverflowWidgets: true, - minimap: { enabled: false }, - renderValidationDecorations: 'on', - renderLineHighlight: 'none', - readOnly: true -}; - -const fixedDiffEditorOptions: IDiffEditorOptions = { - ...fixedEditorOptions, - glyphMargin: true, - enableSplitViewResizing: false, - renderIndicators: false, - readOnly: false, - isInEmbeddedEditor: true -}; - - - -class PropertyHeader extends Disposable { - protected _foldingIndicator!: HTMLElement; - protected _statusSpan!: HTMLElement; - protected _toolbar!: ToolBar; - protected _menu!: IMenu; - - constructor( - readonly cell: CellDiffViewModel, - readonly metadataHeaderContainer: HTMLElement, - readonly notebookEditor: INotebookTextDiffEditor, - readonly accessor: { - updateInfoRendering: () => void; - checkIfModified: (cell: CellDiffViewModel) => boolean; - getFoldingState: (cell: CellDiffViewModel) => PropertyFoldingState; - updateFoldingState: (cell: CellDiffViewModel, newState: PropertyFoldingState) => void; - unChangedLabel: string; - changedLabel: string; - prefix: string; - menuId: MenuId; - }, - @IContextMenuService readonly contextMenuService: IContextMenuService, - @IKeybindingService readonly keybindingService: IKeybindingService, - @INotificationService readonly notificationService: INotificationService, - @IMenuService readonly menuService: IMenuService, - @IContextKeyService readonly contextKeyService: IContextKeyService - ) { - super(); - } - - buildHeader(): void { - let metadataChanged = this.accessor.checkIfModified(this.cell); - this._foldingIndicator = DOM.append(this.metadataHeaderContainer, DOM.$('.property-folding-indicator')); - this._foldingIndicator.classList.add(this.accessor.prefix); - this._updateFoldingIcon(); - const metadataStatus = DOM.append(this.metadataHeaderContainer, DOM.$('div.property-status')); - this._statusSpan = DOM.append(metadataStatus, DOM.$('span')); - - if (metadataChanged) { - this._statusSpan.textContent = this.accessor.changedLabel; - this._statusSpan.style.fontWeight = 'bold'; - this.metadataHeaderContainer.classList.add('modified'); - } else { - this._statusSpan.textContent = this.accessor.unChangedLabel; - } - - const cellToolbarContainer = DOM.append(this.metadataHeaderContainer, DOM.$('div.property-toolbar')); - this._toolbar = new ToolBar(cellToolbarContainer, this.contextMenuService, { - actionViewItemProvider: action => { - if (action instanceof MenuItemAction) { - const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService); - return item; - } - - return undefined; - } - }); - this._register(this._toolbar); - this._toolbar.context = { - cell: this.cell - }; - - this._menu = this.menuService.createMenu(this.accessor.menuId, this.contextKeyService); - this._register(this._menu); - - if (metadataChanged) { - const actions: IAction[] = []; - createAndFillInActionBarActions(this._menu, { shouldForwardArgs: true }, actions); - this._toolbar.setActions(actions); - } - - this._register(this.notebookEditor.onMouseUp(e => { - if (!e.event.target) { - return; - } - - 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 - - const cellViewModel = e.target; - - if (cellViewModel === this.cell) { - const oldFoldingState = this.accessor.getFoldingState(this.cell); - this.accessor.updateFoldingState(this.cell, oldFoldingState === PropertyFoldingState.Expanded ? PropertyFoldingState.Collapsed : PropertyFoldingState.Expanded); - this._updateFoldingIcon(); - this.accessor.updateInfoRendering(); - } - } - - return; - })); - - this._updateFoldingIcon(); - this.accessor.updateInfoRendering(); - } - - refresh() { - let metadataChanged = this.accessor.checkIfModified(this.cell); - if (metadataChanged) { - this._statusSpan.textContent = this.accessor.changedLabel; - this._statusSpan.style.fontWeight = 'bold'; - this.metadataHeaderContainer.classList.add('modified'); - const actions: IAction[] = []; - createAndFillInActionBarActions(this._menu, undefined, actions); - this._toolbar.setActions(actions); - } else { - this._statusSpan.textContent = this.accessor.unChangedLabel; - this._statusSpan.style.fontWeight = 'normal'; - this._toolbar.setActions([]); - } - } - - private _updateFoldingIcon() { - if (this.accessor.getFoldingState(this.cell) === PropertyFoldingState.Collapsed) { - DOM.reset(this._foldingIndicator, ...renderCodicons(ThemeIcon.asCodiconLabel(collapsedIcon))); - } else { - DOM.reset(this._foldingIndicator, ...renderCodicons(ThemeIcon.asCodiconLabel(expandedIcon))); - } - } -} - -abstract class AbstractCellRenderer extends Disposable { - protected _metadataHeaderContainer!: HTMLElement; - protected _metadataHeader!: PropertyHeader; - protected _metadataInfoContainer!: HTMLElement; - protected _metadataEditorContainer?: HTMLElement; - protected _metadataEditorDisposeStore!: DisposableStore; - protected _metadataEditor?: CodeEditorWidget | DiffEditorWidget; - - protected _outputHeaderContainer!: HTMLElement; - protected _outputHeader!: PropertyHeader; - protected _outputInfoContainer!: HTMLElement; - protected _outputEditorContainer?: HTMLElement; - protected _outputEditorDisposeStore!: DisposableStore; - protected _outputEditor?: CodeEditorWidget | DiffEditorWidget; - - - protected _diffEditorContainer!: HTMLElement; - protected _diagonalFill?: HTMLElement; - protected _layoutInfo!: { - editorHeight: number; - editorMargin: number; - metadataStatusHeight: number; - metadataHeight: number; - outputStatusHeight: number; - outputHeight: number; - bodyMargin: number; - }; - protected _isDisposed: boolean; - - constructor( - readonly notebookEditor: INotebookTextDiffEditor, - readonly cell: CellDiffViewModel, - readonly templateData: CellDiffRenderTemplate, - readonly style: 'left' | 'right' | 'full', - protected readonly instantiationService: IInstantiationService, - protected readonly modeService: IModeService, - protected readonly modelService: IModelService, - protected readonly contextMenuService: IContextMenuService, - protected readonly keybindingService: IKeybindingService, - protected readonly notificationService: INotificationService, - protected readonly menuService: IMenuService, - protected readonly contextKeyService: IContextKeyService - - - ) { - super(); - // init - this._isDisposed = false; - this._layoutInfo = { - editorHeight: 0, - editorMargin: 0, - metadataHeight: 0, - metadataStatusHeight: 25, - outputHeight: 0, - outputStatusHeight: 25, - bodyMargin: 32 - }; - this._metadataEditorDisposeStore = new DisposableStore(); - this._outputEditorDisposeStore = new DisposableStore(); - this._register(this._metadataEditorDisposeStore); - this.initData(); - this.buildBody(templateData.container); - this._register(cell.onDidLayoutChange(e => this.onDidLayoutChange(e))); - } - - buildBody(container: HTMLElement) { - const body = DOM.$('.cell-body'); - DOM.append(container, body); - this._diffEditorContainer = DOM.$('.cell-diff-editor-container'); - switch (this.style) { - case 'left': - body.classList.add('left'); - break; - case 'right': - body.classList.add('right'); - break; - default: - body.classList.add('full'); - break; - } - - DOM.append(body, this._diffEditorContainer); - this._diagonalFill = DOM.append(body, DOM.$('.diagonal-fill')); - this.styleContainer(this._diffEditorContainer); - const sourceContainer = DOM.append(this._diffEditorContainer, DOM.$('.source-container')); - this.buildSourceEditor(sourceContainer); - - this._metadataHeaderContainer = DOM.append(this._diffEditorContainer, DOM.$('.metadata-header-container')); - this._metadataInfoContainer = DOM.append(this._diffEditorContainer, DOM.$('.metadata-info-container')); - - const checkIfModified = (cell: CellDiffViewModel) => { - return cell.type !== 'delete' && cell.type !== 'insert' && hash(this._getFormatedMetadataJSON(cell.original?.metadata || {}, cell.original?.language)) !== hash(this._getFormatedMetadataJSON(cell.modified?.metadata ?? {}, cell.modified?.language)); - }; - - if (checkIfModified(this.cell)) { - this.cell.metadataFoldingState = PropertyFoldingState.Expanded; - } - - this._metadataHeader = this.instantiationService.createInstance( - PropertyHeader, - this.cell, - this._metadataHeaderContainer, - this.notebookEditor, - { - updateInfoRendering: this.updateMetadataRendering.bind(this), - checkIfModified: (cell) => { - return checkIfModified(cell); - }, - getFoldingState: (cell) => { - return cell.metadataFoldingState; - }, - updateFoldingState: (cell, state) => { - cell.metadataFoldingState = state; - }, - unChangedLabel: 'Metadata', - changedLabel: 'Metadata changed', - prefix: 'metadata', - menuId: MenuId.NotebookDiffCellMetadataTitle - } - ); - this._register(this._metadataHeader); - this._metadataHeader.buildHeader(); - - if (this.notebookEditor.textModel?.transientOptions.transientOutputs) { - this._layoutInfo.outputHeight = 0; - this._layoutInfo.outputStatusHeight = 0; - this.layout({}); - return; - } - - this._outputHeaderContainer = DOM.append(this._diffEditorContainer, DOM.$('.output-header-container')); - this._outputInfoContainer = DOM.append(this._diffEditorContainer, DOM.$('.output-info-container')); - - const checkIfOutputsModified = (cell: CellDiffViewModel) => { - return cell.type !== 'delete' && cell.type !== 'insert' && !this.notebookEditor.textModel!.transientOptions.transientOutputs && cell.type === 'modified' && hash(cell.original?.outputs ?? []) !== hash(cell.modified?.outputs ?? []); - }; - - if (checkIfOutputsModified(this.cell)) { - this.cell.outputFoldingState = PropertyFoldingState.Expanded; - } - - this._outputHeader = this.instantiationService.createInstance( - PropertyHeader, - this.cell, - this._outputHeaderContainer, - this.notebookEditor, - { - updateInfoRendering: this.updateOutputRendering.bind(this), - checkIfModified: (cell) => { - return checkIfOutputsModified(cell); - }, - getFoldingState: (cell) => { - return cell.outputFoldingState; - }, - updateFoldingState: (cell, state) => { - cell.outputFoldingState = state; - }, - unChangedLabel: 'Outputs', - changedLabel: 'Outputs changed', - prefix: 'output', - menuId: MenuId.NotebookDiffCellOutputsTitle - } - ); - this._register(this._outputHeader); - this._outputHeader.buildHeader(); - } - - updateMetadataRendering() { - if (this.cell.metadataFoldingState === PropertyFoldingState.Expanded) { - // we should expand the metadata editor - this._metadataInfoContainer.style.display = 'block'; - - if (!this._metadataEditorContainer || !this._metadataEditor) { - // create editor - this._metadataEditorContainer = DOM.append(this._metadataInfoContainer, DOM.$('.metadata-editor-container')); - this._buildMetadataEditor(); - } else { - this._layoutInfo.metadataHeight = this._metadataEditor.getContentHeight(); - this.layout({ metadataEditor: true }); - } - } else { - // we should collapse the metadata editor - this._metadataInfoContainer.style.display = 'none'; - this._metadataEditorDisposeStore.clear(); - this._layoutInfo.metadataHeight = 0; - this.layout({}); - } - } - - updateOutputRendering() { - if (this.cell.outputFoldingState === PropertyFoldingState.Expanded) { - this._outputInfoContainer.style.display = 'block'; - - if (!this._outputEditorContainer || !this._outputEditor) { - // create editor - this._outputEditorContainer = DOM.append(this._outputInfoContainer, DOM.$('.output-editor-container')); - this._buildOutputEditor(); - } else { - this._layoutInfo.outputHeight = this._outputEditor.getContentHeight(); - this.layout({ outputEditor: true }); - } - } else { - this._outputInfoContainer.style.display = 'none'; - this._outputEditorDisposeStore.clear(); - this._layoutInfo.outputHeight = 0; - this.layout({}); - } - } - - protected _getFormatedMetadataJSON(metadata: NotebookCellMetadata, language?: string) { - let filteredMetadata: { [key: string]: any } = {}; - - if (this.notebookEditor.textModel) { - const transientMetadata = this.notebookEditor.textModel!.transientOptions.transientMetadata; - - const keys = new Set([...Object.keys(metadata)]); - for (let key of keys) { - if (!(transientMetadata[key as keyof NotebookCellMetadata]) - ) { - filteredMetadata[key] = metadata[key as keyof NotebookCellMetadata]; - } - } - } else { - filteredMetadata = metadata; - } - - const content = JSON.stringify({ - language, - ...filteredMetadata - }); - - const edits = format(content, undefined, {}); - const metadataSource = applyEdits(content, edits); - - return metadataSource; - } - - private _applySanitizedMetadataChanges(currentMetadata: NotebookCellMetadata, newMetadata: any) { - let result: { [key: string]: any } = {}; - let newLangauge: string | undefined = undefined; - try { - const newMetadataObj = JSON.parse(newMetadata); - const keys = new Set([...Object.keys(newMetadataObj)]); - for (let key of keys) { - switch (key as keyof NotebookCellMetadata) { - case 'breakpointMargin': - case 'editable': - case 'hasExecutionOrder': - case 'inputCollapsed': - case 'outputCollapsed': - case 'runnable': - // boolean - if (typeof newMetadataObj[key] === 'boolean') { - result[key] = newMetadataObj[key]; - } else { - result[key] = currentMetadata[key as keyof NotebookCellMetadata]; - } - break; - - case 'executionOrder': - case 'lastRunDuration': - // number - if (typeof newMetadataObj[key] === 'number') { - result[key] = newMetadataObj[key]; - } else { - result[key] = currentMetadata[key as keyof NotebookCellMetadata]; - } - break; - case 'runState': - // enum - if (typeof newMetadataObj[key] === 'number' && [1, 2, 3, 4].indexOf(newMetadataObj[key]) >= 0) { - result[key] = newMetadataObj[key]; - } else { - result[key] = currentMetadata[key as keyof NotebookCellMetadata]; - } - break; - case 'statusMessage': - // string - if (typeof newMetadataObj[key] === 'string') { - result[key] = newMetadataObj[key]; - } else { - result[key] = currentMetadata[key as keyof NotebookCellMetadata]; - } - break; - default: - if (key === 'language') { - newLangauge = newMetadataObj[key]; - } - result[key] = newMetadataObj[key]; - break; - } - } - - if (newLangauge !== undefined && newLangauge !== this.cell.modified!.language) { - const index = this.notebookEditor.textModel!.cells.indexOf(this.cell.modified!); - this.notebookEditor.textModel!.applyEdits( - this.notebookEditor.textModel!.versionId, - [{ editType: CellEditType.CellLanguage, index, language: newLangauge }], - true, - undefined, - () => undefined, - undefined - ); - } - - const index = this.notebookEditor.textModel!.cells.indexOf(this.cell.modified!); - - if (index < 0) { - return; - } - - this.notebookEditor.textModel!.applyEdits(this.notebookEditor.textModel!.versionId, [ - { editType: CellEditType.Metadata, index, metadata: result } - ], true, undefined, () => undefined, undefined); - } catch { - } - } - - private _buildMetadataEditor() { - if (this.cell.type === 'modified' || this.cell.type === 'unchanged') { - const originalMetadataSource = this._getFormatedMetadataJSON(this.cell.original?.metadata || {}, this.cell.original?.language); - const modifiedMetadataSource = this._getFormatedMetadataJSON(this.cell.modified?.metadata || {}, this.cell.modified?.language); - this._metadataEditor = this.instantiationService.createInstance(DiffEditorWidget, this._metadataEditorContainer!, { - ...fixedDiffEditorOptions, - overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode(), - readOnly: false, - originalEditable: false, - ignoreTrimWhitespace: false - }); - this._register(this._metadataEditor); - - this._metadataEditorContainer?.classList.add('diff'); - - const mode = this.modeService.create('json'); - const originalMetadataModel = this.modelService.createModel(originalMetadataSource, mode, CellUri.generateCellMetadataUri(this.cell.original!.uri, this.cell.original!.handle), false); - const modifiedMetadataModel = this.modelService.createModel(modifiedMetadataSource, mode, CellUri.generateCellMetadataUri(this.cell.modified!.uri, this.cell.modified!.handle), false); - this._metadataEditor.setModel({ - original: originalMetadataModel, - modified: modifiedMetadataModel - }); - - this._register(originalMetadataModel); - this._register(modifiedMetadataModel); - - this._layoutInfo.metadataHeight = this._metadataEditor.getContentHeight(); - this.layout({ metadataEditor: true }); - - this._register(this._metadataEditor.onDidContentSizeChange((e) => { - if (e.contentHeightChanged && this.cell.metadataFoldingState === PropertyFoldingState.Expanded) { - this._layoutInfo.metadataHeight = e.contentHeight; - this.layout({ metadataEditor: true }); - } - })); - - let respondingToContentChange = false; - - this._register(modifiedMetadataModel.onDidChangeContent(() => { - respondingToContentChange = true; - const value = modifiedMetadataModel.getValue(); - this._applySanitizedMetadataChanges(this.cell.modified!.metadata, value); - this._metadataHeader.refresh(); - respondingToContentChange = false; - })); - - this._register(this.cell.modified!.onDidChangeMetadata(() => { - if (respondingToContentChange) { - return; - } - - const modifiedMetadataSource = this._getFormatedMetadataJSON(this.cell.modified?.metadata || {}, this.cell.modified?.language); - modifiedMetadataModel.setValue(modifiedMetadataSource); - })); - - return; - } - - this._metadataEditor = this.instantiationService.createInstance(CodeEditorWidget, this._metadataEditorContainer!, { - ...fixedEditorOptions, - dimension: { - width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, true), - height: 0 - }, - overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode(), - readOnly: false - }, {}); - this._register(this._metadataEditor); - - const mode = this.modeService.create('jsonc'); - const originalMetadataSource = this._getFormatedMetadataJSON( - this.cell.type === 'insert' - ? this.cell.modified!.metadata || {} - : this.cell.original!.metadata || {}); - const uri = this.cell.type === 'insert' - ? this.cell.modified!.uri - : this.cell.original!.uri; - const handle = this.cell.type === 'insert' - ? this.cell.modified!.handle - : this.cell.original!.handle; - - const modelUri = CellUri.generateCellMetadataUri(uri, handle); - const metadataModel = this.modelService.createModel(originalMetadataSource, mode, modelUri, false); - this._metadataEditor.setModel(metadataModel); - this._register(metadataModel); - - this._layoutInfo.metadataHeight = this._metadataEditor.getContentHeight(); - this.layout({ metadataEditor: true }); - - this._register(this._metadataEditor.onDidContentSizeChange((e) => { - if (e.contentHeightChanged && this.cell.metadataFoldingState === PropertyFoldingState.Expanded) { - this._layoutInfo.metadataHeight = e.contentHeight; - this.layout({ metadataEditor: true }); - } - })); - } - - private _getFormatedOutputJSON(outputs: any[]) { - const content = JSON.stringify(outputs); - - const edits = format(content, undefined, {}); - const source = applyEdits(content, edits); - - return source; - } - - private _buildOutputEditor() { - if ((this.cell.type === 'modified' || this.cell.type === 'unchanged') && !this.notebookEditor.textModel!.transientOptions.transientOutputs) { - const originalOutputsSource = this._getFormatedOutputJSON(this.cell.original?.outputs || []); - const modifiedOutputsSource = this._getFormatedOutputJSON(this.cell.modified?.outputs || []); - if (originalOutputsSource !== modifiedOutputsSource) { - this._outputEditor = this.instantiationService.createInstance(DiffEditorWidget, this._outputEditorContainer!, { - ...fixedDiffEditorOptions, - overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode(), - readOnly: true, - ignoreTrimWhitespace: false - }); - this._register(this._outputEditor); - - this._outputEditorContainer?.classList.add('diff'); - - const mode = this.modeService.create('json'); - const originalModel = this.modelService.createModel(originalOutputsSource, mode, undefined, true); - const modifiedModel = this.modelService.createModel(modifiedOutputsSource, mode, undefined, true); - this._outputEditor.setModel({ - original: originalModel, - modified: modifiedModel - }); - - this._layoutInfo.outputHeight = this._outputEditor.getContentHeight(); - this.layout({ outputEditor: true }); - - this._register(this._outputEditor.onDidContentSizeChange((e) => { - if (e.contentHeightChanged && this.cell.outputFoldingState === PropertyFoldingState.Expanded) { - this._layoutInfo.outputHeight = e.contentHeight; - this.layout({ outputEditor: true }); - } - })); - - this._register(this.cell.modified!.onDidChangeOutputs(() => { - const modifiedOutputsSource = this._getFormatedOutputJSON(this.cell.modified?.outputs || []); - modifiedModel.setValue(modifiedOutputsSource); - this._outputHeader.refresh(); - })); - - return; - } - } - - this._outputEditor = this.instantiationService.createInstance(CodeEditorWidget, this._outputEditorContainer!, { - ...fixedEditorOptions, - dimension: { - width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, true), - height: 0 - }, - overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode() - }, {}); - this._register(this._outputEditor); - - const mode = this.modeService.create('json'); - const originaloutputSource = this._getFormatedOutputJSON( - this.notebookEditor.textModel!.transientOptions.transientOutputs - ? [] - : this.cell.type === 'insert' - ? this.cell.modified!.outputs || [] - : this.cell.original!.outputs || []); - const outputModel = this.modelService.createModel(originaloutputSource, mode, undefined, true); - this._outputEditor.setModel(outputModel); - - this._layoutInfo.outputHeight = this._outputEditor.getContentHeight(); - this.layout({ outputEditor: true }); - - this._register(this._outputEditor.onDidContentSizeChange((e) => { - if (e.contentHeightChanged && this.cell.outputFoldingState === PropertyFoldingState.Expanded) { - this._layoutInfo.outputHeight = e.contentHeight; - this.layout({ outputEditor: true }); - } - })); - } - - protected layoutNotebookCell() { - this.notebookEditor.layoutNotebookCell( - this.cell, - this._layoutInfo.editorHeight - + this._layoutInfo.editorMargin - + this._layoutInfo.metadataHeight - + this._layoutInfo.metadataStatusHeight - + this._layoutInfo.outputHeight - + this._layoutInfo.outputStatusHeight - + this._layoutInfo.bodyMargin - ); - } - - dispose() { - this._isDisposed = true; - super.dispose(); - } - - abstract initData(): void; - abstract styleContainer(container: HTMLElement): void; - abstract buildSourceEditor(sourceContainer: HTMLElement): void; - abstract onDidLayoutChange(event: CellDiffViewModelLayoutChangeEvent): void; - abstract layout(state: { outerWidth?: boolean, editorHeight?: boolean, metadataEditor?: boolean, outputEditor?: boolean }): void; -} - -export class DeletedCell extends AbstractCellRenderer { - private _editor!: CodeEditorWidget; - constructor( - readonly notebookEditor: INotebookTextDiffEditor, - readonly cell: CellDiffViewModel, - readonly templateData: CellDiffRenderTemplate, - @IModeService readonly modeService: IModeService, - @IModelService readonly modelService: IModelService, - @IInstantiationService protected readonly instantiationService: IInstantiationService, - @IContextMenuService protected readonly contextMenuService: IContextMenuService, - @IKeybindingService protected readonly keybindingService: IKeybindingService, - @INotificationService protected readonly notificationService: INotificationService, - @IMenuService protected readonly menuService: IMenuService, - @IContextKeyService protected readonly contextKeyService: IContextKeyService, - - - ) { - super(notebookEditor, cell, templateData, 'left', instantiationService, modeService, modelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService); - } - - initData(): void { - } - - styleContainer(container: HTMLElement) { - container.classList.add('removed'); - } - - buildSourceEditor(sourceContainer: HTMLElement): void { - const originalCell = this.cell.original!; - const lineCount = originalCell.textBuffer.getLineCount(); - const lineHeight = this.notebookEditor.getLayoutInfo().fontInfo.lineHeight || 17; - const editorHeight = lineCount * lineHeight + getEditorTopPadding() + EDITOR_BOTTOM_PADDING; - - const editorContainer = DOM.append(sourceContainer, DOM.$('.editor-container')); - - this._editor = this.instantiationService.createInstance(CodeEditorWidget, editorContainer, { - ...fixedEditorOptions, - dimension: { - width: (this.notebookEditor.getLayoutInfo().width - 2 * DIFF_CELL_MARGIN) / 2 - 18, - height: editorHeight - }, - overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode() - }, {}); - this._register(this._editor); - - this._layoutInfo.editorHeight = editorHeight; - - this._register(this._editor.onDidContentSizeChange((e) => { - if (e.contentHeightChanged && this._layoutInfo.editorHeight !== e.contentHeight) { - this._layoutInfo.editorHeight = e.contentHeight; - this.layout({ editorHeight: true }); - } - })); - - originalCell.resolveTextModelRef().then(ref => { - if (this._isDisposed) { - return; - } - - this._register(ref); - - const textModel = ref.object.textEditorModel; - this._editor.setModel(textModel); - this._layoutInfo.editorHeight = this._editor.getContentHeight(); - this.layout({ editorHeight: true }); - }); - - } - - onDidLayoutChange(e: CellDiffViewModelLayoutChangeEvent) { - if (e.outerWidth !== undefined) { - this.layout({ outerWidth: true }); - } - } - layout(state: { outerWidth?: boolean, editorHeight?: boolean, metadataEditor?: boolean, outputEditor?: boolean }) { - DOM.scheduleAtNextAnimationFrame(() => { - if (state.editorHeight || state.outerWidth) { - this._editor.layout({ - width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, false), - height: this._layoutInfo.editorHeight - }); - } - - if (state.metadataEditor || state.outerWidth) { - this._metadataEditor?.layout({ - width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, false), - height: this._layoutInfo.metadataHeight - }); - } - - if (state.outputEditor || state.outerWidth) { - this._outputEditor?.layout({ - width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, false), - height: this._layoutInfo.outputHeight - }); - } - - this.layoutNotebookCell(); - }); - } -} - -export class InsertCell extends AbstractCellRenderer { - private _editor!: CodeEditorWidget; - constructor( - readonly notebookEditor: INotebookTextDiffEditor, - readonly cell: CellDiffViewModel, - readonly templateData: CellDiffRenderTemplate, - @IInstantiationService protected readonly instantiationService: IInstantiationService, - @IModeService readonly modeService: IModeService, - @IModelService readonly modelService: IModelService, - @IContextMenuService protected readonly contextMenuService: IContextMenuService, - @IKeybindingService protected readonly keybindingService: IKeybindingService, - @INotificationService protected readonly notificationService: INotificationService, - @IMenuService protected readonly menuService: IMenuService, - @IContextKeyService protected readonly contextKeyService: IContextKeyService, - ) { - super(notebookEditor, cell, templateData, 'right', instantiationService, modeService, modelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService); - } - - initData(): void { - } - - styleContainer(container: HTMLElement): void { - container.classList.add('inserted'); - } - - buildSourceEditor(sourceContainer: HTMLElement): void { - const modifiedCell = this.cell.modified!; - const lineCount = modifiedCell.textBuffer.getLineCount(); - const lineHeight = this.notebookEditor.getLayoutInfo().fontInfo.lineHeight || 17; - const editorHeight = lineCount * lineHeight + getEditorTopPadding() + EDITOR_BOTTOM_PADDING; - const editorContainer = DOM.append(sourceContainer, DOM.$('.editor-container')); - - this._editor = this.instantiationService.createInstance(CodeEditorWidget, editorContainer, { - ...fixedEditorOptions, - dimension: { - width: (this.notebookEditor.getLayoutInfo().width - 2 * DIFF_CELL_MARGIN) / 2 - 18, - height: editorHeight - }, - overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode(), - readOnly: false - }, {}); - this._register(this._editor); - - this._layoutInfo.editorHeight = editorHeight; - - this._register(this._editor.onDidContentSizeChange((e) => { - if (e.contentHeightChanged && this._layoutInfo.editorHeight !== e.contentHeight) { - this._layoutInfo.editorHeight = e.contentHeight; - this.layout({ editorHeight: true }); - } - })); - - modifiedCell.resolveTextModelRef().then(ref => { - if (this._isDisposed) { - return; - } - - this._register(ref); - - const textModel = ref.object.textEditorModel; - this._editor.setModel(textModel); - this._layoutInfo.editorHeight = this._editor.getContentHeight(); - this.layout({ editorHeight: true }); - }); - } - - onDidLayoutChange(e: CellDiffViewModelLayoutChangeEvent) { - if (e.outerWidth !== undefined) { - this.layout({ outerWidth: true }); - } - } - - layout(state: { outerWidth?: boolean, editorHeight?: boolean, metadataEditor?: boolean, outputEditor?: boolean }) { - DOM.scheduleAtNextAnimationFrame(() => { - if (state.editorHeight || state.outerWidth) { - this._editor.layout({ - width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, false), - height: this._layoutInfo.editorHeight - }); - } - - if (state.metadataEditor || state.outerWidth) { - this._metadataEditor?.layout({ - width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, true), - height: this._layoutInfo.metadataHeight - }); - } - - if (state.outputEditor || state.outerWidth) { - this._outputEditor?.layout({ - width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, true), - height: this._layoutInfo.outputHeight - }); - } - - this.layoutNotebookCell(); - }); - } -} - -export class ModifiedCell extends AbstractCellRenderer { - private _editor?: DiffEditorWidget; - private _editorContainer!: HTMLElement; - private _inputToolbarContainer!: HTMLElement; - protected _toolbar!: ToolBar; - protected _menu!: IMenu; - - constructor( - readonly notebookEditor: INotebookTextDiffEditor, - readonly cell: CellDiffViewModel, - readonly templateData: CellDiffRenderTemplate, - @IInstantiationService protected readonly instantiationService: IInstantiationService, - @IModeService readonly modeService: IModeService, - @IModelService readonly modelService: IModelService, - @IContextMenuService protected readonly contextMenuService: IContextMenuService, - @IKeybindingService protected readonly keybindingService: IKeybindingService, - @INotificationService protected readonly notificationService: INotificationService, - @IMenuService protected readonly menuService: IMenuService, - @IContextKeyService protected readonly contextKeyService: IContextKeyService - ) { - super(notebookEditor, cell, templateData, 'full', instantiationService, modeService, modelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService); - } - - initData(): void { - } - - styleContainer(container: HTMLElement): void { - } - - buildSourceEditor(sourceContainer: HTMLElement): void { - const modifiedCell = this.cell.modified!; - const lineCount = modifiedCell.textBuffer.getLineCount(); - const lineHeight = this.notebookEditor.getLayoutInfo().fontInfo.lineHeight || 17; - const editorHeight = lineCount * lineHeight + getEditorTopPadding() + EDITOR_BOTTOM_PADDING; - this._editorContainer = DOM.append(sourceContainer, DOM.$('.editor-container')); - - this._editor = this.instantiationService.createInstance(DiffEditorWidget, this._editorContainer, { - ...fixedDiffEditorOptions, - overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode(), - originalEditable: false, - ignoreTrimWhitespace: false - }); - this._register(this._editor); - this._editorContainer.classList.add('diff'); - - this._editor.layout({ - width: this.notebookEditor.getLayoutInfo().width - 2 * DIFF_CELL_MARGIN, - height: editorHeight - }); - - this._editorContainer.style.height = `${editorHeight}px`; - - this._register(this._editor.onDidContentSizeChange((e) => { - if (e.contentHeightChanged && this._layoutInfo.editorHeight !== e.contentHeight) { - this._layoutInfo.editorHeight = e.contentHeight; - this.layout({ editorHeight: true }); - } - })); - - this._initializeSourceDiffEditor(); - - this._inputToolbarContainer = DOM.append(sourceContainer, DOM.$('.editor-input-toolbar-container')); - const cellToolbarContainer = DOM.append(this._inputToolbarContainer, DOM.$('div.property-toolbar')); - this._toolbar = new ToolBar(cellToolbarContainer, this.contextMenuService, { - actionViewItemProvider: action => { - if (action instanceof MenuItemAction) { - const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService); - return item; - } - - return undefined; - } - }); - - this._toolbar.context = { - cell: this.cell - }; - - this._menu = this.menuService.createMenu(MenuId.NotebookDiffCellInputTitle, this.contextKeyService); - this._register(this._menu); - const actions: IAction[] = []; - createAndFillInActionBarActions(this._menu, { shouldForwardArgs: true }, actions); - this._toolbar.setActions(actions); - - if (this.cell.modified!.getValue() !== this.cell.original!.getValue()) { - this._inputToolbarContainer.style.display = 'block'; - } else { - this._inputToolbarContainer.style.display = 'none'; - } - - this._register(this.cell.modified!.onDidChangeContent(() => { - if (this.cell.modified!.getValue() !== this.cell.original!.getValue()) { - this._inputToolbarContainer.style.display = 'block'; - } else { - this._inputToolbarContainer.style.display = 'none'; - } - })); - } - - private async _initializeSourceDiffEditor() { - const originalCell = this.cell.original!; - const modifiedCell = this.cell.modified!; - - const originalRef = await originalCell.resolveTextModelRef(); - const modifiedRef = await modifiedCell.resolveTextModelRef(); - - if (this._isDisposed) { - return; - } - - const textModel = originalRef.object.textEditorModel; - const modifiedTextModel = modifiedRef.object.textEditorModel; - this._register(originalRef); - this._register(modifiedRef); - - this._editor!.setModel({ - original: textModel, - modified: modifiedTextModel - }); - - const contentHeight = this._editor!.getContentHeight(); - this._layoutInfo.editorHeight = contentHeight; - this.layout({ editorHeight: true }); - - } - - onDidLayoutChange(e: CellDiffViewModelLayoutChangeEvent) { - if (e.outerWidth !== undefined) { - this.layout({ outerWidth: true }); - } - } - - layout(state: { outerWidth?: boolean, editorHeight?: boolean, metadataEditor?: boolean, outputEditor?: boolean }) { - DOM.scheduleAtNextAnimationFrame(() => { - if (state.editorHeight || state.outerWidth) { - this._editorContainer.style.height = `${this._layoutInfo.editorHeight}px`; - this._editor!.layout(); - } - - if (state.metadataEditor || state.outerWidth) { - if (this._metadataEditorContainer) { - this._metadataEditorContainer.style.height = `${this._layoutInfo.metadataHeight}px`; - this._metadataEditor?.layout(); - } - } - - if (state.outputEditor || state.outerWidth) { - if (this._outputEditorContainer) { - this._outputEditorContainer.style.height = `${this._layoutInfo.outputHeight}px`; - this._outputEditor?.layout(); - } - } - - this.layoutNotebookCell(); - }); - } -} diff --git a/src/vs/workbench/contrib/notebook/browser/diff/celllDiffViewModel.ts b/src/vs/workbench/contrib/notebook/browser/diff/celllDiffViewModel.ts deleted file mode 100644 index 55e25cee3..000000000 --- a/src/vs/workbench/contrib/notebook/browser/diff/celllDiffViewModel.ts +++ /dev/null @@ -1,48 +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 { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { NotebookDiffEditorEventDispatcher } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; -import { Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { CellDiffViewModelLayoutChangeEvent, DIFF_CELL_MARGIN } from 'vs/workbench/contrib/notebook/browser/diff/common'; -import { NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; - -export enum PropertyFoldingState { - Expanded, - Collapsed -} - -export class CellDiffViewModel extends Disposable { - public metadataFoldingState: PropertyFoldingState; - public outputFoldingState: PropertyFoldingState; - private _layoutInfoEmitter = new Emitter(); - - onDidLayoutChange = this._layoutInfoEmitter.event; - - constructor( - readonly original: NotebookCellTextModel | undefined, - readonly modified: NotebookCellTextModel | undefined, - readonly type: 'unchanged' | 'insert' | 'delete' | 'modified', - readonly editorEventDispatcher: NotebookDiffEditorEventDispatcher - ) { - super(); - this.metadataFoldingState = PropertyFoldingState.Collapsed; - this.outputFoldingState = PropertyFoldingState.Collapsed; - - this._register(this.editorEventDispatcher.onDidChangeLayout(e => { - this._layoutInfoEmitter.fire({ outerWidth: e.value.width }); - })); - } - - getComputedCellContainerWidth(layoutInfo: NotebookLayoutInfo, diffEditor: boolean, fullWidth: boolean) { - if (fullWidth) { - return layoutInfo.width - 2 * DIFF_CELL_MARGIN + (diffEditor ? DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH : 0) - 2; - } - - return (layoutInfo.width - 2 * DIFF_CELL_MARGIN + (diffEditor ? DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH : 0)) / 2 - 18 - 2; - } -} diff --git a/src/vs/workbench/contrib/notebook/browser/diff/common.ts b/src/vs/workbench/contrib/notebook/browser/diff/common.ts deleted file mode 100644 index 505718cea..000000000 --- a/src/vs/workbench/contrib/notebook/browser/diff/common.ts +++ /dev/null @@ -1,31 +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 { NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { CellDiffViewModel } from 'vs/workbench/contrib/notebook/browser/diff/celllDiffViewModel'; -import { Event } from 'vs/base/common/event'; -import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; - -export interface INotebookTextDiffEditor { - readonly textModel?: NotebookTextModel; - onMouseUp: Event<{ readonly event: MouseEvent; readonly target: CellDiffViewModel; }>; - getOverflowContainerDomNode(): HTMLElement; - getLayoutInfo(): NotebookLayoutInfo; - layoutNotebookCell(cell: CellDiffViewModel, height: number): void; -} - -export interface CellDiffRenderTemplate { - readonly container: HTMLElement; - readonly elementDisposables: DisposableStore; -} - -export interface CellDiffViewModelLayoutChangeEvent { - font?: BareFontInfo; - outerWidth?: number; -} - -export const DIFF_CELL_MARGIN = 16; diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts new file mode 100644 index 000000000..e2f4b6cd8 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -0,0 +1,1463 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { DiffElementViewModelBase, getFormatedMetadataJSON, 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_PROPERTY, NOTEBOOK_DIFF_CELL_PROPERTY_EXPANDED } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; +import { EDITOR_BOTTOM_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { CellEditType, CellUri, IProcessedOutput, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IMenu, IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IAction } from 'vs/base/common/actions'; +import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { Delayer } from 'vs/base/common/async'; +import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellActionView'; +import { getEditorTopPadding } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { collapsedIcon, expandedIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { OutputContainer } from 'vs/workbench/contrib/notebook/browser/diff/diffElementOutputs'; +import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; +import { ContextMenuController } from 'vs/editor/contrib/contextmenu/contextmenu'; +import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; +import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; +import { AccessibilityHelpController } from 'vs/workbench/contrib/codeEditor/browser/accessibility/accessibility'; +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 * as editorCommon from 'vs/editor/common/editorCommon'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; + +export const fixedEditorOptions: IEditorOptions = { + padding: { + top: 12, + bottom: 12 + }, + scrollBeyondLastLine: false, + scrollbar: { + verticalScrollbarSize: 14, + horizontal: 'auto', + vertical: 'hidden', + useShadows: true, + verticalHasArrows: false, + horizontalHasArrows: false, + alwaysConsumeMouseWheel: false, + }, + renderLineHighlightOnlyWhenFocus: true, + overviewRulerLanes: 0, + overviewRulerBorder: false, + selectOnLineNumbers: false, + wordWrap: 'off', + lineNumbers: 'off', + lineDecorationsWidth: 0, + glyphMargin: false, + fixedOverflowWidgets: true, + minimap: { enabled: false }, + renderValidationDecorations: 'on', + renderLineHighlight: 'none', + readOnly: true +}; + +export function getOptimizedNestedCodeEditorWidgetOptions(): ICodeEditorWidgetOptions { + return { + isSimpleWidget: false, + contributions: EditorExtensionsRegistry.getSomeEditorContributions([ + MenuPreventer.ID, + SelectionClipboardContributionID, + ContextMenuController.ID, + SuggestController.ID, + SnippetController2.ID, + TabCompletionController.ID, + AccessibilityHelpController.ID + ]) + }; +} + +export const fixedDiffEditorOptions: IDiffEditorOptions = { + ...fixedEditorOptions, + glyphMargin: true, + enableSplitViewResizing: false, + renderIndicators: false, + readOnly: false, + isInEmbeddedEditor: true, + renderOverviewRuler: false +}; + +class PropertyHeader extends Disposable { + protected _foldingIndicator!: HTMLElement; + protected _statusSpan!: HTMLElement; + protected _toolbar!: ToolBar; + protected _menu!: IMenu; + protected _propertyExpanded?: IContextKey; + + constructor( + readonly cell: DiffElementViewModelBase, + readonly propertyHeaderContainer: HTMLElement, + readonly notebookEditor: INotebookTextDiffEditor, + readonly accessor: { + updateInfoRendering: (renderOutput: boolean) => void; + checkIfModified: (cell: DiffElementViewModelBase) => boolean; + getFoldingState: (cell: DiffElementViewModelBase) => PropertyFoldingState; + updateFoldingState: (cell: DiffElementViewModelBase, newState: PropertyFoldingState) => void; + unChangedLabel: string; + changedLabel: string; + prefix: string; + menuId: MenuId; + }, + @IContextMenuService readonly contextMenuService: IContextMenuService, + @IKeybindingService readonly keybindingService: IKeybindingService, + @INotificationService readonly notificationService: INotificationService, + @IMenuService readonly menuService: IMenuService, + @IContextKeyService readonly contextKeyService: IContextKeyService + ) { + super(); + } + + buildHeader(): void { + let metadataChanged = this.accessor.checkIfModified(this.cell); + this._foldingIndicator = DOM.append(this.propertyHeaderContainer, DOM.$('.property-folding-indicator')); + this._foldingIndicator.classList.add(this.accessor.prefix); + this._updateFoldingIcon(); + const metadataStatus = DOM.append(this.propertyHeaderContainer, DOM.$('div.property-status')); + this._statusSpan = DOM.append(metadataStatus, DOM.$('span')); + + if (metadataChanged) { + this._statusSpan.textContent = this.accessor.changedLabel; + this._statusSpan.style.fontWeight = 'bold'; + this.propertyHeaderContainer.classList.add('modified'); + } else { + this._statusSpan.textContent = this.accessor.unChangedLabel; + } + + const cellToolbarContainer = DOM.append(this.propertyHeaderContainer, DOM.$('div.property-toolbar')); + this._toolbar = new ToolBar(cellToolbarContainer, this.contextMenuService, { + actionViewItemProvider: action => { + if (action instanceof MenuItemAction) { + const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService); + return item; + } + + return undefined; + } + }); + this._register(this._toolbar); + this._toolbar.context = { + cell: this.cell + }; + + const scopedContextKeyService = this.contextKeyService.createScoped(cellToolbarContainer); + this._register(scopedContextKeyService); + const propertyChanged = NOTEBOOK_DIFF_CELL_PROPERTY.bindTo(scopedContextKeyService); + propertyChanged.set(metadataChanged); + this._propertyExpanded = NOTEBOOK_DIFF_CELL_PROPERTY_EXPANDED.bindTo(scopedContextKeyService); + + this._menu = this.menuService.createMenu(this.accessor.menuId, scopedContextKeyService); + this._register(this._menu); + + const actions: IAction[] = []; + createAndFillInActionBarActions(this._menu, { shouldForwardArgs: true }, actions); + this._toolbar.setActions(actions); + + this._register(this._menu.onDidChange(() => { + const actions: IAction[] = []; + createAndFillInActionBarActions(this._menu, { shouldForwardArgs: true }, actions); + this._toolbar.setActions(actions); + })); + + this._register(this.notebookEditor.onMouseUp(e => { + if (!e.event.target) { + return; + } + + 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 + + const cellViewModel = e.target; + + if (cellViewModel === this.cell) { + const oldFoldingState = this.accessor.getFoldingState(this.cell); + this.accessor.updateFoldingState(this.cell, oldFoldingState === PropertyFoldingState.Expanded ? PropertyFoldingState.Collapsed : PropertyFoldingState.Expanded); + this._updateFoldingIcon(); + this.accessor.updateInfoRendering(this.cell.renderOutput); + } + } + + return; + })); + + this._updateFoldingIcon(); + this.accessor.updateInfoRendering(this.cell.renderOutput); + } + + refresh() { + let metadataChanged = this.accessor.checkIfModified(this.cell); + if (metadataChanged) { + this._statusSpan.textContent = this.accessor.changedLabel; + this._statusSpan.style.fontWeight = 'bold'; + this.propertyHeaderContainer.classList.add('modified'); + const actions: IAction[] = []; + createAndFillInActionBarActions(this._menu, undefined, actions); + this._toolbar.setActions(actions); + } else { + this._statusSpan.textContent = this.accessor.unChangedLabel; + this._statusSpan.style.fontWeight = 'normal'; + this._toolbar.setActions([]); + } + } + + private _updateFoldingIcon() { + if (this.accessor.getFoldingState(this.cell) === PropertyFoldingState.Collapsed) { + DOM.reset(this._foldingIndicator, renderIcon(collapsedIcon)); + this._propertyExpanded?.set(false); + } else { + DOM.reset(this._foldingIndicator, renderIcon(expandedIcon)); + this._propertyExpanded?.set(true); + } + + } +} + +abstract class AbstractElementRenderer extends Disposable { + protected _metadataHeaderContainer!: HTMLElement; + protected _metadataHeader!: PropertyHeader; + protected _metadataInfoContainer!: HTMLElement; + protected _metadataEditorContainer?: HTMLElement; + protected _metadataEditorDisposeStore!: DisposableStore; + protected _metadataEditor?: CodeEditorWidget | DiffEditorWidget; + + protected _outputHeaderContainer!: HTMLElement; + protected _outputHeader!: PropertyHeader; + protected _outputInfoContainer!: HTMLElement; + protected _outputEditorContainer?: HTMLElement; + protected _outputViewContainer?: HTMLElement; + protected _outputLeftContainer?: HTMLElement; + protected _outputRightContainer?: HTMLElement; + protected _outputEmptyElement?: HTMLElement; + protected _outputLeftView?: OutputContainer; + protected _outputRightView?: OutputContainer; + protected _outputEditorDisposeStore!: DisposableStore; + protected _outputEditor?: CodeEditorWidget | DiffEditorWidget; + + + protected _diffEditorContainer!: HTMLElement; + protected _diagonalFill?: HTMLElement; + protected _isDisposed: boolean; + + constructor( + readonly notebookEditor: INotebookTextDiffEditor, + readonly cell: DiffElementViewModelBase, + readonly templateData: CellDiffSingleSideRenderTemplate | CellDiffSideBySideRenderTemplate, + readonly style: 'left' | 'right' | 'full', + protected readonly instantiationService: IInstantiationService, + protected readonly modeService: IModeService, + protected readonly modelService: IModelService, + protected readonly textModelService: ITextModelService, + protected readonly contextMenuService: IContextMenuService, + protected readonly keybindingService: IKeybindingService, + protected readonly notificationService: INotificationService, + protected readonly menuService: IMenuService, + protected readonly contextKeyService: IContextKeyService + + + ) { + super(); + // init + this._isDisposed = false; + this._metadataEditorDisposeStore = new DisposableStore(); + this._outputEditorDisposeStore = new DisposableStore(); + this._register(this._metadataEditorDisposeStore); + this._register(this._outputEditorDisposeStore); + this._register(cell.onDidLayoutChange(e => this.layout(e))); + this._register(cell.onDidLayoutChange(e => this.updateBorders())); + this.buildBody(); + + this._register(cell.onDidStateChange(() => { + this.updateOutputRendering(this.cell.renderOutput); + })); + } + + abstract buildBody(): void; + + updateMetadataRendering() { + if (this.cell.metadataFoldingState === PropertyFoldingState.Expanded) { + // we should expand the metadata editor + this._metadataInfoContainer.style.display = 'block'; + + if (!this._metadataEditorContainer || !this._metadataEditor) { + // create editor + this._metadataEditorContainer = DOM.append(this._metadataInfoContainer, DOM.$('.metadata-editor-container')); + this._buildMetadataEditor(); + } else { + this.cell.metadataHeight = this._metadataEditor.getContentHeight(); + } + } else { + // we should collapse the metadata editor + this._metadataInfoContainer.style.display = 'none'; + // this._metadataEditorDisposeStore.clear(); + this.cell.metadataHeight = 0; + } + } + + updateOutputRendering(renderRichOutput: boolean) { + if (this.cell.outputFoldingState === PropertyFoldingState.Expanded) { + this._outputInfoContainer.style.display = 'block'; + if (renderRichOutput) { + this._hideOutputsRaw(); + this._buildOutputRendererContainer(); + this._showOutputsRenderer(); + this._showOutputsEmptyView(); + } else { + this._hideOutputsRenderer(); + this._buildOutputRawContainer(); + this._showOutputsRaw(); + } + } else { + this._outputInfoContainer.style.display = 'none'; + + this._hideOutputsRaw(); + this._hideOutputsRenderer(); + this._hideOutputsEmptyView(); + } + } + + private _buildOutputRawContainer() { + if (!this._outputEditorContainer) { + this._outputEditorContainer = DOM.append(this._outputInfoContainer, DOM.$('.output-editor-container')); + this._buildOutputEditor(); + } + } + + private _showOutputsRaw() { + if (this._outputEditorContainer) { + this._outputEditorContainer.style.display = 'block'; + this.cell.rawOutputHeight = this._outputEditor!.getContentHeight(); + } + } + + private _showOutputsEmptyView() { + this.cell.layoutChange(); + } + + private _hideOutputsRaw() { + if (this._outputEditorContainer) { + this._outputEditorContainer.style.display = 'none'; + this.cell.rawOutputHeight = 0; + } + } + + private _hideOutputsEmptyView() { + this.cell.layoutChange(); + } + + abstract _buildOutputRendererContainer(): void; + abstract _hideOutputsRenderer(): void; + abstract _showOutputsRenderer(): void; + + private _applySanitizedMetadataChanges(currentMetadata: NotebookCellMetadata, newMetadata: any) { + let result: { [key: string]: any } = {}; + let newLangauge: string | undefined = undefined; + try { + const newMetadataObj = JSON.parse(newMetadata); + const keys = new Set([...Object.keys(newMetadataObj)]); + for (let key of keys) { + switch (key as keyof NotebookCellMetadata) { + case 'breakpointMargin': + case 'editable': + case 'hasExecutionOrder': + case 'inputCollapsed': + case 'outputCollapsed': + case 'runnable': + // boolean + if (typeof newMetadataObj[key] === 'boolean') { + result[key] = newMetadataObj[key]; + } else { + result[key] = currentMetadata[key as keyof NotebookCellMetadata]; + } + break; + + case 'executionOrder': + case 'lastRunDuration': + // number + if (typeof newMetadataObj[key] === 'number') { + result[key] = newMetadataObj[key]; + } else { + result[key] = currentMetadata[key as keyof NotebookCellMetadata]; + } + break; + case 'runState': + // enum + if (typeof newMetadataObj[key] === 'number' && [1, 2, 3, 4].indexOf(newMetadataObj[key]) >= 0) { + result[key] = newMetadataObj[key]; + } else { + result[key] = currentMetadata[key as keyof NotebookCellMetadata]; + } + break; + case 'statusMessage': + // string + if (typeof newMetadataObj[key] === 'string') { + result[key] = newMetadataObj[key]; + } else { + result[key] = currentMetadata[key as keyof NotebookCellMetadata]; + } + break; + default: + if (key === 'language') { + newLangauge = newMetadataObj[key]; + } + result[key] = newMetadataObj[key]; + break; + } + } + + if (newLangauge !== undefined && newLangauge !== this.cell.modified!.language) { + const index = this.notebookEditor.textModel!.cells.indexOf(this.cell.modified!.textModel); + this.notebookEditor.textModel!.applyEdits( + this.notebookEditor.textModel!.versionId, + [{ editType: CellEditType.CellLanguage, index, language: newLangauge }], + true, + undefined, + () => undefined, + undefined + ); + } + + const index = this.notebookEditor.textModel!.cells.indexOf(this.cell.modified!.textModel); + + if (index < 0) { + return; + } + + this.notebookEditor.textModel!.applyEdits(this.notebookEditor.textModel!.versionId, [ + { editType: CellEditType.Metadata, index, metadata: result } + ], true, undefined, () => undefined, undefined); + } catch { + } + } + + private async _buildMetadataEditor() { + this._metadataEditorDisposeStore.clear(); + + if (this.cell instanceof SideBySideDiffElementViewModel) { + this._metadataEditor = this.instantiationService.createInstance(DiffEditorWidget, this._metadataEditorContainer!, { + ...fixedDiffEditorOptions, + overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode(), + readOnly: false, + originalEditable: false, + ignoreTrimWhitespace: false, + automaticLayout: false, + dimension: { + height: this.cell.layoutInfo.metadataHeight, + width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), true, true) + } + }, { + originalEditor: getOptimizedNestedCodeEditorWidgetOptions(), + modifiedEditor: getOptimizedNestedCodeEditorWidgetOptions() + }); + this.layout({ metadataHeight: true }); + this._metadataEditorDisposeStore.add(this._metadataEditor); + + this._metadataEditorContainer?.classList.add('diff'); + + const originalMetadataModel = await this.textModelService.createModelReference(CellUri.generateCellMetadataUri(this.cell.originalDocument.uri, this.cell.original!.handle)); + const modifiedMetadataModel = await this.textModelService.createModelReference(CellUri.generateCellMetadataUri(this.cell.modifiedDocument.uri, this.cell.modified!.handle)); + this._metadataEditor.setModel({ + original: originalMetadataModel.object.textEditorModel, + modified: modifiedMetadataModel.object.textEditorModel + }); + + this._metadataEditorDisposeStore.add(originalMetadataModel); + this._metadataEditorDisposeStore.add(modifiedMetadataModel); + + this.cell.metadataHeight = this._metadataEditor.getContentHeight(); + + this._metadataEditorDisposeStore.add(this._metadataEditor.onDidContentSizeChange((e) => { + if (e.contentHeightChanged && this.cell.metadataFoldingState === PropertyFoldingState.Expanded) { + this.cell.metadataHeight = e.contentHeight; + } + })); + + let respondingToContentChange = false; + + this._metadataEditorDisposeStore.add(modifiedMetadataModel.object.textEditorModel.onDidChangeContent(() => { + respondingToContentChange = true; + const value = modifiedMetadataModel.object.textEditorModel.getValue(); + this._applySanitizedMetadataChanges(this.cell.modified!.metadata, value); + this._metadataHeader.refresh(); + respondingToContentChange = false; + })); + + this._metadataEditorDisposeStore.add(this.cell.modified!.textModel.onDidChangeMetadata(() => { + if (respondingToContentChange) { + return; + } + + const modifiedMetadataSource = getFormatedMetadataJSON(this.notebookEditor.textModel!, this.cell.modified?.metadata || {}, this.cell.modified?.language); + modifiedMetadataModel.object.textEditorModel.setValue(modifiedMetadataSource); + })); + + return; + } else { + this._metadataEditor = this.instantiationService.createInstance(CodeEditorWidget, this._metadataEditorContainer!, { + ...fixedEditorOptions, + dimension: { + width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, true), + height: this.cell.layoutInfo.metadataHeight + }, + overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode(), + readOnly: false + }, {}); + this.layout({ metadataHeight: true }); + this._metadataEditorDisposeStore.add(this._metadataEditor); + + const mode = this.modeService.create('jsonc'); + const originalMetadataSource = getFormatedMetadataJSON(this.notebookEditor.textModel!, + this.cell.type === 'insert' + ? this.cell.modified!.metadata || {} + : this.cell.original!.metadata || {}); + const uri = this.cell.type === 'insert' + ? this.cell.modified!.uri + : this.cell.original!.uri; + const handle = this.cell.type === 'insert' + ? this.cell.modified!.handle + : this.cell.original!.handle; + + const modelUri = CellUri.generateCellMetadataUri(uri, handle); + const metadataModel = this.modelService.createModel(originalMetadataSource, mode, modelUri, false); + this._metadataEditor.setModel(metadataModel); + this._metadataEditorDisposeStore.add(metadataModel); + + this.cell.metadataHeight = this._metadataEditor.getContentHeight(); + + this._metadataEditorDisposeStore.add(this._metadataEditor.onDidContentSizeChange((e) => { + if (e.contentHeightChanged && this.cell.metadataFoldingState === PropertyFoldingState.Expanded) { + this.cell.metadataHeight = e.contentHeight; + } + })); + } + } + + private _getFormatedOutputJSON(outputs: IProcessedOutput[]) { + return JSON.stringify(outputs, undefined, '\t'); + } + + private _buildOutputEditor() { + this._outputEditorDisposeStore.clear(); + + if ((this.cell.type === 'modified' || this.cell.type === 'unchanged') && !this.notebookEditor.textModel!.transientOptions.transientOutputs) { + const originalOutputsSource = this._getFormatedOutputJSON(this.cell.original?.outputs || []); + const modifiedOutputsSource = this._getFormatedOutputJSON(this.cell.modified?.outputs || []); + if (originalOutputsSource !== modifiedOutputsSource) { + const mode = this.modeService.create('json'); + const originalModel = this.modelService.createModel(originalOutputsSource, mode, undefined, true); + const modifiedModel = this.modelService.createModel(modifiedOutputsSource, mode, undefined, true); + this._outputEditorDisposeStore.add(originalModel); + this._outputEditorDisposeStore.add(modifiedModel); + + const lineHeight = this.notebookEditor.getLayoutInfo().fontInfo.lineHeight || 17; + const lineCount = Math.max(originalModel.getLineCount(), modifiedModel.getLineCount()); + this._outputEditor = this.instantiationService.createInstance(DiffEditorWidget, this._outputEditorContainer!, { + ...fixedDiffEditorOptions, + overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode(), + readOnly: true, + ignoreTrimWhitespace: false, + automaticLayout: false, + dimension: { + height: Math.min(OUTPUT_EDITOR_HEIGHT_MAGIC, this.cell.layoutInfo.rawOutputHeight || lineHeight * lineCount), + width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, true) + } + }, { + originalEditor: getOptimizedNestedCodeEditorWidgetOptions(), + modifiedEditor: getOptimizedNestedCodeEditorWidgetOptions() + }); + this._outputEditorDisposeStore.add(this._outputEditor); + + this._outputEditorContainer?.classList.add('diff'); + + this._outputEditor.setModel({ + original: originalModel, + modified: modifiedModel + }); + this._outputEditor.restoreViewState(this.cell.getOutputEditorViewState() as editorCommon.IDiffEditorViewState); + + this.cell.rawOutputHeight = this._outputEditor.getContentHeight(); + + this._outputEditorDisposeStore.add(this._outputEditor.onDidContentSizeChange((e) => { + if (e.contentHeightChanged && this.cell.outputFoldingState === PropertyFoldingState.Expanded) { + this.cell.rawOutputHeight = e.contentHeight; + } + })); + + this._outputEditorDisposeStore.add(this.cell.modified!.textModel.onDidChangeOutputs(() => { + const modifiedOutputsSource = this._getFormatedOutputJSON(this.cell.modified?.outputs || []); + modifiedModel.setValue(modifiedOutputsSource); + this._outputHeader.refresh(); + })); + + return; + } + } + + this._outputEditor = this.instantiationService.createInstance(CodeEditorWidget, this._outputEditorContainer!, { + ...fixedEditorOptions, + dimension: { + width: Math.min(OUTPUT_EDITOR_HEIGHT_MAGIC, this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, this.cell.type === 'unchanged' || this.cell.type === 'modified') - 32), + height: this.cell.layoutInfo.rawOutputHeight + }, + overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode() + }, {}); + this._outputEditorDisposeStore.add(this._outputEditor); + + const mode = this.modeService.create('json'); + const originaloutputSource = this._getFormatedOutputJSON( + this.notebookEditor.textModel!.transientOptions.transientOutputs + ? [] + : this.cell.type === 'insert' + ? this.cell.modified!.outputs || [] + : this.cell.original!.outputs || []); + const outputModel = this.modelService.createModel(originaloutputSource, mode, undefined, true); + this._outputEditorDisposeStore.add(outputModel); + this._outputEditor.setModel(outputModel); + this._outputEditor.restoreViewState(this.cell.getOutputEditorViewState()); + + this.cell.rawOutputHeight = this._outputEditor.getContentHeight(); + + this._outputEditorDisposeStore.add(this._outputEditor.onDidContentSizeChange((e) => { + if (e.contentHeightChanged && this.cell.outputFoldingState === PropertyFoldingState.Expanded) { + this.cell.rawOutputHeight = e.contentHeight; + } + })); + } + + protected layoutNotebookCell() { + this.notebookEditor.layoutNotebookCell( + this.cell, + this.cell.layoutInfo.totalHeight + ); + } + + updateBorders() { + this.templateData.leftBorder.style.height = `${this.cell.layoutInfo.totalHeight - 32}px`; + this.templateData.rightBorder.style.height = `${this.cell.layoutInfo.totalHeight - 32}px`; + this.templateData.bottomBorder.style.top = `${this.cell.layoutInfo.totalHeight - 32}px`; + } + + dispose() { + if (this._outputEditor) { + this.cell.saveOutputEditorViewState(this._outputEditor.saveViewState()); + } + + if (this._metadataEditor) { + this.cell.saveMetadataEditorViewState(this._metadataEditor.saveViewState()); + } + + this._metadataEditorDisposeStore.dispose(); + this._outputEditorDisposeStore.dispose(); + + this._isDisposed = true; + super.dispose(); + } + + abstract styleContainer(container: HTMLElement): void; + abstract updateSourceEditor(): void; + abstract layout(state: { outerWidth?: boolean, editorHeight?: boolean, metadataEditor?: boolean, metadataHeight?: boolean, outputEditor?: boolean, outputView?: boolean }): void; +} + +abstract class SingleSideDiffElement extends AbstractElementRenderer { + constructor( + readonly notebookEditor: INotebookTextDiffEditor, + readonly cell: SingleSideDiffElementViewModel, + readonly templateData: CellDiffSingleSideRenderTemplate, + readonly style: 'left' | 'right' | 'full', + protected readonly instantiationService: IInstantiationService, + protected readonly modeService: IModeService, + protected readonly modelService: IModelService, + protected readonly textModelService: ITextModelService, + protected readonly contextMenuService: IContextMenuService, + protected readonly keybindingService: IKeybindingService, + protected readonly notificationService: INotificationService, + protected readonly menuService: IMenuService, + protected readonly contextKeyService: IContextKeyService + + + ) { + super( + notebookEditor, + cell, + templateData, + style, + instantiationService, + modeService, + modelService, + textModelService, + contextMenuService, + keybindingService, + notificationService, + menuService, + contextKeyService + ); + } + + buildBody() { + const body = this.templateData.body; + this._diffEditorContainer = this.templateData.diffEditorContainer; + switch (this.style) { + case 'left': + body.classList.add('left'); + break; + case 'right': + body.classList.add('right'); + break; + default: + body.classList.add('full'); + break; + } + + this._diagonalFill = this.templateData.diagonalFill; + this.styleContainer(this._diffEditorContainer); + this.updateSourceEditor(); + + this._metadataHeaderContainer = this.templateData.metadataHeaderContainer; + this._metadataInfoContainer = this.templateData.metadataInfoContainer; + this._metadataHeaderContainer.innerText = ''; + this._metadataInfoContainer.innerText = ''; + + this._metadataHeader = this.instantiationService.createInstance( + PropertyHeader, + this.cell, + this._metadataHeaderContainer, + this.notebookEditor, + { + updateInfoRendering: this.updateMetadataRendering.bind(this), + checkIfModified: (cell) => { + return cell.checkMetadataIfModified(); + }, + getFoldingState: (cell) => { + return cell.metadataFoldingState; + }, + updateFoldingState: (cell, state) => { + cell.metadataFoldingState = state; + }, + unChangedLabel: 'Metadata', + changedLabel: 'Metadata changed', + prefix: 'metadata', + menuId: MenuId.NotebookDiffCellMetadataTitle + } + ); + this._register(this._metadataHeader); + this._metadataHeader.buildHeader(); + + if (this.notebookEditor.textModel?.transientOptions.transientOutputs) { + this.cell.rawOutputHeight = 0; + this.cell.outputStatusHeight = 0; + this.templateData.outputHeaderContainer.style.display = 'none'; + this.templateData.outputInfoContainer.style.display = 'none'; + return; + } else { + this.templateData.outputHeaderContainer.style.display = 'flex'; + this.templateData.outputInfoContainer.style.display = 'block'; + + } + + this._outputHeaderContainer = this.templateData.outputHeaderContainer; + this._outputInfoContainer = this.templateData.outputInfoContainer; + + this._outputHeaderContainer.innerText = ''; + this._outputInfoContainer.innerText = ''; + + this._outputHeader = this.instantiationService.createInstance( + PropertyHeader, + this.cell, + this._outputHeaderContainer, + this.notebookEditor, + { + updateInfoRendering: this.updateOutputRendering.bind(this), + checkIfModified: (cell) => { + return cell.checkIfOutputsModified(); + }, + getFoldingState: (cell) => { + return cell.outputFoldingState; + }, + updateFoldingState: (cell, state) => { + cell.outputFoldingState = state; + }, + unChangedLabel: 'Outputs', + changedLabel: 'Outputs changed', + prefix: 'output', + menuId: MenuId.NotebookDiffCellOutputsTitle + } + ); + this._register(this._outputHeader); + this._outputHeader.buildHeader(); + } +} +export class DeletedElement extends SingleSideDiffElement { + private _editor!: CodeEditorWidget; + constructor( + readonly notebookEditor: INotebookTextDiffEditor, + readonly cell: SingleSideDiffElementViewModel, + readonly templateData: CellDiffSingleSideRenderTemplate, + @IModeService readonly modeService: IModeService, + @IModelService readonly modelService: IModelService, + @ITextModelService readonly textModelService: ITextModelService, + @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IContextMenuService protected readonly contextMenuService: IContextMenuService, + @IKeybindingService protected readonly keybindingService: IKeybindingService, + @INotificationService protected readonly notificationService: INotificationService, + @IMenuService protected readonly menuService: IMenuService, + @IContextKeyService protected readonly contextKeyService: IContextKeyService, + + + ) { + super(notebookEditor, cell, templateData, 'left', instantiationService, modeService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService); + } + + styleContainer(container: HTMLElement) { + container.classList.remove('inserted'); + 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 + getEditorTopPadding() + EDITOR_BOTTOM_PADDING; + + 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; + } + })); + + originalCell.textModel.resolveTextModelRef().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: { outerWidth?: boolean, editorHeight?: boolean, metadataHeight?: boolean, outputTotalHeight?: boolean }) { + DOM.scheduleAtNextAnimationFrame(() => { + if (state.editorHeight || state.outerWidth) { + this._editor.layout({ + width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, false), + height: this.cell.layoutInfo.editorHeight + }); + } + + if (state.metadataHeight || state.outerWidth) { + this._metadataEditor?.layout({ + width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, false), + height: this.cell.layoutInfo.metadataHeight + }); + } + + if (state.outputTotalHeight || state.outerWidth) { + this._outputEditor?.layout({ + width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, false), + height: this.cell.layoutInfo.outputTotalHeight + }); + } + + if (this._diagonalFill) { + this._diagonalFill.style.height = `${this.cell.layoutInfo.totalHeight - 32}px`; + } + + this.layoutNotebookCell(); + }); + } + + _buildOutputRendererContainer() { + if (!this._outputViewContainer) { + this._outputViewContainer = DOM.append(this._outputInfoContainer, DOM.$('.output-view-container')); + this._outputEmptyElement = DOM.append(this._outputViewContainer, DOM.$('.output-empty-view')); + const span = DOM.append(this._outputEmptyElement, DOM.$('span')); + span.innerText = 'No outputs to render'; + + if (this.cell.original!.outputs.length === 0) { + this._outputEmptyElement.style.display = 'block'; + } else { + this._outputEmptyElement.style.display = 'none'; + } + + this.cell.layoutChange(); + + this._outputLeftView = this.instantiationService.createInstance(OutputContainer, this.notebookEditor, this.notebookEditor.textModel!, this.cell, this.cell.original!, DiffSide.Original, this._outputViewContainer!); + this._register(this._outputLeftView); + this._outputLeftView.render(); + + const removedOutputRenderListener = this.notebookEditor.onDidDynamicOutputRendered(e => { + if (e.cell.uri.toString() === this.cell.original!.uri.toString()) { + this.notebookEditor.deltaCellOutputContainerClassNames(DiffSide.Original, this.cell.original!.id, ['nb-cellDeleted'], []); + removedOutputRenderListener.dispose(); + } + }); + + this._register(removedOutputRenderListener); + } + + this._outputViewContainer.style.display = 'block'; + } + + _decorate() { + this.notebookEditor.deltaCellOutputContainerClassNames(DiffSide.Original, this.cell.original!.id, ['nb-cellDeleted'], []); + } + + _showOutputsRenderer() { + if (this._outputViewContainer) { + this._outputViewContainer.style.display = 'block'; + + this._outputLeftView?.showOutputs(); + this._decorate(); + } + } + + _hideOutputsRenderer() { + if (this._outputViewContainer) { + this._outputViewContainer.style.display = 'none'; + + this._outputLeftView?.hideOutputs(); + } + } + + dispose() { + if (this._editor) { + this.cell.saveSpirceEditorViewState(this._editor.saveViewState()); + } + + super.dispose(); + } +} + +export class InsertElement extends SingleSideDiffElement { + private _editor!: CodeEditorWidget; + constructor( + readonly notebookEditor: INotebookTextDiffEditor, + readonly cell: SingleSideDiffElementViewModel, + readonly templateData: CellDiffSingleSideRenderTemplate, + @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IModeService readonly modeService: IModeService, + @IModelService readonly modelService: IModelService, + @ITextModelService readonly textModelService: ITextModelService, + @IContextMenuService protected readonly contextMenuService: IContextMenuService, + @IKeybindingService protected readonly keybindingService: IKeybindingService, + @INotificationService protected readonly notificationService: INotificationService, + @IMenuService protected readonly menuService: IMenuService, + @IContextKeyService protected readonly contextKeyService: IContextKeyService, + ) { + super(notebookEditor, cell, templateData, 'right', instantiationService, modeService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService); + } + + styleContainer(container: HTMLElement): void { + container.classList.remove('removed'); + 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 + getEditorTopPadding() + EDITOR_BOTTOM_PADDING; + + 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; + } + })); + + modifiedCell.textModel.resolveTextModelRef().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')); + this._outputEmptyElement = DOM.append(this._outputViewContainer, DOM.$('.output-empty-view')); + this._outputEmptyElement.innerText = 'No outputs to render'; + + if (this.cell.modified!.outputs.length === 0) { + this._outputEmptyElement.style.display = 'block'; + } else { + this._outputEmptyElement.style.display = 'none'; + } + + this.cell.layoutChange(); + + this._outputRightView = this.instantiationService.createInstance(OutputContainer, this.notebookEditor, this.notebookEditor.textModel!, this.cell, this.cell.modified!, DiffSide.Modified, this._outputViewContainer!); + this._register(this._outputRightView); + this._outputRightView.render(); + + const insertOutputRenderListener = this.notebookEditor.onDidDynamicOutputRendered(e => { + if (e.cell.uri.toString() === this.cell.modified!.uri.toString()) { + this.notebookEditor.deltaCellOutputContainerClassNames(DiffSide.Modified, this.cell.modified!.id, ['nb-cellAdded'], []); + insertOutputRenderListener.dispose(); + } + }); + this._register(insertOutputRenderListener); + } + + this._outputViewContainer.style.display = 'block'; + } + + _decorate() { + this.notebookEditor.deltaCellOutputContainerClassNames(DiffSide.Modified, this.cell.modified!.id, ['nb-cellAdded'], []); + } + + _showOutputsRenderer() { + if (this._outputViewContainer) { + this._outputViewContainer.style.display = 'block'; + this._outputRightView?.showOutputs(); + this._decorate(); + } + } + + _hideOutputsRenderer() { + if (this._outputViewContainer) { + this._outputViewContainer.style.display = 'none'; + this._outputRightView?.hideOutputs(); + } + } + + layout(state: { outerWidth?: boolean, editorHeight?: boolean, metadataHeight?: boolean, outputTotalHeight?: boolean }) { + DOM.scheduleAtNextAnimationFrame(() => { + if (state.editorHeight || state.outerWidth) { + this._editor.layout({ + width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, false), + height: this.cell.layoutInfo.editorHeight + }); + } + + if (state.metadataHeight || state.outerWidth) { + this._metadataEditor?.layout({ + width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, true), + height: this.cell.layoutInfo.metadataHeight + }); + } + + if (state.outputTotalHeight || state.outerWidth) { + this._outputEditor?.layout({ + width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, false), + height: this.cell.layoutInfo.outputTotalHeight + }); + } + + 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`; + } + }); + } + + dispose() { + if (this._editor) { + this.cell.saveSpirceEditorViewState(this._editor.saveViewState()); + } + + super.dispose(); + } +} + +export class ModifiedElement extends AbstractElementRenderer { + private _editor?: DiffEditorWidget; + private _editorContainer!: HTMLElement; + private _inputToolbarContainer!: HTMLElement; + protected _toolbar!: ToolBar; + protected _menu!: IMenu; + + constructor( + readonly notebookEditor: INotebookTextDiffEditor, + readonly cell: SideBySideDiffElementViewModel, + readonly templateData: CellDiffSideBySideRenderTemplate, + @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IModeService readonly modeService: IModeService, + @IModelService readonly modelService: IModelService, + @ITextModelService readonly textModelService: ITextModelService, + @IContextMenuService protected readonly contextMenuService: IContextMenuService, + @IKeybindingService protected readonly keybindingService: IKeybindingService, + @INotificationService protected readonly notificationService: INotificationService, + @IMenuService protected readonly menuService: IMenuService, + @IContextKeyService protected readonly contextKeyService: IContextKeyService + ) { + super(notebookEditor, cell, templateData, 'full', instantiationService, modeService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService); + } + + styleContainer(container: HTMLElement): void { + container.classList.remove('inserted', 'removed'); + } + + buildBody() { + const body = this.templateData.body; + this._diffEditorContainer = this.templateData.diffEditorContainer; + body.classList.remove('left', 'right', 'full'); + switch (this.style) { + case 'left': + body.classList.add('left'); + break; + case 'right': + body.classList.add('right'); + break; + default: + body.classList.add('full'); + break; + } + + this.styleContainer(this._diffEditorContainer); + this.updateSourceEditor(); + + this._metadataHeaderContainer = this.templateData.metadataHeaderContainer; + this._metadataInfoContainer = this.templateData.metadataInfoContainer; + + this._metadataHeaderContainer.innerText = ''; + this._metadataInfoContainer.innerText = ''; + + this._metadataHeader = this.instantiationService.createInstance( + PropertyHeader, + this.cell, + this._metadataHeaderContainer, + this.notebookEditor, + { + updateInfoRendering: this.updateMetadataRendering.bind(this), + checkIfModified: (cell) => { + return cell.checkMetadataIfModified(); + }, + getFoldingState: (cell) => { + return cell.metadataFoldingState; + }, + updateFoldingState: (cell, state) => { + cell.metadataFoldingState = state; + }, + unChangedLabel: 'Metadata', + changedLabel: 'Metadata changed', + prefix: 'metadata', + menuId: MenuId.NotebookDiffCellMetadataTitle + } + ); + this._register(this._metadataHeader); + this._metadataHeader.buildHeader(); + + if (this.notebookEditor.textModel?.transientOptions.transientOutputs) { + this.cell.rawOutputHeight = 0; + this.cell.outputStatusHeight = 0; + this.templateData.outputHeaderContainer.style.display = 'none'; + this.templateData.outputInfoContainer.style.display = 'none'; + return; + } else { + this.templateData.outputHeaderContainer.style.display = 'flex'; + this.templateData.outputInfoContainer.style.display = 'block'; + } + + this._outputHeaderContainer = this.templateData.outputHeaderContainer; + this._outputInfoContainer = this.templateData.outputInfoContainer; + this._outputHeaderContainer.innerText = ''; + this._outputInfoContainer.innerText = ''; + + if (this.cell.checkIfOutputsModified()) { + this._outputInfoContainer.classList.add('modified'); + } + + this._outputHeader = this.instantiationService.createInstance( + PropertyHeader, + this.cell, + this._outputHeaderContainer, + this.notebookEditor, + { + updateInfoRendering: this.updateOutputRendering.bind(this), + checkIfModified: (cell) => { + return cell.checkIfOutputsModified(); + }, + getFoldingState: (cell) => { + return cell.outputFoldingState; + }, + updateFoldingState: (cell, state) => { + cell.outputFoldingState = state; + }, + unChangedLabel: 'Outputs', + changedLabel: 'Outputs changed', + prefix: 'output', + menuId: MenuId.NotebookDiffCellOutputsTitle + } + ); + this._register(this._outputHeader); + this._outputHeader.buildHeader(); + } + + _buildOutputRendererContainer() { + if (!this._outputViewContainer) { + this._outputViewContainer = DOM.append(this._outputInfoContainer, DOM.$('.output-view-container')); + this._outputEmptyElement = DOM.append(this._outputViewContainer, DOM.$('.output-empty-view')); + this._outputEmptyElement.innerText = 'No outputs to render'; + + if (!this.cell.checkIfOutputsModified() && this.cell.modified.outputs.length === 0) { + this._outputEmptyElement.style.display = 'block'; + } else { + this._outputEmptyElement.style.display = 'none'; + } + + this.cell.layoutChange(); + + this._register(this.cell.modified.textModel.onDidChangeOutputs(() => { + // currently we only allow outputs change to the modified cell + if (!this.cell.checkIfOutputsModified() && this.cell.modified.outputs.length === 0) { + this._outputEmptyElement!.style.display = 'block'; + } else { + this._outputEmptyElement!.style.display = 'none'; + } + })); + + this._outputLeftContainer = DOM.append(this._outputViewContainer!, DOM.$('.output-view-container-left')); + this._outputRightContainer = DOM.append(this._outputViewContainer!, DOM.$('.output-view-container-right')); + // We should use the original text model here + this._outputLeftView = this.instantiationService.createInstance(OutputContainer, this.notebookEditor, this.notebookEditor.textModel!, this.cell, this.cell.original!, DiffSide.Original, this._outputLeftContainer!); + this._outputLeftView.render(); + this._register(this._outputLeftView); + this._outputRightView = this.instantiationService.createInstance(OutputContainer, this.notebookEditor, this.notebookEditor.textModel!, this.cell, this.cell.modified!, DiffSide.Modified, this._outputRightContainer!); + this._outputRightView.render(); + this._register(this._outputRightView); + + const originalOutputRenderListener = this.notebookEditor.onDidDynamicOutputRendered(e => { + if (e.cell.uri.toString() === this.cell.original.uri.toString()) { + this.notebookEditor.deltaCellOutputContainerClassNames(DiffSide.Original, this.cell.original.id, ['nb-cellDeleted'], []); + originalOutputRenderListener.dispose(); + } + }); + + const modifiedOutputRenderListener = this.notebookEditor.onDidDynamicOutputRendered(e => { + if (e.cell.uri.toString() === this.cell.modified.uri.toString()) { + this.notebookEditor.deltaCellOutputContainerClassNames(DiffSide.Modified, this.cell.modified.id, ['nb-cellAdded'], []); + modifiedOutputRenderListener.dispose(); + } + }); + + this._register(originalOutputRenderListener); + this._register(modifiedOutputRenderListener); + + this._decorate(); + } + + this._outputViewContainer.style.display = 'block'; + } + + _decorate() { + this.notebookEditor.deltaCellOutputContainerClassNames(DiffSide.Original, this.cell.original.id, ['nb-cellDeleted'], []); + this.notebookEditor.deltaCellOutputContainerClassNames(DiffSide.Modified, this.cell.modified.id, ['nb-cellAdded'], []); + } + + _showOutputsRenderer() { + if (this._outputViewContainer) { + this._outputViewContainer.style.display = 'block'; + + this._outputLeftView?.showOutputs(); + this._outputRightView?.showOutputs(); + this._decorate(); + } + } + + _hideOutputsRenderer() { + if (this._outputViewContainer) { + this._outputViewContainer.style.display = 'none'; + + this._outputLeftView?.hideOutputs(); + this._outputRightView?.hideOutputs(); + } + } + + updateSourceEditor(): void { + 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 + getEditorTopPadding() + EDITOR_BOTTOM_PADDING; + 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 + }); + + this._editorContainer.style.height = `${editorHeight}px`; + + this._register(this._editor.onDidContentSizeChange((e) => { + if (e.contentHeightChanged && this.cell.layoutInfo.editorHeight !== e.contentHeight) { + this.cell.editorHeight = e.contentHeight; + } + })); + + this._initializeSourceDiffEditor(); + + this._inputToolbarContainer = this.templateData.inputToolbarContainer; + this._toolbar = this.templateData.toolbar; + + this._toolbar.context = { + cell: this.cell + }; + + this._menu = this.menuService.createMenu(MenuId.NotebookDiffCellInputTitle, this.contextKeyService); + this._register(this._menu); + const actions: IAction[] = []; + createAndFillInActionBarActions(this._menu, { shouldForwardArgs: true }, actions); + this._toolbar.setActions(actions); + + if (this.cell.modified!.textModel.getValue() !== this.cell.original!.textModel.getValue()) { + this._inputToolbarContainer.style.display = 'block'; + } else { + this._inputToolbarContainer.style.display = 'none'; + } + + this._register(this.cell.modified!.textModel.onDidChangeContent(() => { + if (this.cell.modified!.textModel.getValue() !== this.cell.original!.textModel.getValue()) { + this._inputToolbarContainer.style.display = 'block'; + } else { + this._inputToolbarContainer.style.display = 'none'; + } + })); + } + + private async _initializeSourceDiffEditor() { + const originalCell = this.cell.original!; + const modifiedCell = this.cell.modified!; + + const originalRef = await originalCell.textModel.resolveTextModelRef(); + const modifiedRef = await modifiedCell.textModel.resolveTextModelRef(); + + if (this._isDisposed) { + return; + } + + const textModel = originalRef.object.textEditorModel; + const modifiedTextModel = modifiedRef.object.textEditorModel; + this._register({ + dispose: () => { + const delayer = new Delayer(5000); + delayer.trigger(() => { + originalRef.dispose(); + delayer.dispose(); + }); + } + }); + this._register({ + dispose: () => { + const delayer = new Delayer(5000); + delayer.trigger(() => { + modifiedRef.dispose(); + delayer.dispose(); + }); + } + }); + + this._editor!.setModel({ + original: textModel, + modified: modifiedTextModel + }); + + this._editor!.restoreViewState(this.cell.getSourceEditorViewState() as editorCommon.IDiffEditorViewState); + + const contentHeight = this._editor!.getContentHeight(); + this.cell.editorHeight = contentHeight; + } + + layout(state: { outerWidth?: boolean, editorHeight?: boolean, metadataHeight?: boolean, outputTotalHeight?: boolean }) { + DOM.scheduleAtNextAnimationFrame(() => { + if (state.editorHeight) { + this._editorContainer.style.height = `${this.cell.layoutInfo.editorHeight}px`; + this._editor!.layout({ + width: this._editor!.getViewWidth(), + height: this.cell.layoutInfo.editorHeight + }); + } + + if (state.outerWidth) { + this._editorContainer.style.height = `${this.cell.layoutInfo.editorHeight}px`; + this._editor!.layout(); + } + + if (state.metadataHeight || state.outerWidth) { + if (this._metadataEditorContainer) { + this._metadataEditorContainer.style.height = `${this.cell.layoutInfo.metadataHeight}px`; + this._metadataEditor?.layout(); + } + } + + if (state.outputTotalHeight || state.outerWidth) { + if (this._outputEditorContainer) { + this._outputEditorContainer.style.height = `${this.cell.layoutInfo.outputTotalHeight}px`; + this._outputEditor?.layout(); + } + } + + + this.layoutNotebookCell(); + }); + } + + dispose() { + if (this._editor) { + this.cell.saveSpirceEditorViewState(this._editor.saveViewState()); + } + + super.dispose(); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts new file mode 100644 index 000000000..74a0010fc --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts @@ -0,0 +1,361 @@ +/*--------------------------------------------------------------------------------------------- + * 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 * as nls from 'vs/nls'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { DiffElementViewModelBase, SideBySideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; +import { DiffSide, INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; +import { ICellOutputViewModel, IDisplayOutputViewModel, IRenderOutput, outputHasDynamicHeight, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { BUILTIN_RENDERER_ID, NotebookCellOutputsSplice } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { DiffNestedCellViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { mimetypeIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; + +interface IMimeTypeRenderer extends IQuickPickItem { + index: number; +} + +export class OutputElement extends Disposable { + readonly resizeListener = new DisposableStore(); + domNode!: HTMLElement; + renderResult?: IRenderOutput; + + constructor( + private _notebookEditor: INotebookTextDiffEditor, + private _notebookTextModel: NotebookTextModel, + private _notebookService: INotebookService, + private _quickInputService: IQuickInputService, + private _diffElementViewModel: DiffElementViewModelBase, + private _diffSide: DiffSide, + private _nestedCell: DiffNestedCellViewModel, + private _outputContainer: HTMLElement, + readonly output: ICellOutputViewModel + ) { + super(); + } + + render(index: number, beforeElement?: HTMLElement) { + const outputItemDiv = document.createElement('div'); + let result: IRenderOutput | undefined = undefined; + + if (this.output.isDisplayOutput()) { + const [mimeTypes, pick] = this.output.resolveMimeTypes(this._notebookTextModel); + const pickedMimeTypeRenderer = mimeTypes[pick]; + if (mimeTypes.length > 1) { + outputItemDiv.style.position = 'relative'; + const mimeTypePicker = DOM.$('.multi-mimetype-output'); + mimeTypePicker.classList.add(...ThemeIcon.asClassNameArray(mimetypeIcon)); + mimeTypePicker.tabIndex = 0; + mimeTypePicker.title = nls.localize('mimeTypePicker', "Choose a different output mimetype, available mimetypes: {0}", mimeTypes.map(mimeType => mimeType.mimeType).join(', ')); + outputItemDiv.appendChild(mimeTypePicker); + this.resizeListener.add(DOM.addStandardDisposableListener(mimeTypePicker, 'mousedown', async e => { + if (e.leftButton) { + e.preventDefault(); + e.stopPropagation(); + await this.pickActiveMimeTypeRenderer(this._notebookTextModel, this.output as IDisplayOutputViewModel); + } + })); + + this.resizeListener.add((DOM.addDisposableListener(mimeTypePicker, DOM.EventType.KEY_DOWN, async e => { + const event = new StandardKeyboardEvent(e); + if ((event.equals(KeyCode.Enter) || event.equals(KeyCode.Space))) { + e.preventDefault(); + e.stopPropagation(); + await this.pickActiveMimeTypeRenderer(this._notebookTextModel, this.output as IDisplayOutputViewModel); + } + }))); + } + + const innerContainer = DOM.$('.output-inner-container'); + DOM.append(outputItemDiv, innerContainer); + + + if (pickedMimeTypeRenderer.rendererId !== BUILTIN_RENDERER_ID) { + const renderer = this._notebookService.getRendererInfo(pickedMimeTypeRenderer.rendererId); + result = renderer + ? { type: RenderOutputType.Extension, renderer, source: this.output, mimeType: pickedMimeTypeRenderer.mimeType } + : this._notebookEditor.getOutputRenderer().render(this.output, innerContainer, pickedMimeTypeRenderer.mimeType, this._notebookTextModel.uri,); + } else { + result = this._notebookEditor.getOutputRenderer().render(this.output, innerContainer, pickedMimeTypeRenderer.mimeType, this._notebookTextModel.uri); + } + + this.output.pickedMimeType = pick; + } else { + // for text and error, there is no mimetype + const innerContainer = DOM.$('.output-inner-container'); + DOM.append(outputItemDiv, innerContainer); + + result = this._notebookEditor.getOutputRenderer().render(this.output, innerContainer, undefined, this._notebookTextModel.uri); + } + + this.domNode = outputItemDiv; + this.renderResult = result; + + if (!result) { + // this.viewCell.updateOutputHeight(index, 0); + return; + } + + if (beforeElement) { + this._outputContainer.insertBefore(outputItemDiv, beforeElement); + } else { + this._outputContainer.appendChild(outputItemDiv); + } + + if (result.type !== RenderOutputType.None) { + // this.viewCell.selfSizeMonitoring = true; + this._notebookEditor.createInset( + this._diffElementViewModel, + this._nestedCell, + result, + () => this.getOutputOffsetInCell(index), + this._diffElementViewModel instanceof SideBySideDiffElementViewModel + ? this._diffSide + : this._diffElementViewModel.type === 'insert' ? DiffSide.Modified : DiffSide.Original + ); + } else { + outputItemDiv.classList.add('foreground', 'output-element'); + outputItemDiv.style.position = 'absolute'; + } + + if (outputHasDynamicHeight(result)) { + // this.viewCell.selfSizeMonitoring = true; + const clientHeight = outputItemDiv.clientHeight; + // TODO, set an inital dimension to avoid force reflow + // const dimension = { + // width: this.cellViewModel., + // height: clientHeight + // }; + + const elementSizeObserver = getResizesObserver(outputItemDiv, undefined, () => { + if (this._outputContainer && document.body.contains(this._outputContainer)) { + const height = Math.ceil(elementSizeObserver.getHeight()); + + if (clientHeight === height) { + return; + } + + const currIndex = this.getCellOutputCurrentIndex(); + if (currIndex < 0) { + return; + } + + this.updateHeight(currIndex, height); + } + }); + elementSizeObserver.startObserving(); + this.resizeListener.add(elementSizeObserver); + this.updateHeight(index, clientHeight); + } else if (result.type === RenderOutputType.None) { // no-op if it's a webview + const clientHeight = Math.ceil(outputItemDiv.clientHeight); + this.updateHeight(index, clientHeight); + + const top = this.getOutputOffsetInContainer(index); + outputItemDiv.style.top = `${top}px`; + } + } + + private async pickActiveMimeTypeRenderer(notebookTextModel: NotebookTextModel, viewModel: IDisplayOutputViewModel) { + const [mimeTypes, currIndex] = viewModel.resolveMimeTypes(notebookTextModel); + + const items = mimeTypes.filter(mimeType => mimeType.isTrusted).map((mimeType, index): IMimeTypeRenderer => ({ + label: mimeType.mimeType, + id: mimeType.mimeType, + index: index, + picked: index === currIndex, + detail: this.generateRendererInfo(mimeType.rendererId), + description: index === currIndex ? nls.localize('curruentActiveMimeType', "Currently Active") : undefined + })); + + const picker = this._quickInputService.createQuickPick(); + picker.items = items; + picker.activeItems = items.filter(item => !!item.picked); + picker.placeholder = items.length !== mimeTypes.length + ? nls.localize('promptChooseMimeTypeInSecure.placeHolder', "Select mimetype to render for current output. Rich mimetypes are available only when the notebook is trusted") + : nls.localize('promptChooseMimeType.placeHolder', "Select mimetype to render for current output"); + + const pick = await new Promise(resolve => { + picker.onDidAccept(() => { + resolve(picker.selectedItems.length === 1 ? (picker.selectedItems[0] as IMimeTypeRenderer).index : undefined); + picker.dispose(); + }); + picker.show(); + }); + + if (pick === undefined) { + return; + } + + if (pick !== currIndex) { + // user chooses another mimetype + const index = this._nestedCell.outputsViewModels.indexOf(viewModel); + const nextElement = this.domNode.nextElementSibling; + this.resizeListener.clear(); + const element = this.domNode; + if (element) { + element.parentElement?.removeChild(element); + this._notebookEditor.removeInset( + this._diffElementViewModel, + this._nestedCell, + viewModel, + this._diffSide + ); + } + + viewModel.pickedMimeType = pick; + this.render(index, nextElement as HTMLElement); + } + } + + private generateRendererInfo(renderId: string | undefined): string { + if (renderId === undefined || renderId === BUILTIN_RENDERER_ID) { + return nls.localize('builtinRenderInfo', "built-in"); + } + + const renderInfo = this._notebookService.getRendererInfo(renderId); + + if (renderInfo) { + const displayName = renderInfo.displayName !== '' ? renderInfo.displayName : renderInfo.id; + return `${displayName} (${renderInfo.extensionId.value})`; + } + + return nls.localize('builtinRenderInfo', "built-in"); + } + + getCellOutputCurrentIndex() { + return this._diffElementViewModel.getNestedCellViewModel(this._diffSide).outputs.indexOf(this.output.model); + } + + updateHeight(index: number, height: number) { + this._diffElementViewModel.updateOutputHeight(this._diffSide, index, height); + } + + getOutputOffsetInContainer(index: number) { + return this._diffElementViewModel.getOutputOffsetInContainer(this._diffSide, index); + } + + getOutputOffsetInCell(index: number) { + return this._diffElementViewModel.getOutputOffsetInCell(this._diffSide, index); + } +} + +export class OutputContainer extends Disposable { + private _outputEntries = new Map(); + constructor( + private _editor: INotebookTextDiffEditor, + private _notebookTextModel: NotebookTextModel, + private _diffElementViewModel: DiffElementViewModelBase, + private _nestedCellViewModel: DiffNestedCellViewModel, + private _diffSide: DiffSide, + private _outputContainer: HTMLElement, + @INotebookService private _notebookService: INotebookService, + @IQuickInputService private readonly _quickInputService: IQuickInputService, + @IOpenerService readonly _openerService: IOpenerService, + @ITextFileService readonly _textFileService: ITextFileService, + + ) { + super(); + this._register(this._diffElementViewModel.onDidLayoutChange(() => { + this._outputEntries.forEach((value, key) => { + const index = _nestedCellViewModel.outputs.indexOf(key.model); + if (index >= 0) { + const top = this._diffElementViewModel.getOutputOffsetInContainer(this._diffSide, index); + value.domNode.style.top = `${top}px`; + } + }); + })); + + this._register(this._nestedCellViewModel.textModel.onDidChangeOutputs(splices => { + this._updateOutputs(splices); + })); + } + + private _updateOutputs(splices: NotebookCellOutputsSplice[]) { + if (!splices.length) { + return; + } + + const removedKeys: ICellOutputViewModel[] = []; + + this._outputEntries.forEach((value, key) => { + if (this._nestedCellViewModel.outputsViewModels.indexOf(key) < 0) { + // already removed + removedKeys.push(key); + // remove element from DOM + this._outputContainer.removeChild(value.domNode); + if (key.isDisplayOutput()) { + this._editor.removeInset(this._diffElementViewModel, this._nestedCellViewModel, key, this._diffSide); + } + } + }); + + removedKeys.forEach(key => { + this._outputEntries.get(key)?.dispose(); + this._outputEntries.delete(key); + }); + + let prevElement: HTMLElement | undefined = undefined; + const outputsToRender = this._nestedCellViewModel.outputsViewModels; + + outputsToRender.reverse().forEach(output => { + if (this._outputEntries.has(output)) { + // already exist + prevElement = this._outputEntries.get(output)!.domNode; + return; + } + + // newly added element + const currIndex = this._nestedCellViewModel.outputsViewModels.indexOf(output); + this._renderOutput(output, currIndex, prevElement); + prevElement = this._outputEntries.get(output)?.domNode; + }); + } + render() { + // TODO, outputs to render (should have a limit) + for (let index = 0; index < this._nestedCellViewModel.outputsViewModels.length; index++) { + const currOutput = this._nestedCellViewModel.outputsViewModels[index]; + + // always add to the end + this._renderOutput(currOutput, index, undefined); + } + } + + showOutputs() { + for (let index = 0; index < this._nestedCellViewModel.outputsViewModels.length; index++) { + const currOutput = this._nestedCellViewModel.outputsViewModels[index]; + + if (currOutput.isDisplayOutput()) { + // always add to the end + this._editor.showInset(this._diffElementViewModel, currOutput.cellViewModel, currOutput, this._diffSide); + } + } + } + + hideOutputs() { + this._outputEntries.forEach((outputElement, cellOutputViewModel) => { + if (cellOutputViewModel.isDisplayOutput()) { + this._editor.hideInset(this._diffElementViewModel, this._nestedCellViewModel, cellOutputViewModel); + } + }); + } + + private _renderOutput(currOutput: ICellOutputViewModel, index: number, beforeElement?: HTMLElement) { + if (!this._outputEntries.has(currOutput)) { + this._outputEntries.set(currOutput, new OutputElement(this._editor, this._notebookTextModel, this._notebookService, this._quickInputService, this._diffElementViewModel, this._diffSide, this._nestedCellViewModel, this._outputContainer, currOutput)); + } + + const renderElement = this._outputEntries.get(currOutput)!; + renderElement.render(index, beforeElement); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts new file mode 100644 index 000000000..4d10ed220 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts @@ -0,0 +1,498 @@ +/*--------------------------------------------------------------------------------------------- + * 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 } from 'vs/base/common/lifecycle'; +import { CellDiffViewModelLayoutChangeEvent, DiffSide, DIFF_CELL_MARGIN, IDiffElementLayoutInfo } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; +import { IGenericCellViewModel, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { hash } from 'vs/base/common/hash'; +import { format } from 'vs/base/common/jsonFormatter'; +import { applyEdits } from 'vs/base/common/jsonEdit'; +import { NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { DiffNestedCellViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel'; +import { URI } from 'vs/base/common/uri'; +import { NotebookDiffEditorEventDispatcher, NotebookDiffViewEventType } from 'vs/workbench/contrib/notebook/browser/diff/eventDispatcher'; +import * as editorCommon from 'vs/editor/common/editorCommon'; + +export enum PropertyFoldingState { + Expanded, + Collapsed +} + +export const OUTPUT_EDITOR_HEIGHT_MAGIC = 1440; + +type ILayoutInfoDelta0 = { [K in keyof IDiffElementLayoutInfo]?: number; }; +interface ILayoutInfoDelta extends ILayoutInfoDelta0 { + rawOutputHeight?: number; + recomputeOutput?: boolean; +} + +export abstract class DiffElementViewModelBase extends Disposable { + public metadataFoldingState: PropertyFoldingState; + public outputFoldingState: PropertyFoldingState; + protected _layoutInfoEmitter = new Emitter(); + onDidLayoutChange = this._layoutInfoEmitter.event; + protected _stateChangeEmitter = new Emitter<{ renderOutput: boolean; }>(); + onDidStateChange = this._stateChangeEmitter.event; + protected _layoutInfo!: IDiffElementLayoutInfo; + + set rawOutputHeight(height: number) { + this._layout({ rawOutputHeight: Math.min(OUTPUT_EDITOR_HEIGHT_MAGIC, height) }); + } + + get rawOutputHeight() { + throw new Error('Use Cell.layoutInfo.rawOutputHeight'); + } + + set outputStatusHeight(height: number) { + this._layout({ outputStatusHeight: height }); + } + + get outputStatusHeight() { + throw new Error('Use Cell.layoutInfo.outputStatusHeight'); + } + + set editorHeight(height: number) { + this._layout({ editorHeight: height }); + } + + get editorHeight() { + throw new Error('Use Cell.layoutInfo.editorHeight'); + } + + set editorMargin(margin: number) { + this._layout({ editorMargin: margin }); + } + + get editorMargin() { + throw new Error('Use Cell.layoutInfo.editorMargin'); + } + + set metadataHeight(height: number) { + this._layout({ metadataHeight: height }); + } + + get metadataHeight() { + throw new Error('Use Cell.layoutInfo.metadataHeight'); + } + + private _renderOutput = true; + + set renderOutput(value: boolean) { + this._renderOutput = value; + this._layout({ recomputeOutput: true }); + this._stateChangeEmitter.fire({ renderOutput: this._renderOutput }); + } + + get renderOutput() { + return this._renderOutput; + } + + get layoutInfo(): IDiffElementLayoutInfo { + return this._layoutInfo; + } + + private _sourceEditorViewState: editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null = null; + private _outputEditorViewState: editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null = null; + private _metadataEditorViewState: editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null = null; + + constructor( + readonly mainDocumentTextModel: NotebookTextModel, + readonly original: DiffNestedCellViewModel | undefined, + readonly modified: DiffNestedCellViewModel | undefined, + readonly type: 'unchanged' | 'insert' | 'delete' | 'modified', + readonly editorEventDispatcher: NotebookDiffEditorEventDispatcher + ) { + super(); + this._layoutInfo = { + width: 0, + editorHeight: 0, + editorMargin: 0, + metadataHeight: 0, + metadataStatusHeight: 25, + rawOutputHeight: 0, + outputTotalHeight: 0, + outputStatusHeight: 25, + bodyMargin: 32, + totalHeight: 82 + }; + + this.metadataFoldingState = PropertyFoldingState.Collapsed; + this.outputFoldingState = PropertyFoldingState.Collapsed; + + this._register(this.editorEventDispatcher.onDidChangeLayout(e => { + this._layoutInfoEmitter.fire({ outerWidth: true }); + })); + } + + layoutChange() { + this._layout({ recomputeOutput: true }); + } + + protected _layout(delta: ILayoutInfoDelta) { + const width = delta.width !== undefined ? delta.width : this._layoutInfo.width; + 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 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; + const bodyMargin = delta.bodyMargin !== undefined ? delta.bodyMargin : this._layoutInfo.bodyMargin; + const outputHeight = (delta.recomputeOutput || delta.rawOutputHeight !== undefined) ? this._getOutputTotalHeight(rawOutputHeight) : this._layoutInfo.outputTotalHeight; + + const totalHeight = editorHeight + + editorMargin + + metadataHeight + + metadataStatusHeight + + outputHeight + + outputStatusHeight + + bodyMargin; + + const newLayout: IDiffElementLayoutInfo = { + width: width, + editorHeight: editorHeight, + editorMargin: editorMargin, + metadataHeight: metadataHeight, + metadataStatusHeight: metadataStatusHeight, + outputTotalHeight: outputHeight, + outputStatusHeight: outputStatusHeight, + bodyMargin: bodyMargin, + rawOutputHeight: rawOutputHeight, + totalHeight: totalHeight + }; + + const changeEvent: CellDiffViewModelLayoutChangeEvent = {}; + + if (newLayout.width !== this._layoutInfo.width) { + changeEvent.width = true; + } + + if (newLayout.editorHeight !== this._layoutInfo.editorHeight) { + changeEvent.editorHeight = true; + } + + if (newLayout.editorMargin !== this._layoutInfo.editorMargin) { + changeEvent.editorMargin = true; + } + + if (newLayout.metadataHeight !== this._layoutInfo.metadataHeight) { + changeEvent.metadataHeight = true; + } + + if (newLayout.metadataStatusHeight !== this._layoutInfo.metadataStatusHeight) { + changeEvent.metadataStatusHeight = true; + } + + if (newLayout.outputTotalHeight !== this._layoutInfo.outputTotalHeight) { + changeEvent.outputTotalHeight = true; + } + + if (newLayout.outputStatusHeight !== this._layoutInfo.outputStatusHeight) { + changeEvent.outputStatusHeight = true; + } + + if (newLayout.bodyMargin !== this._layoutInfo.bodyMargin) { + changeEvent.bodyMargin = true; + } + + if (newLayout.totalHeight !== this._layoutInfo.totalHeight) { + changeEvent.totalHeight = true; + } + + this._layoutInfo = newLayout; + this._fireLayoutChangeEvent(changeEvent); + } + + private _getOutputTotalHeight(rawOutputHeight: number) { + if (this.outputFoldingState === PropertyFoldingState.Collapsed) { + return 0; + } + + if (this.renderOutput) { + if (this.isOutputEmpty()) { + // single line; + return 24; + } + return this.getRichOutputTotalHeight(); + } else { + return rawOutputHeight; + } + } + + private _fireLayoutChangeEvent(state: CellDiffViewModelLayoutChangeEvent) { + this._layoutInfoEmitter.fire(state); + this.editorEventDispatcher.emit([{ type: NotebookDiffViewEventType.CellLayoutChanged, source: this._layoutInfo }]); + } + + abstract checkIfOutputsModified(): boolean; + abstract checkMetadataIfModified(): boolean; + abstract isOutputEmpty(): boolean; + abstract getRichOutputTotalHeight(): number; + abstract getCellByUri(cellUri: URI): IGenericCellViewModel; + abstract getOutputOffsetInCell(diffSide: DiffSide, index: number): number; + abstract getOutputOffsetInContainer(diffSide: DiffSide, index: number): number; + abstract updateOutputHeight(diffSide: DiffSide, index: number, height: number): void; + abstract getNestedCellViewModel(diffSide: DiffSide): DiffNestedCellViewModel; + + getComputedCellContainerWidth(layoutInfo: NotebookLayoutInfo, diffEditor: boolean, fullWidth: boolean) { + if (fullWidth) { + return layoutInfo.width - 2 * DIFF_CELL_MARGIN + (diffEditor ? DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH : 0) - 2; + } + + return (layoutInfo.width - 2 * DIFF_CELL_MARGIN + (diffEditor ? DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH : 0)) / 2 - 18 - 2; + } + + getOutputEditorViewState(): editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null { + return this._outputEditorViewState; + } + + saveOutputEditorViewState(viewState: editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null) { + this._outputEditorViewState = viewState; + } + + getMetadataEditorViewState(): editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null { + return this._metadataEditorViewState; + } + + saveMetadataEditorViewState(viewState: editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null) { + this._metadataEditorViewState = viewState; + } + + getSourceEditorViewState(): editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null { + return this._sourceEditorViewState; + } + + saveSpirceEditorViewState(viewState: editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null) { + this._sourceEditorViewState = viewState; + } +} + +export class SideBySideDiffElementViewModel extends DiffElementViewModelBase { + get originalDocument() { + return this.otherDocumentTextModel; + } + + get modifiedDocument() { + return this.mainDocumentTextModel; + } + + constructor( + readonly mainDocumentTextModel: NotebookTextModel, + readonly otherDocumentTextModel: NotebookTextModel, + readonly original: DiffNestedCellViewModel, + readonly modified: DiffNestedCellViewModel, + readonly type: 'unchanged' | 'modified', + readonly editorEventDispatcher: NotebookDiffEditorEventDispatcher + ) { + super( + mainDocumentTextModel, + original, + modified, + type, + editorEventDispatcher); + + this.metadataFoldingState = PropertyFoldingState.Collapsed; + this.outputFoldingState = PropertyFoldingState.Collapsed; + + if (this.checkMetadataIfModified()) { + this.metadataFoldingState = PropertyFoldingState.Expanded; + } + + if (this.checkIfOutputsModified()) { + this.outputFoldingState = PropertyFoldingState.Expanded; + } + + this._register(this.original.onDidChangeOutputLayout(() => { + this._layout({ recomputeOutput: true }); + })); + + this._register(this.modified.onDidChangeOutputLayout(() => { + this._layout({ recomputeOutput: true }); + })); + } + + checkIfOutputsModified() { + return !this.mainDocumentTextModel.transientOptions.transientOutputs && hash(this.original?.outputs ?? []) !== hash(this.modified?.outputs ?? []); + } + + checkMetadataIfModified(): boolean { + return hash(getFormatedMetadataJSON(this.mainDocumentTextModel, this.original?.metadata || {}, this.original?.language)) !== hash(getFormatedMetadataJSON(this.mainDocumentTextModel, this.modified?.metadata ?? {}, this.modified?.language)); + } + + updateOutputHeight(diffSide: DiffSide, index: number, height: number) { + if (diffSide === DiffSide.Original) { + this.original.updateOutputHeight(index, height); + } else { + this.modified.updateOutputHeight(index, height); + } + } + + getOutputOffsetInContainer(diffSide: DiffSide, index: number) { + if (diffSide === DiffSide.Original) { + return this.original.getOutputOffset(index); + } else { + return this.modified.getOutputOffset(index); + } + } + + getOutputOffsetInCell(diffSide: DiffSide, index: number) { + const offsetInOutputsContainer = this.getOutputOffsetInContainer(diffSide, index); + + return this._layoutInfo.editorHeight + + this._layoutInfo.editorMargin + + this._layoutInfo.metadataHeight + + this._layoutInfo.metadataStatusHeight + + this._layoutInfo.outputStatusHeight + + this._layoutInfo.bodyMargin / 2 + + offsetInOutputsContainer; + } + + isOutputEmpty() { + if (this.mainDocumentTextModel.transientOptions.transientOutputs) { + return true; + } + + if (this.checkIfOutputsModified()) { + return false; + } + + // outputs are not changed + + return (this.original?.outputs || []).length === 0; + } + + getRichOutputTotalHeight() { + return Math.max(this.original.getOutputTotalHeight(), this.modified.getOutputTotalHeight()); + } + + getNestedCellViewModel(diffSide: DiffSide): DiffNestedCellViewModel { + throw new Error('Method not implemented.'); + } + + getCellByUri(cellUri: URI): IGenericCellViewModel { + if (cellUri.toString() === this.original.uri.toString()) { + return this.original; + } else { + return this.modified; + } + } +} + +export class SingleSideDiffElementViewModel extends DiffElementViewModelBase { + get cellViewModel() { + return this.type === 'insert' ? this.modified! : this.original!; + } + + get originalDocument() { + if (this.type === 'insert') { + return this.otherDocumentTextModel; + } else { + return this.mainDocumentTextModel; + } + } + + get modifiedDocument() { + if (this.type === 'insert') { + return this.mainDocumentTextModel; + } else { + return this.otherDocumentTextModel; + } + } + + constructor( + readonly mainDocumentTextModel: NotebookTextModel, + readonly otherDocumentTextModel: NotebookTextModel, + readonly original: DiffNestedCellViewModel | undefined, + readonly modified: DiffNestedCellViewModel | undefined, + readonly type: 'insert' | 'delete', + readonly editorEventDispatcher: NotebookDiffEditorEventDispatcher + ) { + super(mainDocumentTextModel, original, modified, type, editorEventDispatcher); + this._register(this.cellViewModel!.onDidChangeOutputLayout(() => { + this._layout({ recomputeOutput: true }); + })); + } + + getNestedCellViewModel(diffSide: DiffSide): DiffNestedCellViewModel { + return this.type === 'insert' ? this.modified! : this.original!; + } + + + checkIfOutputsModified(): boolean { + return false; + } + + checkMetadataIfModified(): boolean { + return false; + } + + updateOutputHeight(diffSide: DiffSide, index: number, height: number) { + this.cellViewModel?.updateOutputHeight(index, height); + } + + getOutputOffsetInContainer(diffSide: DiffSide, index: number) { + return this.cellViewModel!.getOutputOffset(index); + } + + getOutputOffsetInCell(diffSide: DiffSide, index: number) { + const offsetInOutputsContainer = this.cellViewModel!.getOutputOffset(index); + + return this._layoutInfo.editorHeight + + this._layoutInfo.editorMargin + + this._layoutInfo.metadataHeight + + this._layoutInfo.metadataStatusHeight + + this._layoutInfo.outputStatusHeight + + this._layoutInfo.bodyMargin / 2 + + offsetInOutputsContainer; + } + + isOutputEmpty() { + if (this.mainDocumentTextModel.transientOptions.transientOutputs) { + return true; + } + + // outputs are not changed + + return (this.original?.outputs || this.modified?.outputs || []).length === 0; + } + + getRichOutputTotalHeight() { + return this.cellViewModel?.getOutputTotalHeight() ?? 0; + } + + getCellByUri(cellUri: URI): IGenericCellViewModel { + return this.cellViewModel!; + } +} + +export function getFormatedMetadataJSON(documentTextModel: NotebookTextModel, metadata: NotebookCellMetadata, language?: string) { + let filteredMetadata: { [key: string]: any } = {}; + + if (documentTextModel) { + const transientMetadata = documentTextModel.transientOptions.transientMetadata; + + const keys = new Set([...Object.keys(metadata)]); + for (let key of keys) { + if (!(transientMetadata[key as keyof NotebookCellMetadata]) + ) { + filteredMetadata[key] = metadata[key as keyof NotebookCellMetadata]; + } + } + } else { + filteredMetadata = metadata; + } + + const content = JSON.stringify({ + language, + ...filteredMetadata + }); + + const edits = format(content, undefined, {}); + const metadataSource = applyEdits(content, edits); + + return metadataSource; +} diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel.ts new file mode 100644 index 000000000..bd9f8d2ad --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel.ts @@ -0,0 +1,123 @@ +/*--------------------------------------------------------------------------------------------- + * 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 } from 'vs/base/common/lifecycle'; +import { generateUuid } from 'vs/base/common/uuid'; +import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; +import { IDiffNestedCellViewModel } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; +import { CellViewModelStateChangeEvent, ICellOutputViewModel, IGenericCellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellOutputViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/cellOutputViewModel'; +import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; + +export class DiffNestedCellViewModel extends Disposable implements IDiffNestedCellViewModel, IGenericCellViewModel { + private _id: string; + get id() { + return this._id; + } + + get outputs() { + return this.textModel.outputs; + } + + get language() { + return this.textModel.language; + } + + get metadata() { + return this.textModel.metadata; + } + + get uri() { + return this.textModel.uri; + } + + get handle() { + return this.textModel.handle; + } + + protected readonly _onDidChangeState: Emitter = this._register(new Emitter()); + + private _hoveringOutput: boolean = false; + public get outputIsHovered(): boolean { + return this._hoveringOutput; + } + + public set outputIsHovered(v: boolean) { + this._hoveringOutput = v; + this._onDidChangeState.fire({ outputIsHoveredChanged: true }); + } + private _outputViewModels: ICellOutputViewModel[]; + + get outputsViewModels() { + return this._outputViewModels; + } + + protected _outputCollection: number[] = []; + protected _outputsTop: PrefixSumComputer | null = null; + protected readonly _onDidChangeOutputLayout = new Emitter(); + readonly onDidChangeOutputLayout = this._onDidChangeOutputLayout.event; + + + constructor( + readonly textModel: NotebookCellTextModel, + @INotebookService private _notebookService: INotebookService + ) { + super(); + this._id = generateUuid(); + + this._outputViewModels = this.textModel.outputs.map(output => new CellOutputViewModel(this, output, this._notebookService)); + this._register(this.textModel.onDidChangeOutputs((splices) => { + splices.reverse().forEach(splice => { + this._outputCollection.splice(splice[0], splice[1], ...splice[2].map(() => 0)); + this._outputViewModels.splice(splice[0], splice[1], ...splice[2].map(output => new CellOutputViewModel(this, output, this._notebookService))); + }); + + this._outputsTop = null; + this._onDidChangeOutputLayout.fire(); + })); + this._outputCollection = new Array(this.textModel.outputs.length); + } + + private _ensureOutputsTop() { + if (!this._outputsTop) { + const values = new Uint32Array(this._outputCollection.length); + for (let i = 0; i < this._outputCollection.length; i++) { + values[i] = this._outputCollection[i]; + } + + this._outputsTop = new PrefixSumComputer(values); + } + } + + getOutputOffset(index: number): number { + this._ensureOutputsTop(); + + if (index >= this._outputCollection.length) { + throw new Error('Output index out of range!'); + } + + return this._outputsTop!.getAccumulatedValue(index - 1); + } + + updateOutputHeight(index: number, height: number): void { + if (index >= this._outputCollection.length) { + throw new Error('Output index out of range!'); + } + + this._ensureOutputsTop(); + this._outputCollection[index] = height; + if (this._outputsTop!.changeValue(index, height)) { + this._onDidChangeOutputLayout.fire(); + } + } + + getOutputTotalHeight() { + this._ensureOutputsTop(); + + return this._outputsTop?.getTotalValue() ?? 0; + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/diff/eventDispatcher.ts b/src/vs/workbench/contrib/notebook/browser/diff/eventDispatcher.ts new file mode 100644 index 000000000..2e4cb22ee --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/diff/eventDispatcher.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. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter } from 'vs/base/common/event'; +import { IDiffElementLayoutInfo } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; +import { NotebookLayoutChangeEvent, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; + +export enum NotebookDiffViewEventType { + LayoutChanged = 1, + CellLayoutChanged = 2 + // MetadataChanged = 2, + // CellStateChanged = 3 +} + +export class NotebookDiffLayoutChangedEvent { + public readonly type = NotebookDiffViewEventType.LayoutChanged; + + constructor(readonly source: NotebookLayoutChangeEvent, readonly value: NotebookLayoutInfo) { + + } +} + +export class NotebookCellLayoutChangedEvent { + public readonly type = NotebookDiffViewEventType.CellLayoutChanged; + + constructor(readonly source: IDiffElementLayoutInfo) { + + } +} + +export type NotebookDiffViewEvent = NotebookDiffLayoutChangedEvent | NotebookCellLayoutChangedEvent; + +export class NotebookDiffEditorEventDispatcher { + protected readonly _onDidChangeLayout = new Emitter(); + readonly onDidChangeLayout = this._onDidChangeLayout.event; + protected readonly _onDidChangeCellLayout = new Emitter(); + readonly onDidChangeCellLayout = this._onDidChangeCellLayout.event; + + constructor() { + } + + emit(events: NotebookDiffViewEvent[]) { + for (let i = 0, len = events.length; i < len; i++) { + const e = events[i]; + + switch (e.type) { + case NotebookDiffViewEventType.LayoutChanged: + this._onDidChangeLayout.fire(e); + break; + case NotebookDiffViewEventType.CellLayoutChanged: + this._onDidChangeCellLayout.fire(e); + break; + } + } + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css index 950cbce2f..407465444 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css @@ -21,6 +21,31 @@ flex-direction: row; } +.notebook-text-diff-editor .webview-cover { + user-select: initial; + -webkit-user-select: initial; +} + +.notebook-text-diff-editor .cell-body .border-container { + position: absolute; + width: calc(100% - 32px); +} + +.notebook-text-diff-editor .cell-body .border-container .top-border, +.notebook-text-diff-editor .cell-body .border-container .bottom-border { + position: absolute; + width: 100%; +} + +.notebook-text-diff-editor .cell-body .border-container .left-border, +.notebook-text-diff-editor .cell-body .border-container .right-border { + position: absolute; +} + +.notebook-text-diff-editor .cell-body .border-container .right-border { + left: 100%; +} + .notebook-text-diff-editor .cell-body.right { flex-direction: row-reverse; } @@ -32,18 +57,20 @@ .notebook-text-diff-editor .cell-body .cell-diff-editor-container { width: 100%; - overflow: hidden; + /* why we overflow hidden at the beginning?*/ + /* overflow: hidden; */ } .notebook-text-diff-editor .cell-body .cell-diff-editor-container .metadata-editor-container.diff, .notebook-text-diff-editor .cell-body .cell-diff-editor-container .output-editor-container.diff, .notebook-text-diff-editor .cell-body .cell-diff-editor-container .editor-container.diff { /** 100% + diffOverviewWidth */ - width: calc(100% + 30px); + width: calc(100%); } .notebook-text-diff-editor .cell-body .cell-diff-editor-container .metadata-editor-container .monaco-diff-editor .diffOverview, -.notebook-text-diff-editor .cell-body .cell-diff-editor-container .editor-container.diff .monaco-diff-editor .diffOverview { +.notebook-text-diff-editor .cell-body .cell-diff-editor-container .editor-container.diff .monaco-diff-editor .diffOverview, +.notebook-text-diff-editor .cell-body .cell-diff-editor-container .output-editor-container.diff .monaco-diff-editor .diffOverview { display: none; } @@ -106,10 +133,15 @@ overflow: hidden; } +.monaco-workbench .notebook-text-diff-editor > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row { + overflow: visible !important; +} + .monaco-workbench .notebook-text-diff-editor > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row, .monaco-workbench .notebook-text-diff-editor > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover, .monaco-workbench .notebook-text-diff-editor > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { outline: none !important; + background-color: transparent !important; } .notebook-text-diff-editor .cell-diff-editor-container .editor-input-toolbar-container { @@ -118,3 +150,120 @@ top: 16px; margin: 4px 2px; } + +.monaco-workbench .notebook-text-diff-editor .cell-body { + height: 0; +} + +.monaco-workbench .notebook-text-diff-editor .cell-body .output-view-container { + user-select: text; + -webkit-user-select: text; + -ms-user-select: text; + white-space: initial; + cursor: auto; + position: relative; +} + +.monaco-workbench .notebook-text-diff-editor .cell-body .output-view-container .output-plaintext { + white-space: pre; + overflow-x: hidden; +} + +.monaco-workbench .notebook-text-diff-editor .cell-body.left .output-view-container .output-inner-container, +.monaco-workbench .notebook-text-diff-editor .cell-body.right .output-view-container .output-inner-container { + width: 100%; + padding: 0px 8px; + box-sizing: border-box; + overflow-x: hidden; +} + +.monaco-workbench .notebook-text-diff-editor .cell-body.left .output-view-container .output-inner-container { + padding: 0px 8px 0px 32px; +} + +.monaco-workbench .notebook-text-diff-editor .cell-body.right .output-view-container .output-inner-container { + padding: 0px 8px 0px 32px; +} + +.monaco-workbench .notebook-text-diff-editor .cell-body.full .output-view-container .output-inner-container { + width: 100%; + padding: 4px 8px 4px 32px; + box-sizing: border-box; + overflow-x: hidden; +} + +.monaco-workbench .notebook-text-diff-editor .cell-body.full .output-info-container.modified .output-view-container .output-view-container-left { + top: 0; + position: absolute; + left: 0; +} + +.monaco-workbench .notebook-text-diff-editor .cell-body.full .output-info-container.modified .output-view-container .output-view-container-right { + position: absolute; + top: 0; + left: 50%; +} + +.monaco-workbench .notebook-text-diff-editor .cell-body.full .output-info-container.modified .output-view-container .output-view-container-left, +.monaco-workbench .notebook-text-diff-editor .cell-body.full .output-info-container.modified .output-view-container .output-view-container-right { + width: 50%; + display: inline-block; +} + +.monaco-workbench .notebook-text-diff-editor .cell-body.full .output-info-container.modified .output-view-container .output-view-container-left div.foreground, +.monaco-workbench .notebook-text-diff-editor .cell-body.full .output-info-container.modified .output-view-container .output-view-container-right div.foreground { + width: 100%; +} + +.monaco-workbench .notebook-text-diff-editor .output-view-container > div.foreground { + width: 100%; + min-height: 24px; + box-sizing: border-box; +} + +.monaco-workbench .notebook-text-diff-editor .output-view-container .error_message { + color: red; +} + +.monaco-workbench .notebook-text-diff-editor .output-view-container .error > div { + white-space: normal; +} + +.monaco-workbench .notebook-text-diff-editor .output-view-container .error pre.traceback { + box-sizing: border-box; + padding: 8px 0; + margin: 0px; +} + +.monaco-workbench .notebook-text-diff-editor .output-view-container .error .traceback > span { + display: block; +} + +.monaco-workbench .notebook-text-diff-editor .output-view-container .display img { + max-width: 100%; +} + +.monaco-workbench .notebook-text-diff-editor .output-view-container .multi-mimetype-output { + position: absolute; + top: 4px; + left: 8px; + width: 16px; + height: 16px; + cursor: pointer; + padding: 2px 4px 4px 2px; +} + +.monaco-workbench .notebook-text-diff-editor .output-view-container .output-empty-view span { + opacity: 0.7; +} + +.monaco-workbench .notebook-text-diff-editor .output-view-container .output-empty-view { + font-style: italic; + height: 24px; + margin: auto; + padding-left: 12px; +} + +.monaco-workbench .notebook-text-diff-editor .output-view-container pre { + margin: 4px 0; +} diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts index cb8709d16..0911630f6 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts @@ -8,10 +8,11 @@ import { localize } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ActiveEditorContext, viewColumnToEditorGroup } from 'vs/workbench/common/editor'; -import { CellDiffViewModel } from 'vs/workbench/contrib/notebook/browser/diff/celllDiffViewModel'; +import { DiffElementViewModelBase } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; +import { NOTEBOOK_DIFF_CELL_PROPERTY, NOTEBOOK_DIFF_CELL_PROPERTY_EXPANDED } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; import { NotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor'; import { NotebookDiffEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookDiffEditorInput'; -import { openAsTextIcon, revertIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { openAsTextIcon, renderOutputIcon, revertIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -60,12 +61,14 @@ registerAction2(class extends Action2 { icon: revertIcon, f1: false, menu: { - id: MenuId.NotebookDiffCellMetadataTitle - } + id: MenuId.NotebookDiffCellMetadataTitle, + when: NOTEBOOK_DIFF_CELL_PROPERTY + }, + precondition: NOTEBOOK_DIFF_CELL_PROPERTY } ); } - run(accessor: ServicesAccessor, context?: { cell: CellDiffViewModel }) { + run(accessor: ServicesAccessor, context?: { cell: DiffElementViewModelBase }) { if (!context) { return; } @@ -77,7 +80,55 @@ registerAction2(class extends Action2 { return; } - modified.metadata = original.metadata; + modified.textModel.metadata = original.metadata; + } +}); + +// registerAction2(class extends Action2 { +// constructor() { +// super( +// { +// id: 'notebook.diff.cell.switchOutputRenderingStyle', +// title: localize('notebook.diff.cell.switchOutputRenderingStyle', "Switch Outputs Rendering"), +// icon: renderOutputIcon, +// f1: false, +// menu: { +// id: MenuId.NotebookDiffCellOutputsTitle +// } +// } +// ); +// } +// run(accessor: ServicesAccessor, context?: { cell: DiffElementViewModelBase }) { +// if (!context) { +// return; +// } + +// context.cell.renderOutput = true; +// } +// }); + + +registerAction2(class extends Action2 { + constructor() { + super( + { + id: 'notebook.diff.cell.switchOutputRenderingStyleToText', + title: localize('notebook.diff.cell.switchOutputRenderingStyleToText', "Switch Output Rendering"), + icon: renderOutputIcon, + f1: false, + menu: { + id: MenuId.NotebookDiffCellOutputsTitle, + when: NOTEBOOK_DIFF_CELL_PROPERTY_EXPANDED + } + } + ); + } + run(accessor: ServicesAccessor, context?: { cell: DiffElementViewModelBase }) { + if (!context) { + return; + } + + context.cell.renderOutput = !context.cell.renderOutput; } }); @@ -90,12 +141,14 @@ registerAction2(class extends Action2 { icon: revertIcon, f1: false, menu: { - id: MenuId.NotebookDiffCellOutputsTitle - } + id: MenuId.NotebookDiffCellOutputsTitle, + when: NOTEBOOK_DIFF_CELL_PROPERTY + }, + precondition: NOTEBOOK_DIFF_CELL_PROPERTY } ); } - run(accessor: ServicesAccessor, context?: { cell: CellDiffViewModel }) { + run(accessor: ServicesAccessor, context?: { cell: DiffElementViewModelBase }) { if (!context) { return; } @@ -107,10 +160,11 @@ registerAction2(class extends Action2 { return; } - modified.spliceNotebookCellOutputs([[0, modified.outputs.length, original.outputs]]); + modified.textModel.spliceNotebookCellOutputs([[0, modified.outputs.length, original.outputs]]); } }); + registerAction2(class extends Action2 { constructor() { super( @@ -120,12 +174,15 @@ registerAction2(class extends Action2 { icon: revertIcon, f1: false, menu: { - id: MenuId.NotebookDiffCellInputTitle - } + id: MenuId.NotebookDiffCellInputTitle, + when: NOTEBOOK_DIFF_CELL_PROPERTY + }, + precondition: NOTEBOOK_DIFF_CELL_PROPERTY + } ); } - run(accessor: ServicesAccessor, context?: { cell: CellDiffViewModel }) { + run(accessor: ServicesAccessor, context?: { cell: DiffElementViewModelBase }) { if (!context) { return; } @@ -139,7 +196,7 @@ registerAction2(class extends Action2 { const bulkEditService = accessor.get(IBulkEditService); return bulkEditService.apply([ - new ResourceTextEdit(modified.uri, { range: modified.getFullModelRange(), text: original.getValue() }), + new ResourceTextEdit(modified.uri, { range: modified.textModel.getFullModelRange(), text: original.textModel.getValue() }), ], { quotableLabel: 'Split Notebook Cell' }); } }); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts new file mode 100644 index 000000000..611b0f264 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.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. + *--------------------------------------------------------------------------------------------*/ + +import { ICommonCellInfo, ICommonNotebookEditor, IDisplayOutputViewModel, IGenericCellViewModel, IInsetRenderOutput, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { DiffElementViewModelBase } 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 { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; +import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; +import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; +import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; + +export enum DiffSide { + Original = 0, + Modified = 1 +} + +export interface IDiffCellInfo extends ICommonCellInfo { + diffElement: DiffElementViewModelBase; +} + +export interface INotebookTextDiffEditor extends ICommonNotebookEditor { + readonly textModel?: NotebookTextModel; + onMouseUp: Event<{ readonly event: MouseEvent; readonly target: DiffElementViewModelBase; }>; + onDidDynamicOutputRendered: Event<{ cell: IGenericCellViewModel, output: IDisplayOutputViewModel }>; + getOverflowContainerDomNode(): HTMLElement; + getLayoutInfo(): NotebookLayoutInfo; + layoutNotebookCell(cell: DiffElementViewModelBase, height: number): void; + getOutputRenderer(): OutputRenderer; + createInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, output: IInsetRenderOutput, getOffset: () => number, diffSide: DiffSide): void; + showInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, displayOutput: IDisplayOutputViewModel, diffSide: DiffSide): void; + removeInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, output: IDisplayOutputViewModel, diffSide: DiffSide): void; + hideInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, output: IDisplayOutputViewModel): void; + /** + * Trigger the editor to scroll from scroll event programmatically + */ + triggerScroll(event: IMouseWheelEvent): void; + getCellByInfo(cellInfo: ICommonCellInfo): IGenericCellViewModel; + focusNotebookCell(cell: IGenericCellViewModel, focus: 'editor' | 'container' | 'output'): void; + focusNextNotebookCell(cell: IGenericCellViewModel, focus: 'editor' | 'container' | 'output'): void; + updateOutputHeight(cellInfo: ICommonCellInfo, output: IDisplayOutputViewModel, height: number, isInit: boolean): void; + deltaCellOutputContainerClassNames(diffSide: DiffSide, cellId: string, added: string[], removed: string[]): void; +} + +export interface IDiffNestedCellViewModel { + +} + +export interface CellDiffCommonRenderTemplate { + readonly leftBorder: HTMLElement; + readonly rightBorder: HTMLElement; + readonly topBorder: HTMLElement; + readonly bottomBorder: HTMLElement; +} + +export interface CellDiffSingleSideRenderTemplate extends CellDiffCommonRenderTemplate { + readonly container: HTMLElement; + readonly body: HTMLElement; + readonly diffEditorContainer: HTMLElement; + readonly diagonalFill: HTMLElement; + readonly elementDisposables: DisposableStore; + readonly sourceEditor: CodeEditorWidget; + readonly metadataHeaderContainer: HTMLElement; + readonly metadataInfoContainer: HTMLElement; + readonly outputHeaderContainer: HTMLElement; + readonly outputInfoContainer: HTMLElement; + +} + + +export interface CellDiffSideBySideRenderTemplate extends CellDiffCommonRenderTemplate { + readonly container: HTMLElement; + readonly body: HTMLElement; + readonly diffEditorContainer: HTMLElement; + readonly elementDisposables: DisposableStore; + readonly sourceEditor: DiffEditorWidget; + readonly editorContainer: HTMLElement; + readonly inputToolbarContainer: HTMLElement; + readonly toolbar: ToolBar; + readonly metadataHeaderContainer: HTMLElement; + readonly metadataInfoContainer: HTMLElement; + readonly outputHeaderContainer: HTMLElement; + readonly outputInfoContainer: HTMLElement; +} + +export interface IDiffElementLayoutInfo { + totalHeight: number; + width: number; + editorHeight: number; + editorMargin: number; + metadataHeight: number; + metadataStatusHeight: number; + rawOutputHeight: number; + outputTotalHeight: number; + outputStatusHeight: number; + bodyMargin: number +} + +type IDiffElementSelfLayoutChangeEvent = { [K in keyof IDiffElementLayoutInfo]?: boolean }; + +export interface CellDiffViewModelLayoutChangeEvent extends IDiffElementSelfLayoutChangeEvent { + font?: BareFontInfo; + outerWidth?: boolean; + metadataEditor?: boolean; + outputEditor?: boolean; + outputView?: boolean; +} + +export const DIFF_CELL_MARGIN = 16; +export const NOTEBOOK_DIFF_CELL_PROPERTY = new RawContextKey('notebookDiffCellPropertyChanged', false); +export const NOTEBOOK_DIFF_CELL_PROPERTY_EXPANDED = new RawContextKey('notebookDiffCellPropertyExpanded', false); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts index 13f6973c0..19d4f07ef 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts @@ -13,32 +13,38 @@ import { notebookCellBorder, NotebookEditorWidget } from 'vs/workbench/contrib/n import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { NotebookDiffEditorInput } from '../notebookDiffEditorInput'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { WorkbenchList } from 'vs/platform/list/browser/listService'; -import { CellDiffViewModel } from 'vs/workbench/contrib/notebook/browser/diff/celllDiffViewModel'; +import { DiffElementViewModelBase, SideBySideDiffElementViewModel, SingleSideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { CellDiffRenderer, NotebookCellTextDiffListDelegate, NotebookTextDiffList } from 'vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList'; -import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { CellDiffSideBySideRenderer, CellDiffSingleSideRenderer, NotebookCellTextDiffListDelegate, NotebookTextDiffList } from 'vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { diffDiagonalFill, diffInserted, diffRemoved, editorBackground, focusBorder, foreground } from 'vs/platform/theme/common/colorRegistry'; import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; -import { getZoomLevel } from 'vs/base/browser/browser'; -import { NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { DIFF_CELL_MARGIN, INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/common'; +import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser'; +import { IDisplayOutputLayoutUpdateRequest, IDisplayOutputViewModel, IGenericCellViewModel, IInsetRenderOutput, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { DiffSide, DIFF_CELL_MARGIN, IDiffCellInfo, INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; import { Emitter } from 'vs/base/common/event'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { NotebookDiffEditorEventDispatcher, NotebookLayoutChangedEvent } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; -import { INotebookDiffEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellUri, INotebookDiffEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { FileService } from 'vs/platform/files/common/fileService'; import { IFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { IDiffChange } from 'vs/base/common/diff/diff'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; +import { SequencerByKey } from 'vs/base/common/async'; +import { generateUuid } from 'vs/base/common/uuid'; +import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { DiffNestedCellViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel'; +import { BackLayerWebView } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView'; +import { CELL_OUTPUT_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; +import { NotebookDiffEditorEventDispatcher, NotebookDiffLayoutChangedEvent } from 'vs/workbench/contrib/notebook/browser/diff/eventDispatcher'; -export const IN_NOTEBOOK_TEXT_DIFF_EDITOR = new RawContextKey('isInNotebookTextDiffEditor', false); +const $ = DOM.$; export class NotebookTextDiffEditor extends EditorPane implements INotebookTextDiffEditor { static readonly ID: string = 'workbench.editor.notebookTextDiffEditor'; @@ -46,21 +52,38 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD private _rootElement!: HTMLElement; private _overflowContainer!: HTMLElement; private _dimension: DOM.Dimension | null = null; - private _list!: WorkbenchList; + private _diffElementViewModels: DiffElementViewModelBase[] = []; + private _list!: NotebookTextDiffList; + private _modifiedWebview: BackLayerWebView | null = null; + private _originalWebview: BackLayerWebView | null = null; + private _webviewTransparentCover: HTMLElement | null = null; private _fontInfo: BareFontInfo | undefined; - private readonly _onMouseUp = this._register(new Emitter<{ readonly event: MouseEvent; readonly target: CellDiffViewModel; }>()); + private readonly _onMouseUp = this._register(new Emitter<{ readonly event: MouseEvent; readonly target: DiffElementViewModelBase; }>()); public readonly onMouseUp = this._onMouseUp.event; private _eventDispatcher: NotebookDiffEditorEventDispatcher | undefined; protected _scopeContextKeyService!: IContextKeyService; private _model: INotebookDiffEditorModel | null = null; private _modifiedResourceDisposableStore = new DisposableStore(); + private _outputRenderer: OutputRenderer; get textModel() { return this._model?.modified.notebook; } private _revealFirst: boolean; + private readonly _insetModifyQueueByOutputId = new SequencerByKey(); + + protected _onDidDynamicOutputRendered = new Emitter<{ cell: IGenericCellViewModel, output: IDisplayOutputViewModel }>(); + onDidDynamicOutputRendered = this._onDidDynamicOutputRendered.event; + + private _localStore: DisposableStore = this._register(new DisposableStore()); + + private _isDisposed: boolean = false; + + get isDisposed() { + return this._isDisposed; + } constructor( @IInstantiationService readonly instantiationService: IInstantiationService, @@ -75,10 +98,40 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD ) { super(NotebookTextDiffEditor.ID, telemetryService, themeService, storageService); const editorOptions = this.configurationService.getValue('editor'); - this._fontInfo = BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel()); + this._fontInfo = BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel(), getPixelRatio()); this._revealFirst = true; this._register(this._modifiedResourceDisposableStore); + this._outputRenderer = new OutputRenderer(this, this.instantiationService); + } + + focusNotebookCell(cell: IGenericCellViewModel, focus: 'output' | 'editor' | 'container'): void { + // throw new Error('Method not implemented.'); + } + + focusNextNotebookCell(cell: IGenericCellViewModel, focus: 'output' | 'editor' | 'container'): void { + // throw new Error('Method not implemented.'); + } + + updateOutputHeight(cellInfo: IDiffCellInfo, output: IDisplayOutputViewModel, outputHeight: number, isInit: boolean): void { + const diffElement = cellInfo.diffElement; + const cell = this.getCellByInfo(cellInfo); + const outputIndex = cell.outputsViewModels.indexOf(output); + + if (diffElement instanceof SideBySideDiffElementViewModel) { + const info = CellUri.parse(cellInfo.cellUri); + if (!info) { + return; + } + + diffElement.updateOutputHeight(info.notebook.toString() === this._model?.original.resource.toString() ? DiffSide.Original : DiffSide.Modified, outputIndex, outputHeight); + } else { + diffElement.updateOutputHeight(diffElement.type === 'insert' ? DiffSide.Modified : DiffSide.Original, outputIndex, outputHeight); + } + + if (isInit) { + this._onDidDynamicOutputRendered.fire({ cell, output }); + } } protected createEditor(parent: HTMLElement): void { @@ -87,16 +140,17 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD this._overflowContainer.classList.add('notebook-overflow-widget-container', 'monaco-editor'); DOM.append(parent, this._overflowContainer); - const renderer = this.instantiationService.createInstance(CellDiffRenderer, this); + const renderers = [ + this.instantiationService.createInstance(CellDiffSingleSideRenderer, this), + this.instantiationService.createInstance(CellDiffSideBySideRenderer, this), + ]; this._list = this.instantiationService.createInstance( NotebookTextDiffList, 'NotebookTextDiff', this._rootElement, this.instantiationService.createInstance(NotebookCellTextDiffListDelegate), - [ - renderer - ], + renderers, this.contextKeyService, { setRowLineHeight: false, @@ -140,17 +194,94 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } ); + this._register(this._list); + this._register(this._list.onMouseUp(e => { if (e.element) { this._onMouseUp.fire({ event: e.browserEvent, target: e.element }); } })); + + // transparent cover + this._webviewTransparentCover = DOM.append(this._list.rowsContainer, $('.webview-cover')); + this._webviewTransparentCover.style.display = 'none'; + + this._register(DOM.addStandardDisposableGenericMouseDownListner(this._overflowContainer, (e: StandardMouseEvent) => { + if (e.target.classList.contains('slider') && this._webviewTransparentCover) { + this._webviewTransparentCover.style.display = 'block'; + } + })); + + this._register(DOM.addStandardDisposableGenericMouseUpListner(this._overflowContainer, () => { + if (this._webviewTransparentCover) { + // no matter when + this._webviewTransparentCover.style.display = 'none'; + } + })); + + this._register(this._list.onDidScroll(e => { + this._webviewTransparentCover!.style.top = `${e.scrollTop}px`; + })); + + + } + + private _updateOutputsOffsetsInWebview(scrollTop: number, scrollHeight: number, activeWebview: BackLayerWebView, getActiveNestedCell: (diffElement: DiffElementViewModelBase) => DiffNestedCellViewModel | undefined, diffSide: DiffSide) { + activeWebview.element.style.height = `${scrollHeight}px`; + + if (activeWebview.insetMapping) { + const updateItems: IDisplayOutputLayoutUpdateRequest[] = []; + const removedItems: IDisplayOutputViewModel[] = []; + activeWebview.insetMapping.forEach((value, key) => { + const cell = getActiveNestedCell(value.cellInfo.diffElement); + if (!cell) { + return; + } + + const viewIndex = this._list.indexOf(value.cellInfo.diffElement); + + if (viewIndex === undefined) { + return; + } + + if (cell.outputsViewModels.indexOf(key) < 0) { + // output is already gone + removedItems.push(key); + } else { + const cellTop = this._list.getAbsoluteTopOfElement(value.cellInfo.diffElement); + if (activeWebview.shouldUpdateInset(cell, key, cellTop)) { + const outputIndex = cell.outputsViewModels.indexOf(key); + const outputOffset = cellTop + value.cellInfo.diffElement.getOutputOffsetInCell(diffSide, outputIndex); + + updateItems.push({ + output: key, + cellTop: cellTop, + outputOffset: outputOffset + }); + } + } + + }); + + removedItems.forEach(output => activeWebview.removeInset(output)); + + if (updateItems.length) { + activeWebview.updateViewScrollTop(-scrollTop, false, updateItems); + } + } } async setInput(input: NotebookDiffEditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { await super.setInput(input, options, context, token); - this._model = await input.resolve(); + const model = await input.resolve(); + if (this._model !== model) { + this._detachModel(); + this._model = model; + this._attachModel(); + } + + this._model = model; if (this._model === null) { return; } @@ -196,11 +327,73 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } })); - - this._eventDispatcher = new NotebookDiffEditorEventDispatcher(); + await this._createOriginalWebview(generateUuid(), this._model.original.resource); + await this._createModifiedWebview(generateUuid(), this._model.modified.resource); await this.updateLayout(); } + private _detachModel() { + this._localStore.clear(); + this._originalWebview?.dispose(); + this._originalWebview?.element.remove(); + this._originalWebview = null; + this._modifiedWebview?.dispose(); + this._modifiedWebview?.element.remove(); + this._modifiedWebview = null; + + this._modifiedResourceDisposableStore.clear(); + this._list.clear(); + + } + private _attachModel() { + this._eventDispatcher = new NotebookDiffEditorEventDispatcher(); + const updateInsets = () => { + DOM.scheduleAtNextAnimationFrame(() => { + if (this._isDisposed) { + return; + } + + if (this._modifiedWebview) { + this._updateOutputsOffsetsInWebview(this._list.scrollTop, this._list.scrollHeight, this._modifiedWebview, (diffElement: DiffElementViewModelBase) => { + return diffElement.modified; + }, DiffSide.Modified); + } + + if (this._originalWebview) { + this._updateOutputsOffsetsInWebview(this._list.scrollTop, this._list.scrollHeight, this._originalWebview, (diffElement: DiffElementViewModelBase) => { + return diffElement.original; + }, DiffSide.Original); + } + }); + }; + + this._localStore.add(this._list.onDidChangeContentHeight(() => { + updateInsets(); + })); + + this._localStore.add(this._eventDispatcher.onDidChangeCellLayout(() => { + updateInsets(); + })); + } + + private async _createModifiedWebview(id: string, resource: URI): Promise { + this._modifiedWebview = this.instantiationService.createInstance(BackLayerWebView, this, id, resource, { outputNodePadding: CELL_OUTPUT_PADDING, outputNodeLeftPadding: 32 }) as BackLayerWebView; + // attach the webview container to the DOM tree first + this._list.rowsContainer.insertAdjacentElement('afterbegin', this._modifiedWebview.element); + await this._modifiedWebview.createWebview(); + this._modifiedWebview.element.style.width = `calc(50% - 16px)`; + this._modifiedWebview.element.style.left = `calc(50%)`; + } + + private async _createOriginalWebview(id: string, resource: URI): Promise { + this._originalWebview = this.instantiationService.createInstance(BackLayerWebView, this, id, resource, { outputNodePadding: CELL_OUTPUT_PADDING, outputNodeLeftPadding: 32 }) as BackLayerWebView; + // attach the webview container to the DOM tree first + this._list.rowsContainer.insertAdjacentElement('afterbegin', this._originalWebview.element); + await this._originalWebview.createWebview(); + this._originalWebview.element.style.width = `calc(50% - 16px)`; + this._originalWebview.element.style.left = `16px`; + } + private async _resolveStats(resource: URI) { if (resource.scheme === Schemas.untitled) { return undefined; @@ -222,7 +415,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD const diffResult = await this.notebookEditorWorkerService.computeDiff(this._model.original.resource, this._model.modified.resource); const cellChanges = diffResult.cellsDiff.changes; - const cellDiffViewModels: CellDiffViewModel[] = []; + const diffElementViewModels: DiffElementViewModelBase[] = []; const originalModel = this._model.original.notebook; const modifiedModel = this._model.modified.notebook; let originalCellIndex = 0; @@ -238,20 +431,24 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD const originalCell = originalModel.cells[originalCellIndex + j]; const modifiedCell = modifiedModel.cells[modifiedCellIndex + j]; if (originalCell.getHashValue() === modifiedCell.getHashValue()) { - cellDiffViewModels.push(new CellDiffViewModel( - originalCell, - modifiedCell, + diffElementViewModels.push(new SideBySideDiffElementViewModel( + this._model.modified.notebook, + this._model.original.notebook, + this.instantiationService.createInstance(DiffNestedCellViewModel, originalCell), + this.instantiationService.createInstance(DiffNestedCellViewModel, modifiedCell), 'unchanged', this._eventDispatcher! )); } else { if (firstChangeIndex === -1) { - firstChangeIndex = cellDiffViewModels.length; + firstChangeIndex = diffElementViewModels.length; } - cellDiffViewModels.push(new CellDiffViewModel( - originalCell, - modifiedCell, + diffElementViewModels.push(new SideBySideDiffElementViewModel( + this._model.modified.notebook, + this._model.original.notebook, + this.instantiationService.createInstance(DiffNestedCellViewModel, originalCell), + this.instantiationService.createInstance(DiffNestedCellViewModel, modifiedCell), 'modified', this._eventDispatcher! )); @@ -260,24 +457,35 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD const modifiedLCS = this._computeModifiedLCS(change, originalModel, modifiedModel); if (modifiedLCS.length && firstChangeIndex === -1) { - firstChangeIndex = cellDiffViewModels.length; + firstChangeIndex = diffElementViewModels.length; } - cellDiffViewModels.push(...modifiedLCS); + diffElementViewModels.push(...modifiedLCS); originalCellIndex = change.originalStart + change.originalLength; modifiedCellIndex = change.modifiedStart + change.modifiedLength; } for (let i = originalCellIndex; i < originalModel.cells.length; i++) { - cellDiffViewModels.push(new CellDiffViewModel( - originalModel.cells[i], - modifiedModel.cells[i - originalCellIndex + modifiedCellIndex], + diffElementViewModels.push(new SideBySideDiffElementViewModel( + this._model.modified.notebook, + this._model.original.notebook, + this.instantiationService.createInstance(DiffNestedCellViewModel, originalModel.cells[i]), + this.instantiationService.createInstance(DiffNestedCellViewModel, modifiedModel.cells[i - originalCellIndex + modifiedCellIndex]), 'unchanged', this._eventDispatcher! )); } - this._list.splice(0, this._list.length, cellDiffViewModels); + this._originalWebview?.insetMapping.forEach((value, key) => { + this._originalWebview?.removeInset(key); + }); + + this._modifiedWebview?.insetMapping.forEach((value, key) => { + this._modifiedWebview?.removeInset(key); + }); + + this._diffElementViewModels = diffElementViewModels; + this._list.splice(0, this._list.length, diffElementViewModels); if (this._revealFirst && firstChangeIndex !== -1) { this._revealFirst = false; @@ -287,14 +495,16 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } private _computeModifiedLCS(change: IDiffChange, originalModel: NotebookTextModel, modifiedModel: NotebookTextModel) { - const result: CellDiffViewModel[] = []; + const result: DiffElementViewModelBase[] = []; // modified cells const modifiedLen = Math.min(change.originalLength, change.modifiedLength); for (let j = 0; j < modifiedLen; j++) { - result.push(new CellDiffViewModel( - originalModel.cells[change.originalStart + j], - modifiedModel.cells[change.modifiedStart + j], + result.push(new SideBySideDiffElementViewModel( + modifiedModel, + originalModel, + this.instantiationService.createInstance(DiffNestedCellViewModel, originalModel.cells[change.originalStart + j]), + this.instantiationService.createInstance(DiffNestedCellViewModel, modifiedModel.cells[change.modifiedStart + j]), 'modified', this._eventDispatcher! )); @@ -302,8 +512,10 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD for (let j = modifiedLen; j < change.originalLength; j++) { // deletion - result.push(new CellDiffViewModel( - originalModel.cells[change.originalStart + j], + result.push(new SingleSideDiffElementViewModel( + originalModel, + modifiedModel, + this.instantiationService.createInstance(DiffNestedCellViewModel, originalModel.cells[change.originalStart + j]), undefined, 'delete', this._eventDispatcher! @@ -312,9 +524,11 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD for (let j = modifiedLen; j < change.modifiedLength; j++) { // insertion - result.push(new CellDiffViewModel( + result.push(new SingleSideDiffElementViewModel( + modifiedModel, + originalModel, undefined, - modifiedModel.cells[change.modifiedStart + j], + this.instantiationService.createInstance(DiffNestedCellViewModel, modifiedModel.cells[change.modifiedStart + j]), 'insert', this._eventDispatcher! )); @@ -323,14 +537,12 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD return result; } - private pendingLayouts = new WeakMap(); + private pendingLayouts = new WeakMap(); - layoutNotebookCell(cell: CellDiffViewModel, height: number) { - const relayout = (cell: CellDiffViewModel, height: number) => { - const viewIndex = this._list.indexOf(cell); - - this._list?.updateElementHeight(viewIndex, height); + layoutNotebookCell(cell: DiffElementViewModelBase, height: number) { + const relayout = (cell: DiffElementViewModelBase, height: number) => { + this._list.updateElementHeight2(cell, height); }; if (this.pendingLayouts.has(cell)) { @@ -353,6 +565,79 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD return new Promise(resolve => { r = resolve; }); } + triggerScroll(event: IMouseWheelEvent) { + this._list.triggerScrollFromMouseWheelEvent(event); + } + + createInset(cellDiffViewModel: DiffElementViewModelBase, 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) { + return; + } + + if (!activeWebview.insetMapping.has(output.source)) { + const cellTop = this._list.getAbsoluteTopOfElement(cellDiffViewModel); + await activeWebview.createInset({ diffElement: cellDiffViewModel, cellHandle: cellViewModel.handle, cellId: cellViewModel.id, cellUri: cellViewModel.uri }, output, cellTop, getOffset()); + } else { + const cellTop = this._list.getAbsoluteTopOfElement(cellDiffViewModel); + const scrollTop = this._list.scrollTop; + const outputIndex = cellViewModel.outputsViewModels.indexOf(output.source); + const outputOffset = cellTop + cellDiffViewModel.getOutputOffsetInCell(diffSide, outputIndex); + activeWebview.updateViewScrollTop(-scrollTop, true, [{ output: output.source, cellTop, outputOffset }]); + } + }); + } + + getCellByInfo(cellInfo: IDiffCellInfo): IGenericCellViewModel { + return cellInfo.diffElement.getCellByUri(cellInfo.cellUri); + } + + removeInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: DiffNestedCellViewModel, displayOutput: IDisplayOutputViewModel, 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) { + return; + } + + if (!activeWebview.insetMapping.has(displayOutput)) { + return; + } + + activeWebview.removeInset(displayOutput); + }); + } + + showInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: DiffNestedCellViewModel, displayOutput: IDisplayOutputViewModel, 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) { + return; + } + + if (!activeWebview.insetMapping.has(displayOutput)) { + return; + } + + const cellTop = this._list.getAbsoluteTopOfElement(cellDiffViewModel); + const scrollTop = this._list.scrollTop; + const outputIndex = cellViewModel.outputsViewModels.indexOf(displayOutput); + const outputOffset = cellTop + cellDiffViewModel.getOutputOffsetInCell(diffSide, outputIndex); + activeWebview.updateViewScrollTop(-scrollTop, true, [{ output: displayOutput, cellTop, outputOffset }]); + }); + } + + hideInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: DiffNestedCellViewModel, output: IDisplayOutputViewModel) { + this._modifiedWebview?.hideInset(output); + this._originalWebview?.hideInset(output); + } + + // private async _resolveWebview(rightEditor: boolean): Promise { + // if (rightEditor) { + + // } + // } + getDomNode() { return this._rootElement; } @@ -380,6 +665,18 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD this._list?.splice(0, this._list?.length || 0); } + getOutputRenderer(): OutputRenderer { + return this._outputRenderer; + } + + deltaCellOutputContainerClassNames(diffSide: DiffSide, cellId: string, added: string[], removed: string[]) { + if (diffSide === DiffSide.Original) { + this._originalWebview?.deltaCellOutputContainerClassNames(cellId, added, removed); + } else { + this._modifiedWebview?.deltaCellOutputContainerClassNames(cellId, added, removed); + } + } + getLayoutInfo(): NotebookLayoutInfo { if (!this._list) { throw new Error('Editor is not initalized successfully'); @@ -392,6 +689,56 @@ 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): void { this._rootElement.classList.toggle('mid-width', dimension.width < 1000 && dimension.width >= 600); this._rootElement.classList.toggle('narrow-width', dimension.width < 600); @@ -399,14 +746,39 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD this._rootElement.style.height = `${dimension.height}px`; this._list?.layout(this._dimension.height, this._dimension.width); - this._eventDispatcher?.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]); + + + if (this._modifiedWebview) { + this._modifiedWebview.element.style.width = `calc(50% - 16px)`; + this._modifiedWebview.element.style.left = `calc(50%)`; + } + + if (this._originalWebview) { + this._originalWebview.element.style.width = `calc(50% - 16px)`; + this._originalWebview.element.style.left = `16px`; + } + + if (this._webviewTransparentCover) { + this._webviewTransparentCover.style.height = `${dimension.height}px`; + this._webviewTransparentCover.style.width = `${dimension.width}px`; + } + + this._eventDispatcher?.emit([new NotebookDiffLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]); + } + + dispose() { + this._isDisposed = true; + super.dispose(); } } registerThemingParticipant((theme, collector) => { const cellBorderColor = theme.getColor(notebookCellBorder); if (cellBorderColor) { - collector.addRule(`.notebook-text-diff-editor .cell-body { border: 1px solid ${cellBorderColor};}`); + collector.addRule(`.notebook-text-diff-editor .cell-body .border-container .top-border { border-top: 1px solid ${cellBorderColor};}`); + collector.addRule(`.notebook-text-diff-editor .cell-body .border-container .bottom-border { border-top: 1px solid ${cellBorderColor};}`); + collector.addRule(`.notebook-text-diff-editor .cell-body .border-container .left-border { border-left: 1px solid ${cellBorderColor};}`); + collector.addRule(`.notebook-text-diff-editor .cell-body .border-container .right-border { border-right: 1px solid ${cellBorderColor};}`); collector.addRule(`.notebook-text-diff-editor .cell-diff-editor-container .output-header-container, .notebook-text-diff-editor .cell-diff-editor-container .metadata-header-container { border-top: 1px solid ${cellBorderColor}; @@ -429,6 +801,13 @@ registerThemingParticipant((theme, collector) => { const added = theme.getColor(diffInserted); if (added) { + collector.addRule( + ` + .monaco-workbench .notebook-text-diff-editor .cell-body.full .output-info-container.modified .output-view-container .output-view-container-right div.foreground { background-color: ${added}; } + .monaco-workbench .notebook-text-diff-editor .cell-body.right .output-info-container .output-view-container div.foreground { background-color: ${added}; } + .monaco-workbench .notebook-text-diff-editor .cell-body.right .output-info-container .output-view-container div.output-empty-view { background-color: ${added}; } + ` + ); collector.addRule(` .notebook-text-diff-editor .cell-body .cell-diff-editor-container.inserted .source-container { background-color: ${added}; } .notebook-text-diff-editor .cell-body .cell-diff-editor-container.inserted .source-container .monaco-editor .margin, @@ -460,7 +839,15 @@ registerThemingParticipant((theme, collector) => { ); } const removed = theme.getColor(diffRemoved); - if (added) { + if (removed) { + collector.addRule( + ` + .monaco-workbench .notebook-text-diff-editor .cell-body.full .output-info-container.modified .output-view-container .output-view-container-left div.foreground { background-color: ${removed}; } + .monaco-workbench .notebook-text-diff-editor .cell-body.left .output-info-container .output-view-container div.foreground { background-color: ${removed}; } + .monaco-workbench .notebook-text-diff-editor .cell-body.left .output-info-container .output-view-container div.output-empty-view { background-color: ${removed}; } + + ` + ); collector.addRule(` .notebook-text-diff-editor .cell-body .cell-diff-editor-container.removed .source-container { background-color: ${removed}; } .notebook-text-diff-editor .cell-body .cell-diff-editor-container.removed .source-container .monaco-editor .margin, diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts index 6e664d46a..ce2bc9c6e 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts @@ -14,12 +14,20 @@ 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 { CellDiffViewModel } from 'vs/workbench/contrib/notebook/browser/diff/celllDiffViewModel'; -import { CellDiffRenderTemplate, INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/common'; +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 { isMacintosh } from 'vs/base/common/platform'; -import { DeletedCell, InsertCell, ModifiedCell } from 'vs/workbench/contrib/notebook/browser/diff/cellComponents'; +import { DeletedElement, fixedDiffEditorOptions, fixedEditorOptions, getOptimizedNestedCodeEditorWidgetOptions, InsertElement, ModifiedElement } from 'vs/workbench/contrib/notebook/browser/diff/diffComponents'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; +import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; +import { IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellActionView'; +import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; -export class NotebookCellTextDiffListDelegate implements IListVirtualDelegate { +export class NotebookCellTextDiffListDelegate implements IListVirtualDelegate { // private readonly lineHeight: number; constructor( @@ -29,20 +37,28 @@ export class NotebookCellTextDiffListDelegate implements IListVirtualDelegate { - static readonly TEMPLATE_ID = 'cell_diff'; +export class CellDiffSingleSideRenderer implements IListRenderer { + static readonly TEMPLATE_ID = 'cell_diff_single'; constructor( readonly notebookEditor: INotebookTextDiffEditor, @@ -50,56 +66,228 @@ export class CellDiffRenderer implements IListRenderer implements IDisposable, IStyleController { +export class CellDiffSideBySideRenderer implements IListRenderer { + static readonly TEMPLATE_ID = 'cell_diff_side_by_side'; + + constructor( + readonly notebookEditor: INotebookTextDiffEditor, + @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IContextMenuService protected readonly contextMenuService: IContextMenuService, + @IKeybindingService protected readonly keybindingService: IKeybindingService, + @IMenuService protected readonly menuService: IMenuService, + @IContextKeyService protected readonly contextKeyService: IContextKeyService, + @INotificationService protected readonly notificationService: INotificationService, + ) { } + + get templateId() { + return CellDiffSideBySideRenderer.TEMPLATE_ID; + } + + renderTemplate(container: HTMLElement): CellDiffSideBySideRenderTemplate { + const body = DOM.$('.cell-body'); + DOM.append(container, body); + const diffEditorContainer = DOM.$('.cell-diff-editor-container'); + DOM.append(body, diffEditorContainer); + + const sourceContainer = DOM.append(diffEditorContainer, DOM.$('.source-container')); + const { editor, editorContainer } = this._buildSourceEditor(sourceContainer); + + const inputToolbarContainer = DOM.append(sourceContainer, DOM.$('.editor-input-toolbar-container')); + const cellToolbarContainer = DOM.append(inputToolbarContainer, DOM.$('div.property-toolbar')); + const toolbar = new ToolBar(cellToolbarContainer, this.contextMenuService, { + actionViewItemProvider: action => { + if (action instanceof MenuItemAction) { + const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService); + return item; + } + + return undefined; + } + }); + + const metadataHeaderContainer = DOM.append(diffEditorContainer, DOM.$('.metadata-header-container')); + const metadataInfoContainer = DOM.append(diffEditorContainer, DOM.$('.metadata-info-container')); + + const outputHeaderContainer = DOM.append(diffEditorContainer, DOM.$('.output-header-container')); + const outputInfoContainer = DOM.append(diffEditorContainer, DOM.$('.output-info-container')); + + const borderContainer = DOM.append(body, DOM.$('.border-container')); + const leftBorder = DOM.append(borderContainer, DOM.$('.left-border')); + const rightBorder = DOM.append(borderContainer, DOM.$('.right-border')); + const topBorder = DOM.append(borderContainer, DOM.$('.top-border')); + const bottomBorder = DOM.append(borderContainer, DOM.$('.bottom-border')); + + + return { + body, + container, + diffEditorContainer, + sourceEditor: editor, + editorContainer, + inputToolbarContainer, + toolbar, + metadataHeaderContainer, + metadataInfoContainer, + outputHeaderContainer, + outputInfoContainer, + leftBorder, + rightBorder, + topBorder, + bottomBorder, + elementDisposables: new DisposableStore() + }; + } + + private _buildSourceEditor(sourceContainer: HTMLElement) { + const editorContainer = DOM.append(sourceContainer, DOM.$('.editor-container')); + + const editor = this.instantiationService.createInstance(DiffEditorWidget, editorContainer, { + ...fixedDiffEditorOptions, + overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode(), + originalEditable: false, + ignoreTrimWhitespace: false, + automaticLayout: false, + dimension: { + height: 0, + width: 0 + } + }, { + originalEditor: getOptimizedNestedCodeEditorWidgetOptions(), + modifiedEditor: getOptimizedNestedCodeEditorWidgetOptions() + }); + + return { + editor, + editorContainer + }; + } + + renderElement(element: SideBySideDiffElementViewModel, index: number, templateData: CellDiffSideBySideRenderTemplate, height: number | undefined): void { + templateData.body.classList.remove('left', 'right', 'full'); + + switch (element.type) { + case 'unchanged': + templateData.elementDisposables.add(this.instantiationService.createInstance(ModifiedElement, this.notebookEditor, element, templateData)); + return; + case 'modified': + templateData.elementDisposables.add(this.instantiationService.createInstance(ModifiedElement, this.notebookEditor, element, templateData)); + return; + default: + break; + } + } + + disposeTemplate(templateData: CellDiffSideBySideRenderTemplate): void { + templateData.container.innerText = ''; + templateData.sourceEditor.dispose(); + templateData.toolbar?.dispose(); + } + + disposeElement(element: SideBySideDiffElementViewModel, index: number, templateData: CellDiffSideBySideRenderTemplate): void { + templateData.elementDisposables.clear(); + } +} + +export class NotebookTextDiffList extends WorkbenchList implements IDisposable, IStyleController { private styleElement?: HTMLStyleElement; + get rowsContainer(): HTMLElement { + return this.view.containerDomNode; + } + constructor( listUser: string, container: HTMLElement, - delegate: IListVirtualDelegate, - renderers: IListRenderer[], + delegate: IListVirtualDelegate, + renderers: IListRenderer[], contextKeyService: IContextKeyService, - options: IWorkbenchListOptions, + options: IWorkbenchListOptions, @IListService listService: IListService, @IThemeService themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService, @@ -107,6 +295,32 @@ export class NotebookTextDiffList extends WorkbenchList imple super(listUser, container, delegate, renderers, options, contextKeyService, listService, themeService, configurationService, keybindingService); } + getAbsoluteTopOfElement(element: DiffElementViewModelBase): number { + const index = this.indexOf(element); + // if (index === undefined || index < 0 || index >= this.length) { + // this._getViewIndexUpperBound(element); + // throw new ListError(this.listUser, `Invalid index ${index}`); + // } + + return this.view.elementTop(index); + } + + triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) { + this.view.triggerScrollFromMouseWheelEvent(browserEvent); + } + + clear() { + super.splice(0, this.length); + } + + + updateElementHeight2(element: DiffElementViewModelBase, size: number) { + const viewIndex = this.indexOf(element); + const focused = this.getFocus(); + + this.view.updateElementHeight(viewIndex, size, focused.length ? focused[0] : null); + } + style(styles: IListStyles) { const selectorSuffix = this.view.domId; if (!this.styleElement) { diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index c2b2830c4..09cb20b09 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -184,11 +184,15 @@ .monaco-workbench .notebookOverlay .output .multi-mimetype-output { position: absolute; top: 4px; - left: -26px; + left: -30px; width: 16px; height: 16px; cursor: pointer; - padding: 4px; + padding: 6px; +} + +.monaco-workbench .notebookOverlay .output pre { + margin: 4px 0; } .monaco-workbench .notebookOverlay .output .error_message { @@ -248,15 +252,17 @@ } .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-collapsed-part { + position: relative; box-sizing: border-box; } .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-collapsed-part .codicon { - margin-top: 4px; - position: relative; - left: -23px; + position: absolute; + padding: 2px 6px; + left: -30px; + bottom: 0; cursor: pointer; - z-index: 27; /* Over drag handle */ + z-index: 29; /* Over drag handle and bottom cell toolbar */ } .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.collapsed .notebook-folding-indicator, @@ -475,6 +481,10 @@ text-align: center; } +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .output-collapsed .execution-count-label { + bottom: 24px; +} + .monaco-workbench .notebookOverlay .cell .cell-editor-part { position: relative; } @@ -585,7 +595,7 @@ display: flex; align-items: center; justify-content: center; - z-index: 25; /* over the focus outline on the editor, below the title toolbar */ + z-index: 28; /* over the focus outline on the editor, below the title toolbar */ width: 100%; opacity: 0; transition: opacity 0.2s ease-in-out; @@ -598,8 +608,9 @@ .monaco-workbench .notebookOverlay .cell-list-top-cell-toolbar-container:focus-within, .monaco-workbench .notebookOverlay .cell-list-top-cell-toolbar-container:hover, -.monaco-workbench .notebookOverlay.notebook-editor-editable > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container:focus-within, -.monaco-workbench .notebookOverlay.notebook-editor-editable > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container:hover { +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover .cell-bottom-toolbar-container, +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list:focus-within > .monaco-scrollable-element > .monaco-list-rows:not(:hover) > .monaco-list-row.focused .cell-bottom-toolbar-container, +.monaco-workbench .notebookOverlay.notebook-editor-editable > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container:focus-within { opacity: 1; } @@ -863,3 +874,18 @@ .cell-contributed-items.cell-contributed-items-left { margin-left: 4px; } + +.monaco-workbench .notebookOverlay .output-plaintext::-webkit-scrollbar { + width: 10px; /* width of the entire scrollbar */ + height: 10px; +} + +.monaco-workbench .notebookOverlay .output-plaintext::-webkit-scrollbar-thumb { + height: 10px; + width: 10px; +} + +.monaco-workbench .notebookOverlay .output .output-plaintext { + margin: 4px 0; + overflow-x: auto; +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index b3e9c30f7..9289de4d4 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -31,12 +31,12 @@ import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/noteb import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { NotebookService } from 'vs/workbench/contrib/notebook/browser/notebookServiceImpl'; import { CellKind, CellToolbarLocKey, CellUri, DisplayOrderKey, getCellUndoRedoComparisonKey, NotebookDocumentBackupData, NotebookEditorPriority, NotebookTextDiffEditorPreview, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CustomEditorsAssociations, customEditorsAssociationsSettingId } from 'vs/workbench/services/editor/common/editorOpenWith'; import { CustomEditorInfo } from 'vs/workbench/contrib/customEditor/common/customEditor'; -import { INotebookEditor, NotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookEditor, IN_NOTEBOOK_TEXT_DIFF_EDITOR, NotebookEditorOptions, NOTEBOOK_EDITOR_OPEN } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { INotebookEditorModelResolverService, NotebookModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; @@ -47,11 +47,15 @@ import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/comm import { NotebookEditorWorkerServiceImpl } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl'; import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; import { NotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/browser/notebookCellStatusBarServiceImpl'; +import { INotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidgetService'; +import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidgetServiceImpl'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { Event } from 'vs/base/common/event'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { getFormatedMetadataJSON } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; // Editor Contribution @@ -59,7 +63,7 @@ import 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; import 'vs/workbench/contrib/notebook/browser/contrib/find/findController'; import 'vs/workbench/contrib/notebook/browser/contrib/fold/folding'; import 'vs/workbench/contrib/notebook/browser/contrib/format/formatting'; -import 'vs/workbench/contrib/notebook/browser/contrib/toc/tocProvider'; +import 'vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline'; import 'vs/workbench/contrib/notebook/browser/contrib/marker/markerProvider'; import 'vs/workbench/contrib/notebook/browser/contrib/status/editorStatus'; // import 'vs/workbench/contrib/notebook/browser/contrib/scm/scm'; @@ -204,17 +208,24 @@ Registry.as(EditorInputExtensions.EditorInputFactor ); export class NotebookContribution extends Disposable implements IWorkbenchContribution { + private _notebookEditorIsOpen: IContextKey; + private _notebookDiffEditorIsOpen: IContextKey; constructor( + @IContextKeyService private readonly contextKeyService: IContextKeyService, @IEditorService private readonly editorService: IEditorService, @INotebookService private readonly notebookService: INotebookService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IConfigurationService private readonly configurationService: IConfigurationService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IUndoRedoService undoRedoService: IUndoRedoService, ) { super(); + this._notebookEditorIsOpen = NOTEBOOK_EDITOR_OPEN.bindTo(this.contextKeyService); + this._notebookDiffEditorIsOpen = IN_NOTEBOOK_TEXT_DIFF_EDITOR.bindTo(this.contextKeyService); + this._register(undoRedoService.registerUriComparisonKeyComputer(CellUri.scheme, { getComparisonKey: (uri: URI): string => { return getCellUndoRedoComparisonKey(uri); @@ -224,12 +235,28 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri this._register(this.editorService.overrideOpenEditor({ getEditorOverrides: (resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined) => { - const currentEditorForResource = group?.editors.find(editor => isEqual(editor.resource, resource)); + const currentEditorsForResource = group && this.editorService.findEditors(resource, group); + const currentEditorForResource = currentEditorsForResource && currentEditorsForResource.length ? currentEditorsForResource[0] : undefined; const associatedEditors = distinct([ ...this.getUserAssociatedNotebookEditors(resource), ...this.getContributedEditors(resource) - ], editor => editor.id); + ], editor => editor.id).sort((a, b) => { + // if a content provider is exclusive, it has higher order + if (a.exclusive && b.exclusive) { + return 0; + } + + if (a.exclusive) { + return -1; + } + + if (b.exclusive) { + return 1; + } + + return 0; + }); return associatedEditors.map(info => { return { @@ -264,6 +291,19 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri this.notebookService.updateActiveNotebookEditor(null); } })); + + this.editorGroupService.whenRestored.then(() => this._updateContextKeys()); + this._register(this.editorService.onDidActiveEditorChange(() => this._updateContextKeys())); + this._register(this.editorService.onDidVisibleEditorsChange(() => this._updateContextKeys())); + + this._register(this.editorGroupService.onDidAddGroup(() => this._updateContextKeys())); + this._register(this.editorGroupService.onDidRemoveGroup(() => this._updateContextKeys())); + } + + private _updateContextKeys() { + const activeEditorPane = this.editorService.activeEditorPane as { isNotebookEditor?: boolean } | undefined; + this._notebookEditorIsOpen.set(!!activeEditorPane?.isNotebookEditor); + this._notebookDiffEditorIsOpen.set(this.editorService.activeEditorPane?.getId() === 'workbench.editor.notebookTextDiffEditor'); } getUserAssociatedEditors(resource: URI) { @@ -300,8 +340,70 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri return undefined; } - if (originalInput instanceof NotebookEditorInput) { - return undefined; + // Run reopen with ... + if (id) { + // from the editor tab context menu + if (originalInput instanceof NotebookEditorInput) { + if (originalInput.viewType === id) { + // reopen with the same type + return undefined; + } else { + return { + override: (async () => { + const notebookInput = NotebookEditorInput.create(this.instantiationService, originalInput.resource, originalInput.getName(), id); + const originalEditorIndex = group.getIndexOfEditor(originalInput); + + await group.closeEditor(originalInput); + originalInput.dispose(); + const newEditor = await group.openEditor(notebookInput, { ...options, index: originalEditorIndex, override: false }); + if (newEditor) { + return newEditor; + } else { + return undefined; + } + })() + }; + } + } else { + // from the file explorer + const existingEditors = this.editorService.findEditors(originalInput.resource, group).filter(editor => editor instanceof NotebookEditorInput) as NotebookEditorInput[]; + + if (existingEditors.length) { + // there are notebook editors with the same resource + + if (existingEditors.find(editor => editor.viewType === id)) { + return { override: this.editorService.openEditor(existingEditors.find(editor => editor.viewType === id)!, { ...options, override: false }, group) }; + } else { + return { + override: (async () => { + const firstEditor = existingEditors[0]!; + const originalEditorIndex = group.getIndexOfEditor(firstEditor); + + await group.closeEditor(firstEditor); + firstEditor.dispose(); + const notebookInput = NotebookEditorInput.create(this.instantiationService, originalInput.resource!, originalInput.getName(), id); + const newEditor = await group.openEditor(notebookInput, { ...options, index: originalEditorIndex, override: false }); + + if (newEditor) { + return newEditor; + } else { + return undefined; + } + })() + }; + } + } + } + } + + // Click on the editor tab + if (id === undefined && originalInput instanceof NotebookEditorInput) { + const existingEditors = this.editorService.findEditors(originalInput.resource, group).filter(editor => editor instanceof NotebookEditorInput && editor === originalInput); + + if (existingEditors.length) { + // same + return undefined; + } } if (originalInput instanceof NotebookDiffEditorInput) { @@ -323,10 +425,8 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri } if (id === undefined) { - const existingEditors = group.editors.filter(editor => - editor.resource - && isEqual(editor.resource, notebookUri) - && !(editor instanceof NotebookEditorInput) + const existingEditors = this.editorService.findEditors(notebookUri, group).filter(editor => + !(editor instanceof NotebookEditorInput) && !(editor instanceof NotebookDiffEditorInput) ); @@ -391,7 +491,7 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri return undefined; } - const existingEditors = group.editors.filter(editor => editor.resource && isEqual(editor.resource, notebookUri) && !(editor instanceof NotebookEditorInput)); + const existingEditors = this.editorService.findEditors(notebookUri, group).filter(editor => !(editor instanceof NotebookEditorInput)); if (existingEditors.length) { return undefined; @@ -462,7 +562,7 @@ class CellContentProvider implements ITextModelContentProvider { create: (defaultEOL) => { const newEOL = (defaultEOL === DefaultEndOfLine.CRLF ? '\r\n' : '\n'); (cell.textBuffer as ITextBuffer).setEOL(newEOL); - return cell.textBuffer as ITextBuffer; + return { textBuffer: cell.textBuffer as ITextBuffer, disposable: Disposable.None }; }, getFirstLineText: (limit: number) => { return cell.textBuffer.getLineContent(1).substr(0, limit); @@ -489,6 +589,61 @@ class CellContentProvider implements ITextModelContentProvider { } } +class CellMetadataContentProvider implements ITextModelContentProvider { + private readonly _registration: IDisposable; + + constructor( + @ITextModelService textModelService: ITextModelService, + @IModelService private readonly _modelService: IModelService, + @IModeService private readonly _modeService: IModeService, + @INotebookEditorModelResolverService private readonly _notebookModelResolverService: INotebookEditorModelResolverService, + ) { + this._registration = textModelService.registerTextModelContentProvider(Schemas.vscodeNotebookCellMetadata, this); + } + + dispose(): void { + this._registration.dispose(); + } + + async provideTextContent(resource: URI): Promise { + const existing = this._modelService.getModel(resource); + if (existing) { + return existing; + } + const data = CellUri.parseCellMetadataUri(resource); + // const data = parseCellUri(resource); + if (!data) { + return null; + } + + const ref = await this._notebookModelResolverService.resolve(data.notebook); + let result: ITextModel | null = null; + + const mode = this._modeService.create('json'); + + for (const cell of ref.object.notebook.cells) { + if (cell.handle === data.handle) { + const metadataSource = getFormatedMetadataJSON(ref.object.notebook, cell.metadata || {}, cell.language); + result = this._modelService.createModel( + metadataSource, + mode, + resource + ); + break; + } + } + + if (result) { + const once = result.onWillDispose(() => { + once.dispose(); + ref.dispose(); + }); + } + + return result; + } +} + class RegisterSchemasContribution extends Disposable implements IWorkbenchContribution { constructor() { super(); @@ -596,6 +751,7 @@ class NotebookFileTracker implements IWorkbenchContribution { const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(NotebookContribution, LifecyclePhase.Starting); workbenchContributionsRegistry.registerWorkbenchContribution(CellContentProvider, LifecyclePhase.Starting); +workbenchContributionsRegistry.registerWorkbenchContribution(CellMetadataContentProvider, LifecyclePhase.Starting); workbenchContributionsRegistry.registerWorkbenchContribution(RegisterSchemasContribution, LifecyclePhase.Starting); workbenchContributionsRegistry.registerWorkbenchContribution(NotebookFileTracker, LifecyclePhase.Ready); @@ -603,6 +759,7 @@ registerSingleton(INotebookService, NotebookService); registerSingleton(INotebookEditorWorkerService, NotebookEditorWorkerServiceImpl); registerSingleton(INotebookEditorModelResolverService, NotebookModelResolverService, true); registerSingleton(INotebookCellStatusBarService, NotebookCellStatusBarService, true); +registerSingleton(INotebookEditorWidgetService, NotebookEditorWidgetService, true); const configurationRegistry = Registry.as(Extensions.Configuration); configurationRegistry.registerConfiguration({ diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 1633a9f7b..9f624090c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -22,7 +22,7 @@ import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/outpu import { RunStateRenderer, TimerRenderer } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; import { CellViewModel, IModelDecorationsChangeAccessor, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { CellKind, IProcessedOutput, IRenderOutput, NotebookCellMetadata, NotebookDocumentMetadata, IEditor, INotebookKernelInfo2, IInsetRenderOutput, ICellRange } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, IProcessedOutput, NotebookCellMetadata, NotebookDocumentMetadata, IEditor, INotebookKernelInfo2, ICellRange, IOrderedMimeType, ITransformedDisplayOutputDto, INotebookRendererInfo, IErrorOutput, IStreamOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { IMenu } from 'vs/platform/actions/common/actions'; @@ -32,6 +32,7 @@ import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation'; import { CellEditorStatusBar } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; +//#region Context Keys export const KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED = new RawContextKey('notebookFindWidgetFocused', false); // Is Notebook @@ -39,12 +40,16 @@ export const NOTEBOOK_IS_ACTIVE_EDITOR = ContextKeyExpr.equals('activeEditor', ' // Editor keys export const NOTEBOOK_EDITOR_FOCUSED = new RawContextKey('notebookEditorFocused', false); +export const NOTEBOOK_EDITOR_OPEN = new RawContextKey('notebookEditorOpen', false); export const NOTEBOOK_CELL_LIST_FOCUSED = new RawContextKey('notebookCellListFocused', false); export const NOTEBOOK_OUTPUT_FOCUSED = new RawContextKey('notebookOutputFocused', false); export const NOTEBOOK_EDITOR_EDITABLE = new RawContextKey('notebookEditable', true); export const NOTEBOOK_EDITOR_RUNNABLE = new RawContextKey('notebookRunnable', true); export const NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK = new RawContextKey('notebookExecuting', false); +// Diff Editor Keys +export const IN_NOTEBOOK_TEXT_DIFF_EDITOR = new RawContextKey('isInNotebookTextDiffEditor', false); + // Cell keys export const NOTEBOOK_VIEW_TYPE = new RawContextKey('notebookViewType', undefined); export const NOTEBOOK_CELL_TYPE = new RawContextKey('notebookCellType', undefined); // code, markdown @@ -57,14 +62,114 @@ export const NOTEBOOK_CELL_RUN_STATE = new RawContextKey('notebookCellRu export const NOTEBOOK_CELL_HAS_OUTPUTS = new RawContextKey('notebookCellHasOutputs', false); // bool export const NOTEBOOK_CELL_INPUT_COLLAPSED = new RawContextKey('notebookCellInputIsCollapsed', false); // bool export const NOTEBOOK_CELL_OUTPUT_COLLAPSED = new RawContextKey('notebookCellOutputIsCollapsed', false); // bool +// Kernels +export const NOTEBOOK_HAS_MULTIPLE_KERNELS = new RawContextKey('notebookHasMultipleKernels', false); -// Shared commands +//#endregion + +//#region Shared commands export const EXPAND_CELL_CONTENT_COMMAND_ID = 'notebook.cell.expandCellContent'; export const EXECUTE_CELL_COMMAND_ID = 'notebook.cell.execute'; -// Kernels +//#endregion -export const NOTEBOOK_HAS_MULTIPLE_KERNELS = new RawContextKey('notebookHasMultipleKernels', false); +//#region Output related types +export const enum RenderOutputType { + None, + Html, + Extension +} + +export interface IRenderNoOutput { + type: RenderOutputType.None; + hasDynamicHeight: boolean; +} + +export interface IRenderPlainHtmlOutput { + type: RenderOutputType.Html; + source: IDisplayOutputViewModel; + htmlContent: string; + hasDynamicHeight: boolean; +} + +export interface IRenderOutputViaExtension { + type: RenderOutputType.Extension; + source: IDisplayOutputViewModel; + mimeType: string; + renderer: INotebookRendererInfo; +} + +export type IInsetRenderOutput = IRenderPlainHtmlOutput | IRenderOutputViaExtension; +export type IRenderOutput = IRenderNoOutput | IInsetRenderOutput; + +export const outputHasDynamicHeight = (o: IRenderOutput) => o.type !== RenderOutputType.Extension && o.hasDynamicHeight; + + +export interface ICellOutputViewModel { + cellViewModel: IGenericCellViewModel; + model: IProcessedOutput; + isDisplayOutput(): this is IDisplayOutputViewModel; + isErrorOutput(): this is IErrorOutputViewModel; + isStreamOutput(): this is IStreamOutputViewModel; +} + +export interface IDisplayOutputViewModel extends ICellOutputViewModel { + model: ITransformedDisplayOutputDto; + resolveMimeTypes(textModel: NotebookTextModel): [readonly IOrderedMimeType[], number]; + pickedMimeType: number; +} + +export interface IErrorOutputViewModel extends ICellOutputViewModel { + model: IErrorOutput; +} + +export interface IStreamOutputViewModel extends ICellOutputViewModel { + model: IStreamOutput; +} + +//#endregion + +//#region Shared types between the Notebook Editor and Notebook Diff Editor, they are mostly used for output rendering + +export interface IGenericCellViewModel { + id: string; + handle: number; + uri: URI; + metadata: NotebookCellMetadata | undefined; + outputIsHovered: boolean; + outputsViewModels: ICellOutputViewModel[]; + getOutputOffset(index: number): number; + updateOutputHeight(index: number, height: number): void; +} + +export interface IDisplayOutputLayoutUpdateRequest { + output: IDisplayOutputViewModel; + cellTop: number; + outputOffset: number; +} + +export interface ICommonCellInfo { + cellId: string; + cellHandle: number; + cellUri: URI; +} + +export interface INotebookCellOutputLayoutInfo { + width: number; + height: number; + fontInfo: BareFontInfo; +} + +export interface ICommonNotebookEditor { + getCellOutputLayoutInfo(cell: IGenericCellViewModel): INotebookCellOutputLayoutInfo; + triggerScroll(event: IMouseWheelEvent): void; + getCellByInfo(cellInfo: ICommonCellInfo): IGenericCellViewModel; + focusNotebookCell(cell: IGenericCellViewModel, focus: 'editor' | 'container' | 'output'): void; + focusNextNotebookCell(cell: IGenericCellViewModel, focus: 'editor' | 'container' | 'output'): void; + updateOutputHeight(cellInfo: ICommonCellInfo, output: IDisplayOutputViewModel, height: number, isInit: boolean): void; +} + +//#endregion export interface NotebookLayoutInfo { width: number; @@ -122,7 +227,7 @@ export interface MarkdownCellLayoutChangeEvent { totalHeight?: number; } -export interface ICellViewModel { +export interface ICellViewModel extends IGenericCellViewModel { readonly model: NotebookCellTextModel; readonly id: string; readonly textBuffer: IReadonlyTextBuffer; @@ -133,6 +238,7 @@ export interface ICellViewModel { cellKind: CellKind; editState: CellEditState; focusMode: CellFocusMode; + outputIsHovered: boolean; getText(): string; getTextLength(): number; metadata: NotebookCellMetadata | undefined; @@ -212,7 +318,7 @@ export interface IActiveNotebookEditor extends INotebookEditor { uri: URI; } -export interface INotebookEditor extends IEditor { +export interface INotebookEditor extends IEditor, ICommonNotebookEditor { isEmbedded: boolean; cursorNavigationMode: boolean; @@ -323,6 +429,8 @@ export interface INotebookEditor extends IEditor { */ focusNotebookCell(cell: ICellViewModel, focus: 'editor' | 'container' | 'output'): void; + focusNextNotebookCell(cell: ICellViewModel, focus: 'editor' | 'container' | 'output'): void; + /** * Execute the given notebook cell */ @@ -361,12 +469,12 @@ export interface INotebookEditor extends IEditor { /** * Remove the output from the webview layer */ - removeInset(output: IProcessedOutput): void; + removeInset(output: IDisplayOutputViewModel): void; /** * Hide the inset in the webview layer without removing it */ - hideInset(output: IProcessedOutput): void; + hideInset(output: IDisplayOutputViewModel): void; /** * Send message to the webview for outputs. @@ -395,11 +503,21 @@ export interface INotebookEditor extends IEditor { */ triggerScroll(event: IMouseWheelEvent): void; + /** + * The range will be revealed with as little scrolling as possible. + */ + revealCellRangeInView(range: ICellRange): void; + /** * Reveal cell into viewport. */ revealInView(cell: ICellViewModel): void; + /** + * Reveal cell into the top of viewport. + */ + revealInViewAtTop(cell: ICellViewModel): void; + /** * Reveal cell into viewport center. */ @@ -476,6 +594,9 @@ export interface INotebookEditor extends IEditor { * @return The contribution or null if contribution not found. */ getContribution(id: string): T; + + getCellByInfo(cellInfo: ICommonCellInfo): ICellViewModel; + updateOutputHeight(cellInfo: ICommonCellInfo, output: IDisplayOutputViewModel, height: number, isInit: boolean): void; } export interface INotebookCellList { @@ -494,8 +615,8 @@ export interface INotebookCellList { scrollLeft: number; length: number; rowsContainer: HTMLElement; - readonly onDidRemoveOutput: Event; - readonly onDidHideOutput: Event; + readonly onDidRemoveOutput: Event; + readonly onDidHideOutput: Event; readonly onMouseUp: Event>; readonly onMouseDown: Event>; readonly onContextMenu: Event>; @@ -506,7 +627,9 @@ export interface INotebookCellList { focusElement(element: ICellViewModel): void; selectElement(element: ICellViewModel): void; getFocusedElements(): ICellViewModel[]; + revealElementsInView(range: ICellRange): void; revealElementInView(element: ICellViewModel): void; + revealElementInViewAtTop(element: ICellViewModel): void; revealElementInCenterIfOutsideViewport(element: ICellViewModel): void; revealElementInCenter(element: ICellViewModel): void; revealElementInCenterIfOutsideViewportAsync(element: ICellViewModel): Promise; @@ -594,7 +717,7 @@ export interface IOutputTransformContribution { * This call is allowed to have side effects, such as placing output * directly into the container element. */ - render(output: IProcessedOutput, container: HTMLElement, preferredMimeType: string | undefined, notebookUri: URI | undefined): IRenderOutput; + render(output: ICellOutputViewModel, container: HTMLElement, preferredMimeType: string | undefined, notebookUri: URI | undefined): IRenderOutput; } export interface CellFindMatch { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index d0e140358..1025ccaf6 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -20,13 +20,13 @@ import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { EditorOptions, IEditorInput, IEditorMemento, IEditorOpenContext } from 'vs/workbench/common/editor'; import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; -import { IBorrowValue, INotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidgetService'; import { INotebookEditorViewState, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { IEditorDropService } from 'vs/workbench/services/editor/browser/editorDropService'; import { IEditorGroup, IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { NotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { IBorrowValue, INotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidgetService'; const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState'; @@ -64,14 +64,7 @@ export class NotebookEditor extends EditorPane { this._editorMemento = this.getEditorMemento(_editorGroupService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY); } - set viewModel(newModel: NotebookViewModel | undefined) { - if (this._widget.value) { - this._widget.value.viewModel = newModel; - this._onDidChangeModel.fire(); - } - } - - get viewModel() { + get viewModel(): NotebookViewModel | undefined { return this._widget.value?.viewModel; } @@ -134,17 +127,18 @@ export class NotebookEditor extends EditorPane { this._widget.value?.focus(); } + hasFocus(): boolean { + const activeElement = document.activeElement; + const value = this._widget.value; + + return !!value && (DOM.isAncestor(activeElement, value.getDomNode() || DOM.isAncestor(activeElement, value.getOverflowContainerDomNode()))); + } + async setInput(input: NotebookEditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { const group = this.group!; this._saveEditorViewState(this.input); - await super.setInput(input, options, context, token); - - // Check for cancellation - if (token.isCancellationRequested) { - return undefined; - } this._widgetDisposableStore.clear(); @@ -155,11 +149,16 @@ export class NotebookEditor extends EditorPane { } this._widget = this.instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, group, input); + this._widgetDisposableStore.add(this._widget.value!.onDidChangeModel(() => this._onDidChangeModel.fire())); if (this._dimension) { this._widget.value!.layout(this._dimension, this._rootElement); } + // only now `setInput` and yield/await. this is AFTER the actual widget is ready. This is very important + // so that others synchronously receive a notebook editor with the correct widget being set + await super.setInput(input, options, context, token); + const model = await input.resolve(); // Check for cancellation if (token.isCancellationRequested) { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index b58b545fe..90204097c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getZoomLevel } from 'vs/base/browser/browser'; +import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser'; import * as DOM from 'vs/base/browser/dom'; import * as strings from 'vs/base/common/strings'; import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -40,9 +40,8 @@ import { EditorMemento } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorMemento } from 'vs/workbench/common/editor'; import { Memento, MementoObject } from 'vs/workbench/common/memento'; import { PANEL_BORDER } from 'vs/workbench/common/theme'; -import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugToolBar'; -import { BOTTOM_CELL_TOOLBAR_GAP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CELL_MARGIN, CELL_RUN_GUTTER, CELL_TOP_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT, SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellEditState, CellFocusMode, IActiveNotebookEditor, ICellViewModel, INotebookCellList, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorMouseEvent, NotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { BOTTOM_CELL_TOOLBAR_GAP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CELL_MARGIN, CELL_OUTPUT_PADDING, CELL_RUN_GUTTER, CELL_TOP_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT, SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CellEditState, CellFocusMode, IActiveNotebookEditor, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IDisplayOutputViewModel, IGenericCellViewModel, IInsetRenderOutput, INotebookCellList, INotebookCellOutputLayoutInfo, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorMouseEvent, NotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { NotebookKernelProviderAssociation, NotebookKernelProviderAssociations, notebookKernelProviderAssociationsSettingId } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; @@ -55,13 +54,16 @@ import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewMod import { NotebookEventDispatcher, NotebookLayoutChangedEvent } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; import { CellViewModel, IModelDecorationsChangeAccessor, INotebookEditorViewState, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CellKind, CellToolbarLocKey, ICellRange, IInsetRenderOutput, INotebookDecorationRenderOptions, INotebookKernelInfo2, IProcessedOutput, isTransformedDisplayOutput, NotebookCellRunState, NotebookRunState, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, CellToolbarLocKey, ICellRange, INotebookDecorationRenderOptions, INotebookKernelInfo2, NotebookCellRunState, NotebookRunState, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { editorGutterModifiedBackground } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { configureKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { configureKernelIcon, errorStateIcon, successStateIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugColors'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { extname } from 'vs/base/common/resources'; const $ = DOM.$; @@ -73,9 +75,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private _overlayContainer!: HTMLElement; private _body!: HTMLElement; private _overflowContainer!: HTMLElement; - private _webview: BackLayerWebView | null = null; + private _webview: BackLayerWebView | null = null; private _webviewResolved: boolean = false; - private _webviewResolvePromise: Promise | null = null; + private _webviewResolvePromise: Promise | null> | null = null; private _webviewTransparentCover: HTMLElement | null = null; private _list!: INotebookCellList; private _dndController: CellDragAndDropController | null = null; @@ -146,6 +148,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return this._notebookViewModel?.notebookDocument; } + private _activeKernelExecuted: boolean = false; private _activeKernel: INotebookKernelInfo2 | undefined = undefined; private readonly _onDidChangeKernel = this._register(new Emitter()); readonly onDidChangeKernel: Event = this._onDidChangeKernel.event; @@ -153,6 +156,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor readonly onDidChangeAvailableKernels: Event = this._onDidChangeAvailableKernels.event; private _contributedKernelsComputePromise: CancelablePromise | null = null; + private _initialKernelComputationDone: boolean = false; get activeKernel() { return this._activeKernel; @@ -175,9 +179,12 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._activeKernelResolvePromise = undefined; const memento = this._activeKernelMemento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); - memento[this.viewModel.viewType] = this._activeKernel?.id; + memento[this.viewModel.viewType] = this._activeKernel?.friendlyId; this._activeKernelMemento.saveMemento(); this._onDidChangeKernel.fire(); + if (this._activeKernel) { + this._loadKernelPreloads(this._activeKernel.extensionLocation, this._activeKernel); + } } private _activeKernelResolvePromise: Promise | undefined = undefined; @@ -248,7 +255,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor @IContextMenuService private readonly contextMenuService: IContextMenuService, @IMenuService private readonly menuService: IMenuService, @IQuickInputService private readonly quickInputService: IQuickInputService, - @IThemeService private readonly themeService: IThemeService + @IThemeService private readonly themeService: IThemeService, + @ITelemetryService private readonly telemetryService: ITelemetryService ) { super(); this.isEmbedded = creationOptions.isEmbedded || false; @@ -428,7 +436,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private _generateFontInfo(): void { const editorOptions = this.configurationService.getValue('editor'); - this._fontInfo = BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel()); + this._fontInfo = BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel(), getPixelRatio()); } private _createBody(parent: HTMLElement): void { @@ -653,6 +661,24 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor if (this.viewModel === undefined || !this.viewModel.equal(textModel)) { this._detachModel(); await this._attachModel(textModel, viewState); + + type WorkbenchNotebookOpenClassification = { + scheme: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; }; + ext: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; }; + viewType: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; }; + }; + + type WorkbenchNotebookOpenEvent = { + scheme: string; + ext: string; + viewType: string; + }; + + this.telemetryService.publicLog2('notebook/editorOpened', { + scheme: textModel.uri.scheme, + ext: extname(textModel.uri), + viewType: textModel.viewType + }); } else { this.restoreListViewState(viewState); } @@ -730,6 +756,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._webview?.element.remove(); this._webview = null; this._list.clear(); + this._activeKernel = undefined; + this._activeKernelExecuted = false; } async beginComputeContributedKernels() { @@ -742,6 +770,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor }); const result = await this._contributedKernelsComputePromise; + this._initialKernelComputationDone = true; this._contributedKernelsComputePromise = null; return result; @@ -752,6 +781,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return; } + if (this._activeKernel !== undefined && this._activeKernelExecuted) { + // kernel already executed, we should not change it automatically + return; + } + const provider = this.notebookService.getContributedNotebookProvider(this.viewModel.viewType) || this.notebookService.getContributedNotebookProviders(this.viewModel.uri)[0]; const availableKernels = await this.beginComputeContributedKernels(); @@ -771,7 +805,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this.multipleKernelsAvailable = false; } - const activeKernelStillExist = [...availableKernels].find(kernel => kernel.id === this.activeKernel?.id && this.activeKernel?.id !== undefined); + const activeKernelStillExist = [...availableKernels].find(kernel => kernel.friendlyId === this.activeKernel?.friendlyId && this.activeKernel?.friendlyId !== undefined); if (activeKernelStillExist) { // the kernel still exist, we don't want to modify the selection otherwise user's temporary preference is lost @@ -782,6 +816,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return this._setKernelsFromProviders(provider, availableKernels, tokenSource); } + this._initialKernelComputationDone = true; + tokenSource.dispose(); } @@ -797,7 +833,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor const cachedKernelId = memento[provider.id]; this.activeKernel = filteredKernels.find(kernel => kernel.isPreferred) - || filteredKernels.find(kernel => kernel.id === cachedKernelId) + || filteredKernels.find(kernel => kernel.friendlyId === cachedKernelId) || filteredKernels[0]; } else { this.activeKernel = undefined; @@ -818,7 +854,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } } - memento[provider.id] = this._activeKernel?.id; + memento[provider.id] = this._activeKernel?.friendlyId; this._activeKernelMemento.saveMemento(); tokenSource.dispose(); @@ -831,7 +867,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor const cachedKernelId = memento[provider.id]; const preferedKernel = kernelsFromSameExtension.find(kernel => kernel.isPreferred) - || kernelsFromSameExtension.find(kernel => kernel.id === cachedKernelId) + || kernelsFromSameExtension.find(kernel => kernel.friendlyId === cachedKernelId) || kernelsFromSameExtension[0]; this.activeKernel = preferedKernel; if (this.activeKernel) { @@ -848,7 +884,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return; } - memento[provider.id] = this._activeKernel?.id; + memento[provider.id] = this._activeKernel?.friendlyId; this._activeKernelMemento.saveMemento(); tokenSource.dispose(); return; @@ -892,7 +928,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._notebookExecuting?.set(notebookMetadata.runState === NotebookRunState.Running); } - private async _resolveWebview(): Promise { + private async _resolveWebview(): Promise | null> { if (!this.textModel) { return null; } @@ -902,7 +938,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } if (!this._webview) { - this._webview = this.instantiationService.createInstance(BackLayerWebView, this, this.getId(), this.textModel!.uri); + this._webview = this.instantiationService.createInstance(BackLayerWebView, this, this.getId(), this.textModel!.uri, { outputNodePadding: CELL_OUTPUT_PADDING, outputNodeLeftPadding: CELL_OUTPUT_PADDING }); + this._webview.element.style.width = `calc(100% - ${CODE_CELL_LEFT_MARGIN + (CELL_MARGIN * 2) + CELL_RUN_GUTTER}px)`; + this._webview.element.style.margin = `0px 0 0px ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px`; + // attach the webview container to the DOM tree first this._list.rowsContainer.insertAdjacentElement('afterbegin', this._webview.element); } @@ -950,7 +989,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } private async _createWebview(id: string, resource: URI): Promise { - this._webview = this.instantiationService.createInstance(BackLayerWebView, this, id, resource); + this._webview = this.instantiationService.createInstance(BackLayerWebView, this, id, resource, { outputNodePadding: CELL_OUTPUT_PADDING, outputNodeLeftPadding: CELL_OUTPUT_PADDING }); + this._webview.element.style.width = `calc(100% - ${CODE_CELL_LEFT_MARGIN + (CELL_MARGIN * 2) + CELL_RUN_GUTTER}px)`; + this._webview.element.style.margin = `0px 0 0px ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px`; // attach the webview container to the DOM tree first this._list.rowsContainer.insertAdjacentElement('afterbegin', this._webview.element); } @@ -1016,27 +1057,36 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._webview!.element.style.height = `${scrollHeight}px`; if (this._webview?.insetMapping) { - const updateItems: { cell: CodeCellViewModel, output: IProcessedOutput, cellTop: number }[] = []; - const removedItems: IProcessedOutput[] = []; + const updateItems: IDisplayOutputLayoutUpdateRequest[] = []; + const removedItems: IDisplayOutputViewModel[] = []; this._webview?.insetMapping.forEach((value, key) => { - const cell = value.cell; + const cell = this.viewModel?.getCellByHandle(value.cellInfo.cellHandle); + if (!cell || !(cell instanceof CodeCellViewModel)) { + return; + } + + this.viewModel?.viewCells.find(cell => cell.handle === value.cellInfo.cellHandle); const viewIndex = this._list.getViewIndex(cell); if (viewIndex === undefined) { return; } - if (cell.outputs.indexOf(key) < 0) { + if (cell.outputsViewModels.indexOf(key) < 0) { // output is already gone removedItems.push(key); } const cellTop = this._list.getAbsoluteTopOfElement(cell); if (this._webview!.shouldUpdateInset(cell, key, cellTop)) { + const outputIndex = cell.outputsViewModels.indexOf(key); + + const outputOffset = cellTop + cell.getOutputOffset(outputIndex); + updateItems.push({ - cell: cell, output: key, - cellTop: cellTop + cellTop: cellTop, + outputOffset }); } }); @@ -1212,10 +1262,18 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor // this.viewModel!.selectionHandles = [cell.handle]; } + revealCellRangeInView(range: ICellRange) { + return this._list.revealElementsInView(range); + } + revealInView(cell: ICellViewModel) { this._list.revealElementInView(cell); } + revealInViewAtTop(cell: ICellViewModel) { + this._list.revealElementInViewAtTop(cell); + } + revealInCenterIfOutsideViewport(cell: ICellViewModel) { this._list.revealElementInCenterIfOutsideViewport(cell); } @@ -1421,6 +1479,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor language = this.viewModel.resolvedLanguages[0] || 'plaintext'; } } + + if (this.viewModel.resolvedLanguages.indexOf(language) < 0) { + // the language no longer exists + language = this.viewModel.resolvedLanguages[0] || 'plaintext'; + } } else { language = 'markdown'; } @@ -1627,26 +1690,37 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private async _ensureActiveKernel() { if (this._activeKernel) { - if (this._activeKernelResolvePromise) { - await this._activeKernelResolvePromise; - } - return; } + if (this._activeKernelResolvePromise) { + await this._activeKernelResolvePromise; + + if (this._activeKernel) { + return; + } + } + + + if (!this._initialKernelComputationDone) { + await this._setKernels(new CancellationTokenSource()); + + if (this._activeKernel) { + return; + } + } + // pick active kernel const picker = this.quickInputService.createQuickPick<(IQuickPickItem & { run(): void; kernelProviderId?: string })>(); picker.placeholder = nls.localize('notebook.runCell.selectKernel', "Select a notebook kernel to run this notebook"); picker.matchOnDetail = true; - picker.busy = true; - picker.show(); const tokenSource = new CancellationTokenSource(); const availableKernels = await this.beginComputeContributedKernels(); const picks: QuickPickInput[] = availableKernels.map((a) => { return { - id: a.id, + id: a.friendlyId, label: a.label, picked: false, description: @@ -1736,6 +1810,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } await this._ensureActiveKernel(); + this._activeKernelExecuted = true; await this._activeKernel?.executeNotebookCell!(this.viewModel.uri, undefined); } @@ -1776,6 +1851,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } await this._ensureActiveKernel(); + this._activeKernelExecuted = true; await this._activeKernel?.executeNotebookCell!(this.viewModel.uri, cell.handle); } @@ -1818,6 +1894,20 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } } + focusNextNotebookCell(cell: ICellViewModel, focusItem: 'editor' | 'container' | 'output') { + const idx = this.viewModel?.getCellIndex(cell); + if (typeof idx !== 'number') { + return; + } + + const newCell = this.viewModel?.viewCells[idx + 1]; + if (!newCell) { + return; + } + + this.focusNotebookCell(newCell, focusItem); + } + //#endregion //#region MISC @@ -1842,12 +1932,24 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor }; } + getCellOutputLayoutInfo(cell: IGenericCellViewModel): INotebookCellOutputLayoutInfo { + if (!this._list) { + throw new Error('Editor is not initalized successfully'); + } + + return { + width: this._dimension!.width, + height: this._dimension!.height, + fontInfo: this._fontInfo! + }; + } + triggerScroll(event: IMouseWheelEvent) { this._list.triggerScrollFromMouseWheelEvent(event); } async createInset(cell: CodeCellViewModel, output: IInsetRenderOutput, offset: number): Promise { - this._insetModifyQueueByOutputId.queue(output.source.outputId, async () => { + this._insetModifyQueueByOutputId.queue(output.source.model.outputId, async () => { if (!this._webview) { return; } @@ -1856,22 +1958,24 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor if (!this._webview!.insetMapping.has(output.source)) { const cellTop = this._list.getAbsoluteTopOfElement(cell); - await this._webview!.createInset(cell, output, cellTop, offset); + await this._webview!.createInset({ cellId: cell.id, cellHandle: cell.handle, cellUri: cell.uri }, output, cellTop, offset); } else { const cellTop = this._list.getAbsoluteTopOfElement(cell); const scrollTop = this._list.scrollTop; + const outputIndex = cell.outputsViewModels.indexOf(output.source); + const outputOffset = cellTop + cell.getOutputOffset(outputIndex); - this._webview!.updateViewScrollTop(-scrollTop, true, [{ cell, output: output.source, cellTop }]); + this._webview!.updateViewScrollTop(-scrollTop, true, [{ output: output.source, cellTop, outputOffset }]); } }); } - removeInset(output: IProcessedOutput) { - if (!isTransformedDisplayOutput(output)) { + removeInset(output: ICellOutputViewModel) { + if (!output.isDisplayOutput()) { return; } - this._insetModifyQueueByOutputId.queue(output.outputId, async () => { + this._insetModifyQueueByOutputId.queue(output.model.outputId, async () => { if (!this._webview || !this._webviewResolved) { return; } @@ -1879,16 +1983,16 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor }); } - hideInset(output: IProcessedOutput) { + hideInset(output: ICellOutputViewModel) { if (!this._webview || !this._webviewResolved) { return; } - if (!isTransformedDisplayOutput(output)) { + if (!output.isDisplayOutput()) { return; } - this._insetModifyQueueByOutputId.queue(output.outputId, async () => { + this._insetModifyQueueByOutputId.queue(output.model.outputId, async () => { this._webview!.hideInset(output); }); } @@ -1921,6 +2025,20 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._overlayContainer.classList.remove(className); } + getCellByInfo(cellInfo: ICommonCellInfo): ICellViewModel { + const { cellHandle } = cellInfo; + return this.viewModel?.viewCells.find(vc => vc.handle === cellHandle) as CodeCellViewModel; + } + + updateOutputHeight(cellInfo: ICommonCellInfo, output: IDisplayOutputViewModel, outputHeight: number, isInit: boolean): void { + const cell = this.viewModel?.viewCells.find(vc => vc.handle === cellInfo.cellHandle); + if (cell && cell instanceof CodeCellViewModel) { + const outputIndex = cell.outputsViewModels.indexOf(output); + cell.updateOutputHeight(outputIndex, outputHeight); + this.layoutNotebookCell(cell, cell.layoutInfo.totalHeight); + } + } + //#endregion @@ -2022,7 +2140,7 @@ export const selectedCellBorder = registerColor('notebook.selectedCellBorder', { dark: notebookCellBorder, light: notebookCellBorder, hc: contrastBorder -}, nls.localize('notebook.selectedCellBorder', "The color of the cell's top and bottom border when the cell is selected but not focusd.")); +}, nls.localize('notebook.selectedCellBorder', "The color of the cell's top and bottom border when the cell is selected but not focused.")); export const focusedCellBorder = registerColor('notebook.focusedCellBorder', { dark: focusBorder, @@ -2030,6 +2148,12 @@ export const focusedCellBorder = registerColor('notebook.focusedCellBorder', { hc: focusBorder }, nls.localize('notebook.focusedCellBorder', "The color of the cell's top and bottom border when the cell is focused.")); +export const inactiveFocusedCellBorder = registerColor('notebook.inactiveFocusedCellBorder', { + dark: notebookCellBorder, + light: notebookCellBorder, + hc: 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)), dark: new Color(new RGBA(255, 255, 255, 0.15)), @@ -2042,7 +2166,6 @@ export const cellInsertionIndicator = registerColor('notebook.cellInsertionIndic hc: focusBorder }, nls.localize('notebook.cellInsertionIndicator', "The color of the notebook cell insertion indicator.")); - export const listScrollbarSliderBackground = registerColor('notebookScrollbarSlider.background', { dark: scrollbarSliderBackground, light: scrollbarSliderBackground, @@ -2161,6 +2284,15 @@ registerThemingParticipant((theme, collector) => { border-color: ${focusedCellBorderColor} !important; }`); + const inactiveFocusedBorderColor = theme.getColor(inactiveFocusedCellBorder); + collector.addRule(` + .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-top:before, + .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-bottom:before, + .monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused .cell-inner-container:not(.cell-editor-focus):before, + .monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused .cell-inner-container:not(.cell-editor-focus):after { + border-color: ${inactiveFocusedBorderColor} !important; + }`); + const selectedCellBorderColor = theme.getColor(selectedCellBorder); collector.addRule(` .monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused .cell-editor-focus .cell-focus-indicator-top:before, @@ -2191,12 +2323,12 @@ registerThemingParticipant((theme, collector) => { const cellStatusSuccessIcon = theme.getColor(cellStatusIconSuccess); if (cellStatusSuccessIcon) { - collector.addRule(`.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-run-status .codicon-notebook-state-success { color: ${cellStatusSuccessIcon} }`); + collector.addRule(`.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-run-status ${ThemeIcon.asCSSSelector(successStateIcon)} { color: ${cellStatusSuccessIcon} }`); } const cellStatusErrorIcon = theme.getColor(cellStatusIconError); if (cellStatusErrorIcon) { - collector.addRule(`.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-run-status .codicon-notebook-state-error { color: ${cellStatusErrorIcon} }`); + collector.addRule(`.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-run-status ${ThemeIcon.asCSSSelector(errorStateIcon)} { color: ${cellStatusErrorIcon} }`); } const cellStatusRunningIcon = theme.getColor(cellStatusIconRunning); @@ -2219,12 +2351,14 @@ registerThemingParticipant((theme, collector) => { if (scrollbarSliderBackgroundColor) { collector.addRule(` .notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .scrollbar > .slider { background: ${editorBackgroundColor}; } `); collector.addRule(` .notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .scrollbar > .slider:before { content: ""; width: 100%; height: 100%; position: absolute; background: ${scrollbarSliderBackgroundColor}; } `); /* hack to not have cells see through scroller */ + // collector.addRule(` .monaco-workbench .notebookOverlay .output-plaintext::-webkit-scrollbar-track { background: ${scrollbarSliderBackgroundColor}; } `); } const scrollbarSliderHoverBackgroundColor = theme.getColor(listScrollbarSliderHoverBackground); if (scrollbarSliderHoverBackgroundColor) { collector.addRule(` .notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .scrollbar > .slider:hover { background: ${editorBackgroundColor}; } `); collector.addRule(` .notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .scrollbar > .slider:hover:before { content: ""; width: 100%; height: 100%; position: absolute; background: ${scrollbarSliderHoverBackgroundColor}; } `); /* hack to not have cells see through scroller */ + collector.addRule(` .monaco-workbench .notebookOverlay .output-plaintext::-webkit-scrollbar-thumb { background: ${scrollbarSliderHoverBackgroundColor}; } `); } const scrollbarSliderActiveBackgroundColor = theme.getColor(listScrollbarSliderActiveBackground); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidgetService.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidgetService.ts index f9650ad09..ce817ad43 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidgetService.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidgetService.ts @@ -3,14 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ResourceMap } from 'vs/base/common/map'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { IEditorGroupsService, IEditorGroup, GroupChangeKind, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IInstantiationService, createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +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/browser/notebookEditorInput'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export const INotebookEditorWidgetService = createDecorator('INotebookEditorWidgetService'); @@ -20,139 +16,6 @@ export interface IBorrowValue { export interface INotebookEditorWidgetService { _serviceBrand: undefined; + widgets: NotebookEditorWidget[]; retrieveWidget(accessor: ServicesAccessor, group: IEditorGroup, input: NotebookEditorInput): IBorrowValue; } - -class NotebookEditorWidgetService implements INotebookEditorWidgetService { - - readonly _serviceBrand: undefined; - - private _tokenPool = 1; - - private readonly _notebookWidgets = new Map>(); - private readonly _disposables = new DisposableStore(); - - constructor( - @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IEditorService editorService: IEditorService, - ) { - - const groupListener = new Map(); - const onNewGroup = (group: IEditorGroup) => { - const { id } = group; - const listener = group.onDidGroupChange(e => { - const widgets = this._notebookWidgets.get(group.id); - if (!widgets || e.kind !== GroupChangeKind.EDITOR_CLOSE || !(e.editor instanceof NotebookEditorInput)) { - return; - } - const value = widgets.get(e.editor.resource); - if (!value) { - return; - } - value.token = undefined; - this._disposeWidget(value.widget); - widgets.delete(e.editor.resource); - }); - groupListener.set(id, listener); - }; - this._disposables.add(editorGroupService.onDidAddGroup(onNewGroup)); - editorGroupService.groups.forEach(onNewGroup); - - // group removed -> clean up listeners, clean up widgets - this._disposables.add(editorGroupService.onDidRemoveGroup(group => { - const listener = groupListener.get(group.id); - if (listener) { - listener.dispose(); - groupListener.delete(group.id); - } - const widgets = this._notebookWidgets.get(group.id); - this._notebookWidgets.delete(group.id); - if (widgets) { - for (const value of widgets.values()) { - value.token = undefined; - this._disposeWidget(value.widget); - } - } - })); - - // HACK - // we use the open override to spy on tab movements because that's the only - // way to do that... - this._disposables.add(editorService.overrideOpenEditor({ - open: (input, _options, group, context) => { - if (input instanceof NotebookEditorInput && context === OpenEditorContext.MOVE_EDITOR) { - // when moving a notebook editor we release it from its current tab and we - // "place" it into its future slot so that the editor can pick it up from there - this._freeWidget(input, editorGroupService.activeGroup, group); - } - return undefined; - } - })); - } - - private _disposeWidget(widget: NotebookEditorWidget): void { - widget.onWillHide(); - const domNode = widget.getDomNode(); - widget.dispose(); - domNode.remove(); - } - - private _freeWidget(input: NotebookEditorInput, source: IEditorGroup, target: IEditorGroup): void { - const targetWidget = this._notebookWidgets.get(target.id)?.get(input.resource); - if (targetWidget) { - // not needed - return; - } - - const widget = this._notebookWidgets.get(source.id)?.get(input.resource); - if (!widget) { - throw new Error('no widget at source group'); - } - this._notebookWidgets.get(source.id)?.delete(input.resource); - widget.token = undefined; - - let targetMap = this._notebookWidgets.get(target.id); - if (!targetMap) { - targetMap = new ResourceMap(); - this._notebookWidgets.set(target.id, targetMap); - } - targetMap.set(input.resource, widget); - } - - retrieveWidget(accessor: ServicesAccessor, group: IEditorGroup, input: NotebookEditorInput): IBorrowValue { - - let value = this._notebookWidgets.get(group.id)?.get(input.resource); - - if (!value) { - // NEW widget - const instantiationService = accessor.get(IInstantiationService); - const widget = instantiationService.createInstance(NotebookEditorWidget, { isEmbedded: false }); - const token = this._tokenPool++; - value = { widget, token }; - - let map = this._notebookWidgets.get(group.id); - if (!map) { - map = new ResourceMap(); - this._notebookWidgets.set(group.id, map); - } - map.set(input.resource, value); - - } else { - // reuse a widget which was either free'ed before or which - // is simply being reused... - value.token = this._tokenPool++; - } - - return this._createBorrowValue(value.token!, value); - } - - private _createBorrowValue(myToken: number, widget: { widget: NotebookEditorWidget, token: number | undefined }): IBorrowValue { - return { - get value() { - return widget.token === myToken ? widget.widget : undefined; - } - }; - } -} - -registerSingleton(INotebookEditorWidgetService, NotebookEditorWidgetService, true); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidgetServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidgetServiceImpl.ts new file mode 100644 index 000000000..f365cdfdf --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidgetServiceImpl.ts @@ -0,0 +1,149 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ResourceMap } from 'vs/base/common/map'; +import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { IEditorGroupsService, IEditorGroup, GroupChangeKind, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IBorrowValue, INotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidgetService'; + +export class NotebookEditorWidgetService implements INotebookEditorWidgetService { + + readonly _serviceBrand: undefined; + + private _tokenPool = 1; + + private readonly _notebookWidgets = new Map>(); + private readonly _disposables = new DisposableStore(); + + get widgets() { + return [...this._notebookWidgets.values()].map(val => [...val.values()].map(widget => widget.widget)).reduce((prev, curr) => { return [...prev, ...curr]; }, []); + } + + constructor( + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IEditorService editorService: IEditorService, + ) { + + const groupListener = new Map(); + const onNewGroup = (group: IEditorGroup) => { + const { id } = group; + const listener = group.onDidGroupChange(e => { + const widgets = this._notebookWidgets.get(group.id); + if (!widgets || e.kind !== GroupChangeKind.EDITOR_CLOSE || !(e.editor instanceof NotebookEditorInput)) { + return; + } + const value = widgets.get(e.editor.resource); + if (!value) { + return; + } + value.token = undefined; + this._disposeWidget(value.widget); + widgets.delete(e.editor.resource); + }); + groupListener.set(id, listener); + }; + this._disposables.add(editorGroupService.onDidAddGroup(onNewGroup)); + editorGroupService.groups.forEach(onNewGroup); + + // group removed -> clean up listeners, clean up widgets + this._disposables.add(editorGroupService.onDidRemoveGroup(group => { + const listener = groupListener.get(group.id); + if (listener) { + listener.dispose(); + groupListener.delete(group.id); + } + const widgets = this._notebookWidgets.get(group.id); + this._notebookWidgets.delete(group.id); + if (widgets) { + for (const value of widgets.values()) { + value.token = undefined; + this._disposeWidget(value.widget); + } + } + })); + + // HACK + // we use the open override to spy on tab movements because that's the only + // way to do that... + this._disposables.add(editorService.overrideOpenEditor({ + open: (input, _options, group, context) => { + if (input instanceof NotebookEditorInput && context === OpenEditorContext.MOVE_EDITOR) { + // when moving a notebook editor we release it from its current tab and we + // "place" it into its future slot so that the editor can pick it up from there + this._freeWidget(input, editorGroupService.activeGroup, group); + } + return undefined; + } + })); + } + + private _disposeWidget(widget: NotebookEditorWidget): void { + widget.onWillHide(); + const domNode = widget.getDomNode(); + widget.dispose(); + domNode.remove(); + } + + private _freeWidget(input: NotebookEditorInput, source: IEditorGroup, target: IEditorGroup): void { + const targetWidget = this._notebookWidgets.get(target.id)?.get(input.resource); + if (targetWidget) { + // not needed + return; + } + + const widget = this._notebookWidgets.get(source.id)?.get(input.resource); + if (!widget) { + throw new Error('no widget at source group'); + } + this._notebookWidgets.get(source.id)?.delete(input.resource); + widget.token = undefined; + + let targetMap = this._notebookWidgets.get(target.id); + if (!targetMap) { + targetMap = new ResourceMap(); + this._notebookWidgets.set(target.id, targetMap); + } + targetMap.set(input.resource, widget); + } + + retrieveWidget(accessor: ServicesAccessor, group: IEditorGroup, input: NotebookEditorInput): IBorrowValue { + + let value = this._notebookWidgets.get(group.id)?.get(input.resource); + + if (!value) { + // NEW widget + const instantiationService = accessor.get(IInstantiationService); + const widget = instantiationService.createInstance(NotebookEditorWidget, { isEmbedded: false }); + const token = this._tokenPool++; + value = { widget, token }; + + let map = this._notebookWidgets.get(group.id); + if (!map) { + map = new ResourceMap(); + this._notebookWidgets.set(group.id, map); + } + map.set(input.resource, value); + + } else { + // reuse a widget which was either free'ed before or which + // is simply being reused... + value.token = this._tokenPool++; + } + + return this._createBorrowValue(value.token!, value); + } + + private _createBorrowValue(myToken: number, widget: { widget: NotebookEditorWidget, token: number | undefined }): IBorrowValue { + return { + get value() { + return widget.token === myToken ? widget.widget : undefined; + } + }; + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookIcons.ts b/src/vs/workbench/contrib/notebook/browser/notebookIcons.ts index 6780f1fa9..0ff8cae8f 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookIcons.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookIcons.ts @@ -29,4 +29,5 @@ export const collapsedIcon = registerIcon('notebook-collapsed', Codicon.chevronR export const expandedIcon = registerIcon('notebook-expanded', Codicon.chevronDown, localize('expandedIcon', 'Icon to annotated a 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 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.')); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookRegistry.ts b/src/vs/workbench/contrib/notebook/browser/notebookRegistry.ts index abf2220cc..e97746c12 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookRegistry.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookRegistry.ts @@ -5,9 +5,9 @@ import { CellOutputKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { BrandedService, IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation'; -import { INotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { ICommonNotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -export type IOutputTransformCtor = IConstructorSignature1; +export type IOutputTransformCtor = IConstructorSignature1; export interface IOutputTransformDescription { id: string; @@ -20,7 +20,7 @@ export const NotebookRegistry = new class NotebookRegistryImpl { readonly outputTransforms: IOutputTransformDescription[] = []; - registerOutputTransform(id: string, kind: CellOutputKind, ctor: { new(editor: INotebookEditor, ...services: Services): IOutputTransformContribution }): void { + registerOutputTransform(id: string, kind: CellOutputKind, ctor: { new(editor: ICommonNotebookEditor, ...services: Services): IOutputTransformContribution }): void { this.outputTransforms.push({ id: id, kind: kind, ctor: ctor as IOutputTransformCtor }); } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index ec00de561..6209a04e5 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getZoomLevel } from 'vs/base/browser/browser'; +import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser'; import { flatten } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; @@ -65,6 +65,10 @@ export class NotebookKernelProviderInfoStore extends Disposable { return this._notebookKernelProviders.filter(provider => notebookDocumentFilterMatch(provider.selector, viewType, resource)); } + getContributedKernelProviders() { + return [...this._notebookKernelProviders.values()]; + } + private _updateProviderExtensionsInfo() { NotebookKernelProviderAssociationRegistry.extensionIds.length = 0; NotebookKernelProviderAssociationRegistry.extensionDescriptions.length = 0; @@ -237,7 +241,7 @@ class ModelData implements IDisposable { } export class NotebookService extends Disposable implements INotebookService, ICustomEditorViewTypesHandler { declare readonly _serviceBrand: undefined; - private readonly _notebookProviders = new Map(); + private readonly _notebookProviders = new Map(); notebookProviderInfoStore: NotebookProviderInfoStore; notebookRenderersInfoStore: NotebookOutputRendererInfoStore = new NotebookOutputRendererInfoStore(); notebookKernelProviderInfoStore: NotebookKernelProviderInfoStore = new NotebookKernelProviderInfoStore(); @@ -266,12 +270,12 @@ export class NotebookService extends Disposable implements INotebookService, ICu private readonly _onDidChangeKernels = new Emitter(); onDidChangeKernels: Event = this._onDidChangeKernels.event; - private readonly _onDidChangeNotebookActiveKernel = new Emitter<{ uri: URI, providerHandle: number | undefined, kernelId: string | undefined }>(); - onDidChangeNotebookActiveKernel: Event<{ uri: URI, providerHandle: number | undefined, kernelId: string | undefined }> = this._onDidChangeNotebookActiveKernel.event; + private readonly _onDidChangeNotebookActiveKernel = new Emitter<{ uri: URI, providerHandle: number | undefined, kernelFriendlyId: string | undefined; }>(); + onDidChangeNotebookActiveKernel: Event<{ uri: URI, providerHandle: number | undefined, kernelFriendlyId: string | undefined; }> = this._onDidChangeNotebookActiveKernel.event; private cutItems: NotebookCellTextModel[] | undefined; private _lastClipboardIsCopy: boolean = true; - private _displayOrder: { userOrder: string[], defaultOrder: string[] } = Object.create(null); + private _displayOrder: { userOrder: string[], defaultOrder: string[]; } = Object.create(null); private readonly _decorationOptionProviders = new Map(); constructor( @@ -366,7 +370,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu // there is a `::before` or `::after` text decoration whose position is above or below current line // we at least make sure that the editor top padding is at least one line const editorOptions = this.configurationService.getValue('editor'); - updateEditorTopPadding(BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel()).lineHeight + 2); + updateEditorTopPadding(BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel(), getPixelRatio()).lineHeight + 2); decorationTriggeredAdjustment = true; break; } @@ -387,7 +391,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu }; }; - const PRIORITY = 50; + const PRIORITY = 105; this._register(UndoCommand.addImplementation(PRIORITY, () => { const { editor } = getContext(); if (editor?.viewModel) { @@ -623,6 +627,8 @@ export class NotebookService extends Disposable implements INotebookService, ICu } async canResolve(viewType: string): Promise { + await this._extensionService.activateByEvent(`onNotebook:*`); + if (!this._notebookProviders.has(viewType)) { await this._extensionService.whenInstalledExtensionsRegistered(); // this awaits full activation of all matching extensions @@ -691,9 +697,10 @@ export class NotebookService extends Disposable implements INotebookService, ICu const data = await provider.provideKernels(resource, token); result[index] = data.map(dto => { return { + id: dto.id, extension: dto.extension, extensionLocation: URI.revive(dto.extensionLocation), - id: dto.id, + friendlyId: dto.friendlyId, label: dto.label, description: dto.description, detail: dto.detail, @@ -701,13 +708,13 @@ export class NotebookService extends Disposable implements INotebookService, ICu preloads: dto.preloads, providerHandle: dto.providerHandle, resolve: async (uri: URI, editorId: string, token: CancellationToken) => { - return provider.resolveKernel(editorId, uri, dto.id, token); + return provider.resolveKernel(editorId, uri, dto.friendlyId, token); }, executeNotebookCell: async (uri: URI, handle: number | undefined) => { - return provider.executeNotebook(uri, dto.id, handle); + return provider.executeNotebook(uri, dto.friendlyId, handle); }, cancelNotebookCell: (uri: URI, handle: number | undefined): Promise => { - return provider.cancelNotebook(uri, dto.id, handle); + return provider.cancelNotebook(uri, dto.friendlyId, handle); } }; }); @@ -718,6 +725,11 @@ export class NotebookService extends Disposable implements INotebookService, ICu return flatten(result); } + async getContributedNotebookKernelProviders(): Promise { + const kernelProviders = this.notebookKernelProviderInfoStore.getContributedKernelProviders(); + return kernelProviders; + } + getRendererInfo(id: string): INotebookRendererInfo | undefined { return this.notebookRenderersInfoStore.get(id); } @@ -890,7 +902,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu listVisibleNotebookEditors(): INotebookEditor[] { return this._editorService.visibleEditorPanes - .filter(pane => (pane as unknown as { isNotebookEditor?: boolean }).isNotebookEditor) + .filter(pane => (pane as unknown as { isNotebookEditor?: boolean; }).isNotebookEditor) .map(pane => pane.getControl() as INotebookEditor) .filter(editor => !!editor) .filter(editor => this._notebookEditors.has(editor.getId())); @@ -912,7 +924,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu this._onDidChangeNotebookActiveKernel.fire({ uri: editor.uri!, providerHandle: editor.activeKernel?.providerHandle, - kernelId: editor.activeKernel?.id + kernelFriendlyId: editor.activeKernel?.friendlyId }); })); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index d6d8ed7c0..8d0c6a542 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -19,9 +19,9 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; 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 { CellRevealPosition, CellRevealType, CursorAtBoundary, getVisibleCells, ICellViewModel, INotebookCellList, reduceCellRanges, CellEditState, CellFocusMode, BaseCellRenderTemplate, NOTEBOOK_CELL_LIST_FOCUSED, cellRangesEqual } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellRevealPosition, CellRevealType, CursorAtBoundary, getVisibleCells, ICellViewModel, INotebookCellList, reduceCellRanges, CellEditState, CellFocusMode, BaseCellRenderTemplate, NOTEBOOK_CELL_LIST_FOCUSED, cellRangesEqual, ICellOutputViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { diff, IProcessedOutput, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, CellKind, ICellRange, NOTEBOOK_EDITOR_CURSOR_BEGIN_END } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { diff, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, CellKind, ICellRange, NOTEBOOK_EDITOR_CURSOR_BEGIN_END } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { clamp } from 'vs/base/common/numbers'; import { SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants'; @@ -45,10 +45,10 @@ export class NotebookCellList extends WorkbenchList implements ID private _viewModelStore = new DisposableStore(); private styleElement?: HTMLStyleElement; - private readonly _onDidRemoveOutput = new Emitter(); - readonly onDidRemoveOutput: Event = this._onDidRemoveOutput.event; - private readonly _onDidHideOutput = new Emitter(); - readonly onDidHideOutput: Event = this._onDidHideOutput.event; + private readonly _onDidRemoveOutput = new Emitter(); + readonly onDidRemoveOutput: Event = this._onDidRemoveOutput.event; + private readonly _onDidHideOutput = new Emitter(); + readonly onDidHideOutput: Event = this._onDidHideOutput.event; private _viewModel: NotebookViewModel | null = null; private _hiddenRangeIds: string[] = []; @@ -314,15 +314,17 @@ export class NotebookCellList extends WorkbenchList implements ID if (e.synchronous) { viewDiffs.reverse().forEach((diff) => { // remove output in the webview - const hideOutputs: IProcessedOutput[] = []; - const deletedOutputs: IProcessedOutput[] = []; + const hideOutputs: ICellOutputViewModel[] = []; + const deletedOutputs: ICellOutputViewModel[] = []; for (let i = diff.start; i < diff.start + diff.deleteCount; i++) { const cell = this.element(i); - if (this._viewModel!.hasCell(cell.handle)) { - hideOutputs.push(...cell?.model.outputs); - } else { - deletedOutputs.push(...cell?.model.outputs); + if (cell.cellKind === CellKind.Code) { + if (this._viewModel!.hasCell(cell.handle)) { + hideOutputs.push(...cell?.outputsViewModels); + } else { + deletedOutputs.push(...cell?.outputsViewModels); + } } } @@ -338,15 +340,17 @@ export class NotebookCellList extends WorkbenchList implements ID } viewDiffs.reverse().forEach((diff) => { - const hideOutputs: IProcessedOutput[] = []; - const deletedOutputs: IProcessedOutput[] = []; + const hideOutputs: ICellOutputViewModel[] = []; + const deletedOutputs: ICellOutputViewModel[] = []; for (let i = diff.start; i < diff.start + diff.deleteCount; i++) { const cell = this.element(i); - if (this._viewModel!.hasCell(cell.handle)) { - hideOutputs.push(...cell?.model.outputs); - } else { - deletedOutputs.push(...cell?.model.outputs); + if (cell.cellKind === CellKind.Code) { + if (this._viewModel!.hasCell(cell.handle)) { + hideOutputs.push(...cell?.outputsViewModels); + } else { + deletedOutputs.push(...cell?.outputsViewModels); + } } } @@ -460,15 +464,17 @@ export class NotebookCellList extends WorkbenchList implements ID viewDiffs.reverse().forEach((diff) => { // remove output in the webview - const hideOutputs: IProcessedOutput[] = []; - const deletedOutputs: IProcessedOutput[] = []; + const hideOutputs: ICellOutputViewModel[] = []; + const deletedOutputs: ICellOutputViewModel[] = []; for (let i = diff.start; i < diff.start + diff.deleteCount; i++) { const cell = this.element(i); - if (this._viewModel!.hasCell(cell.handle)) { - hideOutputs.push(...cell?.model.outputs); - } else { - deletedOutputs.push(...cell?.model.outputs); + if (cell.cellKind === CellKind.Code) { + if (this._viewModel!.hasCell(cell.handle)) { + hideOutputs.push(...cell?.outputsViewModels); + } else { + deletedOutputs.push(...cell?.outputsViewModels); + } } } @@ -541,6 +547,22 @@ export class NotebookCellList extends WorkbenchList implements ID return viewIndexInfo.index; } + private _getViewIndexUpperBound2(modelIndex: number) { + if (!this.hiddenRangesPrefixSum) { + return modelIndex; + } + + const viewIndexInfo = this.hiddenRangesPrefixSum.getIndexOf(modelIndex); + + if (viewIndexInfo.remainder !== 0) { + if (modelIndex >= this.hiddenRangesPrefixSum.getTotalValue()) { + return modelIndex - (this.hiddenRangesPrefixSum.getTotalValue() - this.hiddenRangesPrefixSum.getCount()); + } + } + + return viewIndexInfo.index; + } + focusElement(cell: ICellViewModel) { const index = this._getViewIndexUpperBound(cell); @@ -581,6 +603,46 @@ export class NotebookCellList extends WorkbenchList implements ID super.setFocus(indexes, browserEvent); } + revealElementsInView(range: ICellRange) { + const startIndex = this._getViewIndexUpperBound2(range.start); + + if (startIndex < 0) { + return; + } + + const endIndex = this._getViewIndexUpperBound2(range.end); + + const scrollTop = this.getViewScrollTop(); + const wrapperBottom = this.getViewScrollBottom(); + const elementTop = this.view.elementTop(startIndex); + if (elementTop >= scrollTop + && elementTop < wrapperBottom) { + // start element is visible + // check end + + const endElementTop = this.view.elementTop(endIndex); + const endElementHeight = this.view.elementHeight(endIndex); + + if (endElementTop >= wrapperBottom) { + return this._revealInternal(startIndex, false, CellRevealPosition.Top); + } + + if (endElementTop < wrapperBottom) { + // end element partially visible + if (endElementTop + endElementHeight - wrapperBottom < elementTop - scrollTop) { + // there is enough space to just scroll up a little bit to make the end element visible + return this.view.setScrollTop(scrollTop + endElementTop + endElementHeight - wrapperBottom); + } else { + // don't even try it + return this._revealInternal(startIndex, false, CellRevealPosition.Top); + } + } + } + + + this._revealInView(startIndex); + } + revealElementInView(cell: ICellViewModel) { const index = this._getViewIndexUpperBound(cell); @@ -589,6 +651,14 @@ export class NotebookCellList extends WorkbenchList implements ID } } + revealElementInViewAtTop(cell: ICellViewModel) { + const index = this._getViewIndexUpperBound(cell); + + if (index >= 0) { + this._revealInternal(index, false, CellRevealPosition.Top); + } + } + revealElementInCenterIfOutsideViewport(cell: ICellViewModel) { const index = this._getViewIndexUpperBound(cell); diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts index 41a457e3d..39da33ff4 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts @@ -4,10 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IProcessedOutput, IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookRegistry } from 'vs/workbench/contrib/notebook/browser/notebookRegistry'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { INotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { ICellOutputViewModel, ICommonNotebookEditor, IOutputTransformContribution, IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { URI } from 'vs/base/common/uri'; export class OutputRenderer { @@ -15,7 +14,7 @@ export class OutputRenderer { protected readonly _mimeTypeMapping: { [key: number]: IOutputTransformContribution; }; constructor( - notebookEditor: INotebookEditor, + notebookEditor: ICommonNotebookEditor, private readonly instantiationService: IInstantiationService ) { this._contributions = {}; @@ -34,7 +33,8 @@ export class OutputRenderer { } } - renderNoop(output: IProcessedOutput, container: HTMLElement): IRenderOutput { + renderNoop(viewModel: ICellOutputViewModel, container: HTMLElement): IRenderOutput { + const output = viewModel.model; const contentNode = document.createElement('p'); contentNode.innerText = `No renderer could be found for output. It has the following output type: ${output.outputKind}`; @@ -42,13 +42,14 @@ export class OutputRenderer { return { type: RenderOutputType.None, hasDynamicHeight: false }; } - render(output: IProcessedOutput, container: HTMLElement, preferredMimeType: string | undefined, notebookUri: URI | undefined): IRenderOutput { + render(viewModel: ICellOutputViewModel, container: HTMLElement, preferredMimeType: string | undefined, notebookUri: URI | undefined): IRenderOutput { + const output = viewModel.model; const transform = this._mimeTypeMapping[output.outputKind]; if (transform) { - return transform.render(output, container, preferredMimeType, notebookUri); + return transform.render(viewModel, container, preferredMimeType, notebookUri); } else { - return this.renderNoop(output, container); + return this.renderNoop(viewModel, container); } } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts index 10a4839b4..8a0e7216d 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts @@ -3,21 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IRenderOutput, CellOutputKind, IErrorOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellOutputKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookRegistry } from 'vs/workbench/contrib/notebook/browser/notebookRegistry'; import { RGBA, Color } from 'vs/base/common/color'; import { ansiColorIdentifiers } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { INotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { ICommonNotebookEditor, IErrorOutputViewModel, IOutputTransformContribution, IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; class ErrorTransform implements IOutputTransformContribution { constructor( - public editor: INotebookEditor, + public editor: ICommonNotebookEditor, @IThemeService private readonly themeService: IThemeService ) { } - render(output: IErrorOutput, container: HTMLElement): IRenderOutput { + render(viewModel: IErrorOutputViewModel, container: HTMLElement): IRenderOutput { + const output = viewModel.model; const header = document.createElement('div'); const headerMessage = output.ename && output.evalue ? `${output.ename}: ${output.evalue}` diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts index b2cec3995..420f309b4 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IRenderOutput, CellOutputKind, ITransformedDisplayOutputDto, RenderOutputType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellOutputKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookRegistry } from 'vs/workbench/contrib/notebook/browser/notebookRegistry'; import * as DOM from 'vs/base/browser/dom'; -import { INotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { ICommonNotebookEditor, IDisplayOutputViewModel, IOutputTransformContribution, IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { isArray } from 'vs/base/common/types'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; @@ -22,10 +22,10 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; class RichRenderer implements IOutputTransformContribution { - private _richMimeTypeRenderers = new Map IRenderOutput>(); + private _richMimeTypeRenderers = new Map IRenderOutput>(); constructor( - public notebookEditor: INotebookEditor, + public notebookEditor: ICommonNotebookEditor, @IInstantiationService private readonly instantiationService: IInstantiationService, @IModelService private readonly modelService: IModelService, @IModeService private readonly modeService: IModeService, @@ -44,8 +44,8 @@ class RichRenderer implements IOutputTransformContribution { this._richMimeTypeRenderers.set('text/x-javascript', this.renderCode.bind(this)); } - render(output: ITransformedDisplayOutputDto, container: HTMLElement, preferredMimeType: string | undefined, notebookUri: URI): IRenderOutput { - if (!output.data) { + render(output: IDisplayOutputViewModel, container: HTMLElement, preferredMimeType: string | undefined, notebookUri: URI): IRenderOutput { + if (!output.model.data) { const contentNode = document.createElement('p'); contentNode.innerText = `No data could be found for output.`; container.appendChild(contentNode); @@ -55,7 +55,7 @@ class RichRenderer implements IOutputTransformContribution { if (!preferredMimeType || !this._richMimeTypeRenderers.has(preferredMimeType)) { const contentNode = document.createElement('p'); const mimeTypes = []; - for (const property in output.data) { + for (const property in output.model.data) { mimeTypes.push(property); } @@ -75,8 +75,8 @@ class RichRenderer implements IOutputTransformContribution { return renderer!(output, notebookUri, container); } - renderJSON(output: ITransformedDisplayOutputDto, notebookUri: URI, container: HTMLElement): IRenderOutput { - const data = output.data['application/json']; + renderJSON(output: IDisplayOutputViewModel, notebookUri: URI, container: HTMLElement): IRenderOutput { + const data = output.model.data['application/json']; const str = JSON.stringify(data, null, '\t'); const editor = this.instantiationService.createInstance(CodeEditorWidget, container, { @@ -94,8 +94,8 @@ class RichRenderer implements IOutputTransformContribution { const textModel = this.modelService.createModel(str, mode, resource, false); editor.setModel(textModel); - const width = this.notebookEditor.getLayoutInfo().width; - const fontInfo = this.notebookEditor.getLayoutInfo().fontInfo; + const width = this.notebookEditor.getCellOutputLayoutInfo(output.cellViewModel).width; + const fontInfo = this.notebookEditor.getCellOutputLayoutInfo(output.cellViewModel).fontInfo; const height = Math.min(textModel.getLineCount(), 16) * (fontInfo.lineHeight || 18); editor.layout({ @@ -108,8 +108,8 @@ class RichRenderer implements IOutputTransformContribution { return { type: RenderOutputType.None, hasDynamicHeight: true }; } - renderCode(output: ITransformedDisplayOutputDto, notebookUri: URI, container: HTMLElement): IRenderOutput { - const data = output.data['text/x-javascript']; + renderCode(output: IDisplayOutputViewModel, notebookUri: URI, container: HTMLElement): IRenderOutput { + const data = output.model.data['text/x-javascript']; const str = (isArray(data) ? data.join('') : data) as string; const editor = this.instantiationService.createInstance(CodeEditorWidget, container, { @@ -127,8 +127,8 @@ class RichRenderer implements IOutputTransformContribution { const textModel = this.modelService.createModel(str, mode, resource, false); editor.setModel(textModel); - const width = this.notebookEditor.getLayoutInfo().width; - const fontInfo = this.notebookEditor.getLayoutInfo().fontInfo; + const width = this.notebookEditor.getCellOutputLayoutInfo(output.cellViewModel).width; + const fontInfo = this.notebookEditor.getCellOutputLayoutInfo(output.cellViewModel).fontInfo; const height = Math.min(textModel.getLineCount(), 16) * (fontInfo.lineHeight || 18); editor.layout({ @@ -141,8 +141,8 @@ class RichRenderer implements IOutputTransformContribution { return { type: RenderOutputType.None, hasDynamicHeight: true }; } - renderJavaScript(output: ITransformedDisplayOutputDto, notebookUri: URI, container: HTMLElement): IRenderOutput { - const data = output.data['application/javascript']; + renderJavaScript(output: IDisplayOutputViewModel, notebookUri: URI, container: HTMLElement): IRenderOutput { + const data = output.model.data['application/javascript']; const str = isArray(data) ? data.join('') : data; const scriptVal = ``; return { @@ -153,8 +153,8 @@ class RichRenderer implements IOutputTransformContribution { }; } - renderHTML(output: ITransformedDisplayOutputDto, notebookUri: URI, container: HTMLElement): IRenderOutput { - const data = output.data['text/html']; + renderHTML(output: IDisplayOutputViewModel, notebookUri: URI, container: HTMLElement): IRenderOutput { + const data = output.model.data['text/html']; const str = (isArray(data) ? data.join('') : data) as string; return { type: RenderOutputType.Html, @@ -164,8 +164,8 @@ class RichRenderer implements IOutputTransformContribution { }; } - renderSVG(output: ITransformedDisplayOutputDto, notebookUri: URI, container: HTMLElement): IRenderOutput { - const data = output.data['image/svg+xml']; + renderSVG(output: IDisplayOutputViewModel, notebookUri: URI, container: HTMLElement): IRenderOutput { + const data = output.model.data['image/svg+xml']; const str = (isArray(data) ? data.join('') : data) as string; return { type: RenderOutputType.Html, @@ -175,8 +175,8 @@ class RichRenderer implements IOutputTransformContribution { }; } - renderMarkdown(output: ITransformedDisplayOutputDto, notebookUri: URI, container: HTMLElement): IRenderOutput { - const data = output.data['text/markdown']; + renderMarkdown(output: IDisplayOutputViewModel, notebookUri: URI, container: HTMLElement): IRenderOutput { + const data = output.model.data['text/markdown']; const str = (isArray(data) ? data.join('') : data) as string; const mdOutput = document.createElement('div'); const mdRenderer = this.instantiationService.createInstance(MarkdownRenderer, { baseUrl: dirname(notebookUri) }); @@ -186,9 +186,9 @@ class RichRenderer implements IOutputTransformContribution { return { type: RenderOutputType.None, hasDynamicHeight: true }; } - renderPNG(output: ITransformedDisplayOutputDto, notebookUri: URI, container: HTMLElement): IRenderOutput { + renderPNG(output: IDisplayOutputViewModel, notebookUri: URI, container: HTMLElement): IRenderOutput { const image = document.createElement('img'); - image.src = `data:image/png;base64,${output.data['image/png']}`; + image.src = `data:image/png;base64,${output.model.data['image/png']}`; const display = document.createElement('div'); display.classList.add('display'); display.appendChild(image); @@ -196,9 +196,9 @@ class RichRenderer implements IOutputTransformContribution { return { type: RenderOutputType.None, hasDynamicHeight: true }; } - renderJPEG(output: ITransformedDisplayOutputDto, notebookUri: URI, container: HTMLElement): IRenderOutput { + renderJPEG(output: IDisplayOutputViewModel, notebookUri: URI, container: HTMLElement): IRenderOutput { const image = document.createElement('img'); - image.src = `data:image/jpeg;base64,${output.data['image/jpeg']}`; + image.src = `data:image/jpeg;base64,${output.model.data['image/jpeg']}`; const display = document.createElement('div'); display.classList.add('display'); display.appendChild(image); @@ -206,8 +206,8 @@ class RichRenderer implements IOutputTransformContribution { return { type: RenderOutputType.None, hasDynamicHeight: true }; } - renderPlainText(output: ITransformedDisplayOutputDto, notebookUri: URI, container: HTMLElement): IRenderOutput { - const data = output.data['text/plain']; + renderPlainText(output: IDisplayOutputViewModel, notebookUri: URI, container: HTMLElement): IRenderOutput { + const data = output.model.data['text/plain']; const contentNode = DOM.$('.output-plaintext'); truncatedArrayOfString(contentNode, isArray(data) ? data : [data], this.openerService, this.textFileService, this.themeService, true); container.appendChild(contentNode); diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/streamTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/streamTransform.ts index b05a25d3b..5f6f9b773 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/streamTransform.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/streamTransform.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from 'vs/base/browser/dom'; -import { IRenderOutput, CellOutputKind, IStreamOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellOutputKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookRegistry } from 'vs/workbench/contrib/notebook/browser/notebookRegistry'; -import { INotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { ICommonNotebookEditor, IOutputTransformContribution, IRenderOutput, IStreamOutputViewModel, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { truncatedArrayOfString } from 'vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -14,14 +14,15 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; class StreamRenderer implements IOutputTransformContribution { constructor( - editor: INotebookEditor, + editor: ICommonNotebookEditor, @IOpenerService readonly openerService: IOpenerService, @ITextFileService readonly textFileService: ITextFileService, @IThemeService readonly themeService: IThemeService ) { } - render(output: IStreamOutput, container: HTMLElement): IRenderOutput { + render(viewModel: IStreamOutputViewModel, container: HTMLElement): IRenderOutput { + const output = viewModel.model; const contentNode = DOM.$('.output-stream'); truncatedArrayOfString(contentNode, [output.text], this.openerService, this.textFileService, this.themeService, false); container.appendChild(contentNode); diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper.ts index 8ef36f850..c8dc77318 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper.ts @@ -61,14 +61,14 @@ export function truncatedArrayOfString(container: HTMLElement, outputs: string[] const bufferBuilder = new PieceTreeTextBufferBuilder(); outputs.forEach(output => bufferBuilder.acceptChunk(output)); const factory = bufferBuilder.finish(); - buffer = factory.create(DefaultEndOfLine.LF); + buffer = factory.create(DefaultEndOfLine.LF).textBuffer; const sizeBufferLimitPosition = buffer.getPositionAt(SIZE_LIMIT); if (sizeBufferLimitPosition.lineNumber < LINES_LIMIT) { const truncatedText = buffer.getValueInRange(new Range(1, 1, sizeBufferLimitPosition.lineNumber, sizeBufferLimitPosition.column), EndOfLinePreference.TextDefined); if (renderANSI) { container.appendChild(handleANSIOutput(truncatedText, themeService)); } else { - const pre = DOM.$('div'); + const pre = DOM.$('pre'); pre.innerText = truncatedText; container.appendChild(pre); } @@ -83,21 +83,32 @@ export function truncatedArrayOfString(container: HTMLElement, outputs: string[] const bufferBuilder = new PieceTreeTextBufferBuilder(); outputs.forEach(output => bufferBuilder.acceptChunk(output)); const factory = bufferBuilder.finish(); - buffer = factory.create(DefaultEndOfLine.LF); + buffer = factory.create(DefaultEndOfLine.LF).textBuffer; } if (buffer.getLineCount() < LINES_LIMIT) { const lineCount = buffer.getLineCount(); - const fullRange = new Range(1, 1, lineCount, buffer.getLineLastNonWhitespaceColumn(lineCount)); + const fullRange = new Range(1, 1, lineCount, Math.max(1, buffer.getLineLastNonWhitespaceColumn(lineCount))); - container.innerText = buffer.getValueInRange(fullRange, EndOfLinePreference.TextDefined); + if (renderANSI) { + const pre = DOM.$('pre'); + container.appendChild(pre); + + pre.appendChild(handleANSIOutput(buffer.getValueInRange(fullRange, EndOfLinePreference.TextDefined), themeService)); + } else { + const pre = DOM.$('pre'); + container.appendChild(pre); + pre.innerText = buffer.getValueInRange(fullRange, EndOfLinePreference.TextDefined); + } return; } if (renderANSI) { - container.appendChild(handleANSIOutput(buffer.getValueInRange(new Range(1, 1, LINES_LIMIT - 5, buffer.getLineLastNonWhitespaceColumn(LINES_LIMIT - 5)), EndOfLinePreference.TextDefined), themeService)); + const pre = DOM.$('pre'); + container.appendChild(pre); + pre.appendChild(handleANSIOutput(buffer.getValueInRange(new Range(1, 1, LINES_LIMIT - 5, buffer.getLineLastNonWhitespaceColumn(LINES_LIMIT - 5)), EndOfLinePreference.TextDefined), themeService)); } else { - const pre = DOM.$('div'); + const pre = DOM.$('pre'); pre.innerText = buffer.getValueInRange(new Range(1, 1, LINES_LIMIT - 5, buffer.getLineLastNonWhitespaceColumn(LINES_LIMIT - 5)), EndOfLinePreference.TextDefined); container.appendChild(pre); } @@ -108,7 +119,9 @@ export function truncatedArrayOfString(container: HTMLElement, outputs: string[] const lineCount = buffer.getLineCount(); if (renderANSI) { - container.appendChild(handleANSIOutput(buffer.getValueInRange(new Range(lineCount - 5, 1, lineCount, buffer.getLineLastNonWhitespaceColumn(lineCount)), EndOfLinePreference.TextDefined), themeService)); + const pre = DOM.$('div'); + container.appendChild(pre); + pre.appendChild(handleANSIOutput(buffer.getValueInRange(new Range(lineCount - 5, 1, lineCount, buffer.getLineLastNonWhitespaceColumn(lineCount)), EndOfLinePreference.TextDefined), themeService)); } else { const post = DOM.$('div'); post.innerText = buffer.getValueInRange(new Range(lineCount - 5, 1, lineCount, buffer.getLineLastNonWhitespaceColumn(lineCount)), EndOfLinePreference.TextDefined); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 31fc6590e..ce56dffa3 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -10,14 +10,11 @@ import { isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import * as UUID from 'vs/base/common/uuid'; import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener'; -import { CELL_MARGIN, CELL_RUN_GUTTER, CODE_CELL_LEFT_MARGIN, CELL_OUTPUT_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; -import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; -import { CellOutputKind, IDisplayOutput, IInsetRenderOutput, INotebookRendererInfo, IProcessedOutput, ITransformedDisplayOutputDto, RenderOutputType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICommonCellInfo, ICommonNotebookEditor, IDisplayOutputLayoutUpdateRequest, IDisplayOutputViewModel, IGenericCellViewModel, IInsetRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellOutputKind, IDisplayOutput, INotebookRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IWebviewService, WebviewElement, WebviewContentPurpose } from 'vs/workbench/contrib/webview/browser/webview'; import { asWebviewUri } from 'vs/workbench/contrib/webview/common/webviewUri'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { dirname, joinPath } from 'vs/base/common/resources'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { preloadsScriptStr } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads'; @@ -26,6 +23,7 @@ import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IFileService } from 'vs/platform/files/common/files'; import { VSBuffer } from 'vs/base/common/buffer'; import { getExtensionForMimeType } from 'vs/base/common/mime'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export interface WebviewIntialized { __vscode_notebook_message: boolean; @@ -36,6 +34,7 @@ export interface IDimensionMessage { __vscode_notebook_message: boolean; type: 'dimension'; id: string; + init: boolean; data: DOM.Dimension; } @@ -196,9 +195,9 @@ export type ToWebviewMessage = export type AnyMessage = FromWebviewMessage | ToWebviewMessage; -interface ICachedInset { +export interface ICachedInset { outputId: string; - cell: CodeCellViewModel; + cellInfo: K; renderer?: INotebookRendererInfo; cachedCreation: ICreationRequestMessage; } @@ -217,12 +216,12 @@ export interface INotebookWebviewMessage { } let version = 0; -export class BackLayerWebView extends Disposable { +export class BackLayerWebView extends Disposable { element: HTMLElement; webview: WebviewElement | undefined = undefined; - insetMapping: Map = new Map(); - hiddenInsetMapping: Set = new Set(); - reversedInsetMapping: Map = new Map(); + insetMapping: Map> = new Map(); + hiddenInsetMapping: Set = new Set(); + reversedInsetMapping: Map = new Map(); localResourceRootsCache: URI[] | undefined = undefined; rendererRootsCache: URI[] = []; kernelRootsCache: URI[] = []; @@ -234,9 +233,13 @@ export class BackLayerWebView extends Disposable { private _disposed = false; constructor( - public notebookEditor: INotebookEditor, + public notebookEditor: ICommonNotebookEditor, public id: string, public documentUri: URI, + public options: { + outputNodePadding: number, + outputNodeLeftPadding: number + }, @IWebviewService readonly webviewService: IWebviewService, @IOpenerService readonly openerService: IOpenerService, @INotebookService private readonly notebookService: INotebookService, @@ -249,12 +252,10 @@ export class BackLayerWebView extends Disposable { this.element = document.createElement('div'); - this.element.style.width = `calc(100% - ${CODE_CELL_LEFT_MARGIN + (CELL_MARGIN * 2) + CELL_RUN_GUTTER}px)`; this.element.style.height = '1400px'; this.element.style.position = 'absolute'; - this.element.style.margin = `0px 0 0px ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px`; } - generateContent(outputNodePadding: number, coreDependencies: string, baseUrl: string) { + generateContent(coreDependencies: string, baseUrl: string) { return html` @@ -263,7 +264,7 @@ export class BackLayerWebView extends Disposable { + + + + + + + diff --git a/extensions/theme-seti/icons/seti.woff b/extensions/theme-seti/icons/seti.woff index 82e4a516665550fa3a2f7da6d0ded7b7e6dbdb4a..cd77f8d15aabf811fa16255ce8384ab7228f5014 100644 GIT binary patch delta 33720 zcmV)XK&`*rmI8#B0u*;oMn(Vu00000j06A+00000)XKYvJLZDDW#00D#m00Tw< z016_L2?1hfYF`or2WD$#t{YzNNGM2M~m8@blYgo&=VqMP$HWpW#*vuBTvW@NRU?;oS z%^vo$kNq6rAcr_ie-lSI$}x^}f|H!$G-o)=InHx|i(KL|SGdYGu5*K%+~PKOxXV56 z^MHpu;xSKn$}^txf|tc#|BBbV;VtiI<~<+yNDH6%%oo1$jqm)RwfF+-`2VLiLBj4)*dSN_EdS@OJv?#WZp+)-jfjnLx1`2Rc#cRPZ60<6`4;HnNL@F ze}>BInIijUiR_y#vTu&azPTd%=85c^FS2ie$i9Ul`xc4pTP(6~iO9aCBKww!>{~9f zZ-vV5f2Bxcl}KZ?NMnsiW35PIok(N7NMnOYW1~o8lSpH;NMno2&uEj7wPR# zxp${Xdw-Wmd$&k?k4Ss3NPC}1d%sBgfJpnGNc)gT`>;s6Nu+&5q(M zqGS zIjth+{3deF?;_{?A#%>2BIo=ia?al(=lmmb&QH<$FOoatMF0SJoRqu?oFrFyCz$V! z`+vx|GV;!h%FL>+JJ0H_?kaVu?{0O|Qn$KwH8c`5mJpJR43?m=Ktci=S+WJPVI*M1 ziWzKyWNa`Dve?0+MYc3xE}JnnhJmrLFq(m%YzECLcfS{r)h%{m_qSDfL`J-L@s98K zf5&?{oP$4t$Wrb^?#5gW@qi01EF}0WYJV=`cG48wM!b_WJMGQ?LBG8N^+)~D8X65( zQMZP|UfAodqhU9UkXWA`IKN_C<58=uA#P*rZ49zplvMp>+ zjZOG49o4fv9y*@N3E|4N>DZ}h`OxItQO$C+qp0Xgv81+Dq7tQI6)3tp^h=7NIDa<5 zf~tOCE0#&nHJmRk_!cn<=PIX^b1smoI-XIQYEQ>D^H|x?46l8mCImc-s~RVi>kZz{ zi-H+K+Zq+yLge4xsO^d=aO zH5AH02+>Pa;itNU+~F|OxPP7BtL6nU2tjj{7W^u;OqDUDp+(g@VHhG%+jfCsbZkIv zL!*?UM74InoCDc;WpbiU-8>Txr-sF-{*V^7_> z(rvI1Oh>e>!**ITu}%s)qi&^na0|j$~0Fk zv`eD`d{Ys$l?n`HI-V9el)EAKj@%!>XpV-!XeralaSOUY0|CsnkPH@e{3M8)a3z32 z2@(-R!EhaiK@WaO%J3t36EGoM?gc%0AC&ElhQO%s8g+*wpqarin}Z`nF7yUWY3A)YS|Y+ov@?oce@5#?1hAW~|miZ~^j!E9kLQy1oML!UR>eSc_6FY1QS)=m7gcV7fpgGvA*(??~5fpP!0E(kfL+EB;X_iBd8I;UV zffgb*DThuONKG+-khC(KIu$nSi;eofi@g;FkM~tUC^J0h5%x=K5RZFopTjbpK zfIj5-3~D+soOMy_@}<)!tdgM-6RXfG%q)j#8f8pX5tP6Pl&cN>1zDAMiyDDJwFziZ z>T=MP1cy;Ryh26H;E1YHnh{JkqLK~TG%{)qRDpy6I@Q-4RdMyAig~Z(U_?tGccCy> z4S(PwkfU%cbgG?dE;JfT_0MzQSD|2PV3VjCRy;#X7BSmWUjg|6e2EFP5(Idt#+spW zl|mcEwQf1@7rYhdhrp`Jbjenk9y){{0|_cX2PeQ&L1O@FI3b`ISzi)*)LQ_xWFfzb zK=1)oViT>ubkC_%>+9>NIGC<2YNwU{k&|x>27hZ#7CtgPK=)sD6)KM3jZR#(K0Qpz zb9=Y+E`9B#-QJerrmV(4$<5^s&`-BtwdUKc_%Jsy5t^2aYWr%Qr)l}a4c~LD zx?Qkqo$A4Z&2IDH!R!mS_jG0VGF(@H_8qJ-_&I1SFqlwC{_pTqjE1!Q&A%DE|{!7Dxtcs;$wjXd3%c;3ASIJ!h^#Z+) z222DkkBewD8KO|MJ7NUtzCBS-q6wV|LQplS3;~SMlvo0UO;-v;7-p%`(xM%FpB4qT zjc@z`a^4TH^2?4N)~fB6{;z}PO<%_3cYroc9>%4ipsWmJ*>)sL0{EY;?KUUY{Lb=fJ3i{0btaCD^#)i;X-dt|aYlc`M z7{`D+%otP*0!3H#Ta>T=v}Soy7lG<8m_N&H>iZ@9UvgV=J93vws!cXQ_5zX+JJkXb z_{ly3pq^lHC_s`_X8;gSA-9y|t$!4{a~K&A-Bl^pqn@k*z0m3xw>0Bf)6+HEpT1y^ za%AR)SDOVt4z)nTg4};t@U>Ixre81T>-m`zuM-SyMfsY8h4?NsS)b7>H&+eAQ!SjF z28pV3j~GCf7v~*cj~L>0YU+0VaLt5az_{&^@prgKRVSb9ivg~OOk8u)wSPR05n3VS zB+-ac7NEh6l*H0;*h04Cf?TE#8**~ps#JH4R2jN`0%*6EO@G1w(_dyy_sd~kbcmbIMRgFOxJ%f{s5H$g-lfn=^ zfBt=7Ul>t2s1i|2{DsmPfd64z!mJCX2ui^AsT0D}MfanIps-zA1lIAy{@Cq8B z@3)n*va;9?W>ncZdwo!iDrGQ7W_ZMQT;6mP9T*s{SdGf4J7CUVqn)q6?fNx@H-=kx zu3cNL{|sQu)|i0YQ!(o`?L^on(}|hgf72N5B^G;GWiNXQ2+Qp0TN+hL#@}7Pb{*}! zaYI&$(FMbmRie_C6nTuOCQBkKrd)a2CW?3jo!8qwQF0b=+y%K;!-&b@sUT_nrb7ez zI*hV`$p*^WNCTS)m$t^BQ!_$y?8~%+}2E(IwX|YvF98l_v+^e$no=?bBPP)O&Z|(oSZB z&^0hetz`M)#X-DUuK)ofbkX_-`hmAp6?qfDieE4Oad^T0_0SEiIv5KEnT;4Q2FWj7 zywsj*lt`hx)y!Y?_Ct-@Nag6se+xz-1N8$(WDk06zh~e~Cs_w1xpr zxiArHSPrp;$uGPHq%X}D=0yar?Yv>&&HZilKhIu!=5S)KziK$Ta!Yvl^{?Gg>RmWB zymH>1`c(0cr%oQ~TK>a40k|pw0|lOKlxDA7xQx+PEv|UB#21Pl zXf6mtM38Fk3#Ij|2tzp2HnZG%d!f_!2JuX}shJ+D&esJ+NVoE3p#d3VbWR@^NIrLe z?jfm<7ZKQOeHe{3Oi+Of$VSaUTOLRgZJ6PZbRe3kwbzj)EU+p4By%OE*X60ycm=c7 z=hB5C_3Aq62C0ILq^Qfx*%b6hR|go9vV?pnh&y>O%8cLiUz5TVIU_Hs1h5KJA{>wy zzo-%pMw~&jO5g|4|7HF};5FycPxSOJxsx6hA^}&EK^0GbUY{GRt`4?ix_WAd@3f_v zLI_v&WC&)8pI7z#k%phQt%hB&YIFAv=2lkb$lBUqV`H#Mr#r}vVD$lz1gdjc%k7Xw z)DY-}|Mg+CAMJnm#Y-0f4( zIu;~#gX%|<$NZx2Z{hBE`okao z)-QSy?WM2dxi`+t%nUZ))6>&Ap3wZLT)ONAmFTc{~bn^n}UV$d41XcWMIHQWb{fyi|0mi68J-Se$RA=EWojEeQv z7C3RiTiY{uKGtTncrGT6{XatxS*p-z$byEi-Y{c3rUJBLZiyUSKxA>=RCDs%VQFP7)=5 zU$u3zDGy;@pU-KzJoMcM^yUJ_0JET<9u4ugAN>6feeyjA+pV(?tq(6fj$VA*JJ1Uo z)ho7tWc-<<=dod#l%NJVlDSlpCQabvz^@_T9%<)+cuU$z14$V=a1klJ^=hT07p#vP zS1Q4`es*U3b=_A&u?MT__=o7kvp4I19K9ZQ-g32SC@1*;<{H)DTW8Q;gJHD?jq5k- zZh%_j_nv<~1)bLdq;*-Q_p*ObR>NKya5rM2uQstJMD=qB@nCvKxKcv@E zWsqF}VFB20aa-324Pi2>HNE}??Tdcp5|99DY|d?~Sfiin%&D5c_WQV7+|n?A3)3g#g4k6tR&E4y zR3koC+d#BguXQV?C7(9qw!;-^Ee;>b&B(^l6Lz9+6U4bDpFY1>@4@snOK<} zpSnC#Q$E#mttJ#sUAU5U24K@q>;dT_h0#pC__K(oP@IL-BjoB=FGijEHH3sL&kWU5 z+=>-<+rH!ZYHg-e%s1U~Y*^!eF2`WLM8pgnz`Ai0Q2JD@T&lX{JO8kMc(gqW=IpwP zIR^dVc>ol^gvVgg7hKSXRz(o5D@H8>ONc5FSZkhQsn{sRpn+Ot!PT(bA~YU1&3*^)*fc9P|Eu&Ovr0>oz_-@H}JCVX9_42^r!hkkm zfG74;Z_`xGMz_s2-OnO_b%uwTqnx+Vlk4fU9%o;v(x#|QG0%m#bP)Um=jGvC{48Z-(6!=P+=xU@RFgtsbX z1-~-TDlE_(wY`y_>C`IuVQ+2c(lGIy>DI*&rq%MEiHsZLuY$z4WGpfeQP5#x0E9V+ zo_p&4`=7%8Q}2I&|5G^p?8)12Klxb#U*z^HK66e!eu*4`8J?CNy~!MfB7@Z#QD=>Y zxg@Fh=zv(Rz!=m(wd*G3?dnqdl1@LFb1W}E-M+E~@ND0V6A|&4Gr*6QDsFu$U$3^d z9PZDCd1Ba8^ZPG2cHr>7>+EE1Xj_!x#ynoU)_P~@4S8dBKqF= zyZ8Ra|5-=1m%e~@BJ0EV+per7KeEjU%+PV66fIec-ZdXcR z;x#^IANU)x{mI(>-@N9UZ{A;%V;v}e z=@FOX(z1VUgu?5Cc{o!(&{=I zFrI=J(8W>~-+=b$kn1XEDki3zTNgi!2HtKtrEHvQpOokn6 z6-DtvZe4@XnY+T-a{snlW|hw(%Cvx~RAF4xjl5<(;gjPYf`5k5t6;E{DRaw$IKU~A zf83hWgvP{ir{n}~p>UpGzDn*$b8#9V!xI2>?yybn7{778F}{{Oj^~KeZp{B5-@&IE z^XL7LE`A1dZY#GhcL?rGCfKZof;2^99raSjIsB3sw=-D8f^~-@uP5DZ>3Qid>ZfF$ zh!m(us|ySve1G2=gHQwr$~x_F-8w^FXX%Fe9>X*Q;^n#7@xKXb`n$cm4VNjUXUe7u zL`0nTc$&xKvp?LGNiQ)KrxVz5ave8#=ynyOS*Lk+jn;cP8*N$Y58Ldh=y^#Bv&g>?ATMnToXT2(ZHhS*&3YCbDQ2LfL`f z0eDUn#ClM&2xmIgq+z6s*dzSFe(0j7+P1;KsDA^yi-F8A7N%my7KMF-vx`o4ed0mCF9DXxJ#oyFmrVJH^BlP0DVn+D0}!GZ!a z2os~;fNqqkJmigz^dH3pX^a=j6hxL?@nHzJ3xTbuyKDFHy|-L!*>U{F!xvq2@QpFk ztbc>P4Qe__^~--J#{p}^xlV35cQki8_r}~iaz8@~3ef=VMVFyhqhCS4ir$LejqXJs zkhW$zE^+{cG#Wd8*d6v|UPvdkqWQ#^7xmN34}e}#7fuKA8kA|Kf;wrA`s=7WkaB+* zbyIJClEuilOhp3pKWNJwB`{*UQoX`p$}l*=8VLiEbkd};^>j}A(w7N@JISgW4&cU7 z8W2mJ8lzMh$nP}Z5`@6Zei(si?2D6+B8q>8a^4n-ISoKVEmeqN8SuKsFo7_6o?)O< z1zVQM0|vkd(=^%1G&QV%(qn?Em^7IR;u~;+#xW{`b;S@f8Tu%voG`4rqN#ys@C>1< zmUPT=u;MixXn3(eS)q9N*iuc=ymsCUhyj+b?SR?NRIpZMBnwlm%9x{4iz?jYoKkRBtf!Z`IIH^wksu7vbWf81t2q#fu8l%vK$)ZW--sQ-U2TAIUf&YCh1LPAM8BR7z$Qx@3QHDSBg8 zA}VDBV~dEA=hrRW_MxXYviWilVph#}OzRYY7|`s1S`8p9 zDiflKh|jW8Gr$VsMC!;?5`oaT9E2*OA0$Y@`1|ScUlIiWXVUD$lk)g_r`V*F>e}@5 z0%{cJYsRI55<{cnA%v95^5I>*Wkp$7y6D1g;?m#RIXA87i-qko2d;lp^tIUw6#FW~ z2laJ}M}?`W0j^>LV}ne~FP+Q~9TSaQ4w@6_6trovIx0>Ro;V6G`@(RP<@Qbu^vK$t z?dz7&jHcQDK<%WrZ>^pPbKly@^*LbdKSo55MpLoYi;KGBmComByn#@#W?m0cy|WDR z24v(Ym2keapwkF~KN)|tNzP(=zL!V`7>L*9Fmo;fV#vheDoXPkx-v$7{x~~7I0xYI zEiKCh^8K@L`}Ts&v{OkwD$jHxzvQ;JC)>C0J8bLfvQo1UXyl>~q)KVC6GqGC!rodl zK%4^lM`UhwjtbhR9MQRLa{#Ytj|t@XofD0sp~>t}OSP=8M~;6L!XVl(TjuoEHcX@N zszyLaOEj!YEk!YG(5t}hI;j;*!wiphFTL_cU?5uQbSji@eha!|d0S;;_h>M^X%Aip zICDC8ICqmonQ63a(#OpEH)_00tE5pgP*j^C#vnT@hj;UqW~Vz$6IE7GtJlr4?_tnW zMIm)jM$Kv6pf7*X_f?|R1T<6tS2K2b)N*}{z7)1!u}q1sB76RFzw^9%;akq{+ zQ3ZFKWm^TXK(Oh6q$GIvG)SqVFXaz|0_Yq8ndI5hoS?H?K!_MjFvA&@5Td9~RKTOE zR|~48UQ%e*+H>7xS5oLzn$eW01K$HQ(g{d>%dNs%a6a)zwC4MjHf^x&Vp{|uBc z$!AoVBx*(EQ{;-zkjOIFvtC@a-h*yL4`TWdH<#y4ld4C#MvARg(GW)&Bc?gKY{OMy zWLGa{ldJqADf1ZFx?j>k)wE1h6v?vcwk| zBu*i?y*_E22;;)O^j>v5NXtwPxy|QDHvdM!*&?z6bpP z5inGX1y1c!6gA;N76;_P33Vm;n_3{(odACl@fcBv?t}6)J+RbOg?KSAhxGpk%t2NR zrI_y#7TDBd(9}906;0?x&CrZE$rD9oM(h-djbhmp3n0K400=@=;6rsz(*irl1FSOL zST7Iq!T&OmN%-HAw*#8*r*R>W9qC#NA!GS+=Gt-=1K3tH*-^U^aVWurP#JLf~onYkHeUS_2$Z+9lCVC1K!gF z`Shm^aRPG&Rt@F4v1n+DPz_5NXw)(vfDXsUE;@7s)wNhX?ShF*h^cBkUW|3$pbhl# z;AHsy3Ikgz4=}^@3OB*$RlYS%%UObF9%R+6(kONo;)Qf zt9d6F*!XD>o6UDk5%ClvGYCKV>g&jV+<6%}^du)wVf++9&3C`_6h=?OyYYXn=HgRN zVx+?Rwc|$+g71yX&-=EYA^F@Dx!=gv4oE{@Mpr?~3>Op%Q9(mA z{h|bfMCK{ibl_e9o4adNTty3j0|V2;06snL7`6pHk*hs02R5My7ZB)la_#*{PKOB4! zGIj4%`X28hZpC_ly!jfE-qy4L7tx4yA`yvJ)FIm18hJlCdlo*<9bNV4bK}F?fB5L5KSY1L`W$-m z&tFZC?tXOjx#w1&dUX2_f4F_qCyyTkorYl^Yq?x&5J_?tZsG#fXUiR3xyq^A8IF3O zAmY~|HC8#gT(^H}+YW3AFSz2=!3z)f?JKlxkALzq2N07SK_$Tj{f15o1JpSK- z^30!$#dQYJi;v%Z_v7e=&)sv+=dkza(X(fdK1v=vcJ>MEu}3E~65_AHlMOC535L^> zvrg3EuMCq+E*O8CnHyBW;MayN-5e`5h1W!DuEvR{z4``~8r&lXS8u(-!XGt3)oSY` zK+){$G@DlnPE2f1(_Nctx7DWiHVz+rpIr=86%<{2WS>@NRHdx6k~y9iM=WZno?`~r zshhC~3qKDty_mZocOZAUQu29EWofNTv7ky(Z?!_U9;qx%rk7j@~o+YJJV`0Y7AIvo)z(`ez~ zR)%cA-|T;B1{cZ3Dbuq})i6B(IUKLVAO@EwbWp)R0Xxmec|hkml*uALY{@jChT9Px z0i)7$8~6iJqp{d%jQ&j*gl#!Mrno z;{Zk);NL7T|DdzkS~Ab(8&_^^RJxZRIuUYhc69O9h3>DuVy7q9fQ_Hpw`2S6y*t+T z@4Y3N!uz+cfu)jKEvMs}s(IzduZgPlxoRCd`}Z#Iv{ZFv|7&Yr_r&(2b)RX+c3uI~ z(iDH{_-|Xut@qxVwD8{TJLLCifLZ8iOi*lS_j+;sQhl}#ub0I^L4L%!rQ8n5VX4=6 zl8T)6d6?-?lxBp=WdxBlD?q=7GXIG*Ei5y<7XTD`i%l=yfA#Twd$-J@EnD{P`vZA2 z{$kp%U*CIl9kAa*Vu*OpE&GzyDb(DySAIXP;B0Ds z!b0L=9EA&((`fwpISlzP_|t%8R?=J?KV7E@MsJ$*JXs;qPuHDGce=<(AZ=rsP@m<3 zqHB&%H8$)iU4@5R?uYYW2UV-(y?qYl9K&2l0__60NO6rX5 zQtkYft|pYA9T`)Hy6fI<0>*F9)K$kDcDl6$Y@eLW%Yt#*OeV>IYXAAkNLZc28Q z)+4AwKB35dX2<$bvm$*_aPJ6oo=jlQY6C?YdeWN!P-?h{(~v>-(Byn7xF1BB9lMzb z6HQ`e!DRg|yhh#hyO+7QhNESf<_yCv*C|BZWtjHmFvD-HG<4Bf${vte-)W`GX%d4B zk%JzS=GdlRvW7Cg9G=g6*(F>yhj`Y6^k-U2I$&uoN7PQk?b5(b6Sbwwq@8hk7K+A>ORF9JsjF!e191pf1sQ=Aj>N2D zhaoRm+)z!a1)U1Rd7)4_p4cuiP7U41w_!ULM1-Dg$VI3eEENpAY7llOF9gDI+KT5J z0x`!%q&Rbd78d8`j7Y`6rg9ZHVzJ_Y$)~G$o2NKLY;Gn3-3uCC zSTbb}V<(TnV(zZnITFrJXm4UcG zCLjq!35#sqZG>Kww|okX4`kZdvgR6hsnTPn(1GlWqR+_{xzcc9iYgZ;gN397m8xnH z#8icUkzgs2F#rSxnPM(ODQvkSF#aSuY zhKtbuKyp#L5#;k>zFLn0x7ypWy(cEQVTbVF=Dw2q>)iKp|D5~pxfgOT$;|CdQI#&U z4L0*p!yd>&P&%2Y1HmM~`T{|h@vIahet#N{ltwW(i0s~a(yrsgZqfWHBv?B>w7N5yY9CQhAfMo z_etY15xJ4f;PBF-JN!Kc+)qnJ!tIJ?0Ug^bV=*d8|V^ynCWXRDE&G>aw%hho-39XR^yl(w!+Q4)`k;`ENDP_c`?hw{jI z>DwqjeqO$D^LyFX_-%zXo%3pQwyCYywEVMP`rJGXZV z$hbitY|cR%zO}NbSZ6b@S{4JKELYrSufC*!38n+wlfq-^5zj75P_#y~%n~qj+Y7~PAZ+}e&GeA6NVtLfY`{@4R zB60o8sxd8_nz3`f;urE^pr{os*4>Z*J`fh8C2z3+EbVC~%ddfEXM#B@W4??Y*o>%D zDodSd1_&llOCa(zZuwwr!N{wcM$}M~=^PweTm=b2u^QKluQvhONIz~wET$5QbpQ?; zlHnuJr;JfLU4JcXlcDX^-3-1$eh~Ctx%wYankl*=J@O>MCwn_dtCXnK%7RME)AKW3 zQ1JO+SJEy+drS?qC$v&nk!vz@nR(2&{NUJDc))DAbfzEs3XetcOv%~l01Rvm8$0$i zB({ppfYWs8E;ayQDK-E-MY?l+ohrAa^|D(i=NjcrO=$4ANbbK%At|T*CDA&9Jsy;Rs4F4cYp=Q6_4#btF+I+_W}i z0-7Y<(SI6Fh1A4MPty^kv;JJzE%e%nTydK1`6R;<-NC4zJ}TPGPY}Um8KumRmicQF zAqdufnyY4`=44wppjUwIV0U)?poXfr8*aMCGVcH$%Cx#uq9T+?^Dp+zDv${PJBipq z=}==Urb@#vijYfulPzO_@&UT?`h^MsMWzs?q1L>^iw0E8 z=6{!~CY;q&GX`bNtbyh3bHigHF$|%*2B`8p^gb@~IwhsWg#ap)3pn>5i5Yu z6(-{HwBJIPNxh*))vZ!-%3(7|HD4ImMbI&d64qq!gvfMa5?FFK92MA+k6*PR?HwYO zItf*HcrDbzhima-9T7tYUVuYI%-5U^yMJ`8Oxieg&R_K`0xAWGwFkWdtmkXcThKev z@1c*PKR}P5KS7^Ee}?`XJ%j!l{T=!*=vnm7=qG?JUrOy4uo|$1JwUV(F5nnfaSc!5 z7H;ENyo3jM8(zn|@P2$4UxH8K%kbs+mH1WoHTbpoI(#F(3BMV?6`#d#!@mJG*)`ko zUHD!2xA8srefWO-0DcfZg#Ru6IQ|5F6n_eT8viMN9RC@90)HO=NBksy8b5>o6aJEu ztUW}3ytbgpL6e8;D04~jq#?NjemCUrM$<-q)N5>Bll#&n(p%g<=QUo12Ljp{cHDFm zyfARH4c7+lFoM(2GFM7~fh(D681+DM$o6;pWGE|^o4&Q-p#Xk2pV9(&?53`39t_%9 zr4gJPr01YWGRz(<%Y)$$)r4s^B01NIc0xjb9=Lu250^J4Gim8=h~&9$Kav9~?|~Wt z`a~7E$&VOxWCzM0&4i&*ur!djEcKF}nEXhOyYP`#nI6e|cc7)8J0~j%L?T0B0~^UR*|F#@C%tv# zFAI3GT#qnXhWB6zE(PmoNx(1ol2^}tK`E#KZtF}=ba?uW{pCS0T22d`D~-_$ICX8L zmVxRCocit&YJ$K>2vvkoX_}8vB}9cSD{~l`o+=K4*y1+DF*)waTt2bNq&ERX(sL

    C&wRyW`8}3X>|&t zLuuYAMt93m388h!?V_zR!4fKzszyn-Bau?{I6?DrJEHN^CypJ3yDmN~4W>i#_WcxQ zhU|r(7p%$OzkQqBIwo7sfzYqW51ETkNRXGhCY`LwMU<8s!od=}hqRmBCgu3K3J^Vv z$3IN1`7{BNe_qAn0Vo33-+z39H2ESF| zSv3V+tVWt}o~_EHZ@ia~eJ~ps8cUOT z0394ZB;<3(Z>s~rDFFZP0D3?nc>o}%q#)dak$Epv_V+jZ8pY41UXC&^6PcFtB&O@B zhvo%%;sv(!dCxjp;_UCC~5)bI%|B=jtxO(wmKSKMNsMHCbEr0KOS-i^56=bZv)y!gW zX)wpPke6lL_uD<0N}i=}qNn$t-G5+h=SBN&O{Vsr?KGAab}cQQZ7kx2q_MF4`2Mr+ zeOuDTX#bA&`_Ar1Us>8n3pC-{&V|jjCNjn^cV!T6&y9K``0NO@e$DMS9=h@NYd%HH zcx<5It+$T<5`VoAKRK}-$E299#&%`k9d4V1MDSg2zT#8kpS{5tmAgSZS|ri`GG#YQ8>V70KC-wtx44LI8ee|xV1Pc_*|wsVz42R@ zmQSNQ{C^Vqd}Fbmcaqui;>u!q@k394Z1D@Ba|gNY}C*s-mFXKc5Yqy6D=IP)KKzA1Lp5mM~lH&!ySC zZmW6CadytcgjW}O9W_yH#|TtSm09qn7qmblaeqtqs*O@|Z0n%b@~6Ym(snBM>D59N z6x2&5VMrJO$TSh#io*>SwqnkkO{Jz-4C|q$Fbw)gt;?uO65Fc6R7T8p(NY~XFMKy# z^K_FkCQ9e$NdTWr2o+4r@ z5Gua$jk91>_jWVZ+IQ-*ZH(>i#j4j@Z@1SEthZY0c1tx4ttHoKOe?ysOv3@lq;>n~g~GTVoioBd-JM=Pxh_ODN& z_WJ&{sW^%;>g0Bf!XUD7Vk;hd~&AEZ5GhNe-7LXqF?Eg_N>gd7_kXxR3@h z6k(ISYS*(}hC5=i*8%#;Y`$1=P1`7yokX`aCkR)E?;fp2v8OrM&+|wtPOn$Se;3oO zj|OiUZcRp&H9d}Ae|;3|rNH;`W0TND9RqjXx|8rl6n|$yUfH=4%ziK3>q^3Q$$L#X z)5BhBsmlzIWYFvUvSUEQU>pE}bfw+j#;0Wh?)aPX6Wtyt2GW#55d1$5KK5xiK)Ui- z`DGFl?m3j+^C!jD_&!~kS9E;*Mh(6VbnK?d?=8Am5b166sxhJ8>+oGU0lko`AUeRq z>ApOT0h5D96Mx3T=;oKcGd*+c;V&OQ_Ascb_Vmo$^zj$RpPZS&cfB|_^U_ytx#i)9 z=f2dv@fP@dc<$(Vp4oh6m}{m!$6x$R_RAmn)#JxLN>KCv`-z#1|2zEEbU#v=aUdOg z9TIt+VYZF9@IY6zO~3JqW?;=-anltuY7j3JJazGk8-MXBW46&B)&N;RroXos<8LLX zZ8YmMGquDX|84WU?{pgxx!=kCx%B9?-2P@p1ejrdlBTry;X2wBhuvDXyK=S=zBf7- z>WwzH^vITtrXka++&(f*jggx+%C)8G%5-UyhW^A|oh>Cyy)#|uXpA-mZLd36K!esu zZd;ozNlcy6JW79eb4*h=htk!P^aep6l*UkcZ{X2UyVFlnCD-N?|4#N?pi!PC=4@`J zA-zFlp3;(vYUgv4eV|ODril<{vaI0@Rm)?e5Lo2 z1Au(`xgEJZxhrz7$&+YFJW1I@Zbj2{L)FUpLvA&8Y}2T@_+z@JRop{PJ+_^=>K@9M zHPv|OcgwBzwpP2EeW8DM9d7L#?0DPN?^um4Za#SJg`-kboB8dN?MXW*GQHaFtakM6 zd-rU6t7q??zuF4^uGD04hCg_^;AU^J;9SBNz0fOApVGz2`w z@F+gm)@OAxTWQYt0Z~O>bsT6b& zN_pd1qicG4VLR|Sl6-$E_c~=)nF04W0XS)t(1CL7$>sUV|JB%+z)NzK_4YZZ>YO^Y zcU4zaci+Cf)$QuLFTKvpow+xe$t07^mSso=0*T?55DkQvAdBn?VKEU53IxUB`4D7L z@BxYviGX=VgYYBb6PH9$1fINy`cU(GK>SVfzVB4`oq+m&ukU~Dt-7kt_MLAz=llMj zZu?TRXSz@c^v2h~*cCcy9#XzRhO=M@+yv^XJQgZUEB8qP<~>8cpGk~5?qsLRX$ZG0 zK~Ulb(agP09p@3;5jprEQ*@z-VlcjdLy5=0pn2~GRJ@3ukX8~%SN7)+C?)myjhU(h*+!9~n%EvzQ1<)ZX>ZBSk; z*FEVcUUhVEKG@f{Z!gXH?TWp4>O{vd#rmWY3VR};z_)x{-X?Xuy3wE~Utq-*NgoDRp4rqF}Shg#>>q7?)tugqu5>r(-6eIs5dpLyWSf)1s0?;+(%`a zF15fhOi3j-$z4t9aHg43a8vpW2EtfKC=oiM-!apIA-5}QmmIrf-sYlcSj!7@&4JVr zLkwf9X(&AtG_3W_OAT8`^ao4V=hvQTdOCm6xkgN|Z0KkXR4BV>oU&|Pgoev2P0!{L z@Gc|F8ffV1c+|fg1~Xs?F%K*eT5hl(*9{3(kyBc@o1L0svK7Ks)WSFw#EiU$*3bX# zUt=^1Ir5i($xeRmF#OGOmiTO3Xn=OHuW)hU#=?Ip{Dkrwh8Z$Yp_DlWWH%MXq&I&a z&7y}`c7c9yXvecvCNY!Y8<>eC_ww%JER3dnuwO))4EY`vDuV0&o9pRILoZ62i789v zY8beN5v^^m4eOn)?#p8`S07cAieZ+5v~|$|FN)@)2!s=cyna74%%l~^#qdbCM;02x z)y=h%8w7Knbx|t~N~Tdss>4Q=-T;4>)uX7YA^d<&-+u{->q+97fqn$$9MR4{#j3UC z&B3Alr~6BjKS=Lb*|Id>Z?={f+Kt+ODb1JQzlrvz(7$?AFPeTFHCwrrlau6Cue|SPzkB8{i`VSg=mEP3#u~9mb)Jn8?tTp zF=5f$iO)XA^!5Lx7fYdjp7U3M%F8*qwdj%FS!!}}UQjW)5kxNenzUd}Ozx1e{FIdR zR=qi;mwykRRN?*2p2-yFuQPxBjuc4zuYa95k3Ks2^cTPQigV|F_jk#en{S@n3I}_; zyQH#BmJX8L?MDypY;SLq;r3^?u_$BeH-ea!D}`f)Q-zyU-0#cxRseZBcY3`LQJu`- z#L@RcM>H4ZL6%191$Z33WSH9~8ONhB@1kT7<2FK(D{p@E4nZvbG5&v$>3bnxvk&yM zFwINz&82G>oAafjV>y;5OsW;5wVfBbmW?)xoy{UTb;?b7Z+L=1;J=k3@>W=;nLb% zXm4-Z;ar&;rfI|ri_K2g-afhQEG}f-;^8Yt|75!DPV41yqp@1A$NAe7HMSyr_UEF?V4*l9`(we&xv7Z)S_~o+bZybGqDo{ri)$EY$uogFg6Ny0DI~JV2MMVKmC8c3`o%1$ zRu?Lj8lfM1M0YrMba%U*I$#P5_z#@WZ*SkL$yy9I*13PbF?LR4U5E=j&*teBV3RTnCt_^bCS?vYS~5OQG+roiqm1ZuUs5cV4c zo%S-1B8-3Xu&Ypvi}3v0?~z-`?>t1=bIJEU^StMM=FHXbU_broTb?4?b3gOc^IrG7 z_g^!yuAb?Ggc*f3u#Rs5`BfnKp7jc%1E>i0e(#WqTT+|%dq^L#0{fX^6@dUs)ZX_% z*X*qYa z7TBju&ktPceWTmjFMs${P(oc@oqA{U!l`>Yv`xPNv%Fe3ShyT_EEMyD*_1~xk!i{W z3>1HkXYTx|(k@pB4pDwAhqCCVk(zj*>}4r=^2A*yPTci#_|iJ?-2MBXd*C_40|$mC z!o3-wl^uOSA5>YeGjIH?Qv`F&bwD%oM+XiFLl=a&f|F6iAGil%?iZXMy)sIZG1>C91UiRbBeL3V5u_7_e7 z2fG=lJFq5)T-|?p zJS}0IV^pB8VmBHB?}E~xq&^HupZ;sDKO7$FZq7;9nk#|~C(Hq@Rd8qS8QGyK)9Cpp z(Xpptn8y5-4IA_EGz|V7zk;89SJ(89@tB9wb-5nCC~KmLe}hoPn+E=$-~BAcrID=UK?1s$i7zt8g*FN88D`1oVG;jR-`w zy9jrSg-|M#tLDN6^FhCOKIp#p72XXJ?vcXdg|8OAS@_?De<^&o@O|7ViY~z3C{kIc zWa=t1>BAIuqw5+GVx?B81PM&fUai?k=0Ishk7+iY0#`Vaa3*U3R7l36I5!kTPb^0;Wy-|$)f~=T)5UKmm)hcf+lAR zZ*VF}sV6b&gOrv-bio(G6cr$R=lC&R;lh83W)uw!>gep#CjSI4;%ji+;bn3k(+?e5 zq`GC6JyEeO-Im$TQQLp9E74X2v}v=WaiiNbu7!RlziL)EcasjSTQMf{h}ovsP!1@HMrx+;NEb5|xY}Ee3x@;cdlLA1quZbjd;E zBtxTSh_dtj0=!${ns95yp*VQhEIs$Ibmsd`#{rdVhDE^EKv*N<)&g3S#TFv@V=(ME z*Y5>9S7=x5_@HyEc!=J4{{F|;!zc=W>UUoAq8Hu$+191$#?JFM2P6YtPoSvtg8UkHF zpkCEh(X$-9E0cI53+MvuUp~)kwZ^XBjASPagaKA7#v)gC<}Q4J>*Zw|0LhT0^<3=46I@-o414N+2{k}d}MnljrG5%^aR1VyjP!*03 z^Id<&e7mlMG0!z|Mr1Vn8%CI{3;{BY3Q zR_S2Z^QGkJnl&y$V=t(gLsFH_AR7+W2?pm+oo6v!D`o=5q>On!2)rI$MEZjclq1w` zM2d=bgFuyr33^!eun|f}j4$A$%s@Xovo(L7aJnqiOJ+C#^!<$Kp$hJ2yk;T`$K~Y^ zxVC}NG8M4QY|QN^q^VVy4JYaWre>qKq=vCuno3kk(G5Gp-8z_4p@t!tk~=nTT;L7{ zA|)|&#l(!In$0Zg%a}=q@#s2+A_@%7g#TDGE>x%?sX&i2g7v|OK}ur`jW9W!sOx`~ z7*(DcI?+k4$xv5*dYazG%}8LpAp>nj3T|WkCIPDu*AsE41CtaRTsf9G2&;)H>4=S9 z>6j4RwR8)8>EX0c7n5K9$57T?PnsTd4M)smD9q46(@|^rh!$lMPX~!4Y-jy23vJ!k zYs?O}x}4nf9Fu`{s#}4*Zy(yApW=Tov2Ec{vtmaeW~>;*C-*Zp`FV++p0wgnA_+Zw z6MQ>n3xuDA{yW7U${{kzTXNLQ$rA>?S?@55mMNVt3H;Hde;cCvpqTarHht&gr4Cm)_%sW2Oc3Zc(+%ZCiFu?gK@AL>MCX1M<2o=0VTYTgI;oi&j01ms1rr%M zqXGibk(f0^tHg}gkt9&plOgt=8i@FpQF|enBQ~4aqQiFpokhV&PM^zOx zj7SJ2NMpFD_H>GXN?_|^MDoP?5cGLu6{r+$D~G_%EsA8JJObgHxQUqorl#;r3>ZZ( z&?3V@1)BBP5@=J{%2LB;q2Yg7%PtfO1IdF-OuoX5ga;-_@I}IQfF0XFVPXljVI)7` z)Jwi5<=+L+pDNcWp?;%7>DyK?LIQ})V`_x#$v@+}WzNBi1;hC+VVS=5A^gd+rt;=n z^aF*P^8Bs93vj=(H&*GIC@Dt4ET9a^dc}NY*Fzm!qIILdKip6%dIWyj<8FCfvf(8F8%r4 z2t+b;x65oNb-iA>l#jPu$d$DShaN_oWPALrX}pq2bKWSMd(m~ zX}G%U%^5ECqb36fYP_(#+b+Z8 z0VN^3s96q#R(6=e?`>IzS3EG6M!=1R)s|BU7o3XOQ`oyz0|9#3RY|>=xGpoALFiw| zfiNtfb14Xx7Dj(>_FHbAB~x`1^dW{{px9`|(e#8-!q#*;xp^sx$g4Xqf6s}_pXd73 z>Wfcb{DwQ)BKUkU3W^wT{^3m}$t!p0U+IH)_SMj&~rTulr=Wd%jwo{b$Gr;MBc63B>>D6?PF(9{rVZ{?}=e1H7h)!mYw*Jvy88 z-FA1Kf-W_Fdid!>(mi_9s97!duL0x6j8v5M15jE;#U+ThvP;f}mC@NverE0T4W~EO z;bomRE7kJg89lHMkK8z33OBpxHvRy@8aTWHVqAaRzov8dvYXdAvDfjU=fKOUSq?S& z&4OKs3r)2ucyZxW;jGF&iV#>TKR75wLPZP@K`HFCX3n_c=_lxl#)d68A&v62!A$>w z6D}OyyBB$3HKR1^IaF(lc%QO_jZ$CYRYJ=AFV{@f>^dC+fNZaO(~+ zz6gIm*ykU&&HL|%Grrlb?o?YXcxhi1n)Z$j|7IxdSF1zn$E)D0IxgXaSGOy>c>Zps zy}j$mP&zR@cbyA9eLCYn<*2c{nDI>ImPUb!@b#~gjmei7 z$NX`}HhLS2$Cr*RU0Ba8!aS)Lw$L^Si@1Ljq2j2N+e0z;W5c}8slvQTX28GbgM#Z7 z2xgm(#!oP--XU)VX}eU>1vBfVXmW@8#;!6;tq>G|N6{~<9c;YyvJ-#KLw>NjLRQrk zi^YJsUDuf0K{V2iJq^Awa=l$yDS<`PSt;#X4nnJRw$!?4Z*KoKJFe<{M6DKQGhlyz zg|1THTOFZkCX`z+f;=T7h3=H9Orh$~wu+O2L*&WBMT5C5wAZzj_TaYu#*ze8tkg0< z_-&(+0grz=r84$i@X+29Jh_zV?KzO(7W{O3EEhc+Wa>Jk zI2gtwO!sf}nXXQss9TTZQn8_?$>{pgr zJ4bB0f*CKh&grCC>Xt5DEYB0ys+Yl>e(uGWPd+LgKX5Git*f?AT=Ie|S}=cj+9zLn z{l!;D?xjA}gq~<0z2Q{JH+=yE&hsz}E-$_4#IyWX*V*4O5usdf1_yU5iG$91mDT>w z_@V8^%KXgdf5dP{Tq^&$R$;l&FC0`@Ubs8i&4#-~j7P%_lFTsnqkr=Oj6g6A8R>S$ z{RSC_WSo#kjvs&DX-ZB{u7rP=AGM2?RkWY}N-nx~vqF>GNtB)c>a|-HlH5M|R`y!@ z$l9n!=H^!4wngWvYwt7QblZNh%-`~FF$k$arWoEXRmbb(X}N5X=m#+RQ&fM@3*xLh$iNVB zg1`*N-Oai~JQ=vs)4&#zamCNJDyz0jQPPXjqfPH`U;F?WONMWIBy$VFg;pSKT*o*Yo3VdPB`YU9MFfVRJ-=J&{ zshnQ`ty4->-VdO;u~@k_4EE5_hSTYTQPV23M2=yM@&MO%`)!Qh=ws}FN@2m7BWty+ zk-h50N4l|P$+WYY){pKxcewr+#mKH~4kQmWmTra8=YEiBkr{tTALM9jXT(C>Z;OIb z4ECsD%(-Sf$4rxzF|dhht~5<5yzTyStLknZ%y!zXwA;RXJen&TkxYL2;BiT5Q`0+) z%z@Pt6b;iR9P|Lcy%3dj*=7Q)AFy|!2e~Ev0CWHi?Ohd^rrDcuWwr+Qr_3wNBd)$m z!KB>PVx`r%YB7IqG|rUMVv>Vnxpe03Kbi)IDB8l*GP4EdSY`Lcbq$(z*Jsq1P4mkj zLC!0@Ih8PjoVaJ9WH!e@AYsb$lrt3FP?!TZnFBdQe%OD$0 zmrUbXE}MU8@|vOYk$~Tkyz4BPhWRV74CMlvlYQ>}rK~!YE{IOomTCmo$}ZR`xPp|! zAN2RYh1AZJ+M-Z-qjMw-)*X;l)HiL-(CiS+)R@bX-SF+6lf@Ogq17}_F~6D+6`pCn zb~K|JrZ_Ue?qGI!WJ~yZtOEQq$(RnDH3-}gOdWr5__Dz=lbO7JN7Go~*hM30fPJ!j z;G#`kcWWi1W08m#4t0x5!?CFZ!(9grx+2ZCWGivgUP25XVQ^k^8-^Z3 zy=(?*InlcZmX~>GMy4OYjNx|gm{<2k>#PO_s?9d`nXzZ0U-*g!W~9NmL2Huxy07)F zXXt;oVWK}ewe>0xfmI23jhhwC*LlqWyJ2rH_DXi*X}L<1sM`_I7~dNt>XJ7-_89rx zV~?Fb^w?uhJ@y!V?8pA-Hhhv3@bW=;UeLv!O&A>>Q`)O+HT49ol~|qH4OLejD{cDB^W0l`YI?TrvF4>U~R~ z*GR3ypOhV@>87kW-x&supjie)AM}z)W3(fmXf(cJFDCEu);HuGp3CX+9s^!w`#1S4 zTQcwTU7cOLPnw>8GkIij@$;JP9lO(?mmpduk6cGh-@n`UO?uq4&GOAlk1kz3SGs@w z=+SnEYA-y-1k;x~+OW5}O7xq~n6h&rKc8EO3R&TbeC<3@VL!;wr;$J7VpPhV>A`MC z=Xx~>lrzho?G|HZfGPjNwbE=vp8R-cpI{pu=CH{xZfvh-F}XoH%(5oGWSi_b10K9$ zfyi6hZ6R`)kQ(zFjZSDc?PH+YFlT=~<<8_>wqIul*2qnZ`^4n$J{~%f++drgz3&t- zC@lukX>7#k=*uij9Tlyu^veMaZxc*9Pir4W1Rcj+njZ{mQX>X~h)KS8sPegAXM-K8 zmT`t^R}gc6V{@s3s6n-0LjK+>mApv_UdiI`9vz?cI!m2--wZ0ioE<}0zFB{c4eq)@ zQ0mP2^Yz&Dq@{z#u;4LgdZmYVnLNI^`DcIjxFL*EJt>w;FtLFn0vFUQrbEN2EY;(= z%rSM#5suI_S8c~WrWoXdtPU26SEzyww^cY?xC(5z8$kzpN#X9oI}7hCe6sN03*RdI zGto(#jK~pkEqN~aaq>Fyv*dpRq%bPpO5aZ3MSq<>NPmz1 zDSe!Ng?^2Gm!1c0q0Hu4n|0ZU9b|{uF?NDo!(PSS!rsn)j-6xgVIN>0X1~E6WWUe; zf_;U3jXkB+wYIjTU8bGXenfkr_G;~J?Ty-7w2x>X(|%L?ZSD89C$xXR*1n-z`nrBf zKck=3e@1_o{t^9S`tRvq)W58MRsV+mZ4Tx1v1iL1o5;sxSG;&tMU;w|E>;)CKd;t#~*;;+U35&vpb zjgGNw95jv?mm60bHyVGp8ZS0pW!z)@obevxSB#GtzhnHN@tE;tMRw#^9+H>KE9Eog&GI&RhkS{At$c&LSN^O#Cx2CbN`689iToS+ck=J$lkzE( z<+z+%=}@4HO_a99)~K&+iA-RK(}nEe9-_aE6izNqC(LxKyxD)d!*N&n$m3U38q#qL zOfQ2wCLV%@qobE7_2Y3KQHXj&Z;Ytc9)c)=;fTqwlwyGzNtW)?9@Ztno%VJYPseML zT#XyzM&dvnOAzg8r0gOD{pVsZ-G4z})p^46?WnX3zl@X$vpF1$MM;2hg!@zNY#<_R zOE1PN$GL5#YJq?2oB3YJs1!%NKAgUljj`wmZbe5}VPHR*KubB9-mlw6Y&(G$V%>n* zWN;M2Pm>5;*waxLQ_7FT=Ae%qBUIp7+#SXFq7rl+Muedjn;@p=G^61tQ`Zcqy$4OC z{g{_9^Z;G5qb?$@bb%M-cY!hyq;H%I#?UNWh4g3_>M?&DE1Pi!af&EH8(@c|Y8iJl zL=4q=r6wbGDzI0D{(A`jv9r}HDwLaBW8AL9J7Ox9@c>olC>DsT1l$#4*V+o(WD0nS zPQyqiC6OXf5neMIcYBdY`(xBM;n_ten-Q8mSu)7_qJ+`(Ne0cq9jXzOmm?-2tPEVg zc?gcCJR5%{qht$z0tjlfK(!%;U@)f)`QQ40MVl_CrVhwu0~ z!APJU5}%7MD($?paZxR|=HdEre*_8_RIi_80tJ6khR}&wg0xv9crQ@Vqj4YF*xf3L z;UMitFrZMe5l%2Hx4Q|_Z4C1=#S}Qo^)-jC#V|t5N;k?tUO>x57n9@RG$}z>rJ3kL zPY=LS$`$*5uI}NaRw>bOxxbxpq@FHxk!tDI0LBYcS}P;&3U-8LM=4T^J2C zs``IOS1KSWLyAlyq@yl$!&nUwT-p@~Gpm|;ko2L91iHQ(4Z3Qeu#Yji0QV(fnhyuc z%79koT@0i!j<&iu!x5GiVJ3UysLi|BF`%oC`6%wA2OEyP8m3V;&ND5ciYpZGbvN5W zdRJ?@Kz2YZiQ2+YF`r;V&f~}-S>Wb|3KD-pvk8|=RTn^;a3*I%98C<$8Y`RzMW$oS z*DwYtjDs@j<2=AnAl$|V!sKZ-Oh604a^Yf}tLc8SlVcNa;GNq*c-;g^q=8}RNeMT2 zM|tzdTlpS3cFSlO?~n|^d0+@}fhcjJj!zUtnM(U>oCT%6_vhv}veJGUa~_*Jpn!7Z$b= zm}qx^5Je+IbyjUFVNHRLr2?4+j55gG0s0(f7=M^-CjE8;y#wI4oM(#Zfvmgao3gZRzp>|F%#V~)~0~(VlbtVy5 zPpD=Jv_9SjyNdR)uaZ7;3>;BKGkwfV1{?s0x|FmPsrENju3k*ji<$+d1??l}gi_TM z?)HK9fK6jO$126Sxo6uPPy z(z^*YtY+Xk43`NV*U*2su4{aeao;DtuTjYn*Nzdxry5%(8b{n@(^D|aw!?I(8wO^B z#HHO^aew_xeA*fRh8iiQo5zP~->gdFBGh9kRVbliqd2kL_|&@Vst#7t>_Q^&&rmWB0M? zk8BFRh4)d8GE{xlDETdQy{>-NR9)Up$T{*GnAk+MQOrP+nC265ADpWBfiUd-%41ml zt9yNhEK&CJ@bOs~EC(a6u#}i?L30tYYeYy~gdbD`OIUw3O?x&WpC#ny35DyFSCGm> zij9F%KZ6y6mk*E*cDaI#QkSVUP&Lj|ASvt|_At$VB02Kfm%xrB4^X;@cUdIwfMcJY zcGT~vI>Ynz4ABb|iKtFs%!RkZSlZ{Y?m2>64ln(2)k_qqmyx`m(USy+2_XSE7W6hk z-jX*PsYrj_Q&B!N>nMCUM3s|*uBy*;_D)J?gQwu%nJNxmpcbDbpnxj$0-|sf)SEsX zPaIxo5&2`d@=MdwDES9V^AATrjD5a1#H0Btlgw>_*1kOUOqNd7a>iL?PnN2{mJl1U^ene-%3%EB!_CO<+_CI$}{# zHjW$8=qIs-LV7d^N}w^if6MEwFQmLQyji1<%Q6HA8KzKp5Qu1~vL`slC61TNPnPI{NjR~otJ0Z}!R2d=c z5TpubaL^-0+bOYt4sBt|ccb6ePeDsGK~1UY3Fe0u?9*zVQsUS+#h^`ZlPDUvF>m$eG8=-bzJOgp^04_FJ0iiD|97WVJNG) z9>GuOzu#t<4;G#HfG91T)w*uu(3m=#Cq9g&>w;tv*n<%ZsgDI~Y6y|aX^7EoC{Sw{ zPfI|nq+BvCg<`KB%YEoH;xQr&l?odP+Xcz5&6Ozv>C+N|?0XI{dSb@D4a9!{eIm0E zDx@`<2!QX$5#BCC7KCXrFrjvoAQ3?0<$O^F9#jfubbkp*R8a!A?XZNr*NSZEtGTF2 zYQjA<#R&v0&Wk3_V5elc%*L5uGNY(@frKdwr!=Z%TQhQU7mnv@A*M3H=_;tOsm(k` zV8U-8VxS@Nt4`$$2O82Z_g*3xaQcx}om^}u1ry{rw^aXZ=#szXQVq*>qtPLq9ZcWon0b^hW zvIAX2>#K&_H8D$xG(E|Em+K5elr;mPG^8|j18GQN0h}ZnDq%|8S)hMk=&sOtnIR#9 zF6G#M%QuR|H#(W&0mE1pu2|%lJRI{~RDh67k7JOd3na&Ih6#xX@t0*CxY9<~N!nK2 z4KDV)%RqE!YW{OjH{0AZp@9aBFM4xA$Cq_Y0z2VIs!XMr)DR>M!zguAXZ}Bk&Ve21(2-Q7wlXZt8lhPhQCQG8 zPlUy}#VPY#=qh61{uj;>sWachNCiTCj$j^C!qH+;>1G(91@eE92^S+73?k|XGmSGG zH9X`JI0(Yfgw(02^6n6>%2660{u?G-i=MF_2xO+YHb-_&tI#W@nNJ*Ll zglSs_cAsM^bR=j}Nj1x$3s8bC(Z9^XNhL9FH0rII4MWcY=5a*i#%=&AHMr6cQ4`hi zMli2nQW{(n4v>Fj*#hoCn2UNcrpV$0Ygc5;JAp+4mSc0MVckUD#8GlltO@0NeWC@h-#oR zJwVkImag=9m`6Ix>#KBUBtC^!4BeseKtX39%ch;*EJ9GkLYuAMW7m8nHB<65RJ&(i1Tn(L%TsuBR-srQwo{2Kv{#i zUl|OijhMj%<_0?BV&o3UBBEwFSMwTXAjnx)7(f-k?gc7MP(@ui$17Yh8A8L82up$!&j?O{ilJ>WZzCFu*Jc21si|0Tuy$ zLYb}yTm^d+B_%4jn4Ajw)rMAaL>JR=j@rbq4G&$%2+9mm<|~L}%>)jixv0S+eh*&CmC~fZh;hht6@P$4sH{UwM2+y9s7RwsCF(@b zY&CzI2wzpqB-ougLPMji+aOJ}Pz}_`X|As2it@~Sn0?J(s5Z4QP(ioM^7N!Ut&V31 z8yb$Oob#r+*jbeib-B~I`44K0C?^O9?ocIY=a8- zvdP0_XaD}MSUyMqX0Wg}zp_!QrL9f3WP0T|a5>kFV7yiC+O=k9*hCvSoaL;3lJ$QJ z+l7;bn+iW(xEJ@|_CS6-GIj3C;TY-^gOijOQ*Y0saW7o8ulz63F?|+4haFJb1JoFg z@{Mwppgnh6R~T&Q5d}vuEuKn+GKCq6Xmv63=0J)7vDiFVObmbCVTcw^#avpYrXRG5 z2*gL7m29Jvy>i*DW-F&FwjV6%hdO^*2uhI`>3lWT3{ey&aBZ`L;9;)@Le>Lo zPG^zpOZz&mgMbPvuI0Lh()#IHDV%bYm=-sMSM_xCmjpevXt=Q~g^n$4%pa}0_MqF+ zba&&fjjQX6P@(IOw?k7auWs3)>4DAYhR4_HQWL@qvPNox2$sh@11@JWZpVLK$tfl` zo3@M1S_!RygEHWN3MS1NZF$TsgoQeyFwY!HRZprY0aonql1Lb=dXah=6h zF;z){Sw9)$zesUWIEBw?H--09^q-);#~0VT`?DH8vW-?vcg)rw?%P;4H{|k_raMiO zn{>O$1+)qKNcl*cHVjVx35=5ikzA!v+}-MR%MC`j1bYyYq(0&iQjMzTpgE?=IcWK74fcftYW5{1=`0sx=zFtLIwS?a6@nAL#4M< z1@0#hd?xAV_$WwHS<;@x9AE~a9f}c&n5{wWFhqM8rTrKvaEO0=O;;diQSK9lucK-9 z=V|nLH{;4>OgRQ)>9{bg!fqAci`7<>3NcSM_ha_FI>q!eIm(V=oKvqr-2U1od*G-e zmB5Dl1E>`1aR<@sg|8)KT%-DZOvm6gradoqaXrN}YFZn!y$?0bITiXfRKq}hGA*N~ zw<>`{%M!FO7KML2BE9tT^?Bs=`cirE{2%4(ca99opf5^RnU|?wyy6IX%6(1@N`q0f z*TuN)TbebG=p}zA0()7EJCRGv7yC|CHx9Wfx#3}uM251bK#MMI`*uh!vaFYu`C6@n zU|tc}1gHxE*EZUJJRh&k7txv<&25$qv?Op*iqzQ-m~4N=Mh!Gy$;uUDKAk(cRX6Ug z9uK{mCYqq@62tPWOsqE}H&hk@e}3YCRQF6cSW_`_&?Gkz4YFKJJPYowr(X3(P_|M& ztgaQ6iYBN-cGGg)Yofs@4WYTL=(|wrvB0a4%XQ6R0VoB!vuqhQFo+Pe9i7Ff`~=RX zu|ESw8Ek*qW~3X>@XXSoY^PgBU9vZ~J`Y{Msb?|kM#6R$D<-b-(SU=>$&$M45V{v9 zzJ9@XZJNt`NIFG_uuk`(@52Kq?&`^}k!uLN&@ZRoA#>kJzf9t-$)C~YuBFOw*WG z+&slNV2nR&V{Rpkj8>5ea22NiQVCUXA8Ci}k^%0@w0WnrH7qL7)LF6@-K?#0c2T$Nw6?>!p9w*sh<9?*TxMzNf zKm6u+co`(4%u}b~!MNv}kQ48xH#y)2HR{oKcVKt~+z) zJColc)Y`C`9p-c3V5AblnAXrr<&L>wMxn_qI!E7jm4p%|@z6AGF zq8nJam||5hH%F|88s-+UsNbw(xG^J#hr`J)4ToyG z)kl%8qwvW{sv`B&wCJf+q8MK=>cwQ^_8Xsd8@r0FUUy*M#f&&tnc?LJxUYZF8&5v_ zU9F75YD2p0rseCVs1={aj9S=md9w? zYY|L&FtvKGoG%gUBI{A2FvV7 z;j+Tdow~~G_4gXyhaN)`RAHNl+&m==rrmX@Pxe9XbrtNj0 z?5%MR8p}>iO2RM%`Fk#n$@fYn{m>6RhCBb}@p>vSNz!m0j9TdOI654~$Eg>P(4$^R zf=@|Kui;Yi^B>`ozG(7#a!aHKqT{J21W){FcSbi<^vIUoMT{MU|@bh8D`l) znI2N~<5J6#O}T$RaM?%5edPF`Ug12@?f|I;0Ox;*=Kufz000000HOfA0UiOy0<;8T z1e^s91vUk~20{jg2EYfV2tEkX2@DB(3JMDR3tkI+3#JR!3=j;G473dN4SEfl4k`}F z4+IY)4{8tI5EKwR5MmIn5YQ0l5h4<35`q%+6L=ID7Gi%F78kY{WEl<`02+!K*c*}? z)Eqb*cpR=A${gk$XdUVv93Ge+_#e6-`XEjs>?Bwu<|QU2LM31(m?!Wn!YlABDl9}S zWGt{P1}%gx8ZLq^>SwdNGS+-j)TU1;~T_jy(UKCzPUh;UHV_;-pV3^6p!f>7e1ek!B z3kVq){)71p04#0-jR1I@U68#_!!Q(uPujFVD*>~ZhBg5K4FmH0HR$}>Xm6cw(R-?H z>1m|ys+x_>jrEvp{Qo$PO=QSXphSflTbN)Q`>-D;;Q$WeWE{dNI2DqJCk#8d4%g!b z+=#=t2{)57h%h)2yoi_ZGG4)}cnz=P4ZMlB@HXDTF5U%00}cT~iw->`rWl|wLc_q~ zC>%TjB4(IlfhCUNJ-m;Tn}{t{IF5fd3TCaMin&ghxz3m$3l@bmMZ+3WMPugVvc{{5 z@A#fM3!ZZ}P`!d(+uK9DCq6W0u@m)PN}~R6E9XqfD3C*p)UWZqv>(^hV=Iv41B?;fV22CrnI?s!YdKw7HATGJy{h&lLIe z45!?)ti3S3y0J{6<8EgzPOR<0^nS@e~UO*%!T zpOFfb`B_)^xopE$NFHRXp0bdX4kl3<{Mx&Dn9FVwmi9j(W~qi^CIy*n7Ac>nzbiPK svrx^cvB^`}yi2*%Ng1EdjT6~I`D`(&e94^7mZQp!NxQM}FZAW48z~9T1^@s6 delta 33535 zcmV)KK)Sz#m;&6E0u*;oMn(Vu00000i(CK;00000(_oPlKYvGKZDDW#00D#m00Te( z016cOCE4a@YUZFG150EGkq001oj00LlED*ym&Z)0Hq0EILF00A-p00A-& zape+iVR&!=05*nb0000V0000W0&xlvZeeX@004%F0003V0005z-bxwFaBp*T004)m z000BB000G8vJ;2RlL!H6e=wi<1&|vBCV?731OQU94A%evc%1Fjg;pF{7)9ZY1R^BF z-QC^Y-Cc>hyE`#rL>cGR`b{!*YR_W~tgmj-f8TwfS1o`JKuuwCp%$uJsVeTMYAIf= zDqpXmbNPC0Ra5z%+Pq$A|Mjc5R^?j!Tw7kNi}%(R#X9QgNT*_7e-|3)N;ewmP7iw0 ztGH{VH+|?!Kl(F(fed0WLm0|1hBJbZjAArn7|S@uGl7XrDt?D4Ol2C=%kLS?WEQiT z!(8Sup9L&r5sO*EQkJp2*uR36tYS55Sj#%rvw@ApbrYM}!q#Hl#&&kFlU?j)4}00i zehzSuLmcJ^O&sMIf5$n&NltN^Go0ld=efW|E^(PFT;&?qxxr0tahp5b7YE@_x3; z`#B=}=8Ei_C$ew8$i4+4`xc7qTO_h?vB{}+XZ@I|66(ajqitJk@vTwD> zzBMX8|Ft5Gbs~-RB8?3qjg2CWO(KoWB8@E~jjbY$Z6b~BD&Mn1q_t=SA8VMA{cc+LuJymqprFMA}zH+Sf$d*G1YlMA|n++P6g7w?*1_MA~;n z+V@1-_eI(dMA{EU+K)uqk44%~e?;0(McU6q+RsJWFGSieMcU0G?N=i0*COpVBJH;# z?RO&W7LoRQk@g3X_D7Zf+dqk%^I7DaR*`eQh@A6PK>=nfKtm-&5c2uC6+D>MY;#|CV!K#tBHu!!X(7Lgdfm~m-)To-_Nt*|5w$z*SrjC0FFWT8 z4zErUtfS#**co=(33}nsorezH`FrxCvF+7cx4wGYRfBEY1{z~JS2)JIZ|HGO6;^!Z ze=uFs(=Aiao6d0CHlZ+qkR>=e-o5lSm-c$_(M9vk)!nN}t#N0wfBJQd38jpXTTf{k z;crB%hzQm3xB9Lo=$Roxm1iFXQJ|eP|JCVB?`o{y=QVR(_i<*nLoiqiv5$`0; zPJ8n;=(l&E{-{4%L!;p;>ef)$3wvGYTQ?g63&AZFiry9aCB;x2n_xjzKd=?cBmlk}Bn1pkcQ_49PNL3xrC{4AeW1D%bY-onp zzEBeap2by-6Uy}lZ|6n9454j}iftkE`MBDsTAX5Hho+_re$C4+EMsM}uB8>IKO_B%R1k%-nN4FD-+v;(Zre;y6HDa{1XyNb?WAsa5yzAMlGLOI%X)x>CqXC?2j8JJu8q# z)bU=Ie~}+~>eiKR6UK|044%1h@V9Pqbxm8^va%jx#7`X-Ol2kz2wGe}eaz&FLRhK3 zW63nu=2WJ+YN1^k72unSpsiG3DAVz@$f4X#xwqtgA4YRD1V&4lPL5m91sVuou7zZ< zsN*L=)PySm3`&rQAPR=-I1GC5OHzg($(w))f8la3=*jz_Y;QCKMwN1?I~)Pc42Ib} zETTRztz`COQUKP^VozdCe=P8i*pw zKn5AUDHw1Z@pJ?d4GyTQtF+(80%IB!4_=FbJTYZdm{{QR3-T>n$7OeU`C8)n zf8xOQrNZh?h2W)yojdG&({%DtWpx%~p$nz!Iy}r5YXoMCXpB~DhOZs8=Y^`&ZJ53! z&O<|>DTLb^2t>>=@l>Qx4u^149z;vqqYN0H?&>}DX~d8eomh1O3#-)_I*(o5G%$l1 zZcQEBQ*%}2JxeunDk9?am0Af?O;_iie~cV2!ql@mApaKn!;cWK@Byue zm(D-XjNk#>JV<49s=4|@Aq9Rh@IXWe%pEHklzASInxb+` zXrKTUN;Ojo?~5MtwZy%+(_fSOb!ZGz@tvu3kc;6zXiIvFdDLJgst zfu&gvIc88YKLuKd*rXgfWgs=h07BBraOzaptS>g||1S1c7(Cur1)1HG)+j zaaJwG+H8??+XMQL<1?t~z;M<@t;?5ApRh`XMog?iuQ0P5rfHNhRYgz&BT%k3^cQ4R z-Yo)N0A3-WMXAd{R}vgXfBEnV6)}S&s!C}_FxiMoHfYnxs5wvt5(em0UvpH&)r%_T zy^@0wErHyH!dx|gi$IRTvCye@s=3f;EY&~DfnSA!sew(RYFP0MEm_2DOMM082k<2( z&`J>Cp&Dz3%2f(&6xX@sykGEEpdSLOD$^xfWqRlkejFsI03DnFe@_LC0jS}GfMR5Q zN$62;0n}0o*g)_BRbmsZzjW`ZQ|s&Ns5qFeEo!Hg{*i_5iEj7AwI>T7njWABuDS{p z$L~NVu3DcSCgr)kTT-mm+cMmg)%eG`x!i%=VbI=v$+hx9BGuWTs4X1$z%Kv~M^VZN zn*genh-8!udg;3-f6_MyaQROBQ3uX{v$Zy#j1~{ZyA?;%d@Ia{#eB`}biKgZRe=Z0 zF3#3%Loq`1OwDMU?L|JlV781Jyr;W-rfar@Tjw?OlkHcn`F1Nl%uP&$rX{1=zMAK0 zTK;gu_Z+Kk7wlT6dhlSg+dOzM`@-!#UD>@1*L)iPC>OyGe+wm&y=4!B1vmy4?ZHUm zPdE2=e&y|N|CP?ZMssJf^S}Z4Xri*~z5RP{&*!f#gqJ>WX;_d|v2@J#15RQ&H5cY8 zxl5p4px4oWiJ;|i5sfB86pD67j6mJDC+bNwp))}UswR~ofH9g9OMtNHN`VN&oI-0^ zw1e;0qTtT)f6d=R&U*n?e$ny6TD9HM|7Fm;<%^j7HqfTY!?-jQl$C)j+m2*OAa=%p z7O7d-!WC?-GMv{nUv+esi3`!!8kZh+o|%ikmZ0~>>Uvx#7aCqcpLu|FPR7*O5E{#y z%k6p15Gw@Z7;uLfgNi|*=&F8;^7Wt8EKlkpQ2hn-f2X-keZPeNYi>(!NA6NdwaF&P zUO*CJr&>S)KiNkB)DtWY1xS+W3;^OO}7HC+I2M!Cqc52=9>*ahsKXc->f}yP_Uvsb!--agZGkWE= zs$qDle}$9NAW?Ph5d+Bb;=JSQ5ktIAP2H{^u9+|l7`Ht#{ucMB>g1DsF~IeZiEB=} zmd7zdD}_?aD!Qk6vG=PK-9f}({+UND;VEXxykz``m&7c1x?HyMs^`6)W$ciC*!VAVGr$e+ z0hN;-4|^JKTqVsF+iw*=8u2pm>IoQPstww3=HNo{Hj}asFbikuAjX(gjX@VZi<9jS zH36fOBM?1*UOuodjHnz`iKr$1Lg@^^YuJ{sY=VTkLsG?SIK7q@kK`pFy1}q2X+!== z^BYij1r5-5+e%qkS!@S0s_dM-KBz{OGMFPXJYqX8Z#s$&3=CJSMrG6;Fz2t(&KvH$ zaSh>(;ntmN*H!C31=zARCLs4z%z8~b5w^*6VrKV$G=_VL#a>p~%bo(lGJE=#MwOEB zx7V**M>}uckd4vd1*RlvIFM$tNH4+->7b(T zYXwSwqLNcn4P8A^X*MfV<8EGqjyZYj*_PRwSw6bt+GQ=AZM5>_;F~Yny|#UN%anTG z?%UhRY!JEz=BSk{U%WVoSL+oZV1zDO-$38+@!!5z3pm7zm2wrb059&_fb?Dba-M^|nM58v>b z9i`rdQ^PCg-KkF$|7hyu@tqa3e`J2)P?Fzy`LSYEZwD*mOMbHFU&!wLx8K!$vGmiX zUpsW>`pdfGGwWw=JQB`cHEQkZm(eYMtJmBx^M&X?mz{Y1_+|TN#*g)mU%ru_YR})$ z38H^Qg|+eb&dd25fKOj3`_yTthHqwF^)dknrimmPuz)h-OFF>X>6L7_JA;0bIWu^6 zvxFrJ(m)0dUPBYBU4R9IE@q0aXp4U0HT7uj#j=ff~sDo9)!UAwr0tN~^+bGRmxo{byuUcI3 zY>6)vJd6qy6hE)(`6CTKZ(9w!VAbaC8_cb&%#pRV!N$g5lTLS#o5AV>APH3G zvXM*c3;+8gXg}Kj$cvX=icVfiZhqw}$9F&ZBzog3$FF$Ld)_nYe|E2#^Imp$ zX9QBrgHafD&QG_UN3@r|isx>fnVA`EzNe?Bb3CIH!oXT<<)%Ro9L$}N)RIEuNMw$w6oyX7 zB&Cmz>}D?lZPZI?sFw;wIJRU2Wd~YC-6{sHF^fh4e_X?T&=`nJw{BnG-QPXmY7|0U z!^NmrZ*74S7re1OgXd#yR*UCi;@JN)1d*i*jfN~}`07nFwqq(lE9RES(KSw~rdgQo zUt3*Ml)>RD!l9ouF4}N?#*!nehPJlj0$QuR?P#}qbid0~O&I>cy`_R}MwNZSDNPk^ zannhnf8?vSelA7=^ZLV_mdiumeL!z6U<@z|`svXSfAgW=`@qNFd9d9&`|$ek(&OmG zH@yYDuu;8Y`-jG#I(i-(mPrX}kRzE(C27(GP7eGU0`8G^o(vwflLnG9bl@UVdgIkf zNiSF*HLg^GZ~XMk_^Z0Fgkld?)$#YyiRW(9e>u7VciwQdYA7f8e{hXz@QpL*ufVX{ zgU0pSbT>e)@w?AIpMuV70n)lG(|g$ql-018$CRlT^7S=SoZa0VX?2 z;}7YzR2gI!Kv)3wTin()LPMC0YE7?yLHnX#x&$PE8k=+5D%R*HI&-S#ulyeF7PmCa ze}cAlU~tL79s4fOS!@LEXFucAEy1@R7#zQ3Z~*rR)v?MkwQpn!wL$3Nkm~SDs33M# zjFp?g9My==q+Lo3rr#JSj0okuH^NUDpefgg&akFk2j`h`4tKWkSI+TK4mENe`x&p=seEHf0g3=tEAmNf%bv)hKdxHH9O1w zb0$`1$EPmO)Ra&4T&oF%Qx~peodMYN6MI0qNMSTnFa9jzDHLZR^$5B8)r(Q5ehncZ z%QHju6t`l<-L~&|zFM0p74uEE92?g7pUN?qFA*^V2e5A31e88iE0?M+`PSbre;#em zf;qdcVva$7cpd-+FyS$n^aU66p;Zxt>xxl}z!IWL1lF3TSSmJ3F=(JxS#UKhw+M~L zACu_Cb1O*`QAAc&_^GBR$0jxE$!qZ6{v0{=3ZT8$O3Nr!8tJ?9bG}>h-A<%1NxeKU zxGOt9vY6dezUhlsae`8HsGuPfR zdCPwTP8V!Pc-2l|Yk=ZRH>z60dO~rn)L&R~6+*!{FbeOpBGZhl-GKC{f1-!F%D^T~ z&STS^_KAw50_Sl zm+)4ltl(D$T7?CgqqaBlGo4x`KkTjTTpA{xGu^s4!n9i6Gm&v){AG~%mW)LPA__W8 z41h2P(eqC~@W9j9fBL=ee|;KGqW^fM@$=oQR0WodJHVRB`K5 z`Fgdr<#2yC%oD?&n%{rHu>*(qU2i9IOXGryjGE^(azE3a4bWESf74wBvH%9WVvIaHTQln8B8kZI6F*v#XiYVTw#ak6BKc za9Wtlsi@J31})?wf43oRLKHPR?x=xY7=IhD-E`;pv+(_@@sHnh@4feq|MG5Bf9qZ2 z7twdd-@fnH|F1f#z4SS>6Ima;_r814(p%p;{`Otx^~XH^KA;IuSHa;uZ@cHbJeVgtB>DrBNdTrAbw=og(_eq66aBf*wIBRzvi+&r17E-9ny)`l zlg1n7_z~~bldK(WEl%otm@G8l z@l1vtYZXQDLT+7y(V2U~*>eBR+h>(eBg(XZsZ?QH(~Z1lJ?WE19)f>@(W_vvlqqw| zf;hk_lK;3hrwNUT<4(y5+(O|zzkHe8o#x^+K!zs(=-gqO+&#W^zA?UzJb~wk({9ZF zpWni#8uRD0`c60SUm@CpOKty9bm9l&C!DuCKa7lX!c zM-10o&Ig28MN?~eFw3r4F8`oX`5q7_U@?PvW%CcPP|*)ng#*NY2;r@i0Z_w?{RZbs zv6Od8MGbzvt1Ev)_g=v2c_+YRKseM%(s-WYL#00~m;X^E0zpx)X#W9IjLA6k$(G!8 zX^dyW-ZI5%2(UZJ;vPvm?Q&-VKZ6W*MX5JmrbjHNG009LgOaI8>y7|BOpwJI24NzL zW+9Xv2p)jvL_w?vC5v#TQ%xF1x`;i(5A267da7+342*v|u)7$@3}ay`c1%u+VZm|A zHbaERs#Y(Q9aC+$_BE#{*>%hAS6p=9b)oP3*BvlS0+QkyxYJo2t{H}60X%79O0j8> zd>$+)FoQ5L>J8{dsmep%=t%!jOpwNSp-e$!*%cp#aJvxLin_aYAK!cX#g-k%w;sOe zqJy`_OtXIu`ZlQPAk{DbgB%B}5$8I&<=oNS>D;Zkx8#0`6cnNX+KVnjuSUOwei^+H zy#w8c-Y0F%bX?>B3TZTU{IEOh%e;_IYDM#jFE8q+nI8bXqAr{c{3MH!ahZw)=zq|bIZ9x}cBOiS!IUs@f;AEbB| z@;t*prwX<#lLrid5vFOflWA&L0j0+TRWWHY6~s5-1dU@<2J4C;W-|0)PB~#%cSTbJ z(cl?ERW0e5Dj~p33Vq(;UPyvu!SX3f-eFfts%RIcTt(RV?Is7A4ON&}vkc1yiEQeM z%?%VfF@pq0v`f1 z7!>2<#X`|{RUzuWR}y4JHO)LgrDeKa*F3o zQ0zibN}62NFvv|NOlWkMGX>bxG%$izX(0P4Hu6Mf)gU}B5yqHl>9n$J1xB3T=NS~r zXrM~gYr|QC5)m75U@+z@C5smorkSleT-`F-*QNwJR6de%LezYqH=R;6NT`&|Ds;(z zCC{&0y6r1z;%=bdZ0jX z&b^hIi>XYACL%t|O3eT(h!d$JQ%M9u<8lzHh`yg71>^6g$A3W(yw0TAhbQInjZU#i zDb=;<=>^m%&ex1f1to??#lr|GmF2^`ddrHkuyoOd-NdE8xpQt>(H9HbXAWF{ujp&D z7bx~sh!5)P7LN*3Qv+PZ2F3=NmR~xVAvz`+xg0bn&?#usVs%uUCOmNzUiO9ID9i1g z8t9R=J=@nUqZv)Je?{%2w{NYU2y@@s$@Mv4>_0+8kVaFn){Bd}^+Xlu9^XTF_|(!JiC&+9YQ&J>N^D0}RCLa+o<60WoA^aTTTc4P6-{KYyH^ zADjd5`1Y3N0{Q-FxP5y;X4;w8sQ;{LYC+(a>aesHIxgS0l%N3Skg!m@RX9 zYa6CfcvT}Hq$L{GrIw-?Ht1F0cAeA;reTIhyO&;hGcXXXbUGEvH@^klvAnIav3oR_ z-n0j=2b?*bJDj^kqRcc}HtA#L{Tnr2rd86Y87QjF5Mz*?mBYJvOS97*rim)6sMYIc z+4nH$siKg&D5K`IZqS#1=({S>Y62Q6fU6n1JZiZJZT zm$+NUoT!4k&9bcmSRmMRKvELCdm5zF(U>7*fK2jiX-?4DEg(b;CYa%jN(fO@ zCo14k)vE>7QZFeqYwfvivMVX{D$Quh)Pe7TF_24jhDVmtaq~uhXz2tbzUA4TS8!pb zi+d>uLbfxJDTHwl*Gxrto>0JWX=y(K~I4%Vkzfmc`l` zwo@l!8k4k82Hl$}^p)WYpj??A2n-FTWSIIDWgyq<)=+okNp&2!ateZkj1oT>kkmHl zcoH{u291D#tP)Ir5#AXqNKJaV)FR>b&871RnvwUSqVU=O41GS_wd)9OhlI1$|>E;*hDwTyycnacQfS=ib%vYSVN+R zKTAZQnVM#Qf-1Khzm}%>7$PrJVn=>d3>=T@K-0Po@IfFJ85WfxcLH=6F=^rQSd<6l zn8zHHI5DVWVGOXwWI7g_;lwd$F~`MaX4zAOmsdogRH*rytz#ASt7^^EL8HQaz>R<{ zUVJC|9wK0<77Lu(r6_8`gDeingA?jX@;9|Wt~&vLB;qlm5ZwpmX?kF(s|xXAU=Hd3 z5150j7)mkUBP_6~$DpZoKq{Khi<+SsagryB%8b}46dT2|DHcG0F#r&Rs=$ZpoTdeK zkOx?0y0Km!gWr40!p!MYXnpCS3e447S@|9dv@s3 z{SJ6f6XcVhG{gza8CW%x>&Bv?DMB?YWuQ^Zd=NSuAG_$#5meV=^|T8nE+M9>@pv)T zeSHzIG$!}=D^SyRSk#FZdeMO_Jk11 zIe!-Z9>@|iSI#waGcYnakf=81$zVI`tpTW!VX*`VUFPa19U+gTaWIR9q`w?Msa23T z=!fKKNm{S-zjyk9eZ6e0NDxct0t`&p9DU6K3sZ0&$FE&;Xbnd=kI{Q-b4L zfHNp-a-q{AqGnN73{vCLd4ulUDRa$#VXg>n6bztpBD18ZLdo%sS~vDurAC+s)4^~- zp%4``MAI)yKuBbsa!m*B1+clhM#WXM05~u(Jq+N}1+ssa!{=RyI0%K|J2 z#aO^-*)e&|pI_VRcK7#sU-|QEr+U48z22AqVsdcaM>UVXoU7&rxox23 za!9V*2%{tg4*exRT@{qb6;pD5g@@ElQbG>eonDlc41hL9EfCwi0BBTxqbI;p7@nMc z9kEtnD&d2?u5dbvP8V7?YMy@P!0sD~xnKr~Q(oN;F#M^-7tSs|ioaaCvGj_&E+pZ} zQ2OEEi;$`NrqcI#7jY}rgXHzsko?|!tydzmmtKzl`#$mnLi@->Ps<#C$tYKZajd7= z`WworYdi1WxR-b=<_36j9fB*a2H+}N>anNZP=CPK`wFZ$SXW=F;P<^)C(Uq&5 zx}D*u2MQv74N_y3qsw)FtG4aHmhgfrP941PaNoW{+xEoAFGEft@tpBLBh54H$kZz6 z?s4_-?u(A@+|-}*grqT!Tq})m25rXJT=kLGa4JL~E|biKo5#CY2i8BL`RSxWd98 zHbK>D>m)$Y>}xffR|-x{Y){i&n`(E~ruQ}uAAFBp3{({qU3+AoR%cYDthACjo)rVjL8X6+A3}J?U_|gyVM@(PTrSEPx2qfaS=ltk& zM5s)og@Zd7vH^dye`gq6Bpatp&o))V^Z?{=yb^;LT%OQD1^*cAG$ZE$o##*{i~O)9 z(}WssM|1>?O3!WJ_eG7yVxuwsQ}oU83i|lXyMN6O|9$Au@nfjGDUX)O7NE6mZh!9LR2NA}G@BVK74)(|f4!7R?Ez{|QY6!~Xz3gs zHD!W%X8^|mj5NT%Sz!J_XS20rp3OI|+}fygFF$l5+%1>MqRqJ!rI(GK&UEXP_>dOAt)V%JA?MLfA z(~j-D0;Z)YN!0P*w30jSyCZ4gz1w%l@6!OY&@-5z*wF6t;`pWdY#shy76%3S5$Be2 zJ0yptUgJqBa@yx%rbAJh5h|AvMAEDP{Tc?@s&SJHFc@5Yc{Kif+OJ>RdvqPJ-$G)D zc+c(olGQ2H+_qPKuHrZ4cP*L2+08rBdygL&KaN_>+kflV_M$mYZ7$K3t^01@jovo7 zYunx($wVeCkgZ^UzFE#0v~)OGWGr8kfH0Q=?pTu0SbzwijBs!`q#aB%I+6 z)I%>E-Lq?YW@dWVo`*LNio|Zt6)FR-rPsZRW zx0`_R8#Hy*@rIplEdkpnC-bskoOYAf0sG?yc4r{Q*KV5f=N(q+=lI6aHS4>F#GKa(LdCVzA{H_FHY zC^lNh0QK;j5D45lm`hZNTr912cu`l=CL6 z1DncK;E2VF116gW3PtRp#DAuWkp>CW9d4fD5V5(L2y`!KcwxzuIgFh=29v`xLa^<} z4_g#zG6eu$rfGAg8hIUBfvEIdGfY6^BcyGK+_jqRS}Fr^flNRWh!Pgry4wi7C~x@` z8Xw5Cv1QFQ?op-3OrZnW7e$|wD{`gb!W305P6i7}2`W|9B8aI9BY(kCB4Ypu3^K)B zhEmvsL1q|n4GCn}V3W08hp@>DN)?`iaA*sQ$OT#EQK*ax=_Df5HXLgz*3>#ag?$-= z7D0&$0qtjm97EUN%DE-eQpG>tvXq246 zw{abYy#{UB2F;@-3(kH5jkpTe0Yt7{4)9RY3#JPg$rNX$WE(C*|BB?Ib|c8=!+fEYczsY?m_gA^^udZITN0bCnRQsrbgbrNL-n+?oQ`G!O3#tuQ&c17ya z%X|m$gibGz?iK~$ z1MrCyH6v)qI*OpJ$zT-)(2uqhZg4v}2+X+U*y}D|D71fTMiGdcnH<f_%>_(HfLHs$(YEC%-zg=?gW&{y|dh<80;8w^<%J@1poV65Ni zN~?eAJztl>P`uKM&Crv9XveBnrO1$@ADZ!N#Q-}4LavLA&?9v7D)9p&?ydkGfCvUl z#MT2f@jO>7Ev&{dH*AHQd94j66vuDrdDF0U7RRfnnyqA!FRF>^HBSxVt_kLI%Xbvv zdTlLla%ZVPRu`7i>w#{cw@ncKXR!KWi7bCTKh5arFCYuEbH_~!bzc7CxyH)IN+bP1 z?eoX?zwGqu8knq=H$j`LYx!f!KMM7n+BMQ9UpcnPXU?e@IC_b|oU;p5z zAARI_Owa@4+aG)oh0cq=anHGSgDOe$SONHrRNPRpi@t;M$a(3TC_nz8eB(I`5>H4u^leH5tqR@tld}Q5)~0`-h9f^)IW&v}|g|&iRU8 z$cKTVRpQLB{&m6oUHXS$%^^TDp9U556U8fZ^w zrLZE`WacvSm~Z*Pv90ic*>dSjKlT+Ki{hD*v(o_>*cvu=>}g1B6`KL4>C#^(yC#7+Nujxf>Fb+eZMnh`lwcaN^?RdC)MV>OqE5JJZO8;PNxFZdHJl2miJ6|J zBS>fcxv*R4wG+AGG~4q@h9$a#Q9pfDw3(kEg2^&UnIA3l*Cs*`to<}s%|^}1wr)VL z0Nugv?D|0sRdF}mbdP1;0X&pxb*Dr{D3Ru0?3+~}699G+v4hf~##T&~hF=sRmm0|o zgl2F@Ce=U@_;}7#<>G%w!;a)iR-tLLOT{VACSaaB+yppPu3_pCEGn((Vi^?VZ%Tlv zs&T#q*b1l@e(HuZFH?eSAOND~U6sTl&rHyRAZ)?d_x9x%YjS@Z`9KvKAp{j>nai9S zxr-NJ0Pi1C66NY%K#(U}#sK95bmjF66#|M(AxcB7d50GbsF;7vFIP=CtEpxT%9>dN z%iZUO$3kKlLU#>N<$36RT;z31N{b5tR45mA5W}oeszD=G0HG^P#N}zfg)WnNLyf9i zrQ(#sW{_&WFtCfDV-zK<$>0f*>BJs_^hysD+Qz;>9{5 zh77y_hl-f5IU9d=={lLTaq67E>Nx~d3KDA%dIea|*Pu6`x1!%gA4b2A9z}nQK7;-g z{TX@|{T2FK^k30)=%3J!0b9P5+Am-=U<-SIXd_&}F|Ohop298M#HTZgbGrk4C9={Qv#c#sD4mLK~oAEvP?fAFwz4$%&0sJ6- z2tSPfE&eF}7=8?Y0)GxP8uRyb2Elv@z_s=_Yt#;AR`H4cuV_r=ex8 zlmG))GSx8Zf#i_w@Ak=1RxCGtYr{hU{BAy_1@PESUDrGqw6jVhI5$YoL6KybJy@0p z!yl>%(`rO=t`qHqgnvA6{RAE^Z%k&=(%lfrbKQO<2UOkzH3IaBDsqz_G3dw+wnrct zqG2s_Ms?E)L4vRz6ptHmUNm4L!)46Aa7af zB|S0uksf#9Bdsz$lK1XFOFwf?RuYIrhQbCml4r7G(Oph@>&Rag@MO6jVYCeI!4g~w z*3puHU+^Wbp8JAQPy^i7nVjhG^c(xjgJ86r7C2WLqZe@M+DI(})e|`N-6PZlfsqiZ z2%*w6AE8Q!3N2UWFfu(=90ak&ZvlpEf0(kTChn2R3*+YrSPvN)aoAgt}bbWJ<&K zM?V6ym5zVF=^30>csKm|k7z|!KmK!y|AgNHR>(nd1xBw2L-#%?!N3CAfJc3nQE1=7 zN@Kina5osxZ1+Bb9>dkMx5=j=v_a+oc9yX<; zTV;YJR47%Al5R&LrRZ^j=H+%o<7ZABI|_GQd{`Pxhve=1Da;Jn3qLPdlfQrKPPuhV zww?o_Uy>g(7oU(IFLg~iS(A$>EjNUNC3p{MH@i*B@pBa*dKiztpIY;20wn*eip7Ia z1g^h-{sd|AMVyw>gbg>+nk;DKbr|Z9Nx|PP(+LGld}OlEp@6b#3c6U0G~qm3l}X=t zFCqJ2MA#Zj8-kvK2@&I?SeQEH=vtXvDa80ou_33&gULdN9{}}6{)UY$shd|>nzuxU&R$encPQ*Z|qRX@uK|D2rW2l)F$q`noQ91a>wlX(Cg96v1NbH;C~1HmZ( z|L+ERKp}Y$Ag81t+=7vLH&pg_H~k95&!t|DGA|REmh%**>#2w41$g57lj%Ske-Gg9 zKqi$s@}~4o=^`X@XADqV838PDITybMN0-9b;qTVRUq2xy=J>IrDG{K9`_jouKLIbn zu&q5UkN%QEi!?Vd~}&(b&1GyBi(Kd`p*qJ4KHQ~S?$8cPei zmKM)87V$#TSXh2y|JirHDQRQ0f5-a$XZNEoEp4O)ns9CB!sc2N8RM6`e=-QS=SIB| ze0Bs{zvix+58Zs%HJ>16JT}nqjyuMGfnJE8n%ItGQcPE4yE5<&w@pGK_@38a@rm(I zUuTTU-Jl&Ul4$%FSA7yWXuI0Z*JG=(X?wnx97$IIa&c}$m*!$-_zgRQY_}_!vYVxi z$;CQg`=J*NziE1|qZn32e}fM8!ULYw11#!nv^qxQDwerJXDGT{SY)eamyCQNx^#Lz z^t=mp1vk9$%qar$SzQkTT*xb?x_q&g=hqxp)$?oP=JB(+Fjve~bF*p0Wpg9Fp4Eh>x^pQ<)fG1VA%%1}&5a3>+(I_}ov8aub@=_W8e}3n89$j3VTU>JUDeJ0kZp>|~28!t?&Nbbrxw;{!RQYuOG}QPA$6&xTH2bnQtf zq%+A6ly^Kwm@W6`f6{DTx7EDnI6G%z!mA6tj+&^pV+5+E$}D)(3tFI&xTSm5MkzVA zbx>>h)8S}oJC*zNYM}}W>LrsfB#Z!Lnuu-1;RXv^G3U*uQd2C3^-xn727RQ~Wz;2! zZB=0^BWAm3sg9Z#z8kK2x=9%mrStP7fKR4!o!n|}U+$Nre^n=A8xkhWU@E7+}Q$&l&NOrE&*vJa`snctVhBxc)|cz$o)id z+*`fy`44asn&fMqB4R2KD&BhQEEv_j-Hf&Now{rrW4n8?>b2I}?ezoet=78TQcXi^ z$#ojjimofue{cXYX&%Js{e+*QoG*s?TVDD*Fc&B@^*kr<$`zJG1%^}4Ih3DpoYx~llYB)9Q=(%)!(bc$fpn$a-^Qn90`Bb-t))B*7$y1nOAgt{ALZl4Rq|5$?q+?0AfI$zgQ6IZS$%zq2KHAJ(Ji)A%EH< z=(d->H9d3ekuM%U_6Vq|_Vmo$^zj$RAD@}Q_q;ea^U{}Yzx|O%=DyIq`F8mC$lTHM zJhS=CFxO0djz9mI>=!@s%g2v>n4sqW_Y*T2|F`(d>3*a#<3Kw0IwbNs!)zOI;eoDb zn||XJ&A^(w;+89B)F56ccCtJq{mqOBFvI*LO=Cz?*{fWCeTS}OEXS&kS7;OsLUU#s72Cb3Ywl-Umm^!6-lz;B#n5J$H zrK>0D4T3%>jiL13z@wvfr=O%suFWU@o$R?lqdZN_+1yM+dV|P3r6o08x-|}rL~f$* zC@=umtDY7V&3_;d;?1 z=plFx#ah`gRX|XlDnqGA(PTh0HcNHWP%JGkK(4=pJ{+5I0QL<)Y;#%|_X#8o28OB0 z;2)P;fWup^q}I8kfa=j*ka8+OSvxLg8QKzDYO+5jR#{$?fKSC0z<gn zh>RDfpj^7RfPW03u**rwm4QR5Ar*Vj+*J+BR2Q8{ed@gsQ3eJB-iC5gc>--ykc6?o9e!hY3Z_MMl@w*#Kfa(@cKK&F)|Ni=^$v>ja zH^w)oCGTs@kMqkrmzOt|o*fJh!(X@i-9b2r;Vqk(`AY922LSo>b31Z-a#!T8&6CPV zJW07jZbj2{L)FUpLvA&8Y}2T@_#?WeRop{PJ+_^=>K@9MHPv|Ocgn5ywpP2EeW8DM zJ#OtA?0D1FZ&{5lZa#F~g`-kboB6GiPf0r{G`-sHtakM6d-rU6qi64)zuF4^_-I*E zmZNg-oDB6r?wo?Lxh;3Mld?%x0RWQ;N-lr?fA#e)aE@K&ouJM+_nvd#_exjyUP+}Y zm86nX?}t>9x?SDXcDLJZyI9^C zNpJjF8oNTLorjcfvEeKj0-HcRoyS6lY2`ji#Jp$7_cMvHfSv5Nb{fK#B?(Gw5Y61{ z^l_Qc6T!iEyP7UEQS6Q{;ZWij88jc{at12Xk0lW!^$_nPA?JKQvh|L?* zSbw=y-#)*3Q}gu7fuB5VPer=iwHj4>*erq>BGwZtwOXCaWvhl%>S;gVDe`2FWu|v) zvVIEmrIy~B!?s}Bp_569bGy0cZ`+oW&y-r(A#ODN!`1FO=gWU=_5goqD1~1sl_G9r zsw8U}7meET3bA&(;ghvOjS20*F}sr=cuB2VU65u`Fz@1*rTtA)@OvDSt&p%Vww9I; zpIQmis^V2h55sYb0ZA^eRZAKQoBu76QD&-;*2dt$qcnDRaqfq1E1T*xtNfY5dkP;b z{8om*3wwo4zUDB z5)EQXW&j>5Qa4x}?mB|`jmr$PSFCu|W1B;t4sA4oNa@gO8VP@rc*9)uAKLzDq8O9(%j;%Z306KvDG}b zLx(i1&F!l!&jf$^gKL`e8!vT2lbOyJmX=poU) zL_ZkX@obez&1Co%GLht7-UH0SWXcBzMbKm{52#QRT<;&<&)*P-Nw%0dcDYiEOMzu2 z8`~Shh0adzjVYU37}c_><&;b9=H-`#Niv@#B%BcP`h(bVvSykV;}g9;TWSo~w>Qc` zsWcb5mp6agrLtpHv)Zsxz4`0dBg)9r5l6ivW95XIHFKYGG?cJk? z&kt56f7rffZO1jDXtBAv)N0iKr}BK6{x8D*ROZ)Dnnfo{(?)hOT^^jf?C9?HYQ4&R zEOkyk64V>5rPbzQ6dCT$+C8uRp<8D%rYFjzQ8|Bz2k8yX#AnT6^Xt$34ZCmhbNuAw zx7bF?6(da zF29>a;*W%>hX1EoEXU?WDSw8PUMbnDiy?dTCT=VC|5{8=Zjl}eLHF@I8Uw+dgkNn>6u?x4~K6y1A z>>oSEs(WnZ2s^g-)RFzYy*)PE`}`h?vZlTxz_nZ}oGP3v+@@oH-*~VJC|kMX>!qmb zYz8R~{|gg{F4~K%o#;2>b9n0_zfCqyM`M54gLF{i7U0OWM?XA7fX6?^iLQAN_BH#$ zzd%#HJioZ|lI6wua?y8vH&hNcipj?QYwtL+TZs*$RV+<@J}xiUT=q|DE1k-GXRg+X zi%W6+Oy{z_&Y8ONXCCRW+3l^d>m5Ja@1H&H`B$IcvX7YMIC5)~-)yWk=>Jps2S9%s z!l2~%zEcW9Kdw}lT5FxlHV+R@9y`20I=XZ8#0@KRdw~-)JCkoOFEuYOl}4jte0ej9 zS2pHiZ*R|w=PJ^294lQ~UhMR|y|a7%@=~`~JpQ85e|Caar}@UT(O6$tNb`>=&f{)vXlVcQRm)0W99lgw`G2-FLs8S7#p9}-mIDj7n{~$KB&-;0t zF*wo8c*h%_(CPNG0|ROMFuU|)_b5xs!|h}^;(vVFwTf}kb{b3C>K!0bh< zSv+%wTX5QoHf4@vsTGe=6qw>cav)3`pX*#wv=6kT|xNg7#fXi@=?a>rD}Dl zR{L_dRI4pjt98ad^+nT{(l>vDy;j>Nb6C;;k{?H{y>}XRJ*5YmGTIvZ=TR3H5a07; z`&u&2sfd&m>tacI#5@BsM_qnC7;XLj?{ne7--nwab}tO0xHW1REL@g6wM~*@tD!eP z$2ymXW&psC@bV6;)%|FRloVx>Qtp|?nQ+9|ov7+km6U(|S;IaFw-|q8m)2nl;=w{m zub3C&L4(0@uN!heDGz%Z$+$?bzy3bEgZ=K~T)dKf|MRbU)#oo=81nWr#=M-vC-Wz0w+dOJfo~XV&6R0?Ai78IFw@H>2 z8_|*7TFE65ztl|c3(8UGxWT>q`K4rF<(Kk1|9h8mqt`s-bQ{(Em2FEza9mz)+rDso z>ASY;-_u+YpL4>f6!7pHz2;&2M=vEsG%)q0_by(#l)B5;^5cIZ{~qzxt-|5L8JgX< zQ_sD*@J`Yuf3@(h3ZKoRkADaRf1WjSvnS8;2=u(+7!VdT1IL(-z5F}%%rHTQ%&a`v z>7)OK8vRx(?R}@0q;`A5ZoAh`aU6h3X#=b4qux+^>|q~&x4XN&#`NRJ+8Rpz=j_0pE0Tm`!Y8dxNq_Fe-J>;O@QdGp z<4ePGtoau;JmjlsSn|7372l%3G|W%QRK|7?NHcz27GNi~C4d-W2qQMRqqgWgvX6zr zxgZ3>BRzvg#WqW(JWTo-)_Ht>)KFT1ZNzX()6vpodcA*dF00tiLTkgXl}DSyV~6Td zyEva!`~6P!#cPpOrkmL08LNpEus$;%@}#UwD!uwygf0l0mRywB4OE*Lk?|_|x^0UR zS$wh27LCZHOQ|YPQcYu+?Vhl|!mvmt>4@zAy#`Z2eY+gH zsY+=TBhY_yYfD5Vd+#FM@PWdk#Qncmc&706!Z!>5SK+@FzE}7Gc2bf{urQibHu-qw z$(B3A~+wu@d375nwjvD)22^O$@D05<`J5&;p&oR%esG;bO{np*!iI^aa)s|ZkeaPRZEMXF-e8+Z6ZYxE*kEzUiHaXw}r3+&mo&b#_x^o`k_XeM%9qr=fyOhvFBBr zL9KsU>?V_s_3LDkmlmsjn$#9ejk$7IC~g~RP_2YD6*cU0lcyMhK%~*AloKzrN{dn~ z{k&hz%6%JA5v;r#!$n;wM^%aN{nMvpRjTOqhE=o>^w2qEO#U;yslTD)zO1kZgn9Jn zGB;hP5~`}_nx5U=Kk4~iHQ7msHa&4N?bLsWkw$KrE2A4Fw%Kd+owBd&s^vGAn!e=| zeKPL%>xReYcN{?-QuAFmsFow_kX8DrcdD2bdr{F<57+xb^c&I-?c!<_mh6tHIIDIO zr)b%6J+6j}Ce6G?v7SW4JtMm!)2{9O2kF-RtelmU4D7P1cIC-psi?x8v=)&CD3pI` zOVY;J(5hRi;{TvPpKiRNf_ibNjvRN&uRL!1^8>%*lR7es)fXEi$bd+-1Z%6<1kyc% zMaQ}BtLU}HdwNF%&Y$XWe(%KxpV^F)B>u(U{n^*O?*1<{uWE1Y-?hEFCN?fU_?>Iw zcHHl8?68ggi4)&mUmm9idj8X-|DJyY5_u<|P*Y#yeE*`Wvk2unQGaTm4Rsu+Mi-j$ zZ({qb+1CMd>;O0wQ*j8Xn80C;KX!rG0d->L6oKIB+eNZoE51SUpEM*Nc$7lvv>9Y+ z6T@s~wKXsHWlDA{BOylACMI7|e!E=t%1N`_o0CUg_H{d4k1l?UEUciiaP5Ejc`}$S z1s4gbSFKqLT^~;sHXh^>U66e!=ViU#I2J7?b|)?=i>x}t>Q?vXyJ6Xu!km-hv^Uvb zzP@qEFMYx%d9)+)Vdhvf+95CiUWQ&K)rcqgR<4c`82wPLV!nPKRqY5(ITsjgcJX+z z^+1d4Z(@q`Jp?-uCyh(l!rFh@&X8F9M1&(^>Q#C@{7obq;V*0BqgMW z3>F0BZ&HSY%%cne`B0{_b#@R0iJkg<)H%{mQR9hak^&=>1&#noGU0#a009vi*OK%e z4QQErO}2F~aQ?leUS`=1 zy6*t=YDkB>yL16^{tiZPImQywJ|7RTkwt}$_TYsNghuR#4ajO3lJ@Iz*xn4Q@gNIYFO>1VSzdH#Q4A2 zA4oz1u8PR$X?Virk(~-#AYR)r#apHdpY-VyF)y>NrmkI^amU&@`(l zRGvDV%xr1cv8jLk*bK9U%{pSdu^?^6R?uuNxKoVa|YR4-du#-sH#yog! zBVl;pnl60J>9W{VlYja5vAqz4wi8m6W}hhhU$5PSV8ol+T^2mynooQxbolGU|%S`)zna@~Y37&G_-# z=+`Mv0iKom@0@r%hmd6N%26dJ&sz9!-|G}z$M(OXaMIy_9mBm&P5S~(pZb(hn}{{i zSBF@sFLjPO!zCS)1iB|oOM4k2&$6qyg|I?6zl#*>q>_XkHWN)&cMKW_`bp*^bw-s0 zq;DguhEab_gr+eHHlM2lFHJ90~bM0r;Fpd3I;s( zW1FNg-Bf=eMNlQsx)ey4s1Jirpr}%%u#=cmC1jSLlS(MiG5}+_;j5)dUI(wKcHMEazBPsp*xi~-{K!B z+?MAzC0>C2y#848n=-PZ@3H=LrDAF~>df2Pj%sV(qAe7q<3z_9`| z2+QmFIs%R`{{P-FY6ne0Kp{_1H}7IEK79v#qo7 z)DT#hAyLu4>89Bf18!!4YF9OqX!;(w;0&in^M~+3sWUEx?r@1rXBpR>*t653#W8=3 z)rG2^C@)+&b=%2()sRayuRQLaTLWSn)A5^&J8O-lZOd9(*tqcGt*Y&lqFD9z$TolX z{H!BoR@Rr+OhXx^)uvNcmS+PChC6c$)i7F5msXFpDl~aW zN$6g_SScx^;tP#$+jOn4c-dS#A#OCRHT`P5?-qY?yp<`%0Fuw{+h4YiF)`RS?x` zKXv|!pTDQ2N?$4_r6K~dKfX>E>;(b)YLKawsC4!9aij63^Vgj{eRsK7Idj-Zn#D@i zjY@`bu!h4wL^|nQVOJx=K!rG1B^ZtdmlokhS&|>m7y5>y!7QZJ7>H1)Hk=E>l?Ww#{!w+RuLmMe%S$hQg4FqNqsVk+vxZ<8)^RMS{d>2E{$ zjZ=~&Qr_`482qL(EDc-oHf(=1Ikroe9<12|AE#e92E=vv^J4xl@GJ*FZ3CrRhnI$Y zHtBn<-X$=$S_C5OV#J2YqeMq0Yj(+OnHeK+RR`nRJ zKUQt+9rNwj_EUNt^Dp_lnL-kkLt_t+5lrV^%Tz-BppzQW=b!Syq_elj?wmaQ+;ePe z@)aSGU+vUZe{1>l%Bhu0Yn?@!Ckurg*d}oiOA$KGNV^3Tb3cDCNdb5YeoL*=*1NAh^Y3LWkJQ%K zy1rw%SQ0@muqO8~gSFDopkIRB>{ZvwWYKii%7<1={O=f#J+R9Xo;W+d)-)T?cO=qTCqtL zD>p3?{$X|fv}1EOdJtgUacSk8t+*5Za!haRJ{etoq`opZv@{MgR!v?uUTkKa#yPGB5i(h}qPL*YM zPrlXtIsV0sQJ>Attv|HG=V}`tvgmTp`zbsB$bYfhU-Z&j{K>1>!yAM1H{BZ0xA$&r z6)y4NcaUtneTKC(1yi0a?+v9+(=apk`#K(%gw$?VGrVJ5A75ZE$n^@M-=)!?qGnza zXT5*jE*TwotEEo?ZQK8PyTnHO@5U&sTj?G9RqZy3P0 zUZ;4F!nf=7Zln7%KXsy)x~|>sthX0V9(v^X!e0~5cfZwa_gdGCM{^Y` zv9n)3a@yv6(J(uL&5_kpDq4=mBldpC78$BCG9#mZv%vnk1K(lpnKc@Ew_1{Q=~Ru#ly#qU*aP3Q7l|`3$Sav8 zy^c!8_-wGG$KLVu)9jC)e){6kr=NcQ>8JVAKmI3o;>FI;+ehhjNymS9Ie(le%PzDE zrwTs_IXmd|R0iLAQk&qhi*4;eudN38blYQbesG@AX~$e(@98)&tWZ#MJM0I9$DM+PENe_2UbY<+D1oy$LYS_k@Aur~85?lo#C=iSCMXZxoo~ibJ*&Mz^y^ zmrtG)tvc!$JD=_xQevw^7WU*DZ9n?h3ig!-E+igLHuIPCHA~>tO*n- zWg8-DG&-@j=$#@lEd0&3^e2yW51Zn$4R+h|AvO8iPshH^Zt)z)J9Lg%m5~x@HMUYX zZ3-7@fRc^1L8X7h<2#kG{TH+k(H*C;qwvE4DtZiQSdp-KpKJf=O|iSr^^(I-?_eRH zGn&ga+z3S2g#D+mTn;B?dS}bOcXE7F*jee!M^334xJBPmZsb%_O9nxyRPM}0^9!jH z+OA2`Z7CG~^v)1ZnLM+-{pWxFjHRsdLRPGhk!{+x=hJ`WcZEp}=XQA^o$LCJ>H5l7 zh7stk!>6@A`IuNBZ8|K}$lll~94}l)_QtIwuU}udzwo}ohYFu9{BMPC75)n|S&NO> z3HB29O7@fNt?Zw(kFd|Mr`R*>YwYj1;ye5bew)S{csG9!{{a6j{uuv#{_prR{A>Jg z`1klll5Bq!F)v!8Cr09kI4(|!GvWsEGvZz1J>p-8N5rp*kBE-E-KthZb5w|?FF4eQg^6V{ikKe7Iu^)>4`>pxn5Z+*}Dfos)&9EuP5bZc|J(it z$H*}jciKY*SBa$EQahu8)_c1O;VPFhL5D!{8fnZ^OebV5)t;4anfbguw@hx1J4TPN)nof&DU(rvQURaucDkG+&ucUL87 zOFzY(@ewLa?5C?>)n(J?^;*CwQ@9azBW8co zrK1QX%o4aGw?{ps2Op{J-2pmA=^&!CH%jvb5b8Qa_dw&yfX_J1PB-f6dxq2AqbBk} zD$8&qhkI?(1A0b}ctQRYDieTf<7{_K&BBUV$a_?e;aJ-@Gq_41!fcVw+}2BuqapAx z=e0@)986-b8p-u2{-d+?JKW>8cgBC%TEi2OR%5&iH7`jOFmi~yA`YpgaVn9jG!Qqq8nG2fHaFDlbRB0YnJhzkQUB zwq;ve%o61BqH67o2W17~)){}_=%%a< z0Q@oW)iD55dpk*+YBKC1vAd$0j2dLzW)fK~hofGqtG7EGCvEEAq?{nImww}WhNzA{ zh%bOUMJw-YECA(}C*40Dj7TA)>J754fus%?Lk5V zN);Pnf}z~rHc7WJ&C52@nP`6(#vFzeAz{ccHtLeRpq8s15}je1l&P!8X75o??~+!Z zFRBi5br6$Ur?Cs5^jY+$Q;nI{d)~UyOG#W4f2B0e) zT+#(aW(nx1N8K>iLqxas6d*%&Gmo$Vm61``_mbV79w_uNJjJnxM$>(vWJc#U3M%-DcN_#jgsjwjk@DJYYtRkr9q5)-5t=oUVkLA zBVx()a=VV-BYS5aBL}j;Lc9+6*kni`1KmMm?WdT@-62L30XAbDbFayC4D9_eNns4i zXn=Wuunugw5@E`AcbI>X7J_o=X3W+0VYZ*+%x>YyEh4;L1`=r?q&O>MGjfzSf4r0L z_o7=y!*ri@0klFxh>@eG2CO@3FYu$#N45G#9kmPY4b}xJ!C=4@V7S|-iHTw|s*=)s z;bcHf{COpO%Xb54d{CI~79nzy|)cVAVBqobK zIpxGJG$^;mBzA~`66)xzRfebnQvg$< zy6-}QCAp=o5Z#va+9`g}z$U~t#>tfZc9C6vs7*HQM@#p2bzUR1k(g+27jQEpAoS|C zl~GgTV{HXyL8DA^cNhL~UBr@Q+u5Miz)m)u>norTXd-`BEzPgnJp?NeV;IYHgpnU> zwYl9R(!>l>^t>_%Cu;+usSo)|yn@PTk5Wn9vI|NjiKAsp-YvvDXn-?}HSyRo`jXfK zAgCLVaoubHQ8-pKK@w6nbkZd@EX9D>IV*Ysri{|6O*Ubf(eYfV00SxTz~X3lE0*+^aqj(#0T*#>e>E~ z=nLYl4ZJ5vnw=5Rh}_EEKxrDZNtn`r5rODse&mbAGpI3Lte3y( zG=S!`Fe^Y<;IP{}@c9@LX#jbU=oW;X$XUZAy%0o0fOs<8dKNvg=w3Xh17mnhkP|QgI{8pu3?(D z0lRt;rlTuvWx;#MCfT+HJbRi-4np zd`rkjY~?sBc#6=+EbjPpqRut&GL_2=z~CTeD&VY0CHcT_p-T*x10Go9@NgV?BJm6z zO+z;VWXXbUCJbK10t8cv^F;5jYZw=S3}t_rMderqn+ME{hKt1xr~!nY903Tx(h1MA zW18@|xSek0q%j)6T6R%&!@d@qrc7!}(9YB52y{oPqaNv$lLH|27%%5IKnQBQ442Ul zmmY)%2#qi$MX;?=Y?x{2qi_|FdEM|7#}V33AiqMQ`^5C5m|`j6Iohj(>z@W^|5ktB zZ|c*AuFIp0J;Ht)St@iJ)eP}Q7!hL+(4|IHQkHjEySM89`dOc6D_p#vzP=2RPc-rx z7)O{cH5c$pBgWDqozM*|qiTlna>l;E*!vl$`?L#-PAZAUP^q6s#pvxLtb;Ds_&@qK zy=d*V_d`(KU(aP7orX zkK-uqOQ?Ge$CYCRe_Hnv2lWb;_cPocFieQ=mMG{U#@>}T8&ss9sVSeDb&|du<$6cm z)YTWJcpvAp!P8jYLKjDGREy6t(m-`6I@3Y%`oo-#Cx(|=#Qv1-{K~X6&i;Q0-GPBs z4jn+aKigvbR~UPrK94eWk5O$Tn^q10;yQ{J+5LbGI<1nW`vhKa4k#esI@cWmKO++u z%9y1_pDi-xz|%mO)83~_JIMTG<5FcdhlYr4? zxR7ah(>bMQtd-GsTk_vPhogVeUuNGVMx}88mNjK#+(4tBMGKWiV&aeK#?euOy7HY! zYlJo%mXWssH(_vh7<(EdM9=-F^s_RZF9tPA_c+K{1DzSBXWln)Du8b7dh87F#@_U;lS>h?lq9iUWr{yUeW|}<#%?D0U}C&qm!o`1?y}c2)BHuUt%P@s9J6I z9xBAPNebE6gHaU$^m{`n+ko#I(YvkT{k@o^3gGz7MDSLdxkP`5o^tF*;iUA7)DlB+ z$2QCi$$pi1LeEpqd=FEM+VqeOLGeqBH8Cq*3cqbV-#QxKj;(4iGO88`k8asUB~f>J zmbn9I#{B?&tqgSx<7Fyr5>i)+H33;I&4fT&_f6+=P|;TAW-WFj%1`(a%l@`E6rX*kvn`DEEG&xI7h@Io;hjc5iwAo*xi^aWHc6mW*(&p9@hm6LXHDuM@}*j0X5#9FWRM$ zDn&DTutFrNDM5N(Tn6uT6VHzHTr_O%&@&9p2^25Rt3`jzV884J!oy5(gjF=cl1)>V zE@{*%o?+$WuKX}CV&tX3bX8n=+!LX%kiD6Rn5YPZENtC3E;v1PuSflFml;uG053ul zlb5lacY;6IAbG)-iP(*LkQKq+gzchzI_APs031<~7HAqaL_$(1$ zjxeMG7h``O5)z;`te6FL!*CRhfm5N)UQi`Mazcq{ zwSY)YV1_9haNk#VJ9MW9KR@2m+>LGy!>dVj7< zs&RpD3(Hv&rq#F*)+kTez~r2)T3&547un98D1&|Ks|ON5^+GKNS}VJ%%}Es#t} z?7)Adj^sK|k>w%iYSCzJ!6psG6T2iXtAhu zGei@Sd=x5x+r7oq0x*Ly!=d4UOJER`WhmR^j!vS&q%KEmeE4rUbT8aELlVfs2t4Fy z7raKj5(j|jCmNSw;((G2p9s@)Ep(snXsCZ1YEoG@%i>E^f@#AU%EhFzkpvietKrem zi;@T>;FZx0M5UJ08X`1N6Az+!MU&E!hVqFdD=u*lCIaZm$agAp7N0XUn+C~?ts)h` z-6{==kC1yB6W|OPR2kPQlT@Rb9(!;_M1BpS_tu%?6X`NKf&?f^i!#kP%G7tnNKAh% zzW}pis$3${U?ST(@eG}uMN2U~G7@|0o~OA=u2o}{Ee;200cYJJeo3IWN@nZ9urU2C zp9WP0GPff*a9+V+X;K(skt>UOlj{r=(43)gXqRTV2DlIdmX_|saRIeW4(dFHBg%#q-rYH)cSusO&n9?_0_sFh)+Y$Qg;}#q;U{XFbGac$09yz z!)n4TM=E+t=Yw$|x7{>^w&WAJ08>SH#M(_GL^26;ZmvjFV`|evkqTlkJ2&D&%xdIq zr@BRSF{TulwSufcB2PiX=>Y(i(cFOND8fug7BM}`rJmO`1F5$HWf4^nyH|funy8AV z_T<(eQFcrX&k~eM2k228S$sUw3Vo@~82AJ+Gz7eeYb$}nE`rCq&Qz*xLK8h`YPQbe z5+Ls_lGc_+pCS6BGJ}w~3VIZh5(+Ld4N<>(Ceqn+@Ohe}9+p&n}L{`hwvW6@wTj!g<^{|xSyUp$@YH_AO4yfkpK{ui0kufTlIRoxgC_9u#%PnDNU<1-l_Dw`eJ9e z2opJ;vjXA8F#ezNdR?7tlVwuEfzh?IxbFK`6XXpfuzf%n}(Xyrir3c`tc z7VCu$C?5bLj7RxKxlXp7yI5;HEqL$H5u`ZNX)UIZF$u3Ncf)@<5+Xz@o-8eARy6Mm zAU|_8*RF9VDm9A$Rpb6zcdOHV^J-A*uAQ%XQEA0I+UX{dT5?OJauOz{Tu%*46_rCw z+vxxZ>McaZX33p1MG{1|ccU~(KE>6*4FXFm{ro0d`IRJdT>X1*u(* zeb4rg3)l?2-ClpwFoUi8wys}TrV8DBx)nP{Wqrqsosdk%AU?gZU>iy~rEa6`kOa0* zg%;f|?6j4JWxtr+?sx&3wHCW2NxFbf8kp_O{Cz?Z6ym}H5OrtHkGdzdN4qEvj8w&aVhB&mYU;w(7N;E^CvjeABO9IDPs8yY0y*?`J3I-}hMgt#6%tlD+8S zSNPTs;UL+CN?}pQAQqd&wodRW2H6<@RU3=KZTjBswdu312G8>TGs~O3!`(Xmx?9b< z={wE;<)s0&Zl-GFv3l2veX7A$;ToEyZ!SDk_;}%qh5t}^9)5;XSHT&q z;{bnibd#wm6bAgYu%S2eu`*k24b-LpB9aYqoC}atH*3xO$oqPSAvwUC z9Hc~nIvcyG0jrYSXAJM7X(r!kq;#(;Bs5IIJ6LkyKq~xuM%tn~O;hYlA@EmH8yoLx&F5bvzd} zDacfVtroM;rKg+iu=Zn+tXvLj8%2Mup(*aOW9>@XUxdAATgnJTF$$>EQ>C!Vt}zW? zlt?8o{Z-fUh(W}p?3f~j?o;x&t;1bnl%*ZdNlfd-p;JEE-S1VPO7`bA=cx-M4_)Ej z%EbP1)xi=UFGtWSyeVM5p)o59p65?)u4ZvKxOGz45QM zzsu&n+x{v`cP4+%7bkzQ_5DkJ?;E05*eU#M;R$$|A~giu|J%Vd2w$$T;sPLpQ;n32 zos~W!a*J8t)MBjMCgB7prfGa8Hct_=idd-@^5h_jS9{UZT}b7mlVe~TXdA&DynYqwx*A({ zT4($@pw6hjiBBg z|JGxlhDGq1$Nmj8B>JBJ{&fEPF~+|^6#4e+obXVNl1B=>oP;{jje~#eGDEEe+k)LC z*5Bk+5vQcJE@#>C`o8eREh5rr{I?Iy)D-2F?J5b@_*@L#^rkK0_p>c%(()~gP4*5^ z+vYi|w*VAKPg$(%7|wcE|K1`xoMAId{VEzQQ%OBKx8YSvmenr0 zTiKQ`{;L!5%6Bj5zZ-A7aN)a?f6KVL&2R-|b>1Hejk)4Af<$FG{obE8De<{5> zd(`hYrE(vcEf^oJlkR;bwp5Z^M7-QaRgh;PHDd#LB3vFVE+8~ku;atw&xyG*NOET zFFSOFVE%PZe9dJtGWe}$U;csSR)aUUn*4Eo+4!+Lj=%QhSz4cbw14=xOM>Ctu^X<+ znv0c6vw0A&{}24zh1ZjImHSddGlfP@lbJq7Iw#~b5F>xrVztv5-ugO(CQpN#=75_j z;5mSl4hPIVJqIJ5$#^PtbKkIBA?}aZPh0thm#Ssbr-3V)o|SPj{^BU5F(_*M3pjx3h^l3|9z z`VR3~=^1}nWJ;$*23xqelaUtNsxDL#pA_O`-f~DBDejt!z!-4IYIDoJMTW8TZ@*Fa z7AejBdb>qZ*DV>Os$eM-xuUiyh+qaf11x@BG%ZbGzL_4nrj@r98EK+c-p&+gXDnxhnZ)Xz}im9nQPQ*Vuo%IBB2G2@)5of5(-pH@>&FVl=Ds zU1On;==pyO$&43jYL~Ez_sB9kQMkHrx^TX5L*Yh^A5-Ye0N1Dft!z+i$NxDx=NqU-xB^HM~j9KY(w&XWR+m;tUA#MJ;$=lf-iT3=USEfBC*pB{^ zO%Jn^$>g!}FO`<6!l6G=T`C1d#)@lxhNaQW|Nj6&hlJk%004NLV_;-pU;tvvRek&8 z`E9;3a5KLEiZI;19JvaaX1>h48c1<4FoAz~09!!|xBvinoMT{QU|@d100b->KqT{J z21W){FarQS+_y;lw7znTlL}Ka50nqw5Ks_?5Uvo^5g-vl5n2(H650~< z6J`_Y6p9wq7l;@r7}gnp8gd%^8%i7m97Y_f9O@k)9XuUa9oimT9*!RhA8H^hAZ#GO zBVHuRC0HexCA1~dCkiNHEHo@)ES!HVwk*~yA}x3=_AZPr^e9GO$z_dK z72okaa~3@3Y@m7tySBH7cu#z2%wi|%y^utG@+jv4SOTa0Q@<#NW9D*E79YOzT4c9cuqnj&!^c|K)=63p0S1?x_$Dd|CKUreQ*vV7`|E~ZgQ zRt(IItQ<^8{Z~qI(w<5sk47mbO3rBgeZCrOrXs7yB!*ThN`V_zIx2rgHY1&l!qj6b ziX}zVn&vyJOPQ8qtxcfDm?6`4+)lQ8qKjHbJ&kW!Uz^vZiP^p6>V?Emh_j~#MmT3XpI}Cnb?(LeV^zmt&@Iam33xHJfy8rrps*XN*fcm>0}xD)b!p! z3P+5GI$`2rRAsuXqRoF@Y?cXpn0ThhrzD(m&yxCl6AI6nV&z)FwWknP<0|&D=Se(c znew}^EPX0PcUwLz6|FQ?uA=BGADeWGGC%7IKbLLT3Q2lw)k+r9fP+a42EUeU9_F%} zgrz;n|12dhW>S#JW|8uF`gyZC3)P$&o1~P@yA(^E2IBL%aUvO8D4#7xl`omo*>Y6b PF=;n8{sU@1pymJo_0tt? diff --git a/extensions/theme-seti/icons/vs-seti-icon-theme.json b/extensions/theme-seti/icons/vs-seti-icon-theme.json index c21bc3b75..7a10a533a 100644 --- a/extensions/theme-seti/icons/vs-seti-icon-theme.json +++ b/extensions/theme-seti/icons/vs-seti-icon-theme.json @@ -23,1027 +23,1027 @@ ], "iconDefinitions": { "_R_light": { - "fontCharacter": "\\E001", + "fontCharacter": "\\E075", "fontColor": "#498ba7" }, "_R": { - "fontCharacter": "\\E001", + "fontCharacter": "\\E075", "fontColor": "#519aba" }, "_argdown_light": { - "fontCharacter": "\\E003", + "fontCharacter": "\\E002", "fontColor": "#498ba7" }, "_argdown": { - "fontCharacter": "\\E003", + "fontCharacter": "\\E002", "fontColor": "#519aba" }, "_asm_light": { - "fontCharacter": "\\E004", + "fontCharacter": "\\E003", "fontColor": "#b8383d" }, "_asm": { - "fontCharacter": "\\E004", + "fontCharacter": "\\E003", "fontColor": "#cc3e44" }, "_audio_light": { - "fontCharacter": "\\E005", + "fontCharacter": "\\E004", "fontColor": "#9068b0" }, "_audio": { - "fontCharacter": "\\E005", + "fontCharacter": "\\E004", "fontColor": "#a074c4" }, "_babel_light": { - "fontCharacter": "\\E006", + "fontCharacter": "\\E005", "fontColor": "#b7b73b" }, "_babel": { - "fontCharacter": "\\E006", + "fontCharacter": "\\E005", "fontColor": "#cbcb41" }, "_bower_light": { - "fontCharacter": "\\E007", + "fontCharacter": "\\E006", "fontColor": "#cc6d2e" }, "_bower": { - "fontCharacter": "\\E007", + "fontCharacter": "\\E006", "fontColor": "#e37933" }, "_bsl_light": { - "fontCharacter": "\\E008", + "fontCharacter": "\\E007", "fontColor": "#b8383d" }, "_bsl": { - "fontCharacter": "\\E008", + "fontCharacter": "\\E007", "fontColor": "#cc3e44" }, "_c_light": { - "fontCharacter": "\\E00A", + "fontCharacter": "\\E009", "fontColor": "#498ba7" }, "_c": { - "fontCharacter": "\\E00A", + "fontCharacter": "\\E009", "fontColor": "#519aba" }, "_c-sharp_light": { - "fontCharacter": "\\E009", + "fontCharacter": "\\E008", "fontColor": "#498ba7" }, "_c-sharp": { - "fontCharacter": "\\E009", + "fontCharacter": "\\E008", "fontColor": "#519aba" }, "_c_1_light": { - "fontCharacter": "\\E00A", + "fontCharacter": "\\E009", "fontColor": "#9068b0" }, "_c_1": { - "fontCharacter": "\\E00A", + "fontCharacter": "\\E009", "fontColor": "#a074c4" }, "_c_2_light": { - "fontCharacter": "\\E00A", + "fontCharacter": "\\E009", "fontColor": "#b7b73b" }, "_c_2": { - "fontCharacter": "\\E00A", + "fontCharacter": "\\E009", "fontColor": "#cbcb41" }, "_cake_light": { - "fontCharacter": "\\E00B", + "fontCharacter": "\\E00A", "fontColor": "#b8383d" }, "_cake": { - "fontCharacter": "\\E00B", + "fontCharacter": "\\E00A", "fontColor": "#cc3e44" }, "_cake_php_light": { - "fontCharacter": "\\E00C", + "fontCharacter": "\\E00B", "fontColor": "#b8383d" }, "_cake_php": { - "fontCharacter": "\\E00C", + "fontCharacter": "\\E00B", "fontColor": "#cc3e44" }, "_clock_light": { - "fontCharacter": "\\E010", + "fontCharacter": "\\E00F", "fontColor": "#498ba7" }, "_clock": { - "fontCharacter": "\\E010", + "fontCharacter": "\\E00F", "fontColor": "#519aba" }, "_clock_1_light": { - "fontCharacter": "\\E010", + "fontCharacter": "\\E00F", "fontColor": "#627379" }, "_clock_1": { - "fontCharacter": "\\E010", + "fontCharacter": "\\E00F", "fontColor": "#6d8086" }, "_clojure_light": { - "fontCharacter": "\\E011", + "fontCharacter": "\\E010", "fontColor": "#7fae42" }, "_clojure": { - "fontCharacter": "\\E011", + "fontCharacter": "\\E010", "fontColor": "#8dc149" }, "_clojure_1_light": { - "fontCharacter": "\\E011", + "fontCharacter": "\\E010", "fontColor": "#498ba7" }, "_clojure_1": { - "fontCharacter": "\\E011", + "fontCharacter": "\\E010", "fontColor": "#519aba" }, "_code-climate_light": { - "fontCharacter": "\\E012", + "fontCharacter": "\\E011", "fontColor": "#7fae42" }, "_code-climate": { - "fontCharacter": "\\E012", + "fontCharacter": "\\E011", "fontColor": "#8dc149" }, "_code-search_light": { - "fontCharacter": "\\E013", + "fontCharacter": "\\E012", "fontColor": "#9068b0" }, "_code-search": { - "fontCharacter": "\\E013", + "fontCharacter": "\\E012", "fontColor": "#a074c4" }, "_coffee_light": { - "fontCharacter": "\\E014", + "fontCharacter": "\\E013", "fontColor": "#b7b73b" }, "_coffee": { - "fontCharacter": "\\E014", + "fontCharacter": "\\E013", "fontColor": "#cbcb41" }, "_coldfusion_light": { - "fontCharacter": "\\E016", + "fontCharacter": "\\E015", "fontColor": "#498ba7" }, "_coldfusion": { - "fontCharacter": "\\E016", + "fontCharacter": "\\E015", "fontColor": "#519aba" }, "_config_light": { - "fontCharacter": "\\E017", + "fontCharacter": "\\E016", "fontColor": "#627379" }, "_config": { - "fontCharacter": "\\E017", + "fontCharacter": "\\E016", "fontColor": "#6d8086" }, "_cpp_light": { - "fontCharacter": "\\E018", + "fontCharacter": "\\E017", "fontColor": "#498ba7" }, "_cpp": { - "fontCharacter": "\\E018", + "fontCharacter": "\\E017", "fontColor": "#519aba" }, "_cpp_1_light": { - "fontCharacter": "\\E018", + "fontCharacter": "\\E017", "fontColor": "#9068b0" }, "_cpp_1": { - "fontCharacter": "\\E018", + "fontCharacter": "\\E017", "fontColor": "#a074c4" }, "_cpp_2_light": { - "fontCharacter": "\\E018", + "fontCharacter": "\\E017", "fontColor": "#b7b73b" }, "_cpp_2": { - "fontCharacter": "\\E018", + "fontCharacter": "\\E017", "fontColor": "#cbcb41" }, "_crystal_light": { - "fontCharacter": "\\E019", + "fontCharacter": "\\E018", "fontColor": "#bfc2c1" }, "_crystal": { - "fontCharacter": "\\E019", + "fontCharacter": "\\E018", "fontColor": "#d4d7d6" }, "_crystal_embedded_light": { - "fontCharacter": "\\E01A", + "fontCharacter": "\\E019", "fontColor": "#bfc2c1" }, "_crystal_embedded": { - "fontCharacter": "\\E01A", + "fontCharacter": "\\E019", "fontColor": "#d4d7d6" }, "_css_light": { - "fontCharacter": "\\E01B", + "fontCharacter": "\\E01A", "fontColor": "#498ba7" }, "_css": { - "fontCharacter": "\\E01B", + "fontCharacter": "\\E01A", "fontColor": "#519aba" }, "_csv_light": { - "fontCharacter": "\\E01C", + "fontCharacter": "\\E01B", "fontColor": "#7fae42" }, "_csv": { - "fontCharacter": "\\E01C", + "fontCharacter": "\\E01B", "fontColor": "#8dc149" }, "_cu_light": { - "fontCharacter": "\\E01D", + "fontCharacter": "\\E01C", "fontColor": "#7fae42" }, "_cu": { - "fontCharacter": "\\E01D", + "fontCharacter": "\\E01C", "fontColor": "#8dc149" }, "_cu_1_light": { - "fontCharacter": "\\E01D", + "fontCharacter": "\\E01C", "fontColor": "#9068b0" }, "_cu_1": { - "fontCharacter": "\\E01D", + "fontCharacter": "\\E01C", "fontColor": "#a074c4" }, "_d_light": { - "fontCharacter": "\\E01E", + "fontCharacter": "\\E01D", "fontColor": "#b8383d" }, "_d": { - "fontCharacter": "\\E01E", + "fontCharacter": "\\E01D", "fontColor": "#cc3e44" }, "_dart_light": { - "fontCharacter": "\\E01F", + "fontCharacter": "\\E01E", "fontColor": "#498ba7" }, "_dart": { - "fontCharacter": "\\E01F", + "fontCharacter": "\\E01E", "fontColor": "#519aba" }, "_db_light": { - "fontCharacter": "\\E020", + "fontCharacter": "\\E01F", "fontColor": "#dd4b78" }, "_db": { - "fontCharacter": "\\E020", + "fontCharacter": "\\E01F", "fontColor": "#f55385" }, "_default_light": { - "fontCharacter": "\\E021", + "fontCharacter": "\\E020", "fontColor": "#bfc2c1" }, "_default": { - "fontCharacter": "\\E021", + "fontCharacter": "\\E020", "fontColor": "#d4d7d6" }, "_docker_light": { - "fontCharacter": "\\E023", + "fontCharacter": "\\E022", "fontColor": "#498ba7" }, "_docker": { - "fontCharacter": "\\E023", + "fontCharacter": "\\E022", "fontColor": "#519aba" }, "_docker_1_light": { - "fontCharacter": "\\E023", + "fontCharacter": "\\E022", "fontColor": "#455155" }, "_docker_1": { - "fontCharacter": "\\E023", + "fontCharacter": "\\E022", "fontColor": "#4d5a5e" }, "_docker_2_light": { - "fontCharacter": "\\E023", + "fontCharacter": "\\E022", "fontColor": "#7fae42" }, "_docker_2": { - "fontCharacter": "\\E023", + "fontCharacter": "\\E022", "fontColor": "#8dc149" }, "_docker_3_light": { - "fontCharacter": "\\E023", + "fontCharacter": "\\E022", "fontColor": "#dd4b78" }, "_docker_3": { - "fontCharacter": "\\E023", + "fontCharacter": "\\E022", "fontColor": "#f55385" }, "_ejs_light": { - "fontCharacter": "\\E025", + "fontCharacter": "\\E024", "fontColor": "#b7b73b" }, "_ejs": { - "fontCharacter": "\\E025", + "fontCharacter": "\\E024", "fontColor": "#cbcb41" }, "_elixir_light": { - "fontCharacter": "\\E026", + "fontCharacter": "\\E025", "fontColor": "#9068b0" }, "_elixir": { - "fontCharacter": "\\E026", + "fontCharacter": "\\E025", "fontColor": "#a074c4" }, "_elixir_script_light": { - "fontCharacter": "\\E027", + "fontCharacter": "\\E026", "fontColor": "#9068b0" }, "_elixir_script": { - "fontCharacter": "\\E027", + "fontCharacter": "\\E026", "fontColor": "#a074c4" }, "_elm_light": { - "fontCharacter": "\\E028", + "fontCharacter": "\\E027", "fontColor": "#498ba7" }, "_elm": { - "fontCharacter": "\\E028", + "fontCharacter": "\\E027", "fontColor": "#519aba" }, "_eslint_light": { - "fontCharacter": "\\E02A", + "fontCharacter": "\\E029", "fontColor": "#9068b0" }, "_eslint": { - "fontCharacter": "\\E02A", + "fontCharacter": "\\E029", "fontColor": "#a074c4" }, "_eslint_1_light": { - "fontCharacter": "\\E02A", + "fontCharacter": "\\E029", "fontColor": "#455155" }, "_eslint_1": { - "fontCharacter": "\\E02A", + "fontCharacter": "\\E029", "fontColor": "#4d5a5e" }, "_ethereum_light": { - "fontCharacter": "\\E02B", + "fontCharacter": "\\E02A", "fontColor": "#498ba7" }, "_ethereum": { - "fontCharacter": "\\E02B", + "fontCharacter": "\\E02A", "fontColor": "#519aba" }, "_f-sharp_light": { - "fontCharacter": "\\E02C", + "fontCharacter": "\\E02B", "fontColor": "#498ba7" }, "_f-sharp": { - "fontCharacter": "\\E02C", + "fontCharacter": "\\E02B", "fontColor": "#519aba" }, "_favicon_light": { - "fontCharacter": "\\E02D", + "fontCharacter": "\\E02C", "fontColor": "#b7b73b" }, "_favicon": { - "fontCharacter": "\\E02D", + "fontCharacter": "\\E02C", "fontColor": "#cbcb41" }, "_firebase_light": { - "fontCharacter": "\\E02E", + "fontCharacter": "\\E02D", "fontColor": "#cc6d2e" }, "_firebase": { - "fontCharacter": "\\E02E", + "fontCharacter": "\\E02D", "fontColor": "#e37933" }, "_firefox_light": { - "fontCharacter": "\\E02F", + "fontCharacter": "\\E02E", "fontColor": "#cc6d2e" }, "_firefox": { - "fontCharacter": "\\E02F", + "fontCharacter": "\\E02E", "fontColor": "#e37933" }, "_font_light": { - "fontCharacter": "\\E031", + "fontCharacter": "\\E030", "fontColor": "#b8383d" }, "_font": { - "fontCharacter": "\\E031", + "fontCharacter": "\\E030", "fontColor": "#cc3e44" }, "_git_light": { - "fontCharacter": "\\E032", + "fontCharacter": "\\E031", "fontColor": "#3b4b52" }, "_git": { - "fontCharacter": "\\E032", + "fontCharacter": "\\E031", "fontColor": "#41535b" }, "_github_light": { - "fontCharacter": "\\E035", + "fontCharacter": "\\E032", "fontColor": "#bfc2c1" }, "_github": { - "fontCharacter": "\\E035", + "fontCharacter": "\\E032", "fontColor": "#d4d7d6" }, "_go_light": { - "fontCharacter": "\\E036", + "fontCharacter": "\\E035", "fontColor": "#498ba7" }, "_go": { - "fontCharacter": "\\E036", + "fontCharacter": "\\E035", "fontColor": "#519aba" }, "_go2_light": { - "fontCharacter": "\\E037", + "fontCharacter": "\\E036", "fontColor": "#498ba7" }, "_go2": { - "fontCharacter": "\\E037", + "fontCharacter": "\\E036", "fontColor": "#519aba" }, "_gradle_light": { - "fontCharacter": "\\E038", + "fontCharacter": "\\E037", "fontColor": "#498ba7" }, "_gradle": { - "fontCharacter": "\\E038", + "fontCharacter": "\\E037", "fontColor": "#519aba" }, "_grails_light": { - "fontCharacter": "\\E039", + "fontCharacter": "\\E038", "fontColor": "#7fae42" }, "_grails": { - "fontCharacter": "\\E039", + "fontCharacter": "\\E038", "fontColor": "#8dc149" }, "_graphql_light": { - "fontCharacter": "\\E03A", + "fontCharacter": "\\E039", "fontColor": "#dd4b78" }, "_graphql": { - "fontCharacter": "\\E03A", + "fontCharacter": "\\E039", "fontColor": "#f55385" }, "_grunt_light": { - "fontCharacter": "\\E03B", + "fontCharacter": "\\E03A", "fontColor": "#cc6d2e" }, "_grunt": { - "fontCharacter": "\\E03B", + "fontCharacter": "\\E03A", "fontColor": "#e37933" }, "_gulp_light": { - "fontCharacter": "\\E03C", + "fontCharacter": "\\E03B", "fontColor": "#b8383d" }, "_gulp": { - "fontCharacter": "\\E03C", + "fontCharacter": "\\E03B", "fontColor": "#cc3e44" }, "_haml_light": { - "fontCharacter": "\\E03E", + "fontCharacter": "\\E03D", "fontColor": "#b8383d" }, "_haml": { - "fontCharacter": "\\E03E", + "fontCharacter": "\\E03D", "fontColor": "#cc3e44" }, "_happenings_light": { - "fontCharacter": "\\E03F", + "fontCharacter": "\\E03E", "fontColor": "#498ba7" }, "_happenings": { - "fontCharacter": "\\E03F", + "fontCharacter": "\\E03E", "fontColor": "#519aba" }, "_haskell_light": { - "fontCharacter": "\\E040", + "fontCharacter": "\\E03F", "fontColor": "#9068b0" }, "_haskell": { - "fontCharacter": "\\E040", + "fontCharacter": "\\E03F", "fontColor": "#a074c4" }, "_haxe_light": { - "fontCharacter": "\\E041", + "fontCharacter": "\\E040", "fontColor": "#cc6d2e" }, "_haxe": { - "fontCharacter": "\\E041", + "fontCharacter": "\\E040", "fontColor": "#e37933" }, "_haxe_1_light": { - "fontCharacter": "\\E041", + "fontCharacter": "\\E040", "fontColor": "#b7b73b" }, "_haxe_1": { - "fontCharacter": "\\E041", + "fontCharacter": "\\E040", "fontColor": "#cbcb41" }, "_haxe_2_light": { - "fontCharacter": "\\E041", + "fontCharacter": "\\E040", "fontColor": "#498ba7" }, "_haxe_2": { - "fontCharacter": "\\E041", + "fontCharacter": "\\E040", "fontColor": "#519aba" }, "_haxe_3_light": { - "fontCharacter": "\\E041", + "fontCharacter": "\\E040", "fontColor": "#9068b0" }, "_haxe_3": { - "fontCharacter": "\\E041", + "fontCharacter": "\\E040", "fontColor": "#a074c4" }, "_heroku_light": { - "fontCharacter": "\\E042", + "fontCharacter": "\\E041", "fontColor": "#9068b0" }, "_heroku": { - "fontCharacter": "\\E042", + "fontCharacter": "\\E041", "fontColor": "#a074c4" }, "_hex_light": { - "fontCharacter": "\\E043", + "fontCharacter": "\\E042", "fontColor": "#b8383d" }, "_hex": { - "fontCharacter": "\\E043", + "fontCharacter": "\\E042", "fontColor": "#cc3e44" }, "_html_light": { - "fontCharacter": "\\E044", + "fontCharacter": "\\E043", "fontColor": "#498ba7" }, "_html": { - "fontCharacter": "\\E044", + "fontCharacter": "\\E043", "fontColor": "#519aba" }, "_html_1_light": { - "fontCharacter": "\\E044", + "fontCharacter": "\\E043", "fontColor": "#7fae42" }, "_html_1": { - "fontCharacter": "\\E044", + "fontCharacter": "\\E043", "fontColor": "#8dc149" }, "_html_2_light": { - "fontCharacter": "\\E044", + "fontCharacter": "\\E043", "fontColor": "#b7b73b" }, "_html_2": { - "fontCharacter": "\\E044", + "fontCharacter": "\\E043", "fontColor": "#cbcb41" }, "_html_3_light": { - "fontCharacter": "\\E044", + "fontCharacter": "\\E043", "fontColor": "#cc6d2e" }, "_html_3": { - "fontCharacter": "\\E044", + "fontCharacter": "\\E043", "fontColor": "#e37933" }, "_html_erb_light": { - "fontCharacter": "\\E045", + "fontCharacter": "\\E044", "fontColor": "#b8383d" }, "_html_erb": { - "fontCharacter": "\\E045", + "fontCharacter": "\\E044", "fontColor": "#cc3e44" }, "_ignored_light": { - "fontCharacter": "\\E046", + "fontCharacter": "\\E045", "fontColor": "#3b4b52" }, "_ignored": { - "fontCharacter": "\\E046", + "fontCharacter": "\\E045", "fontColor": "#41535b" }, "_illustrator_light": { - "fontCharacter": "\\E047", + "fontCharacter": "\\E046", "fontColor": "#b7b73b" }, "_illustrator": { - "fontCharacter": "\\E047", + "fontCharacter": "\\E046", "fontColor": "#cbcb41" }, "_image_light": { - "fontCharacter": "\\E048", + "fontCharacter": "\\E047", "fontColor": "#9068b0" }, "_image": { - "fontCharacter": "\\E048", + "fontCharacter": "\\E047", "fontColor": "#a074c4" }, "_info_light": { - "fontCharacter": "\\E049", + "fontCharacter": "\\E048", "fontColor": "#498ba7" }, "_info": { - "fontCharacter": "\\E049", + "fontCharacter": "\\E048", "fontColor": "#519aba" }, "_ionic_light": { - "fontCharacter": "\\E04A", + "fontCharacter": "\\E049", "fontColor": "#498ba7" }, "_ionic": { - "fontCharacter": "\\E04A", + "fontCharacter": "\\E049", "fontColor": "#519aba" }, "_jade_light": { - "fontCharacter": "\\E04B", + "fontCharacter": "\\E04A", "fontColor": "#b8383d" }, "_jade": { - "fontCharacter": "\\E04B", + "fontCharacter": "\\E04A", "fontColor": "#cc3e44" }, "_java_light": { - "fontCharacter": "\\E04C", + "fontCharacter": "\\E04B", "fontColor": "#b8383d" }, "_java": { - "fontCharacter": "\\E04C", + "fontCharacter": "\\E04B", "fontColor": "#cc3e44" }, "_javascript_light": { - "fontCharacter": "\\E04D", + "fontCharacter": "\\E04C", "fontColor": "#b7b73b" }, "_javascript": { - "fontCharacter": "\\E04D", + "fontCharacter": "\\E04C", "fontColor": "#cbcb41" }, "_javascript_1_light": { - "fontCharacter": "\\E04D", + "fontCharacter": "\\E04C", "fontColor": "#cc6d2e" }, "_javascript_1": { - "fontCharacter": "\\E04D", + "fontCharacter": "\\E04C", "fontColor": "#e37933" }, "_javascript_2_light": { - "fontCharacter": "\\E04D", + "fontCharacter": "\\E04C", "fontColor": "#498ba7" }, "_javascript_2": { - "fontCharacter": "\\E04D", + "fontCharacter": "\\E04C", "fontColor": "#519aba" }, "_jenkins_light": { - "fontCharacter": "\\E04E", + "fontCharacter": "\\E04D", "fontColor": "#b8383d" }, "_jenkins": { - "fontCharacter": "\\E04E", + "fontCharacter": "\\E04D", "fontColor": "#cc3e44" }, "_jinja_light": { - "fontCharacter": "\\E04F", + "fontCharacter": "\\E04E", "fontColor": "#b8383d" }, "_jinja": { - "fontCharacter": "\\E04F", + "fontCharacter": "\\E04E", "fontColor": "#cc3e44" }, "_json_light": { - "fontCharacter": "\\E051", + "fontCharacter": "\\E04F", "fontColor": "#b7b73b" }, "_json": { - "fontCharacter": "\\E051", + "fontCharacter": "\\E04F", "fontColor": "#cbcb41" }, "_json_1_light": { - "fontCharacter": "\\E051", + "fontCharacter": "\\E04F", "fontColor": "#7fae42" }, "_json_1": { - "fontCharacter": "\\E051", + "fontCharacter": "\\E04F", "fontColor": "#8dc149" }, "_julia_light": { - "fontCharacter": "\\E052", + "fontCharacter": "\\E051", "fontColor": "#9068b0" }, "_julia": { - "fontCharacter": "\\E052", + "fontCharacter": "\\E051", "fontColor": "#a074c4" }, "_karma_light": { - "fontCharacter": "\\E053", + "fontCharacter": "\\E052", "fontColor": "#7fae42" }, "_karma": { - "fontCharacter": "\\E053", + "fontCharacter": "\\E052", "fontColor": "#8dc149" }, "_kotlin_light": { - "fontCharacter": "\\E054", + "fontCharacter": "\\E053", "fontColor": "#cc6d2e" }, "_kotlin": { - "fontCharacter": "\\E054", + "fontCharacter": "\\E053", "fontColor": "#e37933" }, "_less_light": { - "fontCharacter": "\\E055", + "fontCharacter": "\\E054", "fontColor": "#498ba7" }, "_less": { - "fontCharacter": "\\E055", + "fontCharacter": "\\E054", "fontColor": "#519aba" }, "_license_light": { - "fontCharacter": "\\E056", + "fontCharacter": "\\E055", "fontColor": "#b7b73b" }, "_license": { - "fontCharacter": "\\E056", + "fontCharacter": "\\E055", "fontColor": "#cbcb41" }, "_license_1_light": { - "fontCharacter": "\\E056", + "fontCharacter": "\\E055", "fontColor": "#cc6d2e" }, "_license_1": { - "fontCharacter": "\\E056", + "fontCharacter": "\\E055", "fontColor": "#e37933" }, "_license_2_light": { - "fontCharacter": "\\E056", + "fontCharacter": "\\E055", "fontColor": "#b8383d" }, "_license_2": { - "fontCharacter": "\\E056", + "fontCharacter": "\\E055", "fontColor": "#cc3e44" }, "_liquid_light": { - "fontCharacter": "\\E057", + "fontCharacter": "\\E056", "fontColor": "#7fae42" }, "_liquid": { - "fontCharacter": "\\E057", + "fontCharacter": "\\E056", "fontColor": "#8dc149" }, "_livescript_light": { - "fontCharacter": "\\E058", + "fontCharacter": "\\E057", "fontColor": "#498ba7" }, "_livescript": { - "fontCharacter": "\\E058", + "fontCharacter": "\\E057", "fontColor": "#519aba" }, "_lock_light": { - "fontCharacter": "\\E059", + "fontCharacter": "\\E058", "fontColor": "#7fae42" }, "_lock": { - "fontCharacter": "\\E059", + "fontCharacter": "\\E058", "fontColor": "#8dc149" }, "_lua_light": { - "fontCharacter": "\\E05A", + "fontCharacter": "\\E059", "fontColor": "#498ba7" }, "_lua": { - "fontCharacter": "\\E05A", + "fontCharacter": "\\E059", "fontColor": "#519aba" }, "_makefile_light": { - "fontCharacter": "\\E05B", + "fontCharacter": "\\E05A", "fontColor": "#cc6d2e" }, "_makefile": { - "fontCharacter": "\\E05B", + "fontCharacter": "\\E05A", "fontColor": "#e37933" }, "_makefile_1_light": { - "fontCharacter": "\\E05B", + "fontCharacter": "\\E05A", "fontColor": "#9068b0" }, "_makefile_1": { - "fontCharacter": "\\E05B", + "fontCharacter": "\\E05A", "fontColor": "#a074c4" }, "_makefile_2_light": { - "fontCharacter": "\\E05B", + "fontCharacter": "\\E05A", "fontColor": "#627379" }, "_makefile_2": { - "fontCharacter": "\\E05B", + "fontCharacter": "\\E05A", "fontColor": "#6d8086" }, "_makefile_3_light": { - "fontCharacter": "\\E05B", + "fontCharacter": "\\E05A", "fontColor": "#498ba7" }, "_makefile_3": { - "fontCharacter": "\\E05B", + "fontCharacter": "\\E05A", "fontColor": "#519aba" }, "_markdown_light": { - "fontCharacter": "\\E05C", + "fontCharacter": "\\E05B", "fontColor": "#498ba7" }, "_markdown": { - "fontCharacter": "\\E05C", + "fontCharacter": "\\E05B", "fontColor": "#519aba" }, "_maven_light": { - "fontCharacter": "\\E05D", + "fontCharacter": "\\E05C", "fontColor": "#b8383d" }, "_maven": { - "fontCharacter": "\\E05D", + "fontCharacter": "\\E05C", "fontColor": "#cc3e44" }, "_mdo_light": { - "fontCharacter": "\\E05E", + "fontCharacter": "\\E05D", "fontColor": "#b8383d" }, "_mdo": { - "fontCharacter": "\\E05E", + "fontCharacter": "\\E05D", "fontColor": "#cc3e44" }, "_mustache_light": { - "fontCharacter": "\\E05F", + "fontCharacter": "\\E05E", "fontColor": "#cc6d2e" }, "_mustache": { - "fontCharacter": "\\E05F", + "fontCharacter": "\\E05E", "fontColor": "#e37933" }, "_nim_light": { - "fontCharacter": "\\E061", + "fontCharacter": "\\E060", "fontColor": "#b7b73b" }, "_nim": { - "fontCharacter": "\\E061", + "fontCharacter": "\\E060", "fontColor": "#cbcb41" }, "_notebook_light": { - "fontCharacter": "\\E062", + "fontCharacter": "\\E061", "fontColor": "#498ba7" }, "_notebook": { - "fontCharacter": "\\E062", + "fontCharacter": "\\E061", "fontColor": "#519aba" }, "_npm_light": { - "fontCharacter": "\\E063", + "fontCharacter": "\\E062", "fontColor": "#3b4b52" }, "_npm": { - "fontCharacter": "\\E063", + "fontCharacter": "\\E062", "fontColor": "#41535b" }, "_npm_1_light": { - "fontCharacter": "\\E063", + "fontCharacter": "\\E062", "fontColor": "#b8383d" }, "_npm_1": { - "fontCharacter": "\\E063", + "fontCharacter": "\\E062", "fontColor": "#cc3e44" }, "_npm_ignored_light": { - "fontCharacter": "\\E064", + "fontCharacter": "\\E063", "fontColor": "#3b4b52" }, "_npm_ignored": { - "fontCharacter": "\\E064", + "fontCharacter": "\\E063", "fontColor": "#41535b" }, "_nunjucks_light": { - "fontCharacter": "\\E065", + "fontCharacter": "\\E064", "fontColor": "#7fae42" }, "_nunjucks": { - "fontCharacter": "\\E065", + "fontCharacter": "\\E064", "fontColor": "#8dc149" }, "_ocaml_light": { - "fontCharacter": "\\E066", + "fontCharacter": "\\E065", "fontColor": "#cc6d2e" }, "_ocaml": { - "fontCharacter": "\\E066", + "fontCharacter": "\\E065", "fontColor": "#e37933" }, "_odata_light": { - "fontCharacter": "\\E067", + "fontCharacter": "\\E066", "fontColor": "#cc6d2e" }, "_odata": { - "fontCharacter": "\\E067", + "fontCharacter": "\\E066", "fontColor": "#e37933" }, "_pddl_light": { - "fontCharacter": "\\E068", + "fontCharacter": "\\E067", "fontColor": "#9068b0" }, "_pddl": { - "fontCharacter": "\\E068", + "fontCharacter": "\\E067", "fontColor": "#a074c4" }, "_pdf_light": { - "fontCharacter": "\\E069", + "fontCharacter": "\\E068", "fontColor": "#b8383d" }, "_pdf": { - "fontCharacter": "\\E069", + "fontCharacter": "\\E068", "fontColor": "#cc3e44" }, "_perl_light": { - "fontCharacter": "\\E06A", + "fontCharacter": "\\E069", "fontColor": "#498ba7" }, "_perl": { - "fontCharacter": "\\E06A", + "fontCharacter": "\\E069", "fontColor": "#519aba" }, "_photoshop_light": { - "fontCharacter": "\\E06B", + "fontCharacter": "\\E06A", "fontColor": "#498ba7" }, "_photoshop": { - "fontCharacter": "\\E06B", + "fontCharacter": "\\E06A", "fontColor": "#519aba" }, "_php_light": { - "fontCharacter": "\\E06C", + "fontCharacter": "\\E06B", "fontColor": "#9068b0" }, "_php": { - "fontCharacter": "\\E06C", + "fontCharacter": "\\E06B", "fontColor": "#a074c4" }, "_plan_light": { - "fontCharacter": "\\E06D", + "fontCharacter": "\\E06C", "fontColor": "#7fae42" }, "_plan": { - "fontCharacter": "\\E06D", + "fontCharacter": "\\E06C", "fontColor": "#8dc149" }, "_platformio_light": { - "fontCharacter": "\\E06E", + "fontCharacter": "\\E06D", "fontColor": "#cc6d2e" }, "_platformio": { - "fontCharacter": "\\E06E", + "fontCharacter": "\\E06D", "fontColor": "#e37933" }, "_powershell_light": { - "fontCharacter": "\\E06F", + "fontCharacter": "\\E06E", "fontColor": "#498ba7" }, "_powershell": { - "fontCharacter": "\\E06F", + "fontCharacter": "\\E06E", "fontColor": "#519aba" }, "_prisma_light": { - "fontCharacter": "\\E070", + "fontCharacter": "\\E06F", "fontColor": "#498ba7" }, "_prisma": { - "fontCharacter": "\\E070", + "fontCharacter": "\\E06F", "fontColor": "#519aba" }, "_prolog_light": { - "fontCharacter": "\\E072", + "fontCharacter": "\\E071", "fontColor": "#cc6d2e" }, "_prolog": { - "fontCharacter": "\\E072", + "fontCharacter": "\\E071", "fontColor": "#e37933" }, "_pug_light": { - "fontCharacter": "\\E073", + "fontCharacter": "\\E072", "fontColor": "#b8383d" }, "_pug": { - "fontCharacter": "\\E073", + "fontCharacter": "\\E072", "fontColor": "#cc3e44" }, "_puppet_light": { - "fontCharacter": "\\E074", + "fontCharacter": "\\E073", "fontColor": "#b7b73b" }, "_puppet": { - "fontCharacter": "\\E074", + "fontCharacter": "\\E073", "fontColor": "#cbcb41" }, "_python_light": { - "fontCharacter": "\\E075", + "fontCharacter": "\\E074", "fontColor": "#498ba7" }, "_python": { - "fontCharacter": "\\E075", + "fontCharacter": "\\E074", "fontColor": "#519aba" }, "_react_light": { @@ -1198,223 +1198,231 @@ "fontCharacter": "\\E088", "fontColor": "#e37933" }, - "_svg_light": { + "_svelte_light": { "fontCharacter": "\\E089", + "fontColor": "#b8383d" + }, + "_svelte": { + "fontCharacter": "\\E089", + "fontColor": "#cc3e44" + }, + "_svg_light": { + "fontCharacter": "\\E08A", "fontColor": "#9068b0" }, "_svg": { - "fontCharacter": "\\E089", + "fontCharacter": "\\E08A", "fontColor": "#a074c4" }, "_svg_1_light": { - "fontCharacter": "\\E089", + "fontCharacter": "\\E08A", "fontColor": "#498ba7" }, "_svg_1": { - "fontCharacter": "\\E089", + "fontCharacter": "\\E08A", "fontColor": "#519aba" }, "_swift_light": { - "fontCharacter": "\\E08A", + "fontCharacter": "\\E08B", "fontColor": "#cc6d2e" }, "_swift": { - "fontCharacter": "\\E08A", + "fontCharacter": "\\E08B", "fontColor": "#e37933" }, "_terraform_light": { - "fontCharacter": "\\E08B", + "fontCharacter": "\\E08C", "fontColor": "#9068b0" }, "_terraform": { - "fontCharacter": "\\E08B", + "fontCharacter": "\\E08C", "fontColor": "#a074c4" }, "_tex_light": { - "fontCharacter": "\\E08C", + "fontCharacter": "\\E08D", "fontColor": "#498ba7" }, "_tex": { - "fontCharacter": "\\E08C", + "fontCharacter": "\\E08D", "fontColor": "#519aba" }, "_tex_1_light": { - "fontCharacter": "\\E08C", + "fontCharacter": "\\E08D", "fontColor": "#b7b73b" }, "_tex_1": { - "fontCharacter": "\\E08C", + "fontCharacter": "\\E08D", "fontColor": "#cbcb41" }, "_tex_2_light": { - "fontCharacter": "\\E08C", + "fontCharacter": "\\E08D", "fontColor": "#cc6d2e" }, "_tex_2": { - "fontCharacter": "\\E08C", + "fontCharacter": "\\E08D", "fontColor": "#e37933" }, "_tex_3_light": { - "fontCharacter": "\\E08C", + "fontCharacter": "\\E08D", "fontColor": "#bfc2c1" }, "_tex_3": { - "fontCharacter": "\\E08C", + "fontCharacter": "\\E08D", "fontColor": "#d4d7d6" }, "_todo": { - "fontCharacter": "\\E08E" + "fontCharacter": "\\E08F" }, "_tsconfig_light": { - "fontCharacter": "\\E08F", + "fontCharacter": "\\E090", "fontColor": "#498ba7" }, "_tsconfig": { - "fontCharacter": "\\E08F", + "fontCharacter": "\\E090", "fontColor": "#519aba" }, "_twig_light": { - "fontCharacter": "\\E090", + "fontCharacter": "\\E091", "fontColor": "#7fae42" }, "_twig": { - "fontCharacter": "\\E090", + "fontCharacter": "\\E091", "fontColor": "#8dc149" }, "_typescript_light": { - "fontCharacter": "\\E091", + "fontCharacter": "\\E092", "fontColor": "#498ba7" }, "_typescript": { - "fontCharacter": "\\E091", + "fontCharacter": "\\E092", "fontColor": "#519aba" }, "_typescript_1_light": { - "fontCharacter": "\\E091", + "fontCharacter": "\\E092", "fontColor": "#b7b73b" }, "_typescript_1": { - "fontCharacter": "\\E091", + "fontCharacter": "\\E092", "fontColor": "#cbcb41" }, "_vala_light": { - "fontCharacter": "\\E092", + "fontCharacter": "\\E093", "fontColor": "#627379" }, "_vala": { - "fontCharacter": "\\E092", + "fontCharacter": "\\E093", "fontColor": "#6d8086" }, "_video_light": { - "fontCharacter": "\\E093", + "fontCharacter": "\\E094", "fontColor": "#dd4b78" }, "_video": { - "fontCharacter": "\\E093", + "fontCharacter": "\\E094", "fontColor": "#f55385" }, "_vue_light": { - "fontCharacter": "\\E094", + "fontCharacter": "\\E095", "fontColor": "#7fae42" }, "_vue": { - "fontCharacter": "\\E094", + "fontCharacter": "\\E095", "fontColor": "#8dc149" }, "_wasm_light": { - "fontCharacter": "\\E095", + "fontCharacter": "\\E096", "fontColor": "#9068b0" }, "_wasm": { - "fontCharacter": "\\E095", + "fontCharacter": "\\E096", "fontColor": "#a074c4" }, "_wat_light": { - "fontCharacter": "\\E096", + "fontCharacter": "\\E097", "fontColor": "#9068b0" }, "_wat": { - "fontCharacter": "\\E096", + "fontCharacter": "\\E097", "fontColor": "#a074c4" }, "_webpack_light": { - "fontCharacter": "\\E097", + "fontCharacter": "\\E098", "fontColor": "#498ba7" }, "_webpack": { - "fontCharacter": "\\E097", + "fontCharacter": "\\E098", "fontColor": "#519aba" }, "_wgt_light": { - "fontCharacter": "\\E098", + "fontCharacter": "\\E099", "fontColor": "#498ba7" }, "_wgt": { - "fontCharacter": "\\E098", + "fontCharacter": "\\E099", "fontColor": "#519aba" }, "_windows_light": { - "fontCharacter": "\\E099", + "fontCharacter": "\\E09A", "fontColor": "#498ba7" }, "_windows": { - "fontCharacter": "\\E099", + "fontCharacter": "\\E09A", "fontColor": "#519aba" }, "_word_light": { - "fontCharacter": "\\E09A", + "fontCharacter": "\\E09B", "fontColor": "#498ba7" }, "_word": { - "fontCharacter": "\\E09A", + "fontCharacter": "\\E09B", "fontColor": "#519aba" }, "_xls_light": { - "fontCharacter": "\\E09B", + "fontCharacter": "\\E09C", "fontColor": "#7fae42" }, "_xls": { - "fontCharacter": "\\E09B", + "fontCharacter": "\\E09C", "fontColor": "#8dc149" }, "_xml_light": { - "fontCharacter": "\\E09C", + "fontCharacter": "\\E09D", "fontColor": "#cc6d2e" }, "_xml": { - "fontCharacter": "\\E09C", + "fontCharacter": "\\E09D", "fontColor": "#e37933" }, "_yarn_light": { - "fontCharacter": "\\E09D", + "fontCharacter": "\\E09E", "fontColor": "#498ba7" }, "_yarn": { - "fontCharacter": "\\E09D", + "fontCharacter": "\\E09E", "fontColor": "#519aba" }, "_yml_light": { - "fontCharacter": "\\E09E", + "fontCharacter": "\\E09F", "fontColor": "#9068b0" }, "_yml": { - "fontCharacter": "\\E09E", + "fontCharacter": "\\E09F", "fontColor": "#a074c4" }, "_zip_light": { - "fontCharacter": "\\E09F", + "fontCharacter": "\\E0A0", "fontColor": "#b8383d" }, "_zip": { - "fontCharacter": "\\E09F", + "fontCharacter": "\\E0A0", "fontColor": "#cc3e44" }, "_zip_1_light": { - "fontCharacter": "\\E09F", + "fontCharacter": "\\E0A0", "fontColor": "#627379" }, "_zip_1": { - "fontCharacter": "\\E09F", + "fontCharacter": "\\E0A0", "fontColor": "#6d8086" } }, @@ -1546,6 +1554,7 @@ "scala": "_scala", "sol": "_ethereum", "styl": "_stylus", + "svelte": "_svelte", "tf": "_terraform", "tf.json": "_terraform", "tfvars": "_terraform", @@ -1666,9 +1675,6 @@ "gruntfile.coffee": "_grunt", "gulpfile": "_gulp", "gulpfile.js": "_gulp", - "gulpfile.mjs": "_gulp", - "gulpfile.cjs": "_gulp", - "gulpfile.esm.js": "_gulp", "ionic.config.json": "_ionic", "ionic.project": "_ionic", "platformio.ini": "_platformio", @@ -1757,6 +1763,7 @@ "haml": "_haml", "stylus": "_stylus", "vala": "_vala", + "github-issues": "_github", "todo": "_todo", "postcss": "_css", "django-html": "_html_3" @@ -1890,6 +1897,7 @@ "scala": "_scala_light", "sol": "_ethereum_light", "styl": "_stylus_light", + "svelte": "_svelte_light", "tf": "_terraform_light", "tf.json": "_terraform_light", "tfvars": "_terraform_light", @@ -2041,6 +2049,7 @@ "haml": "_haml_light", "stylus": "_stylus_light", "vala": "_vala_light", + "github-issues": "_github_light", "postcss": "_css_light", "django-html": "_html_3_light" }, @@ -2101,5 +2110,5 @@ "npm-debug.log": "_npm_ignored_light" } }, - "version": "https://github.com/jesseweed/seti-ui/commit/4bbf2132df28c71302e305077ce20a811bf7d64b" -} + "version": "https://github.com/jesseweed/seti-ui/commit/9c1c29d6e9358f9ae99bd3a4bf0d2fa804dca686" +} \ No newline at end of file diff --git a/extensions/theme-seti/package.json b/extensions/theme-seti/package.json index bcdbb3209..fd0dec2c8 100644 --- a/extensions/theme-seti/package.json +++ b/extensions/theme-seti/package.json @@ -1,23 +1,29 @@ { - "name": "vscode-theme-seti", - "private": true, - "version": "1.0.0", - "displayName": "%displayName%", - "description": "%description%", - "publisher": "vscode", - "license": "MIT", - "icon": "icons/seti-circular-128x128.png", - "scripts": { - "update": "node ./build/update-icon-theme.js" - }, - "engines": { "vscode": "*" }, - "contributes": { - "iconThemes": [ - { - "id": "vs-seti", - "label": "%themeLabel%", - "path": "./icons/vs-seti-icon-theme.json" - } - ] - } + "name": "vscode-theme-seti", + "private": true, + "version": "1.0.0", + "displayName": "%displayName%", + "description": "%description%", + "publisher": "vscode", + "license": "MIT", + "icon": "icons/seti-circular-128x128.png", + "scripts": { + "update": "node ./build/update-icon-theme.js" + }, + "engines": { + "vscode": "*" + }, + "contributes": { + "iconThemes": [ + { + "id": "vs-seti", + "label": "%themeLabel%", + "path": "./icons/vs-seti-icon-theme.json" + } + ] + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" + } } diff --git a/extensions/theme-seti/yarn.lock b/extensions/theme-seti/yarn.lock new file mode 100644 index 000000000..fb57ccd13 --- /dev/null +++ b/extensions/theme-seti/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/extensions/theme-solarized-dark/package.json b/extensions/theme-solarized-dark/package.json index eb7dc5ff9..fee98f594 100644 --- a/extensions/theme-solarized-dark/package.json +++ b/extensions/theme-solarized-dark/package.json @@ -1,19 +1,25 @@ { - "name": "theme-solarized-dark", - "displayName": "%displayName%", - "description": "%description%", - "version": "1.0.0", - "publisher": "vscode", - "license": "MIT", - "engines": { "vscode": "*" }, - "contributes": { - "themes": [ - { - "id": "Solarized Dark", - "label": "%themeLabel%", - "uiTheme": "vs-dark", - "path": "./themes/solarized-dark-color-theme.json" - } - ] - } + "name": "theme-solarized-dark", + "displayName": "%displayName%", + "description": "%description%", + "version": "1.0.0", + "publisher": "vscode", + "license": "MIT", + "engines": { + "vscode": "*" + }, + "contributes": { + "themes": [ + { + "id": "Solarized Dark", + "label": "%themeLabel%", + "uiTheme": "vs-dark", + "path": "./themes/solarized-dark-color-theme.json" + } + ] + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" + } } diff --git a/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json b/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json index 8a9deb0cd..b5aba9828 100644 --- a/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json +++ b/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json @@ -350,7 +350,7 @@ "list.activeSelectionBackground": "#005A6F", // "list.activeSelectionForeground": "", - "list.focusBackground": "#005A6F", + "quickInputList.focusBackground": "#005A6F", "list.hoverBackground": "#004454AA", "list.inactiveSelectionBackground": "#00445488", "list.dropBackground": "#00445488", diff --git a/extensions/theme-solarized-dark/yarn.lock b/extensions/theme-solarized-dark/yarn.lock new file mode 100644 index 000000000..fb57ccd13 --- /dev/null +++ b/extensions/theme-solarized-dark/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/extensions/theme-solarized-light/package.json b/extensions/theme-solarized-light/package.json index 421aa7a82..7efd642d5 100644 --- a/extensions/theme-solarized-light/package.json +++ b/extensions/theme-solarized-light/package.json @@ -1,19 +1,25 @@ { - "name": "theme-solarized-light", - "displayName": "%displayName%", - "description": "%description%", - "version": "1.0.0", - "publisher": "vscode", - "license": "MIT", - "engines": { "vscode": "*" }, - "contributes": { - "themes": [ - { - "id": "Solarized Light", - "label": "%themeLabel%", - "uiTheme": "vs", - "path": "./themes/solarized-light-color-theme.json" - } - ] - } + "name": "theme-solarized-light", + "displayName": "%displayName%", + "description": "%description%", + "version": "1.0.0", + "publisher": "vscode", + "license": "MIT", + "engines": { + "vscode": "*" + }, + "contributes": { + "themes": [ + { + "id": "Solarized Light", + "label": "%themeLabel%", + "uiTheme": "vs", + "path": "./themes/solarized-light-color-theme.json" + } + ] + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" + } } diff --git a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json index 427b7c3c3..7c14e8996 100644 --- a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json +++ b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json @@ -350,7 +350,7 @@ "list.activeSelectionBackground": "#DFCA88", "list.activeSelectionForeground": "#6C6C6C", - "list.focusBackground": "#DFCA8866", + "quickInputList.focusBackground": "#DFCA8866", "list.hoverBackground": "#DFCA8844", "list.inactiveSelectionBackground": "#D1CBB8", "list.highlightForeground": "#B58900", diff --git a/extensions/theme-solarized-light/yarn.lock b/extensions/theme-solarized-light/yarn.lock new file mode 100644 index 000000000..fb57ccd13 --- /dev/null +++ b/extensions/theme-solarized-light/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/extensions/theme-tomorrow-night-blue/package.json b/extensions/theme-tomorrow-night-blue/package.json index e03f05d8a..a9a6405a9 100644 --- a/extensions/theme-tomorrow-night-blue/package.json +++ b/extensions/theme-tomorrow-night-blue/package.json @@ -1,19 +1,25 @@ { - "name": "theme-tomorrow-night-blue", - "displayName": "%displayName%", - "description": "%description%", - "version": "1.0.0", - "publisher": "vscode", - "license": "MIT", - "engines": { "vscode": "*" }, - "contributes": { - "themes": [ - { - "id": "Tomorrow Night Blue", - "label": "%themeLabel%", - "uiTheme": "vs-dark", - "path": "./themes/tomorrow-night-blue-color-theme.json" - } - ] - } + "name": "theme-tomorrow-night-blue", + "displayName": "%displayName%", + "description": "%description%", + "version": "1.0.0", + "publisher": "vscode", + "license": "MIT", + "engines": { + "vscode": "*" + }, + "contributes": { + "themes": [ + { + "id": "Tomorrow Night Blue", + "label": "%themeLabel%", + "uiTheme": "vs-dark", + "path": "./themes/tomorrow-night-blue-color-theme.json" + } + ] + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" + } } diff --git a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-color-theme.json b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-color-theme.json index 91c135342..f2ea18b7b 100644 --- a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-color-theme.json +++ b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-color-theme.json @@ -5,7 +5,7 @@ "errorForeground": "#a92049", "input.background": "#001733", "dropdown.background": "#001733", - "list.focusBackground": "#ffffff60", + "quickInputList.focusBackground": "#ffffff60", "list.activeSelectionBackground": "#ffffff60", "list.inactiveSelectionBackground": "#ffffff40", "list.hoverBackground": "#ffffff30", diff --git a/extensions/theme-tomorrow-night-blue/yarn.lock b/extensions/theme-tomorrow-night-blue/yarn.lock new file mode 100644 index 000000000..fb57ccd13 --- /dev/null +++ b/extensions/theme-tomorrow-night-blue/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/extensions/typescript-basics/build/update-grammars.js b/extensions/typescript-basics/build/update-grammars.js index b58a2f57d..c70c27068 100644 --- a/extensions/typescript-basics/build/update-grammars.js +++ b/extensions/typescript-basics/build/update-grammars.js @@ -5,7 +5,7 @@ // @ts-check 'use strict'; -var updateGrammar = require('../../../build/npm/update-grammar'); +var updateGrammar = require('vscode-grammar-updater'); function removeDom(grammar) { grammar.repository['support-objects'].patterns = grammar.repository['support-objects'].patterns.filter(pattern => { @@ -26,8 +26,8 @@ function removeNodeTypes(grammar) { } if (pattern.captures) { if (Object.values(pattern.captures).some(capture => - capture.name && (capture.name.startsWith('support.variable.object.process') - || capture.name.startsWith('support.class.console')) + capture.name && (capture.name.startsWith('support.variable.object.process') + || capture.name.startsWith('support.class.console')) )) { return false; } diff --git a/extensions/typescript-basics/cgmanifest.json b/extensions/typescript-basics/cgmanifest.json index 11992b0e4..7aa0f4ee6 100644 --- a/extensions/typescript-basics/cgmanifest.json +++ b/extensions/typescript-basics/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "TypeScript-TmLanguage", "repositoryUrl": "https://github.com/microsoft/TypeScript-TmLanguage", - "commitHash": "398985941eb36cd270054a6e668d03fb9ef92e77" + "commitHash": "d3c203d25a1df5def83082a47d1fe20fda2e80df" } }, "license": "MIT", diff --git a/extensions/typescript-basics/package.json b/extensions/typescript-basics/package.json index 8ef6a585e..65a5326da 100644 --- a/extensions/typescript-basics/package.json +++ b/extensions/typescript-basics/package.json @@ -167,5 +167,9 @@ "path": "./snippets/typescript.code-snippets" } ] + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" } } diff --git a/extensions/typescript-basics/package.nls.json b/extensions/typescript-basics/package.nls.json index 744f91f8a..09b90e9d3 100644 --- a/extensions/typescript-basics/package.nls.json +++ b/extensions/typescript-basics/package.nls.json @@ -1,4 +1,4 @@ { "displayName": "TypeScript Language Basics", "description": "Provides snippets, syntax highlighting, bracket matching and folding in TypeScript files." -} \ No newline at end of file +} diff --git a/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json b/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json index 54748a293..6f0b593db 100644 --- a/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json +++ b/extensions/typescript-basics/syntaxes/TypeScript.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/TypeScript-TmLanguage/commit/398985941eb36cd270054a6e668d03fb9ef92e77", + "version": "https://github.com/microsoft/TypeScript-TmLanguage/commit/d3c203d25a1df5def83082a47d1fe20fda2e80df", "name": "TypeScript", "scopeName": "source.ts", "patterns": [ @@ -134,7 +134,7 @@ "name": "keyword.control.flow.ts" } }, - "end": "(?=[;}]|$|;|(?:^\\s*(?:abstract|async|class|const|declare|enum|export|function|import|interface|let|module|namespace|return|type|var)\\b))", + "end": "(?=[;}]|$|;|^\\s*$|(?:^\\s*(?:abstract|async|class|const|declare|enum|export|function|import|interface|let|module|namespace|return|type|var)\\b))", "patterns": [ { "include": "#expression" @@ -296,7 +296,7 @@ { "name": "meta.var.expr.ts", "begin": "(?=(?=|<>|<|>" }, { - "match": "(?<=[_$[:alnum:]])(\\!)\\s*(/)(?![/*])", + "match": "(?<=[_$[:alnum:]])(\\!)\\s*(?:(/=)|(?:(/)(?![/*])))", "captures": { "1": { "name": "keyword.operator.logical.ts" }, "2": { + "name": "keyword.operator.assignment.compound.ts" + }, + "3": { "name": "keyword.operator.arithmetic.ts" } } @@ -3372,10 +3375,13 @@ "match": "%|\\*|/|-|\\+" }, { - "begin": "(?<=[_$[:alnum:])\\]])\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)+(/)(?![/*]))", - "end": "(/)(?!\\*([^\\*]|(\\*[^\\/]))*\\*\\/)", + "begin": "(?<=[_$[:alnum:])\\]])\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)+(?:(/=)|(?:(/)(?![/*]))))", + "end": "(?:(/=)|(?:(/)(?!\\*([^\\*]|(\\*[^\\/]))*\\*\\/)))", "endCaptures": { "1": { + "name": "keyword.operator.assignment.compound.ts" + }, + "2": { "name": "keyword.operator.arithmetic.ts" } }, @@ -3386,9 +3392,12 @@ ] }, { - "match": "(?<=[_$[:alnum:])\\]])\\s*(/)(?![/*])", + "match": "(?<=[_$[:alnum:])\\]])\\s*(?:(/=)|(?:(/)(?![/*])))", "captures": { "1": { + "name": "keyword.operator.assignment.compound.ts" + }, + "2": { "name": "keyword.operator.arithmetic.ts" } } @@ -3402,7 +3411,7 @@ "name": "keyword.operator.expression.typeof.ts" } }, - "end": "(?=[,);}\\]=>]|$|;|(?:^\\s*(?:abstract|async|class|const|declare|enum|export|function|import|interface|let|module|namespace|return|type|var)\\b))", + "end": "(?=[,);}\\]=>]|$|;|^\\s*$|(?:^\\s*(?:abstract|async|class|const|declare|enum|export|function|import|interface|let|module|namespace|return|type|var)\\b))", "patterns": [ { "include": "#expression" diff --git a/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json b/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json index 5f673691d..54874b652 100644 --- a/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json +++ b/extensions/typescript-basics/syntaxes/TypeScriptReact.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/TypeScript-TmLanguage/commit/398985941eb36cd270054a6e668d03fb9ef92e77", + "version": "https://github.com/microsoft/TypeScript-TmLanguage/commit/d3c203d25a1df5def83082a47d1fe20fda2e80df", "name": "TypeScriptReact", "scopeName": "source.tsx", "patterns": [ @@ -134,7 +134,7 @@ "name": "keyword.control.flow.tsx" } }, - "end": "(?=[;}]|$|;|(?:^\\s*(?:abstract|async|class|const|declare|enum|export|function|import|interface|let|module|namespace|return|type|var)\\b))", + "end": "(?=[;}]|$|;|^\\s*$|(?:^\\s*(?:abstract|async|class|const|declare|enum|export|function|import|interface|let|module|namespace|return|type|var)\\b))", "patterns": [ { "include": "#expression" @@ -299,7 +299,7 @@ { "name": "meta.var.expr.tsx", "begin": "(?=(?=|<>|<|>" }, { - "match": "(?<=[_$[:alnum:]])(\\!)\\s*(/)(?![/*])", + "match": "(?<=[_$[:alnum:]])(\\!)\\s*(?:(/=)|(?:(/)(?![/*])))", "captures": { "1": { "name": "keyword.operator.logical.tsx" }, "2": { + "name": "keyword.operator.assignment.compound.tsx" + }, + "3": { "name": "keyword.operator.arithmetic.tsx" } } @@ -3323,10 +3326,13 @@ "match": "%|\\*|/|-|\\+" }, { - "begin": "(?<=[_$[:alnum:])\\]])\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)+(/)(?![/*]))", - "end": "(/)(?!\\*([^\\*]|(\\*[^\\/]))*\\*\\/)", + "begin": "(?<=[_$[:alnum:])\\]])\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)+(?:(/=)|(?:(/)(?![/*]))))", + "end": "(?:(/=)|(?:(/)(?!\\*([^\\*]|(\\*[^\\/]))*\\*\\/)))", "endCaptures": { "1": { + "name": "keyword.operator.assignment.compound.tsx" + }, + "2": { "name": "keyword.operator.arithmetic.tsx" } }, @@ -3337,9 +3343,12 @@ ] }, { - "match": "(?<=[_$[:alnum:])\\]])\\s*(/)(?![/*])", + "match": "(?<=[_$[:alnum:])\\]])\\s*(?:(/=)|(?:(/)(?![/*])))", "captures": { "1": { + "name": "keyword.operator.assignment.compound.tsx" + }, + "2": { "name": "keyword.operator.arithmetic.tsx" } } @@ -3353,7 +3362,7 @@ "name": "keyword.operator.expression.typeof.tsx" } }, - "end": "(?=[,);}\\]=>]|$|;|(?:^\\s*(?:abstract|async|class|const|declare|enum|export|function|import|interface|let|module|namespace|return|type|var)\\b))", + "end": "(?=[,);}\\]=>]|$|;|^\\s*$|(?:^\\s*(?:abstract|async|class|const|declare|enum|export|function|import|interface|let|module|namespace|return|type|var)\\b))", "patterns": [ { "include": "#expression" diff --git a/extensions/typescript-basics/yarn.lock b/extensions/typescript-basics/yarn.lock new file mode 100644 index 000000000..fb57ccd13 --- /dev/null +++ b/extensions/typescript-basics/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/extensions/typescript-language-features/extension-browser.webpack.config.js b/extensions/typescript-language-features/extension-browser.webpack.config.js index ab1fc1aea..f358adc77 100644 --- a/extensions/typescript-language-features/extension-browser.webpack.config.js +++ b/extensions/typescript-language-features/extension-browser.webpack.config.js @@ -38,19 +38,19 @@ module.exports = withBrowserDefaults({ new CopyPlugin({ patterns: [ { - from: 'node_modules/typescript-web/lib/*.d.ts', - to: 'typescript-web/', + from: '../node_modules/typescript/lib/*.d.ts', + to: 'typescript/', flatten: true }, { - from: 'node_modules/typescript-web/lib/typesMap.json', - to: 'typescript-web/' + from: '../node_modules/typescript/lib/typesMap.json', + to: 'typescript/' }, ...languages.map(lang => ({ - from: `node_modules/typescript-web/lib/${lang}/**/*`, - to: 'typescript-web/', + from: `../node_modules/typescript/lib/${lang}/**/*`, + to: 'typescript/', transformPath: (targetPath) => { - return targetPath.replace(/node_modules[\/\\]typescript-web[\/\\]lib/, ''); + return targetPath.replace(/\.\.[\/\\]node_modules[\/\\]typescript[\/\\]lib/, ''); } })) ], @@ -59,8 +59,8 @@ module.exports = withBrowserDefaults({ new CopyPlugin({ patterns: [ { - from: 'node_modules/typescript-web/lib/tsserver.js', - to: 'typescript-web/tsserver.web.js', + from: '../node_modules/typescript/lib/tsserver.js', + to: 'typescript/tsserver.web.js', transform: (content) => { return Terser.minify(content.toString()).code; diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index d22f57d50..78201193e 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -17,7 +17,6 @@ ], "dependencies": { "jsonc-parser": "^2.2.1", - "rimraf": "^2.6.3", "semver": "5.5.1", "typescript-vscode-sh-plugin": "^0.6.14", "vscode-extension-telemetry": "0.1.1", @@ -26,8 +25,7 @@ "devDependencies": { "@types/node": "^12.19.9", "@types/rimraf": "^2.0.4", - "@types/semver": "^5.5.0", - "typescript-web": "npm:typescript@^4.2.0-dev.20201209" + "@types/semver": "^5.5.0" }, "scripts": { "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:typescript-language-features", @@ -604,6 +602,18 @@ "description": "%configuration.suggest.completeJSDocs%", "scope": "resource" }, + "javascript.suggest.jsdoc.generateReturns": { + "type": "boolean", + "default": true, + "markdownDescription": "%configuration.suggest.jsdoc.generateReturns%", + "scope": "resource" + }, + "typescript.suggest.jsdoc.generateReturns": { + "type": "boolean", + "default": true, + "markdownDescription": "%configuration.suggest.jsdoc.generateReturns%", + "scope": "resource" + }, "typescript.locale": { "type": [ "string", @@ -1200,5 +1210,9 @@ "enableForWorkspaceTypeScriptVersions": true } ] + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" } } diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index b6407a50b..85f9a75b5 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -68,6 +68,7 @@ "configuration.javascript.checkJs.experimentalDecorators.deprecation": "This setting has been deprecated in favor of `js/ts.implicitProjectConfig.experimentalDecorators`.", "configuration.implicitProjectConfig.strictNullChecks": "Enable/disable [strict null checks](https://www.typescriptlang.org/tsconfig#strictNullChecks) in JavaScript and TypeScript files that are not part of a project. Existing `jsconfig.json` or `tsconfig.json` files override this setting.", "configuration.implicitProjectConfig.strictFunctionTypes": "Enable/disable [strict function types](https://www.typescriptlang.org/tsconfig#strictFunctionTypes) in JavaScript and TypeScript files that are not part of a project. Existing `jsconfig.json` or `tsconfig.json` files override this setting.", + "configuration.suggest.jsdoc.generateReturns": "Enable/disable generating `@return` annotations for JSDoc templates. Requires using TypeScript 4.2+ in the workspace.", "configuration.suggest.autoImports": "Enable/disable auto import suggestions.", "taskDefinition.tsconfig.description": "The tsconfig file that defines the TS build.", "javascript.suggestionActions.enabled": "Enable/disable suggestion diagnostics for JavaScript files in the editor.", diff --git a/extensions/typescript-language-features/src/extension.browser.ts b/extensions/typescript-language-features/src/extension.browser.ts index f4818ab8d..676c5f366 100644 --- a/extensions/typescript-language-features/src/extension.browser.ts +++ b/extensions/typescript-language-features/src/extension.browser.ts @@ -52,7 +52,7 @@ export function activate( const versionProvider = new StaticVersionProvider( new TypeScriptVersion( TypeScriptVersionSource.Bundled, - vscode.Uri.joinPath(context.extensionUri, 'dist/browser/typescript-web/tsserver.web.js').toString(), + vscode.Uri.joinPath(context.extensionUri, 'dist/browser/typescript/tsserver.web.js').toString(), API.fromSimpleString('4.2.0'))); const lazyClientHost = createLazyClientHost(context, false, { diff --git a/extensions/typescript-language-features/src/extension.ts b/extensions/typescript-language-features/src/extension.ts index 1d6ccd291..2a567898f 100644 --- a/extensions/typescript-language-features/src/extension.ts +++ b/extensions/typescript-language-features/src/extension.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as rimraf from 'rimraf'; +import * as fs from 'fs'; import * as vscode from 'vscode'; import { Api, getExtensionApi } from './api'; +import { CommandManager } from './commands/commandManager'; import { registerBaseCommands } from './commands/index'; import { LanguageConfigurationManager } from './languageFeatures/languageConfiguration'; import { createLazyClientHost, lazilyActivateClient } from './lazyClientHost'; @@ -13,7 +14,6 @@ import { nodeRequestCancellerFactory } from './tsServer/cancellation.electron'; import { NodeLogDirectoryProvider } from './tsServer/logDirectoryProvider.electron'; import { ChildServerProcess } from './tsServer/serverProcess.electron'; import { DiskTypeScriptVersionProvider } from './tsServer/versionProvider.electron'; -import { CommandManager } from './commands/commandManager'; import { onCaseInsenitiveFileSystem } from './utils/fileSystem.electron'; import { PluginManager } from './utils/plugins'; import * as temp from './utils/temp.electron'; @@ -62,5 +62,5 @@ export function activate( } export function deactivate() { - rimraf.sync(temp.getInstanceTempDir()); + fs.rmdirSync(temp.getInstanceTempDir(), { recursive: true }); } diff --git a/extensions/typescript-language-features/src/languageFeatures/callHierarchy.ts b/extensions/typescript-language-features/src/languageFeatures/callHierarchy.ts index 836f372f2..5064dd148 100644 --- a/extensions/typescript-language-features/src/languageFeatures/callHierarchy.ts +++ b/extensions/typescript-language-features/src/languageFeatures/callHierarchy.ts @@ -54,7 +54,7 @@ class TypeScriptCallHierarchySupport implements vscode.CallHierarchyProvider { return undefined; } - return response.body.map(fromProtocolCallHierchyIncomingCall); + return response.body.map(fromProtocolCallHierarchyIncomingCall); } public async provideCallHierarchyOutgoingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise { @@ -69,7 +69,7 @@ class TypeScriptCallHierarchySupport implements vscode.CallHierarchyProvider { return undefined; } - return response.body.map(fromProtocolCallHierchyOutgoingCall); + return response.body.map(fromProtocolCallHierarchyOutgoingCall); } } @@ -97,14 +97,14 @@ function fromProtocolCallHierarchyItem(item: Proto.CallHierarchyItem): vscode.Ca return result; } -function fromProtocolCallHierchyIncomingCall(item: Proto.CallHierarchyIncomingCall): vscode.CallHierarchyIncomingCall { +function fromProtocolCallHierarchyIncomingCall(item: Proto.CallHierarchyIncomingCall): vscode.CallHierarchyIncomingCall { return new vscode.CallHierarchyIncomingCall( fromProtocolCallHierarchyItem(item.from), item.fromSpans.map(typeConverters.Range.fromTextSpan) ); } -function fromProtocolCallHierchyOutgoingCall(item: Proto.CallHierarchyOutgoingCall): vscode.CallHierarchyOutgoingCall { +function fromProtocolCallHierarchyOutgoingCall(item: Proto.CallHierarchyOutgoingCall): vscode.CallHierarchyOutgoingCall { return new vscode.CallHierarchyOutgoingCall( fromProtocolCallHierarchyItem(item.to), item.fromSpans.map(typeConverters.Range.fromTextSpan) diff --git a/extensions/typescript-language-features/src/languageFeatures/completions.ts b/extensions/typescript-language-features/src/languageFeatures/completions.ts index 13c90d1aa..e7f09e36b 100644 --- a/extensions/typescript-language-features/src/languageFeatures/completions.ts +++ b/extensions/typescript-language-features/src/languageFeatures/completions.ts @@ -45,6 +45,11 @@ interface CompletionContext { readonly useFuzzyWordRangeLogic: boolean, } +type ResolvedCompletionItem = { + readonly edits?: readonly vscode.TextEdit[]; + readonly commands: readonly vscode.Command[]; +}; + class MyCompletionItem extends vscode.CompletionItem { public readonly useCodeSnippet: boolean; @@ -135,6 +140,192 @@ class MyCompletionItem extends vscode.CompletionItem { this.resolveRange(); } + private _resolvedPromise?: { + readonly requestToken: vscode.CancellationTokenSource; + readonly promise: Promise; + waiting: number; + }; + + public async resolveCompletionItem( + client: ITypeScriptServiceClient, + token: vscode.CancellationToken, + ): Promise { + token.onCancellationRequested(() => { + if (this._resolvedPromise && --this._resolvedPromise.waiting <= 0) { + // Give a little extra time for another caller to come in + setTimeout(() => { + if (this._resolvedPromise && this._resolvedPromise.waiting <= 0) { + this._resolvedPromise.requestToken.cancel(); + } + }, 300); + } + }); + + if (this._resolvedPromise) { + ++this._resolvedPromise.waiting; + return this._resolvedPromise.promise; + } + + const requestToken = new vscode.CancellationTokenSource(); + + const promise = (async (): Promise => { + const filepath = client.toOpenedFilePath(this.document); + if (!filepath) { + return undefined; + } + + const args: Proto.CompletionDetailsRequestArgs = { + ...typeConverters.Position.toFileLocationRequestArgs(filepath, this.position), + entryNames: [ + this.tsEntry.source ? { name: this.tsEntry.name, source: this.tsEntry.source } : this.tsEntry.name + ] + }; + const response = await client.interruptGetErr(() => client.execute('completionEntryDetails', args, requestToken.token)); + if (response.type !== 'response' || !response.body || !response.body.length) { + return undefined; + } + + const detail = response.body[0]; + + if (!this.detail && detail.displayParts.length) { + this.detail = Previewer.plain(detail.displayParts); + } + this.documentation = this.getDocumentation(detail, this); + + const codeAction = this.getCodeActions(detail, filepath); + const commands: vscode.Command[] = [{ + command: CompletionAcceptedCommand.ID, + title: '', + arguments: [this] + }]; + if (codeAction.command) { + commands.push(codeAction.command); + } + const additionalTextEdits = codeAction.additionalTextEdits; + + if (this.useCodeSnippet) { + const shouldCompleteFunction = await this.isValidFunctionCompletionContext(client, filepath, this.position, this.document, token); + if (shouldCompleteFunction) { + const { snippet, parameterCount } = snippetForFunctionCall(this, detail.displayParts); + this.insertText = snippet; + if (parameterCount > 0) { + //Fix for https://github.com/microsoft/vscode/issues/104059 + //Don't show parameter hints if "editor.parameterHints.enabled": false + if (vscode.workspace.getConfiguration('editor.parameterHints').get('enabled')) { + commands.push({ title: 'triggerParameterHints', command: 'editor.action.triggerParameterHints' }); + } + } + } + } + + return { commands, edits: additionalTextEdits }; + })(); + + this._resolvedPromise = { + promise, + requestToken, + waiting: 1, + }; + + return this._resolvedPromise.promise; + } + + private getDocumentation( + detail: Proto.CompletionEntryDetails, + item: MyCompletionItem + ): vscode.MarkdownString | undefined { + const documentation = new vscode.MarkdownString(); + if (detail.source) { + const importPath = `'${Previewer.plain(detail.source)}'`; + const autoImportLabel = localize('autoImportLabel', 'Auto import from {0}', importPath); + item.detail = `${autoImportLabel}\n${item.detail}`; + } + Previewer.addMarkdownDocumentation(documentation, detail.documentation, detail.tags); + + return documentation.value.length ? documentation : undefined; + } + + private async isValidFunctionCompletionContext( + client: ITypeScriptServiceClient, + filepath: string, + position: vscode.Position, + document: vscode.TextDocument, + token: vscode.CancellationToken + ): Promise { + // Workaround for https://github.com/microsoft/TypeScript/issues/12677 + // Don't complete function calls inside of destructive assignments or imports + try { + const args: Proto.FileLocationRequestArgs = typeConverters.Position.toFileLocationRequestArgs(filepath, position); + const response = await client.execute('quickinfo', args, token); + if (response.type === 'response' && response.body) { + switch (response.body.kind) { + case 'var': + case 'let': + case 'const': + case 'alias': + return false; + } + } + } catch { + // Noop + } + + // Don't complete function call if there is already something that looks like a function call + // https://github.com/microsoft/vscode/issues/18131 + const after = document.lineAt(position.line).text.slice(position.character); + return after.match(/^[a-z_$0-9]*\s*\(/gi) === null; + } + + private getCodeActions( + detail: Proto.CompletionEntryDetails, + filepath: string + ): { command?: vscode.Command, additionalTextEdits?: vscode.TextEdit[] } { + if (!detail.codeActions || !detail.codeActions.length) { + return {}; + } + + // Try to extract out the additionalTextEdits for the current file. + // Also check if we still have to apply other workspace edits and commands + // using a vscode command + const additionalTextEdits: vscode.TextEdit[] = []; + let hasRemainingCommandsOrEdits = false; + for (const tsAction of detail.codeActions) { + if (tsAction.commands) { + hasRemainingCommandsOrEdits = true; + } + + // Apply all edits in the current file using `additionalTextEdits` + if (tsAction.changes) { + for (const change of tsAction.changes) { + if (change.fileName === filepath) { + additionalTextEdits.push(...change.textChanges.map(typeConverters.TextEdit.fromCodeEdit)); + } else { + hasRemainingCommandsOrEdits = true; + } + } + } + } + + let command: vscode.Command | undefined = undefined; + if (hasRemainingCommandsOrEdits) { + // Create command that applies all edits not in the current file. + command = { + title: '', + command: ApplyCompletionCodeActionCommand.ID, + arguments: [filepath, detail.codeActions.map((x): Proto.CodeAction => ({ + commands: x.commands, + description: x.description, + changes: x.changes.filter(x => x.fileName !== filepath) + }))] + }; + } + + return { + command, + additionalTextEdits: additionalTextEdits.length ? additionalTextEdits : undefined + }; + } + private getRangeFromReplacementSpan(tsEntry: Proto.CompletionEntry, completionContext: CompletionContext, position: vscode.Position) { if (!tsEntry.replacementSpan) { return; @@ -358,6 +549,39 @@ class CompletionAcceptedCommand implements Command { } } +/** + * Command fired when an completion item needs to be applied + */ +class ApplyCompletionCommand implements Command { + public static readonly ID = '_typescript.applyCompletionCommand'; + public readonly id = ApplyCompletionCommand.ID; + + public constructor( + private readonly client: ITypeScriptServiceClient, + ) { } + + public async execute(item: MyCompletionItem) { + const resolved = await item.resolveCompletionItem(this.client, nulToken); + if (!resolved) { + return; + } + + const { edits, commands } = resolved; + + if (edits) { + const workspaceEdit = new vscode.WorkspaceEdit(); + for (const edit of edits) { + workspaceEdit.replace(item.document.uri, edit.range, edit.newText); + } + await vscode.workspace.applyEdit(workspaceEdit); + } + + for (const command of commands) { + await vscode.commands.executeCommand(command.command, ...(command.arguments ?? [])); + } + } +} + class ApplyCompletionCodeActionCommand implements Command { public static readonly ID = '_typescript.applyCompletionCodeAction'; public readonly id = ApplyCompletionCodeActionCommand.ID; @@ -434,6 +658,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< commandManager.register(new ApplyCompletionCodeActionCommand(this.client)); commandManager.register(new CompositeCommand()); commandManager.register(new CompletionAcceptedCommand(onCompletionAccepted, this.telemetryReporter)); + commandManager.register(new ApplyCompletionCommand(this.client)); } public async provideCompletionItems( @@ -535,7 +760,13 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< const items: MyCompletionItem[] = []; for (const entry of entries) { if (!shouldExcludeCompletionEntry(entry, completionConfiguration)) { - items.push(new MyCompletionItem(position, document, entry, completionContext, metadata)); + const item = new MyCompletionItem(position, document, entry, completionContext, metadata); + item.command = { + command: ApplyCompletionCommand.ID, + title: '', + arguments: [item] + }; + items.push(item); includesPackageJsonImport = !!entry.isPackageJsonImport; } } @@ -597,121 +828,10 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< item: MyCompletionItem, token: vscode.CancellationToken ): Promise { - const filepath = this.client.toOpenedFilePath(item.document); - if (!filepath) { - return undefined; - } - - const args: Proto.CompletionDetailsRequestArgs = { - ...typeConverters.Position.toFileLocationRequestArgs(filepath, item.position), - entryNames: [ - item.tsEntry.source ? { name: item.tsEntry.name, source: item.tsEntry.source } : item.tsEntry.name - ] - }; - - const response = await this.client.interruptGetErr(() => this.client.execute('completionEntryDetails', args, token)); - if (response.type !== 'response' || !response.body || !response.body.length) { - return item; - } - - const detail = response.body[0]; - - if (!item.detail && detail.displayParts.length) { - item.detail = Previewer.plain(detail.displayParts); - } - item.documentation = this.getDocumentation(detail, item); - - const codeAction = this.getCodeActions(detail, filepath); - const commands: vscode.Command[] = [{ - command: CompletionAcceptedCommand.ID, - title: '', - arguments: [item] - }]; - if (codeAction.command) { - commands.push(codeAction.command); - } - item.additionalTextEdits = codeAction.additionalTextEdits; - - if (item.useCodeSnippet) { - const shouldCompleteFunction = await this.isValidFunctionCompletionContext(filepath, item.position, item.document, token); - if (shouldCompleteFunction) { - const { snippet, parameterCount } = snippetForFunctionCall(item, detail.displayParts); - item.insertText = snippet; - if (parameterCount > 0) { - //Fix for https://github.com/microsoft/vscode/issues/104059 - //Don't show parameter hints if "editor.parameterHints.enabled": false - if (vscode.workspace.getConfiguration('editor.parameterHints').get('enabled')) { - commands.push({ title: 'triggerParameterHints', command: 'editor.action.triggerParameterHints' }); - } - } - } - } - - if (commands.length) { - if (commands.length === 1) { - item.command = commands[0]; - } else { - item.command = { - command: CompositeCommand.ID, - title: '', - arguments: commands - }; - } - } - + await item.resolveCompletionItem(this.client, token); return item; } - private getCodeActions( - detail: Proto.CompletionEntryDetails, - filepath: string - ): { command?: vscode.Command, additionalTextEdits?: vscode.TextEdit[] } { - if (!detail.codeActions || !detail.codeActions.length) { - return {}; - } - - // Try to extract out the additionalTextEdits for the current file. - // Also check if we still have to apply other workspace edits and commands - // using a vscode command - const additionalTextEdits: vscode.TextEdit[] = []; - let hasReaminingCommandsOrEdits = false; - for (const tsAction of detail.codeActions) { - if (tsAction.commands) { - hasReaminingCommandsOrEdits = true; - } - - // Apply all edits in the current file using `additionalTextEdits` - if (tsAction.changes) { - for (const change of tsAction.changes) { - if (change.fileName === filepath) { - additionalTextEdits.push(...change.textChanges.map(typeConverters.TextEdit.fromCodeEdit)); - } else { - hasReaminingCommandsOrEdits = true; - } - } - } - } - - let command: vscode.Command | undefined = undefined; - if (hasReaminingCommandsOrEdits) { - // Create command that applies all edits not in the current file. - command = { - title: '', - command: ApplyCompletionCodeActionCommand.ID, - arguments: [filepath, detail.codeActions.map((x): Proto.CodeAction => ({ - commands: x.commands, - description: x.description, - changes: x.changes.filter(x => x.fileName !== filepath) - }))] - }; - } - - return { - command, - additionalTextEdits: additionalTextEdits.length ? additionalTextEdits : undefined - }; - } - private isInValidCommitCharacterContext( document: vscode.TextDocument, position: vscode.Position @@ -740,7 +860,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< if ((context.triggerCharacter === '"' || context.triggerCharacter === '\'')) { // make sure we are in something that looks like the start of an import const pre = line.text.slice(0, position.character); - if (!pre.match(/\b(from|import)\s*["']$/) && !pre.match(/\b(import|require)\(['"]$/)) { + if (!/\b(from|import)\s*["']$/.test(pre) && !/\b(import|require)\(['"]$/.test(pre)) { return false; } } @@ -748,7 +868,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< if (context.triggerCharacter === '/') { // make sure we are in something that looks like an import path const pre = line.text.slice(0, position.character); - if (!pre.match(/\b(from|import)\s*["'][^'"]*$/) && !pre.match(/\b(import|require)\(['"][^'"]*$/)) { + if (!/\b(from|import)\s*["'][^'"]*$/.test(pre) && !/\b(import|require)\(['"][^'"]*$/.test(pre)) { return false; } } @@ -756,7 +876,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< if (context.triggerCharacter === '@') { // make sure we are in something that looks like the start of a jsdoc comment const pre = line.text.slice(0, position.character); - if (!pre.match(/^\s*\*[ ]?@/) && !pre.match(/\/\*\*+[ ]?@/)) { + if (!/^\s*\*[ ]?@/.test(pre) && !/\/\*\*+[ ]?@/.test(pre)) { return false; } } @@ -768,51 +888,6 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< return true; } - - private getDocumentation( - detail: Proto.CompletionEntryDetails, - item: MyCompletionItem - ): vscode.MarkdownString | undefined { - const documentation = new vscode.MarkdownString(); - if (detail.source) { - const importPath = `'${Previewer.plain(detail.source)}'`; - const autoImportLabel = localize('autoImportLabel', 'Auto import from {0}', importPath); - item.detail = `${autoImportLabel}\n${item.detail}`; - } - Previewer.addMarkdownDocumentation(documentation, detail.documentation, detail.tags); - - return documentation.value.length ? documentation : undefined; - } - - private async isValidFunctionCompletionContext( - filepath: string, - position: vscode.Position, - document: vscode.TextDocument, - token: vscode.CancellationToken - ): Promise { - // Workaround for https://github.com/microsoft/TypeScript/issues/12677 - // Don't complete function calls inside of destructive assignments or imports - try { - const args: Proto.FileLocationRequestArgs = typeConverters.Position.toFileLocationRequestArgs(filepath, position); - const response = await this.client.execute('quickinfo', args, token); - if (response.type === 'response' && response.body) { - switch (response.body.kind) { - case 'var': - case 'let': - case 'const': - case 'alias': - return false; - } - } - } catch { - // Noop - } - - // Don't complete function call if there is already something that looks like a function call - // https://github.com/microsoft/vscode/issues/18131 - const after = document.lineAt(position.line).text.slice(position.character); - return after.match(/^[a-z_$0-9]*\s*\(/gi) === null; - } } function shouldExcludeCompletionEntry( diff --git a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts index 05fa16cf9..f0c41541b 100644 --- a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts +++ b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts @@ -175,7 +175,6 @@ export default class FileConfigurationManager extends Disposable { const preferences: Proto.UserPreferences = { quotePreference: this.getQuoteStylePreference(preferencesConfig), - // @ts-expect-error until TypeScript 4.2 API importModuleSpecifierPreference: getImportModuleSpecifierPreference(preferencesConfig), importModuleSpecifierEnding: getImportModuleSpecifierEndingPreference(preferencesConfig), allowTextChangesInNewFiles: document.uri.scheme === fileSchemes.file, @@ -183,6 +182,7 @@ export default class FileConfigurationManager extends Disposable { allowRenameOfImportPath: true, includeAutomaticOptionalChainCompletions: config.get('suggest.includeAutomaticOptionalChainCompletions', true), provideRefactorNotApplicableReason: true, + generateReturnInDocTemplate: config.get('suggest.jsdoc.generateReturns', true), }; return preferences; diff --git a/extensions/typescript-language-features/src/languageFeatures/jsDocCompletions.ts b/extensions/typescript-language-features/src/languageFeatures/jsDocCompletions.ts index 6efe472e4..66a51dc23 100644 --- a/extensions/typescript-language-features/src/languageFeatures/jsDocCompletions.ts +++ b/extensions/typescript-language-features/src/languageFeatures/jsDocCompletions.ts @@ -9,6 +9,7 @@ import { ITypeScriptServiceClient } from '../typescriptService'; import { conditionalRegistration, requireConfiguration } from '../utils/dependentRegistration'; import { DocumentSelector } from '../utils/documentSelector'; import * as typeConverters from '../utils/typeConverters'; +import FileConfigurationManager from './fileConfigurationManager'; const localize = nls.loadMessageBundle(); @@ -20,7 +21,7 @@ class JsDocCompletionItem extends vscode.CompletionItem { public readonly document: vscode.TextDocument, public readonly position: vscode.Position ) { - super('/** */', vscode.CompletionItemKind.Snippet); + super('/** */', vscode.CompletionItemKind.Text); this.detail = localize('typescript.jsDocCompletionItem.documentation', 'JSDoc comment'); this.sortText = '\0'; @@ -37,6 +38,7 @@ class JsDocCompletionProvider implements vscode.CompletionItemProvider { constructor( private readonly client: ITypeScriptServiceClient, + private readonly fileConfigurationManager: FileConfigurationManager, ) { } public async provideCompletionItems( @@ -53,8 +55,12 @@ class JsDocCompletionProvider implements vscode.CompletionItemProvider { return undefined; } - const args = typeConverters.Position.toFileLocationRequestArgs(file, position); - const response = await this.client.execute('docCommentTemplate', args, token); + const response = await this.client.interruptGetErr(async () => { + await this.fileConfigurationManager.ensureConfigurationForDocument(document, token); + + const args = typeConverters.Position.toFileLocationRequestArgs(file, position); + return this.client.execute('docCommentTemplate', args, token); + }); if (response.type !== 'response' || !response.body) { return undefined; } @@ -107,6 +113,9 @@ export function templateToSnippet(template: string): vscode.SnippetString { out += post + ` \${${snippetIndex++}}`; return out; }); + + template = template.replace(/\* @returns[ \t]*$/gm, `* @returns \${${snippetIndex++}}`); + return new vscode.SnippetString(template); } @@ -114,12 +123,14 @@ export function register( selector: DocumentSelector, modeId: string, client: ITypeScriptServiceClient, + fileConfigurationManager: FileConfigurationManager, + ): vscode.Disposable { return conditionalRegistration([ requireConfiguration(modeId, 'suggest.completeJSDocs') ], () => { return vscode.languages.registerCompletionItemProvider(selector.syntax, - new JsDocCompletionProvider(client), + new JsDocCompletionProvider(client, fileConfigurationManager), '*'); }); } diff --git a/extensions/typescript-language-features/src/languageFeatures/languageConfiguration.ts b/extensions/typescript-language-features/src/languageFeatures/languageConfiguration.ts index ccd59d517..75d32770a 100644 --- a/extensions/typescript-language-features/src/languageFeatures/languageConfiguration.ts +++ b/extensions/typescript-language-features/src/languageFeatures/languageConfiguration.ts @@ -15,7 +15,9 @@ import * as languageModeIds from '../utils/languageModeIds'; const jsTsLanguageConfiguration: vscode.LanguageConfiguration = { indentationRules: { decreaseIndentPattern: /^((?!.*?\/\*).*\*\/)?\s*[\}\]].*$/, - increaseIndentPattern: /^((?!\/\/).)*(\{[^}"'`]*|\([^)"'`]*|\[[^\]"'`]*)$/ + increaseIndentPattern: /^((?!\/\/).)*(\{[^}"'`]*|\([^)"'`]*|\[[^\]"'`]*)$/, + // e.g. * ...| or */| or *-----*/| + unIndentedLinePattern: /^(\t|[ ])*[ ]\*[^/]*\*\/\s*$|^(\t|[ ])*[ ]\*\/\s*$|^(\t|[ ])*[ ]\*([ ]([^\*]|\*(?!\/))*)?$/ }, wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g, onEnterRules: [ diff --git a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index 2af23b270..cdb6aa35f 100644 --- a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -370,19 +370,19 @@ const fixAllErrorCodes = new Map([ [2345, 2339], ]); -const preferredFixes = new Map([ - [fixNames.annotateWithTypeFromJSDoc, { value: 1 }], - [fixNames.constructorForDerivedNeedSuperCall, { value: 1 }], - [fixNames.extendsInterfaceBecomesImplements, { value: 1 }], - [fixNames.awaitInSyncFunction, { value: 1 }], - [fixNames.classIncorrectlyImplementsInterface, { value: 3 }], - [fixNames.classDoesntImplementInheritedAbstractMember, { value: 3 }], - [fixNames.unreachableCode, { value: 1 }], - [fixNames.unusedIdentifier, { value: 1 }], - [fixNames.forgottenThisPropertyAccess, { value: 1 }], - [fixNames.spelling, { value: 2 }], - [fixNames.addMissingAwait, { value: 1 }], - [fixNames.fixImport, { value: 0, thereCanOnlyBeOne: true }], +const preferredFixes = new Map([ + [fixNames.annotateWithTypeFromJSDoc, { priority: 2 }], + [fixNames.constructorForDerivedNeedSuperCall, { priority: 2 }], + [fixNames.extendsInterfaceBecomesImplements, { priority: 2 }], + [fixNames.awaitInSyncFunction, { priority: 2 }], + [fixNames.classIncorrectlyImplementsInterface, { priority: 3 }], + [fixNames.classDoesntImplementInheritedAbstractMember, { priority: 3 }], + [fixNames.unreachableCode, { priority: 2 }], + [fixNames.unusedIdentifier, { priority: 2 }], + [fixNames.forgottenThisPropertyAccess, { priority: 2 }], + [fixNames.spelling, { priority: 0 }], + [fixNames.addMissingAwait, { priority: 2 }], + [fixNames.fixImport, { priority: 1, thereCanOnlyBeOne: true }], ]); function isPreferredFix( @@ -408,9 +408,9 @@ function isPreferredFix( } const otherFixPriority = preferredFixes.get(otherAction.tsAction.fixName); - if (!otherFixPriority || otherFixPriority.value < fixPriority.value) { + if (!otherFixPriority || otherFixPriority.priority < fixPriority.priority) { return true; - } else if (otherFixPriority.value > fixPriority.value) { + } else if (otherFixPriority.priority > fixPriority.priority) { return false; } diff --git a/extensions/typescript-language-features/src/languageFeatures/refactor.ts b/extensions/typescript-language-features/src/languageFeatures/refactor.ts index b8a4bcfda..c9a2a868b 100644 --- a/extensions/typescript-language-features/src/languageFeatures/refactor.ts +++ b/extensions/typescript-language-features/src/languageFeatures/refactor.ts @@ -309,7 +309,15 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider { + // Don't show 'infer return type' refactoring unless it has been explicitly requested + // https://github.com/microsoft/TypeScript/issues/42993 + if (!context.only && action.kind?.value === 'refactor.rewrite.function.returnType') { + return false; + } + return true; + }); + if (!context.only) { return actions; } diff --git a/extensions/typescript-language-features/src/languageProvider.ts b/extensions/typescript-language-features/src/languageProvider.ts index 5634edb44..2cb36eb94 100644 --- a/extensions/typescript-language-features/src/languageProvider.ts +++ b/extensions/typescript-language-features/src/languageProvider.ts @@ -72,7 +72,7 @@ export default class LanguageProvider extends Disposable { import('./languageFeatures/formatting').then(provider => this._register(provider.register(selector, this.description.id, this.client, this.fileConfigurationManager))), import('./languageFeatures/hover').then(provider => this._register(provider.register(selector, this.client))), import('./languageFeatures/implementations').then(provider => this._register(provider.register(selector, this.client))), - import('./languageFeatures/jsDocCompletions').then(provider => this._register(provider.register(selector, this.description.id, this.client))), + import('./languageFeatures/jsDocCompletions').then(provider => this._register(provider.register(selector, this.description.id, this.client, this.fileConfigurationManager))), import('./languageFeatures/organizeImports').then(provider => this._register(provider.register(selector, this.client, this.commandManager, this.fileConfigurationManager, this.telemetryReporter))), import('./languageFeatures/quickFix').then(provider => this._register(provider.register(selector, this.client, this.fileConfigurationManager, this.commandManager, this.client.diagnosticsManager, this.telemetryReporter))), import('./languageFeatures/refactor').then(provider => this._register(provider.register(selector, this.client, this.fileConfigurationManager, this.commandManager, this.telemetryReporter))), diff --git a/extensions/typescript-language-features/src/lazyClientHost.ts b/extensions/typescript-language-features/src/lazyClientHost.ts index d7108e96e..c5ec9b883 100644 --- a/extensions/typescript-language-features/src/lazyClientHost.ts +++ b/extensions/typescript-language-features/src/lazyClientHost.ts @@ -19,7 +19,7 @@ import { PluginManager } from './utils/plugins'; export function createLazyClientHost( context: vscode.ExtensionContext, - onCaseInsenitiveFileSystem: boolean, + onCaseInsensitiveFileSystem: boolean, services: { pluginManager: PluginManager, commandManager: CommandManager, @@ -34,7 +34,7 @@ export function createLazyClientHost( const clientHost = new TypeScriptServiceClientHost( standardLanguageDescriptions, context, - onCaseInsenitiveFileSystem, + onCaseInsensitiveFileSystem, services, onCompletionAccepted); diff --git a/extensions/typescript-language-features/src/protocol.d.ts b/extensions/typescript-language-features/src/protocol.d.ts index e202c0097..9cf1f851e 100644 --- a/extensions/typescript-language-features/src/protocol.d.ts +++ b/extensions/typescript-language-features/src/protocol.d.ts @@ -10,21 +10,5 @@ declare module 'typescript/lib/protocol' { interface Response { readonly _serverType?: ServerType; } - - interface FileReferencesRequest extends FileRequest { - } - interface FileReferencesResponseBody { - /** - * The file locations referencing the symbol. - */ - refs: readonly ReferencesResponseItem[]; - /** - * The name of the symbol. - */ - symbolName: string; - } - interface FileReferencesResponse extends Response { - body?: FileReferencesResponseBody; - } } diff --git a/extensions/typescript-language-features/src/test/unit/onEnter.test.ts b/extensions/typescript-language-features/src/test/unit/onEnter.test.ts index 0aeb8425b..cd0d99c0e 100644 --- a/extensions/typescript-language-features/src/test/unit/onEnter.test.ts +++ b/extensions/typescript-language-features/src/test/unit/onEnter.test.ts @@ -67,4 +67,47 @@ suite.skip('OnEnter', () => { ` x`)); }); }); + + test('should not indent after a multi-line comment block 1', () => { + return withRandomFileEditor(`/*-----\n * line 1\n * line 2\n *-----*/\n${CURSOR}`, 'js', async (_editor, document) => { + await type(document, '\nx'); + assert.strictEqual( + document.getText(), + joinLines( + `/*-----`, + ` * line 1`, + ` * line 2`, + ` *-----*/`, + ``, + `x`)); + }); + }); + + test('should not indent after a multi-line comment block 2', () => { + return withRandomFileEditor(`/*-----\n * line 1\n * line 2\n */\n${CURSOR}`, 'js', async (_editor, document) => { + await type(document, '\nx'); + assert.strictEqual( + document.getText(), + joinLines( + `/*-----`, + ` * line 1`, + ` * line 2`, + ` */`, + ``, + `x`)); + }); + }); + + test('should indent within a multi-line comment block', () => { + return withRandomFileEditor(`/*-----\n * line 1\n * line 2${CURSOR}`, 'js', async (_editor, document) => { + await type(document, '\nx'); + assert.strictEqual( + document.getText(), + joinLines( + `/*-----`, + ` * line 1`, + ` * line 2`, + ` * x`)); + }); + }); }); diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index c27928ad1..62533a9d2 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -693,7 +693,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType if (isWeb()) { // On web, treat absolute paths as pointing to standard lib files if (filepath.startsWith('/')) { - return vscode.Uri.joinPath(this.context.extensionUri, 'node_modules', 'typescript-web', 'lib', filepath.slice(1)); + return vscode.Uri.joinPath(this.context.extensionUri, 'node_modules', 'typescript', 'lib', filepath.slice(1)); } } @@ -888,7 +888,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType } private dispatchTelemetryEvent(telemetryData: Proto.TelemetryEventBody): void { - const properties: ObjectMap = Object.create(null); + const properties: { [key: string]: string } = Object.create(null); switch (telemetryData.telemetryEventName) { case 'typingsInstalled': const typingsInstalledPayload: Proto.TypingsInstalledTelemetryEventPayload = (telemetryData.payload as Proto.TypingsInstalledTelemetryEventPayload); diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index 3e39f67c4..f95020cdb 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -47,24 +47,6 @@ applicationinsights@1.0.8: diagnostic-channel-publishers "0.2.1" zone.js "0.7.6" -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -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" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - diagnostic-channel-publishers@0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3" @@ -77,67 +59,11 @@ diagnostic-channel@0.2.0: dependencies: semver "^5.3.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 sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -glob@^7.1.3: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - jsonc-parser@^2.2.1: version "2.3.1" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.3.1.tgz#59549150b133f2efacca48fe9ce1ec0659af2342" integrity sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg== -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" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -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 sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -rimraf@^2.6.3: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - semver@5.5.1: version "5.5.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" @@ -153,11 +79,6 @@ typescript-vscode-sh-plugin@^0.6.14: resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.6.14.tgz#a81031b502f6346a26ea49ce082438c3e353bb38" integrity sha512-AkNlRBbI6K7gk29O92qthNSvc6jjmNQ6isVXoYxkFwPa8D04tIv2SOPd+sd+mNpso4tNdL2gy7nVtrd5yFqvlA== -"typescript-web@npm:typescript@^4.2.0-dev.20201209": - version "4.2.0-dev.20201209" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.0-dev.20201209.tgz#fab33fdb1aa7beb857271e0626bca6b200c61351" - integrity sha512-rXJtE/naraN9n3bPBDA5Sa/2nrx1di5se/kVkmAjhUSpBzGmx3zeNHZF59U8XhYQdQ1QsMiDDnjmtjFUGC9LEQ== - vscode-extension-telemetry@0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.1.tgz#91387e06b33400c57abd48979b0e790415ae110b" @@ -170,11 +91,6 @@ vscode-nls@^4.1.1: resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167" integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw== -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - zone.js@0.7.6: version "0.7.6" resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009" diff --git a/extensions/vb/package.json b/extensions/vb/package.json index 29e3cceed..b3472f80a 100644 --- a/extensions/vb/package.json +++ b/extensions/vb/package.json @@ -1,29 +1,49 @@ { - "name": "vb", - "displayName": "%displayName%", - "description": "%description%", - "version": "1.0.0", - "publisher": "vscode", - "license": "MIT", - "engines": { "vscode": "*" }, - "scripts": { - "update-grammar": "node ../../build/npm/update-grammar.js textmate/asp.vb.net.tmbundle Syntaxes/ASP%20VB.net.plist ./syntaxes/asp-vb-net.tmlanguage.json" - }, - "contributes": { - "languages": [{ - "id": "vb", - "extensions": [ ".vb", ".brs", ".vbs", ".bas" ], - "aliases": [ "Visual Basic", "vb" ], - "configuration": "./language-configuration.json" - }], - "grammars": [{ - "language": "vb", - "scopeName": "source.asp.vb.net", - "path": "./syntaxes/asp-vb-net.tmlanguage.json" - }], - "snippets": [{ - "language": "vb", - "path": "./snippets/vb.code-snippets" - }] - } + "name": "vb", + "displayName": "%displayName%", + "description": "%description%", + "version": "1.0.0", + "publisher": "vscode", + "license": "MIT", + "engines": { + "vscode": "*" + }, + "scripts": { + "update-grammar": "node ../node_modules/vscode-grammar-updater/bin textmate/asp.vb.net.tmbundle Syntaxes/ASP%20VB.net.plist ./syntaxes/asp-vb-net.tmlanguage.json" + }, + "contributes": { + "languages": [ + { + "id": "vb", + "extensions": [ + ".vb", + ".brs", + ".vbs", + ".bas" + ], + "aliases": [ + "Visual Basic", + "vb" + ], + "configuration": "./language-configuration.json" + } + ], + "grammars": [ + { + "language": "vb", + "scopeName": "source.asp.vb.net", + "path": "./syntaxes/asp-vb-net.tmlanguage.json" + } + ], + "snippets": [ + { + "language": "vb", + "path": "./snippets/vb.code-snippets" + } + ] + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" + } } diff --git a/extensions/vb/package.nls.json b/extensions/vb/package.nls.json index 45bed3ab4..03698a037 100644 --- a/extensions/vb/package.nls.json +++ b/extensions/vb/package.nls.json @@ -1,4 +1,4 @@ { "displayName": "Visual Basic Language Basics", "description": "Provides snippets, syntax highlighting, bracket matching and folding in Visual Basic files." -} \ No newline at end of file +} diff --git a/extensions/vb/yarn.lock b/extensions/vb/yarn.lock new file mode 100644 index 000000000..fb57ccd13 --- /dev/null +++ b/extensions/vb/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index f7facc24b..7e576805d 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -5,6 +5,7 @@ "publisher": "vscode", "license": "MIT", "enableProposedApi": true, + "requiresWorkspaceTrust": "onDemand", "private": true, "activationEvents": [], "main": "./out/extension", @@ -37,6 +38,15 @@ } } }, + "views": { + "remote": [ + { + "id": "test.treeId", + "name": "test-tree", + "when": "never" + } + ] + }, "configurationDefaults": { "[abcLang]": { "editor.lineNumbers": "off", @@ -106,6 +116,27 @@ } ] } + ], + "notebookProvider": [ + { + "viewType": "notebookCoreTest", + "displayName": "Notebook Core Test", + "selector": [ + { + "filenamePattern": "*.vsctestnb", + "excludeFileNamePattern": "" + } + ] + }, + { + "viewType": "notebook.nbdtest", + "displayName": "notebook.nbdtest", + "selector": [ + { + "filenamePattern": "**/*.nbdtest" + } + ] + } ] }, "scripts": { @@ -115,5 +146,9 @@ "devDependencies": { "@types/mocha": "^8.2.0", "@types/node": "^12.19.9" + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" } } diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts index 1d62a12c1..f9f971bdb 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts @@ -7,9 +7,12 @@ import 'mocha'; import * as assert from 'assert'; import { join } from 'path'; import { commands, workspace, window, Uri, Range, Position, ViewColumn } from 'vscode'; +import { assertNoRpc } from '../utils'; suite('vscode API - commands', () => { + teardown(assertNoRpc); + test('getCommands', function (done) { let p1 = commands.getCommands().then(commands => { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts index 0b6f13fb0..0a9ddf7aa 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts @@ -6,9 +6,12 @@ import 'mocha'; import * as assert from 'assert'; import * as vscode from 'vscode'; +import { assertNoRpc } from '../utils'; suite('vscode API - configuration', () => { + teardown(assertNoRpc); + test('configurations, language defaults', function () { const defaultLanguageSettings = vscode.workspace.getConfiguration().get('[abcLang]'); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts index ec976ff53..145c2ee3b 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts @@ -5,11 +5,13 @@ import * as assert from 'assert'; import { debug, workspace, Disposable, commands, window } from 'vscode'; -import { disposeAll } from '../utils'; +import { assertNoRpc, disposeAll } from '../utils'; import { basename } from 'path'; suite('vscode API - debug', function () { + teardown(assertNoRpc); + test('breakpoints', async function () { assert.equal(debug.breakpoints.length, 0); let onDidChangeBreakpointsCounter = 0; diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts index b5a0a7916..ef16bbe1c 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts @@ -5,11 +5,14 @@ import * as assert from 'assert'; import { workspace, window, Position, Range, commands, TextEditor, TextDocument, TextEditorCursorStyle, TextEditorLineNumbersStyle, SnippetString, Selection, Uri, env } from 'vscode'; -import { createRandomFile, deleteFile, closeAllEditors } from '../utils'; +import { createRandomFile, deleteFile, closeAllEditors, assertNoRpc } from '../utils'; suite('vscode API - editors', () => { - teardown(closeAllEditors); + teardown(async function () { + assertNoRpc(); + await closeAllEditors(); + }); function withRandomFileEditor(initialContents: string, run: (editor: TextEditor, doc: TextDocument) => Thenable): Thenable { return createRandomFile(initialContents).then(file => { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts index 7bc2f7597..e3f0106fe 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts @@ -5,9 +5,12 @@ import * as assert from 'assert'; import { env, extensions, ExtensionKind, UIKind, Uri } from 'vscode'; +import { assertNoRpc } from '../utils'; suite('vscode API - env', () => { + teardown(assertNoRpc); + test('env is set', function () { assert.equal(typeof env.language, 'string'); assert.equal(typeof env.appRoot, 'string'); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/extensions.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/extensions.test.ts new file mode 100644 index 000000000..889faa0bf --- /dev/null +++ b/extensions/vscode-api-tests/src/singlefolder-tests/extensions.test.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 'mocha'; +import * as assert from 'assert'; +import * as vscode from 'vscode'; + +suite('vscode server cli', () => { + + + test('extension is installed and enabled when installed by server cli', function () { + const extension = process.env.TESTRESOLVER_INSTALL_BUILTIN_EXTENSION; + if (!process.env.BUILD_SOURCEVERSION // Skip it when running out of sources + || !process.env.REMOTE_VSCODE // Skip it when not a remote integration test + || !extension // Skip it when extension is not provided to server + ) { + this.skip(); + } + + assert.ok(vscode.extensions.getExtension(extension!)); + }); + +}); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/languages.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/languages.test.ts index a59841f66..0553a4444 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/languages.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/languages.test.ts @@ -6,10 +6,12 @@ import * as assert from 'assert'; import { join } from 'path'; import * as vscode from 'vscode'; -import { createRandomFile, testFs } from '../utils'; +import { assertNoRpc, createRandomFile, testFs } from '../utils'; suite('vscode API - languages', () => { + teardown(assertNoRpc); + const isWindows = process.platform === 'win32'; function positionToString(p: vscode.Position) { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.document.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.document.test.ts new file mode 100644 index 000000000..ceaeec219 --- /dev/null +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.document.test.ts @@ -0,0 +1,391 @@ +/*--------------------------------------------------------------------------------------------- + * 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 vscode from 'vscode'; +import * as utils from '../utils'; + +suite('Notebook Document', function () { + + const contentProvider = new class implements vscode.NotebookContentProvider { + async openNotebook(uri: vscode.Uri, _openContext: vscode.NotebookDocumentOpenContext): Promise { + return { + cells: [{ cellKind: vscode.NotebookCellKind.Code, source: uri.toString(), language: 'javascript', metadata: new vscode.NotebookCellMetadata(), outputs: [] }], + metadata: new vscode.NotebookDocumentMetadata() + }; + } + async resolveNotebook(_document: vscode.NotebookDocument, _webview: vscode.NotebookCommunication) { + // + } + async saveNotebook(_document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) { + // + } + async saveNotebookAs(_targetResource: vscode.Uri, _document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) { + // + } + async backupNotebook(_document: vscode.NotebookDocument, _context: vscode.NotebookDocumentBackupContext, _cancellation: vscode.CancellationToken) { + return { id: '', delete() { } }; + } + }; + + const disposables: vscode.Disposable[] = []; + + suiteTeardown(async function () { + utils.assertNoRpc(); + await utils.revertAllDirty(); + await utils.closeAllEditors(); + utils.disposeAll(disposables); + disposables.length = 0; + + for (let doc of vscode.notebook.notebookDocuments) { + assert.strictEqual(doc.isDirty, false, doc.uri.toString()); + } + }); + + suiteSetup(function () { + disposables.push(vscode.notebook.registerNotebookContentProvider('notebook.nbdtest', contentProvider)); + }); + + test('cannot register sample provider multiple times', function () { + assert.throws(() => { + vscode.notebook.registerNotebookContentProvider('notebook.nbdtest', contentProvider); + }); + }); + + test('cannot open unknown types', async function () { + try { + await vscode.notebook.openNotebookDocument(vscode.Uri.parse('some:///thing.notTypeKnown')); + assert.ok(false); + } catch { + assert.ok(true); + } + }); + + test('document basics', async function () { + const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest'); + const notebook = await vscode.notebook.openNotebookDocument(uri); + + assert.strictEqual(notebook.uri.toString(), uri.toString()); + assert.strictEqual(notebook.isDirty, false); + assert.strictEqual(notebook.isUntitled, false); + assert.strictEqual(notebook.cells.length, 1); + + assert.strictEqual(notebook.viewType, 'notebook.nbdtest'); + }); + + test('notebook open/close, notebook ready when cell-document open event is fired', async function () { + const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest'); + let didHappen = false; + const p = utils.asPromise(vscode.workspace.onDidOpenTextDocument).then(doc => { + if (doc.uri.scheme !== 'vscode-notebook-cell') { + return; + } + const notebook = vscode.notebook.notebookDocuments.find(notebook => { + const cell = notebook.cells.find(cell => cell.document === doc); + return Boolean(cell); + }); + assert.ok(notebook, `notebook for cell ${doc.uri} NOT found`); + didHappen = true; + }); + + await vscode.notebook.openNotebookDocument(uri); + await p; + assert.strictEqual(didHappen, true); + }); + + test('notebook open/close, all cell-documents are ready', async function () { + const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest'); + + const p = utils.asPromise(vscode.notebook.onDidOpenNotebookDocument).then(notebook => { + for (let cell of notebook.cells) { + const doc = vscode.workspace.textDocuments.find(doc => doc.uri.toString() === cell.uri.toString()); + assert.ok(doc); + assert.strictEqual(doc.notebook === notebook, true); + assert.strictEqual(doc === cell.document, true); + assert.strictEqual(doc?.languageId, cell.language); + assert.strictEqual(doc?.isDirty, false); + assert.strictEqual(doc?.isClosed, false); + } + }); + + await vscode.notebook.openNotebookDocument(uri); + await p; + }); + + + test('workspace edit API (replaceCells)', async function () { + const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest'); + + const document = await vscode.notebook.openNotebookDocument(uri); + assert.strictEqual(document.cells.length, 1); + + // inserting two new cells + { + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCells(document.uri, 0, 0, [{ + cellKind: vscode.NotebookCellKind.Markdown, + language: 'markdown', + metadata: undefined, + outputs: [], + source: 'new_markdown' + }, { + cellKind: vscode.NotebookCellKind.Code, + language: 'fooLang', + metadata: undefined, + outputs: [], + source: 'new_code' + }]); + + const success = await vscode.workspace.applyEdit(edit); + assert.strictEqual(success, true); + } + + assert.strictEqual(document.cells.length, 3); + assert.strictEqual(document.cells[0].document.getText(), 'new_markdown'); + assert.strictEqual(document.cells[1].document.getText(), 'new_code'); + + // deleting cell 1 and 3 + { + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCells(document.uri, 0, 1, []); + edit.replaceNotebookCells(document.uri, 2, 3, []); + const success = await vscode.workspace.applyEdit(edit); + assert.strictEqual(success, true); + } + + assert.strictEqual(document.cells.length, 1); + assert.strictEqual(document.cells[0].document.getText(), 'new_code'); + + // replacing all cells + { + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCells(document.uri, 0, 1, [{ + cellKind: vscode.NotebookCellKind.Markdown, + language: 'markdown', + metadata: undefined, + outputs: [], + source: 'new2_markdown' + }, { + cellKind: vscode.NotebookCellKind.Code, + language: 'fooLang', + metadata: undefined, + outputs: [], + source: 'new2_code' + }]); + const success = await vscode.workspace.applyEdit(edit); + assert.strictEqual(success, true); + } + assert.strictEqual(document.cells.length, 2); + assert.strictEqual(document.cells[0].document.getText(), 'new2_markdown'); + assert.strictEqual(document.cells[1].document.getText(), 'new2_code'); + + // remove all cells + { + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCells(document.uri, 0, document.cells.length, []); + const success = await vscode.workspace.applyEdit(edit); + assert.strictEqual(success, true); + } + assert.strictEqual(document.cells.length, 0); + }); + + test('workspace edit API (replaceCells, event)', async function () { + const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest'); + const document = await vscode.notebook.openNotebookDocument(uri); + assert.strictEqual(document.cells.length, 1); + + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCells(document.uri, 0, 0, [{ + cellKind: vscode.NotebookCellKind.Markdown, + language: 'markdown', + metadata: undefined, + outputs: [], + source: 'new_markdown' + }, { + cellKind: vscode.NotebookCellKind.Code, + language: 'fooLang', + metadata: undefined, + outputs: [], + source: 'new_code' + }]); + + const event = utils.asPromise(vscode.notebook.onDidChangeNotebookCells); + + const success = await vscode.workspace.applyEdit(edit); + assert.strictEqual(success, true); + + const data = await event; + + // check document + assert.strictEqual(document.cells.length, 3); + assert.strictEqual(document.cells[0].document.getText(), 'new_markdown'); + assert.strictEqual(document.cells[1].document.getText(), 'new_code'); + + // check event data + assert.strictEqual(data.document === document, true); + assert.strictEqual(data.changes.length, 1); + assert.strictEqual(data.changes[0].deletedCount, 0); + assert.strictEqual(data.changes[0].deletedItems.length, 0); + assert.strictEqual(data.changes[0].items.length, 2); + assert.strictEqual(data.changes[0].items[0], document.cells[0]); + assert.strictEqual(data.changes[0].items[1], document.cells[1]); + }); + + test('workspace edit API (appendNotebookCellOutput, replaceCellOutput, event)', async function () { + const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest'); + const document = await vscode.notebook.openNotebookDocument(uri); + + const outputChangeEvent = utils.asPromise(vscode.notebook.onDidChangeCellOutputs); + const edit = new vscode.WorkspaceEdit(); + const firstCellOutput = new vscode.NotebookCellOutput([new vscode.NotebookCellOutputItem('foo', 'bar')]); + edit.appendNotebookCellOutput(document.uri, 0, [firstCellOutput]); + const success = await vscode.workspace.applyEdit(edit); + const data = await outputChangeEvent; + + assert.strictEqual(success, true); + assert.strictEqual(document.cells.length, 1); + assert.strictEqual(document.cells[0].outputs.length, 1); + assert.deepStrictEqual(document.cells[0].outputs, [firstCellOutput]); + + assert.strictEqual(data.document === document, true); + assert.strictEqual(data.cells.length, 1); + assert.strictEqual(data.cells[0].outputs.length, 1); + assert.deepStrictEqual(data.cells[0].outputs, [firstCellOutput]); + + + { + const outputChangeEvent = utils.asPromise(vscode.notebook.onDidChangeCellOutputs); + const edit = new vscode.WorkspaceEdit(); + const secondCellOutput = new vscode.NotebookCellOutput([new vscode.NotebookCellOutputItem('foo', 'baz')]); + edit.appendNotebookCellOutput(document.uri, 0, [secondCellOutput]); + const success = await vscode.workspace.applyEdit(edit); + const data = await outputChangeEvent; + + assert.strictEqual(success, true); + assert.strictEqual(document.cells.length, 1); + assert.strictEqual(document.cells[0].outputs.length, 2); + assert.deepStrictEqual(document.cells[0].outputs, [firstCellOutput, secondCellOutput]); + + assert.strictEqual(data.document === document, true); + assert.strictEqual(data.cells.length, 1); + assert.strictEqual(data.cells[0].outputs.length, 2); + assert.deepStrictEqual(data.cells[0].outputs, [firstCellOutput, secondCellOutput]); + } + + { + const outputChangeEvent = utils.asPromise(vscode.notebook.onDidChangeCellOutputs); + const edit = new vscode.WorkspaceEdit(); + const thirdOutput = new vscode.NotebookCellOutput([new vscode.NotebookCellOutputItem('foo', 'baz1')]); + edit.replaceNotebookCellOutput(document.uri, 0, [thirdOutput]); + const success = await vscode.workspace.applyEdit(edit); + const data = await outputChangeEvent; + + assert.strictEqual(success, true); + assert.strictEqual(document.cells.length, 1); + assert.strictEqual(document.cells[0].outputs.length, 1); + assert.deepStrictEqual(document.cells[0].outputs, [thirdOutput]); + + assert.strictEqual(data.document === document, true); + assert.strictEqual(data.cells.length, 1); + assert.strictEqual(data.cells[0].outputs.length, 1); + assert.deepStrictEqual(data.cells[0].outputs, [thirdOutput]); + } + }); + + test('document save API', async function () { + const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest'); + const notebook = await vscode.notebook.openNotebookDocument(uri); + + assert.strictEqual(notebook.uri.toString(), uri.toString()); + assert.strictEqual(notebook.isDirty, false); + assert.strictEqual(notebook.isUntitled, false); + assert.strictEqual(notebook.cells.length, 1); + assert.strictEqual(notebook.viewType, 'notebook.nbdtest'); + + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCells(notebook.uri, 0, 0, [{ + cellKind: vscode.NotebookCellKind.Markdown, + language: 'markdown', + metadata: undefined, + outputs: [], + source: 'new_markdown' + }, { + cellKind: vscode.NotebookCellKind.Code, + language: 'fooLang', + metadata: undefined, + outputs: [], + source: 'new_code' + }]); + + const success = await vscode.workspace.applyEdit(edit); + assert.strictEqual(success, true); + assert.strictEqual(notebook.isDirty, true); + + await notebook.save(); + assert.strictEqual(notebook.isDirty, false); + }); + + + test('setTextDocumentLanguage for notebook cells', async function () { + + const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest'); + const notebook = await vscode.notebook.openNotebookDocument(uri); + const first = notebook.cells[0]; + assert.strictEqual(first.document.languageId, 'javascript'); + + const pclose = utils.asPromise(vscode.workspace.onDidCloseTextDocument); + const popen = utils.asPromise(vscode.workspace.onDidOpenTextDocument); + + await vscode.languages.setTextDocumentLanguage(first.document, 'css'); + assert.strictEqual(first.document.languageId, 'css'); + + const closed = await pclose; + const opened = await popen; + + assert.strictEqual(closed.uri.toString(), first.uri.toString()); + assert.strictEqual(opened.uri.toString(), first.uri.toString()); + assert.strictEqual(opened === closed, true); + }); + + + test('#117273, Add multiple outputs', async function () { + + const resource = await utils.createRandomFile(undefined, undefined, '.nbdtest'); + const document = await vscode.notebook.openNotebookDocument(resource); + + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCellOutput(document.uri, 0, [ + new vscode.NotebookCellOutput( + [new vscode.NotebookCellOutputItem('application/x.notebook.stream', '1', { outputType: 'stream', streamName: 'stdout' })], + { outputType: 'stream', streamName: 'stdout' } + ) + ]); + let success = await vscode.workspace.applyEdit(edit); + + assert.ok(success); + assert.strictEqual(document.cells[0].outputs.length, 1); + assert.strictEqual(document.cells[0].outputs[0].outputs.length, 1); + assert.deepStrictEqual(document.cells[0].outputs[0].metadata, { outputType: 'stream', streamName: 'stdout' }); + assert.deepStrictEqual(document.cells[0].outputs[0].outputs[0].metadata, { outputType: 'stream', streamName: 'stdout' }); + + const edit2 = new vscode.WorkspaceEdit(); + edit2.appendNotebookCellOutput(document.uri, 0, [ + new vscode.NotebookCellOutput( + [new vscode.NotebookCellOutputItem('hello', '1', { outputType: 'stream', streamName: 'stderr' })], + { outputType: 'stream', streamName: 'stderr' } + ) + ]); + success = await vscode.workspace.applyEdit(edit2); + assert.ok(success); + + assert.strictEqual(document.cells[0].outputs.length, 2); + assert.strictEqual(document.cells[0].outputs[0].outputs.length, 1); + assert.strictEqual(document.cells[0].outputs[1].outputs.length, 1); + assert.deepStrictEqual(document.cells[0].outputs[0].metadata, { outputType: 'stream', streamName: 'stdout' }); + assert.deepStrictEqual(document.cells[0].outputs[0].outputs[0].metadata, { outputType: 'stream', streamName: 'stdout' }); + assert.deepStrictEqual(document.cells[0].outputs[1].metadata, { outputType: 'stream', streamName: 'stderr' }); + assert.deepStrictEqual(document.cells[0].outputs[1].outputs[0].metadata, { outputType: 'stream', streamName: 'stderr' }); + }); +}); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts new file mode 100644 index 000000000..ad209c97a --- /dev/null +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts @@ -0,0 +1,1285 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { createRandomFile, asPromise, disposeAll, closeAllEditors, revertAllDirty, saveAllEditors, assertNoRpc } from '../utils'; + +// Since `workbench.action.splitEditor` command does await properly +// Notebook editor/document events are not guaranteed to be sent to the ext host when promise resolves +// The workaround here is waiting for the first visible notebook editor change event. +async function splitEditor() { + const once = asPromise(vscode.window.onDidChangeVisibleNotebookEditors); + await vscode.commands.executeCommand('workbench.action.splitEditor'); + await once; +} + +async function saveFileAndCloseAll(resource: vscode.Uri) { + const documentClosed = new Promise((resolve, _reject) => { + const d = vscode.notebook.onDidCloseNotebookDocument(e => { + if (e.uri.toString() === resource.toString()) { + d.dispose(); + resolve(); + } + }); + }); + await vscode.commands.executeCommand('workbench.action.files.save'); + await closeAllEditors(); + await documentClosed; +} + +async function saveAllFilesAndCloseAll(resource: vscode.Uri | undefined) { + const documentClosed = new Promise((resolve, _reject) => { + if (!resource) { + return resolve(); + } + const d = vscode.notebook.onDidCloseNotebookDocument(e => { + if (e.uri.toString() === resource.toString()) { + d.dispose(); + resolve(); + } + }); + }); + await vscode.commands.executeCommand('workbench.action.files.saveAll'); + await closeAllEditors(); + await documentClosed; +} + +async function updateCellMetadata(uri: vscode.Uri, cell: vscode.NotebookCell, newMetadata: vscode.NotebookCellMetadata) { + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCellMetadata(uri, cell.index, newMetadata); + await vscode.workspace.applyEdit(edit); +} + +async function updateNotebookMetadata(uri: vscode.Uri, newMetadata: vscode.NotebookDocumentMetadata) { + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookMetadata(uri, newMetadata); + await vscode.workspace.applyEdit(edit); +} + +async function withEvent(event: vscode.Event, callback: (e: Promise) => Promise) { + const e = asPromise(event); + await callback(e); +} + +suite('Notebook API tests', function () { + + const disposables: vscode.Disposable[] = []; + + suiteTeardown(async function () { + + assertNoRpc(); + + await revertAllDirty(); + await closeAllEditors(); + + disposeAll(disposables); + disposables.length = 0; + }); + + suiteSetup(function () { + disposables.push(vscode.notebook.registerNotebookContentProvider('notebookCoreTest', { + openNotebook: async (_resource: vscode.Uri): Promise => { + if (/.*empty\-.*\.vsctestnb$/.test(_resource.path)) { + return { + metadata: new vscode.NotebookDocumentMetadata(), + cells: [] + }; + } + + const dto: vscode.NotebookData = { + metadata: new vscode.NotebookDocumentMetadata().with({ custom: { testMetadata: false } }), + cells: [ + { + source: 'test', + language: 'typescript', + cellKind: vscode.NotebookCellKind.Code, + outputs: [], + metadata: new vscode.NotebookCellMetadata().with({ custom: { testCellMetadata: 123 } }) + }, + { + source: 'test2', + language: 'typescript', + cellKind: vscode.NotebookCellKind.Code, + outputs: [ + new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('text/plain', 'Hello World', { testOutputItemMetadata: true }) + ], + { testOutputMetadata: true }) + ], + metadata: new vscode.NotebookCellMetadata().with({ custom: { testCellMetadata: 456 } }) + } + ] + }; + return dto; + }, + resolveNotebook: async (_document: vscode.NotebookDocument) => { + return; + }, + saveNotebook: async (_document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => { + return; + }, + saveNotebookAs: async (_targetResource: vscode.Uri, _document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => { + return; + }, + backupNotebook: async (_document: vscode.NotebookDocument, _context: vscode.NotebookDocumentBackupContext, _cancellation: vscode.CancellationToken) => { + return { + id: '1', + delete: () => { } + }; + } + })); + + + const kernel: vscode.NotebookKernel = { + id: 'mainKernel', + label: 'Notebook Test Kernel', + isPreferred: true, + supportedLanguages: ['typescript', 'javascript'], + executeAllCells: async (_document: vscode.NotebookDocument) => { + const edit = new vscode.WorkspaceEdit(); + + edit.replaceNotebookCellOutput(_document.uri, 0, [new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('text/plain', ['my output'], undefined) + ])]); + return vscode.workspace.applyEdit(edit); + }, + cancelAllCellsExecution: async (_document: vscode.NotebookDocument) => { }, + executeCell: async (document: vscode.NotebookDocument, cell: vscode.NotebookCell | undefined) => { + if (!cell) { + cell = document.cells[0]; + } + + if (document.uri.path.endsWith('customRenderer.vsctestnb')) { + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCellOutput(document.uri, cell.index, [new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('text/custom', ['test'], undefined) + ])]); + + return vscode.workspace.applyEdit(edit); + } + + const edit = new vscode.WorkspaceEdit(); + // const previousOutputs = cell.outputs; + edit.replaceNotebookCellOutput(document.uri, cell.index, [new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('text/plain', ['my output'], undefined) + ])]); + + return vscode.workspace.applyEdit(edit); + }, + cancelCellExecution: async (_document: vscode.NotebookDocument, _cell: vscode.NotebookCell) => { } + }; + + const kernel2: vscode.NotebookKernel = { + id: 'secondaryKernel', + label: 'Notebook Secondary Test Kernel', + isPreferred: false, + supportedLanguages: ['typescript', 'javascript'], + executeAllCells: async (_document: vscode.NotebookDocument) => { + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCellOutput(_document.uri, 0, [new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('text/plain', ['my second output'], undefined) + ])]); + + return vscode.workspace.applyEdit(edit); + }, + cancelAllCellsExecution: async (_document: vscode.NotebookDocument) => { }, + executeCell: async (document: vscode.NotebookDocument, cell: vscode.NotebookCell | undefined) => { + if (!cell) { + cell = document.cells[0]; + } + + const edit = new vscode.WorkspaceEdit(); + + if (document.uri.path.endsWith('customRenderer.vsctestnb')) { + edit.replaceNotebookCellOutput(document.uri, cell.index, [new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('text/custom', ['test 2'], undefined) + ])]); + } else { + edit.replaceNotebookCellOutput(document.uri, cell.index, [new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('text/plain', ['my second output'], undefined) + ])]); + } + + return vscode.workspace.applyEdit(edit); + }, + cancelCellExecution: async (_document: vscode.NotebookDocument, _cell: vscode.NotebookCell) => { } + }; + + disposables.push(vscode.notebook.registerNotebookKernelProvider({ filenamePattern: '*.vsctestnb' }, { + provideKernels: async () => { + return [kernel, kernel2]; + } + })); + }); + + test('shared document in notebook editors', async function () { + const resource = await createRandomFile(undefined, undefined, '.vsctestnb'); + let counter = 0; + const disposables: vscode.Disposable[] = []; + disposables.push(vscode.notebook.onDidOpenNotebookDocument(() => { + counter++; + })); + disposables.push(vscode.notebook.onDidCloseNotebookDocument(() => { + counter--; + })); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.strictEqual(counter, 1); + + await splitEditor(); + assert.strictEqual(counter, 1); + await closeAllEditors(); + assert.strictEqual(counter, 0); + + disposables.forEach(d => d.dispose()); + }); + + test('editor open/close event', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + const firstEditorOpen = asPromise(vscode.window.onDidChangeVisibleNotebookEditors); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await firstEditorOpen; + + const firstEditorClose = asPromise(vscode.window.onDidChangeVisibleNotebookEditors); + await closeAllEditors(); + await firstEditorClose; + }); + + test('editor open/close event 2', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + let count = 0; + const disposables: vscode.Disposable[] = []; + disposables.push(vscode.window.onDidChangeVisibleNotebookEditors(() => { + count = vscode.window.visibleNotebookEditors.length; + })); + + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.strictEqual(count, 1); + + await splitEditor(); + assert.strictEqual(count, 2); + + await closeAllEditors(); + assert.strictEqual(count, 0); + }); + + test('editor editing event 2', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const cellsChangeEvent = asPromise(vscode.notebook.onDidChangeNotebookCells); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + const cellChangeEventRet = await cellsChangeEvent; + assert.strictEqual(cellChangeEventRet.document, vscode.window.activeNotebookEditor?.document); + assert.strictEqual(cellChangeEventRet.changes.length, 1); + assert.deepStrictEqual(cellChangeEventRet.changes[0], { + start: 1, + deletedCount: 0, + deletedItems: [], + items: [ + vscode.window.activeNotebookEditor!.document.cells[1] + ] + }); + + const secondCell = vscode.window.activeNotebookEditor!.document.cells[1]; + + const moveCellEvent = asPromise(vscode.notebook.onDidChangeNotebookCells); + await vscode.commands.executeCommand('notebook.cell.moveUp'); + const moveCellEventRet = await moveCellEvent; + assert.deepStrictEqual(moveCellEventRet, { + document: vscode.window.activeNotebookEditor!.document, + changes: [ + { + start: 1, + deletedCount: 1, + deletedItems: [secondCell], + items: [] + }, + { + start: 0, + deletedCount: 0, + deletedItems: [], + items: [vscode.window.activeNotebookEditor?.document.cells[0]] + } + ] + }); + + const cellOutputChange = asPromise(vscode.notebook.onDidChangeCellOutputs); + await vscode.commands.executeCommand('notebook.cell.execute'); + const cellOutputsAddedRet = await cellOutputChange; + assert.deepStrictEqual(cellOutputsAddedRet, { + document: vscode.window.activeNotebookEditor!.document, + cells: [vscode.window.activeNotebookEditor!.document.cells[0]] + }); + assert.strictEqual(cellOutputsAddedRet.cells[0].outputs.length, 1); + + const cellOutputClear = asPromise(vscode.notebook.onDidChangeCellOutputs); + await vscode.commands.executeCommand('notebook.cell.clearOutputs'); + const cellOutputsCleardRet = await cellOutputClear; + assert.deepStrictEqual(cellOutputsCleardRet, { + document: vscode.window.activeNotebookEditor!.document, + cells: [vscode.window.activeNotebookEditor!.document.cells[0]] + }); + assert.strictEqual(cellOutputsAddedRet.cells[0].outputs.length, 0); + + // const cellChangeLanguage = getEventOncePromise(vscode.notebook.onDidChangeCellLanguage); + // await vscode.commands.executeCommand('notebook.cell.changeToMarkdown'); + // const cellChangeLanguageRet = await cellChangeLanguage; + // assert.deepStrictEqual(cellChangeLanguageRet, { + // document: vscode.window.activeNotebookEditor!.document, + // cells: vscode.window.activeNotebookEditor!.document.cells[0], + // language: 'markdown' + // }); + + await saveAllFilesAndCloseAll(undefined); + }); + + test('editor move cell event', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); + await vscode.commands.executeCommand('notebook.focusTop'); + + const activeCell = vscode.window.activeNotebookEditor!.selection; + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); + const moveChange = asPromise(vscode.notebook.onDidChangeNotebookCells); + await vscode.commands.executeCommand('notebook.cell.moveDown'); + const ret = await moveChange; + assert.deepStrictEqual(ret, { + document: vscode.window.activeNotebookEditor?.document, + changes: [ + { + start: 0, + deletedCount: 1, + deletedItems: [activeCell], + items: [] + }, + { + start: 1, + deletedCount: 0, + deletedItems: [], + items: [activeCell] + } + ] + }); + + await saveAllEditors(); + await closeAllEditors(); + + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + const firstEditor = vscode.window.activeNotebookEditor; + assert.strictEqual(firstEditor?.document.cells.length, 2); + await saveAllFilesAndCloseAll(undefined); + }); + + test('notebook editor active/visible', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + const firstEditor = vscode.window.activeNotebookEditor; + assert.strictEqual(firstEditor && vscode.window.visibleNotebookEditors.indexOf(firstEditor) >= 0, true); + + await splitEditor(); + const secondEditor = vscode.window.activeNotebookEditor; + assert.strictEqual(secondEditor && vscode.window.visibleNotebookEditors.indexOf(secondEditor) >= 0, true); + assert.notStrictEqual(firstEditor, secondEditor); + assert.strictEqual(firstEditor && vscode.window.visibleNotebookEditors.indexOf(firstEditor) >= 0, true); + assert.strictEqual(vscode.window.visibleNotebookEditors.length, 2); + + const untitledEditorChange = asPromise(vscode.window.onDidChangeActiveNotebookEditor); + await vscode.commands.executeCommand('workbench.action.files.newUntitledFile'); + await untitledEditorChange; + assert.strictEqual(firstEditor && vscode.window.visibleNotebookEditors.indexOf(firstEditor) >= 0, true); + assert.notStrictEqual(firstEditor, vscode.window.activeNotebookEditor); + assert.strictEqual(secondEditor && vscode.window.visibleNotebookEditors.indexOf(secondEditor) < 0, true); + assert.notStrictEqual(secondEditor, vscode.window.activeNotebookEditor); + assert.strictEqual(vscode.window.visibleNotebookEditors.length, 1); + + const activeEditorClose = asPromise(vscode.window.onDidChangeActiveNotebookEditor); + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + await activeEditorClose; + assert.strictEqual(secondEditor, vscode.window.activeNotebookEditor); + assert.strictEqual(vscode.window.visibleNotebookEditors.length, 2); + assert.strictEqual(secondEditor && vscode.window.visibleNotebookEditors.indexOf(secondEditor) >= 0, true); + + await saveAllFilesAndCloseAll(undefined); + }); + + test('notebook active editor change', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + const firstEditorOpen = asPromise(vscode.window.onDidChangeActiveNotebookEditor); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await firstEditorOpen; + + const firstEditorDeactivate = asPromise(vscode.window.onDidChangeActiveNotebookEditor); + await vscode.commands.executeCommand('workbench.action.splitEditor'); + await firstEditorDeactivate; + + await saveFileAndCloseAll(resource); + }); + + test('change cell language', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + assert.strictEqual(vscode.window.activeNotebookEditor?.document.cells[0].language, 'typescript'); + assert.strictEqual(vscode.window.activeNotebookEditor?.document.cells[0].cellKind, vscode.NotebookCellKind.Code); + await withEvent(vscode.notebook.onDidChangeCellLanguage, async event => { + await vscode.commands.executeCommand('notebook.cell.changeLanguage', { start: 0, end: 1 }, 'javascript'); + await event; + assert.strictEqual(vscode.window.activeNotebookEditor?.document.cells[0].language, 'javascript'); + }); + + // switch to markdown will change the cell kind + await withEvent(vscode.notebook.onDidChangeNotebookCells, async event => { + await vscode.commands.executeCommand('notebook.cell.changeLanguage', { start: 0, end: 1 }, 'markdown'); + await event; + assert.strictEqual(vscode.window.activeNotebookEditor?.document.cells[0].language, 'markdown'); + assert.strictEqual(vscode.window.activeNotebookEditor?.document.cells[0].cellKind, vscode.NotebookCellKind.Markdown); + }); + + await saveAllFilesAndCloseAll(resource); + }); + + test('edit API (replaceMetadata)', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + await vscode.window.activeNotebookEditor!.edit(editBuilder => { + editBuilder.replaceCellMetadata(0, new vscode.NotebookCellMetadata().with({ inputCollapsed: true, executionOrder: 17 })); + }); + + const document = vscode.window.activeNotebookEditor?.document!; + assert.strictEqual(document.cells.length, 2); + assert.strictEqual(document.cells[0].metadata.executionOrder, 17); + assert.strictEqual(document.cells[0].metadata.inputCollapsed, true); + + assert.strictEqual(document.isDirty, true); + await saveFileAndCloseAll(resource); + }); + + test('edit API (replaceMetadata, event)', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const event = asPromise(vscode.notebook.onDidChangeCellMetadata); + + await vscode.window.activeNotebookEditor!.edit(editBuilder => { + editBuilder.replaceCellMetadata(0, new vscode.NotebookCellMetadata().with({ inputCollapsed: true, executionOrder: 17 })); + }); + + const data = await event; + assert.strictEqual(data.document, vscode.window.activeNotebookEditor?.document); + assert.strictEqual(data.cell.metadata.executionOrder, 17); + assert.strictEqual(data.cell.metadata.inputCollapsed, true); + + assert.strictEqual(data.document.isDirty, true); + await saveFileAndCloseAll(resource); + }); + + test('edit API batch edits', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const cellsChangeEvent = asPromise(vscode.notebook.onDidChangeNotebookCells); + const cellMetadataChangeEvent = asPromise(vscode.notebook.onDidChangeCellMetadata); + const version = vscode.window.activeNotebookEditor!.document.version; + await vscode.window.activeNotebookEditor!.edit(editBuilder => { + editBuilder.replaceCells(1, 0, [{ cellKind: vscode.NotebookCellKind.Code, language: 'javascript', source: 'test 2', outputs: [], metadata: undefined }]); + editBuilder.replaceCellMetadata(0, new vscode.NotebookCellMetadata().with({ runnable: false })); + }); + + await cellsChangeEvent; + await cellMetadataChangeEvent; + assert.strictEqual(version + 1, vscode.window.activeNotebookEditor!.document.version); + await saveAllFilesAndCloseAll(resource); + }); + + test('edit API batch edits undo/redo', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const cellsChangeEvent = asPromise(vscode.notebook.onDidChangeNotebookCells); + const cellMetadataChangeEvent = asPromise(vscode.notebook.onDidChangeCellMetadata); + const version = vscode.window.activeNotebookEditor!.document.version; + await vscode.window.activeNotebookEditor!.edit(editBuilder => { + editBuilder.replaceCells(1, 0, [{ cellKind: vscode.NotebookCellKind.Code, language: 'javascript', source: 'test 2', outputs: [], metadata: undefined }]); + editBuilder.replaceCellMetadata(0, new vscode.NotebookCellMetadata().with({ runnable: false })); + }); + + await cellsChangeEvent; + await cellMetadataChangeEvent; + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 3); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0]?.metadata?.runnable, false); + assert.strictEqual(version + 1, vscode.window.activeNotebookEditor!.document.version); + + await vscode.commands.executeCommand('undo'); + assert.strictEqual(version + 2, vscode.window.activeNotebookEditor!.document.version); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0]?.metadata?.runnable, undefined); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 2); + + await saveAllFilesAndCloseAll(resource); + }); + + test('initialzation should not emit cell change events.', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + let count = 0; + const disposables: vscode.Disposable[] = []; + disposables.push(vscode.notebook.onDidChangeNotebookCells(() => { + count++; + })); + + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.strictEqual(count, 0); + + disposables.forEach(d => d.dispose()); + + await saveFileAndCloseAll(resource); + }); + // }); + + // suite('notebook workflow', () => { + + test('notebook open', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); + + const secondCell = vscode.window.activeNotebookEditor!.document.cells[1]; + assert.strictEqual(secondCell!.outputs.length, 1); + assert.deepStrictEqual(secondCell!.outputs[0].metadata, { testOutputMetadata: true }); + assert.strictEqual(secondCell!.outputs[0].outputs.length, 1); + assert.strictEqual(secondCell!.outputs[0].outputs[0].mime, 'text/plain'); + assert.strictEqual(secondCell!.outputs[0].outputs[0].value, 'Hello World'); + assert.deepStrictEqual(secondCell!.outputs[0].outputs[0].metadata, { testOutputItemMetadata: true }); + + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + + await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); + const activeCell = vscode.window.activeNotebookEditor!.selection; + assert.notEqual(vscode.window.activeNotebookEditor!.selection, undefined); + assert.strictEqual(activeCell!.document.getText(), ''); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 4); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + + await vscode.commands.executeCommand('workbench.action.files.save'); + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + }); + + test('notebook cell actions', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); + + // ---- insert cell below and focus ---- // + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + + // ---- insert cell above and focus ---- // + await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); + let activeCell = vscode.window.activeNotebookEditor!.selection; + assert.notEqual(vscode.window.activeNotebookEditor!.selection, undefined); + assert.strictEqual(activeCell!.document.getText(), ''); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 4); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + + // ---- focus bottom ---- // + await vscode.commands.executeCommand('notebook.focusBottom'); + activeCell = vscode.window.activeNotebookEditor!.selection; + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 3); + + // ---- focus top and then copy down ---- // + await vscode.commands.executeCommand('notebook.focusTop'); + activeCell = vscode.window.activeNotebookEditor!.selection; + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); + + await vscode.commands.executeCommand('notebook.cell.copyDown'); + activeCell = vscode.window.activeNotebookEditor!.selection; + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + assert.strictEqual(activeCell?.document.getText(), 'test'); + + await vscode.commands.executeCommand('notebook.cell.delete'); + activeCell = vscode.window.activeNotebookEditor!.selection; + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + assert.strictEqual(activeCell?.document.getText(), ''); + + // ---- focus top and then copy up ---- // + await vscode.commands.executeCommand('notebook.focusTop'); + await vscode.commands.executeCommand('notebook.cell.copyUp'); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 5); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0].document.getText(), 'test'); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[1].document.getText(), 'test'); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[2].document.getText(), ''); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[3].document.getText(), ''); + activeCell = vscode.window.activeNotebookEditor!.selection; + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); + + + // ---- move up and down ---- // + + await vscode.commands.executeCommand('notebook.cell.moveDown'); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(vscode.window.activeNotebookEditor!.selection!), 1, + `first move down, active cell ${vscode.window.activeNotebookEditor!.selection!.uri.toString()}, ${vscode.window.activeNotebookEditor!.selection!.document.getText()}`); + + // await vscode.commands.executeCommand('notebook.cell.moveDown'); + // activeCell = vscode.window.activeNotebookEditor!.selection; + + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 2, + // `second move down, active cell ${vscode.window.activeNotebookEditor!.selection!.uri.toString()}, ${vscode.window.activeNotebookEditor!.selection!.document.getText()}`); + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0].document.getText(), 'test'); + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[1].document.getText(), ''); + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[2].document.getText(), 'test'); + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[3].document.getText(), ''); + + // ---- ---- // + + await vscode.commands.executeCommand('workbench.action.files.save'); + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + }); + + test('notebook join cells', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); + + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + const edit = new vscode.WorkspaceEdit(); + edit.insert(vscode.window.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); + await vscode.workspace.applyEdit(edit); + + const cellsChangeEvent = asPromise(vscode.notebook.onDidChangeNotebookCells); + await vscode.commands.executeCommand('notebook.cell.joinAbove'); + await cellsChangeEvent; + + assert.deepStrictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText().split(/\r\n|\r|\n/), ['test', 'var abc = 0;']); + + await vscode.commands.executeCommand('workbench.action.files.save'); + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + }); + + test('move cells will not recreate cells in ExtHost', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); + await vscode.commands.executeCommand('notebook.focusTop'); + + const activeCell = vscode.window.activeNotebookEditor!.selection; + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); + await vscode.commands.executeCommand('notebook.cell.moveDown'); + await vscode.commands.executeCommand('notebook.cell.moveDown'); + + const newActiveCell = vscode.window.activeNotebookEditor!.selection; + assert.deepStrictEqual(activeCell, newActiveCell); + + await saveFileAndCloseAll(resource); + }); + + + test('cell runnable metadata is respected', async () => { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + const editor = vscode.window.activeNotebookEditor!; + + await vscode.commands.executeCommand('notebook.focusTop'); + const cell = editor.document.cells[0]; + assert.strictEqual(cell.outputs.length, 0); + + let metadataChangeEvent = asPromise(vscode.notebook.onDidChangeCellMetadata); + await updateCellMetadata(resource, cell, cell.metadata.with({ runnable: false })); + await metadataChangeEvent; + + await vscode.commands.executeCommand('notebook.cell.execute'); + assert.strictEqual(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work + + metadataChangeEvent = asPromise(vscode.notebook.onDidChangeCellMetadata); + await updateCellMetadata(resource, cell, cell.metadata.with({ runnable: true })); + await metadataChangeEvent; + + await vscode.commands.executeCommand('notebook.cell.execute'); + assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked + + await vscode.commands.executeCommand('workbench.action.files.save'); + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + }); + + test('document runnable metadata is respected', async () => { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + const editor = vscode.window.activeNotebookEditor!; + + const cell = editor.document.cells[0]; + assert.strictEqual(cell.outputs.length, 0); + + await withEvent(vscode.notebook.onDidChangeNotebookDocumentMetadata, async event => { + updateNotebookMetadata(editor.document.uri, editor.document.metadata.with({ runnable: false })); + await event; + }); + + await vscode.commands.executeCommand('notebook.execute'); + assert.strictEqual(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work + + await withEvent(vscode.notebook.onDidChangeNotebookDocumentMetadata, async event => { + updateNotebookMetadata(editor.document.uri, editor.document.metadata.with({ runnable: true })); + await event; + }); + + await withEvent(vscode.notebook.onDidChangeCellOutputs, async (event) => { + await vscode.commands.executeCommand('notebook.execute'); + await event; + assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked + }); + + await saveAllFilesAndCloseAll(undefined); + }); + + + // TODO@rebornix this is wrong, `await vscode.commands.executeCommand('notebook.execute');` doesn't wait until the workspace edit is applied + test.skip('cell execute command takes arguments', async () => { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + const editor = vscode.window.activeNotebookEditor!; + const cell = editor.document.cells[0]; + + await vscode.commands.executeCommand('notebook.execute'); + assert.strictEqual(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work + + await saveAllFilesAndCloseAll(undefined); + }); + + test('cell execute command takes arguments 2', async () => { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + const editor = vscode.window.activeNotebookEditor!; + const cell = editor.document.cells[0]; + + await withEvent(vscode.notebook.onDidChangeNotebookDocumentMetadata, async event => { + updateNotebookMetadata(editor.document.uri, editor.document.metadata.with({ runnable: true })); + await event; + }); + + await withEvent(vscode.notebook.onDidChangeCellOutputs, async (event) => { + await vscode.commands.executeCommand('notebook.execute'); + await event; + assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked + }); + + await withEvent(vscode.notebook.onDidChangeCellOutputs, async event => { + await vscode.commands.executeCommand('notebook.cell.clearOutputs'); + await event; + assert.strictEqual(cell.outputs.length, 0, 'should clear'); + }); + + const secondResource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); + + await withEvent(vscode.notebook.onDidChangeCellOutputs, async (event) => { + await vscode.commands.executeCommand('notebook.cell.execute', { start: 0, end: 1 }, resource); + await event; + assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked + assert.strictEqual(vscode.window.activeNotebookEditor?.document.uri.fsPath, secondResource.fsPath); + }); + + await saveAllFilesAndCloseAll(undefined); + }); + + test('document execute command takes arguments', async () => { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + const editor = vscode.window.activeNotebookEditor!; + const cell = editor.document.cells[0]; + + const metadataChangeEvent = asPromise(vscode.notebook.onDidChangeNotebookDocumentMetadata); + updateNotebookMetadata(editor.document.uri, editor.document.metadata.with({ runnable: true })); + await metadataChangeEvent; + assert.strictEqual(editor.document.metadata.runnable, true); + + await withEvent(vscode.notebook.onDidChangeCellOutputs, async (event) => { + await vscode.commands.executeCommand('notebook.execute'); + await event; + assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked + }); + + const clearChangeEvent = asPromise(vscode.notebook.onDidChangeCellOutputs); + await vscode.commands.executeCommand('notebook.cell.clearOutputs'); + await clearChangeEvent; + assert.strictEqual(cell.outputs.length, 0, 'should clear'); + + const secondResource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); + + await withEvent(vscode.notebook.onDidChangeCellOutputs, async (event) => { + await vscode.commands.executeCommand('notebook.execute', resource); + await event; + assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked + assert.strictEqual(vscode.window.activeNotebookEditor?.document.uri.fsPath, secondResource.fsPath); + }); + + await saveAllFilesAndCloseAll(undefined); + }); + + test('cell execute and select kernel', async () => { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + const editor = vscode.window.activeNotebookEditor!; + const cell = editor.document.cells[0]; + + const metadataChangeEvent = asPromise(vscode.notebook.onDidChangeNotebookDocumentMetadata); + updateNotebookMetadata(editor.document.uri, editor.document.metadata.with({ runnable: true })); + await metadataChangeEvent; + + await vscode.commands.executeCommand('notebook.cell.execute'); + assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked + assert.strictEqual(cell.outputs[0].outputs.length, 1); + assert.strictEqual(cell.outputs[0].outputs[0].mime, 'text/plain'); + assert.deepStrictEqual(cell.outputs[0].outputs[0].value, [ + 'my output' + ]); + + await vscode.commands.executeCommand('notebook.selectKernel', { extension: 'vscode.vscode-api-tests', id: 'secondaryKernel' }); + await vscode.commands.executeCommand('notebook.cell.execute'); + assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked + assert.strictEqual(cell.outputs[0].outputs.length, 1); + assert.strictEqual(cell.outputs[0].outputs[0].mime, 'text/plain'); + assert.deepStrictEqual(cell.outputs[0].outputs[0].value, [ + 'my second output' + ]); + + await saveAllFilesAndCloseAll(undefined); + }); + // }); + + // suite('notebook dirty state', () => { + test('notebook open', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); + + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + + await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); + const activeCell = vscode.window.activeNotebookEditor!.selection; + assert.notStrictEqual(vscode.window.activeNotebookEditor!.selection, undefined); + assert.strictEqual(activeCell!.document.getText(), ''); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 4); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + + await withEvent(vscode.workspace.onDidChangeTextDocument, async event => { + const edit = new vscode.WorkspaceEdit(); + edit.insert(activeCell!.uri, new vscode.Position(0, 0), 'var abc = 0;'); + await vscode.workspace.applyEdit(edit); + await event; + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true); + assert.strictEqual(vscode.window.activeNotebookEditor?.selection !== undefined, true); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells[1], vscode.window.activeNotebookEditor?.selection); + assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); + }); + + await saveFileAndCloseAll(resource); + }); + // }); + + // suite('notebook undo redo', () => { + test('notebook open', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); + + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + + await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); + const activeCell = vscode.window.activeNotebookEditor!.selection; + assert.notStrictEqual(vscode.window.activeNotebookEditor!.selection, undefined); + assert.strictEqual(activeCell!.document.getText(), ''); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 4); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + + + // modify the second cell, delete it + const edit = new vscode.WorkspaceEdit(); + edit.insert(vscode.window.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); + await vscode.workspace.applyEdit(edit); + await vscode.commands.executeCommand('notebook.cell.delete'); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 3); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(vscode.window.activeNotebookEditor!.selection!), 1); + + + // undo should bring back the deleted cell, and revert to previous content and selection + await vscode.commands.executeCommand('undo'); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 4); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(vscode.window.activeNotebookEditor!.selection!), 1); + assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); + + // redo + // await vscode.commands.executeCommand('notebook.redo'); + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 2); + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(vscode.window.activeNotebookEditor!.selection!), 1); + // assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'test'); + + await saveFileAndCloseAll(resource); + }); + + test('multiple tabs: dirty + clean', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + + await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); + const edit = new vscode.WorkspaceEdit(); + edit.insert(vscode.window.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); + await vscode.workspace.applyEdit(edit); + + const secondResource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + + // make sure that the previous dirty editor is still restored in the extension host and no data loss + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true); + assert.strictEqual(vscode.window.activeNotebookEditor?.selection !== undefined, true); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells[1], vscode.window.activeNotebookEditor?.selection); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells.length, 4); + assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); + + await saveFileAndCloseAll(resource); + }); + + test('multiple tabs: two dirty tabs and switching', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + + await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); + const edit = new vscode.WorkspaceEdit(); + edit.insert(vscode.window.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); + await vscode.workspace.applyEdit(edit); + + const secondResource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + + // switch to the first editor + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true); + assert.strictEqual(vscode.window.activeNotebookEditor?.selection !== undefined, true); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells[1], vscode.window.activeNotebookEditor?.selection); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells.length, 4); + assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); + + // switch to the second editor + await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true); + assert.strictEqual(vscode.window.activeNotebookEditor?.selection !== undefined, true); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells[1], vscode.window.activeNotebookEditor?.selection); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells.length, 3); + assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), ''); + + await saveAllFilesAndCloseAll(secondResource); + }); + + test('multiple tabs: different editors with same document', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + const firstNotebookEditor = vscode.window.activeNotebookEditor; + assert.strictEqual(firstNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(firstNotebookEditor!.selection?.document.getText(), 'test'); + assert.strictEqual(firstNotebookEditor!.selection?.language, 'typescript'); + + await splitEditor(); + const secondNotebookEditor = vscode.window.activeNotebookEditor; + assert.strictEqual(secondNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(secondNotebookEditor!.selection?.document.getText(), 'test'); + assert.strictEqual(secondNotebookEditor!.selection?.language, 'typescript'); + + assert.notEqual(firstNotebookEditor, secondNotebookEditor); + assert.strictEqual(firstNotebookEditor?.document, secondNotebookEditor?.document, 'split notebook editors share the same document'); + + await saveAllFilesAndCloseAll(resource); + }); + + test('custom metadata should be supported', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.metadata.custom!['testCellMetadata'] as number, 123); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); + + await saveFileAndCloseAll(resource); + }); + + + // TODO@rebornix skip as it crashes the process all the time + test.skip('custom metadata should be supported 2', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.metadata.custom!['testCellMetadata'] as number, 123); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); + + // TODO see #101462 + // await vscode.commands.executeCommand('notebook.cell.copyDown'); + // const activeCell = vscode.window.activeNotebookEditor!.selection; + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + // assert.strictEqual(activeCell?.metadata.custom!['testCellMetadata'] as number, 123); + + await saveFileAndCloseAll(resource); + }); + + + test('#106657. Opening a notebook from markers view is broken ', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const document = vscode.window.activeNotebookEditor?.document!; + const [cell] = document.cells; + + await saveAllFilesAndCloseAll(document.uri); + assert.strictEqual(vscode.window.activeNotebookEditor, undefined); + + // opening a cell-uri opens a notebook editor + await vscode.commands.executeCommand('vscode.open', cell.uri, vscode.ViewColumn.Active); + + assert.strictEqual(!!vscode.window.activeNotebookEditor, true); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.uri.toString(), resource.toString()); + }); + + test.skip('Cannot open notebook from cell-uri with vscode.open-command', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const document = vscode.window.activeNotebookEditor?.document!; + const [cell] = document.cells; + + await saveAllFilesAndCloseAll(document.uri); + assert.strictEqual(vscode.window.activeNotebookEditor, undefined); + + // BUG is that the editor opener (https://github.com/microsoft/vscode/blob/8e7877bdc442f1e83a7fec51920d82b696139129/src/vs/editor/browser/services/openerService.ts#L69) + // removes the fragment if it matches something numeric. For notebooks that's not wanted... + await vscode.commands.executeCommand('vscode.open', cell.uri); + + assert.strictEqual(vscode.window.activeNotebookEditor!.document.uri.toString(), resource.toString()); + }); + + test('#97830, #97764. Support switch to other editor types', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + const edit = new vscode.WorkspaceEdit(); + edit.insert(vscode.window.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); + await vscode.workspace.applyEdit(edit); + + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'var abc = 0;'); + + // no kernel -> no default language + // assert.strictEqual(vscode.window.activeNotebookEditor!.kernel, undefined); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); + + await vscode.commands.executeCommand('vscode.openWith', resource, 'default'); + assert.strictEqual(vscode.window.activeTextEditor?.document.uri.path, resource.path); + + await closeAllEditors(); + }); + + // open text editor, pin, and then open a notebook + test('#96105 - dirty editors', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'default'); + const edit = new vscode.WorkspaceEdit(); + edit.insert(resource, new vscode.Position(0, 0), 'var abc = 0;'); + await vscode.workspace.applyEdit(edit); + + // now it's dirty, open the resource with notebook editor should open a new one + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.notEqual(vscode.window.activeNotebookEditor, undefined, 'notebook first'); + // assert.notEqual(vscode.window.activeTextEditor, undefined); + + await closeAllEditors(); + }); + + test('#102411 - untitled notebook creation failed', async function () { + await vscode.commands.executeCommand('workbench.action.files.newUntitledFile', { viewType: 'notebookCoreTest' }); + assert.notEqual(vscode.window.activeNotebookEditor, undefined, 'untitled notebook editor is not undefined'); + + await closeAllEditors(); + }); + + test('#102423 - copy/paste shares the same text buffer', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + let activeCell = vscode.window.activeNotebookEditor!.selection; + assert.strictEqual(activeCell?.document.getText(), 'test'); + + await vscode.commands.executeCommand('notebook.cell.copyDown'); + await vscode.commands.executeCommand('notebook.cell.edit'); + activeCell = vscode.window.activeNotebookEditor!.selection; + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + assert.strictEqual(activeCell?.document.getText(), 'test'); + + const edit = new vscode.WorkspaceEdit(); + edit.insert(vscode.window.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); + await vscode.workspace.applyEdit(edit); + + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 3); + assert.notEqual(vscode.window.activeNotebookEditor!.document.cells[0].document.getText(), vscode.window.activeNotebookEditor!.document.cells[1].document.getText()); + + await closeAllEditors(); + }); + + test('#116598, output items change event.', async function () { + + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const edit = new vscode.WorkspaceEdit(); + edit.appendNotebookCellOutput(resource, 0, [new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('application/foo', 'bar'), + new vscode.NotebookCellOutputItem('application/json', { data: true }, { metadata: true }), + ])]); + await vscode.workspace.applyEdit(edit); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0].outputs.length, 1); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0].outputs[0].outputs.length, 2); + + const appendEdit = new vscode.WorkspaceEdit(); + const newItem = new vscode.NotebookCellOutputItem('text/plain', '1'); + appendEdit.appendNotebookCellOutputItems( + resource, + 0, + vscode.window.activeNotebookEditor!.document.cells[0].outputs[0].id, + [newItem] + ); + await vscode.workspace.applyEdit(appendEdit); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0].outputs[0].outputs.length, 3); + assert.deepStrictEqual(vscode.window.activeNotebookEditor!.document.cells[0].outputs[0].outputs[2], newItem); + }); + + test('#115855 onDidSaveNotebookDocument', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const cellsChangeEvent = asPromise(vscode.notebook.onDidChangeNotebookCells); + await vscode.window.activeNotebookEditor!.edit(editBuilder => { + editBuilder.replaceCells(1, 0, [{ cellKind: vscode.NotebookCellKind.Code, language: 'javascript', source: 'test 2', outputs: [], metadata: undefined }]); + }); + + const cellChangeEventRet = await cellsChangeEvent; + assert.strictEqual(cellChangeEventRet.document === vscode.window.activeNotebookEditor?.document, true); + assert.strictEqual(cellChangeEventRet.document.isDirty, true); + + await withEvent(vscode.notebook.onDidSaveNotebookDocument, async event => { + await vscode.commands.executeCommand('workbench.action.files.saveAll'); + await event; + assert.strictEqual(cellChangeEventRet.document.isDirty, false); + }); + await saveAllFilesAndCloseAll(resource); + }); + + + test('#116808, active kernel should not be undefined', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + await withEvent(vscode.notebook.onDidChangeActiveNotebookKernel, async event => { + await event; + assert.notStrictEqual(vscode.window.activeNotebookEditor?.kernel, undefined); + assert.strictEqual(vscode.window.activeNotebookEditor?.kernel?.id, 'mainKernel'); + }); + + await saveAllFilesAndCloseAll(resource); + }); + + test('Numeric metadata should get updated correctly', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + const document = await vscode.notebook.openNotebookDocument(resource); + + const edit = new vscode.WorkspaceEdit(); + const runStartTime = Date.now(); + const lastRunDuration = Date.now() + 1000; + const runState = vscode.NotebookCellRunState.Success; + const executionOrder = 1234; + const metadata = document.cells[0].metadata.with({ + ...document.cells[0].metadata, + runStartTime, + runState, + lastRunDuration, + executionOrder + }); + edit.replaceNotebookCellMetadata(document.uri, 0, metadata); + await vscode.workspace.applyEdit(edit); + + assert.strictEqual(document.cells[0].metadata.runStartTime, runStartTime); + assert.strictEqual(document.cells[0].metadata.lastRunDuration, lastRunDuration); + assert.strictEqual(document.cells[0].metadata.executionOrder, executionOrder); + assert.strictEqual(document.cells[0].metadata.runState, vscode.NotebookCellRunState.Success); + }); + + // }); + + // suite('webview', () => { + // for web, `asWebUri` gets `https`? + // test('asWebviewUri', async function () { + // if (vscode.env.uiKind === vscode.UIKind.Web) { + // return; + // } + + // const resource = await createRandomFile('', undefined, '.vsctestnb'); + // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + // assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + // const uri = vscode.window.activeNotebookEditor!.asWebviewUri(vscode.Uri.file('./hello.png')); + // assert.strictEqual(uri.scheme, 'vscode-webview-resource'); + // await closeAllEditors(); + // }); + + + // 404 on web + // test('custom renderer message', async function () { + // if (vscode.env.uiKind === vscode.UIKind.Web) { + // return; + // } + + // const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './customRenderer.vsctestnb')); + // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + // const editor = vscode.window.activeNotebookEditor; + // const promise = new Promise(resolve => { + // const messageEmitter = editor?.onDidReceiveMessage(e => { + // if (e.type === 'custom_renderer_initialize') { + // resolve(); + // messageEmitter?.dispose(); + // } + // }); + // }); + + // await vscode.commands.executeCommand('notebook.cell.execute'); + // await promise; + // await closeAllEditors(); + // }); +}); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts index 55fbe0655..701c94742 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { window, commands } from 'vscode'; -import { closeAllEditors } from '../utils'; +import { assertNoRpc, closeAllEditors } from '../utils'; interface QuickPickExpected { events: string[]; @@ -20,7 +20,10 @@ interface QuickPickExpected { suite('vscode API - quick input', function () { - teardown(closeAllEditors); + teardown(async function () { + assertNoRpc(); + await closeAllEditors(); + }); test('createQuickPick, select second', function (_done) { let done = (err?: any) => { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/rpc.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/rpc.test.ts new file mode 100644 index 000000000..2b336f097 --- /dev/null +++ b/extensions/vscode-api-tests/src/singlefolder-tests/rpc.test.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 { assertNoRpcFromEntry, assertNoRpc, disposeAll } from '../utils'; +import * as vscode from 'vscode'; + +suite('vscode', function () { + + const dispo: vscode.Disposable[] = []; + + teardown(() => { + assertNoRpc(); + disposeAll(dispo); + }); + + test('no rpc', function () { + assertNoRpc(); + }); + + test('no rpc, createTextEditorDecorationType(...)', function () { + const item = vscode.window.createTextEditorDecorationType({}); + dispo.push(item); + assertNoRpcFromEntry([item, 'TextEditorDecorationType']); + }); + + test('no rpc, createOutputChannel(...)', function () { + const item = vscode.window.createOutputChannel('hello'); + dispo.push(item); + assertNoRpcFromEntry([item, 'OutputChannel']); + }); + + test('no rpc, createDiagnosticCollection(...)', function () { + const item = vscode.languages.createDiagnosticCollection(); + dispo.push(item); + assertNoRpcFromEntry([item, 'DiagnosticCollection']); + }); + + test('no rpc, createQuickPick(...)', function () { + const item = vscode.window.createQuickPick(); + dispo.push(item); + assertNoRpcFromEntry([item, 'QuickPick']); + }); + + test('no rpc, createInputBox(...)', function () { + const item = vscode.window.createInputBox(); + dispo.push(item); + assertNoRpcFromEntry([item, 'InputBox']); + }); + + test('no rpc, createStatusBarItem(...)', function () { + const item = vscode.window.createStatusBarItem(); + dispo.push(item); + assertNoRpcFromEntry([item, 'StatusBarItem']); + }); + + test('no rpc, createSourceControl(...)', function () { + this.skip(); + const item = vscode.scm.createSourceControl('foo', 'Hello'); + dispo.push(item); + assertNoRpcFromEntry([item, 'SourceControl']); + }); + + test('no rpc, createCommentController(...)', function () { + this.skip(); + const item = vscode.comments.createCommentController('foo', 'Hello'); + dispo.push(item); + assertNoRpcFromEntry([item, 'CommentController']); + }); + + test('no rpc, createWebviewPanel(...)', function () { + const item = vscode.window.createWebviewPanel('webview', 'Hello', vscode.ViewColumn.Active); + dispo.push(item); + assertNoRpcFromEntry([item, 'WebviewPanel']); + }); + + test('no rpc, createTreeView(...)', function () { + const treeDataProvider = new class implements vscode.TreeDataProvider { + getTreeItem(element: string): vscode.TreeItem | Thenable { + return new vscode.TreeItem(element); + } + getChildren(_element?: string): vscode.ProviderResult { + return ['foo', 'bar']; + } + }; + const item = vscode.window.createTreeView('test.treeId', { treeDataProvider }); + dispo.push(item); + assertNoRpcFromEntry([item, 'TreeView']); + }); + + test('no rpc, createNotebookEditorDecorationType(...)', function () { + const item = vscode.notebook.createNotebookEditorDecorationType({ top: {} }); + dispo.push(item); + assertNoRpcFromEntry([item, 'NotebookEditorDecorationType']); + }); +}); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index 01232a99c..df71e4f6b 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { window, Pseudoterminal, EventEmitter, TerminalDimensions, workspace, ConfigurationTarget, Disposable, UIKind, env, EnvironmentVariableMutatorType, EnvironmentVariableMutator, extensions, ExtensionContext, TerminalOptions, ExtensionTerminalOptions } from 'vscode'; -import { doesNotThrow, equal, ok, deepEqual, throws } from 'assert'; +import { window, Pseudoterminal, EventEmitter, TerminalDimensions, workspace, ConfigurationTarget, Disposable, UIKind, env, EnvironmentVariableMutatorType, EnvironmentVariableMutator, extensions, ExtensionContext, TerminalOptions, ExtensionTerminalOptions, Terminal } from 'vscode'; +import { doesNotThrow, equal, deepEqual, throws, strictEqual } from 'assert'; +import { assertNoRpc } from '../utils'; // Disable terminal tests: // - Web https://github.com/microsoft/vscode/issues/92826 @@ -30,61 +31,56 @@ import { doesNotThrow, equal, ok, deepEqual, throws } from 'assert'; let disposables: Disposable[] = []; teardown(() => { + assertNoRpc(); disposables.forEach(d => d.dispose()); disposables.length = 0; }); - test('sendText immediately after createTerminal should not throw', (done) => { - disposables.push(window.onDidOpenTerminal(term => { - try { - equal(terminal, term); - } catch (e) { - done(e); - return; - } - terminal.dispose(); - disposables.push(window.onDidCloseTerminal(() => done())); - })); + test('sendText immediately after createTerminal should not throw', async () => { const terminal = window.createTerminal(); + const result = await new Promise(r => { + disposables.push(window.onDidOpenTerminal(t => { + if (t === terminal) { + r(t); + } + })); + }); + equal(result, terminal); doesNotThrow(terminal.sendText.bind(terminal, 'echo "foo"')); + await new Promise(r => { + disposables.push(window.onDidCloseTerminal(t => { + if (t === terminal) { + r(); + } + })); + terminal.dispose(); + }); }); - (process.platform === 'linux' ? test.skip : test)('echo works in the default shell', (done) => { - disposables.push(window.onDidOpenTerminal(term => { - try { - equal(terminal, term); - } catch (e) { - done(e); - return; - } - let data = ''; - const dataDisposable = window.onDidWriteTerminalData(e => { - try { - equal(terminal, e.terminal); - } catch (e) { - done(e); - return; - } - data += e.data; - if (data.indexOf(expected) !== 0) { - dataDisposable.dispose(); - terminal.dispose(); - disposables.push(window.onDidCloseTerminal(() => { - done(); - })); + test('echo works in the default shell', async () => { + const terminal = await new Promise(r => { + disposables.push(window.onDidOpenTerminal(t => { + if (t === terminal) { + r(terminal); } + })); + // Use a single character to avoid winpty/conpty issues with injected sequences + const terminal = window.createTerminal({ + env: { TEST: '`' } }); - disposables.push(dataDisposable); - })); - // Use a single character to avoid winpty/conpty issues with injected sequences - const expected = '`'; - const terminal = window.createTerminal({ - env: { - TEST: '`' - } + terminal.show(); }); - terminal.show(); - doesNotThrow(() => { + + let data = ''; + await new Promise(r => { + disposables.push(window.onDidWriteTerminalData(e => { + if (e.terminal === terminal) { + data += e.data; + if (data.indexOf('`') !== 0) { + r(); + } + } + })); // Print an environment variable value so the echo statement doesn't get matched if (process.platform === 'win32') { terminal.sendText(`$env:TEST`); @@ -92,126 +88,143 @@ import { doesNotThrow, equal, ok, deepEqual, throws } from 'assert'; terminal.sendText(`echo $TEST`); } }); - }); - test('onDidCloseTerminal event fires when terminal is disposed', (done) => { - disposables.push(window.onDidOpenTerminal(term => { - try { - equal(terminal, term); - } catch (e) { - done(e); - return; - } + await new Promise(r => { terminal.dispose(); - disposables.push(window.onDidCloseTerminal(() => done())); - })); - const terminal = window.createTerminal(); + disposables.push(window.onDidCloseTerminal(t => { + strictEqual(terminal, t); + r(); + })); + }); }); - test('processId immediately after createTerminal should fetch the pid', (done) => { - disposables.push(window.onDidOpenTerminal(term => { - try { - equal(terminal, term); - } catch (e) { - done(e); - return; - } - terminal.processId.then(id => { - try { - ok(id && id > 0); - } catch (e) { - done(e); - return; + test('onDidCloseTerminal event fires when terminal is disposed', async () => { + const terminal = window.createTerminal(); + const result = await new Promise(r => { + disposables.push(window.onDidOpenTerminal(t => { + if (t === terminal) { + r(t); } - terminal.dispose(); - disposables.push(window.onDidCloseTerminal(() => done())); - }); - })); + })); + }); + equal(result, terminal); + await new Promise(r => { + disposables.push(window.onDidCloseTerminal(t => { + if (t === terminal) { + r(); + } + })); + terminal.dispose(); + }); + }); + + test('processId immediately after createTerminal should fetch the pid', async () => { const terminal = window.createTerminal(); + const result = await new Promise(r => { + disposables.push(window.onDidOpenTerminal(t => { + if (t === terminal) { + r(t); + } + })); + }); + equal(result, terminal); + let pid = await result.processId; + equal(true, pid && pid > 0); + await new Promise(r => { + disposables.push(window.onDidCloseTerminal(t => { + if (t === terminal) { + r(); + } + })); + terminal.dispose(); + }); }); - test('name in constructor should set terminal.name', (done) => { - disposables.push(window.onDidOpenTerminal(term => { - try { - equal(terminal, term); - } catch (e) { - done(e); - return; - } - terminal.dispose(); - disposables.push(window.onDidCloseTerminal(() => done())); - })); + test('name in constructor should set terminal.name', async () => { const terminal = window.createTerminal('a'); - try { - equal(terminal.name, 'a'); - } catch (e) { - done(e); - return; - } + const result = await new Promise(r => { + disposables.push(window.onDidOpenTerminal(t => { + if (t === terminal) { + r(t); + } + })); + }); + equal(result, terminal); + await new Promise(r => { + disposables.push(window.onDidCloseTerminal(t => { + if (t === terminal) { + r(); + } + })); + terminal.dispose(); + }); }); - test('creationOptions should be set and readonly for TerminalOptions terminals', (done) => { - disposables.push(window.onDidOpenTerminal(term => { - try { - equal(terminal, term); - } catch (e) { - done(e); - return; - } - terminal.dispose(); - disposables.push(window.onDidCloseTerminal(() => done())); - })); + test('creationOptions should be set and readonly for TerminalOptions terminals', async () => { const options = { name: 'foo', hideFromUser: true }; const terminal = window.createTerminal(options); - try { - equal(terminal.name, 'foo'); - const terminalOptions = terminal.creationOptions as TerminalOptions; - equal(terminalOptions.name, 'foo'); - equal(terminalOptions.hideFromUser, true); - throws(() => terminalOptions.name = 'bad', 'creationOptions should be readonly at runtime'); - } catch (e) { - done(e); - return; - } - }); - - test('onDidOpenTerminal should fire when a terminal is created', (done) => { - disposables.push(window.onDidOpenTerminal(term => { - try { - equal(term.name, 'b'); - } catch (e) { - done(e); - return; - } - disposables.push(window.onDidCloseTerminal(() => done())); - terminal.dispose(); - })); - const terminal = window.createTerminal('b'); - }); - - test('exitStatus.code should be set to undefined after a terminal is disposed', (done) => { - disposables.push(window.onDidOpenTerminal(term => { - try { - equal(term, terminal); - } catch (e) { - done(e); - return; - } - disposables.push(window.onDidCloseTerminal(t => { - try { - deepEqual(t.exitStatus, { code: undefined }); - } catch (e) { - done(e); - return; + const terminalOptions = terminal.creationOptions as TerminalOptions; + const result = await new Promise(r => { + disposables.push(window.onDidOpenTerminal(t => { + if (t === terminal) { + r(t); + } + })); + }); + equal(result, terminal); + await new Promise(r => { + disposables.push(window.onDidCloseTerminal(t => { + if (t === terminal) { + r(); } - done(); })); terminal.dispose(); - })); + }); + throws(() => terminalOptions.name = 'bad', 'creationOptions should be readonly at runtime'); + }); + + test('onDidOpenTerminal should fire when a terminal is created', async () => { + const terminal = window.createTerminal('b'); + const result = await new Promise(r => { + disposables.push(window.onDidOpenTerminal(t => { + if (t === terminal) { + r(t); + } + })); + }); + equal(result, terminal); + await new Promise(r => { + disposables.push(window.onDidCloseTerminal(t => { + if (t === terminal) { + r(); + } + })); + terminal.dispose(); + }); + }); + + test('exitStatus.code should be set to undefined after a terminal is disposed', async () => { const terminal = window.createTerminal(); + const result = await new Promise(r => { + disposables.push(window.onDidOpenTerminal(t => { + if (t === terminal) { + r(t); + } + })); + }); + equal(result, terminal); + await new Promise(r => { + disposables.push(window.onDidCloseTerminal(t => { + if (t === terminal) { + deepEqual(t.exitStatus, { code: undefined }); + r(); + } + })); + terminal.dispose(); + }); }); // test('onDidChangeActiveTerminal should fire when new terminals are created', (done) => { @@ -285,23 +298,25 @@ import { doesNotThrow, equal, ok, deepEqual, throws } from 'assert'; // }); suite('hideFromUser', () => { - test('should be available to terminals API', done => { + test('should be available to terminals API', async () => { const terminal = window.createTerminal({ name: 'bg', hideFromUser: true }); - disposables.push(window.onDidOpenTerminal(t => { - try { - equal(t, terminal); - equal(t.name, 'bg'); - ok(window.terminals.indexOf(terminal) !== -1); - } catch (e) { - done(e); - return; - } - disposables.push(window.onDidCloseTerminal(() => { - // reg3.dispose(); - done(); + const result = await new Promise(r => { + disposables.push(window.onDidOpenTerminal(t => { + if (t === terminal) { + r(t); + } + })); + }); + equal(result, terminal); + equal(true, window.terminals.indexOf(terminal) !== -1); + await new Promise(r => { + disposables.push(window.onDidCloseTerminal(t => { + if (t === terminal) { + r(); + } })); terminal.dispose(); - })); + }); }); }); @@ -626,10 +641,7 @@ import { doesNotThrow, equal, ok, deepEqual, throws } from 'assert'; '~c2~c1' ]; disposables.push(window.onDidWriteTerminalData(e => { - try { - equal(terminal, e.terminal); - } catch (e) { - done(e); + if (terminal !== e.terminal) { return; } // Multiple expected could show up in the same data event @@ -672,10 +684,7 @@ import { doesNotThrow, equal, ok, deepEqual, throws } from 'assert'; '~c2~' ]; disposables.push(window.onDidWriteTerminalData(e => { - try { - equal(terminal, e.terminal); - } catch (e) { - done(e); + if (terminal !== e.terminal) { return; } // Multiple expected could show up in the same data event @@ -717,10 +726,7 @@ import { doesNotThrow, equal, ok, deepEqual, throws } from 'assert'; '~b1~' ]; disposables.push(window.onDidWriteTerminalData(e => { - try { - equal(terminal, e.terminal); - } catch (e) { - done(e); + if (terminal !== e.terminal) { return; } // Multiple expected could show up in the same data event @@ -759,10 +765,7 @@ import { doesNotThrow, equal, ok, deepEqual, throws } from 'assert'; '~b2~' ]; disposables.push(window.onDidWriteTerminalData(e => { - try { - equal(terminal, e.terminal); - } catch (e) { - done(e); + if (terminal !== e.terminal) { return; } // Multiple expected could show up in the same data event diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/types.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/types.test.ts index 53265b35e..7143b40fa 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/types.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/types.test.ts @@ -6,9 +6,12 @@ import 'mocha'; import * as assert from 'assert'; import * as vscode from 'vscode'; +import { assertNoRpc } from '../utils'; suite('vscode API - types', () => { + teardown(assertNoRpc); + test('static properties, es5 compat class', function () { assert.ok(vscode.ThemeIcon.File instanceof vscode.ThemeIcon); assert.ok(vscode.ThemeIcon.Folder instanceof vscode.ThemeIcon); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts index 83357278c..14947da78 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import 'mocha'; import * as os from 'os'; import * as vscode from 'vscode'; -import { closeAllEditors, delay, disposeAll } from '../utils'; +import { assertNoRpc, closeAllEditors, delay, disposeAll } from '../utils'; const webviewId = 'myWebview'; @@ -26,8 +26,8 @@ suite.skip('vscode API - webview', () => { } teardown(async () => { + assertNoRpc(); await closeAllEditors(); - disposeAll(disposables); }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts index 89f3dbb72..615bb79ac 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts @@ -6,12 +6,15 @@ import * as assert from 'assert'; import { workspace, window, commands, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position, CancellationTokenSource, TextEditorSelectionChangeKind, QuickPickItem, TextEditor } from 'vscode'; import { join } from 'path'; -import { closeAllEditors, pathEquals, createRandomFile } from '../utils'; +import { closeAllEditors, pathEquals, createRandomFile, assertNoRpc } from '../utils'; suite('vscode API - window', () => { - teardown(closeAllEditors); + teardown(async function () { + assertNoRpc(); + await closeAllEditors(); + }); test('editor, active text editor', async () => { const doc = await workspace.openTextDocument(join(workspace.rootPath || '', './far.js')); @@ -147,6 +150,13 @@ suite('vscode API - window', () => { }); test('active editor not always correct... #49125', async function () { + + if (!window.state.focused) { + // no focus! + this.skip(); + return; + } + if (process.env['BUILD_SOURCEVERSION']) { this.skip(); return; @@ -429,8 +439,8 @@ suite('vscode API - window', () => { }); test('showQuickPick, select first two', async function () { - const label = 'showQuickPick, select first two'; - let i = 0; + // const label = 'showQuickPick, select first two'; + // let i = 0; const resolves: ((value: string) => void)[] = []; let done: () => void; const unexpected = new Promise((resolve, reject) => { @@ -442,26 +452,26 @@ suite('vscode API - window', () => { canPickMany: true }); const first = new Promise(resolve => resolves.push(resolve)); - console.log(`${label}: ${++i}`); + // console.log(`${label}: ${++i}`); await new Promise(resolve => setTimeout(resolve, 100)); // Allow UI to update. - console.log(`${label}: ${++i}`); + // console.log(`${label}: ${++i}`); await commands.executeCommand('workbench.action.quickOpenSelectNext'); - console.log(`${label}: ${++i}`); + // console.log(`${label}: ${++i}`); assert.equal(await first, 'eins'); - console.log(`${label}: ${++i}`); + // console.log(`${label}: ${++i}`); await commands.executeCommand('workbench.action.quickPickManyToggle'); - console.log(`${label}: ${++i}`); + // console.log(`${label}: ${++i}`); const second = new Promise(resolve => resolves.push(resolve)); await commands.executeCommand('workbench.action.quickOpenSelectNext'); - console.log(`${label}: ${++i}`); + // console.log(`${label}: ${++i}`); assert.equal(await second, 'zwei'); - console.log(`${label}: ${++i}`); + // console.log(`${label}: ${++i}`); await commands.executeCommand('workbench.action.quickPickManyToggle'); - console.log(`${label}: ${++i}`); + // console.log(`${label}: ${++i}`); await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); - console.log(`${label}: ${++i}`); + // console.log(`${label}: ${++i}`); assert.deepStrictEqual(await picks, ['eins', 'zwei']); - console.log(`${label}: ${++i}`); + // console.log(`${label}: ${++i}`); done!(); return unexpected; }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.event.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.event.test.ts index e192c63c0..10ef9563c 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.event.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.event.test.ts @@ -5,20 +5,19 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; -import { createRandomFile, withLogDisabled } from '../utils'; +import { assertNoRpc, createRandomFile, disposeAll, withLogDisabled } from '../utils'; suite('vscode API - workspace events', () => { const disposables: vscode.Disposable[] = []; teardown(() => { - for (const dispo of disposables) { - dispo.dispose(); - } + assertNoRpc(); + disposeAll(disposables); disposables.length = 0; }); - test('onWillCreate/onDidCreate', async function () { + test('onWillCreate/onDidCreate', withLogDisabled(async function () { const base = await createRandomFile(); const newUri = base.with({ path: base.path + '-foo' }); @@ -42,9 +41,9 @@ suite('vscode API - workspace events', () => { assert.ok(onDidCreate); assert.equal(onDidCreate?.files.length, 1); assert.equal(onDidCreate?.files[0].toString(), newUri.toString()); - }); + })); - test('onWillCreate/onDidCreate, make changes, edit another file', async function () { + test('onWillCreate/onDidCreate, make changes, edit another file', withLogDisabled(async function () { const base = await createRandomFile(); const baseDoc = await vscode.workspace.openTextDocument(base); @@ -64,7 +63,7 @@ suite('vscode API - workspace events', () => { assert.ok(success); assert.equal(baseDoc.getText(), 'HALLO_NEW'); - }); + })); test('onWillCreate/onDidCreate, make changes, edit new file fails', withLogDisabled(async function () { @@ -88,7 +87,7 @@ suite('vscode API - workspace events', () => { assert.equal((await vscode.workspace.openTextDocument(newUri)).getText(), ''); })); - test('onWillDelete/onDidDelete', async function () { + test('onWillDelete/onDidDelete', withLogDisabled(async function () { const base = await createRandomFile(); @@ -111,9 +110,9 @@ suite('vscode API - workspace events', () => { assert.ok(onDiddelete); assert.equal(onDiddelete?.files.length, 1); assert.equal(onDiddelete?.files[0].toString(), base.toString()); - }); + })); - test('onWillDelete/onDidDelete, make changes', async function () { + test('onWillDelete/onDidDelete, make changes', withLogDisabled(async function () { const base = await createRandomFile(); const newUri = base.with({ path: base.path + '-NEW' }); @@ -131,9 +130,9 @@ suite('vscode API - workspace events', () => { const success = await vscode.workspace.applyEdit(edit); assert.ok(success); - }); + })); - test('onWillDelete/onDidDelete, make changes, del another file', async function () { + test('onWillDelete/onDidDelete, make changes, del another file', withLogDisabled(async function () { const base = await createRandomFile(); const base2 = await createRandomFile(); @@ -152,9 +151,9 @@ suite('vscode API - workspace events', () => { assert.ok(success); - }); + })); - test('onWillDelete/onDidDelete, make changes, double delete', async function () { + test('onWillDelete/onDidDelete, make changes, double delete', withLogDisabled(async function () { const base = await createRandomFile(); let cnt = 0; @@ -171,9 +170,9 @@ suite('vscode API - workspace events', () => { const success = await vscode.workspace.applyEdit(edit); assert.ok(success); - }); + })); - test('onWillRename/onDidRename', async function () { + test('onWillRename/onDidRename', withLogDisabled(async function () { const oldUri = await createRandomFile(); const newUri = oldUri.with({ path: oldUri.path + '-NEW' }); @@ -199,15 +198,15 @@ suite('vscode API - workspace events', () => { assert.equal(onDidRename?.files.length, 1); assert.equal(onDidRename?.files[0].oldUri.toString(), oldUri.toString()); assert.equal(onDidRename?.files[0].newUri.toString(), newUri.toString()); - }); + })); - test('onWillRename - make changes (saved file)', function () { + test('onWillRename - make changes (saved file)', withLogDisabled(function () { return testOnWillRename(false); - }); + })); - test('onWillRename - make changes (dirty file)', function () { + test('onWillRename - make changes (dirty file)', withLogDisabled(function () { return testOnWillRename(true); - }); + })); async function testOnWillRename(withDirtyFile: boolean): Promise { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts index 2eb21a4c1..193ab54be 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; import { posix } from 'path'; +import { assertNoRpc } from '../utils'; suite('vscode API - workspace-fs', () => { @@ -15,6 +16,8 @@ suite('vscode API - workspace-fs', () => { root = vscode.workspace.workspaceFolders![0]!.uri; }); + teardown(assertNoRpc); + test('fs.stat', async function () { const stat = await vscode.workspace.fs.stat(root); assert.equal(stat.type, vscode.FileType.Directory); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts index c831e6d43..d6761c734 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { window, tasks, Disposable, TaskDefinition, Task, EventEmitter, CustomExecution, Pseudoterminal, TaskScope, commands, env, UIKind, ShellExecution, TaskExecution, Terminal, Event } from 'vscode'; +import { assertNoRpc } from '../utils'; // Disable tasks tests: // - Web https://github.com/microsoft/vscode/issues/90528 @@ -14,6 +15,7 @@ import { window, tasks, Disposable, TaskDefinition, Task, EventEmitter, CustomEx let disposables: Disposable[] = []; teardown(() => { + assertNoRpc(); disposables.forEach(d => d.dispose()); disposables.length = 0; }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index b3241e6d3..ace2479b3 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -5,14 +5,17 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; -import { createRandomFile, deleteFile, closeAllEditors, pathEquals, rndName, disposeAll, testFs, delay, withLogDisabled, revertAllDirty } from '../utils'; +import { createRandomFile, deleteFile, closeAllEditors, pathEquals, rndName, disposeAll, testFs, delay, withLogDisabled, revertAllDirty, assertNoRpc } from '../utils'; import { join, posix, basename } from 'path'; import * as fs from 'fs'; import { TestFS } from '../memfs'; suite('vscode API - workspace', () => { - teardown(closeAllEditors); + teardown(async function () { + assertNoRpc(); + await closeAllEditors(); + }); test('MarkdownString', function () { let md = new vscode.MarkdownString(); diff --git a/extensions/vscode-api-tests/src/utils.ts b/extensions/vscode-api-tests/src/utils.ts index 392d0be84..8d94c8ea1 100644 --- a/extensions/vscode-api-tests/src/utils.ts +++ b/extensions/vscode-api-tests/src/utils.ts @@ -17,7 +17,7 @@ vscode.workspace.registerFileSystemProvider(testFs.scheme, testFs, { isCaseSensi export async function createRandomFile(contents = '', dir: vscode.Uri | undefined = undefined, ext = ''): Promise { let fakeFile: vscode.Uri; if (dir) { - assert.equal(dir.scheme, testFs.scheme); + assert.strictEqual(dir.scheme, testFs.scheme); fakeFile = dir.with({ path: dir.path + '/' + rndName() + ext }); } else { fakeFile = vscode.Uri.parse(`${testFs.scheme}:/${rndName() + ext}`); @@ -48,6 +48,10 @@ export function closeAllEditors(): Thenable { return vscode.commands.executeCommand('workbench.action.closeAllEditors'); } +export function saveAllEditors(): Thenable { + return vscode.commands.executeCommand('workbench.action.files.saveAll'); +} + export async function revertAllDirty(): Promise { return vscode.commands.executeCommand('_workbench.revertAllDirty'); } @@ -72,3 +76,64 @@ export function withLogDisabled(runnable: () => Promise): () => Promise) { + if (!obj) { + return; + } + if (typeof obj !== 'object' && typeof obj !== 'function') { + return; + } + if (seen.has(obj)) { + return; + } + seen.add(obj); + + if (obj[symProtocol]) { + rpcPaths.push(`PROTOCOL via ${path}`); + } + if (obj[symProxy]) { + proxyPaths.push(`PROXY '${obj[symProxy]}' via ${path}`); + } + + for (const key in obj) { + walk(obj[key], `${path}.${String(key)}`, seen); + } + } + + try { + walk(entry[0], entry[1], new Set()); + } catch (err) { + assert.fail(err); + } + assert.strictEqual(rpcPaths.length, 0, rpcPaths.join('\n')); + assert.strictEqual(proxyPaths.length, 0, proxyPaths.join('\n')); // happens... +} + +export async function asPromise(event: vscode.Event, timeout = 5000): Promise { + return new Promise((resolve, reject) => { + + const handle = setTimeout(() => { + sub.dispose(); + reject(new Error('asPromise TIMEOUT reached')); + }, timeout); + + const sub = event(e => { + clearTimeout(handle); + sub.dispose(); + resolve(e); + }); + }); +} diff --git a/extensions/vscode-api-tests/testWorkspace/.vscode/settings.json b/extensions/vscode-api-tests/testWorkspace/.vscode/settings.json index e9f6fb821..31b22c565 100644 --- a/extensions/vscode-api-tests/testWorkspace/.vscode/settings.json +++ b/extensions/vscode-api-tests/testWorkspace/.vscode/settings.json @@ -4,5 +4,6 @@ }, "files.exclude": { "**/files-exclude/**": true - } -} \ No newline at end of file + }, + "editor.minimap.enabled": false // see https://github.com/microsoft/vscode/issues/115747 +} diff --git a/extensions/vscode-api-tests/testWorkspace2/.vscode/settings.json b/extensions/vscode-api-tests/testWorkspace2/.vscode/settings.json new file mode 100644 index 000000000..b196f4685 --- /dev/null +++ b/extensions/vscode-api-tests/testWorkspace2/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.minimap.enabled": false // see https://github.com/microsoft/vscode/issues/115747 +} diff --git a/extensions/vscode-colorize-tests/package.json b/extensions/vscode-colorize-tests/package.json index 30c728cfb..21cb61f65 100644 --- a/extensions/vscode-colorize-tests/package.json +++ b/extensions/vscode-colorize-tests/package.json @@ -52,5 +52,9 @@ "_watch": true } ] + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" } } diff --git a/extensions/vscode-colorize-tests/test/colorize-fixtures/test.jl b/extensions/vscode-colorize-tests/test/colorize-fixtures/test.jl new file mode 100644 index 000000000..3836db893 --- /dev/null +++ b/extensions/vscode-colorize-tests/test/colorize-fixtures/test.jl @@ -0,0 +1,26 @@ +# n-queens (nqueens) solver, for nsquaresx-by-nsquaresy board + +struct Queen + x::Int + y::Int +end +hitshorz(queena, queenb) = queena.x == queenb.x +hitsvert(queena, queenb) = queena.y == queenb.y +hitsdiag(queena, queenb) = abs(queena.x - queenb.x) == abs(queena.y - queenb.y) +hitshvd(qa, qb) = hitshorz(qa, qb) || hitsvert(qa, qb) || hitsdiag(qa, qb) +hitsany(testqueen, queens) = any(q -> hitshvd(testqueen, q), queens) + +function trysolve(nsquaresx, nsquaresy, nqueens, presqueens = ()) + nqueens == 0 && return presqueens + for xsquare in 1:nsquaresx + for ysquare in 1:nsquaresy + testqueen = Queen(xsquare, ysquare) + if !hitsany(testqueen, presqueens) + tryqueens = (presqueens..., testqueen) + maybesol = trysolve(nsquaresx, nsquaresy, nqueens - 1, tryqueens) + maybesol !== nothing && return maybesol + end + end + end + return nothing +end diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_jl.json b/extensions/vscode-colorize-tests/test/colorize-results/test_jl.json new file mode 100644 index 000000000..74646b2c7 --- /dev/null +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_jl.json @@ -0,0 +1,2543 @@ +[ + { + "c": "#", + "t": "source.julia comment.line.number-sign.julia punctuation.definition.comment.julia", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": " n-queens (nqueens) solver, for nsquaresx-by-nsquaresy board", + "t": "source.julia comment.line.number-sign.julia", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": "struct", + "t": "source.julia keyword.other.julia", + "r": { + "dark_plus": "keyword: #569CD6", + "light_plus": "keyword: #0000FF", + "dark_vs": "keyword: #569CD6", + "light_vs": "keyword: #0000FF", + "hc_black": "keyword: #569CD6" + } + }, + { + "c": " Queen", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " x", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "::", + "t": "source.julia keyword.operator.relation.types.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "Int", + "t": "source.julia support.type.julia", + "r": { + "dark_plus": "support.type: #4EC9B0", + "light_plus": "support.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.type: #4EC9B0" + } + }, + { + "c": " y", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "::", + "t": "source.julia keyword.operator.relation.types.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "Int", + "t": "source.julia support.type.julia", + "r": { + "dark_plus": "support.type: #4EC9B0", + "light_plus": "support.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.type: #4EC9B0" + } + }, + { + "c": "end", + "t": "source.julia keyword.control.end.julia", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "hitshorz", + "t": "source.julia entity.name.function.julia", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "queena", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " queenb", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.julia keyword.operator.update.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " queena", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.julia keyword.operator.dots.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "x ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "==", + "t": "source.julia keyword.operator.relation.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " queenb", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.julia keyword.operator.dots.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "x", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "hitsvert", + "t": "source.julia entity.name.function.julia", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "queena", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " queenb", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.julia keyword.operator.update.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " queena", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.julia keyword.operator.dots.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "y ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "==", + "t": "source.julia keyword.operator.relation.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " queenb", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.julia keyword.operator.dots.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "y", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "hitsdiag", + "t": "source.julia entity.name.function.julia", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "queena", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " queenb", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.julia keyword.operator.update.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "abs", + "t": "source.julia support.function.julia", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "queena", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.julia keyword.operator.dots.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "x ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "-", + "t": "source.julia keyword.operator.arithmetic.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " queenb", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.julia keyword.operator.dots.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "x", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "==", + "t": "source.julia keyword.operator.relation.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "abs", + "t": "source.julia support.function.julia", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "queena", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.julia keyword.operator.dots.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "y ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "-", + "t": "source.julia keyword.operator.arithmetic.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " queenb", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.julia keyword.operator.dots.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "y", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "hitshvd", + "t": "source.julia entity.name.function.julia", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "qa", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " qb", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.julia keyword.operator.update.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "hitshorz", + "t": "source.julia support.function.julia", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "qa", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " qb", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "||", + "t": "source.julia keyword.operator.boolean.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "hitsvert", + "t": "source.julia support.function.julia", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "qa", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " qb", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "||", + "t": "source.julia keyword.operator.boolean.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "hitsdiag", + "t": "source.julia support.function.julia", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "qa", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " qb", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "hitsany", + "t": "source.julia entity.name.function.julia", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "testqueen", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " queens", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.julia keyword.operator.update.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "any", + "t": "source.julia support.function.julia", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "q ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "->", + "t": "source.julia keyword.operator.arrow.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "hitshvd", + "t": "source.julia support.function.julia", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "testqueen", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " q", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "),", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " queens", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "function", + "t": "source.julia keyword.other.julia", + "r": { + "dark_plus": "keyword: #569CD6", + "light_plus": "keyword: #0000FF", + "dark_vs": "keyword: #569CD6", + "light_vs": "keyword: #0000FF", + "hc_black": "keyword: #569CD6" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "trysolve", + "t": "source.julia entity.name.function.julia", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "nsquaresx", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " nsquaresy", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " nqueens", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " presqueens ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.julia keyword.operator.update.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "())", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " nqueens ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "==", + "t": "source.julia keyword.operator.relation.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "0", + "t": "source.julia constant.numeric.julia", + "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" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "&&", + "t": "source.julia keyword.operator.boolean.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "return", + "t": "source.julia keyword.control.julia", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " presqueens", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "for", + "t": "source.julia keyword.control.julia", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " xsquare ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "in", + "t": "source.julia keyword.operator.relation.in.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "1", + "t": "source.julia constant.numeric.julia", + "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" + } + }, + { + "c": ":", + "t": "source.julia keyword.operator.range.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "nsquaresx", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "for", + "t": "source.julia keyword.control.julia", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ysquare ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "in", + "t": "source.julia keyword.operator.relation.in.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "1", + "t": "source.julia constant.numeric.julia", + "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" + } + }, + { + "c": ":", + "t": "source.julia keyword.operator.range.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "nsquaresy", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " testqueen ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.julia keyword.operator.update.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "Queen", + "t": "source.julia support.function.julia", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "xsquare", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ysquare", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "if", + "t": "source.julia keyword.control.julia", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "!", + "t": "source.julia keyword.operator.boolean.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "hitsany", + "t": "source.julia support.function.julia", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "testqueen", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " presqueens", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " tryqueens ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.julia keyword.operator.update.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "(", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "presqueens", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "...", + "t": "source.julia keyword.operator.dots.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": ",", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " testqueen", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " maybesol ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.julia keyword.operator.update.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "trysolve", + "t": "source.julia support.function.julia", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "nsquaresx", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " nsquaresy", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " nqueens ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "-", + "t": "source.julia keyword.operator.arithmetic.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "1", + "t": "source.julia constant.numeric.julia", + "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" + } + }, + { + "c": ",", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " tryqueens", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.julia meta.bracket.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " maybesol ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "!==", + "t": "source.julia keyword.operator.relation.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "nothing", + "t": "source.julia constant.language.julia", + "r": { + "dark_plus": "constant.language: #569CD6", + "light_plus": "constant.language: #0000FF", + "dark_vs": "constant.language: #569CD6", + "light_vs": "constant.language: #0000FF", + "hc_black": "constant.language: #569CD6" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "&&", + "t": "source.julia keyword.operator.boolean.julia", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "return", + "t": "source.julia keyword.control.julia", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " maybesol", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "end", + "t": "source.julia keyword.control.end.julia", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "end", + "t": "source.julia keyword.control.end.julia", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "end", + "t": "source.julia keyword.control.end.julia", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "return", + "t": "source.julia keyword.control.julia", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.julia", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "nothing", + "t": "source.julia constant.language.julia", + "r": { + "dark_plus": "constant.language: #569CD6", + "light_plus": "constant.language: #0000FF", + "dark_vs": "constant.language: #569CD6", + "light_vs": "constant.language: #0000FF", + "hc_black": "constant.language: #569CD6" + } + }, + { + "c": "end", + "t": "source.julia keyword.control.end.julia", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + } +] \ No newline at end of file diff --git a/extensions/vscode-custom-editor-tests/package.json b/extensions/vscode-custom-editor-tests/package.json index 481b8db61..8feb93bad 100644 --- a/extensions/vscode-custom-editor-tests/package.json +++ b/extensions/vscode-custom-editor-tests/package.json @@ -36,5 +36,9 @@ ] } ] + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" } } diff --git a/extensions/vscode-notebook-tests/package.json b/extensions/vscode-notebook-tests/package.json index 85f01c009..78164843d 100644 --- a/extensions/vscode-notebook-tests/package.json +++ b/extensions/vscode-notebook-tests/package.json @@ -8,7 +8,7 @@ "activationEvents": [ "*" ], - "main": "./out/notebookTestMain", + "main": "./out/extension", "enableProposedApi": true, "engines": { "vscode": "^1.25.0" @@ -34,16 +34,6 @@ } ], "notebookProvider": [ - { - "viewType": "notebookCoreTest", - "displayName": "Notebook Core Test", - "selector": [ - { - "filenamePattern": "*.vsctestnb", - "excludeFileNamePattern": "" - } - ] - }, { "viewType": "notebookSmokeTest", "displayName": "Notebook Smoke Test", @@ -80,5 +70,9 @@ "url": "vscode://schemas/notebook/cellmetadata" } ] + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" } } diff --git a/extensions/vscode-notebook-tests/src/notebookSmokeTestMain.ts b/extensions/vscode-notebook-tests/src/extension.ts similarity index 77% rename from extensions/vscode-notebook-tests/src/notebookSmokeTestMain.ts rename to extensions/vscode-notebook-tests/src/extension.ts index 10ab61306..552314115 100644 --- a/extensions/vscode-notebook-tests/src/notebookSmokeTestMain.ts +++ b/extensions/vscode-notebook-tests/src/extension.ts @@ -11,7 +11,7 @@ function wait(ms: number): Promise { return new Promise(r => setTimeout(r, ms)); } -export function smokeTestActivate(context: vscode.ExtensionContext): any { +export function activate(context: vscode.ExtensionContext): any { context.subscriptions.push(vscode.commands.registerCommand('vscode-notebook-tests.createNewNotebook', async () => { const workspacePath = vscode.workspace.workspaceFolders![0].uri.fsPath; const notebookPath = path.join(workspacePath, 'test.smoke-nb'); @@ -21,29 +21,23 @@ export function smokeTestActivate(context: vscode.ExtensionContext): any { })); context.subscriptions.push(vscode.notebook.registerNotebookContentProvider('notebookSmokeTest', { - onDidChangeNotebook: new vscode.EventEmitter().event, openNotebook: async (_resource: vscode.Uri) => { const dto: vscode.NotebookData = { - languages: ['typescript'], - metadata: {}, + metadata: new vscode.NotebookDocumentMetadata(), cells: [ { source: 'code()', language: 'typescript', - cellKind: vscode.CellKind.Code, + cellKind: vscode.NotebookCellKind.Code, outputs: [], - metadata: { - custom: { testCellMetadata: 123 } - } + metadata: new vscode.NotebookCellMetadata().with({ custom: { testCellMetadata: 123 } }) }, { source: 'Markdown Cell', language: 'markdown', - cellKind: vscode.CellKind.Markdown, + cellKind: vscode.NotebookCellKind.Markdown, outputs: [], - metadata: { - custom: { testCellMetadata: 123 } - } + metadata: new vscode.NotebookCellMetadata().with({ custom: { testCellMetadata: 123 } }) } ] }; @@ -71,14 +65,14 @@ export function smokeTestActivate(context: vscode.ExtensionContext): any { label: 'notebookSmokeTest', isPreferred: true, executeAllCells: async (_document: vscode.NotebookDocument) => { + const edit = new vscode.WorkspaceEdit(); for (let i = 0; i < _document.cells.length; i++) { - _document.cells[i].outputs = [{ - outputKind: vscode.CellOutputKind.Rich, - data: { - 'text/html': ['test output'] - } - }]; + edit.replaceNotebookCellOutput(_document.uri, i, [new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('text/html', ['test output'], undefined) + ])]); } + + await vscode.workspace.applyEdit(edit); }, cancelAllCellsExecution: async () => { }, executeCell: async (_document: vscode.NotebookDocument, _cell: vscode.NotebookCell | undefined) => { @@ -86,12 +80,11 @@ export function smokeTestActivate(context: vscode.ExtensionContext): any { _cell = _document.cells[0]; } - _cell.outputs = [{ - outputKind: vscode.CellOutputKind.Rich, - data: { - 'text/html': ['test output'] - } - }]; + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCellOutput(_document.uri, _cell.index, [new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('text/html', ['test output'], undefined) + ])]); + await vscode.workspace.applyEdit(edit); return; }, cancelCellExecution: async () => { } diff --git a/extensions/vscode-notebook-tests/src/notebook.test.ts b/extensions/vscode-notebook-tests/src/notebook.test.ts deleted file mode 100644 index bd36c5969..000000000 --- a/extensions/vscode-notebook-tests/src/notebook.test.ts +++ /dev/null @@ -1,1503 +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 'mocha'; -import * as assert from 'assert'; -import * as vscode from 'vscode'; -import { createRandomFile } from './utils'; - -export function timeoutAsync(n: number): Promise { - return new Promise(resolve => { - setTimeout(() => { - resolve(); - }, n); - }); -} - -export function once(event: vscode.Event): vscode.Event { - return (listener: any, thisArgs = null, disposables?: any) => { - // we need this, in case the event fires during the listener call - let didFire = false; - let result: vscode.Disposable; - 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; - }; -} - -async function getEventOncePromise(event: vscode.Event): Promise { - return new Promise((resolve, _reject) => { - once(event)((result: T) => resolve(result)); - }); -} - -// Since `workbench.action.splitEditor` command does await properly -// Notebook editor/document events are not guaranteed to be sent to the ext host when promise resolves -// The workaround here is waiting for the first visible notebook editor change event. -async function splitEditor() { - const once = getEventOncePromise(vscode.window.onDidChangeVisibleNotebookEditors); - await vscode.commands.executeCommand('workbench.action.splitEditor'); - await once; -} - -async function saveFileAndCloseAll(resource: vscode.Uri) { - const documentClosed = new Promise((resolve, _reject) => { - const d = vscode.notebook.onDidCloseNotebookDocument(e => { - if (e.uri.toString() === resource.toString()) { - d.dispose(); - resolve(); - } - }); - }); - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - await documentClosed; -} - -async function saveAllFilesAndCloseAll(resource: vscode.Uri | undefined) { - const documentClosed = new Promise((resolve, _reject) => { - if (!resource) { - return resolve(); - } - const d = vscode.notebook.onDidCloseNotebookDocument(e => { - if (e.uri.toString() === resource.toString()) { - d.dispose(); - resolve(); - } - }); - }); - await vscode.commands.executeCommand('workbench.action.files.saveAll'); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - await documentClosed; -} - -function assertInitalState() { - // no-op unless we figure out why some documents are opened after the editor is closed - - // assert.equal(vscode.window.activeNotebookEditor, undefined); - // assert.equal(vscode.notebook.notebookDocuments.length, 0); - // assert.equal(vscode.notebook.visibleNotebookEditors.length, 0); -} - -suite('Notebook API tests', () => { - // test.only('crash', async function () { - // for (let i = 0; i < 200; i++) { - // let resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); - // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - // await vscode.commands.executeCommand('workbench.action.revertAndCloseActiveEditor'); - - // resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './empty.vsctestnb')); - // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - // await vscode.commands.executeCommand('workbench.action.revertAndCloseActiveEditor'); - // } - // }); - - // test.only('crash', async function () { - // for (let i = 0; i < 200; i++) { - // let resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); - // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - // await vscode.commands.executeCommand('workbench.action.files.save'); - // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - // resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './empty.vsctestnb')); - // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - // await vscode.commands.executeCommand('workbench.action.files.save'); - // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - // } - // }); - - test('document open/close event', async function () { - assertInitalState(); - - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - const firstDocumentOpen = getEventOncePromise(vscode.notebook.onDidOpenNotebookDocument); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - await firstDocumentOpen; - - const firstDocumentClose = getEventOncePromise(vscode.notebook.onDidCloseNotebookDocument); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - await firstDocumentClose; - }); - - test('notebook open/close, all cell-documents are ready', async function () { - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - - const p = getEventOncePromise(vscode.notebook.onDidOpenNotebookDocument).then(notebook => { - for (let cell of notebook.cells) { - const doc = vscode.workspace.textDocuments.find(doc => doc.uri.toString() === cell.uri.toString()); - assert.ok(doc); - assert.strictEqual(doc === cell.document, true); - assert.strictEqual(doc?.languageId, cell.language); - assert.strictEqual(doc?.isDirty, false); - assert.strictEqual(doc?.isClosed, false); - } - }); - - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - await p; - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - }); - - test('notebook open/close, notebook ready when cell-document open event is fired', async function () { - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - let didHappen = false; - const p = getEventOncePromise(vscode.workspace.onDidOpenTextDocument).then(doc => { - if (doc.uri.scheme !== 'vscode-notebook-cell') { - return; - } - const notebook = vscode.notebook.notebookDocuments.find(notebook => { - const cell = notebook.cells.find(cell => cell.document === doc); - return Boolean(cell); - }); - assert.ok(notebook, `notebook for cell ${doc.uri} NOT found`); - didHappen = true; - }); - - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - await p; - assert.strictEqual(didHappen, true); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - }); - - test('shared document in notebook editors', async function () { - assertInitalState(); - - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - let counter = 0; - const disposables: vscode.Disposable[] = []; - disposables.push(vscode.notebook.onDidOpenNotebookDocument(() => { - counter++; - })); - disposables.push(vscode.notebook.onDidCloseNotebookDocument(() => { - counter--; - })); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(counter, 1); - - await splitEditor(); - assert.equal(counter, 1); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - assert.equal(counter, 0); - - disposables.forEach(d => d.dispose()); - }); - - test('editor open/close event', async function () { - assertInitalState(); - - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - const firstEditorOpen = getEventOncePromise(vscode.window.onDidChangeVisibleNotebookEditors); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - await firstEditorOpen; - - const firstEditorClose = getEventOncePromise(vscode.window.onDidChangeVisibleNotebookEditors); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - await firstEditorClose; - }); - - test('editor open/close event 2', async function () { - assertInitalState(); - - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - let count = 0; - const disposables: vscode.Disposable[] = []; - disposables.push(vscode.window.onDidChangeVisibleNotebookEditors(() => { - count = vscode.window.visibleNotebookEditors.length; - })); - - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(count, 1); - - await splitEditor(); - assert.equal(count, 2); - - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - assert.equal(count, 0); - }); - - test('editor editing event 2', async function () { - assertInitalState(); - - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - const cellsChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - const cellChangeEventRet = await cellsChangeEvent; - assert.equal(cellChangeEventRet.document, vscode.window.activeNotebookEditor?.document); - assert.equal(cellChangeEventRet.changes.length, 1); - assert.deepEqual(cellChangeEventRet.changes[0], { - start: 1, - deletedCount: 0, - deletedItems: [], - items: [ - vscode.window.activeNotebookEditor!.document.cells[1] - ] - }); - - const secondCell = vscode.window.activeNotebookEditor!.document.cells[1]; - - const moveCellEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); - await vscode.commands.executeCommand('notebook.cell.moveUp'); - const moveCellEventRet = await moveCellEvent; - assert.deepEqual(moveCellEventRet, { - document: vscode.window.activeNotebookEditor!.document, - changes: [ - { - start: 1, - deletedCount: 1, - deletedItems: [secondCell], - items: [] - }, - { - start: 0, - deletedCount: 0, - deletedItems: [], - items: [vscode.window.activeNotebookEditor?.document.cells[0]] - } - ] - }); - - const cellOutputChange = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); - await vscode.commands.executeCommand('notebook.cell.execute'); - const cellOutputsAddedRet = await cellOutputChange; - assert.deepEqual(cellOutputsAddedRet, { - document: vscode.window.activeNotebookEditor!.document, - cells: [vscode.window.activeNotebookEditor!.document.cells[0]] - }); - assert.equal(cellOutputsAddedRet.cells[0].outputs.length, 1); - - const cellOutputClear = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); - await vscode.commands.executeCommand('notebook.cell.clearOutputs'); - const cellOutputsCleardRet = await cellOutputClear; - assert.deepEqual(cellOutputsCleardRet, { - document: vscode.window.activeNotebookEditor!.document, - cells: [vscode.window.activeNotebookEditor!.document.cells[0]] - }); - assert.equal(cellOutputsAddedRet.cells[0].outputs.length, 0); - - // const cellChangeLanguage = getEventOncePromise(vscode.notebook.onDidChangeCellLanguage); - // await vscode.commands.executeCommand('notebook.cell.changeToMarkdown'); - // const cellChangeLanguageRet = await cellChangeLanguage; - // assert.deepEqual(cellChangeLanguageRet, { - // document: vscode.window.activeNotebookEditor!.document, - // cells: vscode.window.activeNotebookEditor!.document.cells[0], - // language: 'markdown' - // }); - - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - }); - - test('editor move cell event', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - await vscode.commands.executeCommand('notebook.focusTop'); - - const activeCell = vscode.window.activeNotebookEditor!.selection; - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); - const moveChange = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); - await vscode.commands.executeCommand('notebook.cell.moveDown'); - const ret = await moveChange; - assert.deepEqual(ret, { - document: vscode.window.activeNotebookEditor?.document, - changes: [ - { - start: 0, - deletedCount: 1, - deletedItems: [activeCell], - items: [] - }, - { - start: 1, - deletedCount: 0, - deletedItems: [], - items: [activeCell] - } - ] - }); - - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - const firstEditor = vscode.window.activeNotebookEditor; - assert.equal(firstEditor?.document.cells.length, 1); - - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - }); - - test('notebook editor active/visible', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - const firstEditor = vscode.window.activeNotebookEditor; - assert.strictEqual(firstEditor && vscode.window.visibleNotebookEditors.indexOf(firstEditor) >= 0, true); - - await splitEditor(); - const secondEditor = vscode.window.activeNotebookEditor; - assert.strictEqual(secondEditor && vscode.window.visibleNotebookEditors.indexOf(secondEditor) >= 0, true); - assert.notStrictEqual(firstEditor, secondEditor); - assert.strictEqual(firstEditor && vscode.window.visibleNotebookEditors.indexOf(firstEditor) >= 0, true); - assert.equal(vscode.window.visibleNotebookEditors.length, 2); - - const untitledEditorChange = getEventOncePromise(vscode.window.onDidChangeActiveNotebookEditor); - await vscode.commands.executeCommand('workbench.action.files.newUntitledFile'); - await untitledEditorChange; - assert.strictEqual(firstEditor && vscode.window.visibleNotebookEditors.indexOf(firstEditor) >= 0, true); - assert.notStrictEqual(firstEditor, vscode.window.activeNotebookEditor); - assert.strictEqual(secondEditor && vscode.window.visibleNotebookEditors.indexOf(secondEditor) < 0, true); - assert.notStrictEqual(secondEditor, vscode.window.activeNotebookEditor); - assert.equal(vscode.window.visibleNotebookEditors.length, 1); - - const activeEditorClose = getEventOncePromise(vscode.window.onDidChangeActiveNotebookEditor); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - await activeEditorClose; - assert.strictEqual(secondEditor, vscode.window.activeNotebookEditor); - assert.equal(vscode.window.visibleNotebookEditors.length, 2); - assert.strictEqual(secondEditor && vscode.window.visibleNotebookEditors.indexOf(secondEditor) >= 0, true); - - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - }); - - test('notebook active editor change', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - const firstEditorOpen = getEventOncePromise(vscode.window.onDidChangeActiveNotebookEditor); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - await firstEditorOpen; - - const firstEditorDeactivate = getEventOncePromise(vscode.window.onDidChangeActiveNotebookEditor); - await vscode.commands.executeCommand('workbench.action.splitEditor'); - await firstEditorDeactivate; - - await saveFileAndCloseAll(resource); - }); - - test('edit API (replaceCells)', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - const cellsChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); - await vscode.window.activeNotebookEditor!.edit(editBuilder => { - editBuilder.replaceCells(1, 0, [{ cellKind: vscode.CellKind.Code, language: 'javascript', source: 'test 2', outputs: [], metadata: undefined }]); - }); - - const cellChangeEventRet = await cellsChangeEvent; - assert.strictEqual(cellChangeEventRet.document === vscode.window.activeNotebookEditor?.document, true); - assert.strictEqual(cellChangeEventRet.document.isDirty, true); - assert.strictEqual(cellChangeEventRet.changes.length, 1); - assert.strictEqual(cellChangeEventRet.changes[0].start, 1); - assert.strictEqual(cellChangeEventRet.changes[0].deletedCount, 0); - assert.strictEqual(cellChangeEventRet.changes[0].items[0] === vscode.window.activeNotebookEditor!.document.cells[1], true); - - await saveAllFilesAndCloseAll(resource); - }); - - test('edit API (replaceOutput, USE NotebookCellOutput-type)', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await vscode.window.activeNotebookEditor!.edit(editBuilder => { - editBuilder.replaceCellOutput(0, [new vscode.NotebookCellOutput([ - new vscode.NotebookCellOutputItem('application/foo', 'bar'), - new vscode.NotebookCellOutputItem('application/json', { data: true }, { metadata: true }), - ])]); - }); - - const document = vscode.window.activeNotebookEditor?.document!; - assert.strictEqual(document.isDirty, true); - assert.strictEqual(document.cells.length, 1); - assert.strictEqual(document.cells[0].outputs.length, 1); - - // consuming is OLD api (for now) - const [output] = document.cells[0].outputs; - - assert.strictEqual(output.outputKind, vscode.CellOutputKind.Rich); - assert.strictEqual((output).data['application/foo'], 'bar'); - assert.deepStrictEqual((output).data['application/json'], { data: true }); - assert.deepStrictEqual((output).metadata, { custom: { 'application/json': { metadata: true } } }); - - await saveAllFilesAndCloseAll(undefined); - }); - - test('edit API (replaceOutput)', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await vscode.window.activeNotebookEditor!.edit(editBuilder => { - editBuilder.replaceCellOutput(0, [{ outputKind: vscode.CellOutputKind.Rich, data: { foo: 'bar' } }]); - }); - - const document = vscode.window.activeNotebookEditor?.document!; - assert.strictEqual(document.isDirty, true); - assert.strictEqual(document.cells.length, 1); - assert.strictEqual(document.cells[0].outputs.length, 1); - assert.strictEqual(document.cells[0].outputs[0].outputKind, vscode.CellOutputKind.Rich); - - await saveAllFilesAndCloseAll(undefined); - }); - - test('edit API (replaceOutput, event)', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - const outputChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); - await vscode.window.activeNotebookEditor!.edit(editBuilder => { - editBuilder.replaceCellOutput(0, [{ outputKind: vscode.CellOutputKind.Rich, data: { foo: 'bar' } }]); - }); - - const value = await outputChangeEvent; - assert.strictEqual(value.document === vscode.window.activeNotebookEditor?.document, true); - assert.strictEqual(value.document.isDirty, true); - assert.strictEqual(value.cells.length, 1); - assert.strictEqual(value.cells[0].outputs.length, 1); - assert.strictEqual(value.cells[0].outputs[0].outputKind, vscode.CellOutputKind.Rich); - - await saveAllFilesAndCloseAll(undefined); - }); - - test('edit API (replaceMetadata)', async function () { - - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await vscode.window.activeNotebookEditor!.edit(editBuilder => { - editBuilder.replaceCellMetadata(0, { inputCollapsed: true, executionOrder: 17 }); - }); - - const document = vscode.window.activeNotebookEditor?.document!; - assert.strictEqual(document.cells.length, 1); - assert.strictEqual(document.cells[0].metadata.executionOrder, 17); - assert.strictEqual(document.cells[0].metadata.inputCollapsed, true); - - assert.strictEqual(document.isDirty, true); - await saveFileAndCloseAll(resource); - }); - - test('edit API (replaceMetadata, event)', async function () { - - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - const event = getEventOncePromise(vscode.notebook.onDidChangeCellMetadata); - - await vscode.window.activeNotebookEditor!.edit(editBuilder => { - editBuilder.replaceCellMetadata(0, { inputCollapsed: true, executionOrder: 17 }); - }); - - const data = await event; - assert.strictEqual(data.document, vscode.window.activeNotebookEditor?.document); - assert.strictEqual(data.cell.metadata.executionOrder, 17); - assert.strictEqual(data.cell.metadata.inputCollapsed, true); - - assert.strictEqual(data.document.isDirty, true); - await saveFileAndCloseAll(resource); - }); - - test('workspace edit API (replaceCells)', async function () { - - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - const { document } = vscode.window.activeNotebookEditor!; - assert.strictEqual(document.cells.length, 1); - - // inserting two new cells - { - const edit = new vscode.WorkspaceEdit(); - edit.replaceNotebookCells(document.uri, 0, 0, [{ - cellKind: vscode.CellKind.Markdown, - language: 'markdown', - metadata: undefined, - outputs: [], - source: 'new_markdown' - }, { - cellKind: vscode.CellKind.Code, - language: 'fooLang', - metadata: undefined, - outputs: [], - source: 'new_code' - }]); - - const success = await vscode.workspace.applyEdit(edit); - assert.strictEqual(success, true); - } - - assert.strictEqual(document.cells.length, 3); - assert.strictEqual(document.cells[0].document.getText(), 'new_markdown'); - assert.strictEqual(document.cells[1].document.getText(), 'new_code'); - - // deleting cell 1 and 3 - { - const edit = new vscode.WorkspaceEdit(); - edit.replaceNotebookCells(document.uri, 0, 1, []); - edit.replaceNotebookCells(document.uri, 2, 3, []); - const success = await vscode.workspace.applyEdit(edit); - assert.strictEqual(success, true); - } - - assert.strictEqual(document.cells.length, 1); - assert.strictEqual(document.cells[0].document.getText(), 'new_code'); - - // replacing all cells - { - const edit = new vscode.WorkspaceEdit(); - edit.replaceNotebookCells(document.uri, 0, 1, [{ - cellKind: vscode.CellKind.Markdown, - language: 'markdown', - metadata: undefined, - outputs: [], - source: 'new2_markdown' - }, { - cellKind: vscode.CellKind.Code, - language: 'fooLang', - metadata: undefined, - outputs: [], - source: 'new2_code' - }]); - const success = await vscode.workspace.applyEdit(edit); - assert.strictEqual(success, true); - } - assert.strictEqual(document.cells.length, 2); - assert.strictEqual(document.cells[0].document.getText(), 'new2_markdown'); - assert.strictEqual(document.cells[1].document.getText(), 'new2_code'); - - // remove all cells - { - const edit = new vscode.WorkspaceEdit(); - edit.replaceNotebookCells(document.uri, 0, document.cells.length, []); - const success = await vscode.workspace.applyEdit(edit); - assert.strictEqual(success, true); - } - assert.strictEqual(document.cells.length, 0); - - await saveFileAndCloseAll(resource); - }); - - test('workspace edit API (replaceCells, event)', async function () { - - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - const { document } = vscode.window.activeNotebookEditor!; - assert.strictEqual(document.cells.length, 1); - - const edit = new vscode.WorkspaceEdit(); - edit.replaceNotebookCells(document.uri, 0, 0, [{ - cellKind: vscode.CellKind.Markdown, - language: 'markdown', - metadata: undefined, - outputs: [], - source: 'new_markdown' - }, { - cellKind: vscode.CellKind.Code, - language: 'fooLang', - metadata: undefined, - outputs: [], - source: 'new_code' - }]); - - const event = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); - - const success = await vscode.workspace.applyEdit(edit); - assert.strictEqual(success, true); - - const data = await event; - - // check document - assert.strictEqual(document.cells.length, 3); - assert.strictEqual(document.cells[0].document.getText(), 'new_markdown'); - assert.strictEqual(document.cells[1].document.getText(), 'new_code'); - - // check event data - assert.strictEqual(data.document === document, true); - assert.strictEqual(data.changes.length, 1); - assert.strictEqual(data.changes[0].deletedCount, 0); - assert.strictEqual(data.changes[0].deletedItems.length, 0); - assert.strictEqual(data.changes[0].items.length, 2); - assert.strictEqual(data.changes[0].items[0], document.cells[0]); - assert.strictEqual(data.changes[0].items[1], document.cells[1]); - await saveFileAndCloseAll(resource); - }); - - test('edit API batch edits', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - const cellsChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); - const cellMetadataChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeCellMetadata); - const version = vscode.window.activeNotebookEditor!.document.version; - await vscode.window.activeNotebookEditor!.edit(editBuilder => { - editBuilder.replaceCells(1, 0, [{ cellKind: vscode.CellKind.Code, language: 'javascript', source: 'test 2', outputs: [], metadata: undefined }]); - editBuilder.replaceCellMetadata(0, { runnable: false }); - }); - - await cellsChangeEvent; - await cellMetadataChangeEvent; - assert.strictEqual(version + 1, vscode.window.activeNotebookEditor!.document.version); - await saveAllFilesAndCloseAll(resource); - }); - - test('edit API batch edits undo/redo', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - const cellsChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); - const cellMetadataChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeCellMetadata); - const version = vscode.window.activeNotebookEditor!.document.version; - await vscode.window.activeNotebookEditor!.edit(editBuilder => { - editBuilder.replaceCells(1, 0, [{ cellKind: vscode.CellKind.Code, language: 'javascript', source: 'test 2', outputs: [], metadata: undefined }]); - editBuilder.replaceCellMetadata(0, { runnable: false }); - }); - - await cellsChangeEvent; - await cellMetadataChangeEvent; - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 2); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0]?.metadata?.runnable, false); - assert.strictEqual(version + 1, vscode.window.activeNotebookEditor!.document.version); - - await vscode.commands.executeCommand('undo'); - assert.strictEqual(version + 2, vscode.window.activeNotebookEditor!.document.version); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0]?.metadata?.runnable, undefined); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 1); - - await saveAllFilesAndCloseAll(resource); - }); - - test('initialzation should not emit cell change events.', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - - let count = 0; - const disposables: vscode.Disposable[] = []; - disposables.push(vscode.notebook.onDidChangeNotebookCells(() => { - count++; - })); - - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(count, 0); - - disposables.forEach(d => d.dispose()); - - await saveFileAndCloseAll(resource); - }); -}); - -suite('notebook workflow', () => { - test('notebook open', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); - - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); - - await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - const activeCell = vscode.window.activeNotebookEditor!.selection; - assert.notEqual(vscode.window.activeNotebookEditor!.selection, undefined); - assert.equal(activeCell!.document.getText(), ''); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.length, 3); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); - - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - }); - - test('notebook cell actions', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); - - // ---- insert cell below and focus ---- // - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); - - // ---- insert cell above and focus ---- // - await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - let activeCell = vscode.window.activeNotebookEditor!.selection; - assert.notEqual(vscode.window.activeNotebookEditor!.selection, undefined); - assert.equal(activeCell!.document.getText(), ''); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.length, 3); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); - - // ---- focus bottom ---- // - await vscode.commands.executeCommand('notebook.focusBottom'); - activeCell = vscode.window.activeNotebookEditor!.selection; - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 2); - - // ---- focus top and then copy down ---- // - await vscode.commands.executeCommand('notebook.focusTop'); - activeCell = vscode.window.activeNotebookEditor!.selection; - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); - - await vscode.commands.executeCommand('notebook.cell.copyDown'); - activeCell = vscode.window.activeNotebookEditor!.selection; - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); - assert.equal(activeCell?.document.getText(), 'test'); - - await vscode.commands.executeCommand('notebook.cell.delete'); - activeCell = vscode.window.activeNotebookEditor!.selection; - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); - assert.equal(activeCell?.document.getText(), ''); - - // ---- focus top and then copy up ---- // - await vscode.commands.executeCommand('notebook.focusTop'); - await vscode.commands.executeCommand('notebook.cell.copyUp'); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.length, 4); - assert.equal(vscode.window.activeNotebookEditor!.document.cells[0].document.getText(), 'test'); - assert.equal(vscode.window.activeNotebookEditor!.document.cells[1].document.getText(), 'test'); - assert.equal(vscode.window.activeNotebookEditor!.document.cells[2].document.getText(), ''); - assert.equal(vscode.window.activeNotebookEditor!.document.cells[3].document.getText(), ''); - activeCell = vscode.window.activeNotebookEditor!.selection; - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); - - - // ---- move up and down ---- // - - await vscode.commands.executeCommand('notebook.cell.moveDown'); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(vscode.window.activeNotebookEditor!.selection!), 1, - `first move down, active cell ${vscode.window.activeNotebookEditor!.selection!.uri.toString()}, ${vscode.window.activeNotebookEditor!.selection!.document.getText()}`); - - // await vscode.commands.executeCommand('notebook.cell.moveDown'); - // activeCell = vscode.window.activeNotebookEditor!.selection; - - // assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 2, - // `second move down, active cell ${vscode.window.activeNotebookEditor!.selection!.uri.toString()}, ${vscode.window.activeNotebookEditor!.selection!.document.getText()}`); - // assert.equal(vscode.window.activeNotebookEditor!.document.cells[0].document.getText(), 'test'); - // assert.equal(vscode.window.activeNotebookEditor!.document.cells[1].document.getText(), ''); - // assert.equal(vscode.window.activeNotebookEditor!.document.cells[2].document.getText(), 'test'); - // assert.equal(vscode.window.activeNotebookEditor!.document.cells[3].document.getText(), ''); - - // ---- ---- // - - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - }); - - test('notebook join cells', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); - - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); - const edit = new vscode.WorkspaceEdit(); - edit.insert(vscode.window.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); - await vscode.workspace.applyEdit(edit); - - const cellsChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); - await vscode.commands.executeCommand('notebook.cell.joinAbove'); - await cellsChangeEvent; - - assert.deepEqual(vscode.window.activeNotebookEditor!.selection?.document.getText().split(/\r\n|\r|\n/), ['test', 'var abc = 0;']); - - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - }); - - test('move cells will not recreate cells in ExtHost', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - await vscode.commands.executeCommand('notebook.focusTop'); - - const activeCell = vscode.window.activeNotebookEditor!.selection; - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); - await vscode.commands.executeCommand('notebook.cell.moveDown'); - await vscode.commands.executeCommand('notebook.cell.moveDown'); - - const newActiveCell = vscode.window.activeNotebookEditor!.selection; - assert.deepEqual(activeCell, newActiveCell); - - await saveFileAndCloseAll(resource); - // TODO@rebornix, there are still some events order issue. - // assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(newActiveCell!), 2); - }); - - // test.only('document metadata is respected', async function () { - // const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - // assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - // const editor = vscode.window.activeNotebookEditor!; - - // assert.equal(editor.document.cells.length, 1); - // editor.document.metadata.editable = false; - // await editor.edit(builder => builder.delete(0)); - // assert.equal(editor.document.cells.length, 1, 'should not delete cell'); // Not editable, no effect - // await editor.edit(builder => builder.insert(0, 'test', 'python', vscode.CellKind.Code, [], undefined)); - // assert.equal(editor.document.cells.length, 1, 'should not insert cell'); // Not editable, no effect - - // editor.document.metadata.editable = true; - // await editor.edit(builder => builder.delete(0)); - // assert.equal(editor.document.cells.length, 0, 'should delete cell'); // Editable, it worked - // await editor.edit(builder => builder.insert(0, 'test', 'python', vscode.CellKind.Code, [], undefined)); - // assert.equal(editor.document.cells.length, 1, 'should insert cell'); // Editable, it worked - - // // await vscode.commands.executeCommand('workbench.action.files.save'); - // await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - // }); - - test('cell runnable metadata is respected', async () => { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - const editor = vscode.window.activeNotebookEditor!; - - await vscode.commands.executeCommand('notebook.focusTop'); - const cell = editor.document.cells[0]; - assert.equal(cell.outputs.length, 0); - - let metadataChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeCellMetadata); - cell.metadata.runnable = false; - await metadataChangeEvent; - - await vscode.commands.executeCommand('notebook.cell.execute'); - assert.equal(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work - - metadataChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeCellMetadata); - cell.metadata.runnable = true; - await metadataChangeEvent; - - await vscode.commands.executeCommand('notebook.cell.execute'); - assert.equal(cell.outputs.length, 1, 'should execute'); // runnable, it worked - - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - }); - - test('document runnable metadata is respected', async () => { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - const editor = vscode.window.activeNotebookEditor!; - - const cell = editor.document.cells[0]; - assert.equal(cell.outputs.length, 0); - let metadataChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookDocumentMetadata); - editor.document.metadata.runnable = false; - await metadataChangeEvent; - - await vscode.commands.executeCommand('notebook.execute'); - assert.equal(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work - - metadataChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookDocumentMetadata); - editor.document.metadata.runnable = true; - await metadataChangeEvent; - - await vscode.commands.executeCommand('notebook.execute'); - assert.equal(cell.outputs.length, 1, 'should execute'); // runnable, it worked - - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - }); - - test('cell execute command takes arguments', async () => { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - const editor = vscode.window.activeNotebookEditor!; - const cell = editor.document.cells[0]; - - await vscode.commands.executeCommand('notebook.execute'); - assert.equal(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work - - const metadataChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookDocumentMetadata); - editor.document.metadata.runnable = true; - await metadataChangeEvent; - - await vscode.commands.executeCommand('notebook.execute'); - assert.equal(cell.outputs.length, 1, 'should execute'); // runnable, it worked - - const clearChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); - await vscode.commands.executeCommand('notebook.cell.clearOutputs'); - await clearChangeEvent; - assert.equal(cell.outputs.length, 0, 'should clear'); - - const secondResource = await createRandomFile('', undefined, 'second', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); - await vscode.commands.executeCommand('notebook.cell.execute', { start: 0, end: 1 }, resource); - assert.equal(cell.outputs.length, 1, 'should execute'); // runnable, it worked - assert.equal(vscode.window.activeNotebookEditor?.document.uri.fsPath, secondResource.fsPath); - - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - }); - - test('document execute command takes arguments', async () => { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - const editor = vscode.window.activeNotebookEditor!; - const cell = editor.document.cells[0]; - - await vscode.commands.executeCommand('notebook.execute'); - assert.equal(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work - - const metadataChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookDocumentMetadata); - editor.document.metadata.runnable = true; - await metadataChangeEvent; - - await vscode.commands.executeCommand('notebook.execute'); - assert.equal(cell.outputs.length, 1, 'should execute'); // runnable, it worked - - const clearChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); - await vscode.commands.executeCommand('notebook.cell.clearOutputs'); - await clearChangeEvent; - assert.equal(cell.outputs.length, 0, 'should clear'); - - const secondResource = await createRandomFile('', undefined, 'second', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); - await vscode.commands.executeCommand('notebook.execute', resource); - assert.equal(cell.outputs.length, 1, 'should execute'); // runnable, it worked - assert.equal(vscode.window.activeNotebookEditor?.document.uri.fsPath, secondResource.fsPath); - - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - }); - - test('cell execute and select kernel', async () => { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - const editor = vscode.window.activeNotebookEditor!; - const cell = editor.document.cells[0]; - - const metadataChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookDocumentMetadata); - editor.document.metadata.runnable = true; - await metadataChangeEvent; - - await vscode.commands.executeCommand('notebook.cell.execute'); - assert.equal(cell.outputs.length, 1, 'should execute'); // runnable, it worked - assert.deepEqual((cell.outputs[0] as vscode.CellDisplayOutput).data, { - 'text/plain': [ - 'my output' - ] - }); - - await vscode.commands.executeCommand('notebook.selectKernel', { extension: 'vscode.vscode-notebook-tests', id: 'secondaryKernel' }) - await vscode.commands.executeCommand('notebook.cell.execute'); - assert.equal(cell.outputs.length, 1, 'should execute'); // runnable, it worked - assert.deepEqual((cell.outputs[0] as vscode.CellDisplayOutput).data, { - 'text/plain': [ - 'my second output' - ] - }); - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - }); -}); - -suite('notebook dirty state', () => { - test('notebook open', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); - - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); - - await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - const activeCell = vscode.window.activeNotebookEditor!.selection; - assert.notEqual(vscode.window.activeNotebookEditor!.selection, undefined); - assert.equal(activeCell!.document.getText(), ''); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.length, 3); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); - - const edit = new vscode.WorkspaceEdit(); - edit.insert(activeCell!.uri, new vscode.Position(0, 0), 'var abc = 0;'); - await vscode.workspace.applyEdit(edit); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true); - assert.equal(vscode.window.activeNotebookEditor?.selection !== undefined, true); - assert.deepEqual(vscode.window.activeNotebookEditor?.document.cells[1], vscode.window.activeNotebookEditor?.selection); - assert.equal(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); - - await saveFileAndCloseAll(resource); - }); -}); - -suite('notebook undo redo', () => { - test('notebook open', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); - - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); - - await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - const activeCell = vscode.window.activeNotebookEditor!.selection; - assert.notEqual(vscode.window.activeNotebookEditor!.selection, undefined); - assert.equal(activeCell!.document.getText(), ''); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.length, 3); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); - - - // modify the second cell, delete it - const edit = new vscode.WorkspaceEdit(); - edit.insert(vscode.window.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); - await vscode.workspace.applyEdit(edit); - await vscode.commands.executeCommand('notebook.cell.delete'); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.length, 2); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(vscode.window.activeNotebookEditor!.selection!), 1); - - - // undo should bring back the deleted cell, and revert to previous content and selection - await vscode.commands.executeCommand('undo'); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.length, 3); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(vscode.window.activeNotebookEditor!.selection!), 1); - assert.equal(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); - - // redo - // await vscode.commands.executeCommand('notebook.redo'); - // assert.equal(vscode.window.activeNotebookEditor!.document.cells.length, 2); - // assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(vscode.window.activeNotebookEditor!.selection!), 1); - // assert.equal(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'test'); - - await saveFileAndCloseAll(resource); - }); - - test.skip('execute and then undo redo', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - const cellsChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - const cellChangeEventRet = await cellsChangeEvent; - assert.equal(cellChangeEventRet.document, vscode.window.activeNotebookEditor?.document); - assert.equal(cellChangeEventRet.changes.length, 1); - assert.deepEqual(cellChangeEventRet.changes[0], { - start: 1, - deletedCount: 0, - deletedItems: [], - items: [ - vscode.window.activeNotebookEditor!.document.cells[1] - ] - }); - - const secondCell = vscode.window.activeNotebookEditor!.document.cells[1]; - - const moveCellEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); - await vscode.commands.executeCommand('notebook.cell.moveUp'); - const moveCellEventRet = await moveCellEvent; - assert.deepEqual(moveCellEventRet, { - document: vscode.window.activeNotebookEditor!.document, - changes: [ - { - start: 1, - deletedCount: 1, - deletedItems: [secondCell], - items: [] - }, - { - start: 0, - deletedCount: 0, - deletedItems: [], - items: [vscode.window.activeNotebookEditor?.document.cells[0]] - } - ] - }); - - const cellOutputChange = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); - await vscode.commands.executeCommand('notebook.cell.execute'); - const cellOutputsAddedRet = await cellOutputChange; - assert.deepEqual(cellOutputsAddedRet, { - document: vscode.window.activeNotebookEditor!.document, - cells: [vscode.window.activeNotebookEditor!.document.cells[0]] - }); - assert.equal(cellOutputsAddedRet.cells[0].outputs.length, 1); - - const cellOutputClear = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); - await vscode.commands.executeCommand('undo'); - const cellOutputsCleardRet = await cellOutputClear; - assert.deepEqual(cellOutputsCleardRet, { - document: vscode.window.activeNotebookEditor!.document, - cells: [vscode.window.activeNotebookEditor!.document.cells[0]] - }); - assert.equal(cellOutputsAddedRet.cells[0].outputs.length, 0); - - await saveFileAndCloseAll(resource); - }); - -}); - -suite('notebook working copy', () => { - // test('notebook revert on close', async function () { - // const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - // await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - // assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); - - // await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - // await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' }); - - // // close active editor from command will revert the file - // await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - // assert.equal(vscode.window.activeNotebookEditor !== undefined, true); - // assert.equal(vscode.window.activeNotebookEditor?.selection !== undefined, true); - // assert.deepEqual(vscode.window.activeNotebookEditor?.document.cells[0], vscode.window.activeNotebookEditor?.selection); - // assert.equal(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'test'); - - // await vscode.commands.executeCommand('workbench.action.files.save'); - // await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - // }); - - // test('notebook revert', async function () { - // const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - // await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - // assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); - - // await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - // await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' }); - // await vscode.commands.executeCommand('workbench.action.files.revert'); - - // assert.equal(vscode.window.activeNotebookEditor !== undefined, true); - // assert.equal(vscode.window.activeNotebookEditor?.selection !== undefined, true); - // assert.deepEqual(vscode.window.activeNotebookEditor?.document.cells[0], vscode.window.activeNotebookEditor?.selection); - // assert.deepEqual(vscode.window.activeNotebookEditor?.document.cells.length, 1); - // assert.equal(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'test'); - - // await vscode.commands.executeCommand('workbench.action.files.saveAll'); - // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - // }); - - test('multiple tabs: dirty + clean', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); - - await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - const edit = new vscode.WorkspaceEdit(); - edit.insert(vscode.window.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); - await vscode.workspace.applyEdit(edit); - - const secondResource = await createRandomFile('', undefined, 'second', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - - // make sure that the previous dirty editor is still restored in the extension host and no data loss - assert.equal(vscode.window.activeNotebookEditor !== undefined, true); - assert.equal(vscode.window.activeNotebookEditor?.selection !== undefined, true); - assert.deepEqual(vscode.window.activeNotebookEditor?.document.cells[1], vscode.window.activeNotebookEditor?.selection); - assert.deepEqual(vscode.window.activeNotebookEditor?.document.cells.length, 3); - assert.equal(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); - - await saveFileAndCloseAll(resource); - }); - - test('multiple tabs: two dirty tabs and switching', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); - - await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - const edit = new vscode.WorkspaceEdit(); - edit.insert(vscode.window.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); - await vscode.workspace.applyEdit(edit); - - const secondResource = await createRandomFile('', undefined, 'second', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); - - // switch to the first editor - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true); - assert.equal(vscode.window.activeNotebookEditor?.selection !== undefined, true); - assert.deepEqual(vscode.window.activeNotebookEditor?.document.cells[1], vscode.window.activeNotebookEditor?.selection); - assert.deepEqual(vscode.window.activeNotebookEditor?.document.cells.length, 3); - assert.equal(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); - - // switch to the second editor - await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true); - assert.equal(vscode.window.activeNotebookEditor?.selection !== undefined, true); - assert.deepEqual(vscode.window.activeNotebookEditor?.document.cells[1], vscode.window.activeNotebookEditor?.selection); - assert.deepEqual(vscode.window.activeNotebookEditor?.document.cells.length, 2); - assert.equal(vscode.window.activeNotebookEditor?.selection?.document.getText(), ''); - - await saveAllFilesAndCloseAll(secondResource); - // await vscode.commands.executeCommand('workbench.action.files.saveAll'); - // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - }); - - test('multiple tabs: different editors with same document', async function () { - assertInitalState(); - - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - const firstNotebookEditor = vscode.window.activeNotebookEditor; - assert.equal(firstNotebookEditor !== undefined, true, 'notebook first'); - assert.equal(firstNotebookEditor!.selection?.document.getText(), 'test'); - assert.equal(firstNotebookEditor!.selection?.language, 'typescript'); - - await splitEditor(); - const secondNotebookEditor = vscode.window.activeNotebookEditor; - assert.equal(secondNotebookEditor !== undefined, true, 'notebook first'); - assert.equal(secondNotebookEditor!.selection?.document.getText(), 'test'); - assert.equal(secondNotebookEditor!.selection?.language, 'typescript'); - - assert.notEqual(firstNotebookEditor, secondNotebookEditor); - assert.equal(firstNotebookEditor?.document, secondNotebookEditor?.document, 'split notebook editors share the same document'); - assert.notEqual(firstNotebookEditor?.asWebviewUri(vscode.Uri.file('./hello.png')), secondNotebookEditor?.asWebviewUri(vscode.Uri.file('./hello.png'))); - - await saveAllFilesAndCloseAll(resource); - - // await vscode.commands.executeCommand('workbench.action.files.saveAll'); - // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - }); -}); - -suite('metadata', () => { - test('custom metadata should be supported', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.equal(vscode.window.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false); - assert.equal(vscode.window.activeNotebookEditor!.selection?.metadata.custom!['testCellMetadata'] as number, 123); - assert.equal(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); - - await saveFileAndCloseAll(resource); - }); - - - // TODO@rebornix skip as it crashes the process all the time - test.skip('custom metadata should be supported 2', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.equal(vscode.window.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false); - assert.equal(vscode.window.activeNotebookEditor!.selection?.metadata.custom!['testCellMetadata'] as number, 123); - assert.equal(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); - - // TODO see #101462 - // await vscode.commands.executeCommand('notebook.cell.copyDown'); - // const activeCell = vscode.window.activeNotebookEditor!.selection; - // assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); - // assert.equal(activeCell?.metadata.custom!['testCellMetadata'] as number, 123); - - await saveFileAndCloseAll(resource); - }); -}); - -suite('regression', () => { - // test('microsoft/vscode-github-issue-notebooks#26. Insert template cell in the new empty document', async function () { - // assertInitalState(); - // await vscode.commands.executeCommand('workbench.action.files.newUntitledFile', { "viewType": "notebookCoreTest" }); - // assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - // assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); - // assert.equal(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); - // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - // }); - - test('#106657. Opening a notebook from markers view is broken ', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - const document = vscode.window.activeNotebookEditor?.document!; - const [cell] = document.cells; - - await saveAllFilesAndCloseAll(document.uri); - assert.strictEqual(vscode.window.activeNotebookEditor, undefined); - - // opening a cell-uri opens a notebook editor - await vscode.commands.executeCommand('vscode.open', cell.uri, vscode.ViewColumn.Active); - - assert.strictEqual(!!vscode.window.activeNotebookEditor, true); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.uri.toString(), resource.toString()); - }); - - test.skip('Cannot open notebook from cell-uri with vscode.open-command', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - const document = vscode.window.activeNotebookEditor?.document!; - const [cell] = document.cells; - - await saveAllFilesAndCloseAll(document.uri); - assert.strictEqual(vscode.window.activeNotebookEditor, undefined); - - // BUG is that the editor opener (https://github.com/microsoft/vscode/blob/8e7877bdc442f1e83a7fec51920d82b696139129/src/vs/editor/browser/services/openerService.ts#L69) - // removes the fragment if it matches something numeric. For notebooks that's not wanted... - await vscode.commands.executeCommand('vscode.open', cell.uri); - - assert.strictEqual(vscode.window.activeNotebookEditor!.document.uri.toString(), resource.toString()); - }); - - test('#97830, #97764. Support switch to other editor types', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'empty', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - const edit = new vscode.WorkspaceEdit(); - edit.insert(vscode.window.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); - await vscode.workspace.applyEdit(edit); - - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'var abc = 0;'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); - - await vscode.commands.executeCommand('vscode.openWith', resource, 'default'); - assert.equal(vscode.window.activeTextEditor?.document.uri.path, resource.path); - - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - }); - - // open text editor, pin, and then open a notebook - test('#96105 - dirty editors', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'empty', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'default'); - const edit = new vscode.WorkspaceEdit(); - edit.insert(resource, new vscode.Position(0, 0), 'var abc = 0;'); - await vscode.workspace.applyEdit(edit); - - // now it's dirty, open the resource with notebook editor should open a new one - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.notEqual(vscode.window.activeNotebookEditor, undefined, 'notebook first'); - // assert.notEqual(vscode.window.activeTextEditor, undefined); - - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - }); - - test('#102411 - untitled notebook creation failed', async function () { - assertInitalState(); - await vscode.commands.executeCommand('workbench.action.files.newUntitledFile', { viewType: 'notebookCoreTest' }); - assert.notEqual(vscode.window.activeNotebookEditor, undefined, 'untitled notebook editor is not undefined'); - - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - }); - - test('#102423 - copy/paste shares the same text buffer', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - let activeCell = vscode.window.activeNotebookEditor!.selection; - assert.equal(activeCell?.document.getText(), 'test'); - - await vscode.commands.executeCommand('notebook.cell.copyDown'); - await vscode.commands.executeCommand('notebook.cell.edit'); - activeCell = vscode.window.activeNotebookEditor!.selection; - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); - assert.equal(activeCell?.document.getText(), 'test'); - - const edit = new vscode.WorkspaceEdit(); - edit.insert(vscode.window.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); - await vscode.workspace.applyEdit(edit); - - assert.equal(vscode.window.activeNotebookEditor!.document.cells.length, 2); - assert.notEqual(vscode.window.activeNotebookEditor!.document.cells[0].document.getText(), vscode.window.activeNotebookEditor!.document.cells[1].document.getText()); - - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - }); -}); - -suite('webview', () => { - // for web, `asWebUri` gets `https`? - // test('asWebviewUri', async function () { - // if (vscode.env.uiKind === vscode.UIKind.Web) { - // return; - // } - - // const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - // assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - // const uri = vscode.window.activeNotebookEditor!.asWebviewUri(vscode.Uri.file('./hello.png')); - // assert.equal(uri.scheme, 'vscode-webview-resource'); - // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - // }); - - - // 404 on web - // test('custom renderer message', async function () { - // if (vscode.env.uiKind === vscode.UIKind.Web) { - // return; - // } - - // const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './customRenderer.vsctestnb')); - // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - // const editor = vscode.window.activeNotebookEditor; - // const promise = new Promise(resolve => { - // const messageEmitter = editor?.onDidReceiveMessage(e => { - // if (e.type === 'custom_renderer_initialize') { - // resolve(); - // messageEmitter?.dispose(); - // } - // }); - // }); - - // await vscode.commands.executeCommand('notebook.cell.execute'); - // await promise; - // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - // }); -}); diff --git a/extensions/vscode-notebook-tests/src/notebookTestMain.ts b/extensions/vscode-notebook-tests/src/notebookTestMain.ts deleted file mode 100644 index c02749acd..000000000 --- a/extensions/vscode-notebook-tests/src/notebookTestMain.ts +++ /dev/null @@ -1,187 +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 vscode from 'vscode'; -import { smokeTestActivate } from './notebookSmokeTestMain'; - -export function activate(context: vscode.ExtensionContext): any { - smokeTestActivate(context); - - const _onDidChangeNotebook = new vscode.EventEmitter(); - context.subscriptions.push(_onDidChangeNotebook); - context.subscriptions.push(vscode.notebook.registerNotebookContentProvider('notebookCoreTest', { - onDidChangeNotebook: _onDidChangeNotebook.event, - openNotebook: async (_resource: vscode.Uri) => { - if (/.*empty\-.*\.vsctestnb$/.test(_resource.path)) { - return { - languages: ['typescript'], - metadata: {}, - cells: [] - }; - } - - const dto: vscode.NotebookData = { - languages: ['typescript'], - metadata: { - custom: { testMetadata: false } - }, - cells: [ - { - source: 'test', - language: 'typescript', - cellKind: vscode.CellKind.Code, - outputs: [], - metadata: { - custom: { testCellMetadata: 123 } - } - } - ] - }; - - return dto; - }, - resolveNotebook: async (_document: vscode.NotebookDocument) => { - return; - }, - saveNotebook: async (_document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => { - return; - }, - saveNotebookAs: async (_targetResource: vscode.Uri, _document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => { - return; - }, - backupNotebook: async (_document: vscode.NotebookDocument, _context: vscode.NotebookDocumentBackupContext, _cancellation: vscode.CancellationToken) => { - return { - id: '1', - delete: () => { } - }; - } - })); - - const kernel: vscode.NotebookKernel = { - id: 'mainKernel', - label: 'Notebook Test Kernel', - isPreferred: true, - executeAllCells: async (_document: vscode.NotebookDocument) => { - const cell = _document.cells[0]; - - cell.outputs = [{ - outputKind: vscode.CellOutputKind.Rich, - data: { - 'text/plain': ['my output'] - } - }]; - return; - }, - cancelAllCellsExecution: async (_document: vscode.NotebookDocument) => { }, - executeCell: async (document: vscode.NotebookDocument, cell: vscode.NotebookCell | undefined) => { - if (!cell) { - cell = document.cells[0]; - } - - if (document.uri.path.endsWith('customRenderer.vsctestnb')) { - cell.outputs = [{ - outputKind: vscode.CellOutputKind.Rich, - data: { - 'text/custom': 'test' - } - }]; - - return; - } - - const previousOutputs = cell.outputs; - const newOutputs: vscode.CellOutput[] = [{ - outputKind: vscode.CellOutputKind.Rich, - data: { - 'text/plain': ['my output'] - } - }]; - - cell.outputs = newOutputs; - - _onDidChangeNotebook.fire({ - document: document, - undo: () => { - if (cell) { - cell.outputs = previousOutputs; - } - }, - redo: () => { - if (cell) { - cell.outputs = newOutputs; - } - } - }); - return; - }, - cancelCellExecution: async (_document: vscode.NotebookDocument, _cell: vscode.NotebookCell) => { } - }; - - const kernel2: vscode.NotebookKernel = { - id: 'secondaryKernel', - label: 'Notebook Secondary Test Kernel', - isPreferred: false, - executeAllCells: async (_document: vscode.NotebookDocument) => { - const cell = _document.cells[0]; - - cell.outputs = [{ - outputKind: vscode.CellOutputKind.Rich, - data: { - 'text/plain': ['my second output'] - } - }]; - return; - }, - cancelAllCellsExecution: async (_document: vscode.NotebookDocument) => { }, - executeCell: async (document: vscode.NotebookDocument, cell: vscode.NotebookCell | undefined) => { - if (!cell) { - cell = document.cells[0]; - } - - if (document.uri.path.endsWith('customRenderer.vsctestnb')) { - cell.outputs = [{ - outputKind: vscode.CellOutputKind.Rich, - data: { - 'text/custom': 'test 2' - } - }]; - - return; - } - - const previousOutputs = cell.outputs; - const newOutputs: vscode.CellOutput[] = [{ - outputKind: vscode.CellOutputKind.Rich, - data: { - 'text/plain': ['my second output'] - } - }]; - - cell.outputs = newOutputs; - - _onDidChangeNotebook.fire({ - document: document, - undo: () => { - if (cell) { - cell.outputs = previousOutputs; - } - }, - redo: () => { - if (cell) { - cell.outputs = newOutputs; - } - } - }); - return; - }, - cancelCellExecution: async (_document: vscode.NotebookDocument, _cell: vscode.NotebookCell) => { } - }; - - context.subscriptions.push(vscode.notebook.registerNotebookKernelProvider({ filenamePattern: '*.vsctestnb' }, { - provideKernels: async () => { - return [kernel, kernel2]; - } - })); -} diff --git a/extensions/vscode-test-resolver/package.json b/extensions/vscode-test-resolver/package.json index 12571d0dc..c2ddf436b 100644 --- a/extensions/vscode-test-resolver/package.json +++ b/extensions/vscode-test-resolver/package.json @@ -1,135 +1,139 @@ { - "name": "vscode-test-resolver", - "description": "Test resolver for VS Code", - "version": "0.0.1", - "publisher": "vscode", - "license": "MIT", - "enableProposedApi": true, - "private": true, - "engines": { - "vscode": "^1.25.0" - }, - "extensionKind": [ - "ui" - ], - "scripts": { - "compile": "node ./node_modules/vscode/bin/compile -watch -p ./", - "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-test-resolver" - }, - "activationEvents": [ - "onResolveRemoteAuthority:test", - "onCommand:vscode-testresolver.newWindow", - "onCommand:vscode-testresolver.newWindowWithError", - "onCommand:vscode-testresolver.showLog", - "onCommand:vscode-testresolver.openTunnel", - "onCommand:vscode-testresolver.startRemoteServer" - ], - "main": "./out/extension", - "devDependencies": { - "@types/node": "^12.19.9" - }, - "contributes": { - "resourceLabelFormatters": [ - { - "scheme": "vscode-remote", - "authority": "test+*", - "formatting": { - "label": "${path}", - "separator": "/", - "tildify": true, - "workspaceSuffix": "TestResolver" - } - } - ], - "commands": [ - { - "title": "New Window", - "category": "Remote-TestResolver", - "command": "vscode-testresolver.newWindow" - }, - { - "title": "Show Log", - "category": "Remote-TestResolver", - "command": "vscode-testresolver.showLog" - }, - { - "title": "Kill Server and Trigger Handled Error", - "category": "Remote-TestResolver", - "command": "vscode-testresolver.killServerAndTriggerHandledError" - }, - { - "title": "Open Tunnel...", - "category": "Remote-TestResolver", - "command": "vscode-testresolver.openTunnel" - }, - { - "title": "Open Remote Server...", - "category": "Remote-TestResolver", - "command": "vscode-testresolver.startRemoteServer" - } - ], - "menus": { - "commandPalette": [ - { - "command": "vscode-testresolver.openTunnel", - "when": "remoteName == test" - }, - { - "command": "vscode-testresolver.startRemoteServer", - "when": "remoteName == test" - } - ], - "statusBar/windowIndicator": [ - { - "command": "vscode-testresolver.newWindow", - "when": "!remoteName", - "group": "9_local_testresolver@2" - }, - { - "command": "vscode-testresolver.showLog", - "when": "remoteName == test", - "group": "1_remote_testresolver_open@3" - }, - { - "command": "vscode-testresolver.newWindow", - "when": "remoteName == test", - "group": "1_remote_testresolver_open@1" - }, - { - "command": "vscode-testresolver.openTunnel", - "when": "remoteName == test", - "group": "1_remote_testresolver_open@4" - }, - { - "command": "vscode-testresolver.startRemoteServer", - "when": "remoteName == test", - "group": "1_remote_testresolver_open@5" - } - ] - }, - "configuration": { - "properties": { - "testresolver.startupDelay": { - "description": "If set, the resolver will delay for the given amount of seconds. Use ths setting for testing a slow resolver", - "type": "number", - "default": 0 - }, - "testresolver.startupError": { - "description": "If set, the resolver will fail. Use ths setting for testing the failure of a resolver.", - "type": "boolean", - "default": false - }, - "testresolver.pause": { - "description": "If set, connection is paused", - "type": "boolean", - "default": false - }, - "testresolver.supportPublicPorts": { - "description": "If set, the test resolver tunnel factory will support mock public ports. Forwarded ports will not actually be public. Requires reload.", - "type": "boolean", - "default": false - } - } - } - } + "name": "vscode-test-resolver", + "description": "Test resolver for VS Code", + "version": "0.0.1", + "publisher": "vscode", + "license": "MIT", + "enableProposedApi": true, + "private": true, + "engines": { + "vscode": "^1.25.0" + }, + "extensionKind": [ + "ui" + ], + "scripts": { + "compile": "node ./node_modules/vscode/bin/compile -watch -p ./", + "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-test-resolver" + }, + "activationEvents": [ + "onResolveRemoteAuthority:test", + "onCommand:vscode-testresolver.newWindow", + "onCommand:vscode-testresolver.newWindowWithError", + "onCommand:vscode-testresolver.showLog", + "onCommand:vscode-testresolver.openTunnel", + "onCommand:vscode-testresolver.startRemoteServer" + ], + "main": "./out/extension", + "devDependencies": { + "@types/node": "^12.19.9" + }, + "contributes": { + "resourceLabelFormatters": [ + { + "scheme": "vscode-remote", + "authority": "test+*", + "formatting": { + "label": "${path}", + "separator": "/", + "tildify": true, + "workspaceSuffix": "TestResolver" + } + } + ], + "commands": [ + { + "title": "New Window", + "category": "Remote-TestResolver", + "command": "vscode-testresolver.newWindow" + }, + { + "title": "Show Log", + "category": "Remote-TestResolver", + "command": "vscode-testresolver.showLog" + }, + { + "title": "Kill Server and Trigger Handled Error", + "category": "Remote-TestResolver", + "command": "vscode-testresolver.killServerAndTriggerHandledError" + }, + { + "title": "Open Tunnel...", + "category": "Remote-TestResolver", + "command": "vscode-testresolver.openTunnel" + }, + { + "title": "Open Remote Server...", + "category": "Remote-TestResolver", + "command": "vscode-testresolver.startRemoteServer" + } + ], + "menus": { + "commandPalette": [ + { + "command": "vscode-testresolver.openTunnel", + "when": "remoteName == test" + }, + { + "command": "vscode-testresolver.startRemoteServer", + "when": "remoteName == test" + } + ], + "statusBar/windowIndicator": [ + { + "command": "vscode-testresolver.newWindow", + "when": "!remoteName", + "group": "9_local_testresolver@2" + }, + { + "command": "vscode-testresolver.showLog", + "when": "remoteName == test", + "group": "1_remote_testresolver_open@3" + }, + { + "command": "vscode-testresolver.newWindow", + "when": "remoteName == test", + "group": "1_remote_testresolver_open@1" + }, + { + "command": "vscode-testresolver.openTunnel", + "when": "remoteName == test", + "group": "1_remote_testresolver_open@4" + }, + { + "command": "vscode-testresolver.startRemoteServer", + "when": "remoteName == test", + "group": "1_remote_testresolver_open@5" + } + ] + }, + "configuration": { + "properties": { + "testresolver.startupDelay": { + "description": "If set, the resolver will delay for the given amount of seconds. Use ths setting for testing a slow resolver", + "type": "number", + "default": 0 + }, + "testresolver.startupError": { + "description": "If set, the resolver will fail. Use ths setting for testing the failure of a resolver.", + "type": "boolean", + "default": false + }, + "testresolver.pause": { + "description": "If set, connection is paused", + "type": "boolean", + "default": false + }, + "testresolver.supportPublicPorts": { + "description": "If set, the test resolver tunnel factory will support mock public ports. Forwarded ports will not actually be public. Requires reload.", + "type": "boolean", + "default": false + } + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" + } } diff --git a/extensions/vscode-test-resolver/src/extension.ts b/extensions/vscode-test-resolver/src/extension.ts index ccac6c52c..d33fe9cdb 100644 --- a/extensions/vscode-test-resolver/src/extension.ts +++ b/extensions/vscode-test-resolver/src/extension.ts @@ -83,12 +83,6 @@ export function activate(context: vscode.ExtensionContext) { const env = getNewEnv(); const remoteDataDir = process.env['TESTRESOLVER_DATA_FOLDER'] || path.join(os.homedir(), serverDataFolderName || `${dataFolderName}-testresolver`); - const remoteExtension = process.env['TESTRESOLVER_REMOTE_EXTENSION']; - if (remoteExtension) { - commandArgs.push('--install-extension', remoteExtension); - commandArgs.push('--start-server'); - } - env['VSCODE_AGENT_FOLDER'] = remoteDataDir; outputChannel.appendLine(`Using data folder at ${remoteDataDir}`); @@ -98,6 +92,11 @@ export function activate(context: vscode.ExtensionContext) { const serverCommandPath = path.join(vscodePath, 'resources', 'server', 'bin-dev', serverCommand); extHostProcess = cp.spawn(serverCommandPath, commandArgs, { env, cwd: vscodePath }); } else { + const extensionToInstall = process.env['TESTRESOLVER_INSTALL_BUILTIN_EXTENSION']; + if (extensionToInstall) { + commandArgs.push('--install-builtin-extension', extensionToInstall); + commandArgs.push('--start-server'); + } const serverCommand = process.platform === 'win32' ? 'server.cmd' : 'server.sh'; let serverLocation = env['VSCODE_REMOTE_SERVER_PATH']; // support environment variable to specify location of server on disk if (!serverLocation) { diff --git a/extensions/xml/package.json b/extensions/xml/package.json index 3e5042df7..a7a9d2c64 100644 --- a/extensions/xml/package.json +++ b/extensions/xml/package.json @@ -1,99 +1,117 @@ { - "name": "xml", - "displayName": "%displayName%", - "description": "%description%", - "version": "1.0.0", - "publisher": "vscode", - "license": "MIT", - "engines": { "vscode": "*" }, - "contributes": { - "languages": [{ - "id": "xml", - "extensions": [ - ".xml", - ".xsd", - ".ascx", - ".atom", - ".axml", - ".bpmn", - ".cpt", - ".csl", - ".csproj", - ".csproj.user", - ".dita", - ".ditamap", - ".dtd", - ".ent", - ".mod", - ".dtml", - ".fsproj", - ".fxml", - ".iml", - ".isml", - ".jmx", - ".launch", - ".menu", - ".mxml", - ".nuspec", - ".opml", - ".owl", - ".proj", - ".props", - ".pt", - ".publishsettings", - ".pubxml", - ".pubxml.user", - ".rbxlx", - ".rbxmx", - ".rdf", - ".rng", - ".rss", - ".shproj", - ".storyboard", - ".svg", - ".targets", - ".tld", - ".tmx", - ".vbproj", - ".vbproj.user", - ".vcxproj", - ".vcxproj.filters", - ".wsdl", - ".wxi", - ".wxl", - ".wxs", - ".xaml", - ".xbl", - ".xib", - ".xlf", - ".xliff", - ".xpdl", - ".xul", - ".xoml" - ], - "firstLine" : "(\\<\\?xml.*)|(\\/dev/null) --enable-proposed-api=vscode.git --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/emmet/out/test/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test $ALL_PLATFORMS_API_TESTS_EXTRA_ARGS after_suite -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/vscode-notebook-tests/test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-notebook-tests --extensionTestsPath=$ROOT/extensions/vscode-notebook-tests/out/ --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $(mktemp -d 2>/dev/null) --enable-proposed-api=vscode.git --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test $ALL_PLATFORMS_API_TESTS_EXTRA_ARGS +after_suite + +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/vscode-notebook-tests/test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-notebook-tests --extensionTestsPath=$ROOT/extensions/vscode-notebook-tests/out/ $ALL_PLATFORMS_API_TESTS_EXTRA_ARGS after_suite # Tests in commonJS (CSS, HTML) diff --git a/src/bootstrap.js b/src/bootstrap.js index 5392a5602..3c8b0bc02 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -170,6 +170,9 @@ return nlsConfig; } + /** + * @returns {typeof import('./vs/base/parts/sandbox/electron-sandbox/globals') | undefined} + */ function safeGlobals() { const globals = (typeof self === 'object' ? self : typeof global === 'object' ? global : {}); @@ -177,7 +180,7 @@ } /** - * @returns {NodeJS.Process | undefined} + * @returns {import('./vs/base/parts/sandbox/electron-sandbox/globals').IPartialNodeProcess | NodeJS.Process} */ function safeProcess() { if (typeof process !== 'undefined') { @@ -188,16 +191,20 @@ if (globals) { return globals.process; // Native environment (sandboxed) } + + return undefined; } /** - * @returns {Electron.IpcRenderer | undefined} + * @returns {import('./vs/base/parts/sandbox/electron-sandbox/electronTypes').IpcRenderer | undefined} */ function safeIpcRenderer() { const globals = safeGlobals(); if (globals) { return globals.ipcRenderer; } + + return undefined; } /** @@ -236,7 +243,7 @@ } //#endregion - + //#region ApplicationInsights diff --git a/src/main.js b/src/main.js index 618c45bbf..7203aec3f 100644 --- a/src/main.js +++ b/src/main.js @@ -9,14 +9,14 @@ const perf = require('./vs/base/common/performance'); perf.mark('code/didStartMain'); -const lp = require('./vs/base/node/languagePacks'); const path = require('path'); const fs = require('fs'); const os = require('os'); +const { getNLSConfiguration } = require('./vs/base/node/languagePacks'); const bootstrap = require('./bootstrap'); const bootstrapNode = require('./bootstrap-node'); -const paths = require('./paths'); -/** @type {Partial & { applicationName: string}} */ +const { getDefaultUserDataPath } = require('./vs/base/node/userDataPath'); +/** @type {Partial} */ const product = require('../product.json'); const { app, protocol, crashReporter } = require('electron'); @@ -38,70 +38,8 @@ app.setPath('userData', userDataPath); // Configure static command line arguments const argvConfig = configureCommandlineSwitchesSync(args); -// If a crash-reporter-directory is specified we store the crash reports -// in the specified directory and don't upload them to the crash server. -let crashReporterDirectory = args['crash-reporter-directory']; -let submitURL = ''; -if (crashReporterDirectory) { - crashReporterDirectory = path.normalize(crashReporterDirectory); - - if (!path.isAbsolute(crashReporterDirectory)) { - console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory must be absolute.`); - app.exit(1); - } - - if (!fs.existsSync(crashReporterDirectory)) { - try { - fs.mkdirSync(crashReporterDirectory); - } catch (error) { - console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory does not seem to exist or cannot be created.`); - app.exit(1); - } - } - - // Crashes are stored in the crashDumps directory by default, so we - // need to change that directory to the provided one - console.log(`Found --crash-reporter-directory argument. Setting crashDumps directory to be '${crashReporterDirectory}'`); - app.setPath('crashDumps', crashReporterDirectory); -} else { - const appCenter = product.appCenter; - // Disable Appcenter crash reporting if - // * --crash-reporter-directory is specified - // * enable-crash-reporter runtime argument is set to 'false' - // * --disable-crash-reporter command line parameter is set - if (appCenter && argvConfig['enable-crash-reporter'] && !args['disable-crash-reporter']) { - const isWindows = (process.platform === 'win32'); - const isLinux = (process.platform === 'linux'); - const crashReporterId = argvConfig['crash-reporter-id']; - const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; - if (uuidPattern.test(crashReporterId)) { - submitURL = isWindows ? appCenter[process.arch === 'ia32' ? 'win32-ia32' : 'win32-x64'] : isLinux ? appCenter[`linux-x64`] : appCenter.darwin; - submitURL = submitURL.concat('&uid=', crashReporterId, '&iid=', crashReporterId, '&sid=', crashReporterId); - // Send the id for child node process that are explicitly starting crash reporter. - // For vscode this is ExtensionHost process currently. - const argv = process.argv; - const endOfArgsMarkerIndex = argv.indexOf('--'); - if (endOfArgsMarkerIndex === -1) { - argv.push('--crash-reporter-id', crashReporterId); - } else { - // if the we have an argument "--" (end of argument marker) - // we cannot add arguments at the end. rather, we add - // arguments before the "--" marker. - argv.splice(endOfArgsMarkerIndex, 0, '--crash-reporter-id', crashReporterId); - } - } - } -} - -// Start crash reporter for all processes -const productName = (product.crashReporter ? product.crashReporter.productName : undefined) || product.nameShort; -const companyName = (product.crashReporter ? product.crashReporter.companyName : undefined) || 'Microsoft'; -crashReporter.start({ - companyName: companyName, - productName: process.env['VSCODE_DEV'] ? `${productName} Dev` : productName, - submitURL, - uploadToServer: !crashReporterDirectory -}); +// Configure crash reporter +configureCrashReporter(); // Set logs path before app 'ready' event if running portable // to ensure that no 'logs' folder is created on disk at a @@ -118,29 +56,14 @@ setCurrentWorkingDirectory(); protocol.registerSchemesAsPrivileged([ { scheme: 'vscode-webview', - privileges: { - standard: true, - secure: true, - supportFetchAPI: true, - corsEnabled: true, - } + privileges: { standard: true, secure: true, supportFetchAPI: true, corsEnabled: true } }, { scheme: 'vscode-webview-resource', - privileges: { - secure: true, - standard: true, - supportFetchAPI: true, - corsEnabled: true, - } + privileges: { secure: true, standard: true, supportFetchAPI: true, corsEnabled: true } }, { scheme: 'vscode-file', - privileges: { - secure: true, - standard: true, - supportFetchAPI: true, - corsEnabled: true - } + privileges: { secure: true, standard: true, supportFetchAPI: true, corsEnabled: true } } ]); @@ -161,7 +84,7 @@ let nlsConfigurationPromise = undefined; const metaDataFile = path.join(__dirname, 'nls.metadata.json'); const locale = getUserDefinedLocale(argvConfig); if (locale) { - nlsConfigurationPromise = lp.getNLSConfiguration(product.commit, userDataPath, metaDataFile, locale); + nlsConfigurationPromise = getNLSConfiguration(product.commit, userDataPath, metaDataFile, locale); } // Load our code once ready @@ -381,6 +304,106 @@ function getArgvConfigPath() { return path.join(os.homedir(), dataFolderName, 'argv.json'); } +function configureCrashReporter() { + + // If a crash-reporter-directory is specified we store the crash reports + // in the specified directory and don't upload them to the crash server. + let crashReporterDirectory = args['crash-reporter-directory']; + let submitURL = ''; + if (crashReporterDirectory) { + crashReporterDirectory = path.normalize(crashReporterDirectory); + + if (!path.isAbsolute(crashReporterDirectory)) { + console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory must be absolute.`); + app.exit(1); + } + + if (!fs.existsSync(crashReporterDirectory)) { + try { + fs.mkdirSync(crashReporterDirectory); + } catch (error) { + console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory does not seem to exist or cannot be created.`); + app.exit(1); + } + } + + // Crashes are stored in the crashDumps directory by default, so we + // need to change that directory to the provided one + console.log(`Found --crash-reporter-directory argument. Setting crashDumps directory to be '${crashReporterDirectory}'`); + app.setPath('crashDumps', crashReporterDirectory); + } + + // Otherwise we configure the crash reporter from product.json + else { + const appCenter = product.appCenter; + // Disable Appcenter crash reporting if + // * --crash-reporter-directory is specified + // * enable-crash-reporter runtime argument is set to 'false' + // * --disable-crash-reporter command line parameter is set + if (appCenter && argvConfig['enable-crash-reporter'] && !args['disable-crash-reporter']) { + const isWindows = (process.platform === 'win32'); + const isLinux = (process.platform === 'linux'); + const isDarwin = (process.platform === 'darwin'); + const crashReporterId = argvConfig['crash-reporter-id']; + const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + if (uuidPattern.test(crashReporterId)) { + if (isWindows) { + switch (process.arch) { + case 'ia32': + submitURL = appCenter['win32-ia32']; + break; + case 'x64': + submitURL = appCenter['win32-x64']; + break; + case 'arm64': + submitURL = appCenter['win32-arm64']; + break; + } + } else if (isDarwin) { + if (product.darwinUniversalAssetId) { + submitURL = appCenter['darwin-universal']; + } else { + switch (process.arch) { + case 'x64': + submitURL = appCenter['darwin']; + break; + case 'arm64': + submitURL = appCenter['darwin-arm64']; + break; + } + } + } else if (isLinux) { + submitURL = appCenter['linux-x64']; + } + submitURL = submitURL.concat('&uid=', crashReporterId, '&iid=', crashReporterId, '&sid=', crashReporterId); + // Send the id for child node process that are explicitly starting crash reporter. + // For vscode this is ExtensionHost process currently. + const argv = process.argv; + const endOfArgsMarkerIndex = argv.indexOf('--'); + if (endOfArgsMarkerIndex === -1) { + argv.push('--crash-reporter-id', crashReporterId); + } else { + // if the we have an argument "--" (end of argument marker) + // we cannot add arguments at the end. rather, we add + // arguments before the "--" marker. + argv.splice(endOfArgsMarkerIndex, 0, '--crash-reporter-id', crashReporterId); + } + } + } + } + + // Start crash reporter for all processes + const productName = (product.crashReporter ? product.crashReporter.productName : undefined) || product.nameShort; + const companyName = (product.crashReporter ? product.crashReporter.companyName : undefined) || 'Microsoft'; + crashReporter.start({ + companyName: companyName, + productName: process.env['VSCODE_DEV'] ? `${productName} Dev` : productName, + submitURL, + uploadToServer: !crashReporterDirectory, + compress: true + }); +} + /** * @param {import('./vs/platform/environment/common/argv').NativeParsedArgs} cliArgs * @returns {string | null} @@ -411,7 +434,7 @@ function getUserDataPath(cliArgs) { return path.join(portable.portableDataPath, 'user-data'); } - return path.resolve(cliArgs['user-data-dir'] || paths.getDefaultUserDataPath()); + return path.resolve(cliArgs['user-data-dir'] || getDefaultUserDataPath()); } /** @@ -566,7 +589,7 @@ async function resolveNlsConfiguration() { // See above the comment about the loader and case sensitiviness appLocale = appLocale.toLowerCase(); - nlsConfiguration = await lp.getNLSConfiguration(product.commit, userDataPath, metaDataFile, appLocale); + nlsConfiguration = await getNLSConfiguration(product.commit, userDataPath, metaDataFile, appLocale); if (!nlsConfiguration) { nlsConfiguration = { locale: appLocale, availableLanguages: {} }; } diff --git a/src/paths.js b/src/paths.js deleted file mode 100644 index 2042123d7..000000000 --- a/src/paths.js +++ /dev/null @@ -1,48 +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 pkg = require('../package.json'); -const path = require('path'); -const os = require('os'); - -/** - * @returns {string} - */ -function getDefaultUserDataPath() { - - // Support global VSCODE_APPDATA environment variable - let appDataPath = process.env['VSCODE_APPDATA']; - - // Otherwise check per platform - if (!appDataPath) { - switch (process.platform) { - case 'win32': - appDataPath = process.env['APPDATA']; - if (!appDataPath) { - const userProfile = process.env['USERPROFILE']; - if (typeof userProfile !== 'string') { - throw new Error('Windows: Unexpected undefined %USERPROFILE% environment variable'); - } - appDataPath = path.join(userProfile, 'AppData', 'Roaming'); - } - break; - case 'darwin': - appDataPath = path.join(os.homedir(), 'Library', 'Application Support'); - break; - case 'linux': - appDataPath = process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config'); - break; - default: - throw new Error('Platform not supported'); - } - } - - return path.join(appDataPath, pkg.name); -} - -exports.getDefaultUserDataPath = getDefaultUserDataPath; diff --git a/src/tsconfig.json b/src/tsconfig.json index 5e81e5380..2f1c74627 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -13,6 +13,12 @@ "sinon", "winreg", "trusted-types" + ], + "plugins": [ + { + "name": "tsec", + "exemptionConfig": "./tsec.exemptions.json" + } ] }, "include": [ diff --git a/src/tsec.exemptions.json b/src/tsec.exemptions.json index dc1e80586..34fb2f4d8 100644 --- a/src/tsec.exemptions.json +++ b/src/tsec.exemptions.json @@ -1,5 +1,31 @@ { + "ban-eval-calls": [ + "vs/workbench/api/worker/extHostExtensionService.ts" + ], + "ban-function-calls": [ + "vs/workbench/api/worker/extHostExtensionService.ts", + "vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts", + "vs/workbench/services/keybinding/test/electron-browser/keyboardMapperTestUtils.ts" + ], "ban-trustedtypes-createpolicy": [ - "**/*.ts" + "vs/base/browser/dom.ts", + "vs/base/browser/markdownRenderer.ts", + "vs/base/worker/defaultWorkerFactory.ts", + "vs/base/worker/workerMain.ts", + "vs/editor/browser/core/markdownRenderer.ts", + "vs/editor/browser/view/domLineBreaksComputer.ts", + "vs/editor/browser/view/viewLayer.ts", + "vs/editor/browser/widget/diffEditorWidget.ts", + "vs/editor/browser/widget/diffReview.ts", + "vs/editor/standalone/browser/colorizer.ts", + "vs/workbench/api/worker/extHostExtensionService.ts", + "vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts", + "vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts", + "vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts", + "vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts" + ], + "ban-worker-calls": [ + "vs/base/worker/defaultWorkerFactory.ts", + "vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts" ] } diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index 3eb11e78c..485df81cb 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -110,13 +110,13 @@ export const onDidChangeFullscreen = WindowManager.INSTANCE.onDidChangeFullscree const userAgent = navigator.userAgent; -export const isEdgeLegacy = (userAgent.indexOf('Edge/') >= 0); export const isFirefox = (userAgent.indexOf('Firefox') >= 0); export const isWebKit = (userAgent.indexOf('AppleWebKit') >= 0); export const isChrome = (userAgent.indexOf('Chrome') >= 0); export const isSafari = (!isChrome && (userAgent.indexOf('Safari') >= 0)); export const isWebkitWebView = (!isChrome && !isSafari && isWebKit); export const isIPad = (userAgent.indexOf('iPad') >= 0 || (isSafari && navigator.maxTouchPoints > 0)); -export const isEdgeLegacyWebView = isEdgeLegacy && (userAgent.indexOf('WebView/') >= 0); +export const isEdgeLegacyWebView = (userAgent.indexOf('Edge/') >= 0) && (userAgent.indexOf('WebView/') >= 0); export const isElectron = (userAgent.indexOf('Electron/') >= 0); +export const isAndroid = (userAgent.indexOf('Android') >= 0); export const isStandalone = (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches); diff --git a/src/vs/base/browser/canIUse.ts b/src/vs/base/browser/canIUse.ts index ca5ab5e01..6c7abc339 100644 --- a/src/vs/base/browser/canIUse.ts +++ b/src/vs/base/browser/canIUse.ts @@ -25,19 +25,7 @@ export const BrowserFeatures = { readText: ( platform.isNative || !!(navigator && navigator.clipboard && navigator.clipboard.readText) - ), - richText: (() => { - if (browser.isEdgeLegacy) { - let index = navigator.userAgent.indexOf('Edge/'); - let version = parseInt(navigator.userAgent.substring(index + 5, navigator.userAgent.indexOf('.', index)), 10); - - if (!version || (version >= 12 && version <= 16)) { - return false; - } - } - - return true; - })() + ) }, keyboard: (() => { if (platform.isNative || browser.isStandalone) { diff --git a/src/vs/base/browser/contextmenu.ts b/src/vs/base/browser/contextmenu.ts index 2a28dace3..49667d70e 100644 --- a/src/vs/base/browser/contextmenu.ts +++ b/src/vs/base/browser/contextmenu.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAction, IActionRunner, IActionViewItem } from 'vs/base/common/actions'; +import { IAction, IActionRunner } from 'vs/base/common/actions'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { AnchorAlignment, AnchorAxisAlignment } from 'vs/base/browser/ui/contextview/contextview'; +import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; export interface IContextMenuEvent { readonly shiftKey?: boolean; diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index f2f2fa1aa..0bdba6f8d 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -284,7 +284,7 @@ export function modify(callback: () => void): IDisposable { } /** - * Add a throttled listener. `handler` is fired at most every 16ms or with the next animation frame (if browser supports it). + * Add a throttled listener. `handler` is fired at most every 8.33333ms or with the next animation frame (if browser supports it). */ export interface IEventMerger { (lastEvent: R | null, currentEvent: E): R; @@ -295,7 +295,7 @@ export interface DOMEvent { stopPropagation(): void; } -const MINIMUM_TIME_MS = 16; +const MINIMUM_TIME_MS = 8; const DEFAULT_EVENT_MERGER: IEventMerger = function (lastEvent: DOMEvent | null, currentEvent: DOMEvent) { return currentEvent; }; @@ -841,7 +841,7 @@ export const EventType = { MOUSE_OUT: 'mouseout', MOUSE_ENTER: 'mouseenter', MOUSE_LEAVE: 'mouseleave', - MOUSE_WHEEL: browser.isEdgeLegacy ? 'mousewheel' : 'wheel', + MOUSE_WHEEL: 'wheel', POINTER_UP: 'pointerup', POINTER_DOWN: 'pointerdown', POINTER_MOVE: 'pointermove', @@ -1230,6 +1230,10 @@ export function asCSSUrl(uri: URI): string { return `url('${FileAccess.asBrowserUri(uri).toString(true).replace(/'/g, '%27')}')`; } +export function asCSSPropertyValue(value: string) { + return `'${value.replace(/'/g, '%27')}'`; +} + export function triggerDownload(dataOrUri: Uint8Array | URI, name: string): void { // If the data is provided as Buffer, we create a @@ -1471,7 +1475,7 @@ export class ModifierKeyEmitter extends Emitter { metaKey: false }; - this._subscriptions.add(domEvent(document.body, 'keydown', true)(e => { + this._subscriptions.add(domEvent(window, 'keydown', true)(e => { const event = new StandardKeyboardEvent(e); // If Alt-key keydown event is repeated, ignore it #112347 @@ -1505,7 +1509,7 @@ export class ModifierKeyEmitter extends Emitter { } })); - this._subscriptions.add(domEvent(document.body, 'keyup', true)(e => { + this._subscriptions.add(domEvent(window, 'keyup', true)(e => { if (!e.altKey && this._keyStatus.altKey) { this._keyStatus.lastKeyReleased = 'alt'; } else if (!e.ctrlKey && this._keyStatus.ctrlKey) { diff --git a/src/vs/base/browser/event.ts b/src/vs/base/browser/event.ts index 52cc2ae82..02a8488d5 100644 --- a/src/vs/base/browser/event.ts +++ b/src/vs/base/browser/event.ts @@ -31,10 +31,12 @@ export interface CancellableEvent { stopPropagation(): void; } +export function stopEvent(event: T): T { + event.preventDefault(); + event.stopPropagation(); + return event; +} + export function stop(event: BaseEvent): BaseEvent { - return BaseEvent.map(event, e => { - e.preventDefault(); - e.stopPropagation(); - return e; - }); -} \ No newline at end of file + return BaseEvent.map(event, stopEvent); +} diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 81457733d..5378627e5 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -383,5 +383,16 @@ export function renderMarkdownAsPlaintext(markdown: IMarkdownString) { if (value.length > 100_000) { value = `${value.substr(0, 100_000)}…`; } - return sanitizeRenderedMarkdown({ isTrusted: false }, marked.parse(value, { renderer })).toString(); + + const unescapeInfo = new Map([ + ['"', '"'], + ['&', '&'], + [''', '\''], + ['<', '<'], + ['>', '>'], + ]); + + const html = marked.parse(value, { renderer }).replace(/&(#\d+|[a-zA-Z]+);/g, m => unescapeInfo.get(m) ?? m); + + return sanitizeRenderedMarkdown({ isTrusted: false }, html).toString(); } diff --git a/src/vs/base/browser/ui/actionbar/actionViewItems.ts b/src/vs/base/browser/ui/actionbar/actionViewItems.ts index 0968da8a6..f8cde279f 100644 --- a/src/vs/base/browser/ui/actionbar/actionViewItems.ts +++ b/src/vs/base/browser/ui/actionbar/actionViewItems.ts @@ -8,13 +8,14 @@ import * as platform from 'vs/base/common/platform'; import * as nls from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; import { SelectBox, ISelectOptionItem, ISelectBoxOptions } from 'vs/base/browser/ui/selectBox/selectBox'; -import { IAction, IActionRunner, Action, IActionChangeEvent, ActionRunner, Separator, IActionViewItem } from 'vs/base/common/actions'; +import { IAction, IActionRunner, Action, IActionChangeEvent, ActionRunner, Separator } from 'vs/base/common/actions'; import * as types from 'vs/base/common/types'; import { EventType as TouchEventType, Gesture } from 'vs/base/browser/touch'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { DataTransfers } from 'vs/base/browser/dnd'; import { isFirefox } from 'vs/base/browser/browser'; -import { $, addDisposableListener, append, EventHelper, EventLike, EventType, removeTabIndexAndUpdateFocus } from 'vs/base/browser/dom'; +import { $, addDisposableListener, append, EventHelper, EventLike, EventType } from 'vs/base/browser/dom'; +import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; export interface IBaseActionViewItemOptions { draggable?: boolean; @@ -163,8 +164,11 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { this.actionRunner.run(this._action, context); } + // Only set the tabIndex on the element once it is about to get focused + // That way this element wont be a tab stop when it is not needed #106441 focus(): void { if (this.element) { + this.element.tabIndex = 0; this.element.focus(); this.element.classList.add('focused'); } @@ -173,10 +177,21 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { blur(): void { if (this.element) { this.element.blur(); + this.element.tabIndex = -1; this.element.classList.remove('focused'); } } + setFocusable(focusable: boolean): void { + if (this.element) { + this.element.tabIndex = focusable ? 0 : -1; + } + } + + get trapsArrowNavigation(): boolean { + return false; + } + protected updateEnabled(): void { // implement in subclass } @@ -259,14 +274,27 @@ export class ActionViewItem extends BaseActionViewItem { this.updateChecked(); } + // Only set the tabIndex on the element once it is about to get focused + // That way this element wont be a tab stop when it is not needed #106441 focus(): void { - super.focus(); - if (this.label) { + this.label.tabIndex = 0; this.label.focus(); } } + blur(): void { + if (this.label) { + this.label.tabIndex = -1; + } + } + + setFocusable(focusable: boolean): void { + if (this.label) { + this.label.tabIndex = focusable ? 0 : -1; + } + } + updateLabel(): void { if (this.options.label && this.label) { this.label.textContent = this.getAction().label; @@ -320,7 +348,6 @@ export class ActionViewItem extends BaseActionViewItem { if (this.label) { this.label.removeAttribute('aria-disabled'); this.label.classList.remove('disabled'); - this.label.tabIndex = 0; } if (this.element) { @@ -330,7 +357,6 @@ export class ActionViewItem extends BaseActionViewItem { if (this.label) { this.label.setAttribute('aria-disabled', 'true'); this.label.classList.add('disabled'); - removeTabIndexAndUpdateFocus(this.label); } if (this.element) { diff --git a/src/vs/base/browser/ui/actionbar/actionbar.css b/src/vs/base/browser/ui/actionbar/actionbar.css index 75dbbb12c..cc0608fe9 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.css +++ b/src/vs/base/browser/ui/actionbar/actionbar.css @@ -55,6 +55,7 @@ } .monaco-action-bar .action-item.disabled .action-label, +.monaco-action-bar .action-item.disabled .action-label::before, .monaco-action-bar .action-item.disabled .action-label:hover { opacity: 0.4; } diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 788a83d55..414806416 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./actionbar'; -import { Disposable, dispose } from 'vs/base/common/lifecycle'; -import { IAction, IActionRunner, ActionRunner, IRunEvent, Separator, IActionViewItem, IActionViewItemProvider } from 'vs/base/common/actions'; +import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IAction, IActionRunner, ActionRunner, IRunEvent, Separator } from 'vs/base/common/actions'; import * as DOM from 'vs/base/browser/dom'; import * as types from 'vs/base/common/types'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -13,6 +13,19 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Emitter } from 'vs/base/common/event'; import { IActionViewItemOptions, ActionViewItem, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +export interface IActionViewItem extends IDisposable { + actionRunner: IActionRunner; + setActionContext(context: any): void; + render(element: HTMLElement): void; + isEnabled(): boolean; + focus(fromRight?: boolean): void; // TODO@isidorn what is this? + blur(): void; +} + +export interface IActionViewItemProvider { + (action: IAction): IActionViewItem | undefined; +} + export const enum ActionsOrientation { HORIZONTAL, HORIZONTAL_REVERSE, @@ -35,7 +48,7 @@ export interface IActionBarOptions { readonly triggerKeys?: ActionTrigger; readonly allowContextMenu?: boolean; readonly preventLoopNavigation?: boolean; - readonly ignoreOrientationForPreviousAndNextKey?: boolean; + readonly focusOnlyEnabledItems?: boolean; } export interface IActionOptions extends IActionViewItemOptions { @@ -63,6 +76,8 @@ export class ActionBar extends Disposable implements IActionRunner { // Trigger Key Tracking private triggerKeyDown: boolean = false; + private focusable: boolean = true; + // Elements domNode: HTMLElement; protected actionsList: HTMLElement; @@ -117,22 +132,22 @@ export class ActionBar extends Disposable implements IActionRunner { switch (this._orientation) { case ActionsOrientation.HORIZONTAL: - previousKeys = this.options.ignoreOrientationForPreviousAndNextKey ? [KeyCode.LeftArrow, KeyCode.UpArrow] : [KeyCode.LeftArrow]; - nextKeys = this.options.ignoreOrientationForPreviousAndNextKey ? [KeyCode.RightArrow, KeyCode.DownArrow] : [KeyCode.RightArrow]; + previousKeys = [KeyCode.LeftArrow]; + nextKeys = [KeyCode.RightArrow]; break; case ActionsOrientation.HORIZONTAL_REVERSE: - previousKeys = this.options.ignoreOrientationForPreviousAndNextKey ? [KeyCode.RightArrow, KeyCode.DownArrow] : [KeyCode.RightArrow]; - nextKeys = this.options.ignoreOrientationForPreviousAndNextKey ? [KeyCode.LeftArrow, KeyCode.UpArrow] : [KeyCode.LeftArrow]; + previousKeys = [KeyCode.RightArrow]; + nextKeys = [KeyCode.LeftArrow]; this.domNode.className += ' reverse'; break; case ActionsOrientation.VERTICAL: - previousKeys = this.options.ignoreOrientationForPreviousAndNextKey ? [KeyCode.LeftArrow, KeyCode.UpArrow] : [KeyCode.UpArrow]; - nextKeys = this.options.ignoreOrientationForPreviousAndNextKey ? [KeyCode.RightArrow, KeyCode.DownArrow] : [KeyCode.DownArrow]; + previousKeys = [KeyCode.UpArrow]; + nextKeys = [KeyCode.DownArrow]; this.domNode.className += ' vertical'; break; case ActionsOrientation.VERTICAL_REVERSE: - previousKeys = this.options.ignoreOrientationForPreviousAndNextKey ? [KeyCode.RightArrow, KeyCode.DownArrow] : [KeyCode.DownArrow]; - nextKeys = this.options.ignoreOrientationForPreviousAndNextKey ? [KeyCode.LeftArrow, KeyCode.UpArrow] : [KeyCode.UpArrow]; + previousKeys = [KeyCode.DownArrow]; + nextKeys = [KeyCode.UpArrow]; this.domNode.className += ' vertical reverse'; break; } @@ -140,6 +155,7 @@ export class ActionBar extends Disposable implements IActionRunner { this._register(DOM.addDisposableListener(this.domNode, DOM.EventType.KEY_DOWN, e => { const event = new StandardKeyboardEvent(e); let eventHandled = true; + const focusedItem = typeof this.focusedItem === 'number' ? this.viewItems[this.focusedItem] : undefined; if (previousKeys && (event.equals(previousKeys[0]) || event.equals(previousKeys[1]))) { eventHandled = this.focusPrevious(); @@ -147,6 +163,8 @@ export class ActionBar extends Disposable implements IActionRunner { eventHandled = this.focusNext(); } else if (event.equals(KeyCode.Escape) && this.cancelHasListener) { this._onDidCancel.fire(); + } else if (event.equals(KeyCode.Tab) && focusedItem instanceof BaseActionViewItem && focusedItem.trapsArrowNavigation) { + this.focusNext(); } else if (this.isTriggerKeyEvent(event)) { // Staying out of the else branch even if not triggered if (this._triggerKeys.keyDown) { @@ -216,6 +234,25 @@ export class ActionBar extends Disposable implements IActionRunner { } } + // Some action bars should not be focusable at times + // When an action bar is not focusable make sure to make all the elements inside it not focusable + // When an action bar is focusable again, make sure the first item can be focused + setFocusable(focusable: boolean): void { + this.focusable = focusable; + if (this.focusable) { + const firstEnabled = this.viewItems.find(vi => vi instanceof BaseActionViewItem && vi.isEnabled()); + if (firstEnabled instanceof BaseActionViewItem) { + firstEnabled.setFocusable(true); + } + } else { + this.viewItems.forEach(vi => { + if (vi instanceof BaseActionViewItem) { + vi.setFocusable(false); + } + }); + } + } + private isTriggerKeyEvent(event: StandardKeyboardEvent): boolean { let ret = false; this._triggerKeys.keys.forEach(keyCode => { @@ -294,6 +331,11 @@ export class ActionBar extends Disposable implements IActionRunner { item.setActionContext(this.context); item.render(actionViewItemElement); + if (this.focusable && item instanceof BaseActionViewItem && this.viewItems.length === 0) { + // We need to allow for the first enabled item to be focused on using tab navigation #106441 + item.setFocusable(true); + } + if (index === null || index < 0 || index >= this.actionsList.children.length) { this.actionsList.appendChild(actionViewItemElement); this.viewItems.push(item); @@ -305,7 +347,7 @@ export class ActionBar extends Disposable implements IActionRunner { index++; } }); - if (this.focusedItem) { + if (typeof this.focusedItem === 'number') { // After a clear actions might be re-added to simply toggle some actions. We should preserve focus #97128 this.focus(this.focusedItem); } @@ -390,8 +432,8 @@ export class ActionBar extends Disposable implements IActionRunner { const startIndex = this.focusedItem; let item: IActionViewItem; - do { + if (this.options.preventLoopNavigation && this.focusedItem + 1 >= this.viewItems.length) { this.focusedItem = startIndex; return false; @@ -399,11 +441,7 @@ export class ActionBar extends Disposable implements IActionRunner { this.focusedItem = (this.focusedItem + 1) % this.viewItems.length; item = this.viewItems[this.focusedItem]; - } while (this.focusedItem !== startIndex && !item.isEnabled()); - - if (this.focusedItem === startIndex && !item.isEnabled()) { - this.focusedItem = undefined; - } + } while (this.focusedItem !== startIndex && this.options.focusOnlyEnabledItems && !item.isEnabled()); this.updateFocus(); return true; @@ -419,7 +457,6 @@ export class ActionBar extends Disposable implements IActionRunner { do { this.focusedItem = this.focusedItem - 1; - if (this.focusedItem < 0) { if (this.options.preventLoopNavigation) { this.focusedItem = startIndex; @@ -428,13 +465,9 @@ export class ActionBar extends Disposable implements IActionRunner { this.focusedItem = this.viewItems.length - 1; } - item = this.viewItems[this.focusedItem]; - } while (this.focusedItem !== startIndex && !item.isEnabled()); + } while (this.focusedItem !== startIndex && this.options.focusOnlyEnabledItems && !item.isEnabled()); - if (this.focusedItem === startIndex && !item.isEnabled()) { - this.focusedItem = undefined; - } this.updateFocus(true); return true; @@ -450,12 +483,20 @@ export class ActionBar extends Disposable implements IActionRunner { const actionViewItem = item; if (i === this.focusedItem) { - if (types.isFunction(actionViewItem.isEnabled)) { - if (actionViewItem.isEnabled() && types.isFunction(actionViewItem.focus)) { - actionViewItem.focus(fromRight); - } else { - this.actionsList.focus({ preventScroll }); - } + let focusItem = true; + + if (!types.isFunction(actionViewItem.focus)) { + focusItem = false; + } + + if (this.options.focusOnlyEnabledItems && types.isFunction(item.isEnabled) && !item.isEnabled()) { + focusItem = false; + } + + if (focusItem) { + actionViewItem.focus(fromRight); + } else { + this.actionsList.focus({ preventScroll }); } } else { if (types.isFunction(actionViewItem.blur)) { diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index ebfe8d59f..473eb2a2e 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -12,7 +12,7 @@ import { Event as BaseEvent, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; -import { addDisposableListener, IFocusTracker, EventType, EventHelper, trackFocus, reset, removeTabIndexAndUpdateFocus } from 'vs/base/browser/dom'; +import { addDisposableListener, IFocusTracker, EventType, EventHelper, trackFocus, reset } from 'vs/base/browser/dom'; import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; import { Action, IAction, IActionRunner } from 'vs/base/common/actions'; import { CSSIcon, Codicon } from 'vs/base/common/codicons'; @@ -214,7 +214,6 @@ export class Button extends Disposable implements IButton { } else { this._element.classList.add('disabled'); this._element.setAttribute('aria-disabled', String(true)); - removeTabIndexAndUpdateFocus(this._element); } } diff --git a/src/vs/base/browser/ui/checkbox/checkbox.ts b/src/vs/base/browser/ui/checkbox/checkbox.ts index f09bfc0d9..d9319424f 100644 --- a/src/vs/base/browser/ui/checkbox/checkbox.ts +++ b/src/vs/base/browser/ui/checkbox/checkbox.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./checkbox'; -import * as DOM from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Widget } from 'vs/base/browser/ui/widget'; import { Color } from 'vs/base/common/color'; @@ -19,6 +18,7 @@ export interface ICheckboxOpts extends ICheckboxStyles { readonly icon?: CSSIcon; readonly title: string; readonly isChecked: boolean; + readonly notFocusable?: boolean; } export interface ICheckboxStyles { @@ -51,7 +51,8 @@ export class CheckboxActionViewItem extends BaseActionViewItem { this.checkbox = new Checkbox({ actionClassName: this._action.class, isChecked: this._action.checked, - title: this._action.label + title: this._action.label, + notFocusable: true }); this.disposables.add(this.checkbox); this.disposables.add(this.checkbox.onChange(() => this._action.checked = !!this.checkbox && this.checkbox.checked, this)); @@ -74,6 +75,26 @@ export class CheckboxActionViewItem extends BaseActionViewItem { } } + focus(): void { + if (this.checkbox) { + this.checkbox.domNode.tabIndex = 0; + this.checkbox.focus(); + } + } + + blur(): void { + if (this.checkbox) { + this.checkbox.domNode.tabIndex = -1; + this.checkbox.domNode.blur(); + } + } + + setFocusable(focusable: boolean): void { + if (this.checkbox) { + this.checkbox.domNode.tabIndex = focusable ? 0 : -1; + } + } + dispose(): void { this.disposables.dispose(); super.dispose(); @@ -113,7 +134,9 @@ export class Checkbox extends Widget { this.domNode = document.createElement('div'); this.domNode.title = this._opts.title; this.domNode.classList.add(...classes); - this.domNode.tabIndex = 0; + if (!this._opts.notFocusable) { + this.domNode.tabIndex = 0; + } this.domNode.setAttribute('role', 'checkbox'); this.domNode.setAttribute('aria-checked', String(this._checked)); this.domNode.setAttribute('aria-label', this._opts.title); @@ -187,12 +210,10 @@ export class Checkbox extends Widget { } enable(): void { - this.domNode.tabIndex = 0; this.domNode.setAttribute('aria-disabled', String(false)); } disable(): void { - DOM.removeTabIndexAndUpdateFocus(this.domNode); this.domNode.setAttribute('aria-disabled', String(true)); } } diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon-modifiers.css b/src/vs/base/browser/ui/codicons/codicon/codicon-modifiers.css index fc65710a8..4b5b51414 100644 --- a/src/vs/base/browser/ui/codicons/codicon/codicon-modifiers.css +++ b/src/vs/base/browser/ui/codicons/codicon/codicon-modifiers.css @@ -13,7 +13,7 @@ } } -.codicon-sync.codicon-modifier-spin, .codicon-loading.codicon-modifier-spin{ +.codicon-sync.codicon-modifier-spin, .codicon-loading.codicon-modifier-spin, .codicon-gear.codicon-modifier-spin { /* Use steps to throttle FPS to reduce CPU usage */ animation: codicon-spin 1.5s steps(30) infinite; } diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 01abb46b868b7211758d3e2b92189072fc12ff08..b546c0a9775d3a6eae370da888c20094986fc70a 100644 GIT binary patch delta 6915 zcmbW5d3;n=mdAgmDoJIjES02ENg%1J6iGogl2j@cmQ;m33rpAn1PCM~?1V)&l~{-< zi;N3VbQr|lZV(-ssAH3i(rPP>(=^(Ewz#0uitpmuNNbvueO``?PEY?apQ+?~&%O6; z_uYH8`*vM1-rsAy%N4vGpj`m!7p!Pn)%0c9CBQTuNM65a`SlC$t~>h@khuwX{nX;t zrj~v$zOk6kJBfiifqt(5hUZo^ zZCquz5wG&`R6fbk*0iG4c_4kn&49)aG(@jjdEMGI)!lP}OEZB)|J~iLQ4JvdhJa4Kw&GUY#^-FqcI?0|{0r{FZrp==aUcE_ zd+~4Bhy8dE58+`vf=BTf9uMLP{1ON8B%a2va0H!r2LFNQ@oS#YF1&za_zhmd%e7w_SHe86h*A^wa{@F_0hGiXq_gfH+VzQ#AWjBjxTf5mtB z9z6)*2Lcfp$VeuNq+k?9Q!K?%0$C`LlF3SGlurGqKiSDaPI6H;4WwMkr&98gkIHEX z4W(gJL4FEQC5@mlG>)pMnrdl0O`u6sM^kAUO{aRAK{II<4&f>MI}T$s#$Yn0U@EGp z0Ckv-i3l_!4e6}b%dr}JXe=v9JgV^v{2o`S2xo8yMNl~2pcI_N$MDlcc<`TCfet)} z-{AuK<1|iTAO^ulB~(MBX(WxpPW%nMIF1Rdnh~8%(FO{_tm|q*m0tEKEVTJ-4-Bg$ zv?c)uST+l+g3uNStc=iF1XfFEtpYw`xlmwLg|i z5z7q%+Yq#k0%mvYH`{`fxpkw!mIaMHlMdS$w04254cbit+a0u^z!nH?vw+{SyjftY zgtkRs`-FCjz?KSatAIOM-UXKnAUDgq1uA5@TcAN046TD340{V`_XyBWzE_|OmU{%s>-;G8Ndr3*Xnz#g ztw1|1u!DhiMqpP1?X18~2ijW#yB}z83+#xX{YhY#1nnJxof9;DnHhFd(B2i;VL^LO zpENEopBwKB?8TscAh17!c3xo52JJ(EeH=7hjT!cK&^{K}??JmDum^~Nv! zcF3^nh4!_;P8ixZI;?-}kD>iVV9yNgvcO}5_N{=;EUyUcx1s%2U=I%MJAr*UwC@G> z>d<-w_V3XCCiK17RjxDa^I89UxnS7+LkkHU2%!BSa7bX+EO2muB7wsMltJKt0c8|8 zbU=j(97LeP1r8@r5dsGmD3cK67y}h4aI}Gn5;*QaMGG8(pkf4$MNnn|53!6DI6gte z2^^`Q;suUbPzeG@FDQ$E$5|%w9CHDVXi!N4$2O>BfukIhRp59Bl_GHDgR%)61EEp{ zj)qWa0>?$Dbb%uzR6l`ZB~*U_&+{6q41ohERHne86v{4eP=#^`9A2TE0*6I;IIcq# z2pr*|3I&e!P=f`I`cOpz$A73|finRFRS7p3P70tr0;dO1r2@{g^a`9ZK$QucJV5yb zP9vbo1x_fSh6tQmKn)c*$$%OraJm6Cybpc;`M*No90bZQa5e%J5I8S^suVawff^xj zt^zeu;H(8|l)(85)M$Y-8K^M==QL1b1=Mmh}SZrZht! z3(J`TC9<3)P%_H~fpqJcEl?WEIRd4#oU5-t7o?j;qd>Z8%oE7Ya=t(gmQ4aVSvCu# zYyAR&bWLv&Xdug0fpjHaD3GqWiv%jw?SC;h7}Aw&i9otyEfq*ttYre}inUxIU9naO zq$^gNK)Noi6iC;lRRRTAt`?}0YoTrZHW4>t%jiRFy~>H4rqAbr_`?OZUVulr2`>Fdq|(;%K+c zgc<4}P`3)4PD9-$aAFO$P3X9qnwF+3=1zf=ZK&G?OzkL4vt<`>>kffa zaHu;4PR60`5-_o2Y1-i8Zf@NxaCQ#0N8mgi>OKK_-~9rO?KqJ(IQR>0Js@xn54B&w zRhAD5oYzA=Byffg^{@baT#pD8!SYdo^M9zv1iZoWae?;)P)`WFJAmS6N9YAU?zk!4 z7M#dk2L;|VKs_n&_5n(tScW$eQ2N9&yrqD8THwtEls?G}Z!@6uNoIJ%0re|^w;oVO z1m1)|bqc&4fqI5-m)vhqu!QT+3sl4M*8+`Zc~qd0EV~5Wxb>U)cKF z{I(X`KHDkVcc}wYXQtkkdL?ah+R?PL>HX6;r=RTS@3+6-yZy`hAI?C=w2Xb3k(q^= z&6!*6i9vgry}`c8{-(q180qMCE_J?;6`6I9E8lf=z`_9svzxQ8=Dgyrckgo_9*BW8 z1J?{ZJ;*<3^PnTS$X%GbFZXg@UEX7P-{j}y-&9apu)g48VSVA=!mEQT2QMA`_TbA! z$wjXeoiC0ot}H%Oe5xeBq`Bl+$t6#sXSipT=Yl6x8f+-tS$fKw;+^T;?LF@8DH~q4 zwd@&RrEhb2T=~KwONSgB^2yNpq1%UkGpv5t)5G(JZyEkYMP!A)Vtd8$iVOZ^f4RRq zfWSS0p30WWH%CN_xM9T65g&~lIkIKsno(m%y);T2y=L^QV=BiyJvMS|%h>zJo*q{| z?vbkKs?k+#ReP#l2v&Vkom0KGdVlrVn&g_BYF?_1tlc|4di?hBrzbQ_*f-(*i8T}V zOiGzFbkdxTc+-sdTi>&X*ts_P0yV^fBK>N zw)(v@a%U`_aek(GX3fl#v&^&VXYHGHp`oZ@OT(dttFs5puAjYT_USpLbF{g`^5^>J z*3E66yLRsTjaiKg8;>;h%xjzX$h`07Z*Hn=+TC=bIj*^_`RD@kf|3RG3-&BH(K5cJ zt#xec-qwqqxtR~!WA+)BAshMdBG9qdUmk7rku}_#O;%UOd;b0%CH}-1f04lxx{vIk zY=6gif1%k>o*vp!`gQFws<_a(&41eH-NrXDB1+$eCwVL`GFfcoHHBODH8zsBriPwf z9y(!M)|+TJo6~!zn}&?fd5?~EZXcOOK?A^_o%B&SkPBod#~BLAZOSzGjfI9dGI?EA zo5$<&QzpsKcziB8mNR<#w2_HJQ>HFyn=>pnt7cJKQ&zyfdy-?_jT5)&<2~~s54R_O zf;TC}H!#&x?MSGY+qPtKZg@%l_$M3zU$L{*U`r20?kIh&c8~rd4~*)nr_|BSeB zgCW8c7M@}wij66C4azge8f+zQb3|N1a&&lDL^y@VM8?NNg`3NrF)`NIDC2_aaSo@m zVtB)xQ*#=IS2&%Han%cq!HfE{LtkBcwv6KH+SsUdDS7c>@p0Y(d4-N)spd4Z#Su?Y zvGI|anUV3aQ55g6nA2lYhdB!JU1f0zVKJErG1Sk>$07|zV?uq(pE#z{0eIA?D&H5Jpv%2wT^`G_U8LX`9R!?JlV`ICW_O#pW?e_LSfX;N= z?cMgS_V#n__R;^?7ij07@nE1g(Qh~AhYs*@{tvX+(8z&`!6-=QLS8NqUFJ>#_2C7^a*y1$siqdqSVs?ewVm z*)f5xF8((@`c7KAzo&O{z;5`$5$Niz>-KkdbK2GE*|DDDZGH2kYo!q;#B}5i$_UnC z5`WN}!JlB~@jUSp_`&~!403sG+~Z@6e9Y$Z4l`I?9;xb0kJanZ&+<6+KA)4#%E|WO zwCI=V!_^_D)fxEt&+l*N|6m}%)9DWcy89}6-S6k|2YUSUoZla+@w4O;^bm<{5aG&V|k!vh@LlvW?4v*KXH0rqD)eE2XyD(95A$uHEpT z4KT^&_Sq~Ri_f;Y@1}gS6Z!vifFU<`)3nf&iwh9;-?JT$RL-_?Sg|{+pGwXTat!%# z9in&@`mxHpk;gfn4*`tAIB*`HbZts}Njx)dlg;CE|38iQ*{vnDf!h47C3=y!HJ=to z=mzOv^`X;Ub~eK9K0E%$F29)Z*1X!#=ae=%Z|l|XDJk@MZT>B{+Z#D&(M7;m5D1z1=;c7~?>X8W z@A2D1HTFPPd%zJoZufT{dw)qZHFV~FcE}KEm}!`0XfVv~JgY2W=9re&=5>n-*0io! z*Vg&zm;J&MENv^-wl=R^xvXGq>xxy&n~r>YhWbbG;ew{-mFrtgeYNGw`+AyNm#^H= M`QDYmVH~~x4iq*`TL1t6 delta 6031 zcma*rd3=*)wg>RvNz$gY>5{f-=$e+$q;zl7jY1Q4N@>|Cr9h>u70M!lA`-$TA~J$T z9cD%ybzHFHHe_6eRzy81R^V=kw?(B1pZ3XFX2uv8i z<(+F1M}C7i;|2W~XGZ_|fq%vy!^SU+pYwhU4EnwNk6%NrI1FoW2t7r8p_9}d6>K)A zcw7T-31cpd16#!$V*!&3Cc_neVZy+hL~FaXz1knOKdC9IFL;-^V(CHr9ha~NXQ>P) zDGo1FB8Jlx0>zLSYv9ITuoU;<_b8_lv?GC9a1)k8;SKy7UZ*c*o1`E-NMOcg_ScWdFz$&c9 zby$b%u^t<618&4dY{Jdh3_rFYfUVeu9oUIm_&K|9D|X{{?8QFZi6HLAy*Pkh;FtIn z?#Bc8H6Fx6co@IIBLO^$$MFPu@D!fK?{EkqJdYRfBL2YN=Sz46NAN0M!@u*nJcd8v zIR1?P#5;Hw@8LAw#|Jor5AhNHicfGJpW-w84Hxh^G+e|N_!3{?GQP$Ye1mWC4}6C( ze!x}qBZ42vNG1wIQ8W#rSh7$&B~TJs$wp~pr@@p-S(Hr<%A=v=q5>+X3i41DRZ|Vs z(r~J$5j2t-Xf%zbaWtMLQX@^G$<$0!X&OzZ88nk-(QNz{PvU7jgK-#-X_$@~Xry77 zidkrZuN~>gKrdEeE$*Q)oFs{ugvan7_@0V!3O^$Q5#FR^yp50Hr6zcI1$5&sJcmEx z9J25hPM`q8P)nsWfku&+e7KGCF*>*{HXPg^J1Ah$Q#46m2mFuo6xu?;$q21e;2_6E zf|C#0V!>$$ZHeH7gtk=Z>rz`LI7y)`7vMFhbqP*fXxFCdH+*DX+}a93iNOSmopTS` zDxt4DZM7g?k=h!;*$!>3;Jk-+o!}J!ZJpq?0PT9gs{-13!RrIs2Ei)@+6@Bb9B%{y z`VB81Xd4ADBxpAYUYgK030_>#ZWg@EpmB}S@q&Zq7rgYKZ4tZ(p#=mlM`&9GUgx+? z@DdF`+s-?TS1Ys~g4ZpyodS1oyhTtY$6bO~HMCm=_`B713o7EMpCm>p9B&sigd-PT z9p!S|r)%RK{p9Qy-7TQM?jAwu9Pbt64u#`?YZy4Yr}!?$ zH3r(-f~yU*cLdiRXzvQHK+xV3T#KNc7F?B}{Y7wng7&`PN(Jo$!8Hro8Nt;H+K2kt z*-w6Jk(7qMe%JCn9%Q>{~1Q&K_-wQ7B(87X? zJ+vRhz#n#%w;4A8&i{VCVcZL#MFe*RXg><>5Bi!Jz?}k$1osRmgW&D~Wfa^;piF{0 z3RINf-U1aZxXVD9MSvR)RE*%(12sr+6M~8r+>W5)1UDuqi{KUo6)*50N3I4sZd*`^ zf*TlAlHgVbWfgdYV={lQd;@N8P$_~N9aO5|mIq}M-29-@1h+vbyWoZhHCS+Kgi06O zB%v|{w@au@!Hp9tOW*}QuPR$`H-*X(+*hF-f;%i!uHaq^l_$9CLJbi(!qF+X6GIIZ z+>@d51$SpCm(XV}x$4HMkPp$Y{zbf_Z1tsSaZaFd5B5!~*fN(DE5 zs4~GL093i)VF0Q^@K}I=s^lHU69SY+@T>q;C3tFpsunyyK-CDIB%o>q&lFH~f~O0p z;ezK3sCvN@2h<3`vj^130sQ@c&KnJa2NEc+;L!xiCwNGK8YOsKff_A%aDf^lc!Yr( zD|ncJ8Yg(Hff_G(z=4_|c+`QK$j4U``LE~otQrN+KTwkdPeM>lf@dPA$%3aNsAj=) z5>$)ei3w_oz>gfK2{Lk=F37}jhTzGHH)aZo<~U2xAda&I#d2&FWZ^hRP&~&rLHg30 zD=3L$yC5sad4g;l=L<^XxIjPud_#8L=@9yrfVxIdCdY+>vN(1M%I3I8kb~o5LHf#A zB4{YbrGi`>mkH7neYv1=ef@Xw4kJA)uN9;x;tD}}BCZsqCt|lCJrP$4(i3sDAU*lk z2-1^ptsp)5t`npu-#Q(ie`?^3>jmkFwO){(OdAC0$#jDtJ(+G4#JQt33TotdlOR2R zHVM*m=4L^9&TJMmm1DrqH;nW|*&;|!6#ig3(i3H?AU#jE3DWaqyWr^=YKP!C8fvHD zi5lt_!Lv2gF2Pea)U6_znw~yeKlUEMlQ-1uf@g53y#o3l_cOtBIn*73Cv>QNf@gK8 z{Q@n)?dfF|y}Wmi;NczWUcqBL)XxR<#}5b^6Fiq*7I=*J?iW1bLp>nyJ;z@Q9{Zsl z6np^y^^k!686Os8;P@NCmj_Uf2)xPhQNfoCQ2Oy0UpzoP&KKAE4PQq<9Tf0#d_quD z@aYVDXCLqBUoYe945+6BUvWU`Up3=v4=DYsW_%R_^{n9Q5vbn@zA}M2B={NyDkN|s z=*lc>DCNBu1x?`i2SKAa9u}m3HvMa6d`$zTe=>&Kg0E$63zR0^ope0uvejbEv6frC z)>i8>>)zzlM^wPuZW+lX4>ETV6!G^)*gC9&UOka||HT`HtVaCRc3z?0XyE4yawP*EYMY1Pk zAId(RW63Ga>B~7AaF`q}$7aX#j*Gd=@>27<@(vC04tdwPWN6aRS^1Xy$6Vd6hqg?PXm7Or%jo5VEUBl8>b(hVVF@kqhrRd8L!XWGxONY$gHN> zmf3Z)kF`d(ZfWgly)dVHPS2bxZS`&2+fK~Qo!d0`mvdj6d!;?8eO>!o^LEdBW8Rs0 zm*!)>Cwczn`9~I*7ED`kV8K@%LpwHIW4PwEg{cdh7T&S&QfGbV-p)gv?=DJP)U@dM zqDza9F3DKZ721^ZkR$F<<4WYC7}aP9Uh>w(r1SNEbw1gG6~3(CPH%FIp(L`8q9Xp_ zL*CMO%AmT)rbvJD3zTpq^nv$nV@VIcff3Od1f+PZ&J-&-^R4%FbbMg&9E_YYuIx`X zeA1sy*B^;+`3s#NlTM*q#tkOdz$?gU^%$+>$+sGt`6c})9;cFjc}eii@n7ZfUHs!x z6d%A!`SBDscp^nq9C@3cnN90oIr3WQ;)H$9z$?aAkg5;WG{hWjj!w0w+0!a(Yiet% z+%C7v=p5pzuB~#pYihkzTjQ?Lw`%l9ifBlJ!R#`Z8hJ~9CBHC%Y^nAf8rU)?kjp*r zpnk7|a*X_lr_#qe(QYTdf!oFJR2vMhNC@~>bTxRr4P7h3KW&&yF&45U7PxZDV=ct5 zF&UzwjnM{!DLym99=B*w+~ACic$2}vcSccEs?8Exmg_2rvlwipL*t_26O#u;8KX^6 zu`%(nF;Q`zoY>e@ON?>e*wNWJId!!&X1_dpMr~bAPWI@r^NfL)|K->NAL8l}OH6mF zJKmHKUy~G zxYF3eNnzukbh7yenG#g!31Z_DZgaY=Q8s6_lX+~OYO~Yk$!BJM!Q=T$qcc{crHIIHc3OTOM-{$KZYN@RhJlxukr1@%Cb4&-UWxh@!NX;_d$bIYa+EL8r+c(VC04Z{t26 zYWr}$DL&txS8H|W@u93vcktDZ)2g3$&~v%Dk;x8!I2`tNI2;|`KA#?ZA2=L_%G}%q zk=yva9g!x7uh;L(jU01$5lT9DZ7j8hHeGzu5GwuBY6?yMIx{M?@w-zLV_vsmbxCJ; KcW8OI%=BNNJ$7FJ diff --git a/src/vs/base/browser/ui/codicons/codiconStyles.ts b/src/vs/base/browser/ui/codicons/codiconStyles.ts index f2f58befb..0c486197f 100644 --- a/src/vs/base/browser/ui/codicons/codiconStyles.ts +++ b/src/vs/base/browser/ui/codicons/codiconStyles.ts @@ -13,5 +13,5 @@ export function formatRule(c: Codicon) { while (def instanceof Codicon) { def = def.definition; } - return `.codicon-${c.id}:before { content: '${def.character}'; }`; + return `.codicon-${c.id}:before { content: '${def.fontCharacter}'; }`; } diff --git a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts index 2a712e089..67e2e9bb7 100644 --- a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts +++ b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./dropdown'; -import { Action, IAction, IActionRunner, IActionViewItemProvider } from 'vs/base/common/actions'; +import { Action, IAction, IActionRunner } from 'vs/base/common/actions'; import { IDisposable } from 'vs/base/common/lifecycle'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; @@ -14,6 +14,7 @@ import { ActionViewItem, BaseActionViewItem, IActionViewItemOptions, IBaseAction import { IActionProvider, DropdownMenu, IDropdownMenuOptions, ILabelRenderer } from 'vs/base/browser/ui/dropdown/dropdown'; import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; import { Codicon } from 'vs/base/common/codicons'; +import { IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; export interface IKeybindingProvider { (action: IAction): ResolvedKeybinding | undefined; @@ -78,7 +79,6 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { this.element.classList.add(...classNames); - this.element.tabIndex = 0; this.element.setAttribute('role', 'button'); this.element.setAttribute('aria-haspopup', 'true'); this.element.setAttribute('aria-expanded', 'false'); @@ -173,12 +173,14 @@ export class ActionWithDropdownActionViewItem extends ActionViewItem { const menuActionsProvider = { getActions: () => { const actionsProvider = (this.options).menuActionsOrProvider; - return [this._action, ...(Array.isArray(actionsProvider) ? actionsProvider : actionsProvider.getActions())]; + return [this._action, ...(Array.isArray(actionsProvider) + ? actionsProvider + : (actionsProvider as IActionProvider).getActions()) // TODO: microsoft/TypeScript#42768 + ]; } }; this.dropdownMenuActionViewItem = new DropdownMenuActionViewItem(this._register(new Action('dropdownAction', undefined)), menuActionsProvider, this.contextMenuProvider, { classNames: ['dropdown', ...Codicon.dropDownButton.classNamesArray, ...(this.options).menuActionClassNames || []] }); this.dropdownMenuActionViewItem.render(this.element); } } - } diff --git a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts index 2aafbf827..0e853d988 100644 --- a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts +++ b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts @@ -20,5 +20,4 @@ export interface IHoverDelegateOptions { export interface IHoverDelegate { showHover(options: IHoverDelegateOptions): IDisposable | undefined; - hideHover(): void; } diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index 98e87e06a..382f52049 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -192,13 +192,14 @@ export class IconLabel extends Disposable { } } - private static adjustXAndShowCustomHover(hoverOptions: IHoverDelegateOptions | undefined, mouseX: number | undefined, hoverDelegate: IHoverDelegate, isHovering: boolean) { + private static adjustXAndShowCustomHover(hoverOptions: IHoverDelegateOptions | undefined, mouseX: number | undefined, hoverDelegate: IHoverDelegate, isHovering: boolean): IDisposable | undefined { if (hoverOptions && isHovering) { if (mouseX !== undefined) { (hoverOptions.target).x = mouseX + 10; } - hoverDelegate.showHover(hoverOptions); + return hoverDelegate.showHover(hoverOptions); } + return undefined; } private getTooltipForCustom(markdownTooltip: string | IIconLabelMarkdownString): (token: CancellationToken) => Promise { @@ -224,17 +225,22 @@ export class IconLabel extends Disposable { let mouseX: number | undefined; let isHovering = false; let tokenSource: CancellationTokenSource; + let hoverDisposable: IDisposable | undefined; function mouseOver(this: HTMLElement, e: MouseEvent): any { if (isHovering) { return; } tokenSource = new CancellationTokenSource(); function mouseLeaveOrDown(this: HTMLElement, e: MouseEvent): any { - isHovering = false; - hoverOptions = undefined; - tokenSource.dispose(true); - mouseLeaveDisposable.dispose(); - mouseDownDisposable.dispose(); + if ((e.type === dom.EventType.MOUSE_DOWN) || (e).fromElement === htmlElement) { + hoverDisposable?.dispose(); + hoverDisposable = undefined; + isHovering = false; + hoverOptions = undefined; + tokenSource.dispose(true); + mouseLeaveDisposable.dispose(); + mouseDownDisposable.dispose(); + } } const mouseLeaveDisposable = domEvent(htmlElement, dom.EventType.MOUSE_LEAVE, true)(mouseLeaveOrDown.bind(htmlElement)); const mouseDownDisposable = domEvent(htmlElement, dom.EventType.MOUSE_DOWN, true)(mouseLeaveOrDown.bind(htmlElement)); @@ -257,7 +263,7 @@ export class IconLabel extends Disposable { target, anchorPosition: AnchorPosition.BELOW }; - IconLabel.adjustXAndShowCustomHover(hoverOptions, mouseX, hoverDelegate, isHovering); + hoverDisposable = IconLabel.adjustXAndShowCustomHover(hoverOptions, mouseX, hoverDelegate, isHovering); const resolvedTooltip = (await tooltip(tokenSource.token)) ?? (!isString(markdownTooltip) ? markdownTooltip.markdownNotSupportedFallback : undefined); if (resolvedTooltip) { @@ -267,11 +273,13 @@ export class IconLabel extends Disposable { anchorPosition: AnchorPosition.BELOW }; // awaiting the tooltip could take a while. Make sure we're still hovering. - IconLabel.adjustXAndShowCustomHover(hoverOptions, mouseX, hoverDelegate, isHovering); - } else { - hoverDelegate.hideHover(); + hoverDisposable = IconLabel.adjustXAndShowCustomHover(hoverOptions, mouseX, hoverDelegate, isHovering); + } else if (hoverDisposable) { + hoverDisposable.dispose(); + hoverDisposable = undefined; } } + } mouseMoveDisposable.dispose(); }, hoverDelay); diff --git a/src/vs/base/browser/ui/iconLabel/iconLabels.ts b/src/vs/base/browser/ui/iconLabel/iconLabels.ts index 1460c8e15..103bca257 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabels.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabels.ts @@ -6,8 +6,7 @@ import * as dom from 'vs/base/browser/dom'; import { CSSIcon } from 'vs/base/common/codicons'; -const labelWithIconsRegex = /(\\)?\$\(([a-z\-]+(?:~[a-z\-]+)?)\)/gi; - +const labelWithIconsRegex = new RegExp(`(\\\\)?\\$\\((${CSSIcon.iconNameExpression}(?:${CSSIcon.iconModifierExpression})?)\\)`, 'g'); export function renderLabelWithIcons(text: string): Array { const elements = new Array(); let match: RegExpMatchArray | null; diff --git a/src/vs/base/browser/ui/inputbox/inputBox.css b/src/vs/base/browser/ui/inputbox/inputBox.css index f6640a7f8..2279df6c2 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.css +++ b/src/vs/base/browser/ui/inputbox/inputBox.css @@ -89,7 +89,6 @@ padding: 0.4em; font-size: 12px; line-height: 17px; - min-height: 34px; margin-top: -1px; word-wrap: break-word; } diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index c726b9b76..f9ec404f6 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -292,6 +292,9 @@ export class InputBox extends Widget { if (range) { this.input.setSelectionRange(range.start, range.end); + if (range.end === this.input.value.length) { + this.input.scrollLeft = this.input.scrollWidth; + } } } diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index 4f8e796f2..6ea7b6471 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -15,7 +15,7 @@ export interface IListVirtualDelegate { } export interface IListRenderer { - templateId: string; + readonly templateId: string; renderTemplate(container: HTMLElement): TTemplateData; renderElement(element: T, index: number, templateData: TTemplateData, height: number | undefined): void; disposeElement?(element: T, index: number, templateData: TTemplateData, height: number | undefined): void; diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 9bc49fdce..840d53cb5 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -64,6 +64,7 @@ export interface IListViewOptions extends IListViewOptionsUpdate { readonly mouseSupport?: boolean; readonly accessibilityProvider?: IListViewAccessibilityProvider; readonly transformOptimization?: boolean; + readonly alwaysConsumeMouseWheel?: boolean; } const DefaultOptions = { @@ -80,7 +81,8 @@ const DefaultOptions = { drop() { } }, horizontalScrolling: false, - transformOptimization: true + transformOptimization: true, + alwaysConsumeMouseWheel: true, }; export class ElementsDragAndDropData implements IDragAndDropData { @@ -327,6 +329,7 @@ export class ListView implements ISpliceable, IDisposable { this.scrollable = new Scrollable(getOrDefault(options, o => o.smoothScrolling, false) ? 125 : 0, cb => scheduleAtNextAnimationFrame(cb)); this.scrollableElement = this.disposables.add(new SmoothScrollableElement(this.rowsContainer, { + alwaysConsumeMouseWheel: getOrDefault(options, o => o.alwaysConsumeMouseWheel, DefaultOptions.alwaysConsumeMouseWheel), horizontal: ScrollbarVisibility.Auto, vertical: getOrDefault(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode), useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows), @@ -433,7 +436,7 @@ export class ListView implements ISpliceable, IDisposable { const removeRange = Range.intersect(previousRenderRange, deleteRange); // try to reuse rows, avoid removing them from DOM - const rowsToDispose = new Map(); + const rowsToDispose = new Map(); for (let i = removeRange.start; i < removeRange.end; i++) { const item = this.items[i]; item.dragStartDisposable.dispose(); @@ -446,7 +449,13 @@ export class ListView implements ISpliceable, IDisposable { rowsToDispose.set(item.templateId, rows); } - rows.push([item.row, item.element, i, item.size]); + const renderer = this.renderers.get(item.templateId); + + if (renderer && renderer.disposeElement) { + renderer.disposeElement(item.element, i, item.row.templateData, item.size); + } + + rows.push(item.row); } item.row = null; @@ -476,8 +485,8 @@ export class ListView implements ISpliceable, IDisposable { if (start === 0 && deleteCount >= this.items.length) { this.rangeMap = new RangeMap(); this.rangeMap.splice(0, 0, inserted); + deleted = this.items; this.items = inserted; - deleted = []; } else { this.rangeMap.splice(start, deleteCount, inserted); deleted = this.items.splice(start, deleteCount, ...inserted); @@ -509,31 +518,13 @@ export class ListView implements ISpliceable, IDisposable { for (let i = range.start; i < range.end; i++) { const item = this.items[i]; const rows = rowsToDispose.get(item.templateId); - const rowData = rows?.pop(); - - if (!rowData) { - this.insertItemInDOM(i, beforeElement); - } else { - const [row, element, index, size] = rowData; - const renderer = this.renderers.get(item.templateId); - - if (renderer && renderer.disposeElement) { - renderer.disposeElement(element, index, row.templateData, size); - } - - this.insertItemInDOM(i, beforeElement, row); - } + const row = rows?.pop(); + this.insertItemInDOM(i, beforeElement, row); } } - for (const [templateId, rows] of rowsToDispose) { - for (const [row, element, index, size] of rows) { - const renderer = this.renderers.get(templateId); - - if (renderer && renderer.disposeElement) { - renderer.disposeElement(element, index, row.templateData, size); - } - + for (const rows of rowsToDispose.values()) { + for (const row of rows) { this.cache.release(row); } } diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index e530b43d3..6963f8935 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -13,7 +13,7 @@ import { Gesture } from 'vs/base/browser/touch'; import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Event, Emitter, EventBufferer } from 'vs/base/common/event'; -import { domEvent } from 'vs/base/browser/event'; +import { domEvent, stopEvent } from 'vs/base/browser/event'; import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction, ListError, IKeyboardNavigationDelegate } from './list'; import { ListView, IListViewOptions, IListViewDragAndDrop, IListViewAccessibilityProvider, IListViewOptionsUpdate } from './listView'; import { Color } from 'vs/base/common/color'; @@ -761,6 +761,11 @@ export class DefaultStyleController implements IStyleController { `); } + if (styles.listInactiveFocusForeground) { + content.push(`.monaco-list${suffix} .monaco-list-row.focused { color: ${styles.listInactiveFocusForeground}; }`); + content.push(`.monaco-list${suffix} .monaco-list-row.focused:hover { color: ${styles.listInactiveFocusForeground}; }`); // overwrite :hover style in this case! + } + if (styles.listInactiveFocusBackground) { content.push(`.monaco-list${suffix} .monaco-list-row.focused { background-color: ${styles.listInactiveFocusBackground}; }`); content.push(`.monaco-list${suffix} .monaco-list-row.focused:hover { background-color: ${styles.listInactiveFocusBackground}; }`); // overwrite :hover style in this case! @@ -776,7 +781,7 @@ export class DefaultStyleController implements IStyleController { } if (styles.listHoverBackground) { - content.push(`.monaco-list${suffix}:not(.drop-target) .monaco-list-row:hover:not(.selected):not(.focused) { background-color: ${styles.listHoverBackground}; }`); + content.push(`.monaco-list${suffix}:not(.drop-target) .monaco-list-row:hover:not(.selected):not(.focused) { background-color: ${styles.listHoverBackground}; }`); } if (styles.listHoverForeground) { @@ -826,6 +831,14 @@ export class DefaultStyleController implements IStyleController { content.push(`.monaco-list-type-filter { box-shadow: 1px 1px 1px ${styles.listMatchesShadow}; }`); } + if (styles.tableColumnsBorder) { + content.push(` + .monaco-table:hover > .monaco-split-view2, + .monaco-table:hover > .monaco-split-view2 .monaco-sash.vertical::before { + border-color: ${styles.tableColumnsBorder}; + }`); + } + this.styleElement.textContent = content.join('\n'); } } @@ -854,6 +867,7 @@ export interface IListOptions { readonly additionalScrollHeight?: number; readonly transformOptimization?: boolean; readonly smoothScrolling?: boolean; + readonly alwaysConsumeMouseWheel?: boolean; } export interface IListStyles { @@ -866,6 +880,7 @@ export interface IListStyles { listFocusAndSelectionForeground?: Color; listInactiveSelectionBackground?: Color; listInactiveSelectionForeground?: Color; + listInactiveFocusForeground?: Color; listInactiveFocusBackground?: Color; listHoverBackground?: Color; listHoverForeground?: Color; @@ -879,6 +894,7 @@ export interface IListStyles { listFilterWidgetNoMatchesOutline?: Color; listMatchesShadow?: Color; treeIndentGuidesStroke?: Color; + tableColumnsBorder?: Color; } const defaultStyles: IListStyles = { @@ -890,7 +906,8 @@ const defaultStyles: IListStyles = { listInactiveSelectionBackground: Color.fromHex('#3F3F46'), listHoverBackground: Color.fromHex('#2A2D2E'), listDropBackground: Color.fromHex('#383B3D'), - treeIndentGuidesStroke: Color.fromHex('#a9a9a9') + treeIndentGuidesStroke: Color.fromHex('#a9a9a9'), + tableColumnsBorder: Color.fromHex('#cccccc').transparent(0.2) }; const DefaultOptions: IListOptions = { @@ -1148,35 +1165,43 @@ export class List implements ISpliceable, IThemable, IDisposable { get onTouchStart(): Event> { return this.view.onTouchStart; } get onTap(): Event> { return this.view.onTap; } - private didJustPressContextMenuKey: boolean = false; + /** + * Possible context menu trigger events: + * - ContextMenu key + * - Shift F10 + * - Ctrl Option Shift M (macOS with VoiceOver) + * - Mouse right click + */ @memoize get onContextMenu(): Event> { - const fromKeydown = Event.chain(domEvent(this.view.domNode, 'keydown')) + let didJustPressContextMenuKey = false; + + const fromKeyDown = Event.chain(domEvent(this.view.domNode, 'keydown')) .map(e => new StandardKeyboardEvent(e)) - .filter(e => this.didJustPressContextMenuKey = e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10)) - .filter(e => { e.preventDefault(); e.stopPropagation(); return false; }) + .filter(e => didJustPressContextMenuKey = e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10)) + .map(stopEvent) + .filter(() => false) .event as Event; - const fromKeyup = Event.chain(domEvent(this.view.domNode, 'keyup')) - .filter(() => { - const didJustPressContextMenuKey = this.didJustPressContextMenuKey; - this.didJustPressContextMenuKey = false; - return didJustPressContextMenuKey; - }) - .filter(() => this.getFocus().length > 0 && !!this.view.domElement(this.getFocus()[0])) - .map(browserEvent => { - const index = this.getFocus()[0]; - const element = this.view.element(index); - const anchor = this.view.domElement(index) as HTMLElement; + const fromKeyUp = Event.chain(domEvent(this.view.domNode, 'keyup')) + .forEach(() => didJustPressContextMenuKey = false) + .map(e => new StandardKeyboardEvent(e)) + .filter(e => e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10)) + .map(stopEvent) + .map(({ browserEvent }) => { + const focus = this.getFocus(); + const index = focus.length ? focus[0] : undefined; + const element = typeof index !== 'undefined' ? this.view.element(index) : undefined; + const anchor = typeof index !== 'undefined' ? this.view.domElement(index) as HTMLElement : this.view.domNode; return { index, element, anchor, browserEvent }; }) .event; const fromMouse = Event.chain(this.view.onContextMenu) - .filter(() => !this.didJustPressContextMenuKey) + .filter(_ => !didJustPressContextMenuKey) .map(({ element, index, browserEvent }) => ({ element, index, anchor: { x: browserEvent.clientX + 1, y: browserEvent.clientY }, browserEvent })) .event; - return Event.any>(fromKeydown, fromKeyup, fromMouse); + return Event.any>(fromKeyDown, fromKeyUp, fromMouse); } get onKeyDown(): Event { return domEvent(this.view.domNode, 'keydown'); } @@ -1380,7 +1405,7 @@ export class List implements ISpliceable, IThemable, IDisposable { } domFocus(): void { - this.view.domNode.focus(); + this.view.domNode.focus({ preventScroll: true }); } layout(height?: number, width?: number): void { diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 947ba2708..21ef5090c 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -5,10 +5,10 @@ import * as nls from 'vs/nls'; import * as strings from 'vs/base/common/strings'; -import { IActionRunner, IAction, SubmenuAction, Separator, IActionViewItemProvider, EmptySubmenuAction } from 'vs/base/common/actions'; -import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionRunner, IAction, SubmenuAction, Separator, EmptySubmenuAction } from 'vs/base/common/actions'; +import { ActionBar, ActionsOrientation, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes'; -import { EventType, EventHelper, EventLike, removeTabIndexAndUpdateFocus, isAncestor, addDisposableListener, append, $, clearNode, createStyleSheet, isInShadowDOM, getActiveElement, Dimension, IDomNodePagePosition } from 'vs/base/browser/dom'; +import { EventType, EventHelper, EventLike, isAncestor, addDisposableListener, append, $, clearNode, createStyleSheet, isInShadowDOM, getActiveElement, Dimension, IDomNodePagePosition } from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { RunOnceScheduler } from 'vs/base/common/async'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -86,6 +86,7 @@ export class Menu extends ActionBar { context: options.context, actionRunner: options.actionRunner, ariaLabel: options.ariaLabel, + focusOnlyEnabledItems: true, triggerKeys: { keys: [KeyCode.Enter, ...(isMacintosh || isLinux ? [KeyCode.Space] : [])], keyDown: true } }); @@ -617,20 +618,23 @@ class BaseMenuActionViewItem extends BaseActionViewItem { if (this.getAction().enabled) { if (this.element) { this.element.classList.remove('disabled'); + this.element.removeAttribute('aria-disabled'); } if (this.item) { this.item.classList.remove('disabled'); + this.item.removeAttribute('aria-disabled'); this.item.tabIndex = 0; } } else { if (this.element) { this.element.classList.add('disabled'); + this.element.setAttribute('aria-disabled', 'true'); } if (this.item) { this.item.classList.add('disabled'); - removeTabIndexAndUpdateFocus(this.item); + this.item.setAttribute('aria-disabled', 'true'); } } } diff --git a/src/vs/base/browser/ui/menu/menubar.css b/src/vs/base/browser/ui/menu/menubar.css index bfa79a1c5..e6aa5f4dd 100644 --- a/src/vs/base/browser/ui/menu/menubar.css +++ b/src/vs/base/browser/ui/menu/menubar.css @@ -42,7 +42,7 @@ } .menubar .menubar-menu-items-holder { - position: absolute; + position: fixed; left: 0px; opacity: 1; z-index: 2000; diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 6a304f9ff..7ff135971 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -746,7 +746,7 @@ export class MenuBar extends Disposable { private setUnfocusedState(): void { if (this.options.visibility === 'toggle' || this.options.visibility === 'hidden') { this.focusState = MenubarState.HIDDEN; - } else if (this.options.visibility === 'default' && browser.isFullscreen()) { + } else if (this.options.visibility === 'classic' && browser.isFullscreen()) { this.focusState = MenubarState.HIDDEN; } else { this.focusState = MenubarState.VISIBLE; @@ -838,6 +838,22 @@ export class MenuBar extends Disposable { this._mnemonicsInUse = value; } + private get shouldAltKeyFocus(): boolean { + if (isMacintosh) { + return false; + } + + if (!this.options.disableAltFocus) { + return true; + } + + if (this.options.visibility === 'toggle') { + return true; + } + + return false; + } + public get onVisibilityChange(): Event { return this._onVisibilityChange.event; } @@ -869,7 +885,7 @@ export class MenuBar extends Disposable { } // Prevent alt-key default if the menu is not hidden and we use alt to focus - if (modifierKeyStatus.event && !this.options.disableAltFocus) { + if (modifierKeyStatus.event && this.shouldAltKeyFocus) { if (ScanCodeUtils.toEnum(modifierKeyStatus.event.code) === ScanCode.AltLeft) { modifierKeyStatus.event.preventDefault(); } @@ -885,7 +901,7 @@ export class MenuBar extends Disposable { // Clean alt key press and release if (allModifiersReleased && modifierKeyStatus.lastKeyPressed === 'alt' && modifierKeyStatus.lastKeyReleased === 'alt') { if (!this.awaitingAltRelease) { - if (!this.isFocused && !(this.options.disableAltFocus && this.options.visibility !== 'toggle')) { + if (!this.isFocused && this.shouldAltKeyFocus) { this.mnemonicsInUse = true; this.focusedMenu = { index: this.numMenusShown > 0 ? 0 : MenuBar.OVERFLOW_INDEX }; this.focusState = MenubarState.FOCUSED; diff --git a/src/vs/base/browser/ui/sash/sash.css b/src/vs/base/browser/ui/sash/sash.css index db8977d69..08235121a 100644 --- a/src/vs/base/browser/ui/sash/sash.css +++ b/src/vs/base/browser/ui/sash/sash.css @@ -60,8 +60,7 @@ height: var(--sash-size); } -.monaco-sash:not(.disabled).orthogonal-start::before, -.monaco-sash:not(.disabled).orthogonal-end::after { +.monaco-sash:not(.disabled) > .orthogonal-drag-handle { content: " "; height: calc(var(--sash-size) * 2); width: calc(var(--sash-size) * 2); @@ -71,30 +70,34 @@ position: absolute; } -.monaco-sash.horizontal.orthogonal-edge-north:not(.disabled).orthogonal-start::before, -.monaco-sash.horizontal.orthogonal-edge-south:not(.disabled).orthogonal-end::after { +.monaco-sash.horizontal.orthogonal-edge-north:not(.disabled) + > .orthogonal-drag-handle.start, +.monaco-sash.horizontal.orthogonal-edge-south:not(.disabled) + > .orthogonal-drag-handle.end { cursor: nwse-resize; } -.monaco-sash.horizontal.orthogonal-edge-north:not(.disabled).orthogonal-end::after, -.monaco-sash.horizontal.orthogonal-edge-south:not(.disabled).orthogonal-start::before { +.monaco-sash.horizontal.orthogonal-edge-north:not(.disabled) + > .orthogonal-drag-handle.end, +.monaco-sash.horizontal.orthogonal-edge-south:not(.disabled) + > .orthogonal-drag-handle.start { cursor: nesw-resize; } -.monaco-sash.orthogonal-start.vertical::before { - left: -calc(var(--sash-size) / 2); +.monaco-sash.vertical > .orthogonal-drag-handle.start { + left: calc(var(--sash-size) / -2); top: calc(var(--sash-size) * -1); } -.monaco-sash.orthogonal-end.vertical::after { - left: -calc(var(--sash-size) / 2); +.monaco-sash.vertical > .orthogonal-drag-handle.end { + left: calc(var(--sash-size) / -2); bottom: calc(var(--sash-size) * -1); } -.monaco-sash.orthogonal-start.horizontal::before { - top: -calc(var(--sash-size) / 2); +.monaco-sash.horizontal > .orthogonal-drag-handle.start { + top: calc(var(--sash-size) / -2); left: calc(var(--sash-size) * -1); } -.monaco-sash.orthogonal-end.horizontal::after { - top: -calc(var(--sash-size) / 2); +.monaco-sash.horizontal > .orthogonal-drag-handle.end { + top: calc(var(--sash-size) / -2); right: calc(var(--sash-size) * -1); } @@ -113,7 +116,6 @@ background: rgba(0, 255, 255, 0.2); } -.monaco-sash.debug:not(.disabled).orthogonal-start::before, -.monaco-sash.debug:not(.disabled).orthogonal-end::after { +.monaco-sash.debug:not(.disabled) > .orthogonal-drag-handle { background: red; } diff --git a/src/vs/base/browser/ui/sash/sash.ts b/src/vs/base/browser/ui/sash/sash.ts index fa0aa709d..b87068707 100644 --- a/src/vs/base/browser/ui/sash/sash.ts +++ b/src/vs/base/browser/ui/sash/sash.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./sash'; -import { IDisposable, dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { isMacintosh } from 'vs/base/common/platform'; import * as types from 'vs/base/common/types'; import { EventType, GestureEvent, Gesture } from 'vs/base/browser/touch'; @@ -12,8 +12,10 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { Event, Emitter } from 'vs/base/common/event'; import { getElementsByTagName, EventHelper, createStyleSheet, addDisposableListener, append, $ } from 'vs/base/browser/dom'; import { domEvent } from 'vs/base/browser/event'; +import { Delayer } from 'vs/base/common/async'; -const DEBUG = false; +let DEBUG = false; +// DEBUG = Boolean("true"); // done "weirdly" so that a lint warning prevents you from pushing this export interface ISashLayoutProvider { } @@ -86,6 +88,7 @@ export class Sash extends Disposable { private hidden: boolean; private orientation!: Orientation; private size: number; + private hoverDelayer = this._register(new Delayer(300)); private _state: SashState = SashState.Enabled; get state(): SashState { return this._state; } @@ -121,15 +124,29 @@ export class Sash extends Disposable { private readonly orthogonalStartSashDisposables = this._register(new DisposableStore()); private _orthogonalStartSash: Sash | undefined; + private readonly orthogonalStartDragHandleDisposables = this._register(new DisposableStore()); + private _orthogonalStartDragHandle: HTMLElement | undefined; get orthogonalStartSash(): Sash | undefined { return this._orthogonalStartSash; } set orthogonalStartSash(sash: Sash | undefined) { + this.orthogonalStartDragHandleDisposables.clear(); this.orthogonalStartSashDisposables.clear(); if (sash) { - this.orthogonalStartSashDisposables.add(sash.onDidEnablementChange(this.onOrthogonalStartSashEnablementChange, this)); - this.onOrthogonalStartSashEnablementChange(sash.state); - } else { - this.onOrthogonalStartSashEnablementChange(SashState.Disabled); + const onChange = (state: SashState) => { + this.orthogonalStartDragHandleDisposables.clear(); + + if (state !== SashState.Disabled) { + this._orthogonalStartDragHandle = append(this.el, $('.orthogonal-drag-handle.start')); + this.orthogonalStartDragHandleDisposables.add(toDisposable(() => this._orthogonalStartDragHandle!.remove())); + domEvent(this._orthogonalStartDragHandle, 'mouseenter') + (() => Sash.onMouseEnter(sash), undefined, this.orthogonalStartDragHandleDisposables); + domEvent(this._orthogonalStartDragHandle, 'mouseleave') + (() => Sash.onMouseLeave(sash), undefined, this.orthogonalStartDragHandleDisposables); + } + }; + + this.orthogonalStartSashDisposables.add(sash.onDidEnablementChange(onChange, this)); + onChange(sash.state); } this._orthogonalStartSash = sash; @@ -137,15 +154,29 @@ export class Sash extends Disposable { private readonly orthogonalEndSashDisposables = this._register(new DisposableStore()); private _orthogonalEndSash: Sash | undefined; + private readonly orthogonalEndDragHandleDisposables = this._register(new DisposableStore()); + private _orthogonalEndDragHandle: HTMLElement | undefined; get orthogonalEndSash(): Sash | undefined { return this._orthogonalEndSash; } set orthogonalEndSash(sash: Sash | undefined) { + this.orthogonalEndDragHandleDisposables.clear(); this.orthogonalEndSashDisposables.clear(); if (sash) { - this.orthogonalEndSashDisposables.add(sash.onDidEnablementChange(this.onOrthogonalEndSashEnablementChange, this)); - this.onOrthogonalEndSashEnablementChange(sash.state); - } else { - this.onOrthogonalEndSashEnablementChange(SashState.Disabled); + const onChange = (state: SashState) => { + this.orthogonalEndDragHandleDisposables.clear(); + + if (state !== SashState.Disabled) { + this._orthogonalEndDragHandle = append(this.el, $('.orthogonal-drag-handle.end')); + this.orthogonalEndDragHandleDisposables.add(toDisposable(() => this._orthogonalEndDragHandle!.remove())); + domEvent(this._orthogonalEndDragHandle, 'mouseenter') + (() => Sash.onMouseEnter(sash), undefined, this.orthogonalEndDragHandleDisposables); + domEvent(this._orthogonalEndDragHandle, 'mouseleave') + (() => Sash.onMouseLeave(sash), undefined, this.orthogonalEndDragHandleDisposables); + } + }; + + this.orthogonalEndSashDisposables.add(sash.onDidEnablementChange(onChange, this)); + onChange(sash.state); } this._orthogonalEndSash = sash; @@ -168,6 +199,8 @@ export class Sash extends Disposable { this._register(domEvent(this.el, 'mousedown')(this.onMouseDown, this)); this._register(domEvent(this.el, 'dblclick')(this.onMouseDoubleClick, this)); + this._register(domEvent(this.el, 'mouseenter')(() => Sash.onMouseEnter(this))); + this._register(domEvent(this.el, 'mouseleave')(() => Sash.onMouseLeave(this))); this._register(Gesture.addTarget(this.el)); this._register(domEvent(this.el, EventType.Start)(this.onTouchStart, this)); @@ -359,12 +392,34 @@ export class Sash extends Disposable { } })); - listeners.push(addDisposableListener(this.el, EventType.End, (event: GestureEvent) => { + listeners.push(addDisposableListener(this.el, EventType.End, () => { this._onDidEnd.fire(); dispose(listeners); })); } + private static onMouseEnter(sash: Sash, fromLinkedSash: boolean = false): void { + if (sash.el.classList.contains('active')) { + sash.hoverDelayer.cancel(); + sash.el.classList.add('hover'); + } else { + sash.hoverDelayer.trigger(() => sash.el.classList.add('hover')); + } + + if (!fromLinkedSash && sash.linkedSash) { + Sash.onMouseEnter(sash.linkedSash, true); + } + } + + private static onMouseLeave(sash: Sash, fromLinkedSash: boolean = false): void { + sash.hoverDelayer.cancel(); + sash.el.classList.remove('hover'); + + if (!fromLinkedSash && sash.linkedSash) { + Sash.onMouseLeave(sash.linkedSash, true); + } + } + layout(): void { if (this.orientation === Orientation.VERTICAL) { const verticalProvider = (this.layoutProvider); @@ -407,27 +462,13 @@ export class Sash extends Disposable { return this.hidden; } - private onOrthogonalStartSashEnablementChange(state: SashState): void { - this.el.classList.toggle('orthogonal-start', state !== SashState.Disabled); - } - - private onOrthogonalEndSashEnablementChange(state: SashState): void { - this.el.classList.toggle('orthogonal-end', state !== SashState.Disabled); - } - private getOrthogonalSash(e: MouseEvent): Sash | undefined { - if (this.orientation === Orientation.VERTICAL) { - if (e.offsetY <= this.size) { - return this.orthogonalStartSash; - } else if (e.offsetY >= this.el.clientHeight - this.size) { - return this.orthogonalEndSash; - } - } else { - if (e.offsetX <= this.size) { - return this.orthogonalStartSash; - } else if (e.offsetX >= this.el.clientWidth - this.size) { - return this.orthogonalEndSash; - } + if (!e.target || !(e.target instanceof HTMLElement)) { + return undefined; + } + + if (e.target.classList.contains('orthogonal-drag-handle')) { + return e.target.classList.contains('start') ? this.orthogonalStartSash : this.orthogonalEndSash; } return undefined; diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index 841f2ee44..9a2fab22e 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -736,8 +736,8 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Enter).on(e => this.onEnter(e), this)); this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Escape).on(e => this.onEscape(e), this)); - this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.UpArrow).on(this.onUpArrow, this)); - this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.DownArrow).on(this.onDownArrow, this)); + this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.UpArrow).on(e => this.onUpArrow(e), this)); + this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.DownArrow).on(e => this.onDownArrow(e), this)); this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.PageDown).on(this.onPageDown, this)); this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.PageUp).on(this.onPageUp, this)); this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Home).on(this.onHome, this)); @@ -916,8 +916,9 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi } // List navigation - have to handle a disabled option (jump over) - private onDownArrow(): void { + private onDownArrow(e: StandardKeyboardEvent): void { if (this.selected < this.options.length - 1) { + dom.EventHelper.stop(e, true); // Skip disabled options const nextOptionDisabled = this.options[this.selected + 1].isDisabled; @@ -937,8 +938,9 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi } } - private onUpArrow(): void { + private onUpArrow(e: StandardKeyboardEvent): void { if (this.selected > 0) { + dom.EventHelper.stop(e, true); // Skip disabled options const previousOptionDisabled = this.options[this.selected - 1].isDisabled; if (previousOptionDisabled && this.selected > 1) { diff --git a/src/vs/base/browser/ui/splitview/paneview.css b/src/vs/base/browser/ui/splitview/paneview.css index 3ffad073b..a7d76d810 100644 --- a/src/vs/base/browser/ui/splitview/paneview.css +++ b/src/vs/base/browser/ui/splitview/paneview.css @@ -54,6 +54,7 @@ /* TODO: actions should be part of the pane, but they aren't yet */ .monaco-pane-view .pane:hover > .pane-header.expanded > .actions, +.monaco-pane-view .pane:focus-within > .pane-header.expanded > .actions, .monaco-pane-view .pane > .pane-header.actions-always-visible.expanded > .actions, .monaco-pane-view .pane > .pane-header.focused.expanded > .actions { display: initial; diff --git a/src/vs/base/browser/ui/splitview/paneview.ts b/src/vs/base/browser/ui/splitview/paneview.ts index 673094b0c..15bdc774f 100644 --- a/src/vs/base/browser/ui/splitview/paneview.ts +++ b/src/vs/base/browser/ui/splitview/paneview.ts @@ -432,7 +432,7 @@ export class PaneView extends Disposable { private dnd: IPaneDndController | undefined; private dndContext: IDndContext = { draggable: null }; - private el: HTMLElement; + readonly element: HTMLElement; private paneItems: IPaneItem[] = []; private orthogonalSize: number = 0; private size: number = 0; @@ -450,8 +450,8 @@ export class PaneView extends Disposable { this.dnd = options.dnd; this.orientation = options.orientation ?? Orientation.VERTICAL; - this.el = append(container, $('.monaco-pane-view')); - this.splitview = this._register(new SplitView(this.el, { orientation: this.orientation })); + this.element = append(container, $('.monaco-pane-view')); + this.splitview = this._register(new SplitView(this.element, { orientation: this.orientation })); this.onDidSashChange = this.splitview.onDidSashChange; } @@ -534,9 +534,9 @@ export class PaneView extends Disposable { const paneSizes = this.paneItems.map(pane => this.getPaneSize(pane.pane)); this.splitview.dispose(); - clearNode(this.el); + clearNode(this.element); - this.splitview = this._register(new SplitView(this.el, { orientation: this.orientation })); + this.splitview = this._register(new SplitView(this.element, { orientation: this.orientation })); const newOrthogonalSize = this.orientation === Orientation.VERTICAL ? width : height; const newSize = this.orientation === Orientation.HORIZONTAL ? width : height; @@ -560,11 +560,11 @@ export class PaneView extends Disposable { window.clearTimeout(this.animationTimer); } - this.el.classList.add('animated'); + this.element.classList.add('animated'); this.animationTimer = window.setTimeout(() => { this.animationTimer = undefined; - this.el.classList.remove('animated'); + this.element.classList.remove('animated'); }, 200); } diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 8bb893912..40082a227 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -33,6 +33,8 @@ export interface ISplitViewOptions { readonly inverseAltBehavior?: boolean; readonly proportionalLayout?: boolean; // default true, readonly descriptor?: ISplitViewDescriptor; + readonly scrollbarVisibility?: ScrollbarVisibility; + readonly getSashOrthogonalSize?: () => number; } /** @@ -200,7 +202,7 @@ export namespace Sizing { export function Invisible(cachedVisibleSize: number): InvisibleSizing { return { type: 'invisible', cachedVisibleSize }; } } -export interface ISplitViewDescriptor { +export interface ISplitViewDescriptor { size: number; views: { visible?: boolean; @@ -227,6 +229,7 @@ export class SplitView extends Disposable { private state: State = State.Idle; private inverseAltBehavior: boolean; private proportionalLayout: boolean; + private readonly getSashOrthogonalSize: { (): number } | undefined; private _onDidSashChange = this._register(new Emitter()); readonly onDidSashChange = this._onDidSashChange.event; @@ -298,6 +301,7 @@ export class SplitView extends Disposable { this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation; this.inverseAltBehavior = !!options.inverseAltBehavior; this.proportionalLayout = types.isUndefined(options.proportionalLayout) ? true : !!options.proportionalLayout; + this.getSashOrthogonalSize = options.getSashOrthogonalSize; this.el = document.createElement('div'); this.el.classList.add('monaco-split-view2'); @@ -309,8 +313,8 @@ export class SplitView extends Disposable { this.scrollable = new Scrollable(125, scheduleAtNextAnimationFrame); this.scrollableElement = this._register(new SmoothScrollableElement(this.viewContainer, { - vertical: this.orientation === Orientation.VERTICAL ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden, - horizontal: this.orientation === Orientation.HORIZONTAL ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden + vertical: this.orientation === Orientation.VERTICAL ? (options.scrollbarVisibility ?? ScrollbarVisibility.Auto) : ScrollbarVisibility.Hidden, + horizontal: this.orientation === Orientation.HORIZONTAL ? (options.scrollbarVisibility ?? ScrollbarVisibility.Auto) : ScrollbarVisibility.Hidden }, this.scrollable)); this._register(this.scrollableElement.onScroll(e => { @@ -706,17 +710,11 @@ export class SplitView extends Disposable { // Add sash if (this.viewItems.length > 1) { + let opts = { orthogonalStartSash: this.orthogonalStartSash, orthogonalEndSash: this.orthogonalEndSash }; + const sash = this.orientation === Orientation.VERTICAL - ? new Sash(this.sashContainer, { getHorizontalSashTop: (sash: Sash) => this.getSashPosition(sash) }, { - orientation: Orientation.HORIZONTAL, - orthogonalStartSash: this.orthogonalStartSash, - orthogonalEndSash: this.orthogonalEndSash - }) - : new Sash(this.sashContainer, { getVerticalSashLeft: (sash: Sash) => this.getSashPosition(sash) }, { - orientation: Orientation.VERTICAL, - orthogonalStartSash: this.orthogonalStartSash, - orthogonalEndSash: this.orthogonalEndSash - }); + ? new Sash(this.sashContainer, { getHorizontalSashTop: s => this.getSashPosition(s), getHorizontalSashWidth: this.getSashOrthogonalSize }, { ...opts, orientation: Orientation.HORIZONTAL }) + : new Sash(this.sashContainer, { getVerticalSashLeft: s => this.getSashPosition(s), getVerticalSashHeight: this.getSashOrthogonalSize }, { ...opts, orientation: Orientation.VERTICAL }); const sashEventMapper = this.orientation === Orientation.VERTICAL ? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY, alt: e.altKey }) diff --git a/src/vs/base/browser/ui/table/table.css b/src/vs/base/browser/ui/table/table.css new file mode 100644 index 000000000..19ae63929 --- /dev/null +++ b/src/vs/base/browser/ui/table/table.css @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-table { + display: flex; + flex-direction: column; + position: relative; + height: 100%; + width: 100%; + white-space: nowrap; +} + +.monaco-table > .monaco-split-view2 { + border-bottom: 1px solid transparent; +} + +.monaco-table > .monaco-list { + flex: 1; +} + +.monaco-table-tr { + display: flex; +} + +.monaco-table-th { + width: 100%; + height: 100%; + font-weight: bold; + overflow: hidden; + text-overflow: ellipsis; +} + +.monaco-table-th, +.monaco-table-td { + box-sizing: border-box; + padding-left: 10px; + flex-shrink: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.monaco-table-th[data-col-index="0"], +.monaco-table-td[data-col-index="0"] { + padding-left: 20px; +} + +.monaco-table > .monaco-split-view2 .monaco-sash.vertical::before { + content: ""; + position: absolute; + left: calc(var(--sash-size) / 2); + width: 0; + border-left: 1px solid transparent; +} + +.monaco-table > .monaco-split-view2, +.monaco-table > .monaco-split-view2 .monaco-sash.vertical::before { + transition: border-color 0.2s ease-out; +} +/* +.monaco-table:hover > .monaco-split-view2, +.monaco-table:hover > .monaco-split-view2 .monaco-sash.vertical::before { + border-color: rgba(204, 204, 204, 0.2); +} */ diff --git a/src/vs/base/browser/ui/table/table.ts b/src/vs/base/browser/ui/table/table.ts new file mode 100644 index 000000000..2a9786bf4 --- /dev/null +++ b/src/vs/base/browser/ui/table/table.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 { IListContextMenuEvent, IListEvent, IListGestureEvent, IListMouseEvent, IListRenderer, IListTouchEvent } from 'vs/base/browser/ui/list/list'; +import { Event } from 'vs/base/common/event'; + +export interface ITableColumn { + readonly label: string; + readonly tooltip?: string; + readonly weight: number; + readonly templateId: string; + + readonly minimumWidth?: number; + readonly maximumWidth?: number; + readonly onDidChangeWidthConstraints?: Event; + + project(row: TRow): TCell; +} + +export interface ITableVirtualDelegate { + readonly headerRowHeight: number; + getHeight(row: TRow): number; +} + +export interface ITableRenderer extends IListRenderer { } + +export interface ITableEvent extends IListEvent { } +export interface ITableMouseEvent extends IListMouseEvent { } +export interface ITableTouchEvent extends IListTouchEvent { } +export interface ITableGestureEvent extends IListGestureEvent { } +export interface ITableContextMenuEvent extends IListContextMenuEvent { } + +export class TableError extends Error { + + constructor(user: string, message: string) { + super(`TableError [${user}] ${message}`); + } +} diff --git a/src/vs/base/browser/ui/table/tableWidget.ts b/src/vs/base/browser/ui/table/tableWidget.ts new file mode 100644 index 000000000..183200e2e --- /dev/null +++ b/src/vs/base/browser/ui/table/tableWidget.ts @@ -0,0 +1,329 @@ +/*--------------------------------------------------------------------------------------------- + * 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!./table'; +import { IListOptions, IListOptionsUpdate, IListStyles, List } from 'vs/base/browser/ui/list/listWidget'; +import { ITableColumn, ITableContextMenuEvent, ITableEvent, ITableGestureEvent, ITableMouseEvent, ITableRenderer, ITableTouchEvent, ITableVirtualDelegate } from 'vs/base/browser/ui/table/table'; +import { ISpliceable } from 'vs/base/common/sequence'; +import { IThemable } from 'vs/base/common/styler'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { $, append, clearNode, createStyleSheet, getContentHeight, getContentWidth } from 'vs/base/browser/dom'; +import { ISplitViewDescriptor, IView, Orientation, SplitView } from 'vs/base/browser/ui/splitview/splitview'; +import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { Emitter, Event } from 'vs/base/common/event'; +import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable'; + +// TODO@joao +type TCell = any; + +interface RowTemplateData { + readonly container: HTMLElement; + readonly cellContainers: HTMLElement[]; + readonly cellTemplateData: unknown[]; +} + +class TableListRenderer implements IListRenderer { + + static TemplateId = 'row'; + readonly templateId = TableListRenderer.TemplateId; + private renderers: ITableRenderer[]; + private renderedTemplates = new Set(); + + constructor( + private columns: ITableColumn[], + renderers: ITableRenderer[], + private getColumnSize: (index: number) => number + ) { + const rendererMap = new Map(renderers.map(r => [r.templateId, r])); + this.renderers = []; + + for (const column of columns) { + const renderer = rendererMap.get(column.templateId); + + if (!renderer) { + throw new Error(`Table cell renderer for template id ${column.templateId} not found.`); + } + + this.renderers.push(renderer); + } + } + + renderTemplate(container: HTMLElement) { + const rowContainer = append(container, $('.monaco-table-tr')); + const cellContainers: HTMLElement[] = []; + const cellTemplateData: unknown[] = []; + + for (let i = 0; i < this.columns.length; i++) { + const renderer = this.renderers[i]; + const cellContainer = append(rowContainer, $('.monaco-table-td', { 'data-col-index': i })); + + cellContainer.style.width = `${this.getColumnSize(i)}px`; + cellContainers.push(cellContainer); + cellTemplateData.push(renderer.renderTemplate(cellContainer)); + } + + const result = { container, cellContainers, cellTemplateData }; + this.renderedTemplates.add(result); + + return result; + } + + renderElement(element: TRow, index: number, templateData: RowTemplateData, height: number | undefined): void { + for (let i = 0; i < this.columns.length; i++) { + const column = this.columns[i]; + const cell = column.project(element); + const renderer = this.renderers[i]; + renderer.renderElement(cell, index, templateData.cellTemplateData[i], height); + } + } + + disposeElement(element: TRow, index: number, templateData: RowTemplateData, height: number | undefined): void { + for (let i = 0; i < this.columns.length; i++) { + const renderer = this.renderers[i]; + + if (renderer.disposeElement) { + const column = this.columns[i]; + const cell = column.project(element); + + renderer.disposeElement(cell, index, templateData.cellTemplateData[i], height); + } + } + } + + disposeTemplate(templateData: RowTemplateData): void { + for (let i = 0; i < this.columns.length; i++) { + const renderer = this.renderers[i]; + renderer.disposeTemplate(templateData.cellTemplateData[i]); + } + + clearNode(templateData.container); + this.renderedTemplates.delete(templateData); + } + + layoutColumn(index: number, size: number): void { + for (const { cellContainers } of this.renderedTemplates) { + cellContainers[index].style.width = `${size}px`; + } + } +} + +function asListVirtualDelegate(delegate: ITableVirtualDelegate): IListVirtualDelegate { + return { + getHeight(row) { return delegate.getHeight(row); }, + getTemplateId() { return TableListRenderer.TemplateId; }, + }; +} + +class ColumnHeader implements IView { + + readonly element: HTMLElement; + + get minimumSize() { return this.column.minimumWidth ?? 120; } + get maximumSize() { return this.column.maximumWidth ?? Number.POSITIVE_INFINITY; } + get onDidChange() { return this.column.onDidChangeWidthConstraints ?? Event.None; } + + private _onDidLayout = new Emitter<[number, number]>(); + readonly onDidLayout = this._onDidLayout.event; + + constructor(readonly column: ITableColumn, private index: number) { + this.element = $('.monaco-table-th', { 'data-col-index': index, title: column.tooltip }, column.label); + } + + layout(size: number): void { + this._onDidLayout.fire([this.index, size]); + } +} + +export interface ITableOptions extends IListOptions { } +export interface ITableOptionsUpdate extends IListOptionsUpdate { } +export interface ITableStyles extends IListStyles { } + +export class Table implements ISpliceable, IThemable, IDisposable { + + private static InstanceCount = 0; + readonly domId = `table_id_${++Table.InstanceCount}`; + + readonly domNode: HTMLElement; + private splitview: SplitView; + private list: List; + private columnLayoutDisposable: IDisposable; + private cachedHeight: number = 0; + private styleElement: HTMLStyleElement; + + get onDidChangeFocus(): Event> { return this.list.onDidChangeFocus; } + get onDidChangeSelection(): Event> { return this.list.onDidChangeSelection; } + + get onDidScroll(): Event { return this.list.onDidScroll; } + get onMouseClick(): Event> { return this.list.onMouseClick; } + get onMouseDblClick(): Event> { return this.list.onMouseDblClick; } + get onMouseMiddleClick(): Event> { return this.list.onMouseMiddleClick; } + get onPointer(): Event> { return this.list.onPointer; } + get onMouseUp(): Event> { return this.list.onMouseUp; } + get onMouseDown(): Event> { return this.list.onMouseDown; } + get onMouseOver(): Event> { return this.list.onMouseOver; } + get onMouseMove(): Event> { return this.list.onMouseMove; } + get onMouseOut(): Event> { return this.list.onMouseOut; } + get onTouchStart(): Event> { return this.list.onTouchStart; } + get onTap(): Event> { return this.list.onTap; } + get onContextMenu(): Event> { return this.list.onContextMenu; } + + get onDidFocus(): Event { return this.list.onDidFocus; } + get onDidBlur(): Event { return this.list.onDidBlur; } + + get scrollTop(): number { return this.list.scrollTop; } + set scrollTop(scrollTop: number) { this.list.scrollTop = scrollTop; } + get scrollLeft(): number { return this.list.scrollLeft; } + set scrollLeft(scrollLeft: number) { this.list.scrollLeft = scrollLeft; } + get scrollHeight(): number { return this.list.scrollHeight; } + get renderHeight(): number { return this.list.renderHeight; } + get onDidDispose(): Event { return this.list.onDidDispose; } + + constructor( + user: string, + container: HTMLElement, + private virtualDelegate: ITableVirtualDelegate, + columns: ITableColumn[], + renderers: ITableRenderer[], + _options?: ITableOptions + ) { + this.domNode = append(container, $(`.monaco-table.${this.domId}`)); + + const headers = columns.map((c, i) => new ColumnHeader(c, i)); + const descriptor: ISplitViewDescriptor = { + size: headers.reduce((a, b) => a + b.column.weight, 0), + views: headers.map(view => ({ size: view.column.weight, view })) + }; + + this.splitview = new SplitView(this.domNode, { + orientation: Orientation.HORIZONTAL, + scrollbarVisibility: ScrollbarVisibility.Hidden, + getSashOrthogonalSize: () => this.cachedHeight, + descriptor + }); + + this.splitview.el.style.height = `${virtualDelegate.headerRowHeight}px`; + this.splitview.el.style.lineHeight = `${virtualDelegate.headerRowHeight}px`; + + const renderer = new TableListRenderer(columns, renderers, i => this.splitview.getViewSize(i)); + this.list = new List(user, this.domNode, asListVirtualDelegate(virtualDelegate), [renderer], _options); + + this.columnLayoutDisposable = Event.any(...headers.map(h => h.onDidLayout)) + (([index, size]) => renderer.layoutColumn(index, size)); + + this.styleElement = createStyleSheet(this.domNode); + this.style({}); + } + + updateOptions(options: ITableOptionsUpdate): void { + this.list.updateOptions(options); + } + + splice(start: number, deleteCount: number, elements: TRow[] = []): void { + this.list.splice(start, deleteCount, elements); + } + + rerender(): void { + this.list.rerender(); + } + + row(index: number): TRow { + return this.list.element(index); + } + + indexOf(element: TRow): number { + return this.list.indexOf(element); + } + + get length(): number { + return this.list.length; + } + + getHTMLElement(): HTMLElement { + return this.domNode; + } + + layout(height?: number, width?: number): void { + height = height ?? getContentHeight(this.domNode); + width = width ?? getContentWidth(this.domNode); + + this.cachedHeight = height; + this.splitview.layout(width); + this.list.layout(height - this.virtualDelegate.headerRowHeight, width); + } + + toggleKeyboardNavigation(): void { + this.list.toggleKeyboardNavigation(); + } + + style(styles: ITableStyles): void { + const content: string[] = []; + + content.push(`.monaco-table.${this.domId} > .monaco-split-view2 .monaco-sash.vertical::before { + top: ${this.virtualDelegate.headerRowHeight + 1}px; + height: calc(100% - ${this.virtualDelegate.headerRowHeight}px); + }`); + + this.styleElement.textContent = content.join('\n'); + this.list.style(styles); + } + + domFocus(): void { + this.list.domFocus(); + } + + getSelectedElements(): TRow[] { + return this.list.getSelectedElements(); + } + + setSelection(indexes: number[], browserEvent?: UIEvent): void { + this.list.setSelection(indexes, browserEvent); + } + + getSelection(): number[] { + return this.list.getSelection(); + } + + setFocus(indexes: number[], browserEvent?: UIEvent): void { + this.list.setFocus(indexes, browserEvent); + } + + focusNext(n = 1, loop = false, browserEvent?: UIEvent): void { + this.list.focusNext(n, loop, browserEvent); + } + + focusPrevious(n = 1, loop = false, browserEvent?: UIEvent): void { + this.list.focusPrevious(n, loop, browserEvent); + } + + focusNextPage(browserEvent?: UIEvent): void { + this.list.focusNextPage(browserEvent); + } + + focusPreviousPage(browserEvent?: UIEvent): void { + this.list.focusPreviousPage(browserEvent); + } + + focusFirst(browserEvent?: UIEvent): void { + this.list.focusFirst(browserEvent); + } + + focusLast(browserEvent?: UIEvent): void { + this.list.focusLast(browserEvent); + } + + getFocus(): number[] { + return this.list.getFocus(); + } + + reveal(index: number, relativeTop?: number): void { + this.list.reveal(index, relativeTop); + } + + dispose(): void { + this.splitview.dispose(); + this.list.dispose(); + this.columnLayoutDisposable.dispose(); + } +} diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index c1348a93d..8d1c5b6b1 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -5,8 +5,8 @@ import 'vs/css!./toolbar'; import * as nls from 'vs/nls'; -import { Action, IActionRunner, IAction, IActionViewItemProvider, SubmenuAction } from 'vs/base/common/actions'; -import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Action, IActionRunner, IAction, SubmenuAction } from 'vs/base/common/actions'; +import { ActionBar, ActionsOrientation, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 4df19745c..af081547c 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -961,7 +961,7 @@ export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions { readonly filterOnType?: boolean; readonly smoothScrolling?: boolean; readonly horizontalScrolling?: boolean; - readonly expandOnlyOnDoubleClick?: boolean; + readonly expandOnDoubleClick?: boolean; readonly expandOnlyOnTwistieClick?: boolean | ((e: any) => boolean); // e is T } @@ -1121,7 +1121,7 @@ class TreeNodeListMouseController extends MouseController< return super.onViewPointer(e); } - if (this.tree.expandOnlyOnDoubleClick && e.browserEvent.detail !== 2 && !onTwistie) { + if (!this.tree.expandOnDoubleClick && e.browserEvent.detail === 2) { return super.onViewPointer(e); } @@ -1129,6 +1129,7 @@ class TreeNodeListMouseController extends MouseController< const model = ((this.tree as any).model as ITreeModel); // internal const location = model.getNodeLocation(node); const recursive = e.browserEvent.altKey; + this.tree.setFocus([location]); model.setCollapsed(location, undefined, recursive); if (expandOnlyOnTwistieClick && onTwistie) { @@ -1142,7 +1143,7 @@ class TreeNodeListMouseController extends MouseController< protected onDoubleClick(e: IListMouseEvent>): void { const onTwistie = (e.browserEvent.target as HTMLElement).classList.contains('monaco-tl-twistie'); - if (onTwistie) { + if (onTwistie || !this.tree.expandOnDoubleClick) { return; } @@ -1262,8 +1263,8 @@ export abstract class AbstractTree implements IDisposable get filterOnType(): boolean { return !!this._options.filterOnType; } get onDidChangeTypeFilterPattern(): Event { return this.typeFilterController ? this.typeFilterController.onDidChangePattern : Event.None; } - get expandOnlyOnDoubleClick(): boolean { return this._options.expandOnlyOnDoubleClick ?? false; } - get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) { return typeof this._options.expandOnlyOnTwistieClick === 'undefined' ? false : this._options.expandOnlyOnTwistieClick; } + get expandOnDoubleClick(): boolean { return typeof this._options.expandOnDoubleClick === 'undefined' ? true : this._options.expandOnDoubleClick; } + get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) { return typeof this._options.expandOnlyOnTwistieClick === 'undefined' ? true : this._options.expandOnlyOnTwistieClick; } private readonly _onDidUpdateOptions = new Emitter>(); readonly onDidUpdateOptions: Event> = this._onDidUpdateOptions.event; diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index bdc5399f2..51f2c9aea 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -9,7 +9,7 @@ import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOve import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper, ITreeFilter, TreeVisibility, TreeFilterResult } from 'vs/base/browser/ui/tree/tree'; import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; -import { timeout, CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { timeout, CancelablePromise, createCancelablePromise, Promises } from 'vs/base/common/async'; import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; import { Iterable } from 'vs/base/common/iterator'; import { IDragAndDropData } from 'vs/base/browser/dnd'; @@ -740,7 +740,7 @@ export class AsyncDataTree implements IDisposable const childrenToRefresh = await this.doRefreshNode(node, recursive, viewStateContext); node.stale = false; - await Promise.all(childrenToRefresh.map(child => this.doRefreshSubTree(child, recursive, viewStateContext))); + await Promises.settled(childrenToRefresh.map(child => this.doRefreshSubTree(child, recursive, viewStateContext))); } finally { done!(); } @@ -990,16 +990,16 @@ export class AsyncDataTree implements IDisposable const expanded: string[] = []; const root = this.tree.getNode(); - const queue = [root]; + const stack = [root]; - while (queue.length > 0) { - const node = queue.shift()!; + while (stack.length > 0) { + const node = stack.pop()!; if (node !== root && node.collapsible && !node.collapsed) { expanded.push(getId(node.element!.element as T)); } - queue.push(...node.children); + stack.push(...node.children); } return { focus, selection, expanded, scrollTop: this.scrollTop }; diff --git a/src/vs/base/browser/ui/tree/media/tree.css b/src/vs/base/browser/ui/tree/media/tree.css index 06f775d62..2701b559d 100644 --- a/src/vs/base/browser/ui/tree/media/tree.css +++ b/src/vs/base/browser/ui/tree/media/tree.css @@ -56,6 +56,10 @@ overflow: hidden; } +.monaco-tl-twistie::before { + border-radius: 20px; +} + .monaco-tl-twistie.collapsed::before { transform: rotate(-90deg); } diff --git a/src/vs/base/browser/ui/tree/objectTree.ts b/src/vs/base/browser/ui/tree/objectTree.ts index 633d8ca7e..21e655701 100644 --- a/src/vs/base/browser/ui/tree/objectTree.ts +++ b/src/vs/base/browser/ui/tree/objectTree.ts @@ -69,7 +69,7 @@ export class ObjectTree, TFilterData = void> extends this.model.updateElementHeight(element, height); } - resort(element: T, recursive = true): void { + resort(element: T | null, recursive = true): void { this.model.resort(element, recursive); } diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index 41b0fd824..466be3c5c 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -39,19 +39,6 @@ export interface IActionRunner extends IDisposable { readonly onBeforeRun: Event; } -export interface IActionViewItem extends IDisposable { - actionRunner: IActionRunner; - setActionContext(context: any): void; - render(element: any /* HTMLElement */): void; - isEnabled(): boolean; - focus(fromRight?: boolean): void; // TODO@isidorn what is this? - blur(): void; -} - -export interface IActionViewItemProvider { - (action: IAction): IActionViewItem | undefined; -} - export interface IActionChangeEvent { readonly label?: string; readonly tooltip?: string; @@ -205,25 +192,6 @@ export class ActionRunner extends Disposable implements IActionRunner { } } -export class RadioGroup extends Disposable { - - constructor(readonly actions: Action[]) { - super(); - - for (const action of actions) { - this._register(action.onDidChange(e => { - if (e.checked && action.checked) { - for (const candidate of actions) { - if (candidate !== action) { - candidate.checked = false; - } - } - } - })); - } - } -} - export class Separator extends Action { static readonly ID = 'vs.actions.separator'; @@ -235,17 +203,6 @@ export class Separator extends Action { } } -export class ActionWithMenuAction extends Action { - - get actions(): IAction[] { - return this._actions; - } - - constructor(id: string, private _actions: IAction[], label?: string, cssClass?: string, enabled?: boolean, actionCallback?: (event?: any) => Promise) { - super(id, label, cssClass, enabled, actionCallback); - } -} - export class SubmenuAction implements IAction { readonly id: string; diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index b0da12183..28935409f 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -4,13 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import * as errors from 'vs/base/common/errors'; -import { Emitter, Event } from 'vs/base/common/event'; +import { canceled, onUnexpectedError } from 'vs/base/common/errors'; +import { Emitter, Event, Listener } from 'vs/base/common/event'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { LinkedList } from 'vs/base/common/linkedList'; import { URI } from 'vs/base/common/uri'; -export function isThenable(obj: any): obj is Promise { - return obj && typeof (>obj).then === 'function'; +export function isThenable(obj: unknown): obj is Promise { + return !!obj && typeof (obj as unknown as Promise).then === 'function'; } export interface CancelablePromise extends Promise { @@ -23,7 +24,7 @@ export function createCancelablePromise(callback: (token: CancellationToken) const thenable = callback(source.token); const promise = new Promise((resolve, reject) => { source.token.onCancellationRequested(() => { - reject(errors.canceled()); + reject(canceled()); }); Promise.resolve(thenable).then(value => { source.dispose(); @@ -165,10 +166,10 @@ export class Throttler { this.activePromise = promiseFactory(); return new Promise((resolve, reject) => { - this.activePromise!.then((result: any) => { + this.activePromise!.then((result: T) => { this.activePromise = null; resolve(result); - }, (err: any) => { + }, (err: unknown) => { this.activePromise = null; reject(err); }); @@ -178,7 +179,7 @@ export class Throttler { export class Sequencer { - private current: Promise = Promise.resolve(null); + private current: Promise = Promise.resolve(null); queue(promiseTask: ITask>): Promise { return this.current = this.current.then(() => promiseTask(), () => promiseTask()); @@ -187,7 +188,7 @@ export class Sequencer { export class SequencerByKey { - private promiseMap = new Map>(); + private promiseMap = new Map>(); queue(key: TKey, promiseTask: ITask>): Promise { const runningPromise = this.promiseMap.get(key) ?? Promise.resolve(); @@ -282,7 +283,7 @@ export class Delayer implements IDisposable { if (this.completionPromise) { if (this.doReject) { - this.doReject(errors.canceled()); + this.doReject(canceled()); } this.completionPromise = null; } @@ -320,7 +321,7 @@ export class ThrottledDelayer { } trigger(promiseFactory: ITask>, delay?: number): Promise { - return this.delayer.trigger(() => this.throttler.queue(promiseFactory), delay) as any as Promise; + return this.delayer.trigger(() => this.throttler.queue(promiseFactory), delay) as unknown as Promise; } isTriggered(): boolean { @@ -366,6 +367,25 @@ export class Barrier { } } +/** + * A barrier that is initially closed and then becomes opened permanently after a certain period of + * time or when open is called explicitly + */ +export class AutoOpenBarrier extends Barrier { + + private readonly _timeout: any; + + constructor(autoOpenTimeMs: number) { + super(); + this._timeout = setTimeout(() => this.open(), autoOpenTimeMs); + } + + open(): void { + clearTimeout(this._timeout); + super.open(); + } +} + export function timeout(millis: number): CancelablePromise; export function timeout(millis: number, token: CancellationToken): Promise; export function timeout(millis: number, token?: CancellationToken): CancelablePromise | Promise { @@ -377,7 +397,7 @@ export function timeout(millis: number, token?: CancellationToken): CancelablePr const handle = setTimeout(resolve, millis); token.onCancellationRequested(() => { clearTimeout(handle); - reject(errors.canceled()); + reject(canceled()); }); }); } @@ -487,7 +507,7 @@ export function firstParallel(promiseList: Promise[], shouldStop: (t: T) = interface ILimitedTaskFactory { factory: ITask>; c: (value: T | Promise) => void; - e: (error?: any) => void; + e: (error?: unknown) => void; } /** @@ -666,7 +686,7 @@ export class IntervalTimer implements IDisposable { export class RunOnceScheduler { - protected runner: ((...args: any[]) => void) | null; + protected runner: ((...args: unknown[]) => void) | null; private timeoutToken: any; private timeout: number; @@ -826,7 +846,7 @@ export class IdleValue { private _didRun: boolean = false; private _value?: T; - private _error: any; + private _error: unknown; constructor(executor: () => T) { this._executor = () => { @@ -1014,7 +1034,9 @@ export class IntervalCounter { //#endregion -export type ValueCallback = (value: T | Promise) => void; +//#region + +export type ValueCallback = (value: T | Promise) => void; /** * Creates a promise whose resolution or rejection can be controlled imperatively. @@ -1022,7 +1044,7 @@ export type ValueCallback = (value: T | Promise) => void; export class DeferredPromise { private completeCallback!: ValueCallback; - private errorCallback!: (err: any) => void; + private errorCallback!: (err: unknown) => void; private rejected = false; private resolved = false; @@ -1055,7 +1077,7 @@ export class DeferredPromise { }); } - public error(err: any) { + public error(err: unknown) { return new Promise(resolve => { this.errorCallback(err); this.rejected = true; @@ -1065,9 +1087,152 @@ export class DeferredPromise { public cancel() { new Promise(resolve => { - this.errorCallback(errors.canceled()); + this.errorCallback(canceled()); this.rejected = true; resolve(); }); } } + +//#endregion + +//#region + +export interface IWaitUntil { + waitUntil(thenable: Promise): void; +} + +export class AsyncEmitter extends Emitter { + + private _asyncDeliveryQueue?: LinkedList<[Listener, Omit]>; + + async fireAsync(data: Omit, token: CancellationToken, promiseJoin?: (p: Promise, listener: Function) => Promise): Promise { + if (!this._listeners) { + return; + } + + if (!this._asyncDeliveryQueue) { + this._asyncDeliveryQueue = new LinkedList(); + } + + for (const listener of this._listeners) { + this._asyncDeliveryQueue.push([listener, data]); + } + + while (this._asyncDeliveryQueue.size > 0 && !token.isCancellationRequested) { + + const [listener, data] = this._asyncDeliveryQueue.shift()!; + const thenables: Promise[] = []; + + const event = { + ...data, + waitUntil: (p: Promise): void => { + if (Object.isFrozen(thenables)) { + throw new Error('waitUntil can NOT be called asynchronous'); + } + if (promiseJoin) { + p = promiseJoin(p, typeof listener === 'function' ? listener : listener[0]); + } + thenables.push(p); + } + }; + + try { + if (typeof listener === 'function') { + listener.call(undefined, event); + } else { + listener[0].call(listener[1], event); + } + } catch (e) { + onUnexpectedError(e); + continue; + } + + // freeze thenables-collection to enforce sync-calls to + // wait until and then wait for all thenables to resolve + Object.freeze(thenables); + await Promises.settled(thenables).catch(e => onUnexpectedError(e)); + } + } +} + +//#endregion + +//#region Promises + +export namespace Promises { + + export interface IResolvedPromise { + status: 'fulfilled'; + value: T; + } + + export interface IRejectedPromise { + status: 'rejected'; + reason: Error; + } + + /** + * Interface of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled + */ + interface PromiseWithAllSettled { + allSettled(promises: Promise[]): Promise | IRejectedPromise>>; + } + + /** + * A polyfill of `Promise.allSettled`: returns after all promises have + * resolved or rejected and provides access to each result or error + * in the order of the original passed in promises array. + * See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled + */ + export async function allSettled(promises: Promise[]): Promise | IRejectedPromise>> { + if (typeof (Promise as unknown as PromiseWithAllSettled).allSettled === 'function') { + return allSettledNative(promises); // in some environments we can benefit from native implementation + } + + return allSettledShim(promises); + } + + async function allSettledNative(promises: Promise[]): Promise | IRejectedPromise>> { + return (Promise as unknown as PromiseWithAllSettled).allSettled(promises); + } + + async function allSettledShim(promises: Promise[]): Promise | IRejectedPromise>> { + return Promise.all(promises.map(promise => (promise.then(value => { + const fulfilled: IResolvedPromise = { status: 'fulfilled', value }; + + return fulfilled; + }, error => { + const rejected: IRejectedPromise = { status: 'rejected', reason: error }; + + return rejected; + })))); + } + + /** + * A drop-in replacement for `Promise.all` with the only difference + * that the method awaits every promise to either fulfill or reject. + * + * Similar to `Promise.all`, only the first error will be returned + * if any. + */ + export async function settled(promises: Promise[]): Promise { + let firstError: Error | undefined = undefined; + + const result = await Promise.all(promises.map(promise => promise.then(value => value, error => { + if (!firstError) { + firstError = error; + } + + return undefined; // do not rethrow so that other promises can settle + }))); + + if (typeof firstError !== 'undefined') { + throw firstError; + } + + return result as unknown as T[]; // cast is needed and protected by the `throw` above + } +} + +//#endregion diff --git a/src/vs/base/common/buildunit.json b/src/vs/base/common/buildunit.json deleted file mode 100644 index 50e3d7506..000000000 --- a/src/vs/base/common/buildunit.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "vs/base", - "dependencies": [ - { - "name": "vs", - "internal": false - } - ], - "libs": [ - "lib.core.d.ts" - ], - "sources": [ - "**/*.ts" - ], - "declares": [] -} diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index 094951fa8..c6e0d3a96 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -71,21 +71,26 @@ export interface CSSIcon { readonly id: string; } + export namespace CSSIcon { - export const iconIdRegex = /^(codicon\/)?([a-z\-]+)(?:~([a-z\-]+))?$/i; + export const iconNameSegment = '[A-Za-z0-9]+'; + export const iconNameExpression = '[A-Za-z0-9\\-]+'; + export const iconModifierExpression = '~[A-Za-z]+'; + + const cssIconIdRegex = new RegExp(`^(${iconNameExpression})(${iconModifierExpression})?$`); export function asClassNameArray(icon: CSSIcon): string[] { if (icon instanceof Codicon) { return ['codicon', 'codicon-' + icon.id]; } - const match = iconIdRegex.exec(icon.id); + const match = cssIconIdRegex.exec(icon.id); if (!match) { return asClassNameArray(Codicon.error); } - let [, , id, modifier] = match; + let [, id, modifier] = match; const classNames = ['codicon', 'codicon-' + id]; if (modifier) { - classNames.push('codicon-modifier-' + modifier); + classNames.push('codicon-modifier-' + modifier.substr(1)); } return classNames; } @@ -102,443 +107,447 @@ export namespace CSSIcon { interface IconDefinition { - character: string; + fontCharacter: string; } export namespace Codicon { // built-in icons, with image name - export const add = new Codicon('add', { character: '\\ea60' }); - export const plus = new Codicon('plus', { character: '\\ea60' }); - export const gistNew = new Codicon('gist-new', { character: '\\ea60' }); - export const repoCreate = new Codicon('repo-create', { character: '\\ea60' }); - export const lightbulb = new Codicon('lightbulb', { character: '\\ea61' }); - export const lightBulb = new Codicon('light-bulb', { character: '\\ea61' }); - export const repo = new Codicon('repo', { character: '\\ea62' }); - export const repoDelete = new Codicon('repo-delete', { character: '\\ea62' }); - export const gistFork = new Codicon('gist-fork', { character: '\\ea63' }); - export const repoForked = new Codicon('repo-forked', { character: '\\ea63' }); - export const gitPullRequest = new Codicon('git-pull-request', { character: '\\ea64' }); - export const gitPullRequestAbandoned = new Codicon('git-pull-request-abandoned', { character: '\\ea64' }); - export const recordKeys = new Codicon('record-keys', { character: '\\ea65' }); - export const keyboard = new Codicon('keyboard', { character: '\\ea65' }); - export const tag = new Codicon('tag', { character: '\\ea66' }); - export const tagAdd = new Codicon('tag-add', { character: '\\ea66' }); - export const tagRemove = new Codicon('tag-remove', { character: '\\ea66' }); - export const person = new Codicon('person', { character: '\\ea67' }); - export const personAdd = new Codicon('person-add', { character: '\\ea67' }); - export const personFollow = new Codicon('person-follow', { character: '\\ea67' }); - export const personOutline = new Codicon('person-outline', { character: '\\ea67' }); - export const personFilled = new Codicon('person-filled', { character: '\\ea67' }); - export const gitBranch = new Codicon('git-branch', { character: '\\ea68' }); - export const gitBranchCreate = new Codicon('git-branch-create', { character: '\\ea68' }); - export const gitBranchDelete = new Codicon('git-branch-delete', { character: '\\ea68' }); - export const sourceControl = new Codicon('source-control', { character: '\\ea68' }); - export const mirror = new Codicon('mirror', { character: '\\ea69' }); - export const mirrorPublic = new Codicon('mirror-public', { character: '\\ea69' }); - export const star = new Codicon('star', { character: '\\ea6a' }); - export const starAdd = new Codicon('star-add', { character: '\\ea6a' }); - export const starDelete = new Codicon('star-delete', { character: '\\ea6a' }); - export const starEmpty = new Codicon('star-empty', { character: '\\ea6a' }); - export const comment = new Codicon('comment', { character: '\\ea6b' }); - export const commentAdd = new Codicon('comment-add', { character: '\\ea6b' }); - export const alert = new Codicon('alert', { character: '\\ea6c' }); - export const warning = new Codicon('warning', { character: '\\ea6c' }); - export const search = new Codicon('search', { character: '\\ea6d' }); - export const searchSave = new Codicon('search-save', { character: '\\ea6d' }); - export const logOut = new Codicon('log-out', { character: '\\ea6e' }); - export const signOut = new Codicon('sign-out', { character: '\\ea6e' }); - export const logIn = new Codicon('log-in', { character: '\\ea6f' }); - export const signIn = new Codicon('sign-in', { character: '\\ea6f' }); - export const eye = new Codicon('eye', { character: '\\ea70' }); - export const eyeUnwatch = new Codicon('eye-unwatch', { character: '\\ea70' }); - export const eyeWatch = new Codicon('eye-watch', { character: '\\ea70' }); - export const circleFilled = new Codicon('circle-filled', { character: '\\ea71' }); - export const primitiveDot = new Codicon('primitive-dot', { character: '\\ea71' }); - export const closeDirty = new Codicon('close-dirty', { character: '\\ea71' }); - export const debugBreakpoint = new Codicon('debug-breakpoint', { character: '\\ea71' }); - export const debugBreakpointDisabled = new Codicon('debug-breakpoint-disabled', { character: '\\ea71' }); - export const debugHint = new Codicon('debug-hint', { character: '\\ea71' }); - export const primitiveSquare = new Codicon('primitive-square', { character: '\\ea72' }); - export const edit = new Codicon('edit', { character: '\\ea73' }); - export const pencil = new Codicon('pencil', { character: '\\ea73' }); - export const info = new Codicon('info', { character: '\\ea74' }); - export const issueOpened = new Codicon('issue-opened', { character: '\\ea74' }); - export const gistPrivate = new Codicon('gist-private', { character: '\\ea75' }); - export const gitForkPrivate = new Codicon('git-fork-private', { character: '\\ea75' }); - export const lock = new Codicon('lock', { character: '\\ea75' }); - export const mirrorPrivate = new Codicon('mirror-private', { character: '\\ea75' }); - export const close = new Codicon('close', { character: '\\ea76' }); - export const removeClose = new Codicon('remove-close', { character: '\\ea76' }); - export const x = new Codicon('x', { character: '\\ea76' }); - export const repoSync = new Codicon('repo-sync', { character: '\\ea77' }); - export const sync = new Codicon('sync', { character: '\\ea77' }); - export const clone = new Codicon('clone', { character: '\\ea78' }); - export const desktopDownload = new Codicon('desktop-download', { character: '\\ea78' }); - export const beaker = new Codicon('beaker', { character: '\\ea79' }); - export const microscope = new Codicon('microscope', { character: '\\ea79' }); - export const vm = new Codicon('vm', { character: '\\ea7a' }); - export const deviceDesktop = new Codicon('device-desktop', { character: '\\ea7a' }); - export const file = new Codicon('file', { character: '\\ea7b' }); - export const fileText = new Codicon('file-text', { character: '\\ea7b' }); - export const more = new Codicon('more', { character: '\\ea7c' }); - export const ellipsis = new Codicon('ellipsis', { character: '\\ea7c' }); - export const kebabHorizontal = new Codicon('kebab-horizontal', { character: '\\ea7c' }); - export const mailReply = new Codicon('mail-reply', { character: '\\ea7d' }); - export const reply = new Codicon('reply', { character: '\\ea7d' }); - export const organization = new Codicon('organization', { character: '\\ea7e' }); - export const organizationFilled = new Codicon('organization-filled', { character: '\\ea7e' }); - export const organizationOutline = new Codicon('organization-outline', { character: '\\ea7e' }); - export const newFile = new Codicon('new-file', { character: '\\ea7f' }); - export const fileAdd = new Codicon('file-add', { character: '\\ea7f' }); - export const newFolder = new Codicon('new-folder', { character: '\\ea80' }); - export const fileDirectoryCreate = new Codicon('file-directory-create', { character: '\\ea80' }); - export const trash = new Codicon('trash', { character: '\\ea81' }); - export const trashcan = new Codicon('trashcan', { character: '\\ea81' }); - export const history = new Codicon('history', { character: '\\ea82' }); - export const clock = new Codicon('clock', { character: '\\ea82' }); - export const folder = new Codicon('folder', { character: '\\ea83' }); - export const fileDirectory = new Codicon('file-directory', { character: '\\ea83' }); - export const symbolFolder = new Codicon('symbol-folder', { character: '\\ea83' }); - export const logoGithub = new Codicon('logo-github', { character: '\\ea84' }); - export const markGithub = new Codicon('mark-github', { character: '\\ea84' }); - export const github = new Codicon('github', { character: '\\ea84' }); - export const terminal = new Codicon('terminal', { character: '\\ea85' }); - export const console = new Codicon('console', { character: '\\ea85' }); - export const repl = new Codicon('repl', { character: '\\ea85' }); - export const zap = new Codicon('zap', { character: '\\ea86' }); - export const symbolEvent = new Codicon('symbol-event', { character: '\\ea86' }); - export const error = new Codicon('error', { character: '\\ea87' }); - export const stop = new Codicon('stop', { character: '\\ea87' }); - export const variable = new Codicon('variable', { character: '\\ea88' }); - export const symbolVariable = new Codicon('symbol-variable', { character: '\\ea88' }); - export const array = new Codicon('array', { character: '\\ea8a' }); - export const symbolArray = new Codicon('symbol-array', { character: '\\ea8a' }); - export const symbolModule = new Codicon('symbol-module', { character: '\\ea8b' }); - export const symbolPackage = new Codicon('symbol-package', { character: '\\ea8b' }); - export const symbolNamespace = new Codicon('symbol-namespace', { character: '\\ea8b' }); - export const symbolObject = new Codicon('symbol-object', { character: '\\ea8b' }); - export const symbolMethod = new Codicon('symbol-method', { character: '\\ea8c' }); - export const symbolFunction = new Codicon('symbol-function', { character: '\\ea8c' }); - export const symbolConstructor = new Codicon('symbol-constructor', { character: '\\ea8c' }); - export const symbolBoolean = new Codicon('symbol-boolean', { character: '\\ea8f' }); - export const symbolNull = new Codicon('symbol-null', { character: '\\ea8f' }); - export const symbolNumeric = new Codicon('symbol-numeric', { character: '\\ea90' }); - export const symbolNumber = new Codicon('symbol-number', { character: '\\ea90' }); - export const symbolStructure = new Codicon('symbol-structure', { character: '\\ea91' }); - export const symbolStruct = new Codicon('symbol-struct', { character: '\\ea91' }); - export const symbolParameter = new Codicon('symbol-parameter', { character: '\\ea92' }); - export const symbolTypeParameter = new Codicon('symbol-type-parameter', { character: '\\ea92' }); - export const symbolKey = new Codicon('symbol-key', { character: '\\ea93' }); - export const symbolText = new Codicon('symbol-text', { character: '\\ea93' }); - export const symbolReference = new Codicon('symbol-reference', { character: '\\ea94' }); - export const goToFile = new Codicon('go-to-file', { character: '\\ea94' }); - export const symbolEnum = new Codicon('symbol-enum', { character: '\\ea95' }); - export const symbolValue = new Codicon('symbol-value', { character: '\\ea95' }); - export const symbolRuler = new Codicon('symbol-ruler', { character: '\\ea96' }); - export const symbolUnit = new Codicon('symbol-unit', { character: '\\ea96' }); - export const activateBreakpoints = new Codicon('activate-breakpoints', { character: '\\ea97' }); - export const archive = new Codicon('archive', { character: '\\ea98' }); - export const arrowBoth = new Codicon('arrow-both', { character: '\\ea99' }); - export const arrowDown = new Codicon('arrow-down', { character: '\\ea9a' }); - export const arrowLeft = new Codicon('arrow-left', { character: '\\ea9b' }); - export const arrowRight = new Codicon('arrow-right', { character: '\\ea9c' }); - export const arrowSmallDown = new Codicon('arrow-small-down', { character: '\\ea9d' }); - export const arrowSmallLeft = new Codicon('arrow-small-left', { character: '\\ea9e' }); - export const arrowSmallRight = new Codicon('arrow-small-right', { character: '\\ea9f' }); - export const arrowSmallUp = new Codicon('arrow-small-up', { character: '\\eaa0' }); - export const arrowUp = new Codicon('arrow-up', { character: '\\eaa1' }); - export const bell = new Codicon('bell', { character: '\\eaa2' }); - export const bold = new Codicon('bold', { character: '\\eaa3' }); - export const book = new Codicon('book', { character: '\\eaa4' }); - export const bookmark = new Codicon('bookmark', { character: '\\eaa5' }); - export const debugBreakpointConditionalUnverified = new Codicon('debug-breakpoint-conditional-unverified', { character: '\\eaa6' }); - export const debugBreakpointConditional = new Codicon('debug-breakpoint-conditional', { character: '\\eaa7' }); - export const debugBreakpointConditionalDisabled = new Codicon('debug-breakpoint-conditional-disabled', { character: '\\eaa7' }); - export const debugBreakpointDataUnverified = new Codicon('debug-breakpoint-data-unverified', { character: '\\eaa8' }); - export const debugBreakpointData = new Codicon('debug-breakpoint-data', { character: '\\eaa9' }); - export const debugBreakpointDataDisabled = new Codicon('debug-breakpoint-data-disabled', { character: '\\eaa9' }); - export const debugBreakpointLogUnverified = new Codicon('debug-breakpoint-log-unverified', { character: '\\eaaa' }); - export const debugBreakpointLog = new Codicon('debug-breakpoint-log', { character: '\\eaab' }); - export const debugBreakpointLogDisabled = new Codicon('debug-breakpoint-log-disabled', { character: '\\eaab' }); - export const briefcase = new Codicon('briefcase', { character: '\\eaac' }); - export const broadcast = new Codicon('broadcast', { character: '\\eaad' }); - export const browser = new Codicon('browser', { character: '\\eaae' }); - export const bug = new Codicon('bug', { character: '\\eaaf' }); - export const calendar = new Codicon('calendar', { character: '\\eab0' }); - export const caseSensitive = new Codicon('case-sensitive', { character: '\\eab1' }); - export const check = new Codicon('check', { character: '\\eab2' }); - export const checklist = new Codicon('checklist', { character: '\\eab3' }); - export const chevronDown = new Codicon('chevron-down', { character: '\\eab4' }); - export const chevronLeft = new Codicon('chevron-left', { character: '\\eab5' }); - export const chevronRight = new Codicon('chevron-right', { character: '\\eab6' }); - export const chevronUp = new Codicon('chevron-up', { character: '\\eab7' }); - export const chromeClose = new Codicon('chrome-close', { character: '\\eab8' }); - export const chromeMaximize = new Codicon('chrome-maximize', { character: '\\eab9' }); - export const chromeMinimize = new Codicon('chrome-minimize', { character: '\\eaba' }); - export const chromeRestore = new Codicon('chrome-restore', { character: '\\eabb' }); - export const circleOutline = new Codicon('circle-outline', { character: '\\eabc' }); - export const debugBreakpointUnverified = new Codicon('debug-breakpoint-unverified', { character: '\\eabc' }); - export const circleSlash = new Codicon('circle-slash', { character: '\\eabd' }); - export const circuitBoard = new Codicon('circuit-board', { character: '\\eabe' }); - export const clearAll = new Codicon('clear-all', { character: '\\eabf' }); - export const clippy = new Codicon('clippy', { character: '\\eac0' }); - export const closeAll = new Codicon('close-all', { character: '\\eac1' }); - export const cloudDownload = new Codicon('cloud-download', { character: '\\eac2' }); - export const cloudUpload = new Codicon('cloud-upload', { character: '\\eac3' }); - export const code = new Codicon('code', { character: '\\eac4' }); - export const collapseAll = new Codicon('collapse-all', { character: '\\eac5' }); - export const colorMode = new Codicon('color-mode', { character: '\\eac6' }); - export const commentDiscussion = new Codicon('comment-discussion', { character: '\\eac7' }); - export const compareChanges = new Codicon('compare-changes', { character: '\\eafd' }); - export const creditCard = new Codicon('credit-card', { character: '\\eac9' }); - export const dash = new Codicon('dash', { character: '\\eacc' }); - export const dashboard = new Codicon('dashboard', { character: '\\eacd' }); - export const database = new Codicon('database', { character: '\\eace' }); - export const debugContinue = new Codicon('debug-continue', { character: '\\eacf' }); - export const debugDisconnect = new Codicon('debug-disconnect', { character: '\\ead0' }); - export const debugPause = new Codicon('debug-pause', { character: '\\ead1' }); - export const debugRestart = new Codicon('debug-restart', { character: '\\ead2' }); - export const debugStart = new Codicon('debug-start', { character: '\\ead3' }); - export const debugStepInto = new Codicon('debug-step-into', { character: '\\ead4' }); - export const debugStepOut = new Codicon('debug-step-out', { character: '\\ead5' }); - export const debugStepOver = new Codicon('debug-step-over', { character: '\\ead6' }); - export const debugStop = new Codicon('debug-stop', { character: '\\ead7' }); - export const debug = new Codicon('debug', { character: '\\ead8' }); - export const deviceCameraVideo = new Codicon('device-camera-video', { character: '\\ead9' }); - export const deviceCamera = new Codicon('device-camera', { character: '\\eada' }); - export const deviceMobile = new Codicon('device-mobile', { character: '\\eadb' }); - export const diffAdded = new Codicon('diff-added', { character: '\\eadc' }); - export const diffIgnored = new Codicon('diff-ignored', { character: '\\eadd' }); - export const diffModified = new Codicon('diff-modified', { character: '\\eade' }); - export const diffRemoved = new Codicon('diff-removed', { character: '\\eadf' }); - export const diffRenamed = new Codicon('diff-renamed', { character: '\\eae0' }); - export const diff = new Codicon('diff', { character: '\\eae1' }); - export const discard = new Codicon('discard', { character: '\\eae2' }); - export const editorLayout = new Codicon('editor-layout', { character: '\\eae3' }); - export const emptyWindow = new Codicon('empty-window', { character: '\\eae4' }); - export const exclude = new Codicon('exclude', { character: '\\eae5' }); - export const extensions = new Codicon('extensions', { character: '\\eae6' }); - export const eyeClosed = new Codicon('eye-closed', { character: '\\eae7' }); - export const fileBinary = new Codicon('file-binary', { character: '\\eae8' }); - export const fileCode = new Codicon('file-code', { character: '\\eae9' }); - export const fileMedia = new Codicon('file-media', { character: '\\eaea' }); - export const filePdf = new Codicon('file-pdf', { character: '\\eaeb' }); - export const fileSubmodule = new Codicon('file-submodule', { character: '\\eaec' }); - export const fileSymlinkDirectory = new Codicon('file-symlink-directory', { character: '\\eaed' }); - export const fileSymlinkFile = new Codicon('file-symlink-file', { character: '\\eaee' }); - export const fileZip = new Codicon('file-zip', { character: '\\eaef' }); - export const files = new Codicon('files', { character: '\\eaf0' }); - export const filter = new Codicon('filter', { character: '\\eaf1' }); - export const flame = new Codicon('flame', { character: '\\eaf2' }); - export const foldDown = new Codicon('fold-down', { character: '\\eaf3' }); - export const foldUp = new Codicon('fold-up', { character: '\\eaf4' }); - export const fold = new Codicon('fold', { character: '\\eaf5' }); - export const folderActive = new Codicon('folder-active', { character: '\\eaf6' }); - export const folderOpened = new Codicon('folder-opened', { character: '\\eaf7' }); - export const gear = new Codicon('gear', { character: '\\eaf8' }); - export const gift = new Codicon('gift', { character: '\\eaf9' }); - export const gistSecret = new Codicon('gist-secret', { character: '\\eafa' }); - export const gist = new Codicon('gist', { character: '\\eafb' }); - export const gitCommit = new Codicon('git-commit', { character: '\\eafc' }); - export const gitCompare = new Codicon('git-compare', { character: '\\eafd' }); - export const gitMerge = new Codicon('git-merge', { character: '\\eafe' }); - export const githubAction = new Codicon('github-action', { character: '\\eaff' }); - export const githubAlt = new Codicon('github-alt', { character: '\\eb00' }); - export const globe = new Codicon('globe', { character: '\\eb01' }); - export const grabber = new Codicon('grabber', { character: '\\eb02' }); - export const graph = new Codicon('graph', { character: '\\eb03' }); - export const gripper = new Codicon('gripper', { character: '\\eb04' }); - export const heart = new Codicon('heart', { character: '\\eb05' }); - export const home = new Codicon('home', { character: '\\eb06' }); - export const horizontalRule = new Codicon('horizontal-rule', { character: '\\eb07' }); - export const hubot = new Codicon('hubot', { character: '\\eb08' }); - export const inbox = new Codicon('inbox', { character: '\\eb09' }); - export const issueClosed = new Codicon('issue-closed', { character: '\\eb0a' }); - export const issueReopened = new Codicon('issue-reopened', { character: '\\eb0b' }); - export const issues = new Codicon('issues', { character: '\\eb0c' }); - export const italic = new Codicon('italic', { character: '\\eb0d' }); - export const jersey = new Codicon('jersey', { character: '\\eb0e' }); - export const json = new Codicon('json', { character: '\\eb0f' }); - export const kebabVertical = new Codicon('kebab-vertical', { character: '\\eb10' }); - export const key = new Codicon('key', { character: '\\eb11' }); - export const law = new Codicon('law', { character: '\\eb12' }); - export const lightbulbAutofix = new Codicon('lightbulb-autofix', { character: '\\eb13' }); - export const linkExternal = new Codicon('link-external', { character: '\\eb14' }); - export const link = new Codicon('link', { character: '\\eb15' }); - export const listOrdered = new Codicon('list-ordered', { character: '\\eb16' }); - export const listUnordered = new Codicon('list-unordered', { character: '\\eb17' }); - export const liveShare = new Codicon('live-share', { character: '\\eb18' }); - export const loading = new Codicon('loading', { character: '\\eb19' }); - export const location = new Codicon('location', { character: '\\eb1a' }); - export const mailRead = new Codicon('mail-read', { character: '\\eb1b' }); - export const mail = new Codicon('mail', { character: '\\eb1c' }); - export const markdown = new Codicon('markdown', { character: '\\eb1d' }); - export const megaphone = new Codicon('megaphone', { character: '\\eb1e' }); - export const mention = new Codicon('mention', { character: '\\eb1f' }); - export const milestone = new Codicon('milestone', { character: '\\eb20' }); - export const mortarBoard = new Codicon('mortar-board', { character: '\\eb21' }); - export const move = new Codicon('move', { character: '\\eb22' }); - export const multipleWindows = new Codicon('multiple-windows', { character: '\\eb23' }); - export const mute = new Codicon('mute', { character: '\\eb24' }); - export const noNewline = new Codicon('no-newline', { character: '\\eb25' }); - export const note = new Codicon('note', { character: '\\eb26' }); - export const octoface = new Codicon('octoface', { character: '\\eb27' }); - export const openPreview = new Codicon('open-preview', { character: '\\eb28' }); - export const package_ = new Codicon('package', { character: '\\eb29' }); - export const paintcan = new Codicon('paintcan', { character: '\\eb2a' }); - export const pin = new Codicon('pin', { character: '\\eb2b' }); - export const play = new Codicon('play', { character: '\\eb2c' }); - export const run = new Codicon('run', { character: '\\eb2c' }); - export const plug = new Codicon('plug', { character: '\\eb2d' }); - export const preserveCase = new Codicon('preserve-case', { character: '\\eb2e' }); - export const preview = new Codicon('preview', { character: '\\eb2f' }); - export const project = new Codicon('project', { character: '\\eb30' }); - export const pulse = new Codicon('pulse', { character: '\\eb31' }); - export const question = new Codicon('question', { character: '\\eb32' }); - export const quote = new Codicon('quote', { character: '\\eb33' }); - export const radioTower = new Codicon('radio-tower', { character: '\\eb34' }); - export const reactions = new Codicon('reactions', { character: '\\eb35' }); - export const references = new Codicon('references', { character: '\\eb36' }); - export const refresh = new Codicon('refresh', { character: '\\eb37' }); - export const regex = new Codicon('regex', { character: '\\eb38' }); - export const remoteExplorer = new Codicon('remote-explorer', { character: '\\eb39' }); - export const remote = new Codicon('remote', { character: '\\eb3a' }); - export const remove = new Codicon('remove', { character: '\\eb3b' }); - export const replaceAll = new Codicon('replace-all', { character: '\\eb3c' }); - export const replace = new Codicon('replace', { character: '\\eb3d' }); - export const repoClone = new Codicon('repo-clone', { character: '\\eb3e' }); - export const repoForcePush = new Codicon('repo-force-push', { character: '\\eb3f' }); - export const repoPull = new Codicon('repo-pull', { character: '\\eb40' }); - export const repoPush = new Codicon('repo-push', { character: '\\eb41' }); - export const report = new Codicon('report', { character: '\\eb42' }); - export const requestChanges = new Codicon('request-changes', { character: '\\eb43' }); - export const rocket = new Codicon('rocket', { character: '\\eb44' }); - export const rootFolderOpened = new Codicon('root-folder-opened', { character: '\\eb45' }); - export const rootFolder = new Codicon('root-folder', { character: '\\eb46' }); - export const rss = new Codicon('rss', { character: '\\eb47' }); - export const ruby = new Codicon('ruby', { character: '\\eb48' }); - export const saveAll = new Codicon('save-all', { character: '\\eb49' }); - export const saveAs = new Codicon('save-as', { character: '\\eb4a' }); - export const save = new Codicon('save', { character: '\\eb4b' }); - export const screenFull = new Codicon('screen-full', { character: '\\eb4c' }); - export const screenNormal = new Codicon('screen-normal', { character: '\\eb4d' }); - export const searchStop = new Codicon('search-stop', { character: '\\eb4e' }); - export const server = new Codicon('server', { character: '\\eb50' }); - export const settingsGear = new Codicon('settings-gear', { character: '\\eb51' }); - export const settings = new Codicon('settings', { character: '\\eb52' }); - export const shield = new Codicon('shield', { character: '\\eb53' }); - export const smiley = new Codicon('smiley', { character: '\\eb54' }); - export const sortPrecedence = new Codicon('sort-precedence', { character: '\\eb55' }); - export const splitHorizontal = new Codicon('split-horizontal', { character: '\\eb56' }); - export const splitVertical = new Codicon('split-vertical', { character: '\\eb57' }); - export const squirrel = new Codicon('squirrel', { character: '\\eb58' }); - export const starFull = new Codicon('star-full', { character: '\\eb59' }); - export const starHalf = new Codicon('star-half', { character: '\\eb5a' }); - export const symbolClass = new Codicon('symbol-class', { character: '\\eb5b' }); - export const symbolColor = new Codicon('symbol-color', { character: '\\eb5c' }); - export const symbolConstant = new Codicon('symbol-constant', { character: '\\eb5d' }); - export const symbolEnumMember = new Codicon('symbol-enum-member', { character: '\\eb5e' }); - export const symbolField = new Codicon('symbol-field', { character: '\\eb5f' }); - export const symbolFile = new Codicon('symbol-file', { character: '\\eb60' }); - export const symbolInterface = new Codicon('symbol-interface', { character: '\\eb61' }); - export const symbolKeyword = new Codicon('symbol-keyword', { character: '\\eb62' }); - export const symbolMisc = new Codicon('symbol-misc', { character: '\\eb63' }); - export const symbolOperator = new Codicon('symbol-operator', { character: '\\eb64' }); - export const symbolProperty = new Codicon('symbol-property', { character: '\\eb65' }); - export const wrench = new Codicon('wrench', { character: '\\eb65' }); - export const wrenchSubaction = new Codicon('wrench-subaction', { character: '\\eb65' }); - export const symbolSnippet = new Codicon('symbol-snippet', { character: '\\eb66' }); - export const tasklist = new Codicon('tasklist', { character: '\\eb67' }); - export const telescope = new Codicon('telescope', { character: '\\eb68' }); - export const textSize = new Codicon('text-size', { character: '\\eb69' }); - export const threeBars = new Codicon('three-bars', { character: '\\eb6a' }); - export const thumbsdown = new Codicon('thumbsdown', { character: '\\eb6b' }); - export const thumbsup = new Codicon('thumbsup', { character: '\\eb6c' }); - export const tools = new Codicon('tools', { character: '\\eb6d' }); - export const triangleDown = new Codicon('triangle-down', { character: '\\eb6e' }); - export const triangleLeft = new Codicon('triangle-left', { character: '\\eb6f' }); - export const triangleRight = new Codicon('triangle-right', { character: '\\eb70' }); - export const triangleUp = new Codicon('triangle-up', { character: '\\eb71' }); - export const twitter = new Codicon('twitter', { character: '\\eb72' }); - export const unfold = new Codicon('unfold', { character: '\\eb73' }); - export const unlock = new Codicon('unlock', { character: '\\eb74' }); - export const unmute = new Codicon('unmute', { character: '\\eb75' }); - export const unverified = new Codicon('unverified', { character: '\\eb76' }); - export const verified = new Codicon('verified', { character: '\\eb77' }); - export const versions = new Codicon('versions', { character: '\\eb78' }); - export const vmActive = new Codicon('vm-active', { character: '\\eb79' }); - export const vmOutline = new Codicon('vm-outline', { character: '\\eb7a' }); - export const vmRunning = new Codicon('vm-running', { character: '\\eb7b' }); - export const watch = new Codicon('watch', { character: '\\eb7c' }); - export const whitespace = new Codicon('whitespace', { character: '\\eb7d' }); - export const wholeWord = new Codicon('whole-word', { character: '\\eb7e' }); - export const window = new Codicon('window', { character: '\\eb7f' }); - export const wordWrap = new Codicon('word-wrap', { character: '\\eb80' }); - export const zoomIn = new Codicon('zoom-in', { character: '\\eb81' }); - export const zoomOut = new Codicon('zoom-out', { character: '\\eb82' }); - export const listFilter = new Codicon('list-filter', { character: '\\eb83' }); - export const listFlat = new Codicon('list-flat', { character: '\\eb84' }); - export const listSelection = new Codicon('list-selection', { character: '\\eb85' }); - export const selection = new Codicon('selection', { character: '\\eb85' }); - export const listTree = new Codicon('list-tree', { character: '\\eb86' }); - export const debugBreakpointFunctionUnverified = new Codicon('debug-breakpoint-function-unverified', { character: '\\eb87' }); - export const debugBreakpointFunction = new Codicon('debug-breakpoint-function', { character: '\\eb88' }); - export const debugBreakpointFunctionDisabled = new Codicon('debug-breakpoint-function-disabled', { character: '\\eb88' }); - export const debugStackframeActive = new Codicon('debug-stackframe-active', { character: '\\eb89' }); - export const debugStackframeDot = new Codicon('debug-stackframe-dot', { character: '\\eb8a' }); - export const debugStackframe = new Codicon('debug-stackframe', { character: '\\eb8b' }); - export const debugStackframeFocused = new Codicon('debug-stackframe-focused', { character: '\\eb8b' }); - export const debugBreakpointUnsupported = new Codicon('debug-breakpoint-unsupported', { character: '\\eb8c' }); - export const symbolString = new Codicon('symbol-string', { character: '\\eb8d' }); - export const debugReverseContinue = new Codicon('debug-reverse-continue', { character: '\\eb8e' }); - export const debugStepBack = new Codicon('debug-step-back', { character: '\\eb8f' }); - export const debugRestartFrame = new Codicon('debug-restart-frame', { character: '\\eb90' }); - export const callIncoming = new Codicon('call-incoming', { character: '\\eb92' }); - export const callOutgoing = new Codicon('call-outgoing', { character: '\\eb93' }); - export const menu = new Codicon('menu', { character: '\\eb94' }); - export const expandAll = new Codicon('expand-all', { character: '\\eb95' }); - export const feedback = new Codicon('feedback', { character: '\\eb96' }); - export const groupByRefType = new Codicon('group-by-ref-type', { character: '\\eb97' }); - export const ungroupByRefType = new Codicon('ungroup-by-ref-type', { character: '\\eb98' }); - export const account = new Codicon('account', { character: '\\eb99' }); - export const bellDot = new Codicon('bell-dot', { character: '\\eb9a' }); - export const debugConsole = new Codicon('debug-console', { character: '\\eb9b' }); - export const library = new Codicon('library', { character: '\\eb9c' }); - export const output = new Codicon('output', { character: '\\eb9d' }); - export const runAll = new Codicon('run-all', { character: '\\eb9e' }); - export const syncIgnored = new Codicon('sync-ignored', { character: '\\eb9f' }); - export const pinned = new Codicon('pinned', { character: '\\eba0' }); - export const githubInverted = new Codicon('github-inverted', { character: '\\eba1' }); - export const debugAlt = new Codicon('debug-alt', { character: '\\eb91' }); - export const serverProcess = new Codicon('server-process', { character: '\\eba2' }); - export const serverEnvironment = new Codicon('server-environment', { character: '\\eba3' }); - export const pass = new Codicon('pass', { character: '\\eba4' }); - export const stopCircle = new Codicon('stop-circle', { character: '\\eba5' }); - export const playCircle = new Codicon('play-circle', { character: '\\eba6' }); - export const record = new Codicon('record', { character: '\\eba7' }); - export const debugAltSmall = new Codicon('debug-alt-small', { character: '\\eba8' }); - export const vmConnect = new Codicon('vm-connect', { character: '\\eba9' }); - export const cloud = new Codicon('cloud', { character: '\\ebaa' }); - export const merge = new Codicon('merge', { character: '\\ebab' }); - export const exportIcon = new Codicon('export', { character: '\\ebac' }); - export const graphLeft = new Codicon('graph-left', { character: '\\ebad' }); - export const magnet = new Codicon('magnet', { character: '\\ebae' }); - export const notebook = new Codicon('notebook', { character: '\\ebaf' }); - export const redo = new Codicon('redo', { character: '\\ebb0' }); - export const checkAll = new Codicon('check-all', { character: '\\ebb1' }); - export const pinnedDirty = new Codicon('pinned-dirty', { character: '\\ebb2' }); - export const passFilled = new Codicon('pass-filled', { character: '\\ebb3' }); - export const circleLargeFilled = new Codicon('circle-large-filled', { character: '\\ebb4' }); - export const circleLargeOutline = new Codicon('circle-large-outline', { character: '\\ebb5' }); - export const combine = new Codicon('combine', { character: '\\ebb6' }); - export const gather = new Codicon('gather', { character: '\\ebb6' }); - export const table = new Codicon('table', { character: '\\ebb7' }); - export const variableGroup = new Codicon('variable-group', { character: '\\ebb8' }); - export const typeHierarchy = new Codicon('type-hierarchy', { character: '\\ebb9' }); - export const typeHierarchySub = new Codicon('type-hierarchy-sub', { character: '\\ebba' }); - export const typeHierarchySuper = new Codicon('type-hierarchy-super', { character: '\\ebbb' }); - export const gitPullRequestCreate = new Codicon('git-pull-request-create', { character: '\\ebbc' }); + export const add = new Codicon('add', { fontCharacter: '\\ea60' }); + export const plus = new Codicon('plus', { fontCharacter: '\\ea60' }); + export const gistNew = new Codicon('gist-new', { fontCharacter: '\\ea60' }); + export const repoCreate = new Codicon('repo-create', { fontCharacter: '\\ea60' }); + export const lightbulb = new Codicon('lightbulb', { fontCharacter: '\\ea61' }); + export const lightBulb = new Codicon('light-bulb', { fontCharacter: '\\ea61' }); + export const repo = new Codicon('repo', { fontCharacter: '\\ea62' }); + export const repoDelete = new Codicon('repo-delete', { fontCharacter: '\\ea62' }); + export const gistFork = new Codicon('gist-fork', { fontCharacter: '\\ea63' }); + export const repoForked = new Codicon('repo-forked', { fontCharacter: '\\ea63' }); + export const gitPullRequest = new Codicon('git-pull-request', { fontCharacter: '\\ea64' }); + export const gitPullRequestAbandoned = new Codicon('git-pull-request-abandoned', { fontCharacter: '\\ea64' }); + export const recordKeys = new Codicon('record-keys', { fontCharacter: '\\ea65' }); + export const keyboard = new Codicon('keyboard', { fontCharacter: '\\ea65' }); + export const tag = new Codicon('tag', { fontCharacter: '\\ea66' }); + export const tagAdd = new Codicon('tag-add', { fontCharacter: '\\ea66' }); + export const tagRemove = new Codicon('tag-remove', { fontCharacter: '\\ea66' }); + export const person = new Codicon('person', { fontCharacter: '\\ea67' }); + export const personAdd = new Codicon('person-add', { fontCharacter: '\\ea67' }); + export const personFollow = new Codicon('person-follow', { fontCharacter: '\\ea67' }); + export const personOutline = new Codicon('person-outline', { fontCharacter: '\\ea67' }); + export const personFilled = new Codicon('person-filled', { fontCharacter: '\\ea67' }); + export const gitBranch = new Codicon('git-branch', { fontCharacter: '\\ea68' }); + export const gitBranchCreate = new Codicon('git-branch-create', { fontCharacter: '\\ea68' }); + export const gitBranchDelete = new Codicon('git-branch-delete', { fontCharacter: '\\ea68' }); + export const sourceControl = new Codicon('source-control', { fontCharacter: '\\ea68' }); + export const mirror = new Codicon('mirror', { fontCharacter: '\\ea69' }); + export const mirrorPublic = new Codicon('mirror-public', { fontCharacter: '\\ea69' }); + export const star = new Codicon('star', { fontCharacter: '\\ea6a' }); + export const starAdd = new Codicon('star-add', { fontCharacter: '\\ea6a' }); + export const starDelete = new Codicon('star-delete', { fontCharacter: '\\ea6a' }); + export const starEmpty = new Codicon('star-empty', { fontCharacter: '\\ea6a' }); + export const comment = new Codicon('comment', { fontCharacter: '\\ea6b' }); + export const commentAdd = new Codicon('comment-add', { fontCharacter: '\\ea6b' }); + export const alert = new Codicon('alert', { fontCharacter: '\\ea6c' }); + export const warning = new Codicon('warning', { fontCharacter: '\\ea6c' }); + export const search = new Codicon('search', { fontCharacter: '\\ea6d' }); + export const searchSave = new Codicon('search-save', { fontCharacter: '\\ea6d' }); + export const logOut = new Codicon('log-out', { fontCharacter: '\\ea6e' }); + export const signOut = new Codicon('sign-out', { fontCharacter: '\\ea6e' }); + export const logIn = new Codicon('log-in', { fontCharacter: '\\ea6f' }); + export const signIn = new Codicon('sign-in', { fontCharacter: '\\ea6f' }); + export const eye = new Codicon('eye', { fontCharacter: '\\ea70' }); + export const eyeUnwatch = new Codicon('eye-unwatch', { fontCharacter: '\\ea70' }); + export const eyeWatch = new Codicon('eye-watch', { fontCharacter: '\\ea70' }); + export const circleFilled = new Codicon('circle-filled', { fontCharacter: '\\ea71' }); + export const primitiveDot = new Codicon('primitive-dot', { fontCharacter: '\\ea71' }); + export const closeDirty = new Codicon('close-dirty', { fontCharacter: '\\ea71' }); + export const debugBreakpoint = new Codicon('debug-breakpoint', { fontCharacter: '\\ea71' }); + export const debugBreakpointDisabled = new Codicon('debug-breakpoint-disabled', { fontCharacter: '\\ea71' }); + export const debugHint = new Codicon('debug-hint', { fontCharacter: '\\ea71' }); + export const primitiveSquare = new Codicon('primitive-square', { fontCharacter: '\\ea72' }); + export const edit = new Codicon('edit', { fontCharacter: '\\ea73' }); + export const pencil = new Codicon('pencil', { fontCharacter: '\\ea73' }); + export const info = new Codicon('info', { fontCharacter: '\\ea74' }); + export const issueOpened = new Codicon('issue-opened', { fontCharacter: '\\ea74' }); + export const gistPrivate = new Codicon('gist-private', { fontCharacter: '\\ea75' }); + export const gitForkPrivate = new Codicon('git-fork-private', { fontCharacter: '\\ea75' }); + export const lock = new Codicon('lock', { fontCharacter: '\\ea75' }); + export const mirrorPrivate = new Codicon('mirror-private', { fontCharacter: '\\ea75' }); + export const close = new Codicon('close', { fontCharacter: '\\ea76' }); + export const removeClose = new Codicon('remove-close', { fontCharacter: '\\ea76' }); + export const x = new Codicon('x', { fontCharacter: '\\ea76' }); + export const repoSync = new Codicon('repo-sync', { fontCharacter: '\\ea77' }); + export const sync = new Codicon('sync', { fontCharacter: '\\ea77' }); + export const clone = new Codicon('clone', { fontCharacter: '\\ea78' }); + export const desktopDownload = new Codicon('desktop-download', { fontCharacter: '\\ea78' }); + export const beaker = new Codicon('beaker', { fontCharacter: '\\ea79' }); + export const microscope = new Codicon('microscope', { fontCharacter: '\\ea79' }); + export const vm = new Codicon('vm', { fontCharacter: '\\ea7a' }); + export const deviceDesktop = new Codicon('device-desktop', { fontCharacter: '\\ea7a' }); + export const file = new Codicon('file', { fontCharacter: '\\ea7b' }); + export const fileText = new Codicon('file-text', { fontCharacter: '\\ea7b' }); + export const more = new Codicon('more', { fontCharacter: '\\ea7c' }); + export const ellipsis = new Codicon('ellipsis', { fontCharacter: '\\ea7c' }); + export const kebabHorizontal = new Codicon('kebab-horizontal', { fontCharacter: '\\ea7c' }); + export const mailReply = new Codicon('mail-reply', { fontCharacter: '\\ea7d' }); + export const reply = new Codicon('reply', { fontCharacter: '\\ea7d' }); + export const organization = new Codicon('organization', { fontCharacter: '\\ea7e' }); + export const organizationFilled = new Codicon('organization-filled', { fontCharacter: '\\ea7e' }); + export const organizationOutline = new Codicon('organization-outline', { fontCharacter: '\\ea7e' }); + export const newFile = new Codicon('new-file', { fontCharacter: '\\ea7f' }); + export const fileAdd = new Codicon('file-add', { fontCharacter: '\\ea7f' }); + export const newFolder = new Codicon('new-folder', { fontCharacter: '\\ea80' }); + export const fileDirectoryCreate = new Codicon('file-directory-create', { fontCharacter: '\\ea80' }); + export const trash = new Codicon('trash', { fontCharacter: '\\ea81' }); + export const trashcan = new Codicon('trashcan', { fontCharacter: '\\ea81' }); + export const history = new Codicon('history', { fontCharacter: '\\ea82' }); + export const clock = new Codicon('clock', { fontCharacter: '\\ea82' }); + export const folder = new Codicon('folder', { fontCharacter: '\\ea83' }); + export const fileDirectory = new Codicon('file-directory', { fontCharacter: '\\ea83' }); + export const symbolFolder = new Codicon('symbol-folder', { fontCharacter: '\\ea83' }); + export const logoGithub = new Codicon('logo-github', { fontCharacter: '\\ea84' }); + export const markGithub = new Codicon('mark-github', { fontCharacter: '\\ea84' }); + export const github = new Codicon('github', { fontCharacter: '\\ea84' }); + export const terminal = new Codicon('terminal', { fontCharacter: '\\ea85' }); + export const console = new Codicon('console', { fontCharacter: '\\ea85' }); + export const repl = new Codicon('repl', { fontCharacter: '\\ea85' }); + export const zap = new Codicon('zap', { fontCharacter: '\\ea86' }); + export const symbolEvent = new Codicon('symbol-event', { fontCharacter: '\\ea86' }); + export const error = new Codicon('error', { fontCharacter: '\\ea87' }); + export const stop = new Codicon('stop', { fontCharacter: '\\ea87' }); + export const variable = new Codicon('variable', { fontCharacter: '\\ea88' }); + export const symbolVariable = new Codicon('symbol-variable', { fontCharacter: '\\ea88' }); + export const array = new Codicon('array', { fontCharacter: '\\ea8a' }); + export const symbolArray = new Codicon('symbol-array', { fontCharacter: '\\ea8a' }); + export const symbolModule = new Codicon('symbol-module', { fontCharacter: '\\ea8b' }); + export const symbolPackage = new Codicon('symbol-package', { fontCharacter: '\\ea8b' }); + export const symbolNamespace = new Codicon('symbol-namespace', { fontCharacter: '\\ea8b' }); + export const symbolObject = new Codicon('symbol-object', { fontCharacter: '\\ea8b' }); + export const symbolMethod = new Codicon('symbol-method', { fontCharacter: '\\ea8c' }); + export const symbolFunction = new Codicon('symbol-function', { fontCharacter: '\\ea8c' }); + export const symbolConstructor = new Codicon('symbol-constructor', { fontCharacter: '\\ea8c' }); + export const symbolBoolean = new Codicon('symbol-boolean', { fontCharacter: '\\ea8f' }); + export const symbolNull = new Codicon('symbol-null', { fontCharacter: '\\ea8f' }); + export const symbolNumeric = new Codicon('symbol-numeric', { fontCharacter: '\\ea90' }); + export const symbolNumber = new Codicon('symbol-number', { fontCharacter: '\\ea90' }); + export const symbolStructure = new Codicon('symbol-structure', { fontCharacter: '\\ea91' }); + export const symbolStruct = new Codicon('symbol-struct', { fontCharacter: '\\ea91' }); + export const symbolParameter = new Codicon('symbol-parameter', { fontCharacter: '\\ea92' }); + export const symbolTypeParameter = new Codicon('symbol-type-parameter', { fontCharacter: '\\ea92' }); + export const symbolKey = new Codicon('symbol-key', { fontCharacter: '\\ea93' }); + export const symbolText = new Codicon('symbol-text', { fontCharacter: '\\ea93' }); + export const symbolReference = new Codicon('symbol-reference', { fontCharacter: '\\ea94' }); + export const goToFile = new Codicon('go-to-file', { fontCharacter: '\\ea94' }); + export const symbolEnum = new Codicon('symbol-enum', { fontCharacter: '\\ea95' }); + export const symbolValue = new Codicon('symbol-value', { fontCharacter: '\\ea95' }); + export const symbolRuler = new Codicon('symbol-ruler', { fontCharacter: '\\ea96' }); + export const symbolUnit = new Codicon('symbol-unit', { fontCharacter: '\\ea96' }); + export const activateBreakpoints = new Codicon('activate-breakpoints', { fontCharacter: '\\ea97' }); + export const archive = new Codicon('archive', { fontCharacter: '\\ea98' }); + export const arrowBoth = new Codicon('arrow-both', { fontCharacter: '\\ea99' }); + export const arrowDown = new Codicon('arrow-down', { fontCharacter: '\\ea9a' }); + export const arrowLeft = new Codicon('arrow-left', { fontCharacter: '\\ea9b' }); + export const arrowRight = new Codicon('arrow-right', { fontCharacter: '\\ea9c' }); + export const arrowSmallDown = new Codicon('arrow-small-down', { fontCharacter: '\\ea9d' }); + export const arrowSmallLeft = new Codicon('arrow-small-left', { fontCharacter: '\\ea9e' }); + export const arrowSmallRight = new Codicon('arrow-small-right', { fontCharacter: '\\ea9f' }); + export const arrowSmallUp = new Codicon('arrow-small-up', { fontCharacter: '\\eaa0' }); + export const arrowUp = new Codicon('arrow-up', { fontCharacter: '\\eaa1' }); + export const bell = new Codicon('bell', { fontCharacter: '\\eaa2' }); + export const bold = new Codicon('bold', { fontCharacter: '\\eaa3' }); + export const book = new Codicon('book', { fontCharacter: '\\eaa4' }); + export const bookmark = new Codicon('bookmark', { fontCharacter: '\\eaa5' }); + export const debugBreakpointConditionalUnverified = new Codicon('debug-breakpoint-conditional-unverified', { fontCharacter: '\\eaa6' }); + export const debugBreakpointConditional = new Codicon('debug-breakpoint-conditional', { fontCharacter: '\\eaa7' }); + export const debugBreakpointConditionalDisabled = new Codicon('debug-breakpoint-conditional-disabled', { fontCharacter: '\\eaa7' }); + export const debugBreakpointDataUnverified = new Codicon('debug-breakpoint-data-unverified', { fontCharacter: '\\eaa8' }); + export const debugBreakpointData = new Codicon('debug-breakpoint-data', { fontCharacter: '\\eaa9' }); + export const debugBreakpointDataDisabled = new Codicon('debug-breakpoint-data-disabled', { fontCharacter: '\\eaa9' }); + export const debugBreakpointLogUnverified = new Codicon('debug-breakpoint-log-unverified', { fontCharacter: '\\eaaa' }); + export const debugBreakpointLog = new Codicon('debug-breakpoint-log', { fontCharacter: '\\eaab' }); + export const debugBreakpointLogDisabled = new Codicon('debug-breakpoint-log-disabled', { fontCharacter: '\\eaab' }); + export const briefcase = new Codicon('briefcase', { fontCharacter: '\\eaac' }); + export const broadcast = new Codicon('broadcast', { fontCharacter: '\\eaad' }); + export const browser = new Codicon('browser', { fontCharacter: '\\eaae' }); + export const bug = new Codicon('bug', { fontCharacter: '\\eaaf' }); + export const calendar = new Codicon('calendar', { fontCharacter: '\\eab0' }); + export const caseSensitive = new Codicon('case-sensitive', { fontCharacter: '\\eab1' }); + export const check = new Codicon('check', { fontCharacter: '\\eab2' }); + export const checklist = new Codicon('checklist', { fontCharacter: '\\eab3' }); + export const chevronDown = new Codicon('chevron-down', { fontCharacter: '\\eab4' }); + export const chevronLeft = new Codicon('chevron-left', { fontCharacter: '\\eab5' }); + export const chevronRight = new Codicon('chevron-right', { fontCharacter: '\\eab6' }); + export const chevronUp = new Codicon('chevron-up', { fontCharacter: '\\eab7' }); + export const chromeClose = new Codicon('chrome-close', { fontCharacter: '\\eab8' }); + export const chromeMaximize = new Codicon('chrome-maximize', { fontCharacter: '\\eab9' }); + export const chromeMinimize = new Codicon('chrome-minimize', { fontCharacter: '\\eaba' }); + export const chromeRestore = new Codicon('chrome-restore', { fontCharacter: '\\eabb' }); + export const circleOutline = new Codicon('circle-outline', { fontCharacter: '\\eabc' }); + export const debugBreakpointUnverified = new Codicon('debug-breakpoint-unverified', { fontCharacter: '\\eabc' }); + export const circleSlash = new Codicon('circle-slash', { fontCharacter: '\\eabd' }); + export const circuitBoard = new Codicon('circuit-board', { fontCharacter: '\\eabe' }); + export const clearAll = new Codicon('clear-all', { fontCharacter: '\\eabf' }); + export const clippy = new Codicon('clippy', { fontCharacter: '\\eac0' }); + export const closeAll = new Codicon('close-all', { fontCharacter: '\\eac1' }); + export const cloudDownload = new Codicon('cloud-download', { fontCharacter: '\\eac2' }); + export const cloudUpload = new Codicon('cloud-upload', { fontCharacter: '\\eac3' }); + export const code = new Codicon('code', { fontCharacter: '\\eac4' }); + export const collapseAll = new Codicon('collapse-all', { fontCharacter: '\\eac5' }); + export const colorMode = new Codicon('color-mode', { fontCharacter: '\\eac6' }); + export const commentDiscussion = new Codicon('comment-discussion', { fontCharacter: '\\eac7' }); + export const compareChanges = new Codicon('compare-changes', { fontCharacter: '\\eafd' }); + export const creditCard = new Codicon('credit-card', { fontCharacter: '\\eac9' }); + export const dash = new Codicon('dash', { fontCharacter: '\\eacc' }); + export const dashboard = new Codicon('dashboard', { fontCharacter: '\\eacd' }); + export const database = new Codicon('database', { fontCharacter: '\\eace' }); + export const debugContinue = new Codicon('debug-continue', { fontCharacter: '\\eacf' }); + export const debugDisconnect = new Codicon('debug-disconnect', { fontCharacter: '\\ead0' }); + export const debugPause = new Codicon('debug-pause', { fontCharacter: '\\ead1' }); + export const debugRestart = new Codicon('debug-restart', { fontCharacter: '\\ead2' }); + export const debugStart = new Codicon('debug-start', { fontCharacter: '\\ead3' }); + export const debugStepInto = new Codicon('debug-step-into', { fontCharacter: '\\ead4' }); + export const debugStepOut = new Codicon('debug-step-out', { fontCharacter: '\\ead5' }); + export const debugStepOver = new Codicon('debug-step-over', { fontCharacter: '\\ead6' }); + export const debugStop = new Codicon('debug-stop', { fontCharacter: '\\ead7' }); + export const debug = new Codicon('debug', { fontCharacter: '\\ead8' }); + export const deviceCameraVideo = new Codicon('device-camera-video', { fontCharacter: '\\ead9' }); + export const deviceCamera = new Codicon('device-camera', { fontCharacter: '\\eada' }); + export const deviceMobile = new Codicon('device-mobile', { fontCharacter: '\\eadb' }); + export const diffAdded = new Codicon('diff-added', { fontCharacter: '\\eadc' }); + export const diffIgnored = new Codicon('diff-ignored', { fontCharacter: '\\eadd' }); + export const diffModified = new Codicon('diff-modified', { fontCharacter: '\\eade' }); + export const diffRemoved = new Codicon('diff-removed', { fontCharacter: '\\eadf' }); + export const diffRenamed = new Codicon('diff-renamed', { fontCharacter: '\\eae0' }); + export const diff = new Codicon('diff', { fontCharacter: '\\eae1' }); + export const discard = new Codicon('discard', { fontCharacter: '\\eae2' }); + export const editorLayout = new Codicon('editor-layout', { fontCharacter: '\\eae3' }); + export const emptyWindow = new Codicon('empty-window', { fontCharacter: '\\eae4' }); + export const exclude = new Codicon('exclude', { fontCharacter: '\\eae5' }); + export const extensions = new Codicon('extensions', { fontCharacter: '\\eae6' }); + export const eyeClosed = new Codicon('eye-closed', { fontCharacter: '\\eae7' }); + export const fileBinary = new Codicon('file-binary', { fontCharacter: '\\eae8' }); + export const fileCode = new Codicon('file-code', { fontCharacter: '\\eae9' }); + export const fileMedia = new Codicon('file-media', { fontCharacter: '\\eaea' }); + export const filePdf = new Codicon('file-pdf', { fontCharacter: '\\eaeb' }); + export const fileSubmodule = new Codicon('file-submodule', { fontCharacter: '\\eaec' }); + export const fileSymlinkDirectory = new Codicon('file-symlink-directory', { fontCharacter: '\\eaed' }); + export const fileSymlinkFile = new Codicon('file-symlink-file', { fontCharacter: '\\eaee' }); + export const fileZip = new Codicon('file-zip', { fontCharacter: '\\eaef' }); + export const files = new Codicon('files', { fontCharacter: '\\eaf0' }); + export const filter = new Codicon('filter', { fontCharacter: '\\eaf1' }); + export const flame = new Codicon('flame', { fontCharacter: '\\eaf2' }); + export const foldDown = new Codicon('fold-down', { fontCharacter: '\\eaf3' }); + export const foldUp = new Codicon('fold-up', { fontCharacter: '\\eaf4' }); + export const fold = new Codicon('fold', { fontCharacter: '\\eaf5' }); + export const folderActive = new Codicon('folder-active', { fontCharacter: '\\eaf6' }); + export const folderOpened = new Codicon('folder-opened', { fontCharacter: '\\eaf7' }); + export const gear = new Codicon('gear', { fontCharacter: '\\eaf8' }); + export const gift = new Codicon('gift', { fontCharacter: '\\eaf9' }); + export const gistSecret = new Codicon('gist-secret', { fontCharacter: '\\eafa' }); + export const gist = new Codicon('gist', { fontCharacter: '\\eafb' }); + export const gitCommit = new Codicon('git-commit', { fontCharacter: '\\eafc' }); + export const gitCompare = new Codicon('git-compare', { fontCharacter: '\\eafd' }); + export const gitMerge = new Codicon('git-merge', { fontCharacter: '\\eafe' }); + export const githubAction = new Codicon('github-action', { fontCharacter: '\\eaff' }); + export const githubAlt = new Codicon('github-alt', { fontCharacter: '\\eb00' }); + export const globe = new Codicon('globe', { fontCharacter: '\\eb01' }); + export const grabber = new Codicon('grabber', { fontCharacter: '\\eb02' }); + export const graph = new Codicon('graph', { fontCharacter: '\\eb03' }); + export const gripper = new Codicon('gripper', { fontCharacter: '\\eb04' }); + export const heart = new Codicon('heart', { fontCharacter: '\\eb05' }); + export const home = new Codicon('home', { fontCharacter: '\\eb06' }); + export const horizontalRule = new Codicon('horizontal-rule', { fontCharacter: '\\eb07' }); + export const hubot = new Codicon('hubot', { fontCharacter: '\\eb08' }); + export const inbox = new Codicon('inbox', { fontCharacter: '\\eb09' }); + export const issueClosed = new Codicon('issue-closed', { fontCharacter: '\\eb0a' }); + export const issueReopened = new Codicon('issue-reopened', { fontCharacter: '\\eb0b' }); + export const issues = new Codicon('issues', { fontCharacter: '\\eb0c' }); + export const italic = new Codicon('italic', { fontCharacter: '\\eb0d' }); + export const jersey = new Codicon('jersey', { fontCharacter: '\\eb0e' }); + export const json = new Codicon('json', { fontCharacter: '\\eb0f' }); + export const kebabVertical = new Codicon('kebab-vertical', { fontCharacter: '\\eb10' }); + export const key = new Codicon('key', { fontCharacter: '\\eb11' }); + export const law = new Codicon('law', { fontCharacter: '\\eb12' }); + export const lightbulbAutofix = new Codicon('lightbulb-autofix', { fontCharacter: '\\eb13' }); + export const linkExternal = new Codicon('link-external', { fontCharacter: '\\eb14' }); + export const link = new Codicon('link', { fontCharacter: '\\eb15' }); + export const listOrdered = new Codicon('list-ordered', { fontCharacter: '\\eb16' }); + export const listUnordered = new Codicon('list-unordered', { fontCharacter: '\\eb17' }); + export const liveShare = new Codicon('live-share', { fontCharacter: '\\eb18' }); + export const loading = new Codicon('loading', { fontCharacter: '\\eb19' }); + export const location = new Codicon('location', { fontCharacter: '\\eb1a' }); + export const mailRead = new Codicon('mail-read', { fontCharacter: '\\eb1b' }); + export const mail = new Codicon('mail', { fontCharacter: '\\eb1c' }); + export const markdown = new Codicon('markdown', { fontCharacter: '\\eb1d' }); + export const megaphone = new Codicon('megaphone', { fontCharacter: '\\eb1e' }); + export const mention = new Codicon('mention', { fontCharacter: '\\eb1f' }); + export const milestone = new Codicon('milestone', { fontCharacter: '\\eb20' }); + export const mortarBoard = new Codicon('mortar-board', { fontCharacter: '\\eb21' }); + export const move = new Codicon('move', { fontCharacter: '\\eb22' }); + export const multipleWindows = new Codicon('multiple-windows', { fontCharacter: '\\eb23' }); + export const mute = new Codicon('mute', { fontCharacter: '\\eb24' }); + export const noNewline = new Codicon('no-newline', { fontCharacter: '\\eb25' }); + export const note = new Codicon('note', { fontCharacter: '\\eb26' }); + export const octoface = new Codicon('octoface', { fontCharacter: '\\eb27' }); + export const openPreview = new Codicon('open-preview', { fontCharacter: '\\eb28' }); + export const package_ = new Codicon('package', { fontCharacter: '\\eb29' }); + export const paintcan = new Codicon('paintcan', { fontCharacter: '\\eb2a' }); + export const pin = new Codicon('pin', { fontCharacter: '\\eb2b' }); + export const play = new Codicon('play', { fontCharacter: '\\eb2c' }); + export const run = new Codicon('run', { fontCharacter: '\\eb2c' }); + export const plug = new Codicon('plug', { fontCharacter: '\\eb2d' }); + export const preserveCase = new Codicon('preserve-case', { fontCharacter: '\\eb2e' }); + export const preview = new Codicon('preview', { fontCharacter: '\\eb2f' }); + export const project = new Codicon('project', { fontCharacter: '\\eb30' }); + export const pulse = new Codicon('pulse', { fontCharacter: '\\eb31' }); + export const question = new Codicon('question', { fontCharacter: '\\eb32' }); + export const quote = new Codicon('quote', { fontCharacter: '\\eb33' }); + export const radioTower = new Codicon('radio-tower', { fontCharacter: '\\eb34' }); + export const reactions = new Codicon('reactions', { fontCharacter: '\\eb35' }); + export const references = new Codicon('references', { fontCharacter: '\\eb36' }); + export const refresh = new Codicon('refresh', { fontCharacter: '\\eb37' }); + export const regex = new Codicon('regex', { fontCharacter: '\\eb38' }); + export const remoteExplorer = new Codicon('remote-explorer', { fontCharacter: '\\eb39' }); + export const remote = new Codicon('remote', { fontCharacter: '\\eb3a' }); + export const remove = new Codicon('remove', { fontCharacter: '\\eb3b' }); + export const replaceAll = new Codicon('replace-all', { fontCharacter: '\\eb3c' }); + export const replace = new Codicon('replace', { fontCharacter: '\\eb3d' }); + export const repoClone = new Codicon('repo-clone', { fontCharacter: '\\eb3e' }); + export const repoForcePush = new Codicon('repo-force-push', { fontCharacter: '\\eb3f' }); + export const repoPull = new Codicon('repo-pull', { fontCharacter: '\\eb40' }); + export const repoPush = new Codicon('repo-push', { fontCharacter: '\\eb41' }); + export const report = new Codicon('report', { fontCharacter: '\\eb42' }); + export const requestChanges = new Codicon('request-changes', { fontCharacter: '\\eb43' }); + export const rocket = new Codicon('rocket', { fontCharacter: '\\eb44' }); + export const rootFolderOpened = new Codicon('root-folder-opened', { fontCharacter: '\\eb45' }); + export const rootFolder = new Codicon('root-folder', { fontCharacter: '\\eb46' }); + export const rss = new Codicon('rss', { fontCharacter: '\\eb47' }); + export const ruby = new Codicon('ruby', { fontCharacter: '\\eb48' }); + export const saveAll = new Codicon('save-all', { fontCharacter: '\\eb49' }); + export const saveAs = new Codicon('save-as', { fontCharacter: '\\eb4a' }); + export const save = new Codicon('save', { fontCharacter: '\\eb4b' }); + export const screenFull = new Codicon('screen-full', { fontCharacter: '\\eb4c' }); + export const screenNormal = new Codicon('screen-normal', { fontCharacter: '\\eb4d' }); + export const searchStop = new Codicon('search-stop', { fontCharacter: '\\eb4e' }); + export const server = new Codicon('server', { fontCharacter: '\\eb50' }); + export const settingsGear = new Codicon('settings-gear', { fontCharacter: '\\eb51' }); + export const settings = new Codicon('settings', { fontCharacter: '\\eb52' }); + export const shield = new Codicon('shield', { fontCharacter: '\\eb53' }); + export const smiley = new Codicon('smiley', { fontCharacter: '\\eb54' }); + export const sortPrecedence = new Codicon('sort-precedence', { fontCharacter: '\\eb55' }); + export const splitHorizontal = new Codicon('split-horizontal', { fontCharacter: '\\eb56' }); + export const splitVertical = new Codicon('split-vertical', { fontCharacter: '\\eb57' }); + export const squirrel = new Codicon('squirrel', { fontCharacter: '\\eb58' }); + export const starFull = new Codicon('star-full', { fontCharacter: '\\eb59' }); + export const starHalf = new Codicon('star-half', { fontCharacter: '\\eb5a' }); + export const symbolClass = new Codicon('symbol-class', { fontCharacter: '\\eb5b' }); + export const symbolColor = new Codicon('symbol-color', { fontCharacter: '\\eb5c' }); + export const symbolConstant = new Codicon('symbol-constant', { fontCharacter: '\\eb5d' }); + export const symbolEnumMember = new Codicon('symbol-enum-member', { fontCharacter: '\\eb5e' }); + export const symbolField = new Codicon('symbol-field', { fontCharacter: '\\eb5f' }); + export const symbolFile = new Codicon('symbol-file', { fontCharacter: '\\eb60' }); + export const symbolInterface = new Codicon('symbol-interface', { fontCharacter: '\\eb61' }); + export const symbolKeyword = new Codicon('symbol-keyword', { fontCharacter: '\\eb62' }); + export const symbolMisc = new Codicon('symbol-misc', { fontCharacter: '\\eb63' }); + export const symbolOperator = new Codicon('symbol-operator', { fontCharacter: '\\eb64' }); + export const symbolProperty = new Codicon('symbol-property', { fontCharacter: '\\eb65' }); + export const wrench = new Codicon('wrench', { fontCharacter: '\\eb65' }); + export const wrenchSubaction = new Codicon('wrench-subaction', { fontCharacter: '\\eb65' }); + export const symbolSnippet = new Codicon('symbol-snippet', { fontCharacter: '\\eb66' }); + export const tasklist = new Codicon('tasklist', { fontCharacter: '\\eb67' }); + export const telescope = new Codicon('telescope', { fontCharacter: '\\eb68' }); + export const textSize = new Codicon('text-size', { fontCharacter: '\\eb69' }); + export const threeBars = new Codicon('three-bars', { fontCharacter: '\\eb6a' }); + export const thumbsdown = new Codicon('thumbsdown', { fontCharacter: '\\eb6b' }); + export const thumbsup = new Codicon('thumbsup', { fontCharacter: '\\eb6c' }); + export const tools = new Codicon('tools', { fontCharacter: '\\eb6d' }); + export const triangleDown = new Codicon('triangle-down', { fontCharacter: '\\eb6e' }); + export const triangleLeft = new Codicon('triangle-left', { fontCharacter: '\\eb6f' }); + export const triangleRight = new Codicon('triangle-right', { fontCharacter: '\\eb70' }); + export const triangleUp = new Codicon('triangle-up', { fontCharacter: '\\eb71' }); + export const twitter = new Codicon('twitter', { fontCharacter: '\\eb72' }); + export const unfold = new Codicon('unfold', { fontCharacter: '\\eb73' }); + export const unlock = new Codicon('unlock', { fontCharacter: '\\eb74' }); + export const unmute = new Codicon('unmute', { fontCharacter: '\\eb75' }); + export const unverified = new Codicon('unverified', { fontCharacter: '\\eb76' }); + export const verified = new Codicon('verified', { fontCharacter: '\\eb77' }); + export const versions = new Codicon('versions', { fontCharacter: '\\eb78' }); + export const vmActive = new Codicon('vm-active', { fontCharacter: '\\eb79' }); + export const vmOutline = new Codicon('vm-outline', { fontCharacter: '\\eb7a' }); + export const vmRunning = new Codicon('vm-running', { fontCharacter: '\\eb7b' }); + export const watch = new Codicon('watch', { fontCharacter: '\\eb7c' }); + export const whitespace = new Codicon('whitespace', { fontCharacter: '\\eb7d' }); + export const wholeWord = new Codicon('whole-word', { fontCharacter: '\\eb7e' }); + export const window = new Codicon('window', { fontCharacter: '\\eb7f' }); + export const wordWrap = new Codicon('word-wrap', { fontCharacter: '\\eb80' }); + export const zoomIn = new Codicon('zoom-in', { fontCharacter: '\\eb81' }); + export const zoomOut = new Codicon('zoom-out', { fontCharacter: '\\eb82' }); + export const listFilter = new Codicon('list-filter', { fontCharacter: '\\eb83' }); + export const listFlat = new Codicon('list-flat', { fontCharacter: '\\eb84' }); + export const listSelection = new Codicon('list-selection', { fontCharacter: '\\eb85' }); + export const selection = new Codicon('selection', { fontCharacter: '\\eb85' }); + export const listTree = new Codicon('list-tree', { fontCharacter: '\\eb86' }); + export const debugBreakpointFunctionUnverified = new Codicon('debug-breakpoint-function-unverified', { fontCharacter: '\\eb87' }); + export const debugBreakpointFunction = new Codicon('debug-breakpoint-function', { fontCharacter: '\\eb88' }); + export const debugBreakpointFunctionDisabled = new Codicon('debug-breakpoint-function-disabled', { fontCharacter: '\\eb88' }); + export const debugStackframeActive = new Codicon('debug-stackframe-active', { fontCharacter: '\\eb89' }); + export const debugStackframeDot = new Codicon('debug-stackframe-dot', { fontCharacter: '\\eb8a' }); + export const debugStackframe = new Codicon('debug-stackframe', { fontCharacter: '\\eb8b' }); + export const debugStackframeFocused = new Codicon('debug-stackframe-focused', { fontCharacter: '\\eb8b' }); + export const debugBreakpointUnsupported = new Codicon('debug-breakpoint-unsupported', { fontCharacter: '\\eb8c' }); + export const symbolString = new Codicon('symbol-string', { fontCharacter: '\\eb8d' }); + export const debugReverseContinue = new Codicon('debug-reverse-continue', { fontCharacter: '\\eb8e' }); + export const debugStepBack = new Codicon('debug-step-back', { fontCharacter: '\\eb8f' }); + export const debugRestartFrame = new Codicon('debug-restart-frame', { fontCharacter: '\\eb90' }); + export const callIncoming = new Codicon('call-incoming', { fontCharacter: '\\eb92' }); + export const callOutgoing = new Codicon('call-outgoing', { fontCharacter: '\\eb93' }); + export const menu = new Codicon('menu', { fontCharacter: '\\eb94' }); + export const expandAll = new Codicon('expand-all', { fontCharacter: '\\eb95' }); + export const feedback = new Codicon('feedback', { fontCharacter: '\\eb96' }); + export const groupByRefType = new Codicon('group-by-ref-type', { fontCharacter: '\\eb97' }); + export const ungroupByRefType = new Codicon('ungroup-by-ref-type', { fontCharacter: '\\eb98' }); + export const account = new Codicon('account', { fontCharacter: '\\eb99' }); + export const bellDot = new Codicon('bell-dot', { fontCharacter: '\\eb9a' }); + export const debugConsole = new Codicon('debug-console', { fontCharacter: '\\eb9b' }); + export const library = new Codicon('library', { fontCharacter: '\\eb9c' }); + export const output = new Codicon('output', { fontCharacter: '\\eb9d' }); + export const runAll = new Codicon('run-all', { fontCharacter: '\\eb9e' }); + export const syncIgnored = new Codicon('sync-ignored', { fontCharacter: '\\eb9f' }); + export const pinned = new Codicon('pinned', { fontCharacter: '\\eba0' }); + export const githubInverted = new Codicon('github-inverted', { fontCharacter: '\\eba1' }); + export const debugAlt = new Codicon('debug-alt', { fontCharacter: '\\eb91' }); + export const serverProcess = new Codicon('server-process', { fontCharacter: '\\eba2' }); + export const serverEnvironment = new Codicon('server-environment', { fontCharacter: '\\eba3' }); + export const pass = new Codicon('pass', { fontCharacter: '\\eba4' }); + export const stopCircle = new Codicon('stop-circle', { fontCharacter: '\\eba5' }); + export const playCircle = new Codicon('play-circle', { fontCharacter: '\\eba6' }); + export const record = new Codicon('record', { fontCharacter: '\\eba7' }); + export const debugAltSmall = new Codicon('debug-alt-small', { fontCharacter: '\\eba8' }); + export const vmConnect = new Codicon('vm-connect', { fontCharacter: '\\eba9' }); + export const cloud = new Codicon('cloud', { fontCharacter: '\\ebaa' }); + export const merge = new Codicon('merge', { fontCharacter: '\\ebab' }); + export const exportIcon = new Codicon('export', { fontCharacter: '\\ebac' }); + export const graphLeft = new Codicon('graph-left', { fontCharacter: '\\ebad' }); + export const magnet = new Codicon('magnet', { fontCharacter: '\\ebae' }); + export const notebook = new Codicon('notebook', { fontCharacter: '\\ebaf' }); + export const redo = new Codicon('redo', { fontCharacter: '\\ebb0' }); + export const checkAll = new Codicon('check-all', { fontCharacter: '\\ebb1' }); + export const pinnedDirty = new Codicon('pinned-dirty', { fontCharacter: '\\ebb2' }); + export const passFilled = new Codicon('pass-filled', { fontCharacter: '\\ebb3' }); + export const circleLargeFilled = new Codicon('circle-large-filled', { fontCharacter: '\\ebb4' }); + export const circleLargeOutline = new Codicon('circle-large-outline', { fontCharacter: '\\ebb5' }); + export const combine = new Codicon('combine', { fontCharacter: '\\ebb6' }); + export const gather = new Codicon('gather', { fontCharacter: '\\ebb6' }); + export const table = new Codicon('table', { fontCharacter: '\\ebb7' }); + export const variableGroup = new Codicon('variable-group', { fontCharacter: '\\ebb8' }); + export const typeHierarchy = new Codicon('type-hierarchy', { fontCharacter: '\\ebb9' }); + export const typeHierarchySub = new Codicon('type-hierarchy-sub', { fontCharacter: '\\ebba' }); + export const typeHierarchySuper = new Codicon('type-hierarchy-super', { fontCharacter: '\\ebbb' }); + export const gitPullRequestCreate = new Codicon('git-pull-request-create', { fontCharacter: '\\ebbc' }); + export const runAbove = new Codicon('run-above', { fontCharacter: '\\ebbd' }); + export const runBelow = new Codicon('run-below', { fontCharacter: '\\ebbe' }); + export const notebookTemplate = new Codicon('notebook-template', { fontCharacter: '\\ebbf' }); + export const debugRerun = new Codicon('debug-rerun', { fontCharacter: '\\ebc0' }); export const dropDownButton = new Codicon('drop-down-button', Codicon.chevronDown.definition); } diff --git a/src/vs/base/common/errors.ts b/src/vs/base/common/errors.ts index 8c8e874e8..6043f4693 100644 --- a/src/vs/base/common/errors.ts +++ b/src/vs/base/common/errors.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IAction } from 'vs/base/common/actions'; + export interface ErrorListenerCallback { (error: any): void; } @@ -221,3 +223,31 @@ export class NotSupportedError extends Error { } } } + +export class ExpectedError extends Error { + readonly isExpected = true; +} + +export interface IErrorOptions { + actions?: ReadonlyArray; +} + +export interface IErrorWithActions { + actions?: ReadonlyArray; +} + +export function isErrorWithActions(obj: unknown): obj is IErrorWithActions { + const candidate = obj as IErrorWithActions | undefined; + + return candidate instanceof Error && Array.isArray(candidate.actions); +} + +export function createErrorWithActions(message: string, options: IErrorOptions = Object.create(null)): Error & IErrorWithActions { + const result = new Error(message); + + if (options.actions) { + (result as IErrorWithActions).actions = options.actions; + } + + return result; +} diff --git a/src/vs/base/common/errorsWithActions.ts b/src/vs/base/common/errorsWithActions.ts deleted file mode 100644 index fa92b7f45..000000000 --- a/src/vs/base/common/errorsWithActions.ts +++ /dev/null @@ -1,28 +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 { IAction } from 'vs/base/common/actions'; - -export interface IErrorOptions { - actions?: ReadonlyArray; -} - -export interface IErrorWithActions { - actions?: ReadonlyArray; -} - -export function isErrorWithActions(obj: unknown): obj is IErrorWithActions { - return obj instanceof Error && Array.isArray((obj as IErrorWithActions).actions); -} - -export function createErrorWithActions(message: string, options: IErrorOptions = Object.create(null)): Error & IErrorWithActions { - const result = new Error(message); - - if (options.actions) { - (result).actions = options.actions; - } - - return result; -} diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index b433d1a32..add858d25 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -7,7 +7,6 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { once as onceFn } from 'vs/base/common/functional'; import { Disposable, IDisposable, toDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { LinkedList } from 'vs/base/common/linkedList'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { StopWatch } from 'vs/base/common/stopwatch'; /** @@ -379,7 +378,7 @@ export namespace Event { } } -type Listener = [(e: T) => void, any] | ((e: T) => void); +export type Listener = [(e: T) => void, any] | ((e: T) => void); export interface EmitterOptions { onFirstListenerAdd?: Function; @@ -686,64 +685,6 @@ export class PauseableEmitter extends Emitter { } } -export interface IWaitUntil { - waitUntil(thenable: Promise): void; -} - -export class AsyncEmitter extends Emitter { - - private _asyncDeliveryQueue?: LinkedList<[Listener, Omit]>; - - async fireAsync(data: Omit, token: CancellationToken, promiseJoin?: (p: Promise, listener: Function) => Promise): Promise { - if (!this._listeners) { - return; - } - - if (!this._asyncDeliveryQueue) { - this._asyncDeliveryQueue = new LinkedList(); - } - - for (const listener of this._listeners) { - this._asyncDeliveryQueue.push([listener, data]); - } - - while (this._asyncDeliveryQueue.size > 0 && !token.isCancellationRequested) { - - const [listener, data] = this._asyncDeliveryQueue.shift()!; - const thenables: Promise[] = []; - - const event = { - ...data, - waitUntil: (p: Promise): void => { - if (Object.isFrozen(thenables)) { - throw new Error('waitUntil can NOT be called asynchronous'); - } - if (promiseJoin) { - p = promiseJoin(p, typeof listener === 'function' ? listener : listener[0]); - } - thenables.push(p); - } - }; - - try { - if (typeof listener === 'function') { - listener.call(undefined, event); - } else { - listener[0].call(listener[1], event); - } - } catch (e) { - onUnexpectedError(e); - continue; - } - - // freeze thenables-collection to enforce sync-calls to - // wait until and then wait for all thenables to resolve - Object.freeze(thenables); - await Promise.all(thenables).catch(e => onUnexpectedError(e)); - } - } -} - export class EventMultiplexer implements IDisposable { private readonly emitter: Emitter; diff --git a/src/vs/base/common/extpath.ts b/src/vs/base/common/extpath.ts index 40fd732e9..c6d8b39aa 100644 --- a/src/vs/base/common/extpath.ts +++ b/src/vs/base/common/extpath.ts @@ -28,7 +28,6 @@ export function toSlashes(osPath: string) { * or `getRoot('\\server\shares\path') === \\server\shares\` */ export function getRoot(path: string, sep: string = posix.sep): string { - if (!path) { return ''; } diff --git a/src/vs/base/common/filters.ts b/src/vs/base/common/filters.ts index 653587dc5..48f708a44 100644 --- a/src/vs/base/common/filters.ts +++ b/src/vs/base/common/filters.ts @@ -547,8 +547,8 @@ export namespace FuzzyScore { */ export const Default: FuzzyScore = ([-100, 0]); - export function isDefault(score?: FuzzyScore): score is [-100, 0, 0] { - return !score || (score[0] === -100 && score[1] === 0 && score[2] === 0); + export function isDefault(score?: FuzzyScore): score is [-100, 0] { + return !score || (score.length === 2 && score[0] === -100 && score[1] === 0); } } diff --git a/src/vs/base/common/iconLabels.ts b/src/vs/base/common/iconLabels.ts index 8ad65bee7..222e1bb94 100644 --- a/src/vs/base/common/iconLabels.ts +++ b/src/vs/base/common/iconLabels.ts @@ -3,28 +3,26 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CSSIcon } from 'vs/base/common/codicons'; import { matchesFuzzy, IMatch } from 'vs/base/common/filters'; import { ltrim } from 'vs/base/common/strings'; export const iconStartMarker = '$('; -const escapeIconsRegex = /(\\)?\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)/gi; +const iconsRegex = new RegExp(`\\$\\(${CSSIcon.iconNameExpression}(?:${CSSIcon.iconModifierExpression})?\\)`, 'g'); // no capturing groups + +const escapeIconsRegex = new RegExp(`(\\\\)?${iconsRegex.source}`, 'g'); export function escapeIcons(text: string): string { return text.replace(escapeIconsRegex, (match, escaped) => escaped ? match : `\\${match}`); } -const markdownEscapedIconsRegex = /\\\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)/gi; +const markdownEscapedIconsRegex = new RegExp(`\\\\${iconsRegex.source}`, 'g'); export function markdownEscapeEscapedIcons(text: string): string { // Need to add an extra \ for escaping in markdown return text.replace(markdownEscapedIconsRegex, match => `\\${match}`); } -const markdownUnescapeIconsRegex = /(\\)?\$\\\(([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?)\\\)/gi; -export function markdownUnescapeIcons(text: string): string { - return text.replace(markdownUnescapeIconsRegex, (match, escaped, iconId) => escaped ? match : `$(${iconId})`); -} - -const stripIconsRegex = /(\s)?(\\)?\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)(\s)?/gi; +const stripIconsRegex = new RegExp(`(\\s)?(\\\\)?${iconsRegex.source}(\\s)?`, 'g'); export function stripIcons(text: string): string { if (text.indexOf(iconStartMarker) === -1) { return text; diff --git a/src/vs/base/common/iterator.ts b/src/vs/base/common/iterator.ts index 4b66374e5..8b60c8757 100644 --- a/src/vs/base/common/iterator.ts +++ b/src/vs/base/common/iterator.ts @@ -39,6 +39,18 @@ export namespace Iterable { return false; } + export function find(iterable: Iterable, predicate: (t: T) => t is R): T | undefined; + export function find(iterable: Iterable, predicate: (t: T) => boolean): T | undefined; + export function find(iterable: Iterable, predicate: (t: T) => boolean): T | undefined { + for (const element of iterable) { + if (predicate(element)) { + return element; + } + } + + return undefined; + } + export function filter(iterable: Iterable, predicate: (t: T) => t is R): Iterable; export function filter(iterable: Iterable, predicate: (t: T) => boolean): Iterable; export function* filter(iterable: Iterable, predicate: (t: T) => boolean): Iterable { @@ -71,22 +83,30 @@ export namespace Iterable { } } + export function reduce(iterable: Iterable, reducer: (previousValue: R, currentValue: T) => R, initialValue: R): R { + let value = initialValue; + for (const element of iterable) { + value = reducer(value, element); + } + return value; + } + /** * Returns an iterable slice of the array, with the same semantics as `array.slice()`. */ - export function* slice(iterable: ReadonlyArray, from: number, to = iterable.length): Iterable { + export function* slice(arr: ReadonlyArray, from: number, to = arr.length): Iterable { if (from < 0) { - from += iterable.length; + from += arr.length; } if (to < 0) { - to += iterable.length; - } else if (to > iterable.length) { - to = iterable.length; + to += arr.length; + } else if (to > arr.length) { + to = arr.length; } for (; from < to; from++) { - yield iterable[from]; + yield arr[from]; } } @@ -115,4 +135,25 @@ export namespace Iterable { return [consumed, { [Symbol.iterator]() { return iterator; } }]; } + + /** + * Returns whether the iterables are the same length and all items are + * equal using the comparator function. + */ + export function equals(a: Iterable, b: Iterable, comparator = (at: T, bt: T) => at === bt) { + const ai = a[Symbol.iterator](); + const bi = b[Symbol.iterator](); + while (true) { + const an = ai.next(); + const bn = bi.next(); + + if (an.done !== bn.done) { + return false; + } else if (an.done) { + return true; + } else if (!comparator(an.value, bn.value)) { + return false; + } + } + } } diff --git a/src/vs/base/common/keyCodes.ts b/src/vs/base/common/keyCodes.ts index ec08bea7e..1406c4536 100644 --- a/src/vs/base/common/keyCodes.ts +++ b/src/vs/base/common/keyCodes.ts @@ -597,6 +597,17 @@ export abstract class ResolvedKeybinding { /** * Returns the parts that should be used for dispatching. + * Returns null for parts consisting of only modifier keys + * @example keybinding "Shift" -> null + * @example keybinding ("D" with shift == true) -> "shift+D" */ public abstract getDispatchParts(): (string | null)[]; + + /** + * Returns the parts that should be used for dispatching single modifier keys + * Returns null for parts that contain more than one modifier or a regular key. + * @example keybinding "Shift" -> "shift" + * @example keybinding ("D" with shift == true") -> null + */ + public abstract getSingleModifierDispatchParts(): (string | null)[]; } diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index fd4a71fc8..82b3adfab 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -68,7 +68,7 @@ export class MultiDisposeError extends Error { constructor( public readonly errors: any[] ) { - super(`Encounter errors while disposing of store. Errors: [${errors.join(', ')}]`); + super(`Encountered errors while disposing of store. Errors: [${errors.join(', ')}]`); } } @@ -231,9 +231,7 @@ export class MutableDisposable implements IDisposable { return; } - if (this._value) { - this._value.dispose(); - } + this._value?.dispose(); if (value) { markTracked(value); } @@ -247,9 +245,7 @@ export class MutableDisposable implements IDisposable { dispose(): void { this._isDisposed = true; markTracked(this); - if (this._value) { - this._value.dispose(); - } + this._value?.dispose(); this._value = undefined; } } diff --git a/src/vs/base/common/marked/marked.js b/src/vs/base/common/marked/marked.js index ce8d16410..731b513a4 100644 --- a/src/vs/base/common/marked/marked.js +++ b/src/vs/base/common/marked/marked.js @@ -1,6 +1,6 @@ /** * marked - a markdown parser - * Copyright (c) 2011-2020, Christopher Jeffrey. (MIT Licensed) + * Copyright (c) 2011-2021, Christopher Jeffrey. (MIT Licensed) * https://github.com/markedjs/marked */ @@ -73,8 +73,9 @@ return it.next.bind(it); } - function createCommonjsModule(fn, module) { - return module = { exports: {} }, fn(module, module.exports), module.exports; + function createCommonjsModule(fn) { + var module = { exports: {} }; + return fn(module, module.exports), module.exports; } var defaults = createCommonjsModule(function (module) { @@ -111,9 +112,6 @@ changeDefaults: changeDefaults }; }); - var defaults_1 = defaults.defaults; - var defaults_2 = defaults.getDefaults; - var defaults_3 = defaults.changeDefaults; /** * Helpers @@ -490,20 +488,11 @@ } }; - _proto.code = function code(src, tokens) { + _proto.code = function code(src) { var cap = this.rules.block.code.exec(src); if (cap) { - var lastToken = tokens[tokens.length - 1]; // An indented code block cannot interrupt a paragraph. - - if (lastToken && lastToken.type === 'paragraph') { - return { - raw: cap[0], - text: cap[0].trimRight() - }; - } - - var text = cap[0].replace(/^ {4}/gm, ''); + var text = cap[0].replace(/^ {1,4}/gm, ''); return { type: 'code', raw: cap[0], @@ -532,11 +521,24 @@ var cap = this.rules.block.heading.exec(src); if (cap) { + var text = cap[2].trim(); // remove trailing #s + + if (/#$/.test(text)) { + var trimmed = rtrim$1(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: cap[2] + text: text }; } }; @@ -611,7 +613,6 @@ var raw = cap[0]; var bull = cap[2]; var isordered = bull.length > 1; - var isparen = bull[bull.length - 1] === ')'; var list = { type: 'list', raw: raw, @@ -625,18 +626,43 @@ var next = false, item, space, - b, + bcurr, + bnext, addBack, loose, istask, ischecked; var l = itemMatch.length; + bcurr = this.rules.block.listItemStart.exec(itemMatch[0]); for (var i = 0; i < l; i++) { item = itemMatch[i]; - raw = item; // Remove the list item's bullet + raw = item; // Determine whether the next list item belongs here. + // Backpedal if it does not belong in this list. + + if (i !== l - 1) { + bnext = this.rules.block.listItemStart.exec(itemMatch[i + 1]); + + if (!this.options.pedantic ? bnext[1].length > bcurr[0].length || bnext[1].length > 3 : bnext[1].length > bcurr[1].length) { + // nested list + itemMatch.splice(i, 2, itemMatch[i] + '\n' + itemMatch[i + 1]); + i--; + l--; + continue; + } else { + if ( // different bullet style + !this.options.pedantic || this.options.smartLists ? bnext[2][bnext[2].length - 1] !== bull[bull.length - 1] : isordered === (bnext[2].length === 1)) { + addBack = itemMatch.slice(i + 1).join('\n'); + list.raw = list.raw.substring(0, list.raw.length - addBack.length); + i = l - 1; + } + } + + bcurr = bnext; + } // Remove the list item's bullet // so it is seen as the next token. + space = item.length; item = item.replace(/^ *([*+-]|\d+[.)]) ?/, ''); // Outdent whatever the // list item contains. Hacky. @@ -644,18 +670,6 @@ if (~item.indexOf('\n ')) { space -= item.length; item = !this.options.pedantic ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') : item.replace(/^ {1,4}/gm, ''); - } // Determine whether the next list item belongs here. - // Backpedal if it does not belong in this list. - - - if (i !== l - 1) { - b = this.rules.block.bullet.exec(itemMatch[i + 1])[0]; - - if (isordered ? b.length === 1 || !isparen && b[b.length - 1] === ')' : b.length > 1 || this.options.smartLists && b !== bull) { - addBack = itemMatch.slice(i + 1).join('\n'); - list.raw = list.raw.substring(0, list.raw.length - addBack.length); - i = l - 1; - } } // Determine whether item is loose or not. // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ // for discount behavior. @@ -673,12 +687,14 @@ } // Check for task list items - istask = /^\[[ xX]\] /.test(item); - ischecked = undefined; + if (this.options.gfm) { + istask = /^\[[ xX]\] /.test(item); + ischecked = undefined; - if (istask) { - ischecked = item[1] !== ' '; - item = item.replace(/^\[[ xX]\] +/, ''); + if (istask) { + ischecked = item[1] !== ' '; + item = item.replace(/^\[[ xX]\] +/, ''); + } } list.items.push({ @@ -787,19 +803,10 @@ } }; - _proto.text = function text(src, tokens) { + _proto.text = function text(src) { var cap = this.rules.block.text.exec(src); if (cap) { - var lastToken = tokens[tokens.length - 1]; - - if (lastToken && lastToken.type === 'text') { - return { - raw: cap[0], - text: cap[0] - }; - } - return { type: 'text', raw: cap[0], @@ -850,38 +857,63 @@ var cap = this.rules.inline.link.exec(src); if (cap) { - var lastParenIndex = findClosingBracket$1(cap[2], '()'); + var trimmedUrl = cap[2].trim(); - 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] = ''; + if (!this.options.pedantic && /^$/.test(trimmedUrl)) { + return; + } // ending angle bracket cannot be escaped + + + var rtrimSlash = rtrim$1(trimmedUrl.slice(0, -1), '\\'); + + if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) { + return; + } + } else { + // find closing parenthesis + var lastParenIndex = findClosingBracket$1(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 = ''; } } else { title = cap[3] ? cap[3].slice(1, -1) : ''; } - href = href.trim().replace(/^<([\s\S]*)>$/, '$1'); - var token = outputLink(cap, { + 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]); - return token; } }; @@ -901,59 +933,70 @@ }; } - var token = outputLink(cap, link, cap[0]); - return token; + return outputLink(cap, link, cap[0]); } }; - _proto.strong = function strong(src, maskedSrc, prevChar) { + _proto.emStrong = function emStrong(src, maskedSrc, prevChar) { if (prevChar === void 0) { prevChar = ''; } - var match = this.rules.inline.strong.start.exec(src); + var match = this.rules.inline.emStrong.lDelim.exec(src); + if (!match) return; + 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\u08A0-\u08B4\u08B6-\u08C7\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\u0C60\u0C61\u0C66-\u0C6F\u0C78-\u0C7E\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\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-\u170C\u170E-\u1711\u1720-\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-\u1B4B\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-\u2C2E\u2C30-\u2C5E\u2C60-\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-\u9FFC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7BF\uA7C2-\uA7CA\uA7F5-\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\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\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\uDFB0-\uDFCB\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC52-\uDC6F\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]|\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\uDEC0-\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]|[\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\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]|\uD82C[\uDC00-\uDD1E\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]|\uD838[\uDD00-\uDD2C\uDD37-\uDD3D\uDD40-\uDD49\uDD4E\uDEC0-\uDEEB\uDEF0-\uDEF9]|\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-\uDEDD\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A])/)) return; // _ can't be between two alphanumerics. \p{L}\p{N} includes non-english alphabet/numbers as well - if (match && (!match[1] || match[1] && (prevChar === '' || this.rules.inline.punctuation.exec(prevChar)))) { - maskedSrc = maskedSrc.slice(-1 * src.length); - var endReg = match[0] === '**' ? this.rules.inline.strong.endAst : this.rules.inline.strong.endUnd; + 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; - var cap; + maskedSrc = maskedSrc.slice(-1 * src.length + lLength); // Bump maskedSrc to same section of string as src (move to lexer?) while ((match = endReg.exec(maskedSrc)) != null) { - cap = this.rules.inline.strong.middle.exec(maskedSrc.slice(0, match.index + 3)); + rDelim = match[1] || match[2] || match[3] || match[4] || match[5] || match[6]; + if (!rDelim) continue; // matched the first alternative in rules.js (skip the * in __abc*abc__) - if (cap) { - return { - type: 'strong', - raw: src.slice(0, cap[0].length), - text: src.slice(2, cap[0].length - 2) - }; + 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 + } } - } - } - }; - _proto.em = function em(src, maskedSrc, prevChar) { - if (prevChar === void 0) { - prevChar = ''; - } + delimTotal -= rLength; + if (delimTotal > 0) continue; // Haven't found enough closing delimiters + // If this is the last rDelimiter, remove extra characters. *a*** -> *a* - var match = this.rules.inline.em.start.exec(src); + if (delimTotal + midDelimTotal - rLength <= 0 && !maskedSrc.slice(endReg.lastIndex).match(endReg)) { + rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal); + } - if (match && (!match[1] || match[1] && (prevChar === '' || this.rules.inline.punctuation.exec(prevChar)))) { - maskedSrc = maskedSrc.slice(-1 * src.length); - var endReg = match[0] === '*' ? this.rules.inline.em.endAst : this.rules.inline.em.endUnd; - endReg.lastIndex = 0; - var cap; - - while ((match = endReg.exec(maskedSrc)) != null) { - cap = this.rules.inline.em.middle.exec(maskedSrc.slice(0, match.index + 2)); - - if (cap) { + if (Math.min(lLength, rLength) % 2) { return { type: 'em', - raw: src.slice(0, cap[0].length), - text: src.slice(1, cap[0].length - 1) + raw: src.slice(0, lLength + match.index + rLength + 1), + text: src.slice(1, lLength + match.index + rLength) + }; + } + + if (Math.min(lLength, rLength) % 2 === 0) { + return { + type: 'strong', + raw: src.slice(0, lLength + match.index + rLength + 1), + text: src.slice(2, lLength + match.index + rLength - 1) }; } } @@ -966,7 +1009,7 @@ if (cap) { var text = cap[2].replace(/\n/g, ' '); var hasNonSpaceChars = /[^ ]/.test(text); - var hasSpaceCharsOnBothEnds = text.startsWith(' ') && text.endsWith(' '); + var hasSpaceCharsOnBothEnds = /^ /.test(text) && / $/.test(text); if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) { text = text.substring(1, text.length - 1); @@ -999,7 +1042,7 @@ return { type: 'del', raw: cap[0], - text: cap[1] + text: cap[2] }; } }; @@ -1104,13 +1147,13 @@ */ var block = { - newline: /^\n+/, - code: /^( {4}[^\n]+\n*)+/, + 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}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/, - heading: /^ {0,3}(#{1,6}) +([^\n]*?)(?: +#+)? *(?:\n+|$)/, + heading: /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/, blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, - list: /^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, + list: /^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?! {0,3}bull )\n*|\s*$)/, html: '^ {0,3}(?:' // optional indentation + '<(script|pre|style)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)' // (1) + '|comment[^\\n]*(\\n+|$)' // (2) @@ -1127,15 +1170,16 @@ 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)[^\n]+)*)/, + _paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html| +\n)[^\n]+)*)/, text: /^[^\n]+/ }; block._label = /(?!\s*\])(?:\\[\[\]]|[^\[\]])+/; block._title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/; block.def = edit$1(block.def).replace('label', block._label).replace('title', block._title).getRegex(); block.bullet = /(?:[*+-]|\d{1,9}[.)])/; - block.item = /^( *)(bull) ?[^\n]*(?:\n(?!\1bull ?)[^\n]*)*/; + block.item = /^( *)(bull) ?[^\n]*(?:\n(?! *bull ?)[^\n]*)*/; block.item = edit$1(block.item, 'gm').replace(/bull/g, block.bullet).getRegex(); + block.listItemStart = edit$1(/^( *)(bull)/).replace('bull', block.bullet).getRegex(); block.list = edit$1(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 = /|$)/; @@ -1178,7 +1222,7 @@ html: edit$1('^ *(?: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]+?) *(?:#+ *)?(?:\n+|$)/, + heading: /^(#{1,6})(.*)(?:\n+|$)/, fences: noopTest$1, // fences not supported paragraph: edit$1(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() @@ -1201,48 +1245,31 @@ reflink: /^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/, nolink: /^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/, reflinkSearch: 'reflink|nolink(?!\\()', - strong: { - start: /^(?:(\*\*(?=[*punctuation]))|\*\*)(?![\s])|__/, - // (1) returns if starts w/ punctuation - middle: /^\*\*(?:(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)|\*(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)*?\*)+?\*\*$|^__(?![\s])((?:(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)|_(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)*?_)+?)__$/, - endAst: /[^punctuation\s]\*\*(?!\*)|[punctuation]\*\*(?!\*)(?:(?=[punctuation_\s]|$))/, - // last char can't be punct, or final * must also be followed by punct (or endline) - endUnd: /[^\s]__(?!_)(?:(?=[punctuation*\s])|$)/ // last char can't be a space, and final _ must preceed punct or \s (or endline) - - }, - em: { - start: /^(?:(\*(?=[punctuation]))|\*)(?![*\s])|_/, - // (1) returns if starts w/ punctuation - middle: /^\*(?:(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)|\*(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)*?\*)+?\*$|^_(?![_\s])(?:(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)|_(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)*?_)+?_$/, - endAst: /[^punctuation\s]\*(?!\*)|[punctuation]\*(?!\*)(?:(?=[punctuation_\s]|$))/, - // last char can't be punct, or final * must also be followed by punct (or endline) - endUnd: /[^\s]_(?!_)(?:(?=[punctuation*\s])|$)/ // last char can't be a space, and final _ must preceed punct or \s (or endline) + 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 other delimiter (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$1, - text: /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\?@\\[\\]`^{|}~'; inline.punctuation = edit$1(inline.punctuation).replace(/punctuation/g, inline._punctuation).getRegex(); // sequences em should skip over [title](link), `code`, - inline._blockSkip = '\\[[^\\]]*?\\]\\([^\\)]*?\\)|`[^`]*?`|<[^>]*?>'; - inline._overlapSkip = '__[^_]*?__|\\*\\*\\[^\\*\\]*?\\*\\*'; + inline.blockSkip = /\[[^\]]*?\]\([^\)]*?\)|`[^`]*?`|<[^>]*?>/g; + inline.escapedEmSt = /\\\*|\\_/g; inline._comment = edit$1(block._comment).replace('(?:-->|$)', '-->').getRegex(); - inline.em.start = edit$1(inline.em.start).replace(/punctuation/g, inline._punctuation).getRegex(); - inline.em.middle = edit$1(inline.em.middle).replace(/punctuation/g, inline._punctuation).replace(/overlapSkip/g, inline._overlapSkip).getRegex(); - inline.em.endAst = edit$1(inline.em.endAst, 'g').replace(/punctuation/g, inline._punctuation).getRegex(); - inline.em.endUnd = edit$1(inline.em.endUnd, 'g').replace(/punctuation/g, inline._punctuation).getRegex(); - inline.strong.start = edit$1(inline.strong.start).replace(/punctuation/g, inline._punctuation).getRegex(); - inline.strong.middle = edit$1(inline.strong.middle).replace(/punctuation/g, inline._punctuation).replace(/overlapSkip/g, inline._overlapSkip).getRegex(); - inline.strong.endAst = edit$1(inline.strong.endAst, 'g').replace(/punctuation/g, inline._punctuation).getRegex(); - inline.strong.endUnd = edit$1(inline.strong.endUnd, 'g').replace(/punctuation/g, inline._punctuation).getRegex(); - inline.blockSkip = edit$1(inline._blockSkip, 'g').getRegex(); - inline.overlapSkip = edit$1(inline._overlapSkip, 'g').getRegex(); + inline.emStrong.lDelim = edit$1(inline.emStrong.lDelim).replace(/punct/g, inline._punctuation).getRegex(); + inline.emStrong.rDelimAst = edit$1(inline.emStrong.rDelimAst, 'g').replace(/punct/g, inline._punctuation).getRegex(); + inline.emStrong.rDelimUnd = edit$1(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])?)+(?![-_])/; @@ -1250,7 +1277,7 @@ inline._attribute = /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/; inline.tag = edit$1(inline.tag).replace('comment', inline._comment).replace('attribute', inline._attribute).getRegex(); inline._label = /(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/; - inline._href = /<(?:\\[<>]?|[^\s<>\\])*>|[^\s\x00-\x1f]*/; + inline._href = /<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/; inline._title = /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/; inline.link = edit$1(inline.link).replace('label', inline._label).replace('href', inline._href).replace('title', inline._title).getRegex(); inline.reflink = edit$1(inline.reflink).replace('label', inline._label).getRegex(); @@ -1289,8 +1316,8 @@ _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)~+/, - text: /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\' + (escaped ? _code : escape$1(_code, true)) + '\n'; } diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index a409e7722..402d51497 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -64,6 +64,8 @@ export namespace Schemas { export const vscodeSettings = 'vscode-settings'; + export const vscodeWorkspaceTrust = 'vscode-workspace-trust'; + export const webviewPanel = 'webview-panel'; /** diff --git a/src/vs/base/common/paging.ts b/src/vs/base/common/paging.ts index cc808e645..27099588b 100644 --- a/src/vs/base/common/paging.ts +++ b/src/vs/base/common/paging.ts @@ -187,18 +187,3 @@ export function mapPager(pager: IPager, fn: (t: T) => R): IPager { getPage: (pageIndex, token) => pager.getPage(pageIndex, token).then(r => r.map(fn)) }; } - -/** - * Merges two pagers. - */ -export function mergePagers(one: IPager, other: IPager): IPager { - return { - firstPage: [...one.firstPage, ...other.firstPage], - total: one.total + other.total, - pageSize: one.pageSize + other.pageSize, - getPage(pageIndex: number, token): Promise { - return Promise.all([one.getPage(pageIndex, token), other.getPage(pageIndex, token)]) - .then(([onePage, otherPage]) => [...onePage, ...otherPage]); - } - }; -} \ No newline at end of file diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index 1718b5116..e61f64b9d 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -235,7 +235,7 @@ export const setImmediate: ISetImmediate = (function defineSetImmediate() { globals.postMessage({ vscodeSetImmediateId: myId }, '*'); }; } - if (nodeProcess) { + if (nodeProcess && typeof nodeProcess.nextTick === 'function') { return nodeProcess.nextTick.bind(nodeProcess); } const _promise = Promise.resolve(); diff --git a/src/vs/base/common/resources.ts b/src/vs/base/common/resources.ts index 82619c4b0..f516eec53 100644 --- a/src/vs/base/common/resources.ts +++ b/src/vs/base/common/resources.ts @@ -42,8 +42,8 @@ export interface IExtUri { /** * Tests whether a `candidate` URI is a parent or equal of a given `base` URI. * - * @param base A uri which is "longer" - * @param parentCandidate A uri which is "shorter" then `base` + * @param base A uri which is "longer" or at least same length as `parentCandidate` + * @param parentCandidate A uri which is "shorter" or up to same length as `base` * @param ignoreFragment Ignore the fragment (defaults to `false`) */ isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment?: boolean): boolean; diff --git a/src/vs/base/common/stream.ts b/src/vs/base/common/stream.ts index 47df4fcd7..d9e26f56a 100644 --- a/src/vs/base/common/stream.ts +++ b/src/vs/base/common/stream.ts @@ -16,6 +16,13 @@ export interface ReadableStreamEvents { /** * The 'data' event is emitted whenever the stream is * relinquishing ownership of a chunk of data to a consumer. + * + * NOTE: PLEASE UNDERSTAND THAT ADDING A DATA LISTENER CAN + * TURN THE STREAM INTO FLOWING MODE. IT IS THEREFOR THE + * LAST LISTENER THAT SHOULD BE ADDED AND NOT THE FIRST + * + * Use `listenStream` as a helper method to listen to + * stream events in the right order. */ on(event: 'data', callback: (data: T) => void): void; @@ -268,7 +275,7 @@ class WriteableStreamImpl implements WriteableStream { // end with data or error if provided if (result instanceof Error) { this.error(result); - } else if (result) { + } else if (typeof result !== 'undefined') { this.write(result); } @@ -489,22 +496,74 @@ export function peekReadable(readable: Readable, reducer: IReducer, max } /** - * Helper to fully read a T stream into a T. + * Helper to fully read a T stream into a T or consuming + * a stream fully, awaiting all the events without caring + * about the data. */ -export function consumeStream(stream: ReadableStreamEvents, reducer: IReducer): Promise { +export function consumeStream(stream: ReadableStreamEvents, reducer: IReducer): Promise; +export function consumeStream(stream: ReadableStreamEvents): Promise; +export function consumeStream(stream: ReadableStreamEvents, reducer?: IReducer): Promise { return new Promise((resolve, reject) => { const chunks: T[] = []; - stream.on('error', error => reject(error)); - stream.on('end', () => resolve(reducer(chunks))); - - // Adding the `data` listener will turn the stream - // into flowing mode. As such it is important to - // add this listener last (DO NOT CHANGE!) - stream.on('data', data => chunks.push(data)); + listenStream(stream, { + onData: chunk => { + if (reducer) { + chunks.push(chunk); + } + }, + onError: error => { + if (reducer) { + reject(error); + } else { + resolve(undefined); + } + }, + onEnd: () => { + if (reducer) { + resolve(reducer(chunks)); + } else { + resolve(undefined); + } + } + }); }); } +export interface IStreamListener { + + /** + * The 'data' event is emitted whenever the stream is + * relinquishing ownership of a chunk of data to a consumer. + */ + onData(data: T): void; + + /** + * Emitted when any error occurs. + */ + onError(err: Error): void; + + /** + * The 'end' event is emitted when there is no more data + * to be consumed from the stream. The 'end' event will + * not be emitted unless the data is completely consumed. + */ + onEnd(): void; +} + +/** + * Helper to listen to all events of a T stream in proper order. + */ +export function listenStream(stream: ReadableStreamEvents, listener: IStreamListener): void { + stream.on('error', error => listener.onError(error)); + stream.on('end', () => listener.onEnd()); + + // Adding the `data` listener will turn the stream + // into flowing mode. As such it is important to + // add this listener last (DO NOT CHANGE!) + stream.on('data', data => listener.onData(data)); +} + /** * Helper to peek up to `maxChunks` into a stream. The return type signals if * the stream has ended or not. If not, caller needs to add a `data` listener @@ -513,9 +572,9 @@ export function consumeStream(stream: ReadableStreamEvents, reducer: IRedu export function peekStream(stream: ReadableStream, maxChunks: number): Promise> { return new Promise((resolve, reject) => { const streamListeners = new DisposableStore(); + const buffer: T[] = []; // Data Listener - const buffer: T[] = []; const dataListener = (chunk: T) => { // Add to buffer @@ -533,23 +592,27 @@ export function peekStream(stream: ReadableStream, maxChunks: number): Pro } }; - streamListeners.add(toDisposable(() => stream.removeListener('data', dataListener))); - stream.on('data', dataListener); - // Error Listener const errorListener = (error: Error) => { return reject(error); }; - streamListeners.add(toDisposable(() => stream.removeListener('error', errorListener))); - stream.on('error', errorListener); - + // End Listener const endListener = () => { return resolve({ stream, buffer, ended: true }); }; + streamListeners.add(toDisposable(() => stream.removeListener('error', errorListener))); + stream.on('error', errorListener); + streamListeners.add(toDisposable(() => stream.removeListener('end', endListener))); stream.on('end', endListener); + + // Important: leave the `data` listener last because + // this can turn the stream into flowing mode and we + // want `error` events to be received as well. + streamListeners.add(toDisposable(() => stream.removeListener('data', dataListener))); + stream.on('data', dataListener); }); } @@ -589,46 +652,11 @@ export function toReadable(t: T): Readable { export function transform(stream: ReadableStreamEvents, transformer: ITransformer, reducer: IReducer): ReadableStream { const target = newWriteableStream(reducer); - stream.on('data', data => target.write(transformer.data(data))); - stream.on('end', () => target.end()); - stream.on('error', error => target.error(transformer.error ? transformer.error(error) : error)); + listenStream(stream, { + onData: data => target.write(transformer.data(data)), + onError: error => target.error(transformer.error ? transformer.error(error) : error), + onEnd: () => target.end() + }); return target; } - -export interface IReadableStreamObservable { - - /** - * A promise to await the `end` or `error` event - * of a stream. - */ - errorOrEnd: () => Promise; -} - -/** - * Helper to observe a stream for certain events through - * a promise based API. - */ -export function observe(stream: ReadableStream): IReadableStreamObservable { - - // A stream is closed when it ended or errord - // We install this listener right from the - // beginning to catch the events early. - const errorOrEnd = Promise.race([ - new Promise(resolve => stream.on('end', () => resolve())), - new Promise(resolve => stream.on('error', () => resolve())) - ]); - - return { - errorOrEnd(): Promise { - - // We need to ensure the stream is flowing so that our - // listeners are getting triggered. It is possible that - // the stream is not flowing because no `data` listener - // was attached yet. - stream.resume(); - - return errorOrEnd; - } - }; -} diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index 84e9c52f2..e560d1692 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -677,7 +677,7 @@ export function decodeUTF8(buffer: Uint8Array): string { } /** - * Generated using https://github.com/alexandrudima/unicode-utils/blob/master/generate-rtl-test.js + * Generated using https://github.com/alexdima/unicode-utils/blob/master/generate-rtl-test.js */ const CONTAINS_RTL = /(?:[\u05BE\u05C0\u05C3\u05C6\u05D0-\u05F4\u0608\u060B\u060D\u061B-\u064A\u066D-\u066F\u0671-\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u0710\u0712-\u072F\u074D-\u07A5\u07B1-\u07EA\u07F4\u07F5\u07FA-\u0815\u081A\u0824\u0828\u0830-\u0858\u085E-\u08BD\u200F\uFB1D\uFB1F-\uFB28\uFB2A-\uFD3D\uFD50-\uFDFC\uFE70-\uFEFC]|\uD802[\uDC00-\uDD1B\uDD20-\uDE00\uDE10-\uDE33\uDE40-\uDEE4\uDEEB-\uDF35\uDF40-\uDFFF]|\uD803[\uDC00-\uDCFF]|\uD83A[\uDC00-\uDCCF\uDD00-\uDD43\uDD50-\uDFFF]|\uD83B[\uDC00-\uDEBB])/; @@ -689,9 +689,9 @@ export function containsRTL(str: string): boolean { } /** - * Generated using https://github.com/alexandrudima/unicode-utils/blob/master/generate-emoji-test.js + * Generated using https://github.com/alexdima/unicode-utils/blob/master/generate-emoji-test.js */ -const CONTAINS_EMOJI = /(?:[\u231A\u231B\u23F0\u23F3\u2600-\u27BF\u2B50\u2B55]|\uD83C[\uDDE6-\uDDFF\uDF00-\uDFFF]|\uD83D[\uDC00-\uDE4F\uDE80-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD00-\uDDFF\uDE70-\uDE73\uDE78-\uDE82\uDE90-\uDE95])/; +const CONTAINS_EMOJI = /(?:[\u231A\u231B\u23F0\u23F3\u2600-\u27BF\u2B50\u2B55]|\uD83C[\uDDE6-\uDDFF\uDF00-\uDFFF]|\uD83D[\uDC00-\uDE4F\uDE80-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD00-\uDDFF\uDE70-\uDED6])/; export function containsEmoji(str: string): boolean { return CONTAINS_EMOJI.test(str); @@ -771,13 +771,15 @@ export function isFullWidthCharacter(charCode: number): boolean { /** * A fast function (therefore imprecise) to check if code points are emojis. - * Generated using https://github.com/alexandrudima/unicode-utils/blob/master/generate-emoji-test.js + * Generated using https://github.com/alexdima/unicode-utils/blob/master/generate-emoji-test.js */ export function isEmojiImprecise(x: number): boolean { return ( - (x >= 0x1F1E6 && x <= 0x1F1FF) || (x >= 9728 && x <= 10175) || (x >= 127744 && x <= 128591) - || (x >= 128640 && x <= 128764) || (x >= 128992 && x <= 129003) || (x >= 129280 && x <= 129535) - || (x >= 129648 && x <= 129651) || (x >= 129656 && x <= 129666) || (x >= 129680 && x <= 129685) + (x >= 0x1F1E6 && x <= 0x1F1FF) || (x === 8986) || (x === 8987) || (x === 9200) + || (x === 9203) || (x >= 9728 && x <= 10175) || (x === 11088) || (x === 11093) + || (x >= 127744 && x <= 128591) || (x >= 128640 && x <= 128764) + || (x >= 128992 && x <= 129003) || (x >= 129280 && x <= 129535) + || (x >= 129648 && x <= 129750) ); } @@ -1075,7 +1077,7 @@ class GraphemeBreakTree { } function getGraphemeBreakRawData(): number[] { - // generated using https://github.com/alexandrudima/unicode-utils/blob/master/generate-grapheme-break.js + // generated using https://github.com/alexdima/unicode-utils/blob/master/generate-grapheme-break.js return JSON.parse('[0,0,0,51592,51592,11,44424,44424,11,72251,72254,5,7150,7150,7,48008,48008,11,55176,55176,11,128420,128420,14,3276,3277,5,9979,9980,14,46216,46216,11,49800,49800,11,53384,53384,11,70726,70726,5,122915,122916,5,129320,129327,14,2558,2558,5,5906,5908,5,9762,9763,14,43360,43388,8,45320,45320,11,47112,47112,11,48904,48904,11,50696,50696,11,52488,52488,11,54280,54280,11,70082,70083,1,71350,71350,7,73111,73111,5,127892,127893,14,128726,128727,14,129473,129474,14,2027,2035,5,2901,2902,5,3784,3789,5,6754,6754,5,8418,8420,5,9877,9877,14,11088,11088,14,44008,44008,5,44872,44872,11,45768,45768,11,46664,46664,11,47560,47560,11,48456,48456,11,49352,49352,11,50248,50248,11,51144,51144,11,52040,52040,11,52936,52936,11,53832,53832,11,54728,54728,11,69811,69814,5,70459,70460,5,71096,71099,7,71998,71998,5,72874,72880,5,119149,119149,7,127374,127374,14,128335,128335,14,128482,128482,14,128765,128767,14,129399,129400,14,129680,129685,14,1476,1477,5,2377,2380,7,2759,2760,5,3137,3140,7,3458,3459,7,4153,4154,5,6432,6434,5,6978,6978,5,7675,7679,5,9723,9726,14,9823,9823,14,9919,9923,14,10035,10036,14,42736,42737,5,43596,43596,5,44200,44200,11,44648,44648,11,45096,45096,11,45544,45544,11,45992,45992,11,46440,46440,11,46888,46888,11,47336,47336,11,47784,47784,11,48232,48232,11,48680,48680,11,49128,49128,11,49576,49576,11,50024,50024,11,50472,50472,11,50920,50920,11,51368,51368,11,51816,51816,11,52264,52264,11,52712,52712,11,53160,53160,11,53608,53608,11,54056,54056,11,54504,54504,11,54952,54952,11,68108,68111,5,69933,69940,5,70197,70197,7,70498,70499,7,70845,70845,5,71229,71229,5,71727,71735,5,72154,72155,5,72344,72345,5,73023,73029,5,94095,94098,5,121403,121452,5,126981,127182,14,127538,127546,14,127990,127990,14,128391,128391,14,128445,128449,14,128500,128505,14,128752,128752,14,129160,129167,14,129356,129356,14,129432,129442,14,129648,129651,14,129751,131069,14,173,173,4,1757,1757,1,2274,2274,1,2494,2494,5,2641,2641,5,2876,2876,5,3014,3016,7,3262,3262,7,3393,3396,5,3570,3571,7,3968,3972,5,4228,4228,7,6086,6086,5,6679,6680,5,6912,6915,5,7080,7081,5,7380,7392,5,8252,8252,14,9096,9096,14,9748,9749,14,9784,9786,14,9833,9850,14,9890,9894,14,9938,9938,14,9999,9999,14,10085,10087,14,12349,12349,14,43136,43137,7,43454,43456,7,43755,43755,7,44088,44088,11,44312,44312,11,44536,44536,11,44760,44760,11,44984,44984,11,45208,45208,11,45432,45432,11,45656,45656,11,45880,45880,11,46104,46104,11,46328,46328,11,46552,46552,11,46776,46776,11,47000,47000,11,47224,47224,11,47448,47448,11,47672,47672,11,47896,47896,11,48120,48120,11,48344,48344,11,48568,48568,11,48792,48792,11,49016,49016,11,49240,49240,11,49464,49464,11,49688,49688,11,49912,49912,11,50136,50136,11,50360,50360,11,50584,50584,11,50808,50808,11,51032,51032,11,51256,51256,11,51480,51480,11,51704,51704,11,51928,51928,11,52152,52152,11,52376,52376,11,52600,52600,11,52824,52824,11,53048,53048,11,53272,53272,11,53496,53496,11,53720,53720,11,53944,53944,11,54168,54168,11,54392,54392,11,54616,54616,11,54840,54840,11,55064,55064,11,65438,65439,5,69633,69633,5,69837,69837,1,70018,70018,7,70188,70190,7,70368,70370,7,70465,70468,7,70712,70719,5,70835,70840,5,70850,70851,5,71132,71133,5,71340,71340,7,71458,71461,5,71985,71989,7,72002,72002,7,72193,72202,5,72281,72283,5,72766,72766,7,72885,72886,5,73104,73105,5,92912,92916,5,113824,113827,4,119173,119179,5,121505,121519,5,125136,125142,5,127279,127279,14,127489,127490,14,127570,127743,14,127900,127901,14,128254,128254,14,128369,128370,14,128400,128400,14,128425,128432,14,128468,128475,14,128489,128494,14,128715,128720,14,128745,128745,14,128759,128760,14,129004,129023,14,129296,129304,14,129340,129342,14,129388,129392,14,129404,129407,14,129454,129455,14,129485,129487,14,129659,129663,14,129719,129727,14,917536,917631,5,13,13,2,1160,1161,5,1564,1564,4,1807,1807,1,2085,2087,5,2363,2363,7,2402,2403,5,2507,2508,7,2622,2624,7,2691,2691,7,2786,2787,5,2881,2884,5,3006,3006,5,3072,3072,5,3170,3171,5,3267,3268,7,3330,3331,7,3406,3406,1,3538,3540,5,3655,3662,5,3897,3897,5,4038,4038,5,4184,4185,5,4352,4447,8,6068,6069,5,6155,6157,5,6448,6449,7,6742,6742,5,6783,6783,5,6966,6970,5,7042,7042,7,7143,7143,7,7212,7219,5,7412,7412,5,8206,8207,4,8294,8303,4,8596,8601,14,9410,9410,14,9742,9742,14,9757,9757,14,9770,9770,14,9794,9794,14,9828,9828,14,9855,9855,14,9882,9882,14,9900,9903,14,9929,9933,14,9963,9967,14,9987,9988,14,10006,10006,14,10062,10062,14,10175,10175,14,11744,11775,5,42607,42607,5,43043,43044,7,43263,43263,5,43444,43445,7,43569,43570,5,43698,43700,5,43766,43766,5,44032,44032,11,44144,44144,11,44256,44256,11,44368,44368,11,44480,44480,11,44592,44592,11,44704,44704,11,44816,44816,11,44928,44928,11,45040,45040,11,45152,45152,11,45264,45264,11,45376,45376,11,45488,45488,11,45600,45600,11,45712,45712,11,45824,45824,11,45936,45936,11,46048,46048,11,46160,46160,11,46272,46272,11,46384,46384,11,46496,46496,11,46608,46608,11,46720,46720,11,46832,46832,11,46944,46944,11,47056,47056,11,47168,47168,11,47280,47280,11,47392,47392,11,47504,47504,11,47616,47616,11,47728,47728,11,47840,47840,11,47952,47952,11,48064,48064,11,48176,48176,11,48288,48288,11,48400,48400,11,48512,48512,11,48624,48624,11,48736,48736,11,48848,48848,11,48960,48960,11,49072,49072,11,49184,49184,11,49296,49296,11,49408,49408,11,49520,49520,11,49632,49632,11,49744,49744,11,49856,49856,11,49968,49968,11,50080,50080,11,50192,50192,11,50304,50304,11,50416,50416,11,50528,50528,11,50640,50640,11,50752,50752,11,50864,50864,11,50976,50976,11,51088,51088,11,51200,51200,11,51312,51312,11,51424,51424,11,51536,51536,11,51648,51648,11,51760,51760,11,51872,51872,11,51984,51984,11,52096,52096,11,52208,52208,11,52320,52320,11,52432,52432,11,52544,52544,11,52656,52656,11,52768,52768,11,52880,52880,11,52992,52992,11,53104,53104,11,53216,53216,11,53328,53328,11,53440,53440,11,53552,53552,11,53664,53664,11,53776,53776,11,53888,53888,11,54000,54000,11,54112,54112,11,54224,54224,11,54336,54336,11,54448,54448,11,54560,54560,11,54672,54672,11,54784,54784,11,54896,54896,11,55008,55008,11,55120,55120,11,64286,64286,5,66272,66272,5,68900,68903,5,69762,69762,7,69817,69818,5,69927,69931,5,70003,70003,5,70070,70078,5,70094,70094,7,70194,70195,7,70206,70206,5,70400,70401,5,70463,70463,7,70475,70477,7,70512,70516,5,70722,70724,5,70832,70832,5,70842,70842,5,70847,70848,5,71088,71089,7,71102,71102,7,71219,71226,5,71231,71232,5,71342,71343,7,71453,71455,5,71463,71467,5,71737,71738,5,71995,71996,5,72000,72000,7,72145,72147,7,72160,72160,5,72249,72249,7,72273,72278,5,72330,72342,5,72752,72758,5,72850,72871,5,72882,72883,5,73018,73018,5,73031,73031,5,73109,73109,5,73461,73462,7,94031,94031,5,94192,94193,7,119142,119142,7,119155,119162,4,119362,119364,5,121476,121476,5,122888,122904,5,123184,123190,5,126976,126979,14,127184,127231,14,127344,127345,14,127405,127461,14,127514,127514,14,127561,127567,14,127778,127779,14,127896,127896,14,127985,127986,14,127995,127999,5,128326,128328,14,128360,128366,14,128378,128378,14,128394,128397,14,128405,128406,14,128422,128423,14,128435,128443,14,128453,128464,14,128479,128480,14,128484,128487,14,128496,128498,14,128640,128709,14,128723,128724,14,128736,128741,14,128747,128748,14,128755,128755,14,128762,128762,14,128981,128991,14,129096,129103,14,129292,129292,14,129311,129311,14,129329,129330,14,129344,129349,14,129360,129374,14,129394,129394,14,129402,129402,14,129413,129425,14,129445,129450,14,129466,129471,14,129483,129483,14,129511,129535,14,129653,129655,14,129667,129670,14,129705,129711,14,129731,129743,14,917505,917505,4,917760,917999,5,10,10,3,127,159,4,768,879,5,1471,1471,5,1536,1541,1,1648,1648,5,1767,1768,5,1840,1866,5,2070,2073,5,2137,2139,5,2307,2307,7,2366,2368,7,2382,2383,7,2434,2435,7,2497,2500,5,2519,2519,5,2563,2563,7,2631,2632,5,2677,2677,5,2750,2752,7,2763,2764,7,2817,2817,5,2879,2879,5,2891,2892,7,2914,2915,5,3008,3008,5,3021,3021,5,3076,3076,5,3146,3149,5,3202,3203,7,3264,3265,7,3271,3272,7,3298,3299,5,3390,3390,5,3402,3404,7,3426,3427,5,3535,3535,5,3544,3550,7,3635,3635,7,3763,3763,7,3893,3893,5,3953,3966,5,3981,3991,5,4145,4145,7,4157,4158,5,4209,4212,5,4237,4237,5,4520,4607,10,5970,5971,5,6071,6077,5,6089,6099,5,6277,6278,5,6439,6440,5,6451,6456,7,6683,6683,5,6744,6750,5,6765,6770,7,6846,6846,5,6964,6964,5,6972,6972,5,7019,7027,5,7074,7077,5,7083,7085,5,7146,7148,7,7154,7155,7,7222,7223,5,7394,7400,5,7416,7417,5,8204,8204,5,8233,8233,4,8288,8292,4,8413,8416,5,8482,8482,14,8986,8987,14,9193,9203,14,9654,9654,14,9733,9733,14,9745,9745,14,9752,9752,14,9760,9760,14,9766,9766,14,9774,9775,14,9792,9792,14,9800,9811,14,9825,9826,14,9831,9831,14,9852,9853,14,9872,9873,14,9880,9880,14,9885,9887,14,9896,9897,14,9906,9916,14,9926,9927,14,9936,9936,14,9941,9960,14,9974,9974,14,9982,9985,14,9992,9997,14,10002,10002,14,10017,10017,14,10055,10055,14,10071,10071,14,10145,10145,14,11013,11015,14,11503,11505,5,12334,12335,5,12951,12951,14,42612,42621,5,43014,43014,5,43047,43047,7,43204,43205,5,43335,43345,5,43395,43395,7,43450,43451,7,43561,43566,5,43573,43574,5,43644,43644,5,43710,43711,5,43758,43759,7,44005,44005,5,44012,44012,7,44060,44060,11,44116,44116,11,44172,44172,11,44228,44228,11,44284,44284,11,44340,44340,11,44396,44396,11,44452,44452,11,44508,44508,11,44564,44564,11,44620,44620,11,44676,44676,11,44732,44732,11,44788,44788,11,44844,44844,11,44900,44900,11,44956,44956,11,45012,45012,11,45068,45068,11,45124,45124,11,45180,45180,11,45236,45236,11,45292,45292,11,45348,45348,11,45404,45404,11,45460,45460,11,45516,45516,11,45572,45572,11,45628,45628,11,45684,45684,11,45740,45740,11,45796,45796,11,45852,45852,11,45908,45908,11,45964,45964,11,46020,46020,11,46076,46076,11,46132,46132,11,46188,46188,11,46244,46244,11,46300,46300,11,46356,46356,11,46412,46412,11,46468,46468,11,46524,46524,11,46580,46580,11,46636,46636,11,46692,46692,11,46748,46748,11,46804,46804,11,46860,46860,11,46916,46916,11,46972,46972,11,47028,47028,11,47084,47084,11,47140,47140,11,47196,47196,11,47252,47252,11,47308,47308,11,47364,47364,11,47420,47420,11,47476,47476,11,47532,47532,11,47588,47588,11,47644,47644,11,47700,47700,11,47756,47756,11,47812,47812,11,47868,47868,11,47924,47924,11,47980,47980,11,48036,48036,11,48092,48092,11,48148,48148,11,48204,48204,11,48260,48260,11,48316,48316,11,48372,48372,11,48428,48428,11,48484,48484,11,48540,48540,11,48596,48596,11,48652,48652,11,48708,48708,11,48764,48764,11,48820,48820,11,48876,48876,11,48932,48932,11,48988,48988,11,49044,49044,11,49100,49100,11,49156,49156,11,49212,49212,11,49268,49268,11,49324,49324,11,49380,49380,11,49436,49436,11,49492,49492,11,49548,49548,11,49604,49604,11,49660,49660,11,49716,49716,11,49772,49772,11,49828,49828,11,49884,49884,11,49940,49940,11,49996,49996,11,50052,50052,11,50108,50108,11,50164,50164,11,50220,50220,11,50276,50276,11,50332,50332,11,50388,50388,11,50444,50444,11,50500,50500,11,50556,50556,11,50612,50612,11,50668,50668,11,50724,50724,11,50780,50780,11,50836,50836,11,50892,50892,11,50948,50948,11,51004,51004,11,51060,51060,11,51116,51116,11,51172,51172,11,51228,51228,11,51284,51284,11,51340,51340,11,51396,51396,11,51452,51452,11,51508,51508,11,51564,51564,11,51620,51620,11,51676,51676,11,51732,51732,11,51788,51788,11,51844,51844,11,51900,51900,11,51956,51956,11,52012,52012,11,52068,52068,11,52124,52124,11,52180,52180,11,52236,52236,11,52292,52292,11,52348,52348,11,52404,52404,11,52460,52460,11,52516,52516,11,52572,52572,11,52628,52628,11,52684,52684,11,52740,52740,11,52796,52796,11,52852,52852,11,52908,52908,11,52964,52964,11,53020,53020,11,53076,53076,11,53132,53132,11,53188,53188,11,53244,53244,11,53300,53300,11,53356,53356,11,53412,53412,11,53468,53468,11,53524,53524,11,53580,53580,11,53636,53636,11,53692,53692,11,53748,53748,11,53804,53804,11,53860,53860,11,53916,53916,11,53972,53972,11,54028,54028,11,54084,54084,11,54140,54140,11,54196,54196,11,54252,54252,11,54308,54308,11,54364,54364,11,54420,54420,11,54476,54476,11,54532,54532,11,54588,54588,11,54644,54644,11,54700,54700,11,54756,54756,11,54812,54812,11,54868,54868,11,54924,54924,11,54980,54980,11,55036,55036,11,55092,55092,11,55148,55148,11,55216,55238,9,65056,65071,5,65529,65531,4,68097,68099,5,68159,68159,5,69446,69456,5,69688,69702,5,69808,69810,7,69815,69816,7,69821,69821,1,69888,69890,5,69932,69932,7,69957,69958,7,70016,70017,5,70067,70069,7,70079,70080,7,70089,70092,5,70095,70095,5,70191,70193,5,70196,70196,5,70198,70199,5,70367,70367,5,70371,70378,5,70402,70403,7,70462,70462,5,70464,70464,5,70471,70472,7,70487,70487,5,70502,70508,5,70709,70711,7,70720,70721,7,70725,70725,7,70750,70750,5,70833,70834,7,70841,70841,7,70843,70844,7,70846,70846,7,70849,70849,7,71087,71087,5,71090,71093,5,71100,71101,5,71103,71104,5,71216,71218,7,71227,71228,7,71230,71230,7,71339,71339,5,71341,71341,5,71344,71349,5,71351,71351,5,71456,71457,7,71462,71462,7,71724,71726,7,71736,71736,7,71984,71984,5,71991,71992,7,71997,71997,7,71999,71999,1,72001,72001,1,72003,72003,5,72148,72151,5,72156,72159,7,72164,72164,7,72243,72248,5,72250,72250,1,72263,72263,5,72279,72280,7,72324,72329,1,72343,72343,7,72751,72751,7,72760,72765,5,72767,72767,5,72873,72873,7,72881,72881,7,72884,72884,7,73009,73014,5,73020,73021,5,73030,73030,1,73098,73102,7,73107,73108,7,73110,73110,7,73459,73460,5,78896,78904,4,92976,92982,5,94033,94087,7,94180,94180,5,113821,113822,5,119141,119141,5,119143,119145,5,119150,119154,5,119163,119170,5,119210,119213,5,121344,121398,5,121461,121461,5,121499,121503,5,122880,122886,5,122907,122913,5,122918,122922,5,123628,123631,5,125252,125258,5,126980,126980,14,127183,127183,14,127245,127247,14,127340,127343,14,127358,127359,14,127377,127386,14,127462,127487,6,127491,127503,14,127535,127535,14,127548,127551,14,127568,127569,14,127744,127777,14,127780,127891,14,127894,127895,14,127897,127899,14,127902,127984,14,127987,127989,14,127991,127994,14,128000,128253,14,128255,128317,14,128329,128334,14,128336,128359,14,128367,128368,14,128371,128377,14,128379,128390,14,128392,128393,14,128398,128399,14,128401,128404,14,128407,128419,14,128421,128421,14,128424,128424,14,128433,128434,14,128444,128444,14,128450,128452,14,128465,128467,14,128476,128478,14,128481,128481,14,128483,128483,14,128488,128488,14,128495,128495,14,128499,128499,14,128506,128591,14,128710,128714,14,128721,128722,14,128725,128725,14,128728,128735,14,128742,128744,14,128746,128746,14,128749,128751,14,128753,128754,14,128756,128758,14,128761,128761,14,128763,128764,14,128884,128895,14,128992,129003,14,129036,129039,14,129114,129119,14,129198,129279,14,129293,129295,14,129305,129310,14,129312,129319,14,129328,129328,14,129331,129338,14,129343,129343,14,129351,129355,14,129357,129359,14,129375,129387,14,129393,129393,14,129395,129398,14,129401,129401,14,129403,129403,14,129408,129412,14,129426,129431,14,129443,129444,14,129451,129453,14,129456,129465,14,129472,129472,14,129475,129482,14,129484,129484,14,129488,129510,14,129536,129647,14,129652,129652,14,129656,129658,14,129664,129666,14,129671,129679,14,129686,129704,14,129712,129718,14,129728,129730,14,129744,129750,14,917504,917504,4,917506,917535,4,917632,917759,4,918000,921599,4,0,9,4,11,12,4,14,31,4,169,169,14,174,174,14,1155,1159,5,1425,1469,5,1473,1474,5,1479,1479,5,1552,1562,5,1611,1631,5,1750,1756,5,1759,1764,5,1770,1773,5,1809,1809,5,1958,1968,5,2045,2045,5,2075,2083,5,2089,2093,5,2259,2273,5,2275,2306,5,2362,2362,5,2364,2364,5,2369,2376,5,2381,2381,5,2385,2391,5,2433,2433,5,2492,2492,5,2495,2496,7,2503,2504,7,2509,2509,5,2530,2531,5,2561,2562,5,2620,2620,5,2625,2626,5,2635,2637,5,2672,2673,5,2689,2690,5,2748,2748,5,2753,2757,5,2761,2761,7,2765,2765,5,2810,2815,5,2818,2819,7,2878,2878,5,2880,2880,7,2887,2888,7,2893,2893,5,2903,2903,5,2946,2946,5,3007,3007,7,3009,3010,7,3018,3020,7,3031,3031,5,3073,3075,7,3134,3136,5,3142,3144,5,3157,3158,5,3201,3201,5,3260,3260,5,3263,3263,5,3266,3266,5,3270,3270,5,3274,3275,7,3285,3286,5,3328,3329,5,3387,3388,5,3391,3392,7,3398,3400,7,3405,3405,5,3415,3415,5,3457,3457,5,3530,3530,5,3536,3537,7,3542,3542,5,3551,3551,5,3633,3633,5,3636,3642,5,3761,3761,5,3764,3772,5,3864,3865,5,3895,3895,5,3902,3903,7,3967,3967,7,3974,3975,5,3993,4028,5,4141,4144,5,4146,4151,5,4155,4156,7,4182,4183,7,4190,4192,5,4226,4226,5,4229,4230,5,4253,4253,5,4448,4519,9,4957,4959,5,5938,5940,5,6002,6003,5,6070,6070,7,6078,6085,7,6087,6088,7,6109,6109,5,6158,6158,4,6313,6313,5,6435,6438,7,6441,6443,7,6450,6450,5,6457,6459,5,6681,6682,7,6741,6741,7,6743,6743,7,6752,6752,5,6757,6764,5,6771,6780,5,6832,6845,5,6847,6848,5,6916,6916,7,6965,6965,5,6971,6971,7,6973,6977,7,6979,6980,7,7040,7041,5,7073,7073,7,7078,7079,7,7082,7082,7,7142,7142,5,7144,7145,5,7149,7149,5,7151,7153,5,7204,7211,7,7220,7221,7,7376,7378,5,7393,7393,7,7405,7405,5,7415,7415,7,7616,7673,5,8203,8203,4,8205,8205,13,8232,8232,4,8234,8238,4,8265,8265,14,8293,8293,4,8400,8412,5,8417,8417,5,8421,8432,5,8505,8505,14,8617,8618,14,9000,9000,14,9167,9167,14,9208,9210,14,9642,9643,14,9664,9664,14,9728,9732,14,9735,9741,14,9743,9744,14,9746,9746,14,9750,9751,14,9753,9756,14,9758,9759,14,9761,9761,14,9764,9765,14,9767,9769,14,9771,9773,14,9776,9783,14,9787,9791,14,9793,9793,14,9795,9799,14,9812,9822,14,9824,9824,14,9827,9827,14,9829,9830,14,9832,9832,14,9851,9851,14,9854,9854,14,9856,9861,14,9874,9876,14,9878,9879,14,9881,9881,14,9883,9884,14,9888,9889,14,9895,9895,14,9898,9899,14,9904,9905,14,9917,9918,14,9924,9925,14,9928,9928,14,9934,9935,14,9937,9937,14,9939,9940,14,9961,9962,14,9968,9973,14,9975,9978,14,9981,9981,14,9986,9986,14,9989,9989,14,9998,9998,14,10000,10001,14,10004,10004,14,10013,10013,14,10024,10024,14,10052,10052,14,10060,10060,14,10067,10069,14,10083,10084,14,10133,10135,14,10160,10160,14,10548,10549,14,11035,11036,14,11093,11093,14,11647,11647,5,12330,12333,5,12336,12336,14,12441,12442,5,12953,12953,14,42608,42610,5,42654,42655,5,43010,43010,5,43019,43019,5,43045,43046,5,43052,43052,5,43188,43203,7,43232,43249,5,43302,43309,5,43346,43347,7,43392,43394,5,43443,43443,5,43446,43449,5,43452,43453,5,43493,43493,5,43567,43568,7,43571,43572,7,43587,43587,5,43597,43597,7,43696,43696,5,43703,43704,5,43713,43713,5,43756,43757,5,43765,43765,7,44003,44004,7,44006,44007,7,44009,44010,7,44013,44013,5,44033,44059,12,44061,44087,12,44089,44115,12,44117,44143,12,44145,44171,12,44173,44199,12,44201,44227,12,44229,44255,12,44257,44283,12,44285,44311,12,44313,44339,12,44341,44367,12,44369,44395,12,44397,44423,12,44425,44451,12,44453,44479,12,44481,44507,12,44509,44535,12,44537,44563,12,44565,44591,12,44593,44619,12,44621,44647,12,44649,44675,12,44677,44703,12,44705,44731,12,44733,44759,12,44761,44787,12,44789,44815,12,44817,44843,12,44845,44871,12,44873,44899,12,44901,44927,12,44929,44955,12,44957,44983,12,44985,45011,12,45013,45039,12,45041,45067,12,45069,45095,12,45097,45123,12,45125,45151,12,45153,45179,12,45181,45207,12,45209,45235,12,45237,45263,12,45265,45291,12,45293,45319,12,45321,45347,12,45349,45375,12,45377,45403,12,45405,45431,12,45433,45459,12,45461,45487,12,45489,45515,12,45517,45543,12,45545,45571,12,45573,45599,12,45601,45627,12,45629,45655,12,45657,45683,12,45685,45711,12,45713,45739,12,45741,45767,12,45769,45795,12,45797,45823,12,45825,45851,12,45853,45879,12,45881,45907,12,45909,45935,12,45937,45963,12,45965,45991,12,45993,46019,12,46021,46047,12,46049,46075,12,46077,46103,12,46105,46131,12,46133,46159,12,46161,46187,12,46189,46215,12,46217,46243,12,46245,46271,12,46273,46299,12,46301,46327,12,46329,46355,12,46357,46383,12,46385,46411,12,46413,46439,12,46441,46467,12,46469,46495,12,46497,46523,12,46525,46551,12,46553,46579,12,46581,46607,12,46609,46635,12,46637,46663,12,46665,46691,12,46693,46719,12,46721,46747,12,46749,46775,12,46777,46803,12,46805,46831,12,46833,46859,12,46861,46887,12,46889,46915,12,46917,46943,12,46945,46971,12,46973,46999,12,47001,47027,12,47029,47055,12,47057,47083,12,47085,47111,12,47113,47139,12,47141,47167,12,47169,47195,12,47197,47223,12,47225,47251,12,47253,47279,12,47281,47307,12,47309,47335,12,47337,47363,12,47365,47391,12,47393,47419,12,47421,47447,12,47449,47475,12,47477,47503,12,47505,47531,12,47533,47559,12,47561,47587,12,47589,47615,12,47617,47643,12,47645,47671,12,47673,47699,12,47701,47727,12,47729,47755,12,47757,47783,12,47785,47811,12,47813,47839,12,47841,47867,12,47869,47895,12,47897,47923,12,47925,47951,12,47953,47979,12,47981,48007,12,48009,48035,12,48037,48063,12,48065,48091,12,48093,48119,12,48121,48147,12,48149,48175,12,48177,48203,12,48205,48231,12,48233,48259,12,48261,48287,12,48289,48315,12,48317,48343,12,48345,48371,12,48373,48399,12,48401,48427,12,48429,48455,12,48457,48483,12,48485,48511,12,48513,48539,12,48541,48567,12,48569,48595,12,48597,48623,12,48625,48651,12,48653,48679,12,48681,48707,12,48709,48735,12,48737,48763,12,48765,48791,12,48793,48819,12,48821,48847,12,48849,48875,12,48877,48903,12,48905,48931,12,48933,48959,12,48961,48987,12,48989,49015,12,49017,49043,12,49045,49071,12,49073,49099,12,49101,49127,12,49129,49155,12,49157,49183,12,49185,49211,12,49213,49239,12,49241,49267,12,49269,49295,12,49297,49323,12,49325,49351,12,49353,49379,12,49381,49407,12,49409,49435,12,49437,49463,12,49465,49491,12,49493,49519,12,49521,49547,12,49549,49575,12,49577,49603,12,49605,49631,12,49633,49659,12,49661,49687,12,49689,49715,12,49717,49743,12,49745,49771,12,49773,49799,12,49801,49827,12,49829,49855,12,49857,49883,12,49885,49911,12,49913,49939,12,49941,49967,12,49969,49995,12,49997,50023,12,50025,50051,12,50053,50079,12,50081,50107,12,50109,50135,12,50137,50163,12,50165,50191,12,50193,50219,12,50221,50247,12,50249,50275,12,50277,50303,12,50305,50331,12,50333,50359,12,50361,50387,12,50389,50415,12,50417,50443,12,50445,50471,12,50473,50499,12,50501,50527,12,50529,50555,12,50557,50583,12,50585,50611,12,50613,50639,12,50641,50667,12,50669,50695,12,50697,50723,12,50725,50751,12,50753,50779,12,50781,50807,12,50809,50835,12,50837,50863,12,50865,50891,12,50893,50919,12,50921,50947,12,50949,50975,12,50977,51003,12,51005,51031,12,51033,51059,12,51061,51087,12,51089,51115,12,51117,51143,12,51145,51171,12,51173,51199,12,51201,51227,12,51229,51255,12,51257,51283,12,51285,51311,12,51313,51339,12,51341,51367,12,51369,51395,12,51397,51423,12,51425,51451,12,51453,51479,12,51481,51507,12,51509,51535,12,51537,51563,12,51565,51591,12,51593,51619,12,51621,51647,12,51649,51675,12,51677,51703,12,51705,51731,12,51733,51759,12,51761,51787,12,51789,51815,12,51817,51843,12,51845,51871,12,51873,51899,12,51901,51927,12,51929,51955,12,51957,51983,12,51985,52011,12,52013,52039,12,52041,52067,12,52069,52095,12,52097,52123,12,52125,52151,12,52153,52179,12,52181,52207,12,52209,52235,12,52237,52263,12,52265,52291,12,52293,52319,12,52321,52347,12,52349,52375,12,52377,52403,12,52405,52431,12,52433,52459,12,52461,52487,12,52489,52515,12,52517,52543,12,52545,52571,12,52573,52599,12,52601,52627,12,52629,52655,12,52657,52683,12,52685,52711,12,52713,52739,12,52741,52767,12,52769,52795,12,52797,52823,12,52825,52851,12,52853,52879,12,52881,52907,12,52909,52935,12,52937,52963,12,52965,52991,12,52993,53019,12,53021,53047,12,53049,53075,12,53077,53103,12,53105,53131,12,53133,53159,12,53161,53187,12,53189,53215,12,53217,53243,12,53245,53271,12,53273,53299,12,53301,53327,12,53329,53355,12,53357,53383,12,53385,53411,12,53413,53439,12,53441,53467,12,53469,53495,12,53497,53523,12,53525,53551,12,53553,53579,12,53581,53607,12,53609,53635,12,53637,53663,12,53665,53691,12,53693,53719,12,53721,53747,12,53749,53775,12,53777,53803,12,53805,53831,12,53833,53859,12,53861,53887,12,53889,53915,12,53917,53943,12,53945,53971,12,53973,53999,12,54001,54027,12,54029,54055,12,54057,54083,12,54085,54111,12,54113,54139,12,54141,54167,12,54169,54195,12,54197,54223,12,54225,54251,12,54253,54279,12,54281,54307,12,54309,54335,12,54337,54363,12,54365,54391,12,54393,54419,12,54421,54447,12,54449,54475,12,54477,54503,12,54505,54531,12,54533,54559,12,54561,54587,12,54589,54615,12,54617,54643,12,54645,54671,12,54673,54699,12,54701,54727,12,54729,54755,12,54757,54783,12,54785,54811,12,54813,54839,12,54841,54867,12,54869,54895,12,54897,54923,12,54925,54951,12,54953,54979,12,54981,55007,12,55009,55035,12,55037,55063,12,55065,55091,12,55093,55119,12,55121,55147,12,55149,55175,12,55177,55203,12,55243,55291,10,65024,65039,5,65279,65279,4,65520,65528,4,66045,66045,5,66422,66426,5,68101,68102,5,68152,68154,5,68325,68326,5,69291,69292,5,69632,69632,7,69634,69634,7,69759,69761,5]'); } diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index eef27600b..a91a3cfd1 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -8,22 +8,22 @@ import { URI, UriComponents } from 'vs/base/common/uri'; /** * @returns whether the provided parameter is a JavaScript Array or not. */ -export function isArray(array: T | {}): array is T extends readonly any[] ? (unknown extends T ? never : readonly any[]) : any[] { +export function isArray(array: any): array is any[] { return Array.isArray(array); } /** * @returns whether the provided parameter is a JavaScript String or not. */ -export function isString(str: any): str is string { +export function isString(str: unknown): str is string { return (typeof str === 'string'); } /** * @returns whether the provided parameter is a JavaScript Array and each element in the array is a string. */ -export function isStringArray(value: any): value is string[] { - return Array.isArray(value) && (value).every(elem => isString(elem)); +export function isStringArray(value: unknown): value is string[] { + return Array.isArray(value) && (value).every(elem => isString(elem)); } /** @@ -31,7 +31,7 @@ export function isStringArray(value: any): value is string[] { * @returns whether the provided parameter is of type `object` but **not** * `null`, an `array`, a `regexp`, nor a `date`. */ -export function isObject(obj: any): obj is Object { +export function isObject(obj: unknown): obj is Object { // The method can't do a type cast since there are type (like strings) which // are subclasses of any put not positvely matched by the function. Hence type // narrowing results in wrong results. @@ -46,21 +46,21 @@ export function isObject(obj: any): obj is Object { * In **contrast** to just checking `typeof` this will return `false` for `NaN`. * @returns whether the provided parameter is a JavaScript Number or not. */ -export function isNumber(obj: any): obj is number { +export function isNumber(obj: unknown): obj is number { return (typeof obj === 'number' && !isNaN(obj)); } /** * @returns whether the provided parameter is a JavaScript Boolean or not. */ -export function isBoolean(obj: any): obj is boolean { +export function isBoolean(obj: unknown): obj is boolean { return (obj === true || obj === false); } /** * @returns whether the provided parameter is undefined. */ -export function isUndefined(obj: any): obj is undefined { +export function isUndefined(obj: unknown): obj is undefined { return (typeof obj === 'undefined'); } @@ -74,12 +74,12 @@ export function isDefined(arg: T | null | undefined): arg is T { /** * @returns whether the provided parameter is undefined or null. */ -export function isUndefinedOrNull(obj: any): obj is undefined | null { +export function isUndefinedOrNull(obj: unknown): obj is undefined | null { return (isUndefined(obj) || obj === null); } -export function assertType(condition: any, type?: string): asserts condition { +export function assertType(condition: unknown, type?: string): asserts condition { if (!condition) { throw new Error(type ? `Unexpected type, expected '${type}'` : 'Unexpected type'); } @@ -123,7 +123,7 @@ const hasOwnProperty = Object.prototype.hasOwnProperty; /** * @returns whether the provided parameter is an empty JavaScript Object or not. */ -export function isEmptyObject(obj: any): obj is any { +export function isEmptyObject(obj: unknown): obj is object { if (!isObject(obj)) { return false; } @@ -140,27 +140,27 @@ export function isEmptyObject(obj: any): obj is any { /** * @returns whether the provided parameter is a JavaScript Function or not. */ -export function isFunction(obj: any): obj is Function { +export function isFunction(obj: unknown): obj is Function { return (typeof obj === 'function'); } /** * @returns whether the provided parameters is are JavaScript Function or not. */ -export function areFunctions(...objects: any[]): boolean { +export function areFunctions(...objects: unknown[]): boolean { return objects.length > 0 && objects.every(isFunction); } export type TypeConstraint = string | Function; -export function validateConstraints(args: any[], constraints: Array): void { +export function validateConstraints(args: unknown[], constraints: Array): void { const len = Math.min(args.length, constraints.length); for (let i = 0; i < len; i++) { validateConstraint(args[i], constraints[i]); } } -export function validateConstraint(arg: any, constraint: TypeConstraint | undefined): void { +export function validateConstraint(arg: unknown, constraint: TypeConstraint | undefined): void { if (isString(constraint)) { if (typeof arg !== constraint) { @@ -174,7 +174,7 @@ export function validateConstraint(arg: any, constraint: TypeConstraint | undefi } catch { // ignore } - if (!isUndefinedOrNull(arg) && arg.constructor === constraint) { + if (!isUndefinedOrNull(arg) && (arg as any).constructor === constraint) { return; } if (constraint.length === 1 && constraint.call(undefined, arg) === true) { @@ -204,8 +204,8 @@ export function getAllMethodNames(obj: object): string[] { return methods; } -export function createProxyObject(methodNames: string[], invoke: (method: string, args: any[]) => any): T { - const createProxyMethod = (method: string): () => any => { +export function createProxyObject(methodNames: string[], invoke: (method: string, args: unknown[]) => unknown): T { + const createProxyMethod = (method: string): () => unknown => { return function () { const args = Array.prototype.slice.call(arguments, 0); return invoke(method, args); @@ -242,7 +242,7 @@ export type AddFirstParameterToFunctions TargetFunctionsReturnType ? (firstArg: FirstParameter, ...args: Parameters) => ReturnType : + Target[K] extends (...args: any[]) => TargetFunctionsReturnType ? (firstArg: FirstParameter, ...args: Parameters) => ReturnType : // Else: just leave as is Target[K] diff --git a/src/vs/base/common/uuid.ts b/src/vs/base/common/uuid.ts index 141fd83ce..36b5d4e99 100644 --- a/src/vs/base/common/uuid.ts +++ b/src/vs/base/common/uuid.ts @@ -19,13 +19,22 @@ for (let i = 0; i < 256; i++) { // todo@jrieken // 1. node nodejs use`crypto#randomBytes`, see: https://nodejs.org/docs/latest/api/crypto.html#crypto_crypto_randombytes_size_callback -// 2. use browser-crypto -const _fillRandomValues = function (bucket: Uint8Array): Uint8Array { - for (let i = 0; i < bucket.length; i++) { - bucket[i] = Math.floor(Math.random() * 256); - } - return bucket; -}; +let _fillRandomValues: (bucket: Uint8Array) => Uint8Array; + +declare const crypto: undefined | { getRandomValues(data: Uint8Array): Uint8Array }; + +if (typeof crypto === 'object' && typeof crypto.getRandomValues === 'function') { + // browser + _fillRandomValues = crypto.getRandomValues.bind(crypto); + +} else { + _fillRandomValues = function (bucket: Uint8Array): Uint8Array { + for (let i = 0; i < bucket.length; i++) { + bucket[i] = Math.floor(Math.random() * 256); + } + return bucket; + }; +} export function generateUuid(): string { // get data diff --git a/src/vs/base/node/extpath.ts b/src/vs/base/node/extpath.ts index 0096544ad..b43c407d5 100644 --- a/src/vs/base/node/extpath.ts +++ b/src/vs/base/node/extpath.ts @@ -7,7 +7,6 @@ import * as fs from 'fs'; import { rtrim } from 'vs/base/common/strings'; import { sep, join, normalize, dirname, basename } from 'vs/base/common/path'; import { readdirSync } from 'vs/base/node/pfs'; -import { promisify } from 'util'; /** * Copied from: https://github.com/microsoft/vscode-node-debug/blob/master/src/node/pathUtilities.ts#L83 @@ -53,7 +52,7 @@ export function realcaseSync(path: string): string | null { export async function realpath(path: string): Promise { try { - return await promisify(fs.realpath)(path); + return await fs.promises.realpath(path); } catch (error) { // We hit an error calling fs.realpath(). Since fs.realpath() is doing some path normalization @@ -63,7 +62,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 promisify(fs.access)(normalizedPath, fs.constants.R_OK); + await fs.promises.access(normalizedPath, fs.constants.R_OK); return normalizedPath; } diff --git a/src/vs/base/node/languagePacks.js b/src/vs/base/node/languagePacks.js index be9ca50b1..5c14fade8 100644 --- a/src/vs/base/node/languagePacks.js +++ b/src/vs/base/node/languagePacks.js @@ -2,313 +2,258 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; + +/// //@ts-check - -/** - * @param {NodeRequire} nodeRequire - * @param {typeof import('path')} path - * @param {typeof import('fs')} fs - * @param {typeof import('../common/performance')} perf - */ -function factory(nodeRequire, path, fs, perf) { +(function () { + 'use strict'; /** - * @param {string} file - * @returns {Promise} + * @param {NodeRequire} nodeRequire + * @param {typeof import('path')} path + * @param {typeof import('fs')} fs + * @param {typeof import('../common/performance')} perf */ - function exists(file) { - return new Promise(c => fs.exists(file, c)); - } + function factory(nodeRequire, path, fs, perf) { - /** - * @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} file - * @returns {Promise} - */ - function lstat(file) { - return new Promise((c, e) => fs.lstat(file, (err, stats) => err ? e(err) : c(stats))); - } - - /** - * @param {string} dir - * @returns {Promise} - */ - function readdir(dir) { - return new Promise((c, e) => fs.readdir(dir, (err, files) => err ? e(err) : c(files))); - } - - /** - * @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} dir - * @returns {Promise} - */ - function rmdir(dir) { - return new Promise((c, e) => fs.rmdir(dir, err => err ? e(err) : c(undefined))); - } - - /** - * @param {string} file - * @returns {Promise} - */ - function unlink(file) { - return new Promise((c, e) => fs.unlink(file, err => err ? e(err) : c(undefined))); - } - - /** - * @param {string} location - * @returns {Promise} - */ - function rimraf(location) { - return lstat(location).then(stat => { - if (stat.isDirectory() && !stat.isSymbolicLink()) { - return readdir(location) - .then(children => Promise.all(children.map(child => rimraf(path.join(location, child))))) - .then(() => rmdir(location)); - } else { - return unlink(location); - } - }, err => { - if (err.code === 'ENOENT') { - return undefined; - } - throw err; - }); - } - - function readFile(file) { - return new Promise(function (resolve, reject) { - fs.readFile(file, 'utf8', function (err, data) { - if (err) { - reject(err); - return; - } - resolve(data); - }); - }); - } - - /** - * @param {string} file - * @param {string} content - * @returns {Promise} - */ - function writeFile(file, content) { - return new Promise(function (resolve, reject) { - fs.writeFile(file, content, 'utf8', function (err) { - if (err) { - reject(err); - return; - } - resolve(); - }); - }); - } - - - /** - * @param {string} userDataPath - * @returns {object} - */ - function getLanguagePackConfigurations(userDataPath) { - const configFile = path.join(userDataPath, 'languagepacks.json'); - try { - return nodeRequire(configFile); - } catch (err) { - // Do nothing. If we can't read the file we have no - // language pack config. + /** + * @param {string} file + * @returns {Promise} + */ + function exists(file) { + return new Promise(c => fs.exists(file, c)); } - return undefined; - } - /** - * @param {object} config - * @param {string} 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); + /** + * @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.rmdir(location, { recursive: true }, err => (err && err.code !== 'ENOENT') ? 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 {object} + */ + function getLanguagePackConfigurations(userDataPath) { + const configFile = path.join(userDataPath, 'languagepacks.json'); + try { + return nodeRequire(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} locale + */ + function resolveLanguagePackLocale(config, locale) { + try { + while (locale) { + if (config[locale]) { + return locale; } else { - return undefined; + 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); } - } catch (err) { - console.error('Resolving language pack configuration failed.', err); - } - return undefined; - } - - /** - * @param {string} commit - * @param {string} userDataPath - * @param {string} metaDataFile - * @param {string} locale - */ - function getNLSConfiguration(commit, userDataPath, metaDataFile, locale) { - if (locale === 'pseudo') { - return Promise.resolve({ locale: locale, availableLanguages: {}, pseudo: true }); + return undefined; } - if (process.env['VSCODE_DEV']) { - return Promise.resolve({ locale: locale, 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: locale, availableLanguages: {} }); - } - - const initialLocale = locale; - - perf.mark('code/willGenerateNls'); - - const defaultResult = function (locale) { - perf.mark('code/didGenerateNls'); - return Promise.resolve({ locale: locale, availableLanguages: {} }); - }; - try { - if (!commit) { - return defaultResult(initialLocale); + /** + * @param {string} commit + * @param {string} userDataPath + * @param {string} metaDataFile + * @param {string} locale + */ + function getNLSConfiguration(commit, userDataPath, metaDataFile, locale) { + if (locale === 'pseudo') { + return Promise.resolve({ locale: locale, availableLanguages: {}, pseudo: true }); } - const configs = getLanguagePackConfigurations(userDataPath); - if (!configs) { - return defaultResult(initialLocale); + + if (process.env['VSCODE_DEV']) { + return Promise.resolve({ locale: locale, availableLanguages: {} }); } - locale = resolveLanguagePackLocale(configs, locale); - if (!locale) { - return defaultResult(initialLocale); + + // 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: locale, availableLanguages: {} }); } - 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) { + + const initialLocale = locale; + + perf.mark('code/willGenerateNls'); + + const defaultResult = function (locale) { + perf.mark('code/didGenerateNls'); + return Promise.resolve({ locale: locale, availableLanguages: {} }); + }; + try { + if (!commit) { 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, - 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); + const configs = getLanguagePackConfigurations(userDataPath); + if (!configs) { + return defaultResult(initialLocale); + } + locale = resolveLanguagePackLocale(configs, locale); + if (!locale) { + return defaultResult(initialLocale); + } + 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); } - 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))); + 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, + 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; } - 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); + 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); + } catch (err) { + console.error('Generating translation files failed.', err); + return defaultResult(locale); + } } + + return { + getNLSConfiguration + }; } - return { - getNLSConfiguration - }; -} - - -if (typeof define === 'function') { - // amd - define(['path', 'fs', 'vs/base/common/performance'], function (path, fs, perf) { return factory(require.__$__nodeRequire, 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(require, path, fs, perf); -} else { - throw new Error('Unknown context'); -} + if (typeof define === 'function') { + // amd + define(['require', 'path', 'fs', 'vs/base/common/performance'], function (require, /** @type {typeof import('path')} */ path, /** @type {typeof import('fs')} */ fs, /** @type {typeof import('../common/performance')} */ perf) { return factory(require.__$__nodeRequire, 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(require, path, fs, perf); + } else { + throw new Error('Unknown context'); + } +}()); diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts index 11ef31262..830892f20 100644 --- a/src/vs/base/node/pfs.ts +++ b/src/vs/base/node/pfs.ts @@ -7,13 +7,14 @@ import * as fs from 'fs'; import { tmpdir } from 'os'; import { join } from 'vs/base/common/path'; import { Queue } from 'vs/base/common/async'; -import { isMacintosh, isWindows } from 'vs/base/common/platform'; +import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { Event } from 'vs/base/common/event'; -import { promisify } from 'util'; -import { isRootOrDriveLetter } from 'vs/base/common/extpath'; +import { isEqualOrParent, isRootOrDriveLetter } from 'vs/base/common/extpath'; import { generateUuid } from 'vs/base/common/uuid'; import { normalizeNFC } from 'vs/base/common/normalization'; +//#region Constants + // See https://github.com/microsoft/vscode/issues/30180 const WIN32_MAX_FILE_SIZE = 300 * 1024 * 1024; // 300 MB const GENERAL_MAX_FILE_SIZE = 16 * 1024 * 1024 * 1024; // 16 GB @@ -25,6 +26,10 @@ const GENERAL_MAX_HEAP_SIZE = 700 * 2 * 1024 * 1024; // 1400 MB export const MAX_FILE_SIZE = process.arch === 'ia32' ? WIN32_MAX_FILE_SIZE : GENERAL_MAX_FILE_SIZE; export const MAX_HEAP_SIZE = process.arch === 'ia32' ? WIN32_MAX_HEAP_SIZE : GENERAL_MAX_HEAP_SIZE; +//#endregion + +//#region rimraf + export enum RimRafMode { /** @@ -40,12 +45,19 @@ export enum RimRafMode { MOVE } +/** + * Allows to delete the provied path (either file or folder) recursively + * with the options: + * - `UNLINK`: direct removal from disk + * - `MOVE`: faster variant that first moves the target to temp dir and then + * deletes it in the background without waiting for that to finish. + */ export async function rimraf(path: string, mode = RimRafMode.UNLINK): Promise { if (isRootOrDriveLetter(path)) { throw new Error('rimraf - will refuse to recursively delete root'); } - // delete: via unlink + // delete: via rmDir if (mode === RimRafMode.UNLINK) { return rimrafUnlink(path); } @@ -54,32 +66,17 @@ export async function rimraf(path: string, mode = RimRafMode.UNLINK): Promise { +async function rimrafMove(path: string): Promise { try { - const stat = await lstat(path); - - // Folder delete (recursive) - NOT for symbolic links though! - if (stat.isDirectory() && !stat.isSymbolicLink()) { - - // Children - const children = await readdir(path); - await Promise.all(children.map(child => rimrafUnlink(join(path, child)))); - - // Folder - await promisify(fs.rmdir)(path); + const pathInTemp = join(tmpdir(), generateUuid()); + try { + await fs.promises.rename(path, pathInTemp); + } catch (error) { + return rimrafUnlink(path); // if rename fails, delete without tmp dir } - // Single file delete - else { - - // chmod as needed to allow for unlink - const mode = stat.mode; - if (!(mode & fs.constants.S_IWUSR)) { - await chmod(path, mode | fs.constants.S_IWUSR); - } - - return unlink(path); - } + // Delete but do not return as promise + rimrafUnlink(pathInTemp).catch(error => {/* ignore */ }); } catch (error) { if (error.code !== 'ENOENT') { throw error; @@ -87,22 +84,8 @@ async function rimrafUnlink(path: string): Promise { } } -async function rimrafMove(path: string): Promise { - try { - const pathInTemp = join(tmpdir(), generateUuid()); - try { - await rename(path, pathInTemp); - } catch (error) { - return rimrafUnlink(path); // if rename fails, delete without tmp dir - } - - // Delete but do not return as promise - rimrafUnlink(pathInTemp); - } catch (error) { - if (error.code !== 'ENOENT') { - throw error; - } - } +async function rimrafUnlink(path: string): Promise { + return fs.promises.rmdir(path, { recursive: true, maxRetries: 3 }); } export function rimrafSync(path: string): void { @@ -110,193 +93,281 @@ export function rimrafSync(path: string): void { throw new Error('rimraf - will refuse to recursively delete root'); } + fs.rmdirSync(path, { recursive: true }); +} + +//#endregion + +//#region readdir with NFC support (macos) + +export interface IDirent { + name: string; + + isFile(): boolean; + isDirectory(): boolean; + isSymbolicLink(): boolean; +} + +/** + * Drop-in replacement of `fs.readdir` with support + * for converting from macOS NFD unicon form to NFC + * (https://github.com/nodejs/node/issues/2165) + */ +export async function readdir(path: string): Promise; +export async function readdir(path: string, options: { withFileTypes: true }): Promise; +export async function readdir(path: string, options?: { withFileTypes: true }): Promise<(string | IDirent)[]> { + return handleDirectoryChildren(await (options ? safeReaddirWithFileTypes(path) : fs.promises.readdir(path))); +} + +async function safeReaddirWithFileTypes(path: string): Promise { try { - const stat = fs.lstatSync(path); - - // Folder delete (recursive) - NOT for symbolic links though! - if (stat.isDirectory() && !stat.isSymbolicLink()) { - - // Children - const children = readdirSync(path); - children.map(child => rimrafSync(join(path, child))); - - // Folder - fs.rmdirSync(path); - } - - // Single file delete - else { - - // chmod as needed to allow for unlink - const mode = stat.mode; - if (!(mode & fs.constants.S_IWUSR)) { - fs.chmodSync(path, mode | fs.constants.S_IWUSR); - } - - return fs.unlinkSync(path); - } + return await fs.promises.readdir(path, { withFileTypes: true }); } catch (error) { - if (error.code !== 'ENOENT') { - throw error; - } - } -} - -export async function readdir(path: string): Promise { - return handleDirectoryChildren(await promisify(fs.readdir)(path)); -} - -export async function readdirWithFileTypes(path: string): Promise { - const children = await promisify(fs.readdir)(path, { withFileTypes: true }); - - // Mac: uses NFD unicode form on disk, but we want NFC - // See also https://github.com/nodejs/node/issues/2165 - if (isMacintosh) { - for (const child of children) { - child.name = normalizeNFC(child.name); - } + console.warn('[node.js fs] readdir with filetypes failed with error: ', error); } - return children; + // Fallback to manually reading and resolving each + // children of the folder in case we hit an error + // previously. + // This can only really happen on exotic file systems + // such as explained in #115645 where we get entries + // from `readdir` that we can later not `lstat`. + const result: IDirent[] = []; + const children = await readdir(path); + for (const child of children) { + let isFile = false; + let isDirectory = false; + let isSymbolicLink = false; + + try { + const lstat = await fs.promises.lstat(join(path, child)); + + isFile = lstat.isFile(); + isDirectory = lstat.isDirectory(); + isSymbolicLink = lstat.isSymbolicLink(); + } catch (error) { + console.warn('[node.js fs] unexpected error from lstat after readdir: ', error); + } + + result.push({ + name: child, + isFile: () => isFile, + isDirectory: () => isDirectory, + isSymbolicLink: () => isSymbolicLink + }); + } + + return result; } +/** + * Drop-in replacement of `fs.readdirSync` with support + * for converting from macOS NFD unicon form to NFC + * (https://github.com/nodejs/node/issues/2165) + */ export function readdirSync(path: string): string[] { return handleDirectoryChildren(fs.readdirSync(path)); } -function handleDirectoryChildren(children: string[]): string[] { - // Mac: uses NFD unicode form on disk, but we want NFC - // See also https://github.com/nodejs/node/issues/2165 - if (isMacintosh) { - return children.map(child => normalizeNFC(child)); - } +function handleDirectoryChildren(children: string[]): string[]; +function handleDirectoryChildren(children: IDirent[]): IDirent[]; +function handleDirectoryChildren(children: (string | IDirent)[]): (string | IDirent)[]; +function handleDirectoryChildren(children: (string | IDirent)[]): (string | IDirent)[] { + return children.map(child => { - return children; -} + // Mac: uses NFD unicode form on disk, but we want NFC + // See also https://github.com/nodejs/node/issues/2165 -export function exists(path: string): Promise { - return promisify(fs.exists)(path); -} - -export function chmod(path: string, mode: number): Promise { - return promisify(fs.chmod)(path, mode); -} - -export function stat(path: string): Promise { - return promisify(fs.stat)(path); -} - -export interface IStatAndLink { - - // The stats of the file. If the file is a symbolic - // link, the stats will be of that target file and - // not the link itself. - // If the file is a symbolic link pointing to a non - // existing file, the stat will be of the link and - // the `dangling` flag will indicate this. - stat: fs.Stats; - - // Will be provided if the resource is a symbolic link - // on disk. Use the `dangling` flag to find out if it - // points to a resource that does not exist on disk. - symbolicLink?: { dangling: boolean }; -} - -export async function statLink(path: string): Promise { - - // First stat the link - let lstats: fs.Stats | undefined; - try { - lstats = await lstat(path); - - // Return early if the stat is not a symbolic link at all - if (!lstats.isSymbolicLink()) { - return { stat: lstats }; - } - } catch (error) { - /* ignore - use stat() instead */ - } - - // 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 stat(path); - - return { stat: stats, symbolicLink: lstats?.isSymbolicLink() ? { dangling: false } : undefined }; - } catch (error) { - - // If the link points to a non-existing file we still want - // to return it as result while setting dangling: true flag - if (error.code === 'ENOENT' && lstats) { - return { stat: lstats, symbolicLink: { dangling: true } }; + if (typeof child === 'string') { + return isMacintosh ? normalizeNFC(child) : child; } - // Windows: workaround a node.js bug where reparse points - // are not supported (https://github.com/nodejs/node/issues/36790) - if (isWindows && error.code === 'EACCES' && lstats) { - try { - const stats = await stat(await readlink(path)); + child.name = isMacintosh ? normalizeNFC(child.name) : child.name; - return { stat: stats, symbolicLink: lstats.isSymbolicLink() ? { dangling: false } : undefined }; - } catch (error) { + return child; + }); +} - // If the link points to a non-existing file we still want - // to return it as result while setting dangling: true flag - if (error.code === 'ENOENT') { - return { stat: lstats, symbolicLink: { dangling: true } }; - } +/** + * A convinience method to read all children of a path that + * are directories. + */ +export async function readDirsInDir(dirPath: string): Promise { + const children = await readdir(dirPath); + const directories: string[] = []; - throw error; + for (const child of children) { + if (await SymlinkSupport.existsDirectory(join(dirPath, child))) { + directories.push(child); + } + } + + return directories; +} + +//#endregion + +//#region whenDeleted() + +/** + * A `Promise` that resolves when the provided `path` + * is deleted from disk. + */ +export function whenDeleted(path: string, intervalMs = 1000): Promise { + return new Promise(resolve => { + let running = false; + const interval = setInterval(() => { + if (!running) { + running = true; + fs.access(path, err => { + running = false; + + if (err) { + clearInterval(interval); + resolve(undefined); + } + }); } + }, intervalMs); + }); +} + +//#endregion + +//#region Methods with symbolic links support + +export namespace SymlinkSupport { + + export interface IStats { + + // The stats of the file. If the file is a symbolic + // link, the stats will be of that target file and + // not the link itself. + // If the file is a symbolic link pointing to a non + // existing file, the stat will be of the link and + // the `dangling` flag will indicate this. + stat: fs.Stats; + + // Will be provided if the resource is a symbolic link + // on disk. Use the `dangling` flag to find out if it + // points to a resource that does not exist on disk. + symbolicLink?: { dangling: boolean }; + } + + /** + * Resolves the `fs.Stats` of the provided path. If the path is a + * symbolic link, the `fs.Stats` will be from the target it points + * to. If the target does not exist, `dangling: true` will be returned + * as `symbolicLink` value. + */ + export async function stat(path: string): Promise { + + // First stat the link + let lstats: fs.Stats | undefined; + try { + lstats = await fs.promises.lstat(path); + + // Return early if the stat is not a symbolic link at all + if (!lstats.isSymbolicLink()) { + return { stat: lstats }; + } + } catch (error) { + /* ignore - use stat() instead */ } - throw error; + // 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 fs.promises.stat(path); + + return { stat: stats, symbolicLink: lstats?.isSymbolicLink() ? { dangling: false } : undefined }; + } catch (error) { + + // If the link points to a non-existing file we still want + // to return it as result while setting dangling: true flag + if (error.code === 'ENOENT' && lstats) { + return { stat: lstats, symbolicLink: { dangling: true } }; + } + + // Windows: workaround a node.js bug where reparse points + // are not supported (https://github.com/nodejs/node/issues/36790) + if (isWindows && error.code === 'EACCES') { + try { + const stats = await fs.promises.stat(await fs.promises.readlink(path)); + + return { stat: stats, symbolicLink: { dangling: false } }; + } catch (error) { + + // If the link points to a non-existing file we still want + // to return it as result while setting dangling: true flag + if (error.code === 'ENOENT' && lstats) { + return { stat: lstats, symbolicLink: { dangling: true } }; + } + + throw error; + } + } + + throw error; + } + } + + /** + * Figures out if the `path` exists and is a file with support + * for symlinks. + * + * Note: this will return `false` for a symlink that exists on + * disk but is dangling (pointing to a non-existing path). + * + * Use `exists` if you only care about the path existing on disk + * or not without support for symbolic links. + */ + export async function existsFile(path: string): Promise { + try { + const { stat, symbolicLink } = await SymlinkSupport.stat(path); + + return stat.isFile() && symbolicLink?.dangling !== true; + } catch (error) { + // Ignore, path might not exist + } + + return false; + } + + /** + * Figures out if the `path` exists and is a directory with support for + * symlinks. + * + * Note: this will return `false` for a symlink that exists on + * disk but is dangling (pointing to a non-existing path). + * + * Use `exists` if you only care about the path existing on disk + * or not without support for symbolic links. + */ + export async function existsDirectory(path: string): Promise { + try { + const { stat, symbolicLink } = await SymlinkSupport.stat(path); + + return stat.isDirectory() && symbolicLink?.dangling !== true; + } catch (error) { + // Ignore, path might not exist + } + + return false; } } -export function lstat(path: string): Promise { - return promisify(fs.lstat)(path); -} +//#endregion -export function rename(oldPath: string, newPath: string): Promise { - return promisify(fs.rename)(oldPath, newPath); -} - -export function renameIgnoreError(oldPath: string, newPath: string): Promise { - return new Promise(resolve => fs.rename(oldPath, newPath, () => resolve())); -} - -export function readlink(path: string): Promise { - return promisify(fs.readlink)(path); -} - -export function unlink(path: string): Promise { - return promisify(fs.unlink)(path); -} - -export function symlink(target: string, path: string, type?: string): Promise { - return promisify(fs.symlink)(target, path, type); -} - -export function truncate(path: string, len: number): Promise { - return promisify(fs.truncate)(path, len); -} - -export function readFile(path: string): Promise; -export function readFile(path: string, encoding: string): Promise; -export function readFile(path: string, encoding?: string): Promise { - return promisify(fs.readFile)(path, encoding); -} - -export async function mkdirp(path: string, mode?: number): Promise { - return promisify(fs.mkdir)(path, { mode, recursive: true }); -} - -// According to node.js docs (https://nodejs.org/docs/v6.5.0/api/fs.html#fs_fs_writefile_file_data_options_callback) -// it is not safe to call writeFile() on the same path multiple times without waiting for the callback to return. -// Therefor we use a Queue on the path that is given to us to sequentialize calls to the same path properly. -const writeFilePathQueues: Map> = new Map(); +//#region Write File +/** + * Same as `fs.writeFile` but with an additional call to + * `fs.fdatasync` after writing to ensure changes are + * flushed to disk. + * + * In addition, multiple writes to the same path are queued. + */ export function writeFile(path: string, data: string, options?: IWriteFileOptions): Promise; export function writeFile(path: string, data: Buffer, options?: IWriteFileOptions): Promise; export function writeFile(path: string, data: Uint8Array, options?: IWriteFileOptions): Promise; @@ -311,6 +382,11 @@ export function writeFile(path: string, data: string | Buffer | Uint8Array, opti }); } +// According to node.js docs (https://nodejs.org/docs/v6.5.0/api/fs.html#fs_fs_writefile_file_data_options_callback) +// it is not safe to call writeFile() on the same path multiple times without waiting for the callback to return. +// Therefor we use a Queue on the path that is given to us to sequentialize calls to the same path properly. +const writeFilePathQueues: Map> = new Map(); + function toQueueKey(path: string): string { let queueKey = path; if (isWindows || isMacintosh) { @@ -388,6 +464,11 @@ function doWriteFileAndFlush(path: string, data: string | Buffer | Uint8Array, o }); } +/** + * Same as `fs.writeFileSync` but with an additional call to + * `fs.fdatasyncSync` after writing to ensure changes are + * flushed to disk. + */ export function writeFileSync(path: string, data: string | Buffer, options?: IWriteFileOptions): void { const ensuredOptions = ensureWriteOptions(options); @@ -426,87 +507,48 @@ function ensureWriteOptions(options?: IWriteFileOptions): IEnsuredWriteFileOptio }; } -export async function readDirsInDir(dirPath: string): Promise { - const children = await readdir(dirPath); - const directories: string[] = []; +//#endregion - for (const child of children) { - if (await dirExists(join(dirPath, child))) { - directories.push(child); - } - } - - return directories; -} - -export async function dirExists(path: string): Promise { - try { - const { stat, symbolicLink } = await statLink(path); - - return stat.isDirectory() && symbolicLink?.dangling !== true; - } catch (error) { - // Ignore, path might not exist - } - - return false; -} - -export async function fileExists(path: string): Promise { - try { - const { stat, symbolicLink } = await statLink(path); - - return stat.isFile() && symbolicLink?.dangling !== true; - } catch (error) { - // Ignore, path might not exist - } - - return false; -} - -export function whenDeleted(path: string): Promise { - - // Complete when wait marker file is deleted - return new Promise(resolve => { - let running = false; - const interval = setInterval(() => { - if (!running) { - running = true; - fs.exists(path, exists => { - running = false; - - if (!exists) { - clearInterval(interval); - resolve(undefined); - } - }); - } - }, 1000); - }); -} +//#region Move / Copy +/** + * A drop-in replacement for `fs.rename` that: + * - updates the `mtime` of the `source` after the operation + * - allows to move across multiple disks + */ export async function move(source: string, target: string): Promise { if (source === target) { - return; + return; // simulate node.js behaviour here and do a no-op if paths match } + // We have been updating `mtime` for move operations for files since the + // beginning for reasons that are no longer quite clear, but changing + // this could be risky as well. As such, trying to reason about it: + // It is very common as developer to have file watchers enabled that watch + // the current workspace for changes. Updating the `mtime` might make it + // easier for these watchers to recognize an actual change. Since changing + // a source code file also updates the `mtime`, moving a file should do so + // as well because conceptually it is a change of a similar category. async function updateMtime(path: string): Promise { - const stat = await lstat(path); - if (stat.isDirectory() || stat.isSymbolicLink()) { - return; // only for files - } - - const fd = await promisify(fs.open)(path, 'a'); try { - await promisify(fs.futimes)(fd, stat.atime, new Date()); - } catch (error) { - //ignore - } + const stat = await fs.promises.lstat(path); + if (stat.isDirectory() || stat.isSymbolicLink()) { + return; // only for files + } - return promisify(fs.close)(fd); + const fh = await fs.promises.open(path, 'a'); + try { + await fh.utimes(stat.atime, new Date()); + } finally { + await fh.close(); + } + } catch (error) { + // Ignore any error + } } try { - await rename(source, target); + await fs.promises.rename(source, target); await updateMtime(target); } catch (error) { @@ -519,7 +561,7 @@ export async function move(source: string, target: string): Promise { // 2.) The user tries to rename a file/folder that ends with a dot. This is not // really possible to move then, at least on UNC devices. if (source.toLowerCase() !== target.toLowerCase() && error.code === 'EXDEV' || source.endsWith('.')) { - await copy(source, target); + await copy(source, target, { preserveSymlinks: false /* copying to another device */ }); await rimraf(source, RimRafMode.MOVE); await updateMtime(target); } else { @@ -528,74 +570,119 @@ export async function move(source: string, target: string): Promise { } } +interface ICopyPayload { + readonly root: { source: string, target: string }; + readonly options: { preserveSymlinks: boolean }; + readonly handledSourcePaths: Set; +} + +/** + * Recursively copies all of `source` to `target`. + * + * The options `preserveSymlinks` configures how symbolic + * links should be handled when encountered. Set to + * `false` to not preserve them and `true` otherwise. + */ +export async function copy(source: string, target: string, options: { preserveSymlinks: boolean }): Promise { + return doCopy(source, target, { root: { source, target }, options, handledSourcePaths: new Set() }); +} + // When copying a file or folder, we want to preserve the mode // it had and as such provide it when creating. However, modes // can go beyond what we expect (see link below), so we mask it. // (https://github.com/nodejs/node-v0.x-archive/issues/3045#issuecomment-4862588) -// -// The `copy` method is very old so we should probably revisit -// it's implementation and check wether this mask is still needed. const COPY_MODE_MASK = 0o777; -export async function copy(source: string, target: string, handledSourcesIn?: { [path: string]: boolean }): Promise { +async function doCopy(source: string, target: string, payload: ICopyPayload): Promise { // Keep track of paths already copied to prevent // cycles from symbolic links to cause issues - const handledSources = handledSourcesIn ?? Object.create(null); - if (handledSources[source]) { + if (payload.handledSourcePaths.has(source)) { return; } else { - handledSources[source] = true; + payload.handledSourcePaths.add(source); } - const { stat, symbolicLink } = await statLink(source); - if (symbolicLink?.dangling) { - return; // skip over dangling symbolic links (https://github.com/microsoft/vscode/issues/111621) + const { stat, symbolicLink } = await SymlinkSupport.stat(source); + + // Symlink + if (symbolicLink) { + if (symbolicLink.dangling) { + return; // do not copy dangling symbolic links (https://github.com/microsoft/vscode/issues/111621) + } + + // Try to re-create the symlink unless `preserveSymlinks: false` + if (payload.options.preserveSymlinks) { + try { + return await doCopySymlink(source, target, payload); + } catch (error) { + // in any case of an error fallback to normal copy via dereferencing + console.warn('[node.js fs] copy of symlink failed: ', error); + } + } } - if (!stat.isDirectory()) { + // Folder + if (stat.isDirectory()) { + return doCopyDirectory(source, target, stat.mode & COPY_MODE_MASK, payload); + } + + // File or file-like + else { return doCopyFile(source, target, stat.mode & COPY_MODE_MASK); } +} + +async function doCopyDirectory(source: string, target: string, mode: number, payload: ICopyPayload): Promise { // Create folder - await mkdirp(target, stat.mode & COPY_MODE_MASK); + await fs.promises.mkdir(target, { recursive: true, mode }); // Copy each file recursively const files = await readdir(source); - for (let i = 0; i < files.length; i++) { - const file = files[i]; - await copy(join(source, file), join(target, file), handledSources); + for (const file of files) { + await doCopy(join(source, file), join(target, file), payload); } } async function doCopyFile(source: string, target: string, mode: number): Promise { - return new Promise((resolve, reject) => { - const reader = fs.createReadStream(source); - const writer = fs.createWriteStream(target, { mode }); - let finished = false; - const finish = (error?: Error) => { - if (!finished) { - finished = true; + // Copy file + await fs.promises.copyFile(source, target); - // in error cases, pass to callback - if (error) { - return reject(error); - } - - // we need to explicitly chmod because of https://github.com/nodejs/node/issues/1104 - fs.chmod(target, mode, error => error ? reject(error) : resolve()); - } - }; - - // handle errors properly - reader.once('error', error => finish(error)); - writer.once('error', error => finish(error)); - - // we are done (underlying fd has been closed) - writer.once('close', () => finish()); - - // start piping - reader.pipe(writer); - }); + // restore mode (https://github.com/nodejs/node/issues/1104) + await fs.promises.chmod(target, mode); } + +async function doCopySymlink(source: string, target: string, payload: ICopyPayload): Promise { + + // Figure out link target + 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 + // case we want the symlink to point to the target and + // not the source + if (isEqualOrParent(linkTarget, payload.root.source, !isLinux)) { + linkTarget = join(payload.root.target, linkTarget.substr(payload.root.source.length + 1)); + } + + // Create symlink + await fs.promises.symlink(linkTarget, target); +} + +//#endregion + +//#region Async FS Methods + +export async function exists(path: string): Promise { + try { + await fs.promises.access(path); + + return true; + } catch { + return false; + } +} + +//#endregion diff --git a/src/vs/base/node/powershell.ts b/src/vs/base/node/powershell.ts index 29a7f5944..6e4381ed8 100644 --- a/src/vs/base/node/powershell.ts +++ b/src/vs/base/node/powershell.ts @@ -8,18 +8,72 @@ import * as os from 'os'; import * as path from 'vs/base/common/path'; import { env } from 'vs/base/common/process'; -const WindowsPowerShell64BitLabel = 'Windows PowerShell'; -const WindowsPowerShell32BitLabel = 'Windows PowerShell (x86)'; - // This is required, since parseInt("7-preview") will return 7. const IntRegex: RegExp = /^\d+$/; const PwshMsixRegex: RegExp = /^Microsoft.PowerShell_.*/; const PwshPreviewMsixRegex: RegExp = /^Microsoft.PowerShellPreview_.*/; -// The platform details descriptor for the platform we're on -const isProcess64Bit: boolean = process.arch === 'x64'; -const isOS64Bit: boolean = isProcess64Bit || os.arch() === 'x64'; +const enum Arch { + x64, + x86, + ARM +} + +let processArch: Arch; +switch (process.arch) { + case 'ia32': + case 'x32': + processArch = Arch.x86; + break; + case 'arm': + case 'arm64': + processArch = Arch.ARM; + break; + default: + processArch = Arch.x64; + break; +} + +/* +Currently, here are the values for these environment variables on their respective archs: + +On x86 process on x86: +PROCESSOR_ARCHITECTURE is X86 +PROCESSOR_ARCHITEW6432 is undefined + +On x86 process on x64: +PROCESSOR_ARCHITECTURE is X86 +PROCESSOR_ARCHITEW6432 is AMD64 + +On x64 process on x64: +PROCESSOR_ARCHITECTURE is AMD64 +PROCESSOR_ARCHITEW6432 is undefined + +On ARM process on ARM: +PROCESSOR_ARCHITECTURE is ARM64 +PROCESSOR_ARCHITEW6432 is undefined + +On x86 process on ARM: +PROCESSOR_ARCHITECTURE is X86 +PROCESSOR_ARCHITEW6432 is ARM64 + +On x64 process on ARM: +PROCESSOR_ARCHITECTURE is ARM64 +PROCESSOR_ARCHITEW6432 is undefined +*/ +let osArch: Arch; +if (process.env['PROCESSOR_ARCHITEW6432']) { + osArch = process.env['PROCESSOR_ARCHITEW6432'] === 'ARM64' + ? Arch.ARM + : Arch.x64; +} else if (process.env['PROCESSOR_ARCHITECTURE'] === 'ARM64') { + osArch = Arch.ARM; +} else if (process.env['PROCESSOR_ARCHITECTURE'] === 'X86') { + osArch = Arch.x86; +} else { + osArch = Arch.x64; +} export interface IPowerShellExeDetails { readonly displayName: string; @@ -38,7 +92,7 @@ class PossiblePowerShellExe implements IPossiblePowerShellExe { public async exists(): Promise { if (this.knownToExist === undefined) { - this.knownToExist = await pfs.fileExists(this.exePath); + this.knownToExist = await pfs.SymlinkSupport.existsFile(this.exePath); } return this.knownToExist; } @@ -53,12 +107,12 @@ function getProgramFilesPath( } // We might be a 64-bit process looking for 32-bit program files - if (isProcess64Bit) { + if (processArch === Arch.x64) { return env['ProgramFiles(x86)'] || null; } // We might be a 32-bit process looking for 64-bit program files - if (isOS64Bit) { + if (osArch === Arch.x64) { return env.ProgramW6432 || null; } @@ -66,28 +120,6 @@ function getProgramFilesPath( return null; } -function getSystem32Path({ useAlternateBitness = false }: { useAlternateBitness?: boolean } = {}): string { - const windir: string = env.windir!; - - if (!useAlternateBitness) { - // Just use the native system bitness - return path.join(windir, 'System32'); - } - - // We might be a 64-bit process looking for 32-bit system32 - if (isProcess64Bit) { - return path.join(windir, 'SysWOW64'); - } - - // We might be a 32-bit process looking for 64-bit system32 - if (isOS64Bit) { - return path.join(windir, 'Sysnative'); - } - - // We're on a 32-bit Windows, so no alternate bitness - return path.join(windir, 'System32'); -} - async function findPSCoreWindowsInstallation( { useAlternateBitness = false, findPreview = false }: { useAlternateBitness?: boolean; findPreview?: boolean } = {}): Promise { @@ -100,7 +132,7 @@ async function findPSCoreWindowsInstallation( const powerShellInstallBaseDir = path.join(programFilesPath, 'PowerShell'); // Ensure the base directory exists - if (!await pfs.dirExists(powerShellInstallBaseDir)) { + if (!await pfs.SymlinkSupport.existsDirectory(powerShellInstallBaseDir)) { return null; } @@ -142,7 +174,7 @@ async function findPSCoreWindowsInstallation( // Now look for the file const exePath = path.join(powerShellInstallBaseDir, item, 'pwsh.exe'); - if (!await pfs.fileExists(exePath)) { + if (!await pfs.SymlinkSupport.existsFile(exePath)) { continue; } @@ -169,7 +201,7 @@ async function findPSCoreMsix({ findPreview }: { findPreview?: boolean } = {}): // Find the base directory for MSIX application exe shortcuts const msixAppDir = path.join(env.LOCALAPPDATA, 'Microsoft', 'WindowsApps'); - if (!await pfs.dirExists(msixAppDir)) { + if (!await pfs.SymlinkSupport.existsDirectory(msixAppDir)) { return null; } @@ -179,11 +211,15 @@ async function findPSCoreMsix({ findPreview }: { findPreview?: boolean } = {}): : { pwshMsixDirRegex: PwshMsixRegex, pwshMsixName: 'PowerShell (Store)' }; // We should find only one such application, so return on the first one - for (const subdir of await pfs.readdir(msixAppDir)) { - if (pwshMsixDirRegex.test(subdir)) { - const pwshMsixPath = path.join(msixAppDir, subdir, 'pwsh.exe'); - return new PossiblePowerShellExe(pwshMsixPath, pwshMsixName); + try { + for (const subdir of await pfs.readdir(msixAppDir)) { + if (pwshMsixDirRegex.test(subdir)) { + const pwshMsixPath = path.join(msixAppDir, subdir, 'pwsh.exe'); + return new PossiblePowerShellExe(pwshMsixPath, pwshMsixName); + } } + } catch (err) { + console.warn(`Unable to read MSIX directory (${msixAppDir}) because of the following error: ${err}`); } // If we find nothing, return null @@ -196,33 +232,13 @@ function findPSCoreDotnetGlobalTool(): IPossiblePowerShellExe { return new PossiblePowerShellExe(dotnetGlobalToolExePath, '.NET Core PowerShell Global Tool'); } -function findWinPS({ useAlternateBitness = false }: { useAlternateBitness?: boolean } = {}): IPossiblePowerShellExe | null { +function findWinPS(): IPossiblePowerShellExe | null { + const winPSPath = path.join( + env.windir!, + processArch === Arch.x86 && osArch !== Arch.x86 ? 'SysNative' : 'System32', + 'WindowsPowerShell', 'v1.0', 'powershell.exe'); - // x86 and ARM only have one WinPS on them - if (!isOS64Bit && useAlternateBitness) { - return null; - } - - const systemFolderPath = getSystem32Path({ useAlternateBitness }); - - const winPSPath = path.join(systemFolderPath, 'WindowsPowerShell', 'v1.0', 'powershell.exe'); - - let displayName: string; - if (isProcess64Bit) { - displayName = useAlternateBitness - ? WindowsPowerShell32BitLabel - : WindowsPowerShell64BitLabel; - } else if (isOS64Bit) { - displayName = useAlternateBitness - ? WindowsPowerShell64BitLabel - : WindowsPowerShell32BitLabel; - } else { - // NOTE: ARM Windows devices also have Windows PowerShell x86 on them. There is no - // "ARM Windows PowerShell". - displayName = WindowsPowerShell32BitLabel; - } - - return new PossiblePowerShellExe(winPSPath, displayName, true); + return new PossiblePowerShellExe(winPSPath, 'Windows PowerShell', true); } /** @@ -276,18 +292,10 @@ async function* enumerateDefaultPowerShellInstallations(): AsyncIterable { - if (await promisify(fs.exists)(path)) { - return !((await promisify(fs.stat)(path)).isDirectory()); + if (await pfs.exists(path)) { + return !((await fs.promises.stat(path)).isDirectory()); } return false; } diff --git a/extensions/typescript-language-features/src/typings/collections.d.ts b/src/vs/base/node/userDataPath.d.ts similarity index 78% rename from extensions/typescript-language-features/src/typings/collections.d.ts rename to src/vs/base/node/userDataPath.d.ts index c67debc0e..bc09b834a 100644 --- a/extensions/typescript-language-features/src/typings/collections.d.ts +++ b/src/vs/base/node/userDataPath.d.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -interface ObjectMap { - [key: string]: V; -} \ No newline at end of file +/** + * Returns the user data path to use. + */ +export function getDefaultUserDataPath(): string; diff --git a/src/vs/base/node/userDataPath.js b/src/vs/base/node/userDataPath.js new file mode 100644 index 000000000..93384cb8f --- /dev/null +++ b/src/vs/base/node/userDataPath.js @@ -0,0 +1,72 @@ +/*--------------------------------------------------------------------------------------------- + * 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('os')} os + * @param {string} productName + */ + function factory(path, os, productName) { + + function getDefaultUserDataPath() { + + // Support global VSCODE_APPDATA environment variable + let appDataPath = process.env['VSCODE_APPDATA']; + + // Otherwise check per platform + if (!appDataPath) { + switch (process.platform) { + case 'win32': + appDataPath = process.env['APPDATA']; + if (!appDataPath) { + const userProfile = process.env['USERPROFILE']; + if (typeof userProfile !== 'string') { + throw new Error('Windows: Unexpected undefined %USERPROFILE% environment variable'); + } + appDataPath = path.join(userProfile, 'AppData', 'Roaming'); + } + break; + case 'darwin': + appDataPath = path.join(os.homedir(), 'Library', 'Application Support'); + break; + case 'linux': + appDataPath = process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config'); + break; + default: + throw new Error('Platform not supported'); + } + } + + return path.join(appDataPath, productName); + } + + return { + getDefaultUserDataPath + }; + } + + if (typeof define === 'function') { + define(['require', 'path', 'os', 'vs/base/common/network', 'vs/base/common/resources'], function (require, /** @type {typeof import('path')} */ path, /** @type {typeof import('os')} */ os, /** @type {typeof import('../common/network')} */ network, /** @type {typeof import("../common/resources")} */ resources) { + const rootPath = resources.dirname(network.FileAccess.asFileUri('', require)); + const pkg = require.__$__nodeRequire(resources.joinPath(rootPath, 'package.json').fsPath); + + return factory(path, os, pkg.name); + }); // amd + } else if (typeof module === 'object' && typeof module.exports === 'object') { + const pkg = require('../../../../package.json'); + const path = require('path'); + const os = require('os'); + + module.exports = factory(path, os, pkg.name); // commonjs + } else { + throw new Error('Unknown context'); + } +}()); diff --git a/src/vs/base/node/zip.ts b/src/vs/base/node/zip.ts index 4d98c0f29..0d74501d9 100644 --- a/src/vs/base/node/zip.ts +++ b/src/vs/base/node/zip.ts @@ -5,10 +5,10 @@ import * as nls from 'vs/nls'; import * as path from 'vs/base/common/path'; -import { createWriteStream, WriteStream } from 'fs'; +import { promises, createWriteStream, WriteStream } from 'fs'; import { Readable } from 'stream'; import { Sequencer, createCancelablePromise } from 'vs/base/common/async'; -import { mkdirp, rimraf } from 'vs/base/node/pfs'; +import { rimraf } from 'vs/base/node/pfs'; import { open as _openZip, Entry, ZipFile } from 'yauzl'; import * as yazl from 'yazl'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -86,7 +86,7 @@ function extractEntry(stream: Readable, fileName: string, mode: number, targetPa } }); - return Promise.resolve(mkdirp(targetDirName)).then(() => new Promise((c, e) => { + return Promise.resolve(promises.mkdir(targetDirName, { recursive: true })).then(() => new Promise((c, e) => { if (token.isCancellationRequested) { return; } @@ -149,7 +149,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 => mkdirp(targetFileName).then(() => readNextEntry(token)).then(undefined, e)); + last = createCancelablePromise(token => promises.mkdir(targetFileName, { recursive: true }).then(() => readNextEntry(token)).then(undefined, e)); return; } diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index 535502f3e..0757b52ab 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -1023,146 +1023,141 @@ export class StaticRouter implements IClientRouter } } - -//#region createChannelReceiver / createChannelSender - /** - * Use both `createChannelReceiver` and `createChannelSender` - * for automated process <=> process communication over methods - * and events. You do not need to spell out each method on both - * sides, a proxy will take care of this. + * Use ProxyChannels to automatically wrapping and unwrapping + * services to/from IPC channels, instead of manually wrapping + * each service method and event. * - * Rules: - * - if marshalling is enabled, only `URI` and `RegExp` is converted + * Restrictions: + * - If marshalling is enabled, only `URI` and `RegExp` is converted * automatically for you - * - events must follow the naming convention `onUppercase` + * - Events must follow the naming convention `onUpperCase` * - `CancellationToken` is currently not supported - * - if a context is provided, you can use `AddFirstParameterToFunctions` + * - If a context is provided, you can use `AddFirstParameterToFunctions` * utility to signal this in the receiving side type */ +export namespace ProxyChannel { -export interface IBaseChannelOptions { + export interface IProxyOptions { - /** - * Disables automatic marshalling of `URI`. - * If marshalling is disabled, `UriComponents` - * must be used instead. - */ - disableMarshalling?: boolean; -} - -export interface IChannelReceiverOptions extends IBaseChannelOptions { } - -export function createChannelReceiver(service: unknown, options?: IChannelReceiverOptions): IServerChannel { - const handler = service as { [key: string]: unknown }; - const disableMarshalling = options && options.disableMarshalling; - - // Buffer any event that should be supported by - // iterating over all property keys and finding them - const mapEventNameToEvent = new Map>(); - for (const key in handler) { - if (propertyIsEvent(key)) { - mapEventNameToEvent.set(key, Event.buffer(handler[key] as Event, true)); - } + /** + * Disables automatic marshalling of `URI`. + * If marshalling is disabled, `UriComponents` + * must be used instead. + */ + disableMarshalling?: boolean; } - return new class implements IServerChannel { + export interface ICreateServiceChannelOptions extends IProxyOptions { } - listen(_: unknown, event: string): Event { - const eventImpl = mapEventNameToEvent.get(event); - if (eventImpl) { - return eventImpl as Event; + export function fromService(service: unknown, options?: ICreateServiceChannelOptions): IServerChannel { + const handler = service as { [key: string]: unknown }; + const disableMarshalling = options && options.disableMarshalling; + + // Buffer any event that should be supported by + // iterating over all property keys and finding them + const mapEventNameToEvent = new Map>(); + for (const key in handler) { + if (propertyIsEvent(key)) { + mapEventNameToEvent.set(key, Event.buffer(handler[key] as Event, true)); } - - throw new Error(`Event not found: ${event}`); } - call(_: unknown, command: string, args?: any[]): Promise { - const target = handler[command]; - if (typeof target === 'function') { + return new class implements IServerChannel { - // Revive unless marshalling disabled - if (!disableMarshalling && Array.isArray(args)) { - for (let i = 0; i < args.length; i++) { - args[i] = revive(args[i]); - } + listen(_: unknown, event: string): Event { + const eventImpl = mapEventNameToEvent.get(event); + if (eventImpl) { + return eventImpl as Event; } - return target.apply(handler, args); + throw new Error(`Event not found: ${event}`); } - throw new Error(`Method not found: ${command}`); - } - }; -} - -export interface IChannelSenderOptions extends IBaseChannelOptions { - - /** - * If provided, will add the value of `context` - * to each method call to the target. - */ - context?: unknown; - - /** - * If provided, will not proxy any of the properties - * that are part of the Map but rather return that value. - */ - properties?: Map; -} - -export function createChannelSender(channel: IChannel, options?: IChannelSenderOptions): T { - const disableMarshalling = options && options.disableMarshalling; - - return new Proxy({}, { - get(_target: T, propKey: PropertyKey) { - if (typeof propKey === 'string') { - - // Check for predefined values - if (options?.properties?.has(propKey)) { - return options.properties.get(propKey); - } - - // Event - if (propertyIsEvent(propKey)) { - return channel.listen(propKey); - } - - // Function - return async function (...args: any[]) { - - // Add context if any - let methodArgs: any[]; - if (options && !isUndefinedOrNull(options.context)) { - methodArgs = [options.context, ...args]; - } else { - methodArgs = args; - } - - const result = await channel.call(propKey, methodArgs); + call(_: unknown, command: string, args?: any[]): Promise { + const target = handler[command]; + if (typeof target === 'function') { // Revive unless marshalling disabled - if (!disableMarshalling) { - return revive(result); + if (!disableMarshalling && Array.isArray(args)) { + for (let i = 0; i < args.length; i++) { + args[i] = revive(args[i]); + } } - return result; - }; + return target.apply(handler, args); + } + + throw new Error(`Method not found: ${command}`); } + }; + } - throw new Error(`Property not found: ${String(propKey)}`); - } - }) as T; + export interface ICreateProxyServiceOptions extends IProxyOptions { + + /** + * If provided, will add the value of `context` + * to each method call to the target. + */ + context?: unknown; + + /** + * If provided, will not proxy any of the properties + * that are part of the Map but rather return that value. + */ + properties?: Map; + } + + export function toService(channel: IChannel, options?: ICreateProxyServiceOptions): T { + const disableMarshalling = options && options.disableMarshalling; + + return new Proxy({}, { + get(_target: T, propKey: PropertyKey) { + if (typeof propKey === 'string') { + + // Check for predefined values + if (options?.properties?.has(propKey)) { + return options.properties.get(propKey); + } + + // Event + if (propertyIsEvent(propKey)) { + return channel.listen(propKey); + } + + // Function + return async function (...args: any[]) { + + // Add context if any + let methodArgs: any[]; + if (options && !isUndefinedOrNull(options.context)) { + methodArgs = [options.context, ...args]; + } else { + methodArgs = args; + } + + const result = await channel.call(propKey, methodArgs); + + // Revive unless marshalling disabled + if (!disableMarshalling) { + return revive(result); + } + + return result; + }; + } + + throw new Error(`Property not found: ${String(propKey)}`); + } + }) as T; + } + + function propertyIsEvent(name: string): boolean { + // Assume a property is an event if it has a form of "onSomething" + return name[0] === 'o' && name[1] === 'n' && strings.isUpperAsciiLetter(name.charCodeAt(2)); + } } -function propertyIsEvent(name: string): boolean { - // Assume a property is an event if it has a form of "onSomething" - return name[0] === 'o' && name[1] === 'n' && strings.isUpperAsciiLetter(name.charCodeAt(2)); -} - -//#endregion - - const colorTables = [ ['#2977B1', '#FC802D', '#34A13A', '#D3282F', '#9366BA'], ['#8B564C', '#E177C0', '#7F7F7F', '#BBBE3D', '#2EBECD'] diff --git a/src/vs/base/parts/ipc/electron-sandbox/ipc.mp.ts b/src/vs/base/parts/ipc/electron-browser/ipc.mp.ts similarity index 90% rename from src/vs/base/parts/ipc/electron-sandbox/ipc.mp.ts rename to src/vs/base/parts/ipc/electron-browser/ipc.mp.ts index 98e865fe9..3f031a057 100644 --- a/src/vs/base/parts/ipc/electron-sandbox/ipc.mp.ts +++ b/src/vs/base/parts/ipc/electron-browser/ipc.mp.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { ipcRenderer } from 'electron'; import { Event } from 'vs/base/common/event'; import { ClientConnectionEvent, IPCServer } from 'vs/base/parts/ipc/common/ipc'; import { Protocol as MessagePortProtocol } from 'vs/base/parts/ipc/common/ipc.mp'; @@ -39,6 +39,9 @@ export class Server extends IPCServer { }; // Send one port back to the requestor + // Note: we intentionally use `electron` APIs here because + // transferables like the `MessagePort` cannot be transfered + // over preload scripts when `contextIsolation: true` ipcRenderer.postMessage('vscode:createMessageChannelResult', nonce, [outgoingPort]); return result; diff --git a/src/vs/base/parts/ipc/node/ipc.cp.ts b/src/vs/base/parts/ipc/node/ipc.cp.ts index 6733816b8..84ef5cb0a 100644 --- a/src/vs/base/parts/ipc/node/ipc.cp.ts +++ b/src/vs/base/parts/ipc/node/ipc.cp.ts @@ -71,10 +71,8 @@ export interface IIPCOptions { debugBrk?: number; /** - * See https://github.com/microsoft/vscode/issues/27665 - * Allows to pass in fresh execArgv to the forked process such that it doesn't inherit them from `process.execArgv`. - * e.g. Launching the extension host process with `--inspect-brk=xxx` and then forking a process from the extension host - * results in the forked process inheriting `--inspect-brk=xxx`. + * If set, starts the fork with empty execArgv. If not set, execArgv from the parent proces are inherited, + * except --inspect= and --inspect-brk= which are filtered as they would result in a port conflict. */ freshExecArgv?: boolean; @@ -198,6 +196,12 @@ export class Client implements IChannelClient, IDisposable { forkOpts.execArgv = ['--nolazy', '--inspect-brk=' + this.options.debugBrk]; } + if (forkOpts.execArgv === undefined) { + // if not set, the forked process inherits the execArgv of the parent process + // --inspect and --inspect-brk can not be inherited as the port would conflict + forkOpts.execArgv = process.execArgv.filter(a => !/^--inspect(-brk)?=/.test(a)); // remove + } + if (isMacintosh && forkOpts.env) { // Unset `DYLD_LIBRARY_PATH`, as it leads to process crashes // See https://github.com/microsoft/vscode/issues/105848 diff --git a/src/vs/base/parts/ipc/test/common/ipc.test.ts b/src/vs/base/parts/ipc/test/common/ipc.test.ts index 8ed0f3797..6e3dd39a5 100644 --- a/src/vs/base/parts/ipc/test/common/ipc.test.ts +++ b/src/vs/base/parts/ipc/test/common/ipc.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IChannel, IServerChannel, IMessagePassingProtocol, IPCServer, ClientConnectionEvent, IPCClient, createChannelReceiver, createChannelSender } from 'vs/base/parts/ipc/common/ipc'; +import { IChannel, IServerChannel, IMessagePassingProtocol, IPCServer, ClientConnectionEvent, IPCClient, ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { Emitter, Event } from 'vs/base/common/event'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { canceled } from 'vs/base/common/errors'; @@ -332,10 +332,10 @@ suite('Base IPC', function () { const testServer = new TestIPCServer(); server = testServer; - server.registerChannel(TestChannelId, createChannelReceiver(service)); + server.registerChannel(TestChannelId, ProxyChannel.fromService(service)); client = testServer.createConnection('client1'); - ipcService = createChannelSender(client.getChannel(TestChannelId)); + ipcService = ProxyChannel.toService(client.getChannel(TestChannelId)); }); teardown(function () { @@ -398,10 +398,10 @@ suite('Base IPC', function () { const testServer = new TestIPCServer(); server = testServer; - server.registerChannel(TestChannelId, createChannelReceiver(service)); + server.registerChannel(TestChannelId, ProxyChannel.fromService(service)); client = testServer.createConnection('client1'); - ipcService = createChannelSender(client.getChannel(TestChannelId), { context: 'Super Context' }); + ipcService = ProxyChannel.toService(client.getChannel(TestChannelId), { context: 'Super Context' }); }); teardown(function () { diff --git a/src/vs/base/parts/ipc/test/electron-sandbox/ipc.mp.test.ts b/src/vs/base/parts/ipc/test/electron-sandbox/ipc.mp.test.ts index 118900279..06f5451ce 100644 --- a/src/vs/base/parts/ipc/test/electron-sandbox/ipc.mp.test.ts +++ b/src/vs/base/parts/ipc/test/electron-sandbox/ipc.mp.test.ts @@ -11,7 +11,7 @@ suite('IPC, MessagePorts', () => { test('message port close event', async () => { const { port1, port2 } = new MessageChannel(); - new MessagePortClient(port1, 'client1'); + const client1 = new MessagePortClient(port1, 'client1'); const client2 = new MessagePortClient(port2, 'client2'); // This test ensures that Electron's API for the close event @@ -24,5 +24,7 @@ suite('IPC, MessagePorts', () => { client2.dispose(); assert.ok(await whenClosed); + + client1.dispose(); }); }); diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index 038816012..2ab5e7a47 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -56,7 +56,7 @@ export interface IQuickInputStyles { countBadge: ICountBadgetyles; button: IButtonStyles; progressBar: IProgressBarStyles; - list: IListStyles & { listInactiveFocusForeground?: Color; pickerGroupBorder?: Color; pickerGroupForeground?: Color; }; + list: IListStyles & { pickerGroupBorder?: Color; pickerGroupForeground?: Color; }; } export interface IQuickInputWidgetStyles { @@ -1706,10 +1706,6 @@ export class QuickInputController extends Disposable { this.ui.list.style(this.styles.list); const content: string[] = []; - if (this.styles.list.listInactiveFocusForeground) { - content.push(`.monaco-list .monaco-list-row.focused { color: ${this.styles.list.listInactiveFocusForeground}; }`); - content.push(`.monaco-list .monaco-list-row.focused:hover { color: ${this.styles.list.listInactiveFocusForeground}; }`); // overwrite :hover style in this case! - } if (this.styles.list.pickerGroupBorder) { content.push(`.quick-input-list .quick-input-list-entry { border-top-color: ${this.styles.list.pickerGroupBorder}; }`); } diff --git a/src/vs/base/parts/sandbox/electron-browser/preload.js b/src/vs/base/parts/sandbox/electron-browser/preload.js index 11984805d..bd12c6d8f 100644 --- a/src/vs/base/parts/sandbox/electron-browser/preload.js +++ b/src/vs/base/parts/sandbox/electron-browser/preload.js @@ -35,17 +35,6 @@ } }, - /** - * @param {string} channel - * @param {any} message - * @param {MessagePort[]} transfer - */ - postMessage(channel, message, transfer) { - if (validateIPC(channel)) { - ipcRenderer.postMessage(channel, message, transfer); - } - }, - /** * @param {string} channel * @param {any[]} args @@ -88,6 +77,33 @@ } }, + ipcMessagePort: { + + /** + * @param {string} channelRequest + * @param {string} channelResponse + * @param {string} requestNonce + */ + connect(channelRequest, channelResponse, requestNonce) { + if (validateIPC(channelRequest) && validateIPC(channelResponse)) { + const responseListener = (/** @type {import('electron').IpcRendererEvent} */ e, /** @type {string} */ responseNonce) => { + // validate that the nonce from the response is the same + // as when requested. and if so, use `postMessage` to + // send the `MessagePort` safely over, even when context + // isolation is enabled + if (requestNonce === responseNonce) { + ipcRenderer.off(channelResponse, responseListener); + window.postMessage(requestNonce, '*', e.ports); + } + }; + + // request message port from main and await result + ipcRenderer.on(channelResponse, responseListener); + ipcRenderer.send(channelRequest, requestNonce); + } + } + }, + /** * Support for subset of methods of Electron's `webFrame` type. */ diff --git a/src/vs/base/parts/sandbox/electron-sandbox/electronTypes.ts b/src/vs/base/parts/sandbox/electron-sandbox/electronTypes.ts index 1adb322d1..e03f9e136 100644 --- a/src/vs/base/parts/sandbox/electron-sandbox/electronTypes.ts +++ b/src/vs/base/parts/sandbox/electron-sandbox/electronTypes.ts @@ -15,10 +15,12 @@ export interface IpcRendererEvent extends Event { // Docs: https://electronjs.org/docs/api/structures/ipc-renderer-event - /** - * A list of MessagePorts that were transferred with this message - */ - ports: MessagePort[]; + // Note: API with `Transferable` intentionally commented out because you + // cannot transfer these when `contextIsolation: true`. + // /** + // * A list of MessagePorts that were transferred with this message + // */ + // ports: MessagePort[]; /** * The `IpcRenderer` instance that emitted the event originally */ @@ -93,20 +95,23 @@ export interface IpcRenderer { * If you do not need a response to the message, consider using `ipcRenderer.send`. */ invoke(channel: string, ...args: any[]): Promise; - /** - * Send a message to the main process, optionally transferring ownership of zero or - * more `MessagePort` objects. - * - * The transferred `MessagePort` objects will be available in the main process as - * `MessagePortMain` objects by accessing the `ports` property of the emitted - * event. - * - * For example: - * - * For more information on using `MessagePort` and `MessageChannel`, see the MDN - * documentation. - */ - postMessage(channel: string, message: any, transfer?: MessagePort[]): void; + + // Note: API with `Transferable` intentionally commented out because you + // cannot transfer these when `contextIsolation: true`. + // /** + // * Send a message to the main process, optionally transferring ownership of zero or + // * more `MessagePort` objects. + // * + // * The transferred `MessagePort` objects will be available in the main process as + // * `MessagePortMain` objects by accessing the `ports` property of the emitted + // * event. + // * + // * For example: + // * + // * For more information on using `MessagePort` and `MessageChannel`, see the MDN + // * documentation. + // */ + // postMessage(channel: string, message: any): void; } export interface WebFrame { diff --git a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts index 36fda091b..05492311e 100644 --- a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts +++ b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts @@ -3,10 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { globals, INodeProcess, IProcessEnvironment } from 'vs/base/common/platform'; +import { globals, IProcessEnvironment } from 'vs/base/common/platform'; import { ProcessMemoryInfo, CrashReporter, IpcRenderer, WebFrame } from 'vs/base/parts/sandbox/electron-sandbox/electronTypes'; -export interface ISandboxNodeProcess extends INodeProcess { +/** + * In sandboxed renderers we cannot expose all of the `process` global of node.js + */ +export interface IPartialNodeProcess { /** * The process.platform property returns a string identifying the operating system platform @@ -40,24 +43,6 @@ export interface ISandboxNodeProcess extends INodeProcess { */ readonly execPath: string; - /** - * Resolve the true process environment to use and apply it to `process.env`. - * - * There are different layers of environment that will apply: - * - `process.env`: this is the actual environment of the process before this method - * - `shellEnv` : if the program was not started from a terminal, we resolve all shell - * variables to get the same experience as if the program was started from - * a terminal (Linux, macOS) - * - `userEnv` : this is instance specific environment, e.g. if the user started the program - * from a terminal and changed certain variables - * - * The order of overwrites is `process.env` < `shellEnv` < `userEnv`. - * - * It is critical that every process awaits this method early on startup to get the right - * set of environment in `process.env`. - */ - resolveEnv(userEnv: IProcessEnvironment): Promise; - /** * A listener on the process. Only a small subset of listener types are allowed. */ @@ -79,6 +64,28 @@ export interface ISandboxNodeProcess extends INodeProcess { getProcessMemoryInfo: () => Promise; } +export interface ISandboxNodeProcess extends IPartialNodeProcess { + + /** + * A custom method we add to `process`: Resolve the true process environment to use and + * apply it to `process.env`. + * + * There are different layers of environment that will apply: + * - `process.env`: this is the actual environment of the process before this method + * - `shellEnv` : if the program was not started from a terminal, we resolve all shell + * variables to get the same experience as if the program was started from + * a terminal (Linux, macOS) + * - `userEnv` : this is instance specific environment, e.g. if the user started the program + * from a terminal and changed certain variables + * + * The order of overwrites is `process.env` < `shellEnv` < `userEnv`. + * + * It is critical that every process awaits this method early on startup to get the right + * set of environment in `process.env`. + */ + resolveEnv(userEnv: IProcessEnvironment): Promise; +} + export interface ISandboxContext { /** @@ -87,7 +94,23 @@ export interface ISandboxContext { sandbox: boolean; } +export interface IpcMessagePort { + + /** + * Establish a connection via `MessagePort` to a target. The main process + * will need to transfer the port over to the `channelResponse` after listening + * to `channelRequest` with a payload of `requestNonce` so that the + * source can correlate the response. + * + * The source should install a `window.on('message')` listener, ensuring `e.data` + * matches `requestNonce`, `e.source` matches `window` and then receiving the + * `MessagePort` via `e.ports[0]`. + */ + connect(channelRequest: string, channelResponse: string, requestNonce: string): void; +} + export const ipcRenderer: IpcRenderer = globals.vscode.ipcRenderer; +export const ipcMessagePort: IpcMessagePort = globals.vscode.ipcMessagePort; export const webFrame: WebFrame = globals.vscode.webFrame; export const crashReporter: CrashReporter = globals.vscode.crashReporter; export const process: ISandboxNodeProcess = globals.vscode.process; diff --git a/src/vs/base/parts/sandbox/test/electron-sandbox/globals.test.ts b/src/vs/base/parts/sandbox/test/electron-sandbox/globals.test.ts index ac3e65117..688422c52 100644 --- a/src/vs/base/parts/sandbox/test/electron-sandbox/globals.test.ts +++ b/src/vs/base/parts/sandbox/test/electron-sandbox/globals.test.ts @@ -4,12 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ipcRenderer, crashReporter, webFrame } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { ipcRenderer, crashReporter, webFrame, process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; suite('Sandbox', () => { test('globals', () => { - assert.ok(ipcRenderer); - assert.ok(crashReporter); - assert.ok(webFrame); + assert.ok(typeof ipcRenderer.invoke === 'function'); + assert.ok(typeof crashReporter.addExtraParameter === 'function'); + assert.ok(typeof webFrame.setZoomLevel === 'function'); + assert.ok(typeof process.platform === 'string'); }); }); diff --git a/src/vs/base/parts/storage/common/storage.ts b/src/vs/base/parts/storage/common/storage.ts index c2f5ff5a5..a2c28e170 100644 --- a/src/vs/base/parts/storage/common/storage.ts +++ b/src/vs/base/parts/storage/common/storage.ts @@ -84,11 +84,13 @@ export class Storage extends Disposable implements IStorage { private cache = new Map(); - private readonly flushDelayer = this._register(new ThrottledDelayer(Storage.DEFAULT_FLUSH_DELAY)); + private readonly flushDelayer = new ThrottledDelayer(Storage.DEFAULT_FLUSH_DELAY); private pendingDeletes = new Set(); private pendingInserts = new Map(); + private pendingClose: Promise | undefined = undefined; + private readonly whenFlushedCallbacks: Function[] = []; constructor( @@ -256,10 +258,15 @@ export class Storage extends Disposable implements IStorage { } async close(): Promise { - if (this.state === StorageState.Closed) { - return; // return if already closed + if (!this.pendingClose) { + this.pendingClose = this.doClose(); } + return this.pendingClose; + } + + private async doClose(): Promise { + // Update state this.state = StorageState.Closed; @@ -312,6 +319,13 @@ export class Storage extends Disposable implements IStorage { return new Promise(resolve => this.whenFlushedCallbacks.push(resolve)); } + + dispose(): void { + this.flushDelayer.cancel(); // workaround https://github.com/microsoft/vscode/issues/116777 + this.flushDelayer.dispose(); + + super.dispose(); + } } export class InMemoryStorageDatabase implements IStorageDatabase { diff --git a/src/vs/base/parts/storage/node/storage.ts b/src/vs/base/parts/storage/node/storage.ts index b99c08a35..e497862dd 100644 --- a/src/vs/base/parts/storage/node/storage.ts +++ b/src/vs/base/parts/storage/node/storage.ts @@ -4,11 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import type { Database, Statement } from 'vscode-sqlite3'; +import { promises } from 'fs'; import { Event } from 'vs/base/common/event'; import { timeout } from 'vs/base/common/async'; import { mapToString, setToString } from 'vs/base/common/map'; import { basename } from 'vs/base/common/path'; -import { copy, renameIgnoreError, unlink } from 'vs/base/node/pfs'; +import { copy } from 'vs/base/node/pfs'; import { IStorageDatabase, IStorageItemsChangeEvent, IUpdateRequest } from 'vs/base/parts/storage/common/storage'; interface IDatabaseConnection { @@ -186,7 +187,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 unlink(this.path).then(() => { + return promises.unlink(this.path).then(() => { // Re-open the DB fresh return this.doConnect(this.path).then(recoveryConnection => { @@ -216,7 +217,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { private backup(): Promise { const backupPath = this.toBackupPath(this.path); - return copy(this.path, backupPath); + return copy(this.path, backupPath, { preserveSymlinks: false }); } private toBackupPath(path: string): string { @@ -272,8 +273,12 @@ export class SQLiteStorageDatabase implements IStorageDatabase { // folder is really not writeable for us. // try { - await unlink(path); - await renameIgnoreError(this.toBackupPath(path), path); + await promises.unlink(path); + try { + await promises.rename(this.toBackupPath(path), path); + } catch (error) { + // ignore + } return await this.doConnect(path); } catch (error) { diff --git a/src/vs/base/parts/storage/test/node/storage.test.ts b/src/vs/base/parts/storage/test/node/storage.test.ts index ecf8e4679..990e53190 100644 --- a/src/vs/base/parts/storage/test/node/storage.test.ts +++ b/src/vs/base/parts/storage/test/node/storage.test.ts @@ -7,8 +7,9 @@ import { SQLiteStorageDatabase, ISQLiteStorageDatabaseOptions } from 'vs/base/pa import { Storage, IStorageDatabase, IStorageItemsChangeEvent } from 'vs/base/parts/storage/common/storage'; import { join } from 'vs/base/common/path'; import { tmpdir } from 'os'; +import { promises } from 'fs'; import { strictEqual, ok } from 'assert'; -import { mkdirp, writeFile, exists, unlink, rimraf } from 'vs/base/node/pfs'; +import { writeFile, exists, rimraf } from 'vs/base/node/pfs'; import { timeout } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; import { isWindows } from 'vs/base/common/platform'; @@ -22,7 +23,7 @@ flakySuite('Storage Library', function () { setup(function () { testDir = getRandomTestPath(tmpdir(), 'vsctests', 'storagelibrary'); - return mkdirp(testDir); + return promises.mkdir(testDir, { recursive: true }); }); teardown(function () { @@ -103,6 +104,7 @@ flakySuite('Storage Library', function () { strictEqual(deletePromiseResolved, true); await storage.close(); + await storage.close(); // it is ok to call this multiple times }); test('external changes', async () => { @@ -289,16 +291,16 @@ flakySuite('SQLite Storage Library', function () { return set; } - let storageDir: string; + let testdir: string; setup(function () { - storageDir = getRandomTestPath(tmpdir(), 'vsctests', 'storagelibrary'); + testdir = getRandomTestPath(tmpdir(), 'vsctests', 'storagelibrary'); - return mkdirp(storageDir); + return promises.mkdir(testdir, { recursive: true }); }); teardown(function () { - return rimraf(storageDir); + return rimraf(testdir); }); async function testDBBasics(path: string, logError?: (error: Error | string) => void) { @@ -374,16 +376,16 @@ flakySuite('SQLite Storage Library', function () { } test('basics', async () => { - await testDBBasics(join(storageDir, 'storage.db')); + await testDBBasics(join(testdir, 'storage.db')); }); test('basics (open multiple times)', async () => { - await testDBBasics(join(storageDir, 'storage.db')); - await testDBBasics(join(storageDir, 'storage.db')); + await testDBBasics(join(testdir, 'storage.db')); + await testDBBasics(join(testdir, 'storage.db')); }); test('basics (corrupt DB falls back to empty DB)', async () => { - const corruptDBPath = join(storageDir, 'broken.db'); + const corruptDBPath = join(testdir, 'broken.db'); await writeFile(corruptDBPath, 'This is a broken DB'); let expectedError: any; @@ -395,7 +397,7 @@ flakySuite('SQLite Storage Library', function () { }); test('basics (corrupt DB restores from previous backup)', async () => { - const storagePath = join(storageDir, 'storage.db'); + const storagePath = join(testdir, 'storage.db'); let storage = new SQLiteStorageDatabase(storagePath); const items = new Map(); @@ -427,7 +429,7 @@ flakySuite('SQLite Storage Library', function () { }); test('basics (corrupt DB falls back to empty DB if backup is corrupt)', async () => { - const storagePath = join(storageDir, 'storage.db'); + const storagePath = join(testdir, 'storage.db'); let storage = new SQLiteStorageDatabase(storagePath); const items = new Map(); @@ -450,7 +452,7 @@ flakySuite('SQLite Storage Library', function () { }); (isWindows ? test.skip /* Windows will fail to write to open DB due to locking */ : test)('basics (DB that becomes corrupt during runtime stores all state from cache on close)', async () => { - const storagePath = join(storageDir, 'storage.db'); + const storagePath = join(testdir, 'storage.db'); let storage = new SQLiteStorageDatabase(storagePath); const items = new Map(); @@ -475,7 +477,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 unlink(backupPath); // also test that the recovery DB is backed up properly + await promises.unlink(backupPath); // also test that the recovery DB is backed up properly let recoveryCalled = false; await storage.close(() => { @@ -506,7 +508,7 @@ flakySuite('SQLite Storage Library', function () { }); test('real world example', async function () { - let storage = new SQLiteStorageDatabase(join(storageDir, 'storage.db')); + let storage = new SQLiteStorageDatabase(join(testdir, 'storage.db')); const items1 = new Map(); items1.set('colorthemedata', '{"id":"vs vscode-theme-defaults-themes-light_plus-json","label":"Light+ (default light)","settingsId":"Default Light+","selector":"vs.vscode-theme-defaults-themes-light_plus-json","themeTokenColors":[{"settings":{"foreground":"#000000ff","background":"#ffffffff"}},{"scope":["meta.embedded","source.groovy.embedded"],"settings":{"foreground":"#000000ff"}},{"scope":"emphasis","settings":{"fontStyle":"italic"}},{"scope":"strong","settings":{"fontStyle":"bold"}},{"scope":"meta.diff.header","settings":{"foreground":"#000080"}},{"scope":"comment","settings":{"foreground":"#008000"}},{"scope":"constant.language","settings":{"foreground":"#0000ff"}},{"scope":["constant.numeric"],"settings":{"foreground":"#098658"}},{"scope":"constant.regexp","settings":{"foreground":"#811f3f"}},{"name":"css tags in selectors, xml tags","scope":"entity.name.tag","settings":{"foreground":"#800000"}},{"scope":"entity.name.selector","settings":{"foreground":"#800000"}},{"scope":"entity.other.attribute-name","settings":{"foreground":"#ff0000"}},{"scope":["entity.other.attribute-name.class.css","entity.other.attribute-name.class.mixin.css","entity.other.attribute-name.id.css","entity.other.attribute-name.parent-selector.css","entity.other.attribute-name.pseudo-class.css","entity.other.attribute-name.pseudo-element.css","source.css.less entity.other.attribute-name.id","entity.other.attribute-name.attribute.scss","entity.other.attribute-name.scss"],"settings":{"foreground":"#800000"}},{"scope":"invalid","settings":{"foreground":"#cd3131"}},{"scope":"markup.underline","settings":{"fontStyle":"underline"}},{"scope":"markup.bold","settings":{"fontStyle":"bold","foreground":"#000080"}},{"scope":"markup.heading","settings":{"fontStyle":"bold","foreground":"#800000"}},{"scope":"markup.italic","settings":{"fontStyle":"italic"}},{"scope":"markup.inserted","settings":{"foreground":"#098658"}},{"scope":"markup.deleted","settings":{"foreground":"#a31515"}},{"scope":"markup.changed","settings":{"foreground":"#0451a5"}},{"scope":["punctuation.definition.quote.begin.markdown","punctuation.definition.list.begin.markdown"],"settings":{"foreground":"#0451a5"}},{"scope":"markup.inline.raw","settings":{"foreground":"#800000"}},{"name":"brackets of XML/HTML tags","scope":"punctuation.definition.tag","settings":{"foreground":"#800000"}},{"scope":"meta.preprocessor","settings":{"foreground":"#0000ff"}},{"scope":"meta.preprocessor.string","settings":{"foreground":"#a31515"}},{"scope":"meta.preprocessor.numeric","settings":{"foreground":"#098658"}},{"scope":"meta.structure.dictionary.key.python","settings":{"foreground":"#0451a5"}},{"scope":"storage","settings":{"foreground":"#0000ff"}},{"scope":"storage.type","settings":{"foreground":"#0000ff"}},{"scope":"storage.modifier","settings":{"foreground":"#0000ff"}},{"scope":"string","settings":{"foreground":"#a31515"}},{"scope":["string.comment.buffered.block.pug","string.quoted.pug","string.interpolated.pug","string.unquoted.plain.in.yaml","string.unquoted.plain.out.yaml","string.unquoted.block.yaml","string.quoted.single.yaml","string.quoted.double.xml","string.quoted.single.xml","string.unquoted.cdata.xml","string.quoted.double.html","string.quoted.single.html","string.unquoted.html","string.quoted.single.handlebars","string.quoted.double.handlebars"],"settings":{"foreground":"#0000ff"}},{"scope":"string.regexp","settings":{"foreground":"#811f3f"}},{"name":"String interpolation","scope":["punctuation.definition.template-expression.begin","punctuation.definition.template-expression.end","punctuation.section.embedded"],"settings":{"foreground":"#0000ff"}},{"name":"Reset JavaScript string interpolation expression","scope":["meta.template.expression"],"settings":{"foreground":"#000000"}},{"scope":["support.constant.property-value","support.constant.font-name","support.constant.media-type","support.constant.media","constant.other.color.rgb-value","constant.other.rgb-value","support.constant.color"],"settings":{"foreground":"#0451a5"}},{"scope":["support.type.vendored.property-name","support.type.property-name","variable.css","variable.scss","variable.other.less","source.coffee.embedded"],"settings":{"foreground":"#ff0000"}},{"scope":["support.type.property-name.json"],"settings":{"foreground":"#0451a5"}},{"scope":"keyword","settings":{"foreground":"#0000ff"}},{"scope":"keyword.control","settings":{"foreground":"#0000ff"}},{"scope":"keyword.operator","settings":{"foreground":"#000000"}},{"scope":["keyword.operator.new","keyword.operator.expression","keyword.operator.cast","keyword.operator.sizeof","keyword.operator.instanceof","keyword.operator.logical.python"],"settings":{"foreground":"#0000ff"}},{"scope":"keyword.other.unit","settings":{"foreground":"#098658"}},{"scope":["punctuation.section.embedded.begin.php","punctuation.section.embedded.end.php"],"settings":{"foreground":"#800000"}},{"scope":"support.function.git-rebase","settings":{"foreground":"#0451a5"}},{"scope":"constant.sha.git-rebase","settings":{"foreground":"#098658"}},{"name":"coloring of the Java import and package identifiers","scope":["storage.modifier.import.java","variable.language.wildcard.java","storage.modifier.package.java"],"settings":{"foreground":"#000000"}},{"name":"this.self","scope":"variable.language","settings":{"foreground":"#0000ff"}},{"name":"Function declarations","scope":["entity.name.function","support.function","support.constant.handlebars"],"settings":{"foreground":"#795E26"}},{"name":"Types declaration and references","scope":["meta.return-type","support.class","support.type","entity.name.type","entity.name.class","storage.type.numeric.go","storage.type.byte.go","storage.type.boolean.go","storage.type.string.go","storage.type.uintptr.go","storage.type.error.go","storage.type.rune.go","storage.type.cs","storage.type.generic.cs","storage.type.modifier.cs","storage.type.variable.cs","storage.type.annotation.java","storage.type.generic.java","storage.type.java","storage.type.object.array.java","storage.type.primitive.array.java","storage.type.primitive.java","storage.type.token.java","storage.type.groovy","storage.type.annotation.groovy","storage.type.parameters.groovy","storage.type.generic.groovy","storage.type.object.array.groovy","storage.type.primitive.array.groovy","storage.type.primitive.groovy"],"settings":{"foreground":"#267f99"}},{"name":"Types declaration and references, TS grammar specific","scope":["meta.type.cast.expr","meta.type.new.expr","support.constant.math","support.constant.dom","support.constant.json","entity.other.inherited-class"],"settings":{"foreground":"#267f99"}},{"name":"Control flow keywords","scope":"keyword.control","settings":{"foreground":"#AF00DB"}},{"name":"Variable and parameter name","scope":["variable","meta.definition.variable.name","support.variable","entity.name.variable"],"settings":{"foreground":"#001080"}},{"name":"Object keys, TS grammar specific","scope":["meta.object-literal.key"],"settings":{"foreground":"#001080"}},{"name":"CSS property value","scope":["support.constant.property-value","support.constant.font-name","support.constant.media-type","support.constant.media","constant.other.color.rgb-value","constant.other.rgb-value","support.constant.color"],"settings":{"foreground":"#0451a5"}},{"name":"Regular expression groups","scope":["punctuation.definition.group.regexp","punctuation.definition.group.assertion.regexp","punctuation.definition.character-class.regexp","punctuation.character.set.begin.regexp","punctuation.character.set.end.regexp","keyword.operator.negation.regexp","support.other.parenthesis.regexp"],"settings":{"foreground":"#d16969"}},{"scope":["constant.character.character-class.regexp","constant.other.character-class.set.regexp","constant.other.character-class.regexp","constant.character.set.regexp"],"settings":{"foreground":"#811f3f"}},{"scope":"keyword.operator.quantifier.regexp","settings":{"foreground":"#000000"}},{"scope":["keyword.operator.or.regexp","keyword.control.anchor.regexp"],"settings":{"foreground":"#ff0000"}},{"scope":"constant.character","settings":{"foreground":"#0000ff"}},{"scope":"constant.character.escape","settings":{"foreground":"#ff0000"}},{"scope":"token.info-token","settings":{"foreground":"#316bcd"}},{"scope":"token.warn-token","settings":{"foreground":"#cd9731"}},{"scope":"token.error-token","settings":{"foreground":"#cd3131"}},{"scope":"token.debug-token","settings":{"foreground":"#800080"}}],"extensionData":{"extensionId":"vscode.theme-defaults","extensionPublisher":"vscode","extensionName":"theme-defaults","extensionIsBuiltin":true},"colorMap":{"editor.background":"#ffffff","editor.foreground":"#000000","editor.inactiveSelectionBackground":"#e5ebf1","editorIndentGuide.background":"#d3d3d3","editorIndentGuide.activeBackground":"#939393","editor.selectionHighlightBackground":"#add6ff4d","editorSuggestWidget.background":"#f3f3f3","activityBarBadge.background":"#007acc","sideBarTitle.foreground":"#6f6f6f","list.hoverBackground":"#e8e8e8","input.placeholderForeground":"#767676","settings.textInputBorder":"#cecece","settings.numberInputBorder":"#cecece"}}'); @@ -580,7 +582,7 @@ flakySuite('SQLite Storage Library', function () { await storage.close(); - storage = new SQLiteStorageDatabase(join(storageDir, 'storage.db')); + storage = new SQLiteStorageDatabase(join(testdir, 'storage.db')); storedItems = await storage.getItems(); strictEqual(storedItems.size, items1.size + items2.size + items3.size); @@ -589,7 +591,7 @@ flakySuite('SQLite Storage Library', function () { }); test('very large item value', async function () { - let storage = new SQLiteStorageDatabase(join(storageDir, 'storage.db')); + let storage = new SQLiteStorageDatabase(join(testdir, 'storage.db')); const items = new Map(); items.set('colorthemedata', '{"id":"vs vscode-theme-defaults-themes-light_plus-json","label":"Light+ (default light)","settingsId":"Default Light+","selector":"vs.vscode-theme-defaults-themes-light_plus-json","themeTokenColors":[{"settings":{"foreground":"#000000ff","background":"#ffffffff"}},{"scope":["meta.embedded","source.groovy.embedded"],"settings":{"foreground":"#000000ff"}},{"scope":"emphasis","settings":{"fontStyle":"italic"}},{"scope":"strong","settings":{"fontStyle":"bold"}},{"scope":"meta.diff.header","settings":{"foreground":"#000080"}},{"scope":"comment","settings":{"foreground":"#008000"}},{"scope":"constant.language","settings":{"foreground":"#0000ff"}},{"scope":["constant.numeric"],"settings":{"foreground":"#098658"}},{"scope":"constant.regexp","settings":{"foreground":"#811f3f"}},{"name":"css tags in selectors, xml tags","scope":"entity.name.tag","settings":{"foreground":"#800000"}},{"scope":"entity.name.selector","settings":{"foreground":"#800000"}},{"scope":"entity.other.attribute-name","settings":{"foreground":"#ff0000"}},{"scope":["entity.other.attribute-name.class.css","entity.other.attribute-name.class.mixin.css","entity.other.attribute-name.id.css","entity.other.attribute-name.parent-selector.css","entity.other.attribute-name.pseudo-class.css","entity.other.attribute-name.pseudo-element.css","source.css.less entity.other.attribute-name.id","entity.other.attribute-name.attribute.scss","entity.other.attribute-name.scss"],"settings":{"foreground":"#800000"}},{"scope":"invalid","settings":{"foreground":"#cd3131"}},{"scope":"markup.underline","settings":{"fontStyle":"underline"}},{"scope":"markup.bold","settings":{"fontStyle":"bold","foreground":"#000080"}},{"scope":"markup.heading","settings":{"fontStyle":"bold","foreground":"#800000"}},{"scope":"markup.italic","settings":{"fontStyle":"italic"}},{"scope":"markup.inserted","settings":{"foreground":"#098658"}},{"scope":"markup.deleted","settings":{"foreground":"#a31515"}},{"scope":"markup.changed","settings":{"foreground":"#0451a5"}},{"scope":["punctuation.definition.quote.begin.markdown","punctuation.definition.list.begin.markdown"],"settings":{"foreground":"#0451a5"}},{"scope":"markup.inline.raw","settings":{"foreground":"#800000"}},{"name":"brackets of XML/HTML tags","scope":"punctuation.definition.tag","settings":{"foreground":"#800000"}},{"scope":"meta.preprocessor","settings":{"foreground":"#0000ff"}},{"scope":"meta.preprocessor.string","settings":{"foreground":"#a31515"}},{"scope":"meta.preprocessor.numeric","settings":{"foreground":"#098658"}},{"scope":"meta.structure.dictionary.key.python","settings":{"foreground":"#0451a5"}},{"scope":"storage","settings":{"foreground":"#0000ff"}},{"scope":"storage.type","settings":{"foreground":"#0000ff"}},{"scope":"storage.modifier","settings":{"foreground":"#0000ff"}},{"scope":"string","settings":{"foreground":"#a31515"}},{"scope":["string.comment.buffered.block.pug","string.quoted.pug","string.interpolated.pug","string.unquoted.plain.in.yaml","string.unquoted.plain.out.yaml","string.unquoted.block.yaml","string.quoted.single.yaml","string.quoted.double.xml","string.quoted.single.xml","string.unquoted.cdata.xml","string.quoted.double.html","string.quoted.single.html","string.unquoted.html","string.quoted.single.handlebars","string.quoted.double.handlebars"],"settings":{"foreground":"#0000ff"}},{"scope":"string.regexp","settings":{"foreground":"#811f3f"}},{"name":"String interpolation","scope":["punctuation.definition.template-expression.begin","punctuation.definition.template-expression.end","punctuation.section.embedded"],"settings":{"foreground":"#0000ff"}},{"name":"Reset JavaScript string interpolation expression","scope":["meta.template.expression"],"settings":{"foreground":"#000000"}},{"scope":["support.constant.property-value","support.constant.font-name","support.constant.media-type","support.constant.media","constant.other.color.rgb-value","constant.other.rgb-value","support.constant.color"],"settings":{"foreground":"#0451a5"}},{"scope":["support.type.vendored.property-name","support.type.property-name","variable.css","variable.scss","variable.other.less","source.coffee.embedded"],"settings":{"foreground":"#ff0000"}},{"scope":["support.type.property-name.json"],"settings":{"foreground":"#0451a5"}},{"scope":"keyword","settings":{"foreground":"#0000ff"}},{"scope":"keyword.control","settings":{"foreground":"#0000ff"}},{"scope":"keyword.operator","settings":{"foreground":"#000000"}},{"scope":["keyword.operator.new","keyword.operator.expression","keyword.operator.cast","keyword.operator.sizeof","keyword.operator.instanceof","keyword.operator.logical.python"],"settings":{"foreground":"#0000ff"}},{"scope":"keyword.other.unit","settings":{"foreground":"#098658"}},{"scope":["punctuation.section.embedded.begin.php","punctuation.section.embedded.end.php"],"settings":{"foreground":"#800000"}},{"scope":"support.function.git-rebase","settings":{"foreground":"#0451a5"}},{"scope":"constant.sha.git-rebase","settings":{"foreground":"#098658"}},{"name":"coloring of the Java import and package identifiers","scope":["storage.modifier.import.java","variable.language.wildcard.java","storage.modifier.package.java"],"settings":{"foreground":"#000000"}},{"name":"this.self","scope":"variable.language","settings":{"foreground":"#0000ff"}},{"name":"Function declarations","scope":["entity.name.function","support.function","support.constant.handlebars"],"settings":{"foreground":"#795E26"}},{"name":"Types declaration and references","scope":["meta.return-type","support.class","support.type","entity.name.type","entity.name.class","storage.type.numeric.go","storage.type.byte.go","storage.type.boolean.go","storage.type.string.go","storage.type.uintptr.go","storage.type.error.go","storage.type.rune.go","storage.type.cs","storage.type.generic.cs","storage.type.modifier.cs","storage.type.variable.cs","storage.type.annotation.java","storage.type.generic.java","storage.type.java","storage.type.object.array.java","storage.type.primitive.array.java","storage.type.primitive.java","storage.type.token.java","storage.type.groovy","storage.type.annotation.groovy","storage.type.parameters.groovy","storage.type.generic.groovy","storage.type.object.array.groovy","storage.type.primitive.array.groovy","storage.type.primitive.groovy"],"settings":{"foreground":"#267f99"}},{"name":"Types declaration and references, TS grammar specific","scope":["meta.type.cast.expr","meta.type.new.expr","support.constant.math","support.constant.dom","support.constant.json","entity.other.inherited-class"],"settings":{"foreground":"#267f99"}},{"name":"Control flow keywords","scope":"keyword.control","settings":{"foreground":"#AF00DB"}},{"name":"Variable and parameter name","scope":["variable","meta.definition.variable.name","support.variable","entity.name.variable"],"settings":{"foreground":"#001080"}},{"name":"Object keys, TS grammar specific","scope":["meta.object-literal.key"],"settings":{"foreground":"#001080"}},{"name":"CSS property value","scope":["support.constant.property-value","support.constant.font-name","support.constant.media-type","support.constant.media","constant.other.color.rgb-value","constant.other.rgb-value","support.constant.color"],"settings":{"foreground":"#0451a5"}},{"name":"Regular expression groups","scope":["punctuation.definition.group.regexp","punctuation.definition.group.assertion.regexp","punctuation.definition.character-class.regexp","punctuation.character.set.begin.regexp","punctuation.character.set.end.regexp","keyword.operator.negation.regexp","support.other.parenthesis.regexp"],"settings":{"foreground":"#d16969"}},{"scope":["constant.character.character-class.regexp","constant.other.character-class.set.regexp","constant.other.character-class.regexp","constant.character.set.regexp"],"settings":{"foreground":"#811f3f"}},{"scope":"keyword.operator.quantifier.regexp","settings":{"foreground":"#000000"}},{"scope":["keyword.operator.or.regexp","keyword.control.anchor.regexp"],"settings":{"foreground":"#ff0000"}},{"scope":"constant.character","settings":{"foreground":"#0000ff"}},{"scope":"constant.character.escape","settings":{"foreground":"#ff0000"}},{"scope":"token.info-token","settings":{"foreground":"#316bcd"}},{"scope":"token.warn-token","settings":{"foreground":"#cd9731"}},{"scope":"token.error-token","settings":{"foreground":"#cd3131"}},{"scope":"token.debug-token","settings":{"foreground":"#800080"}}],"extensionData":{"extensionId":"vscode.theme-defaults","extensionPublisher":"vscode","extensionName":"theme-defaults","extensionIsBuiltin":true},"colorMap":{"editor.background":"#ffffff","editor.foreground":"#000000","editor.inactiveSelectionBackground":"#e5ebf1","editorIndentGuide.background":"#d3d3d3","editorIndentGuide.activeBackground":"#939393","editor.selectionHighlightBackground":"#add6ff4d","editorSuggestWidget.background":"#f3f3f3","activityBarBadge.background":"#007acc","sideBarTitle.foreground":"#6f6f6f","list.hoverBackground":"#e8e8e8","input.placeholderForeground":"#767676","settings.textInputBorder":"#cecece","settings.numberInputBorder":"#cecece"}}'); @@ -643,7 +645,7 @@ flakySuite('SQLite Storage Library', function () { } } - const storage = new TestStorage(new SQLiteStorageDatabase(join(storageDir, 'storage.db'))); + const storage = new TestStorage(new SQLiteStorageDatabase(join(testdir, 'storage.db'))); await storage.init(); @@ -689,7 +691,7 @@ flakySuite('SQLite Storage Library', function () { }); test('lots of INSERT & DELETE (below inline max)', async () => { - const storage = new SQLiteStorageDatabase(join(storageDir, 'storage.db')); + const storage = new SQLiteStorageDatabase(join(testdir, 'storage.db')); const items = new Map(); const keys: Set = new Set(); @@ -715,7 +717,7 @@ flakySuite('SQLite Storage Library', function () { }); test('lots of INSERT & DELETE (above inline max)', async () => { - const storage = new SQLiteStorageDatabase(join(storageDir, 'storage.db')); + const storage = new SQLiteStorageDatabase(join(testdir, 'storage.db')); const items = new Map(); const keys: Set = new Set(); diff --git a/src/vs/base/test/browser/dom.test.ts b/src/vs/base/test/browser/dom.test.ts index 12a7c3ca7..a99cd9a5e 100644 --- a/src/vs/base/test/browser/dom.test.ts +++ b/src/vs/base/test/browser/dom.test.ts @@ -30,7 +30,7 @@ suite('dom', () => { assert(element.classList.contains('far')); assert(!element.classList.contains('boo')); assert(element.classList.contains('foobar')); - assert.equal(element.className, 'foobar far'); + assert.strictEqual(element.className, 'foobar far'); element = document.createElement('div'); element.className = 'foobar boo far'; @@ -39,19 +39,19 @@ suite('dom', () => { assert(!element.classList.contains('far')); assert(element.classList.contains('boo')); assert(element.classList.contains('foobar')); - assert.equal(element.className, 'foobar boo'); + assert.strictEqual(element.className, 'foobar boo'); element.classList.remove('boo'); assert(!element.classList.contains('far')); assert(!element.classList.contains('boo')); assert(element.classList.contains('foobar')); - assert.equal(element.className, 'foobar'); + assert.strictEqual(element.className, 'foobar'); element.classList.remove('foobar'); assert(!element.classList.contains('far')); assert(!element.classList.contains('boo')); assert(!element.classList.contains('foobar')); - assert.equal(element.className, ''); + assert.strictEqual(element.className, ''); }); test('removeClass should consider hyphens', function () { @@ -83,7 +83,7 @@ suite('dom', () => { const div = $('div'); assert(div); assert(div instanceof HTMLElement); - assert.equal(div.tagName, 'DIV'); + assert.strictEqual(div.tagName, 'DIV'); assert(!div.firstChild); }); @@ -91,42 +91,42 @@ suite('dom', () => { const div = $('div#foo'); assert(div); assert(div instanceof HTMLElement); - assert.equal(div.tagName, 'DIV'); - assert.equal(div.id, 'foo'); + assert.strictEqual(div.tagName, 'DIV'); + assert.strictEqual(div.id, 'foo'); }); test('should buld nodes with class-name', () => { const div = $('div.foo'); assert(div); assert(div instanceof HTMLElement); - assert.equal(div.tagName, 'DIV'); - assert.equal(div.className, 'foo'); + assert.strictEqual(div.tagName, 'DIV'); + assert.strictEqual(div.className, 'foo'); }); test('should build nodes with attributes', () => { let div = $('div', { class: 'test' }); - assert.equal(div.className, 'test'); + assert.strictEqual(div.className, 'test'); div = $('div', undefined); - assert.equal(div.className, ''); + assert.strictEqual(div.className, ''); }); test('should build nodes with children', () => { let div = $('div', undefined, $('span', { id: 'demospan' })); let firstChild = div.firstChild as HTMLElement; - assert.equal(firstChild.tagName, 'SPAN'); - assert.equal(firstChild.id, 'demospan'); + assert.strictEqual(firstChild.tagName, 'SPAN'); + assert.strictEqual(firstChild.id, 'demospan'); div = $('div', undefined, 'hello'); - assert.equal(div.firstChild && div.firstChild.textContent, 'hello'); + assert.strictEqual(div.firstChild && div.firstChild.textContent, 'hello'); }); test('should build nodes with text children', () => { let div = $('div', undefined, 'foobar'); let firstChild = div.firstChild as HTMLElement; - assert.equal(firstChild.tagName, undefined); - assert.equal(firstChild.textContent, 'foobar'); + assert.strictEqual(firstChild.tagName, undefined); + assert.strictEqual(firstChild.textContent, 'foobar'); }); }); }); diff --git a/src/vs/base/test/browser/hash.test.ts b/src/vs/base/test/browser/hash.test.ts index 68ee17eeb..c122b4b3d 100644 --- a/src/vs/base/test/browser/hash.test.ts +++ b/src/vs/base/test/browser/hash.test.ts @@ -9,53 +9,53 @@ import { sha1Hex } from 'vs/base/browser/hash'; suite('Hash', () => { test('string', () => { - assert.equal(hash('hello'), hash('hello')); - assert.notEqual(hash('hello'), hash('world')); - assert.notEqual(hash('hello'), hash('olleh')); - assert.notEqual(hash('hello'), hash('Hello')); - assert.notEqual(hash('hello'), hash('Hello ')); - assert.notEqual(hash('h'), hash('H')); - assert.notEqual(hash('-'), hash('_')); + assert.strictEqual(hash('hello'), hash('hello')); + assert.notStrictEqual(hash('hello'), hash('world')); + assert.notStrictEqual(hash('hello'), hash('olleh')); + assert.notStrictEqual(hash('hello'), hash('Hello')); + assert.notStrictEqual(hash('hello'), hash('Hello ')); + assert.notStrictEqual(hash('h'), hash('H')); + assert.notStrictEqual(hash('-'), hash('_')); }); test('number', () => { - assert.equal(hash(1), hash(1)); - assert.notEqual(hash(0), hash(1)); - assert.notEqual(hash(1), hash(-1)); - assert.notEqual(hash(0x12345678), hash(0x123456789)); + assert.strictEqual(hash(1), hash(1)); + assert.notStrictEqual(hash(0), hash(1)); + assert.notStrictEqual(hash(1), hash(-1)); + assert.notStrictEqual(hash(0x12345678), hash(0x123456789)); }); test('boolean', () => { - assert.equal(hash(true), hash(true)); - assert.notEqual(hash(true), hash(false)); + assert.strictEqual(hash(true), hash(true)); + assert.notStrictEqual(hash(true), hash(false)); }); test('array', () => { - assert.equal(hash([1, 2, 3]), hash([1, 2, 3])); - assert.equal(hash(['foo', 'bar']), hash(['foo', 'bar'])); - assert.equal(hash([]), hash([])); - assert.equal(hash([]), hash(new Array())); - assert.notEqual(hash(['foo', 'bar']), hash(['bar', 'foo'])); - assert.notEqual(hash(['foo', 'bar']), hash(['bar', 'foo', null])); - assert.notEqual(hash(['foo', 'bar', null]), hash(['bar', 'foo', null])); - assert.notEqual(hash(['foo', 'bar']), hash(['bar', 'foo', undefined])); - assert.notEqual(hash(['foo', 'bar', undefined]), hash(['bar', 'foo', undefined])); - assert.notEqual(hash(['foo', 'bar', null]), hash(['foo', 'bar', undefined])); + assert.strictEqual(hash([1, 2, 3]), hash([1, 2, 3])); + assert.strictEqual(hash(['foo', 'bar']), hash(['foo', 'bar'])); + assert.strictEqual(hash([]), hash([])); + assert.strictEqual(hash([]), hash(new Array())); + assert.notStrictEqual(hash(['foo', 'bar']), hash(['bar', 'foo'])); + assert.notStrictEqual(hash(['foo', 'bar']), hash(['bar', 'foo', null])); + assert.notStrictEqual(hash(['foo', 'bar', null]), hash(['bar', 'foo', null])); + assert.notStrictEqual(hash(['foo', 'bar']), hash(['bar', 'foo', undefined])); + assert.notStrictEqual(hash(['foo', 'bar', undefined]), hash(['bar', 'foo', undefined])); + assert.notStrictEqual(hash(['foo', 'bar', null]), hash(['foo', 'bar', undefined])); }); test('object', () => { - assert.equal(hash({}), hash({})); - assert.equal(hash({}), hash(Object.create(null))); - assert.equal(hash({ 'foo': 'bar' }), hash({ 'foo': 'bar' })); - assert.equal(hash({ 'foo': 'bar', 'foo2': undefined }), hash({ 'foo2': undefined, 'foo': 'bar' })); - assert.notEqual(hash({ 'foo': 'bar' }), hash({ 'foo': 'bar2' })); - assert.notEqual(hash({}), hash([])); + assert.strictEqual(hash({}), hash({})); + assert.strictEqual(hash({}), hash(Object.create(null))); + assert.strictEqual(hash({ 'foo': 'bar' }), hash({ 'foo': 'bar' })); + assert.strictEqual(hash({ 'foo': 'bar', 'foo2': undefined }), hash({ 'foo2': undefined, 'foo': 'bar' })); + assert.notStrictEqual(hash({ 'foo': 'bar' }), hash({ 'foo': 'bar2' })); + assert.notStrictEqual(hash({}), hash([])); }); test('array - unexpected collision', function () { const a = hash([undefined, undefined, undefined, undefined, undefined]); const b = hash([undefined, undefined, 'HHHHHH', [{ line: 0, character: 0 }, { line: 0, character: 0 }], undefined]); - assert.notEqual(a, b); + assert.notStrictEqual(a, b); }); test('all different', () => { @@ -65,9 +65,9 @@ suite('Hash', () => { ]; const hashes: number[] = candidates.map(hash); for (let i = 0; i < hashes.length; i++) { - assert.equal(hashes[i], hash(candidates[i])); // verify that repeated invocation returns the same hash + assert.strictEqual(hashes[i], hash(candidates[i])); // verify that repeated invocation returns the same hash for (let k = i + 1; k < hashes.length; k++) { - assert.notEqual(hashes[i], hashes[k], `Same hash ${hashes[i]} for ${JSON.stringify(candidates[i])} and ${JSON.stringify(candidates[k])}`); + assert.notStrictEqual(hashes[i], hashes[k], `Same hash ${hashes[i]} for ${JSON.stringify(candidates[i])} and ${JSON.stringify(candidates[k])}`); } } }); @@ -79,11 +79,11 @@ suite('Hash', () => { const hash = new StringSHA1(); hash.update(str); let actual = hash.digest(); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); // Test with crypto.subtle actual = await sha1Hex(str); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); } test('sha1-1', () => { diff --git a/src/vs/base/test/browser/highlightedLabel.test.ts b/src/vs/base/test/browser/highlightedLabel.test.ts index c658c835c..b29c1b469 100644 --- a/src/vs/base/test/browser/highlightedLabel.test.ts +++ b/src/vs/base/test/browser/highlightedLabel.test.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 assert from 'assert'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; @@ -13,50 +14,50 @@ suite('HighlightedLabel', () => { }); test('empty label', function () { - assert.equal(label.element.innerHTML, ''); + assert.strictEqual(label.element.innerHTML, ''); }); test('no decorations', function () { label.set('hello'); - assert.equal(label.element.innerHTML, 'hello'); + assert.strictEqual(label.element.innerHTML, 'hello'); }); test('escape html', function () { label.set('helhel<lo'); + assert.strictEqual(label.element.innerHTML, 'hel<lo'); }); test('everything highlighted', function () { label.set('hello', [{ start: 0, end: 5 }]); - assert.equal(label.element.innerHTML, 'hello'); + assert.strictEqual(label.element.innerHTML, 'hello'); }); test('beginning highlighted', function () { label.set('hellothere', [{ start: 0, end: 5 }]); - assert.equal(label.element.innerHTML, 'hellothere'); + assert.strictEqual(label.element.innerHTML, 'hellothere'); }); test('ending highlighted', function () { label.set('goodbye', [{ start: 4, end: 7 }]); - assert.equal(label.element.innerHTML, 'goodbye'); + assert.strictEqual(label.element.innerHTML, 'goodbye'); }); test('middle highlighted', function () { label.set('foobarfoo', [{ start: 3, end: 6 }]); - assert.equal(label.element.innerHTML, 'foobarfoo'); + assert.strictEqual(label.element.innerHTML, 'foobarfoo'); }); test('escapeNewLines', () => { let highlights = [{ start: 0, end: 5 }, { start: 7, end: 9 }, { start: 11, end: 12 }];// before,after,after let escaped = HighlightedLabel.escapeNewLines('ACTION\r\n_TYPE2', highlights); - assert.equal(escaped, 'ACTION\u23CE_TYPE2'); - assert.deepEqual(highlights, [{ start: 0, end: 5 }, { start: 6, end: 8 }, { start: 10, end: 11 }]); + assert.strictEqual(escaped, 'ACTION\u23CE_TYPE2'); + assert.deepStrictEqual(highlights, [{ start: 0, end: 5 }, { start: 6, end: 8 }, { start: 10, end: 11 }]); highlights = [{ start: 5, end: 9 }, { start: 11, end: 12 }];//overlap,after escaped = HighlightedLabel.escapeNewLines('ACTION\r\n_TYPE2', highlights); - assert.equal(escaped, 'ACTION\u23CE_TYPE2'); - assert.deepEqual(highlights, [{ start: 5, end: 8 }, { start: 10, end: 11 }]); + assert.strictEqual(escaped, 'ACTION\u23CE_TYPE2'); + assert.deepStrictEqual(highlights, [{ start: 5, end: 8 }, { start: 10, end: 11 }]); }); }); diff --git a/src/vs/base/test/browser/iconLabels.test.ts b/src/vs/base/test/browser/iconLabels.test.ts index db81e37a5..292ec6b0b 100644 --- a/src/vs/base/test/browser/iconLabels.test.ts +++ b/src/vs/base/test/browser/iconLabels.test.ts @@ -3,45 +3,45 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import * as assert from 'assert'; +import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; suite('renderLabelWithIcons', () => { test('no icons', () => { const result = renderLabelWithIcons(' hello World .'); - assert.equal(elementsToString(result), ' hello World .'); + assert.strictEqual(elementsToString(result), ' hello World .'); }); test('icons only', () => { const result = renderLabelWithIcons('$(alert)'); - assert.equal(elementsToString(result), ''); + assert.strictEqual(elementsToString(result), ''); }); test('icon and non-icon strings', () => { const result = renderLabelWithIcons(` $(alert) Unresponsive`); - assert.equal(elementsToString(result), ' Unresponsive'); + assert.strictEqual(elementsToString(result), ' Unresponsive'); }); test('multiple icons', () => { const result = renderLabelWithIcons('$(check)$(error)'); - assert.equal(elementsToString(result), ''); + assert.strictEqual(elementsToString(result), ''); }); test('escaped icons', () => { const result = renderLabelWithIcons('\\$(escaped)'); - assert.equal(elementsToString(result), '$(escaped)'); + assert.strictEqual(elementsToString(result), '$(escaped)'); }); test('icon with animation', () => { const result = renderLabelWithIcons('$(zip~anim)'); - assert.equal(elementsToString(result), ''); + assert.strictEqual(elementsToString(result), ''); }); const elementsToString = (elements: Array): string => { diff --git a/src/vs/base/test/browser/ui/contextview/contextview.test.ts b/src/vs/base/test/browser/ui/contextview/contextview.test.ts index 01cb75d8e..4b49df185 100644 --- a/src/vs/base/test/browser/ui/contextview/contextview.test.ts +++ b/src/vs/base/test/browser/ui/contextview/contextview.test.ts @@ -9,20 +9,20 @@ import { layout, LayoutAnchorPosition } from 'vs/base/browser/ui/contextview/con suite('Contextview', function () { test('layout', () => { - assert.equal(layout(200, 20, { offset: 0, size: 0, position: LayoutAnchorPosition.Before }), 0); - assert.equal(layout(200, 20, { offset: 50, size: 0, position: LayoutAnchorPosition.Before }), 50); - assert.equal(layout(200, 20, { offset: 200, size: 0, position: LayoutAnchorPosition.Before }), 180); + assert.strictEqual(layout(200, 20, { offset: 0, size: 0, position: LayoutAnchorPosition.Before }), 0); + assert.strictEqual(layout(200, 20, { offset: 50, size: 0, position: LayoutAnchorPosition.Before }), 50); + assert.strictEqual(layout(200, 20, { offset: 200, size: 0, position: LayoutAnchorPosition.Before }), 180); - assert.equal(layout(200, 20, { offset: 0, size: 0, position: LayoutAnchorPosition.After }), 0); - assert.equal(layout(200, 20, { offset: 50, size: 0, position: LayoutAnchorPosition.After }), 30); - assert.equal(layout(200, 20, { offset: 200, size: 0, position: LayoutAnchorPosition.After }), 180); + assert.strictEqual(layout(200, 20, { offset: 0, size: 0, position: LayoutAnchorPosition.After }), 0); + assert.strictEqual(layout(200, 20, { offset: 50, size: 0, position: LayoutAnchorPosition.After }), 30); + assert.strictEqual(layout(200, 20, { offset: 200, size: 0, position: LayoutAnchorPosition.After }), 180); - assert.equal(layout(200, 20, { offset: 0, size: 50, position: LayoutAnchorPosition.Before }), 50); - assert.equal(layout(200, 20, { offset: 50, size: 50, position: LayoutAnchorPosition.Before }), 100); - assert.equal(layout(200, 20, { offset: 150, size: 50, position: LayoutAnchorPosition.Before }), 130); + assert.strictEqual(layout(200, 20, { offset: 0, size: 50, position: LayoutAnchorPosition.Before }), 50); + assert.strictEqual(layout(200, 20, { offset: 50, size: 50, position: LayoutAnchorPosition.Before }), 100); + assert.strictEqual(layout(200, 20, { offset: 150, size: 50, position: LayoutAnchorPosition.Before }), 130); - assert.equal(layout(200, 20, { offset: 0, size: 50, position: LayoutAnchorPosition.After }), 50); - assert.equal(layout(200, 20, { offset: 50, size: 50, position: LayoutAnchorPosition.After }), 30); - assert.equal(layout(200, 20, { offset: 150, size: 50, position: LayoutAnchorPosition.After }), 130); + assert.strictEqual(layout(200, 20, { offset: 0, size: 50, position: LayoutAnchorPosition.After }), 50); + assert.strictEqual(layout(200, 20, { offset: 50, size: 50, position: LayoutAnchorPosition.After }), 30); + assert.strictEqual(layout(200, 20, { offset: 150, size: 50, position: LayoutAnchorPosition.After }), 130); }); }); diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index c92df5cd1..83b2eb05a 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -807,4 +807,187 @@ suite('Async', () => { assert.strictEqual(deferred.isRejected, true); }); }); + + suite('Promises.allSettled', () => { + test('resolves', async () => { + const p1 = Promise.resolve(1); + const p2 = async.timeout(1).then(() => 2); + const p3 = async.timeout(2).then(() => 3); + + const result = await async.Promises.allSettled([p1, p2, p3]); + + assert.strictEqual(result.length, 3); + assert.deepStrictEqual(result[0], { status: 'fulfilled', value: 1 }); + assert.deepStrictEqual(result[1], { status: 'fulfilled', value: 2 }); + assert.deepStrictEqual(result[2], { status: 'fulfilled', value: 3 }); + }); + + test('resolves in order', async () => { + const p1 = async.timeout(2).then(() => 1); + const p2 = async.timeout(1).then(() => 2); + const p3 = Promise.resolve(3); + + const result = await async.Promises.allSettled([p1, p2, p3]); + + assert.strictEqual(result.length, 3); + assert.deepStrictEqual(result[0], { status: 'fulfilled', value: 1 }); + assert.deepStrictEqual(result[1], { status: 'fulfilled', value: 2 }); + assert.deepStrictEqual(result[2], { status: 'fulfilled', value: 3 }); + }); + + test('rejects', async () => { + const p1 = Promise.reject(1); + + const p2Error = new Error('2'); + const p2 = async.timeout(1).then(() => { throw p2Error; }); + + const p3Error = new Error('3'); + const p3 = async.timeout(2).then(() => { throw p3Error; }); + + const result = await async.Promises.allSettled([p1, p2, p3]); + + assert.strictEqual(result.length, 3); + assert.deepStrictEqual(result[0], { status: 'rejected', reason: 1 }); + assert.deepStrictEqual(result[1], { status: 'rejected', reason: p2Error }); + assert.deepStrictEqual(result[2], { status: 'rejected', reason: p3Error }); + }); + + test('rejects in order', async () => { + const p1Error = new Error('1'); + const p1 = async.timeout(2).then(() => { throw p1Error; }); + + const p2Error = new Error('2'); + const p2 = async.timeout(1).then(() => { throw p2Error; }); + + const p3 = Promise.reject(3); + + const result = await async.Promises.allSettled([p1, p2, p3]); + + assert.strictEqual(result.length, 3); + assert.deepStrictEqual(result[0], { status: 'rejected', reason: p1Error }); + assert.deepStrictEqual(result[1], { status: 'rejected', reason: p2Error }); + assert.deepStrictEqual(result[2], { status: 'rejected', reason: 3 }); + }); + + test('resolves & rejects', async () => { + const p1 = Promise.resolve(1); + const p2Error = new Error('2'); + const p2 = async.timeout(1).then(() => { throw p2Error; }); + const p3 = async.timeout(2).then(() => 3); + + const result = await async.Promises.allSettled([p1, p2, p3]); + + assert.strictEqual(result.length, 3); + assert.deepStrictEqual(result[0], { status: 'fulfilled', value: 1 }); + assert.deepStrictEqual(result[1], { status: 'rejected', reason: p2Error }); + assert.deepStrictEqual(result[2], { status: 'fulfilled', value: 3 }); + }); + + test('resolves & rejects in order', async () => { + const p1Error = new Error('2'); + const p1 = async.timeout(1).then(() => { throw p1Error; }); + const p2 = async.timeout(2).then(() => 2); + const p3 = Promise.resolve(3); + + const result = await async.Promises.allSettled([p1, p2, p3]); + + assert.strictEqual(result.length, 3); + assert.deepStrictEqual(result[0], { status: 'rejected', reason: p1Error }); + assert.deepStrictEqual(result[1], { status: 'fulfilled', value: 2 }); + assert.deepStrictEqual(result[2], { status: 'fulfilled', value: 3 }); + }); + + test('can empty', async () => { + const result = await async.Promises.allSettled([]); + + assert.strictEqual(result.length, 0); + }); + }); + + suite('Promises.settled', () => { + test('resolves', async () => { + const p1 = Promise.resolve(1); + const p2 = async.timeout(1).then(() => 2); + const p3 = async.timeout(2).then(() => 3); + + const result = await async.Promises.settled([p1, p2, p3]); + + assert.strictEqual(result.length, 3); + assert.deepStrictEqual(result[0], 1); + assert.deepStrictEqual(result[1], 2); + assert.deepStrictEqual(result[2], 3); + }); + + test('resolves in order', async () => { + const p1 = async.timeout(2).then(() => 1); + const p2 = async.timeout(1).then(() => 2); + const p3 = Promise.resolve(3); + + const result = await async.Promises.settled([p1, p2, p3]); + + assert.strictEqual(result.length, 3); + assert.deepStrictEqual(result[0], 1); + assert.deepStrictEqual(result[1], 2); + assert.deepStrictEqual(result[2], 3); + }); + + test('rejects with first error but handles all promises (all errors)', async () => { + const p1 = Promise.reject(1); + + let p2Handled = false; + const p2Error = new Error('2'); + const p2 = async.timeout(1).then(() => { + p2Handled = true; + throw p2Error; + }); + + let p3Handled = false; + const p3Error = new Error('3'); + const p3 = async.timeout(2).then(() => { + p3Handled = true; + throw p3Error; + }); + + let error: Error | undefined = undefined; + try { + await async.Promises.settled([p1, p2, p3]); + } catch (e) { + error = e; + } + + assert.ok(error); + assert.notStrictEqual(error, p2Error); + assert.notStrictEqual(error, p3Error); + assert.ok(p2Handled); + assert.ok(p3Handled); + }); + + test('rejects with first error but handles all promises (1 error)', async () => { + const p1 = Promise.resolve(1); + + let p2Handled = false; + const p2Error = new Error('2'); + const p2 = async.timeout(1).then(() => { + p2Handled = true; + throw p2Error; + }); + + let p3Handled = false; + const p3 = async.timeout(2).then(() => { + p3Handled = true; + return 3; + }); + + let error: Error | undefined = undefined; + try { + await async.Promises.settled([p1, p2, p3]); + } catch (e) { + error = e; + } + + assert.strictEqual(error, p2Error); + assert.ok(p2Handled); + assert.ok(p3Handled); + }); + }); }); diff --git a/src/vs/base/test/common/event.test.ts b/src/vs/base/test/common/event.test.ts index bb589ca76..1dda7864e 100644 --- a/src/vs/base/test/common/event.test.ts +++ b/src/vs/base/test/common/event.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 { Event, Emitter, EventBufferer, EventMultiplexer, IWaitUntil, PauseableEmitter, AsyncEmitter } from 'vs/base/common/event'; +import { Event, Emitter, EventBufferer, EventMultiplexer, PauseableEmitter } from 'vs/base/common/event'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import * as Errors from 'vs/base/common/errors'; -import { timeout } from 'vs/base/common/async'; +import { errorHandler, setUnexpectedErrorHandler } from 'vs/base/common/errors'; +import { AsyncEmitter, IWaitUntil, timeout } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; namespace Samples { @@ -57,7 +57,7 @@ suite('Event', function () { // unhook listener subscription.dispose(); doc.setText('boo'); - assert.equal(counter.count, 2); + assert.strictEqual(counter.count, 2); }); @@ -80,7 +80,7 @@ suite('Event', function () { subscription.dispose(); doc.setText('boo'); - assert.equal(counter.count, 2); + assert.strictEqual(counter.count, 2); }); test('Emitter, store', function () { @@ -100,7 +100,7 @@ suite('Event', function () { subscription.dispose(); doc.setText('boo'); - assert.equal(counter.count, 2); + assert.strictEqual(counter.count, 2); }); test('onFirstAdd|onLastRemove', () => { @@ -112,25 +112,25 @@ suite('Event', function () { onLastListenerRemove() { lastCount += 1; } }); - assert.equal(firstCount, 0); - assert.equal(lastCount, 0); + assert.strictEqual(firstCount, 0); + assert.strictEqual(lastCount, 0); let subscription = a.event(function () { }); - assert.equal(firstCount, 1); - assert.equal(lastCount, 0); + assert.strictEqual(firstCount, 1); + assert.strictEqual(lastCount, 0); subscription.dispose(); - assert.equal(firstCount, 1); - assert.equal(lastCount, 1); + assert.strictEqual(firstCount, 1); + assert.strictEqual(lastCount, 1); subscription = a.event(function () { }); - assert.equal(firstCount, 2); - assert.equal(lastCount, 1); + assert.strictEqual(firstCount, 2); + assert.strictEqual(lastCount, 1); }); test('throwingListener', () => { - const origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); - Errors.setUnexpectedErrorHandler(() => null); + const origErrorHandler = errorHandler.getUnexpectedErrorHandler(); + setUnexpectedErrorHandler(() => null); try { let a = new Emitter(); @@ -143,10 +143,10 @@ suite('Event', function () { hit = true; }); a.fire(undefined); - assert.equal(hit, true); + assert.strictEqual(hit, true); } finally { - Errors.setUnexpectedErrorHandler(origErrorHandler); + setUnexpectedErrorHandler(origErrorHandler); } }); @@ -162,15 +162,15 @@ suite('Event', function () { let reg2 = emitter.event(listener, context); emitter.fire(undefined); - assert.equal(counter, 2); + assert.strictEqual(counter, 2); reg1.dispose(); emitter.fire(undefined); - assert.equal(counter, 3); + assert.strictEqual(counter, 3); reg2.dispose(); emitter.fire(undefined); - assert.equal(counter, 3); + assert.strictEqual(counter, 3); }); test('Debounce Event', function (done: () => void) { @@ -192,9 +192,9 @@ suite('Event', function () { assert.ok(keys, 'was not expecting keys.'); if (count === 1) { doc.setText('4'); - assert.deepEqual(keys, ['1', '2', '3']); + assert.deepStrictEqual(keys, ['1', '2', '3']); } else if (count === 2) { - assert.deepEqual(keys, ['4']); + assert.deepStrictEqual(keys, ['4']); done(); } }); @@ -217,7 +217,7 @@ suite('Event', function () { emitter.fire(); await timeout(1); - assert.equal(calls, 1); + assert.strictEqual(calls, 1); }); test('Debounce Event - leading', async function () { @@ -234,7 +234,7 @@ suite('Event', function () { emitter.fire(); emitter.fire(); await timeout(1); - assert.equal(calls, 2); + assert.strictEqual(calls, 2); }); test('Debounce Event - leading reset', async function () { @@ -248,7 +248,7 @@ suite('Event', function () { emitter.fire(1); await timeout(1); - assert.deepEqual(calls, [1, 1]); + assert.deepStrictEqual(calls, [1, 1]); }); test('Emitter - In Order Delivery', function () { @@ -258,7 +258,7 @@ suite('Event', function () { if (event === 'e1') { a.fire('e2'); // assert that all events are delivered at this point - assert.deepEqual(listener2Events, ['e1', 'e2']); + assert.deepStrictEqual(listener2Events, ['e1', 'e2']); } }); a.event(function listener2(event) { @@ -267,7 +267,7 @@ suite('Event', function () { a.fire('e1'); // assert that all events are delivered in order - assert.deepEqual(listener2Events, ['e1', 'e2']); + assert.deepStrictEqual(listener2Events, ['e1', 'e2']); }); }); @@ -283,9 +283,9 @@ suite('AsyncEmitter', function () { let emitter = new AsyncEmitter(); emitter.event(e => { - assert.equal(e.foo, true); - assert.equal(e.bar, 1); - assert.equal(typeof e.waitUntil, 'function'); + assert.strictEqual(e.foo, true); + assert.strictEqual(e.bar, 1); + assert.strictEqual(typeof e.waitUntil, 'function'); }); emitter.fireAsync({ foo: true, bar: 1, }, CancellationToken.None); @@ -303,20 +303,20 @@ suite('AsyncEmitter', function () { emitter.event(e => { e.waitUntil(timeout(10).then(_ => { - assert.equal(globalState, 0); + assert.strictEqual(globalState, 0); globalState += 1; })); }); emitter.event(e => { e.waitUntil(timeout(1).then(_ => { - assert.equal(globalState, 1); + assert.strictEqual(globalState, 1); globalState += 1; })); }); await emitter.fireAsync({ foo: true }, CancellationToken.None); - assert.equal(globalState, 2); + assert.strictEqual(globalState, 2); }); test('sequential, in-order delivery', async function () { @@ -332,7 +332,7 @@ suite('AsyncEmitter', function () { e.waitUntil(timeout(10).then(async _ => { if (e.foo === 1) { await emitter.fireAsync({ foo: 2 }, CancellationToken.None); - assert.deepEqual(events, [1, 2]); + assert.deepStrictEqual(events, [1, 2]); done = true; } })); @@ -349,8 +349,8 @@ suite('AsyncEmitter', function () { }); test('catch errors', async function () { - const origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); - Errors.setUnexpectedErrorHandler(() => null); + const origErrorHandler = errorHandler.getUnexpectedErrorHandler(); + setUnexpectedErrorHandler(() => null); interface E extends IWaitUntil { foo: boolean; @@ -367,16 +367,17 @@ suite('AsyncEmitter', function () { emitter.event(e => { globalState += 1; e.waitUntil(timeout(10)); + e.waitUntil(timeout(20).then(() => globalState++)); // multiple `waitUntil` are supported and awaited on }); await emitter.fireAsync({ foo: true }, CancellationToken.None).then(() => { - assert.equal(globalState, 2); + assert.strictEqual(globalState, 3); }).catch(e => { console.log(e); assert.ok(false); }); - Errors.setUnexpectedErrorHandler(origErrorHandler); + setUnexpectedErrorHandler(origErrorHandler); }); }); @@ -390,7 +391,7 @@ suite('PausableEmitter', function () { emitter.fire(1); emitter.fire(2); - assert.deepEqual(data, [1, 2]); + assert.deepStrictEqual(data, [1, 2]); }); test('pause/resume - no merge', function () { @@ -400,17 +401,17 @@ suite('PausableEmitter', function () { emitter.event(e => data.push(e)); emitter.fire(1); emitter.fire(2); - assert.deepEqual(data, [1, 2]); + assert.deepStrictEqual(data, [1, 2]); emitter.pause(); emitter.fire(3); emitter.fire(4); - assert.deepEqual(data, [1, 2]); + assert.deepStrictEqual(data, [1, 2]); emitter.resume(); - assert.deepEqual(data, [1, 2, 3, 4]); + assert.deepStrictEqual(data, [1, 2, 3, 4]); emitter.fire(5); - assert.deepEqual(data, [1, 2, 3, 4, 5]); + assert.deepStrictEqual(data, [1, 2, 3, 4, 5]); }); test('pause/resume - merge', function () { @@ -420,18 +421,18 @@ suite('PausableEmitter', function () { emitter.event(e => data.push(e)); emitter.fire(1); emitter.fire(2); - assert.deepEqual(data, [1, 2]); + assert.deepStrictEqual(data, [1, 2]); emitter.pause(); emitter.fire(3); emitter.fire(4); - assert.deepEqual(data, [1, 2]); + assert.deepStrictEqual(data, [1, 2]); emitter.resume(); - assert.deepEqual(data, [1, 2, 7]); + assert.deepStrictEqual(data, [1, 2, 7]); emitter.fire(5); - assert.deepEqual(data, [1, 2, 7, 5]); + assert.deepStrictEqual(data, [1, 2, 7, 5]); }); test('double pause/resume', function () { @@ -441,22 +442,22 @@ suite('PausableEmitter', function () { emitter.event(e => data.push(e)); emitter.fire(1); emitter.fire(2); - assert.deepEqual(data, [1, 2]); + assert.deepStrictEqual(data, [1, 2]); emitter.pause(); emitter.pause(); emitter.fire(3); emitter.fire(4); - assert.deepEqual(data, [1, 2]); + assert.deepStrictEqual(data, [1, 2]); emitter.resume(); - assert.deepEqual(data, [1, 2]); + assert.deepStrictEqual(data, [1, 2]); emitter.resume(); - assert.deepEqual(data, [1, 2, 3, 4]); + assert.deepStrictEqual(data, [1, 2, 3, 4]); emitter.resume(); - assert.deepEqual(data, [1, 2, 3, 4]); + assert.deepStrictEqual(data, [1, 2, 3, 4]); }); test('resume, no pause', function () { @@ -466,11 +467,11 @@ suite('PausableEmitter', function () { emitter.event(e => data.push(e)); emitter.fire(1); emitter.fire(2); - assert.deepEqual(data, [1, 2]); + assert.deepStrictEqual(data, [1, 2]); emitter.resume(); emitter.fire(3); - assert.deepEqual(data, [1, 2, 3]); + assert.deepStrictEqual(data, [1, 2, 3]); }); test('nested pause', function () { @@ -493,16 +494,16 @@ suite('PausableEmitter', function () { emitter.pause(); emitter.fire(1); emitter.fire(2); - assert.deepEqual(data, []); + assert.deepStrictEqual(data, []); emitter.resume(); - assert.deepEqual(data, [1, 1]); // paused after first event + assert.deepStrictEqual(data, [1, 1]); // paused after first event emitter.resume(); - assert.deepEqual(data, [1, 1, 2, 2]); // remaing event delivered + assert.deepStrictEqual(data, [1, 1, 2, 2]); // remaing event delivered emitter.fire(3); - assert.deepEqual(data, [1, 1, 2, 2, 3, 3]); + assert.deepStrictEqual(data, [1, 1, 2, 2, 3, 3]); }); }); @@ -518,13 +519,13 @@ suite('Event utils', () => { const event = bufferer.wrapEvent(emitter.event); const listener = event(counter.onEvent, counter); - assert.equal(counter.count, 0); + assert.strictEqual(counter.count, 0); emitter.fire(); - assert.equal(counter.count, 1); + assert.strictEqual(counter.count, 1); emitter.fire(); - assert.equal(counter.count, 2); + assert.strictEqual(counter.count, 2); emitter.fire(); - assert.equal(counter.count, 3); + assert.strictEqual(counter.count, 3); listener.dispose(); }); @@ -536,20 +537,20 @@ suite('Event utils', () => { const event = bufferer.wrapEvent(emitter.event); const listener = event(counter.onEvent, counter); - assert.equal(counter.count, 0); + assert.strictEqual(counter.count, 0); emitter.fire(); - assert.equal(counter.count, 1); + assert.strictEqual(counter.count, 1); bufferer.bufferEvents(() => { emitter.fire(); - assert.equal(counter.count, 1); + assert.strictEqual(counter.count, 1); emitter.fire(); - assert.equal(counter.count, 1); + assert.strictEqual(counter.count, 1); }); - assert.equal(counter.count, 3); + assert.strictEqual(counter.count, 3); emitter.fire(); - assert.equal(counter.count, 4); + assert.strictEqual(counter.count, 4); listener.dispose(); }); @@ -563,20 +564,20 @@ suite('Event utils', () => { const listener2 = Event.once(emitter.event)(() => counter2++); const listener3 = Event.once(emitter.event)(() => counter3++); - assert.equal(counter1, 0); - assert.equal(counter2, 0); - assert.equal(counter3, 0); + assert.strictEqual(counter1, 0); + assert.strictEqual(counter2, 0); + assert.strictEqual(counter3, 0); listener3.dispose(); emitter.fire(); - assert.equal(counter1, 1); - assert.equal(counter2, 1); - assert.equal(counter3, 0); + assert.strictEqual(counter1, 1); + assert.strictEqual(counter2, 1); + assert.strictEqual(counter3, 0); emitter.fire(); - assert.equal(counter1, 2); - assert.equal(counter2, 1); - assert.equal(counter3, 0); + assert.strictEqual(counter1, 2); + assert.strictEqual(counter2, 1); + assert.strictEqual(counter3, 0); listener1.dispose(); listener2.dispose(); @@ -591,10 +592,10 @@ suite('Event utils', () => { const event = Event.fromPromise(Promise.resolve(null)); event(() => count++); - assert.equal(count, 0); + assert.strictEqual(count, 0); await timeout(10); - assert.equal(count, 1); + assert.strictEqual(count, 1); }); test('should emit when done - setTimeout', async () => { @@ -604,9 +605,9 @@ suite('Event utils', () => { const event = Event.fromPromise(promise); event(() => count++); - assert.equal(count, 0); + assert.strictEqual(count, 0); await promise; - assert.equal(count, 1); + assert.strictEqual(count, 1); }); }); @@ -646,14 +647,14 @@ suite('Event utils', () => { assert.deepEqual(result, []); const listener = bufferedEvent(num => result.push(num)); - assert.deepEqual(result, [1, 2, 3]); + assert.deepStrictEqual(result, [1, 2, 3]); emitter.fire(4); - assert.deepEqual(result, [1, 2, 3, 4]); + assert.deepStrictEqual(result, [1, 2, 3, 4]); listener.dispose(); emitter.fire(5); - assert.deepEqual(result, [1, 2, 3, 4]); + assert.deepStrictEqual(result, [1, 2, 3, 4]); }); test('should buffer events on next tick', async () => { @@ -668,14 +669,14 @@ suite('Event utils', () => { assert.deepEqual(result, []); const listener = bufferedEvent(num => result.push(num)); - assert.deepEqual(result, []); + assert.deepStrictEqual(result, []); await timeout(10); emitter.fire(4); - assert.deepEqual(result, [1, 2, 3, 4]); + assert.deepStrictEqual(result, [1, 2, 3, 4]); listener.dispose(); emitter.fire(5); - assert.deepEqual(result, [1, 2, 3, 4]); + assert.deepStrictEqual(result, [1, 2, 3, 4]); }); test('should fire initial buffer events', () => { @@ -690,7 +691,7 @@ suite('Event utils', () => { assert.deepEqual(result, []); bufferedEvent(num => result.push(num)); - assert.deepEqual(result, [-2, -1, 0, 1, 2, 3]); + assert.deepStrictEqual(result, [-2, -1, 0, 1, 2, 3]); }); }); @@ -704,10 +705,10 @@ suite('Event utils', () => { const e1 = new Emitter(); m.add(e1.event); - assert.deepEqual(result, []); + assert.deepStrictEqual(result, []); e1.fire(0); - assert.deepEqual(result, [0]); + assert.deepStrictEqual(result, [0]); }); test('multiplexer dispose works', () => { @@ -718,16 +719,16 @@ suite('Event utils', () => { const e1 = new Emitter(); m.add(e1.event); - assert.deepEqual(result, []); + assert.deepStrictEqual(result, []); e1.fire(0); - assert.deepEqual(result, [0]); + assert.deepStrictEqual(result, [0]); m.dispose(); - assert.deepEqual(result, [0]); + assert.deepStrictEqual(result, [0]); e1.fire(0); - assert.deepEqual(result, [0]); + assert.deepStrictEqual(result, [0]); }); test('event dispose works', () => { @@ -738,16 +739,16 @@ suite('Event utils', () => { const e1 = new Emitter(); m.add(e1.event); - assert.deepEqual(result, []); + assert.deepStrictEqual(result, []); e1.fire(0); - assert.deepEqual(result, [0]); + assert.deepStrictEqual(result, [0]); e1.dispose(); - assert.deepEqual(result, [0]); + assert.deepStrictEqual(result, [0]); e1.fire(0); - assert.deepEqual(result, [0]); + assert.deepStrictEqual(result, [0]); }); test('mutliplexer event dispose works', () => { @@ -758,16 +759,16 @@ suite('Event utils', () => { const e1 = new Emitter(); const l1 = m.add(e1.event); - assert.deepEqual(result, []); + assert.deepStrictEqual(result, []); e1.fire(0); - assert.deepEqual(result, [0]); + assert.deepStrictEqual(result, [0]); l1.dispose(); - assert.deepEqual(result, [0]); + assert.deepStrictEqual(result, [0]); e1.fire(0); - assert.deepEqual(result, [0]); + assert.deepStrictEqual(result, [0]); }); test('hot start works', () => { @@ -785,7 +786,7 @@ suite('Event utils', () => { e1.fire(1); e2.fire(2); e3.fire(3); - assert.deepEqual(result, [1, 2, 3]); + assert.deepStrictEqual(result, [1, 2, 3]); }); test('cold start works', () => { @@ -804,7 +805,7 @@ suite('Event utils', () => { e1.fire(1); e2.fire(2); e3.fire(3); - assert.deepEqual(result, [1, 2, 3]); + assert.deepStrictEqual(result, [1, 2, 3]); }); test('late add works', () => { @@ -825,7 +826,7 @@ suite('Event utils', () => { m.add(e3.event); e3.fire(3); - assert.deepEqual(result, [1, 2, 3]); + assert.deepStrictEqual(result, [1, 2, 3]); }); test('add dispose works', () => { @@ -845,15 +846,15 @@ suite('Event utils', () => { const e3 = new Emitter(); const l3 = m.add(e3.event); e3.fire(3); - assert.deepEqual(result, [1, 2, 3]); + assert.deepStrictEqual(result, [1, 2, 3]); l3.dispose(); e3.fire(4); - assert.deepEqual(result, [1, 2, 3]); + assert.deepStrictEqual(result, [1, 2, 3]); e2.fire(4); e1.fire(5); - assert.deepEqual(result, [1, 2, 3, 4, 5]); + assert.deepStrictEqual(result, [1, 2, 3, 4, 5]); }); }); @@ -864,31 +865,31 @@ suite('Event utils', () => { const result: number[] = []; const listener = event(num => result.push(num)); - assert.deepEqual(result, []); + assert.deepStrictEqual(result, []); emitter.fire(1); - assert.deepEqual(result, [1]); + assert.deepStrictEqual(result, [1]); emitter.fire(2); - assert.deepEqual(result, [1, 2]); + assert.deepStrictEqual(result, [1, 2]); emitter.fire(2); - assert.deepEqual(result, [1, 2]); + assert.deepStrictEqual(result, [1, 2]); emitter.fire(1); - assert.deepEqual(result, [1, 2, 1]); + assert.deepStrictEqual(result, [1, 2, 1]); emitter.fire(1); - assert.deepEqual(result, [1, 2, 1]); + assert.deepStrictEqual(result, [1, 2, 1]); emitter.fire(3); - assert.deepEqual(result, [1, 2, 1, 3]); + assert.deepStrictEqual(result, [1, 2, 1, 3]); emitter.fire(3); - assert.deepEqual(result, [1, 2, 1, 3]); + assert.deepStrictEqual(result, [1, 2, 1, 3]); emitter.fire(3); - assert.deepEqual(result, [1, 2, 1, 3]); + assert.deepStrictEqual(result, [1, 2, 1, 3]); listener.dispose(); }); diff --git a/src/vs/base/test/common/extpath.test.ts b/src/vs/base/test/common/extpath.test.ts index b05be8d83..92d25f83e 100644 --- a/src/vs/base/test/common/extpath.test.ts +++ b/src/vs/base/test/common/extpath.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as extpath from 'vs/base/common/extpath'; -import * as platform from 'vs/base/common/platform'; +import { isWindows } from 'vs/base/common/platform'; import { CharCode } from 'vs/base/common/charCode'; suite('Paths', () => { @@ -32,7 +32,7 @@ suite('Paths', () => { assert.strictEqual(extpath.getRoot('file://foo'), ''); }); - (!platform.isWindows ? test.skip : test)('isUNC', () => { + (!isWindows ? test.skip : test)('isUNC', () => { assert.ok(!extpath.isUNC('foo')); assert.ok(!extpath.isUNC('/foo')); assert.ok(!extpath.isUNC('\\foo')); @@ -51,7 +51,7 @@ suite('Paths', () => { assert.ok(!extpath.isValidBasename('/test.txt')); assert.ok(!extpath.isValidBasename('\\test.txt')); - if (platform.isWindows) { + if (isWindows) { assert.ok(!extpath.isValidBasename('aux')); assert.ok(!extpath.isValidBasename('Aux')); assert.ok(!extpath.isValidBasename('LPT0')); @@ -72,7 +72,7 @@ suite('Paths', () => { }); test('sanitizeFilePath', () => { - if (platform.isWindows) { + if (isWindows) { assert.strictEqual(extpath.sanitizeFilePath('.', 'C:\\the\\cwd'), 'C:\\the\\cwd'); assert.strictEqual(extpath.sanitizeFilePath('', 'C:\\the\\cwd'), 'C:\\the\\cwd'); @@ -108,7 +108,7 @@ suite('Paths', () => { }); test('isRootOrDriveLetter', () => { - if (platform.isWindows) { + if (isWindows) { assert.ok(extpath.isRootOrDriveLetter('c:')); assert.ok(extpath.isRootOrDriveLetter('D:')); assert.ok(extpath.isRootOrDriveLetter('D:/')); @@ -122,7 +122,7 @@ suite('Paths', () => { }); test('hasDriveLetter', () => { - if (platform.isWindows) { + if (isWindows) { assert.ok(extpath.hasDriveLetter('c:')); assert.ok(extpath.hasDriveLetter('D:')); assert.ok(extpath.hasDriveLetter('D:/')); @@ -136,7 +136,7 @@ suite('Paths', () => { }); test('getDriveLetter', () => { - if (platform.isWindows) { + if (isWindows) { assert.strictEqual(extpath.getDriveLetter('c:'), 'c'); assert.strictEqual(extpath.getDriveLetter('D:'), 'D'); assert.strictEqual(extpath.getDriveLetter('D:/'), 'D'); diff --git a/src/vs/base/test/common/fuzzyScorer.test.ts b/src/vs/base/test/common/fuzzyScorer.test.ts index 59c52ce00..7d11be458 100644 --- a/src/vs/base/test/common/fuzzyScorer.test.ts +++ b/src/vs/base/test/common/fuzzyScorer.test.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as scorer from 'vs/base/common/fuzzyScorer'; +import { IItemAccessor, FuzzyScore, FuzzyScore2, IItemScore, prepareQuery, scoreFuzzy, scoreFuzzy2, scoreItemFuzzy, compareItemsByFuzzyScore, pieceToQuery } from 'vs/base/common/fuzzyScorer'; import { URI } from 'vs/base/common/uri'; import { basename, dirname, sep, posix, win32 } from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; -class ResourceAccessorClass implements scorer.IItemAccessor { +class ResourceAccessorClass implements IItemAccessor { getItemLabel(resource: URI): string { return basename(resource.fsPath); @@ -27,7 +27,7 @@ class ResourceAccessorClass implements scorer.IItemAccessor { const ResourceAccessor = new ResourceAccessorClass(); -class ResourceWithSlashAccessorClass implements scorer.IItemAccessor { +class ResourceWithSlashAccessorClass implements IItemAccessor { getItemLabel(resource: URI): string { return basename(resource.fsPath); @@ -44,7 +44,7 @@ class ResourceWithSlashAccessorClass implements scorer.IItemAccessor { const ResourceWithSlashAccessor = new ResourceWithSlashAccessorClass(); -class ResourceWithBackslashAccessorClass implements scorer.IItemAccessor { +class ResourceWithBackslashAccessorClass implements IItemAccessor { getItemLabel(resource: URI): string { return basename(resource.fsPath); @@ -61,7 +61,7 @@ class ResourceWithBackslashAccessorClass implements scorer.IItemAccessor { const ResourceWithBackslashAccessor = new ResourceWithBackslashAccessorClass(); -class NullAccessorClass implements scorer.IItemAccessor { +class NullAccessorClass implements IItemAccessor { getItemLabel(resource: URI): string { return undefined!; @@ -76,24 +76,24 @@ class NullAccessorClass implements scorer.IItemAccessor { } } -function _doScore(target: string, query: string, fuzzy: boolean): scorer.FuzzyScore { - const preparedQuery = scorer.prepareQuery(query); +function _doScore(target: string, query: string, fuzzy: boolean): FuzzyScore { + const preparedQuery = prepareQuery(query); - return scorer.scoreFuzzy(target, preparedQuery.normalized, preparedQuery.normalizedLowercase, fuzzy); + return scoreFuzzy(target, preparedQuery.normalized, preparedQuery.normalizedLowercase, fuzzy); } -function _doScore2(target: string, query: string, matchOffset: number = 0): scorer.FuzzyScore2 { - const preparedQuery = scorer.prepareQuery(query); +function _doScore2(target: string, query: string, matchOffset: number = 0): FuzzyScore2 { + const preparedQuery = prepareQuery(query); - return scorer.scoreFuzzy2(target, preparedQuery, 0, matchOffset); + return scoreFuzzy2(target, preparedQuery, 0, matchOffset); } -function scoreItem(item: T, query: string, fuzzy: boolean, accessor: scorer.IItemAccessor): scorer.IItemScore { - return scorer.scoreItemFuzzy(item, scorer.prepareQuery(query), fuzzy, accessor, Object.create(null)); +function scoreItem(item: T, query: string, fuzzy: boolean, accessor: IItemAccessor): IItemScore { + return scoreItemFuzzy(item, prepareQuery(query), fuzzy, accessor, Object.create(null)); } -function compareItemsByScore(itemA: T, itemB: T, query: string, fuzzy: boolean, accessor: scorer.IItemAccessor): number { - return scorer.compareItemsByFuzzyScore(itemA, itemB, scorer.prepareQuery(query), fuzzy, accessor, Object.create(null)); +function compareItemsByScore(itemA: T, itemB: T, query: string, fuzzy: boolean, accessor: IItemAccessor): number { + return compareItemsByFuzzyScore(itemA, itemB, prepareQuery(query), fuzzy, accessor, Object.create(null)); } const NullAccessor = new NullAccessorClass(); @@ -103,7 +103,7 @@ suite('Fuzzy Scorer', () => { test('score (fuzzy)', function () { const target = 'HeLlo-World'; - const scores: scorer.FuzzyScore[] = []; + const scores: FuzzyScore[] = []; scores.push(_doScore(target, 'HelLo-World', true)); // direct case match scores.push(_doScore(target, 'hello-world', true)); // direct mix-case match scores.push(_doScore(target, 'HW', true)); // direct case prefix (multiple) @@ -1071,16 +1071,16 @@ suite('Fuzzy Scorer', () => { }); test('prepareQuery', () => { - assert.strictEqual(scorer.prepareQuery(' f*a ').normalized, 'fa'); - assert.strictEqual(scorer.prepareQuery('model Tester.ts').original, 'model Tester.ts'); - assert.strictEqual(scorer.prepareQuery('model Tester.ts').originalLowercase, 'model Tester.ts'.toLowerCase()); - assert.strictEqual(scorer.prepareQuery('model Tester.ts').normalized, 'modelTester.ts'); - assert.strictEqual(scorer.prepareQuery('Model Tester.ts').normalizedLowercase, 'modeltester.ts'); - assert.strictEqual(scorer.prepareQuery('ModelTester.ts').containsPathSeparator, false); - assert.strictEqual(scorer.prepareQuery('Model' + sep + 'Tester.ts').containsPathSeparator, true); + assert.strictEqual(prepareQuery(' f*a ').normalized, 'fa'); + assert.strictEqual(prepareQuery('model Tester.ts').original, 'model Tester.ts'); + assert.strictEqual(prepareQuery('model Tester.ts').originalLowercase, 'model Tester.ts'.toLowerCase()); + assert.strictEqual(prepareQuery('model Tester.ts').normalized, 'modelTester.ts'); + assert.strictEqual(prepareQuery('Model Tester.ts').normalizedLowercase, 'modeltester.ts'); + assert.strictEqual(prepareQuery('ModelTester.ts').containsPathSeparator, false); + assert.strictEqual(prepareQuery('Model' + sep + 'Tester.ts').containsPathSeparator, true); // with spaces - let query = scorer.prepareQuery('He*llo World'); + let query = prepareQuery('He*llo World'); assert.strictEqual(query.original, 'He*llo World'); assert.strictEqual(query.normalized, 'HelloWorld'); assert.strictEqual(query.normalizedLowercase, 'HelloWorld'.toLowerCase()); @@ -1092,13 +1092,13 @@ suite('Fuzzy Scorer', () => { assert.strictEqual(query.values?.[1].normalized, 'World'); assert.strictEqual(query.values?.[1].normalizedLowercase, 'World'.toLowerCase()); - let restoredQuery = scorer.pieceToQuery(query.values!); + let restoredQuery = pieceToQuery(query.values!); assert.strictEqual(restoredQuery.original, query.original); assert.strictEqual(restoredQuery.values?.length, query.values?.length); assert.strictEqual(restoredQuery.containsPathSeparator, query.containsPathSeparator); // with spaces that are empty - query = scorer.prepareQuery(' Hello World '); + query = prepareQuery(' Hello World '); assert.strictEqual(query.original, ' Hello World '); assert.strictEqual(query.originalLowercase, ' Hello World '.toLowerCase()); assert.strictEqual(query.normalized, 'HelloWorld'); @@ -1115,19 +1115,19 @@ suite('Fuzzy Scorer', () => { // Path related if (isWindows) { - assert.strictEqual(scorer.prepareQuery('C:\\some\\path').pathNormalized, 'C:\\some\\path'); - assert.strictEqual(scorer.prepareQuery('C:\\some\\path').normalized, 'C:\\some\\path'); - assert.strictEqual(scorer.prepareQuery('C:\\some\\path').containsPathSeparator, true); - assert.strictEqual(scorer.prepareQuery('C:/some/path').pathNormalized, 'C:\\some\\path'); - assert.strictEqual(scorer.prepareQuery('C:/some/path').normalized, 'C:\\some\\path'); - assert.strictEqual(scorer.prepareQuery('C:/some/path').containsPathSeparator, true); + assert.strictEqual(prepareQuery('C:\\some\\path').pathNormalized, 'C:\\some\\path'); + assert.strictEqual(prepareQuery('C:\\some\\path').normalized, 'C:\\some\\path'); + assert.strictEqual(prepareQuery('C:\\some\\path').containsPathSeparator, true); + assert.strictEqual(prepareQuery('C:/some/path').pathNormalized, 'C:\\some\\path'); + assert.strictEqual(prepareQuery('C:/some/path').normalized, 'C:\\some\\path'); + assert.strictEqual(prepareQuery('C:/some/path').containsPathSeparator, true); } else { - assert.strictEqual(scorer.prepareQuery('/some/path').pathNormalized, '/some/path'); - assert.strictEqual(scorer.prepareQuery('/some/path').normalized, '/some/path'); - assert.strictEqual(scorer.prepareQuery('/some/path').containsPathSeparator, true); - assert.strictEqual(scorer.prepareQuery('\\some\\path').pathNormalized, '/some/path'); - assert.strictEqual(scorer.prepareQuery('\\some\\path').normalized, '/some/path'); - assert.strictEqual(scorer.prepareQuery('\\some\\path').containsPathSeparator, true); + assert.strictEqual(prepareQuery('/some/path').pathNormalized, '/some/path'); + assert.strictEqual(prepareQuery('/some/path').normalized, '/some/path'); + assert.strictEqual(prepareQuery('/some/path').containsPathSeparator, true); + assert.strictEqual(prepareQuery('\\some\\path').pathNormalized, '/some/path'); + assert.strictEqual(prepareQuery('\\some\\path').normalized, '/some/path'); + assert.strictEqual(prepareQuery('\\some\\path').containsPathSeparator, true); } }); diff --git a/src/vs/base/test/common/glob.test.ts b/src/vs/base/test/common/glob.test.ts index 25b838853..dc40c7b4d 100644 --- a/src/vs/base/test/common/glob.test.ts +++ b/src/vs/base/test/common/glob.test.ts @@ -2,9 +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 * as path from 'vs/base/common/path'; import * as glob from 'vs/base/common/glob'; +import { sep } from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; suite('Glob', () => { @@ -952,7 +953,7 @@ suite('Glob', () => { } function nativeSep(slashPath: string): string { - return slashPath.replace(/\//g, path.sep); + return slashPath.replace(/\//g, sep); } test('relative pattern - glob star', function () { diff --git a/src/vs/base/test/common/iconLabels.test.ts b/src/vs/base/test/common/iconLabels.test.ts index 998c68971..ab17675ce 100644 --- a/src/vs/base/test/common/iconLabels.test.ts +++ b/src/vs/base/test/common/iconLabels.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { IMatch } from 'vs/base/common/filters'; -import { matchesFuzzyIconAware, parseLabelWithIcons, IParsedLabelWithIcons, stripIcons } from 'vs/base/common/iconLabels'; +import { matchesFuzzyIconAware, parseLabelWithIcons, IParsedLabelWithIcons, stripIcons, escapeIcons, markdownEscapeEscapedIcons } from 'vs/base/common/iconLabels'; export interface IIconFilter { // Returns null if word doesn't match. @@ -66,9 +66,23 @@ suite('Icon Labels', () => { }); test('stripIcons', () => { - assert.equal(stripIcons('Hello World'), 'Hello World'); - assert.equal(stripIcons('$(Hello World'), '$(Hello World'); - assert.equal(stripIcons('$(Hello) World'), ' World'); - assert.equal(stripIcons('$(Hello) W$(oi)rld'), ' Wrld'); + assert.strictEqual(stripIcons('Hello World'), 'Hello World'); + assert.strictEqual(stripIcons('$(Hello World'), '$(Hello World'); + assert.strictEqual(stripIcons('$(Hello) World'), ' World'); + assert.strictEqual(stripIcons('$(Hello) W$(oi)rld'), ' Wrld'); + }); + + + test('escapeIcons', () => { + assert.strictEqual(escapeIcons('Hello World'), 'Hello World'); + assert.strictEqual(escapeIcons('$(Hello World'), '$(Hello World'); + assert.strictEqual(escapeIcons('$(Hello) World'), '\\$(Hello) World'); + assert.strictEqual(escapeIcons('\\$(Hello) W$(oi)rld'), '\\$(Hello) W\\$(oi)rld'); + }); + + test('markdownEscapeEscapedIcons', () => { + assert.strictEqual(markdownEscapeEscapedIcons('Hello World'), 'Hello World'); + assert.strictEqual(markdownEscapeEscapedIcons('$(Hello) World'), '$(Hello) World'); + assert.strictEqual(markdownEscapeEscapedIcons('\\$(Hello) World'), '\\\\$(Hello) World'); }); }); diff --git a/src/vs/base/test/common/iterator.test.ts b/src/vs/base/test/common/iterator.test.ts index 7f32bc3eb..a7cdd692c 100644 --- a/src/vs/base/test/common/iterator.test.ts +++ b/src/vs/base/test/common/iterator.test.ts @@ -25,4 +25,11 @@ suite('Iterable', function () { assert.equal(Iterable.first(customIterable), 'one'); // fresh }); + test('equals', () => { + assert.strictEqual(Iterable.equals([1, 2], [1, 2]), true); + assert.strictEqual(Iterable.equals([1, 2], [1]), false); + assert.strictEqual(Iterable.equals([1], [1, 2]), false); + assert.strictEqual(Iterable.equals([2, 1], [1, 2]), false); + }); + }); diff --git a/src/vs/base/test/common/labels.test.ts b/src/vs/base/test/common/labels.test.ts index d7f0a401f..90aa411bc 100644 --- a/src/vs/base/test/common/labels.test.ts +++ b/src/vs/base/test/common/labels.test.ts @@ -5,10 +5,10 @@ import * as assert from 'assert'; import * as labels from 'vs/base/common/labels'; -import * as platform from 'vs/base/common/platform'; +import { isMacintosh, isWindows } from 'vs/base/common/platform'; suite('Labels', () => { - (!platform.isWindows ? test.skip : test)('shorten - windows', () => { + (!isWindows ? test.skip : test)('shorten - windows', () => { // nothing to shorten assert.deepStrictEqual(labels.shorten(['a']), ['a']); @@ -59,7 +59,7 @@ suite('Labels', () => { assert.deepStrictEqual(labels.shorten(['src\\vs\\workbench\\parts\\execution\\electron-browser', 'src\\vs\\workbench\\parts\\execution\\electron-browser\\something', 'src\\vs\\workbench\\parts\\terminal\\electron-browser']), ['…\\execution\\electron-browser', '…\\something', '…\\terminal\\…']); }); - (platform.isWindows ? test.skip : test)('shorten - not windows', () => { + (isWindows ? test.skip : test)('shorten - not windows', () => { // nothing to shorten assert.deepStrictEqual(labels.shorten(['a']), ['a']); @@ -134,13 +134,13 @@ suite('Labels', () => { assert.strictEqual(labels.template(t, { dirty: '* ', activeEditorShort: 'somefile.txt', rootName: 'monaco', appName: 'Visual Studio Code', separator: { label: ' - ' } }), '* somefile.txt - monaco - Visual Studio Code'); }); - (platform.isWindows ? test.skip : test)('getBaseLabel - unix', () => { + (isWindows ? test.skip : test)('getBaseLabel - unix', () => { assert.strictEqual(labels.getBaseLabel('/some/folder/file.txt'), 'file.txt'); assert.strictEqual(labels.getBaseLabel('/some/folder'), 'folder'); assert.strictEqual(labels.getBaseLabel('/'), '/'); }); - (!platform.isWindows ? test.skip : test)('getBaseLabel - windows', () => { + (!isWindows ? test.skip : test)('getBaseLabel - windows', () => { assert.strictEqual(labels.getBaseLabel('c:'), 'C:'); assert.strictEqual(labels.getBaseLabel('c:\\'), 'C:'); assert.strictEqual(labels.getBaseLabel('c:\\some\\folder\\file.txt'), 'file.txt'); @@ -151,10 +151,10 @@ suite('Labels', () => { test('mnemonicButtonLabel', () => { assert.strictEqual(labels.mnemonicButtonLabel('Hello World'), 'Hello World'); assert.strictEqual(labels.mnemonicButtonLabel(''), ''); - if (platform.isWindows) { + if (isWindows) { assert.strictEqual(labels.mnemonicButtonLabel('Hello & World'), 'Hello && World'); assert.strictEqual(labels.mnemonicButtonLabel('Do &¬ Save & Continue'), 'Do ¬ Save && Continue'); - } else if (platform.isMacintosh) { + } else if (isMacintosh) { assert.strictEqual(labels.mnemonicButtonLabel('Hello & World'), 'Hello & World'); assert.strictEqual(labels.mnemonicButtonLabel('Do &¬ Save & Continue'), 'Do not Save & Continue'); } else { diff --git a/src/vs/base/test/common/lifecycle.test.ts b/src/vs/base/test/common/lifecycle.test.ts index 7aa87cc6b..347000dfa 100644 --- a/src/vs/base/test/common/lifecycle.test.ts +++ b/src/vs/base/test/common/lifecycle.test.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 assert from 'assert'; import { DisposableStore, dispose, IDisposable, MultiDisposeError, ReferenceCollection, toDisposable } from 'vs/base/common/lifecycle'; @@ -95,8 +96,8 @@ suite('Lifecycle', () => { let array = [{ dispose() { } }, { dispose() { } }]; let array2 = dispose(array); - assert.equal(array.length, 2); - assert.equal(array2.length, 0); + assert.strictEqual(array.length, 2); + assert.strictEqual(array2.length, 0); assert.ok(array !== array2); let set = new Set([{ dispose() { } }, { dispose() { } }]); @@ -165,27 +166,27 @@ suite('Reference Collection', () => { const ref1 = collection.acquire('test'); assert(ref1); - assert.equal(ref1.object, 4); - assert.equal(collection.count, 1); + assert.strictEqual(ref1.object, 4); + assert.strictEqual(collection.count, 1); ref1.dispose(); - assert.equal(collection.count, 0); + assert.strictEqual(collection.count, 0); const ref2 = collection.acquire('test'); const ref3 = collection.acquire('test'); - assert.equal(ref2.object, ref3.object); - assert.equal(collection.count, 1); + assert.strictEqual(ref2.object, ref3.object); + assert.strictEqual(collection.count, 1); const ref4 = collection.acquire('monkey'); - assert.equal(ref4.object, 6); - assert.equal(collection.count, 2); + assert.strictEqual(ref4.object, 6); + assert.strictEqual(collection.count, 2); ref2.dispose(); - assert.equal(collection.count, 2); + assert.strictEqual(collection.count, 2); ref3.dispose(); - assert.equal(collection.count, 1); + assert.strictEqual(collection.count, 1); ref4.dispose(); - assert.equal(collection.count, 0); + assert.strictEqual(collection.count, 0); }); }); diff --git a/src/vs/base/test/common/linkedList.test.ts b/src/vs/base/test/common/linkedList.test.ts index d4eb08320..4d8d8f60b 100644 --- a/src/vs/base/test/common/linkedList.test.ts +++ b/src/vs/base/test/common/linkedList.test.ts @@ -132,6 +132,5 @@ suite('LinkedList', function () { let b = list.pop(); assert.strictEqual(b, 'b'); assertElements(list, 'a'); - }); }); diff --git a/src/vs/base/test/common/linkedText.test.ts b/src/vs/base/test/common/linkedText.test.ts index a7b61a558..fdd0e25b9 100644 --- a/src/vs/base/test/common/linkedText.test.ts +++ b/src/vs/base/test/common/linkedText.test.ts @@ -8,61 +8,61 @@ import { parseLinkedText } from 'vs/base/common/linkedText'; suite('LinkedText', () => { test('parses correctly', () => { - assert.deepEqual(parseLinkedText('').nodes, []); - assert.deepEqual(parseLinkedText('hello').nodes, ['hello']); - assert.deepEqual(parseLinkedText('hello there').nodes, ['hello there']); - assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href).').nodes, [ + assert.deepStrictEqual(parseLinkedText('').nodes, []); + assert.deepStrictEqual(parseLinkedText('hello').nodes, ['hello']); + assert.deepStrictEqual(parseLinkedText('hello there').nodes, ['hello there']); + assert.deepStrictEqual(parseLinkedText('Some message with [link text](http://link.href).').nodes, [ 'Some message with ', { label: 'link text', href: 'http://link.href' }, '.' ]); - assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href "and a title").').nodes, [ + assert.deepStrictEqual(parseLinkedText('Some message with [link text](http://link.href "and a title").').nodes, [ 'Some message with ', { label: 'link text', href: 'http://link.href', title: 'and a title' }, '.' ]); - assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href \'and a title\').').nodes, [ + assert.deepStrictEqual(parseLinkedText('Some message with [link text](http://link.href \'and a title\').').nodes, [ 'Some message with ', { label: 'link text', href: 'http://link.href', title: 'and a title' }, '.' ]); - assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href "and a \'title\'").').nodes, [ + assert.deepStrictEqual(parseLinkedText('Some message with [link text](http://link.href "and a \'title\'").').nodes, [ 'Some message with ', { label: 'link text', href: 'http://link.href', title: 'and a \'title\'' }, '.' ]); - assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href \'and a "title"\').').nodes, [ + assert.deepStrictEqual(parseLinkedText('Some message with [link text](http://link.href \'and a "title"\').').nodes, [ 'Some message with ', { label: 'link text', href: 'http://link.href', title: 'and a "title"' }, '.' ]); - assert.deepEqual(parseLinkedText('Some message with [link text](random stuff).').nodes, [ + assert.deepStrictEqual(parseLinkedText('Some message with [link text](random stuff).').nodes, [ 'Some message with [link text](random stuff).' ]); - assert.deepEqual(parseLinkedText('Some message with [https link](https://link.href).').nodes, [ + assert.deepStrictEqual(parseLinkedText('Some message with [https link](https://link.href).').nodes, [ 'Some message with ', { label: 'https link', href: 'https://link.href' }, '.' ]); - assert.deepEqual(parseLinkedText('Some message with [https link](https:).').nodes, [ + assert.deepStrictEqual(parseLinkedText('Some message with [https link](https:).').nodes, [ 'Some message with [https link](https:).' ]); - assert.deepEqual(parseLinkedText('Some message with [a command](command:foobar).').nodes, [ + assert.deepStrictEqual(parseLinkedText('Some message with [a command](command:foobar).').nodes, [ 'Some message with ', { label: 'a command', href: 'command:foobar' }, '.' ]); - assert.deepEqual(parseLinkedText('Some message with [a command](command:).').nodes, [ + assert.deepStrictEqual(parseLinkedText('Some message with [a command](command:).').nodes, [ 'Some message with [a command](command:).' ]); - assert.deepEqual(parseLinkedText('link [one](command:foo "nice") and link [two](http://foo)...').nodes, [ + assert.deepStrictEqual(parseLinkedText('link [one](command:foo "nice") and link [two](http://foo)...').nodes, [ 'link ', { label: 'one', href: 'command:foo', title: 'nice' }, ' and link ', { label: 'two', href: 'http://foo' }, '...' ]); - assert.deepEqual(parseLinkedText('link\n[one](command:foo "nice")\nand link [two](http://foo)...').nodes, [ + assert.deepStrictEqual(parseLinkedText('link\n[one](command:foo "nice")\nand link [two](http://foo)...').nodes, [ 'link\n', { label: 'one', href: 'command:foo', title: 'nice' }, '\nand link ', diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts index 8988e8e68..2e67001c1 100644 --- a/src/vs/base/test/common/map.test.ts +++ b/src/vs/base/test/common/map.test.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ResourceMap, TernarySearchTree, PathIterator, StringIterator, LinkedMap, Touch, LRUCache, UriIterator, ConfigKeysIterator } from 'vs/base/common/map'; import * as assert from 'assert'; -import { URI } from 'vs/base/common/uri'; +import { ConfigKeysIterator, LinkedMap, LRUCache, PathIterator, ResourceMap, StringIterator, TernarySearchTree, Touch, UriIterator } from 'vs/base/common/map'; import { extUriIgnorePathCase } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; suite('Map', () => { diff --git a/src/vs/base/test/common/network.test.ts b/src/vs/base/test/common/network.test.ts index 240c1abc2..b2291f833 100644 --- a/src/vs/base/test/common/network.test.ts +++ b/src/vs/base/test/common/network.test.ts @@ -19,52 +19,52 @@ suite('network', () => { let browserUri = FileAccess.asBrowserUri(originalFileUri); assert.ok(browserUri.authority.length > 0); let fileUri = FileAccess.asFileUri(browserUri); - assert.equal(fileUri.authority.length, 0); + assert.strictEqual(fileUri.authority.length, 0); assert(isEqual(originalFileUri, fileUri)); // asCodeUri() & asFileUri(): with authority originalFileUri = URI.file('network.test.ts').with({ authority: 'test-authority' }); browserUri = FileAccess.asBrowserUri(originalFileUri); - assert.equal(browserUri.authority, originalFileUri.authority); + assert.strictEqual(browserUri.authority, originalFileUri.authority); fileUri = FileAccess.asFileUri(browserUri); assert(isEqual(originalFileUri, fileUri)); }); (!enableTest ? test.skip : test)('FileAccess: moduleId (native)', () => { const browserUri = FileAccess.asBrowserUri('vs/base/test/node/network.test', require); - assert.equal(browserUri.scheme, Schemas.vscodeFileResource); + assert.strictEqual(browserUri.scheme, Schemas.vscodeFileResource); const fileUri = FileAccess.asFileUri('vs/base/test/node/network.test', require); - assert.equal(fileUri.scheme, Schemas.file); + assert.strictEqual(fileUri.scheme, Schemas.file); }); (!enableTest ? test.skip : test)('FileAccess: query and fragment is dropped (native)', () => { let originalFileUri = URI.file('network.test.ts').with({ query: 'foo=bar', fragment: 'something' }); let browserUri = FileAccess.asBrowserUri(originalFileUri); - assert.equal(browserUri.query, ''); - assert.equal(browserUri.fragment, ''); + assert.strictEqual(browserUri.query, ''); + assert.strictEqual(browserUri.fragment, ''); }); (!enableTest ? test.skip : test)('FileAccess: query and fragment is kept if URI is already of same scheme (native)', () => { let originalFileUri = URI.file('network.test.ts').with({ query: 'foo=bar', fragment: 'something' }); let browserUri = FileAccess.asBrowserUri(originalFileUri.with({ scheme: Schemas.vscodeFileResource })); - assert.equal(browserUri.query, 'foo=bar'); - assert.equal(browserUri.fragment, 'something'); + assert.strictEqual(browserUri.query, 'foo=bar'); + assert.strictEqual(browserUri.fragment, 'something'); let fileUri = FileAccess.asFileUri(originalFileUri); - assert.equal(fileUri.query, 'foo=bar'); - assert.equal(fileUri.fragment, 'something'); + assert.strictEqual(fileUri.query, 'foo=bar'); + assert.strictEqual(fileUri.fragment, 'something'); }); (!enableTest ? test.skip : test)('FileAccess: web', () => { const originalHttpsUri = URI.file('network.test.ts').with({ scheme: 'https' }); const browserUri = FileAccess.asBrowserUri(originalHttpsUri); - assert.equal(originalHttpsUri.toString(), browserUri.toString()); + assert.strictEqual(originalHttpsUri.toString(), browserUri.toString()); }); test('FileAccess: remote URIs', () => { const originalRemoteUri = URI.file('network.test.ts').with({ scheme: Schemas.vscodeRemote }); const browserUri = FileAccess.asBrowserUri(originalRemoteUri); - assert.notEqual(originalRemoteUri.scheme, browserUri.scheme); + assert.notStrictEqual(originalRemoteUri.scheme, browserUri.scheme); }); }); diff --git a/src/vs/base/test/common/stream.test.ts b/src/vs/base/test/common/stream.test.ts index 0fb36467b..7594d7cb8 100644 --- a/src/vs/base/test/common/stream.test.ts +++ b/src/vs/base/test/common/stream.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { isReadableStream, newWriteableStream, Readable, consumeReadable, peekReadable, consumeStream, ReadableStream, toStream, toReadable, transform, peekStream, isReadableBufferedStream, observe } from 'vs/base/common/stream'; +import { isReadableStream, newWriteableStream, Readable, consumeReadable, peekReadable, consumeStream, ReadableStream, toStream, toReadable, transform, peekStream, isReadableBufferedStream, listenStream } from 'vs/base/common/stream'; import { timeout } from 'vs/base/common/async'; suite('Stream', () => { @@ -69,6 +69,7 @@ suite('Stream', () => { stream.end('Final Bit'); assert.strictEqual(chunks.length, 4); assert.strictEqual(chunks[3], 'Final Bit'); + assert.strictEqual(end, true); stream.destroy(); @@ -76,6 +77,15 @@ suite('Stream', () => { assert.strictEqual(chunks.length, 4); }); + test('WriteableStream - end with empty string works', async () => { + const reducer = (strings: string[]) => strings.length > 0 ? strings.join() : 'error'; + const stream = newWriteableStream(reducer); + stream.end(''); + + const result = await consumeStream(stream, reducer); + assert.strictEqual(result, ''); + }); + test('WriteableStream - removeListener', () => { const stream = newWriteableStream(strings => strings.join()); @@ -270,6 +280,56 @@ suite('Stream', () => { assert.strictEqual(consumed, '1,2,3,4,5'); }); + test('consumeStream - without reducer', async () => { + const stream = readableToStream(arrayToReadable(['1', '2', '3', '4', '5'])); + const consumed = await consumeStream(stream); + assert.strictEqual(consumed, undefined); + }); + + test('consumeStream - without reducer and error', async () => { + const stream = newWriteableStream(strings => strings.join()); + stream.error(new Error()); + + const consumed = await consumeStream(stream); + assert.strictEqual(consumed, undefined); + }); + + test('listenStream', () => { + const stream = newWriteableStream(strings => strings.join()); + + let error = false; + let end = false; + let data = ''; + + listenStream(stream, { + onData: d => { + data = d; + }, + onError: e => { + error = true; + }, + onEnd: () => { + end = true; + } + }); + + stream.write('Hello'); + + assert.strictEqual(data, 'Hello'); + + stream.write('World'); + assert.strictEqual(data, 'World'); + + assert.strictEqual(error, false); + assert.strictEqual(end, false); + + stream.error(new Error()); + assert.strictEqual(error, true); + + stream.end('Final Bit'); + assert.strictEqual(end, true); + }); + test('peekStream', async () => { for (let i = 0; i < 5; i++) { const stream = readableToStream(arrayToReadable(['1', '2', '3', '4', '5'])); @@ -335,30 +395,6 @@ suite('Stream', () => { assert.strictEqual(consumed, '11,22,33,44,55'); }); - test('observer', async () => { - const source1 = newWriteableStream(strings => strings.join()); - setTimeout(() => source1.error(new Error())); - await observe(source1).errorOrEnd(); - - const source2 = newWriteableStream(strings => strings.join()); - setTimeout(() => source2.end('Hello Test')); - await observe(source2).errorOrEnd(); - - const source3 = newWriteableStream(strings => strings.join()); - setTimeout(() => { - source3.write('Hello Test'); - source3.error(new Error()); - }); - await observe(source3).errorOrEnd(); - - const source4 = newWriteableStream(strings => strings.join()); - setTimeout(() => { - source4.write('Hello Test'); - source4.end(); - }); - await observe(source4).errorOrEnd(); - }); - test('events are delivered even if a listener is removed during delivery', () => { const stream = newWriteableStream(strings => strings.join()); diff --git a/src/vs/base/test/common/strings.test.ts b/src/vs/base/test/common/strings.test.ts index dc8d9da5e..9a9db74bb 100644 --- a/src/vs/base/test/common/strings.test.ts +++ b/src/vs/base/test/common/strings.test.ts @@ -215,6 +215,11 @@ suite('Strings', () => { assert.strictEqual(strings.containsEmoji('1F1F7 1F1F4 # 🇷🇴 Romania'), true); }); + test('issue #115221: isEmojiImprecise misses ⭐', () => { + const codePoint = strings.getNextCodePoint('⭐', '⭐'.length, 0); + assert.strictEqual(strings.isEmojiImprecise(codePoint), true); + }); + test('isBasicASCII', () => { function assertIsBasicASCII(str: string, expected: boolean): void { assert.strictEqual(strings.isBasicASCII(str), expected, str + ` (${str.charCodeAt(0)})`); diff --git a/src/vs/base/test/node/crypto.test.ts b/src/vs/base/test/node/crypto.test.ts index 16cfc58fe..cb185077f 100644 --- a/src/vs/base/test/node/crypto.test.ts +++ b/src/vs/base/test/node/crypto.test.ts @@ -6,7 +6,8 @@ import { checksum } from 'vs/base/node/crypto'; import { join } from 'vs/base/common/path'; import { tmpdir } from 'os'; -import { mkdirp, rimraf, writeFile } from 'vs/base/node/pfs'; +import { promises } from 'fs'; +import { rimraf, writeFile } from 'vs/base/node/pfs'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; suite('Crypto', () => { @@ -16,7 +17,7 @@ suite('Crypto', () => { setup(function () { testDir = getRandomTestPath(tmpdir(), 'vsctests', 'crypto'); - return mkdirp(testDir); + return promises.mkdir(testDir, { recursive: true }); }); teardown(function () { diff --git a/src/vs/base/test/node/extpath.test.ts b/src/vs/base/test/node/extpath.test.ts index 056110be8..a1c8a7f94 100644 --- a/src/vs/base/test/node/extpath.test.ts +++ b/src/vs/base/test/node/extpath.test.ts @@ -5,7 +5,8 @@ import * as assert from 'assert'; import { tmpdir } from 'os'; -import { mkdirp, rimraf } from 'vs/base/node/pfs'; +import { promises } from 'fs'; +import { rimraf } from 'vs/base/node/pfs'; import { realcaseSync, realpath, realpathSync } from 'vs/base/node/extpath'; import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; @@ -15,7 +16,7 @@ flakySuite('Extpath', () => { setup(() => { testDir = getRandomTestPath(tmpdir(), 'vsctests', 'extpath'); - return mkdirp(testDir, 493); + return promises.mkdir(testDir, { recursive: true }); }); teardown(() => { diff --git a/src/vs/base/test/node/pfs/pfs.test.ts b/src/vs/base/test/node/pfs/pfs.test.ts index eeb380a62..62bb9c7e3 100644 --- a/src/vs/base/test/node/pfs/pfs.test.ts +++ b/src/vs/base/test/node/pfs/pfs.test.ts @@ -8,12 +8,13 @@ import * as fs from 'fs'; import { tmpdir } from 'os'; import { join, sep } from 'vs/base/common/path'; import { generateUuid } from 'vs/base/common/uuid'; -import { copy, exists, mkdirp, move, readdir, readDirsInDir, readdirWithFileTypes, readFile, renameIgnoreError, rimraf, RimRafMode, rimrafSync, statLink, writeFile, writeFileSync } from 'vs/base/node/pfs'; +import { copy, exists, move, readdir, readDirsInDir, rimraf, RimRafMode, rimrafSync, SymlinkSupport, writeFile, writeFileSync } from 'vs/base/node/pfs'; import { timeout } from 'vs/base/common/async'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { canNormalize } from 'vs/base/common/normalization'; import { VSBuffer } from 'vs/base/common/buffer'; import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; +import { isWindows } from 'vs/base/common/platform'; flakySuite('PFS', function () { @@ -22,7 +23,7 @@ flakySuite('PFS', function () { setup(() => { testDir = getRandomTestPath(tmpdir(), 'vsctests', 'pfs'); - return mkdirp(testDir, 493); + return fs.promises.mkdir(testDir, { recursive: true }); }); teardown(() => { @@ -36,7 +37,7 @@ flakySuite('PFS', function () { await writeFile(testFile, 'Hello World', (null!)); - assert.strictEqual((await readFile(testFile)).toString(), 'Hello World'); + assert.strictEqual((await fs.promises.readFile(testFile)).toString(), 'Hello World'); }); test('writeFile - parallel write on different files works', async () => { @@ -153,10 +154,6 @@ flakySuite('PFS', function () { assert.ok(!fs.existsSync(testDir)); }); - test('moveIgnoreError', () => { - return renameIgnoreError(join(testDir, 'foo'), join(testDir, 'bar')); - }); - test('copy, move and delete', async () => { const id = generateUuid(); const id2 = generateUuid(); @@ -165,7 +162,7 @@ flakySuite('PFS', function () { const targetDir = join(parentDir, id); const targetDir2 = join(parentDir, id2); - await copy(sourceDir, targetDir); + await copy(sourceDir, targetDir, { preserveSymlinks: true }); assert.ok(fs.existsSync(targetDir)); assert.ok(fs.existsSync(join(targetDir, 'index.html'))); @@ -194,33 +191,103 @@ flakySuite('PFS', function () { assert.ok(!fs.existsSync(parentDir)); }); - test('copy skips over dangling symbolic links', async () => { + test('copy handles symbolic links', async () => { const id1 = generateUuid(); const symbolicLinkTarget = join(testDir, id1); const id2 = generateUuid(); - const symbolicLink = join(testDir, id2); + const symLink = join(testDir, id2); const id3 = generateUuid(); const copyTarget = join(testDir, id3); - await mkdirp(symbolicLinkTarget, 493); + await fs.promises.mkdir(symbolicLinkTarget, { recursive: true }); - fs.symlinkSync(symbolicLinkTarget, symbolicLink, 'junction'); + fs.symlinkSync(symbolicLinkTarget, symLink, 'junction'); + // Copy preserves symlinks if configured as such + // + // Windows: this test does not work because creating symlinks + // requires priviledged permissions (admin). + if (!isWindows) { + await copy(symLink, copyTarget, { preserveSymlinks: true }); + + assert.ok(fs.existsSync(copyTarget)); + + const { symbolicLink } = await SymlinkSupport.stat(copyTarget); + assert.ok(symbolicLink); + assert.ok(!symbolicLink.dangling); + + const target = await fs.promises.readlink(copyTarget); + assert.strictEqual(target, symbolicLinkTarget); + + // Copy does not preserve symlinks if configured as such + + await rimraf(copyTarget); + await copy(symLink, copyTarget, { preserveSymlinks: false }); + + assert.ok(fs.existsSync(copyTarget)); + + const { symbolicLink: symbolicLink2 } = await SymlinkSupport.stat(copyTarget); + assert.ok(!symbolicLink2); + } + + // Copy ignores dangling symlinks + + await rimraf(copyTarget); await rimraf(symbolicLinkTarget); - await copy(symbolicLink, copyTarget); // this should not throw + await copy(symLink, copyTarget, { preserveSymlinks: true }); // this should not throw assert.ok(!fs.existsSync(copyTarget)); }); - test('mkdirp', async () => { - const newDir = join(testDir, generateUuid()); + test('copy handles symbolic links when the reference is inside source', async () => { - await mkdirp(newDir, 493); + // Source Folder + const sourceFolder = join(testDir, generateUuid(), 'copy-test'); // copy-test + 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 fs.promises.mkdir(sourceLinkMD5JSFolder, { recursive: true }); + await writeFile(sourceLinkMD5JSFile, 'Hello from MD5'); - assert.ok(fs.existsSync(newDir)); + const sourceLinkMD5JSFolderLinked = join(sourceLinkTestFolder, 'md5-linked'); // copy-test/link-test/md5-linked + fs.symlinkSync(sourceLinkMD5JSFolder, sourceLinkMD5JSFolderLinked, 'junction'); + + // Target Folder + const targetLinkTestFolder = join(sourceFolder, 'link-test copy'); // copy-test/link-test copy + const targetLinkMD5JSFolder = join(targetLinkTestFolder, 'md5'); // copy-test/link-test copy/md5 + const targetLinkMD5JSFile = join(targetLinkMD5JSFolder, 'md5.js'); // copy-test/link-test copy/md5/md5.js + const targetLinkMD5JSFolderLinked = join(targetLinkTestFolder, 'md5-linked'); // copy-test/link-test copy/md5-linked + + // Copy with `preserveSymlinks: true` and verify result + // + // Windows: this test does not work because creating symlinks + // requires priviledged permissions (admin). + if (!isWindows) { + await copy(sourceLinkTestFolder, targetLinkTestFolder, { preserveSymlinks: true }); + + assert.ok(fs.existsSync(targetLinkTestFolder)); + assert.ok(fs.existsSync(targetLinkMD5JSFolder)); + assert.ok(fs.existsSync(targetLinkMD5JSFile)); + assert.ok(fs.existsSync(targetLinkMD5JSFolderLinked)); + assert.ok(fs.lstatSync(targetLinkMD5JSFolderLinked).isSymbolicLink()); + + const linkTarget = await fs.promises.readlink(targetLinkMD5JSFolderLinked); + assert.strictEqual(linkTarget, targetLinkMD5JSFolder); + + await fs.promises.rmdir(targetLinkTestFolder, { recursive: true }); + } + + // Copy with `preserveSymlinks: false` and verify result + await copy(sourceLinkTestFolder, targetLinkTestFolder, { preserveSymlinks: false }); + + assert.ok(fs.existsSync(targetLinkTestFolder)); + assert.ok(fs.existsSync(targetLinkMD5JSFolder)); + assert.ok(fs.existsSync(targetLinkMD5JSFile)); + assert.ok(fs.existsSync(targetLinkMD5JSFolderLinked)); + assert.ok(fs.lstatSync(targetLinkMD5JSFolderLinked).isDirectory()); }); test('readDirsInDir', async () => { @@ -244,14 +311,14 @@ flakySuite('PFS', function () { const id2 = generateUuid(); const symbolicLink = join(testDir, id2); - await mkdirp(directory, 493); + await fs.promises.mkdir(directory, { recursive: true }); fs.symlinkSync(directory, symbolicLink, 'junction'); - let statAndIsLink = await statLink(directory); + let statAndIsLink = await SymlinkSupport.stat(directory); assert.ok(!statAndIsLink?.symbolicLink); - statAndIsLink = await statLink(symbolicLink); + statAndIsLink = await SymlinkSupport.stat(symbolicLink); assert.ok(statAndIsLink?.symbolicLink); assert.ok(!statAndIsLink?.symbolicLink?.dangling); }); @@ -263,13 +330,13 @@ flakySuite('PFS', function () { const id2 = generateUuid(); const symbolicLink = join(testDir, id2); - await mkdirp(directory, 493); + await fs.promises.mkdir(directory, { recursive: true }); fs.symlinkSync(directory, symbolicLink, 'junction'); await rimraf(directory); - const statAndIsLink = await statLink(symbolicLink); + const statAndIsLink = await SymlinkSupport.stat(symbolicLink); assert.ok(statAndIsLink?.symbolicLink); assert.ok(statAndIsLink?.symbolicLink?.dangling); }); @@ -279,7 +346,7 @@ flakySuite('PFS', function () { const id = generateUuid(); const newDir = join(testDir, 'pfs', id, 'öäü'); - await mkdirp(newDir, 493); + await fs.promises.mkdir(newDir, { recursive: true }); assert.ok(fs.existsSync(newDir)); @@ -288,16 +355,16 @@ flakySuite('PFS', function () { } }); - test('readdirWithFileTypes', async () => { + test('readdir (with file types)', async () => { if (canNormalize && typeof process.versions['electron'] !== 'undefined' /* needs electron */) { const newDir = join(testDir, 'öäü'); - await mkdirp(newDir, 493); + await fs.promises.mkdir(newDir, { recursive: true }); await writeFile(join(testDir, 'somefile.txt'), 'contents'); assert.ok(fs.existsSync(newDir)); - const children = await readdirWithFileTypes(testDir); + const children = await readdir(testDir, { withFileTypes: true }); assert.strictEqual(children.some(n => n.name === 'öäü'), true); // Mac always converts to NFD, so assert.strictEqual(children.some(n => n.isDirectory()), true); diff --git a/src/vs/base/test/node/powershell.test.ts b/src/vs/base/test/node/powershell.test.ts index 6e88b0982..a93a88fc7 100644 --- a/src/vs/base/test/node/powershell.test.ts +++ b/src/vs/base/test/node/powershell.test.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import * as fs from 'fs'; -import * as os from 'os'; import * as platform from 'vs/base/common/platform'; import { enumeratePowerShellInstallations, getFirstAvailablePowerShellInstallation, IPowerShellExeDetails } from 'vs/base/node/powershell'; @@ -40,44 +39,20 @@ if (platform.isWindows) { }); test('Can enumerate PowerShells', async () => { - const isOS64Bit = os.arch() === 'x64'; const pwshs = new Array(); for await (const p of enumeratePowerShellInstallations()) { pwshs.push(p); } const powershellLog = 'Found these PowerShells:\n' + pwshs.map(p => `${p.displayName}: ${p.exePath}`).join('\n'); - assert.strictEqual(pwshs.length >= (isOS64Bit ? 2 : 1), true, powershellLog); + assert.strictEqual(pwshs.length >= 1, true, powershellLog); for (const pwsh of pwshs) { checkPath(pwsh.exePath); } - - const lastIndex = pwshs.length - 1; - const secondToLastIndex = pwshs.length - 2; - - // 64bit process on 64bit OS - if (process.arch === 'x64') { - checkPath(pwshs[secondToLastIndex].exePath); - assert.strictEqual(pwshs[secondToLastIndex].displayName, 'Windows PowerShell', powershellLog); - - checkPath(pwshs[lastIndex].exePath); - assert.strictEqual(pwshs[lastIndex].displayName, 'Windows PowerShell (x86)', powershellLog); - } else if (isOS64Bit) { - // 32bit process on 64bit OS - - // Windows PowerShell x86 comes first if vscode is 32bit - checkPath(pwshs[secondToLastIndex].exePath); - assert.strictEqual(pwshs[secondToLastIndex].displayName, 'Windows PowerShell (x86)', powershellLog); - - checkPath(pwshs[lastIndex].exePath); - assert.strictEqual(pwshs[lastIndex].displayName, 'Windows PowerShell', powershellLog); - } else { - // 32bit or ARM process - checkPath(pwshs[lastIndex].exePath); - assert.strictEqual(pwshs[lastIndex].displayName, 'Windows PowerShell (x86)', powershellLog); - } + // The last one should always be Windows PowerShell. + assert.strictEqual(pwshs[pwshs.length - 1].displayName, 'Windows PowerShell', powershellLog); }); }); } diff --git a/src/vs/base/node/paths.ts b/src/vs/base/test/node/userDataPath.test.ts similarity index 56% rename from src/vs/base/node/paths.ts rename to src/vs/base/test/node/userDataPath.test.ts index eaf03e6e4..74d0ae4f2 100644 --- a/src/vs/base/node/paths.ts +++ b/src/vs/base/test/node/userDataPath.test.ts @@ -3,9 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { FileAccess } from 'vs/base/common/network'; +import * as assert from 'assert'; +import { getDefaultUserDataPath } from 'vs/base/node/userDataPath'; -const pathsPath = FileAccess.asFileUri('paths', require).fsPath; -const paths = require.__$__nodeRequire<{ getDefaultUserDataPath(): string }>(pathsPath); +suite('User data path', () => { -export const getDefaultUserDataPath = paths.getDefaultUserDataPath; + test('getDefaultUserDataPath', () => { + const path = getDefaultUserDataPath(); + assert.ok(path.length > 0); + }); +}); diff --git a/src/vs/base/test/node/zip/zip.test.ts b/src/vs/base/test/node/zip/zip.test.ts index a98b2609f..360f7dfd0 100644 --- a/src/vs/base/test/node/zip/zip.test.ts +++ b/src/vs/base/test/node/zip/zip.test.ts @@ -6,8 +6,9 @@ import * as assert from 'assert'; import * as path from 'vs/base/common/path'; import { tmpdir } from 'os'; +import { promises } from 'fs'; import { extract } from 'vs/base/node/zip'; -import { rimraf, exists, mkdirp } from 'vs/base/node/pfs'; +import { rimraf, exists } from 'vs/base/node/pfs'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { createCancelablePromise } from 'vs/base/common/async'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; @@ -19,7 +20,7 @@ suite('Zip', () => { setup(() => { testDir = getRandomTestPath(tmpdir(), 'vsctests', 'zip'); - return mkdirp(testDir); + return promises.mkdir(testDir, { recursive: true }); }); teardown(() => { diff --git a/src/vs/base/worker/workerMain.ts b/src/vs/base/worker/workerMain.ts index c39dba9a3..51b02fea5 100644 --- a/src/vs/base/worker/workerMain.ts +++ b/src/vs/base/worker/workerMain.ts @@ -5,10 +5,14 @@ (function () { - let MonacoEnvironment = (self).MonacoEnvironment; - let monacoBaseUrl = MonacoEnvironment && MonacoEnvironment.baseUrl ? MonacoEnvironment.baseUrl : '../../../'; + const MonacoEnvironment = (self).MonacoEnvironment; + const monacoBaseUrl = MonacoEnvironment && MonacoEnvironment.baseUrl ? MonacoEnvironment.baseUrl : '../../../'; - const trustedTypesPolicy = self.trustedTypes?.createPolicy('amdLoader', { createScriptURL: value => value }); + const trustedTypesPolicy = ( + typeof self.trustedTypes?.createPolicy === 'function' + ? self.trustedTypes?.createPolicy('amdLoader', { createScriptURL: value => value }) + : undefined + ); if (typeof (self).define !== 'function' || !(self).define.amd) { let loaderSrc: string | TrustedScriptURL = monacoBaseUrl + 'vs/loader.js'; diff --git a/src/vs/buildunit.json b/src/vs/buildunit.json deleted file mode 100644 index 88fe75c55..000000000 --- a/src/vs/buildunit.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "vs", - "dependencies": [ - ], - "libs": [ - "lib.core.d.ts" - ], - "sources": [ - ], - "declares": [ - "vs/nls.d.ts" - ] -} \ No newline at end of file diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index 1ed7feec9..337e04c1f 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -275,6 +275,8 @@ class WorkspaceProvider implements IWorkspaceProvider { static QUERY_PARAM_PAYLOAD = 'payload'; + readonly trusted = true; + constructor( public readonly workspace: IWorkspace, public readonly payload: object diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts index e060bcc6a..41a73455b 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts +++ b/src/vs/code/electron-browser/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 * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; import { IStringDictionary } from 'vs/base/common/collections'; @@ -52,7 +53,7 @@ export class LanguagePackCachedDataCleaner extends Disposable { : 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months try { const installed: IStringDictionary = Object.create(null); - const metaData: LanguagePackFile = JSON.parse(await pfs.readFile(path.join(this._environmentService.userDataPath, 'languagepacks.json'), 'utf8')); + const metaData: LanguagePackFile = JSON.parse(await fs.promises.readFile(path.join(this._environmentService.userDataPath, 'languagepacks.json'), 'utf8')); for (let locale of Object.keys(metaData)) { const entry = metaData[locale]; installed[`${entry.hash}.${locale}`] = true; @@ -80,7 +81,7 @@ export class LanguagePackCachedDataCleaner extends Disposable { continue; } const candidate = path.join(folder, entry); - const stat = await pfs.stat(candidate); + const stat = await fs.promises.stat(candidate); if (stat.isDirectory()) { const diff = now - stat.mtime.getTime(); if (diff > maxAge) { diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner.ts index 589b87f51..b9ee08d4c 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner.ts @@ -8,6 +8,7 @@ import { join, dirname, basename } from 'vs/base/common/path'; import { readdir, rimraf } from 'vs/base/node/pfs'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Promises } from 'vs/base/common/async'; export class LogsDataCleaner extends Disposable { @@ -31,7 +32,7 @@ export class LogsDataCleaner extends Disposable { const oldSessions = allSessions.sort().filter((d, i) => d !== currentLog); const toDelete = oldSessions.slice(0, Math.max(0, oldSessions.length - 9)); - return Promise.all(toDelete.map(name => rimraf(join(logsRoot, name)))); + return Promises.settled(toDelete.map(name => rimraf(join(logsRoot, name)))); }).then(null, onUnexpectedError); }, 10 * 1000); diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts index a98088010..7c37511ca 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { promises } from 'fs'; import { basename, dirname, join } from 'vs/base/common/path'; import { onUnexpectedError } from 'vs/base/common/errors'; import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { readdir, rimraf, stat } from 'vs/base/node/pfs'; +import { readdir, rimraf } from 'vs/base/node/pfs'; import product from 'vs/platform/product/common/product'; export class NodeCachedDataCleaner { @@ -54,7 +55,7 @@ export class NodeCachedDataCleaner { if (entry !== nodeCachedDataCurrent) { const path = join(nodeCachedDataRootDir, entry); - deletes.push(stat(path).then(stats => { + deletes.push(promises.stat(path).then(stats => { // stat check // * only directories // * only when old enough diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts index 66b8d79b5..fc26cb229 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { promises } from 'fs'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { join } from 'vs/base/common/path'; -import { readdir, readFile, rimraf } from 'vs/base/node/pfs'; +import { readdir, rimraf } from 'vs/base/node/pfs'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IBackupWorkspacesFormat } from 'vs/platform/backup/node/backup'; @@ -32,7 +33,7 @@ export class StorageDataCleaner extends Disposable { try { // Leverage the backup workspace file to find out which empty workspace is currently in use to // determine which empty workspace storage can safely be deleted - const contents = await readFile(this.backupWorkspacesPath, 'utf8'); + const contents = await promises.readFile(this.backupWorkspacesPath, 'utf8'); const workspaces = JSON.parse(contents) as IBackupWorkspacesFormat; const emptyWorkspaces = workspaces.emptyWorkspaceInfos.map(info => info.backupFolder); diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcess.js b/src/vs/code/electron-browser/sharedProcess/sharedProcess.js index 2bb0a0bb7..50d8d3f7f 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcess.js +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcess.js @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ //@ts-check -'use strict'; - (function () { + 'use strict'; + const bootstrap = bootstrapLib(); const bootstrapWindow = bootstrapWindowLib(); @@ -38,5 +38,4 @@ } //#endregion - }()); diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 3aa7cce16..4ebb2bf0f 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import product from 'vs/platform/product/common/product'; import * as fs from 'fs'; import { release } from 'os'; import { gracefulify } from 'graceful-fs'; -import { Server as MessagePortServer } from 'vs/base/parts/ipc/electron-sandbox/ipc.mp'; -import { StaticRouter, createChannelSender, createChannelReceiver } from 'vs/base/parts/ipc/common/ipc'; +import { ipcRenderer } from 'electron'; +import product from 'vs/platform/product/common/product'; +import { Server as MessagePortServer } from 'vs/base/parts/ipc/electron-browser/ipc.mp'; +import { StaticRouter, ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; @@ -28,9 +29,8 @@ import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProp import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; -import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; -import { ILogService, ILoggerService, MultiplexLogService, ConsoleLogService } from 'vs/platform/log/common/log'; -import { LoggerChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; +import { ILogService, ILoggerService, MultiplexLogService, ConsoleLogger } from 'vs/platform/log/common/log'; +import { LogLevelChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; import { combinedDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -40,9 +40,11 @@ import { NodeCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/co import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner'; import { StorageDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner'; import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner'; -import { IMainProcessService, MessagePortMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; -import { SpdLogService } from 'vs/platform/log/node/spdlogService'; -import { DiagnosticsService, IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; +import { MessagePortMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; +import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; +import { IDiagnosticsService } from 'vs/platform/diagnostics/common/diagnostics'; import { FileService } from 'vs/platform/files/common/fileService'; import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; @@ -51,13 +53,12 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration as registerUserDataSyncConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService, IUserDataSyncStoreManagementService, IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncStoreService, UserDataSyncStoreManagementService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; -import { UserDataSyncChannel, UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, UserDataSyncMachinesServiceChannel, UserDataSyncAccountServiceChannel, UserDataSyncStoreManagementServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; +import { UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, UserDataSyncMachinesServiceChannel, UserDataSyncAccountServiceChannel, UserDataSyncStoreManagementServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { LoggerService } from 'vs/platform/log/node/loggerService'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; import { UserDataAutoSyncService } from 'vs/platform/userDataSync/electron-sandbox/userDataAutoSyncService'; -import { NativeStorageService } from 'vs/platform/storage/node/storageService'; -import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc'; +import { NativeStorageService2 } from 'vs/platform/storage/electron-sandbox/storageService2'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; import { UserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSyncResourceEnablementService'; @@ -78,6 +79,11 @@ import { LocalizationsUpdater } from 'vs/code/electron-browser/sharedProcess/con import { DeprecatedExtensionsCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/deprecatedExtensionsCleaner'; import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { join } from 'vs/base/common/path'; +import { TerminalIpcChannels } from 'vs/platform/terminal/common/terminal'; +import { LocalPtyService } from 'vs/platform/terminal/electron-browser/localPtyService'; +import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal'; +import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncServiceIpc'; class SharedProcessMain extends Disposable { @@ -142,13 +148,13 @@ class SharedProcessMain extends Disposable { // Log const mainRouter = new StaticRouter(ctx => ctx === 'main'); - const loggerClient = new LoggerChannelClient(this.server.getChannel('logger', mainRouter)); // we only use this for log levels + const logLevelClient = new LogLevelChannelClient(this.server.getChannel('logLevel', mainRouter)); // we only use this for log levels const multiplexLogger = this._register(new MultiplexLogService([ - this._register(new ConsoleLogService(this.configuration.logLevel)), - this._register(new SpdLogService('sharedprocess', environmentService.logsPath, this.configuration.logLevel)) + this._register(new ConsoleLogger(this.configuration.logLevel)), + this._register(new SpdLogLogger('sharedprocess', join(environmentService.logsPath, 'sharedprocess.log'), true, this.configuration.logLevel)) ])); - const logService = this._register(new FollowerLogService(loggerClient, multiplexLogger)); + const logService = this._register(new FollowerLogService(logLevelClient, multiplexLogger)); services.set(ILogService, logService); // Main Process @@ -168,8 +174,8 @@ class SharedProcessMain extends Disposable { await configurationService.initialize(); - // Storage - const storageService = new NativeStorageService(new GlobalStorageDatabaseChannelClient(mainProcessService.getChannel('storage')), logService, environmentService); + // Storage (global access only) + const storageService = new NativeStorageService2(undefined, mainProcessService, environmentService); services.set(IStorageService, storageService); await storageService.initialize(); @@ -182,7 +188,7 @@ class SharedProcessMain extends Disposable { services.set(IRequestService, new SyncDescriptor(RequestService)); // Native Host - const nativeHostService = createChannelSender(mainProcessService.getChannel('nativeHost'), { context: this.configuration.windowId }); + const nativeHostService = ProxyChannel.toService(mainProcessService.getChannel('nativeHost'), { context: this.configuration.windowId }); services.set(INativeHostService, nativeHostService); // Download @@ -256,51 +262,52 @@ class SharedProcessMain extends Disposable { services.set(IUserDataSyncResourceEnablementService, new SyncDescriptor(UserDataSyncResourceEnablementService)); services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService)); + // Terminal + const localPtyService = this._register(new LocalPtyService(logService)); + services.set(ILocalPtyService, localPtyService); + return new InstantiationService(services); } private initChannels(accessor: ServicesAccessor): void { // Extensions Management - const extensionManagementService = accessor.get(IExtensionManagementService); - const channel = new ExtensionManagementChannel(extensionManagementService, () => null); + const channel = new ExtensionManagementChannel(accessor.get(IExtensionManagementService), () => null); this.server.registerChannel('extensions', channel); // Localizations - const localizationsService = accessor.get(ILocalizationsService); - const localizationsChannel = createChannelReceiver(localizationsService); + const localizationsChannel = ProxyChannel.fromService(accessor.get(ILocalizationsService)); this.server.registerChannel('localizations', localizationsChannel); // Diagnostics - const diagnosticsService = accessor.get(IDiagnosticsService); - const diagnosticsChannel = createChannelReceiver(diagnosticsService); + const diagnosticsChannel = ProxyChannel.fromService(accessor.get(IDiagnosticsService)); this.server.registerChannel('diagnostics', diagnosticsChannel); // Extension Tips - const extensionTipsService = accessor.get(IExtensionTipsService); - const extensionTipsChannel = new ExtensionTipsChannel(extensionTipsService); + const extensionTipsChannel = new ExtensionTipsChannel(accessor.get(IExtensionTipsService)); this.server.registerChannel('extensionTipsService', extensionTipsChannel); // Settings Sync - const userDataSyncMachinesService = accessor.get(IUserDataSyncMachinesService); - const userDataSyncMachineChannel = new UserDataSyncMachinesServiceChannel(userDataSyncMachinesService); + const userDataSyncMachineChannel = new UserDataSyncMachinesServiceChannel(accessor.get(IUserDataSyncMachinesService)); this.server.registerChannel('userDataSyncMachines', userDataSyncMachineChannel); - const authTokenService = accessor.get(IUserDataSyncAccountService); - const authTokenChannel = new UserDataSyncAccountServiceChannel(authTokenService); - this.server.registerChannel('userDataSyncAccount', authTokenChannel); + const userDataSyncAccountChannel = new UserDataSyncAccountServiceChannel(accessor.get(IUserDataSyncAccountService)); + this.server.registerChannel('userDataSyncAccount', userDataSyncAccountChannel); - const userDataSyncStoreManagementService = accessor.get(IUserDataSyncStoreManagementService); - const userDataSyncStoreManagementChannel = new UserDataSyncStoreManagementServiceChannel(userDataSyncStoreManagementService); + const userDataSyncStoreManagementChannel = new UserDataSyncStoreManagementServiceChannel(accessor.get(IUserDataSyncStoreManagementService)); this.server.registerChannel('userDataSyncStoreManagement', userDataSyncStoreManagementChannel); - const userDataSyncService = accessor.get(IUserDataSyncService); - const userDataSyncChannel = new UserDataSyncChannel(this.server, userDataSyncService, accessor.get(ILogService)); + const userDataSyncChannel = new UserDataSyncChannel(accessor.get(IUserDataSyncService), accessor.get(ILogService)); this.server.registerChannel('userDataSync', userDataSyncChannel); const userDataAutoSync = this._register(accessor.get(IInstantiationService).createInstance(UserDataAutoSyncService)); const userDataAutoSyncChannel = new UserDataAutoSyncChannel(userDataAutoSync); this.server.registerChannel('userDataAutoSync', userDataAutoSyncChannel); + + // Terminal + const localPtyService = accessor.get(ILocalPtyService); + const localPtyChannel = ProxyChannel.fromService(localPtyService); + this.server.registerChannel(TerminalIpcChannels.LocalPty, localPtyChannel); } private registerErrorHandler(logService: ILogService): void { diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js index 64082146a..0c52f3cda 100644 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ b/src/vs/code/electron-browser/workbench/workbench.js @@ -6,9 +6,9 @@ /// //@ts-check -'use strict'; - (function () { + 'use strict'; + const bootstrapWindow = bootstrapWindowLib(); // Add a perf entry right from the top @@ -179,5 +179,4 @@ } //#endregion - }()); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 3f85072d5..dc9446a34 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -3,25 +3,26 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { app, ipcMain, systemPreferences, contentTracing, protocol, BrowserWindow, dialog, session } from 'electron'; import { release } from 'os'; +import { statSync } from 'fs'; +import { app, ipcMain, systemPreferences, contentTracing, protocol, BrowserWindow, dialog, session } from 'electron'; import { IProcessEnvironment, isWindows, isMacintosh, isLinux, isLinuxSnap } from 'vs/base/common/platform'; import { WindowsMainService } from 'vs/platform/windows/electron-main/windowsMainService'; import { IWindowOpenable } from 'vs/platform/windows/common/windows'; import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import { resolveShellEnv } from 'vs/code/node/shellEnv'; +import { resolveShellEnv } from 'vs/platform/environment/node/shellEnv'; import { IUpdateService } from 'vs/platform/update/common/update'; -import { UpdateChannel } from 'vs/platform/update/electron-main/updateIpc'; -import { getDelayedChannel, StaticRouter, createChannelReceiver, createChannelSender } from 'vs/base/parts/ipc/common/ipc'; +import { UpdateChannel } from 'vs/platform/update/common/updateIpc'; +import { getDelayedChannel, StaticRouter, ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron'; import { Server as NodeIPCServer } from 'vs/base/parts/ipc/node/ipc.net'; import { Client as MessagePortClient } from 'vs/base/parts/ipc/electron-main/ipc.mp'; -import { SharedProcess } from 'vs/code/electron-main/sharedProcess'; +import { SharedProcess } from 'vs/platform/sharedProcess/electron-main/sharedProcess'; import { LaunchMainService, ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { ILogService } from 'vs/platform/log/common/log'; +import { ILoggerService, ILogService } from 'vs/platform/log/common/log'; import { IStateService } from 'vs/platform/state/node/state'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -36,7 +37,7 @@ import product from 'vs/platform/product/common/product'; import { ProxyAuthHandler } from 'vs/code/electron-main/auth'; import { FileProtocolHandler } from 'vs/code/electron-main/protocol'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IWindowsMainService, ICodeWindow, OpenContext } from 'vs/platform/windows/electron-main/windows'; +import { IWindowsMainService, ICodeWindow, OpenContext, WindowError } from 'vs/platform/windows/electron-main/windows'; import { URI } from 'vs/base/common/uri'; import { hasWorkspaceFileExtension, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; @@ -45,28 +46,25 @@ import { Win32UpdateService } from 'vs/platform/update/electron-main/updateServi import { LinuxUpdateService } from 'vs/platform/update/electron-main/updateService.linux'; import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateService.darwin'; import { IssueMainService, IIssueMainService } from 'vs/platform/issue/electron-main/issueMainService'; -import { LoggerChannel } from 'vs/platform/log/common/logIpc'; +import { LoggerChannel, LogLevelChannel } from 'vs/platform/log/common/logIpc'; import { setUnexpectedErrorHandler, onUnexpectedError } from 'vs/base/common/errors'; import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener'; import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver'; import { IMenubarMainService, MenubarMainService } from 'vs/platform/menubar/electron-main/menubarMainService'; -import { RunOnceScheduler } from 'vs/base/common/async'; import { registerContextMenuListener } from 'vs/base/parts/contextmenu/electron-main/contextmenu'; import { sep, posix, join, isAbsolute } from 'vs/base/common/path'; import { joinPath } from 'vs/base/common/resources'; import { localize } from 'vs/nls'; import { Schemas } from 'vs/base/common/network'; import { SnapUpdateService } from 'vs/platform/update/electron-main/updateService.snap'; -import { IStorageMainService, StorageMainService } from 'vs/platform/storage/node/storageMainService'; -import { GlobalStorageDatabaseChannel } from 'vs/platform/storage/node/storageIpc'; +import { IStorageMainService, StorageMainService } from 'vs/platform/storage/electron-main/storageMainService'; +import { StorageDatabaseChannel } from 'vs/platform/storage/electron-main/storageIpc'; import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; import { WorkspacesHistoryMainService, IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; import { NativeURLService } from 'vs/platform/url/common/urlService'; import { WorkspacesManagementMainService, IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; -import { statSync } from 'fs'; -import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; -import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; +import { IDiagnosticsService } from 'vs/platform/diagnostics/common/diagnostics'; import { ElectronExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/electron-main/extensionHostDebugIpc'; import { INativeHostMainService, NativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService'; import { IDialogMainService, DialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService'; @@ -90,17 +88,20 @@ import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/commo import { ExtensionUrlTrustService } from 'vs/platform/extensionManagement/node/extensionUrlTrustService'; import { once } from 'vs/base/common/functional'; +/** + * The main VS Code application. There will only ever be one instance, + * even if the user starts many instances (e.g. from the command line). + */ export class CodeApplication extends Disposable { private windowsMainService: IWindowsMainService | undefined; - private dialogMainService: IDialogMainService | undefined; private nativeHostMainService: INativeHostMainService | undefined; constructor( - private readonly mainIpcServer: NodeIPCServer, + private readonly mainProcessNodeIpcServer: NodeIPCServer, private readonly userEnv: IProcessEnvironment, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService private readonly mainInstantiationService: IInstantiationService, @ILogService private readonly logService: ILogService, - @IEnvironmentMainService private readonly environmentService: IEnvironmentMainService, + @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IConfigurationService private readonly configurationService: IConfigurationService, @IStateService private readonly stateService: IStateService, @@ -166,7 +167,7 @@ export class CodeApplication extends Disposable { event.preventDefault(); }); app.on('remote-get-current-web-contents', event => { - if (this.environmentService.args.driver) { + if (this.environmentMainService.args.driver) { return; // the driver needs access to web contents } @@ -188,7 +189,7 @@ export class CodeApplication extends Disposable { } const srcUri = uri.fsPath.toLowerCase(); - const rootUri = URI.file(this.environmentService.appRoot).fsPath.toLowerCase(); + const rootUri = URI.file(this.environmentMainService.appRoot).fsPath.toLowerCase(); return srcUri.startsWith(rootUri + sep); }; @@ -223,11 +224,19 @@ export class CodeApplication extends Disposable { this.nativeHostMainService?.openExternal(undefined, url); }); - session.defaultSession.setPermissionRequestHandler((webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback) => { + const webviewFrameUrl = 'about:blank?webviewFrame'; + + session.defaultSession.setPermissionRequestHandler((_webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback, details) => { + if (details.requestingUrl === webviewFrameUrl) { + return callback(permission === 'clipboard-read'); + } return callback(false); }); - session.defaultSession.setPermissionCheckHandler((webContents, permission /* 'media' */) => { + session.defaultSession.setPermissionCheckHandler((_webContents, permission /* 'media' */, _origin, details) => { + if (details.requestingUrl === webviewFrameUrl) { + return permission === 'clipboard-read'; + } return false; }); }); @@ -253,7 +262,7 @@ export class CodeApplication extends Disposable { runningTimeout = setTimeout(() => { this.windowsMainService?.open({ context: OpenContext.DOCK /* can also be opening from finder while app is running */, - cli: this.environmentService.args, + cli: this.environmentMainService.args, urisToOpen: macOpenFileURIs, gotoLineMode: false, preferNewWindow: true /* dropping on the dock or opening from finder prefers to open in a new window */ @@ -326,7 +335,7 @@ export class CodeApplication extends Disposable { args = window.config; env = { ...process.env, ...window.config.userEnv }; } else { - args = this.environmentService.args; + args = this.environmentMainService.args; env = process.env; } @@ -374,7 +383,7 @@ export class CodeApplication extends Disposable { } } - if (typeof path !== 'string' || !isAbsolute(path) || !isEqualOrParent(path, this.environmentService.cachedLanguagesPath, !isLinux)) { + if (typeof path !== 'string' || !isAbsolute(path) || !isEqualOrParent(path, this.environmentMainService.cachedLanguagesPath, !isLinux)) { return undefined; } @@ -386,7 +395,7 @@ export class CodeApplication extends Disposable { // take only the message and stack property const friendlyError = { - message: err.message, + message: `[uncaught exception in main]: ${err.message}`, stack: err.stack }; @@ -402,8 +411,8 @@ export class CodeApplication extends Disposable { async startup(): Promise { this.logService.debug('Starting VS Code'); - this.logService.debug(`from: ${this.environmentService.appRoot}`); - this.logService.debug('args:', this.environmentService.args); + this.logService.debug(`from: ${this.environmentMainService.appRoot}`); + this.logService.debug('args:', this.environmentMainService.args); // Make sure we associate the program with the app user model id // This will help Windows to associate the running program with @@ -429,61 +438,45 @@ export class CodeApplication extends Disposable { } // Setup Protocol Handler - const fileProtocolHandler = this._register(this.instantiationService.createInstance(FileProtocolHandler)); + const fileProtocolHandler = this._register(this.mainInstantiationService.createInstance(FileProtocolHandler)); - // Create Electron IPC Server - const electronIpcServer = new ElectronIPCServer(); + // Main process server (electron IPC based) + const mainProcessElectronServer = new ElectronIPCServer(); // Resolve unique machine ID this.logService.trace('Resolving machine identifier...'); const machineId = await this.resolveMachineId(); this.logService.trace(`Resolved machine identifier: ${machineId}`); - // Spawn shared process after the first window has opened and 3s have passed - const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv); - const sharedProcessClient = (async () => { - this.logService.trace('Main->SharedProcess#connect'); - - const port = await sharedProcess.connect(); - - this.logService.trace('Main->SharedProcess#connect: connection established'); - - return new MessagePortClient(port, 'main'); - })(); - const sharedProcessReady = (async () => { - await sharedProcess.whenReady(); - - return sharedProcessClient; - })(); - this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => { - this._register(new RunOnceScheduler(async () => { - sharedProcess.spawn(await resolveShellEnv(this.logService, this.environmentService.args, process.env)); - }, 3000)).schedule(); - }); + // Shared process + const { sharedProcess, sharedProcessReady, sharedProcessClient } = this.setupSharedProcess(machineId); // Services - const appInstantiationService = await this.createServices(machineId, sharedProcess, sharedProcessReady); + const appInstantiationService = await this.initServices(machineId, sharedProcess, sharedProcessReady); // Create driver - if (this.environmentService.driverHandle) { - const server = await serveDriver(electronIpcServer, this.environmentService.driverHandle, this.environmentService, appInstantiationService); + if (this.environmentMainService.driverHandle) { + const server = await serveDriver(mainProcessElectronServer, this.environmentMainService.driverHandle, this.environmentMainService, appInstantiationService); - this.logService.info('Driver started at:', this.environmentService.driverHandle); + this.logService.info('Driver started at:', this.environmentMainService.driverHandle); this._register(server); } // Setup Auth Handler this._register(appInstantiationService.createInstance(ProxyAuthHandler)); + // Init Channels + appInstantiationService.invokeFunction(accessor => this.initChannels(accessor, mainProcessElectronServer, sharedProcessClient)); + // Open Windows - const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient, fileProtocolHandler)); + const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, mainProcessElectronServer, fileProtocolHandler)); // Post Open Windows Tasks - appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor)); + appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor, sharedProcess)); // Tracing: Stop tracing after windows are ready if enabled - if (this.environmentService.args.trace) { - this.stopTracingEventually(windows); + if (this.environmentMainService.args.trace) { + appInstantiationService.invokeFunction(accessor => this.stopTracingEventually(accessor, windows)); } } @@ -501,9 +494,32 @@ export class CodeApplication extends Disposable { return machineId; } - private async createServices(machineId: string, sharedProcess: SharedProcess, sharedProcessReady: Promise): Promise { + private setupSharedProcess(machineId: string): { sharedProcess: SharedProcess, sharedProcessReady: Promise, sharedProcessClient: Promise } { + const sharedProcess = this._register(this.mainInstantiationService.createInstance(SharedProcess, machineId, this.userEnv)); + + const sharedProcessClient = (async () => { + this.logService.trace('Main->SharedProcess#connect'); + + const port = await sharedProcess.connect(); + + this.logService.trace('Main->SharedProcess#connect: connection established'); + + return new MessagePortClient(port, 'main'); + })(); + + const sharedProcessReady = (async () => { + await sharedProcess.whenReady(); + + return sharedProcessClient; + })(); + + return { sharedProcess, sharedProcessReady, sharedProcessClient }; + } + + private async initServices(machineId: string, sharedProcess: SharedProcess, sharedProcessReady: Promise): Promise { const services = new ServiceCollection(); + // Update switch (process.platform) { case 'win32': services.set(IUpdateService, new SyncDescriptor(Win32UpdateService)); @@ -522,38 +538,63 @@ export class CodeApplication extends Disposable { break; } + // Windows services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, this.userEnv])); - services.set(IDialogMainService, new SyncDescriptor(DialogMainService)); - services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService)); - services.set(IDiagnosticsService, createChannelSender(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics'))))); + // Dialogs + services.set(IDialogMainService, new SyncDescriptor(DialogMainService)); + + // Launch + services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService)); + + // Diagnostics + services.set(IDiagnosticsService, ProxyChannel.toService(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics'))))); + + // Issues services.set(IIssueMainService, new SyncDescriptor(IssueMainService, [machineId, this.userEnv])); + + // Encryption services.set(IEncryptionMainService, new SyncDescriptor(EncryptionMainService, [machineId])); + + // Keyboard Layout services.set(IKeyboardLayoutMainService, new SyncDescriptor(KeyboardLayoutMainService)); + + // Display services.set(IDisplayMainService, new SyncDescriptor(DisplayMainService)); + + // Native Host services.set(INativeHostMainService, new SyncDescriptor(NativeHostMainService, [sharedProcess])); + + // Webview Manager services.set(IWebviewManagerService, new SyncDescriptor(WebviewMainService)); + + // Workspaces services.set(IWorkspacesService, new SyncDescriptor(WorkspacesMainService)); + services.set(IWorkspacesManagementMainService, new SyncDescriptor(WorkspacesManagementMainService)); + services.set(IWorkspacesHistoryMainService, new SyncDescriptor(WorkspacesHistoryMainService)); + + // Menubar services.set(IMenubarMainService, new SyncDescriptor(MenubarMainService)); + + // Extension URL Trust services.set(IExtensionUrlTrustService, new SyncDescriptor(ExtensionUrlTrustService)); - const storageMainService = new StorageMainService(this.logService, this.environmentService); - services.set(IStorageMainService, storageMainService); - this.lifecycleMainService.onWillShutdown(e => e.join(storageMainService.close())); + // Storage + services.set(IStorageMainService, new SyncDescriptor(StorageMainService)); - const backupMainService = new BackupMainService(this.environmentService, this.configurationService, this.logService); + // Backups + const backupMainService = new BackupMainService(this.environmentMainService, this.configurationService, this.logService); services.set(IBackupMainService, backupMainService); - services.set(IWorkspacesHistoryMainService, new SyncDescriptor(WorkspacesHistoryMainService)); + // URL handling services.set(IURLService, new SyncDescriptor(NativeURLService)); - services.set(IWorkspacesManagementMainService, new SyncDescriptor(WorkspacesManagementMainService)); // Telemetry - if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) { + if (!this.environmentMainService.isExtensionDevelopment && !this.environmentMainService.args['disable-telemetry'] && !!product.enableTelemetry) { const channel = getDelayedChannel(sharedProcessReady.then(client => client.getChannel('telemetryAppender'))); const appender = new TelemetryAppenderClient(channel); - const commonProperties = resolveCommonProperties(this.fileService, release(), process.arch, product.commit, product.version, machineId, product.msftInternalDomains, this.environmentService.installSourcePath); - const piiPaths = [this.environmentService.appRoot, this.environmentService.extensionsPath]; + const commonProperties = resolveCommonProperties(this.fileService, release(), process.arch, product.commit, product.version, machineId, product.msftInternalDomains, this.environmentMainService.installSourcePath); + const piiPaths = [this.environmentMainService.appRoot, this.environmentMainService.extensionsPath]; const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, sendErrorTelemetry: true }; services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config])); @@ -564,123 +605,99 @@ export class CodeApplication extends Disposable { // Init services that require it await backupMainService.initialize(); - return this.instantiationService.createChild(services); + return this.mainInstantiationService.createChild(services); } - private stopTracingEventually(windows: ICodeWindow[]): void { - this.logService.info(`Tracing: waiting for windows to get ready...`); + private initChannels(accessor: ServicesAccessor, mainProcessElectronServer: ElectronIPCServer, sharedProcessClient: Promise): void { - let recordingStopped = false; - const stopRecording = async (timeout: boolean) => { - if (recordingStopped) { - return; - } + // Launch: this one is explicitly registered to the node.js + // server because when a second instance starts up, that is + // the only possible connection between the first and the + // second instance. Electron IPC does not work across apps. + const launchChannel = ProxyChannel.fromService(accessor.get(ILaunchMainService), { disableMarshalling: true }); + this.mainProcessNodeIpcServer.registerChannel('launch', launchChannel); - recordingStopped = true; // only once + // Update + const updateChannel = new UpdateChannel(accessor.get(IUpdateService)); + mainProcessElectronServer.registerChannel('update', updateChannel); - const path = await contentTracing.stopRecording(joinPath(this.environmentService.userHome, `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`).fsPath); + // Issues + const issueChannel = ProxyChannel.fromService(accessor.get(IIssueMainService)); + mainProcessElectronServer.registerChannel('issue', issueChannel); - if (!timeout) { - this.dialogMainService?.showMessageBox({ - type: 'info', - message: localize('trace.message', "Successfully created trace."), - detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path), - buttons: [localize('trace.ok', "OK")] - }, withNullAsUndefined(BrowserWindow.getFocusedWindow())); - } else { - this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`); - } - }; + // Encryption + const encryptionChannel = ProxyChannel.fromService(accessor.get(IEncryptionMainService)); + mainProcessElectronServer.registerChannel('encryption', encryptionChannel); - // Wait up to 30s before creating the trace anyways - const timeoutHandle = setTimeout(() => stopRecording(true), 30000); + // Keyboard Layout + const keyboardLayoutChannel = ProxyChannel.fromService(accessor.get(IKeyboardLayoutMainService)); + mainProcessElectronServer.registerChannel('keyboardLayout', keyboardLayoutChannel); - // Wait for all windows to get ready and stop tracing then - Promise.all(windows.map(window => window.ready())).then(() => { - clearTimeout(timeoutHandle); - stopRecording(false); - }); - } + // Display + const displayChannel = ProxyChannel.fromService(accessor.get(IDisplayMainService)); + mainProcessElectronServer.registerChannel('display', displayChannel); - private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise, fileProtocolHandler: FileProtocolHandler): ICodeWindow[] { - - // Register more Main IPC services - const launchMainService = accessor.get(ILaunchMainService); - const launchChannel = createChannelReceiver(launchMainService, { disableMarshalling: true }); - this.mainIpcServer.registerChannel('launch', launchChannel); - - // Register more Electron IPC services - const updateService = accessor.get(IUpdateService); - const updateChannel = new UpdateChannel(updateService); - electronIpcServer.registerChannel('update', updateChannel); - - const issueMainService = accessor.get(IIssueMainService); - const issueChannel = createChannelReceiver(issueMainService); - electronIpcServer.registerChannel('issue', issueChannel); - - const encryptionMainService = accessor.get(IEncryptionMainService); - const encryptionChannel = createChannelReceiver(encryptionMainService); - electronIpcServer.registerChannel('encryption', encryptionChannel); - - const keyboardLayoutMainService = accessor.get(IKeyboardLayoutMainService); - const keyboardLayoutChannel = createChannelReceiver(keyboardLayoutMainService); - electronIpcServer.registerChannel('keyboardLayout', keyboardLayoutChannel); - - const displayMainService = accessor.get(IDisplayMainService); - const displayChannel = createChannelReceiver(displayMainService); - electronIpcServer.registerChannel('display', displayChannel); - - const nativeHostMainService = this.nativeHostMainService = accessor.get(INativeHostMainService); - const nativeHostChannel = createChannelReceiver(this.nativeHostMainService); - electronIpcServer.registerChannel('nativeHost', nativeHostChannel); + // Native host (main & shared process) + this.nativeHostMainService = accessor.get(INativeHostMainService); + const nativeHostChannel = ProxyChannel.fromService(this.nativeHostMainService); + mainProcessElectronServer.registerChannel('nativeHost', nativeHostChannel); sharedProcessClient.then(client => client.registerChannel('nativeHost', nativeHostChannel)); - const workspacesService = accessor.get(IWorkspacesService); - const workspacesChannel = createChannelReceiver(workspacesService); - electronIpcServer.registerChannel('workspaces', workspacesChannel); + // Workspaces + const workspacesChannel = ProxyChannel.fromService(accessor.get(IWorkspacesService)); + mainProcessElectronServer.registerChannel('workspaces', workspacesChannel); - const menubarMainService = accessor.get(IMenubarMainService); - const menubarChannel = createChannelReceiver(menubarMainService); - electronIpcServer.registerChannel('menubar', menubarChannel); + // Menubar + const menubarChannel = ProxyChannel.fromService(accessor.get(IMenubarMainService)); + mainProcessElectronServer.registerChannel('menubar', menubarChannel); - const urlService = accessor.get(IURLService); - const urlChannel = createChannelReceiver(urlService); - electronIpcServer.registerChannel('url', urlChannel); + // URL handling + const urlChannel = ProxyChannel.fromService(accessor.get(IURLService)); + mainProcessElectronServer.registerChannel('url', urlChannel); - const extensionUrlTrustService = accessor.get(IExtensionUrlTrustService); - const extensionUrlTrustChannel = createChannelReceiver(extensionUrlTrustService); - electronIpcServer.registerChannel('extensionUrlTrust', extensionUrlTrustChannel); + // Extension URL Trust + const extensionUrlTrustChannel = ProxyChannel.fromService(accessor.get(IExtensionUrlTrustService)); + mainProcessElectronServer.registerChannel('extensionUrlTrust', extensionUrlTrustChannel); - const webviewManagerService = accessor.get(IWebviewManagerService); - const webviewChannel = createChannelReceiver(webviewManagerService); - electronIpcServer.registerChannel('webview', webviewChannel); + // Webview Manager + const webviewChannel = ProxyChannel.fromService(accessor.get(IWebviewManagerService)); + mainProcessElectronServer.registerChannel('webview', webviewChannel); - const storageMainService = accessor.get(IStorageMainService); - const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService)); - electronIpcServer.registerChannel('storage', storageChannel); + // Storage (main & shared process) + const storageChannel = this._register(new StorageDatabaseChannel(this.logService, accessor.get(IStorageMainService))); + mainProcessElectronServer.registerChannel('storage', storageChannel); sharedProcessClient.then(client => client.registerChannel('storage', storageChannel)); - const loggerChannel = new LoggerChannel(accessor.get(ILogService)); - electronIpcServer.registerChannel('logger', loggerChannel); - sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel)); + // Log Level (main & shared process) + const logLevelChannel = new LogLevelChannel(accessor.get(ILogService)); + mainProcessElectronServer.registerChannel('logLevel', logLevelChannel); + sharedProcessClient.then(client => client.registerChannel('logLevel', logLevelChannel)); + // Logger + const loggerChannel = new LoggerChannel(accessor.get(ILoggerService),); + mainProcessElectronServer.registerChannel('logger', loggerChannel); + + // Extension Host Debug Broadcasting + const electronExtensionHostDebugBroadcastChannel = new ElectronExtensionHostDebugBroadcastChannel(accessor.get(IWindowsMainService)); + mainProcessElectronServer.registerChannel('extensionhostdebugservice', electronExtensionHostDebugBroadcastChannel); + } + + private openFirstWindow(accessor: ServicesAccessor, mainProcessElectronServer: ElectronIPCServer, fileProtocolHandler: FileProtocolHandler): ICodeWindow[] { const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); - fileProtocolHandler.injectWindowsMainService(windowsMainService); - - // ExtensionHost Debug broadcast service - electronIpcServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ElectronExtensionHostDebugBroadcastChannel(windowsMainService)); + const urlService = accessor.get(IURLService); + const nativeHostMainService = accessor.get(INativeHostMainService); // Signal phase: ready (services set) this.lifecycleMainService.phase = LifecycleMainPhase.Ready; - // Propagate to clients - this.dialogMainService = accessor.get(IDialogMainService); + // Forward windows main service to protocol handler + fileProtocolHandler.injectWindowsMainService(this.windowsMainService); // Check for initial URLs to handle from protocol link invocations const pendingWindowOpenablesFromProtocolLinks: IWindowOpenable[] = []; const pendingProtocolLinksToHandle = [ // Windows/Linux: protocol handler invokes CLI with --open-url - ...this.environmentService.args['open-url'] ? this.environmentService.args._urls || [] : [], + ...this.environmentMainService.args['open-url'] ? this.environmentMainService.args._urls || [] : [], // macOS: open-url events ...((global).getOpenUrls() || []) as string[] @@ -717,7 +734,7 @@ export class CodeApplication extends Disposable { // or open new windows. The URL handler will be invoked from // protocol invocations outside of VSCode. const app = this; - const environmentService = this.environmentService; + const environmentService = this.environmentMainService; urlService.registerHandler({ async handleURL(uri: URI, options?: IOpenURLOptions): Promise { @@ -768,14 +785,14 @@ export class CodeApplication extends Disposable { })); const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id)); const urlHandlerRouter = new URLHandlerRouter(activeWindowRouter); - const urlHandlerChannel = electronIpcServer.getChannel('urlHandler', urlHandlerRouter); + const urlHandlerChannel = mainProcessElectronServer.getChannel('urlHandler', urlHandlerRouter); urlService.registerHandler(new URLHandlerChannelClient(urlHandlerChannel)); // Watch Electron URLs and forward them to the UrlService - this._register(new ElectronURLListener(pendingProtocolLinksToHandle, urlService, windowsMainService, this.environmentService)); + this._register(new ElectronURLListener(pendingProtocolLinksToHandle, urlService, windowsMainService, this.environmentMainService)); // Open our first window - const args = this.environmentService.args; + const args = this.environmentMainService.args; const macOpenFiles: string[] = (global).macOpenFiles; const context = isLaunchedFromCli(process.env) ? OpenContext.CLI : OpenContext.DESKTOP; const hasCliArgs = args._.length; @@ -845,7 +862,7 @@ export class CodeApplication extends Disposable { mnemonicButtonLabel(localize({ key: 'cancel', comment: ['&& denotes a mnemonic'] }, "&&No")), ], cancelId: 1, - message: localize('confirmOpenMessage', "An external application wants to open '{0}' in {1}. Do you want to open this file or folder?", getPathLabel(uri.fsPath, this.environmentService), product.nameShort), + message: localize('confirmOpenMessage', "An external application wants to open '{0}' in {1}. Do you want to open this file or folder?", getPathLabel(uri.fsPath, this.environmentMainService), product.nameShort), detail: localize('confirmOpenDetail', "If you did not initiate this request, it may represent an attempted attack on your system. Unless you took an explicit action to initiate this request, you should press 'No'"), noLink: true }); @@ -909,11 +926,36 @@ export class CodeApplication extends Disposable { return { fileUri: URI.file(path) }; } - private async afterWindowOpen(accessor: ServicesAccessor): Promise { + private async afterWindowOpen(accessor: ServicesAccessor, sharedProcess: SharedProcess): Promise { // Signal phase: after window open this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen; + // Observe shared process for errors + const telemetryService = accessor.get(ITelemetryService); + this._register(sharedProcess.onDidError(({ type, details }) => { + + // Logging + let message: string; + if (typeof details === 'string') { + message = details; + } else { + message = `SharedProcess: crashed (detail: ${details.reason})`; + } + onUnexpectedError(new Error(message)); + + // Telemetry + type SharedProcessErrorClassification = { + type: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true }; + reason: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true }; + }; + type SharedProcessErrorEvent = { + type: WindowError; + reason: string | undefined; + }; + telemetryService.publicLog2('sharedprocesserror', { type, reason: typeof details !== 'string' ? details.reason : undefined }); + })); + // Windows: install mutex const win32MutexName = product.win32MutexName; if (isWindows && win32MutexName) { @@ -941,13 +983,13 @@ export class CodeApplication extends Disposable { } // Start to fetch shell environment (if needed) after window has opened - resolveShellEnv(this.logService, this.environmentService.args, process.env); + resolveShellEnv(this.logService, this.environmentMainService.args, process.env); // If enable-crash-reporter argv is undefined then this is a fresh start, // based on telemetry.enableCrashreporter settings, generate a UUID which // will be used as crash reporter id and also update the json file. try { - const argvContent = await this.fileService.readFile(this.environmentService.argvResource); + const argvContent = await this.fileService.readFile(this.environmentMainService.argvResource); const argvString = argvContent.value.toString(); const argvJSON = JSON.parse(stripComments(argvString)); if (argvJSON['enable-crash-reporter'] === undefined) { @@ -965,10 +1007,47 @@ export class CodeApplication extends Disposable { ]; const newArgvString = argvString.substring(0, argvString.length - 2).concat(',\n', additionalArgvContent.join('\n')); - await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(newArgvString)); + await this.fileService.writeFile(this.environmentMainService.argvResource, VSBuffer.fromString(newArgvString)); } } catch (error) { this.logService.error(error); } } + + private stopTracingEventually(accessor: ServicesAccessor, windows: ICodeWindow[]): void { + this.logService.info(`Tracing: waiting for windows to get ready...`); + + const dialogMainService = accessor.get(IDialogMainService); + + let recordingStopped = false; + const stopRecording = async (timeout: boolean) => { + if (recordingStopped) { + return; + } + + recordingStopped = true; // only once + + const path = await contentTracing.stopRecording(joinPath(this.environmentMainService.userHome, `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`).fsPath); + + if (!timeout) { + dialogMainService.showMessageBox({ + type: 'info', + message: localize('trace.message', "Successfully created trace."), + detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path), + buttons: [localize('trace.ok', "OK")] + }, withNullAsUndefined(BrowserWindow.getFocusedWindow())); + } else { + this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`); + } + }; + + // Wait up to 30s before creating the trace anyways + const timeoutHandle = setTimeout(() => stopRecording(true), 30000); + + // Wait for all windows to get ready and stop tracing then + Promise.all(windows.map(window => window.ready())).then(() => { + clearTimeout(timeoutHandle); + stopRecording(false); + }); + } } diff --git a/src/vs/code/electron-main/auth.ts b/src/vs/code/electron-main/auth.ts index b9669f983..70df692cf 100644 --- a/src/vs/code/electron-main/auth.ts +++ b/src/vs/code/electron-main/auth.ts @@ -201,7 +201,7 @@ export class ProxyAuthHandler extends Disposable { const proxyAuthResponseHandler = async (event: ElectronEvent, channel: string, reply: Credentials & { remember: boolean } | undefined /* canceled */) => { if (channel === payload.replyChannel) { this.logService.trace(`auth#doResolveProxyCredentials - exit - received credentials from window ${window.id}`); - window.win.webContents.off('ipc-message', proxyAuthResponseHandler); + window.win?.webContents.off('ipc-message', proxyAuthResponseHandler); // We got credentials from the window if (reply) { @@ -229,7 +229,7 @@ export class ProxyAuthHandler extends Disposable { } }; - window.win.webContents.on('ipc-message', proxyAuthResponseHandler); + window.win?.webContents.on('ipc-message', proxyAuthResponseHandler); }); // Remember credentials for the session in case diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index f0d8b6d2d..7cbdd078f 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -5,15 +5,14 @@ import 'vs/platform/update/common/update.config.contribution'; import { app, dialog } from 'electron'; -import { unlinkSync } from 'fs'; +import { promises, unlinkSync } from 'fs'; import { localize } from 'vs/nls'; import { isWindows, IProcessEnvironment, isMacintosh } from 'vs/base/common/platform'; import product from 'vs/platform/product/common/product'; import { parseMainProcessArgv, addArg } from 'vs/platform/environment/node/argvHelper'; -import { createWaitMarkerFile } from 'vs/platform/environment/node/waitMarkerFile'; -import { mkdirp } from 'vs/base/node/pfs'; +import { createWaitMarkerFile } from 'vs/platform/environment/node/wait'; import { LifecycleMainService, ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import { createChannelSender } from 'vs/base/parts/ipc/common/ipc'; +import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { Server as NodeIPCServer, serve as nodeIPCServe, connect as nodeIPCConnect, XDG_RUNTIME_DIR } from 'vs/base/parts/ipc/node/ipc.net'; import { Client as NodeIPCClient } from 'vs/base/parts/ipc/common/ipc.net'; import { ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService'; @@ -21,7 +20,7 @@ import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiati import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { ILogService, ConsoleLogMainService, MultiplexLogService, getLogLevel } from 'vs/platform/log/common/log'; +import { ILogService, ConsoleMainLogger, MultiplexLogService, getLogLevel, ILoggerService } from 'vs/platform/log/common/log'; import { StateService } from 'vs/platform/state/node/stateService'; import { IStateService } from 'vs/platform/state/node/state'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -32,9 +31,9 @@ import { IRequestService } from 'vs/platform/request/common/request'; import { RequestMainService } from 'vs/platform/request/electron-main/requestMainService'; import { CodeApplication } from 'vs/code/electron-main/app'; import { getPathLabel, mnemonicButtonLabel } from 'vs/base/common/labels'; -import { SpdLogService } from 'vs/platform/log/node/spdlogService'; +import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; import { BufferLogService } from 'vs/platform/log/common/bufferLog'; -import { setUnexpectedErrorHandler } from 'vs/base/common/errors'; +import { ExpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { once } from 'vs/base/common/functional'; import { ISignService } from 'vs/platform/sign/common/sign'; @@ -48,158 +47,143 @@ import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { TunnelService } from 'vs/platform/remote/node/tunnelService'; import { IProductService } from 'vs/platform/product/common/productService'; import { IPathWithLineAndColumn, isValidBasename, parseLineAndColumnAware, sanitizeFilePath } from 'vs/base/common/extpath'; -import { isNumber } from 'vs/base/common/types'; import { rtrim, trim } from 'vs/base/common/strings'; -import { basename, resolve } from 'vs/base/common/path'; +import { basename, join, resolve } from 'vs/base/common/path'; import { coalesce, distinct } from 'vs/base/common/arrays'; import { EnvironmentMainService, IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; +import { LoggerService } from 'vs/platform/log/node/loggerService'; -class ExpectedError extends Error { - readonly isExpected = true; -} - +/** + * The main VS Code entry point. + * + * Note: This class can exist more than once for example when VS Code is already + * running and a second instance is started from the command line. It will always + * try to communicate with an existing instance to prevent that 2 VS Code instances + * are running at the same time. + */ class CodeMain { main(): void { + try { + this.startup(); + } catch (error) { + console.error(error.message); + app.exit(1); + } + } + + private async startup(): Promise { // Set the error handler early enough so that we are not getting the // default electron error dialog popping up setUnexpectedErrorHandler(err => console.error(err)); - // Parse arguments - let args: NativeParsedArgs; - try { - args = parseMainProcessArgv(process.argv); - args = this.validatePaths(args); - } catch (err) { - console.error(err.message); - app.exit(1); + // Resolve command line arguments + const args = this.resolveArgs(); - return; - } + // Create services + const [instantiationService, instanceEnvironment, environmentService, configurationService, stateService, bufferLogService] = this.createServices(args); - // If we are started with --wait create a random temporary file - // and pass it over to the starting instance. We can use this file - // to wait for it to be deleted to monitor that the edited file - // is closed and then exit the waiting process. - // - // Note: we are not doing this if the wait marker has been already - // added as argument. This can happen if Code was started from CLI. - if (args.wait && !args.waitMarkerFilePath) { - const waitMarkerFilePath = createWaitMarkerFile(args.verbose); - if (waitMarkerFilePath) { - addArg(process.argv, '--waitMarkerFilePath', waitMarkerFilePath); - args.waitMarkerFilePath = waitMarkerFilePath; - } - } - - // Launch - this.startup(args); - } - - private async startup(args: NativeParsedArgs): Promise { - - // We need to buffer the spdlog logs until we are sure - // we are the only instance running, otherwise we'll have concurrent - // log file access on Windows (https://github.com/microsoft/vscode/issues/41218) - const bufferLogService = new BufferLogService(); - - const [instantiationService, instanceEnvironment, environmentService] = this.createServices(args, bufferLogService); try { // Init services - await instantiationService.invokeFunction(async accessor => { - const configurationService = accessor.get(IConfigurationService); - const stateService = accessor.get(IStateService); + try { + await this.initServices(environmentService, configurationService, stateService); + } catch (error) { - try { - await this.initServices(environmentService, configurationService as ConfigurationService, stateService as StateService); - } catch (error) { + // Show a dialog for errors that can be resolved by the user + this.handleStartupDataDirError(environmentService, error); - // Show a dialog for errors that can be resolved by the user - this.handleStartupDataDirError(environmentService, error); - - throw error; - } - }); + throw error; + } // Startup await instantiationService.invokeFunction(async accessor => { const logService = accessor.get(ILogService); const lifecycleMainService = accessor.get(ILifecycleMainService); const fileService = accessor.get(IFileService); - const configurationService = accessor.get(IConfigurationService); - const mainIpcServer = await this.doStartup(args, logService, environmentService, lifecycleMainService, instantiationService, true); + // Create the main IPC server by trying to be the server + // If this throws an error it means we are not the first + // instance of VS Code running and so we would quit. + const mainProcessNodeIpcServer = await this.doStartup(args, logService, environmentService, lifecycleMainService, instantiationService, true); - bufferLogService.logger = new SpdLogService('main', environmentService.logsPath, bufferLogService.getLevel()); + // Delay creation of spdlog for perf reasons (https://github.com/microsoft/vscode/issues/72906) + bufferLogService.logger = new SpdLogLogger('main', join(environmentService.logsPath, 'main.log'), true, bufferLogService.getLevel()); + + // Lifecycle once(lifecycleMainService.onWillShutdown)(() => { fileService.dispose(); - (configurationService as ConfigurationService).dispose(); + configurationService.dispose(); }); - return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup(); + return instantiationService.createInstance(CodeApplication, mainProcessNodeIpcServer, instanceEnvironment).startup(); }); } catch (error) { instantiationService.invokeFunction(this.quit, error); } } - private createServices(args: NativeParsedArgs, bufferLogService: BufferLogService): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService] { + private createServices(args: NativeParsedArgs): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, ConfigurationService, StateService, BufferLogService] { const services = new ServiceCollection(); + // Environment const environmentService = new EnvironmentMainService(args); const instanceEnvironment = this.patchEnvironment(environmentService); // Patch `process.env` with the instance's environment services.set(IEnvironmentService, environmentService); services.set(IEnvironmentMainService, environmentService); - const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]); + // Log: We need to buffer the spdlog logs until we are sure + // we are the only instance running, otherwise we'll have concurrent + // log file access on Windows (https://github.com/microsoft/vscode/issues/41218) + const bufferLogService = new BufferLogService(); + const logService = new MultiplexLogService([new ConsoleMainLogger(getLogLevel(environmentService)), bufferLogService]); process.once('exit', () => logService.dispose()); services.set(ILogService, logService); + // Files const fileService = new FileService(logService); services.set(IFileService, fileService); const diskFileSystemProvider = new DiskFileSystemProvider(logService); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - services.set(IConfigurationService, new ConfigurationService(environmentService.settingsResource, fileService)); + // Logger + services.set(ILoggerService, new LoggerService(logService, fileService)); + + // Configuration + const configurationService = new ConfigurationService(environmentService.settingsResource, fileService); + services.set(IConfigurationService, configurationService); + + // Lifecycle services.set(ILifecycleMainService, new SyncDescriptor(LifecycleMainService)); - services.set(IStateService, new SyncDescriptor(StateService)); + + // State + const stateService = new StateService(environmentService, logService); + services.set(IStateService, stateService); + + // Request services.set(IRequestService, new SyncDescriptor(RequestMainService)); + + // Themes services.set(IThemeMainService, new SyncDescriptor(ThemeMainService)); + + // Signing services.set(ISignService, new SyncDescriptor(SignService)); + + // Product services.set(IProductService, { _serviceBrand: undefined, ...product }); + + // Tunnel services.set(ITunnelService, new SyncDescriptor(TunnelService)); - return [new InstantiationService(services, true), instanceEnvironment, environmentService]; + return [new InstantiationService(services, true), instanceEnvironment, environmentService, configurationService, stateService, bufferLogService]; } - private initServices(environmentService: IEnvironmentMainService, configurationService: ConfigurationService, stateService: StateService): Promise { - - // Environment service (paths) - const environmentServiceInitialization = Promise.all([ - environmentService.extensionsPath, - environmentService.nodeCachedDataDir, - environmentService.logsPath, - environmentService.globalStorageHome.fsPath, - environmentService.workspaceStorageHome.fsPath, - environmentService.backupHome - ].map((path): undefined | Promise => path ? mkdirp(path) : undefined)); - - // Configuration service - const configurationServiceInitialization = configurationService.initialize(); - - // State service - const stateServiceInitialization = stateService.init(); - - return Promise.all([environmentServiceInitialization, configurationServiceInitialization, stateServiceInitialization]); - } - - private patchEnvironment(environmentService: IEnvironmentMainService): IProcessEnvironment { + private patchEnvironment(environmentMainService: IEnvironmentMainService): IProcessEnvironment { const instanceEnvironment: IProcessEnvironment = { - VSCODE_IPC_HOOK: environmentService.mainIPCHandle + VSCODE_IPC_HOOK: environmentMainService.mainIPCHandle }; ['VSCODE_NLS_CONFIG', 'VSCODE_PORTABLE'].forEach(key => { @@ -214,15 +198,36 @@ class CodeMain { return instanceEnvironment; } - private async doStartup(args: NativeParsedArgs, logService: ILogService, environmentService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, retry: boolean): Promise { + private initServices(environmentMainService: IEnvironmentMainService, configurationService: ConfigurationService, stateService: StateService): Promise { + + // Environment service (paths) + const environmentServiceInitialization = Promise.all([ + environmentMainService.extensionsPath, + environmentMainService.nodeCachedDataDir, + environmentMainService.logsPath, + environmentMainService.globalStorageHome.fsPath, + environmentMainService.workspaceStorageHome.fsPath, + environmentMainService.backupHome + ].map(path => path ? promises.mkdir(path, { recursive: true }) : undefined)); + + // Configuration service + const configurationServiceInitialization = configurationService.initialize(); + + // State service + const stateServiceInitialization = stateService.init(); + + return Promise.all([environmentServiceInitialization, configurationServiceInitialization, stateServiceInitialization]); + } + + private async doStartup(args: NativeParsedArgs, logService: ILogService, environmentMainService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, retry: boolean): Promise { // Try to setup a server for running. If that succeeds it means // we are the first instance to startup. Otherwise it is likely // that another instance is already running. - let server: NodeIPCServer; + let mainProcessNodeIpcServer: NodeIPCServer; try { - server = await nodeIPCServe(environmentService.mainIPCHandle); - once(lifecycleMainService.onWillShutdown)(() => server.dispose()); + mainProcessNodeIpcServer = await nodeIPCServe(environmentMainService.mainIPCHandle); + once(lifecycleMainService.onWillShutdown)(() => mainProcessNodeIpcServer.dispose()); } catch (error) { // Handle unexpected errors (the only expected error is EADDRINUSE that @@ -230,7 +235,7 @@ class CodeMain { if (error.code !== 'EADDRINUSE') { // Show a dialog for errors that can be resolved by the user - this.handleStartupDataDirError(environmentService, error); + this.handleStartupDataDirError(environmentMainService, error); // Any other runtime error is just printed to the console throw error; @@ -239,7 +244,7 @@ class CodeMain { // there's a running instance, let's connect to it let client: NodeIPCClient; try { - client = await nodeIPCConnect(environmentService.mainIPCHandle, 'main'); + client = await nodeIPCConnect(environmentMainService.mainIPCHandle, 'main'); } catch (error) { // Handle unexpected connection errors by showing a dialog to the user @@ -258,18 +263,18 @@ class CodeMain { // let's delete it, since we can't connect to it and then // retry the whole thing try { - unlinkSync(environmentService.mainIPCHandle); + unlinkSync(environmentMainService.mainIPCHandle); } catch (error) { logService.warn('Could not delete obsolete instance handle', error); throw error; } - return this.doStartup(args, logService, environmentService, lifecycleMainService, instantiationService, false); + return this.doStartup(args, logService, environmentMainService, lifecycleMainService, instantiationService, false); } // Tests from CLI require to be the only instance currently - if (environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.break) { + if (environmentMainService.extensionTestsLocationURI && !environmentMainService.debugExtensionHost.break) { const msg = 'Running extension tests from the command line is currently only supported if no other instance of Code is running.'; logService.error(msg); client.dispose(); @@ -290,7 +295,7 @@ class CodeMain { }, 10000); } - const launchService = createChannelSender(client.getChannel('launch'), { disableMarshalling: true }); + const launchService = ProxyChannel.toService(client.getChannel('launch'), { disableMarshalling: true }); // Process Info if (args.status) { @@ -336,12 +341,12 @@ class CodeMain { // instance to startup. Otherwise we would wrongly overwrite the PID process.env['VSCODE_PID'] = String(process.pid); - return server; + return mainProcessNodeIpcServer; } - private handleStartupDataDirError(environmentService: IEnvironmentMainService, error: NodeJS.ErrnoException): void { + private handleStartupDataDirError(environmentMainService: IEnvironmentMainService, error: NodeJS.ErrnoException): void { if (error.code === 'EACCES' || error.code === 'EPERM') { - const directories = coalesce([environmentService.userDataPath, environmentService.extensionsPath, XDG_RUNTIME_DIR]).map(folder => getPathLabel(folder, environmentService)); + const directories = coalesce([environmentMainService.userDataPath, environmentMainService.extensionsPath, XDG_RUNTIME_DIR]).map(folder => getPathLabel(folder, environmentMainService)); this.showStartupWarningDialog( localize('startupDataDirError', "Unable to write program user data."), @@ -364,9 +369,9 @@ class CodeMain { }); } - private async windowsAllowSetForegroundWindow(launchService: ILaunchMainService, logService: ILogService): Promise { + private async windowsAllowSetForegroundWindow(launchMainService: ILaunchMainService, logService: ILogService): Promise { if (isWindows) { - const processId = await launchService.getMainProcessId(); + const processId = await launchMainService.getMainProcessId(); logService.trace('Sending some foreground love to the running instance:', processId); @@ -403,7 +408,30 @@ class CodeMain { lifecycleMainService.kill(exitCode); } - //#region Helpers + //#region Command line arguments utilities + + private resolveArgs(): NativeParsedArgs { + + // Parse arguments + const args = this.validatePaths(parseMainProcessArgv(process.argv)); + + // If we are started with --wait create a random temporary file + // and pass it over to the starting instance. We can use this file + // to wait for it to be deleted to monitor that the edited file + // is closed and then exit the waiting process. + // + // Note: we are not doing this if the wait marker has been already + // added as argument. This can happen if Code was started from CLI. + if (args.wait && !args.waitMarkerFilePath) { + const waitMarkerFilePath = createWaitMarkerFile(args.verbose); + if (waitMarkerFilePath) { + addArg(process.argv, '--waitMarkerFilePath', waitMarkerFilePath); + args.waitMarkerFilePath = waitMarkerFilePath; + } + } + + return args; + } private validatePaths(args: NativeParsedArgs): NativeParsedArgs { @@ -484,11 +512,11 @@ class CodeMain { private toPath(pathWithLineAndCol: IPathWithLineAndColumn): string { const segments = [pathWithLineAndCol.path]; - if (isNumber(pathWithLineAndCol.line)) { + if (typeof pathWithLineAndCol.line === 'number') { segments.push(String(pathWithLineAndCol.line)); } - if (isNumber(pathWithLineAndCol.column)) { + if (typeof pathWithLineAndCol.column === 'number') { segments.push(String(pathWithLineAndCol.column)); } diff --git a/src/vs/code/electron-main/protocol.ts b/src/vs/code/electron-main/protocol.ts index 5bfbebb68..298bee5c0 100644 --- a/src/vs/code/electron-main/protocol.ts +++ b/src/vs/code/electron-main/protocol.ts @@ -31,33 +31,30 @@ export class FileProtocolHandler extends Disposable { // Define an initial set of roots we allow loading from // - appRoot : all files installed as part of the app // - extensions : all files shipped from extensions + // - storage : all files in global and workspace storage (https://github.com/microsoft/vscode/issues/116735) this.validRoots.set(URI.file(environmentService.appRoot), true); this.validRoots.set(URI.file(environmentService.extensionsPath), true); + this.validRoots.set(environmentService.globalStorageHome, true); + this.validRoots.set(environmentService.workspaceStorageHome, true); // Register vscode-file:// handler defaultSession.protocol.registerFileProtocol(Schemas.vscodeFileResource, (request, callback) => this.handleResourceRequest(request, callback as unknown as ProtocolCallback)); - // Block any file:// access (explicitly enabled only) - if (isPreferringBrowserCodeLoad) { - this.logService.info(`Intercepting ${Schemas.file}: protocol and blocking it`); - - defaultSession.protocol.interceptFileProtocol(Schemas.file, (request, callback) => this.handleFileRequest(request, callback as unknown as ProtocolCallback)); - } + // Intercept any file:// access + defaultSession.protocol.interceptFileProtocol(Schemas.file, (request, callback) => this.handleFileRequest(request, callback as unknown as ProtocolCallback)); // Cleanup this._register(toDisposable(() => { defaultSession.protocol.unregisterProtocol(Schemas.vscodeFileResource); - if (isPreferringBrowserCodeLoad) { - defaultSession.protocol.uninterceptProtocol(Schemas.file); - } + defaultSession.protocol.uninterceptProtocol(Schemas.file); })); } injectWindowsMainService(windowsMainService: IWindowsMainService): void { - this._register(windowsMainService.onWindowReady(window => { + this._register(windowsMainService.onDidSignalReadyWindow(window => { if (window.config?.extensionDevelopmentPath || window.config?.extensionTestsPath) { const disposables = new DisposableStore(); - disposables.add(Event.any(window.onClose, window.onDestroy)(() => disposables.dispose())); + disposables.add(Event.any(window.onDidClose, window.onDidDestroy)(() => disposables.dispose())); // Allow access to extension development path if (window.config.extensionDevelopmentPath) { @@ -85,10 +82,29 @@ export class FileProtocolHandler extends Disposable { } private async handleFileRequest(request: Electron.ProtocolRequest, callback: ProtocolCallback) { - const uri = URI.parse(request.url); + const fileUri = URI.parse(request.url); - this.logService.error(`Refused to load resource ${uri.fsPath} from ${Schemas.file}: protocol`); - callback({ error: -3 /* ABORTED */ }); + // isPreferringBrowserCodeLoad: false + // => ensure the file path is in our expected roots + if (!isPreferringBrowserCodeLoad) { + if (this.validRoots.findSubstr(fileUri)) { + return callback({ + path: fileUri.fsPath + }); + } + + this.logService.error(`${Schemas.file}: Refused to load resource ${fileUri.fsPath} from ${Schemas.file}: protocol (original URL: ${request.url})`); + + return callback({ error: -3 /* ABORTED */ }); + } + + // isPreferringBrowserCodeLoad: true + // => block any file request + else { + this.logService.error(`Refused to load resource ${fileUri.fsPath} from ${Schemas.file}: protocol (original URL: ${request.url})`); + + return callback({ error: -3 /* ABORTED */ }); + } } private async handleResourceRequest(request: Electron.ProtocolRequest, callback: ProtocolCallback) { @@ -102,9 +118,10 @@ export class FileProtocolHandler extends Disposable { return callback({ path: fileUri.fsPath }); - } + } else { + this.logService.error(`${Schemas.vscodeFileResource}: Refused to load resource ${fileUri.fsPath} from ${Schemas.vscodeFileResource}: protocol (original URL: ${request.url})`); - this.logService.error(`${Schemas.vscodeFileResource}: Refused to load resource ${fileUri.fsPath}}`); - callback({ error: -3 /* ABORTED */ }); + return callback({ error: -3 /* ABORTED */ }); + } } } diff --git a/src/vs/code/electron-sandbox/issue/issueReporter.js b/src/vs/code/electron-sandbox/issue/issueReporter.js index 2c3529754..a51159e58 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporter.js +++ b/src/vs/code/electron-sandbox/issue/issueReporter.js @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ //@ts-check -'use strict'; - (function () { + 'use strict'; + const bootstrapWindow = bootstrapWindowLib(); // Load issue reporter into window diff --git a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts index 6d339e858..75b1c60e4 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts @@ -22,11 +22,12 @@ import BaseHtml from 'vs/code/electron-sandbox/issue/issueReporterPage'; import { localize } from 'vs/nls'; import { isRemoteDiagnosticError, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IMainProcessService, ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { IssueReporterData, IssueReporterExtensionData, IssueReporterFeatures, IssueReporterStyles, IssueType } from 'vs/platform/issue/common/issue'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { Codicon } from 'vs/base/common/codicons'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; const MAX_URL_LENGTH = 2045; diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorer.js b/src/vs/code/electron-sandbox/processExplorer/processExplorer.js index 3b84f3acf..36fb6ee55 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorer.js +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorer.js @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ //@ts-check -'use strict'; - (function () { + 'use strict'; + const bootstrapWindow = bootstrapWindowLib(); // Load process explorer into window diff --git a/src/vs/code/electron-sandbox/workbench/workbench.js b/src/vs/code/electron-sandbox/workbench/workbench.js index c5cb1debb..1946d7f5e 100644 --- a/src/vs/code/electron-sandbox/workbench/workbench.js +++ b/src/vs/code/electron-sandbox/workbench/workbench.js @@ -6,9 +6,9 @@ /// //@ts-check -'use strict'; - (function () { + 'use strict'; + const bootstrapWindow = bootstrapWindowLib(); // Add a perf entry right from the top @@ -76,5 +76,4 @@ } //#endregion - }()); diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index ac5570189..43f8ea013 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -9,7 +9,7 @@ import { spawn, ChildProcess, SpawnOptions } from 'child_process'; import { buildHelpMessage, buildVersionMessage, OPTIONS } from 'vs/platform/environment/node/argv'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { parseCLIProcessArgv, addArg } from 'vs/platform/environment/node/argvHelper'; -import { createWaitMarkerFile } from 'vs/platform/environment/node/waitMarkerFile'; +import { createWaitMarkerFile } from 'vs/platform/environment/node/wait'; import product from 'vs/platform/product/common/product'; import { isAbsolute, join } from 'vs/base/common/path'; import { whenDeleted, writeFileSync } from 'vs/base/node/pfs'; diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index ee189f832..8c3d305ce 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -28,12 +28,11 @@ import { RequestService } from 'vs/platform/request/node/requestService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; -import { mkdirp, writeFile } from 'vs/base/node/pfs'; import { IStateService } from 'vs/platform/state/node/state'; import { StateService } from 'vs/platform/state/node/stateService'; -import { ILogService, getLogLevel, LogLevel, ConsoleLogService, MultiplexLogService } from 'vs/platform/log/common/log'; +import { ILogService, getLogLevel, LogLevel, ConsoleLogger, MultiplexLogService, ILogger } from 'vs/platform/log/common/log'; import { Schemas } from 'vs/base/common/network'; -import { SpdLogService } from 'vs/platform/log/node/spdlogService'; +import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; import { buildTelemetryMessage } from 'vs/platform/telemetry/node/telemetry'; import { FileService } from 'vs/platform/files/common/fileService'; import { IFileService } from 'vs/platform/files/common/files'; @@ -46,6 +45,7 @@ import { LocalizationsService } from 'vs/platform/localizations/node/localizatio import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; import { setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { VSBuffer } from 'vs/base/common/buffer'; class CliMain extends Disposable { @@ -73,6 +73,7 @@ class CliMain extends Disposable { return instantiationService.invokeFunction(async accessor => { const logService = accessor.get(ILogService); + const fileService = accessor.get(IFileService); const environmentService = accessor.get(INativeEnvironmentService); const extensionManagementCLIService = accessor.get(IExtensionManagementCLIService); @@ -83,7 +84,7 @@ class CliMain extends Disposable { this.registerErrorHandler(logService); // Run based on argv - await this.doRun(environmentService, extensionManagementCLIService); + await this.doRun(environmentService, extensionManagementCLIService, fileService); // Flush the remaining data in AI adapter (with 1s timeout) return raceTimeout(combinedAppender(...appenders).flush(), 1000); @@ -99,14 +100,14 @@ class CliMain extends Disposable { services.set(INativeEnvironmentService, environmentService); // Init folders - await Promise.all([environmentService.appSettingsHome.fsPath, environmentService.extensionsPath].map(path => path ? mkdirp(path) : undefined)); + await Promise.all([environmentService.appSettingsHome.fsPath, environmentService.extensionsPath].map(path => path ? fs.promises.mkdir(path, { recursive: true }) : undefined)); // Log const logLevel = getLogLevel(environmentService); - const loggers: ILogService[] = []; - loggers.push(new SpdLogService('cli', environmentService.logsPath, logLevel)); + const loggers: ILogger[] = []; + loggers.push(new SpdLogLogger('cli', join(environmentService.logsPath, 'cli.log'), true, logLevel)); if (logLevel === LogLevel.Trace) { - loggers.push(new ConsoleLogService(logLevel)); + loggers.push(new ConsoleLogger(logLevel)); } const logService = this._register(new MultiplexLogService(loggers)); @@ -182,11 +183,11 @@ class CliMain extends Disposable { }); } - private async doRun(environmentService: INativeEnvironmentService, extensionManagementCLIService: IExtensionManagementCLIService): Promise { + private async doRun(environmentService: INativeEnvironmentService, extensionManagementCLIService: IExtensionManagementCLIService, fileService: IFileService): Promise { // Install Source if (this.argv['install-source']) { - return this.setInstallSource(environmentService, this.argv['install-source']); + return this.setInstallSource(environmentService, fileService, this.argv['install-source']); } // List Extensions @@ -219,8 +220,8 @@ class CliMain extends Disposable { return inputs.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(process.cwd(), input)) : input); } - private setInstallSource(environmentService: INativeEnvironmentService, installSource: string): Promise { - return writeFile(environmentService.installSourcePath, installSource.slice(0, 30)); + private async setInstallSource(environmentService: INativeEnvironmentService, fileService: IFileService, installSource: string): Promise { + await fileService.writeFile(URI.file(environmentService.installSourcePath), VSBuffer.fromString(installSource.slice(0, 30))); } } diff --git a/src/vs/editor/browser/controller/coreCommands.ts b/src/vs/editor/browser/controller/coreCommands.ts index fd78371ff..4ed8d0490 100644 --- a/src/vs/editor/browser/controller/coreCommands.ts +++ b/src/vs/editor/browser/controller/coreCommands.ts @@ -235,7 +235,7 @@ export namespace RevealLine_ { name: 'Reveal line argument object', description: `Property-value pairs that can be passed through this argument: * 'lineNumber': A mandatory line number value. - * 'at': Logical position at which line has to be revealed . + * 'at': Logical position at which line has to be revealed. \`\`\` 'top', 'center', 'bottom' \`\`\` @@ -554,6 +554,8 @@ export namespace CoreNavigationCommands { case CursorMove_.Direction.Right: case CursorMove_.Direction.Up: case CursorMove_.Direction.Down: + case CursorMove_.Direction.PrevBlankLine: + case CursorMove_.Direction.NextBlankLine: case CursorMove_.Direction.WrappedLineStart: case CursorMove_.Direction.WrappedLineFirstNonWhitespaceCharacter: case CursorMove_.Direction.WrappedLineColumnCenter: @@ -1929,6 +1931,7 @@ registerOverwritableCommand(Handler.Type, { }] }); registerOverwritableCommand(Handler.ReplacePreviousChar); +registerOverwritableCommand(Handler.CompositionType); registerOverwritableCommand(Handler.CompositionStart); registerOverwritableCommand(Handler.CompositionEnd); registerOverwritableCommand(Handler.Paste); diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index 31f3a0b68..005fe7ad0 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -42,6 +42,7 @@ export interface IPointerHandlerHelper { linesContentDomNode: HTMLElement; focusTextArea(): void; + dispatchTextAreaEvent(event: CustomEvent): void; /** * Get the last rendered information for cursors & textarea. @@ -70,6 +71,7 @@ export class MouseHandler extends ViewEventHandler { protected mouseTargetFactory: MouseTargetFactory; protected readonly _mouseDownOperation: MouseDownOperation; private lastMouseLeaveTime: number; + private _height: number; constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) { super(); @@ -88,6 +90,7 @@ export class MouseHandler extends ViewEventHandler { )); this.lastMouseLeaveTime = -1; + this._height = this._context.configuration.options.get(EditorOption.layoutInfo).height; const mouseEvents = new EditorMouseEventFactory(this.viewHelper.viewDomNode); @@ -112,7 +115,9 @@ export class MouseHandler extends ViewEventHandler { const e = new StandardWheelEvent(browserEvent); const doMouseWheelZoom = ( platform.isMacintosh - ? (browserEvent.metaKey && !browserEvent.ctrlKey && !browserEvent.shiftKey && !browserEvent.altKey) + // on macOS we support cmd + two fingers scroll (`metaKey` set) + // and also the two fingers pinch gesture (`ctrKey` set) + ? ((browserEvent.metaKey || browserEvent.ctrlKey) && !browserEvent.shiftKey && !browserEvent.altKey) : (browserEvent.ctrlKey && !browserEvent.metaKey && !browserEvent.shiftKey && !browserEvent.altKey) ); if (doMouseWheelZoom) { @@ -134,6 +139,17 @@ export class MouseHandler extends ViewEventHandler { } // --- begin event handlers + public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + if (e.hasChanged(EditorOption.layoutInfo)) { + // layout change + const height = this._context.configuration.options.get(EditorOption.layoutInfo).height; + if (this._height !== height) { + this._height = height; + this._mouseDownOperation.onHeightChanged(); + } + } + return false; + } public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { this._mouseDownOperation.onCursorStateChanged(e); return false; @@ -400,6 +416,10 @@ class MouseDownOperation extends Disposable { this._onScrollTimeout.cancel(); } + public onHeightChanged(): void { + this._mouseMoveMonitor.stopMonitoring(); + } + public onScrollChanged(): void { if (!this._isActive) { return; diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index ada5f46af..bc0478ca4 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as browser from 'vs/base/browser/browser'; import { IPointerHandlerHelper } from 'vs/editor/browser/controller/mouseHandler'; import { IMouseTarget, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { ClientCoordinates, EditorMouseEvent, EditorPagePosition, PageCoordinates } from 'vs/editor/browser/editorDom'; @@ -767,11 +766,6 @@ export class MouseTargetFactory { const lineWidth = ctx.getLineWidth(lineNumber); if (request.mouseContentHorizontalOffset > lineWidth) { - if (browser.isEdgeLegacy && pos.column === 1) { - // See https://github.com/microsoft/vscode/issues/10875 - const detail = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth); - return request.fulfill(MouseTargetType.CONTENT_EMPTY, new Position(lineNumber, ctx.model.getLineMaxColumn(lineNumber)), undefined, detail); - } const detail = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth); return request.fulfill(MouseTargetType.CONTENT_EMPTY, pos, undefined, detail); } @@ -974,56 +968,6 @@ export class MouseTargetFactory { }; } - /** - * Most probably IE - */ - private static _doHitTestWithMoveToPoint(ctx: HitTestContext, coords: ClientCoordinates): IHitTestResult { - let resultPosition: Position | null = null; - let resultHitTarget: Element | null = null; - - const textRange: IETextRange = (document.body).createTextRange(); - try { - textRange.moveToPoint(coords.clientX, coords.clientY); - } catch (err) { - return { - position: null, - hitTarget: null - }; - } - - textRange.collapse(true); - - // Now, let's do our best to figure out what we hit :) - const parentElement = textRange ? textRange.parentElement() : null; - const parent1 = parentElement ? parentElement.parentNode : null; - const parent2 = parent1 ? parent1.parentNode : null; - - const parent2ClassName = parent2 && parent2.nodeType === parent2.ELEMENT_NODE ? (parent2).className : ''; - - if (parent2ClassName === ViewLine.CLASS_NAME) { - const rangeToContainEntireSpan = textRange.duplicate(); - rangeToContainEntireSpan.moveToElementText(parentElement!); - rangeToContainEntireSpan.setEndPoint('EndToStart', textRange); - - resultPosition = ctx.getPositionFromDOMInfo(parentElement, rangeToContainEntireSpan.text.length); - // Move range out of the span node, IE doesn't like having many ranges in - // the same spot and will act badly for lines containing dashes ('-') - rangeToContainEntireSpan.moveToElementText(ctx.viewDomNode); - } else { - // Looks like we've hit the hover or something foreign - resultHitTarget = parentElement; - } - - // Move range out of the span node, IE doesn't like having many ranges in - // the same spot and will act badly for lines containing dashes ('-') - textRange.moveToElementText(ctx.viewDomNode); - - return { - position: resultPosition, - hitTarget: resultHitTarget - }; - } - private static _snapToSoftTabBoundary(position: Position, viewModel: IViewModel): Position { const lineContent = viewModel.getLineContent(position.lineNumber); const { tabSize } = viewModel.getTextModelOptions(); @@ -1035,30 +979,12 @@ export class MouseTargetFactory { } private static _doHitTest(ctx: HitTestContext, request: BareHitTestRequest): IHitTestResult { - // State of the art (18.10.2012): - // The spec says browsers should support document.caretPositionFromPoint, but nobody implemented it (http://dev.w3.org/csswg/cssom-view/) - // Gecko: - // - they tried to implement it once, but failed: https://bugzilla.mozilla.org/show_bug.cgi?id=654352 - // - however, they do give out rangeParent/rangeOffset properties on mouse events - // Webkit: - // - they have implemented a previous version of the spec which was using document.caretRangeFromPoint - // IE: - // - they have a proprietary method on ranges, moveToPoint: https://msdn.microsoft.com/en-us/library/ie/ms536632(v=vs.85).aspx - - // 24.08.2016: Edge has added WebKit's document.caretRangeFromPoint, but it is quite buggy - // - when hit testing the cursor it returns the first or the last line in the viewport - // - it inconsistently hits text nodes or span nodes, while WebKit only hits text nodes - // - when toggling render whitespace on, and hit testing in the empty content after a line, it always hits offset 0 of the first span of the line - - // Thank you browsers for making this so 'easy' :) let result: IHitTestResult; if (typeof document.caretRangeFromPoint === 'function') { result = this._doHitTestWithCaretRangeFromPoint(ctx, request); } else if ((document).caretPositionFromPoint) { result = this._doHitTestWithCaretPositionFromPoint(ctx, request.pos.toClientCoordinates()); - } else if ((document.body).createTextRange) { - result = this._doHitTestWithMoveToPoint(ctx, request.pos.toClientCoordinates()); } else { result = { position: null, diff --git a/src/vs/editor/browser/controller/pointerHandler.ts b/src/vs/editor/browser/controller/pointerHandler.ts index b86fd84fd..ee291a86f 100644 --- a/src/vs/editor/browser/controller/pointerHandler.ts +++ b/src/vs/editor/browser/controller/pointerHandler.ts @@ -6,108 +6,14 @@ import * as dom from 'vs/base/browser/dom'; import * as platform from 'vs/base/common/platform'; import { EventType, Gesture, GestureEvent } from 'vs/base/browser/touch'; -import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { IPointerHandlerHelper, MouseHandler, createMouseMoveEventMerger } from 'vs/editor/browser/controller/mouseHandler'; import { IMouseTarget } from 'vs/editor/browser/editorBrowser'; import { EditorMouseEvent, EditorPointerEventFactory } from 'vs/editor/browser/editorDom'; import { ViewController } from 'vs/editor/browser/view/viewController'; import { ViewContext } from 'vs/editor/common/view/viewContext'; import { BrowserFeatures } from 'vs/base/browser/canIUse'; - -interface IThrottledGestureEvent { - translationX: number; - translationY: number; -} - -function gestureChangeEventMerger(lastEvent: IThrottledGestureEvent | null, currentEvent: MSGestureEvent): IThrottledGestureEvent { - const r = { - translationY: currentEvent.translationY, - translationX: currentEvent.translationX - }; - if (lastEvent) { - r.translationY += lastEvent.translationY; - r.translationX += lastEvent.translationX; - } - return r; -} - -/** - * Basically Edge but should be modified to handle any pointerEnabled, even without support of MSGesture - */ -class StandardPointerHandler extends MouseHandler implements IDisposable { - - private _lastPointerType: string; - private _installGestureHandlerTimeout: number; - - constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) { - super(context, viewController, viewHelper); - - this.viewHelper.linesContentDomNode.style.touchAction = 'none'; - - // TODO@Alex -> this expects that the view is added in 100 ms, might not be the case - // This handler should be added when the dom node is in the dom tree - this._installGestureHandlerTimeout = window.setTimeout(() => { - this._installGestureHandlerTimeout = -1; - - // TODO@Alex: replace the usage of MSGesture here with something that works across all browsers - if (window.MSGesture) { - const touchGesture = new MSGesture(); - const penGesture = new MSGesture(); - touchGesture.target = this.viewHelper.linesContentDomNode; - penGesture.target = this.viewHelper.linesContentDomNode; - this.viewHelper.linesContentDomNode.addEventListener('pointerdown', (e: PointerEvent) => { - const pointerType = e.pointerType; - if (pointerType === 'mouse') { - this._lastPointerType = 'mouse'; - return; - } else if (pointerType === 'touch') { - this._lastPointerType = 'touch'; - touchGesture.addPointer(e.pointerId); - } else { - this._lastPointerType = 'pen'; - penGesture.addPointer(e.pointerId); - } - }); - this._register(dom.addDisposableThrottledListener(this.viewHelper.linesContentDomNode, 'MSGestureChange', (e) => this._onGestureChange(e), gestureChangeEventMerger)); - this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, 'MSGestureTap', (e) => this._onCaptureGestureTap(e), true)); - } - }, 100); - this._lastPointerType = 'mouse'; - } - - public _onMouseDown(e: EditorMouseEvent): void { - if (this._lastPointerType === 'mouse') { - super._onMouseDown(e); - } - } - - private _onCaptureGestureTap(rawEvent: MSGestureEvent): void { - const e = new EditorMouseEvent(rawEvent, this.viewHelper.viewDomNode); - const t = this._createMouseTarget(e, false); - if (t.position) { - this.viewController.moveTo(t.position); - } - // IE does not want to focus when coming in from the browser's address bar - if ((e.browserEvent).fromElement) { - e.preventDefault(); - this.viewHelper.focusTextArea(); - } else { - // TODO@Alex -> cancel this is focus is lost - setTimeout(() => { - this.viewHelper.focusTextArea(); - }); - } - } - - private _onGestureChange(e: IThrottledGestureEvent): void { - this._context.model.deltaScrollNow(-e.translationX, -e.translationY); - } - - public dispose(): void { - window.clearTimeout(this._installGestureHandlerTimeout); - super.dispose(); - } -} +import { TextAreaSyntethicEvents } from 'vs/editor/browser/controller/textAreaInput'; /** * Currently only tested on iOS 13/ iPadOS. @@ -210,6 +116,11 @@ class TouchHandler extends MouseHandler { const target = this._createMouseTarget(new EditorMouseEvent(event, this.viewHelper.viewDomNode), false); if (target.position) { + // Send the tap event also to the